Nokia Snake with JavaScript + Canvas

photos/snake.jpg

Keeping with the theme of yesterday's post - "a stroll down memory lane" - I thought I'd re-create the Nokia Snake game (a distant relative of Nibbles) using JavaScript and the canvas element. When I was 16 two of my best friends had the same phone and I remember the three of us sitting around playing snake for hours. Good times!

Score: 0

The Code

Basically it's just a queue!

function Snake(canvas_id, score_id, width, height, scale) {
  var game = this;

  // state
  var game_on = false,
      level = 2,
      score,
      snake_color = "rgb(0, 255, 0)",
      apple_color = "rgb(255, 0, 0)";

  // display elements
  var canvas, score_disp;

  // game board width and scale (in pixels)
  width = width ? width : 20;
  height = height ? height : 16;
  scale = scale ? scale : 30;

  // directions
  var NORTH = 0, EAST = 1, SOUTH = 2, WEST = 3;

  // keys
  var UP = 38, DOWN = 40, LEFT = 37, RIGHT = 39;

  // initialize the snake in the top left
  var snake = {
    "x": 0,
    "y": 0,
    "body": [[0, 0]],
    "direction": EAST,
    "pending_direction": null
  }

  // the first apple goes in the middle
  var apple = {
    "x": (width / 2),
    "y": (height / 2)
  }

  // keyboard handler
  function handleKeys(e) {
    var char;
    var evt = (e) ? e : window.event;

    char = (evt.charCode) ?
      evt.charCode : evt.keyCode;
    if (char > 36 && char < 41) {
      handleChar(char);
      return false;
    };
    return true;
  }

  // character specific keyboard handling
  function handleChar(char) {
    if (!game_on)
      return;

    switch (char) {
      case UP:
        if (snake.direction != SOUTH)
          snake.pending_direction = NORTH;
        break;
      case DOWN:
        if (snake.direction != NORTH)
          snake.pending_direction = SOUTH;
        break;
      case LEFT:
        if (snake.direction != EAST)
          snake.pending_direction = WEST;
        break;
      case RIGHT:
        if (snake.direction != WEST)
          snake.pending_direction = EAST;
        break;
    }
  }

  function reset() {
    // reset all values
    snake.x = snake.y = 0;
    snake.body = [[0, 0]];
    snake.direction = EAST;
    apple.x = (width / 2);
    apple.y = (height / 2);
    score = 0;
  }

  // drawing routine
  function draw() {

    // if paused, keep checking every 1/10th second for unpause
    if (!game_on) {
      t = setTimeout(function() { draw(); }, 100);
      return;
    }

    // get reference to drawing area
    if (canvas.getContext){

      // create drawing context
      var ctx = canvas.getContext('2d');

      if (snake.pending_direction !== null) {
        snake.direction = snake.pending_direction;
        snake.pending_direction = null;
      }

      // move snake
      switch (snake.direction) {
        case NORTH:
          snake.y--;
          break;
        case EAST:
          snake.x++;
          break;
        case SOUTH:
          snake.y++;
          break;
        case WEST:
          snake.x--;
          break;
      }

      // fill game board black
      ctx.fillStyle = "#000";
      ctx.fillRect(0, 0, width * scale, height * scale);

      // push new position onto stack and pop last
      var old_pos = snake.body.pop();
      snake.body.unshift([snake.x, snake.y]);

      // test to see if snake is touching itself
      for (var i = 1; i < snake.body.length; i++)
        if (snake.body[i][0] == snake.x &&
            snake.body[i][1] == snake.y)
          reset();

      // test to see if out of bounds
      if (snake.x < 0 || snake.x >= width ||
          snake.y < 0 || snake.y >= height)
        reset();

      ctx.fillStyle = snake_color;

      //draw snake
      for (var i = 0; i < snake.body.length; i++)
        ctx.fillRect (snake.body[i][0] * scale, snake.body[i][1] * scale,
                      scale, scale);

      // test if snake eats apple and if so, reset apple and make snake grow
      if (snake.x == apple.x && snake.y == apple.y) {
        snake.body.push(old_pos);
        score += parseInt(level);
        var free_space = false;
        while (!free_space) {
          free_space = true;
          apple.x = (Math.floor(Math.random() * width));
          apple.y = (Math.floor(Math.random() * height));

          // make sure apple is draw in free space, not on top of snake
          for(i = 1;i < snake.body.length; i++)
            if(snake.body[i][0] == apple.x && snake.body[i][1] == apple.y)
              free_space = false;
        }
      }

      // draw apple
      ctx.fillStyle = apple_color;
      ctx.fillRect (apple.x * scale, apple.y * scale, scale, scale);

      // display score
      score_disp.innerHTML = score;
    }

    // calculate timeout using level
    t = setTimeout(function() { draw(); }, 120 - (20 * level));
  }

  this.toggle_play = function() {
    if(game_on == true) {
      game_on = false;
    } else {
      game_on = true;
    }
    return game_on;
  }

  this.change_level = function(new_level) {
    level = new_level;
  }

  this.initialize = function() {
    canvas = document.getElementById(canvas_id);
    score_disp = document.getElementById(score_id);

    // create handlers
    document.onkeydown = function(e) { return handleKeys(e) };
    document.onkeypress = function(e) { return handleKeys(e) };

    reset();
    draw();
  }
}

Comments (4)

Greg Bergé | nov 05 2010, at 04:58pm

Hello, great job your games are very impressive ! I work on a AS3 API in JS/canvas, may be you can help me in contributing to the code or in making some game with my API ? You can contact me via my website.

You can check out the actionJS project here : https://github.com/neoziro/actionJS

Charles | nov 04 2010, at 04:47pm

Dang, man, have you been looking at my plan for world domination?

Haiko Schol | nov 04 2010, at 01:21pm

Nice! Please add massive multiplayer capabilities, achievements, allow user to contribute levels with voting and integrate it with Facebook. Then I can remove this from my project ideas list. ;)

Ken Swift | nov 04 2010, at 12:17pm

Thats awsome man!


Commenting has been closed, but please feel free to contact me