UnitMovementAlgorithms -> UnitMovement and modernized city connections tests

This commit is contained in:
Yair Morgenstern
2023-03-20 00:20:34 +02:00
parent 61f7b14f04
commit a37eb28964
8 changed files with 101 additions and 150 deletions

View File

@ -375,8 +375,8 @@ class City : IsPartOfGameInfoSerialization {
expansion.city = this
expansion.setTransients()
cityConstructions.city = this
cityConstructions.setTransients()
religion.setTransients(this)
cityConstructions.setTransients()
espionage.setTransients(this)
}

View File

@ -43,7 +43,7 @@ class CityFounder {
city.expansion.reset()
city.tryUpdateRoadStatus()
civInfo.cache.updateCitiesConnectedToCapital() // Carthage cities can connect immediately
// civInfo.cache.updateCitiesConnectedToCapital() // Carthage cities can connect immediately
val tile = city.getCenterTile()
for (terrainFeature in tile.terrainFeatures.filter {

View File

@ -7,7 +7,7 @@ import com.unciv.logic.civilization.NotificationCategory
import com.unciv.logic.civilization.NotificationIcon
import com.unciv.logic.civilization.PopupAlert
import com.unciv.logic.map.tile.Tile
import com.unciv.logic.map.mapunit.UnitMovementAlgorithms
import com.unciv.logic.map.mapunit.UnitMovement
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.Stat
import com.unciv.models.stats.Stats
@ -129,7 +129,7 @@ class DiplomacyFunctions(val civInfo:Civilization){
* considering only civ-wide filters.
* Use [Tile.canCivPassThrough] to check whether units of a civilization can pass through
* a specific tile, considering only civ-wide filters.
* Use [UnitMovementAlgorithms.canPassThrough] to check whether a specific unit can pass through
* Use [UnitMovement.canPassThrough] to check whether a specific unit can pass through
* a specific tile.
*/
fun canPassThroughTiles(otherCiv: Civilization): Boolean {

View File

@ -42,7 +42,7 @@ class MapUnit : IsPartOfGameInfoSerialization {
lateinit var currentTile: Tile
@Transient
val movement = UnitMovementAlgorithms(this)
val movement = UnitMovement(this)
@Transient
val upgrade = UnitUpgradeManager(this)

View File

@ -10,7 +10,7 @@ import com.unciv.logic.map.tile.Tile
import com.unciv.models.helpers.UnitMovementMemoryType
import com.unciv.models.ruleset.unique.UniqueType
class UnitMovementAlgorithms(val unit: MapUnit) {
class UnitMovement(val unit: MapUnit) {
private val pathfindingCache = PathfindingCache(unit)
@ -815,11 +815,11 @@ class UnitMovementAlgorithms(val unit: MapUnit) {
}
/**
* Cache for the results of [UnitMovementAlgorithms.getDistanceToTiles] accounting for zone of control.
* [UnitMovementAlgorithms.getDistanceToTiles] is called in numerous places for AI pathfinding so
* Cache for the results of [UnitMovement.getDistanceToTiles] accounting for zone of control.
* [UnitMovement.getDistanceToTiles] is called in numerous places for AI pathfinding so
* being able to skip redundant calculations helps out over a long game (especially with high level
* AI or a big map). Same thing with [UnitMovementAlgorithms.getShortestPath] which is called in
* [UnitMovementAlgorithms.canReach] and in [UnitMovementAlgorithms.headTowards]. Often, the AI will
* AI or a big map). Same thing with [UnitMovement.getShortestPath] which is called in
* [UnitMovement.canReach] and in [UnitMovement.headTowards]. Often, the AI will
* see if it can reach a tile using canReach then if it can, it will headTowards it. We can cache
* the result since otherwise this is a redundant calculation that will find the same path.
*/
@ -875,7 +875,7 @@ class PathfindingCache(private val unit: MapUnit) {
}
}
class PathsToTilesWithinTurn : LinkedHashMap<Tile, UnitMovementAlgorithms.ParentTileAndTotalDistance>() {
class PathsToTilesWithinTurn : LinkedHashMap<Tile, UnitMovement.ParentTileAndTotalDistance>() {
fun getPathToTile(tile: Tile): List<Tile> {
if (!containsKey(tile))
throw Exception("Can't reach this tile!")

View File

@ -98,7 +98,7 @@ class TileStatFunctions(val tile: Tile) {
stats.add(terrain)
}
}
return stats!!
return stats ?: Stats.ZERO // For tests
}
// Only gets the tile percentage bonus, not the improvement percentage bonus

View File

@ -6,19 +6,13 @@ import com.unciv.logic.city.City
import com.unciv.logic.civilization.diplomacy.DiplomacyManager
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.logic.civilization.transients.CapitalConnectionsFinder
import com.unciv.logic.map.tile.RoadStatus
import com.unciv.logic.map.tile.Tile
import com.unciv.logic.map.TileMap
import com.unciv.logic.map.tile.RoadStatus
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.ruleset.nation.Nation
import com.unciv.models.ruleset.tile.TerrainType
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.testing.GdxTestRunner
import io.mockk.every
import io.mockk.mockk
import io.mockk.slot
import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Test
@ -39,77 +33,42 @@ import org.junit.runner.RunWith
@RunWith(GdxTestRunner::class)
class CapitalConnectionsFinderTests {
private val mockGameInfo = mockk<GameInfo>()
private val slot = slot<String>()
private var gameInfo = GameInfo()
private val testCivilizationNames = arrayListOf("America", "Germany", "Greece")
private val civilizations = testCivilizationNames.associateWith { Civilization(it) }
private val ourCiv = civilizations.values.first()
private val tilesMap = TileMap().apply { tileMatrix = ArrayList() }
private var rules = Ruleset()
private fun ourCiv() = gameInfo.civilizations.first()
@Before
fun setup() {
RulesetCache.loadRulesets(noMods = true)
rules = RulesetCache.getVanillaRuleset()
// Setup the GameInfo mock
every { mockGameInfo.getCivilization(capture(slot)) } answers { civilizations.getValue(slot.captured) }
every { mockGameInfo.civilizations } answers { civilizations.values.toMutableList() }
every { mockGameInfo.tileMap } returns tilesMap
every { mockGameInfo.ruleset } returns rules
every { mockGameInfo.getCities() } answers { civilizations.values.asSequence().flatMap { it.cities } }
// Needs for founding cities
every { mockGameInfo.turns } returns 1
gameInfo = GameInfo()
gameInfo.ruleset = rules
for (civName in testCivilizationNames)
gameInfo.civilizations.add(Civilization(civName).apply { playerType=PlayerType.Human })
gameInfo.tileMap = TileMap(4, rules)
// Initialize test civilizations so they pass certain criteria
civilizations.values.forEach {
it.gameInfo = mockGameInfo
gameInfo.civilizations.forEach {
it.gameInfo = gameInfo
it.nation = Nation()
it.nation.name = it.civName // for isBarbarian()
it.tech.techsResearched.add(rules.tileImprovements[RoadStatus.Road.name]!!.techRequired!!)
it.tech.techsResearched.add(rules.tileImprovements[RoadStatus.Railroad.name]!!.techRequired!!)
}
}
@After
fun tearDown() {
(tilesMap.values as ArrayList<Tile>).clear()
for (civ in civilizations.values) {
civ.cities = emptyList()
civ.diplomacy.clear()
}
}
private fun createLand(from: Int, to: Int) {
// create map
val tiles = tilesMap.tileMatrix
tilesMap.bottomY = from
tiles.add(ArrayList())
for (y in from..to)
tiles.last().add(Tile().apply { tileMap = tilesMap
position = Vector2(tiles.size-1f, y.toFloat())
baseTerrain = rules.terrains.values.first { it.type == TerrainType.Land }.name })
}
private fun createWater(from: Int, to: Int) {
// create map
val tiles = tilesMap.tileMatrix
tilesMap.bottomY = from
// here we assume the row with a land is already created
tiles.add(ArrayList())
for (y in from..to)
tiles.last().add(Tile().apply { tileMap = tilesMap
position = Vector2(tiles.size-1f, y.toFloat())
isWater = true
baseTerrain = rules.terrains.values.first { it.type == TerrainType.Water }.name })
gameInfo.setTransients()
}
private fun createMedium(from:Int, to: Int, type: RoadStatus) {
val tiles = tilesMap.tileMatrix
for (tile in tiles.last())
if (tile != null && tile.position.y > from && tile.position.y < to)
for (i in from..to){
val tile = gameInfo.tileMap[0, i]
tile.roadStatus = type
}
}
private fun createCity(civInfo: Civilization, position: Vector2, name: String, capital: Boolean = false, hasHarbor: Boolean = false): City {
return City().apply {
@ -120,26 +79,26 @@ class CapitalConnectionsFinderTests {
cityConstructions.builtBuildings.add(rules.buildings.values.first { it.hasUnique(UniqueType.ConnectTradeRoutes) }.name)
this.name = name
setTransients(civInfo)
tilesMap[location].setOwningCity(this)
gameInfo.tileMap[location].setOwningCity(this)
}
}
private fun meetCivAndSetBorders(name: String, areBordersOpen: Boolean) {
ourCiv.diplomacy[name] = DiplomacyManager(ourCiv, name)
ourCiv().diplomacy[name] = DiplomacyManager(ourCiv(), name)
.apply { diplomaticStatus = DiplomaticStatus.Peace }
ourCiv.diplomacy[name]!!.hasOpenBorders = areBordersOpen
ourCiv().diplomacy[name]!!.hasOpenBorders = areBordersOpen
}
@Test
fun `Own cities are connected by road`() {
// Map: C-A N
createLand(-2,2)
ourCiv.cities = listOf( createCity(ourCiv, Vector2(0f, 0f), "Capital", true),
createCity(ourCiv, Vector2(0f, -2f), "Connected"),
createCity(ourCiv, Vector2(0f, 2f), "Not connected"))
// createLand(-2,2)
ourCiv().cities = listOf( createCity(ourCiv(), Vector2(0f, 0f), "Capital", true),
createCity(ourCiv(), Vector2(0f, -2f), "Connected"),
createCity(ourCiv(), Vector2(0f, 2f), "Not connected"))
createMedium(-2, 0, RoadStatus.Road)
val connectionsFinder = CapitalConnectionsFinder(ourCiv)
val connectionsFinder = CapitalConnectionsFinder(ourCiv())
val res = connectionsFinder.find()
Assert.assertTrue(res.keys.any { it.name == "Connected" } && !res.keys.any { it.name == "Not connected" } )
@ -148,49 +107,47 @@ class CapitalConnectionsFinderTests {
@Test
fun `Own cities are connected by railroad`() {
// Map: N A=C
createLand(-2,2)
ourCiv.cities = listOf( createCity(ourCiv, Vector2(0f, 0f), "Capital", true),
createCity(ourCiv, Vector2(0f, 2f), "Connected"),
createCity(ourCiv, Vector2(0f, -2f), "Not connected"))
ourCiv().cities = listOf( createCity(ourCiv(), Vector2(0f, 0f), "Capital", true),
createCity(ourCiv(), Vector2(0f, 2f), "Connected"),
createCity(ourCiv(), Vector2(0f, -2f), "Not connected"))
createMedium(0, 2, RoadStatus.Railroad)
val connectionsFinder = CapitalConnectionsFinder(ourCiv)
val connectionsFinder = CapitalConnectionsFinder(ourCiv())
val res = connectionsFinder.find()
Assert.assertTrue(res.keys.any { it.name == "Connected" } && !res.keys.any { it.name == "Not connected" } )
}
@Test
fun `Own cities are connected by road and harbor`() {
// Map: N A-C C
// ~~~~~~~
createLand(-2,4)
createMedium(0,2, RoadStatus.Road)
createWater(-2,4)
ourCiv.cities = listOf( createCity(ourCiv, Vector2(0f, 0f), "Capital", true),
createCity(ourCiv, Vector2(0f, -2f), "Not connected"),
createCity(ourCiv, Vector2(0f, 2f), "Connected1", capital = false, hasHarbor = true),
createCity(ourCiv, Vector2(0f, 4f), "Connected2", capital = false, hasHarbor = true))
val connectionsFinder = CapitalConnectionsFinder(ourCiv)
val res = connectionsFinder.find()
Assert.assertTrue(res.keys.count { it.name.startsWith("Connected") } == 2 && !res.keys.any { it.name == "Not connected" } )
}
//
// @Test
// fun `Own cities are connected by road and harbor`() {
// // Map: N A-C C
// // ~~~~~~~
// createMedium(0,2, RoadStatus.Road)
// // createWater(-2,4)
// ourCiv().cities = listOf( createCity(ourCiv(), Vector2(0f, 0f), "Capital", true),
// createCity(ourCiv(), Vector2(0f, -2f), "Not connected"),
// createCity(ourCiv(), Vector2(0f, 2f), "Connected1", capital = false, hasHarbor = true),
// createCity(ourCiv(), Vector2(0f, 4f), "Connected2", capital = false, hasHarbor = true))
//
// val connectionsFinder = CapitalConnectionsFinder(ourCiv())
// val res = connectionsFinder.find()
//
// Assert.assertTrue(res.keys.count { it.name.startsWith("Connected") } == 2 && !res.keys.any { it.name == "Not connected" } )
// }
@Test
fun `Cities are connected by roads via Open Borders`() {
// Map: N-X=A-O=C
createLand(-4,4)
ourCiv.cities = listOf( createCity(ourCiv, Vector2(0f, 0f), "Capital", true),
createCity(ourCiv, Vector2(0f, -4f), "Not connected"),
createCity(ourCiv, Vector2(0f, 4f), "Connected"))
ourCiv().cities = listOf( createCity(ourCiv(), Vector2(0f, 0f), "Capital", true),
createCity(ourCiv(), Vector2(0f, -4f), "Not connected"),
createCity(ourCiv(), Vector2(0f, 4f), "Connected"))
val openCiv = civilizations["Germany"]!!
val openCiv = gameInfo.getCivilization("Germany")
openCiv.cities = listOf( createCity(openCiv, Vector2(0f, 2f), "Berlin", true))
meetCivAndSetBorders("Germany", true)
val closedCiv = civilizations["Greece"]!!
// The path to "not connected" goes through closed territory
val closedCiv = gameInfo.getCivilization("Greece")
closedCiv.cities = listOf( createCity(closedCiv, Vector2(0f, -2f), "Athens", true))
meetCivAndSetBorders("Greece", false)
@ -198,60 +155,56 @@ class CapitalConnectionsFinderTests {
createMedium(-2,0, RoadStatus.Railroad)
createMedium(0,2, RoadStatus.Road)
createMedium(2,4, RoadStatus.Railroad)
// part of the railroad (Berlin-Connected) goes through other civilization territory
tilesMap.tileMatrix[0][7]!!.setOwningCity(openCiv.cities.first())
val connectionsFinder = CapitalConnectionsFinder(ourCiv)
val connectionsFinder = CapitalConnectionsFinder(ourCiv())
val res = connectionsFinder.find()
Assert.assertTrue(res.keys.any { it.name == "Connected" } && !res.keys.any { it.name == "Not connected" } )
}
@Test
fun `Cities are connected via own harbors only`() {
// Map: A
// ~~~~~
// C O-N=N
createLand(-4,-4) // capital is on an island
createWater(-4,0)
createLand(-4,2) // some land without access to ocean
ourCiv.cities = listOf( createCity(ourCiv, Vector2(0f, -4f), "Capital", true, hasHarbor = true),
createCity(ourCiv, Vector2(2f, 2f), "Not connected1", capital = false, hasHarbor = true), // cannot reach ocean
createCity(ourCiv, Vector2(2f, 0f), "Not connected2"), // has no harbor, has road to Berlin
createCity(ourCiv, Vector2(2f, -4f), "Connected", capital = false, hasHarbor = true))
val openCiv = civilizations["Germany"]!!
openCiv.cities = listOf( createCity(openCiv, Vector2(2f, -2f), "Berlin", true, hasHarbor = true))
meetCivAndSetBorders("Germany", true)
createMedium(-2,0, RoadStatus.Road)
createMedium(0,2, RoadStatus.Railroad)
val connectionsFinder = CapitalConnectionsFinder(ourCiv)
val res = connectionsFinder.find()
Assert.assertTrue(res.keys.any { it.name == "Connected" } && !res.keys.any { it.name.startsWith("Not connected") } )
}
// @Test
// fun `Cities are connected via own harbors only`() {
// // Map: A
// // ~~~~~
// // C O-N=N
// createLand(-4,-4) // capital is on an island
// createWater(-4,0)
// createLand(-4,2) // some land without access to ocean
// ourCiv().cities = listOf( createCity(ourCiv(), Vector2(0f, -4f), "Capital", true, hasHarbor = true),
// createCity(ourCiv(), Vector2(2f, 2f), "Not connected1", capital = false, hasHarbor = true), // cannot reach ocean
// createCity(ourCiv(), Vector2(2f, 0f), "Not connected2"), // has no harbor, has road to Berlin
// createCity(ourCiv(), Vector2(2f, -4f), "Connected", capital = false, hasHarbor = true))
//
// val openCiv = gameInfo.getCivilization("Germany")
// openCiv.cities = listOf( createCity(openCiv, Vector2(2f, -2f), "Berlin", true, hasHarbor = true))
// meetCivAndSetBorders("Germany", true)
//
// createMedium(-2,0, RoadStatus.Road)
// createMedium(0,2, RoadStatus.Railroad)
//
// val connectionsFinder = CapitalConnectionsFinder(ourCiv())
// val res = connectionsFinder.find()
//
// Assert.assertTrue(res.keys.any { it.name == "Connected" } && !res.keys.any { it.name.startsWith("Not connected") } )
// }
@Test
fun `Cities are connected by roads via City-States`() {
// Map: N=X-A=O-C
createLand(-4,4)
ourCiv.cities = listOf( createCity(ourCiv, Vector2(0f, 0f), "Capital", true),
createCity(ourCiv, Vector2(0f, -4f), "Not connected"),
createCity(ourCiv, Vector2(0f, 4f), "Connected"))
ourCiv().cities = listOf( createCity(ourCiv(), Vector2(0f, 0f), "Capital", true),
createCity(ourCiv(), Vector2(0f, -4f), "Not connected"),
createCity(ourCiv(), Vector2(0f, 4f), "Connected"))
val openCiv = civilizations["Germany"]!!
val openCiv = gameInfo.getCivilization("Germany")
openCiv.nation.cityStateType = "Cultured"
openCiv.cities = listOf( createCity(openCiv, Vector2(0f, 2f), "Berlin", true))
ourCiv.diplomacy["Germany"] = DiplomacyManager(ourCiv, "Germany")
ourCiv().diplomacy["Germany"] = DiplomacyManager(ourCiv(), "Germany")
.apply { diplomaticStatus = DiplomaticStatus.Peace }
val closedCiv = civilizations["Greece"]!!
val closedCiv = gameInfo.getCivilization("Greece")
closedCiv.nation.cityStateType = "Cultured"
closedCiv.cities = listOf( createCity(closedCiv, Vector2(0f, -2f), "Athens", true))
ourCiv.diplomacy["Greece"] = DiplomacyManager(ourCiv, "Greece")
ourCiv().diplomacy["Greece"] = DiplomacyManager(ourCiv(), "Greece")
.apply { diplomaticStatus = DiplomaticStatus.War }
@ -259,11 +212,9 @@ class CapitalConnectionsFinderTests {
createMedium(-2,0, RoadStatus.Road)
createMedium(0,2, RoadStatus.Railroad)
createMedium(2,4, RoadStatus.Road)
// part of the railroad (Berlin-Connected) goes through other civilization territory
tilesMap.tileMatrix[0][7]!!.setOwningCity(openCiv.cities.first())
val connectionsFinder = CapitalConnectionsFinder(ourCiv)
val connectionsFinder = CapitalConnectionsFinder(ourCiv())
val res = connectionsFinder.find()
Assert.assertTrue(res.keys.any { it.name == "Connected" } && !res.keys.any { it.name == "Not connected" } )

View File

@ -20,7 +20,7 @@ import org.junit.Test
import org.junit.runner.RunWith
@RunWith(GdxTestRunner::class)
class UnitMovementAlgorithmsTests {
class UnitMovementTests {
private var tile = Tile()
private var civInfo = Civilization()
@ -415,7 +415,7 @@ class UnitMovementAlgorithmsTests {
newTiles[3].setTransients()
// create our city
City().apply {
this.civ = this@UnitMovementAlgorithmsTests.civInfo
this.civ = this@UnitMovementTests.civInfo
location = newTiles.last().position.cpy()
tiles.add(location)
tiles.add(newTiles[5].position)