Do you like my tutorials?

Then consider supporting me on Ko-fi

Talking about Concentration game, Box2D, Game development, HTML5, Javascript and Phaser.

Did you enjoy the HTML5 physics Word game prototype using Phaser and Planck.js?

Adding physics to old game concepts is a great idea to give old gameplays a new life. I already added physics to a classic game in SamePhysics, my first Box2D game whose source code is available at this page.

Today we are going to add physics to one of the oldest game concepts: Concentration, which I already ported in some languages.

Look at the game:

Click or tap on two covered boxes to uncover them, if numbers match, both boxes will be removed.

Just like in Word game example, keep in mind that at the moment there is not game over condition – it should be when the stack of boxes becomes too high – and there’s no object pooling.

And here is the completely commented source code:

let game;

let gameOptions = {

    // world scale to convert Box2D meters to pixels
    worldScale: 30,

    // game gravity
    gameGravity: 8,

    // amount of boxes when the game starts
    startingBoxes: 16,

    // box size, in pixels
    boxSize: 100,

    // delay between two boxes, in milliseconds
    boxDelay: 1500,

    // time before box value is hidden, in milliseconds
    timeBeforeHide: 2000
}

window.onload = function() {
    let gameConfig = {
        type: Phaser.AUTO,
        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");
    }
    create() {

        // world gravity, as a Vec2 object. It's just a x, y vector
        let gravity = planck.Vec2(0, 8);

        // this is how we create a Box2D world
        this.world = planck.World(gravity);

        // createBox is a method I wrote to create a box
        this.createBox(game.config.width / 2, game.config.height - 20, game.config.width, 40, false);
        this.createBox(20, game.config.height / 2, 40, game.config.height, false);
        this.createBox(game.config.width - 20, game.config.height / 2, 40, game.config.height, false);

        // time event to place the first boxes
        this.startingTimer = this.time.addEvent({
            delay: 200,
            callbackScope: this,
            callback: function() {
                this.createBox(Phaser.Math.Between(100, game.config.width - 100), -100, gameOptions.boxSize, gameOptions.boxSize, true);
                if (this.startingTimer.repeatCount == 0) {

                    // time event to place the remaining boxes
                    this.inGameBoxes = this.time.addEvent({
                        delay: gameOptions.boxDelay,
                        callbackScope: this,
                        callback: function() {
                            this.createBox(Phaser.Math.Between(100, game.config.width - 100), -100, 100, 100, true);
                        },
                        loop: true
                    });
                }
            },
            repeat: gameOptions.startingBoxes
        });

        // array where to store the two selected boxes
        this.selectedBoxes = [];

        // input listener
        this.input.on("pointerdown", this.selectBox, this);
    }

    selectBox(event) {

        // did we select less than 2 boxes?
            if (this.selectedBoxes.length < 2) {

                // loop through all bodies
                for (let body = this.world.getBodyList(); body; body = body.getNext()) {

                    // loop through all fixtures
                    for (let fixture = body.getFixtureList(); fixture; fixture = fixture.getNext()) {

                        // if the fixture contains the input coordinate...
                        if (fixture.testPoint(new planck.Vec2(this.toWorldScale(event.x), this.toWorldScale(event.y)))) {

                            // get body userData
                            let userData = body.getUserData();

                            // if the sprite has a "?" on it...
                            if (body.isDynamic() && userData.text.text == "?") {

                                // show box value
                                userData.text.text = userData.value;

                                // push the box in selectedBoxes array
                                this.selectedBoxes.push(body);

                                // does selectedBoxes array contain two boxes?
                                if (this.selectedBoxes.length == 2) {

                                    // wait 1/2 seconds
                                    this.time.addEvent({
                                        delay: 500,
                                        callbackScope: this,
                                        callback: function() {

                                            // get userData of both boxes
                                            let userData = [this.selectedBoxes[0].getUserData(), this.selectedBoxes[1].getUserData()];

                                            // do boxes have the same value?
                                            if (userData[0].value == userData[1].value) {

                                                // destroy the sprites
                                                userData[0].sprite.destroy();
                                                userData[1].sprite.destroy();

                                                // destroy the texts
                                                userData[0].text.destroy();
                                                userData[1].text.destroy();

                                                // destroy the bodies
                                                this.world.destroyBody(this.selectedBoxes[0]);
                                                this.world.destroyBody(this.selectedBoxes[1]);

                                            }

                                            // do boxes have different values?
                                            else {

                                                // hide boxes values
                                                userData[0].text.text = "?";
                                                userData[1].text.text = "?";
                                            }

                                            // empty selectedBoxes array
                                            this.selectedBoxes = [];
                                        }
                                    })
                                }
                            }
                        }
                    }
                }
            }
            return;
    }

    // simple function to convert pixels to meters
    toWorldScale(n) {
        return n / gameOptions.worldScale;
    }

    // here we go with some Box2D stuff
    // arguments: x, y coordinates of the center, with and height of the box, in pixels
    // we'll conver pixels to meters inside the method
    createBox(posX, posY, width, height, isDynamic) {

        // this is how we create a generic Box2D body
        let box = this.world.createBody();
        if (isDynamic) {

            // Box2D bodies born as static bodies, but we can make them dynamic
            box.setDynamic();
        }

        // a body can have one or more fixtures. This is how we create a box fixture inside a body
        box.createFixture(planck.Box(width / 2 / gameOptions.worldScale, height / 2 / gameOptions.worldScale));

        // now we place the body in the world
        box.setPosition(planck.Vec2(posX / gameOptions.worldScale, posY / gameOptions.worldScale));

        // time to set mass information
        box.setMassData({
            mass: 1,
            center: planck.Vec2(),

            // I have to say I do not know the meaning of this "I", but if you set it to zero, bodies won't rotate
            I: 1
        });

        // now we create a graphics object representing the body
        let color = new Phaser.Display.Color();

        // draw the square
        let debugDraw = this.add.graphics();

        // a box has no value text by default
        let valueText =  null;

        // initial random value
        let randomValue = -1;

        // if isDynamic is set to true
        if (isDynamic) {

            // set a random value
            randomValue = Phaser.Math.Between(0, 9);

            // text to write a random value on the box
            valueText = this.add.text(0, 0, randomValue, {
                font: "64px arial",
                fill: "#000000"
            })
            valueText.setOrigin(0.5);

            // assign the box a random color
            color.random();
            color.brighten(50).saturate(100);
        }

        // if isDynamic is set to true, assign the box a grey color
        else {
            color.setTo(128, 128, 128);
        }

        // draw the rectangle
        debugDraw.fillStyle(color.color, 1);
        debugDraw.fillRect(- width / 2, - height / 2, width, height);

        // set a timed event to hide box value
        let timedEvent = this.time.addEvent({
            delay: gameOptions.timeBeforeHide,
            args: [valueText],
            callback: function(valueText) {
                if (valueText) {
                    valueText.text = "?"
                }
            }
        });

        // set box user data
        let userData = {
            sprite: debugDraw,
            text: valueText,
            value: randomValue,
            event: timedEvent
        }

        // a body can have anything in its user data, normally it's used to store its sprite
        box.setUserData(userData);
    }

    update() {

        // advance the simulation by 1/20 seconds
        this.world.step(1 / 30);

        // crearForces  method should be added at the end on each step
        this.world.clearForces();

        // iterate through all bodies
        for (let body = this.world.getBodyList(); body; body = body.getNext()) {

            // get body position
            let bodyPosition = body.getPosition();

            // get body angle, in radians
            let bodyAngle = body.getAngle();

            // get body user data, the graphics object
            let userData =body.getUserData();

            // adjust graphic object position and rotation
            userData.sprite.x = bodyPosition.x * gameOptions.worldScale;
            userData.sprite.y = bodyPosition.y * gameOptions.worldScale;
            userData.sprite.rotation = bodyAngle;

            // if there is a text on the box...
            if (userData.text) {

                // adjust text position and rotation
                userData.text.x = userData.sprite.x;
                userData.text.y = userData.sprite.y;
                userData.text.rotation = bodyAngle;
            }
        }
    }
};

Which old game would you improve by adding physics? Download the source code and let me know.

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