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 += " \n"; + for (let columnIndex = 0; columnIndex < gameBoard.numColumns; columnIndex++) { + const tile = gameBoard.getTile(rowIndex, columnIndex); + text += " \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
" + tile.value + "" + tile.value + "