rusty_console_game_engine/
lib.rs

1//! # RustyConsoleGameEngine
2//! A Rust port of the olcConsoleGameEngine.
3//! Make simple retro-style console games directly in the terminal,
4//! with an API closely matching the original C++ engine.
5
6// region: Imports
7
8use 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
27// endregion
28
29// region: Constants
30
31/// Provides convenient constants for foreground and background colors.
32///
33/// These values can be combined with pixel densities
34/// (see [`pixel`]) when calling drawing functions like:
35/// `draw_with`, `draw_string_with`, `fill_rect_with`, `clear`, etc.
36///
37/// # Examples
38///
39/// ```rust
40/// use rusty_console_game_engine::*;
41///
42/// engine.clear(BG_BLACK);
43/// engine.draw_with(0, 0, FG_WHITE, SOLID);
44/// ```
45pub mod color {
46    /// Black foreground color.
47    pub const FG_BLACK: u16 = 0x0000;
48    /// Dark blue foreground color.
49    pub const FG_DARK_BLUE: u16 = 0x0001;
50    /// Dark green foreground color.
51    pub const FG_DARK_GREEN: u16 = 0x0002;
52    /// Dark cyan foreground color.
53    pub const FG_DARK_CYAN: u16 = 0x0003;
54    /// Dark red foreground color.
55    pub const FG_DARK_RED: u16 = 0x0004;
56    /// Dark magenta foreground color.
57    pub const FG_DARK_MAGENTA: u16 = 0x0005;
58    /// Dark yellow foreground color.
59    pub const FG_DARK_YELLOW: u16 = 0x0006;
60    /// Grey foreground color.
61    pub const FG_GREY: u16 = 0x0007;
62    /// Dark grey foreground color.
63    pub const FG_DARK_GREY: u16 = 0x0008;
64    /// Blue foreground color.
65    pub const FG_BLUE: u16 = 0x0009;
66    /// Green foreground color.
67    pub const FG_GREEN: u16 = 0x000A;
68    /// Cyan foreground color.
69    pub const FG_CYAN: u16 = 0x000B;
70    /// Red foreground color.
71    pub const FG_RED: u16 = 0x000C;
72    /// Magenta foreground color.
73    pub const FG_MAGENTA: u16 = 0x000D;
74    /// Yellow foreground color.
75    pub const FG_YELLOW: u16 = 0x000E;
76    /// White foreground color.
77    pub const FG_WHITE: u16 = 0x000F;
78
79    /// Black background color.
80    pub const BG_BLACK: u16 = 0x0000;
81    /// Dark blue background color.
82    pub const BG_DARK_BLUE: u16 = 0x0010;
83    /// Dark green background color.
84    pub const BG_DARK_GREEN: u16 = 0x0020;
85    /// Dark cyan background color.
86    pub const BG_DARK_CYAN: u16 = 0x0030;
87    /// Dark red background color.
88    pub const BG_DARK_RED: u16 = 0x0040;
89    /// Dark magenta background color.
90    pub const BG_DARK_MAGENTA: u16 = 0x0050;
91    /// Dark yellow background color.
92    pub const BG_DARK_YELLOW: u16 = 0x0060;
93    /// Grey background color.
94    pub const BG_GREY: u16 = 0x0070;
95    /// Dark grey background color.
96    pub const BG_DARK_GREY: u16 = 0x0080;
97    /// Blue background color.
98    pub const BG_BLUE: u16 = 0x0090;
99    /// Green background color.
100    pub const BG_GREEN: u16 = 0x00A0;
101    /// Cyan background color.
102    pub const BG_CYAN: u16 = 0x00B0;
103    /// Red background color.
104    pub const BG_RED: u16 = 0x00C0;
105    /// Magenta background color.
106    pub const BG_MAGENTA: u16 = 0x00D0;
107    /// Yellow background color.
108    pub const BG_YELLOW: u16 = 0x00E0;
109    /// White background color.
110    pub const BG_WHITE: u16 = 0x00F0;
111}
112
113/// Provides convenient Unicode pixel constants for drawing.
114///
115/// These constants map to block characters commonly used in terminal graphics.
116/// They can be used in conjunction with foreground and background colors
117/// (see [`color`]) when calling drawing functions.
118///
119/// # Examples
120///
121/// ```rust
122/// use rusty_console_game_engine::*;
123///
124/// engine.draw(10, 5, SOLID, FG_RED | BG_BLACK);
125/// engine.draw(11, 5, HALF, FG_BLUE | BG_BLACK);
126/// ```
127pub mod pixel {
128    /// Solid block pixel.
129    pub const SOLID: u16 = 0x2588;
130    /// Three-quarter block pixel.
131    pub const THREE_QUARTERS: u16 = 0x2593;
132    /// Half block pixel.
133    pub const HALF: u16 = 0x2592;
134    /// Quarter block pixel.
135    pub const QUARTER: u16 = 0x2591;
136    /// Empty space (transparent) pixel.
137    pub const EMPTY: u16 = 0x20;
138}
139
140/// Provides constants for mouse button input.
141///
142/// These constants are used with input functions like
143/// `mouse_pressed()`, `mouse_released()`, and `mouse_held()`.
144///
145/// # Examples
146///
147/// ```rust
148/// use rusty_console_game_engine::*;
149///
150/// if engine.mouse_pressed(mouse_button::LEFT) {
151///     engine.draw_string(0, 0, "Left click!");
152/// }
153/// ```
154pub mod mouse_button {
155    /// Left mouse button. Used with `mouse_pressed`, `mouse_released`, or `mouse_held`.
156    pub const LEFT: usize = 0;
157    /// Right mouse button.
158    pub const RIGHT: usize = 1;
159    /// Middle mouse button.
160    pub const MIDDLE: usize = 2;
161    /// X1 mouse button (side button 1).
162    pub const X1: usize = 3;
163    /// X2 mouse button (side button 2).
164    pub const X2: usize = 4;
165}
166
167/// Provides constants for keyboard input.
168///
169/// These constants represent common keys and are used with input
170/// functions like `key_pressed()`, `key_released()`, and `key_held()`.
171///
172/// # Examples
173///
174/// ```rust
175/// use rusty_console_game_engine::*;
176///
177/// if engine.key_held(key::LEFT) {
178///     engine.draw_string(0, 0, "Moving left!");
179/// }
180///
181/// if engine.key_pressed(key::SPACE) {
182///     engine.draw_string(0, 1, "Jump!");
183/// }
184/// ```
185pub mod key {
186    /// Space key. Used with `key_pressed`, `key_released`, or `key_held`.
187    pub const SPACE: usize = 0x20;
188    /// Enter key.
189    pub const ENTER: usize = 0x0D;
190    /// Escape key.
191    pub const ESCAPE: usize = 0x1B;
192    /// Backspace key.
193    pub const BACKSPACE: usize = 0x08;
194    /// Tab key.
195    pub const TAB: usize = 0x09;
196    /// Shift key.
197    pub const SHIFT: usize = 0x10;
198    /// Control key.
199    pub const CONTROL: usize = 0x11;
200    /// Alt key.
201    pub const ALT: usize = 0x12;
202    /// Caps Lock key.
203    pub const CAPSLOCK: usize = 0x14;
204    /// Num Lock key.
205    pub const NUMLOCK: usize = 0x90;
206    /// Scroll Lock key.
207    pub const SCROLL_LOCK: usize = 0x91;
208
209    /// Up arrow key.
210    pub const ARROW_UP: usize = 0x26;
211    /// Down arrow key.
212    pub const ARROW_DOWN: usize = 0x28;
213    /// Left arrow key.
214    pub const ARROW_LEFT: usize = 0x25;
215    /// Right arrow key.
216    pub const ARROW_RIGHT: usize = 0x27;
217
218    /// F1 function key.
219    pub const F1: usize = 0x70;
220    /// F2 function key.
221    pub const F2: usize = 0x71;
222    /// F3 function key.
223    pub const F3: usize = 0x72;
224    /// F4 function key.
225    pub const F4: usize = 0x73;
226    /// F5 function key.
227    pub const F5: usize = 0x74;
228    /// F6 function key.
229    pub const F6: usize = 0x75;
230    /// F7 function key.
231    pub const F7: usize = 0x76;
232    /// F8 function key.
233    pub const F8: usize = 0x77;
234    /// F9 function key.
235    pub const F9: usize = 0x78;
236    /// F10 function key.
237    pub const F10: usize = 0x79;
238    /// F11 function key.
239    pub const F11: usize = 0x7A;
240    /// F12 function key.
241    pub const F12: usize = 0x7B;
242
243    /// Number 0 key.
244    pub const ZERO: usize = 0x30;
245    /// Number 1 key.
246    pub const ONE: usize = 0x31;
247    /// Number 2 key.
248    pub const TWO: usize = 0x32;
249    /// Number 3 key.
250    pub const THREE: usize = 0x33;
251    /// Number 4 key.
252    pub const FOUR: usize = 0x34;
253    /// Number 5 key.
254    pub const FIVE: usize = 0x35;
255    /// Number 6 key.
256    pub const SIX: usize = 0x36;
257    /// Number 7 key.
258    pub const SEVEN: usize = 0x37;
259    /// Number 8 key.
260    pub const EIGHT: usize = 0x38;
261    /// Number 9 key.
262    pub const NINE: usize = 0x39;
263
264    /// Letter A key.
265    pub const A: usize = 0x41;
266    /// Letter B key.
267    pub const B: usize = 0x42;
268    /// Letter C key.
269    pub const C: usize = 0x43;
270    /// Letter D key.
271    pub const D: usize = 0x44;
272    /// Letter E key.
273    pub const E: usize = 0x45;
274    /// Letter F key.
275    pub const F: usize = 0x46;
276    /// Letter G key.
277    pub const G: usize = 0x47;
278    /// Letter H key.
279    pub const H: usize = 0x48;
280    /// Letter I key.
281    pub const I: usize = 0x49;
282    /// Letter J key.
283    pub const J: usize = 0x4A;
284    /// Letter K key.
285    pub const K: usize = 0x4B;
286    /// Letter L key.
287    pub const L: usize = 0x4C;
288    /// Letter M key.
289    pub const M: usize = 0x4D;
290    /// Letter N key.
291    pub const N: usize = 0x4E;
292    /// Letter O key.
293    pub const O: usize = 0x4F;
294    /// Letter P key.
295    pub const P: usize = 0x50;
296    /// Letter Q key.
297    pub const Q: usize = 0x51;
298    /// Letter R key.
299    pub const R: usize = 0x52;
300    /// Letter S key.
301    pub const S: usize = 0x53;
302    /// Letter T key.
303    pub const T: usize = 0x54;
304    /// Letter U key.
305    pub const U: usize = 0x55;
306    /// Letter V key.
307    pub const V: usize = 0x56;
308    /// Letter W key.
309    pub const W: usize = 0x57;
310    /// Letter X key.
311    pub const X: usize = 0x58;
312    /// Letter Y key.
313    pub const Y: usize = 0x59;
314    /// Letter Z key.
315    pub const Z: usize = 0x5A;
316
317    /// Numpad 0 key.
318    pub const NUMPAD_0: usize = 0x60;
319    /// Numpad 1 key.
320    pub const NUMPAD_1: usize = 0x61;
321    /// Numpad 2 key.
322    pub const NUMPAD_2: usize = 0x62;
323    /// Numpad 3 key.
324    pub const NUMPAD_3: usize = 0x63;
325    /// Numpad 4 key.
326    pub const NUMPAD_4: usize = 0x64;
327    /// Numpad 5 key.
328    pub const NUMPAD_5: usize = 0x65;
329    /// Numpad 6 key.
330    pub const NUMPAD_6: usize = 0x66;
331    /// Numpad 7 key.
332    pub const NUMPAD_7: usize = 0x67;
333    /// Numpad 8 key.
334    pub const NUMPAD_8: usize = 0x68;
335    /// Numpad 9 key.
336    pub const NUMPAD_9: usize = 0x69;
337    /// Numpad addition key (+).
338    pub const NUMPAD_ADD: usize = 0x6B;
339    /// Numpad subtraction key (-).
340    pub const NUMPAD_SUBTRACT: usize = 0x6D;
341    /// Numpad multiplication key (*).
342    pub const NUMPAD_MULTIPLY: usize = 0x6A;
343    /// Numpad division key (/).
344    pub const NUMPAD_DIVIDE: usize = 0x6F;
345    /// Numpad Enter key.
346    pub const NUMPAD_ENTER: usize = 0x0D;
347
348    /// Semicolon / Colon key.
349    /// Only works with US ANSI Keyboards
350    pub const SEMICOLON: usize = 0xBA;
351    /// Equals / Plus key.
352    /// Only works with US ANSI Keyboards
353    pub const EQUAL: usize = 0xBB;
354    /// Comma / Less Than key.
355    /// Only works with US ANSI Keyboards
356    pub const COMMA: usize = 0xBC;
357    /// Dash / Underscore key.
358    /// Only works with US ANSI Keyboards
359    pub const DASH: usize = 0xBD;
360    /// Period / Greater Than key.
361    /// Only works with US ANSI Keyboards
362    pub const PERIOD: usize = 0xBE;
363    /// Forward Slash / Question Mark key.
364    /// Only works with US ANSI Keyboards
365    pub const SLASH: usize = 0xBF;
366    /// Backtick / Tilde key.
367    /// Only works with US ANSI Keyboards
368    pub const BACKTICK: usize = 0xC0;
369    /// Left Brace / Left Curly Bracket key.
370    /// Only works with US ANSI Keyboards
371    pub const LEFT_BRACE: usize = 0xDB;
372    /// Backslash / Pipe key.
373    /// Only works with US ANSI Keyboards
374    pub const BACKSLASH: usize = 0xDC;
375    /// Right Brace / Right Curly Bracket key.
376    /// Only works with US ANSI Keyboards
377    pub const RIGHT_BRACE: usize = 0xDD;
378    /// Apostrophe / Double Quote key.
379    /// Only works with US ANSI Keyboards
380    pub const APOSTROPHE: usize = 0xDE;
381}
382
383/// Provides named constants for musical note frequencies (in Hertz).
384///
385/// These constants are designed to be used with the [`AudioEngine`]'s
386/// functions like `play_note()`, `play_notes()`, `note_on()`, and `note_off()`.
387/// Instead of passing raw frequency values, you can use constants
388/// like `note::C4` (Middle C) or `note::A4` (440 Hz).
389///
390/// # Example
391/// ```rust
392/// // Play a single note for 500 ms
393/// engine.audio.play_note(note::A4, 500);
394///
395/// // Play a C major chord
396/// engine.audio.play_notes(&[note::C4, note::E4, note::G4], 750);
397/// ```
398///
399/// Notes follow standard scientific pitch notation:
400/// - Octaves are numbered (ex: `C4`, `A5`).
401/// - Sharps/flats use `_SHARP` (ex: `F_SHARP3`, equivalent to Gb3).
402///
403/// Frequencies are based on equal temperament tuning with `A4 = 440.00 Hz`.
404pub mod note {
405    /// A1 (55.00 Hz). Used with the `AudioEngine`'s `play_note` and `play_notes` functions
406    pub const A1: f32 = 55.00;
407    /// A2 (110.00 Hz)
408    pub const A2: f32 = 110.00;
409    /// A3 (220.00 Hz)
410    pub const A3: f32 = 220.00;
411    /// A4 (440.00 Hz)
412    pub const A4: f32 = 440.00;
413    /// A5 (880.00 Hz)
414    pub const A5: f32 = 880.00;
415    /// A6 (1760.00 Hz)
416    pub const A6: f32 = 1760.00;
417    /// A7 (3520.00 Hz)
418    pub const A7: f32 = 3520.00;
419    /// A8 (7040.00 Hz)
420    pub const A8: f32 = 7040.00;
421
422    /// A#1 / Bb1 (58.27 Hz)
423    pub const A_SHARP1: f32 = 58.27;
424    /// A#2 / Bb2 (116.54 Hz)
425    pub const A_SHARP2: f32 = 116.54;
426    /// A#3 / Bb3 (233.08 Hz)
427    pub const A_SHARP3: f32 = 233.08;
428    /// A#4 / Bb4 (466.16 Hz)
429    pub const A_SHARP4: f32 = 466.16;
430    /// A#5 / Bb5 (932.33 Hz)
431    pub const A_SHARP5: f32 = 932.33;
432    /// A#6 / Bb6 (1864.66 Hz)
433    pub const A_SHARP6: f32 = 1864.66;
434    /// A#7 / Bb7 (3729.31 Hz)
435    pub const A_SHARP7: f32 = 3729.31;
436    /// A#8 / Bb8 (7458.62 Hz)
437    pub const A_SHARP8: f32 = 7458.62;
438
439    /// B1 (61.74 Hz)
440    pub const B1: f32 = 61.74;
441    /// B2 (123.47 Hz)
442    pub const B2: f32 = 123.47;
443    /// B3 (246.94 Hz)
444    pub const B3: f32 = 246.94;
445    /// B4 (493.88 Hz)
446    pub const B4: f32 = 493.88;
447    /// B5 (987.77 Hz)
448    pub const B5: f32 = 987.77;
449    /// B6 (1975.53 Hz)
450    pub const B6: f32 = 1975.53;
451    /// B7 (3951.07 Hz)
452    pub const B7: f32 = 3951.07;
453    /// B8 (7902.13 Hz)
454    pub const B8: f32 = 7902.13;
455
456    /// C1 (32.70 Hz)
457    pub const C1: f32 = 32.70;
458    /// C2 (65.41 Hz)
459    pub const C2: f32 = 65.41;
460    /// C3 (130.81 Hz)
461    pub const C3: f32 = 130.81;
462    /// C4 (261.63 Hz)
463    pub const C4: f32 = 261.63;
464    /// C5 (523.25 Hz)
465    pub const C5: f32 = 523.25;
466    /// C6 (1046.50 Hz)
467    pub const C6: f32 = 1046.50;
468    /// C7 (2093.00 Hz)
469    pub const C7: f32 = 2093.00;
470    /// C8 (4186.01 Hz)
471    pub const C8: f32 = 4186.01;
472
473    /// C#1 / Db1 (34.65 Hz)
474    pub const C_SHARP1: f32 = 34.65;
475    /// C#2 / Db2 (69.30 Hz)
476    pub const C_SHARP2: f32 = 69.30;
477    /// C#3 / Db3 (138.59 Hz)
478    pub const C_SHARP3: f32 = 138.59;
479    /// C#4 / Db4 (277.18 Hz)
480    pub const C_SHARP4: f32 = 277.18;
481    /// C#5 / Db5 (554.37 Hz)
482    pub const C_SHARP5: f32 = 554.37;
483    /// C#6 / Db6 (1108.73 Hz)
484    pub const C_SHARP6: f32 = 1108.73;
485    /// C#7 / Db7 (2217.46 Hz)
486    pub const C_SHARP7: f32 = 2217.46;
487    /// C#8 / Db8 (4434.92 Hz)
488    pub const C_SHARP8: f32 = 4434.92;
489
490    /// D1 (36.71 Hz)
491    pub const D1: f32 = 36.71;
492    /// D2 (73.42 Hz)
493    pub const D2: f32 = 73.42;
494    /// D3 (146.83 Hz)
495    pub const D3: f32 = 146.83;
496    /// D4 (293.66 Hz)
497    pub const D4: f32 = 293.66;
498    /// D5 (587.33 Hz)
499    pub const D5: f32 = 587.33;
500    /// D6 (1174.66 Hz)
501    pub const D6: f32 = 1174.66;
502    /// D7 (2349.32 Hz)
503    pub const D7: f32 = 2349.32;
504    /// D8 (4698.63 Hz)
505    pub const D8: f32 = 4698.63;
506
507    /// D#1 / Eb1 (38.89 Hz)
508    pub const D_SHARP1: f32 = 38.89;
509    /// D#2 / Eb2 (77.78 Hz)
510    pub const D_SHARP2: f32 = 77.78;
511    /// D#3 / Eb3 (155.56 Hz)
512    pub const D_SHARP3: f32 = 155.56;
513    /// D#4 / Eb4 (311.13 Hz)
514    pub const D_SHARP4: f32 = 311.13;
515    /// D#5 / Eb5 (622.25 Hz)
516    pub const D_SHARP5: f32 = 622.25;
517    /// D#6 / Eb6 (1244.51 Hz)
518    pub const D_SHARP6: f32 = 1244.51;
519    /// D#7 / Eb7 (2489.02 Hz)
520    pub const D_SHARP7: f32 = 2489.02;
521    /// D#8 / Eb8 (4978.03 Hz)
522    pub const D_SHARP8: f32 = 4978.03;
523
524    /// E1 (41.20 Hz)
525    pub const E1: f32 = 41.20;
526    /// E2 (82.41 Hz)
527    pub const E2: f32 = 82.41;
528    /// E3 (164.81 Hz)
529    pub const E3: f32 = 164.81;
530    /// E4 (329.63 Hz)
531    pub const E4: f32 = 329.63;
532    /// E5 (659.25 Hz)
533    pub const E5: f32 = 659.25;
534    /// E6 (1318.51 Hz)
535    pub const E6: f32 = 1318.51;
536    /// E7 (2637.02 Hz)
537    pub const E7: f32 = 2637.02;
538    /// E8 (5274.04 Hz)
539    pub const E8: f32 = 5274.04;
540
541    /// F1 (43.65 Hz)
542    pub const F1: f32 = 43.65;
543    /// F2 (87.31 Hz)
544    pub const F2: f32 = 87.31;
545    /// F3 (174.61 Hz)
546    pub const F3: f32 = 174.61;
547    /// F4 (349.23 Hz)
548    pub const F4: f32 = 349.23;
549    /// F5 (698.46 Hz)
550    pub const F5: f32 = 698.46;
551    /// F6 (1396.91 Hz)
552    pub const F6: f32 = 1396.91;
553    /// F7 (2793.83 Hz)
554    pub const F7: f32 = 2793.83;
555    /// F8 (5587.65 Hz)
556    pub const F8: f32 = 5587.65;
557
558    /// F#1 / Gb1 (46.25 Hz)
559    pub const F_SHARP1: f32 = 46.25;
560    /// F#2 / Gb2 (92.50 Hz)
561    pub const F_SHARP2: f32 = 92.50;
562    /// F#3 / Gb3 (185.00 Hz)
563    pub const F_SHARP3: f32 = 185.00;
564    /// F#4 / Gb4 (369.99 Hz)
565    pub const F_SHARP4: f32 = 369.99;
566    /// F#5 / Gb5 (739.99 Hz)
567    pub const F_SHARP5: f32 = 739.99;
568    /// F#6 / Gb6 (1479.98 Hz)
569    pub const F_SHARP6: f32 = 1479.98;
570    /// F#7 / Gb7 (2959.96 Hz)
571    pub const F_SHARP7: f32 = 2959.96;
572    /// F#8 / Gb8 (5919.91 Hz)
573    pub const F_SHARP8: f32 = 5919.91;
574
575    /// G1 (49.00 Hz)
576    pub const G1: f32 = 49.00;
577    /// G2 (98.00 Hz)
578    pub const G2: f32 = 98.00;
579    /// G3 (196.00 Hz)
580    pub const G3: f32 = 196.00;
581    /// G4 (392.00 Hz)
582    pub const G4: f32 = 392.00;
583    /// G5 (783.99 Hz)
584    pub const G5: f32 = 783.99;
585    /// G6 (1567.98 Hz)
586    pub const G6: f32 = 1567.98;
587    /// G7 (3135.96 Hz)
588    pub const G7: f32 = 3135.96;
589    /// G8 (6271.93 Hz)
590    pub const G8: f32 = 6271.93;
591
592    /// G#1 / Ab1 (51.91 Hz)
593    pub const G_SHARP1: f32 = 51.91;
594    /// G#2 / Ab2 (103.83 Hz)
595    pub const G_SHARP2: f32 = 103.83;
596    /// G#3 / Ab3 (207.65 Hz)
597    pub const G_SHARP3: f32 = 207.65;
598    /// G#4 / Ab4 (415.30 Hz)
599    pub const G_SHARP4: f32 = 415.30;
600    /// G#5 / Ab5 (830.61 Hz)
601    pub const G_SHARP5: f32 = 830.61;
602    /// G#6 / Ab6 (1661.22 Hz)
603    pub const G_SHARP6: f32 = 1661.22;
604    /// G#7 / Ab7 (3322.44 Hz)
605    pub const G_SHARP7: f32 = 3322.44;
606    /// G#8 / Ab8 (6644.88 Hz)
607    pub const G_SHARP8: f32 = 6644.88;
608}
609
610// endregion
611
612// region: Prelude
613
614/// The `prelude` re-exports the most commonly used items in the engine,
615/// making it easy to get started without digging through individual modules.
616///
617/// Importing the prelude:
618/// ```
619/// use rusty_console_game_engine::prelude::*;
620/// ```
621///
622/// This brings in:
623/// - Core engine types ([`ConsoleGame`], [`ConsoleGameEngine`], and [`Sprite`])
624/// - Common colors and pixels for drawing
625/// - Frequently used keys and mouse buttons
626/// - A couple of note frequencies for quick audio testing
627///
628/// The goal is that a simple game can be written with only the prelude.
629pub 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// endregion
646
647// region: Console State
648
649#[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// endregion
725
726// region: Sprite
727
728/// A 2D sprite consisting of glyphs and color values.
729///
730/// Sprites can be drawn using `ConsoleGameEngine` methods like `draw_sprite` or
731/// `draw_partial_sprite`.
732#[derive(Debug, Clone, Default, PartialEq)]
733pub struct Sprite {
734    /// Width of the sprite in characters.
735    pub width: usize,
736    /// Height of the sprite in characters.
737    pub height: usize,
738    glyphs: Vec<u16>,
739    colors: Vec<u16>,
740}
741
742impl Sprite {
743    /// Creates a new sprite of the given width and height.
744    /// All glyphs are initialized to `PIXEL_EMPTY` and all colors to `FG_BLACK`.
745    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    /// Loads a sprite from a file (by convention ending in `.spr`).
755    /// The file must contain width and height (u32 little-endian) followed by colors and glyphs.
756    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    /// Saves the sprite to a `.spr` file in the same format as `from_file`.
799    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    /// Sets the glyph at `(x, y)` to `c`.
815    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    /// Sets the color at `(x, y)` to `c`.
822    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    /// Returns the glyph at `(x, y)`, or `PIXEL_EMPTY` if out of bounds.
829    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    /// Returns the color at `(x, y)`, or `FG_BLACK` if out of bounds.
838    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    /// Samples the glyph at normalized coordinates `(x, y)` in `[0.0, 1.0)` space.
857    /// Wrapping occurs for coordinates outside the [0,1) range.
858    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    /// Samples the color at normalized coordinates `(x, y)` in `[0.0, 1.0)` space.
864    /// Wrapping occurs for coordinates outside the [0,1) range.
865    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
871// endregion
872
873// region: Audio
874
875const 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/// Audio engine used through  the `ConsoleGameEngine`.
903///
904/// Handles asynchronous playback of WAV files and synthesized notes.
905///
906/// Users can interact with it via the audio field in the `ConsoleGameEngine`:
907///
908/// ```rust
909/// engine.audio.load_sample("explosion.wav");
910/// engine.audio.play_sample("explosion.wav");
911/// engine.audio.play_note(A4, 500);
912/// engine.audio.play_notes(&[A4, C_SHARP5, E5], 1000);
913/// engine.audio.note_on(A4);
914/// engine.audio.note_off(A4);
915/// ```
916#[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    /// Loads a WAV file asynchronously.
1088    ///
1089    /// The sample can later be played using `play_sample`.
1090    /// The path is used as the key to identify the sample.
1091    /// Normally used in the `create` function when implementing the `ConsoleGame` trait.
1092    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    /// Plays a previously loaded sample asynchronously.
1099    ///
1100    /// Multiple instances of the same sample can play simultaneously.
1101    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    /// Generates and plays a single note of the given frequency (Hz) and duration (ms).
1108    ///
1109    /// Useful for procedural audio or simple effects.
1110    /// Normally used in conjunction with the note constants (A4, C_SHARP5, E5)
1111    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    /// Generates and plays multiple notes simultaneously (like a chord).
1143    ///
1144    /// Each frequency in `freqs` is mixed together, scaled to avoid clipping,
1145    /// and played for the given duration (ms).
1146    /// Normally used in conjunction with the note constants (A4, C_SHARP5, E5)
1147    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    /// Starts playing a note of the given frequency (Hz) immediately.
1189    ///
1190    /// Normally used in conjunction with the note constants (A4, C_SHARP5, E5)
1191    pub fn note_on(&self, freq: f32) {
1192        let _ = self.tx.send(AudioCommand::NoteOn(freq));
1193    }
1194
1195    /// Stops a previously started note of the given frequency (Hz).
1196    ///
1197    /// Normally used in conjunction with the note constants (A4, C_SHARP5, E5)
1198    /// and with `note_on` to control sustained notes.
1199    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
1271// endregion
1272
1273// region: Engine
1274
1275static 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
1284/// Trait that defines the behavior of a game to be run by the `ConsoleGameEngine`.
1285///
1286/// To create a game, define a struct containing your game state and implement this trait
1287/// for it. The engine will call the provided methods during the game loop.
1288pub trait ConsoleGame: Sized {
1289    /// Sets the name of the game or application.
1290    /// The default name is "Default"
1291    ///
1292    /// Override this method to set a custom title for your game.
1293    fn app_name(&self) -> &str {
1294        "Default"
1295    }
1296
1297    /// Called once when the game starts.
1298    ///
1299    /// Use this method to initialize your game state, load sprites, set variables, etc.
1300    ///
1301    /// # Parameters
1302    /// * `engine` - A mutable reference to the running `ConsoleGameEngine`. You can use
1303    ///   this to query the screen, input, or draw anything immediately if needed.
1304    ///
1305    /// # Returns
1306    /// Return `true` to continue running the game, or `false` to immediately exit.
1307    fn create(&mut self, engine: &mut ConsoleGameEngine<Self>) -> bool;
1308
1309    /// Called once per frame to update the game state and render.
1310    ///
1311    /// This is where the main game logic should live: moving objects, handling input,
1312    /// checking collisions, drawing, etc.
1313    ///
1314    /// # Parameters
1315    /// * `engine` - A mutable reference to the `ConsoleGameEngine`. Use it to draw shapes,
1316    ///   sprites, and query input.
1317    /// * `elapsed_time` - Time (in seconds) since the last frame. Useful for smooth movement
1318    ///   and animations.
1319    ///
1320    /// # Returns
1321    /// Return `true` to continue running the game, or `false` to exit.
1322    fn update(&mut self, engine: &mut ConsoleGameEngine<Self>, elapsed_time: f32) -> bool;
1323
1324    /// Called once when the game exits or the engine is shutting down.
1325    ///
1326    /// Use this method to clean up resources, save game state, or free memory.
1327    ///
1328    /// # Parameters
1329    /// * `engine` - A mutable reference to the `ConsoleGameEngine`.
1330    ///
1331    /// # Returns
1332    /// Return `true` to allow normal shutdown, or `false` to prevent shutdown (rarely used).
1333    ///
1334    /// # Default Implementation
1335    /// The default implementation does nothing and returns `true`.
1336    #[allow(unused_variables)]
1337    fn destroy(&mut self, engine: &mut ConsoleGameEngine<Self>) -> bool {
1338        true
1339    }
1340}
1341
1342/// The main engine that runs a game implementing `ConsoleGame`.
1343///
1344/// Handles console creation, input, rendering, and the main game loop.
1345#[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
1383// region: Core
1384
1385impl<G: ConsoleGame> ConsoleGameEngine<G> {
1386    /// Creates a new `ConsoleGameEngine` with the given game.
1387    ///
1388    /// # Parameters
1389    /// * `game` - The user-defined struct implementing `ConsoleGame`.
1390    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    /// Returns the width of the console in characters.
1438    pub fn screen_width(&self) -> i32 {
1439        self.screen_width as i32
1440    }
1441
1442    /// Returns the height of the console in characters.
1443    pub fn screen_height(&self) -> i32 {
1444        self.screen_height as i32
1445    }
1446
1447    /// Returns `true` if the specified key was pressed this frame.
1448    ///
1449    /// Normally used in conjection with key constants such as
1450    /// `K_W`, `K_0`, `K_UP`, etc.
1451    pub fn key_pressed(&self, key: usize) -> bool {
1452        self.key_pressed[key]
1453    }
1454
1455    /// Returns `true` if the specified key was released this frame.
1456    ///
1457    /// Normally used in conjection with key constants such as
1458    /// `K_W`, `K_0`, `K_UP`, etc.
1459    pub fn key_released(&self, key: usize) -> bool {
1460        self.key_released[key]
1461    }
1462
1463    /// Returns `true` if the specified key is currently held down.
1464    ///
1465    /// Normally used in conjection with key constants such as
1466    /// `K_W`, `K_0`, `K_UP`, etc.
1467    pub fn key_held(&self, key: usize) -> bool {
1468        self.key_held[key]
1469    }
1470
1471    /// Returns `true` if the specified mouse button was pressed this frame.
1472    ///
1473    /// Normally used in conjection with mouse button constants
1474    /// such as `M_LEFT`, `M_MIDDLE`, `M_RIGHT`, etc.
1475    pub fn mouse_pressed(&self, button: usize) -> bool {
1476        self.mouse_pressed[button]
1477    }
1478
1479    /// Returns `true` if the specified mouse button was released this frame.
1480    ///
1481    /// Normally used in conjection with mouse button constants
1482    /// such as `M_LEFT`, `M_MIDDLE`, `M_RIGHT`, etc.
1483    pub fn mouse_released(&self, button: usize) -> bool {
1484        self.mouse_released[button]
1485    }
1486
1487    /// Returns `true` if the specified mouse button is currently held down.
1488    ///
1489    /// Normally used in conjection with mouse button constants
1490    /// such as `M_LEFT`, `M_MIDDLE`, `M_RIGHT`, etc.
1491    pub fn mouse_held(&self, button: usize) -> bool {
1492        self.mouse_held[button]
1493    }
1494
1495    /// Returns the current X position of the mouse in console coordinates.
1496    pub fn mouse_x(&self) -> i32 {
1497        self.mouse_x
1498    }
1499
1500    /// Returns the current Y position of the mouse in console coordinates.
1501    pub fn mouse_y(&self) -> i32 {
1502        self.mouse_y
1503    }
1504
1505    /// Returns the current (X, Y) position of the mouse.
1506    pub fn mouse_pos(&self) -> (i32, i32) {
1507        (self.mouse_x, self.mouse_y)
1508    }
1509
1510    /// Returns `true` if the console currently has focus.
1511    pub fn console_focused(&self) -> bool {
1512        self.console_in_focus
1513    }
1514
1515    /// Initializes the console with the given dimensions and font size.
1516    ///
1517    /// This function sets up the console window, screen buffer, font, and other
1518    /// properties. It now returns a `Result` to indicate success or failure.
1519    ///
1520    /// # Parameters
1521    /// - `width` - Console width in characters.
1522    /// - `height` - Console height in characters.
1523    /// - `fontw` - Font width in pixels.
1524    /// - `fonth` - Font height in pixels.
1525    ///
1526    /// # Errors
1527    /// Returns an error if:
1528    /// - The console handle is invalid.
1529    /// - The requested console size exceeds the maximum allowed for the current display/font.
1530    /// - Any Windows API call fails (setting buffer size, window info, font, etc.)
1531    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    /// Starts the game loop and runs the game until it exits.
1690    ///
1691    /// Calls `create()`, `update()`, and `destroy()` on the user's game struct.
1692    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
1763// endregion
1764
1765// region: Win API Wrappers
1766
1767impl<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
1920// endregion
1921
1922// region: Drawing
1923
1924use color::*;
1925use pixel::*;
1926
1927impl<G: ConsoleGame> ConsoleGameEngine<G> {
1928    /// Clamps `x` and `y` to be within the screen boundaries.
1929    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    /// Draws a single white pixel at `(x, y)`.
1945    pub fn draw(&mut self, x: i32, y: i32) {
1946        self.draw_with(x, y, SOLID, FG_WHITE);
1947    }
1948
1949    /// Draws a single pixel at `(x, y)` with the specified glyph and color.
1950    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    /// Clears the entire screen with the given color.
1959    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    /// Draws a string of white text starting at `(x, y)`.
1964    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    /// Draws a string starting at `(x, y)` with the specified color.
1969    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    /// Draws a string at `(x, y)` ignoring spaces (transparent spaces).
1978    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    /// Draws a string at `(x, y)` ignoring spaces (transparent spaces), using the specified color.
1983    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    /// Draws a white line from `(x1, y1)` to `(x2, y2)`.
1994    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    /// Draws a line from `(x1, y1)` to `(x2, y2)` with the specified glyph and color.
1999    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    /// Draws a white triangle connecting three points.
2047    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    /// Draws a triangle connecting three points with the specified glyph and color.
2052    #[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    /// Fills a triangle connecting three points with white pixels.
2070    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    /// Fills a triangle connecting three points with the specified glyph and color.
2075    #[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    /// Draws a white rectangle at `(x, y)` with width `w` and height `h`.
2169    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    /// Draws a rectangle at `(x, y)` with width `w` and height `h` using the specified glyph and color.
2174    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    /// Fills a rectangle from `(x1, y1)` to `(x2, y2)` with white pixels.
2186    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    /// Fills a rectangle from `(x1, y1)` to `(x2, y2)` with the specified glyph and color.
2191    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    /// Draws a white circle centered at `(xc, yc)` with radius `r`.
2211    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    /// Draws a circle centered at `(xc, yc)` with radius `r` using the specified glyph and color.
2216    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    /// Fills a circle centered at `(xc, yc)` with white pixels and radius `r`.
2245    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    /// Fills a circle centered at `(xc, yc)` with radius `r` using the specified glyph and color.
2250    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    /// Draws a 2D wireframe model at a given position, rotation, and scale.
2281    ///
2282    /// # Parameters
2283    /// - `model_coords`: A slice of `(x, y)` coordinates representing the vertices of the model.
2284    /// - `x`, `y`: The position on the screen to draw the model (translation applied to all vertices).
2285    /// - `r`: Rotation in radians, applied around the origin of the model coordinates.
2286    /// - `s`: Scale factor applied to the model.
2287    /// - `col`: Color used to draw the lines.
2288    /// - `c`:  glyph used to draw the lines
2289    #[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    /// Draws a filled 2D model at a given position, rotation, and scale.
2333    /// Works for concave and convex polygons (even-odd fill rule).
2334    ///
2335    /// # Parameters
2336    /// - `model_coords`: A slice of `(x, y)` coordinates representing the vertices of the model.
2337    /// - `x`, `y`: The position on the screen to draw the model (translation applied to all vertices).
2338    /// - `r`: Rotation in radians, applied around the origin of the model coordinates.
2339    /// - `s`: Scale factor applied to the model.
2340    /// - `col`: Color used to draw the filled pixels.
2341    /// - `c`: Glyph used to draw the filled pixels.
2342    #[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    /// Draws a sprite at position `(x, y)`.
2429    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    /// Draws a portion of a sprite at position `(x, y)` on the screen.
2442    ///
2443    /// # Parameters
2444    /// - `x`, `y`: The top-left coordinates on the screen where the sprite portion will be drawn.
2445    /// - `sprite`: The `Sprite` to draw.
2446    /// - `ox`, `oy`: The top-left coordinates inside the sprite to start copying from (offset within the sprite).
2447    /// - `w`, `h`: The width and height of the portion to draw (how much of the sprite to copy).
2448    #[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// endregion
2472
2473// endregion