194 lines
5.4 KiB
Rust
194 lines
5.4 KiB
Rust
|
/// 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;
|
||
|
}
|
||
|
}
|