2
\$\begingroup\$

I was sort of bored and wondered how that Youtuber (Max Wihlborg) implemented his snake-game, so I went on and took a look at his code, and then I and edited his code. I made some minor changes, but the biggest change was that I felt some sort of redundancy where he had to update 2 objects for basically the same purpose (grid and snake), I believe that it is more efficient to update the snake object and then inside of the snake object's update function, make the changes to the grid, instead of calling both functions using the same arguments.

Here is a short example of my changes:

var foo = {
foo_arr: [],
foo_bar: function(foobar) {
foo_arr.push(foobar);
}
}; 

var bar = {
bar_arr: [],
bar_foo: function(barfoo) {
bar_arr.push(barfoo);
}
};

function baz() {
bar.bar_foo(10);
// foo.foo_bar has to be called everytime bar.bar_foo is called.
foo.foo_bar(10);
}

I changed the code so it looks like this:

var foo = {
foo_arr: [],
foo_bar: function(foobar) {
foo_arr.push(foobar);
}
}; 

var bar = {
bar_arr: [],
bar_foo: function(barfoo) {
bar_arr.push(barfoo);
/* now a call to foo.foo_bar does not have to accompany every call to the 
 current function because we made that call to foo.foo_bar part of this function */
foo.foo_bar(barfoo);
}
};

function baz() {
bar.bar_foo(10);
}

Now in the code foo is the equivalent of the grid object and bar is the equivalent of the snake object and the case I just described was occurring multiple times inside of the body of the update function, where every call to snake.insert and snake.remove had to be accompanied by a call to grid.set, in order to synchronize the snake and the grid.

His version: https://github.com/maxwihlborg/youtube-tutorials/blob/master/snake/index.html

Here is my version, where I made a few edits: https://gist.github.com/doubleOrt/c9f83eded71463d3268ce69ae81c379d

So, I moved the grid.set calls inside the bodies of both snake.insert and snake.remove. Before my edits, each call to these 2 methods had to be accompanied by a grid.set call (this would be happening inside of the update function), but this isn't the case now that I have edited the code.

Was the code better off originally, or now?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Simple Snake Game</title>

    <!-- Basic styling, centering of the canvas. -->
    <style>
    canvas {
        display: block;
        position: absolute;
        border: 1px solid #000;
        margin: auto;
        top: 0;
        bottom: 0;
        right: 0;
        left: 0;
    }
    </style>
</head>
<body>

<script>
const 
COLS = 20,
ROWS = 20,
CELL_SIZE = 30,
CANVAS_WIDTH = CELL_SIZE * COLS,
CANVAS_HEIGHT = CELL_SIZE * ROWS,
EMPTY = 0,
SNAKE = 1,
FRUIT = 2,
LEFT  = 0,
UP    = 1,
RIGHT = 2,
DOWN  = 3,
KEY_LEFT  = 37,
KEY_UP    = 38,
KEY_RIGHT = 39,
KEY_DOWN  = 40;
var 
canvas,   /* HTMLCanvas */
ctx,      /* CanvasRenderingContext2d */
current_direction, /* Object, used for keyboard inputs */
frames,   /* number, used for animation */
score;    /* number, keep track of the player score */
/**
 * Grid datastructor, usefull in games where the game world is
 * confined in absolute sized chunks of data or information.
 * 
 * @type {Object}
 */
var grid = {
    width: null,  /* number, the number of columns */
    height: null, /* number, the number of rows */
    _grid: null,  /* Array<any>, data representation */
    /**
     * Initiate and fill a c x r grid with the value of d
     * @param  {any}    d default value to fill with
     * @param  {number} c number of columns
     * @param  {number} r number of rows
     */
    init: function(value, columns, rows) {
        this.width = columns;
        this.height = rows;
        this._grid = [];

        for (var x=0; x < columns; x++) {
            this._grid.push([]);
            for (var y=0; y < rows; y++) {
                this._grid[x].push(value);
            }
        }
    },
    /**
     * Set the value of the grid cell at (x, y)
     * 
     * @param {any}    val what to set
     * @param {number} x   the x-coordinate
     * @param {number} y   the y-coordinate
     */
    set: function(val, x, y) {
        this._grid[x][y] = val;
    },
    /**
     * Get the value of the cell at (x, y)
     * 
     * @param  {number} x the x-coordinate
     * @param  {number} y the y-coordinate
     * @return {any}   the value at the cell
     */
    get: function(x, y) {
        return this._grid[x][y];
    },
    setFood: function() {
    var empty = [];
    // iterate through the grid and find all empty cells
    for (var x=0; x < grid.width; x++) {
        for (var y=0; y < grid.height; y++) {
            if (grid.get(x, y) === EMPTY) {
                empty.push({x:x, y:y});
            }
        }
    }
    // chooses a random cell
    var randpos = empty[Math.round(Math.random()*(empty.length - 1))];
    grid.set(FRUIT, randpos.x, randpos.y);
}
}
/**
 * The snake, works as a queue (FIFO, first in first out) of data
 * with all the current positions in the grid with the snake id
 * 
 * @type {Object}
 */
var snake = {
    direction: null, /* number, the direction */
    get last() {
        return this._queue[0];
    },       /* Object, pointer to the last element in
                        the queue */
    _queue: null,    /* Array<number>, data representation*/
    /**
     * Clears the queue and sets the start position and direction
     * 
     * @param  {number} d start direction
     * @param  {number} x start x-coordinate
     * @param  {number} y start y-coordinate
     */
    init: function(d, x, y) {
        this.direction = d;
        this._queue = [];
        this.insert(x, y);
        grid.set(SNAKE, x, y);
    },
    /**
     * Adds an element to the queue
     * 
     * @param  {number} x x-coordinate
     * @param  {number} y y-coordinate
     */
    insert: function(x, y) {
        // unshift prepends an element to an array
        this._queue.unshift({x:x, y:y});
        // add a snake id at the new position and append it to 
        grid.set(SNAKE, x, y);
    },

    /**
     * Removes and returns the first element in the queue.
     * 
     * @return {Object} the first element
     */
    remove: function() {
        // pop returns the last element of an array
        var tail = this._queue.pop();
        grid.set(EMPTY, tail.x, tail.y);
    }

};
/**
 * Starts the game
 */
function main() {
    // create and initiate the canvas element
    canvas = document.createElement("canvas");
    canvas.width = COLS * CELL_SIZE;
    canvas.height = ROWS * CELL_SIZE;
    ctx = canvas.getContext("2d");
    // add the canvas element to the body of the document
    document.body.appendChild(canvas);
    // sets an base font for bigger score display
    ctx.font = "16px Helvetica";
    frames = 0;
    keystate = {};
    // keeps track of the keybourd input
    document.addEventListener("keydown", function(evt) {
        keystate[evt.keyCode] = true;
    });
    document.addEventListener("keyup", function(evt) {
        delete keystate[evt.keyCode];
    });
    // intatiate game objects and starts the game loop
    init();
    loop();
}
/**
 * Resets and inits game objects
 */
function init() {
    score = 0;
    grid.init(EMPTY, COLS, ROWS);
    var sp = {x:Math.floor(COLS/2), y:ROWS-1};
    snake.init(UP, sp.x, sp.y);
    grid.setFood();
}
/**
 * The game loop function, used for game updates and rendering
 */
function loop() {
    update();
    draw();
    // When ready to redraw the canvas call the loop function
    // first. Runs about 60 frames a second
    window.requestAnimationFrame(loop, canvas);
}
/**
 * Updates the game logic
 */
function update() {
    frames++;
    // changing direction of the snake depending on which keys
    // that are pressed
    if (keystate[KEY_LEFT] && snake.direction !== RIGHT) {
        snake.direction = LEFT;
    }
    if (keystate[KEY_UP] && snake.direction !== DOWN) {
        snake.direction = UP;
    }
    if (keystate[KEY_RIGHT] && snake.direction !== LEFT) {
        snake.direction = RIGHT;
    }
    if (keystate[KEY_DOWN] && snake.direction !== UP) {
        snake.direction = DOWN;
    }
    // each five frames update the game state.
    if (frames%5 === 0) {
        // pop the last element from the snake queue i.e. the
        // head
        var nx = snake.last.x;
        var ny = snake.last.y;
        // updates the position depending on the snake direction
        switch (snake.direction) {
            case LEFT:
                nx--;
                break;
            case UP:
                ny--;
                break;
            case RIGHT:
                nx++;
                break;
            case DOWN:
                ny++;
                break;
        }
        // checks all gameover conditions
        if (0 > nx || nx > grid.width-1  ||
            0 > ny || ny > grid.height-1 ||
            grid.get(nx, ny) === SNAKE
        ) {
            return init();
        }
        // check wheter the new position are on the fruit item
        if (grid.get(nx, ny) === FRUIT) {
            // increment the score and sets a new fruit position
            score++;
            grid.setFood();
        } else {
            // take out the first item from the snake queue i.e
            // the tail and remove id from grid
            snake.remove();
        }

        // the snake queue
        snake.insert(nx, ny);
    }
}
/**
 * Render the grid to the canvas.
 */
function draw() {
    // iterate through the grid and draw all cells
    for (var x=0; x < grid.width; x++) {
        for (var y=0; y < grid.height; y++) {
            // sets the fillstyle depending on the id of
            // each cell
            switch (grid.get(x, y)) {
                case EMPTY:
                    ctx.fillStyle = "#fff";
                    break;
                case SNAKE:
                    ctx.fillStyle = "#0ff";
                    break;
                case FRUIT:
                    ctx.fillStyle = "#f00";
                    break;
            }
            ctx.fillRect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
        }
    }

    ctx.fillStyle = "#000";
    // message to the canvas
    ctx.fillText("SCORE: " + score, 10, canvas.height-10);

}
// start and run the game
main();
</script>
</body>
</html>
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.