我创建了一个小Kotlin克隆的2048游戏,是可以在控制台上玩。我的目标是可读性和无状态,没有别的原因,只有实践。考虑到这是一个代码kata,其中我不想改变原来的网格,而是为每个操作返回一个新的排列。
为此,我非常感谢对我的代码的风格、简单性和可读性的评论和评论。性能不是问题。
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<String>) {
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<Array<Int>>): 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<Array<Int>>): Boolean = grid.any { row -> row.contains(2048) }
fun isGridFull(grid: Array<Array<Int>>): Boolean = grid.all { row -> !row.contains(0) }
fun spawnNumber(grid: Array<Array<Int>>):Array<Array<Int>> {
val coordinates = locateSpawnCoordinates(grid)
val number = generateNumber()
return updateGrid(grid, coordinates, number)
}
fun locateSpawnCoordinates(grid: Array<Array<Int>>): Pair<Int, Int> {
val emptyCells = arrayListOf<Pair<Int, Int>>()
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<Array<Int>>, at: Pair<Int, Int>, value: Int): Array<Array<Int>> {
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 br = BufferedReader(InputStreamReader(System.`in`));
println("Direction? ")
return br.readLine()
}
fun manipulateGrid(grid: Array<Array<Int>>, input: String): Array<Array<Int>> = 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<Int>>): Array<Array<Int>> =
grid.map { row -> mergeAndOrganizeCells(row) }.toTypedArray()
fun shiftCellsRight(grid: Array<Array<Int>>): Array<Array<Int>> =
grid.map { row -> mergeAndOrganizeCells(row.reversed().toTypedArray()).reversed().toTypedArray() }.toTypedArray()
fun shiftCellsUp(grid: Array<Array<Int>>): Array<Array<Int>> {
val rows: Array<Array<Int>> = 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 { row ->
mergeAndOrganizeCells(row)
}.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<Int>>): Array<Array<Int>> {
val rows: Array<Array<Int>> = 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 { row ->
mergeAndOrganizeCells(row)
}.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<Int>): Array<Int> = organize(merge(row.copyOf()))
fun merge(row: Array<Int>, idxToMatch: Int = 0, idxToCompare: Int = 1): Array<Int> {
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)
if (row[idxToMatch] == row[idxToCompare]) {
row[idxToMatch] *= 2
row[idxToCompare] = 0
return merge(row, idxToMatch + 1, idxToMatch + 2)
} else {
return if (row[idxToCompare] != 0) merge(row, idxToMatch + 1, idxToMatch + 2)
else merge(row, idxToMatch, idxToCompare + 1)
}
}
fun organize(row: Array<Int>, idxToMatch: Int = 0, idxToCompare: Int = 1): Array<Int> {
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)
if (row[idxToCompare] != 0) {
row[idxToMatch] = row[idxToCompare]
row[idxToCompare] = 0
return organize(row, idxToMatch + 1, idxToMatch + 2)
} else {
return organize(row, idxToMatch, idxToCompare + 1)
}
}
fun display(grid: Array<Array<Int>>) {
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("+----+----+----+----+")
}警告:游戏目前只检查网格是否已满,而不是是否没有可玩的移动。这是游戏的一个改进,但我还没有实施,没有好的理由。
发布于 2016-02-09 17:17:27
首先,如果返回类型是一行并与=一起返回,则不需要指定返回类型。
我要更改的一件事是函数isGridFull()、isGridSolved()和shiftCells()。
当您为网格创建扩展方法/属性时,我认为它更易读:
fun Array<Array<Int>>.isGridSolved() = this.any { row -> row.contains(2048) }
fun Array<Array<Int>>.isGridFull() = this.all { row -> !row.contains(0) }然后像这样使用它:
if (grid.isGridSolved()) return positiveGameOverMessage
else if (grid.isGridFull()) return negativeGameOverMessage一些与Kotlin无关的事情:
我不会使用数组的ints作为你的网格。最好创建一个包含网格的类,并提供像isFull()、isSolved()和shiftCells()这样的方法。
在类似这样的小型项目中,这不会有多大问题,但后来你的“网格”和一组ints数组之间并没有真正的区别,你可以把它混在一起。
https://codereview.stackexchange.com/questions/109133
复制相似问题