mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-22 10:54:19 -04:00
Under-the-hood improvements around Speed and Years (#13482)
* Mini-refactor: YearsPerTurn can be immutable, support destructuring * Lint: Replace non-rendering unicode points with escapes * Wiki: Describe `Speed.turns` better * A unit test for turn-to-year conversion * Faster turn-to-year math * Readability changes
This commit is contained in:
parent
4ac0400c2e
commit
fce04aaddd
@ -304,21 +304,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
|
||||
return turns + (totalTurns * startPercent / 100)
|
||||
}
|
||||
|
||||
fun getYear(turnOffset: Int = 0): Int {
|
||||
val turn = getEquivalentTurn() + turnOffset
|
||||
val yearsToTurn = speed.yearsPerTurn
|
||||
var year = speed.startYear
|
||||
var i = 0
|
||||
var yearsPerTurn: Float
|
||||
|
||||
while (i < turn) {
|
||||
yearsPerTurn = (yearsToTurn.firstOrNull { i < it.untilTurn }?.yearInterval ?: yearsToTurn.last().yearInterval)
|
||||
year += yearsPerTurn
|
||||
++i
|
||||
}
|
||||
|
||||
return year.toInt()
|
||||
}
|
||||
fun getYear(turnOffset: Int = 0) = speed.turnToYear(getEquivalentTurn() + turnOffset).toInt()
|
||||
|
||||
fun calculateChecksum(): String {
|
||||
val oldChecksum = checksum
|
||||
|
@ -29,11 +29,39 @@ class Speed : RulesetObject(), IsPartOfGameInfoSerialization {
|
||||
var startYear: Float = -4000f
|
||||
var turns: ArrayList<HashMap<String, Float>> = ArrayList()
|
||||
|
||||
data class YearsPerTurn(val yearInterval: Float, val untilTurn: Int)
|
||||
val yearsPerTurn: ArrayList<YearsPerTurn> by lazy {
|
||||
ArrayList<YearsPerTurn>().apply {
|
||||
turns.forEach { this.add(YearsPerTurn(it["yearsPerTurn"]!!, it["untilTurn"]!!.toInt())) }
|
||||
// These could be private but for RulesetValidator checking it
|
||||
data class YearsPerTurn(val yearInterval: Float, val untilTurn: Int) {
|
||||
internal constructor(rawRow: HashMap<String, Float>) : this(rawRow["yearsPerTurn"]!!, rawRow["untilTurn"]!!.toInt())
|
||||
}
|
||||
val yearsPerTurn: ArrayList<YearsPerTurn> by lazy { turns.mapTo(ArrayList()) { YearsPerTurn(it) } }
|
||||
|
||||
/** End of defined turn range, used for starting Era's `startPercent` calculation */
|
||||
fun numTotalTurns(): Int = yearsPerTurn.last().untilTurn
|
||||
|
||||
/** Calculate a Year from a turn number.
|
||||
*
|
||||
* Note that years can have fractional parts and the integer part of the year for two consecutive turns _can_ be equal,
|
||||
* but Unciv currently has no way to display that. This is left as Float to enable such display in the future,
|
||||
* maybe as months, or even the 18+1 'months' of the mayan Haab'.
|
||||
*
|
||||
* @param turn The logical turn number, any offset from starting in an advanced Era already added in
|
||||
*/
|
||||
fun turnToYear(turn: Int): Float {
|
||||
var year = startYear
|
||||
var intervalStartTurn = 0
|
||||
val lastIntervalEndTurn = numTotalTurns()
|
||||
for ((turnLength, intervalEndTurn) in yearsPerTurn) {
|
||||
if (intervalStartTurn >= turn) break // ensure year isn't projected backwards for negative `turn`
|
||||
if (turn <= intervalEndTurn || intervalEndTurn == lastIntervalEndTurn) {
|
||||
// We can interpolate linearly within this interval and are done
|
||||
year += (turn - intervalStartTurn) * turnLength
|
||||
break
|
||||
}
|
||||
// Accumulate total length in years of this interval and move on to the following intervals.
|
||||
year += (intervalEndTurn - intervalStartTurn) * turnLength
|
||||
intervalStartTurn = intervalEndTurn
|
||||
}
|
||||
return year
|
||||
}
|
||||
|
||||
val statCostModifiers: EnumMap<Stat, Float> by lazy {
|
||||
@ -81,6 +109,4 @@ class Speed : RulesetObject(), IsPartOfGameInfoSerialization {
|
||||
yield(FormattedLine("Start year: [" + ("{[${abs(startYear).toInt()}] " + (if (startYear < 0) "BC" else "AD") + "}]").tr()))
|
||||
}.toList()
|
||||
override fun getSortGroup(ruleset: Ruleset): Int = (modifier * 1000).toInt()
|
||||
|
||||
fun numTotalTurns(): Int = yearsPerTurn.last().untilTurn
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ internal class WorldScreenTopBarResources(topbar: WorldScreenTopBar) : ScalingTa
|
||||
val yearText = YearTextUtil.toYearText(
|
||||
civInfo.gameInfo.getYear(), civInfo.isLongCountDisplay()
|
||||
)
|
||||
turnsLabel.setText(Fonts.turn + " " + civInfo.gameInfo.turns.tr() + " | " + yearText)
|
||||
turnsLabel.setText(Fonts.turn + "\u2004" + civInfo.gameInfo.turns.tr() + "\u2004|\u2004" + yearText) // U+2004: Three-Per-Em Space
|
||||
|
||||
resourcesWrapper.clearChildren()
|
||||
val civResources = civInfo.getCivResourcesByName()
|
||||
|
@ -60,7 +60,7 @@ internal class WorldScreenTopBarStats(topbar: WorldScreenTopBar) : ScalingTableW
|
||||
isTransform = false
|
||||
|
||||
defaults().pad(defaultTopPad, defaultHorizontalPad, defaultBottomPad, defaultHorizontalPad)
|
||||
|
||||
|
||||
fun addStat(
|
||||
icon: String,
|
||||
label: Label,
|
||||
@ -124,7 +124,7 @@ internal class WorldScreenTopBarStats(topbar: WorldScreenTopBar) : ScalingTableW
|
||||
resetScale()
|
||||
|
||||
val nextTurnStats = civInfo.stats.statsForNextTurn
|
||||
|
||||
|
||||
goldLabel.setText(civInfo.gold.tr())
|
||||
goldPerTurnLabel.setText(rateLabel(nextTurnStats.gold))
|
||||
|
||||
@ -155,9 +155,9 @@ internal class WorldScreenTopBarStats(topbar: WorldScreenTopBar) : ScalingTableW
|
||||
// kotlin Float division by Zero produces `Float.POSITIVE_INFINITY`, not an exception
|
||||
val turnsToNextPolicy = (civInfo.policies.getCultureNeededForNextPolicy() - civInfo.policies.storedCulture) / nextTurnStats.culture
|
||||
cultureString += when {
|
||||
turnsToNextPolicy <= 0f -> " (!)" // Can choose policy right now
|
||||
nextTurnStats.culture <= 0 -> " (${Fonts.infinity})" // when you start the game, you're not producing any culture
|
||||
else -> " (" + Fonts.turn + " " + ceil(turnsToNextPolicy).toInt().tr() + ")"
|
||||
turnsToNextPolicy <= 0f -> "\u2004(!)" // Can choose policy right now
|
||||
nextTurnStats.culture <= 0 -> "\u2004(${Fonts.infinity})" // when you start the game, you're not producing any culture
|
||||
else -> "\u2004(" + Fonts.turn + "\u2009" + ceil(turnsToNextPolicy).toInt().tr() + ")" // U+2004: Three-Per-Em Space, U+2009: Thin Space
|
||||
}
|
||||
return cultureString
|
||||
}
|
||||
|
@ -105,12 +105,16 @@ Each speed can have the following attributes:
|
||||
|
||||
### Time interval per turn
|
||||
|
||||
The "turns" attribute defines the number of years passed between turns. The attribute consists of a list of hashmaps, each hashmaps in turn having 2 required attributes: "yearsPerTurn" (Float) and "untilTurn" (Integer)
|
||||
The "turns" attribute defines the number of years passing between turns. The attribute consists of a list of objects, each having 2 required attributes: "yearsPerTurn" (Float) and "untilTurn" (Integer)
|
||||
|
||||
| Attribute | Type | Default | Notes |
|
||||
|--------------|---------|----------|------------------------------------------------------------------------------------------|
|
||||
| yearsPerTurn | Integer | Required | Number of years passed between turns |
|
||||
| untilTurn | Integer | Required | Which turn that this "speed" is active until (if it is the last object, this is ignored) |
|
||||
| Attribute | Type | Default | Notes |
|
||||
|--------------|---------|----------|------------------------------------------------------------|
|
||||
| yearsPerTurn | Integer | Required | Number of years passing between turns |
|
||||
| untilTurn | Integer | Required | End of this interval (if it is the last object, see below) |
|
||||
|
||||
For each row, "yearsPerTurn" is applied up to the "untilTurn"-1 to "untilTurn" step.
|
||||
The last "untilTurn" in the list is ignored for year calculation, that is, if a game passes that turn number, years continue to increment by the "yearsPerTurn" of the last entry.
|
||||
However, this is used when starting a game in a later Era: Era.startPercent is relative to the last "untilTurn".
|
||||
|
||||
The code below is an example of a valid "turns" definition and it specifies that the first 50 turns of a game last for 60 years each, then the next 30 turns (and any played after the 80th) last for 40 years each.
|
||||
|
||||
|
@ -4,6 +4,7 @@ package com.unciv.testing
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.unciv.Constants
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.GameInfo
|
||||
import com.unciv.models.metadata.BaseRuleset
|
||||
import com.unciv.models.metadata.GameSettings
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
@ -328,4 +329,50 @@ class BasicTests {
|
||||
}
|
||||
return stats
|
||||
}
|
||||
|
||||
@Test
|
||||
fun turnToYearTest() {
|
||||
// Pretty random choice, but ensures 'turn > last definition' and 'float to int rounding' is tested
|
||||
val testData = mapOf(
|
||||
"Quick" to mapOf(
|
||||
0 to -4000,
|
||||
100 to 800,
|
||||
200 to 1860,
|
||||
300 to 2020,
|
||||
5000 to 6720
|
||||
),
|
||||
"Standard" to mapOf(
|
||||
99 to -400,
|
||||
479 to 2039,
|
||||
999 to 2299
|
||||
),
|
||||
"Epic" to mapOf(
|
||||
66 to -2350,
|
||||
666 to 2008,
|
||||
4242 to 3796
|
||||
),
|
||||
"Marathon" to mapOf(
|
||||
222 to -1280,
|
||||
1111 to 1978,
|
||||
1400 to 2041
|
||||
),
|
||||
)
|
||||
val gameInfo = GameInfo()
|
||||
gameInfo.ruleset = ruleset
|
||||
Assert.assertEquals(0, ruleset.eras[gameInfo.gameParameters.startingEra]!!.startPercent)
|
||||
var fails = 0
|
||||
|
||||
for ((speedName, tests) in testData) {
|
||||
val speed = ruleset.speeds[speedName]!!
|
||||
gameInfo.speed = speed
|
||||
for ((turn, expected) in tests) {
|
||||
val actual = gameInfo.getYear(turn)
|
||||
if (actual == expected) continue
|
||||
println("speed: $speedName, turn: $turn, expected: $expected, actual: $actual")
|
||||
fails++
|
||||
}
|
||||
}
|
||||
|
||||
Assert.assertEquals("Some turn to year conversions do not match", 0, fails)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user