From b948770c512f7040b0dc9c4284487a26ad20306d Mon Sep 17 00:00:00 2001 From: itanasi <44038014+itanasi@users.noreply.github.com> Date: Wed, 25 Dec 2024 10:45:29 -0800 Subject: [PATCH] Simulation: More Robust Stats (#12675) * More robust Simulation Stats * Some code cleanup * Enum --- .../com/unciv/logic/simulation/Simulation.kt | 59 +++++++++++++------ .../unciv/logic/simulation/SimulationStep.kt | 32 +++++++++- 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/core/src/com/unciv/logic/simulation/Simulation.kt b/core/src/com/unciv/logic/simulation/Simulation.kt index 614714facf..389d360ce9 100644 --- a/core/src/com/unciv/logic/simulation/Simulation.kt +++ b/core/src/com/unciv/logic/simulation/Simulation.kt @@ -29,7 +29,7 @@ class Simulation( private var startTime: Long = 0 var steps = ArrayList() var numWins = mutableMapOf() - var sumStat = mutableMapOf>() + private var summaryStats = HashMap>>() // [civ][turn][stat]=value private var winRateByVictory = HashMap>() private var winTurnByVictory = HashMap>() private var avgSpeed = 0f @@ -37,13 +37,22 @@ class Simulation( private var totalTurns = 0 private var totalDuration: Duration = Duration.ZERO private var stepCounter: Int = 0 + enum class Stat { + SUM, + NUM + } init{ for (civ in civilizations) { this.numWins[civ] = MutableInt(0) - for (turn in statTurns) - this.sumStat.getOrPut(civ) { mutableMapOf() }[turn] = MutableInt(0) + for (turn in statTurns) { + this.summaryStats.getOrPut(civ) { hashMapOf() }.getOrPut(turn){hashMapOf()}[Stat.SUM] = MutableInt(0) + this.summaryStats.getOrPut(civ) { hashMapOf() }.getOrPut(turn){hashMapOf()}[Stat.NUM] = MutableInt(0) + } + val turn = -1 // end of game + this.summaryStats.getOrPut(civ) { hashMapOf() }.getOrPut(turn){hashMapOf()}[Stat.SUM] = MutableInt(0) + this.summaryStats.getOrPut(civ) { hashMapOf() }.getOrPut(turn){hashMapOf()}[Stat.NUM] = MutableInt(0) winRateByVictory[civ] = mutableMapOf() for (victory in UncivGame.Current.gameInfo!!.ruleset.victories.keys) winRateByVictory[civ]!![victory] = MutableInt(0) @@ -63,26 +72,28 @@ class Simulation( for (threadId in 1..threadsNumber) { jobs.add(launch(CoroutineName("simulation-${threadId}")) { repeat(simulationsPerThread) { + val step = SimulationStep(newGameInfo, statTurns) val gameInfo = GameStarter.startNewGame(GameSetupInfo(newGameInfo)) gameInfo.simulateUntilWin = true for (turn in statTurns) { gameInfo.simulateMaxTurns = turn gameInfo.nextTurn() - val step = SimulationStep(gameInfo) + step.update(gameInfo) if (step.victoryType != null) break - saveStat(gameInfo) + step.saveTurnStats(gameInfo) } // check if Victory - var step = SimulationStep(gameInfo) + step.update(gameInfo) if (step.victoryType == null) { gameInfo.simulateMaxTurns = maxTurns gameInfo.nextTurn() } - step = SimulationStep(gameInfo) // final game state + step.update(gameInfo) // final game state if (step.victoryType != null) { + step.saveTurnStats(gameInfo) step.winner = step.currentPlayer println("${step.winner} won ${step.victoryType} victory on turn ${step.turns}") } @@ -109,16 +120,6 @@ class Simulation( println("Simulation step ($stepCounter/$maxSimulations)") } - @Synchronized - fun saveStat(gameInfo: GameInfo) { - val turn = gameInfo.turns - for (civ in gameInfo.civilizations.filter { it.civName != Constants.spectator }) { - val popsum = civ.cities.sumOf { it.population.population } - //println("$civ $popsum") - sumStat[civ.civName]!![turn]!!.add(popsum) - } - } - @Synchronized fun print(){ getStats() @@ -130,12 +131,27 @@ class Simulation( numWins.values.forEach { it.value = 0 } winRateByVictory.flatMap { it.value.values }.forEach { it.value = 0 } winTurnByVictory.flatMap { it.value.values }.forEach { it.value = 0 } + summaryStats.flatMap { it.value.values }.forEach { + it.values.forEach { it.value = 0 } + } steps.forEach { if (it.winner != null) { numWins[it.winner!!]!!.inc() winRateByVictory[it.winner!!]!![it.victoryType]!!.inc() winTurnByVictory[it.winner!!]!![it.victoryType]!!.add(it.turns) } + for (civ in civilizations) { + for (turn in statTurns) { + if (it.turnStats[civ]!![turn]!!.value != -1) { + summaryStats[civ]!![turn]!![Stat.SUM]!!.add(it.turnStats[civ]!![turn]!!.value) + summaryStats[civ]!![turn]!![Stat.NUM]!!.inc() + //println("civ ${civ} @ ${turn} value ${it.turnStats[civ]!![turn]!!.value} avg ${summaryStats[civ]!![turn]!!["avg"]!!.value} numAvg ${summaryStats[civ]!![turn]!!["numAvg"]!!.value}") + } + } + val turn = -1 // end of game + summaryStats[civ]!![turn]!![Stat.SUM]!!.add(it.turnStats[civ]!![turn]!!.value) + summaryStats[civ]!![turn]!![Stat.NUM]!!.inc() + } } totalTurns = steps.sumOf { it.turns } totalDuration = (System.currentTimeMillis() - startTime).milliseconds @@ -176,8 +192,13 @@ class Simulation( outString += "$victory: $winsTurns " } outString += "avg turns\n" - for (turn in statTurns) - outString += "avgStat (@$turn): ${sumStat[civ]!![turn]!!.value.toFloat()/numSteps}\n" + for (turn in statTurns) { + val turnStats = summaryStats[civ]!![turn]!! + outString += "@$turn: popsum avg=${turnStats[Stat.SUM]!!.value.toFloat() / turnStats[Stat.NUM]!!.value.toFloat()} cnt=${turnStats[Stat.NUM]!!.value}\n" + } + val turn = -1 // end of match + val turnStats = summaryStats[civ]!![turn]!! + outString += "@END: popsum avg=${turnStats[Stat.SUM]!!.value.toFloat()/turnStats[Stat.NUM]!!.value.toFloat()} cnt=${turnStats[Stat.NUM]!!.value}\n" } outString += "\nAverage speed: %.1f turns/s \n".format(avgSpeed) outString += "Average game duration: $avgDuration\n" diff --git a/core/src/com/unciv/logic/simulation/SimulationStep.kt b/core/src/com/unciv/logic/simulation/SimulationStep.kt index ac6581ea83..0490237942 100644 --- a/core/src/com/unciv/logic/simulation/SimulationStep.kt +++ b/core/src/com/unciv/logic/simulation/SimulationStep.kt @@ -1,12 +1,40 @@ package com.unciv.logic.simulation +import com.unciv.Constants import com.unciv.logic.GameInfo -class SimulationStep (gameInfo: GameInfo) { +class SimulationStep (gameInfo: GameInfo, statTurns: List = listOf()) { + val civilizations = gameInfo.civilizations.filter { it.civName != Constants.spectator }.map { it.civName } var turns = gameInfo.turns var victoryType = gameInfo.getCurrentPlayerCivilization().victoryManager.getVictoryTypeAchieved() var winner: String? = null - val currentPlayer = gameInfo.currentPlayer + var currentPlayer = gameInfo.currentPlayer + val turnStats = mutableMapOf>() // [civ][turn][stat] + + init { + for (civ in civilizations) { + for (turn in statTurns) + this.turnStats.getOrPut(civ) { mutableMapOf() }[turn] = MutableInt(-1) + this.turnStats.getOrPut(civ) { mutableMapOf() }[-1] = MutableInt(-1) // end of game + } + + } + + fun saveTurnStats(gameInfo: GameInfo) { + victoryType = gameInfo.getCurrentPlayerCivilization().victoryManager.getVictoryTypeAchieved() + val turn = if (victoryType != null) -1 else gameInfo.turns + for (civ in gameInfo.civilizations.filter { it.civName != Constants.spectator }) { + val popsum = civ.cities.sumOf { it.population.population } + //println("$civ $popsum") + turnStats[civ.civName]!![turn]!!.set(popsum) + } + } + + fun update(gameInfo: GameInfo) { + turns = gameInfo.turns + victoryType = gameInfo.getCurrentPlayerCivilization().victoryManager.getVictoryTypeAchieved() + currentPlayer = gameInfo.currentPlayer + } }