Compare commits
No commits in common. 'feature/battle' and 'master' have entirely different histories.
feature/ba
...
master
@ -1,9 +0,0 @@ |
|||||||
[package] |
|
||||||
name = "adventure" |
|
||||||
version = "0.1.0" |
|
||||||
authors = ["brnrs <brnrs@brnrs.pl>"] |
|
||||||
edition = "2018" |
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
|
||||||
|
|
||||||
[dependencies] |
|
@ -1,11 +0,0 @@ |
|||||||
pipeline { |
|
||||||
agent any |
|
||||||
stages { |
|
||||||
stage('build') { |
|
||||||
steps { |
|
||||||
sh 'rustup update' |
|
||||||
sh 'cargo test --verbose' |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,7 +1,2 @@ |
|||||||
# adventure |
# adventure |
||||||
|
|
||||||
### TODO: |
|
||||||
- implement battles |
|
||||||
- implement ActorDb |
|
||||||
- implement Decider |
|
||||||
- implement Effector |
|
@ -1,124 +0,0 @@ |
|||||||
use super::base::*; |
|
||||||
|
|
||||||
use std::collections::{BTreeMap, HashMap}; |
|
||||||
|
|
||||||
pub struct Actor { |
|
||||||
pub id: ActorId, |
|
||||||
pub team_id: TeamId, |
|
||||||
pub name: String, |
|
||||||
pub stats: ActorStats, |
|
||||||
pub skills: Vec<SkillId>, |
|
||||||
pub ai: Option<AiId>, |
|
||||||
} |
|
||||||
|
|
||||||
pub struct ActorStats { |
|
||||||
pub max_hp: Hp, |
|
||||||
pub initiative: Increment, |
|
||||||
} |
|
||||||
|
|
||||||
pub struct ActorDb { |
|
||||||
pub container: BTreeMap<ActorId, Actor>, |
|
||||||
} |
|
||||||
|
|
||||||
impl ActorDb { |
|
||||||
#[allow(dead_code)] |
|
||||||
pub fn from(actors: Vec<Actor>) -> ActorDb { |
|
||||||
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 fn iter(&self) -> std::collections::btree_map::Iter<ActorId, Actor> { |
|
||||||
self.container.iter() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
pub struct Team { |
|
||||||
pub id: TeamId, |
|
||||||
pub actors: Vec<ActorId>, |
|
||||||
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 fn get(&self, id: TeamId) -> Option<&Team> { |
|
||||||
self.container.get(&id) |
|
||||||
} |
|
||||||
|
|
||||||
pub fn iter(&self) -> std::collections::btree_map::Iter<TeamId, Team> { |
|
||||||
self.container.iter() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#[allow(dead_code)] |
|
||||||
pub struct ActorStateDb { |
|
||||||
container: HashMap<ActorId, ActorState>, |
|
||||||
} |
|
||||||
|
|
||||||
#[allow(dead_code, unused_variables)] |
|
||||||
impl ActorStateDb { |
|
||||||
pub fn new() -> ActorStateDb { |
|
||||||
ActorStateDb { |
|
||||||
container: HashMap::new(), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
pub fn init(&mut self, actor: &Actor) { |
|
||||||
self.container |
|
||||||
.insert(actor.id, ActorState::Alive(actor.stats.max_hp)) |
|
||||||
.unwrap_none() |
|
||||||
} |
|
||||||
|
|
||||||
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); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)] |
|
||||||
#[allow(dead_code)] |
|
||||||
pub enum ActorState { |
|
||||||
Dead, |
|
||||||
Alive(Hp), |
|
||||||
} |
|
@ -1,17 +0,0 @@ |
|||||||
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,203 +0,0 @@ |
|||||||
pub mod actor; |
|
||||||
pub mod base; |
|
||||||
pub mod queue; |
|
||||||
pub mod skill; |
|
||||||
pub mod uic; |
|
||||||
|
|
||||||
use crate::context::*; |
|
||||||
use crate::require; |
|
||||||
|
|
||||||
use actor::*; |
|
||||||
use base::*; |
|
||||||
use queue::*; |
|
||||||
use skill::*; |
|
||||||
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)); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
println!("Result: {:?}", battle_result); |
|
||||||
Ok(()) |
|
||||||
} |
|
||||||
|
|
||||||
fn create_queue(actor_db: &ActorDb) -> Queue { |
|
||||||
let mut queue = Queue::new(); |
|
||||||
actor_db |
|
||||||
.iter() |
|
||||||
.for_each(|(id, actor)| queue.enqueue((actor.stats.initiative, *id))); |
|
||||||
|
|
||||||
queue |
|
||||||
} |
|
||||||
|
|
||||||
fn determine_ai( |
|
||||||
context: &Context, |
|
||||||
actor_id: base::ActorId, |
|
||||||
default: base::AiId, |
|
||||||
) -> Result<base::AiId, String> { |
|
||||||
require!(context; actor::ActorDb, actor::TeamDb)?; |
|
||||||
|
|
||||||
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 team_db: &actor::TeamDb = &*context.get().unwrap(); |
|
||||||
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); |
|
||||||
} |
|
||||||
|
|
||||||
Ok(default) |
|
||||||
} |
|
||||||
|
|
||||||
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) |
|
||||||
} |
|
||||||
|
|
||||||
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) |
|
||||||
} |
|
||||||
|
|
||||||
struct DeadList(Vec<ActorId>); |
|
||||||
|
|
||||||
fn apply_effects(context: &Context, effects: Vec<SkillEffect>) -> Result<DeadList, String> { |
|
||||||
let mut dead_vec = Vec::new(); |
|
||||||
for effect in effects { |
|
||||||
let actor_db = context.get::<ActorDb>().unwrap(); |
|
||||||
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 { |
|
||||||
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 |
|
||||||
}); |
|
||||||
|
|
||||||
actor_state_db.set(id, new_state); |
|
||||||
uic.hp_effect(context, id, state, new_state); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
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(); |
|
||||||
|
|
||||||
let is_alive = |actor_id: &u32| { |
|
||||||
if let ActorState::Dead = actor_state_db.get(*actor_id).unwrap() { |
|
||||||
false |
|
||||||
} else { |
|
||||||
true |
|
||||||
} |
|
||||||
}; |
|
||||||
let alive: Vec<TeamId> = team_db |
|
||||||
.iter() |
|
||||||
.filter_map(|(team_id, team)| { |
|
||||||
if team.actors.iter().any(is_alive) { |
|
||||||
Some(*team_id) |
|
||||||
} else { |
|
||||||
None |
|
||||||
} |
|
||||||
}) |
|
||||||
.collect(); |
|
||||||
|
|
||||||
match alive.len() { |
|
||||||
0 => Some(BattleResult::AllDead), |
|
||||||
1 => Some(BattleResult::TeamWin(alive[0])), |
|
||||||
_ => None, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fn init_states(context: &Context) { |
|
||||||
let actor_db = context.get::<ActorDb>().unwrap(); |
|
||||||
let mut actor_state_db = context.get_mut::<ActorStateDb>().unwrap(); |
|
||||||
|
|
||||||
for (_, actor) in actor_db.iter() { |
|
||||||
actor_state_db.init(actor) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#[allow(dead_code)] |
|
||||||
#[derive(Debug)] |
|
||||||
enum BattleResult { |
|
||||||
TeamWin(base::TeamId), |
|
||||||
AllDead, |
|
||||||
} |
|
@ -1,145 +0,0 @@ |
|||||||
use super::base::*; |
|
||||||
|
|
||||||
pub struct Queue { |
|
||||||
container: Vec<(Increment, ActorId)>, |
|
||||||
} |
|
||||||
|
|
||||||
impl Queue { |
|
||||||
#[allow(dead_code)] |
|
||||||
pub fn new() -> Queue { |
|
||||||
Queue { |
|
||||||
container: Vec::new(), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#[allow(dead_code)] |
|
||||||
pub fn enqueue(&mut self, (incr, id): (Increment, ActorId)) { |
|
||||||
let mut incr = incr; |
|
||||||
let mut insert = None; |
|
||||||
for (i, (incr2, _)) in self.container.iter().enumerate() { |
|
||||||
if incr < *incr2 { |
|
||||||
insert = Some(i); |
|
||||||
break; |
|
||||||
} else if *incr2 != 0 { |
|
||||||
incr -= incr2; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if let Some(ix) = insert { |
|
||||||
self.container[ix].0 -= incr; |
|
||||||
self.container.insert(ix, (incr, id)); |
|
||||||
} else { |
|
||||||
self.container.push((incr, id)); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#[allow(dead_code)] |
|
||||||
pub fn dequeue(&mut self) -> Option<(Increment, ActorId)> { |
|
||||||
if self.container.is_empty() { |
|
||||||
None |
|
||||||
} else { |
|
||||||
Some(self.container.remove(0)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#[allow(dead_code)] |
|
||||||
pub fn remove(&mut self, id: ActorId) { |
|
||||||
let mut i = 0; |
|
||||||
loop { |
|
||||||
if i >= self.container.len() { |
|
||||||
break; |
|
||||||
} |
|
||||||
if id == self.container[i].1 { |
|
||||||
let (incr, _) = self.container.remove(i); |
|
||||||
if i < self.container.len() { |
|
||||||
self.container[i].0 += incr; |
|
||||||
} else { |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
i += 1; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &(Increment, ActorId)> { |
|
||||||
self.container.iter() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#[cfg(test)] |
|
||||||
mod tests { |
|
||||||
use super::*; |
|
||||||
|
|
||||||
#[test] |
|
||||||
fn test_enqueue_between_elements() { |
|
||||||
// Given
|
|
||||||
let mut queue = Queue::new(); |
|
||||||
queue.enqueue((2, 1)); |
|
||||||
queue.enqueue((7, 2)); |
|
||||||
queue.enqueue((3, 3)); |
|
||||||
|
|
||||||
// Expect
|
|
||||||
// id-1 remains first
|
|
||||||
assert_eq!(queue.dequeue(), Some((2, 1))); |
|
||||||
// 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] |
|
||||||
fn test_remove_by_id_from_middle() { |
|
||||||
// Given
|
|
||||||
let mut queue = Queue::new(); |
|
||||||
queue.enqueue((1, 1)); |
|
||||||
queue.enqueue((2, 2)); |
|
||||||
queue.enqueue((3, 3)); |
|
||||||
queue.enqueue((4, 4)); |
|
||||||
|
|
||||||
// When
|
|
||||||
queue.remove(3); |
|
||||||
|
|
||||||
// Then expect
|
|
||||||
assert_eq!(queue.dequeue(), Some((1, 1))); |
|
||||||
assert_eq!(queue.dequeue(), Some((1, 2))); |
|
||||||
assert_eq!(queue.dequeue(), Some((2, 4))); |
|
||||||
} |
|
||||||
|
|
||||||
#[test] |
|
||||||
fn test_remove_by_id_from_back() { |
|
||||||
// Given
|
|
||||||
let mut queue = Queue::new(); |
|
||||||
queue.enqueue((1, 1)); |
|
||||||
queue.enqueue((2, 2)); |
|
||||||
queue.enqueue((3, 3)); |
|
||||||
queue.enqueue((4, 4)); |
|
||||||
|
|
||||||
// When
|
|
||||||
queue.remove(4); |
|
||||||
|
|
||||||
// Then expect
|
|
||||||
assert_eq!(queue.dequeue(), Some((1, 1))); |
|
||||||
assert_eq!(queue.dequeue(), Some((1, 2))); |
|
||||||
assert_eq!(queue.dequeue(), Some((1, 3))); |
|
||||||
} |
|
||||||
|
|
||||||
#[test] |
|
||||||
fn test_remove_by_id_from_front() { |
|
||||||
// Given
|
|
||||||
let mut queue = Queue::new(); |
|
||||||
queue.enqueue((1, 1)); |
|
||||||
queue.enqueue((2, 2)); |
|
||||||
queue.enqueue((3, 3)); |
|
||||||
queue.enqueue((4, 4)); |
|
||||||
|
|
||||||
// When
|
|
||||||
queue.remove(1); |
|
||||||
|
|
||||||
// Then expect
|
|
||||||
assert_eq!(queue.dequeue(), Some((2, 2))); |
|
||||||
assert_eq!(queue.dequeue(), Some((1, 3))); |
|
||||||
assert_eq!(queue.dequeue(), Some((1, 4))); |
|
||||||
} |
|
||||||
} |
|
@ -1,126 +0,0 @@ |
|||||||
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) |
|
||||||
} |
|
||||||
} |
|
@ -1,157 +0,0 @@ |
|||||||
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); |
|
||||||
} |
|
||||||
} |
|
@ -1,99 +0,0 @@ |
|||||||
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,63 +0,0 @@ |
|||||||
use super::context::Context; |
|
||||||
use super::factory::Factory; |
|
||||||
|
|
||||||
use std::any::{Any, TypeId}; |
|
||||||
use std::cell::RefCell; |
|
||||||
use std::ops::Deref; |
|
||||||
use std::rc::Rc; |
|
||||||
|
|
||||||
pub struct Component<T: Any + Sized + 'static>(Rc<RefCell<T>>); |
|
||||||
#[allow(dead_code)] |
|
||||||
impl<T: Any + Sized + 'static> Component<T> { |
|
||||||
pub fn from(context: &dyn Context) -> Option<Result<Self, String>> { |
|
||||||
if let Some(any) = context.get(TypeId::of::<Self>()) { |
|
||||||
Some(Ok(Self::downcast_clone(any))) |
|
||||||
} else { |
|
||||||
match Factory::<T>::from(context) { |
|
||||||
Some(factory) => Some( |
|
||||||
factory |
|
||||||
.build(context) |
|
||||||
.map(|value| Self::register(context, value)), |
|
||||||
), |
|
||||||
None => None, |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
pub fn register(context: &dyn Context, value: T) -> Component<T> { |
|
||||||
let component = Component(Rc::new(RefCell::new(value))); |
|
||||||
context.set(TypeId::of::<Self>(), Rc::new(component.clone())); |
|
||||||
component |
|
||||||
} |
|
||||||
|
|
||||||
fn downcast_clone(any: Rc<dyn Any + 'static>) -> Self { |
|
||||||
any.downcast_ref::<Self>().unwrap().clone() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl<T: Any + Sized + 'static> Clone for Component<T> { |
|
||||||
fn clone(&self) -> Self { |
|
||||||
Component(self.0.clone()) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl<T: Any + Sized + 'static> Deref for Component<T> { |
|
||||||
type Target = RefCell<T>; |
|
||||||
fn deref(&self) -> &Self::Target { |
|
||||||
self.0.deref() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
pub trait ComponentOverlay { |
|
||||||
fn get_component<T: Any + Sized + 'static>(&self) -> Option<Result<Component<T>, String>>; |
|
||||||
fn register_component<T: Any + Sized + 'static>(&self, value: T) -> Component<T>; |
|
||||||
} |
|
||||||
|
|
||||||
impl ComponentOverlay for dyn Context + '_ { |
|
||||||
fn get_component<T: Any + Sized + 'static>(&self) -> Option<Result<Component<T>, String>> { |
|
||||||
Component::from(self) |
|
||||||
} |
|
||||||
fn register_component<T: Any + Sized + 'static>(&self, value: T) -> Component<T> { |
|
||||||
Component::register(self, value) |
|
||||||
} |
|
||||||
} |
|
@ -1,7 +0,0 @@ |
|||||||
use std::any::{Any, TypeId}; |
|
||||||
use std::rc::Rc; |
|
||||||
|
|
||||||
pub trait Context { |
|
||||||
fn get(&self, type_id: TypeId) -> Option<Rc<dyn Any + 'static>>; |
|
||||||
fn set(&self, type_id: TypeId, component: Rc<dyn Any + 'static>); |
|
||||||
} |
|
@ -1,53 +0,0 @@ |
|||||||
use super::context::Context; |
|
||||||
use super::from_context::FromContext; |
|
||||||
|
|
||||||
use std::any::{Any, TypeId}; |
|
||||||
use std::ops::Deref; |
|
||||||
use std::rc::Rc; |
|
||||||
pub struct Factory<T: Any + Sized + 'static>(Rc<dyn FromContext<Output = T>>); |
|
||||||
#[allow(dead_code)] |
|
||||||
impl<T: Any + Sized + 'static> Factory<T> { |
|
||||||
pub fn from(context: &dyn Context) -> Option<Self> { |
|
||||||
context |
|
||||||
.get(TypeId::of::<Self>()) |
|
||||||
.map(|boxed| boxed.downcast_ref::<Self>().unwrap().clone()) |
|
||||||
} |
|
||||||
|
|
||||||
pub fn register(context: &dyn Context, value: Box<dyn FromContext<Output = T>>) -> Factory<T> { |
|
||||||
let factory = Factory(Rc::from(value)); |
|
||||||
context.set(TypeId::of::<Self>(), Rc::new(factory.clone())); |
|
||||||
factory |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl<T: Any + Sized + 'static> Clone for Factory<T> { |
|
||||||
fn clone(&self) -> Self { |
|
||||||
Factory(self.0.clone()) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl<T: Any + Sized + 'static> Deref for Factory<T> { |
|
||||||
type Target = dyn FromContext<Output = T>; |
|
||||||
fn deref(&self) -> &Self::Target { |
|
||||||
self.0.deref() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
pub trait FactoryOverlay { |
|
||||||
fn get_factory<T: Any + Sized + 'static>(&self) -> Option<Factory<T>>; |
|
||||||
fn register_factory<T: Any + Sized + 'static>( |
|
||||||
&self, |
|
||||||
from_context: Box<dyn FromContext<Output = T>>, |
|
||||||
) -> Factory<T>; |
|
||||||
} |
|
||||||
impl FactoryOverlay for dyn Context { |
|
||||||
fn get_factory<T: Any + Sized + 'static>(&self) -> Option<Factory<T>> { |
|
||||||
Factory::from(self) |
|
||||||
} |
|
||||||
fn register_factory<T: Any + Sized + 'static>( |
|
||||||
&self, |
|
||||||
from_context: Box<dyn FromContext<Output = T>>, |
|
||||||
) -> Factory<T> { |
|
||||||
Factory::register(self, from_context) |
|
||||||
} |
|
||||||
} |
|
@ -1,17 +0,0 @@ |
|||||||
use super::context::Context; |
|
||||||
|
|
||||||
use std::any::Any; |
|
||||||
|
|
||||||
pub trait FromContext { |
|
||||||
type Output: Any + Sized + 'static; |
|
||||||
|
|
||||||
fn build(&self, context: &dyn Context) -> Result<Self::Output, String>; |
|
||||||
} |
|
||||||
|
|
||||||
impl<T: Any + Sized + 'static, FN: Fn(&dyn Context) -> Result<T, String>> FromContext for FN { |
|
||||||
type Output = T; |
|
||||||
|
|
||||||
fn build(&self, context: &dyn Context) -> Result<Self::Output, String> { |
|
||||||
self(context) |
|
||||||
} |
|
||||||
} |
|
@ -1,6 +0,0 @@ |
|||||||
mod component; |
|
||||||
mod context; |
|
||||||
mod factory; |
|
||||||
mod from_context; |
|
||||||
mod simple; |
|
||||||
mod subcontext; |
|
@ -1,214 +0,0 @@ |
|||||||
use super::context::Context; |
|
||||||
use super::subcontext::SubContext; |
|
||||||
|
|
||||||
use std::any::{Any, TypeId}; |
|
||||||
use std::cell::RefCell; |
|
||||||
use std::collections::HashMap; |
|
||||||
use std::ops::Deref; |
|
||||||
use std::rc::Rc; |
|
||||||
|
|
||||||
pub struct SimpleContext(RefCell<HashMap<TypeId, Rc<dyn Any + 'static>>>); |
|
||||||
|
|
||||||
#[allow(dead_code)] |
|
||||||
impl SimpleContext { |
|
||||||
pub fn new() -> SimpleContext { |
|
||||||
SimpleContext(RefCell::new(HashMap::new())) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl Context for SimpleContext { |
|
||||||
fn get(&self, type_id: TypeId) -> Option<Rc<dyn Any + 'static>> { |
|
||||||
self.0.borrow().get(&type_id).cloned() |
|
||||||
} |
|
||||||
|
|
||||||
fn set(&self, type_id: TypeId, component: Rc<dyn Any + 'static>) { |
|
||||||
self.0.borrow_mut().insert(type_id, component); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl Deref for SimpleContext { |
|
||||||
type Target = dyn Context; |
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target { |
|
||||||
self |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
pub struct SimpleSubContext<'t>(SimpleContext, &'t dyn Context); |
|
||||||
|
|
||||||
impl Context for SimpleSubContext<'_> { |
|
||||||
fn get(&self, type_id: TypeId) -> Option<Rc<dyn Any + 'static>> { |
|
||||||
match self.0.get(type_id) { |
|
||||||
any @ Some(_) => any, |
|
||||||
None => self.1.get(type_id), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fn set(&self, type_id: TypeId, component: Rc<dyn Any + 'static>) { |
|
||||||
self.0.set(type_id, component) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl<'t> SubContext<'t> for SimpleSubContext<'t> { |
|
||||||
fn subcontext(base_context: &'t dyn Context) -> Self { |
|
||||||
SimpleSubContext(SimpleContext::new(), base_context) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl<'t> Deref for SimpleSubContext<'t> { |
|
||||||
type Target = dyn Context + 't; |
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target { |
|
||||||
self |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#[cfg(test)] |
|
||||||
mod tests { |
|
||||||
use super::*; |
|
||||||
use crate::contexts::component::*; |
|
||||||
use crate::contexts::factory::*; |
|
||||||
|
|
||||||
#[test] |
|
||||||
fn should_retrieve_components_from_context() { |
|
||||||
// Given
|
|
||||||
#[derive(Debug, Eq, PartialEq)] |
|
||||||
struct A(&'static str); |
|
||||||
#[derive(Debug, Eq, PartialEq)] |
|
||||||
struct B(i32, i32); |
|
||||||
|
|
||||||
let context = SimpleContext::new(); |
|
||||||
|
|
||||||
context.register_component(5 as u32); |
|
||||||
context.register_component("test123"); |
|
||||||
context.register_component(A("some_value")); |
|
||||||
context.register_component(B(8, 0)); |
|
||||||
|
|
||||||
// When
|
|
||||||
let comp_u32 = context.get_component::<u32>().unwrap().unwrap(); |
|
||||||
let comp_str = context.get_component::<&'static str>().unwrap().unwrap(); |
|
||||||
let comp_a = context.get_component::<A>().unwrap().unwrap(); |
|
||||||
let comp_b = context.get_component::<B>().unwrap().unwrap(); |
|
||||||
|
|
||||||
// Then
|
|
||||||
assert_eq!(*comp_u32.borrow(), 5); |
|
||||||
assert_eq!(*comp_str.borrow(), "test123"); |
|
||||||
assert_eq!(*comp_a.borrow(), A("some_value")); |
|
||||||
assert_eq!(*comp_b.borrow(), B(8, 0)); |
|
||||||
} |
|
||||||
|
|
||||||
#[test] |
|
||||||
fn should_utilize_factory_for_building_component() { |
|
||||||
// Given
|
|
||||||
#[derive(Debug, Eq, PartialEq)] |
|
||||||
struct A(&'static str); |
|
||||||
#[derive(Debug, Eq, PartialEq)] |
|
||||||
struct B(i32, i32); |
|
||||||
#[derive(Debug, Eq, PartialEq)] |
|
||||||
struct C(&'static str, i32); |
|
||||||
|
|
||||||
let context = SimpleContext::new(); |
|
||||||
|
|
||||||
let c_from_context = |context: &dyn Context| { |
|
||||||
let comp_a = context |
|
||||||
.get_component::<A>() |
|
||||||
.unwrap_or_else(|| Err(String::from("Component A not found")))?; |
|
||||||
let comp_b = context |
|
||||||
.get_component::<B>() |
|
||||||
.unwrap_or_else(|| Err(String::from("Component B not found")))?; |
|
||||||
|
|
||||||
let &A(txt) = &*comp_a.borrow(); |
|
||||||
let &B(n1, n2) = &*comp_b.borrow(); |
|
||||||
|
|
||||||
Ok(C(txt, n1 + n2)) |
|
||||||
}; |
|
||||||
|
|
||||||
context.register_factory(Box::new(c_from_context)); |
|
||||||
context.register_component(A("test")); |
|
||||||
context.register_component(B(4, 8)); |
|
||||||
|
|
||||||
// When
|
|
||||||
let comp_c = context |
|
||||||
.get_component::<C>() |
|
||||||
.unwrap_or_else(|| Err(String::from("Component C not found"))) |
|
||||||
.unwrap(); |
|
||||||
|
|
||||||
// Then
|
|
||||||
assert_eq!(*comp_c.borrow(), C("test", 12)); |
|
||||||
} |
|
||||||
|
|
||||||
#[test] |
|
||||||
fn should_utilize_factory_recursively() { |
|
||||||
// Given
|
|
||||||
#[derive(Debug, Eq, PartialEq)] |
|
||||||
struct A(&'static str); |
|
||||||
#[derive(Debug, Eq, PartialEq)] |
|
||||||
struct B(i32, i32); |
|
||||||
#[derive(Debug, Eq, PartialEq)] |
|
||||||
struct C(&'static str, i32); |
|
||||||
|
|
||||||
let context = SimpleContext::new(); |
|
||||||
|
|
||||||
let b_from_context = |context: &dyn Context| { |
|
||||||
let comp_a = context |
|
||||||
.get_component::<A>() |
|
||||||
.unwrap_or_else(|| Err(String::from("Component A not found")))?; |
|
||||||
let &A(txt) = &*comp_a.borrow(); |
|
||||||
|
|
||||||
Ok(B( |
|
||||||
txt.len() as i32, |
|
||||||
txt.split_ascii_whitespace().count() as i32, |
|
||||||
)) |
|
||||||
}; |
|
||||||
let c_from_context = |context: &dyn Context| { |
|
||||||
let comp_a = context |
|
||||||
.get_component::<A>() |
|
||||||
.unwrap_or_else(|| Err(String::from("Component A not found")))?; |
|
||||||
let comp_b = context |
|
||||||
.get_component::<B>() |
|
||||||
.unwrap_or_else(|| Err(String::from("Component B not found")))?; |
|
||||||
let &A(txt) = &*comp_a.borrow(); |
|
||||||
let &B(n1, n2) = &*comp_b.borrow(); |
|
||||||
|
|
||||||
Ok(C(txt, n1 + n2)) |
|
||||||
}; |
|
||||||
|
|
||||||
context.register_factory(Box::new(b_from_context)); |
|
||||||
context.register_factory(Box::new(c_from_context)); |
|
||||||
context.register_component(A("test abc")); |
|
||||||
|
|
||||||
// When
|
|
||||||
let comp_c = context |
|
||||||
.get_component::<C>() |
|
||||||
.unwrap_or_else(|| Err(String::from("Component C not found"))) |
|
||||||
.unwrap(); |
|
||||||
let comp_b = context |
|
||||||
.get_component::<B>() |
|
||||||
.unwrap_or_else(|| Err(String::from("Component B not found"))) |
|
||||||
.unwrap(); |
|
||||||
|
|
||||||
// Then
|
|
||||||
assert_eq!(*comp_b.borrow(), B(8, 2)); |
|
||||||
assert_eq!(*comp_c.borrow(), C("test abc", 10)); |
|
||||||
} |
|
||||||
|
|
||||||
#[test] |
|
||||||
fn should_retrieve_component_from_base_context() { |
|
||||||
// Given
|
|
||||||
#[derive(Debug, Eq, PartialEq)] |
|
||||||
struct A(&'static str); |
|
||||||
let base_context = SimpleContext::new(); |
|
||||||
let subcontext = SimpleSubContext::subcontext(&base_context); |
|
||||||
|
|
||||||
base_context.register_component(A("Bob's your uncle")); |
|
||||||
|
|
||||||
// When
|
|
||||||
let comp_a = subcontext |
|
||||||
.get_component::<A>() |
|
||||||
.unwrap_or_else(|| Err(String::from("Component A not found"))) |
|
||||||
.unwrap(); |
|
||||||
|
|
||||||
// Then
|
|
||||||
assert_eq!(*comp_a.borrow(), A("Bob's your uncle")); |
|
||||||
} |
|
||||||
} |
|
@ -1,5 +0,0 @@ |
|||||||
use super::context::Context; |
|
||||||
|
|
||||||
pub trait SubContext<'t>: Context { |
|
||||||
fn subcontext(base_context: &'t dyn Context) -> Self; |
|
||||||
} |
|
@ -1,87 +0,0 @@ |
|||||||
#![feature(trait_alias, option_unwrap_none)] |
|
||||||
mod battle; |
|
||||||
mod context; |
|
||||||
mod contexts; |
|
||||||
|
|
||||||
use battle::actor::*; |
|
||||||
use battle::base::*; |
|
||||||
use battle::skill::*; |
|
||||||
use battle::uic::Uic; |
|
||||||
use context::Context; |
|
||||||
|
|
||||||
fn main() { |
|
||||||
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…
Reference in new issue