···
1
+
pub const VRAM_BEGIN: usize = 0x8000;
2
+
pub const VRAM_END: usize = 0x9FFF;
3
+
pub const VRAM_SIZE: usize = VRAM_END - VRAM_BEGIN + 1;
5
+
#[derive(Copy, Clone, Debug, PartialEq)]
6
+
pub enum TilePixelValue {
13
+
impl TilePixelValue {
14
+
/// Convert pixel value to grayscale color (0-255)
15
+
pub fn to_grayscale(&self) -> u8 {
17
+
TilePixelValue::Zero => 255, // White
18
+
TilePixelValue::One => 170, // Light gray (66% brightness)
19
+
TilePixelValue::Two => 85, // Dark gray (33% brightness)
20
+
TilePixelValue::Three => 0, // Black
24
+
/// Convert pixel value to RGB color tuple
25
+
pub fn to_rgb(&self) -> (u8, u8, u8) {
26
+
let gray = self.to_grayscale();
30
+
/// Convert pixel value to classic Game Boy green colors
31
+
pub fn to_gameboy_green(&self) -> (u8, u8, u8) {
33
+
TilePixelValue::Zero => (224, 248, 208), // Lightest green
34
+
TilePixelValue::One => (136, 192, 112), // Light green
35
+
TilePixelValue::Two => (52, 104, 86), // Dark green
36
+
TilePixelValue::Three => (8, 24, 32), // Darkest green/black
41
+
type Tile = [[TilePixelValue; 8]; 8];
43
+
fn empty_tile() -> Tile {
44
+
[[TilePixelValue::Zero; 8]; 8]
48
+
vram: [u8; VRAM_SIZE],
49
+
tile_set: [Tile; 384], // 384 tiles total (256 from first set + 128 from second set)
53
+
pub fn new() -> Self {
55
+
vram: [0; VRAM_SIZE],
56
+
tile_set: [empty_tile(); 384],
60
+
pub fn read_vram(&self, address: usize) -> u8 {
64
+
pub fn write_vram(&mut self, index: usize, value: u8) {
65
+
self.vram[index] = value;
67
+
// If our index is greater than 0x1800, we're not writing to the tile set storage
68
+
// so we can just return.
69
+
if index >= 0x1800 {
73
+
// Tiles rows are encoded in two bytes with the first byte always
74
+
// on an even address. Bitwise ANDing the address with 0xffe
75
+
// gives us the address of the first byte.
76
+
let normalized_index = index & 0xFFFE;
78
+
// First we need to get the two bytes that encode the tile row.
79
+
let byte1 = self.vram[normalized_index];
80
+
let byte2 = self.vram[normalized_index + 1];
82
+
// A tile is 8 rows tall. Since each row is encoded with two bytes a tile
83
+
// is therefore 16 bytes in total.
84
+
let tile_index = index / 16;
85
+
// Every two bytes is a new row
86
+
let row_index = (index % 16) / 2;
88
+
// Now we're going to loop 8 times to get the 8 pixels that make up a given row.
89
+
for pixel_index in 0..8 {
90
+
let mask = 1 << (7 - pixel_index);
91
+
let lsb = byte1 & mask;
92
+
let msb = byte2 & mask;
94
+
let value = match (lsb != 0, msb != 0) {
95
+
(true, true) => TilePixelValue::Three,
96
+
(false, true) => TilePixelValue::Two,
97
+
(true, false) => TilePixelValue::One,
98
+
(false, false) => TilePixelValue::Zero,
101
+
self.tile_set[tile_index][row_index][pixel_index] = value;
105
+
/// Get a tile by its index
106
+
pub fn get_tile(&self, tile_index: usize) -> Option<&Tile> {
107
+
if tile_index < self.tile_set.len() {
108
+
Some(&self.tile_set[tile_index])
114
+
/// Render a tile to a color buffer (64 pixels as RGB values)
115
+
pub fn render_tile_to_rgb(&self, tile_index: usize) -> Option<[(u8, u8, u8); 64]> {
116
+
let tile = self.get_tile(tile_index)?;
117
+
let mut color_buffer = [(0, 0, 0); 64];
119
+
for (row_idx, row) in tile.iter().enumerate() {
120
+
for (col_idx, &pixel) in row.iter().enumerate() {
121
+
let buffer_index = row_idx * 8 + col_idx;
122
+
color_buffer[buffer_index] = pixel.to_gameboy_green();
129
+
/// Render a tile to grayscale buffer (64 pixels as grayscale values)
130
+
pub fn render_tile_to_grayscale(&self, tile_index: usize) -> Option<[u8; 64]> {
131
+
let tile = self.get_tile(tile_index)?;
132
+
let mut gray_buffer = [0u8; 64];
134
+
for (row_idx, row) in tile.iter().enumerate() {
135
+
for (col_idx, &pixel) in row.iter().enumerate() {
136
+
let buffer_index = row_idx * 8 + col_idx;
137
+
gray_buffer[buffer_index] = pixel.to_grayscale();
144
+
/// Render multiple tiles in a grid pattern
145
+
pub fn render_tile_map(
147
+
tile_indices: &[u8],
150
+
) -> Vec<(u8, u8, u8)> {
151
+
let total_pixels = map_width * 8 * map_height * 8; // 8x8 pixels per tile
152
+
let mut color_buffer = vec![(0, 0, 0); total_pixels];
154
+
for (map_idx, &tile_idx) in tile_indices.iter().enumerate() {
155
+
if let Some(tile) = self.get_tile(tile_idx as usize) {
156
+
let tile_x = map_idx % map_width;
157
+
let tile_y = map_idx / map_width;
159
+
for (row_idx, row) in tile.iter().enumerate() {
160
+
for (col_idx, &pixel) in row.iter().enumerate() {
161
+
let pixel_x = tile_x * 8 + col_idx;
162
+
let pixel_y = tile_y * 8 + row_idx;
163
+
let buffer_index = pixel_y * (map_width * 8) + pixel_x;
165
+
if buffer_index < color_buffer.len() {
166
+
color_buffer[buffer_index] = pixel.to_gameboy_green();
176
+
/// Debug function to print a tile as ASCII art
177
+
pub fn print_tile_ascii(&self, tile_index: usize) {
178
+
if let Some(tile) = self.get_tile(tile_index) {
179
+
// println!("Tile {}:", tile_index);
181
+
for &pixel in row {
182
+
let char = match pixel {
183
+
TilePixelValue::Zero => '░', // Light
184
+
TilePixelValue::One => '▒', // Light gray
185
+
TilePixelValue::Two => '▓', // Dark gray
186
+
TilePixelValue::Three => '█', // Dark
188
+
print!("{}", char);
193
+
println!("Tile {} not found", tile_index);