Created
October 11, 2020 07:10
-
-
Save ditn/0aaa425a958167b34260af5468207eac to your computer and use it in GitHub Desktop.
Conway's Composable Game of Life
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private val rand = Random(0) | |
private const val CELL_SIZE = 50 | |
private val ALIVE_COLOR = Color.White | |
private val DEAD_COLOR = Color.Black | |
private val BooleanToVector: TwoWayConverter<Boolean, AnimationVector1D> = TwoWayConverter( | |
{ AnimationVector1D(if (it) 1f else 0f) }, | |
{ it.value == 1f } | |
) | |
@Composable | |
private fun GameOfLife(modifier: Modifier = Modifier) { | |
val clock = animatedValue(false, BooleanToVector) | |
onActive { | |
clock.animateTo( | |
targetValue = true, | |
anim = repeatable( | |
animation = keyframes { | |
durationMillis = 300 | |
false at 0 | |
true at 150 | |
}, | |
iterations = AnimationConstants.Infinite | |
) | |
) | |
} | |
WithConstraints(modifier = modifier) { | |
val universe = remember { | |
val width = constraints.maxWidth / CELL_SIZE | |
val height = constraints.maxHeight / CELL_SIZE | |
Universe(width = width, height = height) | |
} | |
Canvas(modifier = modifier) { | |
val evolve = clock.value | |
for (y in 0 until universe.height) { | |
for (x in 0 until universe.width) { | |
drawRect( | |
color = universe.field.cells[y][x].toColor(), | |
topLeft = Offset( | |
x = x * CELL_SIZE.toFloat(), | |
y = y * CELL_SIZE.toFloat() | |
), | |
) | |
} | |
} | |
if (evolve) universe.evolve() | |
} | |
} | |
} | |
class Field(private val width: Int, private val height: Int) { | |
val cells: Array<Array<Cell>> = Array(height) { Array(width) { Cell(false) } } | |
operator fun set(x: Int, y: Int, isAlive: Boolean) { | |
cells[y][x].isAlive = isAlive | |
} | |
fun next(x: Int, y: Int): Boolean { | |
var neighbours = 0 | |
for (i in (-1..1)) { | |
for (j in -1..1) { | |
if (isAlive(x + i, y + j) && !(j == 0 && i == 0)) { | |
neighbours++ | |
} | |
} | |
} | |
return neighbours == 3 || (neighbours == 2 && isAlive(x, y)) | |
} | |
private fun isAlive(x: Int, y: Int): Boolean = | |
if (outOfBounds(x, y)) false else cells[y][x].isAlive | |
private fun outOfBounds(x: Int, y: Int): Boolean = | |
(x !in 0 until width) || (y !in 0 until height) | |
} | |
class Universe(val width: Int, val height: Int) { | |
var field: Field = Field(width, height) | |
private var tempField: Field = Field(width, height) | |
init { | |
seed() | |
} | |
private fun seed() { | |
for (i in 0 until width * height / 2) { | |
field[rand.nextInt(width), rand.nextInt(height)] = true | |
} | |
} | |
fun evolve() { | |
for (y in 0 until height) { | |
for (x in 0 until width) { | |
tempField[x, y] = field.next(x, y) | |
} | |
} | |
val t = field | |
field = tempField | |
tempField = t | |
} | |
} | |
class Cell(var isAlive: Boolean) | |
fun Cell.toColor(): Color = when (isAlive) { | |
true -> ALIVE_COLOR | |
false -> DEAD_COLOR | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment