Compare commits

...
Author SHA1 Message Date
adminandClaude Opus 4.5 c5d636a94c Convert command_choice_helpers to use Scala types instead of proto
Migrate command_choice_helpers package to use native Scala types internally:
- CommandChooser, FoodConsumptionUtils, ProvinceGoldSurplusCalculator
- OrganizeCommandSelector, ExpandCommandSelector, AttackCommandChooser
- AvailableMarchCommandFactory, AttackDecisionCommandChooser
- Supporting utilities: HeroUtils, ProvinceUtils, BattalionUtils, FactionUtils

Add proto-accepting overloads for backward compatibility with callers
still using proto types. Update tests to use RoundPhase.PLAYER_COMMANDS
since GameStateConverter now validates the phase field.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 07:10:56 -08:00
42 changed files with 1336 additions and 498 deletions
@@ -8,24 +8,27 @@ import net.eagle0.eagle.api.available_command.{
RecommendedBattleConfig
}
import net.eagle0.eagle.api.command.util.battalion_with_food_cost.BattalionWithFoodCost
import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.province.Province
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
import net.eagle0.eagle.library.settings.{MaxCombatUnitCountPerSide, MinVigorForMarch}
import net.eagle0.eagle.library.util.{BattalionSuitability, LegacyBattalionUtils, ShardokMapInfo}
import net.eagle0.eagle.library.util.{BattalionSuitability, BattalionUtils, ShardokMapInfo}
import net.eagle0.eagle.library.util.command_choice_helpers.{CombatUnitSelector, MarchSuppliesHelpers}
import net.eagle0.eagle.library.util.hero.LegacyHeroUtils
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
import net.eagle0.eagle.library.util.hero.HeroUtils
import net.eagle0.eagle.library.util.province.ProvinceUtils
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
import net.eagle0.eagle.model.proto_converters.BattalionTypeConverter
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.model.state.province.ProvinceT
object AvailableMarchCommandFactory extends AvailableCommandsFactoryForType {
private def oneOriginProvinceCommand(
gameState: GameState,
originProvince: Province
originProvince: ProvinceT
): Option[MarchCommandFromOneProvince] = {
val availableDestinations = originProvince.neighbors
.map(_.provinceId)
.flatMap(gameState.provinces.get)
.filterNot(LegacyProvinceUtils.hasBlizzard)
.map(gameState.provinces)
.filterNot(ProvinceUtils.hasBlizzard)
.map { destinationProvince =>
val startingPositionIndex = destinationProvince.neighbors
.find(n => n.provinceId == originProvince.id)
@@ -49,14 +52,14 @@ object AvailableMarchCommandFactory extends AvailableCommandsFactoryForType {
.map(heroId => gameState.heroes(heroId))
.filter(_.vigor >= MinVigorForMarch.doubleValue)
if LegacyProvinceUtils.hasBlizzard(originProvince) then return None
if ProvinceUtils.hasBlizzard(originProvince) then return None
if availableDestinations.isEmpty then return None
if availableHeroes.isEmpty then return None
val availableBattalions = originProvince.battalionIds
.map(battId => gameState.battalions(battId))
.sortBy(_.`type`.index)
.sortBy(_.typeId.value)
val recommendedSendCount =
availableHeroes.size
@@ -74,7 +77,7 @@ object AvailableMarchCommandFactory extends AvailableCommandsFactoryForType {
RecommendedBattleConfig(
units = units,
food = MarchSuppliesHelpers.foodToTakeOnAttack(
units.flatMap(_.battalionId).map(gameState.battalions),
units.flatMap(_.battalionId).size,
originProvince.food
),
gold = MarchSuppliesHelpers.goldToTakeOnAttack(
@@ -90,9 +93,9 @@ object AvailableMarchCommandFactory extends AvailableCommandsFactoryForType {
availableBattalions = availableBattalions.map { batt =>
BattalionWithFoodCost(
battalionId = batt.id,
monthlyFood = LegacyBattalionUtils.monthlyConsumedFood(
monthlyFood = BattalionUtils.monthlyConsumedFood(
battalion = batt,
bts = gameState.battalionTypes.toVector
bts = gameState.battalionTypes
)
)
}.toVector,
@@ -112,11 +115,12 @@ object AvailableMarchCommandFactory extends AvailableCommandsFactoryForType {
}
override def availableCommand(
gameState: GameState,
gameStateProto: GameStateProto,
factionId: FactionId,
provinceId: ProvinceId
): Option[MarchAvailableCommand] =
LegacyHeroUtils
): Option[MarchAvailableCommand] = {
val gameState = GameStateConverter.fromProto(gameStateProto)
HeroUtils
.consideredProvinces(gameState, factionId, provinceId)
.flatMap(p => oneOriginProvinceCommand(gameState = gameState, originProvince = p)) match {
case items if items.isEmpty => None
@@ -125,8 +129,9 @@ object AvailableMarchCommandFactory extends AvailableCommandsFactoryForType {
MarchAvailableCommand(
actingProvinceId = provinceId,
oneProvinceCommands = opcs,
availableTypes = gameState.battalionTypes
availableTypes = gameState.battalionTypes.map(BattalionTypeConverter.toProto)
)
)
}
}
}
@@ -447,12 +447,16 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/settings:max_combat_unit_count_per_side",
"//src/main/scala/net/eagle0/eagle/library/settings:min_vigor_for_march",
"//src/main/scala/net/eagle0/eagle/library/util:battalion_suitability",
"//src/main/scala/net/eagle0/eagle/library/util:legacy_battalion_utils",
"//src/main/scala/net/eagle0/eagle/library/util:battalion_utils",
"//src/main/scala/net/eagle0/eagle/library/util:shardok_map_info",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:combat_unit_selector",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:march_supplies_helpers",
"//src/main/scala/net/eagle0/eagle/library/util/hero:legacy_hero_utils",
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
"//src/main/scala/net/eagle0/eagle/library/util/hero:hero_utils",
"//src/main/scala/net/eagle0/eagle/library/util/province",
"//src/main/scala/net/eagle0/eagle/model/proto_converters:battalion_type_converter",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/province",
],
)
@@ -45,11 +45,10 @@ scala_library(
],
deps = [
"//src/main/protobuf/net/eagle0/eagle/api/command/util:appropriate_battalions_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/common:battalion_type_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/common:profession_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:battalion_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:hero_scala_proto",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/model/state:battalion_type",
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
"//src/main/scala/net/eagle0/eagle/model/state/hero",
],
)
@@ -64,10 +63,12 @@ scala_library(
],
exports = [
"//src/main/protobuf/net/eagle0/eagle/common:battalion_type_scala_proto",
"//src/main/scala/net/eagle0/eagle/model/state:battalion_type",
"//src/main/scala/net/eagle0/eagle/model/state:battalion_type_id",
],
deps = [
"//src/main/protobuf/net/eagle0/eagle/common:battalion_type_scala_proto",
"//src/main/scala/net/eagle0/eagle/model/state:battalion_type",
"//src/main/scala/net/eagle0/eagle/model/state:battalion_type_id",
],
)
@@ -97,6 +98,7 @@ scala_library(
deps = [
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/model/state:battalion_type",
"//src/main/scala/net/eagle0/eagle/model/state:battalion_type_id",
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
],
)
@@ -10,25 +10,25 @@ import net.eagle0.eagle.api.command.util.appropriate_battalions.SuitableBattalio
RESTRICTIVE,
SUBOPTIMAL
}
import net.eagle0.eagle.common.battalion_type.BattalionType
import net.eagle0.eagle.common.profession.Profession.{ENGINEER, MAGE, NECROMANCER, RANGER}
import net.eagle0.eagle.internal.battalion.Battalion
import net.eagle0.eagle.internal.hero.Hero
import net.eagle0.eagle.model.state.battalion.BattalionT
import net.eagle0.eagle.model.state.hero.HeroT
import net.eagle0.eagle.model.state.hero.Profession.{Engineer, Mage, Necromancer, Ranger}
import net.eagle0.eagle.model.state.BattalionType
import net.eagle0.eagle.HeroId
object BattalionSuitability {
def suitabilityLevel(h: Hero, bt: BattalionType): SuitabilityLevel =
def suitabilityLevel(h: HeroT, bt: BattalionType): SuitabilityLevel =
h.profession match {
case MAGE | NECROMANCER =>
case Mage | Necromancer =>
if !bt.allowsCasting then RESTRICTIVE
else if bt.alwaysArcheryCapable then SUBOPTIMAL
else OPTIMAL
case ENGINEER =>
case Engineer =>
if bt.alwaysArcheryCapable then SUBOPTIMAL
else OPTIMAL
case RANGER =>
case Ranger =>
if !bt.allowsStealth then RESTRICTIVE
else OPTIMAL
@@ -36,8 +36,8 @@ object BattalionSuitability {
}
def suitableBattalions(
h: Hero,
bs: Vector[Battalion],
h: HeroT,
bs: Vector[BattalionT],
bts: Vector[BattalionType]
): SuitableBattalions =
SuitableBattalions(
@@ -45,14 +45,14 @@ object BattalionSuitability {
BattalionIdWithSuitability(
battalionId = b.id,
level = BattalionSuitability
.suitabilityLevel(h, bts.find(_.typeId == b.`type`).get)
.suitabilityLevel(h, bts.find(_.typeId == b.typeId).get)
)
)
)
def suitableBattalionsForHeroes(
hs: Vector[Hero],
bs: Vector[Battalion],
hs: Vector[HeroT],
bs: Vector[BattalionT],
bts: Vector[BattalionType]
): Map[HeroId, SuitableBattalions] =
hs.map(h => h.id -> suitableBattalions(h, bs, bts)).toMap
@@ -4,7 +4,7 @@ import net.eagle0.eagle.common.battalion_type.{
BattalionType as BattalionTypeProto,
BattalionTypeId as BattalionTypeIdProto
}
import net.eagle0.eagle.model.state.BattalionTypeId
import net.eagle0.eagle.model.state.{BattalionType, BattalionTypeId}
object BattalionTypeFinder {
def battalionType(
@@ -18,4 +18,10 @@ object BattalionTypeFinder {
battalionTypes: Vector[BattalionTypeProto]
): BattalionTypeProto =
battalionTypes.find(_.typeId == battalionTypeId).get
def findBattalionType(
battalionTypeId: BattalionTypeId,
battalionTypes: Vector[BattalionType]
): BattalionType =
battalionTypes.find(_.typeId == battalionTypeId).get
}
@@ -1,9 +1,24 @@
package net.eagle0.eagle.library.util
import net.eagle0.eagle.model.state.{BattalionType, BattalionTypeId}
import net.eagle0.eagle.model.state.battalion.BattalionT
import net.eagle0.eagle.model.state.BattalionType
import net.eagle0.eagle.model.state.BattalionTypeId.{
HeavyCavalry,
HeavyInfantry,
LightCavalry,
LightInfantry,
Longbowmen
}
object BattalionUtils {
val powerMultiplier: Map[BattalionTypeId, Double] = Map(
LightInfantry -> 1.0,
HeavyInfantry -> 2.0,
LightCavalry -> 2.0,
HeavyCavalry -> 4.0,
Longbowmen -> 1.5
)
def monthlyConsumedFood(
battalions: Iterable[BattalionT],
bts: Vector[BattalionType]
@@ -21,4 +36,9 @@ object BattalionUtils {
val battalionType = bts.find(_.typeId == battalion.typeId).get
battalion.size.toDouble * battalionType.monthlyFoodCost
}
def power(batt: BattalionT): Double =
powerMultiplier(
batt.typeId
) * batt.size * (1.0 + batt.armament / 100.0) + (1.0 + batt.training / 100.0)
}
@@ -3,41 +3,42 @@ package net.eagle0.eagle.library.util.command_choice_helpers
import net.eagle0.eagle.{FactionId, ProvinceId}
import net.eagle0.eagle.api.available_command.{AlmsAvailableCommand, AvailableCommand}
import net.eagle0.eagle.api.selected_command.AlmsSelectedCommand
import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.hero.Hero
import net.eagle0.eagle.internal.province.Province
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
import net.eagle0.eagle.library.actions.impl.command.AlmsCommand
import net.eagle0.eagle.library.settings.MinSupportForTaxes
import net.eagle0.eagle.library.util.command_choice_helpers.AvailableCommandSelector.flatSelectionForType
import net.eagle0.eagle.library.util.command_choice_helpers.FoodConsumptionUtils.foodConsumptionMonthsToHold
import net.eagle0.eagle.library.util.hero.LegacyHeroUtils
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
import net.eagle0.eagle.library.util.hero.HeroUtils
import net.eagle0.eagle.library.util.province.ProvinceUtils
import net.eagle0.eagle.library.util.CommandSelection
import net.eagle0.eagle.model.proto_converters.hero.HeroConverter
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.model.state.hero.HeroT
import net.eagle0.eagle.model.state.province.ProvinceT
object AlmsCommandSelector {
private def bestHeroWithSupport(
availableHeroes: Vector[Hero]
): (Hero, Double) =
availableHeroes: Vector[HeroT]
): (HeroT, Double) =
availableHeroes
.map(h => (h, AlmsCommand.supportIncreasePerFood(HeroConverter.fromProto(h))))
.map(h => (h, AlmsCommand.supportIncreasePerFood(h)))
.maxBy {
case (hero, inc) =>
(inc, -LegacyHeroUtils.fatigue(hero))
(inc, -HeroUtils.fatigue(hero))
}
private def neededSupport(province: Province): Double =
private def neededSupport(province: ProvinceT): Double =
MinSupportForTaxes.doubleValue - province.support
def surplusFood(province: Province, gameState: GameState): Int =
def surplusFood(province: ProvinceT, gameState: GameState): Int =
Math.max(
0,
province.food - foodConsumptionMonthsToHold(
province.food.toInt - foodConsumptionMonthsToHold(
provinceId = province.id,
gameState = gameState
) * LegacyProvinceUtils
.monthlyFoodConsumption(province.id, gameState)
) * ProvinceUtils
.monthlyFoodConsumption(province, gameState)
)
def chosenAlmsForSupportCommand(
@@ -97,7 +98,7 @@ object AlmsCommandSelector {
gameState: GameState,
availableCommand: AlmsAvailableCommand,
minimumFood: Int,
province: Province
province: ProvinceT
): Option[CommandSelection] = {
val foodAfterFeedingTroops = surplusFood(province, gameState)
val effectiveMinimumFood = minimumFood.min(foodAfterFeedingTroops)
@@ -109,7 +110,7 @@ object AlmsCommandSelector {
)
val neededSupportPerMonth =
neededSupport(province) / (13 - gameState.currentDate.get.month)
neededSupport(province) / (13 - gameState.currentDate.get.month.value)
val neededFoodForSupport =
neededSupport(province) / supportPerFood
val neededFoodPerMonth =
@@ -123,7 +124,7 @@ object AlmsCommandSelector {
val isPartialGift =
neededFoodForSupport > availableCommand.foodAvailable
val isDecember = gameState.currentDate.get.month == 12
val isDecember = gameState.currentDate.get.month.value == 12
val haveExcessToQuellRiot =
(foodAfterFeedingTroops - neededFoodForSupport > 1000) || province.gold > 700
@@ -152,4 +153,50 @@ object AlmsCommandSelector {
)
}
}
// Proto-accepting overloads for backward compatibility
def surplusFood(province: net.eagle0.eagle.internal.province.Province, gameState: GameStateProto): Int =
surplusFood(
net.eagle0.eagle.model.proto_converters.province.ProvinceConverter.fromProto(province),
GameStateConverter.fromProto(gameState)
)
def chosenAlmsForSupportCommand(
actingFactionId: FactionId,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] =
chosenAlmsForSupportCommand(
actingFactionId,
GameStateConverter.fromProto(gameState),
availableCommands
)
def chosenAlmsForSupportWithMinimumCommand(
actingFactionId: FactionId,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
minimumFood: Int
): Option[CommandSelection] =
chosenAlmsForSupportWithMinimumCommand(
actingFactionId,
GameStateConverter.fromProto(gameState),
availableCommands,
minimumFood
)
def chosenAlmsForSupportWithMinimumCommandForProvince(
actingFactionId: FactionId,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
minimumFood: Int,
provinceId: ProvinceId
): Option[CommandSelection] =
chosenAlmsForSupportWithMinimumCommandForProvince(
actingFactionId,
GameStateConverter.fromProto(gameState),
availableCommands,
minimumFood,
provinceId
)
}
@@ -4,22 +4,23 @@ import net.eagle0.eagle.{FactionId, ProvinceId}
import net.eagle0.eagle.api.available_command.*
import net.eagle0.eagle.api.selected_command.*
import net.eagle0.eagle.common.combat_unit.CombatUnit
import net.eagle0.eagle.common.profession.Profession.{ENGINEER, MAGE}
import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.hero.Hero
import net.eagle0.eagle.internal.province.Province
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
import net.eagle0.eagle.library.settings.{
MinVigorForMarch,
RequiredRatioToAttack,
RequiredRatioToAttackWithoutTakingCastles
}
import net.eagle0.eagle.library.util.{CommandSelection, LegacyBattalionUtils, ShardokMapInfo}
import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils
import net.eagle0.eagle.library.util.{BattalionUtils, CommandSelection, LegacyBattalionUtils, ShardokMapInfo}
import net.eagle0.eagle.library.util.faction_utils.FactionUtils
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.model.state.hero.HeroT
import net.eagle0.eagle.model.state.hero.Profession.{Engineer, Mage}
import net.eagle0.eagle.model.state.province.ProvinceT
import net.eagle0.eagle.views.battalion_view.BattalionView
object AttackCommandChooser {
import net.eagle0.eagle.library.util.DateProtoUtils._Date
import AvailableCommandSelector.flatSelectionForType
private val winterMonths = Set(12, 1, 2)
@@ -27,7 +28,7 @@ object AttackCommandChooser {
private def heroesWithSufficientVigorForAttack(
provinceId: ProvinceId,
gs: GameState
): Vector[Hero] =
): Vector[HeroT] =
gs.provinces(provinceId)
.rulingFactionHeroIds
.map(gs.heroes)
@@ -41,10 +42,13 @@ object AttackCommandChooser {
gameState: GameState
): Boolean =
destinationFactionId.exists(dfid =>
dfid != actingFactionId && !LegacyFactionUtils
.hasAlliance(actingFactionId, dfid, gameState) && !LegacyFactionUtils
.truceExpirationDate(dfid, actingFactionId, gameState)
.exists(_ > gameState.currentDate.get)
dfid != actingFactionId && !FactionUtils
.hasAlliance(actingFactionId, dfid, gameState.factions.values.toVector) && !FactionUtils
.factionRelationship(dfid, actingFactionId, gameState.factions.values)
.resetDate
.exists(d =>
d.year > gameState.currentDate.get.year || (d.year == gameState.currentDate.get.year && d.month.value > gameState.currentDate.get.month.value)
)
)
// Calculates the best attack for a particular destination, not taking into account whether it's a good idea --
@@ -56,7 +60,7 @@ object AttackCommandChooser {
goldAvailable: Int,
foodAvailable: Int,
originProvinceId: ProvinceId,
destinationProvince: Province,
destinationProvince: ProvinceT,
gameState: GameState
): MarchSelectedCommand = MarchSelectedCommand(
marchingUnits = selectedUnits,
@@ -65,7 +69,7 @@ object AttackCommandChooser {
gold = MarchSuppliesHelpers
.goldToTakeOnAttack(selectedUnits.size, goldAvailable),
food = MarchSuppliesHelpers.foodToTakeOnAttack(
selectedUnits.flatMap(_.battalionId).map(gameState.battalions),
selectedUnits.flatMap(_.battalionId).size,
foodAvailable
)
)
@@ -81,7 +85,7 @@ object AttackCommandChooser {
goldAvailable: Int,
foodAvailable: Int,
originProvinceId: ProvinceId,
destinationProvince: Province,
destinationProvince: ProvinceT,
requiresRiverCrossing: Boolean,
totalBattalionPower: Double
)
@@ -124,7 +128,7 @@ object AttackCommandChooser {
desiredCount = unitCount,
heroes = heroes,
battalions = battalions.toVector,
battalionTypes = gameState.battalionTypes.toVector
battalionTypes = gameState.battalionTypes
)
// Get river crossing requirement from the available destination
@@ -140,7 +144,7 @@ object AttackCommandChooser {
totalBattalionPower = selectedUnits
.flatMap(_.battalionId)
.map(gameState.battalions)
.map(LegacyBattalionUtils.power)
.map(BattalionUtils.power)
.sum
)
}).toVector
@@ -162,7 +166,7 @@ object AttackCommandChooser {
.filter {
_.destinationProvince.incomingArmies
.exists(incomingArmy =>
incomingArmy.getArmy.factionId == actingFactionId && incomingArmy.arrivalRound > gameState.currentRoundId
incomingArmy.army.factionId == actingFactionId && incomingArmy.arrivalRound > gameState.currentRoundId
)
}
.map {
@@ -258,12 +262,12 @@ object AttackCommandChooser {
.map(u => u.heroId)
.map(gameState.heroes)
.map(_.profession)
.exists(prof => prof == ENGINEER || prof == MAGE)
.exists(prof => prof == Engineer || prof == Mage)
// If it's winter or we have a river crossing profession, take everything.
// Otherwise, only take those that don't require a river crossing.
val isWinter =
winterMonths.contains(gameState.currentDate.get.month)
winterMonths.contains(gameState.currentDate.get.month.value)
val allMarchingCIs =
if isWinter || hasRiverCrossingProfession then commandInfos
@@ -316,4 +320,31 @@ object AttackCommandChooser {
)
}
}
// Proto-accepting overloads for backward compatibility
def possibleAttackCommands(
actingFactionId: FactionId,
gameState: GameStateProto,
ac: MarchAvailableCommand
): Vector[PossibleAttackCommandInfo] =
possibleAttackCommands(
actingFactionId,
GameStateConverter.fromProto(gameState),
ac
)
def chosenAttackCommand(
actingFactionId: FactionId,
gameState: GameStateProto,
acs: Vector[AvailableCommand],
knownEnemyHeroCount: ProvinceId => Int,
knownEnemyBattalions: ProvinceId => Vector[BattalionView]
): Option[CommandSelection] =
chosenAttackCommand(
actingFactionId,
GameStateConverter.fromProto(gameState),
acs,
knownEnemyHeroCount,
knownEnemyBattalions
)
}
@@ -11,11 +11,14 @@ import net.eagle0.eagle.api.command.util.attack_decision_type.{
}
import net.eagle0.eagle.api.selected_command.AttackDecisionSelectedCommand
import net.eagle0.eagle.common.tribute_amount.TributeAmount
import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
import net.eagle0.eagle.library.settings.MinimumPercentageOfEnemyToAttack
import net.eagle0.eagle.library.util.{BattalionPower, CommandSelection, IncomingArmyUtils}
import net.eagle0.eagle.library.util.{BattalionPower, CommandSelection}
import net.eagle0.eagle.library.util.command_choice_helpers.AvailableCommandSelector.flatSelectionForType
import net.eagle0.eagle.library.util.command_choice_helpers.CommandChooserImplicits.*
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.model.state.Army
import net.eagle0.eagle.FactionId
object AttackDecisionCommandChooser {
@@ -127,6 +130,11 @@ object AttackDecisionCommandChooser {
}.flatten
}
private def armyPower(army: Army, gs: GameState): Double = {
val battalions = army.units.flatMap(_.battalionId).map(gs.battalions)
battalions.map(BattalionPower.power).sum
}
private def okToAttack(
availableCommand: AttackDecisionAvailableCommand,
gameState: GameState
@@ -143,7 +151,7 @@ object AttackDecisionCommandChooser {
.find(hag => hag.factionId == armyStats.factionId)
.map(_.armies)
.getOrElse(Vector.empty)
.map(ia => IncomingArmyUtils.armyPower(ia.getArmy, gameState))
.map(ia => armyPower(ia.army, gameState))
.sum
)
.sum
@@ -219,4 +227,18 @@ object AttackDecisionCommandChooser {
)
}
}
// Proto-accepting overload for backward compatibility
def attackDecisionSelectedCommand(
actingFactionId: FactionId,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] =
attackDecisionSelectedCommand(
actingFactionId,
GameStateConverter.fromProto(gameState),
availableCommands,
functionalRandom
)
}
@@ -26,19 +26,26 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:__pkg__",
"//src/test/scala/net/eagle0/eagle/library/util/command_choice_helpers:__pkg__",
],
exports = [
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
],
deps = [
":available_command_selector",
":food_consumption_utils",
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:alms_command",
"//src/main/scala/net/eagle0/eagle/library/settings:min_support_for_taxes",
"//src/main/scala/net/eagle0/eagle/library/util:command_selection",
"//src/main/scala/net/eagle0/eagle/library/util/hero:legacy_hero_utils",
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/hero",
"//src/main/scala/net/eagle0/eagle/library/util/hero:hero_utils",
"//src/main/scala/net/eagle0/eagle/library/util/province",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/province",
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/hero",
"//src/main/scala/net/eagle0/eagle/model/state/province",
],
)
@@ -49,25 +56,30 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/ai:__pkg__",
"//src/test/scala/net/eagle0/eagle/library/util/command_choice_helpers:__pkg__",
],
exports = [
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
],
deps = [
":available_command_selector",
":combat_unit_selector",
":march_supplies_helpers",
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:battalion_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:hero_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:province_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/views:battalion_view_scala_proto",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/library/settings:min_vigor_for_march",
"//src/main/scala/net/eagle0/eagle/library/settings:required_ratio_to_attack",
"//src/main/scala/net/eagle0/eagle/library/settings:required_ratio_to_attack_without_taking_castles",
"//src/main/scala/net/eagle0/eagle/library/util:battalion_type_finder",
"//src/main/scala/net/eagle0/eagle/library/util:battalion_utils",
"//src/main/scala/net/eagle0/eagle/library/util:command_selection",
"//src/main/scala/net/eagle0/eagle/library/util:date_utils",
"//src/main/scala/net/eagle0/eagle/library/util:legacy_battalion_utils",
"//src/main/scala/net/eagle0/eagle/library/util:shardok_map_info",
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils:legacy_faction_utils",
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/hero",
"//src/main/scala/net/eagle0/eagle/model/state/province",
],
)
@@ -78,6 +90,9 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/ai:__pkg__",
"//src/test/scala/net/eagle0/eagle/library/util/command_choice_helpers:__pkg__",
],
exports = [
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
],
deps = [
":available_command_selector",
":command_chooser",
@@ -89,7 +104,9 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/settings:minimum_percentage_of_enemy_to_attack",
"//src/main/scala/net/eagle0/eagle/library/util:battalion_power",
"//src/main/scala/net/eagle0/eagle/library/util:command_selection",
"//src/main/scala/net/eagle0/eagle/library/util:incoming_army_utils",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
"//src/main/scala/net/eagle0/eagle/model/state:army",
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
],
)
@@ -118,12 +135,13 @@ scala_library(
deps = [
"//src/main/protobuf/net/eagle0/eagle/api/command/util:appropriate_battalions_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/common:combat_unit_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:battalion_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:hero_scala_proto",
"//src/main/scala/net/eagle0/eagle/library/util:battalion_suitability",
"//src/main/scala/net/eagle0/eagle/library/util:battalion_utils",
"//src/main/scala/net/eagle0/eagle/library/util:eagle_require",
"//src/main/scala/net/eagle0/eagle/library/util:legacy_battalion_utils",
"//src/main/scala/net/eagle0/eagle/library/util/hero:legacy_hero_utils",
"//src/main/scala/net/eagle0/eagle/library/util/hero:hero_utils",
"//src/main/scala/net/eagle0/eagle/model/state:battalion_type",
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
"//src/main/scala/net/eagle0/eagle/model/state/hero",
],
)
@@ -143,6 +161,8 @@ scala_library(
"//src/main/scala/net/eagle0/common:functional_random",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/library/util:command_selection",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
],
)
@@ -198,13 +218,22 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/settings:vassal_minimum_food_to_send_supplies",
"//src/main/scala/net/eagle0/eagle/library/settings:vassal_minimum_gold_to_send_supplies",
"//src/main/scala/net/eagle0/eagle/library/util:battalion_type_finder",
"//src/main/scala/net/eagle0/eagle/library/util:battalion_utils",
"//src/main/scala/net/eagle0/eagle/library/util:beast_utils",
"//src/main/scala/net/eagle0/eagle/library/util:command_selection",
"//src/main/scala/net/eagle0/eagle/library/util:incoming_army_utils",
"//src/main/scala/net/eagle0/eagle/library/util:province_distances",
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils",
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils:legacy_faction_utils",
"//src/main/scala/net/eagle0/eagle/library/util/hero:hero_utils",
"//src/main/scala/net/eagle0/eagle/library/util/hero:legacy_hero_utils",
"//src/main/scala/net/eagle0/eagle/library/util/province",
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/hero",
"//src/main/scala/net/eagle0/eagle/model/state/province",
],
)
@@ -233,6 +262,10 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/ai:__pkg__",
"//src/test/scala/net/eagle0/eagle/library/util/command_choice_helpers:__pkg__",
],
exports = [
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/province",
],
deps = [
":available_command_selector",
":food_consumption_utils",
@@ -242,12 +275,15 @@ scala_library(
"//src/main/scala/net/eagle0/common:functional_random",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/library/settings:max_combat_unit_count_per_side",
"//src/main/scala/net/eagle0/eagle/library/util:battalion_utils",
"//src/main/scala/net/eagle0/eagle/library/util:command_selection",
"//src/main/scala/net/eagle0/eagle/library/util:eagle_require",
"//src/main/scala/net/eagle0/eagle/library/util:legacy_battalion_utils",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:province_gold_surplus_calculator",
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils:legacy_faction_utils",
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils",
"//src/main/scala/net/eagle0/eagle/library/util/province",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/province",
],
)
@@ -258,10 +294,18 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/ai:__pkg__",
"//src/test/scala/net/eagle0/eagle/library/util/command_choice_helpers:__pkg__",
],
exports = [
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
],
deps = [
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/library/settings:min_support_for_taxes",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
"//src/main/scala/net/eagle0/eagle/model/state/date",
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
],
)
@@ -323,9 +367,9 @@ scala_library(
"//src/test/scala/net/eagle0/eagle/library/util/command_choice_helpers:__pkg__",
],
deps = [
"//src/main/protobuf/net/eagle0/eagle/internal:hero_scala_proto",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/library/util/hero:legacy_hero_utils",
"//src/main/scala/net/eagle0/eagle/library/util/hero:hero_utils",
"//src/main/scala/net/eagle0/eagle/model/state/hero",
],
)
@@ -337,16 +381,21 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/ai:__pkg__",
"//src/test/scala/net/eagle0/eagle/library/util/command_choice_helpers:__pkg__",
],
exports = [
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
],
deps = [
":available_command_selector",
":hero_selector",
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:hero_scala_proto",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/library/util:command_selection",
"//src/main/scala/net/eagle0/eagle/library/util/hero:legacy_hero_utils",
"//src/main/scala/net/eagle0/eagle/library/util/hero:hero_utils",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/province",
],
)
@@ -358,9 +407,7 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
"//src/test/scala/net/eagle0/eagle/library/util/command_choice_helpers:__pkg__",
],
deps = [
"//src/main/protobuf/net/eagle0/eagle/internal:battalion_scala_proto",
],
deps = [],
)
scala_library(
@@ -380,8 +427,13 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/util:battalion_type_finder",
"//src/main/scala/net/eagle0/eagle/library/util:command_selection",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:province_gold_surplus_calculator",
"//src/main/scala/net/eagle0/eagle/library/util/hero:legacy_hero_utils",
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
"//src/main/scala/net/eagle0/eagle/library/util/province",
"//src/main/scala/net/eagle0/eagle/model/proto_converters:battalion_type_id_converter",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
"//src/main/scala/net/eagle0/eagle/model/state:battalion_type",
"//src/main/scala/net/eagle0/eagle/model/state:battalion_type_id",
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
],
)
@@ -396,6 +448,8 @@ scala_library(
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/library/settings:gold_per_hero_held_back",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
],
)
@@ -4,19 +4,19 @@ import scala.annotation.tailrec
import net.eagle0.eagle.api.command.util.appropriate_battalions.SuitableBattalions.SuitabilityLevel
import net.eagle0.eagle.api.command.util.appropriate_battalions.SuitableBattalions.SuitabilityLevel.RESTRICTIVE
import net.eagle0.eagle.common.battalion_type.BattalionType
import net.eagle0.eagle.common.combat_unit.CombatUnit
import net.eagle0.eagle.internal.battalion.Battalion
import net.eagle0.eagle.internal.hero.Hero
import net.eagle0.eagle.library.util.{BattalionSuitability, LegacyBattalionUtils}
import net.eagle0.eagle.library.util.hero.LegacyHeroUtils
import net.eagle0.eagle.library.util.{BattalionSuitability, BattalionUtils}
import net.eagle0.eagle.library.util.hero.HeroUtils
import net.eagle0.eagle.library.util.EagleRequire.internalRequire
import net.eagle0.eagle.model.state.battalion.BattalionT
import net.eagle0.eagle.model.state.hero.HeroT
import net.eagle0.eagle.model.state.BattalionType
object CombatUnitSelector {
def selectedCombatBattalions(
desiredCount: Int,
heroes: Vector[Hero],
battalions: Vector[Battalion],
heroes: Vector[HeroT],
battalions: Vector[BattalionT],
battalionTypes: Vector[BattalionType]
): Vector[CombatUnit] = {
val suitableBattalionsMap =
@@ -26,18 +26,18 @@ object CombatUnitSelector {
bts = battalionTypes
)
def suitability(h: Hero, b: Battalion): SuitabilityLevel =
def suitability(h: HeroT, b: BattalionT): SuitabilityLevel =
suitableBattalionsMap(h.id).battalionIdsWithSuitability
.find(_.battalionId == b.id)
.get
.level
def chooseOne(hs: Vector[Hero], bs: Vector[Battalion]): CombatUnit = {
def chooseOne(hs: Vector[HeroT], bs: Vector[BattalionT]): CombatUnit = {
val bestBattalionOpt = bs
.filter(b => hs.exists(h => suitability(h, b) != RESTRICTIVE))
.maxByOption(LegacyBattalionUtils.power)
.maxByOption(BattalionUtils.power)
bestBattalionOpt.map { bestBattalion =>
val bestHero = hs.minBy(h => (suitability(h, bestBattalion).index, -LegacyHeroUtils.power(h)))
val bestHero = hs.minBy(h => (suitability(h, bestBattalion).index, -HeroUtils.power(h)))
CombatUnit(
heroId = bestHero.id,
battalionId = Some(bestBattalion.id),
@@ -46,8 +46,8 @@ object CombatUnitSelector {
}
.getOrElse(
CombatUnit(
heroId = hs.maxBy(LegacyHeroUtils.power).id,
battalionId = bs.maxByOption(LegacyBattalionUtils.power).map(_.id),
heroId = hs.maxBy(HeroUtils.power).id,
battalionId = bs.maxByOption(BattalionUtils.power).map(_.id),
factionId = hs.head.factionId.get
)
)
@@ -56,8 +56,8 @@ object CombatUnitSelector {
@tailrec
def go(
c: Int,
hs: Vector[Hero],
bs: Vector[Battalion],
hs: Vector[HeroT],
bs: Vector[BattalionT],
acc: Vector[CombatUnit]
): Vector[CombatUnit] =
if c == 0 then acc
@@ -24,7 +24,7 @@ import net.eagle0.eagle.common.recruitment_info.RecruitmentStatus.{
import net.eagle0.eagle.common.tribute_amount.TributeAmount
import net.eagle0.eagle.common.unaffiliated_hero_type.UnaffiliatedHeroType.UNAFFILIATED_HERO_PRISONER
import net.eagle0.eagle.internal.battalion.Battalion
import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
import net.eagle0.eagle.internal.hero.Hero
import net.eagle0.eagle.internal.province.Province
import net.eagle0.eagle.internal.unaffiliated_hero.UnaffiliatedHero
@@ -41,24 +41,32 @@ import net.eagle0.eagle.library.settings.{
VassalMinimumFoodToSendSupplies,
VassalMinimumGoldToSendSupplies
}
import net.eagle0.eagle.library.util.{BattalionTypeFinder, CommandSelection, IncomingArmyUtils, ProvinceDistances}
import net.eagle0.eagle.library.util.{
BattalionTypeFinder,
BattalionUtils,
CommandSelection,
IncomingArmyUtils,
ProvinceDistances
}
import net.eagle0.eagle.library.util.command_choice_helpers.AvailableCommandSelector.randomSelectionForType
import net.eagle0.eagle.library.util.command_choice_helpers.CommandChooserImplicits.*
import net.eagle0.eagle.library.util.command_choice_helpers.CommandChooserProtoImplicits.*
import net.eagle0.eagle.library.util.command_choice_helpers.FoodConsumptionUtils.foodConsumptionMonthsToHold
import net.eagle0.eagle.library.util.command_choice_helpers.ProvinceGoldSurplusCalculator.{
goldToHoldBack,
provinceGoldSurplus
}
import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils
import net.eagle0.eagle.library.util.hero.LegacyHeroUtils
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
import net.eagle0.eagle.library.util.faction_utils.{FactionUtils, LegacyFactionUtils}
import net.eagle0.eagle.library.util.hero.{HeroUtils, LegacyHeroUtils}
import net.eagle0.eagle.library.util.province.{LegacyProvinceUtils, ProvinceUtils}
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
import net.eagle0.eagle.model.state.hero.{HeroT, Profession}
object CommandChoiceHelpers {
import AvailableCommandSelector.{flatSelectionForType, selectionForType}
def chosenUnderAttackCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] =
flatSelectionForType[OrganizeTroopsAvailableCommand](availableCommands) { organizeCommand =>
@@ -80,7 +88,7 @@ object CommandChoiceHelpers {
def excessBeyondMaximumSupplies(
actingProvinceId: ProvinceId,
gameState: GameState
gameState: GameStateProto
): Option[Supplies] = {
def battalionType(battalionTypeId: BattalionTypeId): BattalionType =
BattalionTypeFinder.battalionType(
@@ -149,7 +157,7 @@ object CommandChoiceHelpers {
private def chosenShipExcessSuppliesCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] =
flatSelectionForType[SendSuppliesAvailableCommand](availableCommands) { ac =>
@@ -166,7 +174,7 @@ object CommandChoiceHelpers {
// Commands that should be done first regardless of the province's orders.
def chosenUniversalCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] =
@@ -174,7 +182,7 @@ object CommandChoiceHelpers {
actingFactionId = actingFactionId,
gameState = gameState,
availableCommands = availableCommands,
rankedChoosers = Vector[CommandChooser](
rankedChoosers = Vector[CommandChooserProto](
chosenCommandWhileTraveling,
AttackDecisionCommandChooser.attackDecisionSelectedCommand,
(actingFactionId, gameState, availableCommands, functionalRandom) =>
@@ -231,7 +239,7 @@ object CommandChoiceHelpers {
def chosenDevelopCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] =
@@ -239,7 +247,7 @@ object CommandChoiceHelpers {
actingFactionId = actingFactionId,
gameState = gameState,
availableCommands = availableCommands,
rankedChoosers = Vector[CommandChooser](
rankedChoosers = Vector[CommandChooserProto](
chosenUniversalCommand,
(actingFactionId, gameState, availableCommands, functionalRandom) =>
RandomState(
@@ -275,7 +283,7 @@ object CommandChoiceHelpers {
def chosenMobilizeCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] =
@@ -283,7 +291,7 @@ object CommandChoiceHelpers {
actingFactionId = actingFactionId,
gameState = gameState,
availableCommands = availableCommands,
rankedChoosers = Vector[CommandChooser](
rankedChoosers = Vector[CommandChooserProto](
chosenUniversalCommand,
(actingFactionId, gameState, availableCommands, functionalRandom) =>
RandomState(
@@ -352,7 +360,7 @@ object CommandChoiceHelpers {
def chosenEntrustCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
actingProvinceId: ProvinceId,
functionalRandom: FunctionalRandom
@@ -361,11 +369,11 @@ object CommandChoiceHelpers {
actingFactionId = actingFactionId,
gameState = gameState,
availableCommands = availableCommands,
rankedChoosers = Vector[CommandChooser](
rankedChoosers = Vector[CommandChooserProto](
chosenUniversalCommand,
(
fid: FactionId,
gs: GameState,
gs: GameStateProto,
acs: Vector[AvailableCommand],
fr: FunctionalRandom
) =>
@@ -381,14 +389,14 @@ object CommandChoiceHelpers {
functionalRandom = functionalRandom
)
private def suppressBeastsValue(hero: Hero): Double =
(hero.strength + hero.agility) / 2.0 + (if hero.profession.isNoProfession
private def suppressBeastsValue(hero: HeroT): Double =
(hero.strength + hero.agility) / 2.0 + (if hero.profession == Profession.NoProfession
then 0.0
else 50.0)
def chosenSuppressBeastsCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] =
flatSelectionForType[SuppressBeastsAvailableCommand](availableCommands) { suppressAc =>
@@ -405,10 +413,11 @@ object CommandChoiceHelpers {
if province.battalionIds
.exists(b => gameState.battalions(b).size >= beastPower)
then {
val scalaGs = GameStateConverter.fromProto(gameState)
val leaderId = HeroSelector
.minimallyFatiguedHeroes(
suppressAc.availableHeroIds
.map(gameState.heroes)
.map(scalaGs.heroes)
.toVector
)
.maxBy(suppressBeastsValue(_))
@@ -441,7 +450,7 @@ object CommandChoiceHelpers {
private def chosenMobilizeIfAdjacentEnemyCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
actingProvinceId: ProvinceId,
functionalRandom: FunctionalRandom
@@ -460,7 +469,7 @@ object CommandChoiceHelpers {
def chosenExpandCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] =
@@ -468,12 +477,12 @@ object CommandChoiceHelpers {
actingFactionId = actingFactionId,
gameState = gameState,
availableCommands = availableCommands,
rankedChoosers = Vector[CommandChooser](
rankedChoosers = Vector[CommandChooserProto](
chosenUniversalCommand,
ExpandCommandSelector.maybeChosenExpandCommand,
(
fid: FactionId,
gs: GameState,
gs: GameStateProto,
acs: Vector[AvailableCommand],
fr: FunctionalRandom
) =>
@@ -512,7 +521,7 @@ object CommandChoiceHelpers {
private def chosenShouldRestCommand(
actingFactionId: FactionId,
gs: GameState,
gs: GameStateProto,
acs: Vector[AvailableCommand],
reason: String
): Option[CommandSelection] =
@@ -540,7 +549,7 @@ object CommandChoiceHelpers {
def chosenRestCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
reason: String
): Option[CommandSelection] =
@@ -556,7 +565,7 @@ object CommandChoiceHelpers {
def handleCapturedHeroesSelectedCommand(
actingFactionId: FactionId,
gs: GameState,
gs: GameStateProto,
acs: Vector[AvailableCommand],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] =
@@ -599,7 +608,7 @@ object CommandChoiceHelpers {
def resolvePleaseRecruitMeSelectedCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] =
selectionForType[PleaseRecruitMeAvailableCommand](availableCommands) { pleaseRecruitMeAc =>
@@ -618,7 +627,7 @@ object CommandChoiceHelpers {
private def handleRiotGiveSelectedCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] =
selectionForType[HandleRiotGiveAvailableCommand](availableCommands) { availableCommand =>
@@ -643,7 +652,7 @@ object CommandChoiceHelpers {
private def handleRiotCrackDownSelectedCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] =
selectionForType[HandleRiotCrackDownAvailableCommand](availableCommands) { availableCommand =>
@@ -663,7 +672,7 @@ object CommandChoiceHelpers {
private def handleRiotDoNothingSelectedCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] =
selectionForType[HandleRiotDoNothingAvailableCommand](availableCommands) { availableCommand =>
@@ -678,7 +687,7 @@ object CommandChoiceHelpers {
def handleRiotSelectedCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] =
@@ -686,7 +695,7 @@ object CommandChoiceHelpers {
actingFactionId = actingFactionId,
gameState = gameState,
availableCommands = availableCommands,
rankedChoosers = Vector[CommandChooser](
rankedChoosers = Vector[CommandChooserProto](
(actingFactionId, gameState, availableCommands, functionalRandom) =>
RandomState(
handleRiotGiveSelectedCommand(
@@ -720,7 +729,7 @@ object CommandChoiceHelpers {
def freeForAllDecisionSelectedCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] =
@@ -728,7 +737,7 @@ object CommandChoiceHelpers {
actingFactionId = actingFactionId,
gameState = gameState,
availableCommands = availableCommands,
rankedChoosers = Vector[CommandChooser](
rankedChoosers = Vector[CommandChooserProto](
(actingFactionId, gameState, availableCommands, functionalRandom) =>
RandomState(
advanceFFASelectedCommand(
@@ -753,7 +762,7 @@ object CommandChoiceHelpers {
private def withdrawFFASelectedCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] =
flatSelectionForType[FreeForAllDecisionAvailableCommand](
@@ -777,7 +786,7 @@ object CommandChoiceHelpers {
private def advanceFFASelectedCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] =
flatSelectionForType[FreeForAllDecisionAvailableCommand](
@@ -801,7 +810,7 @@ object CommandChoiceHelpers {
def resolveTributeSelectedCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] =
@@ -836,7 +845,7 @@ object CommandChoiceHelpers {
def defendSelectedCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] =
@@ -873,23 +882,25 @@ object CommandChoiceHelpers {
}
def defendingUnits(
gs: GameState,
gs: GameStateProto,
availableCommand: DefendAvailableCommand
): Vector[CombatUnit] =
): Vector[CombatUnit] = {
val scalaGs = GameStateConverter.fromProto(gs)
CombatUnitSelector.selectedCombatBattalions(
desiredCount = availableCommand.availableHeroIds.size
.min(MaxCombatUnitCountPerSide.intValue),
heroes = availableCommand.availableHeroIds.map(gs.heroes).toVector,
heroes = availableCommand.availableHeroIds.map(scalaGs.heroes).toVector,
battalions = availableCommand.availableBattalions
.map(_.battalionId)
.map(gs.battalions)
.map(scalaGs.battalions)
.toVector,
battalionTypes = gs.battalionTypes.toVector
battalionTypes = scalaGs.battalionTypes
)
}
def foodAmountToBuy(
province: Province,
gameState: GameState,
gameState: GameStateProto,
goldAvailable: Int,
foodBuyPrice: Double
): Option[Int] = {
@@ -910,7 +921,7 @@ object CommandChoiceHelpers {
def maybeChosenTravelToBuyFoodCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] =
flatSelectionForType[TravelAvailableCommand](availableCommands) { availableCommand =>
@@ -929,7 +940,7 @@ object CommandChoiceHelpers {
def chosenBuyFoodCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] =
flatSelectionForType[TradeAvailableCommand](availableCommands) { availableCommand =>
@@ -957,7 +968,7 @@ object CommandChoiceHelpers {
def chosenImproveInfrastructureIfBelowTrainingCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] =
flatSelectionForType[ImproveAvailableCommand](availableCommands) { availableCommand =>
@@ -988,7 +999,7 @@ object CommandChoiceHelpers {
}
}
def suppliesToSend(provinceId: ProvinceId, gameState: GameState): Supplies =
def suppliesToSend(provinceId: ProvinceId, gameState: GameStateProto): Supplies =
Supplies(
food = provinceFoodSurplus(provinceId, gameState),
gold = provinceGoldSurplus(provinceId, gameState)
@@ -996,7 +1007,7 @@ object CommandChoiceHelpers {
private def closestLeader(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
actingProvinceId: ProvinceId
): Option[(HeroId, ProvinceId)] =
gameState
@@ -1027,7 +1038,7 @@ object CommandChoiceHelpers {
def preferredSuppliesDestination(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
ac: SendSuppliesAvailableCommand
): Option[ProvinceId] =
gameState
@@ -1052,7 +1063,7 @@ object CommandChoiceHelpers {
def chosenSendSuppliesCommandWithSuppliesAndDestination(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
ac: SendSuppliesAvailableCommand,
supplies: Supplies,
destination: ProvinceId
@@ -1073,7 +1084,7 @@ object CommandChoiceHelpers {
def chosenSendSuppliesCommandWithSupplies(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
ac: SendSuppliesAvailableCommand,
supplies: Supplies
): Option[CommandSelection] =
@@ -1104,7 +1115,7 @@ object CommandChoiceHelpers {
private def lowLoyaltyHeroes(
provinceId: ProvinceId,
heroIds: Vector[HeroId],
gameState: GameState
gameState: GameStateProto
): Vector[Hero] =
heroIds
.filter(hid =>
@@ -1118,7 +1129,7 @@ object CommandChoiceHelpers {
def chosenFeastCommandIfAvailable(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
reason: String
): Option[CommandSelection] =
@@ -1145,7 +1156,7 @@ object CommandChoiceHelpers {
private def maybeChosenFeastCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
reason: String
): Option[CommandSelection] =
@@ -1163,7 +1174,7 @@ object CommandChoiceHelpers {
private def maybeChosenGiftCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
reason: String
): Option[CommandSelection] =
@@ -1188,7 +1199,7 @@ object CommandChoiceHelpers {
private def maybeSpreadIntoOwnedProvince(
opmc: MarchCommandFromOneProvince,
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
ac: MarchAvailableCommand
): Option[CommandSelection] = {
val originProvince = gameState.provinces(opmc.originProvinceId)
@@ -1242,7 +1253,7 @@ object CommandChoiceHelpers {
private def maybeSpreadIntoEmptyProvince(
opmc: MarchCommandFromOneProvince,
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
ac: MarchAvailableCommand,
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] = {
@@ -1308,7 +1319,7 @@ object CommandChoiceHelpers {
def maybeChosenGetUnderHeroCapCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] =
@@ -1347,13 +1358,13 @@ object CommandChoiceHelpers {
private def maybeChosenAttackToSaveLeaderCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] = None
def maybeRansomLeaderCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] = {
@@ -1419,7 +1430,7 @@ object CommandChoiceHelpers {
private def chosenRescueLeaderIfAllPrisonersCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] =
@@ -1445,7 +1456,7 @@ object CommandChoiceHelpers {
def chosenRescueLeaderCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] =
@@ -1454,7 +1465,7 @@ object CommandChoiceHelpers {
actingFactionId = actingFactionId,
gameState = gameState,
availableCommands = availableCommands,
rankedChoosers = Vector[CommandChooser](
rankedChoosers = Vector[CommandChooserProto](
(actingFactionId, gameState, availableCommands, functionalRandom) =>
RandomState(
maybeChosenAttackToSaveLeaderCommand(
@@ -1472,7 +1483,7 @@ object CommandChoiceHelpers {
private def chosenLoyaltyManagementCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] =
@@ -1481,7 +1492,7 @@ object CommandChoiceHelpers {
actingFactionId = actingFactionId,
gameState = gameState,
availableCommands = availableCommands,
rankedChoosers = Vector[CommandChooser](
rankedChoosers = Vector[CommandChooserProto](
maybeChosenGetUnderHeroCapCommand,
(actingFactionId, gameState, availableCommands, functionalRandom) =>
RandomState(
@@ -1510,7 +1521,7 @@ object CommandChoiceHelpers {
def maybeChosenSendSuppliesCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] =
flatSelectionForType[SendSuppliesAvailableCommand](availableCommands) { ac =>
@@ -1527,7 +1538,7 @@ object CommandChoiceHelpers {
def chosenCommandWhileTraveling(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] =
@@ -1536,7 +1547,7 @@ object CommandChoiceHelpers {
actingFactionId = actingFactionId,
gameState = gameState,
availableCommands = availableCommands,
rankedChoosers = Vector[CommandChooser](
rankedChoosers = Vector[CommandChooserProto](
(actingFactionId, gameState, availableCommands, functionalRandom) =>
RandomState(
maybeChosenRecruitCommand(
@@ -1590,7 +1601,7 @@ object CommandChoiceHelpers {
private def maybeChosenRecruitCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] =
flatSelectionForType[RecruitHeroesAvailableCommand](availableCommands) { availableCommand =>
@@ -1610,7 +1621,7 @@ object CommandChoiceHelpers {
private def maybeChosenDivineCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] =
flatSelectionForType[DivineAvailableCommand](availableCommands) { availableCommand =>
@@ -1637,7 +1648,7 @@ object CommandChoiceHelpers {
private def chosenReturnCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] =
selectionForType[ReturnAvailableCommand](availableCommands) { availableCommand =>
@@ -1652,7 +1663,7 @@ object CommandChoiceHelpers {
def maybeChosenTravelToArmTroopsCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] =
flatSelectionForType[TravelAvailableCommand](availableCommands) { availableCommand =>
@@ -1681,7 +1692,7 @@ object CommandChoiceHelpers {
def maybeMoveToRecruitCommand(
actingFactionId: FactionId,
gs: GameState,
gs: GameStateProto,
acs: Vector[AvailableCommand],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] =
@@ -1768,14 +1779,14 @@ object CommandChoiceHelpers {
private def isInterestingUH(uh: UnaffiliatedHero): Boolean =
uh.recruitmentInfo.get.status == RECRUITMENT_STATUS_WOULD_JOIN || uh.recruitmentInfo.get.status == RECRUITMENT_STATUS_NOT_DIVINED
private def hasInterestingUH(provinceId: ProvinceId, gs: GameState): Boolean =
private def hasInterestingUH(provinceId: ProvinceId, gs: GameStateProto): Boolean =
gs.provinces(provinceId)
.unaffiliatedHeroes
.exists(isInterestingUH)
def maybeChosenTravelToRecruitCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] =
flatSelectionForType[TravelAvailableCommand](availableCommands) { availableCommand =>
@@ -1810,7 +1821,7 @@ object CommandChoiceHelpers {
def chosenTrainCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] =
selectionForType[TrainAvailableCommand](availableCommands) { availableCommand =>
@@ -1827,7 +1838,7 @@ object CommandChoiceHelpers {
private def organizeForFightingBeastsCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
beastPower: Int
): Option[CommandSelection] =
@@ -1863,7 +1874,7 @@ object CommandChoiceHelpers {
private def maybeChosenArmTroopsCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
reason: String
): Option[CommandSelection] =
@@ -1913,7 +1924,7 @@ object CommandChoiceHelpers {
end if
}
private def provinceFoodSurplus(provinceId: ProvinceId, gs: GameState): Int =
private def provinceFoodSurplus(provinceId: ProvinceId, gs: GameStateProto): Int =
(LegacyProvinceUtils.absoluteFoodSurplus(
provinceId,
gs
@@ -1922,7 +1933,7 @@ object CommandChoiceHelpers {
private def destinationCloserTo(
actingFactionId: FactionId,
gs: GameState,
gs: GameStateProto,
actingProvinceId: ProvinceId,
availableProvinceIds: Vector[ProvinceId],
pid: ProvinceId
@@ -1943,7 +1954,7 @@ object CommandChoiceHelpers {
private def topUpBattalionToFightBeastsCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
organizeCommand: OrganizeTroopsAvailableCommand,
province: Province,
costs: Map[BattalionTypeId, Double],
@@ -2001,7 +2012,7 @@ object CommandChoiceHelpers {
private def newBattalionToFightBeastsCommand(
actingFactionId: FactionId,
gameState: GameState,
gameState: GameStateProto,
organizeCommand: AvailableCommand,
province: Province,
costs: Map[BattalionTypeId, Double],
@@ -4,8 +4,10 @@ import scala.annotation.tailrec
import net.eagle0.common.{FunctionalRandom, RandomState}
import net.eagle0.eagle.api.available_command.AvailableCommand
import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
import net.eagle0.eagle.library.util.CommandSelection
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.FactionId
@FunctionalInterface
@@ -35,6 +37,34 @@ object CommandChooser {
}
case _ => RandomState(None, functionalRandom)
}
// Proto-accepting overload for backward compatibility with files still using proto GameState
@tailrec
def choose(
actingFactionId: FactionId,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
rankedChoosers: Vector[CommandChooserProto],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] = rankedChoosers match {
case h +: t =>
h(actingFactionId, gameState, availableCommands, functionalRandom) match {
case rss @ RandomState(Some(_), _) => rss
case RandomState(None, nextFr) =>
choose(actingFactionId, gameState, availableCommands, t, nextFr)
}
case _ => RandomState(None, functionalRandom)
}
}
@FunctionalInterface
trait CommandChooserProto {
def apply(
factionId: FactionId,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]]
}
object CommandChooserImplicits {
@@ -74,3 +104,41 @@ object CommandChooserImplicits {
randomChooser(factionId, gameState, availableCommands, functionalRandom)
}
}
object CommandChooserProtoImplicits {
implicit class DeterministicCommandChooserProto(
deterministicChooser: (
FactionId,
GameStateProto,
Vector[AvailableCommand]
) => Option[CommandSelection]
) extends CommandChooserProto {
override def apply(
factionId: FactionId,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] =
RandomState(
deterministicChooser(factionId, gameState, availableCommands),
functionalRandom
)
}
implicit class RandomCommandChooserProto(
randomChooser: (
FactionId,
GameStateProto,
Vector[AvailableCommand],
FunctionalRandom
) => RandomState[Option[CommandSelection]]
) extends CommandChooserProto {
override def apply(
factionId: FactionId,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] =
randomChooser(factionId, gameState, availableCommands, functionalRandom)
}
}
@@ -7,15 +7,17 @@ import net.eagle0.eagle.{FactionId, HeroId, ProvinceId}
import net.eagle0.eagle.api.available_command.{AvailableCommand, MarchAvailableCommand, MarchCommandFromOneProvince}
import net.eagle0.eagle.api.selected_command.MarchSelectedCommand
import net.eagle0.eagle.common.combat_unit.CombatUnit
import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.province.Province
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
import net.eagle0.eagle.library.settings.MaxCombatUnitCountPerSide
import net.eagle0.eagle.library.util.{CommandSelection, LegacyBattalionUtils}
import net.eagle0.eagle.library.util.{BattalionUtils, CommandSelection}
import net.eagle0.eagle.library.util.command_choice_helpers.AvailableCommandSelector.randomSelectionForType
import net.eagle0.eagle.library.util.command_choice_helpers.ProvinceGoldSurplusCalculator.provinceGoldSurplus
import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
import net.eagle0.eagle.library.util.faction_utils.FactionUtils
import net.eagle0.eagle.library.util.province.ProvinceUtils
import net.eagle0.eagle.library.util.EagleRequire.internalRequire
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.model.state.province.ProvinceT
object ExpandCommandSelector {
private case class MarchUnitsWithFood(units: Vector[CombatUnit], food: Int)
@@ -37,7 +39,7 @@ object ExpandCommandSelector {
if (rulingFactionHeroIds.size < 2) // Don't abandon this province
|| (rulingFactionHeroIds.size - rulingFactionHeroIds.count(
LegacyFactionUtils.isFactionLeader(_, gameState)
FactionUtils.isFactionLeader(_, gameState.factions.values)
) < 2) // Don't leave a faction leader alone
then randomNone
else
@@ -52,7 +54,7 @@ object ExpandCommandSelector {
.map(_.provinceId)
.toVector
).partition(p =>
LegacyProvinceUtils
ProvinceUtils
.isAdjacentEnemy(p.id, actingFactionId, gameState)
)
@@ -90,20 +92,16 @@ object ExpandCommandSelector {
gameState: GameState,
fromProvince: ProvinceId,
eligibleDestinations: Vector[ProvinceId]
): Vector[Province] =
): Vector[ProvinceT] =
eligibleDestinations
.map(gameState.provinces)
.filterNot(_.rulingFactionId.isDefined)
.filterNot(
_.incomingArmies.exists(ma =>
ma.army
.map(_.factionId)
.contains(
gameState
.provinces(fromProvince)
.rulingFactionId
.get
)
gameState
.provinces(fromProvince)
.rulingFactionId
.contains(ma.army.factionId)
)
)
@@ -113,7 +111,7 @@ object ExpandCommandSelector {
gameState: GameState,
availableCommand: MarchAvailableCommand,
oneProvinceCommand: MarchCommandFromOneProvince,
safeExpansionCandidateProvinces: Vector[Province],
safeExpansionCandidateProvinces: Vector[ProvinceT],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] = {
val province = gameState.provinces(availableCommand.actingProvinceId)
@@ -157,7 +155,7 @@ object ExpandCommandSelector {
gameState: GameState,
availableCommand: MarchAvailableCommand,
oneProvinceCommand: MarchCommandFromOneProvince,
adjacentEnemyCandidateProvinces: Vector[Province],
adjacentEnemyCandidateProvinces: Vector[ProvinceT],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] = {
val province = gameState.provinces(availableCommand.actingProvinceId)
@@ -175,7 +173,7 @@ object ExpandCommandSelector {
else {
// If the originating province is safe, only leave two heroes behind
val heroCountToSend = (
if LegacyProvinceUtils.isAdjacentEnemy(
if ProvinceUtils.isAdjacentEnemy(
oneProvinceCommand.originProvinceId,
actingFactionId,
gameState
@@ -251,8 +249,8 @@ object ExpandCommandSelector {
actingFactionId: FactionId,
heroCountToSend: Int,
goldToSend: Int,
originatingProvince: Province,
candidateDestinationProvinces: Vector[Province],
originatingProvince: ProvinceT,
candidateDestinationProvinces: Vector[ProvinceT],
oneProvinceCommand: MarchCommandFromOneProvince,
availableCommand: MarchAvailableCommand,
gameState: GameState,
@@ -268,8 +266,8 @@ object ExpandCommandSelector {
rulingFactionHeroIds.diff(oneProvinceCommand.availableHeroIds)
// Don't leave a faction leader alone in the origin province
if heroCountLeftBehind == 1 && notAvailableHeroes.size == 1 && LegacyFactionUtils
.isFactionLeader(notAvailableHeroes.head, gameState)
if heroCountLeftBehind == 1 && notAvailableHeroes.size == 1 && FactionUtils
.isFactionLeader(notAvailableHeroes.head, gameState.factions.values)
then {
return randomNone
}
@@ -297,9 +295,8 @@ object ExpandCommandSelector {
fid = actingFactionId,
factionLeaders = gameState
.factions(actingFactionId)
.leaders
.intersect(originatingProvince.rulingFactionHeroIds)
.toVector,
.leaderIds
.intersect(originatingProvince.rulingFactionHeroIds),
otherCountLeftBehind =
originatingProvince.rulingFactionHeroIds.length - oneProvinceCommand.availableHeroIds.length,
functionalRandom = innerFr
@@ -310,19 +307,19 @@ object ExpandCommandSelector {
rulingFactionHeroIds.filterNot(takenHeroes.contains)
internalRequire(
takenHeroes.length > 1 || !LegacyFactionUtils
takenHeroes.length > 1 || !FactionUtils
.isFactionLeader(
heroId = takenHeroes.head,
gameState = gameState
factions = gameState.factions.values
),
"trying to take only a faction leader!"
)
internalRequire(
leftHeroes.length > 1 || !LegacyFactionUtils
leftHeroes.length > 1 || !FactionUtils
.isFactionLeader(
heroId = leftHeroes.head,
gameState = gameState
factions = gameState.factions.values
),
"trying to leave a faction leader alone!"
)
@@ -335,9 +332,9 @@ object ExpandCommandSelector {
)
.map(gameState.battalions)
val monthlyFoodConsumptionInOrigin =
LegacyBattalionUtils.monthlyConsumedFood(
BattalionUtils.monthlyConsumedFood(
battalionsHeldBack,
gameState.battalionTypes.toVector
gameState.battalionTypes
)
val foodToHoldBack = monthlyFoodConsumptionInOrigin *
FoodConsumptionUtils.foodConsumptionMonthsToHold(
@@ -424,7 +421,7 @@ object ExpandCommandSelector {
heroCount = heroCountToSend,
battalionCount = battalionCountToSend,
fid = actingFactionId,
factionLeaders = gameState.factions(actingFactionId).leaders.toVector,
factionLeaders = gameState.factions(actingFactionId).leaderIds,
otherCountLeftBehind = originProvince.rulingFactionHeroIds.length - opmc.availableHeroIds.length,
functionalRandom = functionalRandom
).map { marchingUnitsWithFood =>
@@ -451,7 +448,7 @@ object ExpandCommandSelector {
gameState: GameState,
fromProvince: ProvinceId,
eligibleDestinations: Vector[ProvinceId]
): Vector[Province] = {
): Vector[ProvinceT] = {
val p = gameState.provinces(fromProvince)
eligibleDestinations
.map(gameState.provinces)
@@ -533,4 +530,29 @@ object ExpandCommandSelector {
end if
}
}
// Proto-accepting overloads for backward compatibility
def expansionCandidateProvinces(
gameState: GameStateProto,
fromProvince: ProvinceId,
eligibleDestinations: Vector[ProvinceId]
): Vector[ProvinceT] =
expansionCandidateProvinces(
GameStateConverter.fromProto(gameState),
fromProvince,
eligibleDestinations
)
def maybeChosenExpandCommand(
actingFactionId: FactionId,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] =
maybeChosenExpandCommand(
actingFactionId,
GameStateConverter.fromProto(gameState),
availableCommands,
functionalRandom
)
}
@@ -1,10 +1,11 @@
package net.eagle0.eagle.library.util.command_choice_helpers
import net.eagle0.eagle.common.date.Date
import net.eagle0.eagle.common.round_phase.RoundPhase
import net.eagle0.eagle.common.round_phase.RoundPhase.FOOD_CONSUMPTION
import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
import net.eagle0.eagle.library.settings.MinSupportForTaxes
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
import net.eagle0.eagle.model.state.date.Date
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.model.state.RoundPhase
import net.eagle0.eagle.ProvinceId
object FoodConsumptionUtils {
@@ -12,8 +13,8 @@ object FoodConsumptionUtils {
date: Date,
roundPhase: RoundPhase
): Int =
if roundPhase.value < FOOD_CONSUMPTION.value then 13 - date.month
else 12 - date.month
if roundPhase.value < RoundPhase.FoodConsumption.value then 13 - date.month.value
else 12 - date.month.value
private def foodConsumptionMonthsRemainingInYear(gameState: GameState): Int =
foodConsumptionMonthsRemainingInYear(
@@ -28,4 +29,10 @@ object FoodConsumptionUtils {
if gameState.provinces(provinceId).support >= MinSupportForTaxes.doubleValue
then foodConsumptionMonthsRemainingInYear(gameState)
else foodConsumptionMonthsRemainingInYear(gameState).max(6)
def foodConsumptionMonthsToHold(
provinceId: ProvinceId,
gameState: GameStateProto
): Int =
foodConsumptionMonthsToHold(provinceId, GameStateConverter.fromProto(gameState))
}
@@ -1,12 +1,12 @@
package net.eagle0.eagle.library.util.command_choice_helpers
import net.eagle0.eagle.internal.hero.Hero
import net.eagle0.eagle.library.util.hero.LegacyHeroUtils
import net.eagle0.eagle.library.util.hero.HeroUtils
import net.eagle0.eagle.model.state.hero.HeroT
object HeroSelector {
def minimallyFatiguedHeroes(heroes: Vector[Hero]): Vector[Hero] = {
val minimumFatigue = heroes.map(LegacyHeroUtils.fatigue).min
def minimallyFatiguedHeroes(heroes: Vector[HeroT]): Vector[HeroT] = {
val minimumFatigue = heroes.map(HeroUtils.fatigue).min
heroes
.filter(LegacyHeroUtils.fatigue(_) == minimumFatigue)
.filter(HeroUtils.fatigue(_) == minimumFatigue)
}
}
@@ -5,21 +5,23 @@ import net.eagle0.eagle.api.available_command.{AvailableCommand, ImproveAvailabl
import net.eagle0.eagle.api.selected_command.ImproveSelectedCommand
import net.eagle0.eagle.common.improvement_type.ImprovementType
import net.eagle0.eagle.common.improvement_type.ImprovementType.DEVASTATION
import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.province.Province
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
import net.eagle0.eagle.library.util.command_choice_helpers.AvailableCommandSelector.{
flatSelectionForType,
selectionForType
}
import net.eagle0.eagle.library.util.hero.LegacyHeroUtils
import net.eagle0.eagle.library.util.hero.HeroUtils
import net.eagle0.eagle.library.util.CommandSelection
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.model.state.province.ProvinceT
object ImproveCommandSelector {
import net.eagle0.eagle.library.util.command_choice_helpers.HeroSelector.minimallyFatiguedHeroes
private def provinceValueForType(
improvementType: ImprovementType,
province: Province
province: ProvinceT
): Double = improvementType match {
case ImprovementType.ECONOMY => province.economy
case ImprovementType.AGRICULTURE => province.agriculture
@@ -31,7 +33,7 @@ object ImproveCommandSelector {
private def chosenType(
availableTypes: Vector[ImprovementType],
province: Province
province: ProvinceT
): ImprovementType =
if availableTypes.contains(DEVASTATION) then DEVASTATION
else availableTypes.minBy(provinceValueForType(_, province))
@@ -80,7 +82,7 @@ object ImproveCommandSelector {
improveCommand.availableHeroIds.toVector.map(
gameState.heroes(_)
)
).maxBy(LegacyHeroUtils.power(_)).id
).maxBy(HeroUtils.power(_)).id
),
reason = "chosenSpecificImprovementCommand"
)
@@ -105,7 +107,7 @@ object ImproveCommandSelector {
improveAvailableCommand.availableHeroIds.toVector.map(
gameState.heroes(_)
)
).maxBy(LegacyHeroUtils.power(_)).id
).maxBy(HeroUtils.power(_)).id
),
reason = "ImproveCommandSelector.commandSelection"
)
@@ -118,4 +120,29 @@ object ImproveCommandSelector {
selectionForType[ImproveAvailableCommand](availableCommands) { improveCommand =>
commandSelection(gameState, actingFactionId, improveCommand)
}
// Proto-accepting overloads for backward compatibility
def chosenImproveCommand(
actingFactionId: FactionId,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] =
chosenImproveCommand(
actingFactionId,
GameStateConverter.fromProto(gameState),
availableCommands
)
def chosenSpecificImprovementCommand(
actingFactionId: FactionId,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
improvementType: ImprovementType
): Option[CommandSelection] =
chosenSpecificImprovementCommand(
actingFactionId,
GameStateConverter.fromProto(gameState),
availableCommands,
improvementType
)
}
@@ -1,14 +1,12 @@
package net.eagle0.eagle.library.util.command_choice_helpers
import net.eagle0.eagle.internal.battalion.Battalion
object MarchSuppliesHelpers {
def goldToTakeOnAttack(unitCount: Int, goldAvailable: Int): Int =
goldAvailable.min(200 * unitCount)
def foodToTakeOnAttack(
battalions: Vector[Battalion],
battalionCount: Int,
foodAvailable: Int
): Int =
foodAvailable.min(300 * battalions.size)
foodAvailable.min(300 * battalionCount)
}
@@ -5,14 +5,17 @@ import scala.annotation.tailrec
import net.eagle0.eagle.api.available_command.{AvailableCommand, OrganizeTroopsAvailableCommand}
import net.eagle0.eagle.api.selected_command.OrganizeTroopsSelectedCommand
import net.eagle0.eagle.api.selected_command.OrganizeTroopsSelectedCommand.{ChangedBattalion, NewBattalion}
import net.eagle0.eagle.common.battalion_type.{BattalionType, BattalionTypeId}
import net.eagle0.eagle.internal.battalion.Battalion
import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
import net.eagle0.eagle.library.util.{BattalionTypeFinder, CommandSelection}
import net.eagle0.eagle.library.util.command_choice_helpers.AvailableCommandSelector.flatSelectionForType
import net.eagle0.eagle.library.util.command_choice_helpers.ProvinceGoldSurplusCalculator.provinceGoldSurplus
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
import net.eagle0.eagle.library.util.province.ProvinceUtils
import net.eagle0.eagle.library.util.BattalionTypeFinder.battalionType
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
import net.eagle0.eagle.model.proto_converters.BattalionTypeIdConverter
import net.eagle0.eagle.model.state.{BattalionType, BattalionTypeId}
import net.eagle0.eagle.model.state.battalion.BattalionT
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.FactionId
object OrganizeCommandSelector {
@@ -32,7 +35,7 @@ object OrganizeCommandSelector {
gold: Int,
foodSurplus: Int,
costs: Map[BattalionTypeId, Double],
existingBattalions: Vector[Battalion],
existingBattalions: Vector[BattalionT],
battalionTypes: Vector[BattalionType],
eligibleBattalionTypeIds: Vector[BattalionTypeId]
): ChangedBattalionsResult = {
@@ -40,17 +43,19 @@ object OrganizeCommandSelector {
def go(
gold: Int,
foodSurplus: Int,
battalionsInNeed: Vector[Battalion],
battalionsInNeed: Vector[BattalionT],
acc: Vector[ChangedBattalion]
): ChangedBattalionsResult = battalionsInNeed match {
case b +: bs =>
val missing = battalionType(b.`type`, battalionTypes).capacity - b.size
val goldCost = (missing * costs(b.`type`)).ceil.toInt
val missing = BattalionTypeFinder.findBattalionType(b.typeId, battalionTypes).capacity - b.size
val goldCost = (missing * costs(b.typeId)).ceil.toInt
val foodCost =
(missing * battalionType(
b.`type`,
battalionTypes
).monthlyFoodCost).ceil.toInt
(missing * BattalionTypeFinder
.findBattalionType(
b.typeId,
battalionTypes
)
.monthlyFoodCost).ceil.toInt
if goldCost <= gold && foodCost <= foodSurplus then
go(
gold - goldCost,
@@ -77,9 +82,9 @@ object OrganizeCommandSelector {
gold = gold,
foodSurplus = foodSurplus,
battalionsInNeed = existingBattalions
.filter(b => eligibleBattalionTypeIds.contains(b.`type`))
.filter(b => costs.contains(b.`type`))
.filter(b => b.size < battalionType(b.`type`, battalionTypes).capacity),
.filter(b => eligibleBattalionTypeIds.contains(b.typeId))
.filter(b => costs.contains(b.typeId))
.filter(b => b.size < BattalionTypeFinder.findBattalionType(b.typeId, battalionTypes).capacity),
acc = Vector()
)
}
@@ -90,7 +95,7 @@ object OrganizeCommandSelector {
costs: Map[BattalionTypeId, Double],
allBattalionTypes: Vector[BattalionType],
eligibleBattalionTypeIds: Vector[BattalionTypeId],
existingBattalions: Vector[Battalion],
existingBattalions: Vector[BattalionT],
maxCount: Int = 100
): NewBattalionsResult = {
val eligibleCosts = costs.filter {
@@ -120,13 +125,13 @@ object OrganizeCommandSelector {
eligibleCosts.toVector.map {
case (tid, costPerTroop) =>
val battalionType =
BattalionTypeFinder.battalionType(tid, allBattalionTypes)
val cap = battalionType.capacity
val bt =
BattalionTypeFinder.findBattalionType(tid, allBattalionTypes)
val cap = bt.capacity
FoodAndGoldCosts(
battalionTypeId = tid,
goldCost = (costPerTroop * cap).ceil.toInt,
monthlyFoodCost = (cap * battalionType.monthlyFoodCost).ceil.toInt
monthlyFoodCost = (cap * bt.monthlyFoodCost).ceil.toInt
)
}
.filter(_.goldCost <= gold)
@@ -142,8 +147,8 @@ object OrganizeCommandSelector {
.headOption match {
case Some(x) =>
val newBattalion = NewBattalion(
`type` = x.battalionTypeId,
newTroops = battalionType(x.battalionTypeId, allBattalionTypes).capacity
`type` = BattalionTypeIdConverter.toProto(x.battalionTypeId),
newTroops = BattalionTypeFinder.findBattalionType(x.battalionTypeId, allBattalionTypes).capacity
)
go(
gold = gold - x.goldCost,
@@ -166,7 +171,7 @@ object OrganizeCommandSelector {
gold = gold,
foodSurplus = foodSurplus,
acc = Vector(),
existingBattalionTypeCounts = existingBattalions.groupBy(_.`type`).map { case (k, v) => k -> v.size }
existingBattalionTypeCounts = existingBattalions.groupBy(_.typeId).map { case (k, v) => k -> v.size }
)
}
@@ -178,7 +183,7 @@ object OrganizeCommandSelector {
reason: String
): Option[CommandSelection] = {
val foodSurplus =
LegacyProvinceUtils.monthlyFoodSurplus(
ProvinceUtils.monthlyFoodSurplus(
gameState = gameState,
provinceId = availableCommand.actingProvinceId
) + extraFoodSurplus
@@ -187,7 +192,7 @@ object OrganizeCommandSelector {
if foodSurplus <= 0 || goldSurplus <= 0 then None
else {
val costs = availableCommand.troopCosts
.map(tc => tc.`type` -> tc.costPerTroop)
.map(tc => BattalionTypeId.fromInt(tc.`type`.value) -> tc.costPerTroop)
.toMap
val toppedBattalionResults = changedBattalions(
@@ -199,11 +204,11 @@ object OrganizeCommandSelector {
.battalionIds
.toVector
.map(gameState.battalions(_)),
battalionTypes = gameState.battalionTypes.toVector,
battalionTypes = gameState.battalionTypes,
eligibleBattalionTypeIds = availableCommand.availableBattalionTypes
.filter(_.meetsRequirements)
.toVector
.map(_.typeId)
.map(tid => BattalionTypeId.fromInt(tid.typeId.value))
)
val newBattalionResults = newBattalions(
@@ -217,14 +222,14 @@ object OrganizeCommandSelector {
.provinces(availableCommand.actingProvinceId)
.battalionIds
.size,
allBattalionTypes = gameState.battalionTypes.toVector.filter(bt =>
allBattalionTypes = gameState.battalionTypes.filter(bt =>
availableCommand.availableBattalionTypes
.exists(tp => tp.typeId == bt.typeId && tp.meetsRequirements)
.exists(tp => tp.typeId.value == bt.typeId.value && tp.meetsRequirements)
),
eligibleBattalionTypeIds = availableCommand.availableBattalionTypes
.filter(_.meetsRequirements)
.toVector
.map(_.typeId),
.map(tid => BattalionTypeId.fromInt(tid.typeId.value)),
existingBattalions = gameState
.provinces(availableCommand.actingProvinceId)
.battalionIds
@@ -279,4 +284,33 @@ object OrganizeCommandSelector {
extraFoodSurplus = 0,
reason = reason
)
// Proto-accepting overloads for backward compatibility
def selectedOrganizeCommand(
actingFactionId: FactionId,
availableCommand: OrganizeTroopsAvailableCommand,
gameState: GameStateProto,
extraFoodSurplus: Int,
reason: String
): Option[CommandSelection] =
selectedOrganizeCommand(
actingFactionId,
availableCommand,
GameStateConverter.fromProto(gameState),
extraFoodSurplus,
reason
)
def chosenOrganizeCommand(
actingFactionId: FactionId,
gameState: GameStateProto,
availableCommands: Vector[AvailableCommand],
reason: String
): Option[CommandSelection] =
chosenOrganizeCommand(
actingFactionId,
GameStateConverter.fromProto(gameState),
availableCommands,
reason
)
}
@@ -1,7 +1,9 @@
package net.eagle0.eagle.library.util.command_choice_helpers
import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
import net.eagle0.eagle.library.settings.GoldPerHeroHeldBack
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.ProvinceId
object ProvinceGoldSurplusCalculator {
@@ -15,6 +17,12 @@ object ProvinceGoldSurplusCalculator {
.rulingFactionHeroIds
.length).ceil.toInt
def goldToHoldBack(
provinceId: ProvinceId,
gameState: GameStateProto
): Int =
goldToHoldBack(provinceId, GameStateConverter.fromProto(gameState))
def provinceGoldSurplus(
provinceId: ProvinceId,
gameState: GameState
@@ -23,4 +31,10 @@ object ProvinceGoldSurplusCalculator {
provinceId,
gameState
)).max(0)
def provinceGoldSurplus(
provinceId: ProvinceId,
gameState: GameStateProto
): Int =
provinceGoldSurplus(provinceId, GameStateConverter.fromProto(gameState))
}
@@ -1,14 +1,14 @@
package net.eagle0.eagle.library.util.faction_utils
import net.eagle0.eagle.{FactionId, HeroId}
import net.eagle0.eagle.{FactionId, HeroId, ProvinceId}
import net.eagle0.eagle.library.settings.{MinSupportForTaxes, PrestigePerSupportedProvince}
import net.eagle0.eagle.model.state.faction.{FactionRelationship, FactionT}
import net.eagle0.eagle.model.state.faction.FactionRelationship.RelationshipLevel.Ally
import net.eagle0.eagle.model.state.province.ProvinceT
object FactionUtils {
def isFactionLeader(heroId: HeroId, factions: Vector[FactionT]): Boolean =
factions.flatMap(_.leaderIds).contains(heroId)
def isFactionLeader(heroId: HeroId, factions: Iterable[FactionT]): Boolean =
factions.flatMap(_.leaderIds).exists(_ == heroId)
def factionRelationship(
by: FactionId,
@@ -143,4 +143,38 @@ object FactionUtils {
allProvinces
.filter(_.rulingFactionId.contains(factionId))
.count(_.support >= MinSupportForTaxes.doubleValue)
def isFactionHead(heroId: HeroId, factions: Iterable[FactionT]): Boolean =
factions.exists(_.factionHeadId == heroId)
def leadersBesidesHead(factionId: FactionId, factions: Iterable[FactionT]): Vector[HeroId] =
factions
.find(_.id == factionId)
.map(f => f.leaderIds.filterNot(_ == f.factionHeadId))
.getOrElse(Vector.empty)
def factionHeadId(factionId: FactionId, factions: Iterable[FactionT]): Option[HeroId] =
factions.find(_.id == factionId).map(_.factionHeadId)
def ownedNeighbors(
provinces: ProvinceId => ProvinceT,
fid: FactionId
): ProvinceId => Vector[ProvinceId] =
pid =>
provinces(pid).neighbors
.map(_.provinceId)
.filter(n => provinces(n).rulingFactionId.contains(fid))
def hostileNeighbors(
province: ProvinceT,
fid: FactionId,
factions: Vector[FactionT],
provinces: ProvinceId => ProvinceT
): Vector[ProvinceT] =
province.neighbors
.map(_.provinceId)
.map(provinces)
.filter(_.rulingFactionId.isDefined)
.filterNot(_.rulingFactionId.contains(fid))
.filterNot(neighbor => hasTruceOrAlliance(fid, neighbor.rulingFactionId.get, factions))
}
@@ -14,8 +14,10 @@ scala_library(
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils",
"//src/main/scala/net/eagle0/eagle/model/state/faction",
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/hero",
"//src/main/scala/net/eagle0/eagle/model/state/hero:gender",
"//src/main/scala/net/eagle0/eagle/model/state/province",
],
)
@@ -2,11 +2,13 @@ package net.eagle0.eagle.library.util.hero
import scala.annotation.tailrec
import net.eagle0.eagle.{FactionId, HeroId, ProvinceId}
import net.eagle0.eagle.library.util.faction_utils.FactionUtils
import net.eagle0.eagle.model.state.faction.FactionT
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.model.state.hero.HeroT
import net.eagle0.eagle.model.state.hero.Profession.NoProfession
import net.eagle0.eagle.HeroId
import net.eagle0.eagle.model.state.province.ProvinceT
object HeroUtils {
@@ -81,4 +83,39 @@ object HeroUtils {
def sortOrdering(factionLeaderIds: Vector[HeroId]): Ordering[HeroT] =
Ordering.fromLessThan(sortOrderer(factionLeaderIds))
def fatigue(hero: HeroT): Double = hero.constitution - hero.vigor
def seniorityOrder(faction: FactionT, heroId: HeroId): Int =
faction.leaderIds.indexOf(heroId) match {
case -1 => Int.MaxValue
case idx => idx
}
// For commands where the faction leader can act on other provinces; returns the list of provinces to consider
// for this hero. For a vassal, considers just the one province, but for faction leader returns all owned provinces
// that did not contain other faction leaders. Always returns the provinceId as the first element.
def consideredProvinces(
gameState: GameState,
factionId: FactionId,
provinceId: ProvinceId
): Vector[ProvinceT] =
if gameState
.provinces(provinceId)
.rulingHeroId
.exists(hid => FactionUtils.isFactionHead(hid, gameState.factions.values))
then
gameState.provinces.values
.filter(_.rulingFactionId.contains(factionId))
.filterNot(p =>
p.rulingFactionHeroIds.exists(
FactionUtils.leadersBesidesHead(factionId, gameState.factions.values).contains
) && !p.rulingFactionHeroIds
.exists(
FactionUtils.factionHeadId(factionId, gameState.factions.values).contains
)
)
.toVector
.sortBy(p => if p.id == provinceId then 0 else p.id)
else Vector(gameState.provinces(provinceId))
}
@@ -1,6 +1,6 @@
package net.eagle0.eagle.library.util.province
import net.eagle0.eagle.{FactionId, HeroId, RoundId}
import net.eagle0.eagle.{FactionId, HeroId, ProvinceId, RoundId}
import net.eagle0.eagle.common.battalion_type.BattalionType as BattalionTypeProto
import net.eagle0.eagle.library.settings.{
BaseResourceLimit,
@@ -258,6 +258,72 @@ object ProvinceUtils {
province.rulingFactionId.nonEmpty && province.rulingFactionHeroIds.isEmpty
)(abandonedProvinceCP(province))
def isAdjacentEnemy(
provinceId: ProvinceId,
factionId: FactionId,
gameState: GameState
): Boolean =
gameState
.provinces(provinceId)
.neighbors
.map(_.provinceId)
.map(gameState.provinces)
.exists(
_.rulingFactionId.exists(_ != factionId)
)
def adjacentHostiles(
provinceId: ProvinceId,
gameState: GameState
): Vector[ProvinceT] = {
val baseProvince = gameState.provinces(provinceId)
baseProvince.neighbors
.map(_.provinceId)
.map(gameState.provinces)
.filter(_.rulingFactionId.isDefined)
.filter(_.rulingFactionId != baseProvince.rulingFactionId)
}
def locationOf(
heroId: HeroId,
provinces: Iterable[ProvinceT]
): Option[ProvinceId] =
provinces
.find(p =>
(p.rulingFactionHeroIds ++ p.unaffiliatedHeroes.map(_.heroId))
.contains(heroId)
)
.map(_.id)
def absoluteFoodSurplus(provinceId: ProvinceId, gameState: GameState): Int = {
val province = gameState.provinces(provinceId)
val monthlyConsumption = monthlyFoodConsumption(province, gameState)
val annualProduction = annualFoodProduction(province).getOrElse(0)
if annualProduction / 12 < monthlyConsumption then 0
else {
val currentMonth = gameState.currentDate.map(_.month.value).getOrElse(1)
(province.food - monthlyConsumption * (12 - currentMonth)).max(0).toInt
}
}
def containsFactionLeader(pid: ProvinceId, gs: GameState): Boolean =
gs.provinces(pid).rulingFactionId.exists { fid =>
gs.provinces(pid)
.rulingFactionHeroIds
.contains(gs.factions(fid).factionHeadId)
}
def monthlyFoodSurplus(gameState: GameState, provinceId: ProvinceId): Int = {
val province = gameState.provinces(provinceId)
val consumption = monthlyFoodConsumption(province, gameState)
val monthsRemaining = 12 - gameState.currentDate.get.month.value
val monthlyAvailable =
if monthsRemaining == 0 then Int.MaxValue
else (province.food / monthsRemaining).toInt
val monthlyProduction = annualFoodProduction(province).getOrElse(0) / 12
monthlyProduction.min(monthlyAvailable) - consumption
}
def afterHeroDeparture(
cp: ChangedProvinceT,
province: ProvinceT
@@ -106,6 +106,7 @@ scala_library(
visibility = [
"//src/main/scala/net/eagle0/eagle/library:__pkg__",
"//src/main/scala/net/eagle0/eagle/library/actions/impl:__subpackages__",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:__pkg__",
"//src/main/scala/net/eagle0/eagle/library/util/view_filters:__pkg__",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/province:__pkg__",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/view/battalion:__pkg__",
@@ -127,6 +128,7 @@ scala_library(
srcs = ["BattalionTypeConverter.scala"],
visibility = [
"//src/main/scala/net/eagle0/eagle/library:__pkg__",
"//src/main/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
"//src/main/scala/net/eagle0/eagle/library/actions/impl:__subpackages__",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state:__pkg__",
"//src/test/scala/net/eagle0/eagle/library/actions/impl:__subpackages__",
@@ -9,6 +9,7 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/actions/applier:__pkg__",
"//src/main/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
"//src/main/scala/net/eagle0/eagle/library/actions/impl:__subpackages__",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:__pkg__",
"//src/main/scala/net/eagle0/eagle/model/proto_converters:__subpackages__",
"//src/main/scala/net/eagle0/eagle/service:__subpackages__",
"//src/test/scala/net/eagle0/eagle/library:__pkg__",
@@ -7,6 +7,7 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library:__pkg__",
"//src/main/scala/net/eagle0/eagle/library/actions/impl:__subpackages__",
"//src/main/scala/net/eagle0/eagle/library/util:__pkg__",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:__pkg__",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state:__pkg__",
"//src/test/scala/net/eagle0/eagle/library:__subpackages__",
"//src/test/scala/net/eagle0/eagle/library/actions/impl:__subpackages__",
@@ -7,6 +7,7 @@ scala_library(
":__subpackages__",
"//src/main/scala/net/eagle0/eagle/library/actions:__subpackages__",
"//src/main/scala/net/eagle0/eagle/library/util:__pkg__",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:__pkg__",
"//src/main/scala/net/eagle0/eagle/library/util/province:__pkg__",
"//src/main/scala/net/eagle0/eagle/library/util/validations:__pkg__",
"//src/main/scala/net/eagle0/eagle/library/util/view_filters:__pkg__",
@@ -54,6 +54,7 @@ scala_library(
"//src/test/scala/net/eagle0/eagle/library/util:__subpackages__",
],
exports = [
":gender",
":profession",
],
deps = [
@@ -6,6 +6,7 @@ import net.eagle0.eagle.common.battalion_type.BattalionTypeId
import net.eagle0.eagle.common.date.Date
import net.eagle0.eagle.common.gender.Gender.GENDER_OTHER
import net.eagle0.eagle.common.profession.Profession.{NO_PROFESSION, PALADIN}
import net.eagle0.eagle.common.round_phase.RoundPhase
import net.eagle0.eagle.internal.battalion.Battalion
import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.hero.Hero
@@ -52,7 +53,8 @@ class AlmsCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAndAf
pronounGender = GENDER_OTHER
)
),
currentDate = Some(Date(year = 353, month = 12))
currentDate = Some(Date(year = 353, month = 12)),
currentPhase = RoundPhase.PLAYER_COMMANDS
)
val ac = AlmsAvailableCommand(
@@ -101,7 +103,8 @@ class AlmsCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAndAf
pronounGender = GENDER_OTHER
)
),
currentDate = Some(Date(year = 353, month = 12))
currentDate = Some(Date(year = 353, month = 12)),
currentPhase = RoundPhase.PLAYER_COMMANDS
)
val ac = AlmsAvailableCommand(
@@ -150,7 +153,8 @@ class AlmsCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAndAf
pronounGender = GENDER_OTHER
)
),
currentDate = Some(Date(year = 353, month = 12))
currentDate = Some(Date(year = 353, month = 12)),
currentPhase = RoundPhase.PLAYER_COMMANDS
)
val ac = AlmsAvailableCommand(
@@ -191,7 +195,8 @@ class AlmsCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAndAf
pronounGender = GENDER_OTHER
)
),
currentDate = Some(Date(year = 353, month = 12))
currentDate = Some(Date(year = 353, month = 12)),
currentPhase = RoundPhase.PLAYER_COMMANDS
)
val ac = AlmsAvailableCommand(
@@ -232,7 +237,8 @@ class AlmsCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAndAf
pronounGender = GENDER_OTHER
)
),
currentDate = Some(Date(year = 353, month = 12))
currentDate = Some(Date(year = 353, month = 12)),
currentPhase = RoundPhase.PLAYER_COMMANDS
)
val ac = AlmsAvailableCommand(
@@ -281,7 +287,8 @@ class AlmsCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAndAf
pronounGender = GENDER_OTHER
)
),
currentDate = Some(Date(year = 353, month = 11))
currentDate = Some(Date(year = 353, month = 11)),
currentPhase = RoundPhase.PLAYER_COMMANDS
)
val ac = AlmsAvailableCommand(
@@ -330,7 +337,8 @@ class AlmsCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAndAf
pronounGender = GENDER_OTHER
)
),
currentDate = Some(Date(year = 353, month = 11))
currentDate = Some(Date(year = 353, month = 11)),
currentPhase = RoundPhase.PLAYER_COMMANDS
)
val ac = AlmsAvailableCommand(
@@ -379,7 +387,8 @@ class AlmsCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAndAf
pronounGender = GENDER_OTHER
)
),
currentDate = Some(Date(year = 353, month = 11))
currentDate = Some(Date(year = 353, month = 11)),
currentPhase = RoundPhase.PLAYER_COMMANDS
)
val ac = AlmsAvailableCommand(
@@ -428,7 +437,8 @@ class AlmsCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAndAf
pronounGender = GENDER_OTHER
)
),
currentDate = Some(Date(year = 353, month = 11))
currentDate = Some(Date(year = 353, month = 11)),
currentPhase = RoundPhase.PLAYER_COMMANDS
)
val ac = AlmsAvailableCommand(
@@ -474,6 +484,7 @@ class AlmsCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAndAf
Battalion(id = 85, `type` = BattalionTypeId.HEAVY_CAVALRY, size = 600)
),
currentDate = Some(Date(year = 353, month = 11)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes
)
@@ -516,6 +527,7 @@ class AlmsCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAndAf
)
),
currentDate = Some(Date(year = 353, month = 6)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes
)
@@ -558,6 +570,7 @@ class AlmsCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAndAf
)
),
currentDate = Some(Date(year = 353, month = 6)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes
)
@@ -609,6 +622,7 @@ class AlmsCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAndAf
)
),
currentDate = Some(Date(year = 353, month = 6)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes
)
@@ -659,6 +673,7 @@ class AlmsCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAndAf
)
),
currentDate = Some(Date(year = 353, month = 12)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes
)
@@ -709,6 +724,7 @@ class AlmsCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAndAf
)
),
currentDate = Some(Date(year = 353, month = 6)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes
)
@@ -758,7 +774,8 @@ class AlmsCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAndAf
pronounGender = GENDER_OTHER
)
),
currentDate = Some(Date(year = 353, month = 11))
currentDate = Some(Date(year = 353, month = 11)),
currentPhase = RoundPhase.PLAYER_COMMANDS
)
val ac = AlmsAvailableCommand(
@@ -813,6 +830,7 @@ class AlmsCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAndAf
Battalion(id = 85, `type` = BattalionTypeId.HEAVY_CAVALRY, size = 600)
),
currentDate = Some(Date(year = 353, month = 11)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes
)
@@ -871,6 +889,7 @@ class AlmsCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAndAf
Battalion(id = 85, `type` = BattalionTypeId.HEAVY_CAVALRY, size = 600)
),
currentDate = Some(Date(year = 353, month = 11)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes
)
@@ -6,6 +6,7 @@ import net.eagle0.eagle.api.selected_command.MarchSelectedCommand
import net.eagle0.eagle.common.battalion_type.BattalionTypeId.LIGHT_INFANTRY
import net.eagle0.eagle.common.combat_unit.CombatUnit
import net.eagle0.eagle.common.date.Date
import net.eagle0.eagle.common.round_phase.RoundPhase
import net.eagle0.eagle.internal.battalion.Battalion
import net.eagle0.eagle.internal.faction.Faction
import net.eagle0.eagle.internal.faction_relationship.FactionRelationship
@@ -96,6 +97,7 @@ class AttackCommandChooserTest extends AnyFlatSpec with Matchers with BeforeAndA
private val gameState = GameState(
currentDate = Some(Date(year = 501, month = 2)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes,
provinces = mapifyProvinces(
Province(
@@ -9,6 +9,7 @@ import net.eagle0.eagle.api.selected_command.AttackDecisionSelectedCommand
import net.eagle0.eagle.common.battalion_type.BattalionTypeId.LIGHT_INFANTRY
import net.eagle0.eagle.common.combat_unit.CombatUnit
import net.eagle0.eagle.common.date.Date
import net.eagle0.eagle.common.round_phase.RoundPhase
import net.eagle0.eagle.internal.army.{Army, AwaitingDecision, HostileArmyGroup, MovingArmy}
import net.eagle0.eagle.internal.battalion.Battalion
import net.eagle0.eagle.internal.faction.Faction
@@ -111,6 +112,7 @@ class AttackDecisionCommandChooserTest extends AnyFlatSpec with Matchers with Be
// Create GameState
val gameState = GameState(
currentDate = Some(Date(year = 501, month = 1)),
currentPhase = RoundPhase.ATTACK_DECISION,
battalionTypes = battalionTypes,
provinces = mapifyProvinces(province),
battalions = mapifyBattalions(battalion1, battalion2, battalion3),
@@ -224,6 +226,7 @@ class AttackDecisionCommandChooserTest extends AnyFlatSpec with Matchers with Be
// Create GameState
val gameState = GameState(
currentDate = Some(Date(year = 501, month = 1)),
currentPhase = RoundPhase.ATTACK_DECISION,
battalionTypes = battalionTypes,
provinces = mapifyProvinces(province),
battalions = mapifyBattalions(weakBattalion, strongBattalion),
@@ -354,6 +357,7 @@ class AttackDecisionCommandChooserTest extends AnyFlatSpec with Matchers with Be
val gameState = GameState(
currentDate = Some(Date(year = 501, month = 1)),
currentPhase = RoundPhase.ATTACK_DECISION,
battalionTypes = battalionTypes,
provinces = mapifyProvinces(province),
battalions = mapifyBattalions(battalion1, battalion2, battalion3, enemyBattalion),
@@ -484,6 +488,7 @@ class AttackDecisionCommandChooserTest extends AnyFlatSpec with Matchers with Be
val gameState = GameState(
currentDate = Some(Date(year = 501, month = 1)),
currentPhase = RoundPhase.ATTACK_DECISION,
battalionTypes = battalionTypes,
provinces = mapifyProvinces(province),
battalions = mapifyBattalions(
@@ -19,6 +19,7 @@ scala_test(
srcs = ["AlmsCommandSelectorTest.scala"],
deps = [
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/common:round_phase_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:hero_scala_proto",
"//src/main/scala/net/eagle0/eagle/library/settings:alms_paladin_support_multiplier",
@@ -36,6 +37,7 @@ scala_test(
srcs = ["AttackCommandChooserTest.scala"],
deps = [
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/common:round_phase_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/scala/net/eagle0/eagle/library/settings:alms_paladin_support_multiplier",
"//src/main/scala/net/eagle0/eagle/library/settings:alms_support_increase_per_food",
@@ -71,6 +73,7 @@ scala_test(
"//src/main/protobuf/net/eagle0/eagle/common:battalion_type_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/common:combat_unit_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/common:date_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/common:round_phase_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:army_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:battalion_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:faction_scala_proto",
@@ -115,6 +118,7 @@ scala_test(
deps = [
"//src/main/protobuf/net/eagle0/common:hostility_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/common:round_phase_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/scala/net/eagle0/common:functional_random",
"//src/main/scala/net/eagle0/eagle/library/settings:alms_paladin_support_multiplier",
@@ -137,14 +141,14 @@ scala_test(
name = "combat_unit_selector_test",
srcs = ["CombatUnitSelectorTest.scala"],
deps = [
"//src/main/protobuf/net/eagle0/eagle/common:battalion_type_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/common:combat_unit_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/common:profession_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:battalion_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:hero_scala_proto",
"//src/main/scala/net/eagle0/eagle/library/util:idable",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:combat_unit_selector",
"//src/test/scala/net/eagle0/eagle/library/util:battalion_types_test_data",
"//src/main/scala/net/eagle0/eagle/model/state:battalion_type",
"//src/main/scala/net/eagle0/eagle/model/state:battalion_type_id",
"//src/main/scala/net/eagle0/eagle/model/state/battalion/concrete",
"//src/main/scala/net/eagle0/eagle/model/state/hero",
"//src/main/scala/net/eagle0/eagle/model/state/hero/concrete",
],
)
@@ -168,6 +172,7 @@ scala_test(
deps = [
"//src/main/protobuf/net/eagle0/common:hostility_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/common:round_phase_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/scala/net/eagle0/common:functional_random",
"//src/main/scala/net/eagle0/eagle/library/util:command_selection",
@@ -181,8 +186,6 @@ scala_test(
name = "food_consumption_utils_test",
srcs = ["FoodConsumptionUtilsTest.scala"],
deps = [
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:hero_scala_proto",
"//src/main/scala/net/eagle0/eagle/library/settings:alms_paladin_support_multiplier",
"//src/main/scala/net/eagle0/eagle/library/settings:alms_support_increase_per_food",
"//src/main/scala/net/eagle0/eagle/library/settings:base_food_buy_price",
@@ -192,8 +195,9 @@ scala_test(
"//src/main/scala/net/eagle0/eagle/library/settings:prestige_per_supported_province",
"//src/main/scala/net/eagle0/eagle/library/settings:vassal_minimum_food_to_send_supplies",
"//src/main/scala/net/eagle0/eagle/library/settings:vassal_minimum_gold_to_send_supplies",
"//src/main/scala/net/eagle0/eagle/library/util:idable",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:food_consumption_utils",
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
"//src/main/scala/net/eagle0/eagle/model/state/date",
],
)
@@ -217,6 +221,7 @@ scala_test(
deps = [
":selected_command_matcher",
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/common:round_phase_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:hero_scala_proto",
"//src/main/scala/net/eagle0/eagle/library/util:command_selection",
@@ -229,9 +234,13 @@ scala_test(
name = "organize_command_selector_test",
srcs = ["OrganizeCommandSelectorTest.scala"],
deps = [
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/scala/net/eagle0/eagle/library/util:idable",
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/common:battalion_type_scala_proto",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:organize_command_selector",
"//src/main/scala/net/eagle0/eagle/model/state:battalion_type",
"//src/main/scala/net/eagle0/eagle/model/state:battalion_type_id",
"//src/main/scala/net/eagle0/eagle/model/state/battalion/concrete",
],
)
@@ -1,63 +1,128 @@
package net.eagle0.eagle.library.util.command_choice_helpers
import net.eagle0.eagle.common.battalion_type.BattalionTypeId
import net.eagle0.eagle.common.combat_unit.CombatUnit
import net.eagle0.eagle.common.profession.Profession.{ENGINEER, MAGE, NO_PROFESSION}
import net.eagle0.eagle.internal.battalion.Battalion
import net.eagle0.eagle.internal.hero.Hero
import net.eagle0.eagle.library.util.BattalionTypesTestData
import net.eagle0.eagle.model.state.battalion.concrete.BattalionC
import net.eagle0.eagle.model.state.hero.concrete.HeroC
import net.eagle0.eagle.model.state.hero.Profession
import net.eagle0.eagle.model.state.BattalionType
import net.eagle0.eagle.model.state.BattalionTypeId
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import org.scalatest.BeforeAndAfterEach
class CombatUnitSelectorTest extends AnyFlatSpec with Matchers with BeforeAndAfterEach {
private val battalionTypes = BattalionTypesTestData.battalionTypes
private val battalionTypes = Vector(
BattalionType(
typeId = BattalionTypeId.HeavyCavalry,
name = "Heavy Cavalry",
capacity = 600,
allowsCasting = false,
allowsStealth = false,
alwaysArcheryCapable = false,
baseCost = 3.0,
monthlyFoodCost = 0.15,
baseArmamentCostPerTroop = 0.05,
trainingDifficulty = 1.0,
minimumAgriculture = 0,
minimumEconomy = 0
),
BattalionType(
typeId = BattalionTypeId.LightInfantry,
name = "Light Infantry",
capacity = 1000,
allowsCasting = true,
allowsStealth = true,
alwaysArcheryCapable = false,
baseCost = 1.0,
monthlyFoodCost = 0.065,
baseArmamentCostPerTroop = 0.02,
trainingDifficulty = 1.0,
minimumAgriculture = 0,
minimumEconomy = 0
),
BattalionType(
typeId = BattalionTypeId.HeavyInfantry,
name = "Heavy Infantry",
capacity = 800,
allowsCasting = false,
allowsStealth = false,
alwaysArcheryCapable = false,
baseCost = 2.0,
monthlyFoodCost = 0.08,
baseArmamentCostPerTroop = 0.035,
trainingDifficulty = 1.0,
minimumAgriculture = 0,
minimumEconomy = 0
),
BattalionType(
typeId = BattalionTypeId.Longbowmen,
name = "Longbowmen",
capacity = 800,
allowsCasting = true,
allowsStealth = true,
alwaysArcheryCapable = true,
baseCost = 1.5,
monthlyFoodCost = 0.08,
baseArmamentCostPerTroop = 0.03,
trainingDifficulty = 1.0,
minimumAgriculture = 0,
minimumEconomy = 0
)
)
behavior of "CombatUnitSelectorTest"
"selectedCombatBattalions" should "pair a mage with an optimal battalion if possible" in {
val battalions = Vector(
Battalion(id = 1, size = 800, `type` = BattalionTypeId.HEAVY_INFANTRY),
Battalion(id = 2, size = 1000, `type` = BattalionTypeId.LIGHT_INFANTRY),
Battalion(id = 3, size = 800, `type` = BattalionTypeId.LONGBOWMEN)
BattalionC(id = 1, size = 800, typeId = BattalionTypeId.HeavyInfantry, training = 0, armament = 0, name = ""),
BattalionC(id = 2, size = 1000, typeId = BattalionTypeId.LightInfantry, training = 0, armament = 0, name = ""),
BattalionC(id = 3, size = 800, typeId = BattalionTypeId.Longbowmen, training = 0, armament = 0, name = "")
)
val heroes = Vector(
Hero(id = 6, factionId = Some(1), profession = MAGE),
Hero(id = 7, factionId = Some(1), profession = ENGINEER),
Hero(id = 8, factionId = Some(1), profession = NO_PROFESSION)
HeroC(id = 6, factionId = Some(1), profession = Profession.Mage),
HeroC(id = 7, factionId = Some(1), profession = Profession.Engineer),
HeroC(id = 8, factionId = Some(1), profession = Profession.NoProfession)
)
CombatUnitSelector.selectedCombatBattalions(
desiredCount = 3,
heroes = heroes,
battalions = battalions,
battalionTypes = battalionTypes
) should contain(
CombatUnit(factionId = 1, heroId = 6, battalionId = Some(2))
)
CombatUnitSelector
.selectedCombatBattalions(
desiredCount = 3,
heroes = heroes,
battalions = battalions,
battalionTypes = battalionTypes
)
.should(
contain(
CombatUnit(factionId = 1, heroId = 6, battalionId = Some(2))
)
)
}
it should "pair a mage with an longbowmen if no other option" in {
val battalions = Vector(
Battalion(id = 1, size = 800, `type` = BattalionTypeId.HEAVY_INFANTRY),
Battalion(id = 2, size = 600, `type` = BattalionTypeId.HEAVY_CAVALRY),
Battalion(id = 3, size = 800, `type` = BattalionTypeId.LONGBOWMEN)
BattalionC(id = 1, size = 800, typeId = BattalionTypeId.HeavyInfantry, training = 0, armament = 0, name = ""),
BattalionC(id = 2, size = 600, typeId = BattalionTypeId.HeavyCavalry, training = 0, armament = 0, name = ""),
BattalionC(id = 3, size = 800, typeId = BattalionTypeId.Longbowmen, training = 0, armament = 0, name = "")
)
val heroes = Vector(
Hero(id = 6, factionId = Some(1), profession = MAGE),
Hero(id = 7, factionId = Some(1), profession = ENGINEER),
Hero(id = 8, factionId = Some(1), profession = NO_PROFESSION)
)
CombatUnitSelector.selectedCombatBattalions(
desiredCount = 3,
heroes = heroes,
battalions = battalions,
battalionTypes = battalionTypes
) should contain(
CombatUnit(factionId = 1, heroId = 6, battalionId = Some(3))
HeroC(id = 6, factionId = Some(1), profession = Profession.Mage),
HeroC(id = 7, factionId = Some(1), profession = Profession.Engineer),
HeroC(id = 8, factionId = Some(1), profession = Profession.NoProfession)
)
CombatUnitSelector
.selectedCombatBattalions(
desiredCount = 3,
heroes = heroes,
battalions = battalions,
battalionTypes = battalionTypes
)
.should(
contain(
CombatUnit(factionId = 1, heroId = 6, battalionId = Some(3))
)
)
}
}
@@ -21,6 +21,7 @@ import net.eagle0.eagle.common.battalion_type.BattalionTypeId.{HEAVY_CAVALRY, LI
import net.eagle0.eagle.common.combat_unit.CombatUnit
import net.eagle0.eagle.common.date.Date
import net.eagle0.eagle.common.diplomacy_offer.{PrisonerToBeRansomed, RansomOfferDetails}
import net.eagle0.eagle.common.round_phase.RoundPhase
import net.eagle0.eagle.internal.army.{Army, MovingArmy}
import net.eagle0.eagle.internal.battalion.Battalion
import net.eagle0.eagle.internal.faction.Faction
@@ -69,7 +70,8 @@ class CommandChoiceHelpersTest extends AnyFlatSpec with Matchers with BeforeAndA
"chosenUnderAttackCommand" should "organize troops if under attack" in {
val actingFid = 3
val gs = GameState(
currentDate = Some(Date()),
currentDate = Some(Date(year = 500, month = 6)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
provinces = Map(
7 -> Province(
id = 7,
@@ -166,6 +168,7 @@ class CommandChoiceHelpersTest extends AnyFlatSpec with Matchers with BeforeAndA
Battalion(id = 85, `type` = BattalionTypeId.HEAVY_CAVALRY, size = 600)
),
currentDate = Some(Date(year = 353, month = 10)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes
)
@@ -217,6 +220,7 @@ class CommandChoiceHelpersTest extends AnyFlatSpec with Matchers with BeforeAndA
Battalion(id = 85, `type` = BattalionTypeId.HEAVY_CAVALRY, size = 600)
),
currentDate = Some(Date(year = 353, month = 10)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes
)
@@ -268,6 +272,7 @@ class CommandChoiceHelpersTest extends AnyFlatSpec with Matchers with BeforeAndA
Battalion(id = 85, `type` = BattalionTypeId.HEAVY_CAVALRY, size = 600)
),
currentDate = Some(Date(year = 353, month = 10)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes
)
@@ -308,6 +313,7 @@ class CommandChoiceHelpersTest extends AnyFlatSpec with Matchers with BeforeAndA
Battalion(id = 85, `type` = BattalionTypeId.HEAVY_CAVALRY, size = 600)
),
currentDate = Some(Date(year = 353, month = 10)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes
)
@@ -341,6 +347,8 @@ class CommandChoiceHelpersTest extends AnyFlatSpec with Matchers with BeforeAndA
),
Battalion(id = 12, `type` = LONGBOWMEN, size = 800, armament = 100)
),
currentDate = Some(Date(year = 500, month = 6)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes
)
@@ -371,6 +379,8 @@ class CommandChoiceHelpersTest extends AnyFlatSpec with Matchers with BeforeAndA
),
Battalion(id = 12, `type` = LONGBOWMEN, size = 800, armament = 100)
),
currentDate = Some(Date(year = 500, month = 6)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes
)
@@ -396,6 +406,8 @@ class CommandChoiceHelpersTest extends AnyFlatSpec with Matchers with BeforeAndA
Battalion(id = 11, `type` = LIGHT_INFANTRY, size = 950, armament = 100),
Battalion(id = 12, `type` = LONGBOWMEN, size = 800, armament = 100)
),
currentDate = Some(Date(year = 500, month = 6)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes
)
@@ -426,6 +438,8 @@ class CommandChoiceHelpersTest extends AnyFlatSpec with Matchers with BeforeAndA
),
Battalion(id = 12, `type` = LONGBOWMEN, size = 750, armament = 100)
),
currentDate = Some(Date(year = 500, month = 6)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes
)
@@ -456,6 +470,8 @@ class CommandChoiceHelpersTest extends AnyFlatSpec with Matchers with BeforeAndA
),
Battalion(id = 12, `type` = LONGBOWMEN, size = 800, armament = 100)
),
currentDate = Some(Date(year = 500, month = 6)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes
)
@@ -486,6 +502,8 @@ class CommandChoiceHelpersTest extends AnyFlatSpec with Matchers with BeforeAndA
),
Battalion(id = 12, `type` = LONGBOWMEN, size = 800, armament = 100)
),
currentDate = Some(Date(year = 500, month = 6)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes
)
@@ -516,6 +534,8 @@ class CommandChoiceHelpersTest extends AnyFlatSpec with Matchers with BeforeAndA
),
Battalion(id = 12, `type` = LONGBOWMEN, size = 225, armament = 100)
),
currentDate = Some(Date(year = 500, month = 6)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes
)
@@ -546,6 +566,8 @@ class CommandChoiceHelpersTest extends AnyFlatSpec with Matchers with BeforeAndA
),
Battalion(id = 12, `type` = LONGBOWMEN, size = 800, armament = 100)
),
currentDate = Some(Date(year = 500, month = 6)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes
)
@@ -577,6 +599,8 @@ class CommandChoiceHelpersTest extends AnyFlatSpec with Matchers with BeforeAndA
),
Battalion(id = 12, `type` = LONGBOWMEN, size = 800, armament = 100)
),
currentDate = Some(Date(year = 500, month = 6)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes
)
@@ -608,6 +632,8 @@ class CommandChoiceHelpersTest extends AnyFlatSpec with Matchers with BeforeAndA
),
Battalion(id = 12, `type` = LONGBOWMEN, size = 800, armament = 100)
),
currentDate = Some(Date(year = 500, month = 6)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
battalionTypes = battalionTypes
)
@@ -618,7 +644,8 @@ class CommandChoiceHelpersTest extends AnyFlatSpec with Matchers with BeforeAndA
}
private val getUnderHeroCapGameState = GameState(
currentDate = Some(Date()),
currentDate = Some(Date(year = 500, month = 6)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
heroes = mapifyHeroes((1 to 100).map(hid => Hero(id = hid)).toVector*),
provinces = mapifyProvinces(
Province(
@@ -972,6 +999,7 @@ class CommandChoiceHelpersTest extends AnyFlatSpec with Matchers with BeforeAndA
"foodAmountToBuy" should "buy in December if we're out of food" in {
val gameState = GameState(
currentDate = Some(Date(year = 323, month = 12)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
factions = mapifyFactions(Faction(id = 17, leaders = Vector(1))),
provinces = mapifyProvinces(
Province(
@@ -1003,6 +1031,7 @@ class CommandChoiceHelpersTest extends AnyFlatSpec with Matchers with BeforeAndA
it should "buy more if support is not up" in {
val gameState = GameState(
currentDate = Some(Date(year = 323, month = 12)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
factions = mapifyFactions(Faction(id = 17, leaders = Vector(1))),
provinces = mapifyProvinces(
Province(
@@ -17,6 +17,7 @@ import net.eagle0.eagle.common.battalion_type.BattalionTypeId.{HEAVY_CAVALRY, HE
import net.eagle0.eagle.common.combat_unit.CombatUnit
import net.eagle0.eagle.common.date.Date
import net.eagle0.eagle.common.improvement_type.ImprovementType.{AGRICULTURE, ECONOMY, INFRASTRUCTURE}
import net.eagle0.eagle.common.round_phase.RoundPhase
import net.eagle0.eagle.internal.army.{Army, MovingArmy}
import net.eagle0.eagle.internal.battalion.Battalion
import net.eagle0.eagle.internal.faction.Faction
@@ -105,6 +106,7 @@ class ExpandCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAnd
"expandSelectedCommand" should "return a march command if conditions are right" in {
val gameState = GameState(
currentDate = Some(Date(year = 1501, month = 6)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
randomSeed = 1L,
provinces = mapifyProvinces(
Province(
@@ -172,6 +174,7 @@ class ExpandCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAnd
it should "return None if there are 3 heroes and 2 are faction leaders" in {
val gameState = GameState(
currentDate = Some(Date(year = 1501, month = 6)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
randomSeed = 1L,
provinces = mapifyProvinces(
Province(
@@ -352,6 +355,8 @@ class ExpandCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAnd
)
private val marchGameState = GameState(
currentDate = Some(Date(year = 2983, month = 3)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
randomSeed = 1L,
provinces = mapifyProvinces(
actingProvince,
@@ -378,47 +383,54 @@ class ExpandCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAnd
Hero(id = 10, vigor = 90)
),
battalions = battalions.map(b => b.id -> b).toMap,
currentDate = Some(Date(year = 2983, month = 3)),
battalionTypes = battalionTypes
)
"expansionCandidates" should "return neighbors that are unowned" in {
ExpandCommandSelector.expansionCandidateProvinces(
marchGameState,
actingProvince.id,
actingProvince.neighbors.map(_.provinceId).toVector
) should contain.allOf(
unownedProvince1,
unownedProvince2,
unownedProvince3
ExpandCommandSelector
.expansionCandidateProvinces(
marchGameState,
actingProvince.id,
actingProvince.neighbors.map(_.provinceId).toVector
)
.map(_.id) should contain.allOf(
unownedProvince1.id,
unownedProvince2.id,
unownedProvince3.id
)
}
it should "not return provinces that you already own" in {
ExpandCommandSelector.expansionCandidateProvinces(
marchGameState,
actingProvince.id,
actingProvince.neighbors.map(_.provinceId).toVector
) should not contain
ownedProvince
ExpandCommandSelector
.expansionCandidateProvinces(
marchGameState,
actingProvince.id,
actingProvince.neighbors.map(_.provinceId).toVector
)
.map(_.id) should not contain
ownedProvince.id
}
it should "not return provinces that you already have an army heading to" in {
ExpandCommandSelector.expansionCandidateProvinces(
marchGameState,
actingProvince.id,
actingProvince.neighbors.map(_.provinceId).toVector
) should not contain
enRouteProvince
ExpandCommandSelector
.expansionCandidateProvinces(
marchGameState,
actingProvince.id,
actingProvince.neighbors.map(_.provinceId).toVector
)
.map(_.id) should not contain
enRouteProvince.id
}
it should "not return hostile provinces" in {
ExpandCommandSelector.expansionCandidateProvinces(
marchGameState,
actingProvince.id,
actingProvince.neighbors.map(_.provinceId).toVector
) should not contain
hostileProvince
ExpandCommandSelector
.expansionCandidateProvinces(
marchGameState,
actingProvince.id,
actingProvince.neighbors.map(_.provinceId).toVector
)
.map(_.id) should not contain
hostileProvince.id
}
private val improveCommand = ImproveAvailableCommand(
@@ -1,12 +1,5 @@
package net.eagle0.eagle.library.util.command_choice_helpers
import net.eagle0.eagle.common.date.Date
import net.eagle0.eagle.common.round_phase.RoundPhase.{
BATTLE_RESOLUTION,
PLAYER_COMMANDS,
RECON_RESOLUTION,
VASSAL_COMMANDS
}
import net.eagle0.eagle.library.settings.{
AlmsPaladinSupportMultiplier,
AlmsSupportIncreasePerFood,
@@ -18,6 +11,8 @@ import net.eagle0.eagle.library.settings.{
VassalMinimumFoodToSendSupplies,
VassalMinimumGoldToSendSupplies
}
import net.eagle0.eagle.model.state.date.Date
import net.eagle0.eagle.model.state.RoundPhase
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import org.scalatest.BeforeAndAfterEach
@@ -37,38 +32,46 @@ class FoodConsumptionUtilsTest extends AnyFlatSpec with Matchers with BeforeAndA
}
"foodConsumptionMonthsRemainingInYear" should "be 12 in January before FOOD_CONSUMPTION" in {
val date = Date(year = 323, month = 1)
val date = Date(year = 323, month = Date.Month.January)
FoodConsumptionUtils.foodConsumptionMonthsRemainingInYear(
date,
PLAYER_COMMANDS
) shouldBe 12
FoodConsumptionUtils
.foodConsumptionMonthsRemainingInYear(
date,
RoundPhase.PlayerCommands
)
.shouldBe(12)
}
it should "be 11 in January after FOOD_CONSUMPTION" in {
val date = Date(year = 323, month = 1)
val date = Date(year = 323, month = Date.Month.January)
FoodConsumptionUtils.foodConsumptionMonthsRemainingInYear(
date,
BATTLE_RESOLUTION
) shouldBe 11
FoodConsumptionUtils
.foodConsumptionMonthsRemainingInYear(
date,
RoundPhase.BattleResolution
)
.shouldBe(11)
}
it should "be 1 in December before FOOD_CONSUMPTION" in {
val date = Date(year = 323, month = 12)
val date = Date(year = 323, month = Date.Month.December)
FoodConsumptionUtils.foodConsumptionMonthsRemainingInYear(
date,
VASSAL_COMMANDS
) shouldBe 1
FoodConsumptionUtils
.foodConsumptionMonthsRemainingInYear(
date,
RoundPhase.VassalCommands
)
.shouldBe(1)
}
it should "be 0 in December after FOOD_CONSUMPTION" in {
val date = Date(year = 323, month = 12)
val date = Date(year = 323, month = Date.Month.December)
FoodConsumptionUtils.foodConsumptionMonthsRemainingInYear(
date,
RECON_RESOLUTION
) shouldBe 0
FoodConsumptionUtils
.foodConsumptionMonthsRemainingInYear(
date,
RoundPhase.ReconResolution
)
.shouldBe(0)
}
}
@@ -4,6 +4,7 @@ import net.eagle0.eagle.api.available_command.{ImproveAvailableCommand, RestAvai
import net.eagle0.eagle.api.selected_command.ImproveSelectedCommand
import net.eagle0.eagle.common.date.Date
import net.eagle0.eagle.common.improvement_type.ImprovementType.{AGRICULTURE, ECONOMY, INFRASTRUCTURE}
import net.eagle0.eagle.common.round_phase.RoundPhase
import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.hero.Hero
import net.eagle0.eagle.internal.province.Province
@@ -83,7 +84,8 @@ class ImproveCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeAn
veryTiredHero2
),
provinces = Map(7 -> actingProvince),
currentDate = Some(Date(year = 2983, month = 3))
currentDate = Some(Date(year = 2983, month = 3)),
currentPhase = RoundPhase.PLAYER_COMMANDS
)
it should "choose Economy if that is lower than agriculture and infrastructure" in {
@@ -1,9 +1,10 @@
package net.eagle0.eagle.library.util.command_choice_helpers
import net.eagle0.eagle.api.selected_command.OrganizeTroopsSelectedCommand.{ChangedBattalion, NewBattalion}
import net.eagle0.eagle.common.battalion_type.BattalionType
import net.eagle0.eagle.common.battalion_type.BattalionTypeId.{HEAVY_CAVALRY, LIGHT_INFANTRY}
import net.eagle0.eagle.internal.battalion.Battalion
import net.eagle0.eagle.common.battalion_type.BattalionTypeId as ProtoBattalionTypeId
import net.eagle0.eagle.model.state.battalion.concrete.BattalionC
import net.eagle0.eagle.model.state.BattalionType
import net.eagle0.eagle.model.state.BattalionTypeId
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import org.scalatest.BeforeAndAfterEach
@@ -14,278 +15,446 @@ class OrganizeCommandSelectorTest extends AnyFlatSpec with Matchers with BeforeA
val battalionTypes =
Vector(
BattalionType(
typeId = LIGHT_INFANTRY,
typeId = BattalionTypeId.LightInfantry,
name = "Light Infantry",
capacity = 1000,
allowsCasting = true,
allowsStealth = true,
alwaysArcheryCapable = false,
baseCost = 1.0,
monthlyFoodCost = 0.15,
capacity = 1000
baseArmamentCostPerTroop = 0.02,
trainingDifficulty = 1.0,
minimumAgriculture = 0,
minimumEconomy = 0
),
BattalionType(
typeId = HEAVY_CAVALRY,
typeId = BattalionTypeId.HeavyCavalry,
name = "Heavy Cavalry",
capacity = 600,
allowsCasting = false,
allowsStealth = false,
alwaysArcheryCapable = false,
baseCost = 3.0,
monthlyFoodCost = 0.25,
capacity = 600
baseArmamentCostPerTroop = 0.05,
trainingDifficulty = 1.0,
minimumAgriculture = 0,
minimumEconomy = 0
)
)
val cbResult = OrganizeCommandSelector.changedBattalions(
gold = 10000,
foodSurplus = 500,
costs = Map(LIGHT_INFANTRY -> 2.0, HEAVY_CAVALRY -> 3.0),
costs = Map(BattalionTypeId.LightInfantry -> 2.0, BattalionTypeId.HeavyCavalry -> 3.0),
existingBattalions = Vector(
Battalion(id = 1, `type` = LIGHT_INFANTRY, size = 950),
Battalion(id = 2, `type` = HEAVY_CAVALRY, size = 560)
BattalionC(id = 1, typeId = BattalionTypeId.LightInfantry, size = 950, training = 0, armament = 0, name = ""),
BattalionC(id = 2, typeId = BattalionTypeId.HeavyCavalry, size = 560, training = 0, armament = 0, name = "")
),
battalionTypes = battalionTypes,
eligibleBattalionTypeIds = battalionTypes.map(_.typeId)
)
cbResult.changedBattalions should have size 2
cbResult.changedBattalions.should(have).size(2)
val expectedRemainingGold = 9780 // 10000 - 50 * 2.0 - 40 * 3.0
cbResult.remainingGold shouldBe expectedRemainingGold
cbResult.remainingGold.shouldBe(expectedRemainingGold)
val expectedRemainingFoodSurplus = 482 // 500 - 50 * 0.15 - 40 * 0.25
cbResult.remainingFoodSurplus shouldBe expectedRemainingFoodSurplus
cbResult.remainingFoodSurplus.shouldBe(expectedRemainingFoodSurplus)
cbResult.changedBattalions should contain.allOf(
ChangedBattalion(id = 1, newTroops = 50),
ChangedBattalion(id = 2, newTroops = 40)
)
cbResult.changedBattalions
.should(contain)
.allOf(
ChangedBattalion(id = 1, newTroops = 50),
ChangedBattalion(id = 2, newTroops = 40)
)
}
it should "top up only one battalion if there's only food for one" in {
val battalionTypes =
Vector(
BattalionType(
typeId = LIGHT_INFANTRY,
typeId = BattalionTypeId.LightInfantry,
name = "Light Infantry",
capacity = 1000,
allowsCasting = true,
allowsStealth = true,
alwaysArcheryCapable = false,
baseCost = 1.0,
monthlyFoodCost = 0.15,
capacity = 1000
baseArmamentCostPerTroop = 0.02,
trainingDifficulty = 1.0,
minimumAgriculture = 0,
minimumEconomy = 0
),
BattalionType(
typeId = HEAVY_CAVALRY,
typeId = BattalionTypeId.HeavyCavalry,
name = "Heavy Cavalry",
capacity = 600,
allowsCasting = false,
allowsStealth = false,
alwaysArcheryCapable = false,
baseCost = 3.0,
monthlyFoodCost = 0.25,
capacity = 600
baseArmamentCostPerTroop = 0.05,
trainingDifficulty = 1.0,
minimumAgriculture = 0,
minimumEconomy = 0
)
)
val cbResult = OrganizeCommandSelector.changedBattalions(
gold = 10000,
foodSurplus = 11,
costs = Map(LIGHT_INFANTRY -> 2.0, HEAVY_CAVALRY -> 3.0),
costs = Map(BattalionTypeId.LightInfantry -> 2.0, BattalionTypeId.HeavyCavalry -> 3.0),
existingBattalions = Vector(
Battalion(id = 1, `type` = LIGHT_INFANTRY, size = 950),
Battalion(id = 2, `type` = HEAVY_CAVALRY, size = 560)
BattalionC(id = 1, typeId = BattalionTypeId.LightInfantry, size = 950, training = 0, armament = 0, name = ""),
BattalionC(id = 2, typeId = BattalionTypeId.HeavyCavalry, size = 560, training = 0, armament = 0, name = "")
),
battalionTypes = battalionTypes,
eligibleBattalionTypeIds = battalionTypes.map(_.typeId)
)
cbResult.changedBattalions should have size 1
cbResult.changedBattalions.should(have).size(1)
}
it should "top up only one battalion if there's only gold for one" in {
val battalionTypes =
Vector(
BattalionType(
typeId = LIGHT_INFANTRY,
typeId = BattalionTypeId.LightInfantry,
name = "Light Infantry",
capacity = 1000,
allowsCasting = true,
allowsStealth = true,
alwaysArcheryCapable = false,
baseCost = 1.0,
monthlyFoodCost = 0.15,
capacity = 1000
baseArmamentCostPerTroop = 0.02,
trainingDifficulty = 1.0,
minimumAgriculture = 0,
minimumEconomy = 0
),
BattalionType(
typeId = HEAVY_CAVALRY,
typeId = BattalionTypeId.HeavyCavalry,
name = "Heavy Cavalry",
capacity = 600,
allowsCasting = false,
allowsStealth = false,
alwaysArcheryCapable = false,
baseCost = 3.0,
monthlyFoodCost = 0.25,
capacity = 600
baseArmamentCostPerTroop = 0.05,
trainingDifficulty = 1.0,
minimumAgriculture = 0,
minimumEconomy = 0
)
)
val cbResult = OrganizeCommandSelector.changedBattalions(
gold = 150,
foodSurplus = 500,
costs = Map(LIGHT_INFANTRY -> 2.0, HEAVY_CAVALRY -> 3.0),
costs = Map(BattalionTypeId.LightInfantry -> 2.0, BattalionTypeId.HeavyCavalry -> 3.0),
existingBattalions = Vector(
Battalion(id = 1, `type` = LIGHT_INFANTRY, size = 950),
Battalion(id = 2, `type` = HEAVY_CAVALRY, size = 560)
BattalionC(id = 1, typeId = BattalionTypeId.LightInfantry, size = 950, training = 0, armament = 0, name = ""),
BattalionC(id = 2, typeId = BattalionTypeId.HeavyCavalry, size = 560, training = 0, armament = 0, name = "")
),
battalionTypes = battalionTypes,
eligibleBattalionTypeIds = battalionTypes.map(_.typeId)
)
cbResult.changedBattalions should have size 1
cbResult.changedBattalions.should(have).size(1)
}
"newBattalions" should "buy a battalion if there's sufficient gold and food" in {
val battalionTypes =
Vector(
BattalionType(
typeId = LIGHT_INFANTRY,
typeId = BattalionTypeId.LightInfantry,
name = "Light Infantry",
capacity = 1000,
allowsCasting = true,
allowsStealth = true,
alwaysArcheryCapable = false,
baseCost = 1.0,
monthlyFoodCost = 0.15,
capacity = 1000
baseArmamentCostPerTroop = 0.02,
trainingDifficulty = 1.0,
minimumAgriculture = 0,
minimumEconomy = 0
),
BattalionType(
typeId = HEAVY_CAVALRY,
typeId = BattalionTypeId.HeavyCavalry,
name = "Heavy Cavalry",
capacity = 600,
allowsCasting = false,
allowsStealth = false,
alwaysArcheryCapable = false,
baseCost = 3.0,
monthlyFoodCost = 0.25,
capacity = 600
baseArmamentCostPerTroop = 0.05,
trainingDifficulty = 1.0,
minimumAgriculture = 0,
minimumEconomy = 0
)
)
val nbResult = OrganizeCommandSelector.newBattalions(
gold = 10000,
foodSurplus = 200,
costs = Map(LIGHT_INFANTRY -> 2.0, HEAVY_CAVALRY -> 3.0),
costs = Map(BattalionTypeId.LightInfantry -> 2.0, BattalionTypeId.HeavyCavalry -> 3.0),
allBattalionTypes = battalionTypes,
eligibleBattalionTypeIds = battalionTypes.map(_.typeId),
existingBattalions = Vector()
)
nbResult.newBattalions should have size 1
nbResult.newBattalions.should(have).size(1)
}
it should "buy the battalion we have least of" in {
val battalionTypes =
Vector(
BattalionType(
typeId = LIGHT_INFANTRY,
typeId = BattalionTypeId.LightInfantry,
name = "Light Infantry",
capacity = 1000,
allowsCasting = true,
allowsStealth = true,
alwaysArcheryCapable = false,
baseCost = 1.0,
monthlyFoodCost = 0.15,
capacity = 1000
baseArmamentCostPerTroop = 0.02,
trainingDifficulty = 1.0,
minimumAgriculture = 0,
minimumEconomy = 0
),
BattalionType(
typeId = HEAVY_CAVALRY,
typeId = BattalionTypeId.HeavyCavalry,
name = "Heavy Cavalry",
capacity = 600,
allowsCasting = false,
allowsStealth = false,
alwaysArcheryCapable = false,
baseCost = 3.0,
monthlyFoodCost = 0.25,
capacity = 600
baseArmamentCostPerTroop = 0.05,
trainingDifficulty = 1.0,
minimumAgriculture = 0,
minimumEconomy = 0
)
)
val nbResult = OrganizeCommandSelector.newBattalions(
gold = 10000,
foodSurplus = 200,
costs = Map(LIGHT_INFANTRY -> 2.0, HEAVY_CAVALRY -> 4.0),
costs = Map(BattalionTypeId.LightInfantry -> 2.0, BattalionTypeId.HeavyCavalry -> 4.0),
allBattalionTypes = battalionTypes,
eligibleBattalionTypeIds = battalionTypes.map(_.typeId),
existingBattalions = Vector(Battalion(`type` = HEAVY_CAVALRY))
existingBattalions = Vector(
BattalionC(id = 1, typeId = BattalionTypeId.HeavyCavalry, size = 0, training = 0, armament = 0, name = "")
)
)
nbResult.newBattalions should have size 1
nbResult.newBattalions.head.`type` shouldBe LIGHT_INFANTRY
nbResult.newBattalions.head.newTroops shouldBe 1000
nbResult.newBattalions.should(have).size(1)
nbResult.newBattalions.head.`type`.shouldBe(ProtoBattalionTypeId.LIGHT_INFANTRY)
nbResult.newBattalions.head.newTroops.shouldBe(1000)
}
it should "buy the most expensive battalion if there's a tie" in {
val battalionTypes =
Vector(
BattalionType(
typeId = LIGHT_INFANTRY,
typeId = BattalionTypeId.LightInfantry,
name = "Light Infantry",
capacity = 1000,
allowsCasting = true,
allowsStealth = true,
alwaysArcheryCapable = false,
baseCost = 1.0,
monthlyFoodCost = 0.15,
capacity = 1000
baseArmamentCostPerTroop = 0.02,
trainingDifficulty = 1.0,
minimumAgriculture = 0,
minimumEconomy = 0
),
BattalionType(
typeId = HEAVY_CAVALRY,
typeId = BattalionTypeId.HeavyCavalry,
name = "Heavy Cavalry",
capacity = 600,
allowsCasting = false,
allowsStealth = false,
alwaysArcheryCapable = false,
baseCost = 3.0,
monthlyFoodCost = 0.25,
capacity = 600
baseArmamentCostPerTroop = 0.05,
trainingDifficulty = 1.0,
minimumAgriculture = 0,
minimumEconomy = 0
)
)
val nbResult = OrganizeCommandSelector.newBattalions(
gold = 10000,
foodSurplus = 200,
costs = Map(LIGHT_INFANTRY -> 2.0, HEAVY_CAVALRY -> 4.0),
costs = Map(BattalionTypeId.LightInfantry -> 2.0, BattalionTypeId.HeavyCavalry -> 4.0),
allBattalionTypes = battalionTypes,
eligibleBattalionTypeIds = battalionTypes.map(_.typeId),
existingBattalions = Vector(
Battalion(`type` = HEAVY_CAVALRY),
Battalion(`type` = LIGHT_INFANTRY)
BattalionC(id = 1, typeId = BattalionTypeId.HeavyCavalry, size = 0, training = 0, armament = 0, name = ""),
BattalionC(id = 2, typeId = BattalionTypeId.LightInfantry, size = 0, training = 0, armament = 0, name = "")
)
)
nbResult.newBattalions should have size 1
nbResult.newBattalions.head.`type` shouldBe HEAVY_CAVALRY
nbResult.newBattalions.head.newTroops shouldBe 600
nbResult.newBattalions.should(have).size(1)
nbResult.newBattalions.head.`type`.shouldBe(ProtoBattalionTypeId.HEAVY_CAVALRY)
nbResult.newBattalions.head.newTroops.shouldBe(600)
}
it should "buy multiple battalions if there is sufficient gold and food" in {
val battalionTypes =
Vector(
BattalionType(
typeId = LIGHT_INFANTRY,
typeId = BattalionTypeId.LightInfantry,
name = "Light Infantry",
capacity = 1000,
allowsCasting = true,
allowsStealth = true,
alwaysArcheryCapable = false,
baseCost = 1.0,
monthlyFoodCost = 0.15,
capacity = 1000
baseArmamentCostPerTroop = 0.02,
trainingDifficulty = 1.0,
minimumAgriculture = 0,
minimumEconomy = 0
),
BattalionType(
typeId = HEAVY_CAVALRY,
typeId = BattalionTypeId.HeavyCavalry,
name = "Heavy Cavalry",
capacity = 600,
allowsCasting = false,
allowsStealth = false,
alwaysArcheryCapable = false,
baseCost = 3.0,
monthlyFoodCost = 0.3,
capacity = 600
baseArmamentCostPerTroop = 0.05,
trainingDifficulty = 1.0,
minimumAgriculture = 0,
minimumEconomy = 0
)
)
val nbResult = OrganizeCommandSelector.newBattalions(
gold = 10000,
foodSurplus = 330,
costs = Map(LIGHT_INFANTRY -> 2.0, HEAVY_CAVALRY -> 4.0),
costs = Map(BattalionTypeId.LightInfantry -> 2.0, BattalionTypeId.HeavyCavalry -> 4.0),
allBattalionTypes = battalionTypes,
eligibleBattalionTypeIds = battalionTypes.map(_.typeId),
existingBattalions = Vector()
)
nbResult.newBattalions should have size 2
nbResult.newBattalions should contain.allOf(
NewBattalion(`type` = HEAVY_CAVALRY, newTroops = 600),
NewBattalion(`type` = LIGHT_INFANTRY, newTroops = 1000)
)
nbResult.remainingFoodSurplus shouldBe 0
nbResult.remainingGold shouldBe 5600
nbResult.newBattalions.should(have).size(2)
nbResult.newBattalions
.should(contain)
.allOf(
NewBattalion(`type` = ProtoBattalionTypeId.HEAVY_CAVALRY, newTroops = 600),
NewBattalion(`type` = ProtoBattalionTypeId.LIGHT_INFANTRY, newTroops = 1000)
)
nbResult.remainingFoodSurplus.shouldBe(0)
nbResult.remainingGold.shouldBe(5600)
}
it should "not buy anything if there is not enough gold" in {
val battalionTypes =
Vector(
BattalionType(
typeId = LIGHT_INFANTRY,
typeId = BattalionTypeId.LightInfantry,
name = "Light Infantry",
capacity = 1000,
allowsCasting = true,
allowsStealth = true,
alwaysArcheryCapable = false,
baseCost = 1.0,
monthlyFoodCost = 0.15,
capacity = 1000
baseArmamentCostPerTroop = 0.02,
trainingDifficulty = 1.0,
minimumAgriculture = 0,
minimumEconomy = 0
),
BattalionType(
typeId = HEAVY_CAVALRY,
typeId = BattalionTypeId.HeavyCavalry,
name = "Heavy Cavalry",
capacity = 600,
allowsCasting = false,
allowsStealth = false,
alwaysArcheryCapable = false,
baseCost = 3.0,
monthlyFoodCost = 0.3,
capacity = 600
baseArmamentCostPerTroop = 0.05,
trainingDifficulty = 1.0,
minimumAgriculture = 0,
minimumEconomy = 0
)
)
val nbResult = OrganizeCommandSelector.newBattalions(
gold = 1900,
foodSurplus = 330,
costs = Map(LIGHT_INFANTRY -> 2.0, HEAVY_CAVALRY -> 4.0),
costs = Map(BattalionTypeId.LightInfantry -> 2.0, BattalionTypeId.HeavyCavalry -> 4.0),
allBattalionTypes = battalionTypes,
eligibleBattalionTypeIds = battalionTypes.map(_.typeId),
existingBattalions = Vector()
)
nbResult.newBattalions shouldBe empty
nbResult.remainingGold shouldBe 1900
nbResult.remainingFoodSurplus shouldBe 330
nbResult.newBattalions.shouldBe(empty)
nbResult.remainingGold.shouldBe(1900)
nbResult.remainingFoodSurplus.shouldBe(330)
}
it should "not buy anything if there is not enough food" in {
val battalionTypes =
Vector(
BattalionType(
typeId = LIGHT_INFANTRY,
typeId = BattalionTypeId.LightInfantry,
name = "Light Infantry",
capacity = 1000,
allowsCasting = true,
allowsStealth = true,
alwaysArcheryCapable = false,
baseCost = 1.0,
monthlyFoodCost = 0.15,
capacity = 1000
baseArmamentCostPerTroop = 0.02,
trainingDifficulty = 1.0,
minimumAgriculture = 0,
minimumEconomy = 0
),
BattalionType(
typeId = HEAVY_CAVALRY,
typeId = BattalionTypeId.HeavyCavalry,
name = "Heavy Cavalry",
capacity = 600,
allowsCasting = false,
allowsStealth = false,
alwaysArcheryCapable = false,
baseCost = 3.0,
monthlyFoodCost = 0.3,
capacity = 600
baseArmamentCostPerTroop = 0.05,
trainingDifficulty = 1.0,
minimumAgriculture = 0,
minimumEconomy = 0
)
)
val nbResult = OrganizeCommandSelector.newBattalions(
gold = 10000,
foodSurplus = 140,
costs = Map(LIGHT_INFANTRY -> 2.0, HEAVY_CAVALRY -> 4.0),
costs = Map(BattalionTypeId.LightInfantry -> 2.0, BattalionTypeId.HeavyCavalry -> 4.0),
allBattalionTypes = battalionTypes,
eligibleBattalionTypeIds = battalionTypes.map(_.typeId),
existingBattalions = Vector()
)
nbResult.newBattalions shouldBe empty
nbResult.remainingGold shouldBe 10000
nbResult.remainingFoodSurplus shouldBe 140
nbResult.newBattalions.shouldBe(empty)
nbResult.remainingGold.shouldBe(10000)
nbResult.remainingFoodSurplus.shouldBe(140)
}
}
@@ -6,6 +6,7 @@ import net.eagle0.eagle.common.date.Date
import net.eagle0.eagle.common.gender.Gender.GENDER_OTHER
import net.eagle0.eagle.common.profession.Profession.NO_PROFESSION
import net.eagle0.eagle.common.recruitment_info.RecruitmentInfo
import net.eagle0.eagle.common.round_phase.RoundPhase
import net.eagle0.eagle.common.unaffiliated_hero_quest.{AlmsAcrossRealmQuest, ImproveAgricultureQuest, Quest}
import net.eagle0.eagle.internal.faction.Faction
import net.eagle0.eagle.internal.game_state.GameState
@@ -80,6 +81,7 @@ class AlmsAcrossRealmQuestCommandChooserTest extends AnyFlatSpec with BeforeAndA
private val gameState = GameState(
currentDate = Some(Date(year = 1501, month = 6)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
randomSeed = 1L,
heroes = mapifyHeroes(uh1, uh2, factionLeader, vassal),
provinces = mapifyProvinces(
@@ -6,6 +6,7 @@ import net.eagle0.eagle.common.date.Date
import net.eagle0.eagle.common.gender.Gender.GENDER_OTHER
import net.eagle0.eagle.common.profession.Profession.NO_PROFESSION
import net.eagle0.eagle.common.recruitment_info.RecruitmentInfo
import net.eagle0.eagle.common.round_phase.RoundPhase
import net.eagle0.eagle.common.unaffiliated_hero_quest.{AlmsToProvinceQuest, ImproveAgricultureQuest, Quest}
import net.eagle0.eagle.internal.faction.Faction
import net.eagle0.eagle.internal.game_state.GameState
@@ -56,6 +57,7 @@ class AlmsToProvinceQuestCommandChooserTest extends AnyFlatSpec with BeforeAndAf
private val gameState = GameState(
currentDate = Some(Date(year = 1501, month = 6)),
currentPhase = RoundPhase.PLAYER_COMMANDS,
randomSeed = 1L,
heroes = mapifyHeroes(hero, factionLeader),
provinces = mapifyProvinces(
@@ -23,6 +23,7 @@ scala_test(
deps = [
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/common:round_phase_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:hero_scala_proto",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
@@ -40,6 +41,7 @@ scala_test(
deps = [
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/common:round_phase_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:hero_scala_proto",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",