5

How to create the Connect 4 game in Blazor WebAssembly in an hour!

 2 years ago
source link: https://www.roundthecode.com/dotnet/blazor/create-connect-4-blazor-webassembly-in-hour
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

How to create the Connect 4 game in Blazor WebAssembly in one hour!

29th May 2021

We did another live coding challenge on YouTube, and our aim was to create Connect 4 with the Blazor WebAssembly framework.

But, there was a catch. We only had one hour to do it!

As well as a playable game, we also wanted the ability to reset the game and keep a scoreboard.

We will talk through how we went about building the web application and share the code used.

In addition, watch our live stream where we successfully completed our challenge using C# and Razor components.

How does Connect 4 work?

Connect 4 is where there are two players, where one player is red and the other is yellow. Each player takes alternate turns, and drops their coloured disks into a 7 column by 6 row suspended grid.

When the player takes their turn, their disk will fall into the lowest unoccupied space in the column.

The aim of the game is for a player to get four of their pieces in a row. This can be done either horizontally, vertically, or diagonally.

In the example below, the yellow player has won by getting four yellow disks vertically in the centre column.

Connect 4 game built using Blazor WebAssemblyConnect 4 game built using Blazor WebAssembly

The classes

We decided to create a class and an enum.

The first was a PieceEnum enum. This would represent each player piece (or disc) on the grid, with 'Yellow' represented with an index of 0, and 'Red' represented with an index of 1.

// PieceEnum.cs
public enum PieceEnum
{
Yellow,
Red
}

The Grid model

The Grid model holds the background functionality to make Connect 4 work.

The first property is Pieces. The Pieces instance is a two-dimensional array that holds all the different spaces on the grid. The first dimension represents the columns, and the second represents the rows. This array stores the different pieces using an instance of nullable PieceEnum.

This gets created when the Grid model is initialised, and when the game is reset.

In-addition, we also have a couple of other PieceEnum instances. NextTurn is the first, which defines which colour is about to take their turn. Winner is the other, which determines which colour has won the game.

Finally for properties, we have a Red and Yellow property which keeps track on how many games each colour has won. We also have a Boolean property to dictate whether it's a drawn game.

In-terms of methods, we have a ResetGame, SetNextTurn and GetWinner method.

The SetNextTurn method will determine if there is a winner by calling the GetWinner method. Assuming there isn't, it will update the NextTurn property with the opposite colour.

The GetWinner method will go through and check each grid space to see if there are four of the same colour in a row. This can happen horizontally, vertically, or diagonally.

When the ResetGame method is called, it will create a new instance of our Pieces array and reset the winner.

// Grid.cs
public class Grid
{
const int COLS = 7;
const int ROWS = 6;
public PieceEnum?[,] Pieces { get; protected set; }
public PieceEnum NextTurn { get; protected set; }
public PieceEnum? Winner { get; protected set; }
public int Red { get; set; }
public int Yellow { get; set; }
public bool Draw { get; set; }
public Grid()
{
NextTurn = PieceEnum.Red;
ResetGame();
}
public void ResetGame()
{
Pieces = new PieceEnum?[COLS, ROWS];
Draw = false;
if (!Winner.HasValue)
{
NextTurn = (NextTurn == PieceEnum.Red ? PieceEnum.Yellow : PieceEnum.Red);
}
else
{
NextTurn = Winner.Value;
}
Winner = null;
}
public void SetNextTurn()
{
Winner = GetWinner();
if (!Winner.HasValue)
{
if (NextTurn == PieceEnum.Red)
{
NextTurn = PieceEnum.Yellow;
}
else
{
NextTurn = PieceEnum.Red;
}
}
else
{
switch (Winner.Value)
{
case PieceEnum.Red:
Red += 1;
break;
case PieceEnum.Yellow:
Yellow += 1;
break;
}
}
}
public class CheckIndex
{
public int Column { get; }
public int Row { get; }
public CheckIndex(int column, int row)
{
Column = column;
Row = row;
}
}
public PieceEnum? GetWinner()
{
PieceEnum? Winner = null;
for (var column = 0; column <= Pieces.GetUpperBound(0); column++)
{
for (var row = 0; row <= Pieces.GetUpperBound(1); row++)
{
// Check horizontally
Winner = CheckGroup(column, row, (column, row, checkIndex) => new CheckIndex(column + checkIndex, row));
if (Winner.HasValue)
{
return Winner;
}
// Check vertically
Winner = CheckGroup(column, row, (column, row, checkIndex) => new CheckIndex(column, row + checkIndex));
if (Winner.HasValue)
{
return Winner;
}
// Check diagnonal
Winner = CheckGroup(column, row, (column, row, checkIndex) => new CheckIndex(column + checkIndex, row + checkIndex));
if (Winner.HasValue)
{
return Winner;
}
// Check diagnonal
Winner = CheckGroup(column, row, (column, row, checkIndex) => new CheckIndex(column - checkIndex, row + checkIndex));
if (Winner.HasValue)
{
return Winner;
}
}
}
return null;
}
private PieceEnum? CheckGroup(int column, int row, Func<int, int, int, CheckIndex> check)
{
PieceEnum? lastCheck = null;
for (var checkIndex = 0; checkIndex <= 3; checkIndex++)
{
var checkRowColIndex = check?.Invoke(column, row, checkIndex);
if (checkRowColIndex == null)
{
return null;
}
if (checkRowColIndex.Column < Pieces.GetLowerBound(0) || checkRowColIndex.Column > Pieces.GetUpperBound(0)
|| checkRowColIndex.Row < Pieces.GetLowerBound(1) || checkRowColIndex.Row > Pieces.GetUpperBound(1)
)
{
return null;
}
var thisCheck = Pieces[checkRowColIndex.Column, checkRowColIndex.Row];
if (thisCheck == null || (checkIndex > 0 && lastCheck != thisCheck))
{
return null;
}
lastCheck = thisCheck;
}
return lastCheck;
}
}

Creating Razor components in Blazor

We had to go ahead and create two Razor components in our Blazor application to make it work.

The space component

The space component represents each space on the grid. For this component to function, a nullable PieceEnum instance has to be passed in as a parameter.

From here, we check and see if the nullable PieceEnum instance has a value assigned to it.

If it does, we create a div class, with the class name determining whether it's a red or yellow circle that we display. We then add CSS to style the piece as a coloured disc.

<!-- SpaceComponent.razor -->
@using RoundTheCode.Connect4.Models
<div class="space">
@if (Piece.HasValue)
{
<div class="[email protected]().ToLower()"></div>
}
</div>
@code {
[Parameter]
public PieceEnum? Piece { get; set; }
}
/* SpaceComponent.razor.css */
.space {
width: 150px;
height: 100px;
background-color: blue;
border: 1px white solid;
padding: 15px 40px;
}
.piece-red {
background-color:red;
border-radius: 50%;
width: 70px;
height: 70px;
}
.piece-yellow {
background-color: yellow;
border-radius: 50%;
width: 70px;
height: 70px;
}

The grid

Finally, a GridComponent Razor component was created. This would be the main root of the application.

For this to function, a Grid instance is created when it's initialised. The Razor component displays an empty 7-column by 6-row grid, where each column is clickable. The SpaceComponent is displayed for each square on the grid, and this is populated using the Pieces array from our Grid instance. Each value in the Pieces array is passed through as a parameter to the SpaceComponent.

When a column is clicked, it calls a handle which determines which column index has been clicked, and occupies the first unoccupied row of that column. Subsequently, it updates our Pieces array from our Grid instance.

Finally, we displayed a scoreboard and created a Reset button. With the reset button, it calls the ResetGame method in the Grid model instance, which clears the squares and decides which player gets to go first in the next game.

<!-- GridComponent.razor -->
@using RoundTheCode.Connect4.Models
@page "/"
@if (Grid != null) {
<div class="layout">
<div class="message">
<strong>Red @[email protected] Yellow</strong><br />
@if (Grid?.Winner.HasValue ?? false)
{
@(Grid.Winner.Value  + " has won the game")
}
@if (Grid?.Draw ?? false)
{
@("The game is a draw")
}
<button @onclick="@(e => ResetGame(e))">Reset game</button>@Grid.NextTurn's turn is next.
</div>
<div class="grid">
@for (var col = Grid.Pieces.GetLowerBound(0); col <= Grid.Pieces.GetUpperBound(0); col++)
{
var c = col;
<div class="column" @onclick="@(e => ColumnClick(e, c))">
@for (var row = Grid.Pieces.GetUpperBound(1); row >= Grid.Pieces.GetLowerBound(0); row--)
{
<SpaceComponent Piece="@Grid.Pieces[col, row]"></SpaceComponent>
}
</div>
}
</div>
</div>
}
@code {
public Grid Grid { get; set; }
protected override Task OnInitializedAsync()
{
Grid = new Grid();
return base.OnInitializedAsync();
}
public void ColumnClick(MouseEventArgs eventArgs, int col)
{
if (Grid.Winner.HasValue)
{
return;
}
for (var row = Grid.Pieces.GetLowerBound(1); row <= Grid.Pieces.GetUpperBound(1); row++)
{
if (!Grid.Pieces[col, row].HasValue)
{
Grid.Pieces[col, row] = Grid.NextTurn;
Grid.SetNextTurn();
break;
}
Grid.Draw = true;
}
}
public void ResetGame(MouseEventArgs mouseEventArgs)
{
Grid.ResetGame();
}
}
/* GridComponent.razor.css */
.layout {
margin-left: auto;
margin-right: auto;
width: 1050px;
}
.message {
height: 100px;
}
.grid {
height: 700px;
}
.column {
width: 14%;
float: left;
}

The final result

We got the application working and here is how the final result looked:

Connect 4 game built using Blazor WebAssemblyConnect 4 game built using Blazor WebAssembly

If you wish to try it out for yourselves, download the source code for this Blazor demo and try out the application on your machine.

Subscribe to our YouTube Channel

  • Free ASP.NET Core coding tutorial videos.
  • Implement a how-to guide on a topic related to ASP.NET Core.
  • Covers Blazor, Web API's, SQL Server, EF and many more...
Subscribe

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK