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 super::base::*; |
||||||
use std::num::NonZeroU32; |
|
||||||
use std::rc::Rc; |
|
||||||
|
|
||||||
pub type ActorId = u32; |
use std::collections::{BTreeMap, HashMap}; |
||||||
pub type TeamId = u32; |
|
||||||
pub type AiId = u32; |
|
||||||
pub type SkillId = u32; |
|
||||||
|
|
||||||
pub struct Actor { |
pub struct Actor { |
||||||
pub id: ActorId, |
pub id: ActorId, |
||||||
pub team_id: TeamId, |
pub team_id: TeamId, |
||||||
pub ai_id: Option<AiId>, |
|
||||||
pub name: String, |
pub name: String, |
||||||
pub state: ActorState, |
pub stats: ActorStats, |
||||||
pub initiative: NonZeroU32, |
|
||||||
pub skills: Vec<SkillId>, |
pub skills: Vec<SkillId>, |
||||||
|
pub ai: Option<AiId>, |
||||||
} |
} |
||||||
|
|
||||||
type Hp = std::num::NonZeroU32; |
pub struct ActorStats { |
||||||
pub enum ActorState { |
pub max_hp: Hp, |
||||||
Dead, |
pub initiative: Increment, |
||||||
Alive(Hp), |
} |
||||||
|
|
||||||
|
pub struct ActorDb { |
||||||
|
pub container: BTreeMap<ActorId, Actor>, |
||||||
} |
} |
||||||
impl ActorState { |
|
||||||
pub fn is_dead(&self) -> bool { |
impl ActorDb { |
||||||
match self { |
#[allow(dead_code)] |
||||||
Self::Dead => true, |
pub fn from(actors: Vec<Actor>) -> ActorDb { |
||||||
_ => false, |
ActorDb { |
||||||
|
container: actors.into_iter().map(|actor| (actor.id, actor)).collect(), |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
pub fn is_alive(&self) -> bool { |
#[allow(dead_code)] |
||||||
match self { |
pub fn get(&self, id: ActorId) -> Option<&Actor> { |
||||||
Self::Alive(_) => true, |
self.container.get(&id) |
||||||
_ => false, |
|
||||||
} |
|
||||||
} |
} |
||||||
} |
|
||||||
|
|
||||||
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 struct Team { |
||||||
pub id: TeamId, |
pub id: TeamId, |
||||||
pub info: TeamInfo, |
pub actors: Vec<ActorId>, |
||||||
pub members: Box<dyn Iterator<Item = ActorWrp>>, |
pub ai: Option<AiId>, |
||||||
|
} |
||||||
|
|
||||||
|
pub struct TeamDb { |
||||||
|
container: BTreeMap<TeamId, Team>, |
||||||
} |
} |
||||||
|
|
||||||
pub trait ActorDb<'a> { |
pub struct TeamAiOverrides(pub Vec<(TeamId, AiId)>); |
||||||
fn lookup_actor(&self, id: ActorId) -> Option<ActorWrp>; |
|
||||||
|
|
||||||
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; |
use crate::context::*; |
||||||
mod queue; |
use crate::require; |
||||||
|
|
||||||
use actor::*; |
use actor::*; |
||||||
use queue::{Queue, QueueElement}; |
use base::*; |
||||||
|
use queue::*; |
||||||
use std::cell::RefCell; |
use skill::*; |
||||||
use std::rc::Rc; |
use uic::*; |
||||||
|
|
||||||
fn battle( |
#[allow(dead_code, unused_variables)] |
||||||
actor_db: &dyn ActorDb, |
pub fn run_battle(context: Context) -> Result<(), String> { |
||||||
decider: &dyn Decider, |
require!(context; ActorDb, ActorStateDb, TeamDb, DeciderDb, SkillDb, Uic)?; |
||||||
effector: &dyn Effector, |
let uic = context.get::<Uic>().unwrap(); |
||||||
) -> Result<u32, &'static str> { |
init_states(&context); |
||||||
if let Err(err_msg) = check(actor_db) { |
uic.start_battle(&context); |
||||||
return Err(err_msg); |
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(); |
let mut queue = Queue::new(); |
||||||
for actor in actor_db.lookup_actors() { |
actor_db |
||||||
let actor = actor.borrow(); |
.iter() |
||||||
queue.enqueue(actor.initiative.get(), actor.id); |
.for_each(|(id, actor)| queue.enqueue((actor.stats.initiative, *id))); |
||||||
} |
|
||||||
|
|
||||||
while let Some(QueueElement(incr, actor_id)) = queue.next() { |
queue |
||||||
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"), |
|
||||||
}; |
|
||||||
|
|
||||||
let decision = decider.decide(actor_db, actor.clone())?; |
fn determine_ai( |
||||||
effector.effect(actor_db, actor.clone(), decision)?; |
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) { |
let team_db: &actor::TeamDb = &*context.get().unwrap(); |
||||||
break; |
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> { |
fn determine_decision( |
||||||
let stats = actor_db |
context: &Context, |
||||||
.lookup_teams() |
actor_id: ActorId, |
||||||
.map(|mut team| team.members.next().is_none()) |
) -> Result<(SkillId, SkillTarget), String> { |
||||||
.fold((0, 0), |(non_empty, empty), is_empty| { |
let decider_db: &DeciderDb = &*context.get().unwrap(); |
||||||
if is_empty { |
let ai_id = determine_ai(&context, actor_id, 0)?; |
||||||
(non_empty, empty + 1) |
let decider = decider_db |
||||||
} else { |
.get(ai_id) |
||||||
(non_empty + 1, empty) |
.ok_or_else(|| String::from("Decider not found"))?; |
||||||
} |
|
||||||
}); |
decider.decide(context, actor_id) |
||||||
|
} |
||||||
|
|
||||||
match stats { |
fn determine_effects( |
||||||
(0, 0) => Err("No teams provided for battle"), |
context: &Context, |
||||||
(1, 0) => Err("More than one team must be provided for battle"), |
actor_id: ActorId, |
||||||
(_, 0) => Ok(()), |
skill_id: SkillId, |
||||||
(_, empty) => Err("Empty teams cannot participate in battle"), |
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 { |
fn check_result(context: &Context) -> Option<BattleResult> { |
||||||
let (alive, _) = actor_db |
let team_db = context.get::<TeamDb>().unwrap(); |
||||||
.lookup_teams() |
let actor_state_db = context.get::<ActorStateDb>().unwrap(); |
||||||
.map(|mut team| team.members.all(|actor| actor.borrow().state.is_dead())) |
|
||||||
.fold((0, 0), |(alive, dead), all_dead| { |
let alive: Vec<TeamId> = team_db |
||||||
if all_dead { |
.iter() |
||||||
(alive, dead + 1) |
.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 { |
} else { |
||||||
(alive + 1, dead) |
None |
||||||
} |
} |
||||||
}); |
}) |
||||||
|
.collect(); |
||||||
alive <= 1 |
|
||||||
} |
|
||||||
|
|
||||||
enum Target { |
match alive.len() { |
||||||
Oneself, |
0 => Some(BattleResult::AllDead), |
||||||
Actor(ActorId), |
1 => Some(BattleResult::TeamWin(alive[0])), |
||||||
Team(TeamId), |
_ => 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 { |
for (_, actor) in actor_db.iter() { |
||||||
fn decide( |
actor_state_db.init(actor) |
||||||
&self, |
} |
||||||
actor_db: &dyn ActorDb, |
|
||||||
actor: Rc<RefCell<Actor>>, |
|
||||||
) -> Result<Decision, &'static str>; |
|
||||||
} |
} |
||||||
|
|
||||||
trait Effector { |
#[allow(dead_code)] |
||||||
fn effect( |
#[derive(Debug)] |
||||||
&self, |
enum BattleResult { |
||||||
actor_db: &dyn ActorDb, |
TeamWin(base::TeamId), |
||||||
actor: Rc<RefCell<Actor>>, |
AllDead, |
||||||
decision: Decision, |
|
||||||
) -> Result<(), &'static str>; |
|
||||||
} |
} |
||||||
|
@ -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 battle; |
||||||
|
mod context; |
||||||
|
|
||||||
|
use battle::actor::*; |
||||||
|
use battle::base::*; |
||||||
|
use battle::skill::*; |
||||||
|
use battle::uic::Uic; |
||||||
|
use context::Context; |
||||||
|
|
||||||
fn main() { |
fn main() { |
||||||
println!("Hello, world!"); |
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