diff --git a/index.html b/index.html index cbcfe650683e75f872d50de2836610d7982f23fb..29138a6257de990703fa2b9167a1d2923408d4cf 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 b36aea3122986eb8708de896d014d7ae613e3c05..da55aba17acf82be4a43271d090e2cb8027a6e53 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 05fe057805028767f6afd3036e16c8fb69a3d36b..a2215a775b465106658ffb66bc7089343f8f57c4 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 822cc3bdfdc1edf9c91436aed2817e60c82e012e..78bb3378331c32744eeb164d6b5e2c3fa517bd34 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 ec4575dafa61af1b52de9d435e1ae40980a43d97..0f37ec216d321fcbf0307869a138880c314006cf 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 de083331bb8353637e81a67304e2e2f8170a7aae..f12407347b9741951b3d5d7d5abd809484d6e735 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 f8a856ad8141b61d66cf8ff44eaae58ce2e3ced8..aba319b06071872d41ba3d30efd8dae086ca2f60 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 1ec515e1a72123fcd88c9e7994223b5328a0f4a5..aa04aeb12bb8bdfb5ca9ef90264dc1dba1221680 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;