use super::{Adjudicate, Context, MappedMainOrder, OrderState, ResolverState};
use crate::geo::{Map, ProvinceKey};
use crate::order::{Command, MainCommand};
use crate::UnitType;
pub enum ConvoyRouteError {
CanOnlyConvoyArmy,
CanOnlyConvoyMove,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConvoyOutcome<'a> {
NotAtSea,
Dislodged(&'a MappedMainOrder),
Paradox,
NotDisrupted,
}
impl From<&'_ ConvoyOutcome<'_>> for OrderState {
fn from(other: &ConvoyOutcome<'_>) -> Self {
if other == &ConvoyOutcome::NotDisrupted {
OrderState::Succeeds
} else {
OrderState::Fails
}
}
}
impl From<ConvoyOutcome<'_>> for OrderState {
fn from(other: ConvoyOutcome<'_>) -> Self {
(&other).into()
}
}
fn is_convoy_for(convoy: &MappedMainOrder, mv_ord: &MappedMainOrder) -> bool {
match &convoy.command {
MainCommand::Convoy(ref cm) => cm == mv_ord,
_ => false,
}
}
fn route_steps<'a>(
map: &Map,
convoys: &[&'a MappedMainOrder],
origin: &ProvinceKey,
dest: &ProvinceKey,
working_path: Vec<&'a MappedMainOrder>,
) -> Vec<Vec<&'a MappedMainOrder>> {
let adjacent_regions = map.find_bordering(origin);
if !working_path.is_empty() && adjacent_regions.iter().any(|&r| r == dest) {
vec![working_path]
} else {
let mut paths = vec![];
for convoy in convoys {
if !working_path.contains(&convoy) && adjacent_regions.contains(&&convoy.region) {
let mut next_path = working_path.clone();
next_path.push(&convoy);
let mut steps =
route_steps(map, convoys, convoy.region.province(), dest, next_path);
if !steps.is_empty() {
paths.append(&mut steps);
}
}
}
paths
}
}
pub fn routes<'a>(
ctx: &Context<'a, impl Adjudicate>,
state: &mut ResolverState<'a>,
mv_ord: &MappedMainOrder,
) -> Result<Vec<Vec<&'a MappedMainOrder>>, ConvoyRouteError> {
if mv_ord.unit_type == UnitType::Fleet {
Err(ConvoyRouteError::CanOnlyConvoyArmy)
} else if let Some(dst) = mv_ord.move_dest() {
let mut convoy_steps = vec![];
for order in ctx.orders() {
if is_convoy_for(order, mv_ord) && state.resolve(ctx, order).into() {
convoy_steps.push(order);
}
}
Ok(route_steps(
ctx.world_map,
&convoy_steps,
mv_ord.region.province(),
dst.province(),
vec![],
))
} else {
Err(ConvoyRouteError::CanOnlyConvoyMove)
}
}
pub fn route_exists<'a>(
ctx: &Context<'a, impl Adjudicate>,
state: &mut ResolverState<'a>,
mv_ord: &MappedMainOrder,
) -> bool {
routes(ctx, state, mv_ord)
.map(|r| !r.is_empty())
.unwrap_or(false)
}
#[cfg(test)]
mod test {
use crate::geo::{self, ProvinceKey, RegionKey};
use crate::judge::MappedMainOrder;
use crate::order::{ConvoyedMove, Order};
use crate::UnitType;
fn convoy(l: &str, f: &str, t: &str) -> MappedMainOrder {
Order::new(
"eng".into(),
UnitType::Fleet,
RegionKey::new(String::from(l), None),
ConvoyedMove::new(
RegionKey::new(String::from(f), None),
RegionKey::new(String::from(t), None),
)
.into(),
)
}
#[test]
fn pathfinder() {
let convoys = vec![
convoy("ska", "lon", "swe"),
convoy("eng", "lon", "swe"),
convoy("nth", "lon", "swe"),
convoy("nwg", "lon", "swe"),
];
let routes = super::route_steps(
geo::standard_map(),
&convoys.iter().collect::<Vec<_>>(),
&ProvinceKey::new("lon"),
&ProvinceKey::new("swe"),
vec![],
);
for r in &routes {
println!("CHAIN");
for o in r.iter() {
println!(" {}", o);
}
}
assert_eq!(2, routes.len());
}
}