Game of Life Library
Let´s continue with an app which is more challenging implementing.
Conway’s Game of Life is a zero player game. Starting with some cells on a 2d board, a simple set of rules lets these cells evolve to something which seems like a living organism.
The Wikipedia articel describes the game, and it´s rules.
I use this rules for teaching test driven development, so let´s start with the specification of the rules in Kotlin and put them into our existing Kotlin Multi Platform project
@Test
fun aDeadCellWithExactlyThreeLivingNeighboursShouldBeAliveInTheNextGeneration() {
//given
val cell = Cell(false)
cell.livingNeighbours = 3
//when
cell.calculateNextGeneration()
//then
assertTrue(cell.alive)
}
@Test
fun aLivingCellWithLessThanTwoNeighboursShouldBeDeadInTheNextGeneration() {
//given
val cell = Cell(true)
cell.livingNeighbours = 1
//when
cell.calculateNextGeneration()
//then
assertFalse(cell.alive)
}
@Test
fun aLivingCellWithTwoNeighboursShouldBeAliveInTheNextGeneration() {
//given
val cell = Cell(true)
cell.livingNeighbours = 2
//when
cell.calculateNextGeneration()
//then
assertTrue(cell.alive)
}
@Test
fun aLivingCellWithThreeNeighboursShouldBeAliveInTheNextGeneration() {
//given
val cell = Cell(true)
cell.livingNeighbours = 3
//when
cell.calculateNextGeneration()
//then
assertTrue(cell.alive)
}
@Test
fun aLivingCellWithMoreThanThreeNeighboursShouldBeDeadInTheNextGeneration() {
//given
val cell = Cell(true)
cell.livingNeighbours = 4
//when
cell.calculateNextGeneration()
//then
assertFalse(cell.alive)
}
@Test
fun aDeadCellWithLessThanThreeLivingNeighboursShouldBeDead() {
//given
val cell = Cell(false)
cell.livingNeighbours = 2
//when
cell.calculateNextGeneration()
//then
assertFalse(cell.alive)
}
Starting from this specification the GOL rules can be implemented like this.
package egger.software.kotlinmpp.libgol
data class Cell(var alive: Boolean) {
var livingNeighbours: Int = 0
fun calculateNextGeneration() {
if (alive) {
if (!(livingNeighbours == 2 || livingNeighbours == 3)) {
alive = false
}
} else {
if (livingNeighbours == 3) {
alive = true
}
}
}
}
fun calculateNextGeneration() {
for ((rowIdx, row) in cells.withIndex()) {
for ((colIdx, cell) in row.withIndex()) {
cell.livingNeighbours = countLivingNeighbours(colIdx, rowIdx)
}
}
for (row in cells) {
for (cell in row) {
cell.calculateNextGeneration()
}
}
}
One of the great things of the IntelliJ integration of Kotlin Multi Platform is the execution of test. When clicking on “Run” the IDE lets you choose which target platform the test should run on. It not only runs that tests on the selected platform it also configures a standard test runner for that platform (JUnit in case of JVM) and collects the results.
Of course Gradle also executes the test cases. It does so for all configured platforms. For the following example I added a failing test to show the output in the test execution.