use super::{Path, Route, Step, StopLocation, Visit};
use n18hex::HexFace;
use n18map::{HexAddress, Map};
use n18tile::{Connection, Tile};
use std::collections::BTreeSet;
pub enum Error {
InvalidHexAddress(String),
NoTileAtHex(HexAddress),
NoTileAdjacent(HexAddress, HexFace),
InvalidCity(HexAddress, usize),
InvalidDit(HexAddress, usize),
NotConnected(HexAddress, Connection, Connection),
}
pub type Result<T> = std::result::Result<T, Error>;
impl std::fmt::Debug for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use Error::*;
match self {
InvalidHexAddress(s) => write!(f, "Invalid hex address {}", s),
NoTileAtHex(addr) => write!(f, "No tile at hex {}", addr),
NoTileAdjacent(addr, face) => {
write!(f, "No tile adjacent to hex {}, face {:?}", addr, face)
}
InvalidCity(addr, ix) => {
write!(f, "No city #{} at hex {}", ix, addr)
}
InvalidDit(addr, ix) => {
write!(f, "No dit #{} at hex {}", ix, addr)
}
NotConnected(addr, _src, dest) => match dest {
Connection::City { ix } => {
write!(f, "No connection to city #{} on hex {}", ix, addr)
}
Connection::Dit { ix } => {
write!(f, "No connection to dit #{} on hex {}", ix, addr)
}
Connection::Face { face } => {
write!(f, "No connection to {:?} on hex {}", face, addr)
}
Connection::Track { ix, .. } => write!(
f,
"No connection to track #{} on hex {}",
ix, addr
),
},
}
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl std::error::Error for Error {}
pub struct RouteBuilder<'a> {
map: &'a Map,
steps: Vec<Step>,
visits: Vec<Visit>,
num_visits: usize,
num_cities: usize,
num_dits: usize,
num_hexes: usize,
}
fn edge_to_tile_face(
map: &Map,
addr: HexAddress,
face: HexFace,
) -> Result<HexFace> {
let rotn = map
.hex_state(addr)
.ok_or(Error::NoTileAtHex(addr))?
.rotation();
let num_cw_turns = rotn.count_turns();
let mut tile_face = face;
for _ in 0..num_cw_turns {
tile_face = tile_face.anti_clockwise()
}
Ok(tile_face)
}
impl<'a> RouteBuilder<'a> {
fn new(map: &'a Map, start: Step) -> Self {
let (stop, cities, dits) = match start.conn {
Connection::City { ix } => {
(Some(StopLocation::City { ix }), 1, 0)
}
Connection::Dit { ix } => (Some(StopLocation::Dit { ix }), 0, 1),
Connection::Track { .. } => (None, 0, 0),
Connection::Face { .. } => (None, 0, 0),
};
let initial_visit = if let Some(stop) = stop {
let visit = Visit {
addr: start.addr,
revenue: 1,
visits: stop,
};
vec![visit]
} else {
vec![]
};
RouteBuilder {
map,
steps: vec![start],
visits: initial_visit,
num_visits: 1,
num_cities: cities,
num_dits: dits,
num_hexes: 1,
}
}
pub fn from_tile_face(
map: &'a Map,
addr: HexAddress,
face: HexFace,
) -> Result<Self> {
map.tile_at(addr).ok_or(Error::NoTileAtHex(addr))?;
let start = Step {
addr,
conn: Connection::Face { face },
};
Ok(Self::new(map, start))
}
pub fn from_edge(
map: &'a Map,
addr: HexAddress,
face: HexFace,
) -> Result<Self> {
let tile_face = edge_to_tile_face(map, addr, face)?;
Self::from_tile_face(map, addr, tile_face)
}
pub fn from_city(
map: &'a Map,
addr: HexAddress,
ix: usize,
) -> Result<Self> {
let tile = map.tile_at(addr).ok_or(Error::NoTileAtHex(addr))?;
if tile.cities().len() <= ix {
return Err(Error::InvalidCity(addr, ix));
}
let start = Step {
addr,
conn: Connection::City { ix },
};
Ok(Self::new(map, start))
}
pub fn from_dit(
map: &'a Map,
addr: HexAddress,
ix: usize,
) -> Result<Self> {
let tile = map.tile_at(addr).ok_or(Error::NoTileAtHex(addr))?;
if tile.dits().len() <= ix {
return Err(Error::InvalidDit(addr, ix));
}
let start = Step {
addr,
conn: Connection::Dit { ix },
};
Ok(Self::new(map, start))
}
#[allow(clippy::wrong_self_convention)]
pub fn to_tile_face(mut self, face: HexFace) -> Result<Self> {
let curr = self.steps.last().unwrap();
let mut new_steps =
self.find_steps_to(curr, Connection::Face { face })?;
self.steps.append(&mut new_steps);
self.num_hexes += 1;
Ok(self)
}
#[allow(clippy::wrong_self_convention)]
pub fn to_edge(self, face: HexFace) -> Result<Self> {
let curr = self.steps.last().unwrap();
let addr = curr.addr;
let tile_face = edge_to_tile_face(self.map, addr, face)?;
self.to_tile_face(tile_face)
}
#[allow(clippy::wrong_self_convention)]
pub fn to_city(mut self, ix: usize, stop: bool) -> Result<Self> {
let curr = self.steps.last().unwrap();
let addr = curr.addr;
let mut new_steps =
self.find_steps_to(curr, Connection::City { ix })?;
self.steps.append(&mut new_steps);
let revenue = if stop { 1 } else { 0 };
self.num_cities += 1;
self.num_visits += 1;
self.visits.push(Visit {
addr,
revenue,
visits: StopLocation::City { ix },
});
Ok(self)
}
#[allow(clippy::wrong_self_convention)]
pub fn to_dit(mut self, ix: usize, stop: bool) -> Result<Self> {
let curr = self.steps.last().unwrap();
let addr = curr.addr;
let mut new_steps =
self.find_steps_to(curr, Connection::Dit { ix })?;
self.steps.append(&mut new_steps);
let revenue = if stop { 1 } else { 0 };
self.num_dits += 1;
self.num_visits += 1;
self.visits.push(Visit {
addr,
revenue,
visits: StopLocation::Dit { ix },
});
Ok(self)
}
pub fn into_path(self) -> Path {
Path {
steps: self.steps,
conflicts: BTreeSet::new(),
route_conflicts: crate::conflict::RouteConflicts::new(),
visits: self.visits,
num_visits: self.num_visits,
num_cities: self.num_cities,
num_dits: self.num_dits,
num_hexes: self.num_hexes,
revenue: 0,
}
}
pub fn into_route(self) -> Route {
Route {
steps: self.steps,
visits: self.visits,
}
}
fn find_steps_to(
&self,
src: &Step,
dest: Connection,
) -> Result<Vec<Step>> {
let mut seen: BTreeSet<Connection> = BTreeSet::new();
let tile = self
.map
.tile_at(src.addr)
.ok_or(Error::NoTileAtHex(src.addr))?;
let mut steps = self
.depth_first_search(
&mut seen, tile, &src.addr, &src.conn, &dest, 0,
)
.ok_or(Error::NotConnected(src.addr, src.conn, dest))?;
if let Connection::Face { face } = dest {
let adj = self
.map
.adjacent_face(src.addr, face)
.ok_or(Error::NoTileAdjacent(src.addr, face))?;
let (adj_addr, adj_face, _adj_tile) = adj;
let conn = Connection::Face { face: adj_face };
let step = Step {
addr: adj_addr,
conn,
};
steps.push(step)
}
Ok(steps)
}
fn depth_first_search(
&self,
seen: &mut BTreeSet<Connection>,
tile: &Tile,
addr: &HexAddress,
src: &Connection,
dest: &Connection,
level: usize,
) -> Option<Vec<Step>> {
let mut found: Option<Vec<Step>> = None;
for conn in tile.connections(src).unwrap() {
if seen.contains(conn) {
continue;
} else {
seen.insert(*conn)
};
let other_end = conn.other_end().unwrap_or(*conn);
let conn = if conn != &other_end {
if seen.contains(&other_end) {
continue;
} else {
seen.insert(other_end);
&other_end
}
} else {
conn
};
if conn == dest {
found = Some(vec![Step {
addr: *addr,
conn: *conn,
}]);
break;
} else {
let steps_opt = self.depth_first_search(
seen,
tile,
addr,
conn,
dest,
level + 1,
);
if let Some(mut steps) = steps_opt {
steps.insert(
0,
Step {
addr: *addr,
conn: *conn,
},
);
found = Some(steps);
break;
}
}
}
found
}
}