How to recreate the classic video game ‘Snake’ using dizmo

Snake2

Snake consists of the following three components: the field, the snake and the direction.

The field

The field is a two dimensional array (18×18). A ‘1’ indicates a wall and ‘0’ indicates empty space. The actual field is only 16×16. You can extend the array, so that you can add obstacles.

field = [
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
    ];

The snake

The snake is also a two dimensional array (*x2), where the length is equal to the snake length and each element stores the x and y coordinates of the represented snake part.

snake = [[10,8],[9,8],[8,8],[8,9],[8,10],[8,11],[7,11],[6,11],[5,11],[4,11],[3,11],[2,11],[1,11],[0,11]];

The direction

To calculate the next snake position, we require the direction. The direction can be up, down, left or right. This is set with a key press on one of the corresponding arrow keys.

document.onkeydown = function(e) {
    switch (e.which) {  
        case 37: // left  
            snakeDirection = 'left';  
            break;  
        case 38: // up  
            snakeDirection = 'up';  
            break;  
        case 39: // right  
            snakeDirection = 'right';  
            break;  
        case 40: // down  
            snakeDirection = 'down';  
            break;  
        default:  
            return; // exit this handler for other keys  
    }
    e.preventDefault(); // prevent the default action (scroll / move   caret)  
};

The docked dizmo can write a new value into a corresponding tree node to trigger the callback function you provided with the subscription. For example the docked dizmo writes a new date into ‘direction/up’ and the snake will go up.

dizmo.onDock(function() {
    var dockedDizmos = dizmo.getDockedDizmos();
    for (var i = 0; i < dockedDizmos.length; i++) {
        dockedDizmos[i].publicStorage.subscribeToProperty('direction/up', function() {
            snakeDirection = 'up';
        });
        dockedDizmos[i].publicStorage.subscribeToProperty('direction/left', function() {
            snakeDirection = 'left';
        });
        dockedDizmos[i].publicStorage.subscribeToProperty('direction/right', function() {
            snakeDirection = 'right';
        });
        dockedDizmos[i].publicStorage.subscribeToProperty('direction/down', function() {
            snakeDirection = 'down';
        });
    }
});

The game

The basis of the game is the gameController. The gameController function runs as long as the variable gameover is not true.

if (gameover === true) {
    displayGameOver();
    return 0;
}

If gameover is true, the ‘game over’ display is shown and the game stops, otherwise the current snake head is taken from the array. The head is always the last array element and we can simply address it with snake.pop();.

The futureHeadPosition is calculated according to the direction you selected. To prevent the snake from going backwards, an if-condition is added. If your previous direction was left, the snake is not allowed to go right. If this is the case, the direction is overwritten with the previousDirection. The switch condition checks all four directions.

var currentHeadPosition = snake.pop();
    var futureHeadPosition = [0, 0];
    switch (snakeDirection) {
        case 'left':
            if (previousDirection == 'right') {
                snake.push(currentHeadPosition);
                futureHeadPosition = [currentHeadPosition[0], currentHeadPosition[1] + 1];
                snake.push(futureHeadPosition);
                break;
            }
            previousDirection = 'left';
            snake.push(currentHeadPosition);
            futureHeadPosition = [currentHeadPosition[0], currentHeadPosition[1] - 1];
            snake.push(futureHeadPosition);
            break;
            ...

After the futureHeadPosition is determined, it needs to be checked for an obstacle or food. If the snake hits a wall or itself, the game is over. If there is food, the tail is not cut. If there is nothing the tail is cut with snake.shift().

if (field[futureHeadPosition[0]][futureHeadPosition[1]] == 1) {
    gameover = true;
} else if (field[futureHeadPosition[0]][futureHeadPosition[1]] == 2) {
    foodPosition();
} else {
    var currentTailPosition = snake.shift();
    field[currentTailPosition[0]][currentTailPosition[1]] = 0;
}

Draw the snake the field and render the field.

for (var i = snake.length - 1; i >= 0; i--) {
    field[snake[i][0]][snake[i][1]] = 1;
}
drawPlayField();

After the set time of GAMESPEED of 160 milliseconds has passed, setTimeout(function() calls gameController().

    setTimeout(function() {
        gameController();
    }, GAMESPEED);

foodPosition

foodPosition() is a recursive function, which tries to set the food randomly at a position. If there is already something, the function is called again until an empty spot is found. This can take some time (especially, when the snake grows long) and most probably needs to be optimized in the next version.

function foodPosition() {
    var x = Math.floor(Math.random() * 16) + 1;
    var y = Math.floor(Math.random() * 16) + 1;
    if (field[x][y] > 0) {
        foodPosition();
    } else {
        field[x][y] = 2;
    }
}

Find the source code here: https://github.com/dizmo/snake or install the Snake dizmo directly from the dizmoStore.

Leave a Reply

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