From dfc99e97e3127fddb422ed221ced4b3a70ca8b88 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Mon, 24 Mar 2025 21:07:54 -0600 Subject: [PATCH] enqueue returns err on sanity checks, exec returns Turn for a series of turn results --- src/purerust/src/src.rs | 187 ++++++++++++++++++++++++++++++++++------ 1 file changed, 163 insertions(+), 24 deletions(-) diff --git a/src/purerust/src/src.rs b/src/purerust/src/src.rs index 288d3c4..646e9af 100755 --- a/src/purerust/src/src.rs +++ b/src/purerust/src/src.rs @@ -150,11 +150,44 @@ pub mod battle { self.teams.push(Team::new(third)); } - pub fn enqueue(&mut self, m: Move) { + pub fn enqueue(&mut self, m: Move) -> Result<(), String> { + match m { + Move::Attack(w_idx, r_idx) => { + if self.teams()[w_idx].lost { + return Err(format!("team[{w_idx}] lost")); + } + if !self.teams()[w_idx].any_can_attack() { + return Err(format!("team[{w_idx}] cannot attack")); + } + if !self.teams()[r_idx].any_can_be_attacked() { + return Err(format!("team[{r_idx}] cannot be attacked")); + } + } + Move::Pass(team_idx) => { + if self.teams()[team_idx].lost { + return Err(format!("team[{team_idx}] lost")); + } + if !self.teams()[team_idx].any_can_attack() { + return Err(format!("team[{team_idx}] cannot move")); + } + } + Move::Swap(team_idx, mon_idx) => { + if self.teams()[team_idx].lost { + return Err(format!("team[{team_idx}] lost")); + } + if !self.teams()[team_idx].mons()[mon_idx].alive() { + return Err(format!("team[{team_idx}].mons[{mon_idx}] cannot swap in")); + } + if self.teams()[team_idx].mons()[mon_idx].out { + return Err(format!("team[{team_idx}].mons[{mon_idx}] already in")); + } + } + }; self.q.push(m); + Ok(()) } - pub fn exec(&mut self) { + pub fn exec(&mut self) -> Vec { self.turn_order().iter().for_each(|idx| { match self.q[*idx].clone() { Move::Pass(_) => {} @@ -162,19 +195,21 @@ pub mod battle { let r_team = self.teams[r_team_idx].clone(); let w_team = &mut self.teams[w_team_idx]; - for r_mon_idx in 0..r_team.mons.len() { - let r_mon = &r_team.mons[r_mon_idx]; + for r_mon_idx in 0..r_team.mons().len() { + let r_mon = &r_team.mons()[r_mon_idx]; if !r_mon.can_attack() { continue; } - for w_mon_idx in 0..w_team.mons.len() { - let w_mon = &w_team.mons[w_mon_idx]; + for w_mon_idx in 0..w_team.mons().len() { + let w_mon = &w_team.mons()[w_mon_idx]; if !w_mon.can_be_attacked() { continue; } let r_mon = &r_mon.mon; let w_mon = &w_mon.mon; w_team.mons[w_mon_idx].mon = Engine::attack(w_mon, r_mon); + w_team.mons[w_mon_idx].out = + w_team.mons[w_mon_idx].out && w_team.mons[w_mon_idx].alive() } } @@ -183,7 +218,7 @@ pub mod battle { Move::Swap(team_idx, mon_idx) => { let mut team = self.teams[team_idx].clone(); team.mons = team - .mons + .mons() .iter() .map(|m| { let mut m = m.clone(); @@ -196,15 +231,42 @@ pub mod battle { } }; }); - self.new_turn(); + self.new_turn() } pub fn teams(&self) -> Vec { self.teams.iter().map(|t| t.clone()).collect() } - fn new_turn(&mut self) { + fn new_turn(&mut self) -> Vec { self.q = vec![]; + + let mut result = vec![]; + + let teams = self.teams(); + let mut teams_not_lost = vec![]; + for i in 0..teams.len() { + let has_mon_out = teams[i].mons().iter().filter(|m| m.out).count() > 0; + let has_mon_alive = teams[i].mons().iter().filter(|m| m.alive()).count() > 0; + let lost_before = teams[i].lost; + + if !has_mon_out && has_mon_alive { + result.push(Turn::MustSwap(i)); + } else if !has_mon_alive && !lost_before { + self.teams[i].lost = true; + result.push(Turn::Lose(i)); + } + + if !self.teams()[i].lost { + teams_not_lost.push(i); + } + } + + if teams_not_lost.len() == 1 { + result.push(Turn::Win(teams_not_lost[0])); + } + + result } fn turn_order(&self) -> Vec { @@ -281,15 +343,17 @@ pub mod battle { engine.join(team_b()); // player 0 mon 0 attacks team 1 mon 0 - engine.enqueue(Move::Attack(0, 1)); + engine + .enqueue(Move::Attack(0, 1)) + .expect("failed to attack"); // player 1 mon 0 passes - engine.enqueue(Move::Pass(1)); + engine.enqueue(Move::Pass(1)).expect("failed to pass"); // player 1 mon 1 not out - engine.exec(); + assert_eq!(0, engine.exec().len()); - assert_eq!(0, engine.teams[0].mons[0].mon.damage); - assert_eq!(1, engine.teams[1].mons[0].mon.damage); - assert_eq!(0, engine.teams[1].mons[1].mon.damage); + assert_eq!(0, engine.teams()[0].mons()[0].mon.damage); + assert_eq!(1, engine.teams()[1].mons()[0].mon.damage); + assert_eq!(0, engine.teams()[1].mons()[1].mon.damage); } fn team_a() -> Vec { @@ -307,6 +371,7 @@ pub mod battle { #[derive(Clone, Debug)] pub struct Team { mons: Vec, + lost: bool, } impl Team { @@ -320,18 +385,29 @@ pub mod battle { }) .collect(); mons[0].out = true; - Self { mons: mons } + Self { + mons: mons.clone(), + lost: mons.len() == 0, + } } pub fn mons(&self) -> Vec { self.mons.iter().map(|i| i.clone()).collect() } + + fn any_can_attack(&self) -> bool { + self.mons().iter().filter(|m| m.can_attack()).count() > 0 + } + + fn any_can_be_attacked(&self) -> bool { + self.mons().iter().filter(|m| m.can_be_attacked()).count() > 0 + } } #[derive(Clone, Debug)] pub struct Instance { - mon: mon::Instance, - out: bool, + pub mon: mon::Instance, + pub out: bool, } impl Instance { @@ -378,6 +454,13 @@ pub mod battle { Attack(usize, usize), Swap(usize, usize), } + + #[derive(Clone, Debug, PartialEq)] + pub enum Turn { + MustSwap(usize), + Lose(usize), + Win(usize), + } } #[cfg(test)] @@ -400,9 +483,13 @@ mod mon_tests { let mut engine = battle::Engine::new(my_mons, they_mons); // team0mon0 trades attacks with team1mon0 - engine.enqueue(battle::Move::Attack(1, 0)); - engine.enqueue(battle::Move::Attack(0, 1)); - engine.exec(); + engine + .enqueue(battle::Move::Attack(1, 0)) + .expect("failed to attack"); + engine + .enqueue(battle::Move::Attack(0, 1)) + .expect("failed to attack"); + assert_eq!(0, engine.exec().len()); assert_eq!(6, engine.teams()[0].mons()[0].damage()); assert_eq!(0, engine.teams()[0].mons()[1].damage()); assert_eq!(1, engine.teams()[1].mons()[0].damage()); @@ -411,13 +498,65 @@ mod mon_tests { // team0mon0 swaps with team0mon0 // team1mon0 attacks team0 - engine.enqueue(battle::Move::Attack(1, 0)); - engine.enqueue(battle::Move::Swap(0, 1)); - engine.exec(); + engine + .enqueue(battle::Move::Attack(1, 0)) + .expect("failed to attack"); + engine + .enqueue(battle::Move::Swap(0, 1)) + .expect("failed to swap"); + assert_eq!(0, engine.exec().len()); assert_eq!(6, engine.teams()[0].mons()[0].damage()); assert_eq!(1, engine.teams()[0].mons()[1].damage()); assert_eq!(1, engine.teams()[1].mons()[0].damage()); assert_eq!(0, engine.teams()[1].mons()[1].damage()); assert_eq!(0, engine.teams()[1].mons()[2].damage()); + + for i in 2..engine.teams()[0].mons()[1].mon.dex.hp() { + engine + .enqueue(battle::Move::Attack(1, 0)) + .expect("failed to attack"); + assert_eq!(0, engine.exec().len()); + assert_eq!(i, engine.teams()[0].mons()[1].damage()); + } + + engine + .enqueue(battle::Move::Attack(1, 0)) + .expect("failed to kill"); + let turn_results = engine.exec(); + assert_eq!(1, turn_results.len()); + assert_eq!(battle::Turn::MustSwap(0), turn_results[0]); + assert_eq!( + engine.teams()[0].mons()[1].mon.dex.hp(), + engine.teams()[0].mons()[1].damage() + ); + assert!(!engine.teams()[0].mons()[1].out); + + assert!(engine.enqueue(battle::Move::Attack(0, 1)).is_err()); + assert!(engine.enqueue(battle::Move::Pass(0)).is_err()); + engine + .enqueue(battle::Move::Swap(0, 0)) + .expect("failed to swap on MustSwap"); + assert!(engine.enqueue(battle::Move::Attack(1, 0)).is_err()); + assert_eq!(0, engine.exec().len()); + + for i in 7..engine.teams()[0].mons()[0].mon.dex.hp() { + engine + .enqueue(battle::Move::Attack(1, 0)) + .expect("failed to attack"); + assert_eq!(0, engine.exec().len()); + assert_eq!(i, engine.teams()[0].mons()[0].damage()); + } + + engine + .enqueue(battle::Move::Attack(1, 0)) + .expect("failed to kill"); + let turn_results = engine.exec(); + assert_eq!(2, turn_results.len(), "{:?}", turn_results); + assert_eq!(battle::Turn::Lose(0), turn_results[0], "{:?}", turn_results); + assert_eq!(battle::Turn::Win(1), turn_results[1], "{:?}", turn_results); + assert_eq!( + engine.teams()[0].mons()[0].mon.dex.hp(), + engine.teams()[0].mons()[0].damage() + ); } }