fmt: just fmt (rust only)
Build TDK Pinball / build (macos-latest) (push) Canceled after 0s
Build TDK Pinball / build (ubuntu-latest) (push) Canceled after 0s
Build TDK Pinball / build (windows-latest) (push) Canceled after 0s
Build TDK Pinball / build (macos-latest) (push) Canceled after 0s
Build TDK Pinball / build (ubuntu-latest) (push) Canceled after 0s
Build TDK Pinball / build (windows-latest) (push) Canceled after 0s
This commit is contained in:
+78
-53
@@ -1,24 +1,67 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use macroquad::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
assets::Assets,
|
assets::Assets,
|
||||||
game::{Controls, Event, Game, Nudge},
|
game::{Controls, Event, Game, Nudge},
|
||||||
persistence::{
|
persistence::{
|
||||||
HighScore, Language, Persistence, SavedData, insert_high_score, qualifies_high_score,
|
HighScore,
|
||||||
|
Language,
|
||||||
|
Persistence,
|
||||||
|
SavedData,
|
||||||
|
insert_high_score,
|
||||||
|
qualifies_high_score,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use macroquad::prelude::*;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
const WIDTH: f32 = 640.0;
|
const WIDTH: f32 = 640.0;
|
||||||
const HEIGHT: f32 = 460.0;
|
const HEIGHT: f32 = 460.0;
|
||||||
const DETAIL_TIMER_SECONDS: [f32; 5] = [0.050, 0.040, 0.030, 0.020, 0.010];
|
const DETAIL_TIMER_SECONDS: [f32; 5] = [0.050, 0.040, 0.030, 0.020, 0.010];
|
||||||
const ADD_PLAYER_KEYS: [KeyCode; 3] = [KeyCode::KpAdd, KeyCode::RightBracket, KeyCode::Equal];
|
const ADD_PLAYER_KEYS: [KeyCode; 3] = [KeyCode::KpAdd, KeyCode::RightBracket, KeyCode::Equal];
|
||||||
const TARGET_POSITIONS: [[(f32, f32); 5]; 6] = [
|
const TARGET_POSITIONS: [[(f32, f32); 5]; 6] = [
|
||||||
[(9.0, 41.0), (23.0, 11.0), (56.0, 14.0), (63.0, 47.0), (35.0, 63.0)],
|
[
|
||||||
[(11.0, 45.0), (18.0, 13.0), (52.0, 11.0), (63.0, 42.0), (39.0, 64.0)],
|
(9.0, 41.0),
|
||||||
[(13.0, 50.0), (15.0, 17.0), (46.0, 9.0), (64.0, 37.0), (44.0, 62.0)],
|
(23.0, 11.0),
|
||||||
[(17.0, 55.0), (11.0, 23.0), (39.0, 7.0), (64.0, 30.0), (50.0, 59.0)],
|
(56.0, 14.0),
|
||||||
[(21.0, 60.0), (9.0, 28.0), (35.0, 7.0), (62.0, 25.0), (54.0, 56.0)],
|
(63.0, 47.0),
|
||||||
[(8.0, 35.0), (26.0, 8.0), (59.0, 18.0), (59.0, 50.0), (27.0, 61.0)],
|
(35.0, 63.0),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
(11.0, 45.0),
|
||||||
|
(18.0, 13.0),
|
||||||
|
(52.0, 11.0),
|
||||||
|
(63.0, 42.0),
|
||||||
|
(39.0, 64.0),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
(13.0, 50.0),
|
||||||
|
(15.0, 17.0),
|
||||||
|
(46.0, 9.0),
|
||||||
|
(64.0, 37.0),
|
||||||
|
(44.0, 62.0),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
(17.0, 55.0),
|
||||||
|
(11.0, 23.0),
|
||||||
|
(39.0, 7.0),
|
||||||
|
(64.0, 30.0),
|
||||||
|
(50.0, 59.0),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
(21.0, 60.0),
|
||||||
|
(9.0, 28.0),
|
||||||
|
(35.0, 7.0),
|
||||||
|
(62.0, 25.0),
|
||||||
|
(54.0, 56.0),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
(8.0, 35.0),
|
||||||
|
(26.0, 8.0),
|
||||||
|
(59.0, 18.0),
|
||||||
|
(59.0, 50.0),
|
||||||
|
(27.0, 61.0),
|
||||||
|
],
|
||||||
];
|
];
|
||||||
const BUMPER_VALUE_REGIONS: [(i32, i32, i32, i32); 5] = [
|
const BUMPER_VALUE_REGIONS: [(i32, i32, i32, i32); 5] = [
|
||||||
(103, 304, 121, 323),
|
(103, 304, 121, 323),
|
||||||
@@ -116,11 +159,7 @@ impl AttractAnimation {
|
|||||||
|
|
||||||
fn target_active(self, index: usize) -> bool {
|
fn target_active(self, index: usize) -> bool {
|
||||||
let index = u32::try_from(index).unwrap_or(0);
|
let index = u32::try_from(index).unwrap_or(0);
|
||||||
let first_activation = if index == 0 {
|
let first_activation = if index == 0 { 32 } else { index * 2 };
|
||||||
32
|
|
||||||
} else {
|
|
||||||
index * 2
|
|
||||||
};
|
|
||||||
self.tick >= first_activation && self.tick.wrapping_sub(index * 2) % 32 < 16
|
self.tick >= first_activation && self.tick.wrapping_sub(index * 2) % 32 < 16
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,11 +176,7 @@ impl AttractAnimation {
|
|||||||
|
|
||||||
fn record_strip_active(self, index: usize) -> bool {
|
fn record_strip_active(self, index: usize) -> bool {
|
||||||
let index = u32::try_from(index).unwrap_or(0);
|
let index = u32::try_from(index).unwrap_or(0);
|
||||||
let first_activation = if index == 0 {
|
let first_activation = if index == 0 { 40 } else { index * 4 };
|
||||||
40
|
|
||||||
} else {
|
|
||||||
index * 4
|
|
||||||
};
|
|
||||||
self.tick >= first_activation && self.tick.wrapping_sub(index * 4) % 40 < 20
|
self.tick >= first_activation && self.tick.wrapping_sub(index * 4) % 40 < 20
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,11 +186,7 @@ impl AttractAnimation {
|
|||||||
|
|
||||||
fn item_index(self) -> Option<usize> {
|
fn item_index(self) -> Option<usize> {
|
||||||
let phase = (self.tick % 144) / 8;
|
let phase = (self.tick % 144) / 8;
|
||||||
let item = if phase < 10 {
|
let item = if phase < 10 { phase } else { 18 - phase };
|
||||||
phase
|
|
||||||
} else {
|
|
||||||
18 - phase
|
|
||||||
};
|
|
||||||
(item != 0).then(|| usize::try_from(item - 1).unwrap_or(0))
|
(item != 0).then(|| usize::try_from(item - 1).unwrap_or(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,7 +526,8 @@ impl App {
|
|||||||
self.name.pop();
|
self.name.pop();
|
||||||
}
|
}
|
||||||
let click = is_mouse_button_pressed(MouseButton::Left).then(Self::logical_mouse);
|
let click = is_mouse_button_pressed(MouseButton::Left).then(Self::logical_mouse);
|
||||||
let clicked_ok = click.is_some_and(|point| Rect::new(255.0, 256.0, 60.0, 23.0).contains(point));
|
let clicked_ok =
|
||||||
|
click.is_some_and(|point| Rect::new(255.0, 256.0, 60.0, 23.0).contains(point));
|
||||||
let clicked_cancel =
|
let clicked_cancel =
|
||||||
click.is_some_and(|point| Rect::new(324.0, 256.0, 60.0, 23.0).contains(point));
|
click.is_some_and(|point| Rect::new(324.0, 256.0, 60.0, 23.0).contains(point));
|
||||||
if is_key_pressed(KeyCode::Escape) || clicked_cancel {
|
if is_key_pressed(KeyCode::Escape) || clicked_cancel {
|
||||||
@@ -574,11 +606,8 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.attract.magnetic_records_active() {
|
if self.attract.magnetic_records_active() {
|
||||||
for (x, y, width, height) in [
|
for (x, y, width, height) in [(151, 389, 13, 49), (11, 344, 13, 49), (291, 346, 13, 49)]
|
||||||
(151, 389, 13, 49),
|
{
|
||||||
(11, 344, 13, 49),
|
|
||||||
(291, 346, 13, 49),
|
|
||||||
] {
|
|
||||||
self.draw_active_table_region(x, y, width, height);
|
self.draw_active_table_region(x, y, width, height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -605,12 +634,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(item) = self.attract.item_index() {
|
if let Some(item) = self.attract.item_index() {
|
||||||
draw_texture(
|
draw_texture(&self.assets.diamond[item], 123.0, 300.0, WHITE);
|
||||||
&self.assets.diamond[item],
|
|
||||||
123.0,
|
|
||||||
300.0,
|
|
||||||
WHITE,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
self.draw_intro_marquee();
|
self.draw_intro_marquee();
|
||||||
}
|
}
|
||||||
@@ -929,7 +953,15 @@ impl App {
|
|||||||
} else if (66..=128).contains(&frame) {
|
} else if (66..=128).contains(&frame) {
|
||||||
draw_texture_region(&self.assets.wheel, 79.0, 33.0, 91.0, 90.0, 180.0, 90.0);
|
draw_texture_region(&self.assets.wheel, 79.0, 33.0, 91.0, 90.0, 180.0, 90.0);
|
||||||
if frame < 83 {
|
if frame < 83 {
|
||||||
draw_texture_region(wide_a, 79.0, ((82 - frame) * 2) as f32, 91.0, 90.0, 0.0, 0.0);
|
draw_texture_region(
|
||||||
|
wide_a,
|
||||||
|
79.0,
|
||||||
|
((82 - frame) * 2) as f32,
|
||||||
|
91.0,
|
||||||
|
90.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
let height = (128 - frame) * 2;
|
let height = (128 - frame) * 2;
|
||||||
draw_texture_region(
|
draw_texture_region(
|
||||||
@@ -1122,14 +1154,7 @@ impl App {
|
|||||||
draw_centered("SETTINGS", 122.0, 30, BLACK);
|
draw_centered("SETTINGS", 122.0, 30, BLACK);
|
||||||
let rows = [
|
let rows = [
|
||||||
format!("GRAPHICS DETAIL / SPEED: {}", self.saved.settings.speed),
|
format!("GRAPHICS DETAIL / SPEED: {}", self.saved.settings.speed),
|
||||||
format!(
|
format!("SOUND: {}", if self.sounds_enabled { "ON" } else { "OFF" }),
|
||||||
"SOUND: {}",
|
|
||||||
if self.sounds_enabled {
|
|
||||||
"ON"
|
|
||||||
} else {
|
|
||||||
"OFF"
|
|
||||||
}
|
|
||||||
),
|
|
||||||
format!("LANGUAGE: {}", self.saved.settings.language.label()),
|
format!("LANGUAGE: {}", self.saved.settings.language.label()),
|
||||||
];
|
];
|
||||||
for (index, row) in rows.iter().enumerate() {
|
for (index, row) in rows.iter().enumerate() {
|
||||||
@@ -1181,7 +1206,13 @@ impl App {
|
|||||||
draw_rectangle(x, y, 270.0, 124.0, Color::from_rgba(192, 192, 192, 255));
|
draw_rectangle(x, y, 270.0, 124.0, Color::from_rgba(192, 192, 192, 255));
|
||||||
draw_rectangle_lines(x, y, 270.0, 124.0, 2.0, WHITE);
|
draw_rectangle_lines(x, y, 270.0, 124.0, 2.0, WHITE);
|
||||||
draw_rectangle_lines(x + 2.0, y + 2.0, 266.0, 120.0, 2.0, DARKGRAY);
|
draw_rectangle_lines(x + 2.0, y + 2.0, 266.0, 120.0, 2.0, DARKGRAY);
|
||||||
draw_rectangle(x + 4.0, y + 4.0, 262.0, 20.0, Color::from_rgba(0, 0, 128, 255));
|
draw_rectangle(
|
||||||
|
x + 4.0,
|
||||||
|
y + 4.0,
|
||||||
|
262.0,
|
||||||
|
20.0,
|
||||||
|
Color::from_rgba(0, 0, 128, 255),
|
||||||
|
);
|
||||||
draw_text(
|
draw_text(
|
||||||
"Congratulations! This is a Top Ten Score!",
|
"Congratulations! This is a Top Ten Score!",
|
||||||
x + 9.0,
|
x + 9.0,
|
||||||
@@ -1319,13 +1350,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn attract_timer_uses_the_selected_detail_callback() {
|
fn attract_timer_uses_the_selected_detail_callback() {
|
||||||
for (detail, interval) in [
|
for (detail, interval) in [(1, 0.050), (2, 0.040), (3, 0.030), (4, 0.020), (5, 0.010)] {
|
||||||
(1, 0.050),
|
|
||||||
(2, 0.040),
|
|
||||||
(3, 0.030),
|
|
||||||
(4, 0.020),
|
|
||||||
(5, 0.010),
|
|
||||||
] {
|
|
||||||
let mut animation = AttractAnimation::default();
|
let mut animation = AttractAnimation::default();
|
||||||
animation.update(interval - 0.001, detail);
|
animation.update(interval - 0.001, detail);
|
||||||
assert_eq!(animation.tick, 0);
|
assert_eq!(animation.tick, 0);
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ impl Assets {
|
|||||||
let inactive_table = texture(include_bytes!("../assets/original/images/dat_00998.png"));
|
let inactive_table = texture(include_bytes!("../assets/original/images/dat_00998.png"));
|
||||||
let loading = texture(include_bytes!("../assets/original/images/dat_00995.png"));
|
let loading = texture(include_bytes!("../assets/original/images/dat_00995.png"));
|
||||||
let loading_progress = texture(include_bytes!("../assets/original/images/dat_00994.png"));
|
let loading_progress = texture(include_bytes!("../assets/original/images/dat_00994.png"));
|
||||||
let highscore_background = texture(include_bytes!("../assets/original/images/dat_00993.png"));
|
let highscore_background =
|
||||||
|
texture(include_bytes!("../assets/original/images/dat_00993.png"));
|
||||||
let help = [
|
let help = [
|
||||||
texture(include_bytes!("../assets/original/images/dat_01001.png")),
|
texture(include_bytes!("../assets/original/images/dat_01001.png")),
|
||||||
texture(include_bytes!("../assets/original/images/dat_01002.png")),
|
texture(include_bytes!("../assets/original/images/dat_01002.png")),
|
||||||
|
|||||||
@@ -77,15 +77,16 @@ mod tests {
|
|||||||
fn zero_bounds_still_advance_the_seed() {
|
fn zero_bounds_still_advance_the_seed() {
|
||||||
let mut random = BorlandRandom::new(7);
|
let mut random = BorlandRandom::new(7);
|
||||||
assert_eq!(random.below(0), 0);
|
assert_eq!(random.below(0), 0);
|
||||||
assert_eq!(random.seed(), 7_u32.wrapping_mul(MULTIPLIER).wrapping_add(1));
|
assert_eq!(
|
||||||
|
random.seed(),
|
||||||
|
7_u32.wrapping_mul(MULTIPLIER).wrapping_add(1)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unit_interval_is_the_exact_unsigned_seed_fraction() {
|
fn unit_interval_is_the_exact_unsigned_seed_fraction() {
|
||||||
let mut random = BorlandRandom::new(0xfedc_ba98);
|
let mut random = BorlandRandom::new(0xfedc_ba98);
|
||||||
let expected_seed = 0xfedc_ba98_u32
|
let expected_seed = 0xfedc_ba98_u32.wrapping_mul(MULTIPLIER).wrapping_add(1);
|
||||||
.wrapping_mul(MULTIPLIER)
|
|
||||||
.wrapping_add(1);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
random.unit_interval().to_bits(),
|
random.unit_interval().to_bits(),
|
||||||
(f64::from(expected_seed) / TWO_TO_32).to_bits()
|
(f64::from(expected_seed) / TWO_TO_32).to_bits()
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ use crate::{original_physics::MilliVec, real48::Real48};
|
|||||||
const SEARCH_RADIUS: i32 = 54_000;
|
const SEARCH_RADIUS: i32 = 54_000;
|
||||||
const ONE: Real48 = Real48::from_bytes([0x81, 0, 0, 0, 0, 0]);
|
const ONE: Real48 = Real48::from_bytes([0x81, 0, 0, 0, 0, 0]);
|
||||||
const TWO: Real48 = Real48::from_bytes([0x82, 0, 0, 0, 0, 0]);
|
const TWO: Real48 = Real48::from_bytes([0x82, 0, 0, 0, 0, 0]);
|
||||||
const TWO_FIFTHS: Real48 =
|
const TWO_FIFTHS: Real48 = Real48::from_bytes([0x7f, 0xcd, 0xcc, 0xcc, 0xcc, 0x4c]);
|
||||||
Real48::from_bytes([0x7f, 0xcd, 0xcc, 0xcc, 0xcc, 0x4c]);
|
|
||||||
const THOUSAND: Real48 = Real48::from_bytes([0x8a, 0, 0, 0, 0, 0x7a]);
|
const THOUSAND: Real48 = Real48::from_bytes([0x8a, 0, 0, 0, 0, 0x7a]);
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
@@ -171,10 +170,7 @@ fn penetration(geometry: Geometry, delta: i32, ball: MilliVec) -> i32 {
|
|||||||
.wrapping_add(43_000)
|
.wrapping_add(43_000)
|
||||||
.wrapping_sub(geometry.pivot.y),
|
.wrapping_sub(geometry.pivot.y),
|
||||||
);
|
);
|
||||||
geometry
|
geometry.pivot.y.wrapping_sub(
|
||||||
.pivot
|
|
||||||
.y
|
|
||||||
.wrapping_sub(
|
|
||||||
Real48::from_i32(numerator)
|
Real48::from_i32(numerator)
|
||||||
.divide(Real48::from_i32(edge_dx))
|
.divide(Real48::from_i32(edge_dx))
|
||||||
.round_i32(),
|
.round_i32(),
|
||||||
@@ -299,8 +295,14 @@ mod tests {
|
|||||||
let velocities = [
|
let velocities = [
|
||||||
MilliVec { x: 0, y: 2_000 },
|
MilliVec { x: 0, y: 2_000 },
|
||||||
MilliVec { x: 1_000, y: 2_000 },
|
MilliVec { x: 1_000, y: 2_000 },
|
||||||
MilliVec { x: -1_000, y: 2_000 },
|
MilliVec {
|
||||||
MilliVec { x: 2_700, y: -2_700 },
|
x: -1_000,
|
||||||
|
y: 2_000,
|
||||||
|
},
|
||||||
|
MilliVec {
|
||||||
|
x: 2_700,
|
||||||
|
y: -2_700,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
let mut hash = 14_695_981_039_346_656_037_u64;
|
let mut hash = 14_695_981_039_346_656_037_u64;
|
||||||
let mut hits = 0_u32;
|
let mut hits = 0_u32;
|
||||||
@@ -315,10 +317,10 @@ mod tests {
|
|||||||
Real48::from_bytes([0x80, 0, 0, 0, 0, 0]),
|
Real48::from_bytes([0x80, 0, 0, 0, 0, 0]),
|
||||||
3_800,
|
3_800,
|
||||||
);
|
);
|
||||||
let (hit, velocity, movement) = response.map_or(
|
let (hit, velocity, movement) = response
|
||||||
(0_u32, input_velocity, MilliVec::default()),
|
.map_or((0_u32, input_velocity, MilliVec::default()), |response| {
|
||||||
|response| (1, response.velocity, response.movement),
|
(1, response.velocity, response.movement)
|
||||||
);
|
});
|
||||||
hits += hit;
|
hits += hit;
|
||||||
for value in [
|
for value in [
|
||||||
i32_bits(x),
|
i32_bits(x),
|
||||||
@@ -342,40 +344,73 @@ mod tests {
|
|||||||
fn four_direction_vectors_match_the_reconstructed_c_harness() {
|
fn four_direction_vectors_match_the_reconstructed_c_harness() {
|
||||||
let cases = [
|
let cases = [
|
||||||
(
|
(
|
||||||
MilliVec { x: 104_000, y: 384_000 },
|
MilliVec {
|
||||||
|
x: 104_000,
|
||||||
|
y: 384_000,
|
||||||
|
},
|
||||||
MilliVec { x: 1_000, y: 2_000 },
|
MilliVec { x: 1_000, y: 2_000 },
|
||||||
-1,
|
-1,
|
||||||
FlipperSide::Left,
|
FlipperSide::Left,
|
||||||
Real48::from_bytes([0x80, 0, 0, 0, 0, 0]),
|
Real48::from_bytes([0x80, 0, 0, 0, 0, 0]),
|
||||||
MilliVec { x: 1_266, y: 1_511 },
|
MilliVec { x: 1_266, y: 1_511 },
|
||||||
MilliVec { x: -6_703, y: -8_000 },
|
MilliVec {
|
||||||
|
x: -6_703,
|
||||||
|
y: -8_000,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
MilliVec { x: 209_000, y: 419_000 },
|
MilliVec {
|
||||||
MilliVec { x: -1_000, y: 2_000 },
|
x: 209_000,
|
||||||
|
y: 419_000,
|
||||||
|
},
|
||||||
|
MilliVec {
|
||||||
|
x: -1_000,
|
||||||
|
y: 2_000,
|
||||||
|
},
|
||||||
1,
|
1,
|
||||||
FlipperSide::Right,
|
FlipperSide::Right,
|
||||||
Real48::from_bytes([0x80, 0, 0, 0, 0, 0x40]),
|
Real48::from_bytes([0x80, 0, 0, 0, 0, 0x40]),
|
||||||
MilliVec { x: -737, y: 2_263 },
|
MilliVec { x: -737, y: 2_263 },
|
||||||
MilliVec { x: -5_578, y: 17_127 },
|
MilliVec {
|
||||||
|
x: -5_578,
|
||||||
|
y: 17_127,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
MilliVec { x: 104_000, y: 421_000 },
|
MilliVec {
|
||||||
|
x: 104_000,
|
||||||
|
y: 421_000,
|
||||||
|
},
|
||||||
MilliVec { x: 1_000, y: 2_000 },
|
MilliVec { x: 1_000, y: 2_000 },
|
||||||
1,
|
1,
|
||||||
FlipperSide::Left,
|
FlipperSide::Left,
|
||||||
Real48::from_bytes([0x7f, 0, 0, 0, 0, 0]),
|
Real48::from_bytes([0x7f, 0, 0, 0, 0, 0]),
|
||||||
MilliVec { x: 622, y: 2_320 },
|
MilliVec { x: 622, y: 2_320 },
|
||||||
MilliVec { x: 5_414, y: 20_193 },
|
MilliVec {
|
||||||
|
x: 5_414,
|
||||||
|
y: 20_193,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
MilliVec { x: 209_000, y: 385_000 },
|
MilliVec {
|
||||||
MilliVec { x: -1_000, y: 2_000 },
|
x: 209_000,
|
||||||
|
y: 385_000,
|
||||||
|
},
|
||||||
|
MilliVec {
|
||||||
|
x: -1_000,
|
||||||
|
y: 2_000,
|
||||||
|
},
|
||||||
-1,
|
-1,
|
||||||
FlipperSide::Right,
|
FlipperSide::Right,
|
||||||
Real48::ZERO,
|
Real48::ZERO,
|
||||||
MilliVec { x: -1_280, y: 1_560 },
|
MilliVec {
|
||||||
MilliVec { x: 7_385, y: -9_000 },
|
x: -1_280,
|
||||||
|
y: 1_560,
|
||||||
|
},
|
||||||
|
MilliVec {
|
||||||
|
x: 7_385,
|
||||||
|
y: -9_000,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
for (ball, velocity, delta, side, response, expected_velocity, expected_movement) in cases {
|
for (ball, velocity, delta, side, response, expected_velocity, expected_movement) in cases {
|
||||||
@@ -398,7 +433,10 @@ mod tests {
|
|||||||
fn boundary_vectors_match_the_live_original_binary() {
|
fn boundary_vectors_match_the_live_original_binary() {
|
||||||
for (ball, velocity, delta, side, expected_velocity, expected_movement) in [
|
for (ball, velocity, delta, side, expected_velocity, expected_movement) in [
|
||||||
(
|
(
|
||||||
MilliVec { x: 100_000, y: 370_000 },
|
MilliVec {
|
||||||
|
x: 100_000,
|
||||||
|
y: 370_000,
|
||||||
|
},
|
||||||
MilliVec { x: 1_000, y: 2_000 },
|
MilliVec { x: 1_000, y: 2_000 },
|
||||||
-1,
|
-1,
|
||||||
FlipperSide::Left,
|
FlipperSide::Left,
|
||||||
@@ -406,7 +444,10 @@ mod tests {
|
|||||||
MilliVec::default(),
|
MilliVec::default(),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
MilliVec { x: 209_000, y: 419_000 },
|
MilliVec {
|
||||||
|
x: 209_000,
|
||||||
|
y: 419_000,
|
||||||
|
},
|
||||||
MilliVec { x: 1_000, y: 2_000 },
|
MilliVec { x: 1_000, y: 2_000 },
|
||||||
1,
|
1,
|
||||||
FlipperSide::Right,
|
FlipperSide::Right,
|
||||||
|
|||||||
+112
-142
@@ -1,20 +1,34 @@
|
|||||||
|
use macroquad::prelude::{Rect, Vec2, vec2};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
borland_random::BorlandRandom,
|
borland_random::BorlandRandom,
|
||||||
flipper_physics::{FlipperSide, moving_flipper_response},
|
flipper_physics::{FlipperSide, moving_flipper_response},
|
||||||
geometry::Segment,
|
geometry::Segment,
|
||||||
original_physics::{
|
original_physics::{
|
||||||
CollisionMaterial, CollisionResponse, GRAVITY_MILLI_PER_STEP,
|
CollisionMaterial,
|
||||||
MAXIMUM_SPEED_MILLI_PER_STEP, MilliVec, StaticCollisionCandidate,
|
CollisionResponse,
|
||||||
ball_collision_response, capture_collision_candidate, circle_collision_candidate_at,
|
GRAVITY_MILLI_PER_STEP,
|
||||||
line_collision_candidate_at, milli_distance, path_intersects_circle,
|
MAXIMUM_SPEED_MILLI_PER_STEP,
|
||||||
|
MilliVec,
|
||||||
|
StaticCollisionCandidate,
|
||||||
|
ball_collision_response,
|
||||||
|
capture_collision_candidate,
|
||||||
|
circle_collision_candidate_at,
|
||||||
|
line_collision_candidate_at,
|
||||||
|
milli_distance,
|
||||||
|
path_intersects_circle,
|
||||||
},
|
},
|
||||||
real48::Real48,
|
real48::Real48,
|
||||||
table::{
|
table::{
|
||||||
BUMPERS, EFFECT_SENSOR, LOCK_HOLES, PASSIVE_CIRCLES, TARGET_SENSORS, WALLS,
|
BUMPERS,
|
||||||
|
EFFECT_SENSOR,
|
||||||
|
LOCK_HOLES,
|
||||||
|
PASSIVE_CIRCLES,
|
||||||
SPECIAL_HOLE_SENSOR,
|
SPECIAL_HOLE_SENSOR,
|
||||||
|
TARGET_SENSORS,
|
||||||
|
WALLS,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use macroquad::prelude::{Rect, Vec2, vec2};
|
|
||||||
|
|
||||||
const LEFT_FLIPPER_RAISED_TIP: Vec2 = Vec2::new(133.0, 377.0);
|
const LEFT_FLIPPER_RAISED_TIP: Vec2 = Vec2::new(133.0, 377.0);
|
||||||
const RIGHT_FLIPPER_RAISED_TIP: Vec2 = Vec2::new(181.0, 376.0);
|
const RIGHT_FLIPPER_RAISED_TIP: Vec2 = Vec2::new(181.0, 376.0);
|
||||||
@@ -483,7 +497,8 @@ impl Game {
|
|||||||
let variation = self.random.below(3_800) / 40;
|
let variation = self.random.below(3_800) / 40;
|
||||||
launch_velocity = -MAXIMUM_SPEED_MILLI_PER_STEP + i32::from(variation);
|
launch_velocity = -MAXIMUM_SPEED_MILLI_PER_STEP + i32::from(variation);
|
||||||
} else if launch_velocity > -2_280 {
|
} else if launch_velocity > -2_280 {
|
||||||
launch_velocity = launch_velocity.wrapping_sub(i32::from(self.random.below(3_800) / 40));
|
launch_velocity =
|
||||||
|
launch_velocity.wrapping_sub(i32::from(self.random.below(3_800) / 40));
|
||||||
}
|
}
|
||||||
self.ball.in_launcher = false;
|
self.ball.in_launcher = false;
|
||||||
self.ball.velocity = MilliVec {
|
self.ball.velocity = MilliVec {
|
||||||
@@ -516,12 +531,10 @@ impl Game {
|
|||||||
} else if self.tilted {
|
} else if self.tilted {
|
||||||
self.flipper_release_latch[1] = true;
|
self.flipper_release_latch[1] = true;
|
||||||
}
|
}
|
||||||
self.flipper_inputs.left_raised = controls.left_flipper
|
self.flipper_inputs.left_raised =
|
||||||
&& !self.tilted
|
controls.left_flipper && !self.tilted && !self.flipper_release_latch[0];
|
||||||
&& !self.flipper_release_latch[0];
|
self.flipper_inputs.right_raised =
|
||||||
self.flipper_inputs.right_raised = controls.right_flipper
|
controls.right_flipper && !self.tilted && !self.flipper_release_latch[1];
|
||||||
&& !self.tilted
|
|
||||||
&& !self.flipper_release_latch[1];
|
|
||||||
|
|
||||||
if self.ball.in_launcher && !self.tilted {
|
if self.ball.in_launcher && !self.tilted {
|
||||||
if controls.launch_down {
|
if controls.launch_down {
|
||||||
@@ -576,10 +589,7 @@ impl Game {
|
|||||||
self.score_mode = ScoreMode::Multiball;
|
self.score_mode = ScoreMode::Multiball;
|
||||||
}
|
}
|
||||||
for _ in 0..substeps {
|
for _ in 0..substeps {
|
||||||
if self.fixed_update(events)
|
if self.fixed_update(events) || self.finished || self.claw.ball_suspended {
|
||||||
|| self.finished
|
|
||||||
|| self.claw.ball_suspended
|
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -609,18 +619,14 @@ impl Game {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.pending_flipper_edges[0] = match (
|
self.pending_flipper_edges[0] =
|
||||||
self.flipper_inputs.left_raised,
|
match (self.flipper_inputs.left_raised, self.flippers.left_raised) {
|
||||||
self.flippers.left_raised,
|
|
||||||
) {
|
|
||||||
(true, false) => -1,
|
(true, false) => -1,
|
||||||
(false, true) => 1,
|
(false, true) => 1,
|
||||||
_ => 0,
|
_ => 0,
|
||||||
};
|
};
|
||||||
self.pending_flipper_edges[1] = match (
|
self.pending_flipper_edges[1] =
|
||||||
self.flipper_inputs.right_raised,
|
match (self.flipper_inputs.right_raised, self.flippers.right_raised) {
|
||||||
self.flippers.right_raised,
|
|
||||||
) {
|
|
||||||
(true, false) => -1,
|
(true, false) => -1,
|
||||||
(false, true) => 1,
|
(false, true) => 1,
|
||||||
_ => 0,
|
_ => 0,
|
||||||
@@ -706,7 +712,11 @@ impl Game {
|
|||||||
match nudge {
|
match nudge {
|
||||||
Nudge::Left | Nudge::Right => {
|
Nudge::Left | Nudge::Right => {
|
||||||
let amount = (50 - i32::from(self.random.below(20))) * SCALAR;
|
let amount = (50 - i32::from(self.random.below(20))) * SCALAR;
|
||||||
let signed = if nudge == Nudge::Left { -amount } else { amount };
|
let signed = if nudge == Nudge::Left {
|
||||||
|
-amount
|
||||||
|
} else {
|
||||||
|
amount
|
||||||
|
};
|
||||||
velocity.x = velocity.x.wrapping_add(signed);
|
velocity.x = velocity.x.wrapping_add(signed);
|
||||||
}
|
}
|
||||||
Nudge::Center => {
|
Nudge::Center => {
|
||||||
@@ -726,12 +736,10 @@ impl Game {
|
|||||||
}
|
}
|
||||||
if let Some(secondary) = &mut self.secondary_ball {
|
if let Some(secondary) = &mut self.secondary_ball {
|
||||||
let slot_impulse = match nudge {
|
let slot_impulse = match nudge {
|
||||||
Nudge::Left | Nudge::Right => {
|
Nudge::Left | Nudge::Right => MilliVec {
|
||||||
MilliVec {
|
|
||||||
x: if nudge == Nudge::Left { -600 } else { 600 },
|
x: if nudge == Nudge::Left { -600 } else { 600 },
|
||||||
y: 0,
|
y: 0,
|
||||||
}
|
},
|
||||||
}
|
|
||||||
Nudge::Center => {
|
Nudge::Center => {
|
||||||
let horizontal = (i32::from(self.random.below(21)) - 10) * SCALAR;
|
let horizontal = (i32::from(self.random.below(21)) - 10) * SCALAR;
|
||||||
let vertical = -(i32::from(self.random.below(100)) + 50) * SCALAR;
|
let vertical = -(i32::from(self.random.below(100)) + 50) * SCALAR;
|
||||||
@@ -1070,13 +1078,7 @@ impl Game {
|
|||||||
best = Some((id, false, candidate));
|
best = Some((id, false, candidate));
|
||||||
}
|
}
|
||||||
ball.velocity = velocity.to_velocity_per_second();
|
ball.velocity = velocity.to_velocity_per_second();
|
||||||
(
|
(best, SensorScanResult { action }, predicted)
|
||||||
best,
|
|
||||||
SensorScanResult {
|
|
||||||
action,
|
|
||||||
},
|
|
||||||
predicted,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_static_collision_candidate_in_range(
|
fn find_static_collision_candidate_in_range(
|
||||||
@@ -1317,12 +1319,7 @@ impl Game {
|
|||||||
collided || scan.action == BallAction::Suspend
|
collided || scan.action == BallAction::Suspend
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_bumper_rule(
|
fn apply_bumper_rule(&mut self, object_id: u8, auxiliary_fired: bool, events: &mut Vec<Event>) {
|
||||||
&mut self,
|
|
||||||
object_id: u8,
|
|
||||||
auxiliary_fired: bool,
|
|
||||||
events: &mut Vec<Event>,
|
|
||||||
) {
|
|
||||||
if self.tilted || !BUMPERS.iter().any(|bumper| bumper.id == object_id) {
|
if self.tilted || !BUMPERS.iter().any(|bumper| bumper.id == object_id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1504,11 +1501,7 @@ impl Game {
|
|||||||
if !self.tilted
|
if !self.tilted
|
||||||
&& (TARGET_SENSORS.into_iter().any(|sensor| {
|
&& (TARGET_SENSORS.into_iter().any(|sensor| {
|
||||||
self.object_active[usize::from(sensor.id)]
|
self.object_active[usize::from(sensor.id)]
|
||||||
&& trigger_broadphase_contains(
|
&& trigger_broadphase_contains(predicted_position, sensor.center, sensor.radius)
|
||||||
predicted_position,
|
|
||||||
sensor.center,
|
|
||||||
sensor.radius,
|
|
||||||
)
|
|
||||||
}) || self.object_active[usize::from(EFFECT_SENSOR.id)]
|
}) || self.object_active[usize::from(EFFECT_SENSOR.id)]
|
||||||
&& trigger_broadphase_contains(
|
&& trigger_broadphase_contains(
|
||||||
predicted_position,
|
predicted_position,
|
||||||
@@ -1549,8 +1542,7 @@ impl Game {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(completed_action) =
|
if let Some(completed_action) = self.check_lock_holes(
|
||||||
self.check_lock_holes(
|
|
||||||
ball,
|
ball,
|
||||||
ball_number,
|
ball_number,
|
||||||
ball_count,
|
ball_count,
|
||||||
@@ -1558,19 +1550,11 @@ impl Game {
|
|||||||
predicted_position,
|
predicted_position,
|
||||||
&mut capture_candidate,
|
&mut capture_candidate,
|
||||||
events,
|
events,
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
action = completed_action;
|
action = completed_action;
|
||||||
}
|
}
|
||||||
self.check_target_sensors(
|
self.check_target_sensors(ball, old_position, movement_velocity, 140..=147, events);
|
||||||
ball,
|
if let Some(completed_action) = self.check_special_hole(
|
||||||
old_position,
|
|
||||||
movement_velocity,
|
|
||||||
140..=147,
|
|
||||||
events,
|
|
||||||
);
|
|
||||||
if let Some(completed_action) =
|
|
||||||
self.check_special_hole(
|
|
||||||
ball,
|
ball,
|
||||||
ball_number,
|
ball_number,
|
||||||
ball_count,
|
ball_count,
|
||||||
@@ -1578,27 +1562,12 @@ impl Game {
|
|||||||
predicted_position,
|
predicted_position,
|
||||||
&mut capture_candidate,
|
&mut capture_candidate,
|
||||||
events,
|
events,
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
action = completed_action;
|
action = completed_action;
|
||||||
}
|
}
|
||||||
self.check_effect_sensor(
|
self.check_effect_sensor(ball, ball_number, old_position, movement_velocity, events);
|
||||||
ball,
|
self.check_target_sensors(ball, old_position, movement_velocity, 150..=152, events);
|
||||||
ball_number,
|
SensorScanResult { action }
|
||||||
old_position,
|
|
||||||
movement_velocity,
|
|
||||||
events,
|
|
||||||
);
|
|
||||||
self.check_target_sensors(
|
|
||||||
ball,
|
|
||||||
old_position,
|
|
||||||
movement_velocity,
|
|
||||||
150..=152,
|
|
||||||
events,
|
|
||||||
);
|
|
||||||
SensorScanResult {
|
|
||||||
action,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::cast_possible_truncation, clippy::too_many_arguments)]
|
#[allow(clippy::cast_possible_truncation, clippy::too_many_arguments)]
|
||||||
@@ -1858,7 +1827,8 @@ impl Game {
|
|||||||
self.players[self.current_player].secondary_score = 0;
|
self.players[self.current_player].secondary_score = 0;
|
||||||
}
|
}
|
||||||
7 if self.record_contacts[usize::from(SPECIAL_HOLE_SENSOR.id)] != 0
|
7 if self.record_contacts[usize::from(SPECIAL_HOLE_SENSOR.id)] != 0
|
||||||
&& self.record_contacts[usize::from(SPECIAL_HOLE_SENSOR.id)] != ball_number
|
&& self.record_contacts[usize::from(SPECIAL_HOLE_SENSOR.id)]
|
||||||
|
!= ball_number
|
||||||
&& self.secondary_ball.is_none()
|
&& self.secondary_ball.is_none()
|
||||||
&& matches!(
|
&& matches!(
|
||||||
self.multiball_state,
|
self.multiball_state,
|
||||||
@@ -1999,10 +1969,7 @@ impl Game {
|
|||||||
.round_i32()
|
.round_i32()
|
||||||
.wrapping_neg();
|
.wrapping_neg();
|
||||||
let predicted = old_position.add(*velocity);
|
let predicted = old_position.add(*velocity);
|
||||||
if predicted.x < min_x
|
if predicted.x < min_x || predicted.x > max_x || predicted.y < min_y || predicted.y > max_y
|
||||||
|| predicted.x > max_x
|
|
||||||
|| predicted.y < min_y
|
|
||||||
|| predicted.y > max_y
|
|
||||||
{
|
{
|
||||||
self.object_active[object_id] = false;
|
self.object_active[object_id] = false;
|
||||||
}
|
}
|
||||||
@@ -2225,7 +2192,10 @@ impl Game {
|
|||||||
fn special_respawn_pending(&self) -> bool {
|
fn special_respawn_pending(&self) -> bool {
|
||||||
self.secondary_ball.is_none()
|
self.secondary_ball.is_none()
|
||||||
&& self.score_mode == ScoreMode::Normal
|
&& self.score_mode == ScoreMode::Normal
|
||||||
&& matches!(self.multiball_state, MultiballState::Ready | MultiballState::Active)
|
&& matches!(
|
||||||
|
self.multiball_state,
|
||||||
|
MultiballState::Ready | MultiballState::Active
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_current_rule_state(&mut self) {
|
fn save_current_rule_state(&mut self) {
|
||||||
@@ -2336,16 +2306,14 @@ fn claw_release(frame: u8) -> (Vec2, Vec2) {
|
|||||||
fn apply_flipper_response_to_ball(ball: &mut Ball, delta: i32, side: FlipperSide) {
|
fn apply_flipper_response_to_ball(ball: &mut Ball, delta: i32, side: FlipperSide) {
|
||||||
let position = MilliVec::from_position(ball.position);
|
let position = MilliVec::from_position(ball.position);
|
||||||
let velocity = MilliVec::from_velocity_per_second(ball.velocity);
|
let velocity = MilliVec::from_velocity_per_second(ball.velocity);
|
||||||
if let Some(response) =
|
if let Some(response) = moving_flipper_response(
|
||||||
moving_flipper_response(
|
|
||||||
position,
|
position,
|
||||||
velocity,
|
velocity,
|
||||||
delta,
|
delta,
|
||||||
side,
|
side,
|
||||||
Real48::from_bytes([0x80, 0, 0, 0, 0, 0]),
|
Real48::from_bytes([0x80, 0, 0, 0, 0, 0]),
|
||||||
MAXIMUM_SPEED_MILLI_PER_STEP,
|
MAXIMUM_SPEED_MILLI_PER_STEP,
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
ball.position = position.add(response.movement).to_position();
|
ball.position = position.add(response.movement).to_position();
|
||||||
ball.velocity = response.velocity.to_velocity_per_second();
|
ball.velocity = response.velocity.to_velocity_per_second();
|
||||||
}
|
}
|
||||||
@@ -2379,11 +2347,7 @@ mod tests {
|
|||||||
game.ball.in_launcher = false;
|
game.ball.in_launcher = false;
|
||||||
game.ball.position = center;
|
game.ball.position = center;
|
||||||
game.ball.velocity = Vec2::ZERO;
|
game.ball.velocity = Vec2::ZERO;
|
||||||
game.check_sensor_objects(
|
game.check_sensor_objects(MilliVec::from_position(center), MilliVec::default(), events);
|
||||||
MilliVec::from_position(center),
|
|
||||||
MilliVec::default(),
|
|
||||||
events,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2397,7 +2361,8 @@ mod tests {
|
|||||||
) -> CaptureStep {
|
) -> CaptureStep {
|
||||||
let mut ball = game.ball;
|
let mut ball = game.ball;
|
||||||
let mut capture_candidate = None;
|
let mut capture_candidate = None;
|
||||||
let predicted_position = previous_position.add(MilliVec::from_velocity_per_second(ball.velocity));
|
let predicted_position =
|
||||||
|
previous_position.add(MilliVec::from_velocity_per_second(ball.velocity));
|
||||||
let result = game.capture_record_step(
|
let result = game.capture_record_step(
|
||||||
&mut ball,
|
&mut ball,
|
||||||
1,
|
1,
|
||||||
@@ -2456,8 +2421,8 @@ mod tests {
|
|||||||
game.object_active[usize::from(wall.id)] = true;
|
game.object_active[usize::from(wall.id)] = true;
|
||||||
game.ball.in_launcher = false;
|
game.ball.in_launcher = false;
|
||||||
game.ball.position = midpoint - normal;
|
game.ball.position = midpoint - normal;
|
||||||
game.ball.velocity = MilliVec::from_velocity_per_second(normal * 200.0)
|
game.ball.velocity =
|
||||||
.to_velocity_per_second();
|
MilliVec::from_velocity_per_second(normal * 200.0).to_velocity_per_second();
|
||||||
game.fixed_update(&mut Vec::new());
|
game.fixed_update(&mut Vec::new());
|
||||||
if game.last_collision_id != Some(wall.id) {
|
if game.last_collision_id != Some(wall.id) {
|
||||||
missed_walls.push(wall.id);
|
missed_walls.push(wall.id);
|
||||||
@@ -2478,7 +2443,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert!(missed_walls.is_empty(), "missed production wall records: {missed_walls:?}");
|
assert!(
|
||||||
|
missed_walls.is_empty(),
|
||||||
|
"missed production wall records: {missed_walls:?}"
|
||||||
|
);
|
||||||
assert!(
|
assert!(
|
||||||
missed_circles.is_empty(),
|
missed_circles.is_empty(),
|
||||||
"missed production circle records: {missed_circles:?}"
|
"missed production circle records: {missed_circles:?}"
|
||||||
@@ -2869,9 +2837,11 @@ mod tests {
|
|||||||
assert_eq!(special.multiball_state, MultiballState::Unavailable);
|
assert_eq!(special.multiball_state, MultiballState::Unavailable);
|
||||||
assert_eq!(special.record_contacts[148], 0);
|
assert_eq!(special.record_contacts[148], 0);
|
||||||
assert!(!special_events.contains(&Event::Drain));
|
assert!(!special_events.contains(&Event::Drain));
|
||||||
assert!(!special_events
|
assert!(
|
||||||
|
!special_events
|
||||||
.iter()
|
.iter()
|
||||||
.any(|event| event.sound_resource().is_some()));
|
.any(|event| event.sound_resource().is_some())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -3179,10 +3149,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
MilliVec::from_velocity_per_second(game.ball.velocity),
|
MilliVec::from_velocity_per_second(game.ball.velocity),
|
||||||
MilliVec {
|
MilliVec { x: 0, y: 3_055 }
|
||||||
x: 0,
|
|
||||||
y: 3_055,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
assert_eq!(game.special_hole_gate, SpecialHoleGate::Suppressed);
|
assert_eq!(game.special_hole_gate, SpecialHoleGate::Suppressed);
|
||||||
|
|
||||||
@@ -3255,10 +3222,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
MilliVec::from_velocity_per_second(game.ball.velocity),
|
MilliVec::from_velocity_per_second(game.ball.velocity),
|
||||||
MilliVec {
|
MilliVec { x: -302, y: -1_809 }
|
||||||
x: -302,
|
|
||||||
y: -1_809,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
assert_eq!(game.multiball_state, MultiballState::Unavailable);
|
assert_eq!(game.multiball_state, MultiballState::Unavailable);
|
||||||
assert_eq!(game.special_hole_gate, SpecialHoleGate::Suppressed);
|
assert_eq!(game.special_hole_gate, SpecialHoleGate::Suppressed);
|
||||||
@@ -3297,9 +3261,7 @@ mod tests {
|
|||||||
spin: Real48::ZERO,
|
spin: Real48::ZERO,
|
||||||
capture_age: 0,
|
capture_age: 0,
|
||||||
};
|
};
|
||||||
let old_position = MilliVec::from_position(
|
let old_position = MilliVec::from_position(sensor.center - vec2(sensor.radius + 1.0, 0.0));
|
||||||
sensor.center - vec2(sensor.radius + 1.0, 0.0),
|
|
||||||
);
|
|
||||||
let current_position = MilliVec::from_position(sensor.center);
|
let current_position = MilliVec::from_position(sensor.center);
|
||||||
let movement = MilliVec {
|
let movement = MilliVec {
|
||||||
x: current_position.x.wrapping_sub(old_position.x),
|
x: current_position.x.wrapping_sub(old_position.x),
|
||||||
@@ -3391,7 +3353,10 @@ mod tests {
|
|||||||
BallAction::Keep
|
BallAction::Keep
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(game.secondary_ball.map(|ball| ball.position), Some(secondary.position));
|
assert_eq!(
|
||||||
|
game.secondary_ball.map(|ball| ball.position),
|
||||||
|
Some(secondary.position)
|
||||||
|
);
|
||||||
assert_eq!(game.multiball_state, MultiballState::Active);
|
assert_eq!(game.multiball_state, MultiballState::Active);
|
||||||
assert_eq!(game.target_effect, 0);
|
assert_eq!(game.target_effect, 0);
|
||||||
}
|
}
|
||||||
@@ -3511,7 +3476,6 @@ mod tests {
|
|||||||
assert!(game.secondary_ball.is_none());
|
assert!(game.secondary_ball.is_none());
|
||||||
assert_eq!(game.player().balls, 3);
|
assert_eq!(game.player().balls, 3);
|
||||||
assert_eq!(game.score_mode, ScoreMode::Normal);
|
assert_eq!(game.score_mode, ScoreMode::Normal);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -3605,10 +3569,7 @@ mod tests {
|
|||||||
|
|
||||||
assert!(game.fixed_update(&mut events));
|
assert!(game.fixed_update(&mut events));
|
||||||
assert_eq!(game.last_collision_id, Some(57));
|
assert_eq!(game.last_collision_id, Some(57));
|
||||||
assert_eq!(
|
assert_eq!(game.ball.spin.bytes(), [0x83, 0x5d, 0x8f, 0xc2, 0xf5, 0xa8]);
|
||||||
game.ball.spin.bytes(),
|
|
||||||
[0x83, 0x5d, 0x8f, 0xc2, 0xf5, 0xa8]
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
MilliVec::from_position(game.ball.position),
|
MilliVec::from_position(game.ball.position),
|
||||||
MilliVec {
|
MilliVec {
|
||||||
@@ -3645,10 +3606,7 @@ mod tests {
|
|||||||
|
|
||||||
assert!(game.fixed_update(&mut events));
|
assert!(game.fixed_update(&mut events));
|
||||||
assert_eq!(game.last_collision_id, Some(72));
|
assert_eq!(game.last_collision_id, Some(72));
|
||||||
assert_eq!(
|
assert_eq!(game.ball.spin.bytes(), [0x83, 0x86, 0xeb, 0x51, 0xb8, 0x2e]);
|
||||||
game.ball.spin.bytes(),
|
|
||||||
[0x83, 0x86, 0xeb, 0x51, 0xb8, 0x2e]
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
MilliVec::from_position(game.ball.position),
|
MilliVec::from_position(game.ball.position),
|
||||||
MilliVec {
|
MilliVec {
|
||||||
@@ -3658,7 +3616,10 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
MilliVec::from_velocity_per_second(game.ball.velocity),
|
MilliVec::from_velocity_per_second(game.ball.velocity),
|
||||||
MilliVec { x: -2_095, y: 1_669 }
|
MilliVec {
|
||||||
|
x: -2_095,
|
||||||
|
y: 1_669
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3928,8 +3889,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn raised_flipper_tips_use_their_asymmetric_swept_bounds() {
|
fn raised_flipper_tips_use_their_asymmetric_swept_bounds() {
|
||||||
for (object_id, center) in [(67_u8, vec2(133.0, 377.0)), (82, vec2(181.0, 376.0))]
|
for (object_id, center) in [(67_u8, vec2(133.0, 377.0)), (82, vec2(181.0, 376.0))] {
|
||||||
{
|
|
||||||
let mut game = Game::new(1);
|
let mut game = Game::new(1);
|
||||||
game.object_active.fill(false);
|
game.object_active.fill(false);
|
||||||
game.object_active[usize::from(object_id)] = true;
|
game.object_active[usize::from(object_id)] = true;
|
||||||
@@ -4012,7 +3972,13 @@ mod tests {
|
|||||||
let velocity_after_edge = game.ball.velocity;
|
let velocity_after_edge = game.ball.velocity;
|
||||||
assert_eq!(game.ball.position, vec2(138.124, 355.0));
|
assert_eq!(game.ball.position, vec2(138.124, 355.0));
|
||||||
let raw_velocity = MilliVec::from_velocity_per_second(velocity_after_edge);
|
let raw_velocity = MilliVec::from_velocity_per_second(velocity_after_edge);
|
||||||
assert_eq!(raw_velocity, MilliVec { x: 2_209, y: -5_891 });
|
assert_eq!(
|
||||||
|
raw_velocity,
|
||||||
|
MilliVec {
|
||||||
|
x: 2_209,
|
||||||
|
y: -5_891
|
||||||
|
}
|
||||||
|
);
|
||||||
assert_eq!(game.pending_flipper_edges, [0, 0]);
|
assert_eq!(game.pending_flipper_edges, [0, 0]);
|
||||||
|
|
||||||
game.apply_flipper_kicks();
|
game.apply_flipper_kicks();
|
||||||
@@ -4102,20 +4068,20 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tilt_counter_decays_once_per_selected_detail_callback() {
|
fn tilt_counter_decays_once_per_selected_detail_callback() {
|
||||||
for (detail, interval) in [
|
for (detail, interval) in [(1, 0.050), (2, 0.040), (3, 0.030), (4, 0.020), (5, 0.010)] {
|
||||||
(1, 0.050),
|
|
||||||
(2, 0.040),
|
|
||||||
(3, 0.030),
|
|
||||||
(4, 0.020),
|
|
||||||
(5, 0.010),
|
|
||||||
] {
|
|
||||||
let mut game = Game::new(1);
|
let mut game = Game::new(1);
|
||||||
game.tilt_counter = 2;
|
game.tilt_counter = 2;
|
||||||
|
|
||||||
game.update(interval - 0.001, detail, Controls::default());
|
game.update(interval - 0.001, detail, Controls::default());
|
||||||
assert_eq!(game.tilt_counter, 2, "detail {detail} decayed before its callback");
|
assert_eq!(
|
||||||
|
game.tilt_counter, 2,
|
||||||
|
"detail {detail} decayed before its callback"
|
||||||
|
);
|
||||||
game.update(0.001_1, detail, Controls::default());
|
game.update(0.001_1, detail, Controls::default());
|
||||||
assert_eq!(game.tilt_counter, 1, "detail {detail} did not decay at its callback");
|
assert_eq!(
|
||||||
|
game.tilt_counter, 1,
|
||||||
|
"detail {detail} did not decay at its callback"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4462,11 +4428,7 @@ mod tests {
|
|||||||
5
|
5
|
||||||
);
|
);
|
||||||
|
|
||||||
complete_stationary_type_three_capture(
|
complete_stationary_type_three_capture(&mut game, SPECIAL_HOLE_SENSOR.center, &mut events);
|
||||||
&mut game,
|
|
||||||
SPECIAL_HOLE_SENSOR.center,
|
|
||||||
&mut events,
|
|
||||||
);
|
|
||||||
assert!(game.wheel_holes.iter().all(|filled| *filled));
|
assert!(game.wheel_holes.iter().all(|filled| *filled));
|
||||||
assert_eq!(game.multiball_state, MultiballState::Ready);
|
assert_eq!(game.multiball_state, MultiballState::Ready);
|
||||||
assert_eq!(game.record_contacts[148], 2);
|
assert_eq!(game.record_contacts[148], 2);
|
||||||
@@ -4696,7 +4658,11 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(game.panel_frame, None);
|
assert_eq!(game.panel_frame, None);
|
||||||
assert_eq!(game.wheel_holes, [false; 5]);
|
assert_eq!(game.wheel_holes, [false; 5]);
|
||||||
assert!(game.record_contacts[129..=133].iter().all(|contact| *contact == 0));
|
assert!(
|
||||||
|
game.record_contacts[129..=133]
|
||||||
|
.iter()
|
||||||
|
.all(|contact| *contact == 0)
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
events,
|
events,
|
||||||
[
|
[
|
||||||
@@ -4758,7 +4724,11 @@ mod tests {
|
|||||||
game.record_countdowns[51] = 5;
|
game.record_countdowns[51] = 5;
|
||||||
|
|
||||||
game.timer_tick(0.030, 0, &mut Vec::new());
|
game.timer_tick(0.030, 0, &mut Vec::new());
|
||||||
assert_eq!(game.record_countdown(51), 5, "launcher idle pauses countdowns");
|
assert_eq!(
|
||||||
|
game.record_countdown(51),
|
||||||
|
5,
|
||||||
|
"launcher idle pauses countdowns"
|
||||||
|
);
|
||||||
|
|
||||||
game.ball.in_launcher = false;
|
game.ball.in_launcher = false;
|
||||||
for expected in (0..5).rev() {
|
for expected in (0..5).rev() {
|
||||||
|
|||||||
@@ -73,11 +73,7 @@ impl CollisionMaterial {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn circle(
|
pub const fn circle(normal_rebound: f64, tangent_coupling: f64, normal_kick: f64) -> Self {
|
||||||
normal_rebound: f64,
|
|
||||||
tangent_coupling: f64,
|
|
||||||
normal_kick: f64,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
normal_rebound,
|
normal_rebound,
|
||||||
tangent_coupling,
|
tangent_coupling,
|
||||||
@@ -173,8 +169,7 @@ impl MilliVec {
|
|||||||
// `maximum / speed` is mathematically equivalent, but can differ by
|
// `maximum / speed` is mathematically equivalent, but can differ by
|
||||||
// one millipixel because each Real48 operation is rounded
|
// one millipixel because each Real48 operation is rounded
|
||||||
// independently.
|
// independently.
|
||||||
let excess = Real48::from_i32(speed.wrapping_sub(maximum))
|
let excess = Real48::from_i32(speed.wrapping_sub(maximum)).divide(Real48::from_i32(speed));
|
||||||
.divide(Real48::from_i32(speed));
|
|
||||||
self.x = self
|
self.x = self
|
||||||
.x
|
.x
|
||||||
.wrapping_sub(Real48::from_i32(self.x).multiply(excess).round_i32());
|
.wrapping_sub(Real48::from_i32(self.x).multiply(excess).round_i32());
|
||||||
@@ -262,11 +257,7 @@ fn tangent_velocity(velocity: MilliVec, normal_x: Real48, normal_y: Real48, leng
|
|||||||
.round_i32()
|
.round_i32()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cross_at_endpoint(
|
fn cross_at_endpoint(point: MilliVec, current: MilliVec, predicted: MilliVec) -> i32 {
|
||||||
point: MilliVec,
|
|
||||||
current: MilliVec,
|
|
||||||
predicted: MilliVec,
|
|
||||||
) -> i32 {
|
|
||||||
predicted
|
predicted
|
||||||
.x
|
.x
|
||||||
.wrapping_sub(point.x)
|
.wrapping_sub(point.x)
|
||||||
@@ -383,9 +374,7 @@ pub fn line_collision_candidate_at(
|
|||||||
let vertical_units = normal_y.divide(THOUSAND);
|
let vertical_units = normal_y.divide(THOUSAND);
|
||||||
let distance = Real48::from_i32(old_position.x.wrapping_sub(start.x))
|
let distance = Real48::from_i32(old_position.x.wrapping_sub(start.x))
|
||||||
.multiply(vertical_units)
|
.multiply(vertical_units)
|
||||||
.subtract(
|
.subtract(Real48::from_i32(old_position.y.wrapping_sub(start.y)).multiply(horizontal_units))
|
||||||
Real48::from_i32(old_position.y.wrapping_sub(start.y)).multiply(horizontal_units),
|
|
||||||
)
|
|
||||||
.divide(length_units);
|
.divide(length_units);
|
||||||
let distance = if distance.compare(ZERO).is_lt() {
|
let distance = if distance.compare(ZERO).is_lt() {
|
||||||
ZERO.subtract(distance)
|
ZERO.subtract(distance)
|
||||||
@@ -525,10 +514,7 @@ pub fn capture_collision_candidate(
|
|||||||
let radius_milli = (radius * 1_000.0).round() as i32;
|
let radius_milli = (radius * 1_000.0).round() as i32;
|
||||||
let delta = MilliVec {
|
let delta = MilliVec {
|
||||||
x: center.x.wrapping_sub(old_position.x),
|
x: center.x.wrapping_sub(old_position.x),
|
||||||
y: center
|
y: center.y.wrapping_sub(old_position.y).wrapping_sub(2_000),
|
||||||
.y
|
|
||||||
.wrapping_sub(old_position.y)
|
|
||||||
.wrapping_sub(2_000),
|
|
||||||
};
|
};
|
||||||
let surface_distance = milli_distance(delta).wrapping_sub(radius_milli);
|
let surface_distance = milli_distance(delta).wrapping_sub(radius_milli);
|
||||||
let normal_x = Real48::from_i32(center.y.wrapping_sub(old_position.y));
|
let normal_x = Real48::from_i32(center.y.wrapping_sub(old_position.y));
|
||||||
@@ -541,9 +527,7 @@ pub fn capture_collision_candidate(
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let normal_velocity = normal_velocity(velocity, normal_x, normal_y, length);
|
let normal_velocity = normal_velocity(velocity, normal_x, normal_y, length);
|
||||||
if normal_velocity >= 0
|
if normal_velocity >= 0 || normal_velocity.wrapping_abs() < surface_distance.wrapping_abs() {
|
||||||
|| normal_velocity.wrapping_abs() < surface_distance.wrapping_abs()
|
|
||||||
{
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Some(StaticCollisionCandidate {
|
Some(StaticCollisionCandidate {
|
||||||
@@ -681,8 +665,14 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn float_views_roundtrip_every_gameplay_velocity_millipixel() {
|
fn float_views_roundtrip_every_gameplay_velocity_millipixel() {
|
||||||
for value in -10_000..=10_000 {
|
for value in -10_000..=10_000 {
|
||||||
let milli = MilliVec { x: value, y: -value };
|
let milli = MilliVec {
|
||||||
assert_eq!(MilliVec::from_velocity_per_second(milli.to_velocity_per_second()), milli);
|
x: value,
|
||||||
|
y: -value,
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
MilliVec::from_velocity_per_second(milli.to_velocity_per_second()),
|
||||||
|
milli
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -697,8 +687,7 @@ mod tests {
|
|||||||
if speed <= maximum {
|
if speed <= maximum {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let excess = Real48::from_i32(speed - maximum)
|
let excess = Real48::from_i32(speed - maximum).divide(Real48::from_i32(speed));
|
||||||
.divide(Real48::from_i32(speed));
|
|
||||||
let expected = MilliVec {
|
let expected = MilliVec {
|
||||||
x: x.wrapping_sub(Real48::from_i32(x).multiply(excess).round_i32()),
|
x: x.wrapping_sub(Real48::from_i32(x).multiply(excess).round_i32()),
|
||||||
y: y.wrapping_sub(Real48::from_i32(y).multiply(excess).round_i32()),
|
y: y.wrapping_sub(Real48::from_i32(y).multiply(excess).round_i32()),
|
||||||
@@ -716,7 +705,10 @@ mod tests {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert!(mismatches.is_empty(), "speed-clamp mismatches: {mismatches:?}");
|
assert!(
|
||||||
|
mismatches.is_empty(),
|
||||||
|
"speed-clamp mismatches: {mismatches:?}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -783,7 +775,9 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(candidate.surface_distance, -800);
|
assert_eq!(candidate.surface_distance, -800);
|
||||||
assert_ne!(
|
assert_ne!(
|
||||||
candidate.resolve(MilliVec { x: 1_000, y: 0 }, Real48::ZERO).velocity,
|
candidate
|
||||||
|
.resolve(MilliVec { x: 1_000, y: 0 }, Real48::ZERO)
|
||||||
|
.velocity,
|
||||||
MilliVec { x: 1_000, y: 0 }
|
MilliVec { x: 1_000, y: 0 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -887,8 +881,7 @@ mod tests {
|
|||||||
fn surface_distance_orders_candidate_contacts() {
|
fn surface_distance_orders_candidate_contacts() {
|
||||||
let old = MilliVec::default();
|
let old = MilliVec::default();
|
||||||
let velocity = MilliVec { x: 10_000, y: 0 };
|
let velocity = MilliVec { x: 10_000, y: 0 };
|
||||||
let near =
|
let near = line_collision_response(
|
||||||
line_collision_response(
|
|
||||||
old,
|
old,
|
||||||
velocity,
|
velocity,
|
||||||
vec2(2.0, 1.0),
|
vec2(2.0, 1.0),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
use std::{fs, io, path::PathBuf};
|
||||||
|
|
||||||
use directories::ProjectDirs;
|
use directories::ProjectDirs;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{fs, io, path::PathBuf};
|
|
||||||
|
|
||||||
const ORIGINAL_HIGHSCORES: &[u8] = include_bytes!("../assets/original/HISCORES.DAT");
|
const ORIGINAL_HIGHSCORES: &[u8] = include_bytes!("../assets/original/HISCORES.DAT");
|
||||||
const ORIGINAL_INI: &str = include_str!("../../original/TDKPIN.INI");
|
const ORIGINAL_INI: &str = include_str!("../../original/TDKPIN.INI");
|
||||||
@@ -154,7 +155,10 @@ pub fn parse_original_settings(text: &str) -> Settings {
|
|||||||
_ => Language::English,
|
_ => Language::English,
|
||||||
};
|
};
|
||||||
} else if key.trim().eq_ignore_ascii_case("Speed") {
|
} else if key.trim().eq_ignore_ascii_case("Speed") {
|
||||||
settings.speed = u8::try_from(value).ok().filter(|speed| (1..=5).contains(speed)).unwrap_or(3);
|
settings.speed = u8::try_from(value)
|
||||||
|
.ok()
|
||||||
|
.filter(|speed| (1..=5).contains(speed))
|
||||||
|
.unwrap_or(3);
|
||||||
} else if key.trim().eq_ignore_ascii_case("Sound") {
|
} else if key.trim().eq_ignore_ascii_case("Sound") {
|
||||||
settings.sounds = value != 0;
|
settings.sounds = value != 0;
|
||||||
}
|
}
|
||||||
@@ -249,14 +253,16 @@ mod tests {
|
|||||||
let settings = parse_original_settings(ORIGINAL_INI);
|
let settings = parse_original_settings(ORIGINAL_INI);
|
||||||
assert_eq!(settings.speed, 3);
|
assert_eq!(settings.speed, 3);
|
||||||
assert_eq!(settings.language, Language::German);
|
assert_eq!(settings.language, Language::German);
|
||||||
assert!(settings.sounds, "the original reads singular Sound, not Sounds");
|
assert!(
|
||||||
|
settings.sounds,
|
||||||
|
"the original reads singular Sound, not Sounds"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn original_ini_values_are_case_insensitive_and_clamped() {
|
fn original_ini_values_are_case_insensitive_and_clamped() {
|
||||||
let settings = parse_original_settings(
|
let settings =
|
||||||
"[settings]\nlanguage=5\nSPEED=9\nSound=0\nSounds=1\n",
|
parse_original_settings("[settings]\nlanguage=5\nSPEED=9\nSound=0\nSounds=1\n");
|
||||||
);
|
|
||||||
assert_eq!(settings.speed, 3);
|
assert_eq!(settings.speed, 3);
|
||||||
assert_eq!(settings.language, Language::Spanish);
|
assert_eq!(settings.language, Language::Spanish);
|
||||||
assert!(!settings.sounds);
|
assert!(!settings.sounds);
|
||||||
|
|||||||
+7
-13
@@ -167,8 +167,7 @@ impl Real48 {
|
|||||||
}
|
}
|
||||||
let mut numerator = self.significand();
|
let mut numerator = self.significand();
|
||||||
let denominator = right.significand();
|
let denominator = right.significand();
|
||||||
let mut exponent =
|
let mut exponent = i16::from(self.exponent()) - i16::from(right.exponent()) + EXPONENT_BIAS;
|
||||||
i16::from(self.exponent()) - i16::from(right.exponent()) + EXPONENT_BIAS;
|
|
||||||
if numerator < denominator {
|
if numerator < denominator {
|
||||||
numerator <<= 1;
|
numerator <<= 1;
|
||||||
exponent -= 1;
|
exponent -= 1;
|
||||||
@@ -254,16 +253,8 @@ impl Real48 {
|
|||||||
assert!(highest_bit < 32, "Real48 integer overflow");
|
assert!(highest_bit < 32, "Real48 integer overflow");
|
||||||
let significand = self.significand();
|
let significand = self.significand();
|
||||||
let shift = i16::try_from(FRACTION_BITS).unwrap_or(39) - highest_bit;
|
let shift = i16::try_from(FRACTION_BITS).unwrap_or(39) - highest_bit;
|
||||||
let mut magnitude = if shift >= 64 {
|
let mut magnitude = if shift >= 64 { 0 } else { significand >> shift };
|
||||||
0
|
if round && shift > 0 && shift <= 40 && (significand >> (shift - 1)) & 1 != 0 {
|
||||||
} else {
|
|
||||||
significand >> shift
|
|
||||||
};
|
|
||||||
if round
|
|
||||||
&& shift > 0
|
|
||||||
&& shift <= 40
|
|
||||||
&& (significand >> (shift - 1)) & 1 != 0
|
|
||||||
{
|
|
||||||
magnitude += 1;
|
magnitude += 1;
|
||||||
}
|
}
|
||||||
let limit = if self.negative() {
|
let limit = if self.negative() {
|
||||||
@@ -344,7 +335,10 @@ mod tests {
|
|||||||
assert_eq!(HALF.round_i32(), 1);
|
assert_eq!(HALF.round_i32(), 1);
|
||||||
assert_eq!(Real48::from_bytes([0x80, 0, 0, 0, 0, 0x80]).round_i32(), -1);
|
assert_eq!(Real48::from_bytes([0x80, 0, 0, 0, 0, 0x80]).round_i32(), -1);
|
||||||
assert_eq!(ONE.compare(TWO), Ordering::Less);
|
assert_eq!(ONE.compare(TWO), Ordering::Less);
|
||||||
assert_eq!(Real48::from_i32(-2).compare(Real48::from_i32(-1)), Ordering::Less);
|
assert_eq!(
|
||||||
|
Real48::from_i32(-2).compare(Real48::from_i32(-1)),
|
||||||
|
Ordering::Less
|
||||||
|
);
|
||||||
assert_eq!(TWO.sqrt().bytes(), [0x81, 0xfa, 0x33, 0xf3, 0x04, 0x35]);
|
assert_eq!(TWO.sqrt().bytes(), [0x81, 0xfa, 0x33, 0xf3, 0x04, 0x35]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ONE_AND_HALF.sqrt().bytes(),
|
ONE_AND_HALF.sqrt().bytes(),
|
||||||
|
|||||||
+33
-10
@@ -1,11 +1,13 @@
|
|||||||
use crate::game::{ClawSpriteBank, Controls, Event, Game};
|
|
||||||
use serde::Serialize;
|
|
||||||
use std::{
|
use std::{
|
||||||
fs,
|
fs,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::game::{ClawSpriteBank, Controls, Event, Game};
|
||||||
|
|
||||||
pub const SIMULATION_HZ: u32 = 120;
|
pub const SIMULATION_HZ: u32 = 120;
|
||||||
const SIMULATION_HZ_F64: f64 = 120.0;
|
const SIMULATION_HZ_F64: f64 = 120.0;
|
||||||
const SIMULATION_DT: f32 = 1.0 / 120.0;
|
const SIMULATION_DT: f32 = 1.0 / 120.0;
|
||||||
@@ -376,9 +378,7 @@ fn controls_for(scenario: Scenario, step: u64) -> Controls {
|
|||||||
| Scenario::Claw1
|
| Scenario::Claw1
|
||||||
| Scenario::Claw6
|
| Scenario::Claw6
|
||||||
| Scenario::Claw7
|
| Scenario::Claw7
|
||||||
| Scenario::Claw18 => {
|
| Scenario::Claw18 => Controls::default(),
|
||||||
Controls::default()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -507,9 +507,24 @@ mod tests {
|
|||||||
.iter()
|
.iter()
|
||||||
.flat_map(|snapshot| snapshot.events.iter().map(String::as_str))
|
.flat_map(|snapshot| snapshot.events.iter().map(String::as_str))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
assert_eq!(events.iter().filter(|event| **event == "StopSound").count(), 3);
|
assert_eq!(
|
||||||
assert_eq!(events.iter().filter(|event| **event == "Sound(2013)").count(), 4);
|
events.iter().filter(|event| **event == "StopSound").count(),
|
||||||
assert_eq!(events.iter().filter(|event| **event == "Sound(2012)").count(), 2);
|
3
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
events
|
||||||
|
.iter()
|
||||||
|
.filter(|event| **event == "Sound(2013)")
|
||||||
|
.count(),
|
||||||
|
4
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
events
|
||||||
|
.iter()
|
||||||
|
.filter(|event| **event == "Sound(2012)")
|
||||||
|
.count(),
|
||||||
|
2
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -521,7 +536,10 @@ mod tests {
|
|||||||
|
|
||||||
simulation.advance_to(22);
|
simulation.advance_to(22);
|
||||||
assert_eq!(simulation.game.target_rotation_state, Some(6));
|
assert_eq!(simulation.game.target_rotation_state, Some(6));
|
||||||
assert_eq!(simulation.game.wheel_holes, [false, true, false, false, true]);
|
assert_eq!(
|
||||||
|
simulation.game.wheel_holes,
|
||||||
|
[false, true, false, false, true]
|
||||||
|
);
|
||||||
simulation.advance_to(26);
|
simulation.advance_to(26);
|
||||||
assert_eq!(simulation.game.target_rotation_state, None);
|
assert_eq!(simulation.game.target_rotation_state, None);
|
||||||
}
|
}
|
||||||
@@ -561,7 +579,12 @@ mod tests {
|
|||||||
.filter(|event| **event == "ClawRelease")
|
.filter(|event| **event == "ClawRelease")
|
||||||
.count();
|
.count();
|
||||||
assert!(captures >= 1, "{} must enter the claw", scenario.name());
|
assert!(captures >= 1, "{} must enter the claw", scenario.name());
|
||||||
assert_eq!(releases, 1, "{} must release its seeded capture", scenario.name());
|
assert_eq!(
|
||||||
|
releases,
|
||||||
|
1,
|
||||||
|
"{} must release its seeded capture",
|
||||||
|
scenario.name()
|
||||||
|
);
|
||||||
assert!(captures >= releases);
|
assert!(captures >= releases);
|
||||||
assert!(simulation.game.ball.position.is_finite());
|
assert!(simulation.game.ball.position.is_finite());
|
||||||
assert!(simulation.game.ball.velocity.is_finite());
|
assert!(simulation.game.ball.velocity.is_finite());
|
||||||
|
|||||||
@@ -9,9 +9,10 @@
|
|||||||
//! each object's bounds by `param_26`. They must not be enlarged again by the
|
//! each object's bounds by `param_26`. They must not be enlarged again by the
|
||||||
//! radius of the rendered ball.
|
//! radius of the rendered ball.
|
||||||
|
|
||||||
use crate::geometry::Segment;
|
|
||||||
use macroquad::prelude::Vec2;
|
use macroquad::prelude::Vec2;
|
||||||
|
|
||||||
|
use crate::geometry::Segment;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct TableSegment {
|
pub struct TableSegment {
|
||||||
pub id: u8,
|
pub id: u8,
|
||||||
@@ -425,7 +426,10 @@ mod tests {
|
|||||||
.iter()
|
.iter()
|
||||||
.find(|wall| wall.id == id)
|
.find(|wall| wall.id == id)
|
||||||
.expect("relative line record must be present");
|
.expect("relative line record must be present");
|
||||||
assert_eq!((wall.segment.start, wall.segment.end), (expected_start, expected_end));
|
assert_eq!(
|
||||||
|
(wall.segment.start, wall.segment.end),
|
||||||
|
(expected_start, expected_end)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
for (id, expected_center) in [
|
for (id, expected_center) in [
|
||||||
(54, Vec2::new(53.0, 292.0)),
|
(54, Vec2::new(53.0, 292.0)),
|
||||||
|
|||||||
Reference in New Issue
Block a user