Click here to Skip to main content
15,884,017 members
Articles / Web Development / XHTML

Doodle Riddle - A JavaScript Windows 8 Game

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
22 Oct 2012CPOL6 min read 16.3K   2   2
A riddle game for Windows 8 using JavaScript and HTML5

This article is an entry in our AppInnovation Contest. Articles in this sub-section are not required to be full articles so care should be taken when voting.

Introduction

Doodle Riddle is an HTML5/JavaScript entry in the AppInnovation contest. It is a Windows 8 style game under development. This article will give an overview about the application and a small introduction into game development with JavaScript for Windows 8. It uses the accelerometer, the inclinometer, touch input as well as the keyboard to control the game.

doodle riddle

Game Description

A short introduction to the game and terms. The player (a hedgehog) has to escape the maze by moving to the goal (a flag). One mode starts either with pressing an arrow-key, tilt the computer or slide on the screen in the desired direction. The player will continue moving until reaching a border or wall. If there is no border, the player will re-appear on the opposite site of the playfield. The modes are limited so hurry up to reach the goal and continue with the next level or if you fail, retry this level.

Game Development

No matter where you develop a game, it consists always of the same stages. It has an initialization, a main loop and sometimes a cleanup stage.

doodle riddle

In the initialization state, all resources will be loaded and prepared for later use, the play field will be prepared and all elements like the player will be set up. The main loop consists of two methods, update and draw. This loop will repeat as fast as possible (at least as often as your desired frames per second). Update will calculate the changes in the game world while draw will display the changed game world to the gamer. All inputs from the user while updating or drawing need to be kept until the next update to observe them in the next update.

How to Realize the Main Loop in JavaScript ?

In general, you need a timer with a timing like 1/60 sec. for 60 frames per sec. After the timer has elapsed, it will callback and needs to refresh. Fortunately, JavaScript provides such a timer with a constant timing for us with the window.requestAnimationFrame function. So we start with a skeleton of our game consisting of init, update, draw and the main loop like this:

JavaScript
function init () { };
function update() { };
function draw () { }

function gameLoop() {
  update();
  draw();
  window.requestAnimationFrame(gameLoop);
};

How to Represent a Level ?

The easiest way to represent a level is serialize the playfield to some kind of string. Let's define a P for Player, W for Wall, G for Goal and B for Border. Then you may define your levels an array of strings with some additional information about the maxMoves and level dimensions.

JavaScript
var levels = [{
     field:
       "BBBBBBBB BBBBBBBB"+
       "B               B"+
       "B               B"+
       "B        G      B"+
       "B               B"+
       "B               B"+
       "   P      W      "+
       "B               B"+
       "B               B"+
       "B               B"+
       "B               B"+
       "BBBBBBBB BBBBBBBB"
     ,
     rows: 12,
     cols: 17,
     maxMoves: 2,
 }, { ... } , { ... } ] ;

Preload Some Resources

Before we can load a level, we need to talk about pre-loading our resources. This game mainly uses some sprites. We will define them directly at the default.html page. To make sure the images will not show, let's surround them with a <div style="display:none;" />.

HTML
<div id="imgPreload" style="display: none">
  <img id="imgBorder1" src="http://www.codeproject.com/images/tile01.png"/>
  <img id="imgBorder2" src="http://www.codeproject.com/images/tile02.png"/>
  <img id="imgBorder3" src="http://www.codeproject.com/images/tile03.png"/>
  <img id="imgBorder4" src="http://www.codeproject.com/images/tile04.png"/>
  <img id="imgWall" src="http://www.codeproject.com/images/tile05.png"/>
  <img id="imgPlayer" src="http://www.codeproject.com/images/hedgehog.png"/>
  <img id="imgGoal" src="http://www.codeproject.com/images/goal.png"/>
</div>

The game will live in a canvas. There, we are able to draw at a position and desired size. This canvas needs to be placed on the main page default.html as well. Properly as the first child in the client area. <canvas id="gameCanvas"> You should never see this Wink | <img src= " /> </canvas>. You might want to set up the position on the screen with some CSS.

Now we can define the init-function where we will fill out internal variables with the values from out page. Furthermore, we will define two callback functions, for updating the game state and for finishing a level with a parameter if the level was finished successfully.

JavaScript
  // some golbal size definitions
  var width = 850, height = 600;
  var scaleX = 50, scaleY = 50;

   // our canvas to draw on
   var canvas;
   var ctx;
   // our preloaded resources
   var spriteBorder;
   var spritePlayer;
   var spriteGoal;
   var spriteWall;
   // same callback functions
  var updateStatus;
  var levelFinished;
  // keeps the current level
  var currentLevel;
  // the initialize function (loading the resources)
  function init(statusCallback, levelCallback) {
    canvas = document.getElementById('gameCanvas');
    ctx = canvas.getContext('2d');
    canvas.width = width;
    canvas.height = height;

    updateStatus = statusCallback;
    levelFinished = levelCallback;
    spriteBorder = document.getElementById("imgBorder1");
    spritePlayer = document.getElementById("imgPlayer");
    spriteGoal = document.getElementById("imgGoal");
    spriteWall = document.getElementById("imgWall");
}

Game World vs. Screen World

The game world consists of 17x12 pads. The screen world has 850x600 pixel. The game world is represented in a one-dimensional array. The screen world has two dimensions. Therefore, we need some function to convert from game world to screen world.

JavaScript
  function pos2Point(pos) {
  return {
     x: (pos % currentLevel.cols) * scaleX,
     y: ((pos - (pos % currentLevel.cols)) / currentLevel.cols) * scaleY };
}

Load a Level

Now we are ready to load a level. Therefore, we need some additional definitions. A default velocity (3 seems to be good), a direction object which converts a direction to a 2D velocity and a variable which tells us later if the game is already running or not. Furthermore, we need some variables for more game elements.

JavaScript
  var velocity = 3;
  var epsilon = 1;
  var dir = Object.freeze({ "up":{ x: 0, y: -velocity },"down": { x: 0, y: velocity },
                            "left":{ x: -velocity, y: 0 }, "right":{ x: velocity, y: 0 },
                            "none": { x: 0, y: 0 } });

  // game elements
  var borders;
  var walls;
  var goal;
  var player;
  var gameRunning;
  var movesLeft;

  // loads and starts a level
  function loadLevel(nr) {
  if (window.Puzzle.levels.length <= nr) return false;
  currentLevel = window.Puzzle.levels[nr];
  currentLevelNr = nr;

  movesLeft = currentLevel.maxMoves;
  borders = [];
  walls = [];
  goal = null;
  player = null;

  ctx.clearRect(0, 0, width, height);
  for (var i = 0; i < currentLevel.field.length; i++) {
    switch (currentLevel.field[i]) {
      case "P":
        player = { position: pos2Point(i), sprite: spritePlayer, direction: dir.none };
        break;
      case "B":
        var b = { position: pos2Point(i), sprite: spriteBorder };
        borders.push(b);
        break;
      case "W":
        var w = { position: pos2Point(i), sprite: spriteWall };
        walls.push(w);
        break;
      case "G":
        goal = { position: pos2Point(i), sprite: spriteGoal };
        break;
    }
  }
  updateStatus(currentLevelNr, movesLeft);
  gameRunning = true;
  gameLoop();
  return true;
}

Move the Player

To move the player, we define a function move which takes a direction object. While the player is moving, we will not accept any direction changes. If the gamer has done a new mode, we need to update our status. This will be done by calling the updateStatus callback.

JavaScript
function move(dir) {
 if (player.direction == dir.none) {
   movesLeft -= 1;
   updateStatus(currentLevelNr, movesLeft);
   player.direction = dir;
 }  

The Update Function

If the player stops at a wall or at a border, we need to ensure that we are in the grid as well as there are enough moves left to continue. If not, we need to end the game either with or without success.

JavaScript
 function stopPlayer() {
  // stop the player and ensure we are snapped to the playfield grid
  player.direction = dir.none;
  player.position.x = Math.round(player.position.x / scaleX) * scaleX;
  player.position.y = Math.round(player.position.y / scaleY) * scaleY;
}

function endGame(success) {
  // stop the game and return the result
  gameRunning = false;
  ctx.clearRect(0, 0, width, height);
  levelFinished(success);
}

The update function itself is the heart of the game Wink | <img src= " />. Here, we will check where our player is located and what to do. If we leave the field, just jump to the opposite border. If we are next to a wall or border, stop the current move, if we are in the goal, exit with success. And last but not least, if there are no more moves left, exit with no success.

JavaScript
  function update() {
  // calculate new player pos
  var playerNewPos = { x: player.position.x += player.direction.x,
                       y: player.position.y += player.direction.y };

  // check the border
  if (playerNewPos.x < 0) playerNewPos.x = width - scaleX;
  if (playerNewPos.y < 0) playerNewPos.y = height - scaleY;
  if (playerNewPos.x > width - scaleX) playerNewPos.x = 0;
  if (playerNewPos.y > height - scaleY) playerNewPos.y = 0;

  //check borders & walls
  borders.forEach(function(b) {
    if ((Math.abs(playerNewPos.x - b.position.x) < scaleX &&
         Math.abs(playerNewPos.y - b.position.y) <= epsilon) ||
      (Math.abs(playerNewPos.y - b.position.y) < scaleY &&
       Math.abs(playerNewPos.x - b.position.x) <= epsilon)) {
      stopPlayer();
      if (movesLeft <= 0) { endGame(false); }
    }
  });

  walls.forEach(function(w) {
    if ((Math.abs(playerNewPos.x - w.position.x) < scaleX &&
         Math.abs(playerNewPos.y - w.position.y) <= epsilon) ||
      (Math.abs(playerNewPos.y - w.position.y) < scaleY &&
       Math.abs(playerNewPos.x - w.position.x) <= epsilon)) {
      stopPlayer();
      if (movesLeft <= 0) { endGame(false); }
    }
  });

  // check for goal
  if ((Math.abs(playerNewPos.x - goal.position.x) < epsilon &&
       Math.abs(playerNewPos.y - goal.position.y) <= epsilon) ||
      (Math.abs(playerNewPos.y - goal.position.y) < epsilon &&
       Math.abs(playerNewPos.x - goal.position.x) <= epsilon)) {
    stopPlayer();
    endGame(true);
  }
  // accept the move ?
  if (player.direction != dirEnum.none) {
    player.position = playerNewPos;
  }
}

The Draw Function

The draw function will clear the canvas and draw all game object at their positions.

JavaScript
 function draw() {
  // draw the playfield
  ctx.clearRect(0, 0, width, height);
  borders.forEach(function (b) {
    ctx.drawImage(b.sprite, b.position.x, b.position.y, scaleX, scaleY);
  });
  walls.forEach(function (w) {
    ctx.drawImage(w.sprite, w.position.x, w.position.y, scaleX, scaleY);
  });
  ctx.drawImage(goal.sprite, goal.position.x, goal.position.y, scaleX, scaleY);
  ctx.drawImage(player.sprite, player.position.x, player.position.y, scaleX, scaleY);
}

The final game loop looks like this, that we can stop it after the level is finished:

JavaScript
 function gameLoop() {
  update();
  draw();
  if (gameRunning) window.requestAnimationFrame(gameLoop);
};

Stick Things Together

To enable restarting and continue with the next level, we will need two helper functions:

JavaScript
function runNextLevel() {
  return loadLevel(currentLevelNr + 1);
}

function retryLevel() {
  loadLevel(currentLevelNr);
  return currentLevelNr;
}

Put all variables and function in a module to keep the main namespace clean Wink | <img src= " />.

JavaScript
(function(){
  
  // variables and functions of the game as described above  
  window.Puzzle.direction = dir;
  window.Puzzle.init = init;
  window.Puzzle.hideMenu = hideMenu;
  window.Puzzle.loadLevel = loadLevel;
  window.Puzzle.move = move;
  window.Puzzle.runNextLevel = runNextLevel;
  window.Puzzle.retryLevel = retryLevel;

})()

The Menus

The game should have some menus displaying the state of the game. Let's define some additional divs with the menus content on the main page default.html.

HTML
<div id="mainMenu">
    <h1>doodle riddle</h1>
    <h3>for windows 8</h3>
    <h3>...by <a href="http://twitter.com/dotnetbird/" target="_blank">Matthias Fischer</a></h3>

    <a class="button" id="btnStart">Start new game</a>

    <div id="info" class="info">
      <!- Instruction how to play here -->
    </div>
  </div>

  <div id="gameOverMenu">
    <div class="bigMsg">The game is over !</div>
    <a class="button" id="btnGameOver">Main menu</a>
  </div>

  <div id="nextLevelMenu">
    <div class="bigMsg">Level Solved</div>
    <a class="button" id="btnContinue">Continue</a>
    <a class="button" id="btnMainMenu">Main menu</a>
  </div>

Depending on the game state, we will show either the main, the nextlevel or the gameOver-menu.

After starting the application, we need to initialize our game. This will be done with the initialize function. Depending on your framework, you will need different code to enable buttons and to show divs (your prepared menus).

JavaScript
window.Puzzle.init(
 function (level,moves) {
   // update your divs with the level and with the moves left info
 },
 function (success) {
   if (success) {
   // show the nextLevelMenu
   // and enable the continue button
   } else {
   // show the game over menu and enable the mainMenuButton
   }
 });

Add some listener to the keys to tell the game the next move(s).

JavaScript
document.body.addEventListener('keyup', function (e) {
  if (e.keyCode === WinJS.Utilities.Key.leftArrow) {
     window.Puzzle.move(window.Puzzle.direction.left);
  } else if (e.keyCode === WinJS.Utilities.Key.rightArrow) {
     window.Puzzle.move(window.Puzzle.direction.right);
  } else if (e.keyCode === WinJS.Utilities.Key.upArrow) {
     window.Puzzle.move(window.Puzzle.direction.up);
  } else if (e.keyCode === WinJS.Utilities.Key.downArrow) {
     window.Puzzle.move(window.Puzzle.direction.down);
  }
}, false);

How the Detect if the Computer is Tilt?

Therefore, we will use the inclinometer sensor. This sensor will tell if there are changes in the angel. For our game, we need the pitch and the roll value. If the pitch has been increased, move up, if it has been decreased, move down. The same with the roll value if it has been increased, move left, else move right. If there is no change since the last reading, do nothing. Please note that we need a threshold of about 5 degrees (you may need some fine tuning here). The timing should be about 500ms.

doodle riddle

JavaScript
var lastPitch;
var lastRoll;
var inclinometer = Windows.Devices.Sensors.Inclinometer.getDefault();
if (inclinometer) {
  inclinometer.reportInterval = 500; //wait 0.5sek
  inclinometer.addEventListener("readingchanged", function (e) {
    var reading = e.reading;
    var pitch = reading.pitchDegrees.toFixed(2);
    var roll = reading.rollDegrees.toFixed(2);

    var pitchDist = lastPitch - pitch;
    var rollDist = lastRoll - roll;
    if (Math.abs(pitchDist) > 5) {
      window.Puzzle.move((pitchDist > 0)
          ? window.Puzzle.direction.up : window.Puzzle.direction.down);
    } else if (Math.abs(rollDist) > 5) {
      window.Puzzle.move((rollDist > 0)
          ? window.Puzzle.direction.left : window.Puzzle.direction.right);
    }

    lastPitch = pitch;
    lastRoll = roll;
  });
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL). You are free to use this code in your own projects as long as you tell me by email if you are doing so and as long as you are not publishing a clone of my game Wink | <img src= " />

History

  • 2012.10.22 - First cut

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) MF IT Consult
Germany Germany
My name is Matthias Fischer. I’m Nokia Developer Certified Trainer, a .NET consultant and a textbook writer living in Rathenow, Germany. For nearly 10 years I’m co-organizer of the .NET user group in Berlin-Brandenburg. I’m an experienced software developer, architect and speaker for mobile and web solutions since 2001. In the last years I have focused the following technologies and frameworks: Windows Phone, WIndows 8, WPF, WCF, ASP.NET, ASP.NET MVC, ADO.NET, jQuery

Comments and Discussions

 
QuestionPlatform Pin
Friedhelm Schuetz1-Aug-13 3:05
Friedhelm Schuetz1-Aug-13 3:05 
AnswerRe: Platform Pin
Matthias.Fischer1-Aug-13 3:20
Matthias.Fischer1-Aug-13 3:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.