5
\$\begingroup\$

I am a beginner in JavaScript and Phaser and wrote a Snake game. I think there are lot of things I could have done better. I am looking for some advice on how I can improve the design and refactoring of the code. You can play the game here.

var mainGame = {
    preload: function() {
        game.load.image('snake', 'images/snake.png');
        game.load.image('apple', 'images/apple.png');
    },
    create: function() {
        // data members
        this.snake = [];
        this.initialSnakeLength = 3;
        this.squareSize = 15;
        this.speed = 9;
        this.count = this.speed;
        this.width  = Math.floor(game.world.width / this.squareSize);
        this.height = Math.floor(game.world.height / this.squareSize);
        this.cursors = game.input.keyboard.createCursorKeys();
        this.direction = "right";
        this.apple = game.add.sprite(0, 0, 'apple');
        this.apple.visible = false;
        this.score = 0;
        this.scoreText = game.add.text(10, 10, "", fontStyle);
        this.updateXY = {up: {x: 0, y: -1}, down: {x: 0, y: 1}, left: {x: -1, y: 0}, right: {x: 1, y: 0}};

        this.createSnake();
        this.drawScore();
        this.drawApple();
    },
    update: function() {
        this.updateDirection();
        this.moveSnake();
        this.snakeEatApple();
        this.checkGameOver();
    },
    checkGameOver: function() {
        this.checkOutOfWorld();
        this.checkSnakeCollision();
    },
    checkSnakeCollision: function() {
        var head = this.snake[this.snake.length - 1];
        for (var i = 0; i < this.snake.length - 1; ++i) {
            if (head.x == this.snake[i].x && head.y === this.snake[i].y) {
                this.gameOver();
            }
        }
    },
    checkOutOfWorld: function() {
        var head = this.snake[this.snake.length - 1];
        if (head.x < 0 || head.x > game.world.width || head.y < 0 || head.y > game.world.height) {
            this.gameOver();
        }
    },
    gameOver: function() {
        finalScore = this.score;
        game.state.start('gameOver');
    },
    drawScore: function() {
        this.scoreText.setText("Score: " + this.score);
    },
    createSnake: function() {
        for (var i = 0; i < this.initialSnakeLength; ++i) {
            this.snake.push(this.add.sprite(game.world.centerX + i * this.squareSize, game.world.centerY, 'snake'));
        }
    },
    snakeEatApple: function() {
        var head = this.snake[this.snake.length - 1];
        if (head.x === this.apple.x && head.y === this.apple.y) {
            this.score += 1;
            this.speed = Math.max(4, this.speed - Math.floor((this.score) / 5)); // increment speed
            this.growSnake();
            this.drawScore();
            this.drawApple();
        }
    },
    growSnake: function() {
        var tail = this.snake[0];
        var x = this.snake[this.snake.length - 1].x + this.updateXY[this.direction].x * this.squareSize;
        var y = this.snake[this.snake.length - 1].y + this.updateXY[this.direction].y * this.squareSize;
        this.snake.push(game.add.sprite(x, y, 'snake'));
    },
    moveSnake: function() {
        --this.count;
        if (this.count === 0) {
            this.count = this.speed;
            var tail = this.snake.shift();
            tail.x = this.snake[this.snake.length - 1].x + this.updateXY[this.direction].x * this.squareSize;
            tail.y = this.snake[this.snake.length - 1].y + this.updateXY[this.direction].y * this.squareSize;
            this.snake.push(tail);
        }
    },
    updateDirection: function() {
        if (this.cursors.up.isDown && this.direction != "down") {
            this.direction = "up";
        }
        else if (this.cursors.down.isDown && this.direction != "up") {
            this.direction = "down";
        }
        else if (this.cursors.left.isDown && this.direction != "right") {
            this.direction = "left";
        }
        else if (this.cursors.right.isDown && this.direction != "left") {
            this.direction = "right";
        }
    },
    drawApple: function() {
        var posX = game.rnd.integerInRange(0, this.width - 1);
        var posY = game.rnd.integerInRange(0, this.height - 1);
        var collidingWithSnake = false;
        for (var i = 0; i < this.snake.length; ++i) {
            if (posX === this.snake[i].x && posY === this.snake[i].y) {
                collidingWithSnake = true;
                break;
            }
        }
        if (!collidingWithSnake) this.apple.reset(posX * this.squareSize, posY * this.squareSize);
        else this.drawApple();
    }
}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

This is really excellent code for a beginner. The high-level structure is clear, and that's one of the most important and difficult things to get right.

This isn't a full critique, but I did notice one function where you can remove a lot of repitition. You can rewrite:

updateDirection: function() {
    if (this.cursors.up.isDown && this.direction != "down") {
        this.direction = "up";
    }
    else if (this.cursors.down.isDown && this.direction != "up") {
        this.direction = "down";
    }
    else if (this.cursors.left.isDown && this.direction != "right") {
        this.direction = "left";
    }
    else if (this.cursors.right.isDown && this.direction != "left") {
        this.direction = "right";
    }
},

as:

updateDirection: function() {
  const opposites  = {down: 'up', up: 'down', left: 'right', right: 'left' };
  const directions = Object.keys(opposites);
  const isPressed  = d => this.cursors[d].isDown;
  const isLegal    = d => this[d] != opposites[d];
  const newDir     = directions.find(d => isPressed(d) && isLegal(d));
  if (newDir) this.direction = newDir;
},

Note: the above is untested, but hopefully helpful. Also note, if needed for performance, you can pull the constants opposites and directions and isPressed and isLegal out of the function, so they won't be recreated each time it's called. The "work" of the function is all accomplished in the final two lines.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.