mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-27 05:46:43 -04:00
GameStarter wrap and shape aware (#5107)
- Starting locations code aware of wrap and map shape - RiverGenerator wrap aware - RiverGenerator guard against endless loop
This commit is contained in:
parent
7f386da2bc
commit
7118e9779c
@ -1,6 +1,5 @@
|
|||||||
package com.unciv.logic
|
package com.unciv.logic
|
||||||
|
|
||||||
import com.badlogic.gdx.math.Vector2
|
|
||||||
import com.unciv.Constants
|
import com.unciv.Constants
|
||||||
import com.unciv.UncivGame
|
import com.unciv.UncivGame
|
||||||
import com.unciv.logic.civilization.*
|
import com.unciv.logic.civilization.*
|
||||||
@ -9,12 +8,12 @@ import com.unciv.logic.map.TileInfo
|
|||||||
import com.unciv.logic.map.TileMap
|
import com.unciv.logic.map.TileMap
|
||||||
import com.unciv.logic.map.mapgenerator.MapGenerator
|
import com.unciv.logic.map.mapgenerator.MapGenerator
|
||||||
import com.unciv.models.metadata.GameParameters
|
import com.unciv.models.metadata.GameParameters
|
||||||
|
import com.unciv.models.metadata.GameSetupInfo
|
||||||
import com.unciv.models.ruleset.Era
|
import com.unciv.models.ruleset.Era
|
||||||
import com.unciv.models.ruleset.ModOptionsConstants
|
import com.unciv.models.ruleset.ModOptionsConstants
|
||||||
import com.unciv.models.ruleset.Ruleset
|
import com.unciv.models.ruleset.Ruleset
|
||||||
import com.unciv.models.ruleset.RulesetCache
|
import com.unciv.models.ruleset.RulesetCache
|
||||||
import com.unciv.models.ruleset.tile.ResourceType
|
import com.unciv.models.ruleset.tile.ResourceType
|
||||||
import com.unciv.models.metadata.GameSetupInfo
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
@ -358,8 +357,10 @@ object GameStarter {
|
|||||||
|
|
||||||
for (minimumDistanceBetweenStartingLocations in tileMap.tileMatrix.size / 4 downTo 0) {
|
for (minimumDistanceBetweenStartingLocations in tileMap.tileMatrix.size / 4 downTo 0) {
|
||||||
val freeTiles = landTilesInBigEnoughGroup
|
val freeTiles = landTilesInBigEnoughGroup
|
||||||
.filter { vectorIsAtLeastNTilesAwayFromEdge(it.position, (minimumDistanceBetweenStartingLocations * 2) /3, tileMap) }
|
.filter {
|
||||||
.toMutableList()
|
HexMath.getDistanceFromEdge(it.position, tileMap.mapParameters) >=
|
||||||
|
(minimumDistanceBetweenStartingLocations * 2) /3
|
||||||
|
}.toMutableList()
|
||||||
|
|
||||||
val startingLocations = HashMap<CivilizationInfo, TileInfo>()
|
val startingLocations = HashMap<CivilizationInfo, TileInfo>()
|
||||||
for (civ in civsOrderedByAvailableLocations) {
|
for (civ in civsOrderedByAvailableLocations) {
|
||||||
@ -446,13 +447,4 @@ object GameStarter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun vectorIsAtLeastNTilesAwayFromEdge(vector: Vector2, n: Int, tileMap: TileMap): Boolean {
|
|
||||||
// Since all maps are HEXAGONAL, the easiest way of checking if a tile is n steps away from the
|
|
||||||
// edge is checking the distance to the CENTER POINT
|
|
||||||
// Can't believe we used a dumb way of calculating this before!
|
|
||||||
val hexagonalRadius = -tileMap.leftX
|
|
||||||
val distanceFromCenter = HexMath.getDistance(vector, Vector2.Zero)
|
|
||||||
return hexagonalRadius - distanceFromCenter >= n
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ package com.unciv.logic
|
|||||||
|
|
||||||
import com.badlogic.gdx.math.Vector2
|
import com.badlogic.gdx.math.Vector2
|
||||||
import com.badlogic.gdx.math.Vector3
|
import com.badlogic.gdx.math.Vector3
|
||||||
|
import com.unciv.logic.map.MapParameters
|
||||||
|
import com.unciv.logic.map.MapShape
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
|
|
||||||
@Suppress("MemberVisibilityCanBePrivate", "unused") // this is a library offering optional services
|
@Suppress("MemberVisibilityCanBePrivate", "unused") // this is a library offering optional services
|
||||||
@ -194,4 +196,34 @@ object HexMath {
|
|||||||
|
|
||||||
fun getClockDirectionToWorldVector(clockDirection: Int): Vector2 =
|
fun getClockDirectionToWorldVector(clockDirection: Int): Vector2 =
|
||||||
clockToWorldVectors[clockDirection] ?: Vector2.Zero
|
clockToWorldVectors[clockDirection] ?: Vector2.Zero
|
||||||
|
|
||||||
|
fun getDistanceFromEdge(vector: Vector2, mapParameters: MapParameters): Int {
|
||||||
|
val x = vector.x.toInt()
|
||||||
|
val y = vector.y.toInt()
|
||||||
|
if (mapParameters.shape == MapShape.rectangular) {
|
||||||
|
val height = mapParameters.mapSize.height
|
||||||
|
val width = mapParameters.mapSize.width
|
||||||
|
val left = if (mapParameters.worldWrap) Int.MAX_VALUE else width / 2 - (x - y)
|
||||||
|
val right = if (mapParameters.worldWrap) Int.MAX_VALUE else (width - 1) / 2 - (y - x)
|
||||||
|
val top = height / 2 - (x + y) / 2
|
||||||
|
// kotlin's Int division rounds in different directions depending on sign! Thus 1 extra `-1`
|
||||||
|
val bottom = (x + y - 1) / 2 + (height - 1) / 2
|
||||||
|
return min(min(left, right), min(top, bottom))
|
||||||
|
} else {
|
||||||
|
val radius = mapParameters.mapSize.radius
|
||||||
|
if (mapParameters.worldWrap) {
|
||||||
|
// The non-wrapping method holds in the upper two and lower two 'triangles' of the hexagon
|
||||||
|
// but needs special casing for left and right 'wedges', where only distance from the
|
||||||
|
// 'choke points' counts (upper and lower hex at the 'seam' where height is smallest).
|
||||||
|
// These are at (radius,0) and (0,-radius)
|
||||||
|
if (x.sign == y.sign) return radius - getDistance(vector, Vector2.Zero)
|
||||||
|
// left wedge - the 'choke points' are not wrapped relative to us
|
||||||
|
if (x > 0) return min(getDistance(vector, Vector2(radius.toFloat(),0f)), getDistance(vector, Vector2(0f, -radius.toFloat())))
|
||||||
|
// right wedge - compensate wrap by using a hex 1 off along the edge - same result
|
||||||
|
return min(getDistance(vector, Vector2(1f, radius.toFloat())), getDistance(vector, Vector2(-radius.toFloat(), -1f)))
|
||||||
|
} else {
|
||||||
|
return radius - getDistance(vector, Vector2.Zero)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -276,24 +276,27 @@ open class TileInfo {
|
|||||||
fun getTileStartScore(): Float {
|
fun getTileStartScore(): Float {
|
||||||
var sum = 0f
|
var sum = 0f
|
||||||
for (tile in getTilesInDistance(2)) {
|
for (tile in getTilesInDistance(2)) {
|
||||||
if (tile == this)
|
val tileYield = tile.getTileStartYield(tile == this)
|
||||||
continue
|
sum += tileYield
|
||||||
sum += tile.getTileStartYield()
|
|
||||||
if (tile in neighbors)
|
if (tile in neighbors)
|
||||||
sum += tile.getTileStartYield()
|
sum += tileYield
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isHill())
|
if (isHill())
|
||||||
sum -= 2
|
sum -= 2f
|
||||||
if (isAdjacentToRiver())
|
if (isAdjacentToRiver())
|
||||||
sum += 2
|
sum += 2f
|
||||||
if (neighbors.any { it.baseTerrain == Constants.mountain })
|
if (neighbors.any { it.baseTerrain == Constants.mountain })
|
||||||
sum += 2
|
sum += 2f
|
||||||
|
if (isCoastalTile())
|
||||||
|
sum += 3f
|
||||||
|
if (!isCoastalTile() && neighbors.any { it.isCoastalTile() })
|
||||||
|
sum -= 7f
|
||||||
|
|
||||||
return sum
|
return sum
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTileStartYield(): Float {
|
private fun getTileStartYield(isCenter: Boolean): Float {
|
||||||
var stats = getBaseTerrain().clone()
|
var stats = getBaseTerrain().clone()
|
||||||
|
|
||||||
for (terrainFeatureBase in getTerrainFeatures()) {
|
for (terrainFeatureBase in getTerrainFeatures()) {
|
||||||
@ -303,7 +306,12 @@ open class TileInfo {
|
|||||||
stats.add(terrainFeatureBase)
|
stats.add(terrainFeatureBase)
|
||||||
}
|
}
|
||||||
if (resource != null) stats.add(getTileResource())
|
if (resource != null) stats.add(getTileResource())
|
||||||
|
|
||||||
if (stats.production < 0) stats.production = 0f
|
if (stats.production < 0) stats.production = 0f
|
||||||
|
if (isCenter) {
|
||||||
|
if (stats.food < 2) stats.food = 2f
|
||||||
|
if (stats.production < 1) stats.production = 1f
|
||||||
|
}
|
||||||
|
|
||||||
return stats.food + stats.production + stats.gold
|
return stats.food + stats.production + stats.gold
|
||||||
}
|
}
|
||||||
|
@ -195,7 +195,7 @@ class TileMap {
|
|||||||
}.filterNotNull()
|
}.filterNotNull()
|
||||||
|
|
||||||
/** @return tile at hex coordinates ([x],[y]) or null if they are outside the map. Respects map edges and world wrap. */
|
/** @return tile at hex coordinates ([x],[y]) or null if they are outside the map. Respects map edges and world wrap. */
|
||||||
private fun getIfTileExistsOrNull(x: Int, y: Int): TileInfo? {
|
fun getIfTileExistsOrNull(x: Int, y: Int): TileInfo? {
|
||||||
if (contains(x, y))
|
if (contains(x, y))
|
||||||
return get(x, y)
|
return get(x, y)
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package com.unciv.logic.map.mapgenerator
|
package com.unciv.logic.map.mapgenerator
|
||||||
|
|
||||||
import com.badlogic.gdx.math.Vector2
|
|
||||||
import com.unciv.Constants
|
import com.unciv.Constants
|
||||||
import com.unciv.logic.HexMath
|
import com.unciv.logic.HexMath
|
||||||
import com.unciv.logic.map.*
|
import com.unciv.logic.map.*
|
||||||
@ -9,7 +8,10 @@ import com.unciv.models.ruleset.Ruleset
|
|||||||
import com.unciv.models.ruleset.tile.ResourceType
|
import com.unciv.models.ruleset.tile.ResourceType
|
||||||
import com.unciv.models.ruleset.tile.Terrain
|
import com.unciv.models.ruleset.tile.Terrain
|
||||||
import com.unciv.models.ruleset.tile.TerrainType
|
import com.unciv.models.ruleset.tile.TerrainType
|
||||||
import kotlin.math.*
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.pow
|
||||||
|
import kotlin.math.sign
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
|
||||||
@ -73,7 +75,7 @@ class MapGenerator(val ruleset: Ruleset) {
|
|||||||
NaturalWonderGenerator(ruleset, randomness).spawnNaturalWonders(map)
|
NaturalWonderGenerator(ruleset, randomness).spawnNaturalWonders(map)
|
||||||
}
|
}
|
||||||
runAndMeasure("RiverGenerator") {
|
runAndMeasure("RiverGenerator") {
|
||||||
RiverGenerator(randomness).spawnRivers(map)
|
RiverGenerator(map, randomness).spawnRivers()
|
||||||
}
|
}
|
||||||
runAndMeasure("spreadResources") {
|
runAndMeasure("spreadResources") {
|
||||||
spreadResources(map)
|
spreadResources(map)
|
||||||
@ -459,7 +461,7 @@ class MapGenerator(val ruleset: Ruleset) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MapGenerationRandomness{
|
class MapGenerationRandomness {
|
||||||
var RNG = Random(42)
|
var RNG = Random(42)
|
||||||
|
|
||||||
fun seedRNG(seed: Long = 42) {
|
fun seedRNG(seed: Long = 42) {
|
||||||
@ -532,30 +534,3 @@ class MapGenerationRandomness{
|
|||||||
throw Exception()
|
throw Exception()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class RiverCoordinate(val position: Vector2, val bottomRightOrLeft: BottomRightOrLeft) {
|
|
||||||
enum class BottomRightOrLeft {
|
|
||||||
/** 7 O'Clock of the tile */
|
|
||||||
BottomLeft,
|
|
||||||
|
|
||||||
/** 5 O'Clock of the tile */
|
|
||||||
BottomRight
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getAdjacentPositions(): Sequence<RiverCoordinate> {
|
|
||||||
// What's nice is that adjacents are always the OPPOSITE in terms of right-left - rights are adjacent to only lefts, and vice-versa
|
|
||||||
// This means that a lot of obviously-wrong assignments are simple to spot
|
|
||||||
return if (bottomRightOrLeft == BottomRightOrLeft.BottomLeft) {
|
|
||||||
sequenceOf(RiverCoordinate(position, BottomRightOrLeft.BottomRight), // same tile, other side
|
|
||||||
RiverCoordinate(position.cpy().add(1f, 0f), BottomRightOrLeft.BottomRight), // tile to MY top-left, take its bottom right corner
|
|
||||||
RiverCoordinate(position.cpy().add(0f, -1f), BottomRightOrLeft.BottomRight) // Tile to MY bottom-left, take its bottom right
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
sequenceOf(RiverCoordinate(position, BottomRightOrLeft.BottomLeft), // same tile, other side
|
|
||||||
RiverCoordinate(position.cpy().add(0f, 1f), BottomRightOrLeft.BottomLeft), // tile to MY top-right, take its bottom left
|
|
||||||
RiverCoordinate(position.cpy().add(-1f, 0f), BottomRightOrLeft.BottomLeft) // tile to MY bottom-right, take its bottom left
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,111 +1,167 @@
|
|||||||
package com.unciv.logic.map.mapgenerator
|
package com.unciv.logic.map.mapgenerator
|
||||||
|
|
||||||
|
import com.badlogic.gdx.math.Vector2
|
||||||
import com.unciv.Constants
|
import com.unciv.Constants
|
||||||
import com.unciv.logic.map.TileInfo
|
import com.unciv.logic.map.TileInfo
|
||||||
import com.unciv.logic.map.TileMap
|
import com.unciv.logic.map.TileMap
|
||||||
|
|
||||||
class RiverGenerator(val randomness: MapGenerationRandomness) {
|
class RiverGenerator(
|
||||||
|
private val tileMap: TileMap,
|
||||||
|
private val randomness: MapGenerationRandomness
|
||||||
|
) {
|
||||||
|
companion object{
|
||||||
|
const val MAP_TILES_PER_RIVER = 100
|
||||||
|
const val MIN_RIVER_LENGTH = 5
|
||||||
|
const val MAX_RIVER_LENGTH = 666 // Do not set < max map radius
|
||||||
|
}
|
||||||
|
|
||||||
fun spawnRivers(map: TileMap) {
|
fun spawnRivers() {
|
||||||
val numberOfRivers = map.values.count { it.isLand } / 100
|
val numberOfRivers = tileMap.values.count { it.isLand } / MAP_TILES_PER_RIVER
|
||||||
|
|
||||||
var optionalTiles = map.values.asSequence()
|
var optionalTiles = tileMap.values.asSequence()
|
||||||
.filter { it.baseTerrain == Constants.mountain && it.aerialDistanceTo(getClosestWaterTile(it)) > 4 }.toMutableList()
|
.filter { it.baseTerrain == Constants.mountain && it.isFarEnoughFromWater() }.toMutableList()
|
||||||
if (optionalTiles.size < numberOfRivers)
|
if (optionalTiles.size < numberOfRivers)
|
||||||
optionalTiles.addAll(map.values.filter { it.isHill() && it.aerialDistanceTo(getClosestWaterTile(it)) > 4 })
|
optionalTiles.addAll(tileMap.values.filter { it.isHill() && it.isFarEnoughFromWater() })
|
||||||
if (optionalTiles.size < numberOfRivers)
|
if (optionalTiles.size < numberOfRivers)
|
||||||
optionalTiles = map.values.filter { it.isLand && it.aerialDistanceTo(getClosestWaterTile(it)) > 4 }.toMutableList()
|
optionalTiles = tileMap.values.filter { it.isLand && it.isFarEnoughFromWater() }.toMutableList()
|
||||||
|
|
||||||
val mapRadius = map.mapParameters.mapSize.radius
|
val mapRadius = tileMap.mapParameters.mapSize.radius
|
||||||
val riverStarts = randomness.chooseSpreadOutLocations(numberOfRivers, optionalTiles, mapRadius)
|
val riverStarts = randomness.chooseSpreadOutLocations(numberOfRivers, optionalTiles, mapRadius)
|
||||||
for (tile in riverStarts) spawnRiver(tile, map)
|
for (tile in riverStarts) spawnRiver(tile)
|
||||||
|
|
||||||
for (tile in map.values) {
|
for (tile in tileMap.values) {
|
||||||
if (tile.isAdjacentToRiver()) {
|
if (tile.isAdjacentToRiver()) {
|
||||||
if (tile.baseTerrain == Constants.desert && tile.terrainFeatures.isEmpty()) tile.terrainFeatures.add(Constants.floodPlains)
|
when {
|
||||||
else if (tile.baseTerrain == Constants.snow) tile.baseTerrain = Constants.tundra
|
tile.baseTerrain == Constants.desert && tile.terrainFeatures.isEmpty() ->
|
||||||
else if (tile.baseTerrain == Constants.tundra) tile.baseTerrain = Constants.plains
|
tile.terrainFeatures.add(Constants.floodPlains)
|
||||||
|
tile.baseTerrain == Constants.snow -> tile.baseTerrain = Constants.tundra
|
||||||
|
tile.baseTerrain == Constants.tundra -> tile.baseTerrain = Constants.plains
|
||||||
|
}
|
||||||
tile.setTerrainTransients()
|
tile.setTerrainTransients()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getClosestWaterTile(tile: TileInfo): TileInfo {
|
private fun TileInfo.isFarEnoughFromWater(): Boolean {
|
||||||
var distance = 1
|
for (distance in 1 until MIN_RIVER_LENGTH) {
|
||||||
while (true) {
|
if (getTilesAtDistance(distance).any { it.isWater }) return false
|
||||||
val waterTiles = tile.getTilesAtDistance(distance).filter { it.isWater }
|
|
||||||
if (waterTiles.none()) {
|
|
||||||
distance++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return waterTiles.toList().random(randomness.RNG)
|
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun spawnRiver(initialPosition: TileInfo, map: TileMap) {
|
private fun getClosestWaterTile(tile: TileInfo): TileInfo {
|
||||||
|
for (distance in 1..MAX_RIVER_LENGTH) {
|
||||||
|
val waterTiles = tile.getTilesAtDistance(distance).filter { it.isWater }
|
||||||
|
if (waterTiles.any())
|
||||||
|
return waterTiles.toList().random(randomness.RNG)
|
||||||
|
}
|
||||||
|
throw IllegalStateException()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun spawnRiver(initialPosition: TileInfo) {
|
||||||
// Recommendation: Draw a bunch of hexagons on paper before trying to understand this, it's super helpful!
|
// Recommendation: Draw a bunch of hexagons on paper before trying to understand this, it's super helpful!
|
||||||
val endPosition = getClosestWaterTile(initialPosition)
|
val endPosition = getClosestWaterTile(initialPosition)
|
||||||
|
|
||||||
var riverCoordinate = RiverCoordinate(initialPosition.position,
|
var riverCoordinate = RiverCoordinate(initialPosition.position,
|
||||||
RiverCoordinate.BottomRightOrLeft.values().random(randomness.RNG))
|
RiverCoordinate.BottomRightOrLeft.values().random(randomness.RNG))
|
||||||
|
|
||||||
|
for (step in 1..MAX_RIVER_LENGTH) { // Arbitrary max on river length, otherwise this will go in circles - rarely
|
||||||
while (getAdjacentTiles(riverCoordinate, map).none { it.isWater }) {
|
if (riverCoordinate.getAdjacentTiles(tileMap).any { it.isWater }) return
|
||||||
val possibleCoordinates = riverCoordinate.getAdjacentPositions()
|
val possibleCoordinates = riverCoordinate.getAdjacentPositions(tileMap)
|
||||||
.filter { map.contains(it.position) }
|
|
||||||
if (possibleCoordinates.none()) return // end of the line
|
if (possibleCoordinates.none()) return // end of the line
|
||||||
val newCoordinate = possibleCoordinates
|
val newCoordinate = possibleCoordinates
|
||||||
.groupBy {
|
.groupBy { newCoordinate ->
|
||||||
getAdjacentTiles(it, map).map { it.aerialDistanceTo(endPosition) }
|
newCoordinate.getAdjacentTiles(tileMap).map { it.aerialDistanceTo(endPosition) }
|
||||||
.minOrNull()!!
|
.minOrNull()!!
|
||||||
}
|
}
|
||||||
.minByOrNull { it.key }!!
|
.minByOrNull { it.key }!!
|
||||||
.component2().random(randomness.RNG)
|
.component2().random(randomness.RNG)
|
||||||
|
|
||||||
// set new rivers in place
|
// set new rivers in place
|
||||||
val riverCoordinateTile = map[riverCoordinate.position]
|
val riverCoordinateTile = tileMap[riverCoordinate.position]
|
||||||
if (newCoordinate.position == riverCoordinate.position) // same tile, switched right-to-left
|
if (newCoordinate.position == riverCoordinate.position) // same tile, switched right-to-left
|
||||||
riverCoordinateTile.hasBottomRiver = true
|
riverCoordinateTile.hasBottomRiver = true
|
||||||
else if (riverCoordinate.bottomRightOrLeft == RiverCoordinate.BottomRightOrLeft.BottomRight) {
|
else if (riverCoordinate.bottomRightOrLeft == RiverCoordinate.BottomRightOrLeft.BottomRight) {
|
||||||
if (getAdjacentTiles(newCoordinate, map).contains(riverCoordinateTile)) // moved from our 5 O'Clock to our 3 O'Clock
|
if (newCoordinate.getAdjacentTiles(tileMap).contains(riverCoordinateTile)) // moved from our 5 O'Clock to our 3 O'Clock
|
||||||
riverCoordinateTile.hasBottomRightRiver = true
|
riverCoordinateTile.hasBottomRightRiver = true
|
||||||
else // moved from our 5 O'Clock down in the 5 O'Clock direction - this is the 8 O'Clock river of the tile to our 4 O'Clock!
|
else // moved from our 5 O'Clock down in the 5 O'Clock direction - this is the 8 O'Clock river of the tile to our 4 O'Clock!
|
||||||
map[newCoordinate.position].hasBottomLeftRiver = true
|
tileMap[newCoordinate.position].hasBottomLeftRiver = true
|
||||||
} else { // riverCoordinate.bottomRightOrLeft==RiverCoordinate.BottomRightOrLeft.Left
|
} else { // riverCoordinate.bottomRightOrLeft==RiverCoordinate.BottomRightOrLeft.Left
|
||||||
if (getAdjacentTiles(newCoordinate, map).contains(riverCoordinateTile)) // moved from our 7 O'Clock to our 9 O'Clock
|
if (newCoordinate.getAdjacentTiles(tileMap).contains(riverCoordinateTile)) // moved from our 7 O'Clock to our 9 O'Clock
|
||||||
riverCoordinateTile.hasBottomLeftRiver = true
|
riverCoordinateTile.hasBottomLeftRiver = true
|
||||||
else // moved from our 7 O'Clock down in the 7 O'Clock direction
|
else // moved from our 7 O'Clock down in the 7 O'Clock direction
|
||||||
map[newCoordinate.position].hasBottomRightRiver = true
|
tileMap[newCoordinate.position].hasBottomRightRiver = true
|
||||||
}
|
}
|
||||||
riverCoordinate = newCoordinate
|
riverCoordinate = newCoordinate
|
||||||
}
|
}
|
||||||
|
println("River reached max length!")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAdjacentTiles(riverCoordinate: RiverCoordinate, map: TileMap): Sequence<TileInfo> {
|
/*
|
||||||
val potentialPositions = sequenceOf(
|
fun numberOfConnectedRivers(riverCoordinate: RiverCoordinate): Int {
|
||||||
riverCoordinate.position,
|
|
||||||
riverCoordinate.position.cpy().add(-1f, -1f), // tile directly below us,
|
|
||||||
if (riverCoordinate.bottomRightOrLeft == RiverCoordinate.BottomRightOrLeft.BottomLeft)
|
|
||||||
riverCoordinate.position.cpy().add(0f, -1f) // tile to our bottom-left
|
|
||||||
else riverCoordinate.position.cpy().add(-1f, 0f) // tile to our bottom-right
|
|
||||||
)
|
|
||||||
return potentialPositions.map { if (map.contains(it)) map[it] else null }.filterNotNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun numberOfConnectedRivers(riverCoordinate: RiverCoordinate, map: TileMap): Int {
|
|
||||||
var sum = 0
|
var sum = 0
|
||||||
if (map.contains(riverCoordinate.position) && map[riverCoordinate.position].hasBottomRiver) sum += 1
|
if (tileMap.contains(riverCoordinate.position) && tileMap[riverCoordinate.position].hasBottomRiver) sum += 1
|
||||||
if (riverCoordinate.bottomRightOrLeft == RiverCoordinate.BottomRightOrLeft.BottomLeft) {
|
if (riverCoordinate.bottomRightOrLeft == RiverCoordinate.BottomRightOrLeft.BottomLeft) {
|
||||||
if (map.contains(riverCoordinate.position) && map[riverCoordinate.position].hasBottomLeftRiver) sum += 1
|
if (tileMap.contains(riverCoordinate.position) && tileMap[riverCoordinate.position].hasBottomLeftRiver) sum += 1
|
||||||
val bottomLeftTilePosition = riverCoordinate.position.cpy().add(0f, -1f)
|
val bottomLeftTilePosition = riverCoordinate.position.cpy().add(0f, -1f)
|
||||||
if (map.contains(bottomLeftTilePosition) && map[bottomLeftTilePosition].hasBottomRightRiver) sum += 1
|
if (tileMap.contains(bottomLeftTilePosition) && tileMap[bottomLeftTilePosition].hasBottomRightRiver) sum += 1
|
||||||
} else {
|
} else {
|
||||||
if (map.contains(riverCoordinate.position) && map[riverCoordinate.position].hasBottomRightRiver) sum += 1
|
if (tileMap.contains(riverCoordinate.position) && tileMap[riverCoordinate.position].hasBottomRightRiver) sum += 1
|
||||||
val bottomLeftTilePosition = riverCoordinate.position.cpy().add(-1f, 0f)
|
val bottomLeftTilePosition = riverCoordinate.position.cpy().add(-1f, 0f)
|
||||||
if (map.contains(bottomLeftTilePosition) && map[bottomLeftTilePosition].hasBottomLeftRiver) sum += 1
|
if (tileMap.contains(bottomLeftTilePosition) && tileMap[bottomLeftTilePosition].hasBottomLeftRiver) sum += 1
|
||||||
}
|
}
|
||||||
return sum
|
return sum
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Describes a _Vertex_ on our hexagonal grid via a neighboring hex and clock direction, normalized
|
||||||
|
* such that always the north-most hex and one of the two clock directions 5 / 7 o'clock are used. */
|
||||||
|
class RiverCoordinate(val position: Vector2, val bottomRightOrLeft: BottomRightOrLeft) {
|
||||||
|
enum class BottomRightOrLeft {
|
||||||
|
/** 7 O'Clock of the tile */
|
||||||
|
BottomLeft,
|
||||||
|
|
||||||
|
/** 5 O'Clock of the tile */
|
||||||
|
BottomRight
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Lists the three neighboring vertices which have their anchor hex on the map
|
||||||
|
* (yes some positions on the map's outer border will be included, some not) */
|
||||||
|
fun getAdjacentPositions(tileMap: TileMap): Sequence<RiverCoordinate> = sequence {
|
||||||
|
// What's nice is that adjacents are always the OPPOSITE in terms of right-left - rights are adjacent to only lefts, and vice-versa
|
||||||
|
// This means that a lot of obviously-wrong assignments are simple to spot
|
||||||
|
val x = position.x.toInt()
|
||||||
|
val y = position.y.toInt()
|
||||||
|
if (bottomRightOrLeft == BottomRightOrLeft.BottomLeft) {
|
||||||
|
yield(RiverCoordinate(position, BottomRightOrLeft.BottomRight)) // same tile, other side
|
||||||
|
val myTopLeft = tileMap.getIfTileExistsOrNull(x + 1, y)
|
||||||
|
if (myTopLeft != null)
|
||||||
|
yield(RiverCoordinate(myTopLeft.position, BottomRightOrLeft.BottomRight)) // tile to MY top-left, take its bottom right corner
|
||||||
|
val myBottomLeft = tileMap.getIfTileExistsOrNull(x, y - 1)
|
||||||
|
if (myBottomLeft != null)
|
||||||
|
yield(RiverCoordinate(myBottomLeft.position, BottomRightOrLeft.BottomRight)) // Tile to MY bottom-left, take its bottom right
|
||||||
|
} else {
|
||||||
|
yield(RiverCoordinate(position, BottomRightOrLeft.BottomLeft)) // same tile, other side
|
||||||
|
val myTopRight = tileMap.getIfTileExistsOrNull(x, y + 1)
|
||||||
|
if (myTopRight != null)
|
||||||
|
yield(RiverCoordinate(myTopRight.position, BottomRightOrLeft.BottomLeft)) // tile to MY top-right, take its bottom left
|
||||||
|
val myBottomRight = tileMap.getIfTileExistsOrNull(x - 1, y)
|
||||||
|
if (myBottomRight != null)
|
||||||
|
yield(RiverCoordinate(myBottomRight.position, BottomRightOrLeft.BottomLeft)) // tile to MY bottom-right, take its bottom left
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Lists the three neighboring hexes to this vertex which are on the map */
|
||||||
|
fun getAdjacentTiles(tileMap: TileMap): Sequence<TileInfo> = sequence {
|
||||||
|
val x = position.x.toInt()
|
||||||
|
val y = position.y.toInt()
|
||||||
|
yield(tileMap[x, y])
|
||||||
|
val below = tileMap.getIfTileExistsOrNull(x - 1, y - 1) // tile directly below us,
|
||||||
|
if (below != null) yield(below)
|
||||||
|
val leftOrRight = if (bottomRightOrLeft == BottomRightOrLeft.BottomLeft)
|
||||||
|
tileMap.getIfTileExistsOrNull(x, y - 1) // tile to our bottom-left
|
||||||
|
else tileMap.getIfTileExistsOrNull(x - 1, y) // tile to our bottom-right
|
||||||
|
if (leftOrRight != null) yield(leftOrRight)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -11,7 +11,6 @@ import com.unciv.UncivGame
|
|||||||
import com.unciv.logic.civilization.CivilizationInfo
|
import com.unciv.logic.civilization.CivilizationInfo
|
||||||
import com.unciv.logic.map.RoadStatus
|
import com.unciv.logic.map.RoadStatus
|
||||||
import com.unciv.logic.map.TileInfo
|
import com.unciv.logic.map.TileInfo
|
||||||
import com.unciv.logic.map.TileMap
|
|
||||||
import com.unciv.ui.cityscreen.YieldGroup
|
import com.unciv.ui.cityscreen.YieldGroup
|
||||||
import com.unciv.ui.utils.ImageGetter
|
import com.unciv.ui.utils.ImageGetter
|
||||||
import com.unciv.ui.utils.center
|
import com.unciv.ui.utils.center
|
||||||
|
Loading…
x
Reference in New Issue
Block a user