Playing around with reading gameboy roms, and maybe emulation
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

cleanup

+7 -555
-229
Cargo.lock
··· 5 5 [[package]] 6 6 name = "GameBoyPlayground" 7 7 version = "0.1.0" 8 - dependencies = [ 9 - "minifb", 10 - ] 11 - 12 - [[package]] 13 - name = "bitflags" 14 - version = "1.3.2" 15 - source = "registry+https://github.com/rust-lang/crates.io-index" 16 - checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 17 - 18 - [[package]] 19 - name = "bitflags" 20 - version = "2.9.1" 21 - source = "registry+https://github.com/rust-lang/crates.io-index" 22 - checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 23 - 24 - [[package]] 25 - name = "cc" 26 - version = "1.2.30" 27 - source = "registry+https://github.com/rust-lang/crates.io-index" 28 - checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" 29 - dependencies = [ 30 - "shlex", 31 - ] 32 - 33 - [[package]] 34 - name = "cfg-if" 35 - version = "1.0.1" 36 - source = "registry+https://github.com/rust-lang/crates.io-index" 37 - checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" 38 - 39 - [[package]] 40 - name = "gdi32-sys" 41 - version = "0.1.2" 42 - source = "registry+https://github.com/rust-lang/crates.io-index" 43 - checksum = "8e3eb92c1107527888f86b6ebb0b7f82794777dbf172a932998660a0a2e26c11" 44 - dependencies = [ 45 - "winapi 0.2.8", 46 - "winapi-build", 47 - ] 48 - 49 - [[package]] 50 - name = "kernel32-sys" 51 - version = "0.2.2" 52 - source = "registry+https://github.com/rust-lang/crates.io-index" 53 - checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 54 - dependencies = [ 55 - "winapi 0.2.8", 56 - "winapi-build", 57 - ] 58 - 59 - [[package]] 60 - name = "lazy_static" 61 - version = "0.2.11" 62 - source = "registry+https://github.com/rust-lang/crates.io-index" 63 - checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" 64 - 65 - [[package]] 66 - name = "lazy_static" 67 - version = "1.5.0" 68 - source = "registry+https://github.com/rust-lang/crates.io-index" 69 - checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 70 - 71 - [[package]] 72 - name = "libc" 73 - version = "0.2.174" 74 - source = "registry+https://github.com/rust-lang/crates.io-index" 75 - checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" 76 - 77 - [[package]] 78 - name = "libredox" 79 - version = "0.1.6" 80 - source = "registry+https://github.com/rust-lang/crates.io-index" 81 - checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" 82 - dependencies = [ 83 - "bitflags 2.9.1", 84 - "libc", 85 - "redox_syscall", 86 - ] 87 - 88 - [[package]] 89 - name = "minifb" 90 - version = "0.10.7" 91 - source = "registry+https://github.com/rust-lang/crates.io-index" 92 - checksum = "8cebe353532532dd30eaab68ece91e624ccf6f97453f2da84042f5915450a137" 93 - dependencies = [ 94 - "cc", 95 - "gdi32-sys", 96 - "kernel32-sys", 97 - "orbclient", 98 - "time", 99 - "user32-sys", 100 - "winapi 0.2.8", 101 - "x11-dl", 102 - ] 103 - 104 - [[package]] 105 - name = "orbclient" 106 - version = "0.3.48" 107 - source = "registry+https://github.com/rust-lang/crates.io-index" 108 - checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" 109 - dependencies = [ 110 - "libc", 111 - "libredox", 112 - "sdl2", 113 - "sdl2-sys", 114 - ] 115 - 116 - [[package]] 117 - name = "pkg-config" 118 - version = "0.3.32" 119 - source = "registry+https://github.com/rust-lang/crates.io-index" 120 - checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 121 - 122 - [[package]] 123 - name = "redox_syscall" 124 - version = "0.5.15" 125 - source = "registry+https://github.com/rust-lang/crates.io-index" 126 - checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" 127 - dependencies = [ 128 - "bitflags 2.9.1", 129 - ] 130 - 131 - [[package]] 132 - name = "sdl2" 133 - version = "0.35.2" 134 - source = "registry+https://github.com/rust-lang/crates.io-index" 135 - checksum = "f7959277b623f1fb9e04aea73686c3ca52f01b2145f8ea16f4ff30d8b7623b1a" 136 - dependencies = [ 137 - "bitflags 1.3.2", 138 - "lazy_static 1.5.0", 139 - "libc", 140 - "sdl2-sys", 141 - ] 142 - 143 - [[package]] 144 - name = "sdl2-sys" 145 - version = "0.35.2" 146 - source = "registry+https://github.com/rust-lang/crates.io-index" 147 - checksum = "e3586be2cf6c0a8099a79a12b4084357aa9b3e0b0d7980e3b67aaf7a9d55f9f0" 148 - dependencies = [ 149 - "cfg-if", 150 - "libc", 151 - "version-compare", 152 - ] 153 - 154 - [[package]] 155 - name = "shlex" 156 - version = "1.3.0" 157 - source = "registry+https://github.com/rust-lang/crates.io-index" 158 - checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 159 - 160 - [[package]] 161 - name = "time" 162 - version = "0.1.45" 163 - source = "registry+https://github.com/rust-lang/crates.io-index" 164 - checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" 165 - dependencies = [ 166 - "libc", 167 - "wasi", 168 - "winapi 0.3.9", 169 - ] 170 - 171 - [[package]] 172 - name = "user32-sys" 173 - version = "0.1.3" 174 - source = "registry+https://github.com/rust-lang/crates.io-index" 175 - checksum = "e6b719983b952c04198829b51653c06af36f0e44c967fcc1a2bb397ceafbf80a" 176 - dependencies = [ 177 - "winapi 0.2.8", 178 - "winapi-build", 179 - ] 180 - 181 - [[package]] 182 - name = "version-compare" 183 - version = "0.1.1" 184 - source = "registry+https://github.com/rust-lang/crates.io-index" 185 - checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" 186 - 187 - [[package]] 188 - name = "wasi" 189 - version = "0.10.0+wasi-snapshot-preview1" 190 - source = "registry+https://github.com/rust-lang/crates.io-index" 191 - checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 192 - 193 - [[package]] 194 - name = "winapi" 195 - version = "0.2.8" 196 - source = "registry+https://github.com/rust-lang/crates.io-index" 197 - checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 198 - 199 - [[package]] 200 - name = "winapi" 201 - version = "0.3.9" 202 - source = "registry+https://github.com/rust-lang/crates.io-index" 203 - checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 204 - dependencies = [ 205 - "winapi-i686-pc-windows-gnu", 206 - "winapi-x86_64-pc-windows-gnu", 207 - ] 208 - 209 - [[package]] 210 - name = "winapi-build" 211 - version = "0.1.1" 212 - source = "registry+https://github.com/rust-lang/crates.io-index" 213 - checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 214 - 215 - [[package]] 216 - name = "winapi-i686-pc-windows-gnu" 217 - version = "0.4.0" 218 - source = "registry+https://github.com/rust-lang/crates.io-index" 219 - checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 220 - 221 - [[package]] 222 - name = "winapi-x86_64-pc-windows-gnu" 223 - version = "0.4.0" 224 - source = "registry+https://github.com/rust-lang/crates.io-index" 225 - checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 226 - 227 - [[package]] 228 - name = "x11-dl" 229 - version = "2.14.0" 230 - source = "registry+https://github.com/rust-lang/crates.io-index" 231 - checksum = "326c500cdc166fd7c70dd8c8a829cd5c0ce7be5a5d98c25817de2b9bdc67faf8" 232 - dependencies = [ 233 - "lazy_static 0.2.11", 234 - "libc", 235 - "pkg-config", 236 - ]
-1
Cargo.toml
··· 4 4 edition = "2024" 5 5 6 6 [dependencies] 7 - minifb = "0.10.7"
+3 -9
src/cartridge_header.rs
··· 5 5 6 6 pub struct CartridgeHeader { 7 7 //Should be 80 bytes (0x014F(335) - 0x0100(256)) + 1 to include the last address 8 - pub buffer: [u8; 80], 8 + pub _buffer: [u8; 80], 9 9 pub title: [char; 16], 10 10 pub manufacturer_code: [char; 4], 11 11 pub cgb_flag: CGBFlag, ··· 49 49 for (i, true_logo_byte) in NINTENDO_LOGO.iter().enumerate() { 50 50 let rom_byte = nintendo_logo_from_rom[i]; 51 51 if rom_byte != *true_logo_byte { 52 - return Err(Error::CartridgeReadError); 52 + return Err(Error::NotAValidRom); 53 53 } 54 54 } 55 55 ··· 97 97 ]); 98 98 99 99 Ok(Self { 100 - buffer: header_buffer 100 + _buffer: header_buffer 101 101 .try_into() 102 102 .map_err(|_| Error::CartridgeReadError)?, 103 103 title: title_chars, ··· 114 114 header_checksum, 115 115 global_checksum, 116 116 }) 117 - } 118 - 119 - fn print_test(&self) { 120 - for byte in self.buffer.iter() { 121 - print!("{} ", *byte as char); 122 - } 123 117 } 124 118 }
+1 -1
src/enums.rs
··· 3 3 CartridgeHeaderStart = 0x0100, 4 4 CartridgeHeaderEnd = 0x014F, 5 5 // 0100-0103 — Entry point 6 - EntryPointEnd = 0x0103, 6 + _EntryPointEnd = 0x0103, 7 7 // 0104-0133 — Nintendo logo 8 8 NintendoLogoStart = 0x0104, 9 9 NintendoLogoEnd = 0x00133,
+3 -41
src/main.rs
··· 1 1 mod cartridge_header; 2 2 mod enums; 3 - mod tile_map; 3 + 4 4 use crate::cartridge_header::CartridgeHeader; 5 - use crate::enums::CartridgeHeaderAddress::OldLicenseeCode; 6 - use crate::enums::{ 7 - CGBFlag, CartridgeHeaderAddress, CartridgeType, DestinationCode, Error, RamSize, RomSize, 8 - }; 9 - use crate::tile_map::{GPU, VRAM_BEGIN, VRAM_END}; 10 - use minifb::{Key, Window, WindowOptions}; 5 + use crate::enums::DestinationCode; 11 6 use std::fs::File; 12 7 use std::io::Read; 13 - 14 - const WINDOW_DIMENSIONS: [usize; 2] = [(160 * 1), (144 * 1)]; 15 8 16 9 // https://github.com/ISSOtm/gb-bootroms/blob/2dce25910043ce2ad1d1d3691436f2c7aabbda00/src/dmg.asm#L259-L269 17 10 // Each tile is encoded using 2 (!) bytes ··· 40 33 rom_file.read_to_end(&mut rom_buffer)?; 41 34 let cart_header = match CartridgeHeader::parse(&*rom_buffer) { 42 35 Ok(header) => header, 43 - Err(err) => { 36 + Err(_err) => { 44 37 return Err(std::io::Error::new( 45 38 std::io::ErrorKind::Other, 46 39 "Rom failed to parse", ··· 73 66 println!("Version: {:?}", cart_header.version); 74 67 println!("Header Checksum: {:#X}", cart_header.header_checksum); 75 68 println!("Global Checksum: {:#X}", cart_header.global_checksum); 76 - 77 - let mut gpu = GPU::new(); 78 - let tile_map_buffer = &rom_buffer[VRAM_BEGIN as usize..VRAM_END as usize]; 79 - for (i, byte) in tile_map_buffer.iter().enumerate() { 80 - gpu.write_vram(i, *byte); 81 - } 82 - gpu.render_tile_to_rgb(0); 83 - // let range_of_tiles = 0..255; 84 - // for tile_id in range_of_tiles { 85 - // let idk = gpu.print_tile_ascii(tile_id); 86 - // println!("{:?}", idk); 87 - // } 88 - let mut window = Window::new( 89 - "DMG-01", 90 - WINDOW_DIMENSIONS[0], 91 - WINDOW_DIMENSIONS[1], 92 - WindowOptions { 93 - scale: minifb::Scale::X2, 94 - ..WindowOptions::default() 95 - }, 96 - ) 97 - .unwrap(); 98 - let mut tile_ids: Vec<u8> = (0..100).collect(); 99 - let tile_map_buffer = gpu.render_background_to_rgb(true, true, 25, 25); // let idk = gpu.render_tile_to_rgb(1).unwrap(); 100 - let buffer_u32: Vec<u32> = tile_map_buffer 101 - .iter() 102 - .map(|(r, g, b)| ((*r as u32) << 16) | ((*g as u32) << 8) | (*b as u32)) 103 - .collect(); 104 - while window.is_open() && !window.is_key_down(Key::Escape) { 105 - window.update_with_buffer(&buffer_u32).unwrap(); 106 - } 107 69 108 70 Ok(()) 109 71 }
-274
src/tile_map.rs
··· 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; 4 - 5 - // Tilemap locations in VRAM 6 - pub const TILEMAP_0_START: usize = 0x1800; // $9800 - $8000 = 0x1800 7 - pub const TILEMAP_1_START: usize = 0x1C00; // $9C00 - $8000 = 0x1C00 8 - pub const TILEMAP_SIZE: usize = 32 * 32; // 1024 bytes 9 - 10 - #[derive(Copy, Clone, Debug, PartialEq)] 11 - pub enum TilePixelValue { 12 - Zero, 13 - One, 14 - Two, 15 - Three, 16 - } 17 - 18 - impl TilePixelValue { 19 - /// Convert pixel value to grayscale color (0-255) 20 - pub fn to_grayscale(&self) -> u8 { 21 - match self { 22 - TilePixelValue::Zero => 255, // White 23 - TilePixelValue::One => 170, // Light gray (66% brightness) 24 - TilePixelValue::Two => 85, // Dark gray (33% brightness) 25 - TilePixelValue::Three => 0, // Black 26 - } 27 - } 28 - 29 - /// Convert pixel value to RGB color tuple 30 - pub fn to_rgb(&self) -> (u8, u8, u8) { 31 - let gray = self.to_grayscale(); 32 - (gray, gray, gray) 33 - } 34 - 35 - /// Convert pixel value to classic Game Boy green colors 36 - pub fn to_gameboy_green(&self) -> (u8, u8, u8) { 37 - match self { 38 - TilePixelValue::Zero => (224, 248, 208), // Lightest green 39 - TilePixelValue::One => (136, 192, 112), // Light green 40 - TilePixelValue::Two => (52, 104, 86), // Dark green 41 - TilePixelValue::Three => (8, 24, 32), // Darkest green/black 42 - } 43 - } 44 - } 45 - 46 - type Tile = [[TilePixelValue; 8]; 8]; 47 - 48 - fn empty_tile() -> Tile { 49 - [[TilePixelValue::Zero; 8]; 8] 50 - } 51 - 52 - pub struct GPU { 53 - vram: [u8; VRAM_SIZE], 54 - tile_set: [Tile; 384], // 384 tiles total (256 from first set + 128 from second set) 55 - } 56 - 57 - impl GPU { 58 - pub fn new() -> Self { 59 - Self { 60 - vram: [0; VRAM_SIZE], 61 - tile_set: [empty_tile(); 384], 62 - } 63 - } 64 - 65 - pub fn read_vram(&self, address: usize) -> u8 { 66 - self.vram[address] 67 - } 68 - 69 - pub fn write_vram(&mut self, index: usize, value: u8) { 70 - self.vram[index] = value; 71 - 72 - // If our index is greater than 0x1800, we're not writing to the tile set storage 73 - // so we can just return. 74 - if index >= 0x1800 { 75 - return; 76 - } 77 - 78 - // Tiles rows are encoded in two bytes with the first byte always 79 - // on an even address. Bitwise ANDing the address with 0xffe 80 - // gives us the address of the first byte. 81 - let normalized_index = index & 0xFFFE; 82 - 83 - // First we need to get the two bytes that encode the tile row. 84 - let byte1 = self.vram[normalized_index]; 85 - let byte2 = self.vram[normalized_index + 1]; 86 - 87 - // A tile is 8 rows tall. Since each row is encoded with two bytes a tile 88 - // is therefore 16 bytes in total. 89 - let tile_index = index / 16; 90 - // Every two bytes is a new row 91 - let row_index = (index % 16) / 2; 92 - 93 - // Now we're going to loop 8 times to get the 8 pixels that make up a given row. 94 - for pixel_index in 0..8 { 95 - let mask = 1 << (7 - pixel_index); 96 - let lsb = byte1 & mask; 97 - let msb = byte2 & mask; 98 - 99 - let value = match (lsb != 0, msb != 0) { 100 - (true, true) => TilePixelValue::Three, 101 - (false, true) => TilePixelValue::Two, 102 - (true, false) => TilePixelValue::One, 103 - (false, false) => TilePixelValue::Zero, 104 - }; 105 - 106 - self.tile_set[tile_index][row_index][pixel_index] = value; 107 - } 108 - } 109 - 110 - /// Get a tile by its index, handling Game Boy's two addressing modes 111 - pub fn get_tile(&self, tile_index: u8, use_signed_addressing: bool) -> Option<&Tile> { 112 - let actual_index = if use_signed_addressing { 113 - // Signed addressing mode: $8800-$97FF 114 - // Index 0-127 maps to tiles 256-383, index 128-255 maps to tiles 0-127 115 - if tile_index < 128 { 116 - 256 + tile_index as usize 117 - } else { 118 - (tile_index as i8 as i16 + 256) as usize 119 - } 120 - } else { 121 - // Unsigned addressing mode: $8000-$8FFF 122 - tile_index as usize 123 - }; 124 - 125 - if actual_index < self.tile_set.len() { 126 - Some(&self.tile_set[actual_index]) 127 - } else { 128 - None 129 - } 130 - } 131 - 132 - /// Read tilemap data from VRAM 133 - pub fn get_tilemap_data(&self, tilemap_select: bool) -> [u8; TILEMAP_SIZE] { 134 - let start_addr = if tilemap_select { 135 - TILEMAP_1_START 136 - } else { 137 - TILEMAP_0_START 138 - }; 139 - 140 - let mut tilemap = [0u8; TILEMAP_SIZE]; 141 - for i in 0..TILEMAP_SIZE { 142 - tilemap[i] = self.vram[start_addr + i]; 143 - } 144 - tilemap 145 - } 146 - 147 - /// Render the entire tilemap to RGB (256x256 pixels) 148 - pub fn render_full_tilemap_to_rgb( 149 - &self, 150 - tilemap_select: bool, 151 - use_signed_addressing: bool, 152 - ) -> Vec<(u8, u8, u8)> { 153 - let tilemap_data = self.get_tilemap_data(tilemap_select); 154 - let total_pixels = 256 * 256; // 32x32 tiles, each 8x8 pixels 155 - let mut color_buffer = vec![(0, 0, 0); total_pixels]; 156 - 157 - for tilemap_y in 0..32 { 158 - for tilemap_x in 0..32 { 159 - let tilemap_index = tilemap_y * 32 + tilemap_x; 160 - let tile_id = tilemap_data[tilemap_index]; 161 - 162 - if let Some(tile) = self.get_tile(tile_id, use_signed_addressing) { 163 - // Render this tile into the color buffer 164 - for tile_row in 0..8 { 165 - for tile_col in 0..8 { 166 - let pixel_x = tilemap_x * 8 + tile_col; 167 - let pixel_y = tilemap_y * 8 + tile_row; 168 - let buffer_index = pixel_y * 256 + pixel_x; 169 - 170 - if buffer_index < color_buffer.len() { 171 - color_buffer[buffer_index] = 172 - tile[tile_row][tile_col].to_gameboy_green(); 173 - } 174 - } 175 - } 176 - } 177 - } 178 - } 179 - 180 - color_buffer 181 - } 182 - 183 - /// Render a visible portion of the tilemap (160x144 pixels) with scrolling 184 - pub fn render_background_to_rgb( 185 - &self, 186 - tilemap_select: bool, 187 - use_signed_addressing: bool, 188 - scroll_x: u8, 189 - scroll_y: u8, 190 - ) -> Vec<(u8, u8, u8)> { 191 - let tilemap_data = self.get_tilemap_data(tilemap_select); 192 - let mut color_buffer = vec![(0, 0, 0); 160 * 144]; 193 - 194 - for screen_y in 0..144 { 195 - for screen_x in 0..160 { 196 - // Calculate the position in the 256x256 tilemap with wrapping 197 - let bg_x = ((screen_x as u16 + scroll_x as u16) % 256) as u8; 198 - let bg_y = ((screen_y as u16 + scroll_y as u16) % 256) as u8; 199 - 200 - // Which tile are we in? 201 - let tile_x = (bg_x / 8) as usize; 202 - let tile_y = (bg_y / 8) as usize; 203 - let tilemap_index = tile_y * 32 + tile_x; 204 - 205 - // Which pixel within that tile? 206 - let pixel_x = (bg_x % 8) as usize; 207 - let pixel_y = (bg_y % 8) as usize; 208 - 209 - let tile_id = tilemap_data[tilemap_index]; 210 - 211 - if let Some(tile) = self.get_tile(tile_id, use_signed_addressing) { 212 - let buffer_index = screen_y * 160 + screen_x; 213 - color_buffer[buffer_index] = tile[pixel_y][pixel_x].to_gameboy_green(); 214 - } 215 - } 216 - } 217 - 218 - color_buffer 219 - } 220 - 221 - /// Render a tile to a color buffer (64 pixels as RGB values) 222 - pub fn render_tile_to_rgb(&self, tile_index: usize) -> Option<[(u8, u8, u8); 64]> { 223 - if tile_index >= self.tile_set.len() { 224 - return None; 225 - } 226 - 227 - let tile = &self.tile_set[tile_index]; 228 - let mut color_buffer = [(0, 0, 0); 64]; 229 - 230 - for (row_idx, row) in tile.iter().enumerate() { 231 - for (col_idx, &pixel) in row.iter().enumerate() { 232 - let buffer_index = row_idx * 8 + col_idx; 233 - color_buffer[buffer_index] = pixel.to_gameboy_green(); 234 - } 235 - } 236 - 237 - Some(color_buffer) 238 - } 239 - 240 - /// Debug function to print tilemap as hex values 241 - pub fn print_tilemap_hex(&self, tilemap_select: bool) { 242 - let tilemap_data = self.get_tilemap_data(tilemap_select); 243 - println!("Tilemap {} contents:", if tilemap_select { 1 } else { 0 }); 244 - 245 - for row in 0..32 { 246 - for col in 0..32 { 247 - let index = row * 32 + col; 248 - print!("{:02X} ", tilemap_data[index]); 249 - } 250 - println!(); 251 - } 252 - } 253 - 254 - /// Debug function to print a tile as ASCII art 255 - pub fn print_tile_ascii(&self, tile_index: usize) { 256 - if let Some(tile) = self.tile_set.get(tile_index) { 257 - println!("Tile {}:", tile_index); 258 - for row in tile { 259 - for &pixel in row { 260 - let char = match pixel { 261 - TilePixelValue::Zero => '░', // Light 262 - TilePixelValue::One => '▒', // Light gray 263 - TilePixelValue::Two => '▓', // Dark gray 264 - TilePixelValue::Three => '█', // Dark 265 - }; 266 - print!("{}", char); 267 - } 268 - println!(); 269 - } 270 - } else { 271 - println!("Tile {} not found", tile_index); 272 - } 273 - } 274 - }