mirror of https://github.com/BRAVO68WEB/2048.git
Fixes: Random Fixes
This commit is contained in:
parent
9533666bde
commit
59a3d06b87
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"compile-hero.disable-compile-files-on-did-save-code": false
|
||||
}
|
|
@ -0,0 +1,585 @@
|
|||
"use strict";
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// Wait till the browser is ready to render the game (avoids glitches)
|
||||
window.requestAnimationFrame(function () {
|
||||
var manager = new GameManager(4, KeyboardInputManager, HTMLActuator);
|
||||
});
|
||||
});
|
||||
|
||||
if ("serviceWorker" in navigator) {
|
||||
window.addEventListener("load", function () {
|
||||
navigator.serviceWorker.register("./sw.js").then(function (registration) {
|
||||
// Registration was successful
|
||||
console.log("ServiceWorker registration successful with scope: ", registration.scope);
|
||||
}, function (err) {
|
||||
// registration failed :(
|
||||
console.log("ServiceWorker registration failed: ", err);
|
||||
});
|
||||
});
|
||||
} // (function () {
|
||||
// //If serviceWorker supports, then register it.
|
||||
// if ("serviceWorker" in navigator) {
|
||||
// navigator.serviceWorker
|
||||
// .register("./service-worker.js", { scope: "./" }) //setting scope of sw
|
||||
// .then(function (registration) {
|
||||
// console.info("Service worker is registered!");
|
||||
// checkForPageUpdate(registration); // To check if new content is updated or not
|
||||
// })
|
||||
// .catch(function (error) {
|
||||
// console.error("Service worker failed ", error);
|
||||
// });
|
||||
// }
|
||||
// // To content update on service worker state change
|
||||
// function checkForPageUpdate(registration) {
|
||||
// // onupdatefound will fire on first time install and when serviceWorker.js file changes
|
||||
// registration.addEventListener("updatefound", function () {
|
||||
// // To check if service worker is already installed and controlling the page or not
|
||||
// if (navigator.serviceWorker.controller) {
|
||||
// var installingSW = registration.installing;
|
||||
// installingSW.onstatechange = function () {
|
||||
// console.info("Service Worker State :", installingSW.state);
|
||||
// switch (installingSW.state) {
|
||||
// case "installed":
|
||||
// // Now new contents will be added to cache and old contents will be remove so
|
||||
// // this is perfect time to show user that page content is updated.
|
||||
// toast("Site is updated. Refresh the page.", 5000);
|
||||
// break;
|
||||
// case "redundant":
|
||||
// throw new Error(
|
||||
// "The installing service worker became redundant."
|
||||
// );
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// })();
|
||||
|
||||
|
||||
function GameManager(size, InputManager, Actuator) {
|
||||
this.size = size; // Size of the grid
|
||||
|
||||
this.inputManager = new InputManager();
|
||||
this.actuator = new Actuator();
|
||||
this.startTiles = 2;
|
||||
this.inputManager.on("move", this.move.bind(this));
|
||||
this.inputManager.on("restart", this.restart.bind(this));
|
||||
this.setup();
|
||||
} // Restart the game
|
||||
|
||||
|
||||
GameManager.prototype.restart = function () {
|
||||
this.actuator.restart();
|
||||
this.setup();
|
||||
}; // Set up the game
|
||||
|
||||
|
||||
GameManager.prototype.setup = function () {
|
||||
this.grid = new Grid(this.size);
|
||||
this.score = 0;
|
||||
this.over = false;
|
||||
this.won = false; // Add the initial tiles
|
||||
|
||||
this.addStartTiles(); // Update the actuator
|
||||
|
||||
this.actuate();
|
||||
}; // Set up the initial tiles to start the game with
|
||||
|
||||
|
||||
GameManager.prototype.addStartTiles = function () {
|
||||
for (var i = 0; i < this.startTiles; i++) {
|
||||
this.addRandomTile();
|
||||
}
|
||||
}; // Adds a tile in a random position
|
||||
|
||||
|
||||
GameManager.prototype.addRandomTile = function () {
|
||||
if (this.grid.cellsAvailable()) {
|
||||
var value = Math.random() < 0.9 ? 2 : 4;
|
||||
var tile = new Tile(this.grid.randomAvailableCell(), value);
|
||||
this.grid.insertTile(tile);
|
||||
}
|
||||
}; // Sends the updated grid to the actuator
|
||||
|
||||
|
||||
GameManager.prototype.actuate = function () {
|
||||
this.actuator.actuate(this.grid, {
|
||||
score: this.score,
|
||||
over: this.over,
|
||||
won: this.won
|
||||
});
|
||||
}; // Save all tile positions and remove merger info
|
||||
|
||||
|
||||
GameManager.prototype.prepareTiles = function () {
|
||||
this.grid.eachCell(function (x, y, tile) {
|
||||
if (tile) {
|
||||
tile.mergedFrom = null;
|
||||
tile.savePosition();
|
||||
}
|
||||
});
|
||||
}; // Move a tile and its representation
|
||||
|
||||
|
||||
GameManager.prototype.moveTile = function (tile, cell) {
|
||||
this.grid.cells[tile.x][tile.y] = null;
|
||||
this.grid.cells[cell.x][cell.y] = tile;
|
||||
tile.updatePosition(cell);
|
||||
}; // Move tiles on the grid in the specified direction
|
||||
|
||||
|
||||
GameManager.prototype.move = function (direction) {
|
||||
// 0: up, 1: right, 2:down, 3: left
|
||||
var self = this;
|
||||
if (this.over || this.won) return; // Don't do anything if the game's over
|
||||
|
||||
var cell, tile;
|
||||
var vector = this.getVector(direction);
|
||||
var traversals = this.buildTraversals(vector);
|
||||
var moved = false; // Save the current tile positions and remove merger information
|
||||
|
||||
this.prepareTiles(); // Traverse the grid in the right direction and move tiles
|
||||
|
||||
traversals.x.forEach(function (x) {
|
||||
traversals.y.forEach(function (y) {
|
||||
cell = {
|
||||
x: x,
|
||||
y: y
|
||||
};
|
||||
tile = self.grid.cellContent(cell);
|
||||
|
||||
if (tile) {
|
||||
var positions = self.findFarthestPosition(cell, vector);
|
||||
var next = self.grid.cellContent(positions.next); // Only one merger per row traversal?
|
||||
|
||||
if (next && next.value === tile.value && !next.mergedFrom) {
|
||||
var merged = new Tile(positions.next, tile.value * 2);
|
||||
merged.mergedFrom = [tile, next];
|
||||
self.grid.insertTile(merged);
|
||||
self.grid.removeTile(tile); // Converge the two tiles' positions
|
||||
|
||||
tile.updatePosition(positions.next); // Update the score
|
||||
|
||||
self.score += merged.value; // The mighty 2048 tile
|
||||
|
||||
if (merged.value === 2048) self.won = true;
|
||||
} else {
|
||||
self.moveTile(tile, positions.farthest);
|
||||
}
|
||||
|
||||
if (!self.positionsEqual(cell, tile)) {
|
||||
moved = true; // The tile moved from its original cell!
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (moved) {
|
||||
this.addRandomTile();
|
||||
|
||||
if (!this.movesAvailable()) {
|
||||
this.over = true; // Game over!
|
||||
}
|
||||
|
||||
this.actuate();
|
||||
}
|
||||
}; // Get the vector representing the chosen direction
|
||||
|
||||
|
||||
GameManager.prototype.getVector = function (direction) {
|
||||
// Vectors representing tile movement
|
||||
var map = {
|
||||
0: {
|
||||
x: 0,
|
||||
y: -1
|
||||
},
|
||||
// up
|
||||
1: {
|
||||
x: 1,
|
||||
y: 0
|
||||
},
|
||||
// right
|
||||
2: {
|
||||
x: 0,
|
||||
y: 1
|
||||
},
|
||||
// down
|
||||
3: {
|
||||
x: -1,
|
||||
y: 0
|
||||
} // left
|
||||
|
||||
};
|
||||
return map[direction];
|
||||
}; // Build a list of positions to traverse in the right order
|
||||
|
||||
|
||||
GameManager.prototype.buildTraversals = function (vector) {
|
||||
var traversals = {
|
||||
x: [],
|
||||
y: []
|
||||
};
|
||||
|
||||
for (var pos = 0; pos < this.size; pos++) {
|
||||
traversals.x.push(pos);
|
||||
traversals.y.push(pos);
|
||||
} // Always traverse from the farthest cell in the chosen direction
|
||||
|
||||
|
||||
if (vector.x === 1) traversals.x = traversals.x.reverse();
|
||||
if (vector.y === 1) traversals.y = traversals.y.reverse();
|
||||
return traversals;
|
||||
};
|
||||
|
||||
GameManager.prototype.findFarthestPosition = function (cell, vector) {
|
||||
var previous; // Progress towards the vector direction until an obstacle is found
|
||||
|
||||
do {
|
||||
previous = cell;
|
||||
cell = {
|
||||
x: previous.x + vector.x,
|
||||
y: previous.y + vector.y
|
||||
};
|
||||
} while (this.grid.withinBounds(cell) && this.grid.cellAvailable(cell));
|
||||
|
||||
return {
|
||||
farthest: previous,
|
||||
next: cell // Used to check if a merge is required
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
GameManager.prototype.movesAvailable = function () {
|
||||
return this.grid.cellsAvailable() || this.tileMatchesAvailable();
|
||||
}; // Check for available matches between tiles (more expensive check)
|
||||
|
||||
|
||||
GameManager.prototype.tileMatchesAvailable = function () {
|
||||
var self = this;
|
||||
var tile;
|
||||
|
||||
for (var x = 0; x < this.size; x++) {
|
||||
for (var y = 0; y < this.size; y++) {
|
||||
tile = this.grid.cellContent({
|
||||
x: x,
|
||||
y: y
|
||||
});
|
||||
|
||||
if (tile) {
|
||||
for (var direction = 0; direction < 4; direction++) {
|
||||
var vector = self.getVector(direction);
|
||||
var cell = {
|
||||
x: x + vector.x,
|
||||
y: y + vector.y
|
||||
};
|
||||
var other = self.grid.cellContent(cell);
|
||||
|
||||
if (other) {}
|
||||
|
||||
if (other && other.value === tile.value) {
|
||||
return true; // These two tiles can be merged
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
GameManager.prototype.positionsEqual = function (first, second) {
|
||||
return first.x === second.x && first.y === second.y;
|
||||
};
|
||||
|
||||
function Grid(size) {
|
||||
this.size = size;
|
||||
this.cells = [];
|
||||
this.build();
|
||||
} // Build a grid of the specified size
|
||||
|
||||
|
||||
Grid.prototype.build = function () {
|
||||
for (var x = 0; x < this.size; x++) {
|
||||
var row = this.cells[x] = [];
|
||||
|
||||
for (var y = 0; y < this.size; y++) {
|
||||
row.push(null);
|
||||
}
|
||||
}
|
||||
}; // Find the first available random position
|
||||
|
||||
|
||||
Grid.prototype.randomAvailableCell = function () {
|
||||
var cells = this.availableCells();
|
||||
|
||||
if (cells.length) {
|
||||
return cells[Math.floor(Math.random() * cells.length)];
|
||||
}
|
||||
};
|
||||
|
||||
Grid.prototype.availableCells = function () {
|
||||
var cells = [];
|
||||
this.eachCell(function (x, y, tile) {
|
||||
if (!tile) {
|
||||
cells.push({
|
||||
x: x,
|
||||
y: y
|
||||
});
|
||||
}
|
||||
});
|
||||
return cells;
|
||||
}; // Call callback for every cell
|
||||
|
||||
|
||||
Grid.prototype.eachCell = function (callback) {
|
||||
for (var x = 0; x < this.size; x++) {
|
||||
for (var y = 0; y < this.size; y++) {
|
||||
callback(x, y, this.cells[x][y]);
|
||||
}
|
||||
}
|
||||
}; // Check if there are any cells available
|
||||
|
||||
|
||||
Grid.prototype.cellsAvailable = function () {
|
||||
return !!this.availableCells().length;
|
||||
}; // Check if the specified cell is taken
|
||||
|
||||
|
||||
Grid.prototype.cellAvailable = function (cell) {
|
||||
return !this.cellOccupied(cell);
|
||||
};
|
||||
|
||||
Grid.prototype.cellOccupied = function (cell) {
|
||||
return !!this.cellContent(cell);
|
||||
};
|
||||
|
||||
Grid.prototype.cellContent = function (cell) {
|
||||
if (this.withinBounds(cell)) {
|
||||
return this.cells[cell.x][cell.y];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}; // Inserts a tile at its position
|
||||
|
||||
|
||||
Grid.prototype.insertTile = function (tile) {
|
||||
this.cells[tile.x][tile.y] = tile;
|
||||
};
|
||||
|
||||
Grid.prototype.removeTile = function (tile) {
|
||||
this.cells[tile.x][tile.y] = null;
|
||||
};
|
||||
|
||||
Grid.prototype.withinBounds = function (position) {
|
||||
return position.x >= 0 && position.x < this.size && position.y >= 0 && position.y < this.size;
|
||||
};
|
||||
|
||||
function HTMLActuator() {
|
||||
this.tileContainer = document.getElementsByClassName("tile-container")[0];
|
||||
this.scoreContainer = document.getElementsByClassName("score-container")[0];
|
||||
this.messageContainer = document.getElementsByClassName("game-message")[0];
|
||||
this.score = 0;
|
||||
}
|
||||
|
||||
HTMLActuator.prototype.actuate = function (grid, metadata) {
|
||||
var self = this;
|
||||
window.requestAnimationFrame(function () {
|
||||
self.clearContainer(self.tileContainer);
|
||||
grid.cells.forEach(function (column) {
|
||||
column.forEach(function (cell) {
|
||||
if (cell) {
|
||||
self.addTile(cell);
|
||||
}
|
||||
});
|
||||
});
|
||||
self.updateScore(metadata.score);
|
||||
if (metadata.over) self.message(false); // You lose
|
||||
|
||||
if (metadata.won) self.message(true); // You win!
|
||||
});
|
||||
};
|
||||
|
||||
HTMLActuator.prototype.restart = function () {
|
||||
this.clearMessage();
|
||||
};
|
||||
|
||||
HTMLActuator.prototype.clearContainer = function (container) {
|
||||
while (container.firstChild) {
|
||||
container.removeChild(container.firstChild);
|
||||
}
|
||||
};
|
||||
|
||||
HTMLActuator.prototype.addTile = function (tile) {
|
||||
var self = this;
|
||||
var element = document.createElement("div");
|
||||
var position = tile.previousPosition || {
|
||||
x: tile.x,
|
||||
y: tile.y
|
||||
};
|
||||
positionClass = this.positionClass(position); // We can't use classlist because it somehow glitches when replacing classes
|
||||
|
||||
var classes = ["tile", "tile-" + tile.value, positionClass];
|
||||
this.applyClasses(element, classes);
|
||||
element.textContent = tile.value;
|
||||
|
||||
if (tile.previousPosition) {
|
||||
// Make sure that the tile gets rendered in the previous position first
|
||||
window.requestAnimationFrame(function () {
|
||||
classes[2] = self.positionClass({
|
||||
x: tile.x,
|
||||
y: tile.y
|
||||
});
|
||||
self.applyClasses(element, classes); // Update the position
|
||||
});
|
||||
} else if (tile.mergedFrom) {
|
||||
classes.push("tile-merged");
|
||||
this.applyClasses(element, classes); // Render the tiles that merged
|
||||
|
||||
tile.mergedFrom.forEach(function (merged) {
|
||||
self.addTile(merged);
|
||||
});
|
||||
} else {
|
||||
classes.push("tile-new");
|
||||
this.applyClasses(element, classes);
|
||||
} // Put the tile on the board
|
||||
|
||||
|
||||
this.tileContainer.appendChild(element);
|
||||
};
|
||||
|
||||
HTMLActuator.prototype.applyClasses = function (element, classes) {
|
||||
element.setAttribute("class", classes.join(" "));
|
||||
};
|
||||
|
||||
HTMLActuator.prototype.normalizePosition = function (position) {
|
||||
return {
|
||||
x: position.x + 1,
|
||||
y: position.y + 1
|
||||
};
|
||||
};
|
||||
|
||||
HTMLActuator.prototype.positionClass = function (position) {
|
||||
position = this.normalizePosition(position);
|
||||
return "tile-position-" + position.x + "-" + position.y;
|
||||
};
|
||||
|
||||
HTMLActuator.prototype.updateScore = function (score) {
|
||||
this.clearContainer(this.scoreContainer);
|
||||
var difference = score - this.score;
|
||||
this.score = score;
|
||||
this.scoreContainer.textContent = this.score;
|
||||
|
||||
if (difference > 0) {
|
||||
var addition = document.createElement("div");
|
||||
addition.classList.add("score-addition");
|
||||
addition.textContent = "+" + difference;
|
||||
this.scoreContainer.appendChild(addition);
|
||||
}
|
||||
};
|
||||
|
||||
HTMLActuator.prototype.message = function (won) {
|
||||
var type = won ? "game-won" : "game-over";
|
||||
var message = won ? "You win!" : "Game over!"; // if (ga) ga("send", "event", "game", "end", type, this.score);
|
||||
|
||||
this.messageContainer.classList.add(type);
|
||||
this.messageContainer.getElementsByTagName("p")[0].textContent = message;
|
||||
};
|
||||
|
||||
HTMLActuator.prototype.clearMessage = function () {
|
||||
this.messageContainer.classList.remove("game-won", "game-over");
|
||||
};
|
||||
|
||||
function KeyboardInputManager() {
|
||||
this.events = {};
|
||||
this.listen();
|
||||
}
|
||||
|
||||
KeyboardInputManager.prototype.on = function (event, callback) {
|
||||
if (!this.events[event]) {
|
||||
this.events[event] = [];
|
||||
}
|
||||
|
||||
this.events[event].push(callback);
|
||||
};
|
||||
|
||||
KeyboardInputManager.prototype.emit = function (event, data) {
|
||||
var callbacks = this.events[event];
|
||||
|
||||
if (callbacks) {
|
||||
callbacks.forEach(function (callback) {
|
||||
callback(data);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
KeyboardInputManager.prototype.listen = function () {
|
||||
var self = this;
|
||||
var map = {
|
||||
38: 0,
|
||||
// Up
|
||||
39: 1,
|
||||
// Right
|
||||
40: 2,
|
||||
// Down
|
||||
37: 3,
|
||||
// Left
|
||||
75: 0,
|
||||
// vim keybindings
|
||||
76: 1,
|
||||
74: 2,
|
||||
72: 3
|
||||
};
|
||||
document.addEventListener("keydown", function (event) {
|
||||
var modifiers = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
||||
var mapped = map[event.which];
|
||||
|
||||
if (!modifiers) {
|
||||
if (mapped !== undefined) {
|
||||
event.preventDefault();
|
||||
self.emit("move", mapped);
|
||||
}
|
||||
|
||||
if (event.which === 32) self.restart.bind(self)(event);
|
||||
}
|
||||
});
|
||||
var retry = document.getElementsByClassName("retry-button")[0];
|
||||
retry.addEventListener("click", this.restart.bind(this)); // Listen to swipe events
|
||||
|
||||
var gestures = [Hammer.DIRECTION_UP, Hammer.DIRECTION_RIGHT, Hammer.DIRECTION_DOWN, Hammer.DIRECTION_LEFT];
|
||||
var gameContainer = document.getElementsByClassName("game-container")[0];
|
||||
var handler = Hammer(gameContainer, {
|
||||
drag_block_horizontal: true,
|
||||
drag_block_vertical: true
|
||||
});
|
||||
handler.on("swipe", function (event) {
|
||||
event.gesture.preventDefault();
|
||||
mapped = gestures.indexOf(event.gesture.direction);
|
||||
if (mapped !== -1) self.emit("move", mapped);
|
||||
});
|
||||
};
|
||||
|
||||
KeyboardInputManager.prototype.restart = function (event) {
|
||||
event.preventDefault();
|
||||
this.emit("restart");
|
||||
};
|
||||
|
||||
function Tile(position, value) {
|
||||
this.x = position.x;
|
||||
this.y = position.y;
|
||||
this.value = value || 2;
|
||||
this.previousPosition = null;
|
||||
this.mergedFrom = null; // Tracks tiles that merged together
|
||||
}
|
||||
|
||||
Tile.prototype.savePosition = function () {
|
||||
this.previousPosition = {
|
||||
x: this.x,
|
||||
y: this.y
|
||||
};
|
||||
};
|
||||
|
||||
Tile.prototype.updatePosition = function (position) {
|
||||
this.x = position.x;
|
||||
this.y = position.y;
|
||||
};
|
Loading…
Reference in New Issue