Blazor Hang 'Um


 I found a cute hangman game sample project using javascript so I thought it would a be cool little Blazor application.  The UI was good, but there was a lot of interactivity with the application that would need to be rewritten in C#.

The flow of the application is the normal flow of a hangman.  You guess a letter of the word until you either run out of guess and hand the guy or you get the word.

In this post, I wanted to highlight the unique parts that I had to do to make if a Blazor application.

Steps of the project

1. Create a project
2. Port over the HTML/CSS
3. Get a list of words to use
4. Show the hangman frame
5. Show how many letters in the word
6. Allow the user to guess
7. Show the letter if correct
8. Show a piece of the hangman and show the wrong letter if incorrect
9. Check to see if out of guess, if so end game
10. Did they get the word, if so end game


Create a Project

You just need to create a normal Blazor project with no authentication.  I choose the server-side Blazor template because I find it easier to use but there is nothing that you required server-side of WASM client.

Since this application is only going to use the index page, I removed the counter and Weather forecast pages and services.

I remove the nav and changed the main layout.

Port over the UI

I found this project here.  It is from Traversy Media.  He has a great YouTube Channel and has several Udemy courses.  You should check them out.

I was able to copy the HTML and place it in the index.razor page.  We will only have 1 page for this application.   I replaced the CSS in the site.css file as well.  I did have to make a "main container" div, but more this a little later.

I like that they used SVG to draw the hangman and the frame.  That made the logic very straight forward.  If the user missed a word, just show a section with the SVG.


Get a list of words to use

I wanted a simple list of words to use but more than 10.  I did a simple google search for "Most common English nouns".  I found a list of 100.  I copied that list into a simple text file.

I could have made an API or a function to get a word, but with this project, I wanted to focus on just the interactivity of the user.

Show the hangman frame

The actual frame is in a div with the class of figure-container.  The trick on this one was not to use the actual hangman figure yet.  Since each part of the figure was blocked out as its own SVG, there was a class that enabled able to use to turn it on / off.  On startup, we set the class off.

There are several places where we are turning UI elements on or off.  I went with the normal method of doing this, setting the class as a variable, and changing the value.  When I have used this before it felt and worked fine.  Using that method here, I just did not feel right, not sure why.  I left it that way, but I plan on going back and refactoring it.

Show how many letters in the word

Normal hangman the users see a dash for each letter of the word they are trying to guess.  For example, if the word was hangman the UI would look like:

_ _ _ _ _ _ _ 

This gives the user a starting point.  It is also where we will be putting the letters when the user guesses correctly.

We will need to calculate the number of characters in the word we are using.  I am using a code-behind my page so I can separate the code from the UI.  I have a constructor method that is responsible for getting the setting the word.

It calls the word service that reads in our 100 words and randomly selects 1.  The page will set that as the word we are using.  We make a blank copy of the word to hold our guesses.

On the page, we loop through each letter of the guess and add the blank _ to the page.  This is done with a class:

              @foreach (char c in guess)
                    {
                        <span class="letter">
                            @c
                        </span>
                    }




Allow the user to guess

This was the most challenging part of the project.  We need to capture a keystroke from anywhere on the page.

I first used an input control but that was too limiting and looked bad.  I did some reach and it seems this is a browser issue, not a Blazor issue.  To capture a keystroke you have to have the focus on a container that is handling the keypress event.

Sounded simple enough, but not quite so.  I first had to add a container at the page level.  Since the real main container in a Blazor application is the "app" component in _Host.HTML then the container in the main layout, it became problematic to attach the keypress event to either of those.

I ended up creating an oversized container that held the game container.  I was then able to add the keypress event handler. 

<div @onkeydown="@KeyDown" tabindex="0" @ref="hangManDiv" class="main-container">

Then in the OnAfterRenderAsync event of the page, I invoke the JSInterop to set focus.

            if (firstRender)
            {
                await JSRuntime.InvokeVoidAsync("SetFocusToElement", hangManDiv);
            }

I just added the scrpit to the _Host.cshtml page:
        <script>
            window.SetFocusToElement = (element) => {
                element.focus();
            };
        </script>

This will allow us to capture the keystrokes when the browser has focus.

Show a piece of the hangman and show the wrong letter if incorrect

If the letter is wrong, we will:
    1. Check to see if this letter was already guessed.  
        a. If so, display the "You are ready entered this letter.
        b. To display the message we set the class value of that div.  The message its self is pretty cool, it pops up from the bottom of the page.
        c. This message is also on a timer
        d. Set the class of the next piece of the figure to revel
    2. Add the letter to the list of missed guesses and update the UI.
    3. Update the display with the value of guess

Show the letter if correct

Now we have a keypress to process, we have to check a couple of things.
        a. Is it a valid letter?
            We don't want any special characters or numbers
        b. Is it only 1 character?
            The Control, shift, and Alt keys add additional keys and we want to ignore those.
        c. Is the letter in the word?
            If it is we want to add it to the guess in the correct location

Check to see if we won or if we are out of guess, if so end game

If the guess value = the word we are using, display the message "Congratulations! You won! 😃"

If we hit the max guesses, display the "Unfortunately you lost. 😕" and show what the word was.

Then reset the game:
1. Set all classes back to default
2. Set guess count to 0
3. Get a new word
4. reset the focus back to the main container.  Since we used a pop up for the game over the message it takes focus away from our game container, so we have to reset the focus back.

Summary

This was a fun project that included some interesting challeges.  Handling the keystrokes and setting focus was the biggest part.  I did like how they used the 2 different popup messages, the normal popup, and the popup from the bottom.  I will be using those again.

From the Blazor side, it looked good to use the code behind format.  And I still want to figure out a better way of setting classed to turn elements on or off.  

Overall this was a fun project.

Comments

Popular posts from this blog

Yes, Blazor Server can scale!

Blazor new and improved Search Box

Blazor Wizard Step Component