use super::{MappedBuildOrder, OrderState};
use crate::geo::{Map, ProvinceKey, RegionKey, SupplyCenter};
use crate::order::BuildCommand;
use crate::{Nation, ShortName, UnitType};
use std::collections::{HashMap, HashSet};
use std::convert::TryInto;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum OrderOutcome {
Succeeds,
RedeploymentProhibited,
InvalidProvince,
ForeignControlled,
OccupiedProvince,
InvalidTerrain,
DisbandingNonexistentUnit,
DisbandingForeignUnit,
AllBuildsUsed,
AllDisbandsUsed,
}
impl From<OrderOutcome> for OrderState {
fn from(outcome: OrderOutcome) -> Self {
if outcome == OrderOutcome::Succeeds {
OrderState::Succeeds
} else {
OrderState::Fails
}
}
}
pub trait WorldState {
fn nations(&self) -> HashSet<&Nation>;
fn occupier(&self, province: &ProvinceKey) -> Option<&Nation>;
fn unit_count(&self, nation: &Nation) -> u8;
fn units(&self, nation: &Nation) -> HashSet<(UnitType, RegionKey)>;
}
pub struct ResolverContext<'a, W: WorldState> {
world: &'a Map,
home_scs: HashMap<&'a Nation, HashSet<ProvinceKey>>,
ownerships: HashMap<&'a Nation, i16>,
last_time: &'a HashMap<ProvinceKey, Nation>,
this_time: &'a W,
orders: Vec<&'a MappedBuildOrder>,
}
impl<'a, W: WorldState> ResolverContext<'a, W> {
pub fn new(
world: &'a Map,
last_time: &'a HashMap<ProvinceKey, Nation>,
this_time: &'a W,
orders: Vec<&'a MappedBuildOrder>,
) -> Self {
if last_time.is_empty() {
panic!("At least one supply center must have been owned by at least one nation. Did you forget to pass the initial world state?");
}
let mut home_scs = HashMap::with_capacity(25);
let mut ownerships = HashMap::new();
for province in world.provinces().filter(|p| p.is_supply_center()) {
if let SupplyCenter::Home(nat) = &province.supply_center {
home_scs
.entry(nat)
.or_insert_with(HashSet::new)
.insert(province.into());
}
let key = ProvinceKey::from(province);
if let Some(nation) = this_time.occupier(&key).or_else(|| last_time.get(&key)) {
*ownerships.entry(nation).or_insert(0) += 1;
}
}
Self {
world,
home_scs,
ownerships,
last_time,
this_time,
orders,
}
}
pub fn current_owner(&'a self, province: &ProvinceKey) -> Option<&'a Nation> {
self.this_time
.occupier(province)
.or_else(|| self.last_time.get(province))
}
pub fn resolve(&'a self) -> Outcome<'a> {
Resolution::new(self).resolve(self)
}
}
struct Resolution<'a> {
deltas: HashMap<&'a Nation, (BuildCommand, i16)>,
state: HashMap<&'a MappedBuildOrder, OrderOutcome>,
civil_disorder: HashSet<(UnitType, RegionKey)>,
final_units: HashMap<&'a Nation, HashSet<(UnitType, RegionKey)>>,
}
impl<'a> Resolution<'a> {
pub fn new<W: WorldState>(context: &'a ResolverContext<W>) -> Self {
let final_units = context
.this_time
.nations()
.into_iter()
.map(|nation| (nation, context.this_time.units(nation)))
.collect();
let deltas = context
.ownerships
.iter()
.filter_map(|(&nation, ownerships)| {
let adjustment = ownerships - context.this_time.unit_count(nation) as i16;
match adjustment {
0 => None,
x if x > 0 => Some((nation, (BuildCommand::Build, x))),
x => Some((nation, (BuildCommand::Disband, -x))),
}
})
.collect();
Resolution {
deltas,
state: HashMap::with_capacity(context.orders.len()),
civil_disorder: HashSet::new(),
final_units,
}
}
pub fn resolve(mut self, context: &'a ResolverContext<impl WorldState>) -> Outcome<'a> {
for order in &context.orders {
self.resolve_order(context, order);
}
for (nation, delta) in &mut self.deltas {
if delta.0 == BuildCommand::Build || delta.1 == 0 {
continue;
}
let usize_delta: usize = delta.1.try_into().unwrap();
let units = self.final_units.remove(nation).unwrap();
for unit in units.clone().into_iter().take(usize_delta) {
self.civil_disorder.insert(unit);
}
self.final_units
.insert(nation, units.into_iter().skip(usize_delta).collect());
}
Outcome {
orders: self.state,
final_units: self.final_units,
civil_disorder: self.civil_disorder,
}
}
fn resolve_order(
&mut self,
context: &'a ResolverContext<impl WorldState>,
order: &'a MappedBuildOrder,
) -> OrderOutcome {
use self::OrderOutcome::*;
if let Some(outcome) = self.state.get(order) {
return *outcome;
}
let mut delta = if let Some(delta) = self.deltas.get_mut(&order.nation) {
delta
} else {
return self.resolve_as(order, RedeploymentProhibited);
};
if delta.0 != order.command {
return self.resolve_as(order, RedeploymentProhibited);
}
let adjudication = adjudicate(context, order);
if adjudication != OrderOutcome::Succeeds {
return self.resolve_as(order, adjudication);
}
match order.command {
BuildCommand::Build => {
if delta.1 == 0 {
return self.resolve_as(order, AllBuildsUsed);
}
delta.1 -= 1;
self.final_units
.entry(&order.nation)
.or_insert_with(HashSet::new)
.insert((order.unit_type, order.region.clone()));
self.resolve_as(order, Succeeds)
}
BuildCommand::Disband => {
if delta.1 == 0 {
return self.resolve_as(order, AllDisbandsUsed);
}
delta.1 -= 1;
self.final_units
.entry(&order.nation)
.or_insert_with(HashSet::new)
.remove(&(order.unit_type, order.region.clone()));
self.resolve_as(order, Succeeds)
}
}
}
fn resolve_as(
&mut self,
order: &'a MappedBuildOrder,
resolution: OrderOutcome,
) -> OrderOutcome {
self.state.insert(order, resolution);
resolution
}
}
#[derive(Debug, Clone)]
pub struct Outcome<'a> {
pub orders: HashMap<&'a MappedBuildOrder, OrderOutcome>,
pub civil_disorder: HashSet<(UnitType, RegionKey)>,
pub final_units: HashMap<&'a Nation, HashSet<(UnitType, RegionKey)>>,
}
fn adjudicate(
context: &ResolverContext<impl WorldState>,
order: &MappedBuildOrder,
) -> OrderOutcome {
use self::OrderOutcome::*;
let province = order.region.province();
match order.command {
BuildCommand::Build => {
if !context
.home_scs
.get(&order.nation)
.expect("Every nation should have home SCs")
.contains(province)
{
return InvalidProvince;
}
if Some(&order.nation) != context.current_owner(province) {
return ForeignControlled;
}
if context.this_time.occupier(province).is_some() {
return OccupiedProvince;
}
let region = if let Some(region) = context.world.find_region(&order.region.short_name())
{
region
} else {
return InvalidProvince;
};
if !order.unit_type.can_occupy(region.terrain()) {
return InvalidTerrain;
}
Succeeds
}
BuildCommand::Disband => match context.this_time.occupier(province) {
None => DisbandingNonexistentUnit,
Some(nation) if &order.nation != nation => DisbandingForeignUnit,
_ => Succeeds,
},
}
}
pub fn to_initial_ownerships(map: &Map) -> HashMap<ProvinceKey, Nation> {
map.provinces()
.filter_map(|province| {
if let SupplyCenter::Home(nat) = &province.supply_center {
Some((province.into(), nat.clone()))
} else {
None
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::to_initial_ownerships;
use crate::geo::{standard_map, ProvinceKey};
use crate::Nation;
#[test]
fn to_initial_ownerships_for_standard_map() {
let ownerships = to_initial_ownerships(standard_map());
assert_eq!(
Some(&Nation::from("AUS")),
ownerships.get(&ProvinceKey::from("bud"))
);
assert_eq!(None, ownerships.get(&ProvinceKey::from("bel")));
}
}