patternkotlinMinor
Stateless 2048 Game
Viewed 0 times
statelessgame2048
Problem
I created a little Kotlin clone of the 2048 game that is playable in the console. My goals were readability and statelessness for no other reason than practice. Consider it a code kata wherein I didn't want to mutate the original grid, instead returning a new permutation for every operation.
To this end, I would greatly appreciate review and comments of my code pertaining to style, simplicity and readability. Performance is not an issue.
```
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 updated
To this end, I would greatly appreciate review and comments of my code pertaining to style, simplicity and readability. Performance is not an issue.
```
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 updated
Solution
First of all, you don't need to specify the return type if it is a one-liner and returned with a
One thing that I'd change are the functions
I think it's more readable when you create extension methods/properties for your grid:
And then use it like that:
Something not Kotlin related:
I wouldn't use an array of array of
It won't be much of a problem in smaller projects like this, but later there is no real distinction between your "grid" and an array of array of
=.One thing that I'd change are the functions
isGridFull(), isGridSolved(), and shiftCells().I think it's more readable when you create extension methods/properties for your grid:
fun Array>.isGridSolved() = this.any { row -> row.contains(2048) }
fun Array>.isGridFull() = this.all { row -> !row.contains(0) }And then use it like that:
if (grid.isGridSolved()) return positiveGameOverMessage
else if (grid.isGridFull()) return negativeGameOverMessageSomething not Kotlin related:
I wouldn't use an array of array of
ints for your grid. Better create a class which contains the grid and offers methods like isFull(), isSolved(), and shiftCells().It won't be much of a problem in smaller projects like this, but later there is no real distinction between your "grid" and an array of array of
ints and you could mix it up.Code Snippets
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 negativeGameOverMessageContext
StackExchange Code Review Q#109133, answer score: 2
Revisions (0)
No revisions yet.