Game of life in Kotlin
The reason why I build this Game of Life example is because an interview. That’s really interesting because the leetcode also has this problem (289. Game of Life). So I try to use a brute force solution to get the next state. Then follow the Android MVP pattern to build the app.
Rule
The rule of this game is:
- Any live cell with fewer than two live neighbours dies, as if by underpopulation.
- Any live cell with two or three live neighbours lives on to the next generation.
- Any live cell with more than three live neighbours dies, as if by overpopulation.
- Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
Algorithm
The main transition will be this function:
override fun getNextStatus(): Array<IntArray> {
val newPixelArray = Array(cellArray.size) { IntArray(cellArray[0].size) }
for (i in newPixelArray.indices) {
for (j in newPixelArray[0].indices) {
val newValue = if (isLive(cellArray, i, j)) 1 else 0
if (newPixelArray[i][j] == newValue) continue
newPixelArray[i][j] = newValue
}
}
for (i in cellArray.indices) {
for (j in cellArray[0].indices) {
cellArray[i][j] = newPixelArray[i][j]
}
}
return newPixelArray
}
So I copy an array then check each element neighbours finally assign back to the array cellArray
Follow rules, the alive function would be:
private fun isLive(pixelArray: Array<IntArray>, i: Int, j: Int): Boolean {
var neighborLiveCount = 0
for (k in ref.indices) {
if (i + ref[k][0] < 0 || j + ref[k][1] < 0
|| i + ref[k][0] >= pixelArray.size
|| j + ref[k][1] >= pixelArray[0].size
) continue
neighborLiveCount += pixelArray[i + ref[k][0]][j + ref[k][1]]
}
return if (pixelArray[i][j] == 0) {
neighborLiveCount == 3
} else {
neighborLiveCount in 2..3
}
}
For these logic we should define in ViewModel
or Presenter
(Depends on what kind of architecture pattern you using), then we can write some unit test to make sure our logic is run as our expected.
Write a Test
/**
* leetcode example
* https://leetcode.com/problems/game-of-life/
* input [[0,1,0],[0,0,1],[1,1,1],[0,0,0]]
* output [[0,0,0],[1,0,1],[0,1,1],[0,1,0]]
*/
@Test
fun getNextStatus_failed() {
// Given
presenter.setCellArrayElement(0, 1)
presenter.setCellArrayElement(1, 2)
presenter.setCellArrayElement(2, 0)
presenter.setCellArrayElement(2, 1)
presenter.setCellArrayElement(2, 2)
// When
presenter.getNextStatus()
// Then
assertEquals(0, presenter.cellArray[0][0])
assertEquals(0, presenter.cellArray[0][1])
assertEquals(0, presenter.cellArray[0][2])
assertEquals(1, presenter.cellArray[1][0])
assertEquals(0, presenter.cellArray[1][1])
assertEquals(1, presenter.cellArray[1][2])
assertEquals(0, presenter.cellArray[2][0])
assertEquals(1, presenter.cellArray[2][1])
assertEquals(1, presenter.cellArray[2][2])
assertEquals(0, presenter.cellArray[3][0])
assertEquals(1, presenter.cellArray[3][1])
assertEquals(0, presenter.cellArray[3][2])
}
Then we can use CountDownTimer
, Rxjava2
…etc method to change the state by periodically. Consider it might be block UI, so I strongly recommended to put these logic into other worker thread to prevent large works doing in main thread.
Draw the game
After that, we need to think about how to draw this game. We can simply to use a custom view to draw the game. So first we should define the base cell size for the game then we can use drawRect
accordingly. Here is a code snipped FYR.
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawCells(canvas)
}
private fun drawCells(canvas: Canvas) {
cellArray.let {
for (i in it.indices) {
for (j in it[0].indices) {
rect.set(
i * cellSize, j * cellSize,
(i + 1) * cellSize, (j + 1) * cellSize
)
paint.color = if (it[i][j] == 1) {
liveColor
} else {
deadColor
}
canvas.drawRect(rect, paint)
}
}
}
}
That’s it. And we can use a touch event to draw everywhere you want to init. Finally the game will looks like:
For that interview experience is really good. Because i never imagine that one day I could apply a game algorithm into the app. Here is my full demo code. Hope you like it. :)