Blazor is the new hotness in the ASP.NET Core world, and for good reason.
The latest framework included in .NET Core promises to allow us to create full-blown client-side applications using nothing but ASP.NET Core and C#, something that has not been possible until now. With the release of .NET Core 3.0, we are closer to realizing this dream, as Blazor (at least, a version of it, see below) is now out of preview and natively supported.
So, in keeping with the spirit of my prior Modeling Practice posts, let's learn how to use a new framework by building a board game; specifically, let's build the classic childhood game ConnectFour in Blazor!
Sample Project
As with all of my posts tagged "Sample Project", this post has a corresponding GitHub Repository Feel free to suggest changes, make pull requests, or leave comments about the code either there or in the comments on this post.
Creating a New Blazor App
First things first; we need to create a new ASP.NET Core and Blazor app.
Note: This post was written using .NET Core 3.0 and Visual Studio 2019.
First, create a new project, then select Blazor App as the type:
Name your project and click Create.
On finishing this step, you will have a basic but functional Blazor application.
What Did We Create?
If you read the description of the "Blazor App" option closely, you'll see that there are actually two different kinds of Blazor apps: server-side using SignalR and client-side using Web Assembly. Only the server-side variant is fully supported in .NET Core 3.0. You may want to read up on the difference between the two hosting types if you are not yet familiar with them.
The main thing to remember is that this particular post (and the corresponding sample project) are built using Blazor server-side.
Modeling the Game
With our sample app ready to go, it's time to think about how we want to model a game of ConnectFour. In order to do this, we first need to think about what kinds of things represent a game in progress.
First and foremost we have the "board"; in reality it's a vertical series of slits, 7 spaces wide and 6 tall, into which the pieces are inserted. Then we have the pieces themselves; various red and yellow discs which slide easily into the board. This is a relatively simple game, so (luckily for us) the amount of modeling we have to do is limited.
First, for posterity, we will need an Enum to represent the color of the piece:
public enum PieceColor
{
Red,
Yellow,
Blank
}
The value "blank" will be used to represent spaces on the board that haven't gotten filled by a piece yet.
We now need a basic class to represent a game piece. Lucky for us, the game piece really only has one property: its color.
public class GamePiece
{
public PieceColor Color;
public GamePiece(PieceColor color)
{
Color = color;
}
}
Finally, we need to model the board itself. We're going to think of the board as being a multidimensional array, 7 wide by 6 tall, and filled with "blank" pieces at the beginning of the game; these blank pieces will be replaced by colored pieces as the game progresses.
public class GameBoard
{
public GamePiece[,] Board { get; set; }
public GameBoard()
{
Board = new GamePiece[7, 6];
//Populate the Board with blank pieces
for(int i = 0; i <= 6; i++)
{
for(int j = 0; j <= 5; j++)
{
Board[i, j] = new GamePiece(PieceColor.Blank);
}
}
}
}
Our models are complete! Let's add some functionality so that we can play a game of ConnectFour!
Creating a New Razor Page
We need to create a new page which will contain the following code. In Visual Studio, we right-click the Pages folder and select Add > Razor Page:
I named this page ConnectFour.razor. We now need to put some very basic content into this page:
@page "/connectfour"
@using BlazorConnectFour.Data;
<h1>ConnectFour</h1>
Note the @page directive. This directive sets up the route to which this page will answer to; in that way it is similar to Attribute Routing in ASP.NET MVC. Our route says this page will respond to <rootsite>/connectfour.
We also need to include the namespace of our model classes, BlazorConnectFour.Data. Hence, we use the @using directive seen above.
The @code Directive
We now need to add some code to this Razor Page. We can do this one of two ways: either in a backing "code behind" C# file or in the page itself. Because this demo is relatively simply, I've chosen the latter method; you can choose whichever you like better.
Let's imagine the state of the game before the first player take their first turn. At this point, the board is empty; no pieces have been added. The player who is using the red pieces goes first.
Let's output the board to the page:
@code {
GameBoard board = new GameBoard();
PieceColor currentTurn = PieceColor.Red;
}
<h2>@currentTurn's Turn!</h2>
<div class="board">
@for (int i = 0; i < 7; i++)
{
<div class="column">
@for (int j = 0; j < 6; j++)
{
<div class="gamepiece @board.Board[i,j].Color.ToString().ToLower()"></div>
}
</div>
}
</div>
Let's talk about that @code directive for a second. That directive tells ASP.NET that anything within the braces must be treated as running on this page. Further, variables declared inside the braces can be accessed in our HTML, such as the <h2> tag accessing the @currentTurn variable.
Finally, we output the current state of the board. For each of the 7 columns and each of the 6 rows, we output the current color occupying that space.
CSS Magic
Longtime readers will know that, as far as I am concerned, CSS is magic and something I cannot do. But our board isn't going to look like a real ConnectFour board without some CSS magic. After a few hours of fiddling around, here's the CSS classes I ended up using in this example:
.board {
background-color: blue;
display:flex;
flex-direction:row;
max-width:490px;
}
.column {
min-width:60px;
min-height:60px;
height:100%;
}
.gamepiece {
border:solid 1px black;
margin:5px;
min-height:60px;
min-width:60px;
border-radius:50px;
}
.red {
background-color:red;
}
.yellow {
background-color:yellow;
}
.blank {
background-color:white;
}
All of this put together results in our board looking good:
Now we need to deal with the last bit of this app: how do we make the board respond to clicks and place the correct color pieces?
Responding to Clicks
In order to make our board spaces respond to being clicked, we can use the @onclick directive on the <div> that represents the board spaces to call a new private method in the @code directive:
@code {
...
private void PieceClicked(int x, int y)
{ ... }
}
<div class="board">
@for (int i = 0; i < 7; i++)
{
<div class="column">
@for (int j = 0; j < 6; j++)
{
<div class="gamepiece @board.Board[i,j].Color.ToString().ToLower()"
@onclick="@(() => PieceClicked(i,j))></div>
}
</div>
}
</div>
Except this doesn't actually work. See, the values of i and j are only available within the loops that declared them; in Razor Pages these values cannot be persisted to other functions. So, we need to declare new variables which can be persisted:
@code {
...
private void PieceClicked(int x, int y)
{ ... }
}
<div class="board">
@for (int i = 0; i < 7; i++)
{
<div class="column">
@for (int j = 0; j < 6; j++)
{
int x = i; //Need to declare these variables so they can be persisted.
int y = j;
<div class="gamepiece @board.Board[i,j].Color.ToString().ToLower()"
@onclick="@(() => PieceClicked(x,y))></div>
}
</div>
}
</div>
Clicking a Piece
Now we need to fill out the PieceClicked() method. To do that, let's think about how pieces behave in a real game of ConnectFour.
When you insert a piece into a column slot, they will slide down to the lowest available slot due to gravity. A large part of the strategy of ConnectFour (such as it is) is taking this into account when making moves. Our code, therefore, needs to reflect that no matter where in a column we click, the piece must "fall" to the lowest available slot. This is why we needed the "blank" color.
@code {
GameBoard board = new GameBoard();
PieceColor currentTurn = PieceColor.Red;
private void PieceClicked(int x, int y)
{
var clickedSpace = board.Board[x, y];
//The piece must "fall" to the lowest unoccupied space in the clicked column
if (clickedSpace.Color == PieceColor.Blank)
{
while (y < 5)
{
var nextSpace = board.Board[x, y + 1];
y = y + 1;
if (nextSpace.Color == PieceColor.Blank)
clickedSpace = nextSpace;
}
clickedSpace.Color = currentTurn;
SwitchTurns();
}
}
private void SwitchTurns()
{
if(currentTurn == PieceColor.Red)
{
currentTurn = PieceColor.Yellow;
}
else
{
currentTurn = PieceColor.Red;
}
}
}
A Working Game
Ta da! We now have a working ConnectFour game. It looks like this:
The Full Razor Page
Here's the complete code for the ConnectFour.razor page:
"/connectfour"
@using BlazorConnectFour.Data;
<h1>ConnectFour</h1>
@code {
GameBoard board = new GameBoard();
PieceColor currentTurn = PieceColor.Red;
private void PieceClicked(int x, int y)
{
var clickedSpace = board.Board[x, y];
if (clickedSpace.Color == PieceColor.Blank)
{
while (y < 5)
{
var nextSpace = board.Board[x, y + 1];
y = y + 1;
if (nextSpace.Color == PieceColor.Blank)
clickedSpace = nextSpace;
}
clickedSpace.Color = currentTurn;
SwitchTurns();
}
}
private void SwitchTurns()
{
if(currentTurn == PieceColor.Red)
{
currentTurn = PieceColor.Yellow;
}
else
{
currentTurn = PieceColor.Red;
}
}
}
<h2>@currentTurn's Turn!</h2>
<div class="board">
@for (int i = 0; i < 7; i++)
{
<div class="column">
@for (int j = 0; j < 6; j++)
{
var x = i;
var y = j;
<div class="gamepiece @board.Board[i,j].Color.ToString().ToLower()"
@onclick="@(() => PieceClicked(x,y))"></div>
}
</div>
}
</div>
Improvements
A major improvement I'd like to make to this game is to be able to determine the win condition (e.g. a player gets four in a row vertically, horizontally, or diagonally) and switch the board "off" so that it takes no further input. I haven't yet worked out a good algorithm for determining the win condition, though; if you think of one, feel free to submit a pull request!
Special Update: Highlighting the Winning Pieces!
May 4th, 2020: Contributor John Tomlinson on GitHub submitted a pull request against this repository that I found particularly helpful. His code highlights the winning pieces! I've merged his code into the main repository. Check out his pull request here:
Special Update: Youtube Playlist!
Sep 28th, 2020: Reader Kaushik Roy Chowdhury has created a YouTube playlist where he builds this ConnectFour game with Blazor and walks through each step. Check it out!
Summary
Using ASP.NET Core 3.0 and Blazor, we now have a working ConnectFour game! Any improvements, new functionality, or other suggestions are welcome; please leave them in the comments below.
Don't forget to check out the corresponding GitHub repository where the code for this project is kept.
Happy Coding!