<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>实现一个2048游戏</title>
<style>
html, body {
margin: 0;
padding: 0;
background: #faf8ef;
color: #776e65;
font-family: "Clear Sans", "Helvetica Neue", Arial, sans-serif;
font-size: 18px;
}
body {
margin: 20px 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 fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.game-container {
margin-top: 10px;
position: relative;
padding: 15px;
cursor: default;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
background: #bbada0;
border-radius: 6px;
width: 500px;
height: 500px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.game-container .game-message {
display: none;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(238, 228, 218, 0.5);
z-index: 100;
text-align: center;
-webkit-animation: fade-in 800ms ease 1200ms;
-moz-animation: fade-in 800ms ease 1200ms;
-webkit-animation-fill-mode: both;
-moz-animation-fill-mode: both;
}
.game-container .game-message p {
font-size: 60px;
font-weight: bold;
height: 60px;
line-height: 60px;
margin-top: 222px;
}
.game-container .game-message .lower {
display: block;
margin-top: 59px;
}
.game-container .game-message a {
display: inline-block;
background: #8f7a66;
border-radius: 3px;
padding: 0 20px;
text-decoration: none;
color: #f9f6f2;
height: 40px;
line-height: 42px;
margin-left: 9px;
}
.game-container .game-message.game-won {
background: rgba(237, 194, 46, 0.5);
color: #f9f6f2;
}
.game-container .game-message.game-won, .game-container .game-message.game-over {
display: block;
}
.grid-container {
position: absolute;
z-index: 1;
}
.grid-row {
margin-bottom: 15px;
}
.grid-row:last-child {
margin-bottom: 0;
}
.grid-row:after {
content: "";
display: block;
clear: both;
}
.grid-cell {
width: 106.25px;
height: 106.25px;
margin-right: 15px;
float: left;
border-radius: 3px;
background: rgba(238, 228, 218, 0.35);
}
.grid-cell:last-child {
margin-right: 0;
}
.tile-container {
position: absolute;
z-index: 2;
}
.tile {
width: 106.25px;
height: 106.25px;
line-height: 116.25px;
}
.tile.tile-position-1-1 {
position: absolute;
left: 0px;
top: 0px;
}
.tile.tile-position-1-2 {
position: absolute;
left: 0px;
top: 121px;
}
.tile.tile-position-1-3 {
position: absolute;
left: 0px;
top: 243px;
}
.tile.tile-position-1-4 {
position: absolute;
left: 0px;
top: 364px;
}
.tile.tile-position-2-1 {
position: absolute;
left: 121px;
top: 0px;
}
.tile.tile-position-2-2 {
position: absolute;
left: 121px;
top: 121px;
}
.tile.tile-position-2-3 {
position: absolute;
left: 121px;
top: 243px;
}
.tile.tile-position-2-4 {
position: absolute;
left: 121px;
top: 364px;
}
.tile.tile-position-3-1 {
position: absolute;
left: 243px;
top: 0px;
}
.tile.tile-position-3-2 {
position: absolute;
left: 243px;
top: 121px;
}
.tile.tile-position-3-3 {
position: absolute;
left: 243px;
top: 243px;
}
.tile.tile-position-3-4 {
position: absolute;
left: 243px;
top: 364px;
}
.tile.tile-position-4-1 {
position: absolute;
left: 364px;
top: 0px;
}
.tile.tile-position-4-2 {
position: absolute;
left: 364px;
top: 121px;
}
.tile.tile-position-4-3 {
position: absolute;
left: 364px;
top: 243px;
}
.tile.tile-position-4-4 {
position: absolute;
left: 364px;
top: 364px;
}
.tile {
border-radius: 3px;
background: #eee4da;
text-align: center;
font-weight: bold;
z-index: 10;
font-size: 55px;
-webkit-transition: 100ms ease-in-out;
-moz-transition: 100ms ease-in-out;
-webkit-transition-property: top, left;
-moz-transition-property: top, left;
}
.tile.tile-2 {
background: #eee4da;
box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0);
}
.tile.tile-4 {
background: #ede0c8;
box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0);
}
.tile.tile-8 {
color: #f9f6f2;
background: #f2b179;
}
.tile.tile-16 {
color: #f9f6f2;
background: #f59563;
}
.tile.tile-32 {
color: #f9f6f2;
background: #f67c5f;
}
.tile.tile-64 {
color: #f9f6f2;
background: #f65e3b;
}
.tile.tile-128 {
color: #f9f6f2;
background: #edcf72;
box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.2381), inset 0 0 0 1px rgba(255, 255, 255, 0.14286);
font-size: 45px;
}
@media screen and (max-width: 480px) {
.tile.tile-128 {
font-size: 25px;
}
}
.tile.tile-256 {
color: #f9f6f2;
background: #edcc61;
box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.31746), inset 0 0 0 1px rgba(255, 255, 255, 0.19048);
font-size: 45px;
}
@media screen and (max-width: 480px) {
.tile.tile-256 {
font-size: 25px;
}
}
.tile.tile-512 {
color: #f9f6f2;
background: #edc850;
box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.39683), inset 0 0 0 1px rgba(255, 255, 255, 0.2381);
font-size: 45px;
}
@media screen and (max-width: 480px) {
.tile.tile-512 {
font-size: 25px;
}
}
.tile.tile-1024 {
color: #f9f6f2;
background: #edc53f;
box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.47619), inset 0 0 0 1px rgba(255, 255, 255, 0.28571);
font-size: 35px;
}
@media screen and (max-width: 480px) {
.tile.tile-1024 {
font-size: 15px;
}
}
.tile.tile-2048 {
color: #f9f6f2;
background: #edc22e;
box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.55556), inset 0 0 0 1px rgba(255, 255, 255, 0.33333);
font-size: 35px;
}
@media screen and (max-width: 480px) {
.tile.tile-2048 {
font-size: 15px;
}
}
@-webkit-keyframes appear {
0% {
opacity: 0;
-webkit-transform: scale(0);
-moz-transform: scale(0);
}
100% {
opacity: 1;
-webkit-transform: scale(1);
-moz-transform: scale(1);
}
}
@-moz-keyframes appear {
0% {
opacity: 0;
-webkit-transform: scale(0);
-moz-transform: scale(0);
}
100% {
opacity: 1;
-webkit-transform: scale(1);
-moz-transform: scale(1);
}
}
@keyframes appear {
0% {
opacity: 0;
-webkit-transform: scale(0);
-moz-transform: scale(0);
}
100% {
opacity: 1;
-webkit-transform: scale(1);
-moz-transform: scale(1);
}
}
.tile-new {
-webkit-animation: appear 200ms ease 100ms;
-moz-animation: appear 200ms ease 100ms;
-webkit-animation-fill-mode: both;
-moz-animation-fill-mode: both;
}
@-webkit-keyframes pop {
0% {
-webkit-transform: scale(0);
-moz-transform: scale(0);
}
50% {
-webkit-transform: scale(1.2);
-moz-transform: scale(1.2);
}
100% {
-webkit-transform: scale(1);
-moz-transform: scale(1);
}
}
@-moz-keyframes pop {
0% {
-webkit-transform: scale(0);
-moz-transform: scale(0);
}
50% {
-webkit-transform: scale(1.2);
-moz-transform: scale(1.2);
}
100% {
-webkit-transform: scale(1);
-moz-transform: scale(1);
}
}
@keyframes pop {
0% {
-webkit-transform: scale(0);
-moz-transform: scale(0);
}
50% {
-webkit-transform: scale(1.2);
-moz-transform: scale(1.2);
}
100% {
-webkit-transform: scale(1);
-moz-transform: scale(1);
}
}
.tile-merged {
z-index: 20;
-webkit-animation: pop 200ms ease 100ms;
-moz-animation: pop 200ms ease 100ms;
-webkit-animation-fill-mode: both;
-moz-animation-fill-mode: both;
}
.game-intro {
margin-bottom: 0;
}
.game-explanation {
margin-top: 50px;
}
@media screen and (max-width: 480px) {
html, body {
font-size: 15px;
}
body {
margin: 20px 0;
padding: 0 20px;
}
h1.title {
font-size: 50px;
}
.container {
width: 280px;
margin: 0 auto;
}
.score-container {
margin-top: 0;
}
.heading {
margin-bottom: 10px;
}
.game-container {
margin-top: 40px;
position: relative;
padding: 10px;
cursor: default;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
background: #bbada0;
border-radius: 6px;
width: 280px;
height: 280px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.game-container .game-message {
display: none;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(238, 228, 218, 0.5);
z-index: 100;
text-align: center;
-webkit-animation: fade-in 800ms ease 1200ms;
-moz-animation: fade-in 800ms ease 1200ms;
-webkit-animation-fill-mode: both;
-moz-animation-fill-mode: both;
}
.game-container .game-message p {
font-size: 60px;
font-weight: bold;
height: 60px;
line-height: 60px;
margin-top: 222px;
}
.game-container .game-message .lower {
display: block;
margin-top: 59px;
}
.game-container .game-message a {
display: inline-block;
background: #8f7a66;
border-radius: 3px;
padding: 0 20px;
text-decoration: none;
color: #f9f6f2;
height: 40px;
line-height: 42px;
margin-left: 9px;
}
.game-container .game-message.game-won {
background: rgba(237, 194, 46, 0.5);
color: #f9f6f2;
}
.game-container .game-message.game-won, .game-container .game-message.game-over {
display: block;
}
.grid-container {
position: absolute;
z-index: 1;
}
.grid-row {
margin-bottom: 10px;
}
.grid-row:last-child {
margin-bottom: 0;
}
.grid-row:after {
content: "";
display: block;
clear: both;
}
.grid-cell {
width: 57.5px;
height: 57.5px;
margin-right: 10px;
float: left;
border-radius: 3px;
background: rgba(238, 228, 218, 0.35);
}
.grid-cell:last-child {
margin-right: 0;
}
.tile-container {
position: absolute;
z-index: 2;
}
.tile {
width: 57.5px;
height: 57.5px;
line-height: 67.5px;
}
.tile.tile-position-1-1 {
position: absolute;
left: 0px;
top: 0px;
}
.tile.tile-position-1-2 {
position: absolute;
left: 0px;
top: 68px;
}
.tile.tile-position-1-3 {
position: absolute;
left: 0px;
top: 135px;
}
.tile.tile-position-1-4 {
position: absolute;
left: 0px;
top: 203px;
}
.tile.tile-position-2-1 {
position: absolute;
left: 68px;
top: 0px;
}
.tile.tile-position-2-2 {
position: absolute;
left: 68px;
top: 68px;
}
.tile.tile-position-2-3 {
position: absolute;
left: 68px;
top: 135px;
}
.tile.tile-position-2-4 {
position: absolute;
left: 68px;
top: 203px;
}
.tile.tile-position-3-1 {
position: absolute;
left: 135px;
top: 0px;
}
.tile.tile-position-3-2 {
position: absolute;
left: 135px;
top: 68px;
}
.tile.tile-position-3-3 {
position: absolute;
left: 135px;
top: 135px;
}
.tile.tile-position-3-4 {
position: absolute;
left: 135px;
top: 203px;
}
.tile.tile-position-4-1 {
position: absolute;
left: 203px;
top: 0px;
}
.tile.tile-position-4-2 {
position: absolute;
left: 203px;
top: 68px;
}
.tile.tile-position-4-3 {
position: absolute;
left: 203px;
top: 135px;
}
.tile.tile-position-4-4 {
position: absolute;
left: 203px;
top: 203px;
}
.game-container {
margin-top: 20px;
}
.tile {
font-size: 35px;
}
.game-message p {
font-size: 30px !important;
height: 30px !important;
line-height: 30px !important;
margin-top: 90px !important;
}
.game-message .lower {
margin-top: 30px !important;
}
</style>
</head>
<body>
<div class="container">
<div class="heading">
<h1 class="title">2048</h1>
<div class="score-container">0</div>
</div>
<div class="game-container">
<div class="game-message">
<p></p>
<div class="lower">
<a class="retry-button">重玩</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>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/1.0.6/hammer.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
window.requestAnimationFrame(function () {
var manager = new GameManager(4, KeyboardInputManager, HTMLActuator);
});
});
function GameManager(size, InputManager, Actuator) {
this.size = size;
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();
}
GameManager.prototype.restart = function () {
this.actuator.restart();
this.setup();
};
GameManager.prototype.setup = function () {
this.grid = new Grid(this.size);
this.score = 0;
this.over = false;
this.won = false;
this.addStartTiles();
this.actuate();
};
GameManager.prototype.addStartTiles = function () {
for (var i = 0; i < this.startTiles; i++) {
this.addRandomTile();
}
};
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);
}
};
GameManager.prototype.actuate = function () {
this.actuator.actuate(this.grid, {
score: this.score,
over: this.over,
won: this.won,
});
};
GameManager.prototype.prepareTiles = function () {
this.grid.eachCell(function (x, y, tile) {
if (tile) {
tile.mergedFrom = null;
tile.savePosition();
}
});
};
GameManager.prototype.moveTile = function (tile, cell) {
this.grid.cells[tile.x][tile.y] = null;
this.grid.cells[cell.x][cell.y] = tile;
tile.updatePosition(cell);
};
GameManager.prototype.move = function (direction) {
var self = this;
if (this.over || this.won) return;
var cell, tile;
var vector = this.getVector(direction);
var traversals = this.buildTraversals(vector);
var moved = false;
this.prepareTiles();
traversals.x.forEach(function (x) {
traversals.y.forEach(function (y) {
cell = { x: x, y: y };
tile = self.grid.cellContent(cell);
if (tile) {
var positions = self.findFarthestPosition(cell, vector);
var next = self.grid.cellContent(positions.next);
if (next && next.value === tile.value && !next.mergedFrom) {
var merged = new Tile(positions.next, tile.value * 2);
merged.mergedFrom = [tile, next];
self.grid.insertTile(merged);
self.grid.removeTile(tile);
tile.updatePosition(positions.next);
self.score += merged.value;
if (merged.value === 2048) self.won = true;
} else {
self.moveTile(tile, positions.farthest);
}
if (!self.positionsEqual(cell, tile)) {
moved = true;
}
}
});
});
if (moved) {
this.addRandomTile();
if (!this.movesAvailable()) {
this.over = true;
}
this.actuate();
}
};
GameManager.prototype.getVector = function (direction) {
var map = {
0: { x: 0, y: -1 },
1: { x: 1, y: 0 },
2: { x: 0, y: 1 },
3: { x: -1, y: 0 },
};
return map[direction];
};
GameManager.prototype.buildTraversals = function (vector) {
var traversals = { x: [], y: [] };
for (var pos = 0; pos < this.size; pos++) {
traversals.x.push(pos);
traversals.y.push(pos);
}
if (vector.x === 1) traversals.x = traversals.x.reverse();
if (vector.y === 1) traversals.y = traversals.y.reverse();
return traversals;
};
GameManager.prototype.findFarthestPosition = function (cell, vector) {
var previous;
do {
previous = cell;
cell = { x: previous.x + vector.x, y: previous.y + vector.y };
} while (this.grid.withinBounds(cell) && this.grid.cellAvailable(cell));
return {
farthest: previous,
next: cell,
};
};
GameManager.prototype.movesAvailable = function () {
return this.grid.cellsAvailable() || this.tileMatchesAvailable();
};
GameManager.prototype.tileMatchesAvailable = function () {
var self = this;
var tile;
for (var x = 0; x < this.size; x++) {
for (var y = 0; y < this.size; y++) {
tile = this.grid.cellContent({ x: x, y: y });
if (tile) {
for (var direction = 0; direction < 4; direction++) {
var vector = self.getVector(direction);
var cell = { x: x + vector.x, y: y + vector.y };
var other = self.grid.cellContent(cell);
if (other) {
}
if (other && other.value === tile.value) {
return true;
}
}
}
}
}
return false;
};
GameManager.prototype.positionsEqual = function (first, second) {
return first.x === second.x && first.y === second.y;
};
function Grid(size) {
this.size = size;
this.cells = [];
this.build();
}
Grid.prototype.build = function () {
for (var x = 0; x < this.size; x++) {
var row = (this.cells[x] = []);
for (var y = 0; y < this.size; y++) {
row.push(null);
}
}
};
Grid.prototype.randomAvailableCell = function () {
var cells = this.availableCells();
if (cells.length) {
return cells[Math.floor(Math.random() * cells.length)];
}
};
Grid.prototype.availableCells = function () {
var cells = [];
this.eachCell(function (x, y, tile) {
if (!tile) {
cells.push({ x: x, y: y });
}
});
return cells;
};
Grid.prototype.eachCell = function (callback) {
for (var x = 0; x < this.size; x++) {
for (var y = 0; y < this.size; y++) {
callback(x, y, this.cells[x][y]);
}
}
};
Grid.prototype.cellsAvailable = function () {
return !!this.availableCells().length;
};
Grid.prototype.cellAvailable = function (cell) {
return !this.cellOccupied(cell);
};
Grid.prototype.cellOccupied = function (cell) {
return !!this.cellContent(cell);
};
Grid.prototype.cellContent = function (cell) {
if (this.withinBounds(cell)) {
return this.cells[cell.x][cell.y];
} else {
return null;
}
};
Grid.prototype.insertTile = function (tile) {
this.cells[tile.x][tile.y] = tile;
};
Grid.prototype.removeTile = function (tile) {
this.cells[tile.x][tile.y] = null;
};
Grid.prototype.withinBounds = function (position) {
return (
position.x >= 0 &&
position.x < this.size &&
position.y >= 0 &&
position.y < this.size
);
};
function HTMLActuator() {
this.tileContainer =
document.getElementsByClassName('tile-container')[0];
this.scoreContainer =
document.getElementsByClassName('score-container')[0];
this.messageContainer =
document.getElementsByClassName('game-message')[0];
this.score = 0;
}
HTMLActuator.prototype.actuate = function (grid, metadata) {
var self = this;
window.requestAnimationFrame(function () {
self.clearContainer(self.tileContainer);
grid.cells.forEach(function (column) {
column.forEach(function (cell) {
if (cell) {
self.addTile(cell);
}
});
});
self.updateScore(metadata.score);
if (metadata.over) self.message(false);
if (metadata.won) self.message(true);
});
};
HTMLActuator.prototype.restart = function () {
this.clearMessage();
};
HTMLActuator.prototype.clearContainer = function (container) {
while (container.firstChild) {
container.removeChild(container.firstChild);
}
};
HTMLActuator.prototype.addTile = function (tile) {
var self = this;
var element = document.createElement('div');
var position = tile.previousPosition || { x: tile.x, y: tile.y };
positionClass = this.positionClass(position);
var classes = ['tile', 'tile-' + tile.value, positionClass];
this.applyClasses(element, classes);
element.textContent = tile.value;
if (tile.previousPosition) {
window.requestAnimationFrame(function () {
classes[2] = self.positionClass({ x: tile.x, y: tile.y });
self.applyClasses(element, classes);
});
} else if (tile.mergedFrom) {
classes.push('tile-merged');
this.applyClasses(element, classes);
tile.mergedFrom.forEach(function (merged) {
self.addTile(merged);
});
} else {
classes.push('tile-new');
this.applyClasses(element, classes);
}
this.tileContainer.appendChild(element);
};
HTMLActuator.prototype.applyClasses = function (element, classes) {
element.setAttribute('class', classes.join(' '));
};
HTMLActuator.prototype.normalizePosition = function (position) {
return { x: position.x + 1, y: position.y + 1 };
};
HTMLActuator.prototype.positionClass = function (position) {
position = this.normalizePosition(position);
return 'tile-position-' + position.x + '-' + position.y;
};
HTMLActuator.prototype.updateScore = function (score) {
this.clearContainer(this.scoreContainer);
var difference = score - this.score;
this.score = score;
this.scoreContainer.textContent = this.score;
if (difference > 0) {
var addition = document.createElement('div');
addition.classList.add('score-addition');
addition.textContent = '+' + difference;
this.scoreContainer.appendChild(addition);
}
};
HTMLActuator.prototype.message = function (won) {
var type = won ? 'game-won' : 'game-over';
var message = won ? 'You win!' : 'Game over!';
this.messageContainer.classList.add(type);
this.messageContainer.getElementsByTagName('p')[0].textContent =
message;
};
HTMLActuator.prototype.clearMessage = function () {
this.messageContainer.classList.remove('game-won', 'game-over');
};
function KeyboardInputManager() {
this.events = {};
this.listen();
}
KeyboardInputManager.prototype.on = function (event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
};
KeyboardInputManager.prototype.emit = function (event, data) {
var callbacks = this.events[event];
if (callbacks) {
callbacks.forEach(function (callback) {
callback(data);
});
}
};
KeyboardInputManager.prototype.listen = function () {
var self = this;
var map = {
38: 0,
39: 1,
40: 2,
37: 3,
75: 0,
76: 1,
74: 2,
72: 3,
};
document.addEventListener('keydown', function (event) {
var modifiers =
event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
var mapped = map[event.which];
if (!modifiers) {
if (mapped !== undefined) {
event.preventDefault();
self.emit('move', mapped);
}
if (event.which === 32) self.restart.bind(self)(event);
}
});
var retry = document.getElementsByClassName('retry-button')[0];
retry.addEventListener('click', this.restart.bind(this));
var gestures = [
Hammer.DIRECTION_UP,
Hammer.DIRECTION_RIGHT,
Hammer.DIRECTION_DOWN,
Hammer.DIRECTION_LEFT,
];
var gameContainer =
document.getElementsByClassName('game-container')[0];
var handler = Hammer(gameContainer, {
drag_block_horizontal: true,
drag_block_vertical: true,
});
handler.on('swipe', function (event) {
event.gesture.preventDefault();
mapped = gestures.indexOf(event.gesture.direction);
if (mapped !== -1) self.emit('move', mapped);
});
};
KeyboardInputManager.prototype.restart = function (event) {
event.preventDefault();
this.emit('restart');
};
function Tile(position, value) {
this.x = position.x;
this.y = position.y;
this.value = value || 2;
this.previousPosition = null;
this.mergedFrom = null;
}
Tile.prototype.savePosition = function () {
this.previousPosition = { x: this.x, y: this.y };
};
Tile.prototype.updatePosition = function (position) {
this.x = position.x;
this.y = position.y;
};
</script>
</body>
</html>