mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-23 14:19:15 +07:00
Silently log ranking stats for each major civ every turn (#8964)
* Record each stat each round for each civilization. * Implement custom serialization and encapsulate logic in separate CivRankingHistory.kt * Address comments * Address comments and add RankingTypeTest.kt
This commit is contained in:
@ -4,6 +4,7 @@ import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.files.FileHandle
|
||||
import com.badlogic.gdx.utils.Json
|
||||
import com.badlogic.gdx.utils.SerializationException
|
||||
import com.unciv.logic.civilization.CivRankingHistory
|
||||
import com.unciv.logic.map.tile.TileHistory
|
||||
import com.unciv.ui.components.KeyCharAndCode
|
||||
import com.unciv.ui.components.KeyboardBindings
|
||||
@ -27,6 +28,7 @@ fun json() = Json().apply {
|
||||
setSerializer(KeyCharAndCode::class.java, KeyCharAndCode.Serializer())
|
||||
setSerializer(KeyboardBindings::class.java, KeyboardBindings.Serializer())
|
||||
setSerializer(TileHistory::class.java, TileHistory.Serializer())
|
||||
setSerializer(CivRankingHistory::class.java, CivRankingHistory.Serializer())
|
||||
}
|
||||
|
||||
/**
|
||||
|
57
core/src/com/unciv/logic/civilization/CivRankingHistory.kt
Normal file
57
core/src/com/unciv/logic/civilization/CivRankingHistory.kt
Normal file
@ -0,0 +1,57 @@
|
||||
package com.unciv.logic.civilization
|
||||
|
||||
import com.badlogic.gdx.utils.Json
|
||||
import com.badlogic.gdx.utils.JsonValue
|
||||
import com.unciv.logic.IsPartOfGameInfoSerialization
|
||||
import com.unciv.ui.screens.victoryscreen.RankingType
|
||||
|
||||
/** Records for each turn (key of outer map) what the score (value of inner map) was for each RankingType. */
|
||||
open class CivRankingHistory : HashMap<Int, Map<RankingType, Int>>(),
|
||||
IsPartOfGameInfoSerialization {
|
||||
|
||||
/**
|
||||
* Returns a shallow copy of this [CivRankingHistory] instance.
|
||||
* The inner [Map] instances are not cloned, only their references are copied.
|
||||
*/
|
||||
override fun clone(): CivRankingHistory {
|
||||
val toReturn = CivRankingHistory()
|
||||
toReturn.putAll(this)
|
||||
return toReturn
|
||||
}
|
||||
|
||||
fun recordRankingStats(civilization: Civilization) {
|
||||
this[civilization.gameInfo.turns] =
|
||||
RankingType.values().associateWith { civilization.getStatForRanking(it) }
|
||||
}
|
||||
|
||||
/** Custom Json formatter for a [CivRankingHistory].
|
||||
* Output looks like this: `statsHistory:{0:{S:50,G:120,...},1:{S:55,G:80,...}}`
|
||||
*/
|
||||
class Serializer : Json.Serializer<CivRankingHistory> {
|
||||
override fun write(json: Json, `object`: CivRankingHistory, knownType: Class<*>?) {
|
||||
json.writeObjectStart()
|
||||
for ((turn, rankings) in `object`) {
|
||||
json.writeObjectStart(turn.toString())
|
||||
for ((rankingType, score) in rankings) {
|
||||
json.writeValue(rankingType.idForSerialization, score)
|
||||
}
|
||||
json.writeObjectEnd()
|
||||
}
|
||||
json.writeObjectEnd()
|
||||
}
|
||||
|
||||
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?) =
|
||||
CivRankingHistory().apply {
|
||||
for (entry in jsonData) {
|
||||
val turn = entry.name.toInt()
|
||||
val rankings = mutableMapOf<RankingType, Int>()
|
||||
for (rankingEntry in entry) {
|
||||
val rankingType = RankingType.fromIdForSerialization(rankingEntry.name)
|
||||
?: continue // Silently drop unknown ranking types.
|
||||
rankings[rankingType] = rankingEntry.asInt()
|
||||
}
|
||||
this[turn] = rankings
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -237,6 +237,8 @@ class Civilization : IsPartOfGameInfoSerialization {
|
||||
@Transient
|
||||
var hasLongCountDisplayUnique = false
|
||||
|
||||
var statsHistory = CivRankingHistory()
|
||||
|
||||
constructor()
|
||||
|
||||
constructor(civName: String) {
|
||||
@ -285,6 +287,7 @@ class Civilization : IsPartOfGameInfoSerialization {
|
||||
toReturn.totalFaithForContests = totalFaithForContests
|
||||
toReturn.attacksSinceTurnStart = attacksSinceTurnStart.copy()
|
||||
toReturn.hasMovedAutomatedUnits = hasMovedAutomatedUnits
|
||||
toReturn.statsHistory = statsHistory.clone()
|
||||
return toReturn
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,10 @@ class TurnManager(val civInfo: Civilization) {
|
||||
|
||||
|
||||
fun startTurn() {
|
||||
if (civInfo.isMajorCiv() && civInfo.isAlive()) {
|
||||
civInfo.statsHistory.recordRankingStats(civInfo)
|
||||
}
|
||||
|
||||
civInfo.civConstructions.startTurn()
|
||||
civInfo.attacksSinceTurnStart.clear()
|
||||
civInfo.updateStatsForNextTurn() // for things that change when turn passes e.g. golden age, city state influence
|
||||
|
@ -4,16 +4,24 @@ import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Image
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
|
||||
enum class RankingType(val getImage: ()->Image?) {
|
||||
enum class RankingType(val getImage: () -> Image?, val idForSerialization: String) {
|
||||
// production, gold, happiness, and culture already have icons added when the line is `tr()`anslated
|
||||
Score({ ImageGetter.getImage("CityStateIcons/Cultured").apply { color = Color.FIREBRICK } }),
|
||||
Population({ ImageGetter.getStatIcon("Population") }),
|
||||
Crop_Yield({ ImageGetter.getStatIcon("Food") }),
|
||||
Production({ null }),
|
||||
Gold({ null }),
|
||||
Territory({ ImageGetter.getImage("OtherIcons/Hexagon") }),
|
||||
Force({ ImageGetter.getImage("OtherIcons/Shield") }),
|
||||
Happiness({ null }),
|
||||
Technologies({ ImageGetter.getStatIcon("Science") }),
|
||||
Culture({ null })
|
||||
Score(
|
||||
{ ImageGetter.getImage("CityStateIcons/Cultured").apply { color = Color.FIREBRICK } },
|
||||
"S"
|
||||
),
|
||||
Population({ ImageGetter.getStatIcon("Population") }, "N"),
|
||||
Crop_Yield({ ImageGetter.getStatIcon("Food") }, "C"),
|
||||
Production({ null }, "P"),
|
||||
Gold({ null }, "G"),
|
||||
Territory({ ImageGetter.getImage("OtherIcons/Hexagon") }, "T"),
|
||||
Force({ ImageGetter.getImage("OtherIcons/Shield") }, "F"),
|
||||
Happiness({ null }, "H"),
|
||||
Technologies({ ImageGetter.getStatIcon("Science") }, "W"),
|
||||
Culture({ null }, "A");
|
||||
|
||||
companion object {
|
||||
fun fromIdForSerialization(s: String): RankingType? =
|
||||
values().firstOrNull { it.idForSerialization == s }
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,22 @@
|
||||
package com.unciv.ui.screens.victoryscreen
|
||||
|
||||
import com.unciv.testing.GdxTestRunner
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(GdxTestRunner::class)
|
||||
class RankingTypeTests {
|
||||
|
||||
@Test
|
||||
fun checkIdForSerializationUniqueness() {
|
||||
val uniqueIds = HashSet<String>()
|
||||
for (rankingType in RankingType.values()) {
|
||||
val id = rankingType.idForSerialization
|
||||
Assert.assertTrue(
|
||||
"Id $id for RankingType $rankingType is not unique",
|
||||
uniqueIds.add(id)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user