Skip to content

Commit

Permalink
add familyTree and descdentants counting
Browse files Browse the repository at this point in the history
  • Loading branch information
halotukozak committed Jan 23, 2024
1 parent 967f979 commit 07c5379
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 94 deletions.
28 changes: 11 additions & 17 deletions src/main/kotlin/backend/Simulation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ class Simulation(

val day = MutableStateFlow(0)

private val _isRunning = MutableStateFlow(false)
val isRunning: StateFlow<Boolean> = _isRunning

private var _dayDuration = MutableStateFlow(1000L)
val dayDuration: StateFlow<Long> = _dayDuration

Expand All @@ -35,9 +32,12 @@ class Simulation(
}

val plants = map.plants
val animals = map.animals
val aliveAnimals = map.aliveAnimals
val deadAnimals = map.deadAnimals
val preferredFields = map.preferredFields

val familyTree = map.familyTree

private suspend fun nextDay() {
println("${day.updateAndGet { it + 1 }} day!")
map.growAnimals()
Expand All @@ -48,7 +48,7 @@ class Simulation(
map.breedAnimals { launch { statisticsService.registerBirth(day.value) } }
map.growPlants(config.plantsPerDay)

statisticsService.registerEndOfDay(day.value, plants.value, animals.value.flattenValues())
statisticsService.registerEndOfDay(day.value, plants.value, aliveAnimals.value.flattenValues())
}

private var simulationJob: Job = launch {
Expand All @@ -62,28 +62,22 @@ class Simulation(
}
}

fun pause() = _isRunning.update {
simulationJob.cancel()
false
}
fun pause() = simulationJob.cancel()

fun resume() = _isRunning.update {
simulationJob = launchSimulation()
simulationJob.start()
true
fun resume() {
simulationJob = launchSimulation().apply(Job::start)
}

fun faster() = _dayDuration.updateAndGet { maxOf(50, it - 100) }
fun slower() = _dayDuration.updateAndGet {
fun faster() = _dayDuration.update { maxOf(50, it - 100) }
fun slower() = _dayDuration.update {
when {
it < 100 -> 0
else -> it
} + 100
}

override fun close() { ///todo make it working
override fun close() {
launchMainImmediate { simulationJob.cancelAndJoin() }
}

}

35 changes: 26 additions & 9 deletions src/main/kotlin/backend/map/AbstractMap.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@ import backend.GenomeManager
import backend.config.Config
import backend.model.Animal
import backend.model.Direction
import frontend.simulation.FamilyTree
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import shared.*
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.component3
import kotlin.coroutines.coroutineContext
import kotlin.random.Random

@Suppress("PropertyName")
Expand All @@ -19,7 +26,7 @@ abstract class AbstractMap(protected val config: Config) {
(0..<config.mapHeight).map { y -> Vector(x, y) }
}

protected val _animals = MutableStateFlow(generateSequence {
protected val _aliveAnimals = MutableStateFlow(generateSequence {
Vector(random.nextInt(config.mapWidth), random.nextInt(config.mapHeight))
}
.take(config.initialAnimals)
Expand All @@ -38,15 +45,20 @@ abstract class AbstractMap(protected val config: Config) {
)
}
})

private val _deadAnimals = MutableStateFlow(emptySet<Animal>())
protected val _plants = MutableStateFlow(emptySet<Vector>())
protected val _preferredFields = MutableStateFlow(emptySet<Vector>())

val animals: StateFlow<List<Pair<Vector, List<Animal>>>> = _animals
val aliveAnimals: StateFlow<List<Pair<Vector, List<Animal>>>> = _aliveAnimals
val deadAnimals: StateFlow<Set<Animal>> = _deadAnimals
val plants: StateFlow<Set<Vector>> = _plants
val preferredFields: StateFlow<Set<Vector>> = _preferredFields

val familyTree = FamilyTree(_aliveAnimals.value.flattenValues().map { it.id })

private suspend fun updateAnimals(f: Animal.() -> Animal, callback: suspend (List<Animal>) -> Unit) =
_animals.update { animals ->
_aliveAnimals.update { animals ->
animals.mapValuesAsync { set -> set.map(f) }.also {
callback(it.flattenValues())
}
Expand All @@ -56,17 +68,18 @@ abstract class AbstractMap(protected val config: Config) {

suspend fun rotateAnimals(callback: suspend (List<Animal>) -> Unit = {}) = updateAnimals(Animal::rotate, callback)

suspend fun removeDeadAnimals(callback: suspend (List<Animal>) -> Unit = {}) = _animals.update {
it.mapValuesAsync { set ->
suspend fun removeDeadAnimals(callback: suspend (List<Animal>) -> Unit = {}) = _aliveAnimals.update { animals ->
animals.mapValuesAsync { set ->
set.partition(Animal::isDead).let { (dead, alive) ->
callback(dead)
_deadAnimals.update { it + dead }
alive
}
}
}

@OptIn(ExperimentalCoroutinesApi::class)
suspend fun moveAnimals() = _animals.update {
suspend fun moveAnimals() = _aliveAnimals.update {
it.asFlow()
.flatMapMerge { (position, set) ->
set
Expand Down Expand Up @@ -94,7 +107,7 @@ abstract class AbstractMap(protected val config: Config) {

suspend fun consumePlants() = _plants.update { plants ->
val newPlants = plants.toMutableSet()
_animals.update { animals ->
_aliveAnimals.update { animals ->
animals.mapValuesAsync { position, set ->
if (position in plants)
set.mapMax {
Expand All @@ -107,7 +120,8 @@ abstract class AbstractMap(protected val config: Config) {
newPlants
}

suspend fun breedAnimals(callback: (Animal) -> Unit = {}) = _animals.update { animals ->
suspend fun breedAnimals(callback: (Animal) -> Unit = {}) = _aliveAnimals.update { animals ->
val scope = CoroutineScope(coroutineContext)
animals.mapValuesAsync { set ->
(set.size >= 2).ifTake {
val (animal1, animal2) = set.max().let { it to (set - it).max() }
Expand All @@ -116,8 +130,11 @@ abstract class AbstractMap(protected val config: Config) {
animal2,
config.reproductionEnergyRatio,
mutator,
).also { (_, _, child) ->
).also { (parent1, parent2, child) ->
scope.launch {
familyTree.add(child.id, parent1.id, parent2.id)
callback(child)
}
}
}
} ?: set
Expand Down
17 changes: 9 additions & 8 deletions src/main/kotlin/backend/model/Animal.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ data class Animal(
val genome: Genome,
val direction: Direction,
val age: Int = 0,
val children: Set<Animal> = setOf(),
val children: Int = 0,
val id: UUID = UUID.randomUUID(),
val parents: Pair<UUID, UUID>? = null,
) : Comparable<Animal> {

val isDead by lazy { energy <= 0 }
Expand All @@ -20,7 +21,7 @@ data class Animal(
fun eat(energy: Int): Animal = this.copy(energy = this.energy + energy)

private fun decreaseEnergy(energy: Int): Animal = this.copy(energy = this.energy - energy)
private fun withChild(child: Animal): Animal = this.copy(children = children + child)
private fun withChild(): Animal = this.copy(children = this.children + 1)

fun cover(other: Animal, reproductionEnergyRatio: Double, mutator: GenomeManager): List<Animal> {
val (energyLoss1, parent1) = (this.energy * reproductionEnergyRatio).toInt().let {
Expand All @@ -31,21 +32,21 @@ data class Animal(
}

val child = Animal(
energyLoss1 + energyLoss2,
mutator.combine(this.genome, other.genome, energyLoss1.toDouble() / (energyLoss1 + energyLoss2)),
Direction.entries.random(),
energy = energyLoss1 + energyLoss2,
genome = mutator.combine(this.genome, other.genome, energyLoss1.toDouble() / (energyLoss1 + energyLoss2)),
direction = Direction.entries.random(), parents = Pair(parent1.id, parent2.id)
)

return listOf(
parent1.withChild(child),
parent2.withChild(child),
parent1.withChild(),
parent2.withChild(),
child,
)
}

override fun compareTo(other: Animal): Int = when {
this.energy.compareTo(other.energy) != 0 -> this.energy.compareTo(other.energy)
this.age.compareTo(other.age) != 0 -> this.age.compareTo(other.age)
else -> this.children.size.compareTo(other.children.size)
else -> this.children.compareTo(other.children)
}
}
8 changes: 6 additions & 2 deletions src/main/kotlin/frontend/animal/FollowedAnimalsView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import backend.model.Animal
import frontend.animal.FollowedAnimalsViewModel.FollowedAnimal
import frontend.components.View
import frontend.components.card
import frontend.simulation.FamilyTree
import javafx.scene.text.Text
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
Expand All @@ -17,10 +18,12 @@ import java.util.*
class FollowedAnimalsView(
energyStep: Int,
followedIds: MutableStateFlow<List<UUID>>,
animals: StateFlow<List<Pair<Vector, List<Animal>>>>,
aliveAnimals: StateFlow<List<Pair<Vector, List<Animal>>>>,
deadAnimals: StateFlow<Set<Animal>>,
familyTree: FamilyTree,
) : View() {

override val viewModel = FollowedAnimalsViewModel(energyStep, followedIds, animals)
override val viewModel = FollowedAnimalsViewModel(energyStep, followedIds, familyTree, aliveAnimals, deadAnimals)

override val root = with(viewModel) {
card {
Expand All @@ -43,6 +46,7 @@ class FollowedAnimalsView(
readonlyColumn("Direction", FollowedAnimal::direction)
readonlyColumn("Age", FollowedAnimal::age)
readonlyColumn("Children", FollowedAnimal::children)
readonlyColumn("Descendants", FollowedAnimal::descendants)
readonlyColumn("Unfollow", FollowedAnimal::unfollowButton)

}
Expand Down
72 changes: 28 additions & 44 deletions src/main/kotlin/frontend/animal/FollowedAnimalsViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import backend.model.Animal
import backend.model.Direction.*
import frontend.components.ViewModel
import frontend.components.fontIcon
import frontend.simulation.FamilyTree
import javafx.event.EventHandler
import javafx.scene.layout.HBox
import javafx.scene.paint.Color
Expand All @@ -22,52 +23,44 @@ import java.util.*
class FollowedAnimalsViewModel(
private val energyStep: Int,
val followedIds: MutableStateFlow<List<UUID>>,
val familyTree: FamilyTree,
aliveAnimals: StateFlow<List<Pair<Vector, List<Animal>>>>,
deadAnimals: StateFlow<Set<Animal>>,
) : ViewModel() {


val followedAnimals = MutableStateFlow(emptyList<FollowedAnimal>())

init {
combine(aliveAnimals, followedIds) { animals, ids ->
followedAnimals.update { oldAnimals ->
animals
val followedAnimals = combine(aliveAnimals, deadAnimals, followedIds) { aliveAnimals, deadAnimals, ids ->
aliveAnimals
.asFlow()
.flatMapMerge { (position, set) ->
set
.asFlow()
.flatMapMerge { (position, set) ->
set
.asFlow()
.filter { it.id in ids }
.map { animal -> FollowedAnimal(position, animal) }
}.let { newAnimals ->
val remainingIds = newAnimals.map { it.id }.toList()
oldAnimals
.asFlow()
.filter { it.id in ids && it.id !in remainingIds }
.map { animal -> animal.killed }
.toList() + newAnimals.toList()
}
}
}.start()
.filter { it.id in ids }
.map { FollowedAnimal(position, it) }
}.onCompletion {
emitAll(deadAnimals
.asFlow()
.filter { it.id in ids }
.map { FollowedAnimal(null, it) }
)
}.toList()
}


inner class FollowedAnimal(
val id: UUID,
val x: Int?,
val y: Int?,
val energy: Text,
val genome: Text,
val direction: FontIcon,
val age: HBox,
val children: Int,
val descendants: Int,
val unfollowButton: FontIcon,
) {

constructor(
vector: Vector?,
animal: Animal,
) : this(
id = animal.id,
x = vector?.x,
y = vector?.y,
energy = Text(animal.energy.toString()).apply {
Expand Down Expand Up @@ -108,27 +101,18 @@ class FollowedAnimalsViewModel(
}
)
},
children = animal.children.size,
children = animal.children,
descendants = findDescendants(animal.id),
unfollowButton = FontIcon(Material2SharpAL.DELETE).apply {
onMouseClicked = EventHandler { followedIds.update { it - animal.id } }
}
)

val killed
get() = FollowedAnimal(
id,
x,
y,
Text("Dead").apply {
style {
textFill = Color.BLACK
}
},
genome,
direction,
age,
children,
unfollowButton
)
}
}

private val descendantsMap = MutableStateFlow(mapOf<UUID, FamilyTree>())

private fun findDescendants(id: UUID) = descendantsMap.updateAndGet {
if (id in it) it else it + (id to familyTree.find(id)!!)
}[id]!!.descendants

}
32 changes: 32 additions & 0 deletions src/main/kotlin/frontend/simulation/FamilyTree.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package frontend.simulation

import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import java.util.*

class FamilyTree private constructor(
private val id: UUID? = null,
private val children: MutableStateFlow<Set<FamilyTree>> = MutableStateFlow(setOf()),
) {
constructor(children: List<UUID>) : this(children = MutableStateFlow(children.map(::FamilyTree).toSet()))

fun add(childId: UUID, vararg parentIds: UUID) = FamilyTree(id = childId).let { child ->
parentIds.forEach { id ->
find(id)?.children?.update { it + child } //shit, it can be null
}
}

fun find(id: UUID): FamilyTree? = if (this.id == id) this else children.value.firstNotNullOfOrNull { it.find(id) }

val descendants: Int
get() {
tailrec fun loop(visited: Set<FamilyTree>, acc: Set<FamilyTree>): Int =
if (acc.isEmpty()) visited.size
else {
val current = acc.first()
loop(visited + current, acc - current + current.children.value)
}
return loop(setOf(), children.value)
}

}
Loading

0 comments on commit 07c5379

Please sign in to comment.