fix(game): restore wheel locks and reset target
Replace the inferred eight-sector wheel and central lock rectangle with the six recovered type-3 records. Fill the five lock holes at their exact centers, double each successive bonus from 10,000 through 160,000, transfer the complete 310,000 on the fifth, and return a launcher ball without consuming one. Restore object 148 as the wheel-reset target and clear all filled-hole state on contact. Keep these sensors on the same swept path and contact-latch machinery as the claw and type-4 targets. Test Plan: - `cargo test --all-targets` -- 47 passed - `cargo clippy --all-targets -- -D warnings` -- passed - `cargo build --profile production` -- passed - live five-hole bonus/score sequence matched exactly - live object-148 reset cleared all five contact states - `git diff --cached --check` -- passed
This commit is contained in:
+104
-61
@@ -4,7 +4,7 @@ use crate::{
|
||||
CollisionResponse, GRAVITY_MILLI_PER_STEP, MAXIMUM_SPEED_MILLI_PER_STEP, MilliVec,
|
||||
STEP_SECONDS, circle_collision_response, line_collision_response, path_intersects_circle,
|
||||
},
|
||||
table::{BUMPERS, PASSIVE_CIRCLES, TARGET_SENSORS, WALLS},
|
||||
table::{BUMPERS, LOCK_HOLES, PASSIVE_CIRCLES, TARGET_SENSORS, WALLS, WHEEL_RESET_SENSOR},
|
||||
};
|
||||
use macroquad::prelude::{Rect, Vec2, vec2};
|
||||
|
||||
@@ -197,10 +197,8 @@ pub struct Game {
|
||||
pub current_player: usize,
|
||||
pub ball: Ball,
|
||||
pub bonus: u32,
|
||||
pub wheel_holes: [bool; 8],
|
||||
pub side_targets: [bool; 5],
|
||||
pub wheel_holes: [bool; 5],
|
||||
pub top_targets: [bool; 3],
|
||||
pub lock_lights: u8,
|
||||
pub magnets: f32,
|
||||
pub tilted: bool,
|
||||
pub flippers: Flippers,
|
||||
@@ -212,7 +210,6 @@ pub struct Game {
|
||||
pub finished: bool,
|
||||
pub last_collision_id: Option<u8>,
|
||||
accumulator: f32,
|
||||
target_cooldown: f32,
|
||||
bumper_cooldown: f32,
|
||||
nudge_cooldown: f32,
|
||||
nudge_meter: f32,
|
||||
@@ -238,10 +235,8 @@ impl Game {
|
||||
current_player: 0,
|
||||
ball: Ball::default(),
|
||||
bonus: 0,
|
||||
wheel_holes: [false; 8],
|
||||
side_targets: [false; 5],
|
||||
wheel_holes: [false; 5],
|
||||
top_targets: [false; 3],
|
||||
lock_lights: 0,
|
||||
magnets: 0.0,
|
||||
tilted: false,
|
||||
flippers: Flippers::default(),
|
||||
@@ -253,7 +248,6 @@ impl Game {
|
||||
finished: false,
|
||||
last_collision_id: None,
|
||||
accumulator: 0.0,
|
||||
target_cooldown: 0.0,
|
||||
bumper_cooldown: 0.0,
|
||||
nudge_cooldown: 0.0,
|
||||
nudge_meter: 0.0,
|
||||
@@ -385,7 +379,6 @@ impl Game {
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn fixed_update(&mut self, dt: f32, events: &mut Vec<Event>) {
|
||||
self.target_cooldown = (self.target_cooldown - dt).max(0.0);
|
||||
self.bumper_cooldown = (self.bumper_cooldown - dt).max(0.0);
|
||||
self.nudge_cooldown = (self.nudge_cooldown - dt).max(0.0);
|
||||
self.magnets = (self.magnets - dt).max(0.0);
|
||||
@@ -515,7 +508,6 @@ impl Game {
|
||||
}
|
||||
|
||||
if !self.tilted {
|
||||
self.check_targets(events);
|
||||
self.check_media(events);
|
||||
}
|
||||
if self.claw.ball_suspended {
|
||||
@@ -531,51 +523,6 @@ impl Game {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_targets(&mut self, events: &mut Vec<Event>) {
|
||||
if self.target_cooldown > 0.0 {
|
||||
return;
|
||||
}
|
||||
let position = self.ball.position;
|
||||
|
||||
let wheel_center = vec2(114.0, 79.0);
|
||||
let from_wheel = position - wheel_center;
|
||||
if (25.0..=43.0).contains(&from_wheel.length()) {
|
||||
let angle =
|
||||
(from_wheel.y.atan2(from_wheel.x) + std::f32::consts::TAU) % std::f32::consts::TAU;
|
||||
let sector_boundaries = [
|
||||
std::f32::consts::TAU / 16.0,
|
||||
std::f32::consts::TAU * 3.0 / 16.0,
|
||||
std::f32::consts::TAU * 5.0 / 16.0,
|
||||
std::f32::consts::TAU * 7.0 / 16.0,
|
||||
std::f32::consts::TAU * 9.0 / 16.0,
|
||||
std::f32::consts::TAU * 11.0 / 16.0,
|
||||
std::f32::consts::TAU * 13.0 / 16.0,
|
||||
std::f32::consts::TAU * 15.0 / 16.0,
|
||||
];
|
||||
let hole = sector_boundaries.partition_point(|boundary| angle >= *boundary) % 8;
|
||||
if !self.wheel_holes[hole] {
|
||||
self.wheel_holes[hole] = true;
|
||||
self.add_score(2_500);
|
||||
self.target_cooldown = 0.15;
|
||||
self.wheel_animation = 0.65;
|
||||
events.push(Event::Wheel);
|
||||
}
|
||||
if self.wheel_holes.iter().all(|hole| *hole) {
|
||||
self.wheel_holes.fill(false);
|
||||
self.add_score(25_000);
|
||||
}
|
||||
}
|
||||
|
||||
if (151.0..=220.0).contains(&position.x) && (187.0..=205.0).contains(&position.y) {
|
||||
self.lock_lights = (self.lock_lights + 1).min(4);
|
||||
self.add_score(u32::from(self.lock_lights) * 5_000);
|
||||
self.ball.position = vec2(185.0, 181.0);
|
||||
self.ball.velocity = vec2(-40.0 + f32::from(self.lock_lights) * 18.0, -120.0);
|
||||
self.target_cooldown = 0.4;
|
||||
events.push(Event::Lock);
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_wall_rule(&mut self, object_id: u8, events: &mut Vec<Event>) {
|
||||
let wall = WALLS
|
||||
.iter()
|
||||
@@ -611,7 +558,6 @@ impl Game {
|
||||
0
|
||||
};
|
||||
self.add_score(completion_bonus);
|
||||
self.side_targets.fill(false);
|
||||
for active in &mut self.object_active[90..=104] {
|
||||
*active = true;
|
||||
}
|
||||
@@ -668,6 +614,61 @@ impl Game {
|
||||
}
|
||||
|
||||
let current_position = MilliVec::from_position(self.ball.position);
|
||||
for (index, sensor) in LOCK_HOLES.into_iter().enumerate() {
|
||||
let touched = path_intersects_circle(
|
||||
old_position,
|
||||
movement_velocity,
|
||||
sensor.center,
|
||||
sensor.radius,
|
||||
);
|
||||
let contact_index = usize::from(sensor.id);
|
||||
let entered =
|
||||
touched && !self.trigger_contacts[contact_index] && !self.wheel_holes[index];
|
||||
self.trigger_contacts[contact_index] = path_intersects_circle(
|
||||
current_position,
|
||||
MilliVec::default(),
|
||||
sensor.center,
|
||||
sensor.radius,
|
||||
);
|
||||
if !entered || self.tilted {
|
||||
continue;
|
||||
}
|
||||
let filled_before = self.wheel_holes.iter().filter(|filled| **filled).count();
|
||||
self.wheel_holes[index] = true;
|
||||
self.bonus = self
|
||||
.bonus
|
||||
.saturating_add(10_000_u32 << u32::try_from(filled_before).unwrap_or_default());
|
||||
if self.wheel_holes.iter().all(|filled| *filled) {
|
||||
self.add_score(self.bonus);
|
||||
self.bonus = 0;
|
||||
}
|
||||
self.reset_ball_to_launcher();
|
||||
events.push(Event::Lock);
|
||||
return;
|
||||
}
|
||||
|
||||
let reset_touched = path_intersects_circle(
|
||||
old_position,
|
||||
movement_velocity,
|
||||
WHEEL_RESET_SENSOR.center,
|
||||
WHEEL_RESET_SENSOR.radius,
|
||||
);
|
||||
let reset_index = usize::from(WHEEL_RESET_SENSOR.id);
|
||||
let reset_entered = reset_touched && !self.trigger_contacts[reset_index];
|
||||
self.trigger_contacts[reset_index] = path_intersects_circle(
|
||||
current_position,
|
||||
MilliVec::default(),
|
||||
WHEEL_RESET_SENSOR.center,
|
||||
WHEEL_RESET_SENSOR.radius,
|
||||
);
|
||||
if reset_entered && !self.tilted {
|
||||
self.wheel_holes.fill(false);
|
||||
self.wheel_animation = 0.65;
|
||||
self.reset_ball_to_launcher();
|
||||
events.push(Event::Wheel);
|
||||
return;
|
||||
}
|
||||
|
||||
for sensor in TARGET_SENSORS {
|
||||
let touched = path_intersects_circle(
|
||||
old_position,
|
||||
@@ -695,6 +696,15 @@ impl Game {
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_ball_to_launcher(&mut self) {
|
||||
self.ball = Ball::default();
|
||||
self.launcher_charge = 0.0;
|
||||
self.launcher_was_down = false;
|
||||
self.launcher_hold_seconds = 0.0;
|
||||
self.launcher_next_repeat = LAUNCHER_REPEAT_DELAY_SECONDS;
|
||||
self.launcher_velocity_milli = 0;
|
||||
}
|
||||
|
||||
fn next_claw_terminal_frame(&mut self) -> u8 {
|
||||
let value = self.next_random_value();
|
||||
CLAW_TERMINAL_FRAMES[value as usize % CLAW_TERMINAL_FRAMES.len()]
|
||||
@@ -825,10 +835,8 @@ impl Game {
|
||||
}
|
||||
events.push(Event::Drain);
|
||||
self.bonus = 0;
|
||||
self.side_targets.fill(false);
|
||||
self.top_targets.fill(false);
|
||||
self.wheel_holes.fill(false);
|
||||
self.lock_lights = 0;
|
||||
self.magnets = 0.0;
|
||||
self.tilted = false;
|
||||
self.nudge_meter = 0.0;
|
||||
@@ -1023,7 +1031,7 @@ mod tests {
|
||||
#[test]
|
||||
fn charged_ball_clears_the_shooter_lane() {
|
||||
for detail in 1..=5 {
|
||||
let mut game = Game::new(1);
|
||||
let mut game = Game::new_with_seed(1, u32::from(detail));
|
||||
launch_ball(&mut game, 60);
|
||||
let mut entered_table = false;
|
||||
let mut returned_to_launcher = false;
|
||||
@@ -1035,7 +1043,7 @@ mod tests {
|
||||
if game.ball.position.x < 280.0 && game.ball.position.y > 30.0 {
|
||||
entered_table = true;
|
||||
}
|
||||
returned_to_launcher |= game.ball.in_launcher;
|
||||
returned_to_launcher |= game.ball.in_launcher && !entered_table;
|
||||
}
|
||||
|
||||
assert!(
|
||||
@@ -1449,6 +1457,41 @@ mod tests {
|
||||
assert_eq!(game.player().score, 1_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wheel_holes_double_bonus_then_transfer_all_310k() {
|
||||
let mut game = Game::new(1);
|
||||
game.ball.in_launcher = false;
|
||||
let mut events = Vec::new();
|
||||
let expected_bonus = [10_000, 30_000, 70_000, 150_000, 0];
|
||||
|
||||
for (index, sensor) in LOCK_HOLES.into_iter().enumerate() {
|
||||
game.ball.position = sensor.center;
|
||||
game.check_sensor_objects(
|
||||
MilliVec::from_position(sensor.center),
|
||||
MilliVec::default(),
|
||||
&mut events,
|
||||
);
|
||||
assert_eq!(game.bonus, expected_bonus[index]);
|
||||
assert!(game.ball.in_launcher);
|
||||
}
|
||||
assert_eq!(game.player().score, 310_000);
|
||||
assert!(game.wheel_holes.iter().all(|filled| *filled));
|
||||
assert_eq!(
|
||||
events.iter().filter(|event| **event == Event::Lock).count(),
|
||||
5
|
||||
);
|
||||
|
||||
game.ball.in_launcher = false;
|
||||
game.ball.position = WHEEL_RESET_SENSOR.center;
|
||||
game.check_sensor_objects(
|
||||
MilliVec::from_position(WHEEL_RESET_SENSOR.center),
|
||||
MilliVec::default(),
|
||||
&mut events,
|
||||
);
|
||||
assert!(game.wheel_holes.iter().all(|filled| !*filled));
|
||||
assert_eq!(events.last(), Some(&Event::Wheel));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claw_release_table_decodes_the_original_thousandth_pixel_coordinates() {
|
||||
for (frame, expected) in [
|
||||
|
||||
Reference in New Issue
Block a user