diff --git a/main.js b/main.js
index 70846ed..896ef6e 100644
--- a/main.js
+++ b/main.js
@@ -6,141 +6,63 @@ const Direction = Object.freeze({
get values() { return Object.values(Direction); },
fromName(name) { return this.values.filter(i=>i.name == name)[0] || null; },
});
-const globals = {
- grid: null,
-};
+
+let gameBoard;
const btnStart_onClick = () => {
- globals.grid = new Grid(4, 4, new Tile(0, false));
- addRandomTile();
- // globals.grid = makeDevGrid([ // can't slide down or left
- // [2,1,3,4],
- // [3,5,7,9],
- // [1,2,1,2],
- // [3,4,5,0],
- // ]);
- redraw(globals.grid);
+ gameBoard = new GameBoard(new Grid(4, 4));
+ // gameBoard = new GameBoard(
+ // makeDevGrid([ // can't slide down or left
+ // [2,1,3,4],
+ // [3,5,7,9],
+ // [1,2,1,2],
+ // [3,4,5,0],
+ // ])
+ // );
+ gameBoard.addRandomTile();
+ redraw(gameBoard);
}
const btnRedraw_onClick = () => {
- redraw(globals.grid);
+ redraw(gameBoard);
}
-const getSettingsForDirection = (direction) => {
- let settings = {
- merge: (from, to) => {
- to.value++;
- from.value = 0;
- from.isVisible = false;
- }
- };
-
- if (direction === Direction.Up || direction === Direction.Left) {
- settings = {
- ...settings,
- advance: i => i + 1,
- testForDone: (fromIndex, length) => fromIndex >= length,
- getInitialFromIndex: () => 1,
- getInitialToIndex: () => 0
- };
- } else if (direction === Direction.Down || direction === Direction.Right) {
- settings = {
- ...settings,
- advance: i => i - 1,
- testForDone: fromIndex => fromIndex < 0,
- getInitialFromIndex: (length) => length - 2,
- getInitialToIndex: (length) => length - 1
- };
- }
-
- if (direction === Direction.Up || direction === Direction.Down) {
- settings = {
- ...settings,
- getVector: (grid, index) => grid.getColumn(index),
- swap: (grid, vector, columnIndex, from, to) => {
- grid.swapTiles(from, columnIndex, to, columnIndex);
- const tempTile = vector[to];
- vector[to] = vector[from];
- vector[from] = tempTile;
- },
- getLength: (grid) => grid.numColumns
- }
- } else if (direction === Direction.Left || direction === Direction.Right) {
- settings = {
- ...settings,
- getVector: (grid, index) => grid.getRow(index),
- swap: (grid, vector, rowIndex, from, to) => {
- grid.swapTiles(rowIndex, from, rowIndex, to);
- const tempTile = vector[to];
- vector[to] = vector[from];
- vector[from] = tempTile;
- },
- getLength: (grid) => grid.numRows
- }
- }
-
- return settings;
-}
-
-const slideInDirection = (grid, direction) => {
- const settings = getSettingsForDirection(direction);
- const length = settings.getLength(grid);
- let didChange = false;
-
- for (let index = 0; index < length; index++) {
- const vector = settings.getVector(grid, index);
- const swap = settings.swap.bind(this, grid, vector, index);
- const initialFromIndex = settings.getInitialFromIndex(vector.length);
- const initialToIndex = settings.getInitialToIndex(vector.length);
- const didChangeThisPass = slide(
- initialFromIndex,
- initialToIndex,
- vector,
- settings.advance,
- swap,
- settings.merge,
- settings.testForDone
- );
- didChange = didChange || didChangeThisPass;
- }
- return didChange;
-}
-
-const playMove = (grid, direction) => {
- if (slideInDirection(grid, direction)) {
- addRandomTile();
- redraw(grid)
+// TODO(tom): This should take GameBoard instead of Grid or be moved into GameBoard.
+const playMove = (gameBoard, direction) => {
+ if (gameBoard.slideInDirection(direction)) {
+ gameBoard.addRandomTile();
+ redraw(gameBoard)
} else {
- alert(`Unable to slide ${direction.name}.`);
+ // alert(`Unable to slide ${direction.name}.`);
}
- checkForEnd(grid);
+ checkForEnd(gameBoard);
}
const btnUp_onClick = () => {
- playMove(globals.grid, Direction.Up);
+ playMove(gameBoard, Direction.Up);
}
const btnDown_onClick = () => {
- playMove(globals.grid, Direction.Down);
+ playMove(gameBoard, Direction.Down);
}
const btnLeft_onClick = () => {
- playMove(globals.grid, Direction.Left);
+ playMove(gameBoard, Direction.Left);
}
const btnRight_onClick = () => {
- playMove(globals.grid, Direction.Right);
+ playMove(gameBoard, Direction.Right);
}
-const drawGrid = (grid, element) => {
- // grid is our grid
- // element is our div
+const redraw = (gameBoard) => {
+ const element = document.getElementById('output');
+
let text = "
\n";
- for (let rowIndex = 0; rowIndex < grid.numRows; rowIndex++) {
+ for (let rowIndex = 0; rowIndex < gameBoard.numRows; rowIndex++) {
text += " \n";
- for (let columnIndex = 0; columnIndex < grid.numColumns; columnIndex++) {
- const tile = grid.getTile(rowIndex, columnIndex);
- text += " | " + tile.value + " | \n";
+ for (let columnIndex = 0; columnIndex < gameBoard.numColumns; columnIndex++) {
+ const tile = gameBoard.getTile(rowIndex, columnIndex);
+ text += " " + tile.value + " | \n";
}
text += "
\n";
}
@@ -148,92 +70,8 @@ const drawGrid = (grid, element) => {
element.innerHTML = text;
}
-const addRandomTile = () => {
- const hiddenTiles = globals.grid.tiles.filter(tile => !tile.isVisible);
- if (hiddenTiles.length <=0) return;
- const index = Math.floor(Math.random() * hiddenTiles.length);
- const tile = hiddenTiles[index];
- tile.value = 1;
- tile.isVisible = true;
-}
-
-const redraw = (grid) => {
- const element = document.getElementById('output');
- drawGrid(grid, element);
-}
-
-const slide = (fromIndex, toIndex, vector, advance, swap, merge, testForDone) => {
- let didIDoIt = false;
- let hasMergedPrevious = false;
- while(!testForDone(fromIndex, vector.length)) {
- const fromTile = vector[fromIndex];
- const toTile = toIndex < vector.length && toIndex >= 0 ? vector[toIndex] : null;
- if (toTile == null) {
- // Advance toIndex
- toIndex = advance(toIndex);
- hasMergedPrevious = false;
- continue;
- }
- if (fromIndex == toIndex) {
- // Advance fromIndex
- fromIndex = advance(fromIndex);
- hasMergedPrevious = false;
- continue;
- }
- if (!toTile.isVisible) {
- if (fromTile.isVisible) {
- swap(fromIndex, toIndex);
- // Advance fromIndex
- fromIndex = advance(fromIndex);
- hasMergedPrevious = false;
- didIDoIt = true;
- } else {
- // Advance fromIndex
- fromIndex = advance(fromIndex);
- }
- } else {
- if (fromTile.isVisible) {
- if (fromTile.value == toTile.value) {
- if (hasMergedPrevious) {
- // Advance toIndex
- toIndex = advance(toIndex);
- hasMergedPrevious = false;
- didIDoIt = true;
- } else {
- // Merge tiles
- merge(fromTile, toTile);
- // Advance fromIndex
- fromIndex = advance(fromIndex);
- hasMergedPrevious = true;
- didIDoIt = true;
- }
- } else {
- // Advance toIndex
- toIndex = advance(toIndex);
- hasMergedPrevious = false;
- }
- } else {
- fromIndex = advance(fromIndex);
- }
- }
- }
- return didIDoIt;
-}
-
-const canSlideInDirection = (grid, direction) => {
- const clonedGrid = grid.clone();
- return slideInDirection(clonedGrid, direction);
-}
-
-const gameIsLost = (grid) => {
- return !canSlideInDirection(grid, Direction.Up)
- && !canSlideInDirection(grid, Direction.Right)
- && !canSlideInDirection(grid, Direction.Down)
- && !canSlideInDirection(grid, Direction.Left);
-}
-
-const checkForEnd = (grid) => {
- if (gameIsLost(grid)) {
+const checkForEnd = (gameBoard) => {
+ if (gameBoard.gameIsLost()) {
alert('A loser is you!');
}
}
@@ -241,8 +79,7 @@ const checkForEnd = (grid) => {
const makeDevGrid = (matrix) => {
const numRows = matrix.length;
const numColumns = matrix[0].length;
- const initialValue = new Tile(0, false, new GridPosition(0, 0));
- const grid = new Grid(numRows, numColumns, initialValue);
+ const grid = new Grid(numRows, numColumns);
for (let rowIndex = 0; rowIndex < numRows; rowIndex++) {
const row = matrix[rowIndex];
for (let colIndex = 0; colIndex < numColumns; colIndex++) {
@@ -275,45 +112,204 @@ up()
*/
class GameBoard {
- #grid_;
+ #grid;
- constructor() {
-
+ get numColumns() {
+ return this.#grid.numColumns;
}
+ get numRows() {
+ return this.#grid.numRows;
+ }
+
+ constructor(grid) {
+ this.#grid = grid;
+ }
+
+ getTile(row, column) {
+ return this.#grid.getTile(row, column);
+ }
+
+ addRandomTile() {
+ const hiddenTiles = this.#grid.tiles.filter(tile => !tile.isVisible);
+ if (hiddenTiles.length <= 0) { return; }
+ const randomIndex = Math.floor(Math.random() * hiddenTiles.length);
+ const tile = hiddenTiles[randomIndex];
+ // TODO(tom): Make this random of either 1 or 2 and heavily weight 1.
+ tile.value = 1;
+ tile.isVisible = true;
+ }
+
+ slideInDirection (direction) {
+ return GameBoard.#slideInDirection_impl(direction, this.#grid);
+ }
+
+ gameIsLost() {
+ return !this.#canSlideInDirection(Direction.Up)
+ && !this.#canSlideInDirection(Direction.Right)
+ && !this.#canSlideInDirection(Direction.Down)
+ && !this.#canSlideInDirection(Direction.Left);
+ }
+
+ #canSlideInDirection(direction) {
+ const clonedGrid = this.#grid.clone();
+ return GameBoard.#slideInDirection_impl(direction, clonedGrid);
+ }
+
+ static #slideInDirection_impl (direction, grid) {
+ const settings = GameBoard.#getSettingsForDirection(direction);
+ const length = settings.getLength(grid);
+ let didChange = false;
+
+ for (let index = 0; index < length; index++) {
+ const vector = settings.getVector(grid, index);
+ const swap = settings.swap.bind(this, grid, vector, index);
+ const initialFromIndex = settings.getInitialFromIndex(vector.length);
+ const initialToIndex = settings.getInitialToIndex(vector.length);
+ const didChangeThisPass = GameBoard.#slide(
+ initialFromIndex,
+ initialToIndex,
+ vector,
+ settings.advance,
+ swap,
+ settings.merge,
+ settings.testForDone
+ );
+ didChange = didChange || didChangeThisPass;
+ }
+ return didChange;
+ }
+
+ static #getSettingsForDirection(direction) {
+ const settings = new Settings();
+ settings.merge = (from, to) => {
+ to.value++;
+ from.value = 0;
+ from.isVisible = false;
+ };
+
+ if (direction === Direction.Up || direction === Direction.Left) {
+ settings.advance = i => i + 1;
+ settings.testForDone = (fromIndex, length) => fromIndex >= length;
+ settings.getInitialFromIndex = () => 1;
+ settings.getInitialToIndex = () => 0;
+ } else if (direction === Direction.Down || direction === Direction.Right) {
+ settings.advance = i => i - 1;
+ settings.testForDone = fromIndex => fromIndex < 0;
+ settings.getInitialFromIndex = (length) => length - 2;
+ settings.getInitialToIndex = (length) => length - 1;
+ }
+
+ if (direction === Direction.Up || direction === Direction.Down) {
+ settings.getVector = (grid, index) => grid.getColumn(index);
+ settings.swap = (grid, vector, columnIndex, from, to) => {
+ grid.swapTiles(from, columnIndex, to, columnIndex);
+ const tempTile = vector[to];
+ vector[to] = vector[from];
+ vector[from] = tempTile;
+ }
+ settings.getLength = grid => grid.numColumns;
+ } else if (direction === Direction.Left || direction === Direction.Right) {
+ settings.getVector = (grid, index) => grid.getRow(index);
+ settings.swap = (grid, vector, rowIndex, from, to) => {
+ grid.swapTiles(rowIndex, from, rowIndex, to);
+ const tempTile = vector[to];
+ vector[to] = vector[from];
+ vector[from] = tempTile;
+ };
+ settings.getLength = grid => grid.numRows;
+ }
+ return settings;
+ }
+
+ static #slide(fromIndex, toIndex, vector, advance, swap, merge, testForDone) {
+ let didChange = false;
+ let hasMergedPrevious = false;
+ while(!testForDone(fromIndex, vector.length)) {
+ const fromTile = vector[fromIndex];
+ const toTile = toIndex < vector.length && toIndex >= 0 ? vector[toIndex] : null;
+ if (toTile == null) {
+ // Advance toIndex
+ toIndex = advance(toIndex);
+ hasMergedPrevious = false;
+ continue;
+ }
+ if (fromIndex == toIndex) {
+ // Advance fromIndex
+ fromIndex = advance(fromIndex);
+ hasMergedPrevious = false;
+ continue;
+ }
+ if (!toTile.isVisible) {
+ if (fromTile.isVisible) {
+ swap(fromIndex, toIndex);
+ // Advance fromIndex
+ fromIndex = advance(fromIndex);
+ hasMergedPrevious = false;
+ didChange = true;
+ } else {
+ // Advance fromIndex
+ fromIndex = advance(fromIndex);
+ }
+ } else {
+ if (fromTile.isVisible) {
+ if (fromTile.value == toTile.value) {
+ if (hasMergedPrevious) {
+ // Advance toIndex
+ toIndex = advance(toIndex);
+ hasMergedPrevious = false;
+ didChange = true;
+ } else {
+ // Merge tiles
+ merge(fromTile, toTile);
+ // Advance fromIndex
+ fromIndex = advance(fromIndex);
+ hasMergedPrevious = true;
+ didChange = true;
+ }
+ } else {
+ // Advance toIndex
+ toIndex = advance(toIndex);
+ hasMergedPrevious = false;
+ }
+ } else {
+ fromIndex = advance(fromIndex);
+ }
+ }
+ }
+ return didChange;
+ }
};
class Grid {
- #tiles_ = [];
- #rows_ = [];
- #numRows_;
- #numColumns_;
- get tiles() { return [...this.#tiles_]; }
- get numRows() { return this.#numRows_; }
- get numColumns() { return this.#numColumns_; }
+ #tiles = [];
+ #rows = [];
+ #numRows;
+ #numColumns;
+ get tiles() { return [...this.#tiles]; }
+ get numRows() { return this.#numRows; }
+ get numColumns() { return this.#numColumns; }
- constructor(numRows, numColumns, initialValue) {
- this.#numRows_ = numRows;
- this.#numColumns_ = numColumns;
+ constructor(numRows, numColumns) {
+ this.#numRows = numRows;
+ this.#numColumns = numColumns;
for (let rowIndex = 0; rowIndex < numRows; rowIndex++) {
const row = [];
for (let columnIndex = 0; columnIndex < this.numColumns; columnIndex++) {
- const position = new GridPosition(rowIndex, columnIndex);
- const tile = new Tile(initialValue.value, initialValue.isVisible, position);
- this.#tiles_.push(tile);
+ const tile = new Tile();
+ this.#tiles.push(tile);
row.push(tile);
}
- this.#rows_.push(row);
+ this.#rows.push(row);
}
}
clone() {
- const newGrid = new Grid(this.numRows, this.numColumns, new Tile(0, false, new GridPosition(0, 0)));
+ const newGrid = new Grid(this.numRows, this.numColumns);
for (let rowIndex = 0; rowIndex < this.numRows; rowIndex++) {
for (let columnIndex = 0; columnIndex < this.numColumns; columnIndex++) {
const newTile = newGrid.getTile(rowIndex, columnIndex);
const oldTile = this.getTile(rowIndex, columnIndex);
- // TODO(tom): Set the originalPosition of newTile to be the same as oldTile.
newTile.value = oldTile.value;
newTile.isVisible = oldTile.isVisible;
}
@@ -340,7 +336,7 @@ class Grid {
getTile (row, column) {
if (row >= 0 && row < this.numRows && column >= 0 && column < this.numColumns) {
// TODO(tom): Check that this is the right order.
- return this.#rows_[column][row]
+ return this.#rows[column][row]
}
return null;
}
@@ -348,7 +344,7 @@ class Grid {
setTile (row, column, tile) {
if (row >= 0 && row < this.numRows && column >= 0 && column < this.numColumns) {
// TODO(tom): Check that this is the right order.
- this.#rows_[column][row] = tile;
+ this.#rows[column][row] = tile;
}
}
@@ -366,52 +362,56 @@ class Grid {
}
toString (format = t => t.value) {
- return `[[${t(this.getTile(0, 0))},${t(this.getTile(0, 1))},${t(this.getTile(0, 2))},${t(this.getTile(0, 3))}],
- [${t(this.getTile(1, 0))},${t(this.getTile(1, 1))},${t(this.getTile(1, 2))},${t(this.getTile(1, 3))}],
- [${t(this.getTile(2, 0))},${t(this.getTile(2, 1))},${t(this.getTile(2, 2))},${t(this.getTile(2, 3))}],
- [${t(this.getTile(3, 0))},${t(this.getTile(3, 1))},${t(this.getTile(3, 2))},${t(this.getTile(3, 3))}]]`;
+ return `[[${format(this.getTile(0, 0))},${format(this.getTile(0, 1))},${format(this.getTile(0, 2))},${format(this.getTile(0, 3))}],
+ [${format(this.getTile(1, 0))},${format(this.getTile(1, 1))},${format(this.getTile(1, 2))},${format(this.getTile(1, 3))}],
+ [${format(this.getTile(2, 0))},${format(this.getTile(2, 1))},${format(this.getTile(2, 2))},${format(this.getTile(2, 3))}],
+ [${format(this.getTile(3, 0))},${format(this.getTile(3, 1))},${format(this.getTile(3, 2))},${format(this.getTile(3, 3))}]]`;
}
};
class GridPosition {
- #row_;
- #column_;
+ #row;
+ #column;
constructor (row, column) {
- this.#row_ = row || 0;
- this.#column_ = column || 0;
+ this.#row = row || 0;
+ this.#column = column || 0;
}
- get row() { return this.#row_; }
- get column() { return this.#column_; }
+ get row() { return this.#row; }
+ get column() { return this.#column; }
equals (other) {
return other !== null && other !== undefined
- && this.#row_ === other.#row_
- && this.#column_ === other.#column_;
+ && this.#row === other.#row
+ && this.#column === other.#column;
}
toString() {
- return `{ row: ${this.#row_}, column: ${this.#column_} }`;
+ return `{ row: ${this.#row}, column: ${this.#column} }`;
}
}
class Tile {
- #originalPosition_;
value;
isVisible;
- get originalPosition() {
- return {...this.#originalPosition_};
- }
- constructor (value, isVisible, originalPosition = new GridPosition(0, 0)) {
- this.value = value;
- this.isVisible = isVisible;
- this.originalPosition_ = {...originalPosition};
+ constructor () {
+ this.value = 0;
+ this.isVisible = false;
}
equals (other) {
return other !== null && other !== undefined
&& this.value === other.value
- && this.isVisible === other.isVisible
- && this.#originalPosition_.row === other.#originalPosition_.row
- && this.#originalPosition_.column === other.#originalPosition_.column;
+ && this.isVisible === other.isVisible;
}
toString() {
- return `{ value: ${this.value}, originalPosition: { row: ${this.#originalPosition_.row}, column: ${this.#originalPosition_.column} }, isVisible: ${this.isVisible ? "true" : "false"} }`;
+ return `{ value: ${this.value}, isVisible: ${this.isVisible ? "true" : "false"} }`;
}
};
+
+class Settings {
+ merge;
+ advance;
+ testForDone;
+ getInitialFromIndex;
+ getInitialToIndex;
+ getVector;
+ getLength;
+ swap;
+};
\ No newline at end of file