Automated (and UI) workers now build roads between cities if they're big enough!

This was the major cause for the AI's economic failure!
This commit is contained in:
Yair Morgenstern 2018-07-02 23:40:50 +03:00
parent c49a282a74
commit 71f4c04948
8 changed files with 130 additions and 28 deletions

View File

@ -2,6 +2,8 @@ package com.unciv.logic
import com.badlogic.gdx.math.Vector2
import java.util.*
import kotlin.math.abs
import kotlin.math.max
class HexMath {
@ -72,13 +74,8 @@ class HexMath {
return hexesToReturn
}
fun GetDistance(origin: Vector2, destination: Vector2): Int { // Yes, this is a dumb implementation. But I can't be arsed to think of a better one right now, other stuff to do.
var distance = 0
while (true) {
if (GetVectorsAtDistance(origin, distance).contains(destination)) return distance
distance++
}
fun getDistance(origin: Vector2, destination: Vector2): Int { // Yes, this is a dumb implementation. But I can't be arsed to think of a better one right now, other stuff to do.
return max(abs(origin.x-destination.x),abs(origin.y-destination.y) ).toInt()
}
}

View File

@ -1,10 +1,12 @@
package com.unciv.logic.automation
import com.badlogic.gdx.graphics.Color
import com.unciv.logic.HexMath
import com.unciv.logic.city.CityConstructions
import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.RoadStatus
import com.unciv.logic.map.TileInfo
import com.unciv.models.gamebasics.GameBasics
import com.unciv.models.gamebasics.unit.Unit
@ -42,6 +44,15 @@ class Automation {
civInfo.policies.adopt(policyToAdopt)
}
// Order roads between cities if you can
// for(city in civInfo.cities.filter { it.population.population>3 && !it.isCapital()
// && !it.cityStats.isConnectedToCapital(RoadStatus.Road) }){
// val closestConnectedCity = civInfo.cities.filter { it.isCapital() || it.cityStats.isConnectedToCapital(RoadStatus.Road) }
// .minBy { HexMath().getDistance(city.location,it.location) }!!
// val pathToClosestCity = civInfo.gameInfo.tileMap.getShortestPathBetweenTwoTiles(city.getCenterTile(),closestConnectedCity.getCenterTile())
// }
val rangedUnits = mutableListOf<MapUnit>()
val meleeUnits = mutableListOf<MapUnit>()
val civilianUnits = mutableListOf<MapUnit>()

View File

@ -72,7 +72,7 @@ class UnitAutomation{
}
if (unit.name == "Worker") {
WorkerAutomation().automateWorkerAction(unit)
WorkerAutomation(unit).automateWorkerAction()
return
}

View File

@ -1,21 +1,28 @@
package com.unciv.logic.automation
import com.unciv.logic.HexMath
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.RoadStatus
import com.unciv.logic.map.TileInfo
import com.unciv.models.gamebasics.GameBasics
import com.unciv.models.gamebasics.tile.TileImprovement
class WorkerAutomation {
class WorkerAutomation(val unit: MapUnit) {
fun automateWorkerAction(unit: MapUnit) {
fun automateWorkerAction() {
val enemyUnitsInWalkingDistance = unit.getDistanceToTiles().keys
.filter { it.militaryUnit!=null && it.militaryUnit!!.civInfo!=unit.civInfo }
if(enemyUnitsInWalkingDistance.isNotEmpty()) return // Don't you dare move.
val tile = unit.getTile()
val tileToWork = findTileToWork(unit)
val tileToWork = findTileToWork()
if(getPriority(tileToWork,unit.civInfo) < 3){ // building roads is more important
if(tryConnectingCities()) return
}
if (tileToWork != tile) {
unit.movementAlgs().headTowards(tileToWork)
unit.doPreTurnAction()
@ -23,34 +30,68 @@ class WorkerAutomation {
}
if (tile.improvementInProgress == null) {
val improvement = chooseImprovement(tile)
if (tile.canBuildImprovement(improvement, unit.civInfo))
// What if we're stuck on this tile but can't build there?
if (tile.canBuildImprovement(improvement, unit.civInfo)) {
// What if we're stuck on this tile but can't build there?
tile.startWorkingOnImprovement(improvement, unit.civInfo)
return
}
}
if(tile.improvementInProgress!=null) return // we're working!
}
private fun findTileToWork(worker:MapUnit): TileInfo {
val currentTile=worker.getTile()
fun tryConnectingCities():Boolean{ // returns whether we actually did anything
val cityThatNeedsConnecting = unit.civInfo.cities.filter { it.population.population>3 && !it.isCapital()
&& !it.cityStats.isConnectedToCapital(RoadStatus.Road) }
.minBy { HexMath().getDistance(it.location, unit.getTile().position) }
if(cityThatNeedsConnecting==null) return false// do nothing.
val closestConnectedCity = unit.civInfo.cities.filter { it.isCapital() || it.cityStats.isConnectedToCapital(RoadStatus.Road) }
.minBy { HexMath().getDistance(cityThatNeedsConnecting.location,it.location) }!!
val pathToClosestCity = unit.civInfo.gameInfo.tileMap
.getShortestPathBetweenTwoTiles(cityThatNeedsConnecting.getCenterTile(),
closestConnectedCity.getCenterTile())
.filter { it.roadStatus==RoadStatus.None}
val unitTile = unit.getTile()
if(unitTile in pathToClosestCity){
if(unitTile.improvementInProgress==null)
unitTile.startWorkingOnImprovement(GameBasics.TileImprovements["Road"]!!,unit.civInfo)
return true
}
val closestTileInPathWithNoRoad = pathToClosestCity.filter { unit.canMoveTo(it)}
.minBy { HexMath().getDistance(unit.getTile().position, it.position) }
if(closestTileInPathWithNoRoad==null) return false
unit.movementAlgs().headTowards(closestTileInPathWithNoRoad)
if(unit.currentMovement>0 && unit.getTile()==closestTileInPathWithNoRoad)
closestTileInPathWithNoRoad.startWorkingOnImprovement(GameBasics.TileImprovements["Road"]!!,unit.civInfo)
return true
}
private fun findTileToWork(): TileInfo {
val currentTile=unit.getTile()
val workableTiles = currentTile.getTilesInDistance(4)
.filter {
(it.civilianUnit== null || it == currentTile)
&& it.improvement == null
&& it.canBuildImprovement(chooseImprovement(it), worker.civInfo)
&& {val city=it.getCity(); city==null || it.getCity()?.civInfo == worker.civInfo}() // don't work tiles belonging to another civ
}.sortedByDescending { getPriority(it, worker.civInfo) }.toMutableList()
&& it.canBuildImprovement(chooseImprovement(it), unit.civInfo)
&& {val city=it.getCity(); city==null || it.getCity()?.civInfo == unit.civInfo}() // don't work tiles belonging to another civ
}.sortedByDescending { getPriority(it, unit.civInfo) }.toMutableList()
// the tile needs to be actually reachable - more difficult than it seems,
// which is why we DON'T calculate this for every possible tile in the radius,
// but only for the tile that's about to be chosen.
val selectedTile = workableTiles.firstOrNull{
worker.movementAlgs()
unit.movementAlgs()
.getShortestPath(workableTiles.first())
.isNotEmpty()}
if (selectedTile != null
&& getPriority(selectedTile, worker.civInfo)>1
&& getPriority(selectedTile, unit.civInfo)>1
&& (!workableTiles.contains(currentTile)
|| getPriority(selectedTile, worker.civInfo) > getPriority(currentTile,worker.civInfo)))
|| getPriority(selectedTile, unit.civInfo) > getPriority(currentTile, unit.civInfo)))
return selectedTile
else return currentTile
}
@ -58,10 +99,12 @@ class WorkerAutomation {
private fun getPriority(tileInfo: TileInfo, civInfo: CivilizationInfo): Int {
var priority = 0
if (tileInfo.isWorked()) priority += 3
if (tileInfo.getOwner() == civInfo) priority += 2
if (tileInfo.hasViewableResource(civInfo)) priority += 1
if (tileInfo.getOwner() == civInfo){
priority += 2
if (tileInfo.isWorked()) priority += 3
}
else if (tileInfo.neighbors.any { it.getOwner() != null }) priority += 1
if (tileInfo.hasViewableResource(civInfo)) priority += 1
return priority
}
@ -80,4 +123,25 @@ class WorkerAutomation {
return GameBasics.TileImprovements[improvementString]!!
}
fun constructRoadTo(destination:TileInfo) {
val currentTile = unit.getTile()
if (currentTile.roadStatus == RoadStatus.None) {
currentTile.startWorkingOnImprovement(GameBasics.TileImprovements["Road"]!!, unit.civInfo)
return
}
val pathToDestination = unit.movementAlgs().getShortestPath(destination)
val destinationThisTurn = pathToDestination.first()
val fullPathToCurrentDestination = unit.movementAlgs().getFullPathToCloseTile(destinationThisTurn)
val firstTileWithoutRoad = fullPathToCurrentDestination.firstOrNull { it.roadStatus == RoadStatus.None && unit.canMoveTo(it) }
if (firstTileWithoutRoad == null) {
unit.moveToTile(destinationThisTurn)
return
}
unit.moveToTile(firstTileWithoutRoad)
if (unit.currentMovement > 0)
firstTileWithoutRoad.startWorkingOnImprovement(GameBasics.TileImprovements["Road"]!!, unit.civInfo)
}
}

View File

@ -60,7 +60,7 @@ class MapUnit {
return
}
if (action == "automation") WorkerAutomation().automateWorkerAction(this)
if (action == "automation") WorkerAutomation(this).automateWorkerAction()
}
private fun doPostTurnAction() {

View File

@ -5,6 +5,7 @@ import com.unciv.logic.GameInfo
import com.unciv.logic.HexMath
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.models.gamebasics.GameBasics
import kotlin.math.abs
class TileMap {
@ -74,4 +75,15 @@ class TileMap {
}
}
fun getShortestPathBetweenTwoTiles(from:TileInfo, to:TileInfo): ArrayList<TileInfo> {
val path = ArrayList<TileInfo>()
var currentTile = from
while(currentTile!=to){
path += currentTile
currentTile = currentTile.neighbors.minBy { abs(it.position.x-to.position.x)+abs(it.position.y-to.position.y) }!!
}
path+=to
return path
}
}

View File

@ -34,10 +34,9 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
val updatedTiles = ArrayList<TileInfo>()
for (tileToCheck in tilesToCheck)
for (neighbor in tileToCheck.neighbors) {
var totalDistanceToTile:Float
if ((neighbor.getOwner() != unit.civInfo && neighbor.isCityCenter())// Enemy city,
|| neighbor.getUnits().isNotEmpty() && neighbor.getUnits().first().civInfo!=unit.civInfo) // Enemy unit
|| neighbor.getUnits().isNotEmpty() && neighbor.getUnits().first().civInfo!=unit.civInfo) // Enemy unit
totalDistanceToTile = unitMovement // can't move through it - we'll be "stuck" there
else {
@ -149,4 +148,23 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
return getShortestPath(destination).isNotEmpty()
}
fun getFullPathToCloseTile(destination: TileInfo): List<TileInfo> {
val currentUnitTile = unit.getTile()
val distanceToTiles = unit.getDistanceToTiles()
val reversedList = ArrayList<TileInfo>()
var currentTile = destination
while(currentTile != currentUnitTile){
reversedList.add(currentTile)
val distanceToCurrentTile = distanceToTiles[currentTile]!!
if(currentUnitTile in currentTile.neighbors
&& getMovementCostBetweenAdjacentTiles(currentUnitTile,currentTile) == distanceToCurrentTile)
return reversedList.reversed()
for(tile in currentTile.neighbors)
currentTile = currentTile.neighbors.first{it in distanceToTiles
&& getMovementCostBetweenAdjacentTiles(it,currentTile) == distanceToCurrentTile - distanceToTiles[it]!!}
}
throw Exception("We couldn't get the path between the two tiles")
}
}

View File

@ -110,7 +110,7 @@ class UnitActions {
actionList += UnitAction("Automate",
{
unit.action = "automation"
WorkerAutomation().automateWorkerAction(unit)
WorkerAutomation(unit).automateWorkerAction()
},unit.currentMovement != 0f
)
}