premades and many-v-many combat

main
HeNine 2 years ago
parent d26a39f56f
commit 110eefd597

@ -17,3 +17,9 @@ strum = "0.24"
strum_macros = "0.24" strum_macros = "0.24"
rayon = "1.7.0" rayon = "1.7.0"
log = "0.4.18"
env_logger = "0.10.0"
[dev-dependencies]
test-log = "0.2.11"

@ -44,6 +44,8 @@ where
} }
fn main() { fn main() {
env_logger::init();
let mut rng = thread_rng(); let mut rng = thread_rng();
let n = 100; let n = 100;
@ -52,8 +54,6 @@ fn main() {
let mut characters: Vec<Character> = iter::repeat_with(random_character).take(n).collect(); let mut characters: Vec<Character> = iter::repeat_with(random_character).take(n).collect();
print_chars(characters.iter().take(keep).collect::<Vec<&Character>>()); print_chars(characters.iter().take(keep).collect::<Vec<&Character>>());
// print_chars(&characters1);
// print_chars(&characters2);
for g in 0..gen { for g in 0..gen {
characters.shuffle(&mut rng); characters.shuffle(&mut rng);
@ -76,7 +76,7 @@ fn main() {
for _i in 0..n_rounds { for _i in 0..n_rounds {
let mut c1l = c1.to_owned(); let mut c1l = c1.to_owned();
let mut c2l = c2.to_owned(); let mut c2l = c2.to_owned();
let t = make_them_fight(&mut c1l, &mut c2l); let t = make_them_fight(vec![&mut c1l], vec![&mut c2l]);
match t { match t {
Some(_turns) => { Some(_turns) => {

@ -1,40 +1,43 @@
use kartsimrust::{ use kartsimrust::{
character::create_character, combat::make_them_fight,
class::Class, premade::{get_bob, get_drub},
combat::EncounterType,
equipment::{Armor, WeaponType},
stats::StatBlock,
}; };
fn main() { fn main() {
let mut bob = create_character( let mut a = 1.0;
String::from("Bob"), let mut b = 1.0;
Class::Guard,
Class::Brawler,
StatBlock::from((1, 0, 1, 0, 0, 0, 0, 0)),
StatBlock::from((2, 0, 2, 0, -1, -2, 0, -1)),
Armor::Medium,
WeaponType::BladedWeapon.create_weapon("Longsword".to_owned()),
);
let mut drub = create_character( let mut total_turns = 0.0;
String::from("Drub"), let mut battles = 0.0;
Class::Hunter,
Class::Thief,
StatBlock::from((0, 1, 1, 0, 0, 0, 0, 0)),
StatBlock::from((0, 2, 2, 0, -2, 0, 0, -2)),
Armor::Light,
WeaponType::RangedWeapon.create_weapon(String::from("Shortbow")),
);
bob.init_dice_pool(EncounterType::Physical); loop {
drub.init_dice_pool(EncounterType::Physical); let mut bob = get_bob();
println!("{}", bob); let mut drub = get_drub();
println!("{}", drub); let mut drub2 = get_drub();
bob.attacks(&mut drub); if let Some(turns) = make_them_fight(vec![&mut bob], vec![&mut drub, &mut drub2]) {
battles += 1.0;
total_turns += turns as f64;
println!("{}", bob); if bob.hp <= 0 {
println!("{}", drub); b += 1.0;
println!("Bob died in {} turns.", turns)
} else {
a += 1.0;
println!("Drub died in {} turns.", turns)
}
if (a * b) / (f64::powi(a + b, 2) * (a + b + 1.0)) <= 0.00001 {
break;
}
} else {
println!("No one died.")
}
}
println!(
"Bob won {:.2}% of battles in an average of {:.1} turns.",
a / (a + b) * 100.0,
total_turns / battles
)
} }

@ -107,6 +107,10 @@ impl Character {
adjusted_stat_block[stat] adjusted_stat_block[stat]
} }
pub fn is_dead(&self) -> bool {
self.hp <= 0
}
} }
pub fn create_character( pub fn create_character(
@ -120,6 +124,7 @@ pub fn create_character(
) -> Character { ) -> Character {
if !(Vec::from(stat_choice).iter().all(|s| *s >= 0) if !(Vec::from(stat_choice).iter().all(|s| *s >= 0)
&& Vec::from(stat_choice).iter().sum::<i64>() == 2i64) && Vec::from(stat_choice).iter().sum::<i64>() == 2i64)
&& !(class1 == Class::NPC && class2 == Class::NPC)
{ {
panic!("Invalid stat choice: {:?}", stat_choice) panic!("Invalid stat choice: {:?}", stat_choice)
} }

@ -19,6 +19,7 @@ pub enum Class {
Hunter, Hunter,
Witch, Witch,
Wizard, Wizard,
NPC, // Stubbed in for "no class"
} }
impl ToStatBlock for Class { impl ToStatBlock for Class {
@ -36,6 +37,8 @@ impl ToStatBlock for Class {
// ----- // -----
Class::Witch => StatBlock::from((0, 0, 0, 0, 1, 1, 1, 0)), Class::Witch => StatBlock::from((0, 0, 0, 0, 1, 1, 1, 0)),
Class::Wizard => StatBlock::from((0, 0, 0, 0, 1, 1, 0, 1)), Class::Wizard => StatBlock::from((0, 0, 0, 0, 1, 1, 0, 1)),
// -----
Class::NPC => StatBlock::from((0, 0, 0, 0, 0, 0, 0, 0)),
} }
} }
} }
@ -103,6 +106,7 @@ impl ToProficiencies for Class {
..Default::default() ..Default::default()
}, },
Class::Wizard => Proficiencies::default(), Class::Wizard => Proficiencies::default(),
Class::NPC => Proficiencies::default(),
} }
} }
} }
@ -119,33 +123,23 @@ mod class_tests {
use crate::{class::Class, stats::ToStatBlock}; use crate::{class::Class, stats::ToStatBlock};
// const CLASSES: [Class; 9] = [
// Class::Guard,
// Class::Knight,
// Class::Brawler,
// Class::Thief,
// Class::Swashbuckler,
// Class::Survivalist,
// Class::Hunter,
// Class::Witch,
// Class::Wizard,
// ];
#[test] #[test]
fn stat_sums() { fn stat_sums() {
for class in Class::iter() { for class in Class::iter() {
let stat_block = class.stat_block(); let stat_block = class.stat_block();
assert_eq!( if class != Class::NPC {
3, assert_eq!(
stat_block.STR 3,
+ stat_block.DEX stat_block.STR
+ stat_block.CON + stat_block.DEX
+ stat_block.STB + stat_block.CON
+ stat_block.INT + stat_block.STB
+ stat_block.KND + stat_block.INT
+ stat_block.CMP + stat_block.KND
+ stat_block.CHA + stat_block.CMP
) + stat_block.CHA
)
}
} }
} }

@ -1,6 +1,6 @@
use crate::{character::Character, dice::successes}; use crate::{character::Character, dice::successes};
#[derive(Debug,PartialEq, Eq, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum EncounterType { pub enum EncounterType {
Physical, Physical,
Mental, Mental,
@ -13,41 +13,158 @@ impl Character {
(opponent.ac() - self.weapon_proficiency() as i64).clamp(1, 12), (opponent.ac() - self.weapon_proficiency() as i64).clamp(1, 12),
); );
let damage = self.compute_damage(margin);//(margin + self.weapon_proficiency()).max(0) * self.weapon.damage_dice; let damage = self.compute_damage(margin); //(margin + self.weapon_proficiency()).max(0) * self.weapon.damage_dice;
opponent.hp = opponent.hp - damage as i64; opponent.hp = opponent.hp - damage as i64;
self.adjust_dice_pool((-fail_dice).min(0), EncounterType::Physical); self.adjust_dice_pool((-fail_dice).min(0), EncounterType::Physical);
// println!( log::trace!(
// "{} attacks {} getting a margin of {} (s: {}, f: {}) and dealing {} damage.", "{} attacks {} getting a margin of {} and dealing {} damage.",
// self.name, opponent.name, margin, succ, fail, damage self.name,
// ) opponent.name,
margin,
damage
)
} }
} }
pub fn make_them_fight(character1: &mut Character, character2: &mut Character) -> Option<i64> { pub fn make_them_fight(mut red: Vec<&mut Character>, mut blue: Vec<&mut Character>) -> Option<i64> {
character1.init_encounter(EncounterType::Physical); for character in red.iter_mut() {
character2.init_encounter(EncounterType::Physical); character.init_encounter(EncounterType::Physical);
}
for character in blue.iter_mut() {
character.init_encounter(EncounterType::Physical);
}
let mut turn = 0; let mut turn = 0;
loop { let mut dead_r = 0;
let mut dead_b = 0;
while red.iter().any(|c| !c.is_dead()) && blue.iter().any(|c| !c.is_dead()) {
turn += 1; turn += 1;
character1.attacks(character2); if turn > 15 {
if character2.hp <= 0 { return None;
break;
} }
character2.attacks(character1); log::trace!("Turn {}", turn);
if character1.hp <= 0 { log::trace!(
break; "{:?}",
} red.iter()
.map(|c| (c.name.to_owned(), c.hp))
.collect::<Vec<(String, i64)>>()
);
log::trace!(
"{:?}",
blue.iter()
.map(|c| (c.name.to_owned(), c.hp))
.collect::<Vec<(String, i64)>>()
);
if turn > 15 { let mut init_r = 0;
return None let mut init_b = 0;
let mut target_r = 0;
let mut target_b = 0;
while init_r < red.len() || init_b < blue.len() {
// Find next living red char
log::trace!("Searching init");
while init_r < red.len() && red[init_r].is_dead() {
init_r += 1;
}
// If character is found
if init_r < red.len() {
// Find target
log::trace!("Searching target");
while blue[target_b].is_dead() {
target_b += 1;
target_b %= blue.len();
}
log::trace!("Target found");
// Attack target
red[init_r].attacks(blue[target_b]);
if blue[target_b].is_dead() {
log::trace!("{} is dead.", blue[target_b].name);
dead_b += 1;
}
target_b += 1;
target_b %= blue.len();
init_r += 1;
}
// All blues are dead
if dead_b == blue.len() {
break;
}
// Find next living blue
log::trace!("Searching init");
while init_b < blue.len() && blue[init_b].is_dead() {
init_b += 1;
}
// If found
if init_b < blue.len() {
log::trace!("Searching target");
while red[target_r].hp <= 0 {
target_r += 1;
target_r %= red.len();
}
blue[init_b].attacks(red[target_r]);
if red[target_r].is_dead() {
log::trace!("{} is dead.", red[target_r].name);
dead_r += 1;
}
target_r += 1;
target_r %= red.len();
init_b += 1;
}
if dead_r == red.len() {
break;
}
} }
} }
Some(turn) Some(turn)
} }
#[cfg(test)]
mod combat_tests {
use crate::premade::{get_bob, get_drub};
use super::make_them_fight;
#[test_log::test]
fn make_them_fight_debug() {
let mut red = vec![get_bob(), get_bob()];
red[1].name = "Bob 2".to_owned();
let mut blue = vec![get_drub(), get_drub()];
blue[1].name = "Drub 2".to_owned();
make_them_fight(red.iter_mut().collect(), blue.iter_mut().collect());
print!(
"{:?}",
red.into_iter()
.map(|c| (c.name, c.hp))
.collect::<Vec<(String, i64)>>()
);
print!(
"{:?}",
blue.into_iter()
.map(|c| (c.name, c.hp))
.collect::<Vec<(String, i64)>>()
);
}
}

@ -7,4 +7,6 @@ pub mod equipment;
pub mod skills; pub mod skills;
pub mod stats; pub mod stats;
pub mod random_character; pub mod random_character;
pub mod premade;

@ -0,0 +1,61 @@
use super::character::{create_character, Character};
use super::class::Class;
use super::equipment::{Armor, WeaponType};
use super::stats::StatBlock;
/*
* Bob has chosen to play a knight who is also an [NPC].
* He has chosen to make a knight who is strong and dexterous.
* He has also decided that his knight is not charismatic, but rather athletic.
*/
pub fn get_bob() -> Character {
create_character(
"Bob".to_owned(),
Class::Knight,
Class::NPC,
StatBlock::from((1, 1, 0, 0, 0, 0, 0, 0)),
StatBlock::from((0, 0, 2, 0, 0, 0, 0, -2)),
Armor::Medium,
WeaponType::BladedWeapon.create_weapon("Longsword".to_owned()),
)
}
/*
* Drub is a goblin.
*/
pub fn get_drub() -> Character {
let mut drub = create_character(
String::from("Drub"),
Class::NPC,
Class::NPC,
StatBlock::from((2, 3, 2, 0, 1, 0, -3, -2)),
StatBlock::default(),
Armor::Light,
WeaponType::SimpleWeapon.create_weapon(String::from("Knife")),
);
drub.proficiencies.simple_weapons = true;
drub.proficiencies.light_armor = true;
return drub
}
#[cfg(test)]
mod premade_tests {
use super::{get_bob, get_drub};
#[test]
fn print_bob() {
let bob = get_bob();
println!("{}", bob);
}
#[test]
fn print_drub() {
let drub = get_drub();
println!("{}", drub);
}
}
Loading…
Cancel
Save