diff --git a/tdkpin-rs/src/app.rs b/tdkpin-rs/src/app.rs index dd76719..ef88215 100644 --- a/tdkpin-rs/src/app.rs +++ b/tdkpin-rs/src/app.rs @@ -452,6 +452,30 @@ impl App { }, ); } + for (raised, region) in [ + ( + game.flippers.left_raised, + Rect::new(93.0, 373.0, 47.0, 52.0), + ), + ( + game.flippers.right_raised, + Rect::new(175.0, 372.0, 45.0, 53.0), + ), + ] { + if raised { + draw_texture_ex( + &self.assets.active_table, + region.x, + region.y, + WHITE, + DrawTextureParams { + dest_size: Some(vec2(region.w, region.h)), + source: Some(region), + ..Default::default() + }, + ); + } + } draw_texture( &self.assets.ball, game.ball.position.x - 8.0, diff --git a/tdkpin-rs/src/game.rs b/tdkpin-rs/src/game.rs index f2c561f..289a7ec 100644 --- a/tdkpin-rs/src/game.rs +++ b/tdkpin-rs/src/game.rs @@ -10,6 +10,12 @@ const FLIPPER_CONTACT_RADIUS: f32 = 9.0; // the rendered ball radius to every recovered boundary. const TABLE_LINE_RADIUS: f32 = 1.0; const SHOOTER_EXIT_GATE_ID: u8 = 22; +const LEFT_FLIPPER_PIVOT: Vec2 = Vec2::new(103.0, 397.0); +const LEFT_FLIPPER_REST_TIP: Vec2 = Vec2::new(134.0, 419.0); +const LEFT_FLIPPER_RAISED_TIP: Vec2 = Vec2::new(134.0, 376.0); +const RIGHT_FLIPPER_PIVOT: Vec2 = Vec2::new(210.0, 397.0); +const RIGHT_FLIPPER_REST_TIP: Vec2 = Vec2::new(179.0, 419.0); +const RIGHT_FLIPPER_RAISED_TIP: Vec2 = Vec2::new(179.0, 376.0); const GRAVITY: f32 = 135.0; const MAX_SPEED: f32 = 430.0; const BALL_SEARCH_DELAY: f32 = 3.0; @@ -45,6 +51,12 @@ pub enum Event { Drain, } +#[derive(Clone, Copy, Debug, Default)] +pub struct Flippers { + pub left_raised: bool, + pub right_raised: bool, +} + #[derive(Clone, Debug)] pub struct Player { pub score: u32, @@ -97,8 +109,7 @@ pub struct Game { pub lock_lights: u8, pub magnets: f32, pub tilted: bool, - pub left_flipper_angle: f32, - pub right_flipper_angle: f32, + pub flippers: Flippers, pub bumper_flash: [f32; 3], pub wheel_animation: f32, pub robot_animation: f32, @@ -127,8 +138,7 @@ impl Game { lock_lights: 0, magnets: 0.0, tilted: false, - left_flipper_angle: -2.72, - right_flipper_angle: -0.42, + flippers: Flippers::default(), bumper_flash: [0.0; 3], wheel_animation: 0.0, robot_animation: 0.0, @@ -163,26 +173,11 @@ impl Game { return Vec::new(); } let mut events = Vec::new(); - let desired_left = if controls.left_flipper && !self.tilted { - -2.18 - } else { - -2.72 - }; - let desired_right = if controls.right_flipper && !self.tilted { - -0.96 - } else { - -0.42 - }; - let flipper_rate = 14.0 * frame_time; - let old_left = self.left_flipper_angle; - let old_right = self.right_flipper_angle; - self.left_flipper_angle += - (desired_left - self.left_flipper_angle).clamp(-flipper_rate, flipper_rate); - self.right_flipper_angle += - (desired_right - self.right_flipper_angle).clamp(-flipper_rate, flipper_rate); - if !self.tilted - && ((controls.left_flipper && old_left < -2.68) - || (controls.right_flipper && old_right > -0.46)) + let old_flippers = self.flippers; + self.flippers.left_raised = controls.left_flipper && !self.tilted; + self.flippers.right_raised = controls.right_flipper && !self.tilted; + if (self.flippers.left_raised && !old_flippers.left_raised) + || (self.flippers.right_raised && !old_flippers.right_raised) { events.push(Event::Flipper); } @@ -264,6 +259,11 @@ impl Game { let mut drained = false; for wall in WALLS { + // Objects 66/68 and 81/83 are the resting flipper edges. + // Their live shapes are handled as moving capsules below. + if matches!(wall.id, 66 | 68 | 81 | 83) { + continue; + } // The upper shooter exit is a one-way gate. After the outer // curve turns the launched ball down and left, object 22 must // let it enter the playfield. A ball approaching from below @@ -308,6 +308,11 @@ impl Game { for circle in PASSIVE_CIRCLES { debug_assert_ne!(circle.id, 174, "the live ball is not a static obstacle"); + // Objects 67 and 82 are the movable flipper tips. The fixed + // pivot circles (65 and 80) remain valid in either position. + if matches!(circle.id, 67 | 82) { + continue; + } circle_collision( &mut self.ball.position, &mut self.ball.velocity, @@ -318,25 +323,24 @@ impl Game { ); } - 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); + let (left_flipper, right_flipper) = self.flipper_segments(); if segment_collision( &mut self.ball.position, &mut self.ball.velocity, FLIPPER_CONTACT_RADIUS, left_flipper, - ) && self.left_flipper_angle > -2.55 + ) && self.flippers.left_raised { - self.ball.velocity += vec2(-20.0, -115.0); + self.ball.velocity += vec2(20.0, -115.0); } if segment_collision( &mut self.ball.position, &mut self.ball.velocity, FLIPPER_CONTACT_RADIUS, right_flipper, - ) && self.right_flipper_angle < -0.58 + ) && self.flippers.right_raised { - self.ball.velocity += vec2(20.0, -115.0); + self.ball.velocity += vec2(-20.0, -115.0); } for (index, bumper) in BUMPERS.into_iter().enumerate() { @@ -560,10 +564,27 @@ impl Game { } self.finished = true; } + + fn flipper_segments(&self) -> (Segment, Segment) { + let left_tip = if self.flippers.left_raised { + LEFT_FLIPPER_RAISED_TIP + } else { + LEFT_FLIPPER_REST_TIP + }; + let right_tip = if self.flippers.right_raised { + RIGHT_FLIPPER_RAISED_TIP + } else { + RIGHT_FLIPPER_REST_TIP + }; + ( + flipper_segment(LEFT_FLIPPER_PIVOT, left_tip), + flipper_segment(RIGHT_FLIPPER_PIVOT, right_tip), + ) + } } -fn flipper_segment(pivot: Vec2, angle: f32) -> Segment { - Segment::new(pivot, pivot + vec2(angle.cos(), angle.sin()) * 49.0, 0.88) +fn flipper_segment(pivot: Vec2, tip: Vec2) -> Segment { + Segment::new(pivot, tip, 0.88) } #[allow(clippy::too_many_lines)] @@ -751,8 +772,8 @@ mod tests { assert!(game.tilted); assert_eq!(game.bonus, 0); - game.left_flipper_angle = -2.18; - game.right_flipper_angle = -0.96; + game.flippers.left_raised = true; + game.flippers.right_raised = true; let score = game.player().score; game.ball.in_launcher = false; game.ball.position = vec2(201.0, 285.0); @@ -767,11 +788,39 @@ mod tests { }, ); - assert!(game.left_flipper_angle < -2.18); - assert!(game.right_flipper_angle > -0.96); + assert!(!game.flippers.left_raised); + assert!(!game.flippers.right_raised); assert_eq!(game.player().score, score); } + #[test] + fn flippers_use_the_recovered_binary_positions() { + let mut game = Game::new(1); + let (left, right) = game.flipper_segments(); + assert_eq!( + (left.start, left.end), + (LEFT_FLIPPER_PIVOT, LEFT_FLIPPER_REST_TIP) + ); + assert_eq!( + (right.start, right.end), + (RIGHT_FLIPPER_PIVOT, RIGHT_FLIPPER_REST_TIP) + ); + + let events = game.update( + 1.0 / 60.0, + 3, + Controls { + left_flipper: true, + right_flipper: true, + ..Controls::default() + }, + ); + let (left, right) = game.flipper_segments(); + assert!(events.contains(&Event::Flipper)); + assert_eq!(left.end, LEFT_FLIPPER_RAISED_TIP); + assert_eq!(right.end, RIGHT_FLIPPER_RAISED_TIP); + } + #[test] fn drain_clears_latched_tilt() { let mut game = Game::new(1);