Simulation: New Stats (#13821)

* Changes adding new stats

* Refactor in to functions

* Flag prints

* Fix crash with minor Civs
This commit is contained in:
itanasi 2025-08-22 03:09:45 -07:00 committed by GitHub
parent 57eb2b33f3
commit 126c1953c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 108 additions and 32 deletions

View File

@ -25,12 +25,16 @@ class Simulation(
private val statTurns: List<Int> = listOf() private val statTurns: List<Int> = listOf()
) { ) {
private val maxSimulations = threadsNumber * simulationsPerThread private val maxSimulations = threadsNumber * simulationsPerThread
val civilizations = newGameInfo.civilizations.filter { it.civName != Constants.spectator }.map { it.civName } //val civilizations = newGameInfo.civilizations.filter { it.civName != Constants.spectator }.map { it.civName }
private val majorCivs = newGameInfo.civilizations.filter { it.civName != Constants.spectator }.filter { it.isMajorCiv() }.size private val majorCivs = newGameInfo.civilizations.filter { it.civName != Constants.spectator }.filter { it.isMajorCiv() }.map { it.civName }
private val numMajorCivs = newGameInfo.civilizations.filter { it.civName != Constants.spectator }.filter { it.isMajorCiv() }.size
private var startTime: Long = 0 private var startTime: Long = 0
var steps = ArrayList<SimulationStep>() var steps = ArrayList<SimulationStep>()
var numWins = mutableMapOf<String, MutableInt>() var numWins = mutableMapOf<String, MutableInt>()
private var summaryStats = HashMap<String, HashMap<Int, HashMap<Stat, MutableInt>>>() // [civ][turn][stat]=value private var summaryStatsPop = HashMap<String, HashMap<Int, HashMap<Stat, MutableInt>>>() // [civ][turn][stat]=value
private var summaryStatsProd = HashMap<String, HashMap<Int, HashMap<Stat, MutableInt>>>() // [civ][turn][stat]=value
private var summaryStatsCities = HashMap<String, HashMap<Int, HashMap<Stat, MutableInt>>>() // [civ][turn][stat]=value
private var summaryStatsAvgPop = HashMap<String, HashMap<Int, HashMap<Stat, MutableInt>>>() // [civ][turn][stat]=value
private var winRateByVictory = HashMap<String, MutableMap<String, MutableInt>>() private var winRateByVictory = HashMap<String, MutableMap<String, MutableInt>>()
private var winTurnByVictory = HashMap<String, MutableMap<String, MutableInt>>() private var winTurnByVictory = HashMap<String, MutableMap<String, MutableInt>>()
private var avgSpeed = 0f private var avgSpeed = 0f
@ -42,18 +46,15 @@ class Simulation(
SUM, SUM,
NUM NUM
} }
// print flags
private val printPop = true
private val printProd = false
private val printCityCnt = false
private val printAvgCityPop = false
init{ init{
for (civ in civilizations) { for (civ in majorCivs) {
this.numWins[civ] = MutableInt(0) this.numWins[civ] = 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() winRateByVictory[civ] = mutableMapOf()
for (victory in UncivGame.Current.gameInfo!!.ruleset.victories.keys) for (victory in UncivGame.Current.gameInfo!!.ruleset.victories.keys)
winRateByVictory[civ]!![victory] = MutableInt(0) winRateByVictory[civ]!![victory] = MutableInt(0)
@ -61,6 +62,24 @@ class Simulation(
for (victory in UncivGame.Current.gameInfo!!.ruleset.victories.keys) for (victory in UncivGame.Current.gameInfo!!.ruleset.victories.keys)
winTurnByVictory[civ]!![victory] = MutableInt(0) winTurnByVictory[civ]!![victory] = MutableInt(0)
} }
initHash(summaryStatsPop)
initHash(summaryStatsProd)
initHash(summaryStatsCities)
initHash(summaryStatsAvgPop)
}
// Need to initialize the values
// Later will iterate with flatMap
private fun initHash(summary: HashMap<String, HashMap<Int, HashMap<Stat, MutableInt>>>) {
for (civ in majorCivs) {
for (turn in statTurns) {
summary.getOrPut(civ) { hashMapOf() }.getOrPut(turn){hashMapOf()}[Stat.SUM] = MutableInt(0)
summary.getOrPut(civ) { hashMapOf() }.getOrPut(turn){hashMapOf()}[Stat.NUM] = MutableInt(0)
}
val turn = -1 // end of game
summary.getOrPut(civ) { hashMapOf() }.getOrPut(turn){hashMapOf()}[Stat.SUM] = MutableInt(0)
summary.getOrPut(civ) { hashMapOf() }.getOrPut(turn){hashMapOf()}[Stat.NUM] = MutableInt(0)
}
} }
fun start() = runBlocking { fun start() = runBlocking {
@ -136,13 +155,32 @@ class Simulation(
getStats() getStats()
println(text()) println(text())
} }
private fun summaryStatSet(summaryHash: HashMap<String, HashMap<Int, HashMap<Stat, MutableInt>>>,
civ: String, turn: Int, stat: MutableMap<String, MutableMap<Int, MutableInt>>) {
if (stat[civ]!![turn]!!.value != -1) {
summaryHash[civ]!![turn]!![Stat.SUM]!!.add(stat[civ]!![turn]!!.value)
summaryHash[civ]!![turn]!![Stat.NUM]!!.inc()
//println("civ ${civ} @ ${turn} value ${stat[civ]!![turn]!!.value}")
}
}
fun getStats() { private fun getStats() {
// win Rate // win Rate
numWins.values.forEach { it.value = 0 } numWins.values.forEach { it.value = 0 }
winRateByVictory.flatMap { it.value.values }.forEach { it.value = 0 } winRateByVictory.flatMap { it.value.values }.forEach { it.value = 0 }
winTurnByVictory.flatMap { it.value.values }.forEach { it.value = 0 } winTurnByVictory.flatMap { it.value.values }.forEach { it.value = 0 }
summaryStats.flatMap { it.value.values }.forEach { // reset to 0
summaryStatsPop.flatMap { it.value.values }.forEach {
it.values.forEach { it.value = 0 }
}
summaryStatsProd.flatMap { it.value.values }.forEach {
it.values.forEach { it.value = 0 }
}
summaryStatsCities.flatMap { it.value.values }.forEach {
it.values.forEach { it.value = 0 }
}
summaryStatsAvgPop.flatMap { it.value.values }.forEach {
it.values.forEach { it.value = 0 } it.values.forEach { it.value = 0 }
} }
steps.forEach { steps.forEach {
@ -151,17 +189,22 @@ class Simulation(
winRateByVictory[it.winner!!]!![it.victoryType]!!.inc() winRateByVictory[it.winner!!]!![it.victoryType]!!.inc()
winTurnByVictory[it.winner!!]!![it.victoryType]!!.add(it.turns) winTurnByVictory[it.winner!!]!![it.victoryType]!!.add(it.turns)
} }
for (civ in civilizations) { for (civ in majorCivs) {
for (turn in statTurns) { for (turn in statTurns) {
if (it.turnStats[civ]!![turn]!!.value != -1) { summaryStatSet(summaryStatsPop, civ, turn, it.turnStatsPop)
summaryStats[civ]!![turn]!![Stat.SUM]!!.add(it.turnStats[civ]!![turn]!!.value) summaryStatSet(summaryStatsProd, civ, turn, it.turnStatsProd)
summaryStats[civ]!![turn]!![Stat.NUM]!!.inc() summaryStatSet(summaryStatsCities, civ, turn, it.turnStatsCities)
//println("civ ${civ} @ ${turn} value ${it.turnStats[civ]!![turn]!!.value} avg ${summaryStats[civ]!![turn]!!["avg"]!!.value} numAvg ${summaryStats[civ]!![turn]!!["numAvg"]!!.value}") if (it.turnStatsPop[civ]!![turn]!!.value != -1 && it.turnStatsCities[civ]!![turn]!!.value != -1) {
summaryStatsAvgPop[civ]!![turn]!![Stat.SUM]!!.add(it.turnStatsPop[civ]!![turn]!!.value/it.turnStatsCities[civ]!![turn]!!.value)
summaryStatsAvgPop[civ]!![turn]!![Stat.NUM]!!.inc()
} }
} }
val turn = -1 // end of game val turn = -1 // end of game
summaryStats[civ]!![turn]!![Stat.SUM]!!.add(it.turnStats[civ]!![turn]!!.value) summaryStatSet(summaryStatsPop, civ, turn, it.turnStatsPop)
summaryStats[civ]!![turn]!![Stat.NUM]!!.inc() summaryStatSet(summaryStatsProd, civ, turn, it.turnStatsProd)
summaryStatSet(summaryStatsCities, civ, turn, it.turnStatsCities)
summaryStatsAvgPop[civ]!![turn]!![Stat.SUM]!!.add(it.turnStatsPop[civ]!![turn]!!.value/it.turnStatsCities[civ]!![turn]!!.value)
summaryStatsAvgPop[civ]!![turn]!![Stat.NUM]!!.inc()
} }
} }
totalTurns = steps.sumOf { it.turns } totalTurns = steps.sumOf { it.turns }
@ -169,13 +212,20 @@ class Simulation(
avgSpeed = totalTurns.toFloat() / totalDuration.inWholeSeconds avgSpeed = totalTurns.toFloat() / totalDuration.inWholeSeconds
avgDuration = totalDuration / steps.size avgDuration = totalDuration / steps.size
} }
// Helper text formatter
private fun summaryStatsText(summaryStats: HashMap<Stat, MutableInt>,
turn: Int, statStr: String): String {
val turnStr = if(turn == -1) "END" else turn
return "@$turnStr: $statStr avg=${summaryStats[Stat.SUM]!!.value.toFloat() / summaryStats[Stat.NUM]!!.value.toFloat()} cnt=${summaryStats[Stat.NUM]!!.value}\n"
}
fun text(): String { fun text(): String {
var outString = "" var outString = ""
for (civ in civilizations) { for (civ in majorCivs) {
val numSteps = max(steps.size, 1) val numSteps = max(steps.size, 1)
val expWinRate = 1f / majorCivs val expWinRate = 1f / numMajorCivs
if (numWins[civ]!!.value == 0) continue if (numWins[civ]!!.value == 0) continue
val winRate = String.format("%.1f", numWins[civ]!!.value * 100f / numSteps) val winRate = String.format("%.1f", numWins[civ]!!.value * 100f / numSteps)
@ -204,12 +254,24 @@ class Simulation(
} }
outString += "avg turns\n" outString += "avg turns\n"
for (turn in statTurns) { for (turn in statTurns) {
val turnStats = summaryStats[civ]!![turn]!! if(printPop)
outString += "@$turn: popsum avg=${turnStats[Stat.SUM]!!.value.toFloat() / turnStats[Stat.NUM]!!.value.toFloat()} cnt=${turnStats[Stat.NUM]!!.value}\n" outString += summaryStatsText(summaryStatsPop[civ]!![turn]!!, turn, "popSum")
if(printProd)
outString += summaryStatsText(summaryStatsProd[civ]!![turn]!!, turn, "prodSum")
if(printCityCnt)
outString += summaryStatsText(summaryStatsCities[civ]!![turn]!!, turn, "cityCount")
if(printAvgCityPop)
outString += summaryStatsText(summaryStatsAvgPop[civ]!![turn]!!, turn, "avgCityPop")
} }
val turn = -1 // end of match val turn = -1 // end of match
val turnStats = summaryStats[civ]!![turn]!! if(printPop)
outString += "@END: popsum avg=${turnStats[Stat.SUM]!!.value.toFloat()/turnStats[Stat.NUM]!!.value.toFloat()} cnt=${turnStats[Stat.NUM]!!.value}\n" outString += summaryStatsText(summaryStatsPop[civ]!![turn]!!, turn, "popSum")
if(printProd)
outString += summaryStatsText(summaryStatsProd[civ]!![turn]!!, turn, "prodSum")
if(printCityCnt)
outString += summaryStatsText(summaryStatsCities[civ]!![turn]!!, turn, "cityCount")
if(printAvgCityPop)
outString += summaryStatsText(summaryStatsAvgPop[civ]!![turn]!!, turn, "avgCityPop")
} }
outString += "\nAverage speed: %.1f turns/s \n".format(avgSpeed) outString += "\nAverage speed: %.1f turns/s \n".format(avgSpeed)
outString += "Average game duration: $avgDuration\n" outString += "Average game duration: $avgDuration\n"

View File

@ -2,6 +2,7 @@ package com.unciv.logic.simulation
import com.unciv.Constants import com.unciv.Constants
import com.unciv.logic.GameInfo import com.unciv.logic.GameInfo
import kotlin.math.roundToInt
class SimulationStep (gameInfo: GameInfo, statTurns: List<Int> = listOf()) { class SimulationStep (gameInfo: GameInfo, statTurns: List<Int> = listOf()) {
val civilizations = gameInfo.civilizations.filter { it.civName != Constants.spectator }.map { it.civName } val civilizations = gameInfo.civilizations.filter { it.civName != Constants.spectator }.map { it.civName }
@ -9,13 +10,20 @@ class SimulationStep (gameInfo: GameInfo, statTurns: List<Int> = listOf()) {
var victoryType = gameInfo.getCurrentPlayerCivilization().victoryManager.getVictoryTypeAchieved() var victoryType = gameInfo.getCurrentPlayerCivilization().victoryManager.getVictoryTypeAchieved()
var winner: String? = null var winner: String? = null
var currentPlayer = gameInfo.currentPlayer var currentPlayer = gameInfo.currentPlayer
val turnStats = mutableMapOf<String, MutableMap<Int, MutableInt>>() // [civ][turn][stat] val turnStatsPop = mutableMapOf<String, MutableMap<Int, MutableInt>>() // [civ][turn][stat]
val turnStatsProd = mutableMapOf<String, MutableMap<Int, MutableInt>>() // [civ][turn][stat]
val turnStatsCities = mutableMapOf<String, MutableMap<Int, MutableInt>>() // [civ][turn][stat]
init { init {
for (civ in civilizations) { for (civ in civilizations) {
for (turn in statTurns) for (turn in statTurns) {
this.turnStats.getOrPut(civ) { mutableMapOf() }[turn] = MutableInt(-1) this.turnStatsPop.getOrPut(civ) { mutableMapOf() }[turn] = MutableInt(-1)
this.turnStats.getOrPut(civ) { mutableMapOf() }[-1] = MutableInt(-1) // end of game this.turnStatsProd.getOrPut(civ) { mutableMapOf() }[turn] = MutableInt(-1)
this.turnStatsCities.getOrPut(civ) { mutableMapOf() }[turn] = MutableInt(-1)
}
this.turnStatsPop.getOrPut(civ) { mutableMapOf() }[-1] = MutableInt(-1) // end of game
this.turnStatsProd.getOrPut(civ) { mutableMapOf() }[-1] = MutableInt(-1) // end of game
this.turnStatsCities.getOrPut(civ) { mutableMapOf() }[-1] = MutableInt(-1) // end of game
} }
} }
@ -26,7 +34,13 @@ class SimulationStep (gameInfo: GameInfo, statTurns: List<Int> = listOf()) {
for (civ in gameInfo.civilizations.filter { it.civName != Constants.spectator }) { for (civ in gameInfo.civilizations.filter { it.civName != Constants.spectator }) {
val popsum = civ.cities.sumOf { it.population.population } val popsum = civ.cities.sumOf { it.population.population }
//println("$civ $popsum") //println("$civ $popsum")
turnStats[civ.civName]!![turn]!!.set(popsum) turnStatsPop[civ.civName]!![turn]!!.set(popsum)
val prodsum = civ.cities.sumOf { it.cityStats.currentCityStats.production.roundToInt() }
//println("$civ $prodsum")
turnStatsProd[civ.civName]!![turn]!!.set(prodsum)
val cityCnt = civ.cities.count()
//println("$civ $cityCnt")
turnStatsCities[civ.civName]!![turn]!!.set(cityCnt)
} }
} }