Get the full commented source code of

HTML5 Suika Watermelon Game

Talking about Turn game, Game development, HTML5, Javascript and Phaser.

It’s not a secret Ketchapp is capable of releasing a lot of fun and easy games, and Turn is one of them.

You have to make perfect turns by tapping at the right moment. The more you shrink the tighter the dungeon.

It’s another perfect game to build in HTML5 with the magic of Phaser, have a look:

Tap to turn at the right time, or the square will shrink, and shrink, and shrink…

The source code is less than 200 lines, still uncommented because I want to add some special effects like particles, but it’s quite easy to understand.

No physics engines have been used.

Keep in mind that like most endless runner games, the main character – the white square in this case – does not move, it’s the entire environment which moves around it.

Also, the terrain is just a background color, while the tunnels are sprites managed with object pooling.

let game;

let gameOptions = {
    playerSize: 128,
    tunnelLengthRange: [400, 600],
    playerSpeed: 250,
    playerSpeedIncrement: 2
}

const CLOCKWISE = 0;
const COUNTERCLOCKWISE = 1;
const UP = 0;
const RIGHT = 1;
const DOWN = 2;
const LEFT = 3;

window.onload = function() {
    let gameConfig = {
        type: Phaser.AUTO,
        backgroundColor: 0x2980b9,
        scale: {
            mode: Phaser.Scale.FIT,
            autoCenter: Phaser.Scale.CENTER_BOTH,
            parent: "thegame",
            width: 750,
            height: 1334
        },
        scene: playGame
    }
    game = new Phaser.Game(gameConfig);
    window.focus();
}

class playGame extends Phaser.Scene {
    constructor() {
        super("PlayGame");
    }
    preload() {
        this.load.image("hole", "hole.png");
        this.load.image("player", "player.png");
    }
    create() {
        this.currentSpeed = gameOptions.playerSpeed;
        this.tunnels = [
            this.add.sprite(0, 0, "hole"),
            this.add.sprite(0, 0, "hole"),
            this.add.sprite(0, 0, "hole")
        ];
        this.tunnelToWatch = 0;
        this.player = this.add.sprite(game.config.width / 2, game.config.height / 2, "player");
        this.player.displayWidth = gameOptions.playerSize;
        this.player.displayHeight = gameOptions.playerSize;
        this.player.setDepth(1);
        this.tunnels[0].x = game.config.width / 2,
        this.tunnels[0].y = this.player.getBounds().bottom;
        this.tunnels[0].setOrigin(0.5, 1);
        this.tunnels[0].direction = UP;
        this.tunnels[0].displayWidth = this.player.displayWidth;
        this.tunnels[0].displayHeight = gameOptions.tunnelLengthRange[1];
        this.input.on("pointerdown", this.changeDirection, this);
        this.attachTunnel(0);
        this.attachTunnel(1);
    }

    attachTunnel(n) {
        let nextDirection = Phaser.Math.Between(CLOCKWISE, COUNTERCLOCKWISE);
        let nextLength = Phaser.Math.Between(gameOptions.tunnelLengthRange[0], gameOptions.tunnelLengthRange[1]);
        let bounds = this.tunnels[n].getBounds();
        let tunnelIndex = (n + 1) % 3;
        switch(this.tunnels[n].direction) {
            case UP: {
                this.tunnels[tunnelIndex].x = (nextDirection == CLOCKWISE) ? bounds.left : bounds.right;
                this.tunnels[tunnelIndex].y = bounds.top;
                this.tunnels[tunnelIndex].displayWidth = nextLength;
                this.tunnels[tunnelIndex].displayHeight = this.player.displayHeight;
                this.tunnels[tunnelIndex].direction = (nextDirection == CLOCKWISE) ? RIGHT : LEFT;
                this.tunnels[tunnelIndex].setOrigin((nextDirection == CLOCKWISE) ? 0 : 1, 0);
                break;
            }
            case LEFT: {
                this.tunnels[tunnelIndex].x = bounds.left;
                this.tunnels[tunnelIndex].y = (nextDirection == CLOCKWISE) ? bounds.bottom : bounds.top;
                this.tunnels[tunnelIndex].displayWidth = this.player.displayWidth;
                this.tunnels[tunnelIndex].displayHeight = nextLength;
                this.tunnels[tunnelIndex].direction = (nextDirection == CLOCKWISE) ? UP : DOWN;
                this.tunnels[tunnelIndex].setOrigin(0, (nextDirection == CLOCKWISE) ? 1 : 0);
                break;
            }
            case RIGHT: {
                this.tunnels[tunnelIndex].x = bounds.right;
                this.tunnels[tunnelIndex].y = (nextDirection == CLOCKWISE) ? bounds.top : bounds.bottom;
                this.tunnels[tunnelIndex].displayWidth = this.player.displayWidth;
                this.tunnels[tunnelIndex].displayHeight = nextLength;
                this.tunnels[tunnelIndex].direction = (nextDirection == CLOCKWISE) ? DOWN : UP;
                this.tunnels[tunnelIndex].setOrigin(1, (nextDirection == CLOCKWISE) ? 0 : 1);
                break;
            }
            case DOWN: {
                this.tunnels[tunnelIndex].x = (nextDirection == CLOCKWISE) ? bounds.right : bounds.left;
                this.tunnels[tunnelIndex].y = bounds.bottom;
                this.tunnels[tunnelIndex].displayWidth = nextLength;
                this.tunnels[tunnelIndex].displayHeight = this.player.displayHeight;
                this.tunnels[tunnelIndex].direction = (nextDirection == CLOCKWISE) ? LEFT : RIGHT;
                this.tunnels[tunnelIndex].setOrigin((nextDirection == CLOCKWISE) ? 1 : 0, 1);
                break;
            }
        }
    }

    update(t, dt) {
        this.scrollTunnels(this.tunnels[this.tunnelToWatch % 3].direction, this.currentSpeed * dt / 1000);
        switch(this.tunnels[this.tunnelToWatch % 3].direction) {
            case UP: {
                if(this.player.getBounds().bottom <= this.tunnels[this.tunnelToWatch % 3].getBounds().top) {
                    this.gameOver();
                }
                break;
            }
            case DOWN: {
                if(this.player.getBounds().top >= this.tunnels[this.tunnelToWatch % 3].getBounds().bottom) {
                    this.gameOver();
                }
                break;
            }
            case LEFT: {
                if(this.player.getBounds().right <= this.tunnels[this.tunnelToWatch % 3].getBounds().left) {
                    this.gameOver();
                }
                break;
            }
            case RIGHT: {
                if(this.player.getBounds().left >= this.tunnels[this.tunnelToWatch % 3].getBounds().right) {
                    this.gameOver();
                }
                break;
            }
        }
    }

    scrollTunnels(dir, n) {
        for(let i = 0; i < this.tunnels.length; i ++) {
            this.tunnels[i].x += (dir == LEFT) ? n : (dir == RIGHT ? -n : 0);
            this.tunnels[i].y += (dir == UP) ? n : (dir == DOWN ? -n : 0);
        }
    }

    changeDirection() {
        let currentTunnel = this.tunnels[this.tunnelToWatch % 3];
        let nextTunnelBounds = this.tunnels[(this.tunnelToWatch + 1) % 3].getBounds();
        switch(currentTunnel.direction) {
            case UP:
            case DOWN: {
                let difference = (currentTunnel.direction == UP) ? (this.player.getBounds().top - currentTunnel.getBounds().top) : (this.player.getBounds().bottom - currentTunnel.getBounds().bottom);
                if(this.player.displayHeight <= Math.abs(difference)) {
                    this.gameOver();
                }
                this.player.displayHeight -= Math.abs(difference);
                if(difference > 0) {
                    this.player.y = nextTunnelBounds.bottom - this.player.displayHeight / 2;
                }
                else {
                    this.player.y = nextTunnelBounds.top + this.player.displayHeight / 2;
                }
                break;
            }
            case LEFT:
            case RIGHT: {
                let difference = (currentTunnel.direction == LEFT) ? (this.player.getBounds().left - currentTunnel.getBounds().left) : (this.player.getBounds().right - currentTunnel.getBounds().right);
                if(this.player.displayWidth <= Math.abs(difference)) {
                    this.gameOver();
                }
                this.player.displayWidth -= Math.abs(difference);
                if(difference > 0) {
                    this.player.x = nextTunnelBounds.right - this.player.displayWidth / 2;
                }
                else {
                    this.player.x = nextTunnelBounds.left + this.player.displayWidth / 2;
                }
            }
            break;
        }
        this.tunnelToWatch ++;
        this.attachTunnel((this.tunnelToWatch + 1) % 3);
        this.currentSpeed += gameOptions.playerSpeedIncrement;
    }

    gameOver() {
        this.scene.start("PlayGame");
    }
}

Next time I’ll improve the game with some visual effect, meanwhile download the source code.

Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.