From 02a24c0610c56da8a3e7e0ed3790f252a760e9c9 Mon Sep 17 00:00:00 2001 From: Mark Frederiksen Date: Tue, 18 Mar 2014 14:48:55 -0400 Subject: [PATCH] Added persistence of game state to localstorage after each move Added rebuilding of game state from localstoage on page load Added New Game button to allow game restarts now that refreshes resume the game --- index.html | 2 +- js/game_manager.js | 51 ++++++++++++++++++++++++++---------- js/grid.js | 42 ++++++++++++++++++++++++----- js/keyboard_input_manager.js | 4 +++ js/local_score_manager.js | 23 ++++++++++++---- js/tile.js | 10 +++++++ style/main.css | 16 +++++++++-- style/main.scss | 9 ++++++- 8 files changed, 127 insertions(+), 30 deletions(-) diff --git a/index.html b/index.html index cbcfe65..29138a6 100644 --- a/index.html +++ b/index.html @@ -23,7 +23,7 @@

Join the numbers and get to the 2048 tile!

- + New Game

diff --git a/js/game_manager.js b/js/game_manager.js index b36aea3..da55aba 100644 --- a/js/game_manager.js +++ b/js/game_manager.js @@ -1,7 +1,7 @@ -function GameManager(size, InputManager, Actuator, ScoreManager) { +function GameManager(size, InputManager, Actuator, StorageManager) { this.size = size; // Size of the grid this.inputManager = new InputManager; - this.scoreManager = new ScoreManager; + this.storageManager = new StorageManager; this.actuator = new Actuator; this.startTiles = 2; @@ -15,6 +15,7 @@ function GameManager(size, InputManager, Actuator, ScoreManager) { // Restart the game GameManager.prototype.restart = function () { + this.storageManager.clearGameState(); this.actuator.continue(); this.setup(); }; @@ -35,15 +36,24 @@ GameManager.prototype.isGameTerminated = function () { // Set up the game GameManager.prototype.setup = function () { - this.grid = new Grid(this.size); - - this.score = 0; - this.over = false; - this.won = false; - this.keepPlaying = false; - - // Add the initial tiles - this.addStartTiles(); + var previousGameState = this.storageManager.getGameState(); + + if (previousGameState) { + this.grid = new Grid(previousGameState.grid.size, previousGameState.grid.cells); + this.score = previousGameState.score; + this.over = previousGameState.over; + this.won = previousGameState.won; + this.keepPlaying = previousGameState.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(); @@ -68,20 +78,33 @@ GameManager.prototype.addRandomTile = function () { // Sends the updated grid to the actuator GameManager.prototype.actuate = function () { - if (this.scoreManager.get() < this.score) { - this.scoreManager.set(this.score); + if (this.storageManager.getBestScore() < this.score) { + this.storageManager.setBestScore(this.score); } + this.storageManager.setGameState(this.serializeGameState()); + this.actuator.actuate(this.grid, { score: this.score, over: this.over, won: this.won, - bestScore: this.scoreManager.get(), + bestScore: this.storageManager.getBestScore(), terminated: this.isGameTerminated() }); }; +GameManager.prototype.serializeGameState = function () { + return { + size: this.size, + grid: this.grid.gridState(), + 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) { diff --git a/js/grid.js b/js/grid.js index 05fe057..a2215a7 100644 --- a/js/grid.js +++ b/js/grid.js @@ -1,20 +1,32 @@ -function Grid(size) { +function Grid(size, previousCellState) { this.size = size; - - this.cells = []; - - this.build(); + this.cells = previousCellState ? this.buildFromPreviousState(previousCellState) : this.buildNew(); } // Build a grid of the specified size -Grid.prototype.build = function () { +Grid.prototype.buildNew = function () { + var cells = []; for (var x = 0; x < this.size; x++) { - var row = this.cells[x] = []; + var row = cells[x] = []; for (var y = 0; y < this.size; y++) { row.push(null); } } + return cells; +}; + +Grid.prototype.buildFromPreviousState = function (state) { + var cells = []; + for (var x = 0; x < this.size; x++) { + var row = cells[x] = []; + + for (var y = 0; y < this.size; y++) { + var tileState = state[x][y]; + row.push(tileState ? new Tile(tileState.position, tileState.value) : null); + } + } + return cells; }; // Find the first available random position @@ -82,3 +94,19 @@ Grid.prototype.withinBounds = function (position) { return position.x >= 0 && position.x < this.size && position.y >= 0 && position.y < this.size; }; + +Grid.prototype.gridState = function () { + var cellState = []; + for (var x = 0; x < this.size; x++) { + var row = cellState[x] = []; + + for (var y = 0; y < this.size; y++) { + row.push(this.cells[x][y] ? this.cells[x][y].tileState() : null); + } + } + + return { + size: this.size, + cells: cellState + } +}; diff --git a/js/keyboard_input_manager.js b/js/keyboard_input_manager.js index 822cc3b..78bb337 100644 --- a/js/keyboard_input_manager.js +++ b/js/keyboard_input_manager.js @@ -57,6 +57,10 @@ KeyboardInputManager.prototype.listen = function () { retry.addEventListener("click", this.restart.bind(this)); retry.addEventListener("touchend", this.restart.bind(this)); + var restart = document.querySelector(".restart-button"); + restart.addEventListener("click", this.restart.bind(this)); + restart.addEventListener("touchend", this.restart.bind(this)); + var keepPlaying = document.querySelector(".keep-playing-button"); keepPlaying.addEventListener("click", this.keepPlaying.bind(this)); keepPlaying.addEventListener("touchend", this.keepPlaying.bind(this)); diff --git a/js/local_score_manager.js b/js/local_score_manager.js index ec4575d..0f37ec2 100644 --- a/js/local_score_manager.js +++ b/js/local_score_manager.js @@ -19,7 +19,8 @@ window.fakeStorage = { }; function LocalScoreManager() { - this.key = "bestScore"; + this.bestScoreKey = "bestScore"; + this.gameStateKey = "gameState"; var supported = this.localStorageSupported(); this.storage = supported ? window.localStorage : window.fakeStorage; @@ -38,11 +39,23 @@ LocalScoreManager.prototype.localStorageSupported = function () { } }; -LocalScoreManager.prototype.get = function () { - return this.storage.getItem(this.key) || 0; +LocalScoreManager.prototype.getBestScore = function () { + return this.storage.getItem(this.bestScoreKey) || 0; }; -LocalScoreManager.prototype.set = function (score) { - this.storage.setItem(this.key, score); +LocalScoreManager.prototype.setBestScore = function (score) { + this.storage.setItem(this.bestScoreKey, score); }; +LocalScoreManager.prototype.getGameState = function () { + var stateJSON = this.storage.getItem(this.gameStateKey); + return stateJSON ? JSON.parse(stateJSON) : null; +}; + +LocalScoreManager.prototype.setGameState = function (gameState) { + this.storage.setItem(this.gameStateKey, JSON.stringify(gameState)); +}; + +LocalScoreManager.prototype.clearGameState = function () { + this.storage.removeItem(this.gameStateKey); +}; diff --git a/js/tile.js b/js/tile.js index de08333..f124073 100644 --- a/js/tile.js +++ b/js/tile.js @@ -15,3 +15,13 @@ Tile.prototype.updatePosition = function (position) { this.x = position.x; this.y = position.y; }; + +Tile.prototype.tileState = function () { + return { + position: { + x: this.x, + y: this.y + }, + value: this.value + }; +} \ No newline at end of file diff --git a/style/main.css b/style/main.css index f8a856a..aba319b 100644 --- a/style/main.css +++ b/style/main.css @@ -143,8 +143,21 @@ hr { 100% { opacity: 1; } } +.restart-button { + display: inline-block; + background: #8f7a66; + border-radius: 3px; + padding: 0 20px; + text-decoration: none; + color: #f9f6f2; + height: 40px; + line-height: 42px; + display: block; + width: 100px; + margin: 10px auto 10px auto; + text-align: center; } + .game-container { - margin-top: 40px; position: relative; padding: 15px; cursor: default; @@ -515,7 +528,6 @@ hr { margin-bottom: 10px; } .game-container { - margin-top: 40px; position: relative; padding: 10px; cursor: default; diff --git a/style/main.scss b/style/main.scss index 1ec515e..aa04aeb 100644 --- a/style/main.scss +++ b/style/main.scss @@ -168,10 +168,17 @@ hr { line-height: 42px; } +.restart-button { + @include button; + display: block; + width: 100px; + margin: 10px auto 10px auto; + text-align: center; +} + // Game field mixin used to render CSS at different width @mixin game-field { .game-container { - margin-top: 40px; position: relative; padding: $grid-spacing; -- GitLab