Xmas eXcavation – a game for Christmas

game screenshot

Xmas eXcavation

A merry Christmas to all, and to all a good 2018.

In celebration, and to give everyone a fun, indoor activity away from the cold, I present Xmas eXcavation (click to play). There’s also a quick Javascript tutorial below!

The game will be familiar to any Minesweeper players, but with one key difference: you’re trying to locate and uncover presents hidden under the snow, while avoiding the dirty lumps of coal. Double click (or double tap) to uncover a patch of snow. Single click to mark a suspected piece of coal. Good luck!

For anyone who cares, the game is written in straightforward Javascript. One of the few tricky bits was to override the default behaviour for double-tapping on a mobile, which usually zooms in.

If double-tapping causes your phone to zoom, you might have to check your settings as some browsers allow you to choose to override any attempts to override the default behaviour. Very irritating for programmers!

Every time you start a new game (or refresh the page) the game grid is drawn from scratch with a random assortment of presents and coal. The script creates a table one cell at a time and decides whether each cell should contain an item.

The default game difficulty is 13. This means that, for every cell, there is a 13% chance that the cell will contain an item, and a 87% chance the cell will be empty. It’s random, so sometimes you’ll end up with a slightly easier game, sometimes a little harder, but on average it will be a reasonable challenge for new players.

for (var r=0;r<rows;r++){
  //create an empty row
  var tr = grid.appendChild(document.createElement('tr'));
  for (var c=0;c<cols;c++){
    //create an empty cell
    var cell = tr.appendChild(document.createElement('td'));
    //add item to the cell
    answerGrid[r][c] = randomFill(); //returns a random number based on game difficulty. 0 is empty; -1 is coal; -2 and -3 are presents
    //keep a count of how many presents and pieces of coal there are
    if(answerGrid[r][c]==-1){coalSquares++;}
    if(answerGrid[r][c]==-2){p2Squares++;}
    if(answerGrid[r][c]==-3){p3Squares++;}
  }
}

There’s a hard mode for the more experienced, where the difficulty is ramped up to 20. If you want to go even higher, say to level 25, add “?=25” after the URL and reload the page. Or follow this link: https://dreamsofgerontius.com/scripts/xmasexcavation.html?=25.

Once the presents and coal are added, the script goes through the whole board again, counting up the objects in order to store the clue numbers. The whole answer grid is stored as a 2D array and a test array containing the relative co-ordinates of the eight surrounding squares is used to quickly check each of them.

//loop through rows
for (var r=0;r<rows;r++){
  //loop through columns
  for (var c=0;c<cols;c++){
    //only update value if the square is empty
    if (answerGrid[r][c] == 0){
      //check each of the 8 surrounding squares
      for (var i=0;i<8;i++){ //check the square is within the bounds of the gamegrid 
        if (r + testArray[i][0] >= 0 && r + testArray[i][0] < gridHeight && c + testArray[i][1] >= 0 && c + testArray[i][1] < gridWidth){
          //check the contents of the square and add its value to the clue number if it's an item (by subtracting the negative item value)
          if (answerGrid[r + testArray[i][0]][c + testArray[i][1]] < 0){
            answerGrid[r][c] -= answerGrid[r + testArray[i][0]][c + testArray[i][1]];
          }
        }
      }
    }
  }
}
animation

Animated reveal

When a square is uncovered, the contents are revealed and the score updated if the player uncovers an item. If a square is blank, that means there can’t be any items in any of the surrounding squares, so the game might as well reveal them all. And if any of those squares are blank, it reveals them too, and so on.

There’s an animation routine to reveal the whole area one square at a time, every 5 milliseconds. This was the other tricky bit of the code to write, making use of an asycnhronous function and a “promise” to make the game stop while running the animation.

function sleep(ms){
  return new Promise(resolve => setTimeout(resolve, ms));
}
async function emptySquareRevealed(r,c){
  gameRunning=false; //"pause" the game by ignoring clicks until this routine is finished
  //create an array to use as a stack to iterate through to find and clear the entire area
  var stack = [];
  stack.push(c); //push the column of the first revealed square onto the stack
  stack.push(r); //push the row of the first revealed square onto the stack
  while (stack.length > 0) //so long as there are entries in the stack{
    //cycle through the 8 surrounding squares
    for (var i = 0; i < 8; i++){
      var tmpr = r + testArray[i][0]; 
      var tmpc = c + testArray[i][1]; 
      //check the adjacent square is within the bounds of the gamegrid 
      if (tmpr >= 0 && tmpr < gridHeight && tmpc >= 0 && tmpc < gridWidth){
        //if square filled with snow, empty it
        if (snowGrid[tmpr][tmpc]==1){
          clearEmptySquare(tmpr,tmpc); //updates the table to remove snow and draw whatever is hidden underneath
          await sleep(5); //sleep for 5ms
          //if the revealed square is also empty, add it to stack of squares that need processing
            if (answerGrid[tmpr][tmpc] == 0){
            stack.push(tmpc);
            stack.push(tmpr);
          }
        }
      }
    }
    //get the top two items off the stack for the next iteration of the while loop
    //if the original r,c are removed from the stack, the loop ends - that square has already been revealed
    r = stack.pop();
    c = stack.pop();
  } //end while-loop
  gameRunning=true; //restart the game
}

Once all the presents are found, the game finishes. My other half, Nicola over at NESchof.com, wrote the victory routine, to flash between green and red coloured grass tiles, after I got frustrated with Javascript doing some weird things with arrays. The function still bears her name!

function victoryAnim_Nicola(){
  var row = Math.floor(Math.random()*gridHeight);
  var column = Math.floor(Math.random()*gridWidth);
  var grass_id = 'r'+row+'c'+column;
  var grassclass = document.getElementById(grass_id).className;
  if(grassclass == "cleared"){
    document.getElementById(grass_id).className = "clearedred";
  }
  else if(grassclass == "clearedred"){
    document.getElementById(grass_id).className = "cleared";
  }
}

I instead spent my time writing the music for achieving a perfect score. It’s a modified version of my God Rest Ye Merry Gentlemen arrangement I wrote a couple of years back for my YouTube Christmas Round Robin.

Enjoy the game!

Leave a Reply

Your email address will not be published. Required fields are marked *