Path: blob/main/public/games/files/2048/js/game_manager.js
1036 views
function GameManager(size, InputManager, Actuator, StorageManager) {1this.size = size; // Size of the grid2this.inputManager = new InputManager;3this.storageManager = new StorageManager;4this.actuator = new Actuator;56this.startTiles = 2;78this.inputManager.on("move", this.move.bind(this));9this.inputManager.on("restart", this.restart.bind(this));10this.inputManager.on("keepPlaying", this.keepPlaying.bind(this));1112this.setup();13}1415// Restart the game16GameManager.prototype.restart = function () {17this.storageManager.clearGameState();18this.actuator.continueGame(); // Clear the game won/lost message19this.setup();20};2122// Keep playing after winning (allows going over 2048)23GameManager.prototype.keepPlaying = function () {24this.keepPlaying = true;25this.actuator.continueGame(); // Clear the game won/lost message26};2728// Return true if the game is lost, or has won and the user hasn't kept playing29GameManager.prototype.isGameTerminated = function () {30return this.over || (this.won && !this.keepPlaying);31};3233// Set up the game34GameManager.prototype.setup = function () {35var previousState = this.storageManager.getGameState();3637// Reload the game from a previous game if present38if (previousState) {39this.grid = new Grid(previousState.grid.size,40previousState.grid.cells); // Reload grid41this.score = previousState.score;42this.over = previousState.over;43this.won = previousState.won;44this.keepPlaying = previousState.keepPlaying;45} else {46this.grid = new Grid(this.size);47this.score = 0;48this.over = false;49this.won = false;50this.keepPlaying = false;5152// Add the initial tiles53this.addStartTiles();54}5556// Update the actuator57this.actuate();58};5960// Set up the initial tiles to start the game with61GameManager.prototype.addStartTiles = function () {62for (var i = 0; i < this.startTiles; i++) {63this.addRandomTile();64}65};6667// Adds a tile in a random position68GameManager.prototype.addRandomTile = function () {69if (this.grid.cellsAvailable()) {70var value = Math.random() < 0.9 ? 2 : 4;71var tile = new Tile(this.grid.randomAvailableCell(), value);7273this.grid.insertTile(tile);74}75};7677// Sends the updated grid to the actuator78GameManager.prototype.actuate = function () {79if (this.storageManager.getBestScore() < this.score) {80this.storageManager.setBestScore(this.score);81}8283// Clear the state when the game is over (game over only, not win)84if (this.over) {85this.storageManager.clearGameState();86} else {87this.storageManager.setGameState(this.serialize());88}8990this.actuator.actuate(this.grid, {91score: this.score,92over: this.over,93won: this.won,94bestScore: this.storageManager.getBestScore(),95terminated: this.isGameTerminated()96});9798};99100// Represent the current game as an object101GameManager.prototype.serialize = function () {102return {103grid: this.grid.serialize(),104score: this.score,105over: this.over,106won: this.won,107keepPlaying: this.keepPlaying108};109};110111// Save all tile positions and remove merger info112GameManager.prototype.prepareTiles = function () {113this.grid.eachCell(function (x, y, tile) {114if (tile) {115tile.mergedFrom = null;116tile.savePosition();117}118});119};120121// Move a tile and its representation122GameManager.prototype.moveTile = function (tile, cell) {123this.grid.cells[tile.x][tile.y] = null;124this.grid.cells[cell.x][cell.y] = tile;125tile.updatePosition(cell);126};127128// Move tiles on the grid in the specified direction129GameManager.prototype.move = function (direction) {130// 0: up, 1: right, 2: down, 3: left131var self = this;132133if (this.isGameTerminated()) return; // Don't do anything if the game's over134135var cell, tile;136137var vector = this.getVector(direction);138var traversals = this.buildTraversals(vector);139var moved = false;140141// Save the current tile positions and remove merger information142this.prepareTiles();143144// Traverse the grid in the right direction and move tiles145traversals.x.forEach(function (x) {146traversals.y.forEach(function (y) {147cell = { x: x, y: y };148tile = self.grid.cellContent(cell);149150if (tile) {151var positions = self.findFarthestPosition(cell, vector);152var next = self.grid.cellContent(positions.next);153154// Only one merger per row traversal?155if (next && next.value === tile.value && !next.mergedFrom) {156var merged = new Tile(positions.next, tile.value * 2);157merged.mergedFrom = [tile, next];158159self.grid.insertTile(merged);160self.grid.removeTile(tile);161162// Converge the two tiles' positions163tile.updatePosition(positions.next);164165// Update the score166self.score += merged.value;167168// The mighty 2048 tile169if (merged.value === 2048) self.won = true;170} else {171self.moveTile(tile, positions.farthest);172}173174if (!self.positionsEqual(cell, tile)) {175moved = true; // The tile moved from its original cell!176}177}178});179});180181if (moved) {182this.addRandomTile();183184if (!this.movesAvailable()) {185this.over = true; // Game over!186}187188this.actuate();189}190};191192// Get the vector representing the chosen direction193GameManager.prototype.getVector = function (direction) {194// Vectors representing tile movement195var map = {1960: { x: 0, y: -1 }, // Up1971: { x: 1, y: 0 }, // Right1982: { x: 0, y: 1 }, // Down1993: { x: -1, y: 0 } // Left200};201202return map[direction];203};204205// Build a list of positions to traverse in the right order206GameManager.prototype.buildTraversals = function (vector) {207var traversals = { x: [], y: [] };208209for (var pos = 0; pos < this.size; pos++) {210traversals.x.push(pos);211traversals.y.push(pos);212}213214// Always traverse from the farthest cell in the chosen direction215if (vector.x === 1) traversals.x = traversals.x.reverse();216if (vector.y === 1) traversals.y = traversals.y.reverse();217218return traversals;219};220221GameManager.prototype.findFarthestPosition = function (cell, vector) {222var previous;223224// Progress towards the vector direction until an obstacle is found225do {226previous = cell;227cell = { x: previous.x + vector.x, y: previous.y + vector.y };228} while (this.grid.withinBounds(cell) &&229this.grid.cellAvailable(cell));230231return {232farthest: previous,233next: cell // Used to check if a merge is required234};235};236237GameManager.prototype.movesAvailable = function () {238return this.grid.cellsAvailable() || this.tileMatchesAvailable();239};240241// Check for available matches between tiles (more expensive check)242GameManager.prototype.tileMatchesAvailable = function () {243var self = this;244245var tile;246247for (var x = 0; x < this.size; x++) {248for (var y = 0; y < this.size; y++) {249tile = this.grid.cellContent({ x: x, y: y });250251if (tile) {252for (var direction = 0; direction < 4; direction++) {253var vector = self.getVector(direction);254var cell = { x: x + vector.x, y: y + vector.y };255256var other = self.grid.cellContent(cell);257258if (other && other.value === tile.value) {259return true; // These two tiles can be merged260}261}262}263}264}265266return false;267};268269GameManager.prototype.positionsEqual = function (first, second) {270return first.x === second.x && first.y === second.y;271};272273274