Add BugSweeper sample
This commit is contained in:
parent
0ecc2d19b5
commit
d0734ae795
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
var debug = true;
|
var debug = false;
|
||||||
|
|
||||||
const nodes = {};
|
const nodes = {};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace BugSweeper
|
||||||
|
{
|
||||||
|
public class App : Application
|
||||||
|
{
|
||||||
|
public App ()
|
||||||
|
{
|
||||||
|
MainPage = new BugSweeperPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,235 @@
|
||||||
|
using System;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace BugSweeper
|
||||||
|
{
|
||||||
|
class Board : AbsoluteLayout
|
||||||
|
{
|
||||||
|
// Alternative sizes make the tiles a tad small.
|
||||||
|
const int COLS = 9; // 16
|
||||||
|
const int ROWS = 9; // 16
|
||||||
|
const int BUGS = 10; // 40
|
||||||
|
|
||||||
|
Tile[,] tiles = new Tile[ROWS, COLS];
|
||||||
|
int flaggedTileCount;
|
||||||
|
bool isGameInProgress; // on first tap
|
||||||
|
bool isGameInitialized; // on first double-tap
|
||||||
|
bool isGameEnded;
|
||||||
|
|
||||||
|
// Events to notify page.
|
||||||
|
public event EventHandler GameStarted;
|
||||||
|
public event EventHandler<bool> GameEnded;
|
||||||
|
|
||||||
|
public Board()
|
||||||
|
{
|
||||||
|
for (int row = 0; row < ROWS; row++)
|
||||||
|
for (int col = 0; col < COLS; col++)
|
||||||
|
{
|
||||||
|
Tile tile = new Tile(row, col);
|
||||||
|
tile.TileStatusChanged += OnTileStatusChanged;
|
||||||
|
this.Children.Add(tile);
|
||||||
|
tiles[row, col] = tile;
|
||||||
|
}
|
||||||
|
|
||||||
|
SizeChanged += (sender, args) =>
|
||||||
|
{
|
||||||
|
double tileWidth = this.Width / COLS;
|
||||||
|
double tileHeight = this.Height / ROWS;
|
||||||
|
|
||||||
|
foreach (Tile tile in tiles)
|
||||||
|
{
|
||||||
|
Rectangle bounds = new Rectangle(tile.Col * tileWidth,
|
||||||
|
tile.Row * tileHeight,
|
||||||
|
tileWidth, tileHeight);
|
||||||
|
AbsoluteLayout.SetLayoutBounds(tile, bounds);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NewGameInitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NewGameInitialize()
|
||||||
|
{
|
||||||
|
// Clear all the tiles.
|
||||||
|
foreach (Tile tile in tiles)
|
||||||
|
tile.Initialize();
|
||||||
|
|
||||||
|
isGameInProgress = false;
|
||||||
|
isGameInitialized = false;
|
||||||
|
isGameEnded = false;
|
||||||
|
this.FlaggedTileCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int FlaggedTileCount
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (flaggedTileCount != value)
|
||||||
|
{
|
||||||
|
flaggedTileCount = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return flaggedTileCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int BugCount
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return BUGS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Not called until the first tile is double-tapped.
|
||||||
|
void DefineNewBoard(int tappedRow, int tappedCol)
|
||||||
|
{
|
||||||
|
// Begin the assignment of bugs.
|
||||||
|
Random random = new Random();
|
||||||
|
int bugCount = 0;
|
||||||
|
|
||||||
|
while (bugCount < BUGS)
|
||||||
|
{
|
||||||
|
// Get random row and column.
|
||||||
|
int row = random.Next(ROWS);
|
||||||
|
int col = random.Next(COLS);
|
||||||
|
|
||||||
|
// Skip it if it's already a bug.
|
||||||
|
if (tiles[row, col].IsBug)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid the tappedRow & Col & surrounding ones.
|
||||||
|
if (row >= tappedRow - 1 &&
|
||||||
|
row <= tappedRow + 1 &&
|
||||||
|
col >= tappedCol - 1 &&
|
||||||
|
col <= tappedCol + 1)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's a bug!
|
||||||
|
tiles[row, col].IsBug = true;
|
||||||
|
|
||||||
|
// Calculate the surrounding bug count.
|
||||||
|
CycleThroughNeighbors(row, col,
|
||||||
|
(neighborRow, neighborCol) =>
|
||||||
|
{
|
||||||
|
++tiles[neighborRow, neighborCol].SurroundingBugCount;
|
||||||
|
});
|
||||||
|
|
||||||
|
bugCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CycleThroughNeighbors(int row, int col, Action<int, int> callback)
|
||||||
|
{
|
||||||
|
int minRow = Math.Max(0, row - 1);
|
||||||
|
int maxRow = Math.Min(ROWS - 1, row + 1);
|
||||||
|
int minCol = Math.Max(0, col - 1);
|
||||||
|
int maxCol = Math.Min(COLS - 1, col + 1);
|
||||||
|
|
||||||
|
for (int neighborRow = minRow; neighborRow <= maxRow; neighborRow++)
|
||||||
|
for (int neighborCol = minCol; neighborCol <= maxCol; neighborCol++)
|
||||||
|
{
|
||||||
|
if (neighborRow != row || neighborCol != col)
|
||||||
|
callback(neighborRow, neighborCol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnTileStatusChanged(object sender, TileStatus tileStatus)
|
||||||
|
{
|
||||||
|
if (isGameEnded)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// With a first tile tapped, the game is now in progress.
|
||||||
|
if (!isGameInProgress)
|
||||||
|
{
|
||||||
|
isGameInProgress = true;
|
||||||
|
|
||||||
|
// Fire the GameStarted event.
|
||||||
|
if (GameStarted != null)
|
||||||
|
{
|
||||||
|
GameStarted(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the "flagged" bug count before checking for a loss.
|
||||||
|
int flaggedCount = 0;
|
||||||
|
|
||||||
|
foreach (Tile tile in tiles)
|
||||||
|
if (tile.Status == TileStatus.Flagged)
|
||||||
|
flaggedCount++;
|
||||||
|
|
||||||
|
this.FlaggedTileCount = flaggedCount;
|
||||||
|
|
||||||
|
// Get the tile whose status has changed.
|
||||||
|
Tile changedTile = (Tile)sender;
|
||||||
|
|
||||||
|
// If it's exposed, some actions are required.
|
||||||
|
if (tileStatus == TileStatus.Exposed)
|
||||||
|
{
|
||||||
|
if (!isGameInitialized)
|
||||||
|
{
|
||||||
|
DefineNewBoard(changedTile.Row, changedTile.Col);
|
||||||
|
isGameInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changedTile.IsBug)
|
||||||
|
{
|
||||||
|
isGameInProgress = false;
|
||||||
|
isGameEnded = true;
|
||||||
|
|
||||||
|
// Fire the GameEnded event!
|
||||||
|
if (GameEnded != null)
|
||||||
|
{
|
||||||
|
GameEnded(this, false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto expose for zero surrounding bugs.
|
||||||
|
if (changedTile.SurroundingBugCount == 0)
|
||||||
|
{
|
||||||
|
CycleThroughNeighbors(changedTile.Row, changedTile.Col,
|
||||||
|
(neighborRow, neighborCol) =>
|
||||||
|
{
|
||||||
|
// Expose all the neighbors.
|
||||||
|
tiles[neighborRow, neighborCol].Status = TileStatus.Exposed;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a win.
|
||||||
|
bool hasWon = true;
|
||||||
|
|
||||||
|
foreach (Tile til in tiles)
|
||||||
|
{
|
||||||
|
if (til.IsBug && til.Status != TileStatus.Flagged)
|
||||||
|
hasWon = false;
|
||||||
|
|
||||||
|
if (!til.IsBug && til.Status != TileStatus.Exposed)
|
||||||
|
hasWon = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's a win, celebrate!
|
||||||
|
if (hasWon)
|
||||||
|
{
|
||||||
|
isGameInProgress = false;
|
||||||
|
isGameEnded = true;
|
||||||
|
|
||||||
|
// Fire the GameEnded event!
|
||||||
|
if (GameEnded != null)
|
||||||
|
{
|
||||||
|
GameEnded(this, true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
xmlns:local="clr-namespace:BugSweeper"
|
||||||
|
x:Class="BugSweeper.BugSweeperPage"
|
||||||
|
Title="BugSweeper">
|
||||||
|
<ContentPage.Padding>
|
||||||
|
<OnPlatform x:TypeArguments="Thickness" iOS="0, 20, 0, 0" Android="0, 0, 0, 0" WinPhone="0, 0, 0, 0" />
|
||||||
|
</ContentPage.Padding>
|
||||||
|
<ContentView SizeChanged="OnMainContentViewSizeChanged">
|
||||||
|
<Grid x:Name="mainGrid" ColumnSpacing="0" RowSpacing="0">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="7*" />
|
||||||
|
<RowDefinition Height="4*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="0" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<StackLayout x:Name="textStack" Grid.Row="0" Grid.Column="1" Spacing="0">
|
||||||
|
<StackLayout HorizontalOptions="Center" Spacing="0">
|
||||||
|
<Label Text="BugSweeper" Font="Bold, Large" TextColor="Accent" />
|
||||||
|
<BoxView Color="Accent" HeightRequest="3" />
|
||||||
|
</StackLayout>
|
||||||
|
<Label Text="Tap to flag/unflag a potential bug." VerticalOptions="CenterAndExpand" HorizontalTextAlignment="Center" />
|
||||||
|
<Label Text="Double-tap if you're sure it's not a bug.
The first double-tap is always safe!" VerticalOptions="CenterAndExpand" HorizontalTextAlignment="Center" />
|
||||||
|
<StackLayout Orientation="Horizontal" Spacing="0" VerticalOptions="CenterAndExpand" HorizontalOptions="Center">
|
||||||
|
<Label BindingContext="{x:Reference board}" Text="{Binding FlaggedTileCount, StringFormat='Flagged {0} '}" />
|
||||||
|
<Label BindingContext="{x:Reference board}" Text="{Binding BugCount, StringFormat=' out of {0} bugs.'}" />
|
||||||
|
</StackLayout>
|
||||||
|
<!-- Make this a binding??? -->
|
||||||
|
<Label x:Name="timeLabel" Text="0:00" VerticalOptions="CenterAndExpand" HorizontalTextAlignment="Center" />
|
||||||
|
</StackLayout>
|
||||||
|
<ContentView Grid.Row="1" Grid.Column="1" SizeChanged="OnBoardContentViewSizeChanged">
|
||||||
|
<!-- Single-cell Grid for Board and overlays. -->
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<local:Board x:Name="board" />
|
||||||
|
<StackLayout x:Name="congratulationsText" Orientation="Horizontal" HorizontalOptions="Center" VerticalOptions="Center" Spacing="0">
|
||||||
|
<Label Text="C" TextColor="Red" />
|
||||||
|
<Label Text="O" TextColor="Red" />
|
||||||
|
<Label Text="N" TextColor="Red" />
|
||||||
|
<Label Text="G" TextColor="Red" />
|
||||||
|
<Label Text="R" TextColor="Red" />
|
||||||
|
<Label Text="A" TextColor="Red" />
|
||||||
|
<Label Text="T" TextColor="Red" />
|
||||||
|
<Label Text="U" TextColor="Red" />
|
||||||
|
<Label Text="L" TextColor="Red" />
|
||||||
|
<Label Text="A" TextColor="Red" />
|
||||||
|
<Label Text="T" TextColor="Red" />
|
||||||
|
<Label Text="I" TextColor="Red" />
|
||||||
|
<Label Text="O" TextColor="Red" />
|
||||||
|
<Label Text="N" TextColor="Red" />
|
||||||
|
<Label Text="S" TextColor="Red" />
|
||||||
|
<Label Text="!" TextColor="Red" />
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout x:Name="consolationText" Orientation="Horizontal" Spacing="0" HorizontalOptions="Center" VerticalOptions="Center">
|
||||||
|
<Label Text="T" TextColor="Red" />
|
||||||
|
<Label Text="O" TextColor="Red" />
|
||||||
|
<Label Text="O" TextColor="Red" />
|
||||||
|
<Label Text=" " TextColor="Red" />
|
||||||
|
<Label Text="B" TextColor="Red" />
|
||||||
|
<Label Text="A" TextColor="Red" />
|
||||||
|
<Label Text="D" TextColor="Red" />
|
||||||
|
<Label Text="!" TextColor="Red" />
|
||||||
|
</StackLayout>
|
||||||
|
<Button x:Name="playAgainButton" Text=" Play Another Game? " HorizontalOptions="Center" VerticalOptions="Center" Clicked="OnplayAgainButtonClicked"
|
||||||
|
BorderColor="Black" BorderWidth="2" BackgroundColor="White" TextColor="Black" />
|
||||||
|
</Grid>
|
||||||
|
</ContentView>
|
||||||
|
</Grid>
|
||||||
|
</ContentView>
|
||||||
|
</ContentPage>
|
|
@ -0,0 +1,174 @@
|
||||||
|
#define FIX_WINPHONE_BUTTON // IsEnabled = false doesn't disable button
|
||||||
|
|
||||||
|
#pragma warning disable 4014 // for non-await'ed async call
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace BugSweeper
|
||||||
|
{
|
||||||
|
public partial class BugSweeperPage : ContentPage
|
||||||
|
{
|
||||||
|
const string timeFormat = @"%m\:ss";
|
||||||
|
|
||||||
|
bool isGameInProgress;
|
||||||
|
DateTime gameStartTime;
|
||||||
|
|
||||||
|
public BugSweeperPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
board.GameStarted += (sender, args) =>
|
||||||
|
{
|
||||||
|
isGameInProgress = true;
|
||||||
|
gameStartTime = DateTime.Now;
|
||||||
|
|
||||||
|
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
|
||||||
|
{
|
||||||
|
timeLabel.Text = (DateTime.Now - gameStartTime).ToString(timeFormat);
|
||||||
|
return isGameInProgress;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
board.GameEnded += (sender, hasWon) =>
|
||||||
|
{
|
||||||
|
isGameInProgress = false;
|
||||||
|
|
||||||
|
if (hasWon)
|
||||||
|
{
|
||||||
|
DisplayWonAnimation();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DisplayLostAnimation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
PrepareForNewGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrepareForNewGame()
|
||||||
|
{
|
||||||
|
board.NewGameInitialize();
|
||||||
|
|
||||||
|
congratulationsText.IsVisible = false;
|
||||||
|
consolationText.IsVisible = false;
|
||||||
|
playAgainButton.IsVisible = false;
|
||||||
|
playAgainButton.IsEnabled = false;
|
||||||
|
|
||||||
|
timeLabel.Text = new TimeSpan().ToString(timeFormat);
|
||||||
|
isGameInProgress = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnMainContentViewSizeChanged(object sender, EventArgs args)
|
||||||
|
{
|
||||||
|
ContentView contentView = (ContentView)sender;
|
||||||
|
double width = contentView.Width;
|
||||||
|
double height = contentView.Height;
|
||||||
|
|
||||||
|
bool isLandscape = width > height;
|
||||||
|
|
||||||
|
if (isLandscape)
|
||||||
|
{
|
||||||
|
mainGrid.RowDefinitions[0].Height = 0;
|
||||||
|
mainGrid.RowDefinitions[1].Height = new GridLength(1, GridUnitType.Star);
|
||||||
|
|
||||||
|
mainGrid.ColumnDefinitions[0].Width = new GridLength(1, GridUnitType.Star);
|
||||||
|
mainGrid.ColumnDefinitions[1].Width = new GridLength(1, GridUnitType.Star);
|
||||||
|
|
||||||
|
Grid.SetRow(textStack, 1);
|
||||||
|
Grid.SetColumn(textStack, 0);
|
||||||
|
}
|
||||||
|
else // portrait
|
||||||
|
{
|
||||||
|
mainGrid.RowDefinitions[0].Height = new GridLength(3, GridUnitType.Star);
|
||||||
|
mainGrid.RowDefinitions[1].Height = new GridLength(5, GridUnitType.Star);
|
||||||
|
|
||||||
|
mainGrid.ColumnDefinitions[0].Width = 0;
|
||||||
|
mainGrid.ColumnDefinitions[1].Width = new GridLength(1, GridUnitType.Star);
|
||||||
|
|
||||||
|
Grid.SetRow(textStack, 0);
|
||||||
|
Grid.SetColumn(textStack, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maintains a square aspect ratio for the board.
|
||||||
|
void OnBoardContentViewSizeChanged(object sender, EventArgs args)
|
||||||
|
{
|
||||||
|
ContentView contentView = (ContentView)sender;
|
||||||
|
double width = contentView.Width;
|
||||||
|
double height = contentView.Height;
|
||||||
|
double dimension = Math.Min(width, height);
|
||||||
|
double horzPadding = (width - dimension) / 2;
|
||||||
|
double vertPadding = (height - dimension) / 2;
|
||||||
|
contentView.Padding = new Thickness(horzPadding, vertPadding);
|
||||||
|
}
|
||||||
|
|
||||||
|
async void DisplayWonAnimation()
|
||||||
|
{
|
||||||
|
congratulationsText.Scale = 0;
|
||||||
|
congratulationsText.IsVisible = true;
|
||||||
|
|
||||||
|
// Because IsVisible has been false, the text might not have a size yet,
|
||||||
|
// in which case Measure will return a size.
|
||||||
|
double congratulationsTextWidth = congratulationsText.Measure(Double.PositiveInfinity, Double.PositiveInfinity).Request.Width;
|
||||||
|
|
||||||
|
congratulationsText.Rotation = 0;
|
||||||
|
congratulationsText.RotateTo(3 * 360, 1000, Easing.CubicOut);
|
||||||
|
|
||||||
|
double maxScale = 0.9 * board.Width / congratulationsTextWidth;
|
||||||
|
await congratulationsText.ScaleTo(maxScale, 1000);
|
||||||
|
|
||||||
|
foreach (View view in congratulationsText.Children)
|
||||||
|
{
|
||||||
|
view.Rotation = 0;
|
||||||
|
view.RotateTo(180);
|
||||||
|
await view.ScaleTo(3, 100);
|
||||||
|
view.RotateTo(360);
|
||||||
|
await view.ScaleTo(1, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
await DisplayPlayAgainButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
async void DisplayLostAnimation()
|
||||||
|
{
|
||||||
|
consolationText.Scale = 0;
|
||||||
|
consolationText.IsVisible = true;
|
||||||
|
|
||||||
|
// (See above for rationale)
|
||||||
|
double consolationTextWidth = consolationText.Measure(Double.PositiveInfinity, Double.PositiveInfinity).Request.Width;
|
||||||
|
|
||||||
|
double maxScale = 0.9 * board.Width / consolationTextWidth;
|
||||||
|
await consolationText.ScaleTo(maxScale, 1000);
|
||||||
|
await Task.Delay(1000);
|
||||||
|
await DisplayPlayAgainButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task DisplayPlayAgainButton()
|
||||||
|
{
|
||||||
|
playAgainButton.Scale = 0;
|
||||||
|
playAgainButton.IsVisible = true;
|
||||||
|
playAgainButton.IsEnabled = true;
|
||||||
|
|
||||||
|
// (See above for rationale)
|
||||||
|
double playAgainButtonWidth = playAgainButton.Measure(Double.PositiveInfinity, Double.PositiveInfinity).Request.Width;
|
||||||
|
|
||||||
|
double maxScale = board.Width / playAgainButtonWidth;
|
||||||
|
await playAgainButton.ScaleTo(maxScale, 1000, Easing.SpringOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnplayAgainButtonClicked(object sender, object EventArgs)
|
||||||
|
{
|
||||||
|
#if FIX_WINPHONE_BUTTON
|
||||||
|
|
||||||
|
if (Device.OS == TargetPlatform.WinPhone && !((Button)sender).IsEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
PrepareForNewGame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.0 KiB |
|
@ -0,0 +1,191 @@
|
||||||
|
#define FIX_WINDOWS_DOUBLE_TAPS // Double-taps don't work well on Windows Runtime as of 2.3.0
|
||||||
|
#define FIX_WINDOWS_PHONE_NULL_CONTENT // Set Content of Frame to null doesn't work in Windows as of 2.3.0
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace BugSweeper
|
||||||
|
{
|
||||||
|
enum TileStatus
|
||||||
|
{
|
||||||
|
Hidden,
|
||||||
|
Flagged,
|
||||||
|
Exposed
|
||||||
|
}
|
||||||
|
|
||||||
|
class Tile : Frame
|
||||||
|
{
|
||||||
|
TileStatus tileStatus = TileStatus.Hidden;
|
||||||
|
Label label;
|
||||||
|
Image flagImage, bugImage;
|
||||||
|
static ImageSource flagImageSource;
|
||||||
|
static ImageSource bugImageSource;
|
||||||
|
bool doNotFireEvent;
|
||||||
|
|
||||||
|
public event EventHandler<TileStatus> TileStatusChanged;
|
||||||
|
|
||||||
|
static Tile()
|
||||||
|
{
|
||||||
|
flagImageSource = ImageSource.FromResource("Samples.BugSweeper.Images.Xamarin120.png", System.Reflection.Assembly.GetCallingAssembly());
|
||||||
|
bugImageSource = ImageSource.FromResource("Samples.BugSweeper.Images.RedBug.png", System.Reflection.Assembly.GetCallingAssembly ());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tile(int row, int col)
|
||||||
|
{
|
||||||
|
this.Row = row;
|
||||||
|
this.Col = col;
|
||||||
|
|
||||||
|
this.BackgroundColor = Color.Yellow;
|
||||||
|
this.OutlineColor = Color.Blue;
|
||||||
|
this.Padding = 2;
|
||||||
|
|
||||||
|
label = new Label {
|
||||||
|
Text = " ",
|
||||||
|
TextColor = Color.Yellow,
|
||||||
|
BackgroundColor = Color.Blue,
|
||||||
|
HorizontalTextAlignment = TextAlignment.Center,
|
||||||
|
VerticalTextAlignment = TextAlignment.Center,
|
||||||
|
};
|
||||||
|
|
||||||
|
flagImage = new Image {
|
||||||
|
Source = flagImageSource,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
bugImage = new Image {
|
||||||
|
Source = bugImageSource
|
||||||
|
};
|
||||||
|
|
||||||
|
TapGestureRecognizer singleTap = new TapGestureRecognizer {
|
||||||
|
NumberOfTapsRequired = 1
|
||||||
|
};
|
||||||
|
singleTap.Tapped += OnSingleTap;
|
||||||
|
this.GestureRecognizers.Add(singleTap);
|
||||||
|
|
||||||
|
#if FIX_WINDOWS_DOUBLE_TAPS
|
||||||
|
|
||||||
|
if (Device.OS != TargetPlatform.Windows && Device.OS != TargetPlatform.WinPhone) {
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TapGestureRecognizer doubleTap = new TapGestureRecognizer {
|
||||||
|
NumberOfTapsRequired = 2
|
||||||
|
};
|
||||||
|
doubleTap.Tapped += OnDoubleTap;
|
||||||
|
this.GestureRecognizers.Add(doubleTap);
|
||||||
|
|
||||||
|
#if FIX_WINDOWS_DOUBLE_TAPS
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Row { private set; get; }
|
||||||
|
|
||||||
|
public int Col { private set; get; }
|
||||||
|
|
||||||
|
public bool IsBug { set; get; }
|
||||||
|
|
||||||
|
public int SurroundingBugCount { set; get; }
|
||||||
|
|
||||||
|
public TileStatus Status {
|
||||||
|
set {
|
||||||
|
if (tileStatus != value) {
|
||||||
|
tileStatus = value;
|
||||||
|
|
||||||
|
switch (tileStatus) {
|
||||||
|
case TileStatus.Hidden:
|
||||||
|
this.Content = null;
|
||||||
|
|
||||||
|
#if FIX_WINDOWS_PHONE_NULL_CONTENT
|
||||||
|
|
||||||
|
if (Device.OS == TargetPlatform.WinPhone || Device.OS == TargetPlatform.Windows) {
|
||||||
|
this.Content = new Label { Text = " " };
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TileStatus.Flagged:
|
||||||
|
this.Content = flagImage;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TileStatus.Exposed:
|
||||||
|
if (this.IsBug) {
|
||||||
|
this.Content = bugImage;
|
||||||
|
} else {
|
||||||
|
this.Content = label;
|
||||||
|
label.Text =
|
||||||
|
(this.SurroundingBugCount > 0) ?
|
||||||
|
this.SurroundingBugCount.ToString() : " ";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doNotFireEvent && TileStatusChanged != null) {
|
||||||
|
TileStatusChanged(this, tileStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get {
|
||||||
|
return tileStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does not fire TileStatusChanged events.
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
doNotFireEvent = true;
|
||||||
|
this.Status = TileStatus.Hidden;
|
||||||
|
this.IsBug = false;
|
||||||
|
this.SurroundingBugCount = 0;
|
||||||
|
doNotFireEvent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if FIX_WINDOWS_DOUBLE_TAPS
|
||||||
|
|
||||||
|
bool lastTapSingle;
|
||||||
|
DateTime lastTapTime;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void OnSingleTap(object sender, object args)
|
||||||
|
{
|
||||||
|
|
||||||
|
#if FIX_WINDOWS_DOUBLE_TAPS
|
||||||
|
|
||||||
|
if (Device.OS == TargetPlatform.Windows || Device.OS == TargetPlatform.WinPhone) {
|
||||||
|
if (lastTapSingle && DateTime.Now - lastTapTime < TimeSpan.FromMilliseconds (500)) {
|
||||||
|
OnDoubleTap (sender, args);
|
||||||
|
lastTapSingle = false;
|
||||||
|
} else {
|
||||||
|
lastTapTime = DateTime.Now;
|
||||||
|
lastTapSingle = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
switch (this.Status) {
|
||||||
|
case TileStatus.Hidden:
|
||||||
|
this.Status = TileStatus.Flagged;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TileStatus.Flagged:
|
||||||
|
this.Status = TileStatus.Hidden;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TileStatus.Exposed:
|
||||||
|
// Do nothing
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDoubleTap (object sender, object args)
|
||||||
|
{
|
||||||
|
this.Status = TileStatus.Exposed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
using Ooui;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Samples
|
||||||
|
{
|
||||||
|
public class BugSweeperSample : ISample
|
||||||
|
{
|
||||||
|
public string Title => "Xamarin.Forms BugSweeper";
|
||||||
|
|
||||||
|
public Ooui.Element CreateElement ()
|
||||||
|
{
|
||||||
|
var page = new BugSweeper.BugSweeperPage ();
|
||||||
|
return page.GetOouiElement ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,8 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="**/*.xaml" />
|
<EmbeddedResource Include="**/*.xaml" />
|
||||||
|
<EmbeddedResource Include="BugSweeper\Images\RedBug.png" />
|
||||||
|
<EmbeddedResource Include="BugSweeper\Images\Xamarin120.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -41,6 +43,10 @@
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="BugSweeper\Images\RedBug.png" />
|
||||||
|
<None Remove="BugSweeper\Images\Xamarin120.png" />
|
||||||
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
|
|
Loading…
Reference in New Issue