fix(game): restore the recovered collision table
Replace the mirrored, partial wall transcription with all active static line and circle objects from the Win16 registration table. Preserve the original object IDs for drain and shooter-stop handling, and use bounded physics substeps so fast balls cannot skip thin rails. Test Plan: - cargo fmt --all -- --check - cargo test - cargo clippy --all-targets --all-features -- -D warnings - git diff --check
This commit is contained in:
@@ -2,6 +2,7 @@ use crate::{
|
||||
assets::Assets,
|
||||
game::{Controls, Event, Game},
|
||||
persistence::{HighScore, Language, Persistence, SavedData, insert_high_score},
|
||||
table::BUMPERS,
|
||||
};
|
||||
use macroquad::prelude::*;
|
||||
|
||||
@@ -460,13 +461,10 @@ impl App {
|
||||
},
|
||||
);
|
||||
}
|
||||
for (index, center) in [vec2(207.0, 109.0), vec2(167.0, 148.0), vec2(219.0, 167.0)]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
for (index, bumper) in BUMPERS.into_iter().enumerate() {
|
||||
if game.bumper_flash[index] > 0.0 {
|
||||
draw_circle_lines(center.x, center.y, 17.0, 3.0, WHITE);
|
||||
draw_circle_lines(center.x, center.y, 13.0, 2.0, YELLOW);
|
||||
draw_circle_lines(bumper.center.x, bumper.center.y, 17.0, 3.0, WHITE);
|
||||
draw_circle_lines(bumper.center.x, bumper.center.y, 13.0, 2.0, YELLOW);
|
||||
}
|
||||
}
|
||||
if game.player().diamond_segments > 0 {
|
||||
@@ -503,8 +501,8 @@ impl App {
|
||||
}
|
||||
|
||||
for (start, end) in [
|
||||
(vec2(103.0, 394.0), vec2(58.0, 374.0)),
|
||||
(vec2(211.0, 394.0), vec2(256.0, 374.0)),
|
||||
(vec2(103.0, 397.0), vec2(58.0, 374.0)),
|
||||
(vec2(210.0, 397.0), vec2(256.0, 374.0)),
|
||||
] {
|
||||
draw_line(start.x, start.y, end.x, end.y, 22.0, BLACK);
|
||||
draw_circle(start.x, start.y, 11.0, BLACK);
|
||||
|
||||
+102
-171
@@ -1,6 +1,8 @@
|
||||
use crate::geometry::{Segment, circle_collision, segment_collision};
|
||||
use crate::{
|
||||
geometry::{Segment, circle_collision, segment_collision},
|
||||
table::{BUMPERS, PASSIVE_CIRCLES, WALLS},
|
||||
};
|
||||
use macroquad::prelude::{Vec2, vec2};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
const BALL_RADIUS: f32 = 7.0;
|
||||
const GRAVITY: f32 = 135.0;
|
||||
@@ -228,59 +230,91 @@ impl Game {
|
||||
return;
|
||||
}
|
||||
|
||||
self.ball.velocity.y += GRAVITY * dt;
|
||||
self.ball.velocity *= 1.0 - dt * 0.055;
|
||||
self.ball.velocity = self.ball.velocity.clamp_length_max(MAX_SPEED);
|
||||
self.ball.position += self.ball.velocity * dt;
|
||||
let travel = self.ball.velocity.length() * dt;
|
||||
let maximum_step_travel = BALL_RADIUS * 0.45;
|
||||
let mut substeps = 1_u8;
|
||||
while f32::from(substeps) * maximum_step_travel < travel && substeps < 12 {
|
||||
substeps += 1;
|
||||
}
|
||||
let substep = dt / f32::from(substeps);
|
||||
for _ in 0..substeps {
|
||||
self.ball.velocity.y += GRAVITY * substep;
|
||||
self.ball.velocity *= 1.0 - substep * 0.055;
|
||||
self.ball.velocity = self.ball.velocity.clamp_length_max(MAX_SPEED);
|
||||
self.ball.position += self.ball.velocity * substep;
|
||||
|
||||
for wall in table_walls() {
|
||||
segment_collision(
|
||||
let mut drained = false;
|
||||
for wall in WALLS {
|
||||
// Object 25 is the one-way shooter stop. It catches a returning
|
||||
// ball, but must not reflect an upward launch from the line.
|
||||
if wall.id == 25 && self.ball.velocity.y < 0.0 {
|
||||
continue;
|
||||
}
|
||||
let hit = segment_collision(
|
||||
&mut self.ball.position,
|
||||
&mut self.ball.velocity,
|
||||
BALL_RADIUS,
|
||||
wall.segment,
|
||||
);
|
||||
if hit && wall.id == 2 {
|
||||
drained = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if drained {
|
||||
self.drain(events);
|
||||
return;
|
||||
}
|
||||
|
||||
for circle in PASSIVE_CIRCLES {
|
||||
debug_assert_ne!(circle.id, 174, "the live ball is not a static obstacle");
|
||||
circle_collision(
|
||||
&mut self.ball.position,
|
||||
&mut self.ball.velocity,
|
||||
BALL_RADIUS,
|
||||
circle.center,
|
||||
circle.radius,
|
||||
0.0,
|
||||
);
|
||||
}
|
||||
|
||||
let left_flipper = flipper_segment(vec2(103.0, 397.0), self.left_flipper_angle);
|
||||
let right_flipper = flipper_segment(vec2(210.0, 397.0), self.right_flipper_angle);
|
||||
if segment_collision(
|
||||
&mut self.ball.position,
|
||||
&mut self.ball.velocity,
|
||||
BALL_RADIUS,
|
||||
*wall,
|
||||
);
|
||||
}
|
||||
|
||||
let left_flipper = flipper_segment(vec2(103.0, 394.0), self.left_flipper_angle);
|
||||
let right_flipper = flipper_segment(vec2(211.0, 394.0), self.right_flipper_angle);
|
||||
if segment_collision(
|
||||
&mut self.ball.position,
|
||||
&mut self.ball.velocity,
|
||||
BALL_RADIUS + 2.0,
|
||||
left_flipper,
|
||||
) && self.left_flipper_angle > -2.55
|
||||
{
|
||||
self.ball.velocity += vec2(-20.0, -115.0);
|
||||
}
|
||||
if segment_collision(
|
||||
&mut self.ball.position,
|
||||
&mut self.ball.velocity,
|
||||
BALL_RADIUS + 2.0,
|
||||
right_flipper,
|
||||
) && self.right_flipper_angle < -0.58
|
||||
{
|
||||
self.ball.velocity += vec2(20.0, -115.0);
|
||||
}
|
||||
|
||||
for (index, center) in [vec2(207.0, 109.0), vec2(167.0, 148.0), vec2(219.0, 167.0)]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
let hit = circle_collision(
|
||||
BALL_RADIUS + 2.0,
|
||||
left_flipper,
|
||||
) && self.left_flipper_angle > -2.55
|
||||
{
|
||||
self.ball.velocity += vec2(-20.0, -115.0);
|
||||
}
|
||||
if segment_collision(
|
||||
&mut self.ball.position,
|
||||
&mut self.ball.velocity,
|
||||
BALL_RADIUS,
|
||||
center,
|
||||
14.0,
|
||||
105.0,
|
||||
);
|
||||
if hit && !self.tilted && self.bumper_cooldown <= 0.0 {
|
||||
self.add_score(self.player().bumper_value);
|
||||
self.bonus = self.bonus.saturating_add(100);
|
||||
self.bumper_cooldown = 0.08;
|
||||
self.bumper_flash[index] = 0.16;
|
||||
events.push(Event::Bumper);
|
||||
BALL_RADIUS + 2.0,
|
||||
right_flipper,
|
||||
) && self.right_flipper_angle < -0.58
|
||||
{
|
||||
self.ball.velocity += vec2(20.0, -115.0);
|
||||
}
|
||||
|
||||
for (index, bumper) in BUMPERS.into_iter().enumerate() {
|
||||
let hit = circle_collision(
|
||||
&mut self.ball.position,
|
||||
&mut self.ball.velocity,
|
||||
BALL_RADIUS,
|
||||
bumper.center,
|
||||
bumper.radius,
|
||||
105.0,
|
||||
);
|
||||
if hit && !self.tilted && self.bumper_cooldown <= 0.0 {
|
||||
self.add_score(self.player().bumper_value);
|
||||
self.bonus = self.bonus.saturating_add(100);
|
||||
self.bumper_cooldown = 0.08;
|
||||
self.bumper_flash[index] = 0.16;
|
||||
events.push(Event::Bumper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,8 +521,8 @@ impl Game {
|
||||
|
||||
pub fn flippers(&self) -> (Segment, Segment) {
|
||||
(
|
||||
flipper_segment(vec2(103.0, 394.0), self.left_flipper_angle),
|
||||
flipper_segment(vec2(211.0, 394.0), self.right_flipper_angle),
|
||||
flipper_segment(vec2(103.0, 397.0), self.left_flipper_angle),
|
||||
flipper_segment(vec2(210.0, 397.0), self.right_flipper_angle),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -498,117 +532,6 @@ fn flipper_segment(pivot: Vec2, angle: f32) -> Segment {
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn table_walls() -> &'static [Segment] {
|
||||
static WALLS: LazyLock<Vec<Segment>> = LazyLock::new(|| {
|
||||
vec![
|
||||
Segment::new(Vec2::new(125.0, 21.0), Vec2::new(7.0, 61.0), 0.82),
|
||||
Segment::new(Vec2::new(300.0, 3.0), Vec2::new(10.0, 3.0), 0.82),
|
||||
Segment::new(Vec2::new(120.0, 6.0), Vec2::new(120.0, 26.0), 0.82),
|
||||
Segment::new(Vec2::new(195.0, 26.0), Vec2::new(195.0, 6.0), 0.82),
|
||||
Segment::new(Vec2::new(134.0, 33.0), Vec2::new(134.0, 16.0), 0.82),
|
||||
Segment::new(Vec2::new(148.0, 11.0), Vec2::new(148.0, 33.0), 0.82),
|
||||
Segment::new(Vec2::new(164.0, 12.0), Vec2::new(165.0, 33.0), 0.82),
|
||||
Segment::new(Vec2::new(180.0, 11.0), Vec2::new(179.0, 33.0), 0.82),
|
||||
Segment::new(Vec2::new(304.0, 64.0), Vec2::new(183.0, 21.0), 0.82),
|
||||
Segment::new(Vec2::new(298.0, 417.0), Vec2::new(298.0, 313.0), 0.82),
|
||||
Segment::new(Vec2::new(298.0, 336.0), Vec2::new(310.0, 336.0), 0.82),
|
||||
Segment::new(Vec2::new(292.0, 374.0), Vec2::new(291.0, 342.0), 0.82),
|
||||
Segment::new(Vec2::new(281.0, 378.0), Vec2::new(300.0, 369.0), 0.82),
|
||||
Segment::new(Vec2::new(275.0, 343.0), Vec2::new(290.0, 382.0), 0.82),
|
||||
Segment::new(Vec2::new(248.0, 364.0), Vec2::new(264.0, 339.0), 0.82),
|
||||
Segment::new(Vec2::new(257.0, 387.0), Vec2::new(249.0, 364.0), 0.82),
|
||||
Segment::new(Vec2::new(277.0, 429.0), Vec2::new(257.0, 387.0), 0.82),
|
||||
Segment::new(Vec2::new(287.0, 446.0), Vec2::new(277.0, 427.0), 0.82),
|
||||
Segment::new(Vec2::new(303.0, 430.0), Vec2::new(283.0, 436.0), 0.82),
|
||||
Segment::new(Vec2::new(320.0, 410.0), Vec2::new(302.0, 430.0), 0.82),
|
||||
Segment::new(Vec2::new(320.0, 21.0), Vec2::new(320.0, 410.0), 0.82),
|
||||
Segment::new(Vec2::new(307.0, 437.0), Vec2::new(316.0, 430.0), 0.82),
|
||||
Segment::new(Vec2::new(296.0, 442.0), Vec2::new(307.0, 437.0), 0.82),
|
||||
Segment::new(Vec2::new(285.0, 443.0), Vec2::new(296.0, 442.0), 0.82),
|
||||
// Preserve the recovered lower apron on either side of the center drain.
|
||||
Segment::new(Vec2::new(112.0, 443.0), Vec2::new(141.0, 443.0), 0.82),
|
||||
Segment::new(Vec2::new(173.0, 443.0), Vec2::new(285.0, 443.0), 0.82),
|
||||
Segment::new(Vec2::new(99.0, 438.0), Vec2::new(121.0, 444.0), 0.82),
|
||||
Segment::new(Vec2::new(78.0, 427.0), Vec2::new(100.0, 438.0), 0.82),
|
||||
Segment::new(Vec2::new(66.0, 409.0), Vec2::new(79.0, 428.0), 0.82),
|
||||
Segment::new(Vec2::new(57.0, 385.0), Vec2::new(68.0, 413.0), 0.82),
|
||||
Segment::new(Vec2::new(59.0, 304.0), Vec2::new(59.0, 396.0), 0.82),
|
||||
Segment::new(Vec2::new(67.0, 288.0), Vec2::new(58.0, 311.0), 0.82),
|
||||
Segment::new(Vec2::new(41.0, 268.0), Vec2::new(64.0, 279.0), 0.82),
|
||||
Segment::new(Vec2::new(22.0, 293.0), Vec2::new(31.0, 272.0), 0.82),
|
||||
Segment::new(Vec2::new(22.0, 447.0), Vec2::new(22.0, 293.0), 0.82),
|
||||
Segment::new(Vec2::new(11.0, 445.0), Vec2::new(23.0, 445.0), 0.82),
|
||||
Segment::new(Vec2::new(13.0, 288.0), Vec2::new(13.0, 448.0), 0.82),
|
||||
Segment::new(Vec2::new(32.0, 228.0), Vec2::new(12.0, 293.0), 0.82),
|
||||
Segment::new(Vec2::new(10.0, 158.0), Vec2::new(33.0, 227.0), 0.82),
|
||||
Segment::new(Vec2::new(12.0, 51.0), Vec2::new(12.0, 197.0), 0.82),
|
||||
Segment::new(Vec2::new(37.0, 111.0), Vec2::new(37.0, 166.0), 0.82),
|
||||
Segment::new(Vec2::new(110.0, 74.0), Vec2::new(37.0, 111.0), 0.82),
|
||||
Segment::new(Vec2::new(19.0, 73.0), Vec2::new(92.0, 50.0), 0.82),
|
||||
Segment::new(Vec2::new(19.0, 167.0), Vec2::new(19.0, 73.0), 0.82),
|
||||
Segment::new(Vec2::new(141.0, 45.0), Vec2::new(112.0, 72.0), 0.82),
|
||||
Segment::new(Vec2::new(97.0, 47.0), Vec2::new(130.0, 31.0), 0.82),
|
||||
Segment::new(Vec2::new(0.0, 433.0), Vec2::new(7.0, 478.0), 0.82),
|
||||
Segment::new(Vec2::new(292.0, 73.0), Vec2::new(292.0, 167.0), 0.82),
|
||||
Segment::new(Vec2::new(221.0, 50.0), Vec2::new(292.0, 73.0), 0.82),
|
||||
Segment::new(Vec2::new(275.0, 111.0), Vec2::new(205.0, 74.0), 0.82),
|
||||
Segment::new(Vec2::new(275.0, 166.0), Vec2::new(275.0, 111.0), 0.82),
|
||||
Segment::new(Vec2::new(177.0, 31.0), Vec2::new(216.0, 47.0), 0.82),
|
||||
Segment::new(Vec2::new(201.0, 72.0), Vec2::new(172.0, 45.0), 0.82),
|
||||
Segment::new(Vec2::new(40.0, 257.0), Vec2::new(73.0, 273.0), 0.82),
|
||||
Segment::new(Vec2::new(73.0, 273.0), Vec2::new(67.0, 285.0), 0.82),
|
||||
Segment::new(Vec2::new(34.0, 269.0), Vec2::new(40.0, 257.0), 0.82),
|
||||
Segment::new(Vec2::new(292.0, 231.0), Vec2::new(292.0, 205.0), 0.82),
|
||||
Segment::new(Vec2::new(292.0, 205.0), Vec2::new(302.0, 205.0), 0.82),
|
||||
Segment::new(Vec2::new(302.0, 231.0), Vec2::new(292.0, 231.0), 0.82),
|
||||
Segment::new(Vec2::new(292.0, 247.0), Vec2::new(292.0, 222.0), 0.82),
|
||||
Segment::new(Vec2::new(292.0, 222.0), Vec2::new(302.0, 222.0), 0.82),
|
||||
Segment::new(Vec2::new(302.0, 247.0), Vec2::new(292.0, 247.0), 0.82),
|
||||
Segment::new(Vec2::new(292.0, 261.0), Vec2::new(292.0, 237.0), 0.82),
|
||||
Segment::new(Vec2::new(292.0, 237.0), Vec2::new(302.0, 237.0), 0.82),
|
||||
Segment::new(Vec2::new(302.0, 261.0), Vec2::new(292.0, 261.0), 0.82),
|
||||
Segment::new(Vec2::new(292.0, 277.0), Vec2::new(292.0, 252.0), 0.82),
|
||||
Segment::new(Vec2::new(292.0, 252.0), Vec2::new(302.0, 252.0), 0.82),
|
||||
Segment::new(Vec2::new(302.0, 276.0), Vec2::new(292.0, 276.0), 0.82),
|
||||
Segment::new(Vec2::new(292.0, 291.0), Vec2::new(292.0, 268.0), 0.82),
|
||||
Segment::new(Vec2::new(292.0, 269.0), Vec2::new(302.0, 268.0), 0.82),
|
||||
Segment::new(Vec2::new(302.0, 295.0), Vec2::new(292.0, 290.0), 0.82),
|
||||
Segment::new(Vec2::new(148.0, 254.0), Vec2::new(203.0, 236.0), 0.82),
|
||||
Segment::new(Vec2::new(215.0, 261.0), Vec2::new(158.0, 280.0), 0.82),
|
||||
Segment::new(Vec2::new(187.0, 232.0), Vec2::new(209.0, 225.0), 0.82),
|
||||
Segment::new(Vec2::new(209.0, 225.0), Vec2::new(214.0, 240.0), 0.82),
|
||||
Segment::new(Vec2::new(191.0, 244.0), Vec2::new(187.0, 232.0), 0.82),
|
||||
Segment::new(Vec2::new(169.0, 239.0), Vec2::new(198.0, 229.0), 0.82),
|
||||
Segment::new(Vec2::new(198.0, 229.0), Vec2::new(202.0, 241.0), 0.82),
|
||||
Segment::new(Vec2::new(173.0, 251.0), Vec2::new(169.0, 239.0), 0.82),
|
||||
Segment::new(Vec2::new(153.0, 245.0), Vec2::new(182.0, 235.0), 0.82),
|
||||
Segment::new(Vec2::new(182.0, 235.0), Vec2::new(186.0, 247.0), 0.82),
|
||||
Segment::new(Vec2::new(157.0, 257.0), Vec2::new(153.0, 245.0), 0.82),
|
||||
Segment::new(Vec2::new(139.0, 252.0), Vec2::new(163.0, 241.0), 0.82),
|
||||
Segment::new(Vec2::new(163.0, 241.0), Vec2::new(167.0, 253.0), 0.82),
|
||||
Segment::new(Vec2::new(144.0, 267.0), Vec2::new(139.0, 252.0), 0.82),
|
||||
Segment::new(Vec2::new(84.0, 318.0), Vec2::new(105.0, 329.0), 0.82),
|
||||
Segment::new(Vec2::new(105.0, 329.0), Vec2::new(101.0, 339.0), 0.82),
|
||||
Segment::new(Vec2::new(80.0, 328.0), Vec2::new(84.0, 318.0), 0.82),
|
||||
Segment::new(Vec2::new(87.0, 327.0), Vec2::new(111.0, 339.0), 0.82),
|
||||
Segment::new(Vec2::new(77.0, 370.0), Vec2::new(77.0, 331.0), 0.82),
|
||||
Segment::new(Vec2::new(87.0, 372.0), Vec2::new(77.0, 370.0), 0.82),
|
||||
Segment::new(Vec2::new(110.0, 337.0), Vec2::new(87.0, 372.0), 0.82),
|
||||
Segment::new(Vec2::new(184.0, 426.0), Vec2::new(172.0, 403.0), 0.82),
|
||||
Segment::new(Vec2::new(193.0, 396.0), Vec2::new(205.0, 417.0), 0.82),
|
||||
Segment::new(Vec2::new(213.0, 426.0), Vec2::new(201.0, 403.0), 0.82),
|
||||
Segment::new(Vec2::new(222.0, 396.0), Vec2::new(234.0, 417.0), 0.82),
|
||||
Segment::new(Vec2::new(242.0, 426.0), Vec2::new(230.0, 403.0), 0.82),
|
||||
Segment::new(Vec2::new(251.0, 396.0), Vec2::new(263.0, 417.0), 0.82),
|
||||
Segment::new(Vec2::new(291.0, 205.0), Vec2::new(298.0, 187.0), 0.82),
|
||||
Segment::new(Vec2::new(298.0, 378.0), Vec2::new(298.0, 58.0), 0.82),
|
||||
Segment::new(Vec2::new(298.0, 288.0), Vec2::new(298.0, 211.0), 0.82),
|
||||
Segment::new(Vec2::new(298.0, 313.0), Vec2::new(291.0, 290.0), 0.82),
|
||||
]
|
||||
});
|
||||
&WALLS
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -661,21 +584,29 @@ mod tests {
|
||||
fn stalled_ball_search_restores_motion() {
|
||||
let mut game = Game::new(1);
|
||||
game.ball.in_launcher = false;
|
||||
game.ball.position = vec2(220.0, 437.0);
|
||||
game.ball.position = vec2(250.0, 300.0);
|
||||
game.ball.velocity = Vec2::ZERO;
|
||||
game.stalled_for = BALL_SEARCH_DELAY - 1.0 / 120.0;
|
||||
|
||||
let mut events = Vec::new();
|
||||
for _ in 0..600 {
|
||||
events.extend(game.update(1.0 / 120.0, 3, Controls::default()));
|
||||
if events.contains(&Event::BallSearch) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let events = game.update(1.0 / 120.0, 3, Controls::default());
|
||||
|
||||
assert!(events.contains(&Event::BallSearch));
|
||||
assert!(game.ball.velocity.y < 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fast_ball_cannot_tunnel_through_the_top_rail() {
|
||||
let mut game = Game::new(1);
|
||||
game.ball.in_launcher = false;
|
||||
game.ball.position = vec2(270.0, 30.0);
|
||||
game.ball.velocity = vec2(0.0, -MAX_SPEED);
|
||||
|
||||
game.fixed_update(1.0 / 30.0, &mut Vec::new());
|
||||
|
||||
assert!(game.ball.position.y >= 15.0 + BALL_RADIUS - 0.01);
|
||||
assert!(game.ball.velocity.y > 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tilt_latches_and_disables_flippers_and_scoring() {
|
||||
let mut game = Game::new(1);
|
||||
|
||||
@@ -3,6 +3,7 @@ mod assets;
|
||||
mod game;
|
||||
mod geometry;
|
||||
mod persistence;
|
||||
mod table;
|
||||
|
||||
use app::App;
|
||||
use macroquad::prelude::*;
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
//! Collision primitives recovered from the original Win16 object table.
|
||||
//!
|
||||
//! `FUN_1000_34ab` registers objects 1 through 175 through
|
||||
//! `FUN_1008_0138`. The latter reveals the coordinate conversion: ordinary
|
||||
//! objects use `(param_34, param_32 - 20)` and `(param_30, param_28 - 20)`;
|
||||
//! relative objects accumulate those values from the preceding endpoint.
|
||||
//! The old port used `478 - x`, which mirrored the complete table vertically.
|
||||
|
||||
use crate::geometry::Segment;
|
||||
use macroquad::prelude::Vec2;
|
||||
|
||||
const WALL_BOUNCE: f32 = 0.82;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct TableSegment {
|
||||
pub id: u8,
|
||||
pub segment: Segment,
|
||||
}
|
||||
|
||||
impl TableSegment {
|
||||
const fn new(id: u8, start: Vec2, end: Vec2) -> Self {
|
||||
Self {
|
||||
id,
|
||||
segment: Segment::new(start, end, WALL_BOUNCE),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct StaticCircle {
|
||||
pub id: u8,
|
||||
pub center: Vec2,
|
||||
pub radius: f32,
|
||||
}
|
||||
|
||||
impl StaticCircle {
|
||||
const fn new(id: u8, center: Vec2, radius: f32) -> Self {
|
||||
Self { id, center, radius }
|
||||
}
|
||||
}
|
||||
|
||||
/// Active type-2 objects after later registrations overwrite objects 169 and
|
||||
/// 170. Object 2 is the drain sensor and object 25 is the shooter stop; callers
|
||||
/// apply their special behavior after detecting contact.
|
||||
pub const WALLS: &[TableSegment] = &[
|
||||
TableSegment::new(1, Vec2::new(7.0, 397.0), Vec2::new(125.0, 437.0)),
|
||||
TableSegment::new(2, Vec2::new(10.0, 455.0), Vec2::new(300.0, 455.0)),
|
||||
TableSegment::new(3, Vec2::new(134.0, 442.0), Vec2::new(134.0, 425.0)),
|
||||
TableSegment::new(5, Vec2::new(148.0, 425.0), Vec2::new(148.0, 447.0)),
|
||||
TableSegment::new(7, Vec2::new(165.0, 425.0), Vec2::new(164.0, 446.0)),
|
||||
TableSegment::new(9, Vec2::new(179.0, 425.0), Vec2::new(180.0, 447.0)),
|
||||
TableSegment::new(10, Vec2::new(183.0, 437.0), Vec2::new(304.0, 394.0)),
|
||||
TableSegment::new(11, Vec2::new(298.0, 145.0), Vec2::new(298.0, 41.0)),
|
||||
TableSegment::new(12, Vec2::new(310.0, 122.0), Vec2::new(298.0, 122.0)),
|
||||
TableSegment::new(14, Vec2::new(291.0, 116.0), Vec2::new(292.0, 84.0)),
|
||||
TableSegment::new(15, Vec2::new(300.0, 89.0), Vec2::new(281.0, 80.0)),
|
||||
TableSegment::new(16, Vec2::new(290.0, 76.0), Vec2::new(275.0, 115.0)),
|
||||
TableSegment::new(18, Vec2::new(264.0, 119.0), Vec2::new(248.0, 94.0)),
|
||||
TableSegment::new(19, Vec2::new(249.0, 94.0), Vec2::new(257.0, 71.0)),
|
||||
TableSegment::new(20, Vec2::new(257.0, 71.0), Vec2::new(277.0, 29.0)),
|
||||
TableSegment::new(21, Vec2::new(277.0, 31.0), Vec2::new(287.0, 12.0)),
|
||||
TableSegment::new(22, Vec2::new(283.0, 22.0), Vec2::new(303.0, 28.0)),
|
||||
TableSegment::new(23, Vec2::new(302.0, 28.0), Vec2::new(320.0, 48.0)),
|
||||
TableSegment::new(24, Vec2::new(320.0, 48.0), Vec2::new(320.0, 437.0)),
|
||||
TableSegment::new(25, Vec2::new(315.0, 413.0), Vec2::new(332.0, 413.0)),
|
||||
TableSegment::new(27, Vec2::new(328.0, 58.0), Vec2::new(326.0, 47.0)),
|
||||
TableSegment::new(28, Vec2::new(326.0, 47.0), Vec2::new(322.0, 37.0)),
|
||||
TableSegment::new(29, Vec2::new(322.0, 37.0), Vec2::new(316.0, 28.0)),
|
||||
TableSegment::new(30, Vec2::new(316.0, 28.0), Vec2::new(307.0, 21.0)),
|
||||
TableSegment::new(31, Vec2::new(307.0, 21.0), Vec2::new(296.0, 16.0)),
|
||||
TableSegment::new(32, Vec2::new(296.0, 16.0), Vec2::new(285.0, 15.0)),
|
||||
TableSegment::new(33, Vec2::new(285.0, 15.0), Vec2::new(112.0, 15.0)),
|
||||
TableSegment::new(34, Vec2::new(121.0, 14.0), Vec2::new(99.0, 20.0)),
|
||||
TableSegment::new(35, Vec2::new(100.0, 20.0), Vec2::new(78.0, 31.0)),
|
||||
TableSegment::new(36, Vec2::new(79.0, 30.0), Vec2::new(66.0, 49.0)),
|
||||
TableSegment::new(37, Vec2::new(68.0, 45.0), Vec2::new(57.0, 73.0)),
|
||||
TableSegment::new(38, Vec2::new(59.0, 62.0), Vec2::new(59.0, 154.0)),
|
||||
TableSegment::new(39, Vec2::new(58.0, 147.0), Vec2::new(67.0, 170.0)),
|
||||
TableSegment::new(41, Vec2::new(64.0, 179.0), Vec2::new(41.0, 190.0)),
|
||||
TableSegment::new(43, Vec2::new(31.0, 186.0), Vec2::new(22.0, 165.0)),
|
||||
TableSegment::new(44, Vec2::new(22.0, 165.0), Vec2::new(22.0, 11.0)),
|
||||
TableSegment::new(45, Vec2::new(23.0, 13.0), Vec2::new(11.0, 13.0)),
|
||||
TableSegment::new(46, Vec2::new(13.0, 10.0), Vec2::new(13.0, 170.0)),
|
||||
TableSegment::new(47, Vec2::new(12.0, 165.0), Vec2::new(32.0, 230.0)),
|
||||
TableSegment::new(49, Vec2::new(33.0, 231.0), Vec2::new(10.0, 300.0)),
|
||||
TableSegment::new(50, Vec2::new(12.0, 261.0), Vec2::new(12.0, 407.0)),
|
||||
TableSegment::new(55, Vec2::new(59.0, 287.0), Vec2::new(107.0, 359.0)),
|
||||
TableSegment::new(57, Vec2::new(49.0, 294.0), Vec2::new(2.0, 268.0)),
|
||||
TableSegment::new(59, Vec2::new(44.0, 287.0), Vec2::new(44.0, 242.0)),
|
||||
TableSegment::new(61, Vec2::new(37.0, 292.0), Vec2::new(37.0, 347.0)),
|
||||
TableSegment::new(62, Vec2::new(37.0, 347.0), Vec2::new(110.0, 384.0)),
|
||||
TableSegment::new(63, Vec2::new(92.0, 408.0), Vec2::new(19.0, 385.0)),
|
||||
TableSegment::new(64, Vec2::new(19.0, 385.0), Vec2::new(19.0, 291.0)),
|
||||
TableSegment::new(66, Vec2::new(112.0, 386.0), Vec2::new(141.0, 413.0)),
|
||||
TableSegment::new(68, Vec2::new(130.0, 427.0), Vec2::new(97.0, 411.0)),
|
||||
TableSegment::new(70, Vec2::new(267.0, 292.0), Vec2::new(267.0, 337.0)),
|
||||
TableSegment::new(72, Vec2::new(261.0, 299.0), Vec2::new(214.0, 321.0)),
|
||||
TableSegment::new(74, Vec2::new(252.0, 289.0), Vec2::new(302.0, 218.0)),
|
||||
TableSegment::new(76, Vec2::new(292.0, 291.0), Vec2::new(292.0, 385.0)),
|
||||
TableSegment::new(77, Vec2::new(292.0, 385.0), Vec2::new(221.0, 408.0)),
|
||||
TableSegment::new(78, Vec2::new(205.0, 384.0), Vec2::new(275.0, 347.0)),
|
||||
TableSegment::new(79, Vec2::new(275.0, 347.0), Vec2::new(275.0, 292.0)),
|
||||
TableSegment::new(81, Vec2::new(216.0, 411.0), Vec2::new(177.0, 427.0)),
|
||||
TableSegment::new(83, Vec2::new(172.0, 413.0), Vec2::new(201.0, 386.0)),
|
||||
TableSegment::new(84, Vec2::new(73.0, 185.0), Vec2::new(40.0, 201.0)),
|
||||
TableSegment::new(85, Vec2::new(67.0, 173.0), Vec2::new(73.0, 185.0)),
|
||||
TableSegment::new(86, Vec2::new(40.0, 201.0), Vec2::new(34.0, 189.0)),
|
||||
TableSegment::new(90, Vec2::new(292.0, 253.0), Vec2::new(292.0, 227.0)),
|
||||
TableSegment::new(91, Vec2::new(302.0, 253.0), Vec2::new(292.0, 253.0)),
|
||||
TableSegment::new(92, Vec2::new(292.0, 227.0), Vec2::new(302.0, 227.0)),
|
||||
TableSegment::new(93, Vec2::new(292.0, 236.0), Vec2::new(292.0, 211.0)),
|
||||
TableSegment::new(94, Vec2::new(302.0, 236.0), Vec2::new(292.0, 236.0)),
|
||||
TableSegment::new(95, Vec2::new(292.0, 211.0), Vec2::new(302.0, 211.0)),
|
||||
TableSegment::new(96, Vec2::new(292.0, 221.0), Vec2::new(292.0, 197.0)),
|
||||
TableSegment::new(97, Vec2::new(302.0, 221.0), Vec2::new(292.0, 221.0)),
|
||||
TableSegment::new(98, Vec2::new(292.0, 197.0), Vec2::new(302.0, 197.0)),
|
||||
TableSegment::new(99, Vec2::new(292.0, 206.0), Vec2::new(292.0, 181.0)),
|
||||
TableSegment::new(100, Vec2::new(302.0, 206.0), Vec2::new(292.0, 206.0)),
|
||||
TableSegment::new(101, Vec2::new(292.0, 182.0), Vec2::new(302.0, 182.0)),
|
||||
TableSegment::new(102, Vec2::new(292.0, 190.0), Vec2::new(292.0, 167.0)),
|
||||
TableSegment::new(103, Vec2::new(302.0, 190.0), Vec2::new(292.0, 189.0)),
|
||||
TableSegment::new(104, Vec2::new(292.0, 168.0), Vec2::new(302.0, 163.0)),
|
||||
TableSegment::new(105, Vec2::new(203.0, 222.0), Vec2::new(148.0, 204.0)),
|
||||
TableSegment::new(107, Vec2::new(158.0, 178.0), Vec2::new(215.0, 197.0)),
|
||||
TableSegment::new(109, Vec2::new(209.0, 233.0), Vec2::new(187.0, 226.0)),
|
||||
TableSegment::new(110, Vec2::new(214.0, 218.0), Vec2::new(209.0, 233.0)),
|
||||
TableSegment::new(111, Vec2::new(187.0, 226.0), Vec2::new(191.0, 214.0)),
|
||||
TableSegment::new(112, Vec2::new(198.0, 229.0), Vec2::new(169.0, 219.0)),
|
||||
TableSegment::new(113, Vec2::new(202.0, 217.0), Vec2::new(198.0, 229.0)),
|
||||
TableSegment::new(114, Vec2::new(169.0, 219.0), Vec2::new(173.0, 207.0)),
|
||||
TableSegment::new(115, Vec2::new(182.0, 223.0), Vec2::new(153.0, 213.0)),
|
||||
TableSegment::new(116, Vec2::new(186.0, 211.0), Vec2::new(182.0, 223.0)),
|
||||
TableSegment::new(117, Vec2::new(153.0, 213.0), Vec2::new(157.0, 201.0)),
|
||||
TableSegment::new(118, Vec2::new(163.0, 217.0), Vec2::new(139.0, 206.0)),
|
||||
TableSegment::new(119, Vec2::new(167.0, 205.0), Vec2::new(163.0, 217.0)),
|
||||
TableSegment::new(120, Vec2::new(139.0, 206.0), Vec2::new(144.0, 191.0)),
|
||||
TableSegment::new(121, Vec2::new(105.0, 129.0), Vec2::new(84.0, 140.0)),
|
||||
TableSegment::new(122, Vec2::new(101.0, 119.0), Vec2::new(105.0, 129.0)),
|
||||
TableSegment::new(123, Vec2::new(84.0, 140.0), Vec2::new(80.0, 130.0)),
|
||||
TableSegment::new(124, Vec2::new(111.0, 119.0), Vec2::new(87.0, 131.0)),
|
||||
TableSegment::new(126, Vec2::new(77.0, 127.0), Vec2::new(77.0, 88.0)),
|
||||
TableSegment::new(127, Vec2::new(77.0, 88.0), Vec2::new(87.0, 86.0)),
|
||||
TableSegment::new(128, Vec2::new(87.0, 86.0), Vec2::new(110.0, 121.0)),
|
||||
TableSegment::new(156, Vec2::new(172.0, 55.0), Vec2::new(184.0, 32.0)),
|
||||
TableSegment::new(158, Vec2::new(205.0, 41.0), Vec2::new(193.0, 62.0)),
|
||||
TableSegment::new(160, Vec2::new(201.0, 55.0), Vec2::new(213.0, 32.0)),
|
||||
TableSegment::new(162, Vec2::new(234.0, 41.0), Vec2::new(222.0, 62.0)),
|
||||
TableSegment::new(164, Vec2::new(230.0, 55.0), Vec2::new(242.0, 32.0)),
|
||||
TableSegment::new(166, Vec2::new(263.0, 41.0), Vec2::new(251.0, 62.0)),
|
||||
TableSegment::new(168, Vec2::new(298.0, 271.0), Vec2::new(291.0, 253.0)),
|
||||
TableSegment::new(169, Vec2::new(328.0, 422.0), Vec2::new(328.0, 58.0)),
|
||||
TableSegment::new(170, Vec2::new(298.0, 400.0), Vec2::new(298.0, 80.0)),
|
||||
TableSegment::new(171, Vec2::new(298.0, 247.0), Vec2::new(298.0, 170.0)),
|
||||
TableSegment::new(173, Vec2::new(291.0, 168.0), Vec2::new(298.0, 145.0)),
|
||||
];
|
||||
|
||||
/// Active type-1 solid circles, excluding the three scored bumpers and object
|
||||
/// 174, whose position is overwritten every frame for the live ball.
|
||||
pub const PASSIVE_CIRCLES: &[StaticCircle] = &[
|
||||
StaticCircle::new(4, Vec2::new(141.0, 426.0), 8.0),
|
||||
StaticCircle::new(8, Vec2::new(172.0, 426.0), 8.0),
|
||||
StaticCircle::new(13, Vec2::new(298.0, 116.0), 7.0),
|
||||
StaticCircle::new(17, Vec2::new(269.0, 114.0), 7.0),
|
||||
StaticCircle::new(26, Vec2::new(257.0, 95.0), 7.0),
|
||||
StaticCircle::new(40, Vec2::new(60.0, 173.0), 8.0),
|
||||
StaticCircle::new(42, Vec2::new(38.0, 183.0), 8.0),
|
||||
StaticCircle::new(48, Vec2::new(25.0, 231.0), 8.0),
|
||||
StaticCircle::new(54, Vec2::new(53.0, 292.0), 8.0),
|
||||
StaticCircle::new(56, Vec2::new(50.0, 287.0), 8.0),
|
||||
StaticCircle::new(58, Vec2::new(52.0, 287.0), 8.0),
|
||||
StaticCircle::new(60, Vec2::new(29.0, 294.0), 10.0),
|
||||
StaticCircle::new(65, Vec2::new(103.0, 397.0), 15.0),
|
||||
StaticCircle::new(67, Vec2::new(134.0, 419.0), 9.0),
|
||||
StaticCircle::new(69, Vec2::new(260.0, 292.0), 8.0),
|
||||
StaticCircle::new(71, Vec2::new(260.0, 291.0), 8.0),
|
||||
StaticCircle::new(73, Vec2::new(261.0, 291.0), 8.0),
|
||||
StaticCircle::new(75, Vec2::new(284.0, 292.0), 9.0),
|
||||
StaticCircle::new(80, Vec2::new(210.0, 397.0), 15.0),
|
||||
StaticCircle::new(82, Vec2::new(179.0, 419.0), 9.0),
|
||||
StaticCircle::new(106, Vec2::new(156.0, 192.0), 14.0),
|
||||
StaticCircle::new(108, Vec2::new(210.0, 210.0), 14.0),
|
||||
StaticCircle::new(125, Vec2::new(84.0, 126.0), 8.0),
|
||||
StaticCircle::new(134, Vec2::new(124.0, 76.0), 20.0),
|
||||
StaticCircle::new(135, Vec2::new(100.0, 58.0), 17.0),
|
||||
StaticCircle::new(136, Vec2::new(133.0, 48.0), 17.0),
|
||||
StaticCircle::new(137, Vec2::new(154.0, 76.0), 17.0),
|
||||
StaticCircle::new(138, Vec2::new(133.0, 105.0), 17.0),
|
||||
StaticCircle::new(139, Vec2::new(100.0, 94.0), 17.0),
|
||||
StaticCircle::new(155, Vec2::new(183.0, 59.0), 11.0),
|
||||
StaticCircle::new(157, Vec2::new(195.0, 36.0), 11.0),
|
||||
StaticCircle::new(159, Vec2::new(212.0, 59.0), 11.0),
|
||||
StaticCircle::new(161, Vec2::new(224.0, 36.0), 11.0),
|
||||
StaticCircle::new(163, Vec2::new(241.0, 59.0), 11.0),
|
||||
StaticCircle::new(165, Vec2::new(253.0, 36.0), 11.0),
|
||||
StaticCircle::new(167, Vec2::new(299.0, 248.0), 9.0),
|
||||
StaticCircle::new(172, Vec2::new(299.0, 171.0), 9.0),
|
||||
];
|
||||
|
||||
pub const BUMPERS: [StaticCircle; 3] = [
|
||||
StaticCircle::new(51, Vec2::new(165.0, 148.0), 23.0),
|
||||
StaticCircle::new(52, Vec2::new(206.0, 110.0), 23.0),
|
||||
StaticCircle::new(53, Vec2::new(219.0, 165.0), 23.0),
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn recovered_object_inventory_is_complete() {
|
||||
assert_eq!(WALLS.len(), 109);
|
||||
assert_eq!(PASSIVE_CIRCLES.len(), 37);
|
||||
assert_eq!(BUMPERS.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recovered_landmarks_match_the_visible_table() {
|
||||
let left_flipper_post = PASSIVE_CIRCLES
|
||||
.iter()
|
||||
.find(|circle| circle.id == 65)
|
||||
.expect("object 65 should be the left flipper post");
|
||||
assert_eq!(left_flipper_post.center, Vec2::new(103.0, 397.0));
|
||||
|
||||
let shooter_wall = WALLS
|
||||
.iter()
|
||||
.find(|wall| wall.id == 169)
|
||||
.expect("object 169 should be the outside shooter wall");
|
||||
assert_eq!(shooter_wall.segment.start, Vec2::new(328.0, 422.0));
|
||||
assert_eq!(shooter_wall.segment.end, Vec2::new(328.0, 58.0));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user