function GameManager(size, InputManager, Actuator, StorageManager) { this.size = size; // Size of the grid this.inputManager = new InputManager; this.storageManager = new StorageManager; this.actuator = new Actuator; this.startTiles = 2; 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(); } // Restart the game GameManager.prototype.restart = function () { this.storageManager.clearGameState(); this.actuator.continue(); // Clear the game won/lost message this.setup(); }; // Keep playing after winning (allows going over 2048) GameManager.prototype.keepPlaying = function () { this.keepPlaying = true; this.actuator.continue(); // Clear the game won/lost message }; // Return true if the game is lost, or has won and the user hasn't kept playing GameManager.prototype.isGameTerminated = function () { if (this.over || (this.won && !this.keepPlaying)) { return true; } else { return false; } }; // Set up the game GameManager.prototype.setup = function () { var previousState = this.storageManager.getGameState(); // Reload the game from a previous game if present if (previousState) { this.grid = new Grid(previousState.grid.size, previousState.grid.cells); // Reload grid 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; // 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 () { if (this.storageManager.getBestScore() < this.score) { this.storageManager.setBestScore(this.score); } // Clear the state when the game is over (game over only, not win) 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() }); }; // Represent the current game as an object GameManager.prototype.serialize = function () { return { grid: this.grid.serialize(), score: this.score, over: this.over, won: this.won, keepPlaying: this.keepPlaying }; }; // 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) { this.grid.cells[tile.x][tile.y] = null; this.grid.cells[cell.x][cell.y] = tile; tile.updatePosition(cell); }; // Move tiles on the grid in the specified direction GameManager.prototype.move = function (direction) { // 0: up, 1: right, 2: down, 3: left var self = this; if (this.isGameTerminated()) return; // Don't do anything if the game's over var cell, tile; var vector = this.getVector(direction); var traversals = this.buildTraversals(vector); var moved = false; // Save the current tile positions and remove merger information this.prepareTiles(); // Traverse the grid in the right direction and move tiles 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); // Only one merger per row traversal? 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); // Converge the two tiles' positions tile.updatePosition(positions.next); // Update the score self.score += merged.value; // The mighty 2048 tile if (merged.value === 2048) self.won = true; } else { self.moveTile(tile, positions.farthest); } if (!self.positionsEqual(cell, tile)) { moved = true; // The tile moved from its original cell! } } }); }); if (moved) { this.addRandomTile(); if (!this.movesAvailable()) { this.over = true; // Game over! } this.actuate(); } /******************************************/ /******************************************/ /* THE AI CODE STARTS FROM HERE */ /******************************************/ /******************************************/ /* 0:Up 1:Right 2:Down 3:Left */ this.isValid = function(x,y){ if(x < 0 || x >3 || y <0 || y > 3) return false; return true; } this.moveCells = function(matrix, move){ var dx = [-1,0,1,0]; var dy = [0,1,0,-1]; var nx,ny; for(var k = 0;k<3;k++){ for(var i = 0;i<4;i++){ for(var j = 0; j<4; j++){ nx = i+dx[move]; ny = j+dy[move]; if(self.isValid(nx,ny)){ if(matrix[nx][ny] == 0){ matrix[nx][ny] = matrix[i][j]; matrix[i][j] = 0; } } } } } for(var i = 0;i<4;i++){ for(var j = 0; j<4; j++){ nx = i + dx[move]; ny = j + dy[move]; if(self.isValid(nx,ny)){ if(matrix[i][j] == matrix[nx][ny]){ matrix[nx][ny] *= -2; matrix[i][j] = 0; } } } } for(var k = 0;k<3;k++){ for(var i = 0;i<4;i++){ for(var j = 0; j<4; j++){ if(matrix[i][j] <0) matrix[i][j] *= -1; nx = i+dx[move]; ny = j+dy[move]; if(self.isValid(nx,ny)){ if(matrix[nx][ny] == 0){ matrix[nx][ny] = matrix[i][j]; matrix[i][j] = 0; } } } } } return matrix; } this.evaluateMatrix = function(matrix){ /* Count Number of Free Spaces */ var cc = 0; for(var i = 0;i<4;i++) for(var j = 0;j<4;j++){ if(matrix[i][j] == 0) cc += 100; else cc += matrix[i][j]*matrix[i][j]; } return cc; } this.printMatrix = function(matrix){ for(var i = 0;i<4;i++){ var str = "" for(var j = 0;j<4;j++) str += matrix[i][j] + " "; console.log(str) } console.log("******************************"); } this.findFreeCell = function(matrix){ var i,j,k=0; do{ i = (Math.floor(Math.random()*100))%4; j = (Math.floor(Math.random()*100))%4; k++; }while(matrix[i][j] != 0 && k != 500); if(matrix[i][j] != 0) for(i = 0;i<4;i++) for(j = 0;j<4;j++) if(matrix[i][j] == 0) return ({x:i, y:j}); return ({x:i, y:j}); } this.isEqualMatrix = function(m1,m2){ for(var i = 0;i<4;i++) for(var j = 0;j<4;j++) if(m1[i][j] != m2[i][j]) return false; return true; } this.minMax = function(matrix, move, depth){ if(depth == 6) return 0; var rmatrix = self.moveCells(self.createCopy(matrix),move); var areSame = self.isEqualMatrix(rmatrix, matrix); var score = self.evaluateMatrix(rmatrix); if(areSame == true) return score-1; var maxVal=-1000,val,ret; var freeCell = self.findFreeCell(rmatrix); if(freeCell.x == 4 || freeCell.y == 4) console.log("YES VALUE IS 4 || " + freeCell.x + " | " + freeCell.y); rmatrix[freeCell.x][freeCell.y] = 2; for(var x = 0;x<4;x++) { val = this.minMax(self.createCopy(rmatrix), x, depth+1); if(val > maxVal) maxVal = val; } return (score+maxVal); } this.getMove = function(matrix){ var maxVal = 0,val,ret; for(var x = 0; x < 4;x++){ val = this.minMax(self.createCopy(matrix),x,0); // console.log("Score for "+ x + ":" + val ) if(val > maxVal){ maxVal = val; ret = x; } } return ret; } this.getMatrix = function(){ var matrix = []; for (var i = 0 ; i <4 ; i++) { var row = []; for (var j = 0; j < 4; j++) { tile = self.grid.cellContent({x:j, y:i}); if(tile == null) row.push(0); else row.push(tile["value"]); }; matrix.push(row); }; return matrix; } this.createCopy = function(matrix){ var ret =[[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]; for(var i = 0; i < 4;i++) for(var j = 0; j < 4; j++) ret[i][j] = matrix[i][j].valueOf(); return ret; } setTimeout(function() { matrix = self.getMatrix(); var myMove = self.getMove(self.createCopy(matrix)); var rmat = self.moveCells(self.createCopy(matrix), myMove); console.log(myMove); if( self.isEqualMatrix(rmat,matrix)) myMove = (Math.floor(Math.random()*100))%4; self.move(myMove); }, 100); /******************************************/ /******************************************/ /* THE AI CODE ENDS HERE */ /******************************************/ /******************************************/ }; // Get the vector representing the chosen direction GameManager.prototype.getVector = function (direction) { // Vectors representing tile movement var map = { 0: { x: 0, y: -1 }, // Up 1: { x: 1, y: 0 }, // Right 2: { x: 0, y: 1 }, // Down 3: { x: -1, y: 0 } // Left }; return map[direction]; }; // Build a list of positions to traverse in the right order 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); } // Always traverse from the farthest cell in the chosen direction 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; // Progress towards the vector direction until an obstacle is found 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 // Used to check if a merge is required }; }; GameManager.prototype.movesAvailable = function () { return this.grid.cellsAvailable() || this.tileMatchesAvailable(); }; // Check for available matches between tiles (more expensive check) 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; // These two tiles can be merged } } } } } return false; }; GameManager.prototype.positionsEqual = function (first, second) { return first.x === second.x && first.y === second.y; };