const Direction = Object.freeze({ Up: { name: "up" }, Right: { name: "right" }, Down: { name: "down" }, Left: { name: "left" }, get values() { return Object.values(Direction); }, fromName(name) { return this.values.filter(i=>i.name == name)[0] || null; }, }); const globals = { grid: null, }; const btnStart_onClick = () => { // globals.grid = makeGrid(4, 4, {value: 0, visible: false}); // addRandomTile(); globals.grid = makeDevGrid([ // can't slide down or left [1,0,0,0], [0,0,0,0], [0,0,0,0], [0,0,0,0], ]); redraw(globals.grid); } const btnRedraw_onClick = () => { redraw(globals.grid); } const doTheLoop = (advance, getVector, getFromIndex, getToIndex, testForDone, swap, merge, length) => { let didIDoIt = false; for (let index = 0; index < length; index++) { const vector = getVector(index); const swap2 = swap.bind(this, vector, index); const fromIndex = getFromIndex(vector.length); const toIndex = getToIndex(vector.length); const didChangeThisPass = slide(fromIndex, toIndex, vector, advance, swap2, merge, testForDone); didIDoIt = didIDoIt || didChangeThisPass; } return didIDoIt; } const slideVertical = (grid, direction) => { const advanceForward = i => i - 1; const advanceBackward = i => i + 1; const testForDoneForward = fromIndex => fromIndex < 0; const testForDoneBackward = (fromIndex, length) => fromIndex >= length; const getFromIndexForward = (length) => length - 2; const getFromIndexBackward = () => 1; const getToIndexForward = (length) => length - 1; const getToIndexBackward = () => 0; const getColumn = (index) => grid.getColumn(index); const getRow = (index) => grid.getRow(index); const swapInRow = (vector, rowIndex, from, to) => { grid.swapTiles(rowIndex, from, rowIndex, to); const tempTile = vector[to]; vector[to] = vector[from]; vector[from] = tempTile; }; const swapInColumn = (vector, columnIndex, from, to) => { grid.swapTiles(from, columnIndex, to, columnIndex); const tempTile = vector[to]; vector[to] = vector[from]; vector[from] = tempTile; } const merge = (from, to) => { to.value++; from.value = 0; from.visible = false; }; switch(direction) { case Direction.Up: { const advance = advanceBackward; const getVector = getColumn; const getFromIndex = getFromIndexBackward; const getToIndex = getToIndexBackward; const testForDone = testForDoneBackward; const swap = swapInColumn; const length = grid.numColumns; return doTheLoop(advance, getVector, getFromIndex, getToIndex, testForDone, swap, merge, length); } case Direction.Down: { const advance = advanceForward; const getVector = getColumn; const getFromIndex = getFromIndexForward; const getToIndex = getToIndexForward; const testForDone = testForDoneForward; const swap = swapInColumn; const length = grid.numColumns; return doTheLoop(advance, getVector, getFromIndex, getToIndex, testForDone, swap, merge, length); } } return false; } const slideHorizontal = (grid, direction) => { const advanceForward = i => i - 1; const advanceBackward = i => i + 1; const testForDoneForward = fromIndex => fromIndex < 0; const testForDoneBackward = (fromIndex, length) => fromIndex >= length; const getFromIndexForward = (length) => length - 2; const getFromIndexBackward = () => 1; const getToIndexForward = (length) => length - 1; const getToIndexBackward = () => 0; const getColumn = (index) => grid.getColumn(index); const getRow = (index) => grid.getRow(index); const swapInRow = (vector, rowIndex, from, to) => { grid.swapTiles(rowIndex, from, rowIndex, to); const tempTile = vector[to]; vector[to] = vector[from]; vector[from] = tempTile; }; const swapInColumn = (vector, columnIndex, from, to) => { grid.swapTiles(from, columnIndex, to, columnIndex); const tempTile = vector[to]; vector[to] = vector[from]; vector[from] = tempTile; } const merge = (from, to) => { to.value++; from.value = 0; from.visible = false; }; switch (direction) { case Direction.Left: { const advance = advanceBackward; const getVector = getRow; const getFromIndex = getFromIndexBackward; const getToIndex = getToIndexBackward; const testForDone = testForDoneBackward; const swap = swapInRow; const length = grid.numRows; return doTheLoop(advance, getVector, getFromIndex, getToIndex, testForDone, swap, merge, length); } case Direction.Right: { const advance = advanceForward; const getVector = getRow; const getFromIndex = getFromIndexForward; const getToIndex = getToIndexForward; const testForDone = testForDoneForward; const swap = swapInRow; const length = grid.numRows; return doTheLoop(advance, getVector, getFromIndex, getToIndex, testForDone, swap, merge, length); } } return false; } const slideInDirection = (grid, direction) => { switch(direction) { case Direction.Up: return slideVertical(grid, direction); case Direction.Down: return slideVertical(grid, direction); case Direction.Left: return slideHorizontal(grid, direction); case Direction.Right: return slideHorizontal(grid, direction); default: return false; } } const playMove = (grid, direction) => { if (slideInDirection(grid, direction)) { addRandomTile(); redraw(grid) } else { alert(`Unable to slide ${direction.name}.`); } // checkForEnd(grid); } const btnUp_onClick = () => { playMove(globals.grid, Direction.Up); } const btnDown_onClick = () => { playMove(globals.grid, Direction.Down); } const btnLeft_onClick = () => { playMove(globals.grid, Direction.Left); } const btnRight_onClick = () => { playMove(globals.grid, Direction.Right); } const drawGrid = (grid, element) => { // grid is our grid // element is our div let text = "\n"; for (let rowIndex = 0; rowIndex < grid.numRows; rowIndex++) { text += " \n"; for (let columnIndex = 0; columnIndex < grid.numColumns; columnIndex++) { const tile = grid.getTile(rowIndex, columnIndex); text += " \n"; } text += " \n"; } text += "
" + tile.value + "
"; element.innerHTML = text; } const makeGrid = (numRows, numColumns, initialValue) => { const tiles = []; const rows = []; for (rowIndex = 0; rowIndex < numRows; rowIndex++) { const row = []; for (columnIndex = 0; columnIndex < numColumns; columnIndex++) { position = {row: rowIndex, column: columnIndex}; const tile = makeTile(initialValue.value, initialValue.visible, rowIndex, columnIndex); tiles.push(tile); row.push(tile); } rows.push(row); } const getTile = (grid, row, column) => { return grid.getTile(row, column); } const grid = { rows, numRows, numColumns, tiles, clone: () => { const newGrid = makeGrid(numRows, numColumns, makeTile(0, false, 0, 0)); for (let rowIndex = 0; rowIndex < numRows; rowIndex++) { for (let columnIndex = 0; columnIndex < numColumns; columnIndex++) { const newTile = newGrid.getTile(rowIndex, columnIndex); const oldTile = grid.getTile(rowIndex, columnIndex); newTile.originalLocation.column = oldTile.originalLocation.column; newTile.originalLocation.row = oldTile.originalLocation.row; newTile.value = oldTile.value; newTile.visible = oldTile.visible; } } return newGrid; }, getColumn: (index) => { let vector = []; for (let rowIndex = 0; rowIndex < grid.numRows; rowIndex++) { vector.push(grid.getTile(rowIndex, index)); } return vector; }, getRow: (index) => { let vector = []; for (let columnIndex = 0; columnIndex < grid.numColumns; columnIndex++) { vector.push(grid.getTile(index, columnIndex)); } return vector; }, getTile: (row, column) => { if (row >= 0 && row < numRows && column >= 0 && column < numColumns) { return rows[column][row]; } return null; }, setTile: (row, column, tile) => { if (row >= 0 && row < numRows && column >= 0 && column < numColumns) { rows[column][row] = tile; } }, swapTiles: (fromRow, fromColumn, toRow, toColumn) => { if ( fromRow >= 0 && fromRow < numRows && fromColumn >= 0 && fromColumn < numColumns && toRow >= 0 && toRow < numRows && toColumn >= 0 && toColumn < numColumns) { fromTile = grid.getTile(fromRow, fromColumn); toTile = grid.getTile(toRow, toColumn); // maybe set previous locations on the tiles grid.setTile(fromRow, fromColumn, toTile); grid.setTile(toRow, toColumn, fromTile); } }, toString: (mapFn = i => i.value) => { return "[[" + mapFn(grid.getTile(0, 0)) + "," + mapFn(grid.getTile(0, 1)) + "," + mapFn(grid.getTile(0, 2)) + "," + mapFn(grid.getTile(0, 3)) + "],\n" + " [" + mapFn(grid.getTile(1, 0)) + "," + mapFn(grid.getTile(1, 1)) + "," + mapFn(grid.getTile(1, 2)) + "," + mapFn(grid.getTile(1, 3)) + "],\n" + " [" + mapFn(grid.getTile(2, 0)) + "," + mapFn(grid.getTile(2, 1)) + "," + mapFn(grid.getTile(2, 2)) + "," + mapFn(grid.getTile(2, 3)) + "],\n" + " [" + mapFn(grid.getTile(3, 0)) + "," + mapFn(grid.getTile(3, 1)) + "," + mapFn(grid.getTile(3, 2)) + "," + mapFn(grid.getTile(3, 3)) + "]]"; } }; return grid; } const makeTile = (value, visible, row, column) => { return { value, visible, originalLocation: { row, column, }, equals: (other) => { return this.value == other.value && this.visible == other.visible && this.originalLocation.row == other.originalLocation.row && this.originalLocation.column == other.originalLocation.column; } }; }; const addRandomTile = () => { const hiddenTiles = globals.grid.tiles.filter(tile => !tile.visible); if (hiddenTiles.length <=0) return; const index = Math.floor(Math.random() * hiddenTiles.length); const tile = hiddenTiles[index]; tile.value = 1; tile.visible = 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.visible) { if (fromTile.visible) { // need to choose row or column swap(fromIndex, toIndex); // Advance fromIndex fromIndex = advance(fromIndex); hasMergedPrevious = false; didIDoIt = true; } else { // Advance fromIndex fromIndex = advance(fromIndex); hasMergedPrevious = false; } } else { if (fromTile.visible) { 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); hasMergedPrevious = false; } } } return didIDoIt; } const slideUp = (grid) => { let didIDoIt = false; const advance = i => i + 1; for (let columnIndex = 0; columnIndex < grid.numColumns; columnIndex++) { const swap = (from, to) => { grid.swapTiles(from, columnIndex, to, columnIndex); const tempTile = vector[to]; vector[to] = vector[from]; vector[from] = tempTile; }; const merge = (from, to) => { to.value++; from.value = 0; from.visible = false; }; const vector = grid.getColumn(columnIndex); const testForDone = fromIndex => fromIndex >= vector.length; let fromIndex = 0; let toIndex = -1; const didChangeThisPass = slide(fromIndex, toIndex, vector, advance, swap, merge, testForDone); didIDoIt = didIDoIt || didChangeThisPass; } return didIDoIt; } const slideLeft = (grid) => { let didIDoIt = false; const advance = i => i + 1; for (let rowIndex = 0; rowIndex < grid.numRows; rowIndex++) { const swap = (from, to) => { grid.swapTiles(rowIndex, from, rowIndex, to); const tempTile = vector[to]; vector[to] = vector[from]; vector[from] = tempTile; }; const merge = (from, to) => { to.value++; from.value = 0; from.visible = false; }; const vector = grid.getRow(rowIndex); const testForDone = fromIndex => fromIndex >= vector.length; let fromIndex = 0; let toIndex = -1; const didChangeThisPass = slide(fromIndex, toIndex, vector, advance, swap, merge, testForDone); didIDoIt = didIDoIt || didChangeThisPass; } return didIDoIt; } const slideDown = (grid) => { let didIDoIt = false; const advance = i => i - 1; for (let columnIndex = 0; columnIndex < grid.numColumns; columnIndex++) { const swap = (from, to) => { grid.swapTiles(from, columnIndex, to, columnIndex); const tempTile = vector[to]; vector[to] = vector[from]; vector[from] = tempTile; }; const merge = (from, to) => { to.value++; from.value = 0; from.visible = false; }; const vector = grid.getColumn(columnIndex); const testForDone = fromIndex => fromIndex < 0; let fromIndex = vector.length - 1; let toIndex = vector.length; const didChangeThisPass = slide(fromIndex, toIndex, vector, advance, swap, merge, testForDone); didIDoIt = didIDoIt || didChangeThisPass; } return didIDoIt; } const slideRight = (grid) => { let didIDoIt = false; const advance = i => i - 1; for (let rowIndex = 0; rowIndex < grid.numRows; rowIndex++) { const swap = (from, to) => { grid.swapTiles(rowIndex, from, rowIndex, to); const tempTile = vector[to]; vector[to] = vector[from]; vector[from] = tempTile; }; const merge = (from, to) => { to.value++; from.value = 0; from.visible = false; }; const vector = grid.getRow(rowIndex); const testForDone = fromIndex => fromIndex < 0; let fromIndex = vector.length - 1; let toIndex = vector.length; const didChangeThisPass = slide(fromIndex, toIndex, vector, advance, swap, merge, testForDone); didIDoIt = didIDoIt || didChangeThisPass; } return didIDoIt; } const canSlideUp = (grid) => { const cloneGrid = grid.clone(); return slideUp(cloneGrid); } const canSlideDown = (grid) => { const cloneGrid = grid.clone(); return slideDown(cloneGrid); } const canSlideLeft = (grid) => { const cloneGrid = grid.clone(); return slideLeft(cloneGrid); } const canSlideRight = (grid) => { const cloneGrid = grid.clone(); return slideRight(cloneGrid); } const gameIsLost = (grid) => { // return !canSlideUp(grid); // return false; return !canSlideUp(grid) && !canSlideRight(grid) && !canSlideDown(grid) && !canSlideLeft(grid); } const checkForEnd = (grid) => { if (gameIsLost(grid)) { alert('A loser is you!'); } } const makeDevGrid = (matrix) => { const numRows = matrix.length; const numColumns = matrix[0].length; const initialValue = makeTile(0, false, 0, 0); const grid = makeGrid(numRows, numColumns, initialValue); for (let rowIndex = 0; rowIndex < numRows; rowIndex++) { const row = matrix[rowIndex]; for (let colIndex = 0; colIndex < numColumns; colIndex++) { const value = row[colIndex]; const tile = grid.getTile(rowIndex, colIndex); tile.value = value; tile.visible = value > 0; } } return grid; } /* [0,0,0,0], [0,0,1,0], [0,0,0,0], [2,1,2,2], can't slide down. [0,0,1,0], [0,0,0,0], [0,0,0,0], [3,2,3,0], can't slide down. can't slide left. up() [1,0,1,0], [0,0,3,0], [0,0,0,0], [3,2,0,0], either the 3 doesn't go up or the 1 bounces it back? // fixed */