HiveBrain v1.2.0
Get Started
← Back to all entries
patternkotlinMinor

Stateless 2048 Game

Submitted by: @import:stackexchange-codereview··
0
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

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 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 negativeGameOverMessage


Something 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 negativeGameOverMessage

Context

StackExchange Code Review Q#109133, answer score: 2

Revisions (0)

No revisions yet.