Let's Make Dice Wars, Part 2

Previously, we have created a basic HTML page, with a simple dice rolling logic, and simple UI, with Bootstrap and jQuery, so we have the beginnings of a web-based version of Dice Wars. 

In this part, we will complete the "game", where you roll the dice for two players, determine who won, adjust scores accordingly, and declare winner or loser after X points. There will also need to be a "reset" button so we can start again. 

You will learn in this segment:
  • Very simple Bootstrap grid layout, centering, and so on
  • Simple DOM manipulation with jQuery on multiple DOM elements
  • How to write JavaScript function with parameters so it can be reused
  • How to write make one function call another function
  • Create a properly working, if simple, game

We will set the starting points to 5, and first player to hit 0 is the loser, and the other is the winner. Keep in mind later, we may want this to be variable so we can setup variants of the game. 

Setup Two Players

Right now, let's setup two different die roll areas, side by side, one for player A, and the other for player B. We are also going to change the text a little bit. We'll center the title, the button, and change the layout to look like this: 

Bootstrap really helps here as the changes are pretty minimal. Here's the new form:

<div class="container-fluid bg-light py-2">
        <h1 class="text-center">Dice Wars</h1>
    </div>
    <div class="container-fluid bg-light py-2">
        <P class="text-center"><button id="btnRollDice" type="button" class="btn btn-primary">War!</button></P>
        <div class="row">
            <div class="col">
                <P>
                    <B>Player A</B> score = <span id="scoreB"></span>
                </P>
                <P id="rollResultsA" class="d-md-flex p-2 text-white bg-dark">
                </P>
            </div>
            <div class="col">
                <P>
                    <B>Player B</B> score = <span id="scoreB"></span>
                </P>
                <P id="rollResultsB" class="d-md-flex p-2 text-white bg-dark">
                </P>
            </div>
        </div>
    </div>


Note how I added  class="text-center" to the title and the button wrapper that centered them. 

I changed the text on the result field to white on black background to add more contrast. 

Also see how I created "row" and "col" to make the side-by-side grid look. I made SPAN tag for scores with ID so I can access them later. And I renamed rollResults into A and B as I need to target them specifically. 

However, this would break funcBtnRoll3Dice() as the result field is no longer there. The simplest way is to write two of them, one for A, and one for B, by copying and editing each slightly. Yes, that's the simple way. But it's also duplicating code. What if we pass in a parameter, so we can choose which to target? 

The obvious parameter is the results field. It really is just a string. 

Now funcBtnRoll3Dice() changes to funcBtnRoll3Dice(rollresultsID)

Now the function looks like this: 

function funcBtnRoll3Dice(rollResultsID) {
    //roll 3 dice
    var dice1, dice2, dice3, tstr;
    dice1 = rollDice();
    dice2 = rollDice();
    dice3 = rollDice();
    // stick results into rollResults paragraph
    tstr = "You rolled " + dice1 + ", " + dice2 + ", and " + dice3;
    $("#" + rollResultsID).html(tstr);
    console.log(tstr + " written to " + rollResultsID)
}

See how the $ line changed? It now uses the parameters passed in. I also changed the console.log to show what was passed in. 

If we run this, we have two sets of dice rolls, displaying two separate results, and we can see the debug messages in the console. It works!


Okay, we got this far. How about keeping score? This is where we need to create a "reset" button that will reset the rollResults to nothing, and reset each player's score to 5. We agreed that was the starting value... for now. 

Once we got this button working, we can then work on two functions... a) updateScore which updates the respective scores based on the dice rolls, and b) checkForVictory where we have to declare the winner or keep playing. 

Let's do the reset button first. The only thing the button really needed to do is to set both rollResults to blank. So it's a two-liner. 

The button itself: 

   <button id="btnReset" type="button" class="btn btn-danger">Reset</button>

btn-danger means a red button. I know, the color seems backwards, we can change it later. 

The function: 

function funcBtnReset() {
       $("#rollResultsA").html("");
       $("#rollResultsB").html("");
       $("#scoreA").html("5");
       $("#scoreB").html("5");
}

That's simple enough. We do need to add a click handler just below the existing one. Click on btnReset calls funcBtnReset.

$("#btnReset").click(function() {
       funcBtnReset();
}

Reload the website, and you can see this works. Throw the dice once, both results get updated. Click on reset, the fields go empty. Simple enough. 

Now let write updateScore(), and we can take a similar approach, where we pass in the field to update, so we don't repeat the code. 

And this is where we noticed that we didn't actually leave the two separate die rolls in an accessible place so they can be read by a function. Which means we really should RETURN the result from the function. 

Let us modify funcBtnRoll3Dice again, this time, returning the sum of the rolls, in addition to displaying the roll results. After console.log, add this line

return dice1+dice2+dice3;

We need to save the results, so we need to modify the click handler, and pass the results to updateScore()

$("#btnRollDice").click(function() {
   var scA, scB;
   scA = funcBtnRoll3Dice("rollResultsA");
   scB = funcBtnRoll3Dice("rollResultsB");
   updateScore(scA,scB)
   ...

So what would be in updateScore? We have basically 3 scenarios: A wins, B wins, or Draw. 

In case of draw, nothing else happens. 

In case of A wins, A gains a point, B loses a point. 

In case of B wins, B gains a point, A loses a point

So the code would look like:

function updateScore(doA, doB) {
    if (doA == doB) {
        console.log("Draw")
    } else {
        if (doA > doB) {
            console.log("A won") // inc A 
            tnum = parseInt($("#scoreA").html());
            tnum++;
            console.log("A=" + tnum)
            $("#scoreA").html(String(tnum))
            // dec B 
            tnum = parseInt($("#scoreB").html());
            tnum--;
            console.log("B=" + tnum)
            $("#scoreB").html(String(tnum))
        } else {
            console.log("B won")
            // inc B 
            tnum = parseInt($("#scoreB").html());
            tnum++
            console.log("B=" + tnum)
            $("#scoreB").html(String(tnum))
            // dec A 
            tnum = parseInt($("#scoreA").html());
            tnum--
            console.log("A=" + tnum)
            $("#scoreA").html(String(tnum))
        }
    }
}

One thing to point out... the what the html() function returns is actually a string. What's why we have to parseInt() it to get an actual number. Then we have to use String() to turn it back into a String to update the display. 

Now this will update the score, but it doesn't stop when you reach endgame, where one reached 10 and the other got a big fat 0. So we check either for 10 (or 0). If we got it, the game is over. 

But what do we do when the game's over?  For now, let's pop up a JavaScript alert, congratulate the winner, then reset the game to go again. We need to check both A and B. So the code looks like this:

function checkForVictory() {
    if ($("#scoreA").html() == "10") {
         alert("A has won the war!\n\n\nOK to reset")
         funcBtnReset();
    }
    if ($("#scoreB").html() == "10") {
       alert("B has won the war!\n\n\nOK to reset")
         funcBtnReset();
    }
}

But where do we call checkForVictory()? Logically, it should follow directly after a score update, which means it should go inside updateScore(), in the last few braces. 

            ...

               $("#scoreA").html(String(tnum))
           }
           checkForVictory()
       }
   }

Now the game is working.  You can find the working version in the archive at DiceWars2.html

Part 3 will be about polishing the UI issues and add some extra features, like a play log, display the dice thrown rather than use a number, a better winner display, change some colors around, and so on. 

 

Comments

Popular posts from this blog

Yet another take on recursion

Problem Solving for Programmers: a Neglected Topic?

How to Solve a Problem, with Examples