Create a Simple Counting Game using Blazor Web Assembly
Getting started with Blazor Web Assembly in .NET 8
I created a simple monkey counting game for my two years old daughter:
It was developed using Blazor Web Assembly and hosted in the Azure Static App. It is part of my journey learning Blazor through hands-on experience.
This article will be a detailed guide to developing the monkey counting game from scratch.
Let's begin.
Create a New Blazor Web Assembly Project
Open your Visual Studio 2022, then select the Blazor Web Assembly Standalone App project template.
Set the project name to BlazorCountingGame or any other name of your choice.
Set the framework to .NET 8.0, the authentication type to None, and select Configure for HTTPS.
Click [Create] to initialize a new Blazor Web Assembly project. The project structure will be like this:
Add Bootstrap CSS from CDN
We will be using Bootstrap for styling. Open the index.html file inside the wwwroot folder, then add the link to the bootstrap CSS file below to the <head>
tag:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
I also modified the content of index.html to make it simpler. This is the complete code of the index.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Monkey Count - juldhais.net</title>
<base href="/" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<link rel="stylesheet" href="css/app.css" />
</head>
<body>
<div id="app">
Loading...
</div>
<script src="_framework/blazor.webassembly.js"></script>
</body>
</html>
Generate a Random Number of Monkeys
Now go to the Home.razor file inside the Pages folder. This is the place where we write our code for the game.
@page "/"
<PageTitle>Monkey Count</PageTitle>
<div class="mx-4 my-4">
<div class="display-3 text-center">How many monkeys are there?</div>
<div>
<div class="row row-cols-3">
@for (int i = 0; i < count; i++)
{
<div class="col">🙉</div>
}
</div>
</div>
</div>
@code {
int count;
protected override void OnInitialized()
{
NextQuestion();
}
void NextQuestion()
{
// generate random number of monkeys
count = Random.Shared.Next(1, 10);
}
}
@page Directive
@page "/"
This directive makes the component a page accessible via the root URL ("/"). It defines a routing endpoint for the application.
PageTitle Component
<PageTitle>Monkey Count</PageTitle>
Sets the title of the page, which appears on the browser tab.
HTML Content
The <div class="mx-4 my-4">
acts as the main container with margin properties set by Bootstrap classes (mx-4
for horizontal margins and my-4
for vertical margins).
The div
structures are used to create a grid (using Bootstrap's grid system with row row-cols-3
classes) that will display the monkey emojis. The number of emojis displayed is dynamically generated based on the value of the count
variable.
@code Block
The @code
block contains the C# code that handles the interactivity of the page:
count
variable holds the number of monkey emojis to be displayed.OnInitialized
is a lifecycle method overridden to call theNextQuestion
method when the component is initialized.NextQuestion
method generates a random number between 1 and 9 and assigns it to thecount
variable. This determines the number of monkey emojis (🙉
) that will be displayed in the grid.
Create and Shuffle the Choices
The player must select the correct number of monkeys. Three choices are given, and only one of them is correct.
We have to modify the code section like below:
@code {
int count;
List<int> choices = [];
protected override void OnInitialized()
{
NextQuestion();
}
void NextQuestion()
{
// generate random number
count = Random.Shared.Next(1, 10);
choices.Clear();
// add count to choices
choices.Add(count);
// add 2 more unique random number to choices
while (choices.Count < 3)
{
int choice = Random.Shared.Next(1, 9);
if (!choices.Contains(choice))
{
choices.Add(choice);
}
}
// shuffle the choices using Fisher-Yates algorithm
var n = choices.Count;
while (n > 1)
{
n--;
int k = Random.Shared.Next(n + 1);
int value = choices[k];
choices[k] = choices[n];
choices[n] = value;
}
}
}
The choices
variable is a list of integers. It is intended to store the correct answer (count
) and two additional unique random numbers as possible choices for the answer.
Changes in the NextQuestion
method:
- The
choices.Clear()
removes any choices from previous questions. - The
choices.Add(count)
adds thecount
(the correct answer) to thechoices
list. - The
while
loop continues to add random numbers between 1 and 9 to thechoices
list until there are three choices in total. It ensures that these numbers are unique. - Lastly, it implements the Fisher-Yates shuffle algorithm to randomize the order of the elements in the
choices
list.
Next, we have to display the content of choices
variable:
<div class="mx-4 my-4">
...
<div class="row">
@foreach (var choice in choices)
{
<div class="col-4 text-center border py-3">
@choice
</div>
}
</div>
</div>
Add Choice Selection Event
We have to add the CheckAnswer
method to handle the event when users make a choice. If the answer is correct, proceed to the next question.
@code {
...
void CheckAnswer(int choice)
{
if (choice == count)
{
NextQuestion();
}
}
}
We need to set the @onclick
event callback to call the CheckAnswer
method when clicked:
<div class="row">
@foreach (var choice in choices)
{
<div class="col-4 text-center border py-3" @onclick="(() => CheckAnswer(choice))">
@choice
</div>
}
</div>
Add Custom CSS and Animation
We will add custom CSS to make our monkeys and choices look bigger and some animations when the player selects the correct or wrong answer.
Custom CSS
Open the app.css file in the wwwroot/css folder, then replace its content with the code below:
.monkey {
font-size: 60px;
text-align: center;
}
.choice {
font-size: 60px;
cursor: pointer;
}
@keyframes shake {
0%, 100% {
transform: translateX(0);
}
10%, 30%, 50%, 70%, 90% {
transform: translateX(-10px);
}
20%, 40%, 60%, 80% {
transform: translateX(10px);
}
}
.shake {
animation: shake 0.82s cubic-bezier(.36, .07, .19, .97) both;
color: darkred;
}
@keyframes jump {
0%, 100% {
transform: translateY(0);
}
10%, 30%, 50%, 70%, 90% {
transform: translateY(-10px);
}
20%, 40%, 60%, 80% {
transform: translateY(0);
}
}
.jump {
animation: jump 1s ease;
color: green;
}
ChatGPT helped me create the shake and jump animation 😄
Now go back to the Home.razor file and add the .monkey
and .choice
CSS classes into the corresponding element:
<div style="height:330px">
<div class="row row-cols-3">
@for (int i = 0; i < count; i++)
{
<div class="col monkey">🙉</div>
}
</div>
</div>
<div class="row">
@foreach (var choice in choices)
{
<div class="col-4 text-center border py-3 choice" @onclick="(() => CheckAnswer(choice))">
@choice
</div>
}
</div>
Animation
The jump animation will be performed when the player chooses the correct answer. If the answer is incorrect, it will instead perform the shake animation.
To achieve that, we have to dynamically add the .shake
or .jump
classes depending on the answer. In JavaScript, we can easily accomplish that by using the combination of getElementById
and classList.add
method.
However, in Blazor, directly manipulating the DOM elements by ID is not the standard approach. Blazor encourages a component-based architecture, where we control the UI elements through component state and properties rather than direct DOM manipulation.
To enable the animation, we have to add two more variables, animationClass
and selectedChoice
:
@code {
...
string animationClass = "";
int? selectedChoice = null;
...
}
We also have to add TriggerAnimation
and GetAnimationClass
method:
@code {
...
async Task TriggerAnimation(int choice)
{
// prevent overlapping animation
if (selectedChoice != null) return;
selectedChoice = choice;
animationClass = selectedChoice == count ? "jump" : "shake";
await Task.Delay(1500);
animationClass = "";
selectedChoice = null;
}
string GetAnimationClass(int choice)
{
return selectedChoice == choice ? animationClass : "";
}
}
We need to modify the CheckAnswer
to call the TriggerAnimation
method and call the StateHasChanged
to notify the component that its state has changed:
async void CheckAnswer(int choice)
{
await TriggerAnimation(choice);
if (choice == count)
{
NextQuestion();
}
StateHasChanged();
}
To apply the animation class only to the selected choice, we use the GetAnimationClass(choice)
method within the @foreach
loop that renders the choices:
<div class="row">
@foreach (var choice in choices)
{
<div class="col-4 text-center border py-3 choice" @onclick="(() => CheckAnswer(choice))">
<div class="@GetAnimationClass(choice)">
@choice
</div>
</div>
}
</div>
We can also perform the animation for monkeys by adding the @animationClass
into the monkey emoji:
<div style="height:330px">
<div class="row row-cols-3">
@for (int i = 0; i < count; i++)
{
<div class="col monkey @animationClass">🙉</div>
}
</div>
</div>
The complete source code of this article can be found here:
Thank you for reading 👍