Introduction
This is a familiar game with a new twist. To win, you must find and flag all ten bugs. Ten bugs are hidden in a 9-by-9 grid of tiles.That first double tab is always safe. Thereafter, numbers indicate the number of surrounding tiles with bugs. Flag it with a single tab. Avoid double-tabbing a tile with bug!.
Let's start.
Step 1
You can create Xamarin.Forms app by going to File >> New >> Visual C# >> Cross-platform >> Cross-Platform App (Xamarin.Native or Xamarin.Forms), enter desired name for your project, and press OK.
(Project name- BugSweeper)
Step 2
After the project creation, go to Solution Explorer >> BugSweeper(PCL) app >> click open MainPage.xaml and add the following code.
Used ToolBox items,
- StackLayout
- Label
- BoxView
- Grid
- <?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:BugSwapperApp"
- x:Class="BugSwapperApp.MainPage">
-
- <ContentView SizeChanged="OnMainContentView_SizeChanged">
- <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="Navy"/>
- <BoxView Color="Orange" HeightRequest="3"/>
- </StackLayout>
- <Label Text="Tab to flag/unflag a potential bug" VerticalOptions="CenterAndExpand" HorizontalOptions="Center"/>
- <Label Text="Double-tab if you're sure it's not a bug.
- The First Double tab be safe" VerticalOptions="CenterAndExpand" HorizontalOptions="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>
-
- <Label x:Name="timeLabel" Text="0:00" VerticalOptions="CenterAndExpand" HorizontalOptions="Center"/>
- </StackLayout>
- <ContentView Grid.Row="1" Grid.Column="1" SizeChanged="OnBoardContentView_SizeChanged">
- <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="Black"/>
- <Label Text="O" TextColor="Black"/>
- <Label Text="N" TextColor="Black"/>
- <Label Text="G" TextColor="Black"/>
- <Label Text="R" TextColor="Black"/>
- <Label Text="A" TextColor="Black"/>
- <Label Text="T" TextColor="Black"/>
- <Label Text="U" TextColor="Black"/>
- <Label Text="L" TextColor="Black"/>
- <Label Text="A" TextColor="Black"/>
- <Label Text="T" TextColor="Black"/>
- <Label Text="I" TextColor="Black"/>
- <Label Text="O" TextColor="Black"/>
- <Label Text="N" TextColor="Black"/>
- <Label Text="S" TextColor="Black"/>
- <Label Text="!" TextColor="Black"/>
- <Label Text="!" TextColor="Black"/>
- </StackLayout>
- <StackLayout x:Name="consolationText" Orientation="Horizontal" Spacing="0" HorizontalOptions="Center" VerticalOptions="Center">
- <Label Text="T" TextColor="Black"/>
- <Label Text="O" TextColor="Black"/>
- <Label Text="O" TextColor="Black"/>
- <Label Text="L" TextColor="Black"/>
- <Label Text="O" TextColor="Black"/>
- <Label Text="S" TextColor="Black"/>
- <Label Text="T" TextColor="Black"/>
- <Label Text="!" TextColor="Black"/>
- <Label Text=":" TextColor="Gray"/>
- <Label Text=")" TextColor="Green"/>
- </StackLayout>
- <Button x:Name="playAgainButton" Text="Play Another Game?" VerticalOptions="Center" HorizontalOptions="Center" Clicked="onplayAgainButton_Clicked" BorderColor="Black" BorderWidth="2" BackgroundColor="White" TextColor="Blue"/>
- </Grid>
- </ContentView>
-
- </Grid>
-
- </ContentView>
-
- </ContentPage>
Step 3
In this step, open Solution Explorer >> BugSweeper (PCL) >> MainPage.xaml.cs and double click to open MainPage.xaml.cs. Here is the code.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- using Xamarin.Forms;
-
- namespace BugSwapperApp
- {
- public partial class MainPage : ContentPage
- {
- const string timeFormat = @"%m\:ss";
-
- bool isGameInProgress;
- DateTime gameStartTime;
- public MainPage()
- {
- 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 OnMainContentView_SizeChanged(object sender, EventArgs e)
- {
- 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
- {
- 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);
- }
- }
-
- void OnBoardContentView_SizeChanged(object sender, EventArgs e)
- {
- 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;
-
-
-
- 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;
-
-
- 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;
-
-
- double playAgainButtonWidth = playAgainButton.Measure(Double.PositiveInfinity, Double.PositiveInfinity).Request.Width;
-
- double maxScale = board.Width / playAgainButtonWidth;
- await playAgainButton.ScaleTo(maxScale, 1000, Easing.SpringOut);
- }
-
-
-
- void onplayAgainButton_Clicked(object sender, EventArgs e)
- {
- PrepareForNewGame();
- }
- }
- }
Step 4
Open Solution Explorer >> BugSweeper(PCL) >> right click and select New Item. In this popup window, select Cross Platform >> Class.
This way, you can add a new class. Now, create a new class named Board.cs and double-click to get into its design view and insert the code given below.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- using Xamarin.Forms;
-
- namespace BugSwapperApp
- {
- class Board : AbsoluteLayout
- {
- const int COLS = 9;
- const int ROWS = 9;
- const int BUGS = 10;
- Tile[,] tiles = new Tile[ROWS, COLS];
- int flaggedTileCount;
- bool isGameInProgress;
- bool isGameInitialized;
- bool isGameEnded;
-
- 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()
- {
-
- 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;
- }
- }
-
-
-
- void DefineNewBoard(int tappedRow, int tappedCol)
- {
-
- Random random = new Random();
- int bugCount = 0;
-
- while (bugCount < BUGS)
- {
-
- int row = random.Next(ROWS);
- int col = random.Next(COLS);
-
-
- if (tiles[row, col].IsBug)
- {
- continue;
- }
-
-
- if (row >= tappedRow - 1 &&
- row <= tappedRow + 1 &&
- col >= tappedCol - 1 &&
- col <= tappedCol + 1)
- {
- continue;
- }
-
-
- tiles[row, col].IsBug = true;
-
-
- 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;
-
-
- if (!isGameInProgress)
- {
- isGameInProgress = true;
-
-
- if (GameStarted != null)
- {
- GameStarted(this, EventArgs.Empty);
- }
- }
-
-
- int flaggedCount = 0;
-
- foreach (Tile tile in tiles)
- if (tile.Status == TileStatus.Flagged)
- flaggedCount++;
-
- this.FlaggedTileCount = flaggedCount;
-
-
- Tile changedTile = (Tile)sender;
-
-
- if (tileStatus == TileStatus.Exposed)
- {
- if (!isGameInitialized)
- {
- DefineNewBoard(changedTile.Row, changedTile.Col);
- isGameInitialized = true;
- }
-
- if (changedTile.IsBug)
- {
- isGameInProgress = false;
- isGameEnded = true;
-
-
- if (GameEnded != null)
- {
- GameEnded(this, false);
- }
- return;
- }
-
-
- if (changedTile.SurroundingBugCount == 0)
- {
- CycleThroughNeighbors(changedTile.Row, changedTile.Col,
- (neighborRow, neighborCol) =>
- {
-
- tiles[neighborRow, neighborCol].Status = TileStatus.Exposed;
- });
- }
- }
-
-
- 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 (hasWon)
- {
- isGameInProgress = false;
- isGameEnded = true;
-
-
- if (GameEnded != null)
- {
- GameEnded(this, true);
- }
- return;
- }
- }
- }
- }
Step 5
Now, we need to add images. For that, go to Solution Explorer >> BugSweeper(PCL) >> right-click on BugSweeper(PCL) project and select Add>> Existing Item. In this popup window, select images to insert and the images are used for tiles.
- BugSwapperApp.Microsoft.png
- BugSwapperApp.Microsoft.png
Step 6
In this step, similarly, create a new class named Tile.cs. Here is the code for this class.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- using Xamarin.Forms;
-
- namespace BugSwapperApp
- {
- 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("BugSwapperApp.Microsoft.png");
- bugImageSource = ImageSource.FromResource("BugSwapperApp.Xbox.png");
- }
-
- 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 (Device.OS != TargetPlatform.Windows && Device.OS != TargetPlatform.WinPhone)
- {
- TapGestureRecognizer doubleTap = new TapGestureRecognizer
- {
- NumberOfTapsRequired = 2
- };
- doubleTap.Tapped += OnDoubleTap;
- this.GestureRecognizers.Add(doubleTap);
-
-
- }
- }
-
- 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;
- }
- }
-
-
- public void Initialize()
- {
- doNotFireEvent = true;
- this.Status = TileStatus.Hidden;
- this.IsBug = false;
- this.SurroundingBugCount = 0;
- doNotFireEvent = false;
- }
-
-
-
- bool lastTapSingle;
- DateTime lastTapTime;
-
-
- void OnSingleTap(object sender, object args)
- {
-
- switch (this.Status)
- {
- case TileStatus.Hidden:
- this.Status = TileStatus.Flagged;
- break;
-
- case TileStatus.Flagged:
- this.Status = TileStatus.Hidden;
- break;
-
- case TileStatus.Exposed:
-
- break;
- }
- }
-
- void OnDoubleTap(object sender, object args)
- {
- this.Status = TileStatus.Exposed;
- }
- }
- }
Step 7
Click ' F5 ' or Build to run your projects. Running this project, you will have the result like below.
Finally, we have successfully created a Xamarin.Forms BugSweeper Application.