deca/
display.rs

1/// A struct representing a CHIP-8 display.
2pub struct Display {
3    /// The display buffer.
4    pub display: [[u8; 128]; 64],
5    /// A dirty flag denoting whether the display buffer has changed or not. This can be used by a frontend
6    /// to minimize drawing calls when the display is unchanged. When reading the display buffer, the
7    /// frontend should unset this flag.
8    pub dirty: bool,
9    /// A flag denoting whether the display buffer is cleared or not. This can be used by a frontend to quickly
10    /// clear the display rather than drawing the empty display buffer.
11    pub clear: bool,
12    /// A flag denoting whether the display is currently in high-resolution mode or not.
13    pub hires: bool,
14    /// The width of the current display in pixels.
15    pub width: u8,
16    /// The height of the current display in pixels.
17    pub height: u8,
18    /// The currently active bitplane, for XO-CHIP compatibility.
19    pub active_plane: u8,
20}
21
22impl Display {
23    /// Create a new CHIP-8 display.
24    pub fn new() -> Display {
25        Display {
26            display: [[0; 128]; 64],
27            dirty: false,
28            clear: true,
29            hires: false,
30            width: 64,
31            height: 32,
32            active_plane: 1,
33        }
34    }
35
36    /// Clear the currently active display plane.
37    pub fn clear(&mut self, all_planes: bool) {
38        for y in self.display.iter_mut() {
39            for pixel in y.iter_mut() {
40                if all_planes {
41                    *pixel = 0;
42                } else {
43                    *pixel &= !self.active_plane;
44                }
45            }
46        }
47
48        self.dirty = true;
49        self.clear = true;
50    }
51
52    /// Draw a sprite at the given coordinates in the currently active display plane.
53    // TODO: Observe clip and collision quirks.
54    pub fn draw(&mut self, sprite: Vec<Vec<u8>>, x: u8, y: u8) -> u8 {
55        let x = x % self.width as u8;
56        let y = y % self.height as u8;
57        let mut collision = 0;
58        for (row, sprite_row) in sprite.into_iter().enumerate() {
59            if row + y as usize >= self.height as usize {
60                break;
61            }
62            for (col, pixel) in sprite_row.iter().enumerate() {
63                if col + x as usize >= self.width as usize {
64                    break;
65                }
66                if *pixel == 1 {
67                    let display_pixel = &mut self.display[y as usize + row][x as usize + col];
68                    if *display_pixel & self.active_plane == 0 {
69                        *display_pixel |= self.active_plane;
70                    } else {
71                        *display_pixel &= !self.active_plane;
72                        collision = 1;
73                    };
74                }
75            }
76        }
77        self.clear = false;
78        self.dirty = true;
79        collision
80    }
81
82    /// Scroll the currently active display plane up.
83    pub fn scroll_up(&mut self, pixels: u8) {
84        if !self.clear && pixels > 0 {
85            for y in pixels..self.height {
86                for x in 0..self.width {
87                    self.display[(y - pixels) as usize][x as usize] |=
88                        self.display[y as usize][x as usize] & self.active_plane;
89                    self.display[y as usize][x as usize] &= !self.active_plane;
90                }
91            }
92            for y in (self.height - pixels)..self.height {
93                for x in 0..=self.width {
94                    self.display[y as usize][x as usize] &= !self.active_plane;
95                }
96            }
97
98            self.dirty = true;
99        }
100    }
101
102    /// Scroll the currently active display plane down.
103    pub fn scroll_down(&mut self, pixels: u8) {
104        if !self.clear && pixels > 0 {
105            for y in (0..self.height - pixels).rev() {
106                for x in 0..self.width {
107                    self.display[(y + pixels) as usize][x as usize] |=
108                        self.display[y as usize][x as usize] & self.active_plane;
109                    self.display[y as usize][x as usize] &= !self.active_plane;
110                }
111            }
112            for y in 0..pixels {
113                for x in 0..self.width {
114                    self.display[y as usize][x as usize] &= !self.active_plane;
115                }
116            }
117
118            self.dirty = true;
119        }
120    }
121
122    /// Scroll the currently active display plane left.
123    pub fn scroll_left(&mut self, pixels: u8) {
124        if !self.clear && pixels > 0 {
125            for y in 0..self.height {
126                for x in pixels..self.width {
127                    self.display[y as usize][(x - pixels) as usize] |=
128                        self.display[y as usize][x as usize] & self.active_plane;
129                    self.display[y as usize][x as usize] &= !self.active_plane;
130                }
131            }
132            for y in 0..self.height {
133                for x in (self.width - pixels)..self.width {
134                    self.display[y as usize][x as usize] &= !self.active_plane;
135                }
136            }
137
138            self.dirty = true;
139        }
140    }
141
142    /// Scroll the currently active display plane right.
143    pub fn scroll_right(&mut self, pixels: u8) {
144        if !self.clear && pixels > 0 {
145            for y in 0..self.height {
146                for x in (0..self.width - pixels).rev() {
147                    self.display[y as usize][(x + pixels) as usize] |=
148                        self.display[y as usize][x as usize] & self.active_plane;
149                    self.display[y as usize][x as usize] &= !self.active_plane;
150                }
151            }
152            for y in 0..self.height {
153                for x in 0..pixels {
154                    self.display[y as usize][x as usize] &= !self.active_plane;
155                }
156            }
157
158            self.dirty = true;
159        }
160    }
161
162    /// Change the currently active plane.
163    pub fn plane(&mut self, plane: u8) {
164        self.active_plane = plane;
165    }
166
167    /// Switch to high-resolution mode.
168    pub fn hires(&mut self, clear: bool) {
169        self.hires = true;
170        self.width = 128;
171        self.height = 64;
172        if clear && !self.clear {
173            self.clear(true);
174            self.clear = true;
175        }
176    }
177
178    /// Switch to low-resolution mode.
179    pub fn lores(&mut self, clear: bool) {
180        self.hires = false;
181        self.width = 64;
182        self.height = 32;
183        if clear && !self.clear {
184            self.clear(true);
185            self.clear = true;
186        }
187    }
188}
189
190impl Default for Display {
191    fn default() -> Self {
192        Self::new()
193    }
194}