Skip to content

Instantly share code, notes, and snippets.

@ditn
Created October 11, 2020 07:10
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ditn/0aaa425a958167b34260af5468207eac to your computer and use it in GitHub Desktop.
Save ditn/0aaa425a958167b34260af5468207eac to your computer and use it in GitHub Desktop.
Conway's Composable Game of Life
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