mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-23 19:43:13 -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)
|
return turns + (totalTurns * startPercent / 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getYear(turnOffset: Int = 0): Int {
|
fun getYear(turnOffset: Int = 0) = speed.turnToYear(getEquivalentTurn() + turnOffset).toInt()
|
||||||
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 calculateChecksum(): String {
|
fun calculateChecksum(): String {
|
||||||
val oldChecksum = checksum
|
val oldChecksum = checksum
|
||||||
|
@ -29,11 +29,39 @@ class Speed : RulesetObject(), IsPartOfGameInfoSerialization {
|
|||||||
var startYear: Float = -4000f
|
var startYear: Float = -4000f
|
||||||
var turns: ArrayList<HashMap<String, Float>> = ArrayList()
|
var turns: ArrayList<HashMap<String, Float>> = ArrayList()
|
||||||
|
|
||||||
data class YearsPerTurn(val yearInterval: Float, val untilTurn: Int)
|
// These could be private but for RulesetValidator checking it
|
||||||
val yearsPerTurn: ArrayList<YearsPerTurn> by lazy {
|
data class YearsPerTurn(val yearInterval: Float, val untilTurn: Int) {
|
||||||
ArrayList<YearsPerTurn>().apply {
|
internal constructor(rawRow: HashMap<String, Float>) : this(rawRow["yearsPerTurn"]!!, rawRow["untilTurn"]!!.toInt())
|
||||||
turns.forEach { this.add(YearsPerTurn(it["yearsPerTurn"]!!, it["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 {
|
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()))
|
yield(FormattedLine("Start year: [" + ("{[${abs(startYear).toInt()}] " + (if (startYear < 0) "BC" else "AD") + "}]").tr()))
|
||||||
}.toList()
|
}.toList()
|
||||||
override fun getSortGroup(ruleset: Ruleset): Int = (modifier * 1000).toInt()
|
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(
|
val yearText = YearTextUtil.toYearText(
|
||||||
civInfo.gameInfo.getYear(), civInfo.isLongCountDisplay()
|
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()
|
resourcesWrapper.clearChildren()
|
||||||
val civResources = civInfo.getCivResourcesByName()
|
val civResources = civInfo.getCivResourcesByName()
|
||||||
|
@ -155,9 +155,9 @@ internal class WorldScreenTopBarStats(topbar: WorldScreenTopBar) : ScalingTableW
|
|||||||
// kotlin Float division by Zero produces `Float.POSITIVE_INFINITY`, not an exception
|
// kotlin Float division by Zero produces `Float.POSITIVE_INFINITY`, not an exception
|
||||||
val turnsToNextPolicy = (civInfo.policies.getCultureNeededForNextPolicy() - civInfo.policies.storedCulture) / nextTurnStats.culture
|
val turnsToNextPolicy = (civInfo.policies.getCultureNeededForNextPolicy() - civInfo.policies.storedCulture) / nextTurnStats.culture
|
||||||
cultureString += when {
|
cultureString += when {
|
||||||
turnsToNextPolicy <= 0f -> " (!)" // Can choose policy right now
|
turnsToNextPolicy <= 0f -> "\u2004(!)" // Can choose policy right now
|
||||||
nextTurnStats.culture <= 0 -> " (${Fonts.infinity})" // when you start the game, you're not producing any culture
|
nextTurnStats.culture <= 0 -> "\u2004(${Fonts.infinity})" // when you start the game, you're not producing any culture
|
||||||
else -> " (" + Fonts.turn + " " + ceil(turnsToNextPolicy).toInt().tr() + ")"
|
else -> "\u2004(" + Fonts.turn + "\u2009" + ceil(turnsToNextPolicy).toInt().tr() + ")" // U+2004: Three-Per-Em Space, U+2009: Thin Space
|
||||||
}
|
}
|
||||||
return cultureString
|
return cultureString
|
||||||
}
|
}
|
||||||
|
@ -105,12 +105,16 @@ Each speed can have the following attributes:
|
|||||||
|
|
||||||
### Time interval per turn
|
### 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 |
|
| Attribute | Type | Default | Notes |
|
||||||
|--------------|---------|----------|------------------------------------------------------------------------------------------|
|
|--------------|---------|----------|------------------------------------------------------------|
|
||||||
| yearsPerTurn | Integer | Required | Number of years passed between turns |
|
| yearsPerTurn | Integer | Required | Number of years passing between turns |
|
||||||
| untilTurn | Integer | Required | Which turn that this "speed" is active until (if it is the last object, this is ignored) |
|
| 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.
|
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.badlogic.gdx.Gdx
|
||||||
import com.unciv.Constants
|
import com.unciv.Constants
|
||||||
import com.unciv.UncivGame
|
import com.unciv.UncivGame
|
||||||
|
import com.unciv.logic.GameInfo
|
||||||
import com.unciv.models.metadata.BaseRuleset
|
import com.unciv.models.metadata.BaseRuleset
|
||||||
import com.unciv.models.metadata.GameSettings
|
import com.unciv.models.metadata.GameSettings
|
||||||
import com.unciv.models.ruleset.Ruleset
|
import com.unciv.models.ruleset.Ruleset
|
||||||
@ -328,4 +329,50 @@ class BasicTests {
|
|||||||
}
|
}
|
||||||
return stats
|
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