diff --git a/tdkpin-rs/README.md b/tdkpin-rs/README.md index 240d249..8215b70 100644 --- a/tdkpin-rs/README.md +++ b/tdkpin-rs/README.md @@ -5,6 +5,10 @@ Machine**. It uses the extracted original artwork and sound, recreates the playfield as a fixed-step simulation, and runs from the same source on Linux, macOS, and Windows. +The game opens at 960x690, an exact 1.5x enlargement of its original 640x460 +canvas, so modern desktop scaling cannot distort or vertically offset the +pixel-art presentation. + The original program is not required at runtime. All required game assets are embedded in the executable at build time. diff --git a/tdkpin-rs/RECONSTRUCTION.md b/tdkpin-rs/RECONSTRUCTION.md index ed76a3b..573b60f 100644 --- a/tdkpin-rs/RECONSTRUCTION.md +++ b/tdkpin-rs/RECONSTRUCTION.md @@ -28,7 +28,7 @@ implementation. | Numeric scoring | Partly inferred | Visible 2000-6000 target values and recovered registration values are preserved. Some bumper, bank-completion, robot, wheel, lock, and media thresholds are best-evidence reconstructions because the decompiler did not recover meaningful names or a clean rule table. | | High scores | Compatible import | The original 276-byte table is decoded as ten `IWIK`-XOR-obfuscated little-endian scores plus ten 22-byte names, sorted, then migrated to portable JSON. | | Configuration | Behaviorally compatible | Sound, language, and five detail levels are retained. Storage moves from a local Win16 INI file to the platform user-data directory. | -| Windows UI shell | Deliberately modernized | Win16 menus, modal dialogs, GDI blitting, and multimedia timers are replaced by a resizable, letterboxed native window with keyboard overlays. The visible game and original help remain at 640x460 logical pixels. | +| Windows UI shell | Deliberately modernized | Win16 menus, modal dialogs, GDI blitting, and multimedia timers are replaced by an aspect-correct 960x690 native window with keyboard overlays. The visible game and original help remain at 640x460 logical pixels. | ## Extracted asset inventory diff --git a/tdkpin-rs/src/app.rs b/tdkpin-rs/src/app.rs index b3a6c24..84c4929 100644 --- a/tdkpin-rs/src/app.rs +++ b/tdkpin-rs/src/app.rs @@ -559,24 +559,33 @@ impl App { }, ); } - draw_text("F1 HELP F2 SETUP", 345.0, 448.0, 11.0, BLACK); + draw_rectangle( + 340.0, + 444.0, + 292.0, + 14.0, + Color::from_rgba(214, 214, 214, 235), + ); + draw_text("F1 HELP F2 SETUP", 347.0, 455.0, 10.0, BLACK); } #[allow(clippy::cast_precision_loss)] fn draw_displays(&self, game: &Game) { - draw_rectangle( + draw_texture_ex( + &self.assets.active_table, 371.0, - 235.0, - 222.0, - 31.0, - Color::from_rgba(0, 191, 209, 255), + 233.0, + WHITE, + DrawTextureParams { + dest_size: Some(vec2(244.0, 34.0)), + source: Some(Rect::new(371.0, 233.0, 244.0, 34.0)), + ..Default::default() + }, ); - draw_text( - format!("BONUS {:07}", game.bonus), - 374.0, - 258.0, - 24.0, - BLACK, + self.draw_digits( + &format!("{:07}", game.bonus), + vec2(458.0, 240.0), + vec2(10.0, 18.0), ); let media_level = game.player().media_level; @@ -587,20 +596,31 @@ impl App { draw_text( format!("{:07} KBytes", game.player().score / 1_000), 395.0, - 211.0, - 16.0, + 205.0, + 14.0, WHITE, ); for (index, player) in game.players.iter().enumerate() { let y = 274.0 + index as f32 * 44.0; - let panel = if index == game.current_player { - Color::from_rgba(0, 191, 209, 255) - } else { - Color::from_rgba(36, 82, 137, 255) - }; - draw_rectangle(472.0, y, 121.0, 30.0, panel); - draw_text(format!("{:09}", player.score), 478.0, y + 21.0, 18.0, BLACK); + if index == game.current_player { + draw_texture_ex( + &self.assets.active_table, + 472.0, + y, + WHITE, + DrawTextureParams { + dest_size: Some(vec2(143.0, 30.0)), + source: Some(Rect::new(472.0, y, 143.0, 30.0)), + ..Default::default() + }, + ); + } + self.draw_digits( + &format!("{:09}", player.score), + vec2(480.0, y + 6.0), + vec2(10.0, 18.0), + ); for ball in 0..6 { let lit = ball < usize::from(player.balls + player.extra_balls); draw_circle( @@ -613,6 +633,28 @@ impl App { } } + fn draw_digits(&self, digits: &str, position: Vec2, digit_size: Vec2) { + let mut x = position.x; + for digit in digits.bytes() { + if !digit.is_ascii_digit() { + continue; + } + let source_x = f32::from(digit - b'0') * 16.0; + draw_texture_ex( + &self.assets.digits, + x, + position.y, + BLACK, + DrawTextureParams { + dest_size: Some(digit_size), + source: Some(Rect::new(source_x, 0.0, 16.0, 28.0)), + ..Default::default() + }, + ); + x += digit_size.x; + } + } + #[allow(clippy::cast_precision_loss)] fn draw_settings(&self) { draw_texture(&self.assets.inactive_table, 0.0, 0.0, WHITE); diff --git a/tdkpin-rs/src/assets.rs b/tdkpin-rs/src/assets.rs index 41616e8..2725c6d 100644 --- a/tdkpin-rs/src/assets.rs +++ b/tdkpin-rs/src/assets.rs @@ -14,6 +14,7 @@ pub struct Assets { pub robot: Texture2D, pub magnet: Texture2D, pub ball: Texture2D, + pub digits: Texture2D, sounds: Vec<(u16, Sound)>, } @@ -54,6 +55,8 @@ impl Assets { include_bytes!("../assets/original/images/bitmap_00101.png"), include_bytes!("../assets/original/images/bitmap_00102.png"), ); + let digits = + monochrome_texture(include_bytes!("../assets/original/images/bitmap_00500.png")); let sound_bytes: [(u16, &[u8]); 16] = [ ( @@ -139,6 +142,7 @@ impl Assets { robot, magnet, ball, + digits, sounds, } } @@ -182,3 +186,17 @@ fn masked_texture(color_bytes: &[u8], mask_bytes: &[u8]) -> Texture2D { texture.set_filter(FilterMode::Nearest); texture } + +fn monochrome_texture(bytes: &[u8]) -> Texture2D { + let mut image = + Image::from_file_with_format(bytes, None).expect("embedded monochrome bitmap must decode"); + let (pixels, remainder) = image.bytes.as_chunks_mut::<4>(); + assert!(remainder.is_empty()); + for pixel in pixels { + pixel[3] = u8::MAX - pixel[0]; + pixel[..3].fill(u8::MAX); + } + let texture = Texture2D::from_image(&image); + texture.set_filter(FilterMode::Nearest); + texture +} diff --git a/tdkpin-rs/src/main.rs b/tdkpin-rs/src/main.rs index 3ede069..391bb3f 100644 --- a/tdkpin-rs/src/main.rs +++ b/tdkpin-rs/src/main.rs @@ -7,16 +7,16 @@ mod persistence; use app::App; use macroquad::prelude::*; -const LOGICAL_WIDTH: i32 = 640; -const LOGICAL_HEIGHT: i32 = 460; +const WINDOW_WIDTH: i32 = 960; +const WINDOW_HEIGHT: i32 = 690; fn window_conf() -> Conf { Conf { window_title: "TDK Pinball Machine".to_owned(), - window_width: LOGICAL_WIDTH, - window_height: LOGICAL_HEIGHT, - window_resizable: true, - high_dpi: true, + window_width: WINDOW_WIDTH, + window_height: WINDOW_HEIGHT, + window_resizable: false, + high_dpi: false, ..Default::default() } }