- Home
- .NET tutorials
- Create a Roulette wheel. Game development using Blazor WebAssembly
Create a Roulette wheel. Game development using Blazor WebAssembly
Published: Saturday 19 June 2021
Another live coding challenge happened on YouTube. This time, we had an hour to build a Roulette wheel in Blazor WebAssembly.
The classes
Before we can get on with building the Roulette wheel in Blazor, we need to go ahead and create some classes.
The wheel number
There are 37 numbers on a European Roulette wheel. The numbers are either red or black. That is apart from 0, where green is the colour.
WheelNumberColourEnum enum which would represent the three colours.
// WheelNumberColourEnum.cs
public enum WheelNumberColourEnum
{
Red,
Black,
Green
}
From there, a WheelNumber class was created. The number and colour are featured properties on this class. In addition, a boolean selected property was included. This is so we know where the ball has landed on the roulette wheel.
// WheelNumber.cs
public class WheelNumber
{
public int Number { get; }
public WheelNumberColourEnum Colour { get; }
public bool Selected { get; set; }
public WheelNumber(int number, WheelNumberColourEnum colour)
{
Number = number;
Colour = colour;
}
}
The wheel
Our final class is where a lot of the business logic will occur. The Wheel class has a number of properties, events and methods to make the game work.
WheelNumber array which stores all 37 numbers of the roulette wheel. In addition, we have a number of other properties, such as where the ball is sitting on the roulette wheel, if there is a winning number and a winning colour.
// Wheel.cs
public class Wheel
{
public readonly WheelNumber[] WheelNumbers =
{
new WheelNumber(0, WheelNumberColourEnum.Green),
new WheelNumber(32, WheelNumberColourEnum.Red),
new WheelNumber(15, WheelNumberColourEnum.Black),
new WheelNumber(19, WheelNumberColourEnum.Red),
new WheelNumber(4, WheelNumberColourEnum.Black),
new WheelNumber(21, WheelNumberColourEnum.Red),
new WheelNumber(2, WheelNumberColourEnum.Black),
new WheelNumber(25, WheelNumberColourEnum.Red),
new WheelNumber(17, WheelNumberColourEnum.Black),
new WheelNumber(34, WheelNumberColourEnum.Red),
new WheelNumber(6, WheelNumberColourEnum.Black),
new WheelNumber(27, WheelNumberColourEnum.Red),
new WheelNumber(13, WheelNumberColourEnum.Black),
new WheelNumber(36, WheelNumberColourEnum.Red),
new WheelNumber(11, WheelNumberColourEnum.Black),
new WheelNumber(30, WheelNumberColourEnum.Red),
new WheelNumber(8, WheelNumberColourEnum.Black),
new WheelNumber(23, WheelNumberColourEnum.Red),
new WheelNumber(10, WheelNumberColourEnum.Black),
new WheelNumber(5, WheelNumberColourEnum.Red),
new WheelNumber(24, WheelNumberColourEnum.Black),
new WheelNumber(16, WheelNumberColourEnum.Red),
new WheelNumber(33, WheelNumberColourEnum.Black),
new WheelNumber(1, WheelNumberColourEnum.Red),
new WheelNumber(20, WheelNumberColourEnum.Black),
new WheelNumber(14, WheelNumberColourEnum.Red),
new WheelNumber(31, WheelNumberColourEnum.Black),
new WheelNumber(9, WheelNumberColourEnum.Red),
new WheelNumber(22, WheelNumberColourEnum.Black),
new WheelNumber(18, WheelNumberColourEnum.Red),
new WheelNumber(29, WheelNumberColourEnum.Black),
new WheelNumber(7, WheelNumberColourEnum.Red),
new WheelNumber(28, WheelNumberColourEnum.Black),
new WheelNumber(12, WheelNumberColourEnum.Red),
new WheelNumber(35, WheelNumberColourEnum.Black),
new WheelNumber(3, WheelNumberColourEnum.Red),
new WheelNumber(26, WheelNumberColourEnum.Black)
};
public int CurrentNumberIndex { get; protected set; }
public WheelNumber WinningNumber { get; protected set; }
public WheelNumberColourEnum? Colour { get; set; }
public bool Running { get; protected set; }
public event Func<Task> OnStartAsync;
public event Func<Task> OnFinishAsync;
public event Func<Task> OnNumberChangedAsync;
public async Task RollTheBallAsync()
{
WinningNumber = null;
Running = true;
if (OnStartAsync != null)
{
await OnStartAsync.Invoke();
}
var running = true;
var random = new Random();
var stopwatch = new Stopwatch();
stopwatch.Start();
var lengthOfSpin = new TimeSpan(0, 0, random.Next(5, 15));
random = new Random();
var speed = new TimeSpan(random.Next(30000, 40000));
while (running)
{
CurrentNumberIndex += 1;
if (CurrentNumberIndex > WheelNumbers.GetUpperBound(0))
{
CurrentNumberIndex = 0;
}
if (OnNumberChangedAsync != null)
{
await OnNumberChangedAsync.Invoke();
}
await Task.Delay(speed);
if (stopwatch.Elapsed.TotalSeconds > lengthOfSpin.TotalSeconds - 5)
{
random = new Random();
speed = new TimeSpan(random.Next(100000, 200000));
}
if (stopwatch.Elapsed.TotalSeconds > lengthOfSpin.TotalSeconds - 2)
{
random = new Random();
speed = new TimeSpan(random.Next(500000, 700000));
}
if (stopwatch.Elapsed.TotalSeconds > lengthOfSpin.TotalSeconds)
{
running = false;
}
}
WinningNumber = WheelNumbers[CurrentNumberIndex];
Running = false;
if (OnFinishAsync != null)
{
await OnFinishAsync.Invoke();
}
}
}
Creating Razor components in Blazor
Time to create the Razor components in Blazor.
The wheel number component
A WheelNumberRazorComponent was created, and this represents the number on the wheel.
<li> tag. If the ball has landed on that number, there are separate HTML elements to represent the ball.
WheelNumber instance as a parameter. This is so we know the number, colour and whether the ball has landed on that number.
<!-- WheelNumberComponent.razor -->
@using RoundTheCode.Roulette.Models
@if (WheelNumber != null)
{
<li class="@WheelNumber.Colour.ToString().ToLower()"><span class="number">@WheelNumber.Number</span>
@if (WheelNumber.Selected)
{
<span class="ball-container"><span class="ball"></span></span>
}
</li>
}
@code {
[Parameter]
public WheelNumber WheelNumber { get; set; }
}
/* WheelNumberComponent.razor.css */
li {
position: absolute;
height: 700px;
width: 59.435536px;
left: 320px;
border-top: 350px #000 solid;
border-left: 30.3px transparent solid;
border-right: 30.3px transparent solid;
box-sizing: border-box;
margin-top: -16px;
}
li.red {
border-top-color: #ff0000;
}
li.green {
border-top-color: #009933;
}
li span.number {
z-index: 5;
width: 59.435536px;
height: 40px;
display: inline-block;
position: absolute;
color: #fff;
top: -350px;
margin-left: -29.717768px;
text-align: center;
font-size: 30px;
font-weight: bold;
}
li span.ball-container {
z-index: 6;
width: 59.435536px;
height: 20px;
top: -250px;
display: block;
position: absolute;
margin-left: -29.717768px;
text-align: center;
}
li span.ball-container span.ball {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: #fff;
display: inline-block;
}
li:nth-child(0) {
transform: rotateZ(0deg);
}
li:nth-child(1) {
transform: rotateZ(9.72972973deg);
}
li:nth-child(2) {
transform: rotateZ(19.45945946deg);
}
li:nth-child(3) {
transform: rotateZ(29.18918919deg);
}
li:nth-child(4) {
transform: rotateZ(38.91891892deg);
}
li:nth-child(5) {
transform: rotateZ(48.64864865deg);
}
li:nth-child(6) {
transform: rotateZ(58.37837838deg);
}
li:nth-child(7) {
transform: rotateZ(68.10810811deg);
}
li:nth-child(8) {
transform: rotateZ(77.83783784deg);
}
li:nth-child(9) {
transform: rotateZ(87.56756757deg);
}
li:nth-child(10) {
transform: rotateZ(97.2972973deg);
}
li:nth-child(11) {
transform: rotateZ(107.02702703deg);
}
li:nth-child(12) {
transform: rotateZ(116.75675676deg);
}
li:nth-child(13) {
transform: rotateZ(126.48648649deg);
}
li:nth-child(14) {
transform: rotateZ(136.21621622deg);
}
li:nth-child(15) {
transform: rotateZ(145.94594595deg);
}
li:nth-child(16) {
transform: rotateZ(155.67567568deg);
}
li:nth-child(17) {
transform: rotateZ(165.40540541deg);
}
li:nth-child(18) {
transform: rotateZ(175.13513514deg);
}
li:nth-child(19) {
transform: rotateZ(184.86486486deg);
}
li:nth-child(20) {
transform: rotateZ(194.59459459deg);
}
li:nth-child(21) {
transform: rotateZ(204.32432432deg);
}
li:nth-child(22) {
transform: rotateZ(214.05405405deg);
}
li:nth-child(23) {
transform: rotateZ(223.78378378deg);
}
li:nth-child(24) {
transform: rotateZ(233.51351351deg);
}
li:nth-child(25) {
transform: rotateZ(243.24324324deg);
}
li:nth-child(26) {
transform: rotateZ(252.97297297deg);
}
li:nth-child(27) {
transform: rotateZ(262.7027027deg);
}
li:nth-child(28) {
transform: rotateZ(272.43243243deg);
}
li:nth-child(29) {
transform: rotateZ(282.16216216deg);
}
li:nth-child(30) {
transform: rotateZ(291.89189189deg);
}
li:nth-child(31) {
transform: rotateZ(301.62162162deg);
}
li:nth-child(32) {
transform: rotateZ(311.35135135deg);
}
li:nth-child(33) {
transform: rotateZ(321.08108108deg);
}
li:nth-child(34) {
transform: rotateZ(330.81081081deg);
}
li:nth-child(35) {
transform: rotateZ(340.54054054deg);
}
li:nth-child(36) {
transform: rotateZ(350.27027027deg);
}
The wheel component
The WheelComponent is were the magic happens. We create a Wheel instance, and use our WheelNumberRazorComponent to display each of the 37 numbers.
RollTheBallAsync method from our Wheel instance. This instance calls the OnStartAsync, OnNumberChangedAsync, and OnFinishAsync events from our Wheel instance.
WheelRazorComponent hooks into these events, refreshes the razor component and changes where the ball is currently residing.
<!-- WheelComponent.razor -->
@using RoundTheCode.Roulette.Models
@page "/"
@if (Wheel != null)
{
<div class="container">
<div class="roulette">
<div class="numbers">
<ul>
@foreach (var wheelNumber in Wheel.WheelNumbers)
{
<WheelNumberComponent WheelNumber="@wheelNumber"></WheelNumberComponent>
}
</ul>
</div>
<div class="ball-circle">
</div>
<div class="inner-circle">
</div>
</div>
@if (Wheel.Colour != null)
{
<p>You have picked @Wheel.Colour.Value</p>
}
@if (Wheel.WinningNumber != null)
{
<p>The winning number is @Wheel.WinningNumber.Number @Wheel.WinningNumber.Colour. @(Wheel.WinningNumber.Colour == Wheel.Colour ? "You win" : "You lose")</p>
}
@if (!Wheel.Running)
{
<p>
<button type="submit" @onclick="@(async(e) => await OnRollAsync(e, WheelNumberColourEnum.Red))">Red</button>
<button type="submit" @onclick="@(async(e) => await OnRollAsync(e, WheelNumberColourEnum.Black))">Black</button>
</p>
}
</div>
}
@code {
public Wheel Wheel { get; set; }
protected override async Task OnInitializedAsync()
{
Wheel = new Wheel();
Wheel.OnStartAsync += async () =>
{
StateHasChanged();
await Task.CompletedTask;
};
Wheel.OnFinishAsync += async () =>
{
StateHasChanged();
await Task.CompletedTask;
};
Wheel.OnNumberChangedAsync += async () =>
{
for (var index = 0; index <= Wheel.WheelNumbers.GetUpperBound(0); index ++)
{
if (index == Wheel.CurrentNumberIndex)
{
Wheel.WheelNumbers[index].Selected = true;
}
else
{
Wheel.WheelNumbers[index].Selected = false;
}
}
StateHasChanged();
await Task.CompletedTask;
};
await base.OnInitializedAsync();
}
public async Task OnRollAsync(MouseEventArgs mouseEventArgs, WheelNumberColourEnum colour)
{
Wheel.Colour = colour;
await Wheel.RollTheBallAsync();
}
}
/* WheelComponent.razor.css */
@keyframes rotate {
0% {
transform: rotateZ(360deg);
}
100% {
transform: rotateZ(0deg);
}
}
.container {
width: 700px;
margin-left: auto;
margin-right: auto;
}
.roulette {
border: 10px #D4AF37 solid;
border-radius: 50%;
width: 700px;
height: 700px;
animation: rotate 15s infinite linear
}
.numbers {
z-index: 1;
}
.ball-circle {
z-index: 2;
width: 600px;
height: 600px;
border-radius: 50%;
border: 10px #D4AF37 solid;
position: absolute;
margin: 40px;
}
.inner-circle {
z-index: 3;
width: 400px;
height: 400px;
border-radius: 50%;
border: 10px #D4AF37 solid;
position: absolute;
background-color: #aaa;
margin: 140px;
}
.numbers ul {
list-style: none;
padding-left: 0;
position: absolute;
}
p {
font-size: 26px;
}
The final result
The final result looks like a Roulette wheel. The user can select whether the ball will land on red or black.
A roulette wheel written in C# for game development in Blazor WebAssembly
Related tutorials
How to create the Tic-Tac-Toe game in Blazor WebAssembly in one hour!
Creating Tic-Tac-Toe (Noughts & Crosses) using the Blazor WebAssembly framework. Example coded using C# & Razor components.