Path: blob/main/projects/cupcake2048/js/game_manager.js
1834 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;78/* this.inputManager.on("crowd", this.crowd.bind(this)); */9this.inputManager.on("move", this.move.bind(this));10this.inputManager.on("restart", this.restart.bind(this));11this.inputManager.on("keepPlaying", this.keepPlaying.bind(this));1213this.setup();14}151617// Set Prices18function kcal(exp) {19var kcal = [];20kcal[2]=200;21kcal[4]=250;22kcal[8]=320;23kcal[16]=400;24kcal[32]=500;25kcal[64]=650;26kcal[128]=820;27kcal[256]=1000;28kcal[512]=1200;29kcal[1024]=1500;30kcal[2048]=2000;31kcal[4096]=3000;32kcal[8192]=5000;33return kcal[exp];34}3536// Restart the game37GameManager.prototype.restart = function () {38this.storageManager.clearGameState();39this.actuator.continueGame(); // Clear the game won/lost message40this.setup();41};4243// Crowd board44GameManager.prototype.crowd = function () {45this.storageManager.clearGameState();46this.actuator.continueGame(); // Clear the game won/lost message47this.grid = new Grid(this.size);48this.score = 0;49this.points = 0;50this.over = false;51this.won = false;52this.keepPlaying = false;53this.actuate();54var counter = 0;55for (var i = 0; i < 3; i++) {56for (var j = 0; j < 4; j++) {57counter++;58var value = Math.pow(2, counter);59var tile = new Tile({ x: j, y: i }, value);60if (value <= 8192) this.grid.insertTile(tile);61}62}63};6465// Keep playing after winning (allows going over 2048)66GameManager.prototype.keepPlaying = function () {67this.keepPlaying = true;68this.actuator.continueGame(); // Clear the game won/lost message69};7071// Return true if the game is lost, or has won and the user hasn't kept playing72GameManager.prototype.isGameTerminated = function () {73if (this.over || (this.won && !this.keepPlaying)) {74return true;75} else {76return false;77}78};7980// Set up the game81GameManager.prototype.setup = function () {82var previousState = this.storageManager.getGameState();8384// Reload the game from a previous game if present85if (previousState) {86this.grid = new Grid(previousState.grid.size,87previousState.grid.cells); // Reload grid88this.score = previousState.score;89this.points = previousState.points;90this.over = previousState.over;91this.won = previousState.won;92this.keepPlaying = previousState.keepPlaying;93} else {94this.grid = new Grid(this.size);95this.score = 0;96this.points = 0;97this.over = false;98this.won = false;99this.keepPlaying = false;100101// Add the initial tiles102this.addStartTiles();103}104105// Localize page elements106this.localizeElements();107108// Fill legend109this.fillLegend();110111// Update the actuator112this.actuate();113};114115// Passive localization of page elements116GameManager.prototype.localizeElements = function () {117var elementArray = [118'game-intro',119'restart-button',120'retry-button',121'keep-playing-button',122'game-explanation',123'disclaimer',124'tile-legend',125'credits'126];127for (var i in elementArray) {128LocalizeElement(elementArray[i]);129}130};131132// Fill legend133GameManager.prototype.fillLegend = function () {134135var legend = document.getElementsByClassName("tile-legend");136for (var i = 1; i <= 13; i++) {137var exp = Math.pow(2, i);138var row = document.createElement("div");139var grid = document.createElement("div");140var cell = document.createElement("div");141var img = document.createElement("img");142var p = document.createElement("p");143row.classList.add('legend-row');144grid.classList.add('legend-grid');145cell.classList.add('legend-cell');146cell.classList.add('cell-' + exp);147img.src = "style/img/" + exp + ".jpg";148cell.appendChild(img);149grid.appendChild(cell);150row.appendChild(grid);151p.textContent = Localize(exp) + " (" + kcal(exp) + " Kcal)";152row.appendChild(p);153154legend[0].appendChild(row);155}156157};158159// Set up the initial tiles to start the game with160GameManager.prototype.addStartTiles = function () {161for (var i = 0; i < this.startTiles; i++) {162this.addRandomTile();163}164};165166// Adds a tile in a random position167GameManager.prototype.addRandomTile = function () {168if (this.grid.cellsAvailable()) {169var value = Math.random() < 0.9 ? 2 : 4;170var tile = new Tile(this.grid.randomAvailableCell(), value);171172this.grid.insertTile(tile);173}174};175176// Sends the updated grid to the actuator177GameManager.prototype.actuate = function () {178if (this.storageManager.getBestScore() < this.score) {179this.storageManager.setBestScore(this.score);180}181if (this.storageManager.getBestPoints() < this.points) {182this.storageManager.setBestPoints(this.points);183}184// Clear the state when the game is over (game over only, not win)185if (this.over) {186this.storageManager.clearGameState();187} else {188this.storageManager.setGameState(this.serialize());189}190191this.actuator.actuate(this.grid, {192score: this.score,193points: this.points,194over: this.over,195won: this.won,196bestScore: this.storageManager.getBestScore(),197bestPoints: this.storageManager.getBestPoints(),198terminated: this.isGameTerminated()199});200201};202203// Represent the current game as an object204GameManager.prototype.serialize = function () {205return {206grid: this.grid.serialize(),207score: this.score,208points: this.points,209over: this.over,210won: this.won,211keepPlaying: this.keepPlaying212};213};214215// Save all tile positions and remove merger info216GameManager.prototype.prepareTiles = function () {217this.grid.eachCell(function (x, y, tile) {218if (tile) {219tile.mergedFrom = null;220tile.savePosition();221}222});223};224225// Move a tile and its representation226GameManager.prototype.moveTile = function (tile, cell) {227this.grid.cells[tile.x][tile.y] = null;228this.grid.cells[cell.x][cell.y] = tile;229tile.updatePosition(cell);230};231232// Move tiles on the grid in the specified direction233GameManager.prototype.move = function (direction) {234// 0: up, 1: right, 2: down, 3: left235var self = this;236237if (this.isGameTerminated()) return; // Don't do anything if the game's over238239var cell, tile;240241var vector = this.getVector(direction);242var traversals = this.buildTraversals(vector);243var moved = false;244245// Save the current tile positions and remove merger information246this.prepareTiles();247248// Traverse the grid in the right direction and move tiles249traversals.x.forEach(function (x) {250traversals.y.forEach(function (y) {251cell = { x: x, y: y };252tile = self.grid.cellContent(cell);253254if (tile) {255var positions = self.findFarthestPosition(cell, vector);256var next = self.grid.cellContent(positions.next);257258// Only one merger per row traversal?259if (next && next.value === tile.value && !next.mergedFrom) {260var merged = new Tile(positions.next, tile.value * 2);261merged.mergedFrom = [tile, next];262263self.grid.insertTile(merged);264self.grid.removeTile(tile);265266// Converge the two tiles' positions267tile.updatePosition(positions.next);268269// Update the score270self.points += kcal(tile.value) * 2;271if (merged.value > self.score) self.score = merged.value;272273// The mighty 2048 tile274if (merged.value === 2048 || merged.value === 2048) self.won = true;275} else {276self.moveTile(tile, positions.farthest);277}278279if (!self.positionsEqual(cell, tile)) {280moved = true; // The tile moved from its original cell!281}282}283});284});285286if (moved) {287this.addRandomTile();288289if (!this.movesAvailable()) {290this.over = true; // Game over!291}292293this.actuate();294}295};296297// Get the vector representing the chosen direction298GameManager.prototype.getVector = function (direction) {299// Vectors representing tile movement300var map = {3010: { x: 0, y: -1 }, // Up3021: { x: 1, y: 0 }, // Right3032: { x: 0, y: 1 }, // Down3043: { x: -1, y: 0 } // Left305};306307return map[direction];308};309310// Build a list of positions to traverse in the right order311GameManager.prototype.buildTraversals = function (vector) {312var traversals = { x: [], y: [] };313314for (var pos = 0; pos < this.size; pos++) {315traversals.x.push(pos);316traversals.y.push(pos);317}318319// Always traverse from the farthest cell in the chosen direction320if (vector.x === 1) traversals.x = traversals.x.reverse();321if (vector.y === 1) traversals.y = traversals.y.reverse();322323return traversals;324};325326GameManager.prototype.findFarthestPosition = function (cell, vector) {327var previous;328329// Progress towards the vector direction until an obstacle is found330do {331previous = cell;332cell = { x: previous.x + vector.x, y: previous.y + vector.y };333} while (this.grid.withinBounds(cell) &&334this.grid.cellAvailable(cell));335336return {337farthest: previous,338next: cell // Used to check if a merge is required339};340};341342GameManager.prototype.movesAvailable = function () {343return this.grid.cellsAvailable() || this.tileMatchesAvailable();344};345346// Check for available matches between tiles (more expensive check)347GameManager.prototype.tileMatchesAvailable = function () {348var self = this;349350var tile;351352for (var x = 0; x < this.size; x++) {353for (var y = 0; y < this.size; y++) {354tile = this.grid.cellContent({ x: x, y: y });355356if (tile) {357for (var direction = 0; direction < 4; direction++) {358var vector = self.getVector(direction);359var cell = { x: x + vector.x, y: y + vector.y };360361var other = self.grid.cellContent(cell);362363if (other && other.value === tile.value) {364return true; // These two tiles can be merged365}366}367}368}369}370371return false;372};373374GameManager.prototype.positionsEqual = function (first, second) {375return first.x === second.x && first.y === second.y;376};377378379