Course 08 · Blazor · Course wrap-up
The final lesson. A fast victory lap through everything you've learned this course, two quick new tricks — drag & drop and sound — and then a brand-new game that puts it all together.
Today's goal
Take a victory lap through the whole course, learn two new tricks — drag & drop and sound — and use them with everything you already know to build a colour-matching game.
By the end of today you'll have:
if/else and Random.Looking back
Thirteen weeks ago, a "program" was a mystery. Hands up — who remembers doing each of these?
Recap
Shout out the answers — quick questions spanning the whole course.
= vs == in C# — which one compares?@bind) do that just showing @variable doesn't?new Random().Next(1, 11) produce?EventCallback, and why did <MyButton> expose one?Fast-fire · together
Read the code and predict the answer before we reveal it.
After pressing the button, what can picked be?
<MyButton OnClick="Pick">Pick a colour</MyButton>
<p>@picked</p>
@code {
string[] colours = { "Red", "Blue", "Green", "Amber" };
string picked = "";
void Pick()
{
picked = colours[new Random().Next(colours.Length)];
}
}
One of "Red", "Blue", "Green" or "Amber", picked at random. Next(colours.Length) is Next(4) → a number 0, 1, 2 or 3 (the upper bound is excluded), which are exactly the valid positions in the array. This is the trick behind today's game: a random index into a list of colours.
Fast-fire · together
Read this carefully and shout out anything that's wrong. It only uses things we've already covered.
<input @bind="answer" />
<MyButton OnClick="Check">Check</MyButton>
<p>@result</p>
@code {
string answer;
int secret = 7;
string result = "";
void Check()
{
if (answer == secret)
{
result = "Correct!"
}
}
}
string answer; is the wrong type. We compare it against secret, which is an int, so answer must be an int too — you can't compare text to a number. It should be int answer;.result = "Correct!" is missing its semicolon — every statement ends with ;.Fast-fire · together
Here's another. This one picks a random colour from a list — shout out anything that looks wrong.
<p>@message</p>
<MyButton OnClick="NewColour">New colour</MyButton>
@code {
string[] colours = { "Red", "Blue", "Green", "Amber" };
string message = "";
void NewColour()
{
int i = new Random().Next(1, 4);
message = "I picked " + colours(i);
}
}
new Random().Next(1, 4) can only give 1, 2 or 3 — never 0. So it can never pick colours[0] ("Red"), and never covers all four. To choose any of the four, use Next(0, 4) (or Next(colours.Length)) — remember the upper number is excluded.colours(i) uses round brackets, but you read an item from an array with square brackets: colours[i].The whole course on one page
Almost everything you built this term has the same shape: some state in @code, the user does something (an event), you decide with if/else, and the screen updates automatically.
<input @bind="answer" /> <!-- binding: the box ↔ a variable -->
<MyButton OnClick="Check">Go</MyButton> <!-- your component + an event -->
<p>@message</p> <!-- show state on screen -->
@code {
int secret = new Random().Next(1, 11); // random
int answer;
string message = "";
void Check() // event handler
{
if (answer == secret) // decide
{
message = "Correct!";
}
else
{
message = "Try again!";
}
}
}
New trick · 1 of 2 · Build
Two ingredients. You make something draggable, and you make a drop zone that accepts it. Each zone must do two things: allow the drop with @ondragover:preventDefault="true", and react to it with @ondrop. What happens on a drop is just an if/else — exactly what you already know. The four zones are the same recipe repeated once per colour.
ColourMatch.razor, and replace its contents with the code below.@page "/colour-match"
<h1>Colour Match</h1>
<p>Drag the card onto the zone with the same name.</p>
<div class="zones">
<div class="zone" style="background:#E5484D"
@ondragover:preventDefault="true" @ondrop='() => Drop("Red")'>Red</div>
<div class="zone" style="background:#2563EB"
@ondragover:preventDefault="true" @ondrop='() => Drop("Blue")'>Blue</div>
<div class="zone" style="background:#22A06B"
@ondragover:preventDefault="true" @ondrop='() => Drop("Green")'>Green</div>
<div class="zone" style="background:#F0A202; color:#18122B"
@ondragover:preventDefault="true" @ondrop='() => Drop("Amber")'>Amber</div>
</div>
<div class="card" draggable="true">@cardColour</div>
<p>@message</p>
@code {
string[] colours = { "Red", "Blue", "Green", "Amber" };
string cardColour = "Red";
string message = "";
void Drop(string zoneColour)
{
if (zoneColour == cardColour)
{
message = "🎉 Correct!";
cardColour = colours[new Random().Next(colours.Length)]; // deal a new card
}
else
{
message = "Try again!";
}
}
}
The @page "/colour-match" line gives the page its own address — we'll link to it in a moment. Notice the handler quotes: the attribute uses single quotes (@ondrop='…') so the "Red" inside can use double quotes.
@ondragover:preventDefault="true" the drop simply never happens — the page just bounces the card back. (Drag & drop is a mouse gesture, so this game is best on a laptop, not a touch screen.)New trick · 1 of 2 · Style
Right now those <div>s are just text — you can't see a zone to aim at, or a card to grab. Like MyButton back in lesson 11, a component carries its own scoped CSS in a matching .razor.css file, so these styles only affect this page.
ColourMatch.razor.css in the same folder as ColourMatch.razor (right-click the Pages folder → Add → New Item → Style Sheet). Blazor links it to the page automatically by matching the name..zones {
display: flex;
gap: 10px;
margin: 16px 0;
}
.zone {
flex: 1;
height: 64px;
border-radius: 12px;
border: 3px dashed rgba(255, 255, 255, .6);
color: #fff;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
}
.card {
width: 120px;
height: 64px;
border-radius: 12px;
background: #fff;
border: 2px solid #ECE7F2;
box-shadow: 0 6px 16px rgba(24, 18, 43, .2);
font-weight: 700;
cursor: grab; /* the "you can drag me" hand */
user-select: none;
display: flex;
align-items: center;
justify-content: center;
}
.card:active { cursor: grabbing; }
The zones get their colour from the style="background:…" you wrote in the markup; this CSS gives them shape and the dashed "drop here" look. cursor: grab is the small touch that tells the player the card can be picked up.
Build · Make it reachable
Your page lives at the address /colour-match — but nothing points to it yet, so the only way to reach it is to type the address by hand. Real apps give every page a link in the menu. In a Blazor project that menu lives in one shared file, NavMenu.razor, and each page you want in the sidebar gets its own <NavLink>.
Shared/NavMenu.razor and add the block below next to the menu items already there (Home, Counter, Weather — and your Guess the Number link from last time).<div class="nav-item px-3">
<NavLink class="nav-link" href="colour-match">
Colour Match
</NavLink>
</div>
The href="colour-match" here matches the @page "/colour-match" on your page — that's how Blazor connects a menu link to a page: by its route. (The menu drops the leading slash; same address.)
New trick · 2 of 2
To play a sound, show an <audio> element with autoplay. The trick to making it play again on the next try is @key: change the key and Blazor rebuilds the element from scratch, so it plays once more.
Add them to your project — step by step:
wwwroot folder in Solution Explorer (the file tree, usually on the right). wwwroot is where a Blazor app keeps files the browser can load — images, sounds, and so on.wwwroot → Add → New Folder, and name the new folder sounds.sounds folder. (Or right-click the sounds folder → Add → Existing Item… and pick them.)Now your app can reach them at sounds/correct.wav and sounds/wrong.wav. Wire them up like this:
@if (sound is not null)
{
<audio autoplay src="@sound" @key="soundKey"></audio>
}
@code {
string? sound;
int soundKey;
void Play(string file) // e.g. Play("sounds/correct.wav")
{
sound = file;
soundKey++; // re-key so the same sound replays
}
}
Call Play("sounds/correct.wav") when the colours match, and Play("sounds/wrong.wav") when they don't.
<audio> + @key idea underneath.Your turn
Here's the finished game. Drag the card onto the matching colour: a correct drop cheers and deals a new card; a wrong one buzzes and lets you retry. Build your streak!
Now make it your own — pick your level:
Change the four colours and their names (the Colours list). Try pastels, or your favourite team's colours.
Reword the congratulations and try-again messages, and pick your own emojis.
Add your own correct.mp3 / wrong.mp3 in wwwroot/sounds and play them with the <audio> + @key trick above.
Remember the highest streak reached and show “Best: N” next to the current streak.
Add a fifth colour and a fifth zone. Notice how little code changes when the colours live in a list.
Add a 30-second countdown: how many can you match before time runs out? (You'll need a timer — ask, and we'll point you at System.Timers.)
Where to go next
No homework this time — the course is complete. 🎉 But the fun part is that you can keep going. A few good next adventures:
Show a whole list of things on screen with @foreach — high scores, a to-do list, a gallery.
Fetch real data from a free web API (a joke, the weather, a fact) and show it in your app.
Put one of your apps online so friends and family can use it from a link.