parent
0a02f83c27
commit
d594185d1d
@ -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, |
||||
} |
||||
|
@ -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…
Reference in new issue