1use std::collections::HashMap;
9use std::f32::consts::PI;
10use std::fs::File;
11use std::io::{Read, Write};
12use std::path::Path;
13use std::process::exit;
14use std::sync::{
15 atomic::{AtomicBool, AtomicU64, Ordering::*},
16 mpsc::{self, Sender},
17};
18use std::thread;
19use std::time::Instant;
20
21use windows::core::{BOOL, HSTRING, PCWSTR, PSTR, PWSTR};
22use windows::Win32::{
23 Foundation::*, Graphics::Gdi::*, Media::Audio::*, Media::MMSYSERR_NOERROR, System::Console::*,
24 UI::Input::KeyboardAndMouse::GetAsyncKeyState, UI::WindowsAndMessaging::wsprintfW,
25};
26
27pub mod color {
46 pub const FG_BLACK: u16 = 0x0000;
48 pub const FG_DARK_BLUE: u16 = 0x0001;
50 pub const FG_DARK_GREEN: u16 = 0x0002;
52 pub const FG_DARK_CYAN: u16 = 0x0003;
54 pub const FG_DARK_RED: u16 = 0x0004;
56 pub const FG_DARK_MAGENTA: u16 = 0x0005;
58 pub const FG_DARK_YELLOW: u16 = 0x0006;
60 pub const FG_GREY: u16 = 0x0007;
62 pub const FG_DARK_GREY: u16 = 0x0008;
64 pub const FG_BLUE: u16 = 0x0009;
66 pub const FG_GREEN: u16 = 0x000A;
68 pub const FG_CYAN: u16 = 0x000B;
70 pub const FG_RED: u16 = 0x000C;
72 pub const FG_MAGENTA: u16 = 0x000D;
74 pub const FG_YELLOW: u16 = 0x000E;
76 pub const FG_WHITE: u16 = 0x000F;
78
79 pub const BG_BLACK: u16 = 0x0000;
81 pub const BG_DARK_BLUE: u16 = 0x0010;
83 pub const BG_DARK_GREEN: u16 = 0x0020;
85 pub const BG_DARK_CYAN: u16 = 0x0030;
87 pub const BG_DARK_RED: u16 = 0x0040;
89 pub const BG_DARK_MAGENTA: u16 = 0x0050;
91 pub const BG_DARK_YELLOW: u16 = 0x0060;
93 pub const BG_GREY: u16 = 0x0070;
95 pub const BG_DARK_GREY: u16 = 0x0080;
97 pub const BG_BLUE: u16 = 0x0090;
99 pub const BG_GREEN: u16 = 0x00A0;
101 pub const BG_CYAN: u16 = 0x00B0;
103 pub const BG_RED: u16 = 0x00C0;
105 pub const BG_MAGENTA: u16 = 0x00D0;
107 pub const BG_YELLOW: u16 = 0x00E0;
109 pub const BG_WHITE: u16 = 0x00F0;
111}
112
113pub mod pixel {
128 pub const SOLID: u16 = 0x2588;
130 pub const THREE_QUARTERS: u16 = 0x2593;
132 pub const HALF: u16 = 0x2592;
134 pub const QUARTER: u16 = 0x2591;
136 pub const EMPTY: u16 = 0x20;
138}
139
140pub mod mouse_button {
155 pub const LEFT: usize = 0;
157 pub const RIGHT: usize = 1;
159 pub const MIDDLE: usize = 2;
161 pub const X1: usize = 3;
163 pub const X2: usize = 4;
165}
166
167pub mod key {
186 pub const SPACE: usize = 0x20;
188 pub const ENTER: usize = 0x0D;
190 pub const ESCAPE: usize = 0x1B;
192 pub const BACKSPACE: usize = 0x08;
194 pub const TAB: usize = 0x09;
196 pub const SHIFT: usize = 0x10;
198 pub const CONTROL: usize = 0x11;
200 pub const ALT: usize = 0x12;
202 pub const CAPSLOCK: usize = 0x14;
204 pub const NUMLOCK: usize = 0x90;
206 pub const SCROLL_LOCK: usize = 0x91;
208
209 pub const ARROW_UP: usize = 0x26;
211 pub const ARROW_DOWN: usize = 0x28;
213 pub const ARROW_LEFT: usize = 0x25;
215 pub const ARROW_RIGHT: usize = 0x27;
217
218 pub const F1: usize = 0x70;
220 pub const F2: usize = 0x71;
222 pub const F3: usize = 0x72;
224 pub const F4: usize = 0x73;
226 pub const F5: usize = 0x74;
228 pub const F6: usize = 0x75;
230 pub const F7: usize = 0x76;
232 pub const F8: usize = 0x77;
234 pub const F9: usize = 0x78;
236 pub const F10: usize = 0x79;
238 pub const F11: usize = 0x7A;
240 pub const F12: usize = 0x7B;
242
243 pub const ZERO: usize = 0x30;
245 pub const ONE: usize = 0x31;
247 pub const TWO: usize = 0x32;
249 pub const THREE: usize = 0x33;
251 pub const FOUR: usize = 0x34;
253 pub const FIVE: usize = 0x35;
255 pub const SIX: usize = 0x36;
257 pub const SEVEN: usize = 0x37;
259 pub const EIGHT: usize = 0x38;
261 pub const NINE: usize = 0x39;
263
264 pub const A: usize = 0x41;
266 pub const B: usize = 0x42;
268 pub const C: usize = 0x43;
270 pub const D: usize = 0x44;
272 pub const E: usize = 0x45;
274 pub const F: usize = 0x46;
276 pub const G: usize = 0x47;
278 pub const H: usize = 0x48;
280 pub const I: usize = 0x49;
282 pub const J: usize = 0x4A;
284 pub const K: usize = 0x4B;
286 pub const L: usize = 0x4C;
288 pub const M: usize = 0x4D;
290 pub const N: usize = 0x4E;
292 pub const O: usize = 0x4F;
294 pub const P: usize = 0x50;
296 pub const Q: usize = 0x51;
298 pub const R: usize = 0x52;
300 pub const S: usize = 0x53;
302 pub const T: usize = 0x54;
304 pub const U: usize = 0x55;
306 pub const V: usize = 0x56;
308 pub const W: usize = 0x57;
310 pub const X: usize = 0x58;
312 pub const Y: usize = 0x59;
314 pub const Z: usize = 0x5A;
316
317 pub const NUMPAD_0: usize = 0x60;
319 pub const NUMPAD_1: usize = 0x61;
321 pub const NUMPAD_2: usize = 0x62;
323 pub const NUMPAD_3: usize = 0x63;
325 pub const NUMPAD_4: usize = 0x64;
327 pub const NUMPAD_5: usize = 0x65;
329 pub const NUMPAD_6: usize = 0x66;
331 pub const NUMPAD_7: usize = 0x67;
333 pub const NUMPAD_8: usize = 0x68;
335 pub const NUMPAD_9: usize = 0x69;
337 pub const NUMPAD_ADD: usize = 0x6B;
339 pub const NUMPAD_SUBTRACT: usize = 0x6D;
341 pub const NUMPAD_MULTIPLY: usize = 0x6A;
343 pub const NUMPAD_DIVIDE: usize = 0x6F;
345 pub const NUMPAD_ENTER: usize = 0x0D;
347
348 pub const SEMICOLON: usize = 0xBA;
351 pub const EQUAL: usize = 0xBB;
354 pub const COMMA: usize = 0xBC;
357 pub const DASH: usize = 0xBD;
360 pub const PERIOD: usize = 0xBE;
363 pub const SLASH: usize = 0xBF;
366 pub const BACKTICK: usize = 0xC0;
369 pub const LEFT_BRACE: usize = 0xDB;
372 pub const BACKSLASH: usize = 0xDC;
375 pub const RIGHT_BRACE: usize = 0xDD;
378 pub const APOSTROPHE: usize = 0xDE;
381}
382
383pub mod note {
405 pub const A1: f32 = 55.00;
407 pub const A2: f32 = 110.00;
409 pub const A3: f32 = 220.00;
411 pub const A4: f32 = 440.00;
413 pub const A5: f32 = 880.00;
415 pub const A6: f32 = 1760.00;
417 pub const A7: f32 = 3520.00;
419 pub const A8: f32 = 7040.00;
421
422 pub const A_SHARP1: f32 = 58.27;
424 pub const A_SHARP2: f32 = 116.54;
426 pub const A_SHARP3: f32 = 233.08;
428 pub const A_SHARP4: f32 = 466.16;
430 pub const A_SHARP5: f32 = 932.33;
432 pub const A_SHARP6: f32 = 1864.66;
434 pub const A_SHARP7: f32 = 3729.31;
436 pub const A_SHARP8: f32 = 7458.62;
438
439 pub const B1: f32 = 61.74;
441 pub const B2: f32 = 123.47;
443 pub const B3: f32 = 246.94;
445 pub const B4: f32 = 493.88;
447 pub const B5: f32 = 987.77;
449 pub const B6: f32 = 1975.53;
451 pub const B7: f32 = 3951.07;
453 pub const B8: f32 = 7902.13;
455
456 pub const C1: f32 = 32.70;
458 pub const C2: f32 = 65.41;
460 pub const C3: f32 = 130.81;
462 pub const C4: f32 = 261.63;
464 pub const C5: f32 = 523.25;
466 pub const C6: f32 = 1046.50;
468 pub const C7: f32 = 2093.00;
470 pub const C8: f32 = 4186.01;
472
473 pub const C_SHARP1: f32 = 34.65;
475 pub const C_SHARP2: f32 = 69.30;
477 pub const C_SHARP3: f32 = 138.59;
479 pub const C_SHARP4: f32 = 277.18;
481 pub const C_SHARP5: f32 = 554.37;
483 pub const C_SHARP6: f32 = 1108.73;
485 pub const C_SHARP7: f32 = 2217.46;
487 pub const C_SHARP8: f32 = 4434.92;
489
490 pub const D1: f32 = 36.71;
492 pub const D2: f32 = 73.42;
494 pub const D3: f32 = 146.83;
496 pub const D4: f32 = 293.66;
498 pub const D5: f32 = 587.33;
500 pub const D6: f32 = 1174.66;
502 pub const D7: f32 = 2349.32;
504 pub const D8: f32 = 4698.63;
506
507 pub const D_SHARP1: f32 = 38.89;
509 pub const D_SHARP2: f32 = 77.78;
511 pub const D_SHARP3: f32 = 155.56;
513 pub const D_SHARP4: f32 = 311.13;
515 pub const D_SHARP5: f32 = 622.25;
517 pub const D_SHARP6: f32 = 1244.51;
519 pub const D_SHARP7: f32 = 2489.02;
521 pub const D_SHARP8: f32 = 4978.03;
523
524 pub const E1: f32 = 41.20;
526 pub const E2: f32 = 82.41;
528 pub const E3: f32 = 164.81;
530 pub const E4: f32 = 329.63;
532 pub const E5: f32 = 659.25;
534 pub const E6: f32 = 1318.51;
536 pub const E7: f32 = 2637.02;
538 pub const E8: f32 = 5274.04;
540
541 pub const F1: f32 = 43.65;
543 pub const F2: f32 = 87.31;
545 pub const F3: f32 = 174.61;
547 pub const F4: f32 = 349.23;
549 pub const F5: f32 = 698.46;
551 pub const F6: f32 = 1396.91;
553 pub const F7: f32 = 2793.83;
555 pub const F8: f32 = 5587.65;
557
558 pub const F_SHARP1: f32 = 46.25;
560 pub const F_SHARP2: f32 = 92.50;
562 pub const F_SHARP3: f32 = 185.00;
564 pub const F_SHARP4: f32 = 369.99;
566 pub const F_SHARP5: f32 = 739.99;
568 pub const F_SHARP6: f32 = 1479.98;
570 pub const F_SHARP7: f32 = 2959.96;
572 pub const F_SHARP8: f32 = 5919.91;
574
575 pub const G1: f32 = 49.00;
577 pub const G2: f32 = 98.00;
579 pub const G3: f32 = 196.00;
581 pub const G4: f32 = 392.00;
583 pub const G5: f32 = 783.99;
585 pub const G6: f32 = 1567.98;
587 pub const G7: f32 = 3135.96;
589 pub const G8: f32 = 6271.93;
591
592 pub const G_SHARP1: f32 = 51.91;
594 pub const G_SHARP2: f32 = 103.83;
596 pub const G_SHARP3: f32 = 207.65;
598 pub const G_SHARP4: f32 = 415.30;
600 pub const G_SHARP5: f32 = 830.61;
602 pub const G_SHARP6: f32 = 1661.22;
604 pub const G_SHARP7: f32 = 3322.44;
606 pub const G_SHARP8: f32 = 6644.88;
608}
609
610pub mod prelude {
630 pub use crate::ConsoleGame;
631 pub use crate::ConsoleGameEngine;
632 pub use crate::Sprite;
633
634 pub use crate::color::{BG_BLACK, BG_WHITE, FG_BLACK, FG_BLUE, FG_GREEN, FG_RED, FG_WHITE};
635
636 pub use crate::pixel::{EMPTY, HALF, QUARTER, SOLID, THREE_QUARTERS};
637
638 pub use crate::key::{A, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP, D, S, SPACE, W};
639
640 pub use crate::mouse_button::{LEFT, MIDDLE, RIGHT};
641
642 pub use crate::note::{A4, B4, C4, D4, E4, F4, G4};
643}
644
645#[derive(Clone)]
650struct ConsoleState {
651 screen_width: i16,
652 screen_height: i16,
653 window_rect: SMALL_RECT,
654 font_cfi: CONSOLE_FONT_INFOEX,
655 cursor_info: CONSOLE_CURSOR_INFO,
656 console_mode: CONSOLE_MODE,
657}
658
659impl ConsoleState {
660 fn save(output_handle: HANDLE, input_handle: HANDLE) -> Self {
661 let mut csbi = CONSOLE_SCREEN_BUFFER_INFO::default();
662 unsafe {
663 GetConsoleScreenBufferInfo(output_handle, &mut csbi)
664 .expect("Failed to get console screen buffer info");
665 }
666
667 let mut font_cfi = CONSOLE_FONT_INFOEX {
668 cbSize: std::mem::size_of::<CONSOLE_FONT_INFOEX>() as u32,
669 ..Default::default()
670 };
671 unsafe {
672 GetCurrentConsoleFontEx(output_handle, false, &mut font_cfi)
673 .expect("Failed to get current font");
674 }
675
676 let mut cursor_info = CONSOLE_CURSOR_INFO::default();
677 unsafe {
678 GetConsoleCursorInfo(output_handle, &mut cursor_info)
679 .expect("Failed to get cursor info");
680 }
681
682 let mut mode = CONSOLE_MODE(0);
683 unsafe {
684 GetConsoleMode(input_handle, &mut mode).expect("Failed to get console mode");
685 }
686
687 Self {
688 screen_width: csbi.dwSize.X,
689 screen_height: csbi.dwSize.Y,
690 window_rect: csbi.srWindow,
691 font_cfi,
692 cursor_info,
693 console_mode: mode,
694 }
695 }
696
697 fn restore(&self, output_handle: HANDLE, input_handle: HANDLE) {
698 unsafe {
699 let mut chars_written = 0;
700 let size = (self.screen_width as u32) * (self.screen_height as u32);
701 FillConsoleOutputCharacterW(
702 output_handle,
703 EMPTY,
704 size,
705 COORD { X: 0, Y: 0 },
706 &mut chars_written,
707 )
708 .ok();
709 SetConsoleCursorPosition(output_handle, COORD { X: 0, Y: 0 }).ok();
710
711 let coord = COORD {
712 X: self.screen_width,
713 Y: self.screen_height,
714 };
715 SetConsoleScreenBufferSize(output_handle, coord).ok();
716 SetConsoleWindowInfo(output_handle, true, &self.window_rect).ok();
717 SetCurrentConsoleFontEx(output_handle, false, &self.font_cfi).ok();
718 SetConsoleCursorInfo(output_handle, &self.cursor_info).ok();
719 SetConsoleMode(input_handle, self.console_mode).ok();
720 }
721 }
722}
723
724#[derive(Debug, Clone, Default, PartialEq)]
733pub struct Sprite {
734 pub width: usize,
736 pub height: usize,
738 glyphs: Vec<u16>,
739 colors: Vec<u16>,
740}
741
742impl Sprite {
743 pub fn new(width: usize, height: usize) -> Self {
746 Self {
747 width,
748 height,
749 glyphs: vec![EMPTY; width * height],
750 colors: vec![FG_BLACK; width * height],
751 }
752 }
753
754 pub fn from_file(path: &str) -> Result<Self, Box<dyn std::error::Error>> {
757 let mut file = File::open(path)?;
758 let mut buf = Vec::new();
759 file.read_to_end(&mut buf)?;
760
761 if buf.len() < 8 {
762 return Err("sprite file too small".into());
763 }
764
765 let width = u32::from_le_bytes(buf[0..4].try_into().unwrap()) as usize;
766 let height = u32::from_le_bytes(buf[4..8].try_into().unwrap()) as usize;
767 let count = width
768 .checked_mul(height)
769 .ok_or("sprite dimensions overflow")?;
770 let expected = 8 + 2 * count * 2;
771 if buf.len() < expected {
772 return Err("sprite file truncated".into());
773 }
774
775 let mut offset = 8;
776 let mut colors = Vec::with_capacity(count);
777 for _ in 0..count {
778 let v = u16::from_le_bytes(buf[offset..offset + 2].try_into().unwrap());
779 offset += 2;
780 colors.push(v);
781 }
782
783 let mut glyphs = Vec::with_capacity(count);
784 for _ in 0..count {
785 let v = u16::from_le_bytes(buf[offset..offset + 2].try_into().unwrap());
786 offset += 2;
787 glyphs.push(v);
788 }
789
790 Ok(Self {
791 width,
792 height,
793 glyphs,
794 colors,
795 })
796 }
797
798 pub fn save_to_file(&self, path: &str) -> Result<(), Box<dyn std::error::Error>> {
800 let mut file = File::create(path)?;
801 file.write_all(&(self.width as u32).to_le_bytes())?;
802 file.write_all(&(self.height as u32).to_le_bytes())?;
803
804 for &c in &self.colors {
805 file.write_all(&c.to_le_bytes())?;
806 }
807 for &g in &self.glyphs {
808 file.write_all(&g.to_le_bytes())?;
809 }
810
811 Ok(())
812 }
813
814 pub fn set_glyph(&mut self, x: usize, y: usize, g: u16) {
816 if x < self.width && y < self.height {
817 self.glyphs[y * self.width + x] = g;
818 }
819 }
820
821 pub fn set_color(&mut self, x: usize, y: usize, c: u16) {
823 if x < self.width && y < self.height {
824 self.colors[y * self.width + x] = c;
825 }
826 }
827
828 pub fn get_glyph(&self, x: usize, y: usize) -> u16 {
830 if x < self.width && y < self.height {
831 self.glyphs[y * self.width + x]
832 } else {
833 EMPTY
834 }
835 }
836
837 pub fn get_color(&self, x: usize, y: usize) -> u16 {
839 if x < self.width && y < self.height {
840 self.colors[y * self.width + x]
841 } else {
842 FG_BLACK
843 }
844 }
845
846 fn wrapped_sample_index(&self, x: f32, y: f32) -> (usize, usize) {
847 let fx = x - x.floor();
848 let fy = y - y.floor();
849 let sx =
850 ((fx * self.width as f32).floor() as isize).rem_euclid(self.width as isize) as usize;
851 let sy =
852 ((fy * self.height as f32).floor() as isize).rem_euclid(self.height as isize) as usize;
853 (sx, sy)
854 }
855
856 pub fn sample_glyph(&self, x: f32, y: f32) -> u16 {
859 let (sx, sy) = self.wrapped_sample_index(x, y);
860 self.get_glyph(sx, sy)
861 }
862
863 pub fn sample_color(&self, x: f32, y: f32) -> u16 {
866 let (sx, sy) = self.wrapped_sample_index(x, y);
867 self.get_color(sx, sy)
868 }
869}
870
871const CHUNK_SIZE: usize = 512;
876static NOTE_COUNTER: AtomicU64 = AtomicU64::new(0);
877
878#[derive(Clone)]
879enum AudioCommand {
880 LoadSample(String),
881 PlaySample(String),
882 LoadSampleFromBuffer(String, Vec<i16>),
883 NoteOn(f32),
884 NoteOff(f32),
885 Quit,
886}
887
888struct PlayingSound {
889 data: Vec<i16>,
890 cursor: usize,
891}
892
893struct PlayingNote {
894 freq: f32,
895 phase: f32,
896 amplitude: f32,
897 target_amp: f32,
898 step: f32,
899 active: bool,
900}
901
902#[derive(Clone)]
917pub struct AudioEngine {
918 tx: Sender<AudioCommand>,
919}
920
921impl AudioEngine {
922 #[allow(clippy::new_without_default)]
923 fn new() -> Self {
924 let (tx, rx) = mpsc::channel::<AudioCommand>();
925
926 thread::spawn(move || {
927 let format = WAVEFORMATEX {
928 wFormatTag: WAVE_FORMAT_PCM as u16,
929 nChannels: 2,
930 nSamplesPerSec: 44100,
931 nAvgBytesPerSec: 44100 * 2 * 2,
932 nBlockAlign: 4,
933 wBitsPerSample: 16,
934 cbSize: 0,
935 };
936
937 let mut h_waveout = HWAVEOUT::default();
938 unsafe {
939 let res = waveOutOpen(
940 Some(&mut h_waveout),
941 WAVE_MAPPER,
942 &format,
943 None,
944 Some(0),
945 CALLBACK_NULL,
946 );
947
948 if res != MMSYSERR_NOERROR {
949 eprintln!("Failed to open audio device: {}", res);
950 return;
951 }
952 }
953
954 let mut samples = HashMap::new();
955 let mut active_sounds = Vec::new();
956 let mut active_notes = Vec::new();
957
958 'audio_loop: loop {
959 while let Ok(cmd) = rx.try_recv() {
960 match cmd {
961 AudioCommand::LoadSample(path) => {
962 if let Ok(data) = AudioEngine::load_wav(&path) {
963 samples.insert(path, data);
964 }
965 }
966 AudioCommand::LoadSampleFromBuffer(key, buffer) => {
967 samples.insert(key, buffer);
968 }
969 AudioCommand::PlaySample(path) => {
970 if let Some(data) = samples.get(&path) {
971 active_sounds.push(PlayingSound {
972 data: data.clone(),
973 cursor: 0,
974 });
975 }
976 }
977 AudioCommand::NoteOn(freq) => {
978 let sample_rate = 44100.0;
979 let attack_samples = 100;
980 let mut buffer = vec![0i16; attack_samples * 2];
981 for i in 0..attack_samples {
982 let t = i as f32 / sample_rate;
983 let s = ((2.0 * PI * freq * t).sin() * i16::MAX as f32 * 0.1)
984 .clamp(i16::MIN as f32, i16::MAX as f32);
985 buffer[i * 2] = s as i16;
986 buffer[i * 2 + 1] = s as i16;
987 }
988 AudioEngine::play_buffer(h_waveout, buffer);
989
990 let attack_ms = 50.0;
991 let step = 1.0 / (44100.0 * (attack_ms / 1000.0));
992 active_notes.push(PlayingNote {
993 freq,
994 phase: 0.0,
995 amplitude: 0.0,
996 target_amp: 1.0,
997 step,
998 active: true,
999 });
1000 }
1001 AudioCommand::NoteOff(freq) => {
1002 let sample_rate = 44100.0;
1003 let release_samples = 100;
1004 let mut buffer = vec![0i16; release_samples * 2];
1005
1006 for i in 0..release_samples {
1007 let t = i as f32 / sample_rate;
1008 let s = ((2.0 * PI * freq * t).sin() * i16::MAX as f32 * 0.05)
1009 .clamp(i16::MIN as f32, i16::MAX as f32);
1010 buffer[i * 2] = s as i16;
1011 buffer[i * 2 + 1] = s as i16;
1012 }
1013 AudioEngine::play_buffer(h_waveout, buffer);
1014
1015 for note in active_notes.iter_mut() {
1016 if (note.freq - freq).abs() < f32::EPSILON && note.active {
1017 let release_ms = 50.0;
1018 note.target_amp = 0.0;
1019 note.step = -(1.0 / (44100.0 * (release_ms / 1000.0)));
1020 }
1021 }
1022 }
1023 AudioCommand::Quit => break 'audio_loop,
1024 }
1025 }
1026
1027 let mut mix_buffer = vec![0i32; CHUNK_SIZE * 2];
1028
1029 for sound in active_sounds.iter_mut() {
1030 for i in 0..CHUNK_SIZE {
1031 let idx = i * 2;
1032 if sound.cursor + 1 < sound.data.len() {
1033 mix_buffer[idx] += sound.data[sound.cursor] as i32;
1034 mix_buffer[idx + 1] += sound.data[sound.cursor + 1] as i32;
1035 sound.cursor += 2;
1036 }
1037 }
1038 }
1039
1040 let sample_rate = 44100.0;
1041 let max_notes = active_notes.len().max(1) as f32;
1042
1043 for note in active_notes.iter_mut().filter(|n| n.active) {
1044 let step = 2.0 * PI * note.freq / sample_rate;
1045
1046 for i in 0..CHUNK_SIZE {
1047 let idx = i * 2;
1048
1049 if (note.step > 0.0 && note.amplitude < note.target_amp)
1050 || (note.step < 0.0 && note.amplitude > note.target_amp)
1051 {
1052 note.amplitude = (note.amplitude + note.step).clamp(0.0, 1.0);
1053 } else if note.target_amp == 0.0 {
1054 note.active = false;
1055 }
1056
1057 let s = (note.phase).sin() * note.amplitude * (0.3 / max_notes);
1058
1059 note.phase += step;
1060 if note.phase > PI * 2.0 {
1061 note.phase -= PI * 2.0;
1062 }
1063
1064 let si = (s * i16::MAX as f32) as i16;
1065 mix_buffer[idx] += si as i32;
1066 mix_buffer[idx + 1] += si as i32;
1067 }
1068 }
1069
1070 let final_buffer: Vec<i16> = mix_buffer
1071 .into_iter()
1072 .map(|s| s.clamp(i16::MIN as i32, i16::MAX as i32) as i16)
1073 .collect();
1074
1075 AudioEngine::play_buffer(h_waveout, final_buffer);
1076
1077 active_sounds.retain(|s| s.cursor < s.data.len());
1078 active_notes.retain(|n| n.active);
1079
1080 thread::sleep(std::time::Duration::from_millis(10));
1081 }
1082 });
1083
1084 Self { tx }
1085 }
1086
1087 pub fn load_sample<P: AsRef<Path>>(&self, path: P) {
1093 let _ = self.tx.send(AudioCommand::LoadSample(
1094 path.as_ref().to_string_lossy().into(),
1095 ));
1096 }
1097
1098 pub fn play_sample<P: AsRef<Path>>(&self, path: P) {
1102 let _ = self.tx.send(AudioCommand::PlaySample(
1103 path.as_ref().to_string_lossy().into(),
1104 ));
1105 }
1106
1107 pub fn play_note(&self, frequency: f32, duration_ms: u32) {
1112 let sample_rate = 44100;
1113 let sample_count = ((duration_ms as f32 / 1000.0) * sample_rate as f32) as usize;
1114 if sample_count == 0 {
1115 return;
1116 }
1117
1118 let mut mono = vec![0f32; sample_count];
1119 for (n, v) in mono.iter_mut().enumerate() {
1120 let t = n as f32 / sample_rate as f32;
1121 *v = (t * frequency * 2.0 * PI).sin();
1122 }
1123
1124 Self::apply_attack_release(&mut mono, sample_rate, duration_ms);
1125
1126 let mut stereo = vec![0i16; sample_count * 2];
1127 let scale = 0.95;
1128 for (n, &v) in mono.iter().enumerate() {
1129 let s = (v * scale * i16::MAX as f32).clamp(i16::MIN as f32, i16::MAX as f32);
1130 let si = s as i16;
1131 stereo[n * 2] = si;
1132 stereo[n * 2 + 1] = si;
1133 }
1134
1135 let key = Self::generate_unique_key();
1136 let _ = self
1137 .tx
1138 .send(AudioCommand::LoadSampleFromBuffer(key.clone(), stereo));
1139 let _ = self.tx.send(AudioCommand::PlaySample(key));
1140 }
1141
1142 pub fn play_notes(&self, freqs: &[f32], duration_ms: u32) {
1148 if freqs.is_empty() {
1149 return;
1150 }
1151 let sample_rate = 44100u32;
1152 let sample_count = ((duration_ms as f32 / 1000.0) * sample_rate as f32) as usize;
1153 if sample_count == 0 {
1154 return;
1155 }
1156
1157 let mut mono = vec![0f32; sample_count];
1158 for &freq in freqs {
1159 for (n, v) in mono.iter_mut().enumerate() {
1160 let t = n as f32 / sample_rate as f32;
1161 *v += (t * freq * 2.0 * PI).sin();
1162 }
1163 }
1164
1165 let max_possible = freqs.len() as f32;
1166 let scale = 0.9 / max_possible;
1167 for v in mono.iter_mut() {
1168 *v *= scale;
1169 }
1170
1171 Self::apply_attack_release(&mut mono, sample_rate, duration_ms);
1172
1173 let mut stereo = vec![0i16; sample_count * 2];
1174 for (n, &v) in mono.iter().enumerate() {
1175 let s = (v * i16::MAX as f32).clamp(i16::MIN as f32, i16::MAX as f32);
1176 let si = s as i16;
1177 stereo[n * 2] = si;
1178 stereo[n * 2 + 1] = si;
1179 }
1180
1181 let key = Self::generate_unique_key();
1182 let _ = self
1183 .tx
1184 .send(AudioCommand::LoadSampleFromBuffer(key.clone(), stereo));
1185 let _ = self.tx.send(AudioCommand::PlaySample(key));
1186 }
1187
1188 pub fn note_on(&self, freq: f32) {
1192 let _ = self.tx.send(AudioCommand::NoteOn(freq));
1193 }
1194
1195 pub fn note_off(&self, freq: f32) {
1200 let _ = self.tx.send(AudioCommand::NoteOff(freq));
1201 }
1202
1203 fn apply_attack_release(buffer: &mut [f32], sample_rate: u32, duration_ms: u32) {
1204 let len = buffer.len();
1205 if len == 0 {
1206 return;
1207 }
1208
1209 let ramp_pct = 0.10;
1210 let ramp_samps =
1211 ((duration_ms as f32 / 1000.0) * sample_rate as f32 * ramp_pct).round() as usize;
1212 let ramp_samps = ramp_samps.min(len / 2);
1213
1214 for (i, v) in buffer.iter_mut().take(ramp_samps).enumerate() {
1215 *v *= i as f32 / ramp_samps.max(1) as f32;
1216 }
1217
1218 for i in 0..ramp_samps {
1219 let idx = len - ramp_samps + i;
1220 buffer[idx] *= 1.0 - (i as f32 / ramp_samps as f32);
1221 }
1222 }
1223
1224 fn generate_unique_key() -> String {
1225 let id = NOTE_COUNTER.fetch_add(1, Relaxed);
1226 format!("__temp_notes_{}", id)
1227 }
1228
1229 fn load_wav(path: &str) -> std::io::Result<Vec<i16>> {
1230 let mut file = File::open(path)?;
1231 let mut buf = Vec::new();
1232 file.read_to_end(&mut buf)?;
1233
1234 let data_start = buf.windows(4).position(|w| w == b"data").unwrap() + 8;
1235 let samples: Vec<i16> = buf[data_start..]
1236 .chunks_exact(2)
1237 .map(|b| i16::from_le_bytes([b[0], b[1]]))
1238 .collect();
1239
1240 Ok(samples)
1241 }
1242
1243 fn play_buffer(h_waveout: HWAVEOUT, data: Vec<i16>) {
1244 let boxed_data = Box::new(data);
1245 let raw_data = Box::into_raw(boxed_data);
1246
1247 let mut hdr = Box::new(WAVEHDR {
1248 lpData: PSTR(unsafe { (*raw_data).as_ptr() as *mut u8 }),
1249 dwBufferLength: (unsafe { (*raw_data).len() * 2 } as u32),
1250 dwFlags: 0,
1251 dwLoops: 0,
1252 dwUser: raw_data as usize,
1253 ..Default::default()
1254 });
1255
1256 unsafe {
1257 waveOutPrepareHeader(h_waveout, &mut *hdr, std::mem::size_of::<WAVEHDR>() as u32);
1258 waveOutWrite(h_waveout, &mut *hdr, std::mem::size_of::<WAVEHDR>() as u32);
1259 }
1260
1261 let _ = Box::into_raw(hdr);
1262 }
1263}
1264
1265impl Drop for AudioEngine {
1266 fn drop(&mut self) {
1267 let _ = self.tx.send(AudioCommand::Quit);
1268 }
1269}
1270
1271static RUNNING: AtomicBool = AtomicBool::new(true);
1276
1277unsafe extern "system" fn console_handler(ctrl_type: u32) -> BOOL {
1278 if ctrl_type == CTRL_CLOSE_EVENT {
1279 RUNNING.store(false, SeqCst);
1280 }
1281 BOOL(1)
1282}
1283
1284pub trait ConsoleGame: Sized {
1289 fn app_name(&self) -> &str {
1294 "Default"
1295 }
1296
1297 fn create(&mut self, engine: &mut ConsoleGameEngine<Self>) -> bool;
1308
1309 fn update(&mut self, engine: &mut ConsoleGameEngine<Self>, elapsed_time: f32) -> bool;
1323
1324 #[allow(unused_variables)]
1337 fn destroy(&mut self, engine: &mut ConsoleGameEngine<Self>) -> bool {
1338 true
1339 }
1340}
1341
1342#[derive(Clone)]
1346pub struct ConsoleGameEngine<G: ConsoleGame> {
1347 app_name: String,
1348
1349 output_handle: HANDLE,
1350 input_handle: HANDLE,
1351
1352 original_state: ConsoleState,
1353
1354 key_new_state: [u16; 256],
1355 key_old_state: [u16; 256],
1356 key_pressed: [bool; 256],
1357 key_released: [bool; 256],
1358 key_held: [bool; 256],
1359
1360 mouse_new_state: [bool; 5],
1361 mouse_old_state: [bool; 5],
1362 mouse_pressed: [bool; 5],
1363 mouse_released: [bool; 5],
1364 mouse_held: [bool; 5],
1365
1366 mouse_x: i32,
1367 mouse_y: i32,
1368
1369 console_in_focus: bool,
1370
1371 rect: SMALL_RECT,
1372
1373 screen_width: i16,
1374 screen_height: i16,
1375
1376 window_buffer: Vec<CHAR_INFO>,
1377
1378 pub audio: AudioEngine,
1379
1380 game: Option<G>,
1381}
1382
1383impl<G: ConsoleGame> ConsoleGameEngine<G> {
1386 pub fn new(game: G) -> Self {
1391 let app_name = game.app_name().to_string();
1392 let mouse_x = 0;
1393 let mouse_y = 0;
1394 let output_handle = unsafe {
1395 GetStdHandle(STD_OUTPUT_HANDLE).unwrap_or_else(|e| {
1396 eprintln!("Error getting stdout handle: {:?}", e);
1397 exit(1);
1398 })
1399 };
1400 let input_handle = unsafe {
1401 GetStdHandle(STD_INPUT_HANDLE).unwrap_or_else(|e| {
1402 eprintln!("Error getting stin handle: {:?}", e);
1403 exit(1);
1404 })
1405 };
1406 let original_state = ConsoleState::save(output_handle, input_handle);
1407 let rect = SMALL_RECT::default();
1408 let window_buffer = Vec::new();
1409
1410 Self {
1411 app_name,
1412 output_handle,
1413 input_handle,
1414 original_state,
1415 key_new_state: [0; 256],
1416 key_old_state: [0; 256],
1417 key_pressed: [false; 256],
1418 key_released: [false; 256],
1419 key_held: [false; 256],
1420 mouse_new_state: [false; 5],
1421 mouse_old_state: [false; 5],
1422 mouse_pressed: [false; 5],
1423 mouse_released: [false; 5],
1424 mouse_held: [false; 5],
1425 mouse_x,
1426 mouse_y,
1427 console_in_focus: true,
1428 rect,
1429 screen_width: 80,
1430 screen_height: 80,
1431 window_buffer,
1432 audio: AudioEngine::new(),
1433 game: Some(game),
1434 }
1435 }
1436
1437 pub fn screen_width(&self) -> i32 {
1439 self.screen_width as i32
1440 }
1441
1442 pub fn screen_height(&self) -> i32 {
1444 self.screen_height as i32
1445 }
1446
1447 pub fn key_pressed(&self, key: usize) -> bool {
1452 self.key_pressed[key]
1453 }
1454
1455 pub fn key_released(&self, key: usize) -> bool {
1460 self.key_released[key]
1461 }
1462
1463 pub fn key_held(&self, key: usize) -> bool {
1468 self.key_held[key]
1469 }
1470
1471 pub fn mouse_pressed(&self, button: usize) -> bool {
1476 self.mouse_pressed[button]
1477 }
1478
1479 pub fn mouse_released(&self, button: usize) -> bool {
1484 self.mouse_released[button]
1485 }
1486
1487 pub fn mouse_held(&self, button: usize) -> bool {
1492 self.mouse_held[button]
1493 }
1494
1495 pub fn mouse_x(&self) -> i32 {
1497 self.mouse_x
1498 }
1499
1500 pub fn mouse_y(&self) -> i32 {
1502 self.mouse_y
1503 }
1504
1505 pub fn mouse_pos(&self) -> (i32, i32) {
1507 (self.mouse_x, self.mouse_y)
1508 }
1509
1510 pub fn console_focused(&self) -> bool {
1512 self.console_in_focus
1513 }
1514
1515 pub fn construct_console(
1532 &mut self,
1533 width: i16,
1534 height: i16,
1535 fontw: i16,
1536 fonth: i16,
1537 ) -> Result<(), Box<dyn std::error::Error>> {
1538 if self.output_handle == INVALID_HANDLE_VALUE {
1539 return Err("Bad Handle".into());
1540 }
1541
1542 self.screen_width = width;
1543 self.screen_height = height;
1544
1545 self.rect = SMALL_RECT {
1546 Left: 0,
1547 Top: 0,
1548 Right: 1,
1549 Bottom: 1,
1550 };
1551
1552 self.set_console_window_info(self.output_handle, true, &self.rect)?;
1553
1554 let coord = COORD {
1555 X: self.screen_width,
1556 Y: self.screen_height,
1557 };
1558
1559 self.set_console_screen_buffer_size(self.output_handle, coord)?;
1560
1561 self.set_console_active_screen_buffer(self.output_handle)?;
1562
1563 let mut font_cfi = CONSOLE_FONT_INFOEX {
1564 cbSize: size_of::<CONSOLE_FONT_INFOEX>().try_into().unwrap(),
1565 nFont: 0,
1566 dwFontSize: COORD { X: fontw, Y: fonth },
1567 FontFamily: FF_DONTCARE.0 as u32,
1568 FontWeight: FW_NORMAL.0,
1569 ..Default::default()
1570 };
1571
1572 self.set_face_name(&mut font_cfi.FaceName, "Consolas");
1573
1574 self.set_current_console_font_ex(self.output_handle, false, &font_cfi)?;
1575
1576 let max_size = unsafe { GetLargestConsoleWindowSize(self.output_handle) };
1577
1578 if width > max_size.X || height > max_size.Y {
1579 return Err(format!(
1580 "Requested console size {}x{} exceeds maximum {}x{} for this display/font.",
1581 width, height, max_size.X, max_size.Y
1582 )
1583 .into());
1584 }
1585
1586 let mut screen_buffer_csbi = CONSOLE_SCREEN_BUFFER_INFO::default();
1587 self.get_console_screen_buffer_info(self.output_handle, &mut screen_buffer_csbi)?;
1588
1589 self.validate_window_size(&screen_buffer_csbi)?;
1590
1591 self.rect = SMALL_RECT {
1592 Left: 0,
1593 Top: 0,
1594 Right: self.screen_width - 1,
1595 Bottom: self.screen_height - 1,
1596 };
1597
1598 self.set_console_window_info(self.output_handle, true, &self.rect)?;
1599
1600 self.window_buffer = vec![
1601 CHAR_INFO::default();
1602 (self.screen_width as i32 * self.screen_height as i32) as usize
1603 ];
1604
1605 self.set_ctrl_handler(Some(console_handler), true)?;
1606
1607 self.set_console_mode()?;
1608
1609 self.set_console_cursor_info()?;
1610
1611 Ok(())
1612 }
1613
1614 fn update_keys(&mut self) {
1615 for i in 0..256 {
1616 self.key_pressed[i] = false;
1617 self.key_released[i] = false;
1618
1619 self.key_new_state[i] = unsafe { GetAsyncKeyState(i as i32) as u16 };
1620
1621 if self.key_new_state[i] != self.key_old_state[i] {
1622 if (self.key_new_state[i] & 0x8000) != 0 {
1623 self.key_pressed[i] = !self.key_held[i];
1624 self.key_held[i] = true;
1625 } else {
1626 self.key_released[i] = true;
1627 self.key_held[i] = false;
1628 }
1629 }
1630
1631 self.key_old_state[i] = self.key_new_state[i];
1632 }
1633 }
1634
1635 fn update_mouse(&mut self) {
1636 let mut events: u32 = 0;
1637 self.get_number_of_console_input_events(&mut events);
1638 if events == 0 {
1639 return;
1640 }
1641
1642 let count = events.min(32);
1643 let mut in_buf = [INPUT_RECORD::default(); 32];
1644 let mut read = 0;
1645 self.read_console_input_w(count as usize, &mut in_buf, &mut read);
1646
1647 for record in &in_buf[..read as usize] {
1648 match record.EventType as u32 {
1649 FOCUS_EVENT => unsafe {
1650 self.console_in_focus = record.Event.FocusEvent.bSetFocus.as_bool();
1651 },
1652 MOUSE_EVENT => {
1653 let me = unsafe { record.Event.MouseEvent };
1654 match me.dwEventFlags {
1655 0 => {
1656 for m in 0..5 {
1657 self.mouse_new_state[m] = (me.dwButtonState & (1 << m)) != 0;
1658 }
1659 }
1660 MOUSE_MOVED => {
1661 self.mouse_x = me.dwMousePosition.X as i32;
1662 self.mouse_y = me.dwMousePosition.Y as i32;
1663 }
1664 _ => {}
1665 }
1666 }
1667 _ => {}
1668 }
1669 }
1670
1671 for m in 0..5 {
1672 self.mouse_pressed[m] = false;
1673 self.mouse_released[m] = false;
1674
1675 if self.mouse_new_state[m] != self.mouse_old_state[m] {
1676 if self.mouse_new_state[m] {
1677 self.mouse_pressed[m] = true;
1678 self.mouse_held[m] = true;
1679 } else {
1680 self.mouse_released[m] = true;
1681 self.mouse_held[m] = false;
1682 }
1683 }
1684
1685 self.mouse_old_state[m] = self.mouse_new_state[m];
1686 }
1687 }
1688
1689 pub fn start(mut self) {
1693 let mut game = self.game.take().unwrap();
1694
1695 if !game.create(&mut self) {
1696 RUNNING.store(false, SeqCst);
1697 }
1698
1699 let mut s: [u16; 256] = [0; 256];
1700 let s_ptr = s.as_mut_ptr();
1701
1702 let mut tp_1 = Instant::now();
1703
1704 while RUNNING.load(SeqCst) {
1705 while RUNNING.load(SeqCst) {
1706 let tp_2 = Instant::now();
1707 let elapsed = tp_2.duration_since(tp_1);
1708 tp_1 = tp_2;
1709
1710 let elapsed_time = elapsed.as_secs_f32();
1711
1712 let fps = if elapsed_time > 0.0 {
1713 1.0 / elapsed_time
1714 } else {
1715 0.0
1716 };
1717
1718 self.update_keys();
1719 self.update_mouse();
1720
1721 if !game.update(&mut self, elapsed_time) {
1722 RUNNING.store(false, SeqCst);
1723 }
1724
1725 unsafe {
1726 let mut rect = self.rect;
1727
1728 let w_char =
1729 format!("Console Game Engine - {} - FPS: {:.2}", self.app_name, fps);
1730 let w_string = HSTRING::from(w_char);
1731
1732 wsprintfW(PWSTR(s_ptr), PCWSTR(w_string.as_ptr()));
1733
1734 self.set_console_title(PCWSTR(s.as_ptr()));
1735
1736 self.write_console_output(
1737 self.output_handle,
1738 self.window_buffer.as_ptr(),
1739 COORD {
1740 X: self.screen_width,
1741 Y: self.screen_height,
1742 },
1743 COORD { X: 0, Y: 0 },
1744 &mut rect,
1745 );
1746 }
1747 }
1748
1749 if !game.destroy(&mut self) {
1750 RUNNING.store(true, SeqCst);
1751 }
1752 }
1753 }
1754}
1755
1756impl<G: ConsoleGame> Drop for ConsoleGameEngine<G> {
1757 fn drop(&mut self) {
1758 self.original_state
1759 .restore(self.output_handle, self.input_handle);
1760 }
1761}
1762
1763impl<G: ConsoleGame> ConsoleGameEngine<G> {
1768 fn set_console_window_info(
1769 &self,
1770 handle: HANDLE,
1771 absolute: bool,
1772 rect: *const SMALL_RECT,
1773 ) -> windows::core::Result<()> {
1774 unsafe {
1775 SetConsoleWindowInfo(handle, absolute, rect)?;
1776 }
1777 Ok(())
1778 }
1779
1780 fn set_console_screen_buffer_size(
1781 &self,
1782 handle: HANDLE,
1783 size: COORD,
1784 ) -> windows::core::Result<()> {
1785 unsafe {
1786 SetConsoleScreenBufferSize(handle, size)?;
1787 }
1788 Ok(())
1789 }
1790
1791 fn set_console_active_screen_buffer(&self, handle: HANDLE) -> windows::core::Result<()> {
1792 unsafe {
1793 SetConsoleActiveScreenBuffer(handle)?;
1794 }
1795 Ok(())
1796 }
1797
1798 fn set_current_console_font_ex(
1799 &self,
1800 handle: HANDLE,
1801 max_window: bool,
1802 font: *const CONSOLE_FONT_INFOEX,
1803 ) -> windows::core::Result<()> {
1804 unsafe {
1805 SetCurrentConsoleFontEx(handle, max_window, font)?;
1806 }
1807 Ok(())
1808 }
1809
1810 fn get_console_screen_buffer_info(
1811 &self,
1812 handle: HANDLE,
1813 buffer: *mut CONSOLE_SCREEN_BUFFER_INFO,
1814 ) -> windows::core::Result<()> {
1815 unsafe {
1816 GetConsoleScreenBufferInfo(handle, buffer)?;
1817 }
1818 Ok(())
1819 }
1820
1821 fn set_ctrl_handler(&self, routine: PHANDLER_ROUTINE, add: bool) -> windows::core::Result<()> {
1822 unsafe {
1823 SetConsoleCtrlHandler(routine, add)?;
1824 }
1825 Ok(())
1826 }
1827
1828 fn set_face_name(&self, face_name_field: &mut [u16], value: &str) {
1829 let wide: Vec<u16> = value.encode_utf16().chain(Some(0)).collect();
1830 let len = wide.len().min(face_name_field.len());
1831 face_name_field[..len].copy_from_slice(&wide[..len]);
1832 }
1833
1834 fn validate_window_size(&self, buffer: &CONSOLE_SCREEN_BUFFER_INFO) -> Result<(), String> {
1835 if self.screen_height > buffer.dwMaximumWindowSize.Y {
1836 return Err("Screen height or font height too big".into());
1837 }
1838 if self.screen_width > buffer.dwMaximumWindowSize.X {
1839 return Err("Screen width or font width too big".into());
1840 }
1841 Ok(())
1842 }
1843
1844 fn set_console_title(&self, title: PCWSTR) {
1845 unsafe {
1846 SetConsoleTitleW(title).unwrap_or_else(|e| {
1847 eprintln!("SetConsoleTitleW Failed: {:?}", e);
1848 exit(1);
1849 });
1850 }
1851 }
1852
1853 fn write_console_output(
1854 &self,
1855 handle: HANDLE,
1856 buffer: *const CHAR_INFO,
1857 buffer_size: COORD,
1858 buffer_coord: COORD,
1859 write_region: *mut SMALL_RECT,
1860 ) {
1861 unsafe {
1862 WriteConsoleOutputW(handle, buffer, buffer_size, buffer_coord, write_region)
1863 .unwrap_or_else(|e| {
1864 eprintln!("WriteConsoleOutputW Failed: {:?}", e);
1865 exit(1);
1866 });
1867 }
1868 }
1869
1870 fn set_console_mode(&self) -> windows::core::Result<()> {
1871 unsafe {
1872 let mut mode = CONSOLE_MODE(0);
1873 GetConsoleMode(self.input_handle, &mut mode)?;
1874
1875 mode &= !ENABLE_QUICK_EDIT_MODE;
1876 mode |= ENABLE_EXTENDED_FLAGS | ENABLE_MOUSE_INPUT | ENABLE_WINDOW_INPUT;
1877
1878 SetConsoleMode(self.input_handle, mode)?;
1879 }
1880 Ok(())
1881 }
1882
1883 fn set_console_cursor_info(&self) -> windows::core::Result<()> {
1884 unsafe {
1885 let info = CONSOLE_CURSOR_INFO {
1886 dwSize: 1,
1887 bVisible: FALSE,
1888 };
1889 SetConsoleCursorInfo(self.output_handle, &info)?;
1890 }
1891 Ok(())
1892 }
1893
1894 fn get_number_of_console_input_events(&self, num_events: &mut u32) {
1895 unsafe {
1896 GetNumberOfConsoleInputEvents(self.input_handle, num_events).unwrap_or_else(|e| {
1897 eprintln!("GetNumberOfConsoleInputEvents Failed: {:?}", e);
1898 exit(1);
1899 })
1900 };
1901 }
1902
1903 fn read_console_input_w(
1904 &self,
1905 count: usize,
1906 buffer: &mut [INPUT_RECORD],
1907 num_events: &mut u32,
1908 ) {
1909 unsafe {
1910 ReadConsoleInputW(self.input_handle, &mut buffer[..count], num_events).unwrap_or_else(
1911 |e| {
1912 eprintln!("ReadConsoleInputW Failed: {:?}", e);
1913 exit(1);
1914 },
1915 );
1916 }
1917 }
1918}
1919
1920use color::*;
1925use pixel::*;
1926
1927impl<G: ConsoleGame> ConsoleGameEngine<G> {
1928 pub fn clip(&self, x: &mut i32, y: &mut i32) {
1930 if *x < 0 {
1931 *x = 0
1932 };
1933 if *x >= self.screen_width() {
1934 *x = self.screen_width()
1935 };
1936 if *y < 0 {
1937 *y = 0
1938 };
1939 if *y >= self.screen_height() {
1940 *y = self.screen_height()
1941 };
1942 }
1943
1944 pub fn draw(&mut self, x: i32, y: i32) {
1946 self.draw_with(x, y, SOLID, FG_WHITE);
1947 }
1948
1949 pub fn draw_with(&mut self, x: i32, y: i32, c: u16, col: u16) {
1951 if x >= 0 && x < self.screen_width as i32 && y >= 0 && y < self.screen_height as i32 {
1952 let idx = (y * self.screen_width as i32 + x) as usize;
1953 self.window_buffer[idx].Char.UnicodeChar = c;
1954 self.window_buffer[idx].Attributes = col;
1955 }
1956 }
1957
1958 pub fn clear(&mut self, col: u16) {
1960 self.fill_rect_with(0, 0, self.screen_width(), self.screen_height(), EMPTY, col);
1961 }
1962
1963 pub fn draw_string(&mut self, x: i32, y: i32, text: &str) {
1965 self.draw_string_with(x, y, text, FG_WHITE);
1966 }
1967
1968 pub fn draw_string_with(&mut self, x: i32, y: i32, text: &str, col: u16) {
1970 for (i, ch) in text.encode_utf16().enumerate() {
1971 let idx = (y as usize) * self.screen_width as usize + (x as usize + i);
1972 self.window_buffer[idx].Char.UnicodeChar = ch;
1973 self.window_buffer[idx].Attributes = col;
1974 }
1975 }
1976
1977 pub fn draw_string_alpha(&mut self, x: i32, y: i32, text: &str) {
1979 self.draw_string_alpha_with(x, y, text, FG_WHITE);
1980 }
1981
1982 pub fn draw_string_alpha_with(&mut self, x: i32, y: i32, text: &str, col: u16) {
1984 for (i, ch) in text.encode_utf16().enumerate() {
1985 if ch != ' ' as u16 {
1986 let idx = (y as usize) * self.screen_width as usize + (x as usize + i);
1987 self.window_buffer[idx].Char.UnicodeChar = ch;
1988 self.window_buffer[idx].Attributes = col;
1989 }
1990 }
1991 }
1992
1993 pub fn draw_line(&mut self, x1: i32, y1: i32, x2: i32, y2: i32) {
1995 self.draw_line_with(x1, y1, x2, y2, SOLID, FG_WHITE);
1996 }
1997
1998 pub fn draw_line_with(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, c: u16, col: u16) {
2000 let dx = x2 - x1;
2001 let dy = y2 - y1;
2002 let dx1 = dx.abs();
2003 let dy1 = dy.abs();
2004 let mut px = 2 * dy1 - dx1;
2005 let mut py = 2 * dx1 - dy1;
2006
2007 if dy1 <= dx1 {
2008 let (mut x, mut y, xe) = if dx >= 0 { (x1, y1, x2) } else { (x2, y2, x1) };
2009 self.draw_with(x, y, c, col);
2010
2011 while x < xe {
2012 x += 1;
2013 if px < 0 {
2014 px += 2 * dy1;
2015 } else {
2016 if (dx < 0 && dy < 0) || (dx > 0 && dy > 0) {
2017 y += 1;
2018 } else {
2019 y -= 1;
2020 }
2021 px += 2 * (dy1 - dx1);
2022 }
2023 self.draw_with(x, y, c, col);
2024 }
2025 } else {
2026 let (mut x, mut y, ye) = if dy >= 0 { (x1, y1, y2) } else { (x2, y2, y1) };
2027 self.draw_with(x, y, c, col);
2028
2029 while y < ye {
2030 y += 1;
2031 if py <= 0 {
2032 py += 2 * dx1;
2033 } else {
2034 if (dx < 0 && dy < 0) || (dx > 0 && dy > 0) {
2035 x += 1;
2036 } else {
2037 x -= 1;
2038 }
2039 py += 2 * (dx1 - dy1);
2040 }
2041 self.draw_with(x, y, c, col);
2042 }
2043 }
2044 }
2045
2046 pub fn draw_triangle(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, x3: i32, y3: i32) {
2048 self.draw_triangle_with(x1, y1, x2, y2, x3, y3, SOLID, FG_WHITE);
2049 }
2050
2051 #[allow(clippy::too_many_arguments)]
2053 pub fn draw_triangle_with(
2054 &mut self,
2055 x1: i32,
2056 y1: i32,
2057 x2: i32,
2058 y2: i32,
2059 x3: i32,
2060 y3: i32,
2061 c: u16,
2062 col: u16,
2063 ) {
2064 self.draw_line_with(x1, y1, x2, y2, c, col);
2065 self.draw_line_with(x2, y2, x3, y3, c, col);
2066 self.draw_line_with(x3, y3, x1, y1, c, col);
2067 }
2068
2069 pub fn fill_triangle(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, x3: i32, y3: i32) {
2071 self.fill_triangle_with(x1, y1, x2, y2, x3, y3, SOLID, FG_WHITE);
2072 }
2073
2074 #[allow(clippy::too_many_arguments)]
2076 pub fn fill_triangle_with(
2077 &mut self,
2078 mut x1: i32,
2079 mut y1: i32,
2080 mut x2: i32,
2081 mut y2: i32,
2082 mut x3: i32,
2083 mut y3: i32,
2084 c: u16,
2085 col: u16,
2086 ) {
2087 use std::mem::swap;
2088
2089 let draw_line = |engine: &mut Self, sx: i32, ex: i32, y: i32| {
2090 for i in sx..=ex {
2091 engine.draw_with(i, y, c, col);
2092 }
2093 };
2094
2095 if y1 > y2 {
2096 swap(&mut y1, &mut y2);
2097 swap(&mut x1, &mut x2);
2098 }
2099 if y1 > y3 {
2100 swap(&mut y1, &mut y3);
2101 swap(&mut x1, &mut x3);
2102 }
2103 if y2 > y3 {
2104 swap(&mut y2, &mut y3);
2105 swap(&mut x2, &mut x3);
2106 }
2107
2108 let mut t1x = x1;
2109 let mut t2x = x1;
2110 let mut y = y1;
2111
2112 let mut dx1 = x2 - x1;
2113 let mut dy1 = y2 - y1;
2114 let signx1 = if dx1 < 0 { -1 } else { 1 };
2115 dx1 = dx1.abs();
2116
2117 let dx2 = x3 - x1;
2118 let dy2 = y3 - y1;
2119 let signx2 = if dx2 < 0 { -1 } else { 1 };
2120 let dx2 = dx2.abs();
2121
2122 let changed1 = dy1 > dx1;
2123 let changed2 = dy2 > dx2;
2124
2125 if y1 != y2 {
2126 let mut i = 0;
2127 while i < dx1 {
2128 let minx = t1x.min(t2x);
2129 let maxx = t1x.max(t2x);
2130 draw_line(self, minx, maxx, y);
2131
2132 y += 1;
2133 i += 1;
2134 if changed1 {
2135 t1x += signx1;
2136 }
2137 if changed2 {
2138 t2x += signx2;
2139 }
2140 }
2141 }
2142
2143 dx1 = (x3 - x2).abs();
2144 dy1 = y3 - y2;
2145 let signx1 = if x3 - x2 < 0 { -1 } else { 1 };
2146 t1x = x2;
2147 let changed1 = dy1 > dx1;
2148
2149 while y <= y3 {
2150 let minx = t1x.min(t2x);
2151 let maxx = t1x.max(t2x);
2152 draw_line(self, minx, maxx, y);
2153
2154 y += 1;
2155 if changed1 {
2156 t1x += signx1;
2157 }
2158 if changed2 {
2159 t2x += signx2;
2160 }
2161
2162 if y > y3 {
2163 break;
2164 }
2165 }
2166 }
2167
2168 pub fn draw_rectangle(&mut self, x: i32, y: i32, w: i32, h: i32) {
2170 self.draw_rectangle_with(x, y, w, h, SOLID, FG_WHITE);
2171 }
2172
2173 pub fn draw_rectangle_with(&mut self, x: i32, y: i32, w: i32, h: i32, c: u16, col: u16) {
2175 if w <= 0 || h <= 0 {
2176 return;
2177 }
2178
2179 self.draw_line_with(x, y, x + w - 1, y, c, col);
2180 self.draw_line_with(x, y + h - 1, x + w - 1, y + h - 1, c, col);
2181 self.draw_line_with(x, y, x, y + h - 1, c, col);
2182 self.draw_line_with(x + w - 1, y, x + w - 1, y + h - 1, c, col);
2183 }
2184
2185 pub fn fill_rect(&mut self, x1: i32, y1: i32, x2: i32, y2: i32) {
2187 self.fill_rect_with(x1, y1, x2, y2, SOLID, FG_WHITE);
2188 }
2189
2190 pub fn fill_rect_with(
2192 &mut self,
2193 mut x1: i32,
2194 mut y1: i32,
2195 mut x2: i32,
2196 mut y2: i32,
2197 c: u16,
2198 col: u16,
2199 ) {
2200 self.clip(&mut x1, &mut y1);
2201 self.clip(&mut x2, &mut y2);
2202
2203 for x in x1..x2 {
2204 for y in y1..y2 {
2205 self.draw_with(x, y, c, col);
2206 }
2207 }
2208 }
2209
2210 pub fn draw_circle(&mut self, xc: i32, yc: i32, r: i32) {
2212 self.draw_circle_with(xc, yc, r, SOLID, FG_WHITE);
2213 }
2214
2215 pub fn draw_circle_with(&mut self, xc: i32, yc: i32, r: i32, c: u16, col: u16) {
2217 if r == 0 {
2218 return;
2219 }
2220 let mut x = 0;
2221 let mut y = r;
2222 let mut p = 3 - 2 * r;
2223
2224 while y >= x {
2225 self.draw_with(xc - x, yc - y, c, col);
2226 self.draw_with(xc - y, yc - x, c, col);
2227 self.draw_with(xc + y, yc - x, c, col);
2228 self.draw_with(xc + x, yc - y, c, col);
2229 self.draw_with(xc - x, yc + y, c, col);
2230 self.draw_with(xc - y, yc + x, c, col);
2231 self.draw_with(xc + y, yc + x, c, col);
2232 self.draw_with(xc + x, yc + y, c, col);
2233
2234 if p < 0 {
2235 p += 4 * x + 6;
2236 } else {
2237 p += 4 * (x - y) + 10;
2238 y -= 1;
2239 }
2240 x += 1;
2241 }
2242 }
2243
2244 pub fn fill_circle(&mut self, xc: i32, yc: i32, r: i32) {
2246 self.fill_circle_with(xc, yc, r, SOLID, FG_WHITE);
2247 }
2248
2249 pub fn fill_circle_with(&mut self, xc: i32, yc: i32, r: i32, c: u16, col: u16) {
2251 if r == 0 {
2252 return;
2253 }
2254 let mut x = 0;
2255 let mut y = r;
2256 let mut p = 3 - 2 * r;
2257
2258 let draw_line = |engine: &mut Self, sx: i32, ex: i32, ny: i32| {
2259 for i in sx..=ex {
2260 engine.draw_with(i, ny, c, col);
2261 }
2262 };
2263
2264 while y >= x {
2265 draw_line(self, xc - x, xc + x, yc - y);
2266 draw_line(self, xc - y, xc + y, yc - x);
2267 draw_line(self, xc - x, xc + x, yc + y);
2268 draw_line(self, xc - y, xc + y, yc + x);
2269
2270 if p < 0 {
2271 p += 4 * x + 6;
2272 } else {
2273 p += 4 * (x - y) + 10;
2274 y -= 1;
2275 }
2276 x += 1;
2277 }
2278 }
2279
2280 #[allow(clippy::too_many_arguments)]
2290 pub fn draw_wireframe_model(
2291 &mut self,
2292 model_coords: &[(f32, f32)],
2293 x: f32,
2294 y: f32,
2295 r: f32,
2296 s: f32,
2297 col: u16,
2298 c: u16,
2299 ) {
2300 let verts = model_coords.len();
2301 let mut transformed: Vec<(f32, f32)> = vec![(0.0, 0.0); verts];
2302
2303 for i in 0..verts {
2304 let (px, py) = model_coords[i];
2305 transformed[i].0 = px * r.cos() - py * r.sin();
2306 transformed[i].1 = px * r.sin() + py * r.cos();
2307 }
2308
2309 for t in &mut transformed {
2310 t.0 *= s;
2311 t.1 *= s;
2312 }
2313
2314 for t in &mut transformed {
2315 t.0 += x;
2316 t.1 += y;
2317 }
2318
2319 for i in 0..verts {
2320 let j = (i + 1) % verts;
2321 self.draw_line_with(
2322 transformed[i].0 as i32,
2323 transformed[i].1 as i32,
2324 transformed[j].0 as i32,
2325 transformed[j].1 as i32,
2326 c,
2327 col,
2328 );
2329 }
2330 }
2331
2332 #[allow(clippy::too_many_arguments)]
2343 pub fn draw_filled_model(
2344 &mut self,
2345 model_coords: &[(f32, f32)],
2346 x: f32,
2347 y: f32,
2348 r: f32,
2349 s: f32,
2350 col: u16,
2351 c: u16,
2352 ) {
2353 let verts = model_coords.len();
2354 if verts < 3 {
2355 return;
2356 }
2357
2358 let cos_r = r.cos();
2359 let sin_r = r.sin();
2360 let mut transformed: Vec<(f32, f32)> = Vec::with_capacity(verts);
2361 for &(px, py) in model_coords {
2362 let tx = px * cos_r - py * sin_r;
2363 let ty = px * sin_r + py * cos_r;
2364 transformed.push((tx * s + x, ty * s + y));
2365 }
2366
2367 let min_yf = transformed
2368 .iter()
2369 .map(|t| t.1)
2370 .fold(f32::INFINITY, |a, b| a.min(b));
2371 let max_yf = transformed
2372 .iter()
2373 .map(|t| t.1)
2374 .fold(f32::NEG_INFINITY, |a, b| a.max(b));
2375 let y_start = min_yf.floor() as i32;
2376 let y_end = max_yf.ceil() as i32;
2377
2378 for y_scan in y_start..=y_end {
2379 let sample_y = y_scan as f32 + 0.5;
2380 let mut intersects: Vec<f32> = Vec::new();
2381
2382 for i in 0..verts {
2383 let (x1, y1) = transformed[i];
2384 let (x2, y2) = transformed[(i + 1) % verts];
2385
2386 if (y1 - y2).abs() < f32::EPSILON {
2387 continue;
2388 }
2389
2390 let (ymin, ymax, x_a, y_a, x_b, y_b) = if y1 < y2 {
2391 (y1, y2, x1, y1, x2, y2)
2392 } else {
2393 (y2, y1, x2, y2, x1, y1)
2394 };
2395
2396 if sample_y >= ymin && sample_y < ymax {
2397 let t = (sample_y - y_a) / (y_b - y_a);
2398 let xi = x_a + t * (x_b - x_a);
2399 intersects.push(xi);
2400 }
2401 }
2402
2403 if intersects.is_empty() {
2404 continue;
2405 }
2406
2407 intersects.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
2408
2409 let mut k = 0usize;
2410 while k + 1 < intersects.len() {
2411 let x_left_f = intersects[k];
2412 let x_right_f = intersects[k + 1];
2413
2414 let x_start = x_left_f.ceil() as i32;
2415 let x_end = x_right_f.floor() as i32;
2416
2417 if x_end >= x_start {
2418 for xi in x_start..=x_end {
2419 self.draw_with(xi, y_scan, c, col);
2420 }
2421 }
2422
2423 k += 2;
2424 }
2425 }
2426 }
2427
2428 pub fn draw_sprite(&mut self, x: i32, y: i32, sprite: &Sprite) {
2430 for i in 0..sprite.width {
2431 for j in 0..sprite.height {
2432 let glyph = sprite.get_glyph(i, j);
2433 if glyph != EMPTY {
2434 let color = sprite.get_color(i, j);
2435 self.draw_with(x + i as i32, y + j as i32, glyph, color);
2436 }
2437 }
2438 }
2439 }
2440
2441 #[allow(clippy::too_many_arguments)]
2449 pub fn draw_partial_sprite(
2450 &mut self,
2451 x: i32,
2452 y: i32,
2453 sprite: &Sprite,
2454 ox: usize,
2455 oy: usize,
2456 w: usize,
2457 h: usize,
2458 ) {
2459 for i in 0..w {
2460 for j in 0..h {
2461 let glyph = sprite.get_glyph(i + ox, j + oy);
2462 if glyph != EMPTY {
2463 let color = sprite.get_color(i + ox, j + oy);
2464 self.draw_with(x + i as i32, y + j as i32, glyph, color);
2465 }
2466 }
2467 }
2468 }
2469}
2470
2471