Click here to Skip to main content
Click here to Skip to main content
Go to top

Doodle Riddle - A JavaScript Windows 8 Game

, 22 Oct 2012
Rate this:
Please Sign up or sign in to vote.
A riddle game for windows 8 using JavaScript and HTML5.

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 of you fail retry this level.  

Game development

 No matter where you develop a game, it consists always of the same stages. Is has a 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 needs 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 is 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:

  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 informations about the maxMoves and level dimensions.

 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 use some sprites. We will define them direct at the default.html page. To make sure the images will not show let's surround them with a <div style="display:none;" />.

    <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 in 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 call back functions, for updating the game sate and for finishing a level with a parameter if the level was finished successfully.

    // 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.  Therefor we need some function to convert from game world to screen world.

    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. Therefor 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

    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 dome a new mode, we need to update out status. This will be done by calling the updateStatus callback.

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.

   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 funktion 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 leaf 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.

    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.

   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:

   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:

  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= " />

(function(){
  
  // variables and functions of the game as discribed 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 div's with the menus content in on the main page default.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 div's (your prepaired menus).

         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).

        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 sinve 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

     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 your 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)

Share

About the Author

Matthias.Fischer
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
Follow on   Twitter   Google+

Comments and Discussions

 
QuestionPlatform PinmemberFriedhelm Schuetz1-Aug-13 3:05 
AnswerRe: Platform PinmemberMatthias.Fischer1-Aug-13 3:20 

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

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

| Advertise | Privacy | Mobile
Web04 | 2.8.140926.1 | Last Updated 22 Oct 2012
Article Copyright 2012 by Matthias.Fischer
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid