Compare commits

..

13 Commits

Author SHA1 Message Date
brnrs 4dcd6cc826 context changes 5 years ago
brnrs 13015bbc04 move Contetx ComponentMap into RefCell 5 years ago
brnrs babd1084a6 gitignore fix 5 years ago
brnrs e99e97637d added .vscode files to .gitignore 5 years ago
brnrs 28d5069b02 component factories + formatting 5 years ago
brnrs 1a52695945 traits for ComponentFactory and ContextComponentFactory 5 years ago
brnrs 28564b11bd WIP: contexts 5 years ago
brnrs 505016f160 Merge remote-tracking branch 'origin/feature/battle' into feature/battle 5 years ago
brnrs d594185d1d refactor 5 years ago
brnrs 63bf5f8f66 add jenkinsfile 5 years ago
brnrs 0a02f83c27 additions to todo 5 years ago
brnrs fe43ff2cd6 todo entry: battles 5 years ago
brnrs cb96c33135 WIP: battle 5 years ago
  1. 1
      .gitignore
  2. 9
      Cargo.toml
  3. 11
      Jenkinsfile
  4. 5
      README.md
  5. 124
      src/battle/actor.rs
  6. 17
      src/battle/base.rs
  7. 203
      src/battle/mod.rs
  8. 145
      src/battle/queue.rs
  9. 126
      src/battle/skill.rs
  10. 157
      src/battle/uic/mod.rs
  11. 99
      src/context.rs
  12. 63
      src/contexts/component.rs
  13. 7
      src/contexts/context.rs
  14. 53
      src/contexts/factory.rs
  15. 17
      src/contexts/from_context.rs
  16. 6
      src/contexts/mod.rs
  17. 214
      src/contexts/simple.rs
  18. 5
      src/contexts/subcontext.rs
  19. 0
      src/contexts/type_map.rs
  20. 87
      src/main.rs

1
.gitignore vendored

@ -10,3 +10,4 @@ Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
.vscode/

@ -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]

11
Jenkinsfile vendored

@ -0,0 +1,11 @@
pipeline {
agent any
stages {
stage('build') {
steps {
sh 'rustup update'
sh 'cargo test --verbose'
}
}
}
}

@ -1,2 +1,7 @@
# 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…
Cancel
Save