···
-
pub const VRAM_BEGIN: usize = 0x8000;
-
pub const VRAM_END: usize = 0x9FFF;
-
pub const VRAM_SIZE: usize = VRAM_END - VRAM_BEGIN + 1;
-
// Tilemap locations in VRAM
-
pub const TILEMAP_0_START: usize = 0x1800; // $9800 - $8000 = 0x1800
-
pub const TILEMAP_1_START: usize = 0x1C00; // $9C00 - $8000 = 0x1C00
-
pub const TILEMAP_SIZE: usize = 32 * 32; // 1024 bytes
-
#[derive(Copy, Clone, Debug, PartialEq)]
-
pub enum TilePixelValue {
-
/// Convert pixel value to grayscale color (0-255)
-
pub fn to_grayscale(&self) -> u8 {
-
TilePixelValue::Zero => 255, // White
-
TilePixelValue::One => 170, // Light gray (66% brightness)
-
TilePixelValue::Two => 85, // Dark gray (33% brightness)
-
TilePixelValue::Three => 0, // Black
-
/// Convert pixel value to RGB color tuple
-
pub fn to_rgb(&self) -> (u8, u8, u8) {
-
let gray = self.to_grayscale();
-
/// Convert pixel value to classic Game Boy green colors
-
pub fn to_gameboy_green(&self) -> (u8, u8, u8) {
-
TilePixelValue::Zero => (224, 248, 208), // Lightest green
-
TilePixelValue::One => (136, 192, 112), // Light green
-
TilePixelValue::Two => (52, 104, 86), // Dark green
-
TilePixelValue::Three => (8, 24, 32), // Darkest green/black
-
type Tile = [[TilePixelValue; 8]; 8];
-
fn empty_tile() -> Tile {
-
[[TilePixelValue::Zero; 8]; 8]
-
tile_set: [Tile; 384], // 384 tiles total (256 from first set + 128 from second set)
-
tile_set: [empty_tile(); 384],
-
pub fn read_vram(&self, address: usize) -> u8 {
-
pub fn write_vram(&mut self, index: usize, value: u8) {
-
self.vram[index] = value;
-
// If our index is greater than 0x1800, we're not writing to the tile set storage
-
// so we can just return.
-
// Tiles rows are encoded in two bytes with the first byte always
-
// on an even address. Bitwise ANDing the address with 0xffe
-
// gives us the address of the first byte.
-
let normalized_index = index & 0xFFFE;
-
// First we need to get the two bytes that encode the tile row.
-
let byte1 = self.vram[normalized_index];
-
let byte2 = self.vram[normalized_index + 1];
-
// A tile is 8 rows tall. Since each row is encoded with two bytes a tile
-
// is therefore 16 bytes in total.
-
let tile_index = index / 16;
-
// Every two bytes is a new row
-
let row_index = (index % 16) / 2;
-
// Now we're going to loop 8 times to get the 8 pixels that make up a given row.
-
for pixel_index in 0..8 {
-
let mask = 1 << (7 - pixel_index);
-
let lsb = byte1 & mask;
-
let msb = byte2 & mask;
-
let value = match (lsb != 0, msb != 0) {
-
(true, true) => TilePixelValue::Three,
-
(false, true) => TilePixelValue::Two,
-
(true, false) => TilePixelValue::One,
-
(false, false) => TilePixelValue::Zero,
-
self.tile_set[tile_index][row_index][pixel_index] = value;
-
/// Get a tile by its index, handling Game Boy's two addressing modes
-
pub fn get_tile(&self, tile_index: u8, use_signed_addressing: bool) -> Option<&Tile> {
-
let actual_index = if use_signed_addressing {
-
// Signed addressing mode: $8800-$97FF
-
// Index 0-127 maps to tiles 256-383, index 128-255 maps to tiles 0-127
-
256 + tile_index as usize
-
(tile_index as i8 as i16 + 256) as usize
-
// Unsigned addressing mode: $8000-$8FFF
-
if actual_index < self.tile_set.len() {
-
Some(&self.tile_set[actual_index])
-
/// Read tilemap data from VRAM
-
pub fn get_tilemap_data(&self, tilemap_select: bool) -> [u8; TILEMAP_SIZE] {
-
let start_addr = if tilemap_select {
-
let mut tilemap = [0u8; TILEMAP_SIZE];
-
for i in 0..TILEMAP_SIZE {
-
tilemap[i] = self.vram[start_addr + i];
-
/// Render the entire tilemap to RGB (256x256 pixels)
-
pub fn render_full_tilemap_to_rgb(
-
use_signed_addressing: bool,
-
) -> Vec<(u8, u8, u8)> {
-
let tilemap_data = self.get_tilemap_data(tilemap_select);
-
let total_pixels = 256 * 256; // 32x32 tiles, each 8x8 pixels
-
let mut color_buffer = vec![(0, 0, 0); total_pixels];
-
for tilemap_y in 0..32 {
-
for tilemap_x in 0..32 {
-
let tilemap_index = tilemap_y * 32 + tilemap_x;
-
let tile_id = tilemap_data[tilemap_index];
-
if let Some(tile) = self.get_tile(tile_id, use_signed_addressing) {
-
// Render this tile into the color buffer
-
let pixel_x = tilemap_x * 8 + tile_col;
-
let pixel_y = tilemap_y * 8 + tile_row;
-
let buffer_index = pixel_y * 256 + pixel_x;
-
if buffer_index < color_buffer.len() {
-
color_buffer[buffer_index] =
-
tile[tile_row][tile_col].to_gameboy_green();
-
/// Render a visible portion of the tilemap (160x144 pixels) with scrolling
-
pub fn render_background_to_rgb(
-
use_signed_addressing: bool,
-
) -> Vec<(u8, u8, u8)> {
-
let tilemap_data = self.get_tilemap_data(tilemap_select);
-
let mut color_buffer = vec![(0, 0, 0); 160 * 144];
-
for screen_y in 0..144 {
-
for screen_x in 0..160 {
-
// Calculate the position in the 256x256 tilemap with wrapping
-
let bg_x = ((screen_x as u16 + scroll_x as u16) % 256) as u8;
-
let bg_y = ((screen_y as u16 + scroll_y as u16) % 256) as u8;
-
// Which tile are we in?
-
let tile_x = (bg_x / 8) as usize;
-
let tile_y = (bg_y / 8) as usize;
-
let tilemap_index = tile_y * 32 + tile_x;
-
// Which pixel within that tile?
-
let pixel_x = (bg_x % 8) as usize;
-
let pixel_y = (bg_y % 8) as usize;
-
let tile_id = tilemap_data[tilemap_index];
-
if let Some(tile) = self.get_tile(tile_id, use_signed_addressing) {
-
let buffer_index = screen_y * 160 + screen_x;
-
color_buffer[buffer_index] = tile[pixel_y][pixel_x].to_gameboy_green();
-
/// Render a tile to a color buffer (64 pixels as RGB values)
-
pub fn render_tile_to_rgb(&self, tile_index: usize) -> Option<[(u8, u8, u8); 64]> {
-
if tile_index >= self.tile_set.len() {
-
let tile = &self.tile_set[tile_index];
-
let mut color_buffer = [(0, 0, 0); 64];
-
for (row_idx, row) in tile.iter().enumerate() {
-
for (col_idx, &pixel) in row.iter().enumerate() {
-
let buffer_index = row_idx * 8 + col_idx;
-
color_buffer[buffer_index] = pixel.to_gameboy_green();
-
/// Debug function to print tilemap as hex values
-
pub fn print_tilemap_hex(&self, tilemap_select: bool) {
-
let tilemap_data = self.get_tilemap_data(tilemap_select);
-
println!("Tilemap {} contents:", if tilemap_select { 1 } else { 0 });
-
let index = row * 32 + col;
-
print!("{:02X} ", tilemap_data[index]);
-
/// Debug function to print a tile as ASCII art
-
pub fn print_tile_ascii(&self, tile_index: usize) {
-
if let Some(tile) = self.tile_set.get(tile_index) {
-
println!("Tile {}:", tile_index);
-
let char = match pixel {
-
TilePixelValue::Zero => '░', // Light
-
TilePixelValue::One => '▒', // Light gray
-
TilePixelValue::Two => '▓', // Dark gray
-
TilePixelValue::Three => '█', // Dark
-
println!("Tile {} not found", tile_index);