fix(game): preserve rule state across player turns
The rewrite reset wheel holes, top targets, collision latches, active records, selected effects, and multiball readiness on every drain. The reconstructed turn-state code instead saves all 175 live record states for the outgoing player and restores the incoming player's mirror. Add a per-player rule snapshot and save/load it at normal turn transitions. Keep transient ball, claw, tilt, and presentation state global while retaining each player's mechanism progress and contact/activation state across later turns and extra balls. Test Plan: - `cargo test --all-targets` -- passed, 58 tests - `cargo clippy --all-targets -- -D warnings` -- passed - `rumdl check tdkpin-rs/CHANGELOG.md tdkpin-rs/RECONSTRUCTION.md` -- passed - `git diff --cached --check` -- passed
This commit is contained in:
+79
-6
@@ -162,6 +162,7 @@ pub struct Player {
|
||||
pub diamond_segments: u8,
|
||||
pub double_score: bool,
|
||||
pub media_level: u8,
|
||||
rules: RuleState,
|
||||
}
|
||||
|
||||
impl Default for Player {
|
||||
@@ -176,6 +177,7 @@ impl Default for Player {
|
||||
diamond_segments: 0,
|
||||
double_score: false,
|
||||
media_level: 0,
|
||||
rules: RuleState::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,6 +202,29 @@ enum MultiballState {
|
||||
Ready,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct RuleState {
|
||||
wheel_holes: [bool; 5],
|
||||
top_targets: [bool; 3],
|
||||
trigger_contacts: [bool; 176],
|
||||
object_active: [bool; 176],
|
||||
target_effect: u8,
|
||||
multiball_state: MultiballState,
|
||||
}
|
||||
|
||||
impl Default for RuleState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
wheel_holes: [false; 5],
|
||||
top_targets: [false; 3],
|
||||
trigger_contacts: [false; 176],
|
||||
object_active: initial_object_activity(),
|
||||
target_effect: 0,
|
||||
multiball_state: MultiballState::Unavailable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn initial_object_activity() -> [bool; 176] {
|
||||
let mut active = [true; 176];
|
||||
active[0] = false;
|
||||
@@ -1165,20 +1190,15 @@ impl Game {
|
||||
} else {
|
||||
player.balls = player.balls.saturating_sub(1);
|
||||
}
|
||||
self.save_current_rule_state();
|
||||
events.push(Event::Drain);
|
||||
events.push(Event::Sound(2008));
|
||||
self.top_targets.fill(false);
|
||||
self.wheel_holes.fill(false);
|
||||
self.tilted = false;
|
||||
self.tilt_counter = 0;
|
||||
self.tilt_counter_accumulator = 0.0;
|
||||
self.bumper_flash.fill(0.0);
|
||||
self.wheel_animation = 0.0;
|
||||
self.claw = Claw::default();
|
||||
self.trigger_contacts.fill(false);
|
||||
self.object_active = initial_object_activity();
|
||||
self.target_effect = 0;
|
||||
self.multiball_state = MultiballState::Unavailable;
|
||||
self.secondary_ball = None;
|
||||
self.nudge_shake = 0.0;
|
||||
self.launcher_charge = 0.0;
|
||||
@@ -1191,6 +1211,7 @@ impl Game {
|
||||
for _ in 0..self.players.len() {
|
||||
if self.players[next].balls > 0 || self.players[next].extra_balls > 0 {
|
||||
self.current_player = next;
|
||||
self.load_current_rule_state();
|
||||
self.ball = Ball::default();
|
||||
return;
|
||||
}
|
||||
@@ -1199,6 +1220,27 @@ impl Game {
|
||||
self.finished = true;
|
||||
}
|
||||
|
||||
fn save_current_rule_state(&mut self) {
|
||||
self.players[self.current_player].rules = RuleState {
|
||||
wheel_holes: self.wheel_holes,
|
||||
top_targets: self.top_targets,
|
||||
trigger_contacts: self.trigger_contacts,
|
||||
object_active: self.object_active,
|
||||
target_effect: self.target_effect,
|
||||
multiball_state: self.multiball_state,
|
||||
};
|
||||
}
|
||||
|
||||
fn load_current_rule_state(&mut self) {
|
||||
let state = self.players[self.current_player].rules;
|
||||
self.wheel_holes = state.wheel_holes;
|
||||
self.top_targets = state.top_targets;
|
||||
self.trigger_contacts = state.trigger_contacts;
|
||||
self.object_active = state.object_active;
|
||||
self.target_effect = state.target_effect;
|
||||
self.multiball_state = state.multiball_state;
|
||||
}
|
||||
|
||||
fn live_wall_segment(&self, object_id: u8, resting: Segment) -> Segment {
|
||||
let (start, end) = match object_id {
|
||||
66 if self.flippers.left_raised => (vec2(98.0, 382.0), vec2(131.0, 369.0)),
|
||||
@@ -1561,6 +1603,37 @@ mod tests {
|
||||
assert!(game.ball.in_launcher);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn player_turns_save_and_restore_all_rule_record_state() {
|
||||
let mut game = Game::new(2);
|
||||
game.wheel_holes[2] = true;
|
||||
game.top_targets[1] = true;
|
||||
game.trigger_contacts[149] = true;
|
||||
game.object_active[90] = false;
|
||||
game.target_effect = 3;
|
||||
game.multiball_state = MultiballState::Ready;
|
||||
|
||||
game.drain(&mut Vec::new());
|
||||
assert_eq!(game.current_player, 1);
|
||||
assert_eq!(game.wheel_holes, [false; 5]);
|
||||
assert_eq!(game.top_targets, [false; 3]);
|
||||
assert!(!game.trigger_contacts[149]);
|
||||
assert!(game.object_active[90]);
|
||||
assert_eq!(game.target_effect, 0);
|
||||
assert_eq!(game.multiball_state, MultiballState::Unavailable);
|
||||
|
||||
game.wheel_holes[4] = true;
|
||||
game.drain(&mut Vec::new());
|
||||
assert_eq!(game.current_player, 0);
|
||||
assert!(game.wheel_holes[2]);
|
||||
assert!(!game.wheel_holes[4]);
|
||||
assert!(game.top_targets[1]);
|
||||
assert!(game.trigger_contacts[149]);
|
||||
assert!(!game.object_active[90]);
|
||||
assert_eq!(game.target_effect, 3);
|
||||
assert_eq!(game.multiball_state, MultiballState::Ready);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fast_ball_cannot_tunnel_through_the_top_rail() {
|
||||
let mut game = Game::new(1);
|
||||
|
||||
Reference in New Issue
Block a user