import java.io.BufferedReader import java.io.InputStreamReader const val positiveGameOverMessage = "So sorry, but you won the game." const val negativeGameOverMessage = "So sorry, but you lost the game." fun main(args: Array) { val grid = arrayOf( arrayOf(0, 0, 0, 0), arrayOf(0, 0, 0, 0), arrayOf(0, 0, 0, 0), arrayOf(0, 0, 0, 0) ) val gameOverMessage = run2048(grid) println(gameOverMessage) } fun run2048(grid: Array>): String { if (isGridSolved(grid)) return positiveGameOverMessage else if (isGridFull(grid)) return negativeGameOverMessage val populatedGrid = spawnNumber(grid) display(populatedGrid) return run2048(manipulateGrid(populatedGrid, waitForValidInput())) } fun isGridSolved(grid: Array>): Boolean = grid.any { row -> row.contains(2048) } fun isGridFull(grid: Array>): Boolean = grid.all { row -> !row.contains(0) } fun spawnNumber(grid: Array>): Array> { val coordinates = locateSpawnCoordinates(grid) val number = generateNumber() return updateGrid(grid, coordinates, number) } fun locateSpawnCoordinates(grid: Array>): Pair { val emptyCells = arrayListOf>() grid.forEachIndexed { x, row -> row.forEachIndexed { y, cell -> if (cell == 0) emptyCells.add(Pair(x, y)) } } return emptyCells[(Math.random() * (emptyCells.size - 1)).toInt()] } fun generateNumber(): Int = if (Math.random() > 0.10) 2 else 4 fun updateGrid(grid: Array>, at: Pair, value: Int): Array> { val updatedGrid = grid.copyOf() updatedGrid[at.first][at.second] = value return updatedGrid } fun waitForValidInput(): String { val input = waitForInput() return if (isValidInput(input)) input else waitForValidInput() } fun isValidInput(input: String): Boolean = arrayOf("a", "s", "d", "w").contains(input) fun waitForInput(): String { val reader = BufferedReader(InputStreamReader(System.`in`)) println("Direction? ") return reader.readLine() } fun manipulateGrid(grid: Array>, input: String): Array> = when (input) { "a" -> shiftCellsLeft(grid) "s" -> shiftCellsDown(grid) "d" -> shiftCellsRight(grid) "w" -> shiftCellsUp(grid) else -> throw IllegalArgumentException("Expected one of [a, s, d, w]") } fun shiftCellsLeft(grid: Array>): Array> = grid.map(::mergeAndOrganizeCells).toTypedArray() fun shiftCellsRight(grid: Array>): Array> = grid.map { row -> mergeAndOrganizeCells(row.reversed().toTypedArray()).reversed().toTypedArray() }.toTypedArray() fun shiftCellsUp(grid: Array>): Array> { val rows: Array> = arrayOf( arrayOf(grid[0][0], grid[1][0], grid[2][0], grid[3][0]), arrayOf(grid[0][1], grid[1][1], grid[2][1], grid[3][1]), arrayOf(grid[0][2], grid[1][2], grid[2][2], grid[3][2]), arrayOf(grid[0][3], grid[1][3], grid[2][3], grid[3][3]) ) val updatedGrid = grid.copyOf() rows.map(::mergeAndOrganizeCells).forEachIndexed { rowIdx, row -> updatedGrid[0][rowIdx] = row[0] updatedGrid[1][rowIdx] = row[1] updatedGrid[2][rowIdx] = row[2] updatedGrid[3][rowIdx] = row[3] } return updatedGrid } fun shiftCellsDown(grid: Array>): Array> { val rows: Array> = arrayOf( arrayOf(grid[3][0], grid[2][0], grid[1][0], grid[0][0]), arrayOf(grid[3][1], grid[2][1], grid[1][1], grid[0][1]), arrayOf(grid[3][2], grid[2][2], grid[1][2], grid[0][2]), arrayOf(grid[3][3], grid[2][3], grid[1][3], grid[0][3]) ) val updatedGrid = grid.copyOf() rows.map(::mergeAndOrganizeCells).forEachIndexed { rowIdx, row -> updatedGrid[3][rowIdx] = row[0] updatedGrid[2][rowIdx] = row[1] updatedGrid[1][rowIdx] = row[2] updatedGrid[0][rowIdx] = row[3] } return updatedGrid } fun mergeAndOrganizeCells(row: Array): Array = organize(merge(row.copyOf())) fun merge(row: Array, idxToMatch: Int = 0, idxToCompare: Int = 1): Array { if (idxToMatch >= row.size) return row if (idxToCompare >= row.size) return merge(row, idxToMatch + 1, idxToMatch + 2) if (row[idxToMatch] == 0) return merge(row, idxToMatch + 1, idxToMatch + 2) return if (row[idxToMatch] == row[idxToCompare]) { row[idxToMatch] *= 2 row[idxToCompare] = 0 merge(row, idxToMatch + 1, idxToMatch + 2) } else { if (row[idxToCompare] != 0) merge(row, idxToMatch + 1, idxToMatch + 2) else merge(row, idxToMatch, idxToCompare + 1) } } fun organize(row: Array, idxToMatch: Int = 0, idxToCompare: Int = 1): Array { if (idxToMatch >= row.size) return row if (idxToCompare >= row.size) return organize(row, idxToMatch + 1, idxToMatch + 2) if (row[idxToMatch] != 0) return organize(row, idxToMatch + 1, idxToMatch + 2) return if (row[idxToCompare] != 0) { row[idxToMatch] = row[idxToCompare] row[idxToCompare] = 0 organize(row, idxToMatch + 1, idxToMatch + 2) } else { organize(row, idxToMatch, idxToCompare + 1) } } fun display(grid: Array>) { val prettyPrintableGrid = grid.map { row -> row.map { cell -> if (cell == 0) " " else java.lang.String.format("%4d", cell) } } println("New Grid:") prettyPrintableGrid.forEach { row -> println("+----+----+----+----+") row.forEach { print("|$it") } println("|") } println("+----+----+----+----+") }