| function GameManager(size, InputManager, Actuator, StorageManager) {
|
| this.size = size;
|
| this.inputManager = new InputManager;
|
| this.storageManager = new StorageManager;
|
| this.actuator = new Actuator;
|
|
|
| this.startTiles = 2;
|
| this.maxRNG = false;
|
| this.ntupleAI = null;
|
|
|
| this.inputManager.on("move", this.move.bind(this));
|
| this.inputManager.on("restart", this.restart.bind(this));
|
| this.inputManager.on("keepPlaying", this.keepPlaying.bind(this));
|
|
|
| this.setup();
|
| }
|
|
|
|
|
| GameManager.prototype.restart = function () {
|
| this.storageManager.clearGameState();
|
| this.actuator.continueGame();
|
| this.setup();
|
| };
|
|
|
|
|
| GameManager.prototype.keepPlaying = function () {
|
| this.keepPlaying = true;
|
| this.actuator.continueGame();
|
| };
|
|
|
|
|
| GameManager.prototype.isGameTerminated = function () {
|
| return this.over || (this.won && !this.keepPlaying);
|
| };
|
|
|
|
|
| GameManager.prototype.setup = function () {
|
| var previousState = this.storageManager.getGameState();
|
|
|
|
|
| if (previousState) {
|
| this.grid = new Grid(previousState.grid.size,
|
| previousState.grid.cells);
|
| this.score = previousState.score;
|
| this.over = previousState.over;
|
| this.won = previousState.won;
|
| this.keepPlaying = previousState.keepPlaying;
|
| } else {
|
| this.grid = new Grid(this.size);
|
| this.score = 0;
|
| this.over = false;
|
| this.won = false;
|
| this.keepPlaying = 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()) {
|
|
|
| if (this.pendingTile) {
|
|
|
| var pt = this.pendingTile;
|
| this.pendingTile = null;
|
| var tile = new Tile({ x: pt.x, y: pt.y }, pt.value);
|
| this.grid.insertTile(tile);
|
| } else if (this.maxRNG && this.ntupleAI) {
|
|
|
| var cells = [];
|
| for (var x = 0; x < this.size; x++) {
|
| cells[x] = [];
|
| for (var y = 0; y < this.size; y++) {
|
| var t = this.grid.cells[x][y];
|
| cells[x][y] = t ? t.value : 0;
|
| }
|
| }
|
|
|
| var empty = this.grid.availableCells();
|
| var bestScore = -Infinity;
|
| var bestCell = empty[0];
|
| var bestValue = 2;
|
|
|
| for (var i = 0; i < empty.length; i++) {
|
| var c = empty[i];
|
|
|
| cells[c.x][c.y] = 2;
|
| var s2 = this.ntupleAI.evaluate(cells);
|
| if (s2 > bestScore) { bestScore = s2; bestCell = c; bestValue = 2; }
|
|
|
| cells[c.x][c.y] = 4;
|
| var s4 = this.ntupleAI.evaluate(cells);
|
| if (s4 > bestScore) { bestScore = s4; bestCell = c; bestValue = 4; }
|
| cells[c.x][c.y] = 0;
|
| }
|
|
|
| var tile = new Tile(bestCell, bestValue);
|
| this.grid.insertTile(tile);
|
| } else {
|
| var value = Math.random() < 0.9 ? 2 : 4;
|
| var tile = new Tile(this.grid.randomAvailableCell(), value);
|
| this.grid.insertTile(tile);
|
| }
|
| }
|
| };
|
|
|
|
|
| GameManager.prototype.actuate = function () {
|
| if (this.storageManager.getBestScore() < this.score) {
|
| this.storageManager.setBestScore(this.score);
|
| }
|
|
|
|
|
| if (this.over) {
|
| this.storageManager.clearGameState();
|
| } else {
|
| this.storageManager.setGameState(this.serialize());
|
| }
|
|
|
| this.actuator.actuate(this.grid, {
|
| score: this.score,
|
| over: this.over,
|
| won: this.won,
|
| bestScore: this.storageManager.getBestScore(),
|
| terminated: this.isGameTerminated()
|
| });
|
|
|
| };
|
|
|
|
|
| GameManager.prototype.serialize = function () {
|
| return {
|
| grid: this.grid.serialize(),
|
| score: this.score,
|
| over: this.over,
|
| won: this.won,
|
| keepPlaying: this.keepPlaying
|
| };
|
| };
|
|
|
|
|
| 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.isGameTerminated()) 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 && other.value === tile.value) {
|
| return true;
|
| }
|
| }
|
| }
|
| }
|
| }
|
|
|
| return false;
|
| };
|
|
|
| GameManager.prototype.positionsEqual = function (first, second) {
|
| return first.x === second.x && first.y === second.y;
|
| };
|
|
|