JSFiddle - React, Tailwind, and code Playground

by Jennifer Perrin

CSS

html,
body {
    margin: 0;
    padding: 0;
    background: #faf8ef;
    color: #776E65;
    font-family: "Clear Sans", "Helvetica Neue", Arial, sans-serif;
    font-size: 18px;
}

body {
    margin: 80px 0;
}

.heading:after {
    content: "";
    display: block;
    clear: both;
}

h1.title {
    font-size: 80px;
    font-weight: bold;
    margin: 0;
    display: block;
    float: left;
}

@-webkit-keyframes $animation-name {
    0% {
        top: 25px;
        opacity: 1;
    }
    100% {
        top: -50px;
        opacity: 0;
    }
}

@-moz-keyframes $animation-name {
    0% {
        top: 25px;
        opacity: 1;
    }
    100% {
        top: -50px;
        opacity: 0;
    }
}

@keyframes $animation-name {
    0% {
        top: 25px;
        opacity: 1;
    }
    100% {
        top: -50px;
        opacity: 0;
    }
}

.score-container {
    position: relative;
    float: right;
    background: #bbada0;
    padding: 15px 25px;
    font-size: 25px;
    height: 25px;
    line-height: 47px;
    font-weight: bold;
    border-radius: 3px;
    color: white;
    margin-top: 8px;
}

.score-container:after {
    position: absolute;
    width: 100%;
    top: 10px;
    left: 0;
    content: "Score";
    text-transform: uppercase;
    font-size: 13px;
    line-height: 13px;
    text-align: center;
    color: #eee4da;
}

.score-container .score-addition {
    position: absolute;
    right: 30px;
    color: red;
    font-size: 25px;
    line-height: 25px;
    font-weight: bold;
    color: rgba(119, 110, 101, 0.9);
    z-index: 100;
    -webkit-animation: move-up 600ms ease-in;
    -moz-animation: move-up 600ms ease-in;
    -webkit-animation-fill-mode: both;
    -moz-animation-fill-mode: both;
}

p {
    margin-top: 0;
    margin-bottom: 10px;
    line-height: 1.65;
}

a {
    color: #776E65;
    font-weight: bold;
    text-decoration: underline;
    cursor: pointer;
}

strong.important {
    text-transform: uppercase;
}

hr {
    border: none;
    border-bottom: 1px...

JavaScript

document.addEventListener("DOMContentLoaded", function() {
    // Wait till the browser is ready to render the game (avoids glitches)
    window.requestAnimationFrame(function() {
        var manager = new GameManager(4, KeyboardInputManager, HTMLActuator);
    });
});


function GameManager(size, InputManager, Actuator) {
    this.size = size; // Size of the grid
    this.inputManager = new InputManager;
    this.actuator = new Actuator;

    this.startTiles = 2;

    this.inputManager.on("move", this.move.bind(this));
    this.inputManager.on("restart", this.restart.bind(this));

    this.setup();
}

// Restart the game
GameManager.prototype.restart = function() {
    this.actuator.restart();
    this.setup();
};

// Set up the game
GameManager.prototype.setup = function() {
    this.grid = new Grid(this.size);

    this.score = 0;
    this.over = false;
    this.won = false;

    // Add the initial tiles
    this.addStartTiles();

    // Update the actuator
    this.actuate();
};

// Set up the initial tiles to start the game with
GameManager.prototype.addStartTiles = function() {
    for (var i = 0; i < this.startTiles; i++) {
        this.addRandomTile();
    }
};

// Adds a tile in a random position
GameManager.prototype.addRandomTile = function() {
    if (this.grid.cellsAvailable()) {
        var value = Math.random() < 0.9 ? 2 : 4;
        var tile = new Tile(this.grid.randomAvailableCell(), value);

        this.grid.insertTile(tile);
    }
};

// Sends the updated grid to the actuator
GameManager.prototype.actuate = function() {
    this.actuator.actuate(this.grid, {
        score: this.score,
        over: this.over,
        won: this.won
    });
};

// Save all tile positions and remove merger info
GameManager.prototype.prepareTiles = function() {
    this.grid.eachCell(function(x, y, tile) {
        if (tile) {
            tile.mergedFrom = null;
            tile.savePosition();
        }
    });
};

// Move a tile and its...