Compare commits
13 Commits
master
...
feature/ba
Author | SHA1 | Date |
---|---|---|
brnrs | 4dcd6cc826 | 5 years ago |
brnrs | 13015bbc04 | 5 years ago |
brnrs | babd1084a6 | 5 years ago |
brnrs | e99e97637d | 5 years ago |
brnrs | 28d5069b02 | 5 years ago |
brnrs | 1a52695945 | 5 years ago |
brnrs | 28564b11bd | 5 years ago |
brnrs | 505016f160 | 5 years ago |
brnrs | d594185d1d | 5 years ago |
brnrs | 63bf5f8f66 | 5 years ago |
brnrs | 0a02f83c27 | 5 years ago |
brnrs | fe43ff2cd6 | 5 years ago |
brnrs | cb96c33135 | 5 years ago |
@ -0,0 +1,9 @@ |
|||||||
|
[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] |
@ -0,0 +1,11 @@ |
|||||||
|
pipeline { |
||||||
|
agent any |
||||||
|
stages { |
||||||
|
stage('build') { |
||||||
|
steps { |
||||||
|
sh 'rustup update' |
||||||
|
sh 'cargo test --verbose' |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,2 +1,7 @@ |
|||||||
# adventure |
# adventure |
||||||
|
|
||||||
|
### TODO: |
||||||
|
- implement battles |
||||||
|
- implement ActorDb |
||||||
|
- implement Decider |
||||||
|
- implement Effector |
@ -0,0 +1,124 @@ |
|||||||
|
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), |
||||||
|
} |
@ -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); |
@ -0,0 +1,203 @@ |
|||||||
|
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, |
||||||
|
} |
@ -0,0 +1,145 @@ |
|||||||
|
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))); |
||||||
|
} |
||||||
|
} |
@ -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") |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
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) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
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>); |
||||||
|
} |
@ -0,0 +1,53 @@ |
|||||||
|
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) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
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) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,6 @@ |
|||||||
|
mod component; |
||||||
|
mod context; |
||||||
|
mod factory; |
||||||
|
mod from_context; |
||||||
|
mod simple; |
||||||
|
mod subcontext; |
@ -0,0 +1,214 @@ |
|||||||
|
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")); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,5 @@ |
|||||||
|
use super::context::Context; |
||||||
|
|
||||||
|
pub trait SubContext<'t>: Context { |
||||||
|
fn subcontext(base_context: &'t dyn Context) -> Self; |
||||||
|
} |
@ -0,0 +1,87 @@ |
|||||||
|
#![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