Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
FogNetwork
GitHub Repository: FogNetwork/Tsunami
Path: blob/main/public/games/files/2048/js/game_manager.js
1036 views
1
function GameManager(size, InputManager, Actuator, StorageManager) {
2
this.size = size; // Size of the grid
3
this.inputManager = new InputManager;
4
this.storageManager = new StorageManager;
5
this.actuator = new Actuator;
6
7
this.startTiles = 2;
8
9
this.inputManager.on("move", this.move.bind(this));
10
this.inputManager.on("restart", this.restart.bind(this));
11
this.inputManager.on("keepPlaying", this.keepPlaying.bind(this));
12
13
this.setup();
14
}
15
16
// Restart the game
17
GameManager.prototype.restart = function () {
18
this.storageManager.clearGameState();
19
this.actuator.continueGame(); // Clear the game won/lost message
20
this.setup();
21
};
22
23
// Keep playing after winning (allows going over 2048)
24
GameManager.prototype.keepPlaying = function () {
25
this.keepPlaying = true;
26
this.actuator.continueGame(); // Clear the game won/lost message
27
};
28
29
// Return true if the game is lost, or has won and the user hasn't kept playing
30
GameManager.prototype.isGameTerminated = function () {
31
return this.over || (this.won && !this.keepPlaying);
32
};
33
34
// Set up the game
35
GameManager.prototype.setup = function () {
36
var previousState = this.storageManager.getGameState();
37
38
// Reload the game from a previous game if present
39
if (previousState) {
40
this.grid = new Grid(previousState.grid.size,
41
previousState.grid.cells); // Reload grid
42
this.score = previousState.score;
43
this.over = previousState.over;
44
this.won = previousState.won;
45
this.keepPlaying = previousState.keepPlaying;
46
} else {
47
this.grid = new Grid(this.size);
48
this.score = 0;
49
this.over = false;
50
this.won = false;
51
this.keepPlaying = false;
52
53
// Add the initial tiles
54
this.addStartTiles();
55
}
56
57
// Update the actuator
58
this.actuate();
59
};
60
61
// Set up the initial tiles to start the game with
62
GameManager.prototype.addStartTiles = function () {
63
for (var i = 0; i < this.startTiles; i++) {
64
this.addRandomTile();
65
}
66
};
67
68
// Adds a tile in a random position
69
GameManager.prototype.addRandomTile = function () {
70
if (this.grid.cellsAvailable()) {
71
var value = Math.random() < 0.9 ? 2 : 4;
72
var tile = new Tile(this.grid.randomAvailableCell(), value);
73
74
this.grid.insertTile(tile);
75
}
76
};
77
78
// Sends the updated grid to the actuator
79
GameManager.prototype.actuate = function () {
80
if (this.storageManager.getBestScore() < this.score) {
81
this.storageManager.setBestScore(this.score);
82
}
83
84
// Clear the state when the game is over (game over only, not win)
85
if (this.over) {
86
this.storageManager.clearGameState();
87
} else {
88
this.storageManager.setGameState(this.serialize());
89
}
90
91
this.actuator.actuate(this.grid, {
92
score: this.score,
93
over: this.over,
94
won: this.won,
95
bestScore: this.storageManager.getBestScore(),
96
terminated: this.isGameTerminated()
97
});
98
99
};
100
101
// Represent the current game as an object
102
GameManager.prototype.serialize = function () {
103
return {
104
grid: this.grid.serialize(),
105
score: this.score,
106
over: this.over,
107
won: this.won,
108
keepPlaying: this.keepPlaying
109
};
110
};
111
112
// Save all tile positions and remove merger info
113
GameManager.prototype.prepareTiles = function () {
114
this.grid.eachCell(function (x, y, tile) {
115
if (tile) {
116
tile.mergedFrom = null;
117
tile.savePosition();
118
}
119
});
120
};
121
122
// Move a tile and its representation
123
GameManager.prototype.moveTile = function (tile, cell) {
124
this.grid.cells[tile.x][tile.y] = null;
125
this.grid.cells[cell.x][cell.y] = tile;
126
tile.updatePosition(cell);
127
};
128
129
// Move tiles on the grid in the specified direction
130
GameManager.prototype.move = function (direction) {
131
// 0: up, 1: right, 2: down, 3: left
132
var self = this;
133
134
if (this.isGameTerminated()) return; // Don't do anything if the game's over
135
136
var cell, tile;
137
138
var vector = this.getVector(direction);
139
var traversals = this.buildTraversals(vector);
140
var moved = false;
141
142
// Save the current tile positions and remove merger information
143
this.prepareTiles();
144
145
// Traverse the grid in the right direction and move tiles
146
traversals.x.forEach(function (x) {
147
traversals.y.forEach(function (y) {
148
cell = { x: x, y: y };
149
tile = self.grid.cellContent(cell);
150
151
if (tile) {
152
var positions = self.findFarthestPosition(cell, vector);
153
var next = self.grid.cellContent(positions.next);
154
155
// Only one merger per row traversal?
156
if (next && next.value === tile.value && !next.mergedFrom) {
157
var merged = new Tile(positions.next, tile.value * 2);
158
merged.mergedFrom = [tile, next];
159
160
self.grid.insertTile(merged);
161
self.grid.removeTile(tile);
162
163
// Converge the two tiles' positions
164
tile.updatePosition(positions.next);
165
166
// Update the score
167
self.score += merged.value;
168
169
// The mighty 2048 tile
170
if (merged.value === 2048) self.won = true;
171
} else {
172
self.moveTile(tile, positions.farthest);
173
}
174
175
if (!self.positionsEqual(cell, tile)) {
176
moved = true; // The tile moved from its original cell!
177
}
178
}
179
});
180
});
181
182
if (moved) {
183
this.addRandomTile();
184
185
if (!this.movesAvailable()) {
186
this.over = true; // Game over!
187
}
188
189
this.actuate();
190
}
191
};
192
193
// Get the vector representing the chosen direction
194
GameManager.prototype.getVector = function (direction) {
195
// Vectors representing tile movement
196
var map = {
197
0: { x: 0, y: -1 }, // Up
198
1: { x: 1, y: 0 }, // Right
199
2: { x: 0, y: 1 }, // Down
200
3: { x: -1, y: 0 } // Left
201
};
202
203
return map[direction];
204
};
205
206
// Build a list of positions to traverse in the right order
207
GameManager.prototype.buildTraversals = function (vector) {
208
var traversals = { x: [], y: [] };
209
210
for (var pos = 0; pos < this.size; pos++) {
211
traversals.x.push(pos);
212
traversals.y.push(pos);
213
}
214
215
// Always traverse from the farthest cell in the chosen direction
216
if (vector.x === 1) traversals.x = traversals.x.reverse();
217
if (vector.y === 1) traversals.y = traversals.y.reverse();
218
219
return traversals;
220
};
221
222
GameManager.prototype.findFarthestPosition = function (cell, vector) {
223
var previous;
224
225
// Progress towards the vector direction until an obstacle is found
226
do {
227
previous = cell;
228
cell = { x: previous.x + vector.x, y: previous.y + vector.y };
229
} while (this.grid.withinBounds(cell) &&
230
this.grid.cellAvailable(cell));
231
232
return {
233
farthest: previous,
234
next: cell // Used to check if a merge is required
235
};
236
};
237
238
GameManager.prototype.movesAvailable = function () {
239
return this.grid.cellsAvailable() || this.tileMatchesAvailable();
240
};
241
242
// Check for available matches between tiles (more expensive check)
243
GameManager.prototype.tileMatchesAvailable = function () {
244
var self = this;
245
246
var tile;
247
248
for (var x = 0; x < this.size; x++) {
249
for (var y = 0; y < this.size; y++) {
250
tile = this.grid.cellContent({ x: x, y: y });
251
252
if (tile) {
253
for (var direction = 0; direction < 4; direction++) {
254
var vector = self.getVector(direction);
255
var cell = { x: x + vector.x, y: y + vector.y };
256
257
var other = self.grid.cellContent(cell);
258
259
if (other && other.value === tile.value) {
260
return true; // These two tiles can be merged
261
}
262
}
263
}
264
}
265
}
266
267
return false;
268
};
269
270
GameManager.prototype.positionsEqual = function (first, second) {
271
return first.x === second.x && first.y === second.y;
272
};
273
274