feature/battle
brnrs 5 years ago
parent 0a02f83c27
commit d594185d1d
  1. 13
      .vscode/extensions.json
  2. 132
      src/battle/actor.rs
  3. 17
      src/battle/base.rs
  4. 243
      src/battle/mod.rs
  5. 248
      src/battle/queue.rs
  6. 126
      src/battle/skill.rs
  7. 157
      src/battle/uic/mod.rs
  8. 99
      src/context.rs
  9. 81
      src/main.rs

@ -0,0 +1,13 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [
]
}

@ -1,64 +1,124 @@
use std::cell::RefCell; use super::base::*;
use std::num::NonZeroU32;
use std::rc::Rc;
pub type ActorId = u32; use std::collections::{BTreeMap, HashMap};
pub type TeamId = u32;
pub type AiId = u32;
pub type SkillId = u32;
pub struct Actor { pub struct Actor {
pub id: ActorId, pub id: ActorId,
pub team_id: TeamId, pub team_id: TeamId,
pub ai_id: Option<AiId>,
pub name: String, pub name: String,
pub state: ActorState, pub stats: ActorStats,
pub initiative: NonZeroU32,
pub skills: Vec<SkillId>, pub skills: Vec<SkillId>,
pub ai: Option<AiId>,
} }
type Hp = std::num::NonZeroU32; pub struct ActorStats {
pub enum ActorState { pub max_hp: Hp,
Dead, pub initiative: Increment,
Alive(Hp),
}
impl ActorState {
pub fn is_dead(&self) -> bool {
match self {
Self::Dead => true,
_ => false,
} }
pub struct ActorDb {
pub container: BTreeMap<ActorId, Actor>,
} }
pub fn is_alive(&self) -> bool { impl ActorDb {
match self { #[allow(dead_code)]
Self::Alive(_) => true, pub fn from(actors: Vec<Actor>) -> ActorDb {
_ => false, ActorDb {
container: actors.into_iter().map(|actor| (actor.id, actor)).collect(),
} }
} }
#[allow(dead_code)]
pub fn get(&self, id: ActorId) -> Option<&Actor> {
self.container.get(&id)
} }
pub struct TeamInfo(Option<AiId>); pub fn iter(&self) -> std::collections::btree_map::Iter<ActorId, Actor> {
self.container.iter()
}
}
type ActorWrp = Rc<RefCell<Actor>>;
pub struct Team { pub struct Team {
pub id: TeamId, pub id: TeamId,
pub info: TeamInfo, pub actors: Vec<ActorId>,
pub members: Box<dyn Iterator<Item = ActorWrp>>, pub ai: Option<AiId>,
}
pub struct TeamDb {
container: BTreeMap<TeamId, Team>,
}
pub struct TeamAiOverrides(pub Vec<(TeamId, AiId)>);
#[allow(dead_code, unused_variables)]
impl TeamDb {
pub fn from(actor_db: &ActorDb, ai_overrides: TeamAiOverrides) -> TeamDb {
let mut map: BTreeMap<TeamId, Team> = BTreeMap::new();
actor_db.iter().for_each(|(actor_id, actor)| {
let team_id = actor.team_id;
if let Some(entry) = map.get_mut(&team_id) {
entry.actors.push(*actor_id);
} else {
let ai = ai_overrides
.0
.iter()
.find(|(tid, aid)| *tid == team_id)
.map(|(_, aid)| *aid);
map.insert(
team_id,
Team {
id: team_id,
actors: vec![*actor_id],
ai,
},
);
}
});
TeamDb { container: map }
} }
pub trait ActorDb<'a> { pub fn get(&self, id: TeamId) -> Option<&Team> {
fn lookup_actor(&self, id: ActorId) -> Option<ActorWrp>; self.container.get(&id)
}
fn lookup_team_info(&self, id: TeamId) -> Option<TeamInfo>; pub fn iter(&self) -> std::collections::btree_map::Iter<TeamId, Team> {
self.container.iter()
}
}
fn lookup_team(&self, id: TeamId) -> Option<Team>; #[allow(dead_code)]
pub struct ActorStateDb {
container: HashMap<ActorId, ActorState>,
}
fn lookup_actor_ids(&self) -> Box<dyn Iterator<Item = ActorId>>; #[allow(dead_code, unused_variables)]
impl ActorStateDb {
pub fn new() -> ActorStateDb {
ActorStateDb {
container: HashMap::new(),
}
}
fn lookup_team_ids(&self) -> Box<dyn Iterator<Item = TeamId>>; pub fn init(&mut self, actor: &Actor) {
self.container
.insert(actor.id, ActorState::Alive(actor.stats.max_hp))
.unwrap_none()
}
fn lookup_actors(&self) -> Box<dyn Iterator<Item = ActorWrp>>; pub fn get(&self, actor_id: ActorId) -> Option<ActorState> {
self.container.get(&actor_id).cloned()
}
pub fn set(&mut self, actor_id: ActorId, state: ActorState) {
self.container.insert(actor_id, state);
}
}
fn lookup_teams(&self) -> Box<dyn Iterator<Item = Team>>; #[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub enum ActorState {
Dead,
Alive(Hp),
} }

@ -0,0 +1,17 @@
pub type ActorId = u32;
#[allow(dead_code)]
pub type TeamId = u32;
#[allow(dead_code)]
pub type SkillId = u32;
pub type Increment = u32;
pub type Hp = u32;
pub type AiId = u32;
#[allow(dead_code)]
pub enum SkillTarget {
None,
Actor(ActorId),
Team(TeamId),
}
pub struct EffectTarget(pub ActorId);

@ -1,105 +1,202 @@
#![allow(dead_code,unused_variables,unused_imports,unused_mut)] pub mod actor;
pub mod base;
pub mod queue;
pub mod skill;
pub mod uic;
mod actor; use crate::context::*;
mod queue; use crate::require;
use actor::*; use actor::*;
use queue::{Queue, QueueElement}; use base::*;
use queue::*;
use std::cell::RefCell; use skill::*;
use std::rc::Rc; use uic::*;
#[allow(dead_code, unused_variables)]
pub fn run_battle(context: Context) -> Result<(), String> {
require!(context; ActorDb, ActorStateDb, TeamDb, DeciderDb, SkillDb, Uic)?;
let uic = context.get::<Uic>().unwrap();
init_states(&context);
uic.start_battle(&context);
let mut queue = create_queue(&context.get::<ActorDb>().unwrap());
uic.create_queue(&context, &queue);
let battle_result = loop {
let actor_id = match queue.dequeue() {
None => break BattleResult::AllDead,
Some((_, actor_id)) => actor_id,
};
uic.start_turn(&context, actor_id);
let (skill_id, target) = determine_decision(&context, actor_id)?;
let effects = determine_effects(&context, actor_id, skill_id, target)?;
let results = apply_effects(&context, effects)?;
if !results.0.is_empty() {
if let Some(battle_result) = check_result(&context) {
break battle_result;
}
}
let actor_db = context.get::<ActorDb>().unwrap();
let actor_state_db = context.get::<ActorStateDb>().unwrap();
if let ActorState::Alive(_) = actor_state_db.get(actor_id).unwrap() {
let actor = actor_db.get(actor_id).unwrap();
queue.enqueue((actor.stats.initiative, actor_id));
}
};
fn battle( println!("Result: {:?}", battle_result);
actor_db: &dyn ActorDb, Ok(())
decider: &dyn Decider,
effector: &dyn Effector,
) -> Result<u32, &'static str> {
if let Err(err_msg) = check(actor_db) {
return Err(err_msg);
} }
fn create_queue(actor_db: &ActorDb) -> Queue {
let mut queue = Queue::new(); let mut queue = Queue::new();
for actor in actor_db.lookup_actors() { actor_db
let actor = actor.borrow(); .iter()
queue.enqueue(actor.initiative.get(), actor.id); .for_each(|(id, actor)| queue.enqueue((actor.stats.initiative, *id)));
queue
} }
while let Some(QueueElement(incr, actor_id)) = queue.next() { fn determine_ai(
let actor = match actor_db.lookup_actor(actor_id) { context: &Context,
Some(actor) => actor, actor_id: base::ActorId,
None => return Err("Couldn't find actor with specified id"), default: base::AiId,
}; ) -> Result<base::AiId, String> {
let team_info = match actor_db.lookup_team_info(actor.borrow().team_id) { require!(context; actor::ActorDb, actor::TeamDb)?;
Some(team_info) => team_info,
None => return Err("Couldn't find team with specified id"), let actor_db: &actor::ActorDb = &*context.get().unwrap();
}; let actor = actor_db
.get(actor_id)
.ok_or_else(|| String::from("Actor not found"))?;
if let Some(ai_id) = actor.ai {
return Ok(ai_id);
}
let decision = decider.decide(actor_db, actor.clone())?; let team_db: &actor::TeamDb = &*context.get().unwrap();
effector.effect(actor_db, actor.clone(), decision)?; let team = team_db
.get(actor.team_id)
.ok_or_else(|| String::from("Team not found"))?;
if let Some(ai_id) = team.ai {
return Ok(ai_id);
}
if at_most_one_team_left_alive(actor_db) { Ok(default)
break;
} }
fn determine_decision(
context: &Context,
actor_id: ActorId,
) -> Result<(SkillId, SkillTarget), String> {
let decider_db: &DeciderDb = &*context.get().unwrap();
let ai_id = determine_ai(&context, actor_id, 0)?;
let decider = decider_db
.get(ai_id)
.ok_or_else(|| String::from("Decider not found"))?;
decider.decide(context, actor_id)
} }
Err("not implemented") fn determine_effects(
context: &Context,
actor_id: ActorId,
skill_id: SkillId,
target: SkillTarget,
) -> Result<Vec<SkillEffect>, String> {
let skill_db: &SkillDb = &*context.get().unwrap();
let skill = skill_db
.get(skill_id)
.ok_or_else(|| String::from("Skill not found"))?;
skill.func.apply(context, actor_id, target)
} }
fn check(actor_db: &dyn ActorDb) -> Result<(), &'static str> { struct DeadList(Vec<ActorId>);
let stats = actor_db
.lookup_teams() fn apply_effects(context: &Context, effects: Vec<SkillEffect>) -> Result<DeadList, String> {
.map(|mut team| team.members.next().is_none()) let mut dead_vec = Vec::new();
.fold((0, 0), |(non_empty, empty), is_empty| { for effect in effects {
if is_empty { let actor_db = context.get::<ActorDb>().unwrap();
(non_empty, empty + 1) let uic = context.get::<Uic>().unwrap();
let mut actor_state_db = context.get_mut::<ActorStateDb>().unwrap();
match effect {
SkillEffect::Damage(dmg, EffectTarget(id)) => {
// TODO: error handling
let state = actor_state_db.get(id).unwrap();
if let ActorState::Alive(hp_left) = state {
let new_hp = hp_left.saturating_sub(dmg);
let new_state = if new_hp > 0 {
ActorState::Alive(new_hp)
} else { } else {
(non_empty + 1, empty) dead_vec.push(id);
ActorState::Dead
};
actor_state_db.set(id, new_state);
uic.hp_effect(context, id, state, new_state);
}
} }
SkillEffect::Heal(hp, EffectTarget(id)) => {
// TODO: error handling
let max_hp = actor_db.get(id).unwrap().stats.max_hp;
let state = actor_state_db.get(id).unwrap();
if let ActorState::Alive(hp_left) = state {
let after_heal = hp_left.saturating_add(hp);
let new_state = ActorState::Alive(if after_heal > max_hp {
max_hp
} else {
after_heal
}); });
match stats { actor_state_db.set(id, new_state);
(0, 0) => Err("No teams provided for battle"), uic.hp_effect(context, id, state, new_state);
(1, 0) => Err("More than one team must be provided for battle"), }
(_, 0) => Ok(()), }
(_, empty) => Err("Empty teams cannot participate in battle"),
} }
} }
Ok(DeadList(dead_vec))
}
fn check_result(context: &Context) -> Option<BattleResult> {
let team_db = context.get::<TeamDb>().unwrap();
let actor_state_db = context.get::<ActorStateDb>().unwrap();
fn at_most_one_team_left_alive(actor_db: &dyn ActorDb) -> bool { let alive: Vec<TeamId> = team_db
let (alive, _) = actor_db .iter()
.lookup_teams() .filter_map(|(team_id, team)| {
.map(|mut team| team.members.all(|actor| actor.borrow().state.is_dead())) if team.actors.iter().any(|actor_id| {
.fold((0, 0), |(alive, dead), all_dead| { if let ActorState::Dead = actor_state_db.get(*actor_id).unwrap() {
if all_dead { false
(alive, dead + 1)
} else { } else {
(alive + 1, dead) true
} }
}); }) {
Some(*team_id)
alive <= 1 } else {
None
} }
})
.collect();
enum Target { match alive.len() {
Oneself, 0 => Some(BattleResult::AllDead),
Actor(ActorId), 1 => Some(BattleResult::TeamWin(alive[0])),
Team(TeamId), _ => None,
}
} }
struct Decision(SkillId, Target); fn init_states(context: &Context) {
let actor_db = context.get::<ActorDb>().unwrap();
let mut actor_state_db = context.get_mut::<ActorStateDb>().unwrap();
trait Decider { for (_, actor) in actor_db.iter() {
fn decide( actor_state_db.init(actor)
&self, }
actor_db: &dyn ActorDb,
actor: Rc<RefCell<Actor>>,
) -> Result<Decision, &'static str>;
} }
trait Effector { #[allow(dead_code)]
fn effect( #[derive(Debug)]
&self, enum BattleResult {
actor_db: &dyn ActorDb, TeamWin(base::TeamId),
actor: Rc<RefCell<Actor>>, AllDead,
decision: Decision,
) -> Result<(), &'static str>;
} }

@ -1,81 +1,68 @@
type Increment = u32; use super::base::*;
type Sum = u64;
#[derive(Debug, PartialEq)] pub struct Queue {
pub struct QueueElement<T>(pub Increment, pub T); container: Vec<(Increment, ActorId)>,
pub struct Queue<T> {
container: Vec<QueueElement<T>>,
sum: Sum,
} }
impl<T> Queue<T> { impl Queue {
pub fn new() -> Queue<T> { #[allow(dead_code)]
pub fn new() -> Queue {
Queue { Queue {
container: Vec::new(), container: Vec::new(),
sum: 0,
} }
} }
pub fn next(&mut self) -> Option<QueueElement<T>> { #[allow(dead_code)]
if self.container.is_empty() { pub fn enqueue(&mut self, (incr, id): (Increment, ActorId)) {
None let mut incr = incr;
} else { let mut insert = None;
let next = self.container.remove(0); for (i, (incr2, _)) in self.container.iter().enumerate() {
if self.sum < next.0.into() { if incr < *incr2 {
panic!("Sum of increments should not be smaller than a single increment"); insert = Some(i);
} else { break;
self.sum -= next.0 as u64; } else if *incr2 != 0 {
Some(next) incr -= incr2;
}
} }
} }
pub fn enqueue(&mut self, mut incr: Increment, t: T) { if let Some(ix) = insert {
let mut insert_pos = 0; self.container[ix].0 -= incr;
for (i, el) in self.container.iter_mut().enumerate() { self.container.insert(ix, (incr, id));
if el.0 > incr { } else {
el.0 -= incr; self.container.push((incr, id));
break; }
} }
insert_pos = i + 1; #[allow(dead_code)]
if el.0 < incr { pub fn dequeue(&mut self) -> Option<(Increment, ActorId)> {
incr -= el.0; if self.container.is_empty() {
None
} else {
Some(self.container.remove(0))
} }
} }
if insert_pos == self.container.len() {
self.sum += incr as u64; #[allow(dead_code)]
pub fn remove(&mut self, id: ActorId) {
let mut i = 0;
loop {
if i >= self.container.len() {
break;
} }
self.container.insert(insert_pos, QueueElement(incr, t)); if id == self.container[i].1 {
let (incr, _) = self.container.remove(i);
if i < self.container.len() {
self.container[i].0 += incr;
} else {
break;
} }
pub fn from<I: IntoIterator<Item = T>, F: Fn(&T) -> Increment>(
iter: I,
key_extractor: F,
) -> Queue<T> {
let mut queue = Queue::new();
iter.into_iter()
.map(move |t| (key_extractor(&t), t))
.for_each(|(incr, t)| queue.enqueue(incr, t));
queue
} }
i += 1;
pub fn get_container(&self) -> &[QueueElement<T>] {
&self.container
} }
} }
impl<'a, T: 'a + Clone> Queue<T> { pub fn iter(&self) -> impl Iterator<Item = &(Increment, ActorId)> {
pub fn from_clone<I: IntoIterator<Item = &'a T>, F: Fn(&T) -> Increment>( self.container.iter()
iter: I,
key_extractor: F,
) -> Queue<T> {
let mut queue = Queue::new();
iter.into_iter()
.map(|t| (key_extractor(t), t.clone()))
.for_each(|(incr, t)| queue.enqueue(incr, t));
queue
} }
} }
@ -84,128 +71,75 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn retrieve_from_empty() { fn test_enqueue_between_elements() {
// Given // Given
let mut queue: Queue<&'static str> = Queue::new(); let mut queue = Queue::new();
queue.enqueue((2, 1));
// When queue.enqueue((7, 2));
let no_more = queue.next(); queue.enqueue((3, 3));
// 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 // Expect
assert_eq!(first, Some(QueueElement(1, "B"))); // id-1 remains first
assert_eq!(second, Some(QueueElement(1, "A"))); assert_eq!(queue.dequeue(), Some((2, 1)));
assert_eq!(no_more, None); // id-3 moves before id-2, with increment relative to id-1
assert_eq!(queue.dequeue(), Some((1, 3)));
// id-2 has increment adjusted to be relative to id-3
assert_eq!(queue.dequeue(), Some((4, 2)));
// no more remain
assert_eq!(queue.dequeue(), None);
} }
#[test] #[test]
fn insert_two_maintaining_order_and_adjusting_increments() { fn test_remove_by_id_from_middle() {
// Given // Given
let mut queue: Queue<&'static str> = Queue::new(); let mut queue = Queue::new();
queue.enqueue(1, "A"); queue.enqueue((1, 1));
queue.enqueue(2, "B"); queue.enqueue((2, 2));
queue.enqueue((3, 3));
queue.enqueue((4, 4));
// When // When
let first = queue.next(); queue.remove(3);
let second = queue.next();
let no_more = queue.next();
// Then // Then expect
assert_eq!(first, Some(QueueElement(1, "A"))); assert_eq!(queue.dequeue(), Some((1, 1)));
assert_eq!(second, Some(QueueElement(1, "B"))); assert_eq!(queue.dequeue(), Some((1, 2)));
assert_eq!(no_more, None); assert_eq!(queue.dequeue(), Some((2, 4)));
} }
#[test] #[test]
fn insert_three_maintaining_order_and_adjusting_increments() { fn test_remove_by_id_from_back() {
// Given // Given
let mut queue: Queue<&'static str> = Queue::new(); let mut queue = Queue::new();
queue.enqueue(1, "A"); queue.enqueue((1, 1));
queue.enqueue(2, "B"); queue.enqueue((2, 2));
queue.enqueue(3, "C"); queue.enqueue((3, 3));
queue.enqueue((4, 4));
// When // When
let first = queue.next(); queue.remove(4);
let second = queue.next();
let third = queue.next();
let no_more = queue.next();
// Then // Then expect
assert_eq!(first, Some(QueueElement(1, "A"))); assert_eq!(queue.dequeue(), Some((1, 1)));
assert_eq!(second, Some(QueueElement(1, "B"))); assert_eq!(queue.dequeue(), Some((1, 2)));
assert_eq!(third, Some(QueueElement(1, "C"))); assert_eq!(queue.dequeue(), Some((1, 3)));
assert_eq!(no_more, None);
} }
#[test] #[test]
fn insert_three_with_last_in_the_middle_and_adjusting_increments() { fn test_remove_by_id_from_front() {
// Given // Given
let mut queue: Queue<&'static str> = Queue::new(); let mut queue = Queue::new();
queue.enqueue(1, "A"); queue.enqueue((1, 1));
queue.enqueue(3, "B"); queue.enqueue((2, 2));
queue.enqueue(2, "C"); queue.enqueue((3, 3));
queue.enqueue((4, 4));
// When // When
let first = queue.next(); queue.remove(1);
let second = queue.next();
let third = queue.next();
let no_more = queue.next();
// Then // Then expect
assert_eq!(first, Some(QueueElement(1, "A"))); assert_eq!(queue.dequeue(), Some((2, 2)));
assert_eq!(second, Some(QueueElement(1, "C"))); assert_eq!(queue.dequeue(), Some((1, 3)));
assert_eq!(third, Some(QueueElement(1, "B"))); assert_eq!(queue.dequeue(), Some((1, 4)));
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);
} }
} }

@ -0,0 +1,126 @@
use super::base::*;
use crate::context::Context;
use std::collections::HashMap;
#[allow(dead_code)]
pub struct DeciderDb {
container: HashMap<AiId, Box<dyn Decider>>,
}
#[allow(dead_code)]
impl DeciderDb {
pub fn from(deciders: Vec<(AiId, Box<dyn Decider>)>) -> DeciderDb {
DeciderDb {
container: deciders.into_iter().collect(),
}
}
pub fn get(&self, ai_id: AiId) -> Option<&dyn Decider> {
self.container.get(&ai_id).map(|x| x.as_ref())
}
}
pub trait Decider {
fn decide(
&self,
context: &Context,
actor_id: ActorId,
) -> Result<(SkillId, SkillTarget), String>;
}
pub struct HumanDecider;
impl Decider for HumanDecider {
fn decide(
&self,
context: &Context,
actor_id: ActorId,
) -> Result<(SkillId, SkillTarget), String> {
let actor_db = context.get::<super::ActorDb>().unwrap();
let skill_db = context.get::<super::SkillDb>().unwrap();
let uic = context.get::<super::Uic>().unwrap();
let actor = actor_db.get(actor_id).unwrap();
let skill_id = uic.chose_skill(context, &actor.skills);
match skill_db.get(skill_id).unwrap().accepts {
AcceptableTargetTypes::Actor(alignments) => Ok((
skill_id,
SkillTarget::Actor(uic.chose_target_actor(context, actor.team_id, alignments)),
)),
AcceptableTargetTypes::Team(aligments) => Ok((
skill_id,
SkillTarget::Team(uic.chose_target_team(context, actor.team_id, aligments)),
)),
AcceptableTargetTypes::None => Ok((skill_id, SkillTarget::None)),
}
}
}
#[allow(dead_code)]
pub enum SkillEffect {
Damage(Hp, EffectTarget),
Heal(Hp, EffectTarget),
}
#[allow(dead_code)]
#[derive(Clone, Copy)]
pub enum AcceptableTargetAlignments {
Friend,
Enemy,
All,
}
#[allow(dead_code)]
pub enum AcceptableTargetTypes {
Actor(AcceptableTargetAlignments),
Team(AcceptableTargetAlignments),
None,
}
#[allow(dead_code)]
pub struct SkillDb {
container: HashMap<SkillId, Skill>,
}
#[allow(dead_code)]
impl SkillDb {
pub fn from(skills: Vec<Skill>) -> SkillDb {
SkillDb {
container: skills.into_iter().map(|skill| (skill.id, skill)).collect(),
}
}
pub fn get(&self, skill_id: SkillId) -> Option<&Skill> {
self.container.get(&skill_id)
}
}
#[allow(dead_code)]
pub struct Skill {
pub id: SkillId,
pub name: String,
pub accepts: AcceptableTargetTypes,
pub func: Box<dyn SkillFunc>,
}
pub trait SkillFunc {
fn apply(
&self,
context: &Context,
actor_id: ActorId,
target: SkillTarget,
) -> Result<Vec<SkillEffect>, String>;
}
impl<T> SkillFunc for T
where
T: Fn(&Context, ActorId, SkillTarget) -> Result<Vec<SkillEffect>, String>,
{
fn apply(
&self,
context: &Context,
actor_id: ActorId,
target: SkillTarget,
) -> Result<Vec<SkillEffect>, String> {
self(context, actor_id, target)
}
}

@ -0,0 +1,157 @@
use super::actor::{Actor, ActorDb, ActorState, ActorStateDb, Team, TeamDb};
use super::base::{ActorId, SkillId, TeamId};
use super::queue::Queue;
use super::skill::{AcceptableTargetAlignments, Skill, SkillDb};
use crate::context::Context;
#[allow(dead_code, unused_variables)]
pub struct Uic;
#[allow(dead_code, unused_variables)]
impl Uic {
pub fn start_battle(&self, context: &Context) {
println!("Starting battle!");
}
pub fn create_queue(&self, context: &Context, queue: &Queue) {
print!("Turn Order: [");
let actor_db: &ActorDb = &*context.get().unwrap();
let mut iter = queue.iter();
if let Some((_, id)) = iter.next() {
print!("{}", actor_db.get(*id).unwrap().name);
}
for (_, id) in iter {
print!(", {}", actor_db.get(*id).unwrap().name);
}
println!("]");
}
pub fn start_turn(&self, context: &Context, actor_id: ActorId) {
let actor_db = context.get::<ActorDb>().unwrap();
let actor_state_db = context.get::<ActorStateDb>().unwrap();
let actor = actor_db.get(actor_id).unwrap();
let actor_state = actor_state_db.get(actor_id).unwrap();
println!("Turn of {}", actor.name);
println!(
"Hp: {}/{}",
if let ActorState::Alive(hp) = actor_state {
hp
} else {
panic!()
},
actor.stats.max_hp
);
}
pub fn chose_skill(&self, context: &Context, skill_ids: &[SkillId]) -> SkillId {
let skill_db = context.get::<SkillDb>().unwrap();
let skills: Vec<&Skill> = skill_ids
.iter()
.map(|id| skill_db.get(*id).unwrap())
.collect();
println!("Choose skill:");
for (i, skill) in skills.iter().enumerate() {
println!("{}: {}", i, skill.name);
}
let stdin = std::io::stdin();
let mut buf = String::new();
stdin.read_line(&mut buf).unwrap();
let i: usize = buf.trim().parse().unwrap();
skills[i].id
}
pub fn chose_target_actor(
&self,
context: &Context,
team_id: TeamId,
alignments: AcceptableTargetAlignments,
) -> ActorId {
let team_db = context.get::<TeamDb>().unwrap();
let actor_db = context.get::<ActorDb>().unwrap();
let condition: Box<dyn Fn(&(&TeamId, &Team)) -> bool> = match alignments {
AcceptableTargetAlignments::Friend => {
Box::from(|(id, _): &(&TeamId, &Team)| **id == team_id)
}
AcceptableTargetAlignments::Enemy => {
Box::from(|(id, _): &(&TeamId, &Team)| **id != team_id)
}
AcceptableTargetAlignments::All => Box::from(|_: &(&TeamId, &Team)| true),
};
let targets: Vec<&Actor> = team_db
.iter()
.filter(condition)
.flat_map(|(team_id, team)| team.actors.clone())
.map(|id| actor_db.get(id).unwrap())
.collect();
println!("Choose target:");
for (i, actor) in targets.iter().enumerate() {
println!("{}: {}", i, actor.name)
}
let stdin = std::io::stdin();
let mut buf = String::new();
stdin.read_line(&mut buf).unwrap();
let i: usize = buf.trim().parse().unwrap();
targets[i].id
}
pub fn chose_target_team(
&self,
context: &Context,
team_id: TeamId,
alignments: AcceptableTargetAlignments,
) -> TeamId {
let team_db = context.get::<TeamDb>().unwrap();
let actor_db = context.get::<ActorDb>().unwrap();
let condition: Box<dyn Fn(&(&TeamId, &Team)) -> bool> = match alignments {
AcceptableTargetAlignments::Friend => {
Box::from(|(id, _): &(&TeamId, &Team)| **id == team_id)
}
AcceptableTargetAlignments::Enemy => {
Box::from(|(id, _): &(&TeamId, &Team)| **id != team_id)
}
AcceptableTargetAlignments::All => Box::from(|_: &(&TeamId, &Team)| true),
};
let targets: Vec<TeamId> = team_db
.iter()
.filter(condition)
.map(|(team_id, team)| *team_id)
.collect();
println!("Choose target:");
for (i, team_id) in targets.iter().enumerate() {
println!("{}: {}", i, team_id)
}
let stdin = std::io::stdin();
let mut buf = String::new();
stdin.read_line(&mut buf).unwrap();
let i: usize = buf.trim().parse().unwrap();
targets[i]
}
pub fn hp_effect(
&self,
context: &Context,
actor_id: ActorId,
old_state: ActorState,
new_state: ActorState,
) {
let actor_db = context.get::<ActorDb>().unwrap();
let actor = actor_db.get(actor_id).unwrap();
let max_hp = actor.stats.max_hp;
let name = &actor.name;
let old_str = match old_state {
ActorState::Alive(hp) => format!("{}/{}", hp, max_hp),
ActorState::Dead => String::from("Dead"),
};
let new_str = match new_state {
ActorState::Alive(hp) => format!("{}/{}", hp, max_hp),
ActorState::Dead => String::from("Dead"),
};
println!("Effect on {}: {} => {}", name, old_str, new_str);
}
}

@ -0,0 +1,99 @@
use std::any::{Any, TypeId};
use std::cell::{Ref, RefCell, RefMut};
use std::collections::HashMap;
#[allow(dead_code)]
pub struct Context {
map: HashMap<TypeId, Box<RefCell<dyn Any>>>,
}
#[allow(dead_code)]
impl Context {
pub fn new() -> Context {
Context {
map: HashMap::new(),
}
}
pub fn set<T: Any + 'static>(&mut self, t: T) {
self.map
.insert(TypeId::of::<T>(), Box::new(RefCell::new(t)));
}
pub fn get<T: Any + 'static>(&self) -> Option<Ref<T>> {
self.map
.get(&TypeId::of::<T>())
.map(|cell| Ref::map(cell.borrow(), |t| t.downcast_ref::<T>().unwrap()))
}
pub fn get_mut<T: Any + 'static>(&self) -> Option<RefMut<T>> {
self.map.get(&TypeId::of::<T>()).map(|cell| {
RefMut::map(cell.borrow_mut(), |t: &mut dyn Any| {
t.downcast_mut::<T>().unwrap()
})
})
}
pub fn contains<T: Any + 'static>(&self) -> bool {
self.map.contains_key(&TypeId::of::<T>())
}
}
#[macro_export]
macro_rules! require {
($context:expr; $( $T:ty ),* ) => {
Ok(())
$(
.and(
if !$context.contains::<$T>() {
Err(format!("Context does not contain {}", std::any::type_name::<$T>()))
} else {
Ok(())
}
)
)*
as std::result::Result<(), String>
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Eq, PartialEq, Debug)]
struct TestStruct(&'static str);
#[test]
fn test_context() {
// Given
let test_data = TestStruct("some content");
let mut context = Context::new();
// When
context.set(test_data);
let retrieved_data: Ref<TestStruct> = context.get().unwrap();
// Then
assert_eq!(*retrieved_data, TestStruct("some content"))
}
#[test]
fn test_mut() {
// Given
let test_data = TestStruct("some content");
let mut context = Context::new();
// When
context.set(test_data);
{
let mut retrieved_data: RefMut<TestStruct> = context.get_mut().unwrap();
retrieved_data.0 = "some other content";
}
// Then
assert_eq!(
*context.get::<TestStruct>().unwrap(),
TestStruct("some other content")
);
}
}

@ -1,5 +1,86 @@
#![feature(trait_alias, option_unwrap_none)]
mod battle; mod battle;
mod context;
use battle::actor::*;
use battle::base::*;
use battle::skill::*;
use battle::uic::Uic;
use context::Context;
fn main() { fn main() {
println!("Hello, world!"); println!("Hello, world!");
let mut context = Context::new();
context.set(ActorDb::from(actors_example()));
context.set(ActorStateDb::new());
let team_db = TeamDb::from(&context.get().unwrap(), TeamAiOverrides(Vec::new()));
context.set(team_db);
context.set(DeciderDb::from(vec![(0, Box::from(HumanDecider))]));
let skill_db = SkillDb::from(skills_example());
context.set(skill_db);
context.set(Uic);
battle::run_battle(context).unwrap();
}
fn actors_example() -> Vec<Actor> {
vec![
Actor {
id: 0,
team_id: 0,
name: String::from("Geralt"),
stats: ActorStats {
initiative: 100,
max_hp: 100,
},
ai: None,
skills: vec![0, 1],
},
Actor {
id: 1,
team_id: 1,
name: String::from("Steff"),
stats: ActorStats {
initiative: 120,
max_hp: 60,
},
ai: None,
skills: vec![0, 2],
},
]
}
fn skills_example() -> Vec<Skill> {
vec![
Skill {
id: 0,
name: String::from("Wait"),
accepts: AcceptableTargetTypes::None,
func: Box::from(|_: &Context, _: ActorId, _: SkillTarget| Ok(Vec::new())),
},
Skill {
id: 1,
name: String::from("Heavy Scratch"),
accepts: AcceptableTargetTypes::Actor(AcceptableTargetAlignments::Enemy),
func: Box::from(|_: &Context, _: ActorId, t: SkillTarget| {
if let SkillTarget::Actor(id) = t {
Ok(vec![SkillEffect::Damage(25, EffectTarget(id))])
} else {
Err(String::from("Accepts only actor-targets"))
}
}),
},
Skill {
id: 2,
name: String::from("Light Scratch"),
accepts: AcceptableTargetTypes::Actor(AcceptableTargetAlignments::Enemy),
func: Box::from(|_: &Context, _: ActorId, t: SkillTarget| {
if let SkillTarget::Actor(id) = t {
Ok(vec![SkillEffect::Damage(20, EffectTarget(id))])
} else {
Err(String::from("Accepts only actor-targets"))
}
}),
},
]
} }

Loading…
Cancel
Save