1use std::time::{Duration, Instant};
21
22use crate::palette::Rgb;
23
24static FONT8: &[u8; 2048] = include_bytes!("../assets/vga_8x8.bin");
25static FONT16: &[u8; 4096] = include_bytes!("../assets/vga_8x16.bin");
26static FONT6: &[u8; 1536] = include_bytes!("../assets/font6x6.bin");
28
29pub const MAX_WIDTH: usize = 640;
30pub const MAX_HEIGHT: usize = 400;
31pub const COLS: usize = 80;
33pub const ROWS: usize = 25;
34const VERSION: &[u8] = b"2.19";
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub enum Res {
39 Low,
40 Medium,
41 High,
42}
43
44impl Res {
45 pub fn size(self) -> (usize, usize) {
46 match self {
47 Res::Low => (320, 200),
48 Res::Medium => (640, 200),
49 Res::High => (640, 400),
50 }
51 }
52
53 fn colors(self) -> u8 {
54 match self {
55 Res::Low => 16,
56 Res::Medium => 4,
57 Res::High => 2,
58 }
59 }
60
61 fn cell_h(self) -> usize {
63 if self == Res::High { 16 } else { 8 }
64 }
65
66 fn register(self, pen: u8) -> u8 {
68 const LOW: [u8; 16] = [0, 15, 1, 2, 4, 6, 3, 5, 7, 8, 9, 10, 12, 14, 11, 13];
69 match self {
70 Res::Low => LOW[(pen & 15) as usize],
71 Res::Medium => [0, 3, 1, 2].get(pen as usize).copied().unwrap_or(3),
72 Res::High => (pen != 0) as u8,
73 }
74 }
75
76 fn aspect(self) -> f64 {
78 match self {
79 Res::Low => 338.0 / 372.0,
80 Res::Medium => 169.0 / 372.0,
81 Res::High => 1.0,
82 }
83 }
84
85 fn number(self) -> u8 {
86 match self {
87 Res::Low => 0,
88 Res::Medium => 1,
89 Res::High => 2,
90 }
91 }
92}
93
94type StColor = u16;
96
97const DESKTOP: [StColor; 16] =
99 [0x777, 0x700, 0x070, 0x770, 0x007, 0x707, 0x077, 0x555, 0x333, 0x733, 0x373, 0x773, 0x337, 0x737, 0x377, 0x000];
100const DESKTOP_MEDIUM: [StColor; 4] = [0x777, 0x700, 0x070, 0x000];
101const IG_PALETTE: [StColor; 16] =
103 [0x777, 0x700, 0x070, 0x000, 0x007, 0x770, 0x077, 0x546, 0x532, 0x743, 0x043, 0x030, 0x333, 0x210, 0x134, 0x000];
104
105fn rgb(c: StColor) -> Rgb {
106 let v = |n: u16| ((n & 7) as u32 * 255 + 3) / 7;
107 v(c >> 8) << 16 | v(c >> 4) << 8 | v(c)
108}
109
110const ROM_UD_PATTERN: [u16; 16] = [
112 0x07E0, 0x0FF0, 0x1FD8, 0x1808, 0x1808, 0x1008, 0x1E78, 0x1348, 0x1108, 0x0810, 0x0B70, 0x0650, 0x07A0, 0x1E20,
113 0x1BC0, 0x1800,
114];
115const DITHER: [u16; 32] = [
116 0x0000, 0x4444, 0x0000, 0x1111, 0x0000, 0x5555, 0x0000, 0x5555, 0x8888, 0x5555, 0x2222, 0x5555, 0xAAAA, 0x5555,
117 0xAAAA, 0x5555, 0xAAAA, 0xDDDD, 0xAAAA, 0x7777, 0xAAAA, 0xFFFF, 0xAAAA, 0xFFFF, 0xEEEE, 0xFFFF, 0xBBBB, 0xFFFF,
118 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
119];
120const OEMPAT: [u16; 128] = [
121 0xFFFF, 0x8080, 0x8080, 0x8080, 0xFFFF, 0x0808, 0x0808, 0x0808, 0x2020, 0x4040, 0x8080, 0x4141, 0x2222, 0x1414,
122 0x0808, 0x1010, 0x0000, 0x0000, 0x1010, 0x2828, 0x0000, 0x0000, 0x0101, 0x8282, 0x0202, 0x0202, 0xAAAA, 0x5050,
123 0x2020, 0x2020, 0xAAAA, 0x0505, 0x4040, 0x8080, 0x0000, 0x0808, 0x0404, 0x0202, 0x0000, 0x2020, 0x6606, 0xC6C6,
124 0xD8D8, 0x1818, 0x8181, 0x8DB1, 0x0C33, 0x6000, 0x0000, 0x0000, 0x0400, 0x0000, 0x0010, 0x0000, 0x8000, 0x0000,
125 0xF8F8, 0x6C6C, 0xC6C6, 0x8F8F, 0x1F1F, 0x3636, 0x6363, 0xF1F1, 0xAAAA, 0x0000, 0x8888, 0x1414, 0x2222, 0x4141,
126 0x8888, 0x0000, 0x0808, 0x0000, 0xAAAA, 0x0000, 0x0808, 0x0000, 0x8888, 0x0000, 0x7777, 0x9898, 0xF8F8, 0xF8F8,
127 0x7777, 0x8989, 0x8F8F, 0x8F8F, 0x8080, 0x8080, 0x4141, 0x3E3E, 0x0808, 0x0808, 0x1414, 0xE3E3, 0x8181, 0x4242,
128 0x2424, 0x1818, 0x0606, 0x0101, 0x8080, 0x8080, 0xF0F0, 0xF0F0, 0xF0F0, 0xF0F0, 0x0F0F, 0x0F0F, 0x0F0F, 0x0F0F,
129 0x0808, 0x1C1C, 0x3E3E, 0x7F7F, 0xFFFF, 0x7F7F, 0x3E3E, 0x1C1C, 0x1111, 0x2222, 0x4444, 0xFFFF, 0x8888, 0x4444,
130 0x2222, 0xFFFF,
131];
132const HATCH0: [u16; 48] = [
133 0x0101, 0x0202, 0x0404, 0x0808, 0x1010, 0x2020, 0x4040, 0x8080, 0x6060, 0xC0C0, 0x8181, 0x0303, 0x0606, 0x0C0C,
134 0x1818, 0x3030, 0x4242, 0x8181, 0x8181, 0x4242, 0x2424, 0x1818, 0x1818, 0x2424, 0x8080, 0x8080, 0x8080, 0x8080,
135 0x8080, 0x8080, 0x8080, 0x8080, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0x8080,
136 0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080,
137];
138const HATCH1: [u16; 96] = [
139 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000,
140 0x4000, 0x8000, 0x8003, 0x0007, 0x000E, 0x001C, 0x0038, 0x0070, 0x00E0, 0x01C0, 0x0380, 0x0700, 0x0E00, 0x1C00,
141 0x3800, 0x7000, 0xE000, 0xC001, 0x8001, 0x4002, 0x2004, 0x1008, 0x0810, 0x0420, 0x0240, 0x0180, 0x0180, 0x0240,
142 0x0420, 0x0810, 0x1008, 0x2004, 0x4002, 0x8001, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000,
143 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
144 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0x8080, 0x8080, 0x8080,
145 0x8080, 0x8080, 0x8080, 0x8080, 0xFFFF, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080,
146];
147const LINE_STYLES: [u16; 6] = [0xFFFF, 0xFFF0, 0xC0C0, 0xFF18, 0xFF00, 0xF191];
148const MARKERS: [&[&[(i32, i32)]]; 6] = [
151 &[&[(0, 0), (0, 0)]],
152 &[&[(0, -3), (0, 3)], &[(-4, 0), (4, 0)]],
153 &[&[(0, -3), (0, 3)], &[(3, 2), (-3, -2)], &[(3, -2), (-3, 2)]],
154 &[&[(-4, -3), (4, -3), (4, 3), (-4, 3), (-4, -3)]],
155 &[&[(-4, -3), (4, 3)], &[(-4, 3), (4, -3)]],
156 &[&[(-4, 0), (0, -3), (4, 0), (0, 3), (-4, 0)]],
157];
158
159#[derive(Clone, Copy, Debug, PartialEq, Eq)]
161enum Tok {
162 Num(i32),
163 X,
165 Y,
167 Rand,
169 RandBig,
170 Plus(i32),
172 Minus(i32),
173 Bang(i32),
174}
175
176#[derive(Debug)]
178struct Incomplete;
179
180type Res2<T> = Result<T, Incomplete>;
181
182struct Reader<'a> {
183 data: &'a [u8],
184 pos: usize,
185}
186
187impl Reader<'_> {
188 fn next(&mut self) -> Res2<u8> {
189 let b = *self.data.get(self.pos).ok_or(Incomplete)?;
190 self.pos += 1;
191 Ok(b)
192 }
193
194 fn token(&mut self) -> Res2<Tok> {
196 let (mut num, mut digits) = (0i32, 0);
197 let mut sign: Option<u8> = None;
198 loop {
199 match self.next()? {
200 d @ b'0'..=b'9' => {
201 num = num * 10 + (d - b'0') as i32;
202 digits += 1;
203 }
204 b'\r' | b'\n' | b'_' => {}
207 c @ (b'x' | b'y' | b'r' | b'R') if digits == 0 => {
208 self.next()?;
209 return Ok(match c {
210 b'x' => Tok::X,
211 b'y' => Tok::Y,
212 b'r' => Tok::Rand,
213 _ => Tok::RandBig,
214 });
215 }
216 c @ (b'+' | b'-' | b'!') if digits == 0 && sign.is_none() => sign = Some(c),
217 _ => break,
218 }
219 }
220 if digits > 4 {
221 num = 0;
222 }
223 Ok(match sign {
224 Some(b'+') => Tok::Plus(num),
225 Some(b'-') => Tok::Minus(num),
226 Some(_) => Tok::Bang(num),
227 None => Tok::Num(num),
228 })
229 }
230
231 fn text(&mut self) -> Res2<Vec<u8>> {
233 let mut out = Vec::new();
234 loop {
235 match self.next()? {
236 b'@' => return Ok(out),
237 b'\r' | b'\n' => {}
238 b if out.len() < 128 => out.push(b),
239 _ => return Ok(out),
240 }
241 }
242 }
243}
244
245struct Loop {
247 params: Vec<Tok>,
248 at: usize,
249 value: i32,
251 to: i32,
252 step: i32,
253 from: i32,
254 kind: u8,
256 x_count: i32,
257 first: i32,
258 second: i32,
259 was: i32,
260 plain: bool,
262 running: bool,
263}
264
265impl Loop {
266 fn next(&mut self) -> Tok {
267 if self.at == 0 {
268 self.x_count = 0;
269 }
270 let t = self.params[self.at];
271 let relative = !matches!(t, Tok::Num(_) | Tok::Rand | Tok::RandBig);
272 let mut out = t;
273 if relative {
274 self.x_count += 1;
275 if self.second == 0 {
276 self.was = self.value;
277 }
278 if self.x_count < 2 && self.first != 0 {
279 self.value += self.step;
280 self.second += 1;
281 if self.second == 1 && self.kind == b'|' {
282 self.value = self.was;
283 }
284 if self.second == 2 && self.kind == b'|' {
285 self.second = 0;
286 }
287 }
288 self.first += 1;
289 out = Tok::Num(match t {
290 Tok::X => self.value,
291 Tok::Y => match self.from.cmp(&self.to) {
292 std::cmp::Ordering::Less => self.to - self.value,
293 std::cmp::Ordering::Greater => self.from - self.value,
294 std::cmp::Ordering::Equal => 0,
295 },
296 Tok::Plus(n) => n + self.value,
297 Tok::Minus(n) => self.value - n,
298 Tok::Bang(n) => n - self.value,
299 t => return t,
300 });
301 }
302 if self.plain && self.at == 0 {
303 self.value += self.step;
304 }
305 let last = self.at == self.params.len() - 1;
306 if last && (self.step >= 1 && self.value >= self.to || self.step < 0 && self.value <= self.to) {
307 self.running = false;
308 }
309 self.at = (self.at + 1) % self.params.len();
310 out
311 }
312}
313
314#[derive(Clone, Copy)]
315struct LineAttr {
316 pen: u8,
317 style: u8,
318 width: i32,
319 ends: (u8, u8),
321}
322
323#[derive(Clone, Copy)]
324struct FillAttr {
325 pen: u8,
326 interior: u8,
328 style: u8,
329 perimeter: bool,
330}
331
332#[derive(Clone, Copy)]
333struct TextAttr {
334 pen: u8,
335 effects: u8,
336 points: i32,
337 rotation: u8,
339}
340
341#[derive(Clone, Copy)]
342struct Attrs {
343 line: LineAttr,
344 fill: FillAttr,
345 text: TextAttr,
346 marker: (u8, u8, i32),
347 mode: u8,
349}
350
351impl Default for Attrs {
352 fn default() -> Attrs {
353 Attrs {
354 line: LineAttr { pen: 1, style: 1, width: 1, ends: (0, 0) },
355 fill: FillAttr { pen: 1, interior: 1, style: 1, perimeter: true },
356 text: TextAttr { pen: 1, effects: 0, points: 9, rotation: 0 },
357 marker: (1, 1, 1),
358 mode: 1,
359 }
360 }
361}
362
363#[derive(Clone, Copy, Debug)]
365struct Cycle {
366 from: u8,
367 to: u8,
368 steps: u32,
369 step: Duration,
370}
371
372type Zone = (i32, i32, i32, i32, Vec<u8>);
374
375#[derive(Clone, Copy, Debug, PartialEq, Eq)]
377enum Esc {
378 None,
379 Start,
380 Row,
381 Col(u8),
382 Fg,
383 Bg,
384}
385
386#[derive(Clone, Copy, Debug, PartialEq, Eq)]
387enum Mode {
388 Text,
389 Command,
391}
392
393pub struct Igs {
394 pub res: Res,
395 pixels: Vec<u8>,
396 memory: Vec<u8>,
398 memory_rect: (i32, i32, i32, i32),
399 regs: [StColor; 16],
400 attrs: Attrs,
401 user_patterns: [[u16; 16]; 8],
402 pen_at: (i32, i32),
404 hollow: bool,
405 scaling: i32,
406 rand: (i32, i32),
407 rand_big: (i32, i32),
408 seed: u32,
409 col: usize,
411 row: usize,
412 fg: u8,
413 bg: u8,
414 reverse: bool,
415 wrap: bool,
416 pub cursor_on: bool,
417 destructive_bs: bool,
418 saved: (usize, usize),
419 esc: Esc,
420 mode: Mode,
422 g: bool,
424 buf: Vec<u8>,
425 stored: Vec<u8>,
427 run_stored: Option<Vec<u8>>,
428 pub hold: Option<Instant>,
430 cycle: Option<Cycle>,
432 kept_palette: [StColor; 16],
434 looping: Option<Loop>,
435 last_text: Vec<u8>,
436 zones: Vec<Option<Zone>>,
438 awaiting_zone: Option<(bool, bool)>,
440 pub replies: Vec<u8>,
442 pub bells: usize,
443 effects: Box<[[i16; 56]; 20]>,
446 effect_loops: u32,
447 midi: Vec<u8>,
449 chip: Vec<u8>,
450 next_midi: usize,
452 pub audio: Vec<crate::audio::AudioOp>,
454}
455
456const EFFECT_CHANNEL: usize = 2;
458const VOICE_CHANNELS: usize = 3;
459const CHIP_CHANNEL: usize = 6;
460const MIDI_CHANNELS: std::ops::Range<usize> = 7..13;
461
462impl Default for Igs {
463 fn default() -> Igs {
464 let mut igs = Igs {
465 res: Res::Medium,
466 pixels: Vec::new(),
467 memory: Vec::new(),
468 memory_rect: (0, 0, 0, 0),
469 regs: DESKTOP,
470 attrs: Attrs::default(),
471 user_patterns: [ROM_UD_PATTERN; 8],
472 pen_at: (0, 0),
473 hollow: false,
474 scaling: 0,
475 rand: (0, 199),
476 rand_big: (0, 199),
477 seed: 0x1234_5678,
478 col: 0,
479 row: 0,
480 fg: 3,
481 bg: 0,
482 reverse: false,
483 wrap: true,
484 cursor_on: true,
485 destructive_bs: false,
486 saved: (0, 0),
487 esc: Esc::None,
488 mode: Mode::Text,
489 g: false,
490 buf: Vec::new(),
491 stored: Vec::new(),
492 run_stored: None,
493 hold: None,
494 cycle: None,
495 kept_palette: DESKTOP,
496 looping: None,
497 last_text: Vec::new(),
498 zones: vec![None; 48],
499 awaiting_zone: None,
500 replies: Vec::new(),
501 bells: 0,
502 effects: Box::new(crate::igs_sound::EFFECTS),
503 effect_loops: 5,
504 midi: Vec::new(),
505 chip: Vec::new(),
506 next_midi: 0,
507 audio: Vec::new(),
508 };
509 igs.set_res(Res::Medium);
510 igs.desktop_palette();
511 igs
512 }
513}
514
515impl Igs {
516 pub fn width(&self) -> usize {
517 self.res.size().0
518 }
519
520 pub fn height(&self) -> usize {
521 self.res.size().1
522 }
523
524 pub fn pixels(&self) -> &[u8] {
526 &self.pixels
527 }
528
529 pub fn palette(&self) -> [Rgb; 16] {
530 std::array::from_fn(|i| rgb(self.regs[i]))
531 }
532
533 pub fn cursor(&self) -> (usize, usize) {
535 match self.res {
536 Res::Low => (self.col * 2, self.row),
537 Res::Medium => (self.col, self.row),
538 Res::High => (self.col, self.row / 2),
539 }
540 }
541
542 fn text_size(&self) -> (usize, usize) {
543 (self.width() / 8, self.height() / self.res.cell_h())
544 }
545
546 fn set_res(&mut self, res: Res) {
547 self.res = res;
548 let (w, h) = res.size();
549 self.pixels = vec![0; w * h];
550 self.memory = vec![0; w * h];
551 let (cols, rows) = self.text_size();
552 self.col = self.col.min(cols - 1);
553 self.row = self.row.min(rows - 1);
554 let mask = res.colors() - 1;
555 self.fg &= mask;
556 self.bg &= mask;
557 if res == Res::Medium {
558 self.fg = 3;
559 }
560 }
561
562 fn desktop_palette(&mut self) {
563 self.regs = DESKTOP;
564 match self.res {
565 Res::Medium => self.regs[..4].copy_from_slice(&DESKTOP_MEDIUM),
566 Res::High => self.regs[..2].copy_from_slice(&[0x777, 0x000]),
567 Res::Low => {}
568 }
569 }
570
571 fn random(&mut self, (lo, hi): (i32, i32)) -> i32 {
572 self.seed ^= self.seed << 13;
573 self.seed ^= self.seed >> 17;
574 self.seed ^= self.seed << 5;
575 lo + (self.seed % ((hi - lo).max(0) as u32 + 1)) as i32
576 }
577
578 pub fn feed(&mut self, data: &[u8]) {
580 self.buf.extend_from_slice(data);
581 let mut buf = std::mem::take(&mut self.buf);
582 let mut pos = 0;
583 while pos < buf.len() && self.hold.is_none() {
584 match self.mode {
585 Mode::Text => {
586 let b = buf[pos];
587 pos += 1;
588 self.text_byte(b);
589 }
590 Mode::Command => {
591 let mut r = Reader { data: &buf, pos };
592 match self.command(&mut r) {
593 Ok(chain) => {
594 pos = r.pos;
595 self.mode = if chain { Mode::Command } else { Mode::Text };
596 if let Some(run) = self.run_stored.take() {
598 buf.splice(pos..pos, run);
599 }
600 }
601 Err(Incomplete) => {
602 self.looping = None;
603 break;
604 }
605 }
606 }
607 }
608 }
609 buf.drain(..pos);
610 if buf.len() > 1 << 20 {
612 buf.clear();
613 self.mode = Mode::Text;
614 }
615 self.buf = buf;
616 }
617
618 pub fn poll(&mut self, now: Instant) {
621 if self.hold.is_some_and(|t| now >= t) {
622 self.hold = None;
623 if self.cycle.is_some() {
624 self.cycle_step();
625 if self.hold.is_some() {
626 return;
627 }
628 }
629 self.feed(&[]);
630 }
631 }
632
633 pub fn interrupt(&mut self) {
636 if self.hold.take().is_some() {
637 self.cycle = None;
638 self.feed(&[]);
639 }
640 }
641
642 fn cycle_step(&mut self) {
645 let Some(c) = self.cycle.as_mut() else { return };
646 let (lo, hi) = (c.from.min(c.to), c.from.max(c.to));
647 let regs: Vec<usize> = (lo..=hi).map(|pen| self.res.register(pen) as usize).collect();
648 let mut colors: Vec<StColor> = regs.iter().map(|&r| self.regs[r]).collect();
649 if c.from < c.to {
650 colors.rotate_right(1);
651 } else {
652 colors.rotate_left(1);
653 }
654 for (&r, &col) in regs.iter().zip(&colors) {
655 self.regs[r] = col;
656 }
657 c.steps -= 1;
658 if c.steps == 0 {
659 self.cycle = None;
660 } else {
661 self.hold = Some(Instant::now() + c.step);
662 }
663 }
664
665 fn text_byte(&mut self, b: u8) {
668 if self.g {
669 self.g = false;
670 if b == b'#' && self.esc == Esc::None {
671 self.mode = Mode::Command;
672 return;
673 }
674 self.vt52(b'G');
675 }
676 if b == b'G' && self.esc == Esc::None {
677 self.g = true;
678 return;
679 }
680 self.vt52(b);
681 }
682
683 fn vt52(&mut self, b: u8) {
684 let (cols, rows) = self.text_size();
685 match self.esc {
686 Esc::Start => {
687 self.esc = Esc::None;
688 self.vt52_escape(b);
689 return;
690 }
691 Esc::Row => {
692 self.esc = Esc::Col(b.saturating_sub(32));
693 return;
694 }
695 Esc::Col(r) => {
696 self.esc = Esc::None;
697 self.row = (r as usize).min(rows - 1);
698 self.col = (b.saturating_sub(32) as usize).min(cols - 1);
699 return;
700 }
701 Esc::Fg => {
702 self.esc = Esc::None;
703 self.fg = b & (self.res.colors() - 1);
704 return;
705 }
706 Esc::Bg => {
707 self.esc = Esc::None;
708 self.bg = b & (self.res.colors() - 1);
709 return;
710 }
711 Esc::None => {}
712 }
713 match b {
714 0x07 => self.bells += 1,
715 0x08 => {
716 self.col = self.col.saturating_sub(1);
717 if self.destructive_bs {
718 self.glyph_cell(b' ');
719 }
720 }
721 0x09 => self.col = ((self.col / 8 + 1) * 8).min(cols - 1),
722 0x0A..=0x0C => self.line_feed(),
723 0x0D => self.col = 0,
724 0x1B => self.esc = Esc::Start,
725 0x00..=0x1F => {}
726 _ => {
727 if self.col >= cols {
728 if !self.wrap {
729 self.col = cols - 1;
730 } else {
731 self.col = 0;
732 self.line_feed();
733 }
734 }
735 self.glyph_cell(b);
736 self.col += 1;
737 if self.col >= cols && !self.wrap {
738 self.col = cols - 1;
739 }
740 }
741 }
742 }
743
744 fn vt52_escape(&mut self, b: u8) {
745 let (cols, rows) = self.text_size();
746 match b {
747 b'A' => self.row = self.row.saturating_sub(1),
748 b'B' => self.row = (self.row + 1).min(rows - 1),
749 b'C' => self.col = (self.col + 1).min(cols - 1),
750 b'D' => self.col = self.col.saturating_sub(1),
751 b'E' => {
752 self.clear_text_rows(0, rows);
753 (self.col, self.row) = (0, 0);
754 }
755 b'H' => (self.col, self.row) = (0, 0),
756 b'I' => {
757 if self.row == 0 {
758 self.scroll_text(0, false);
759 } else {
760 self.row -= 1;
761 }
762 }
763 b'J' => {
764 self.clear_line_from(self.col);
765 self.clear_text_rows(self.row + 1, rows);
766 }
767 b'K' => self.clear_line_from(self.col),
768 b'L' => {
769 self.scroll_text(self.row, false);
770 self.col = 0;
771 }
772 b'M' => {
773 self.scroll_text(self.row, true);
774 self.col = 0;
775 }
776 b'Y' => self.esc = Esc::Row,
777 b'b' => self.esc = Esc::Fg,
778 b'c' => self.esc = Esc::Bg,
779 b'd' => {
780 self.clear_text_rows(0, self.row);
781 self.clear_line_to(self.col);
782 }
783 b'e' => self.cursor_on = true,
784 b'f' => self.cursor_on = false,
785 b'j' => self.saved = (self.col, self.row),
786 b'k' => (self.col, self.row) = self.saved,
787 b'l' => self.clear_text_rows(self.row, self.row + 1),
788 b'o' => self.clear_line_to(self.col),
789 b'p' => self.reverse = true,
790 b'q' => self.reverse = false,
791 b'v' => self.wrap = true,
792 b'w' => self.wrap = false,
793 _ => {}
794 }
795 }
796
797 fn line_feed(&mut self) {
798 let rows = self.text_size().1;
799 if self.row + 1 >= rows {
800 self.scroll_text(0, true);
801 } else {
802 self.row += 1;
803 }
804 }
805
806 fn scroll_text(&mut self, top: usize, up: bool) {
809 let (w, h) = self.res.size();
810 let ch = self.res.cell_h();
811 let (start, end) = (top * ch * w, h * w);
812 if start >= end {
813 return;
814 }
815 if up {
816 self.pixels.copy_within(start + ch * w..end, start);
817 self.pixels[end - ch * w..end].fill(self.bg);
818 } else {
819 self.pixels.copy_within(start..end - ch * w, start + ch * w);
820 self.pixels[start..start + ch * w].fill(self.bg);
821 }
822 }
823
824 fn clear_text_rows(&mut self, from: usize, to: usize) {
825 let w = self.width();
826 let ch = self.res.cell_h();
827 let (a, b) = ((from * ch * w).min(self.pixels.len()), (to * ch * w).min(self.pixels.len()));
828 if a < b {
829 self.pixels[a..b].fill(self.bg);
830 }
831 }
832
833 fn clear_cells(&mut self, row: usize, from: usize, to: usize) {
834 let w = self.width();
835 let ch = self.res.cell_h();
836 for y in row * ch..(row + 1) * ch {
837 let line = &mut self.pixels[y * w..(y + 1) * w];
838 line[(from * 8).min(w)..(to * 8).min(w)].fill(self.bg);
839 }
840 }
841
842 fn clear_line_from(&mut self, col: usize) {
843 let cols = self.text_size().0;
844 self.clear_cells(self.row, col, cols);
845 }
846
847 fn clear_line_to(&mut self, col: usize) {
848 self.clear_cells(self.row, 0, col + 1);
849 }
850
851 fn glyph_cell(&mut self, ch: u8) {
853 let w = self.width();
854 let h = self.res.cell_h();
855 let (fg, bg) = if self.reverse { (self.bg, self.fg) } else { (self.fg, self.bg) };
856 let (x0, y0) = (self.col * 8, self.row * h);
857 for gy in 0..h {
858 let bits = glyph_row(ch, gy, h);
859 let row = &mut self.pixels[(y0 + gy) * w + x0..][..8];
860 for (gx, p) in row.iter_mut().enumerate() {
861 *p = if bits & (0x80 >> gx) != 0 { fg } else { bg };
862 }
863 }
864 }
865
866 fn mask(&self) -> u8 {
869 self.res.colors() - 1
870 }
871
872 fn plot(&mut self, x: i32, y: i32, on: bool, color: u8, mode: u8) {
874 let (w, h) = self.res.size();
875 if x < 0 || y < 0 || x as usize >= w || y as usize >= h {
876 return;
877 }
878 let p = &mut self.pixels[y as usize * w + x as usize];
879 match mode {
880 2 if on => *p = color,
881 3 if on => *p ^= self.res.colors() - 1,
882 4 if !on => *p = color,
883 1 => *p = if on { color } else { 0 },
884 _ => {}
885 }
886 }
887
888 fn reg(&self, pen: u8) -> u8 {
889 self.res.register(pen)
890 }
891
892 fn pattern_bit(&self, x: i32, y: i32) -> bool {
894 let f = self.attrs.fill;
895 let fi = f.style.max(1) as usize - 1;
896 let word = match f.interior {
897 0 => return false,
898 1 => return true,
899 2 if fi < 8 => DITHER[fi * 4 + (y & 3) as usize],
900 2 => OEMPAT[((fi - 8).min(15)) * 8 + (y & 7) as usize],
901 3 if fi < 6 => HATCH0[fi * 8 + (y & 7) as usize],
902 3 => HATCH1[((fi - 6).min(5)) * 16 + (y & 15) as usize],
903 _ => self.user_patterns[fi.min(7)][(y & 15) as usize],
904 };
905 word & (0x8000 >> (x & 15)) != 0
906 }
907
908 fn fill_span(&mut self, x0: i32, x1: i32, y: i32) {
910 if self.attrs.fill.interior == 0 {
911 return;
912 }
913 let color = self.reg(self.attrs.fill.pen);
914 let mode = self.attrs.mode;
915 for x in x0.max(0)..=x1.min(self.width() as i32 - 1) {
916 let on = self.pattern_bit(x, y);
917 self.plot(x, y, on, color, mode);
918 }
919 }
920
921 fn polygon(&mut self, pts: &[(i32, i32)], perimeter: bool) {
923 if pts.is_empty() {
924 return;
925 }
926 let (ymin, ymax) = pts.iter().fold((i32::MAX, i32::MIN), |(a, b), p| (a.min(p.1), b.max(p.1)));
927 let h = self.height() as i32;
928 for y in ymin.max(0)..=ymax.min(h - 1) {
929 let mut xs: Vec<i32> = Vec::new();
930 for i in 0..pts.len() {
931 let (a, b) = (pts[i], pts[(i + 1) % pts.len()]);
932 let (lo, hi) = if a.1 < b.1 { (a, b) } else { (b, a) };
933 if y >= lo.1 && y < hi.1 || y == ymax && hi.1 == ymax && lo.1 != hi.1 {
934 let t = (y - lo.1) as f64 / (hi.1 - lo.1) as f64;
935 xs.push((lo.0 as f64 + t * (hi.0 - lo.0) as f64).round() as i32);
936 }
937 }
938 xs.sort_unstable();
939 for pair in xs.chunks(2) {
940 if let [a, b] = pair {
941 self.fill_span(*a, *b, y);
942 }
943 }
944 }
945 if perimeter {
946 let color = self.reg(self.attrs.fill.pen);
947 let mut closed = pts.to_vec();
948 closed.push(pts[0]);
949 for seg in closed.windows(2) {
950 self.thin_line(seg[0], seg[1], color, 0xFFFF, self.attrs.mode);
951 }
952 }
953 }
954
955 fn thin_line(&mut self, (x0, y0): (i32, i32), (x1, y1): (i32, i32), color: u8, style: u16, mode: u8) {
957 let (dx, dy) = ((x1 - x0).abs(), -(y1 - y0).abs());
958 let (sx, sy) = (if x0 < x1 { 1 } else { -1 }, if y0 < y1 { 1 } else { -1 });
959 let (mut x, mut y, mut err) = (x0, y0, dx + dy);
960 let mut bit = 0u32;
961 loop {
962 let on = style.rotate_left(bit) & 0x8000 != 0;
963 if on || mode == 1 || mode == 4 {
965 self.plot(x, y, on, color, mode);
966 }
967 bit += 1;
968 if x == x1 && y == y1 {
969 break;
970 }
971 let e2 = 2 * err;
972 if e2 >= dy {
973 err += dy;
974 x += sx;
975 }
976 if e2 <= dx {
977 err += dx;
978 y += sy;
979 }
980 }
981 }
982
983 fn line(&mut self, a: (i32, i32), b: (i32, i32)) {
986 let l = self.attrs.line;
987 let color = self.reg(l.pen);
988 let style = match l.style {
989 1..=6 => LINE_STYLES[l.style as usize - 1],
990 _ => {
991 let p = self.user_patterns[6];
992 p[0]
993 }
994 };
995 if l.width <= 1 {
996 self.thin_line(a, b, color, style, self.attrs.mode);
997 self.line_ends(a, b, color, 1);
998 return;
999 }
1000 let saved = self.attrs.fill;
1001 self.attrs.fill = FillAttr { pen: l.pen, interior: 1, style: 1, perimeter: false };
1002 let (dx, dy) = ((b.0 - a.0) as f64, (b.1 - a.1) as f64);
1003 let len = (dx * dx + dy * dy).sqrt().max(1.0);
1004 let half = l.width as f64 / 2.0;
1005 let (px, py) = (-dy / len * half, dx / len * half * self.res.aspect());
1007 let q = |p: (i32, i32), s: f64| ((p.0 as f64 + px * s).round() as i32, (p.1 as f64 + py * s).round() as i32);
1008 self.polygon(&[q(a, 1.0), q(b, 1.0), q(b, -1.0), q(a, -1.0)], false);
1009 self.line_ends(a, b, color, l.width);
1010 self.attrs.fill = saved;
1011 }
1012
1013 fn line_ends(&mut self, a: (i32, i32), b: (i32, i32), color: u8, width: i32) {
1014 let ends = self.attrs.line.ends;
1015 for (end, (at, from)) in [(ends.0, (a, b)), (ends.1, (b, a))] {
1016 match end {
1017 1 => self.arrow(at, from, color, width),
1018 2 if width > 1 => {
1019 let r = width / 2;
1020 let saved = self.attrs.fill;
1021 self.attrs.fill = FillAttr { pen: self.attrs.line.pen, interior: 1, style: 1, perimeter: false };
1022 let pts = self.ellipse_points(at, r, (r as f64 * self.res.aspect()) as i32, 0, 3600);
1023 self.polygon(&pts, false);
1024 self.attrs.fill = saved;
1025 }
1026 _ => {}
1027 }
1028 }
1029 }
1030
1031 fn arrow(&mut self, tip: (i32, i32), from: (i32, i32), _color: u8, width: i32) {
1032 let (dx, dy) = ((tip.0 - from.0) as f64, (tip.1 - from.1) as f64);
1033 let len = (dx * dx + dy * dy).sqrt().max(1.0);
1034 let size = (8 + 2 * width) as f64;
1035 let (ux, uy) = (dx / len, dy / len);
1036 let back = (tip.0 as f64 - ux * size, tip.1 as f64 - uy * size);
1037 let side = size / 2.0;
1038 let p1 = ((back.0 - uy * side) as i32, (back.1 + ux * side * self.res.aspect()) as i32);
1039 let p2 = ((back.0 + uy * side) as i32, (back.1 - ux * side * self.res.aspect()) as i32);
1040 let saved = self.attrs.fill;
1041 self.attrs.fill = FillAttr { pen: self.attrs.line.pen, interior: 1, style: 1, perimeter: false };
1042 self.polygon(&[tip, p1, p2], false);
1043 self.attrs.fill = saved;
1044 }
1045
1046 fn ellipse_points(&self, c: (i32, i32), rx: i32, ry: i32, start: i32, end: i32) -> Vec<(i32, i32)> {
1049 let mut span = end - start;
1050 if span <= 0 {
1051 span += 3600;
1052 }
1053 let steps = (rx.max(ry) / 4).clamp(32, 128);
1054 (0..=steps)
1055 .map(|i| {
1056 let a = (start as f64 + span as f64 * i as f64 / steps as f64).to_radians() / 10.0;
1057 ((c.0 as f64 + rx as f64 * a.cos()).round() as i32, (c.1 as f64 - ry as f64 * a.sin()).round() as i32)
1058 })
1059 .collect()
1060 }
1061
1062 fn polyline(&mut self, pts: &[(i32, i32)]) {
1063 for seg in pts.windows(2) {
1064 self.line(seg[0], seg[1]);
1065 }
1066 }
1067
1068 fn shape(&mut self, pts: &[(i32, i32)]) {
1070 self.polygon(pts, self.attrs.fill.perimeter);
1071 }
1072
1073 fn rounded_box(&mut self, (x0, y0, x1, y1): (i32, i32, i32, i32), filled: bool) {
1074 let (x0, x1) = (x0.min(x1), x0.max(x1));
1075 let (y0, y1) = (y0.min(y1), y0.max(y1));
1076 let rx = ((x1 - x0) / 2).min(8);
1077 let ry = (((y1 - y0) / 2) as f64).min(8.0 * self.res.aspect()) as i32;
1078 let mut pts = Vec::new();
1079 for (cx, cy, a0) in
1080 [(x1 - rx, y0 + ry, 0), (x0 + rx, y0 + ry, 900), (x0 + rx, y1 - ry, 1800), (x1 - rx, y1 - ry, 2700)]
1081 {
1082 pts.extend(self.ellipse_points((cx, cy), rx, ry, a0, a0 + 900));
1083 }
1084 if filled {
1085 self.shape(&pts);
1086 } else {
1087 pts.push(pts[0]);
1088 self.polyline(&pts);
1089 }
1090 }
1091
1092 fn flood(&mut self, x: i32, y: i32) {
1094 let (w, h) = self.res.size();
1095 if x < 0 || y < 0 || x as usize >= w || y as usize >= h {
1096 return;
1097 }
1098 let target = self.pixels[y as usize * w + x as usize];
1099 let mut seen = vec![false; w * h];
1100 let mut stack = vec![(x as usize, y as usize)];
1101 let mut spans = Vec::new();
1102 while let Some((x, y)) = stack.pop() {
1103 if seen[y * w + x] || self.pixels[y * w + x] != target {
1104 continue;
1105 }
1106 let (mut a, mut b) = (x, x);
1107 while a > 0 && !seen[y * w + a - 1] && self.pixels[y * w + a - 1] == target {
1108 a -= 1;
1109 }
1110 while b + 1 < w && !seen[y * w + b + 1] && self.pixels[y * w + b + 1] == target {
1111 b += 1;
1112 }
1113 for i in a..=b {
1114 seen[y * w + i] = true;
1115 for ny in [y.wrapping_sub(1), y + 1] {
1116 if ny < h && !seen[ny * w + i] && self.pixels[ny * w + i] == target {
1117 stack.push((i, ny));
1118 }
1119 }
1120 }
1121 spans.push((a as i32, b as i32, y as i32));
1122 }
1123 for (a, b, y) in spans {
1124 self.fill_span(a, b, y);
1125 }
1126 }
1127
1128 fn marker(&mut self, x: i32, y: i32) {
1129 let (pen, kind, size) = self.attrs.marker;
1130 let color = self.reg(pen);
1131 let shape = MARKERS[(kind.clamp(1, 6) - 1) as usize];
1132 let scale = if kind == 1 { 0.0 } else { size.max(1) as f64 };
1133 for poly in shape {
1134 let pts: Vec<(i32, i32)> =
1135 poly.iter().map(|&(dx, dy)| (x + (dx as f64 * scale) as i32, y + (dy as f64 * scale) as i32)).collect();
1136 for seg in pts.windows(2) {
1137 self.thin_line(seg[0], seg[1], color, 0xFFFF, self.attrs.mode);
1138 }
1139 }
1140 }
1141
1142 fn write_text(&mut self, x: i32, y: i32, text: &[u8]) {
1144 let t = self.attrs.text;
1145 let color = self.reg(t.pen);
1146 let (cell, scale) = match t.points {
1149 ..=8 => (6, 1),
1150 9 => (8, 1),
1151 10..=15 => (16, 1),
1152 16..=17 => (6, 2),
1153 18..=19 => (8, 2),
1154 _ => (16, 2),
1155 };
1156 let ascent = match cell {
1157 6 => 4,
1158 8 => 6,
1159 _ => 13,
1160 } * scale;
1161 let (cw, ch) = (cell.min(8) * scale, cell * scale);
1162 for (i, &c) in text.iter().enumerate() {
1163 for gy in 0..ch {
1164 let mut bits = glyph_row(c, (gy / scale) as usize, cell as usize) as u32;
1165 if t.effects & 1 != 0 {
1166 bits |= bits >> 1;
1167 }
1168 if t.effects & 2 != 0 && gy % 2 == 1 {
1169 bits &= 0x55;
1170 }
1171 if t.effects & 8 != 0 && gy == ascent + scale {
1172 bits = 0xFF;
1173 }
1174 let skew = if t.effects & 4 != 0 { (ch - gy) / 4 } else { 0 };
1175 for gx in 0..cw {
1176 let on = bits & (0x80 >> (gx / scale)) != 0;
1177 if !on && self.attrs.mode != 1 {
1178 continue;
1179 }
1180 let (ax, ay) = ((i as i32) * cw + gx + skew, gy - ascent);
1182 let (px, py) = match t.rotation % 4 {
1183 1 => (x + ay, y - ax),
1184 2 => (x - ax, y - ay),
1185 3 => (x - ay, y + ax),
1186 _ => (x + ax, y + ay),
1187 };
1188 self.plot(px, py, on, color, self.attrs.mode);
1189 }
1190 }
1191 }
1192 }
1193
1194 fn blit(&mut self, from_memory: bool, src: (i32, i32, i32, i32), to_memory: bool, dst: (i32, i32), op: u8) {
1197 let (w, h) = self.res.size();
1198 let (x0, y0, x1, y1) = (src.0.min(src.2), src.1.min(src.3), src.0.max(src.2), src.1.max(src.3));
1199 let source: Vec<u8> = if from_memory { self.memory.clone() } else { self.pixels.clone() };
1200 let mask = self.mask();
1201 for y in 0..=(y1 - y0) {
1202 for x in 0..=(x1 - x0) {
1203 let (sx, sy, dx, dy) = (x0 + x, y0 + y, dst.0 + x, dst.1 + y);
1204 if sx < 0 || sy < 0 || dx < 0 || dy < 0 {
1205 continue;
1206 }
1207 let (sx, sy, dx, dy) = (sx as usize, sy as usize, dx as usize, dy as usize);
1208 if sx >= w || sy >= h || dx >= w || dy >= h {
1209 continue;
1210 }
1211 let s = source[sy * w + sx];
1212 let target = if to_memory { &mut self.memory } else { &mut self.pixels };
1213 let d = target[dy * w + dx];
1214 let v = match op {
1215 0 => 0,
1216 1 => s & d,
1217 2 => s & !d,
1218 4 => !s & d,
1219 5 => d,
1220 6 => s ^ d,
1221 7 => s | d,
1222 8 => !(s | d),
1223 9 => !(s ^ d),
1224 10 => !d,
1225 11 => s | !d,
1226 12 => !s,
1227 13 => !s | d,
1228 14 => !(s & d),
1229 15 => 0xFF,
1230 _ => s,
1231 };
1232 target[dy * w + dx] = v & mask;
1233 }
1234 }
1235 }
1236
1237 fn clear_screen(&mut self) {
1238 self.pixels.fill(0);
1239 }
1240}
1241
1242fn glyph_row(c: u8, y: usize, h: usize) -> u8 {
1245 match h {
1246 16 => FONT16[c as usize * 16 + y.min(15)],
1247 6 if (0x20..0x7F).contains(&c) => FONT6[c as usize * 6 + y.min(5)],
1248 6 => FONT8[c as usize * 8 + (y * 8 / 6).min(7)],
1249 _ => FONT8[c as usize * 8 + y.min(7)],
1250 }
1251}
1252
1253impl Igs {
1255 fn val(&mut self, r: &mut Reader, axis: u8) -> Res2<i32> {
1258 let tok = match self.looping.as_mut() {
1259 Some(lp) if lp.running || lp.at != 0 => lp.next(),
1260 _ => r.token()?,
1261 };
1262 let mut n = match tok {
1263 Tok::Num(n) | Tok::Plus(n) | Tok::Bang(n) => n,
1264 Tok::Minus(n) => -n,
1265 Tok::Rand => self.random(self.rand),
1266 Tok::RandBig => self.random(self.rand_big),
1267 Tok::X | Tok::Y => 0,
1268 };
1269 if self.scaling == 1 && axis > 0 && n > 0 {
1270 let max = if axis == 1 { self.width() - 1 } else { self.height() - 1 } as f64;
1271 n = (n as f64 * max / 9999.0).round() as i32;
1272 } else if self.scaling == 2 && axis == 2 && self.res == Res::High {
1273 n *= 2;
1274 }
1275 Ok(n)
1276 }
1277
1278 fn point(&mut self, r: &mut Reader) -> Res2<(i32, i32)> {
1279 Ok((self.val(r, 1)?, self.val(r, 2)?))
1280 }
1281
1282 fn command(&mut self, r: &mut Reader) -> Res2<bool> {
1285 let mut key = r.next()?;
1286 while key == b'G' && r.data.get(r.pos) == Some(&b'#') {
1288 r.pos += 1;
1289 key = r.next()?;
1290 }
1291 let chain = r.next()? == b'>';
1292 if key == b'&' {
1293 self.loop_command(r)?;
1294 } else {
1295 self.run(key, r)?;
1296 }
1297 Ok(chain)
1298 }
1299
1300 fn loop_command(&mut self, r: &mut Reader) -> Res2<()> {
1302 let (from, to, step, _delay) = (r.token()?, r.token()?, r.token()?, r.token()?);
1303 let num = |t: Tok| if let Tok::Num(n) = t { n } else { 0 };
1304 let (from, to, step) = (num(from), num(to), num(step));
1305 let mut step = if from > to { -step } else { step };
1306 if step == 0 {
1307 step = 1;
1308 }
1309 let key = r.next()?;
1310 let gang = if key == b'>' { Some(r.text()?) } else { None };
1311 let kind = r.next()?;
1312 let count = (num(r.token()?)).clamp(1, 2048) as usize;
1313 let mut params = Vec::with_capacity(count);
1314 for _ in 0..count {
1315 params.push(r.token()?);
1316 }
1317 let plain = !params.iter().any(|t| !matches!(t, Tok::Num(_) | Tok::Rand | Tok::RandBig));
1318 self.looping = Some(Loop {
1319 params,
1320 at: 0,
1321 value: from,
1322 to,
1323 step,
1324 from,
1325 kind,
1326 x_count: 0,
1327 first: 0,
1328 second: 0,
1329 was: from,
1330 plain,
1331 running: true,
1332 });
1333 let mut guard = 0;
1334 loop {
1335 let cmd = match &gang {
1336 Some(g) => {
1337 let i = self.val(r, 0)?.clamp(0, 127) as usize;
1338 g.get(i).copied().unwrap_or(b' ')
1339 }
1340 None => key,
1341 };
1342 self.run(cmd, r)?;
1344 guard += 1;
1345 let done = self.looping.as_ref().is_none_or(|lp| !lp.running && lp.at == 0);
1346 if done || guard > 100_000 {
1347 break;
1348 }
1349 }
1350 self.looping = None;
1351 Ok(())
1352 }
1353
1354 fn in_loop(&self) -> bool {
1355 self.looping.is_some()
1356 }
1357
1358 fn run(&mut self, key: u8, r: &mut Reader) -> Res2<()> {
1359 match key {
1360 b'A' => {
1361 let (t, p, b) = (self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1362 let f = &mut self.attrs.fill;
1363 f.interior = if (0..=4).contains(&t) { t as u8 } else { 0 };
1364 if f.interior == 4 && p >= 8 {
1365 self.user_patterns[0] = if p == 8 {
1367 std::array::from_fn(|_| {
1368 self.seed ^= self.seed << 13;
1369 self.seed ^= self.seed >> 17;
1370 self.seed ^= self.seed << 5;
1371 (self.seed & self.seed >> 7) as u16
1372 })
1373 } else {
1374 ROM_UD_PATTERN
1375 };
1376 self.attrs.fill.style = 1;
1377 } else {
1378 self.attrs.fill.style =
1379 if self.attrs.fill.interior == 4 { (p + 1).clamp(1, 8) as u8 } else { p.clamp(1, 24) as u8 };
1380 }
1381 self.attrs.fill.perimeter = b != 0;
1382 }
1383 b'B' => {
1384 let (a, b, round) = (self.point(r)?, self.point(r)?, self.val(r, 0)?);
1385 if round != 0 {
1386 self.rounded_box((a.0, a.1, b.0, b.1), true);
1387 } else {
1388 self.shape(&[(a.0, a.1), (b.0, a.1), (b.0, b.1), (a.0, b.1)]);
1389 }
1390 }
1391 b'C' => {
1392 let (which, pen) = (self.val(r, 0)?, self.val(r, 0)?);
1393 let pen = if (0..=15).contains(&pen) { pen as u8 } else { 0 };
1394 match which {
1395 0 => self.attrs.marker.0 = pen,
1396 1 => self.attrs.line.pen = pen,
1397 2 => self.attrs.fill.pen = pen,
1398 3 => self.attrs.text.pen = pen,
1399 _ => self.attrs.marker.0 = pen,
1400 }
1401 }
1402 b'D' => {
1403 let to = self.point(r)?;
1404 let from = self.pen_at;
1405 self.line(from, to);
1406 self.pen_at = to;
1407 }
1408 b'E' => {
1409 let (fx, size, rot) = (self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1410 self.attrs.text.effects = fx as u8;
1411 self.attrs.text.points = size.clamp(8, 20);
1412 self.attrs.text.rotation = if (0..=4).contains(&rot) { rot as u8 } else { 0 };
1413 }
1414 b'F' => {
1415 let (x, y) = self.point(r)?;
1416 self.flood(x, y);
1417 }
1418 b'f' | b'z' => {
1419 let n = self.val(r, 0)?.clamp(0, 128) as usize;
1420 let mut pts = Vec::with_capacity(n);
1421 for _ in 0..n {
1422 pts.push(self.point(r)?);
1423 }
1424 if key == b'f' {
1425 self.shape(&pts);
1426 } else if n >= 2 {
1427 self.polyline(&pts);
1428 }
1429 }
1430 b'G' => self.grab(r)?,
1431 b'g' => {
1432 let v = self.val(r, 0)?;
1433 self.scaling = if (0..=2).contains(&v) { v } else { 0 };
1434 }
1435 b'H' => {
1436 self.hollow = self.val(r, 0)? != 0;
1437 if self.hollow {
1438 self.attrs.fill.interior = 0;
1439 self.attrs.mode = 2;
1440 self.attrs.fill.perimeter = true;
1441 } else {
1442 self.attrs.fill.interior = 1;
1443 self.attrs.mode = 1;
1444 self.attrs.fill.perimeter = false;
1445 }
1446 }
1447 b'I' => match self.val(r, 0)? {
1448 1 => self.desktop_palette(),
1449 2 => self.attrs = Attrs::default(),
1450 3 => self.regs = IG_PALETTE,
1451 4 => self.desktop_palette(),
1452 5 => {
1453 if self.res == Res::Low {
1454 self.set_res(Res::Medium);
1455 }
1456 }
1457 _ => {
1458 self.desktop_palette();
1459 self.attrs = Attrs::default();
1460 }
1461 },
1462 b'J' | b'Y' => {
1463 let (c, rx, ry) = (self.point(r)?, self.val(r, 1)?, self.val(r, 2)?);
1464 let (a0, a1) = (self.val(r, 0)? * 10, self.val(r, 0)? * 10);
1465 let pts = self.ellipse_points(c, rx, ry, a0, a1);
1466 if key == b'J' {
1467 self.polyline(&pts);
1468 } else {
1469 let mut pie = vec![c];
1470 pie.extend(pts);
1471 self.shape(&pie);
1472 }
1473 }
1474 b'K' | b'V' => {
1475 let (c, rad) = (self.point(r)?, self.val(r, 1)?);
1476 let (a0, a1) = (self.val(r, 0)? * 10, self.val(r, 0)? * 10);
1477 let ry = (rad as f64 * self.res.aspect()).round() as i32;
1478 let pts = self.ellipse_points(c, rad, ry, a0, a1);
1479 if key == b'K' {
1480 self.polyline(&pts);
1481 } else {
1482 let mut pie = vec![c];
1483 pie.extend(pts);
1484 self.shape(&pie);
1485 }
1486 }
1487 b'k' => match self.val(r, 0)? {
1488 0 => self.cursor_on = false,
1489 1 => self.cursor_on = true,
1490 2 => self.destructive_bs = true,
1491 _ => self.destructive_bs = false,
1492 },
1493 b'L' => {
1494 let (a, b) = (self.point(r)?, self.point(r)?);
1495 self.line(a, b);
1496 self.pen_at = b;
1497 }
1498 b'M' => {
1499 let m = self.val(r, 0)?;
1500 self.attrs.mode = if (1..=4).contains(&m) { m as u8 } else { 1 };
1501 }
1502 b'N' => self.noise(r)?,
1503 b'n' => {
1504 let (effect, voice, volume) = (self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1505 let (pitch, timing, stop) = (self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1506 let channel = VOICE_CHANNELS + voice.clamp(0, 2) as usize;
1507 if pitch > 0 {
1508 let words = self.effects[effect.clamp(0, 19) as usize];
1509 let ticks = (timing > 0 && stop != 0).then_some(timing as u32);
1510 let (volume, pitch) = (volume.clamp(0, 15) as u8, pitch.clamp(0, 127) as u8);
1511 self.play(channel, 400, move || crate::igs_sound::note(&words, volume, pitch, ticks));
1512 } else if stop != 0 {
1513 self.silence(if stop >= 3 { VOICE_CHANNELS..VOICE_CHANNELS + 3 } else { channel..channel + 1 });
1514 }
1515 if timing > 0 {
1517 self.hold = Some(Instant::now() + Duration::from_millis(timing.min(6000) as u64 * 5));
1518 }
1519 }
1520 b'O' => {
1521 let (c, rad) = (self.point(r)?, self.val(r, 1)?);
1522 let ry = (rad as f64 * self.res.aspect()).round() as i32;
1523 let pts = self.ellipse_points(c, rad, ry, 0, 3600);
1524 self.shape(&pts);
1525 }
1526 b'P' => {
1527 let p = self.point(r)?;
1528 self.marker(p.0, p.1);
1529 self.pen_at = p;
1530 }
1531 b'Q' => {
1532 let (c, rx, ry) = (self.point(r)?, self.val(r, 1)?, self.val(r, 2)?);
1533 let pts = self.ellipse_points(c, rx, ry, 0, 3600);
1534 self.shape(&pts);
1535 }
1536 b'q' => {
1537 let n = self.val(r, 0)?;
1538 if (1..=180).contains(&n) && !self.in_loop() {
1539 self.hold = Some(Instant::now() + Duration::from_millis(n as u64 * 1000 / 60));
1540 }
1541 }
1542 b'R' => {
1543 let (res, pal) = (self.val(r, 0)?, self.val(r, 0)?);
1544 let want = match res {
1545 0 => Res::Low,
1546 1 => Res::Medium,
1547 _ => self.res,
1548 };
1549 if self.res != Res::High && want != self.res {
1550 self.set_res(want);
1551 }
1552 match pal {
1553 1 | 3 => self.desktop_palette(),
1554 2 => self.regs = IG_PALETTE,
1555 _ => {}
1556 }
1557 }
1558 b'S' => {
1559 let pen = self.val(r, 0)?;
1560 let (cr, cg, cb) = (self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1561 let pen = if (0..=15).contains(&pen) { pen as u8 } else { 0 };
1562 let c = |v: i32| v.clamp(0, 7) as u16;
1563 let reg = self.reg(pen) as usize;
1564 self.regs[reg] = c(cr) << 8 | c(cg) << 4 | c(cb);
1565 }
1566 b's' => match self.val(r, 0)? {
1567 0 => self.vt52_escape(b'E'),
1568 1 => self.vt52_escape(b'd'),
1569 2 => self.vt52_escape(b'J'),
1570 3 => self.clear_screen(),
1571 4 => {
1572 self.clear_screen();
1573 (self.col, self.row) = (0, 0);
1574 }
1575 _ => {
1576 self.reverse = false;
1577 self.bg = 0;
1578 self.fg = 3 & self.mask();
1579 self.vt52_escape(b'E');
1580 }
1581 },
1582 b'T' => {
1583 let which = self.val(r, 0)?;
1584 if !(1..=2).contains(&which) {
1585 return Ok(());
1586 }
1587 let kind = self.val(r, 0)?;
1588 let size = self.val(r, 0)?;
1589 if which == 1 {
1590 self.attrs.marker.1 = if (1..=6).contains(&kind) { kind as u8 } else { 1 };
1591 self.attrs.marker.2 = if (1..=8).contains(&size) { size } else { 1 };
1592 } else {
1593 let kind = if (1..=7).contains(&kind) { kind as u8 } else { 1 };
1594 if (1..42).contains(&size) {
1595 self.attrs.line.style = kind;
1596 self.attrs.line.width = if kind > 1 { 1 } else { size };
1597 }
1598 let ends = match size {
1599 0 => Some((0, 0)),
1600 50 => Some((1, 1)),
1601 51 => Some((1, 0)),
1602 52 => Some((0, 1)),
1603 53 => Some((1, 2)),
1604 54 => Some((2, 1)),
1605 60 => Some((2, 2)),
1606 61 => Some((2, 0)),
1607 62 => Some((0, 2)),
1608 63 => Some((2, 1)),
1609 64 => Some((1, 2)),
1610 _ => None,
1611 };
1612 if let Some(e) = ends {
1613 self.attrs.line.ends = e;
1614 }
1615 }
1616 }
1617 b't' => {
1618 let n = self.val(r, 0)?;
1619 if n > 0 && !self.in_loop() {
1620 self.hold = Some(Instant::now() + Duration::from_secs(n.min(30) as u64));
1621 }
1622 }
1623 b'U' => {
1624 let (a, b, filled) = (self.point(r)?, self.point(r)?, self.val(r, 0)?);
1625 self.rounded_box((a.0, a.1, b.0, b.1), filled != 0);
1626 }
1627 b'W' => {
1628 let p = self.point(r)?;
1629 let reads = self.looping.as_ref().is_none_or(|lp| lp.kind == b'@');
1630 if reads {
1631 self.last_text = r.text()?;
1632 }
1633 let text = self.last_text.clone();
1634 self.write_text(p.0, p.1, &text);
1635 }
1636 b'X' => self.extended(r)?,
1637 b'Z' => {
1638 let (a, b) = (self.point(r)?, self.point(r)?);
1639 let (x0, x1, y0, y1) = (a.0.min(b.0), a.0.max(b.0), a.1.min(b.1), a.1.max(b.1));
1640 for y in y0..=y1 {
1641 self.fill_span(x0, x1, y);
1642 }
1643 }
1644 b'b' => match self.val(r, 0)? {
1645 n @ 0..=19 => self.effect(n as usize),
1646 20 => {
1647 let (play, snd, elem) = (self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1648 let (neg, thousands, hundreds) = (self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1649 let snd = snd.clamp(0, 19) as usize;
1650 let value = thousands.clamp(0, 32) * 1000 + hundreds.clamp(0, 999);
1651 self.effects[snd][elem.clamp(0, 55) as usize] = if neg != 0 { -value } else { value } as i16;
1652 if play != 0 {
1653 self.effect(snd);
1654 }
1655 }
1656 21 => self.silence(EFFECT_CHANNEL..MIDI_CHANNELS.end),
1657 22 => {
1658 let snd = self.val(r, 0)?.clamp(0, 19) as usize;
1659 self.effects[snd] = crate::igs_sound::EFFECTS[snd];
1660 }
1661 23 => self.effect_loops = self.val(r, 0)?.clamp(0, 50) as u32,
1662 _ => {}
1663 },
1664 b'<' => {
1665 let (cr, kind, show) = (self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1666 if (2..=10).contains(&kind) {
1667 self.awaiting_zone = Some((cr != 0, show < 2));
1668 }
1669 }
1670 b'?' => {
1671 let q = self.val(r, 0)?;
1672 match q {
1673 0 => self.replies.extend_from_slice(VERSION),
1674 1 => {
1675 self.val(r, 0)?;
1676 let (c, rw) = (self.col as u8 + 32, self.row as u8 + 32);
1677 self.replies.extend_from_slice(&[c, rw, 32]);
1678 }
1679 2 => {
1680 self.val(r, 0)?;
1681 self.replies.extend_from_slice(b"0,0,0:");
1682 }
1683 3 => self.replies.extend_from_slice(&[b'0' + self.res.number(), b':']),
1684 _ => {}
1685 }
1686 }
1687 b'c' => {
1689 let (which, reg) = (self.val(r, 0)?, self.val(r, 0)?);
1690 let reg = reg as u8 & self.mask();
1691 if which == 0 {
1692 self.bg = reg;
1693 } else {
1694 self.fg = reg;
1695 }
1696 }
1697 b'd' => {
1698 for _ in 0..self.val(r, 0)?.clamp(0, 50) {
1699 self.vt52_escape(b'M');
1700 }
1701 }
1702 b'i' => {
1703 let (kind, n) = (self.val(r, 0)?, self.val(r, 0)?);
1704 for _ in 0..n.clamp(0, 50) {
1705 if kind == 0 {
1706 self.vt52_escape(b'I');
1707 } else {
1708 self.vt52_escape(b'L');
1709 }
1710 }
1711 }
1712 b'l' => match self.val(r, 0)? {
1713 0 => {
1714 self.vt52_escape(b'l');
1715 self.col = 0;
1716 }
1717 1 => self.vt52_escape(b'o'),
1718 _ => self.vt52_escape(b'K'),
1719 },
1720 b'm' => {
1721 let (dir, n) = (self.val(r, 0)?, self.val(r, 0)?);
1722 let esc = match dir {
1723 1 => b'A',
1724 2 => b'B',
1725 3 => b'C',
1726 4 => b'D',
1727 _ => b'H',
1728 };
1729 for _ in 0..n.clamp(1, 80) {
1730 self.vt52_escape(esc);
1731 }
1732 }
1733 b'p' => {
1734 let (c, rw) = (self.val(r, 0)?, self.val(r, 0)?);
1735 let (cols, rows) = self.text_size();
1736 self.col = (c.max(0) as usize).min(cols - 1);
1737 self.row = (rw.max(0) as usize).min(rows - 1);
1738 }
1739 b'r' => {
1740 if self.val(r, 0)? == 0 {
1741 self.saved = (self.col, self.row);
1742 } else {
1743 (self.col, self.row) = self.saved;
1744 }
1745 }
1746 b'v' => self.reverse = self.val(r, 0)? != 0,
1747 b'w' => self.wrap = self.val(r, 0)? != 0,
1748 _ => log::debug!("unknown IGS command {:?}", key as char),
1749 }
1750 Ok(())
1751 }
1752
1753 fn grab(&mut self, r: &mut Reader) -> Res2<()> {
1754 let (kind, op) = (self.val(r, 0)?, self.val(r, 0)?);
1755 let op = if (0..=15).contains(&op) { op as u8 } else { 3 };
1756 let a = self.point(r)?;
1757 match kind {
1758 0 => {
1759 let b = self.point(r)?;
1760 let d = self.point(r)?;
1761 self.blit(false, (a.0, a.1, b.0, b.1), false, d, op);
1762 }
1763 1 => {
1764 let b = self.point(r)?;
1765 self.memory_rect = (a.0, a.1, b.0, b.1);
1766 self.blit(false, self.memory_rect, true, (a.0.min(b.0), a.1.min(b.1)), op);
1767 }
1768 2 => self.blit(true, self.memory_rect, false, a, op),
1769 _ => {
1770 let b = self.point(r)?;
1771 let d = self.point(r)?;
1772 self.blit(true, (a.0, a.1, b.0, b.1), kind == 4, d, op);
1773 }
1774 }
1775 Ok(())
1776 }
1777
1778 fn play(&mut self, channel: usize, ms: usize, render: impl FnOnce() -> crate::audio::Pcm + Send + 'static) {
1781 use crate::audio::{AudioOp, RATE, Sound};
1782 let sound = Sound::decoded_later(ms * RATE as usize / 1000, move || Some(render().to_vec()));
1783 self.audio.push(AudioOp::Flush { channel, fade_out: 0 });
1784 self.audio.push(AudioOp::Queue {
1785 channel,
1786 sound,
1787 fade_in: 0,
1788 fade_out: 0,
1789 crossfade: false,
1790 looped: false,
1791 volume: (0.0, 0.0),
1792 });
1793 }
1794
1795 fn silence(&mut self, channels: std::ops::Range<usize>) {
1796 for channel in channels {
1797 self.audio.push(crate::audio::AudioOp::Flush { channel, fade_out: 256 });
1798 }
1799 }
1800
1801 fn effect(&mut self, n: usize) {
1803 let words = self.effects[n];
1804 let times = if n <= 4 { self.effect_loops.max(1) } else { 1 };
1805 self.play(EFFECT_CHANNEL, 1000, move || crate::igs_sound::effect(&words, times));
1806 }
1807
1808 fn noise(&mut self, r: &mut Reader) -> Res2<()> {
1811 match self.val(r, 0)? {
1812 op @ (0 | 1 | 3 | 4) => {
1813 let n = self.val(r, 0)?.clamp(0, 10_001) as usize;
1814 let mut data = Vec::with_capacity(n);
1815 for _ in 0..n {
1816 data.push(r.next()?);
1817 }
1818 if op < 3 {
1819 self.midi = data;
1820 } else {
1821 self.chip = data;
1822 }
1823 match op {
1824 1 => self.play_midi(),
1825 4 => self.play_chip(),
1826 _ => {}
1827 }
1828 }
1829 2 => self.play_midi(),
1830 5 => self.play_chip(),
1831 6 => {
1832 self.val(r, 0)?;
1833 self.val(r, 0)?;
1834 }
1835 _ => {}
1836 }
1837 Ok(())
1838 }
1839
1840 fn play_midi(&mut self) {
1841 for (note, velocity) in crate::igs_sound::midi_notes(&self.midi) {
1842 let channel = MIDI_CHANNELS.start + self.next_midi % MIDI_CHANNELS.len();
1843 self.next_midi += 1;
1844 self.play(channel, 600, move || crate::igs_sound::pluck(note, velocity));
1845 }
1846 }
1847
1848 fn play_chip(&mut self) {
1849 let data = self.chip.clone();
1850 self.play(CHIP_CHANNEL, 1000, move || crate::igs_sound::dosound(&data));
1851 }
1852
1853 fn extended(&mut self, r: &mut Reader) -> Res2<()> {
1854 match self.val(r, 0)? {
1855 0 => {
1857 let (a, rx, ry, n) = (self.point(r)?, self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1858 for _ in 0..n.clamp(0, 9999) {
1859 let x = a.0 + self.random((0, rx.clamp(0, 255)));
1860 let y = a.1 + self.random((0, ry.clamp(0, 255)));
1861 self.marker(x, y);
1862 }
1863 }
1864 1 => {
1866 let (reg, v) = (self.val(r, 0)?, self.val(r, 0)?);
1867 self.regs[(reg & 15) as usize] = (v as u16) & 0x777;
1868 }
1869 2 => {
1870 let (a, b) = (self.val(r, 0)?, self.val(r, 0)?);
1871 if a == b {
1872 let c = self.val(r, 0)?;
1873 self.rand_big = (b, c);
1874 } else {
1875 self.rand = (a, b);
1876 }
1877 }
1878 3 => match self.val(r, 0)? {
1880 1 => {
1881 self.val(r, 0)?;
1882 }
1883 2 => {
1884 let (_on, _cr, len) = (self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1885 for _ in 0..len.clamp(0, 80) + 1 {
1886 r.next()?;
1887 }
1888 }
1889 _ => {}
1890 },
1891 4 => {
1893 let id = self.val(r, 0)?;
1894 if id >= 9997 {
1895 if id == 9999 {
1896 self.zones = vec![None; 48];
1897 }
1898 return Ok(());
1899 }
1900 let (a, b, len) = (self.point(r)?, self.point(r)?, self.val(r, 0)?);
1901 let mut text = Vec::new();
1902 for _ in 0..len.clamp(0, 80) {
1903 text.push(r.next()?);
1904 }
1905 r.next()?;
1906 if let Some(z) = self.zones.get_mut(id.clamp(0, 47) as usize) {
1907 *z = Some((a.0.min(b.0), a.1.min(b.1), a.0.max(b.0), a.1.max(b.1), text));
1908 }
1909 }
1910 5 => {
1911 if matches!(self.val(r, 0)?, 2 | 3) {
1912 self.val(r, 0)?;
1913 self.val(r, 0)?;
1914 }
1915 }
1916 6 => {
1917 self.val(r, 0)?;
1918 }
1919 7 => {
1921 let slot = self.val(r, 0)?.clamp(0, 7) as usize;
1922 let mut rows = [0u16; 16];
1923 for row in &mut rows {
1924 let text = r.text()?;
1925 *row =
1926 text.iter().take(16).enumerate().fold(0, |acc, (i, &c)| {
1927 if c.eq_ignore_ascii_case(&b'X') { acc | 0x8000 >> i } else { acc }
1928 });
1929 }
1930 self.user_patterns[slot] = rows;
1931 }
1932 8 => {
1935 let (a, b, times, delay) = (self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1936 if (a, b, times, delay) == (1, 1, 1, 1) {
1937 self.kept_palette = self.regs;
1938 }
1939 let clamp = |v: i32| if (0..=15).contains(&v) { v as u8 } else { 15 };
1940 let (a, b) = (clamp(a), clamp(b));
1941 if times == 0 {
1942 self.regs = self.kept_palette;
1943 } else if a != b {
1944 let step =
1947 Duration::from_millis(((a.abs_diff(b) as u64 + 2) * 1000 / 60) + delay.max(0) as u64 * 5);
1948 self.cycle = Some(Cycle { from: a, to: b, steps: times as u32 + 1, step });
1949 self.cycle_step();
1950 }
1951 }
1952 9 => match self.val(r, 0)? {
1954 0 => {
1955 let mut stored = Vec::new();
1956 while !stored.ends_with(b"||}") {
1957 stored.push(r.next()?);
1958 }
1959 stored.truncate(stored.len() - 3);
1960 self.stored = stored;
1961 }
1962 1 => self.run_stored = Some(self.stored.clone()),
1963 _ => self.stored.clear(),
1964 },
1965 10 => self.pen_at = self.point(r)?,
1966 _ => {}
1967 }
1968 Ok(())
1969 }
1970
1971 pub fn click(&mut self, x: i32, y: i32) -> Option<Vec<u8>> {
1974 let (cr, send) = self.awaiting_zone?;
1975 let hit = self.zones[..47]
1976 .iter()
1977 .flatten()
1978 .find(|z| x >= z.0 && x <= z.2 && y >= z.1 && y <= z.3)
1979 .or(self.zones[47].as_ref())?;
1980 let mut out = if send { hit.4.clone() } else { Vec::new() };
1981 self.awaiting_zone = None;
1982 if cr && send {
1983 out.push(b'\r');
1984 }
1985 Some(out)
1986 }
1987}
1988
1989#[cfg(test)]
1990mod tests {
1991 use super::*;
1992
1993 fn igs(data: &[u8]) -> Igs {
1994 let mut g = Igs::default();
1995 g.feed(data);
1996 g
1997 }
1998
1999 fn at(g: &Igs, x: usize, y: usize) -> u8 {
2000 g.pixels[y * g.width() + x]
2001 }
2002
2003 #[test]
2004 fn sound_effects_chip_music_and_midi() {
2005 use crate::audio::AudioOp;
2006 let queued = |g: &Igs| -> Vec<usize> {
2007 g.audio
2008 .iter()
2009 .filter_map(|op| if let AudioOp::Queue { channel, .. } = op { Some(*channel) } else { None })
2010 .collect()
2011 };
2012 let g = igs(b"G#b>2:");
2014 assert_eq!(queued(&g), [EFFECT_CHANNEL]);
2015 let mut g = igs(b"G#b>20,1,2,0,0,0,50:");
2017 assert_eq!(g.effects[2][0], 50);
2018 assert_eq!(queued(&g), [EFFECT_CHANNEL]);
2019 g.feed(b"G#b>22,2:");
2020 assert_eq!(g.effects[2], crate::igs_sound::EFFECTS[2]);
2021 let g = igs(b"G#n>8,1,15,60,100,1:");
2023 assert_eq!(queued(&g), [VOICE_CHANNELS + 1]);
2024 assert!(g.hold.is_some());
2025 let g = igs(b"G#N>1,6,\x90<d@Z\x80:");
2027 assert_eq!(queued(&g), [MIDI_CHANNELS.start, MIDI_CHANNELS.start + 1]);
2028 let g = igs(b"G#N>4,4,\x08\x0f\x82\x00:");
2029 assert_eq!(queued(&g), [CHIP_CHANNEL]);
2030 let g = igs(b"G#b>21:");
2032 assert!(g.audio.iter().all(|op| matches!(op, AudioOp::Flush { .. })) && !g.audio.is_empty());
2033 }
2034
2035 #[test]
2036 fn numbers_as_ig_reads_them() {
2037 let mut r = Reader { data: b"12,7:_\r\n34,1\r\n5\r\n2,x,+10,-3,!99,r,12345,", pos: 0 };
2038 let toks: Vec<Tok> = (0..9).map(|_| r.token().unwrap()).collect();
2039 assert_eq!(
2040 toks,
2041 [
2042 Tok::Num(12),
2043 Tok::Num(7),
2044 Tok::Num(34),
2045 Tok::Num(152),
2046 Tok::X,
2047 Tok::Plus(10),
2048 Tok::Minus(3),
2049 Tok::Bang(99),
2050 Tok::Rand,
2051 ]
2052 );
2053 assert_eq!(r.token().unwrap(), Tok::Num(0), "five digits are too many");
2054 assert!(Reader { data: b"12", pos: 0 }.token().is_err());
2055 }
2056
2057 #[test]
2058 fn lines_boxes_and_chains() {
2059 let g = igs(b"G#A>1,1,0:B>10,10,20,20,0:C>1,2:L 0,50,100,50:ok");
2062 assert_eq!(at(&g, 15, 15), 3);
2063 assert_eq!(at(&g, 25, 15), 0);
2064 assert_eq!(at(&g, 50, 50), 1);
2065 assert!((0..8).any(|y| (0..16).any(|x| at(&g, x, y) == 3)));
2067 }
2068
2069 #[test]
2070 fn commands_split_across_reads() {
2071 let mut g = Igs::default();
2072 g.feed(b"G#C>2,2:B>0,0,");
2073 assert_eq!(at(&g, 5, 5), 0);
2074 g.feed(b"10,10,0:");
2075 assert_eq!(at(&g, 5, 5), 1);
2076 }
2077
2078 #[test]
2079 fn loops_step_and_read_data() {
2080 let g = igs(b"G#C>1,1:&>0,30,10,0,L,4,x,0,x,9:");
2082 for x in [0, 10, 20, 30] {
2083 assert_eq!(at(&g, x, 5), 3, "x={x}");
2084 }
2085 assert_eq!(at(&g, 5, 5), 0);
2086 assert_eq!(at(&g, 40, 5), 0);
2087 let g = igs(b"G#C>1,1:&>0,0,1,0,L,8,100,0,100,9,200,0,200,9:");
2089 assert_eq!((at(&g, 100, 5), at(&g, 200, 5)), (3, 3));
2090 }
2091
2092 #[test]
2093 fn flood_fill_and_patterns() {
2094 let g = igs(b"G#A>0,1,1:B>0,0,40,20,0:A>2,4,0:F>10,10:");
2096 assert_eq!(at(&g, 0, 0), 3, "the border");
2097 let inside: Vec<u8> = (8..12).map(|x| at(&g, x, 10)).collect();
2098 assert!(inside.contains(&3) && inside.contains(&0), "{inside:?}");
2099 assert_eq!(at(&g, 50, 10), 0, "outside");
2100 }
2101
2102 #[test]
2103 fn vt52_text_and_queries() {
2104 let mut g = igs(b"\x1bY\x22\x25X\x1bb\x01Y");
2105 assert!((16..24).any(|y| (40..48).any(|x| at(&g, x, y) == 3)));
2107 assert!((16..24).any(|y| (48..56).any(|x| at(&g, x, y) == 1)));
2108 g.feed(b"G#?>0:?>3:");
2109 assert_eq!(g.replies, b"2.191:");
2110 g.feed(b"G#R>0,0:C>2,15:Z>0,0,9,9:");
2112 assert_eq!((g.width(), at(&g, 5, 5)), (320, 13));
2113 }
2114
2115 #[test]
2116 fn mouse_zones() {
2117 let mut g = igs(b"G#X>4,1,0,0,99,99,3,f/L:X>4,47,0,0,0,0,1,Q:<>1,2,1:");
2118 assert_eq!(g.click(50, 50), Some(b"f/L\r".to_vec()));
2119 assert_eq!(g.click(50, 50), None, "only while waiting");
2120 g.feed(b"G#< 0,2,1:");
2121 assert_eq!(g.click(300, 150), Some(b"Q".to_vec()));
2122 }
2123
2124 #[test]
2125 fn color_cycling_steps_over_time() {
2126 let mut g = igs(b"G#R>0,0:X>8,1,1,1,1:X>8,1,3,1,0:C>2,1:Z>0,0,9,9:");
2128 let pens = |g: &Igs| [1u8, 2, 3].map(|p| g.regs[g.res.register(p) as usize]);
2129 let start = [DESKTOP[15], DESKTOP[1], DESKTOP[2]];
2130 assert_eq!(pens(&g), [start[2], start[0], start[1]]);
2132 assert_eq!(at(&g, 5, 5), 0, "the rest waits for the cycle");
2133 g.poll(Instant::now() + Duration::from_secs(1));
2134 assert_eq!(pens(&g), [start[1], start[2], start[0]]);
2135 assert_eq!(at(&g, 5, 5), 15, "then carries on");
2136 g.feed(b"G#X>8,1,3,0,0:");
2138 assert_eq!(pens(&g), start);
2139 }
2140
2141 #[test]
2142 fn pauses_hold_the_stream() {
2143 let mut g = igs(b"G#t>1:C>2,2:Z>0,0,9,9:");
2144 assert!(g.hold.is_some());
2145 assert_eq!(at(&g, 5, 5), 0);
2146 g.poll(Instant::now() + Duration::from_secs(2));
2147 assert_eq!(at(&g, 5, 5), 1);
2148 }
2149}