diff options
Diffstat (limited to 'planetwars-rules/src/rules.rs')
-rw-r--r-- | planetwars-rules/src/rules.rs | 192 |
1 files changed, 192 insertions, 0 deletions
diff --git a/planetwars-rules/src/rules.rs b/planetwars-rules/src/rules.rs new file mode 100644 index 0000000..587098f --- /dev/null +++ b/planetwars-rules/src/rules.rs @@ -0,0 +1,192 @@ +/// The planet wars game rules. +#[derive(Debug)] +pub struct PwState { + pub players: Vec<Player>, + pub planets: Vec<Planet>, + pub expeditions: Vec<Expedition>, + // How many expeditions were already dispatched. + // This is needed for assigning expedition identifiers. + pub expedition_num: u64, + pub turn_num: u64, + pub max_turns: u64, +} + +#[derive(Debug)] +pub struct Player { + pub id: usize, + pub alive: bool, +} + +#[derive(Debug)] +pub struct Fleet { + pub owner: Option<usize>, + pub ship_count: u64, +} + +#[derive(Debug)] +pub struct Planet { + pub id: usize, + pub name: String, + pub fleets: Vec<Fleet>, + pub x: f64, + pub y: f64, +} + +#[derive(Debug)] +pub struct Expedition { + pub id: u64, + pub origin: usize, + pub target: usize, + pub fleet: Fleet, + pub turns_remaining: u64, +} + +#[derive(Debug)] +pub struct Dispatch { + pub origin: usize, + pub target: usize, + pub ship_count: u64, +} + +impl PwState { + pub fn dispatch(&mut self, dispatch: &Dispatch) { + let distance = self.planets[dispatch.origin].distance(&self.planets[dispatch.target]); + + let origin = &mut self.planets[dispatch.origin]; + origin.fleets[0].ship_count -= dispatch.ship_count; + + let expedition = Expedition { + id: self.expedition_num, + origin: dispatch.origin, + target: dispatch.target, + turns_remaining: distance, + fleet: Fleet { + owner: origin.owner(), + ship_count: dispatch.ship_count, + }, + }; + + // increment counter + self.expedition_num += 1; + self.expeditions.push(expedition); + } + + // Play one step of the game + pub fn step(&mut self) { + self.turn_num += 1; + + // Initially mark all players dead, re-marking them as alive once we + // encounter a sign of life. + for player in self.players.iter_mut() { + player.alive = false; + } + + self.step_expeditions(); + self.resolve_combat(); + } + + pub fn repopulate(&mut self) { + for planet in self.planets.iter_mut() { + if planet.owner().is_some() { + planet.fleets[0].ship_count += 1; + } + } + } + + fn step_expeditions(&mut self) { + let mut i = 0; + let exps = &mut self.expeditions; + while i < exps.len() { + // compare with 1 to avoid issues with planet distance 0 + if exps[i].turns_remaining <= 1 { + // remove expedition from expeditions, and add to fleet + let exp = exps.swap_remove(i); + let planet = &mut self.planets[exp.target]; + planet.orbit(exp.fleet); + } else { + exps[i].turns_remaining -= 1; + if let Some(owner_num) = exps[i].fleet.owner { + // owner has an expedition in progress; this is a sign of life. + self.players[owner_num].alive = true; + } + + // proceed to next expedition + i += 1; + } + } + } + + fn resolve_combat(&mut self) { + for planet in self.planets.iter_mut() { + planet.resolve_combat(); + if let Some(owner_num) = planet.owner() { + // owner owns a planet; this is a sign of life. + self.players[owner_num].alive = true; + } + } + } + + pub fn is_finished(&self) -> bool { + let remaining = self.players.iter().filter(|p| p.alive).count(); + return remaining < 2 || self.turn_num >= self.max_turns; + } + + pub fn living_players(&self) -> Vec<usize> { + self.players + .iter() + .filter_map(|p| if p.alive { Some(p.id) } else { None }) + .collect() + } +} + +impl Planet { + pub fn owner(&self) -> Option<usize> { + self.fleets.first().and_then(|f| f.owner) + } + + pub fn ship_count(&self) -> u64 { + self.fleets.first().map_or(0, |f| f.ship_count) + } + + /// Make a fleet orbit this planet. + fn orbit(&mut self, fleet: Fleet) { + // If owner already has a fleet present, merge + for other in self.fleets.iter_mut() { + if other.owner == fleet.owner { + other.ship_count += fleet.ship_count; + return; + } + } + // else, add fleet to fleets list + self.fleets.push(fleet); + } + + fn resolve_combat(&mut self) { + // The player owning the largest fleet present will win the combat. + // Here, we resolve how many ships he will have left. + // note: in the current implementation, we could resolve by doing + // winner.ship_count -= second_largest.ship_count, but this does not + // allow for simple customizations (such as changing combat balance). + + self.fleets + .sort_by(|a, b| a.ship_count.cmp(&b.ship_count).reverse()); + while self.fleets.len() > 1 { + let fleet = self.fleets.pop().unwrap(); + // destroy some ships + for other in self.fleets.iter_mut() { + other.ship_count -= fleet.ship_count; + } + + // remove dead fleets + while self.fleets.last().map(|f| f.ship_count) == Some(0) { + self.fleets.pop(); + } + } + } + + fn distance(&self, other: &Planet) -> u64 { + let dx = self.x - other.x; + let dy = self.y - other.y; + return (dx.powi(2) + dy.powi(2)).sqrt().ceil() as u64; + } +} |