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 a29744cc0ae26c8b87ba95bafe2a3c6220c23365..cf670286e83fe4bf151a69b345a6b06fd1db7851 100644
--- a/js/keyboard_input_manager.js
+++ b/js/keyboard_input_manager.js
@@ -68,6 +68,10 @@ KeyboardInputManager.prototype.listen = function () {
retry.addEventListener("click", this.restart.bind(this));
retry.addEventListener(this.eventTouchend, 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 f5cbe2d4693031a020678f0de382503c0f3c1c04..5fc88e3fc0851e1edc1136ad395e11067f8fc491 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;
@@ -516,7 +529,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 00b6acc22ba3f49a33400b9143e868fae1337e49..5386e537084ee93801a7f9c6b38cf312c23c1fca 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;