diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..50b44cf --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "adventure" +version = "0.1.0" +authors = ["brnrs "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/battle/actor.rs b/src/battle/actor.rs new file mode 100644 index 0000000..3ca5e51 --- /dev/null +++ b/src/battle/actor.rs @@ -0,0 +1,64 @@ +use std::cell::RefCell; +use std::num::NonZeroU32; +use std::rc::Rc; + +pub type ActorId = u32; +pub type TeamId = u32; +pub type AiId = u32; +pub type SkillId = u32; + +pub struct Actor { + pub id: ActorId, + pub team_id: TeamId, + pub ai_id: Option, + pub name: String, + pub state: ActorState, + pub initiative: NonZeroU32, + pub skills: Vec, +} + +type Hp = std::num::NonZeroU32; +pub enum ActorState { + Dead, + Alive(Hp), +} +impl ActorState { + pub fn is_dead(&self) -> bool { + match self { + Self::Dead => true, + _ => false, + } + } + + pub fn is_alive(&self) -> bool { + match self { + Self::Alive(_) => true, + _ => false, + } + } +} + +pub struct TeamInfo(Option); + +type ActorWrp = Rc>; +pub struct Team { + pub id: TeamId, + pub info: TeamInfo, + pub members: Box>, +} + +pub trait ActorDb<'a> { + fn lookup_actor(&self, id: ActorId) -> Option; + + fn lookup_team_info(&self, id: TeamId) -> Option; + + fn lookup_team(&self, id: TeamId) -> Option; + + fn lookup_actor_ids(&self) -> Box>; + + fn lookup_team_ids(&self) -> Box>; + + fn lookup_actors(&self) -> Box>; + + fn lookup_teams(&self) -> Box>; +} diff --git a/src/battle/mod.rs b/src/battle/mod.rs new file mode 100644 index 0000000..f0e76f5 --- /dev/null +++ b/src/battle/mod.rs @@ -0,0 +1,105 @@ +#![allow(dead_code,unused_variables,unused_imports,unused_mut)] + +mod actor; +mod queue; + +use actor::*; +use queue::{Queue, QueueElement}; + +use std::cell::RefCell; +use std::rc::Rc; + +fn battle( + actor_db: &dyn ActorDb, + decider: &dyn Decider, + effector: &dyn Effector, +) -> Result { + if let Err(err_msg) = check(actor_db) { + return Err(err_msg); + } + let mut queue = Queue::new(); + for actor in actor_db.lookup_actors() { + let actor = actor.borrow(); + queue.enqueue(actor.initiative.get(), actor.id); + } + + while let Some(QueueElement(incr, actor_id)) = queue.next() { + let actor = match actor_db.lookup_actor(actor_id) { + Some(actor) => actor, + None => return Err("Couldn't find actor with specified id"), + }; + let team_info = match actor_db.lookup_team_info(actor.borrow().team_id) { + Some(team_info) => team_info, + None => return Err("Couldn't find team with specified id"), + }; + + let decision = decider.decide(actor_db, actor.clone())?; + effector.effect(actor_db, actor.clone(), decision)?; + + if at_most_one_team_left_alive(actor_db) { + break; + } + } + + Err("not implemented") +} + +fn check(actor_db: &dyn ActorDb) -> Result<(), &'static str> { + let stats = actor_db + .lookup_teams() + .map(|mut team| team.members.next().is_none()) + .fold((0, 0), |(non_empty, empty), is_empty| { + if is_empty { + (non_empty, empty + 1) + } else { + (non_empty + 1, empty) + } + }); + + match stats { + (0, 0) => Err("No teams provided for battle"), + (1, 0) => Err("More than one team must be provided for battle"), + (_, 0) => Ok(()), + (_, empty) => Err("Empty teams cannot participate in battle"), + } +} + +fn at_most_one_team_left_alive(actor_db: &dyn ActorDb) -> bool { + let (alive, _) = actor_db + .lookup_teams() + .map(|mut team| team.members.all(|actor| actor.borrow().state.is_dead())) + .fold((0, 0), |(alive, dead), all_dead| { + if all_dead { + (alive, dead + 1) + } else { + (alive + 1, dead) + } + }); + + alive <= 1 +} + +enum Target { + Oneself, + Actor(ActorId), + Team(TeamId), +} + +struct Decision(SkillId, Target); + +trait Decider { + fn decide( + &self, + actor_db: &dyn ActorDb, + actor: Rc>, + ) -> Result; +} + +trait Effector { + fn effect( + &self, + actor_db: &dyn ActorDb, + actor: Rc>, + decision: Decision, + ) -> Result<(), &'static str>; +} diff --git a/src/battle/queue.rs b/src/battle/queue.rs new file mode 100644 index 0000000..5d00c3c --- /dev/null +++ b/src/battle/queue.rs @@ -0,0 +1,211 @@ +type Increment = u32; +type Sum = u64; + +#[derive(Debug, PartialEq)] +pub struct QueueElement(pub Increment, pub T); + +pub struct Queue { + container: Vec>, + sum: Sum, +} + +impl Queue { + pub fn new() -> Queue { + Queue { + container: Vec::new(), + sum: 0, + } + } + + pub fn next(&mut self) -> Option> { + if self.container.is_empty() { + None + } else { + let next = self.container.remove(0); + if self.sum < next.0.into() { + panic!("Sum of increments should not be smaller than a single increment"); + } else { + self.sum -= next.0 as u64; + Some(next) + } + } + } + + pub fn enqueue(&mut self, mut incr: Increment, t: T) { + let mut insert_pos = 0; + for (i, el) in self.container.iter_mut().enumerate() { + if el.0 > incr { + el.0 -= incr; + break; + } + + insert_pos = i + 1; + if el.0 < incr { + incr -= el.0; + } + } + if insert_pos == self.container.len() { + self.sum += incr as u64; + } + self.container.insert(insert_pos, QueueElement(incr, t)); + } + + pub fn from, F: Fn(&T) -> Increment>( + iter: I, + key_extractor: F, + ) -> Queue { + let mut queue = Queue::new(); + iter.into_iter() + .map(move |t| (key_extractor(&t), t)) + .for_each(|(incr, t)| queue.enqueue(incr, t)); + queue + } + + pub fn get_container(&self) -> &[QueueElement] { + &self.container + } +} + +impl<'a, T: 'a + Clone> Queue { + pub fn from_clone, F: Fn(&T) -> Increment>( + iter: I, + key_extractor: F, + ) -> Queue { + let mut queue = Queue::new(); + iter.into_iter() + .map(|t| (key_extractor(t), t.clone())) + .for_each(|(incr, t)| queue.enqueue(incr, t)); + queue + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn retrieve_from_empty() { + // Given + let mut queue: Queue<&'static str> = Queue::new(); + + // When + let no_more = queue.next(); + + // Then + assert_eq!(no_more, None); + } + + #[test] + fn retrieve_only_one() { + // Given + let mut queue: Queue<&'static str> = Queue::new(); + queue.enqueue(1, "A"); + + // When + let first = queue.next(); + let no_more = queue.next(); + + // Then + assert_eq!(first, Some(QueueElement(1, "A"))); + assert_eq!(no_more, None); + } + + #[test] + fn inserting_two_reversing_order_and_adjusting_increments() { + // Given + let mut queue: Queue<&'static str> = Queue::new(); + queue.enqueue(2, "A"); + queue.enqueue(1, "B"); + + // When + let first = queue.next(); + let second = queue.next(); + let no_more = queue.next(); + + // Then + assert_eq!(first, Some(QueueElement(1, "B"))); + assert_eq!(second, Some(QueueElement(1, "A"))); + assert_eq!(no_more, None); + } + + #[test] + fn insert_two_maintaining_order_and_adjusting_increments() { + // Given + let mut queue: Queue<&'static str> = Queue::new(); + queue.enqueue(1, "A"); + queue.enqueue(2, "B"); + + // When + let first = queue.next(); + let second = queue.next(); + let no_more = queue.next(); + + // Then + assert_eq!(first, Some(QueueElement(1, "A"))); + assert_eq!(second, Some(QueueElement(1, "B"))); + assert_eq!(no_more, None); + } + + #[test] + fn insert_three_maintaining_order_and_adjusting_increments() { + // Given + let mut queue: Queue<&'static str> = Queue::new(); + queue.enqueue(1, "A"); + queue.enqueue(2, "B"); + queue.enqueue(3, "C"); + + // When + let first = queue.next(); + let second = queue.next(); + let third = queue.next(); + let no_more = queue.next(); + + // Then + assert_eq!(first, Some(QueueElement(1, "A"))); + assert_eq!(second, Some(QueueElement(1, "B"))); + assert_eq!(third, Some(QueueElement(1, "C"))); + assert_eq!(no_more, None); + } + + #[test] + fn insert_three_with_last_in_the_middle_and_adjusting_increments() { + // Given + let mut queue: Queue<&'static str> = Queue::new(); + queue.enqueue(1, "A"); + queue.enqueue(3, "B"); + queue.enqueue(2, "C"); + + // When + let first = queue.next(); + let second = queue.next(); + let third = queue.next(); + let no_more = queue.next(); + + // Then + assert_eq!(first, Some(QueueElement(1, "A"))); + assert_eq!(second, Some(QueueElement(1, "C"))); + assert_eq!(third, Some(QueueElement(1, "B"))); + assert_eq!(no_more, None); + } + + #[test] + fn insert_three_reversing_order_and_adjusting_increments() { + // Given + let mut queue: Queue<&'static str> = Queue::new(); + queue.enqueue(3, "A"); + queue.enqueue(2, "B"); + queue.enqueue(1, "C"); + + // When + let first = queue.next(); + let second = queue.next(); + let third = queue.next(); + let no_more = queue.next(); + + // Then + assert_eq!(first, Some(QueueElement(1, "C"))); + assert_eq!(second, Some(QueueElement(1, "B"))); + assert_eq!(third, Some(QueueElement(1, "A"))); + assert_eq!(no_more, None); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..d53640b --- /dev/null +++ b/src/main.rs @@ -0,0 +1,5 @@ +mod battle; + +fn main() { + println!("Hello, world!"); +}