# Strike 9 Game

At my second meet-up of this week, I ventured north to the east 20s for Building JavaScript Games. Our task was to build a seemingly simple game called Strike 9.

### How the Game is Played

At the beginning of a turn, the “computer” rolls two die. The player has squares numbered 1 through 9 to make the sum of the computer’s roll. The trick is that when the player uses a number it is removed from the board. So for example, if computer rolls a 9, the player can choose to remove any of the following combinations: [9], [1,8], [2,7], [1,2,6], [2,3,4], etc. If he or she uses, say, the 1 and the 8, on the next roll neither 1 or 8 will be available.

### Huh? / Live Demo

Yeah, it’s kind of hard to explain in words. Here’s a live demo (and my GitHub repo).

### How I Approached the Basic Gameplay

The meat of this project lives in `strike9-board.js`

. `onload`

we initialize some arrays and variables, then call `resetGame()`

, which draws our 9 HTML `canvas`

’s and rolls the 2 die for the first time.

From there we wait for the player to click one of the 9 canvases. When a canvas is clicked, we make sure it hasn’t been clicked yet in this game. Then we mark it as clicked in the `board_array`

and we remove that number from the `player_moves_remaining`

array. We also add the number to `player_total`

.

Eventually we have to figure out if the player won, lost, or can keep playing on that turn. Here’s a excerpt of that code:

```
// the player just made a sum...
// check if player has won
if (board_array.indexOf(0) == -1){
alert("Oh hey, You won!");
resetGame();
} else { //still playing
// re-roll dice.
computer_roll = rollTwoDie();
// with new dice roll, we can already figure out if the game is over
if (isGameOver(computer_roll, player_moves_remaining)){
alert("Computer's next roll is " + computer_roll + "...Game over :(");
resetGame();
} else { // if there exists a way to make the roll's sum...
sendMessage(randPraise() + "<br>New roll is " + computer_roll);
// reset for new roll, same game
possible_combinations = [];
player_total = 0;
}
}
```

By far the most interesting of these outcomes to check is if the player lost– which we do with the `isGameOver()`

function called above. I wanted the game to know if the player lost right away– in other words if the player had 1, 2, and 4 remaining and the computer’s new roll was 5, I wanted the game to tell the player they had lost.

This might sounds simple, but– at least the way I went about it– turned out to be pretty non-trivial.

### Coolest Challenge: When is the Game Over?

Ideally I wanted a function called `isGameOver`

that returns `true`

if the player can’t make the sum with the remaining numbers (like the case above), and `false`

if the player can make the sum.

My approach was to generate an array of all ways to make a sum (in the above hypothetical, all the ways to add numbers to make 5). So given, say, `6`

, `getAllPossAddends`

should return an array of arrays: `[[6], [1,5], [2,4], [1,2,3]]`

. Note that we don’t want `[3,3]`

because there’s only one 3 on the board, and we don’t want `[5,1]`

because we already have `[1,5]`

and order doesn’t matter here. And `6`

itself should be included because, in line with the rules of the game, if the computer rolls a 6 you can just use the 6 square.

Once we get this array of possible combinations to make a given sum, which I call `possible_combinations`

, we’ll just need to compare it to what numbers the player has left (`player_moves_remaining`

) to see if `isGameOver`

should be `true`

or `false`

. Cool? Sounds easy? OMG took me a whole day.

Let’s do this.

### Part 1: Generating possible_combinations

*Note: The section below has been edited and updated to reflect some significant refactoring I’ve performed.*

So the first thing I wanted to do was write the function that would take in a `sum`

, say `6`

and spit back `[[1,5], [2,4], [1,2,3], [6]]`

. I decided to call this function `getAllPossAddends`

(“addends”, according to Wikipedia, is the technical term for what you add together to get a sum).

After a few days of tinkering, I wrote a recursive function to accomplish this task.

```
var possible_combinations = []; // I made this global
function getAllPossAddends(sum, baggage){
// if baggage argument is undefined, this is our first run through
// so set baggage = []
// baggage is effectively an optional argument with a default value of [] if undefined in function call
if (typeof baggage == 'undefined'){
baggage = [];
possible_combinations = [];
possible_combinations.push([sum]); // can just play the number itself
}
for (var j=1; j < sum/2; j++){
// if j is not in baggage and sum-j is not in baggage
if (baggage.indexOf(j) == -1 && baggage.indexOf(sum-j) == -1){
// add j and sum-j back to baggage to make our new array we may add as a possible_combination
var new_array = baggage.concat(j, sum-j);
// if new_array is NOT a subArray of possible_combinations yet...
if (!isSubArray(new_array,possible_combinations)){
possible_combinations.push(new_array);
}
// prep new_baggage and send new sum and baggage back to top of the function
var new_baggage = baggage.concat(j);
getAllPossAddends(sum-j, new_baggage);
}
}
return possible_combinations;
}
```

Let’s walk through it as if we called it with `getAllPossAddends(10)`

, so `sum = 10`

and `baggage`

is `undefined`

.

Right off the bat we use an `if`

statement to check if `baggage`

is undefined. If it is, we know that this is the “first time” we’re running through `getAllPossAddends`

, and that we should set `baggage = []`

and reset `possible_combinations`

. This setting of `baggage`

to `[]`

will make a little more sense in a minute.

Now to the meat of the function. First we loop from 1 to `sum/2`

(5 in our case). We then do some checks against baggage, which we’ll learn about in a minute. Note: this loop doesn’t go up to 5 because we put `i < sum/2`

not `i <= sum/2`

, which is what we want– remember there aren’t 2 5s so we don’t want `[5,5]`

to count as a `possible_combination`

.

Eventually we make a new_array and set it equal to `baggage.concat(j, sum-j)`

. We don’t have any baggage yet, so for the first run through, `j==1`

and `sum-j==9`

. And that’s good, because [1,9] is a possible way of making 10.

That’s a start, but what about `[1,4,5]`

or (gulp) `[1,2,3,4]`

? This is the problem that the recursion handles:

```
// prep new_baggage and send new sum and baggage back to top of the function
var new_baggage = baggage.concat(j);
getAllPossAddends(sum-j, new_baggage);
```

Basically, we want to take that `9`

in `[1,9]`

and expand it out to `[[1,9], [1,2,7], [1, 3, 6], [1, 4, 5]]`

. We want to break that 9 down while keeping the 1 untouched (Notice how the `1`

is the first element of every “sub-array”). I decided to call the `1`

`baggage`

, because it’s like, just along for the ride. With the baggage taken care of, what we want to do to the 9 is very similar to what we set out to do the 10– namely find all the ways to sum to it. Sounds like a case for recursion!

We’re going to call `getAllPossAddends`

again, but first we have to set the `new_baggage`

. In our example, right after we add `[1,9]`

, 9 is the `sum-j`

we want to break down further, and j is `1`

, the baggage we want to pass along. We have to use `baggage.concat(j)`

in case we got any baggage at the beginning of the run-through of the function, which will be the case every time except the first run-through.

On the second run-through in our example, the getAllPossAddends call is: `getAllPossAddends(9, [1])`

. Now 9 is the new `sum`

and `[1]`

is the `baggage`

(since `baggage`

is defined, we do not execute that first `if`

statement).

So the array we prepare, `new_array`

, WOULD BE [1,1,8], but we have a check against duplicated numbers: `if (baggage.indexOf(j) == -1 && baggage.indexOf(sum-j) == -1)`

. `j`

, which is equal to `1`

, is in fact present in `baggage`

, so we’re not going to do anything in this `for`

iteration. (Note: This is a bit wasteful, but refactoring further may make this thing a little too confusing.)

So we try again. On the next iteration of the `for`

loop, `j`

will be equal to 2 and `sum-j`

== 7. This time we will make it through the `if`

checks. `baggage`

is still `[1]`

, so when we get to `baggage.concat(2, 7)`

it’s going to evaluate to `[1,2,7]`

. Then we’ll add it to `possible_combinations`

.

We’ll then call the function again which `sum-j`

equal to 7 and `baggage`

== `[1,2]`

. So the call will be `getAllPossAddends(7, [1,2])`

, and we’ll go break down that 7. And so on! Crazy, right?

### Part 2: Building My Own Array Tools

You may have noticed my use of a function called `isSubArray`

. It checks whether the array we’re trying to add to `possible_combinations`

has already been added (again, we don’t want duplicates).

Of course this function is not a standard JavaScript function, but one I wrote myself. I ended up writing 3 of these “array helper” functions in total.

```
// believe it or not, array.sort sorts numbers alphabetically, so we have to define our own sort for numbers
function sortArray(array){
return array.sort(function(a, b){return a-b});
}
// this guy takes an array of arrays and sorts all of its sub-arrays using the above function
function sortAllSubArrays(array){
for(var i = 0; i < array.length; i++){
array[i] = sortArray(array[i]);
}
return array;
}
// the workhorse function in the trio. It sorts both the sub-array and the array of arrays using their respective functions.
// then sees if any of the arrays in `array` match the given `subArray`
function isSubArray (subArray, array) {
subArray = sortArray(subArray); // need to sort both the subArray...
array = sortAllSubArrays(array); // and the sub-arrays in array in order to compare them more easily
for(var i = 0; i < array.length; i++) {
if(subArray.toString() === array[i].toString()){
return true;
}
}
return false;
}
```

### Part 3: Putting It All Together

Cool, so now we have an array, `possible_combinations`

, that’s full of all the ways our player could make a given sum (a given dice roll in our case). As I mentioned above, we also have an array of numbers that the player has left, called `player_moves_remaining`

. Now all we have to do is compare them to figure out if the game is over.

Again, I broke this into 2 functions, but most of the work happens in `playerHasAMove`

. `player_moves_left`

is `player_moves_remaining`

and `passing_moves`

is our mega-awesome array of arrays, `possible_combinations`

.

```
function playerHasAMove(player_moves_left, passing_moves){
for(var i = 0; i < passing_moves.length; i++) {
var matches = 0;
for(var j = 0; j < passing_moves[i].length; j++) {
if (player_moves_left.indexOf(passing_moves[i][j]) != -1){
// we foudn a match
matches = matches + 1;
}
}
if (matches == passing_moves[i].length){
return true;
}
}
return false;
}
function isGameOver(roll, player_moves_remaining){
var ways_to_fulfill_roll = getAllPossAddends(roll);
if (playerHasAMove(player_moves_remaining, ways_to_fulfill_roll)){
return false; // game is not over
} else {
return true; // game is over
}
}
```

`playerHasAMove`

first loops over the `passing_moves`

and yields the sub-arrays like `[1,9]`

and `[1,3,6]`

. It then loops through these sub-arrays and looks for `matches`

in `player_moves_left`

using the `indexOf`

method. If, for a given sub-array, all of its element are contained in `player_moves_left`

, we return true. If we get to the end of `passing_moves`

and the player is out of luck, we return false. This easily plugs into `isGameOver`

, where we return the final true/false verdict.

Again, here’s where `isGameOver`

is called:

```
// player just made a sum
if (board_array.indexOf(0) == -1){ // if board_array has no 0s, i.e. is all 1s
alert("Oh hey, You won!");
resetGame();
} else { //still playing
// re-roll dice.
computer_roll = rollTwoDie();
// with new dice roll, we can already figure out if the game is over
if (isGameOver(computer_roll, player_moves_remaining)){
alert("Computer's next roll is " + computer_roll + "...Game over :(");
resetGame();
} else { // if there exists a way to make the roll's sum...
sendMessage(randPraise() + "<br>New roll is " + computer_roll);
// reset for new roll, same game
possible_combinations = [];
player_total = 0;
}
}
```

Think that’s about it!

*Side Note*: I hope to write more programs that have a `randPraise`

function:

```
function randPraise(){
var praise = ["Awesome!", "Good job!", "Knew you could do it!", "Sweet!", "You got this!", "Again! Again!", "Keep it up!", "Keep going!", "Easy, right?"];
var rand = Math.floor(Math.random() * (praise.length));
return praise[rand];
}
```