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

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

@ -1,64 +1,124 @@
use std::cell::RefCell;
use std::num::NonZeroU32;
use std::rc::Rc;
use super::base::*;
pub type ActorId = u32;
pub type TeamId = u32;
pub type AiId = u32;
pub type SkillId = u32;
use std::collections::{BTreeMap, HashMap};
pub struct Actor {
pub id: ActorId,
pub team_id: TeamId,
pub ai_id: Option<AiId>,
pub name: String,
pub state: ActorState,
pub initiative: NonZeroU32,
pub stats: ActorStats,
pub skills: Vec<SkillId>,
pub ai: Option<AiId>,
}
type Hp = std::num::NonZeroU32;
pub enum ActorState {
Dead,
Alive(Hp),
pub struct ActorStats {
pub max_hp: Hp,
pub initiative: Increment,
}
pub struct ActorDb {
pub container: BTreeMap<ActorId, Actor>,
}
impl ActorState {
pub fn is_dead(&self) -> bool {
match self {
Self::Dead => true,
_ => false,
impl ActorDb {
#[allow(dead_code)]
pub fn from(actors: Vec<Actor>) -> ActorDb {
ActorDb {
container: actors.into_iter().map(|actor| (actor.id, actor)).collect(),
}
}
pub fn is_alive(&self) -> bool {
match self {
Self::Alive(_) => true,
_ => false,
}
#[allow(dead_code)]
pub fn get(&self, id: ActorId) -> Option<&Actor> {
self.container.get(&id)
}
}
pub struct TeamInfo(Option<AiId>);
pub fn iter(&self) -> std::collections::btree_map::Iter<ActorId, Actor> {
self.container.iter()
}
}
type ActorWrp = Rc<RefCell<Actor>>;
pub struct Team {
pub id: TeamId,
pub info: TeamInfo,
pub members: Box<dyn Iterator<Item = ActorWrp>>,
pub actors: Vec<ActorId>,
pub ai: Option<AiId>,
}
pub struct TeamDb {
container: BTreeMap<TeamId, Team>,
}
pub trait ActorDb<'a> {
fn lookup_actor(&self, id: ActorId) -> Option<ActorWrp>;
pub struct TeamAiOverrides(pub Vec<(TeamId, AiId)>);
fn lookup_team_info(&self, id: TeamId) -> Option<TeamInfo>;
#[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();
fn lookup_team(&self, id: TeamId) -> Option<Team>;
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,
},
);
}
});
fn lookup_actor_ids(&self) -> Box<dyn Iterator<Item = ActorId>>;
TeamDb { container: map }
}
fn lookup_team_ids(&self) -> Box<dyn Iterator<Item = TeamId>>;
pub fn get(&self, id: TeamId) -> Option<&Team> {
self.container.get(&id)
}
fn lookup_actors(&self) -> Box<dyn Iterator<Item = ActorWrp>>;
pub fn iter(&self) -> std::collections::btree_map::Iter<TeamId, Team> {
self.container.iter()
}
}
fn lookup_teams(&self) -> Box<dyn Iterator<Item = Team>>;
#[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);

@ -1,105 +1,202 @@
#![allow(dead_code,unused_variables,unused_imports,unused_mut)]
pub mod actor;
pub mod base;
pub mod queue;
pub mod skill;
pub mod uic;
mod actor;
mod queue;
use crate::context::*;
use crate::require;
use actor::*;
use queue::{Queue, QueueElement};
use std::cell::RefCell;
use std::rc::Rc;
fn battle(
actor_db: &dyn ActorDb,
decider: &dyn Decider,
effector: &dyn Effector,
) -> Result<u32, &'static str> {
if let Err(err_msg) = check(actor_db) {
return Err(err_msg);
}
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();
for actor in actor_db.lookup_actors() {
let actor = actor.borrow();
queue.enqueue(actor.initiative.get(), actor.id);
}
actor_db
.iter()
.for_each(|(id, actor)| queue.enqueue((actor.stats.initiative, *id)));
while let Some(QueueElement(incr, actor_id)) = queue.next() {
let actor = match actor_db.lookup_actor(actor_id) {
Some(actor) => actor,
None => return Err("Couldn't find actor with specified id"),
};
let team_info = match actor_db.lookup_team_info(actor.borrow().team_id) {
Some(team_info) => team_info,
None => return Err("Couldn't find team with specified id"),
};
queue
}
let decision = decider.decide(actor_db, actor.clone())?;
effector.effect(actor_db, actor.clone(), decision)?;
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);
}
if at_most_one_team_left_alive(actor_db) {
break;
}
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);
}
Err("not implemented")
Ok(default)
}
fn check(actor_db: &dyn ActorDb) -> Result<(), &'static str> {
let stats = actor_db
.lookup_teams()
.map(|mut team| team.members.next().is_none())
.fold((0, 0), |(non_empty, empty), is_empty| {
if is_empty {
(non_empty, empty + 1)
} else {
(non_empty + 1, empty)
}
});
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)
}
match stats {
(0, 0) => Err("No teams provided for battle"),
(1, 0) => Err("More than one team must be provided for battle"),
(_, 0) => Ok(()),
(_, empty) => Err("Empty teams cannot participate in battle"),
fn 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 at_most_one_team_left_alive(actor_db: &dyn ActorDb) -> bool {
let (alive, _) = actor_db
.lookup_teams()
.map(|mut team| team.members.all(|actor| actor.borrow().state.is_dead()))
.fold((0, 0), |(alive, dead), all_dead| {
if all_dead {
(alive, dead + 1)
fn check_result(context: &Context) -> Option<BattleResult> {
let team_db = context.get::<TeamDb>().unwrap();
let actor_state_db = context.get::<ActorStateDb>().unwrap();
let alive: Vec<TeamId> = team_db
.iter()
.filter_map(|(team_id, team)| {
if team.actors.iter().any(|actor_id| {
if let ActorState::Dead = actor_state_db.get(*actor_id).unwrap() {
false
} else {
true
}
}) {
Some(*team_id)
} else {
(alive + 1, dead)
None
}
});
alive <= 1
}
})
.collect();
enum Target {
Oneself,
Actor(ActorId),
Team(TeamId),
match alive.len() {
0 => Some(BattleResult::AllDead),
1 => Some(BattleResult::TeamWin(alive[0])),
_ => None,
}
}
struct Decision(SkillId, Target);
fn init_states(context: &Context) {
let actor_db = context.get::<ActorDb>().unwrap();
let mut actor_state_db = context.get_mut::<ActorStateDb>().unwrap();
trait Decider {
fn decide(
&self,
actor_db: &dyn ActorDb,
actor: Rc<RefCell<Actor>>,
) -> Result<Decision, &'static str>;
for (_, actor) in actor_db.iter() {
actor_state_db.init(actor)
}
}
trait Effector {
fn effect(
&self,
actor_db: &dyn ActorDb,
actor: Rc<RefCell<Actor>>,
decision: Decision,
) -> Result<(), &'static str>;
#[allow(dead_code)]
#[derive(Debug)]
enum BattleResult {
TeamWin(base::TeamId),
AllDead,
}

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

@ -1,5 +1,86 @@
#![feature(trait_alias, option_unwrap_none)]
mod battle;
mod context;
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