JSFiddle - React, Tailwind, and code Playground

by kougiland

HTML

<div class="container">
  <div class="heading">
    <h1 class="title">2048</h1>
    <div class="score-container">0</div>
  </div>
  <p class="game-intro">Join the numbers and get to the <strong>2048 tile!</strong></p>

  <div class="game-container">
    <div class="game-message">
      <p></p>
      <div class="lower">
        <a class="retry-button">Try again</a>
      </div>
    </div>

    <div class="grid-container">
      <div class="grid-row">
        <div class="grid-cell"></div>
        <div class="grid-cell"></div>
        <div class="grid-cell"></div>
        <div class="grid-cell"></div>
      </div>
      <div class="grid-row">
        <div class="grid-cell"></div>
        <div class="grid-cell"></div>
        <div class="grid-cell"></div>
        <div class="grid-cell"></div>
      </div>
      <div class="grid-row">
        <div class="grid-cell"></div>
        <div class="grid-cell"></div>
        <div class="grid-cell"></div>
        <div class="grid-cell"></div>
      </div>
      <div class="grid-row">
        <div class="grid-cell"></div>
        <div class="grid-cell"></div>
        <div class="grid-cell"></div>
        <div class="grid-cell"></div>
      </div>
    </div>

    <div class="tile-container">

    </div>
  </div>

  <p class="game-explanation">
    <strong class="important">How to play:</strong> Use your <strong>arrow keys</strong> to move the tiles. When two tiles with the same number touch, they <strong>merge into one!</strong>
  </p>
  <hr>
  <p>
  Created by <a href="http://gabrielecirulli.com" target="_blank">Gabriele Cirulli.</a> Based on <a href="https://itunes.apple.com/us/app/1024!/id823499224" target="_blank">1024 by Veewo Studio</a> and conceptually similar to <a href="http://asherv.com/threes/" target="_blank">Threes by Asher Vollmer.</a>
  </p>
</div>

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 move-up {
  0% {
    top: 25px;
    opacity: 1;
  }

  100% {
    top: -50px;
    opacity: 0;
  }
}
@-moz-keyframes move-up {
  0% {
    top: 25px;
    opacity: 1;
  }

  100% {
    top: -50px;
    opacity: 0;
  }
}
@keyframes move-up {
  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 solid #d8d4d0;
  margin-top: 20px;
  margin-bottom: 30px;
}

.container {
  width: 500px;
  margin: 0 auto;
}

@-webkit-keyframes fade-in {
  0% {
    opacity: 0;
  }

  100% {
    opacity: 1;
  }
}
@-moz-keyframes...

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 representation
GameManager.prototype.moveTile = function (tile, cell) {
 ...