mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-08 14:07:47 -06:00
refactor: separate map parsing into MapTileParser, get tests working
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
use glam::Vec2;
|
||||
|
||||
use crate::entity::direction::DIRECTIONS;
|
||||
|
||||
use super::direction::Direction;
|
||||
|
||||
/// A unique identifier for a node, represented by its index in the graph's storage.
|
||||
|
||||
11
src/lib.rs
Normal file
11
src/lib.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
//! Pac-Man game library crate.
|
||||
|
||||
pub mod app;
|
||||
pub mod asset;
|
||||
pub mod audio;
|
||||
pub mod constants;
|
||||
pub mod emscripten;
|
||||
pub mod entity;
|
||||
pub mod game;
|
||||
pub mod map;
|
||||
pub mod texture;
|
||||
206
src/map.rs
206
src/map.rs
@@ -13,6 +13,114 @@ use tracing::debug;
|
||||
use crate::entity::graph::{Graph, Node, NodeId};
|
||||
use crate::texture::text::TextTexture;
|
||||
|
||||
/// Error type for map parsing operations.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ParseError {
|
||||
#[error("Unknown character in board: {0}")]
|
||||
UnknownCharacter(char),
|
||||
#[error("House door must have exactly 2 positions, found {0}")]
|
||||
InvalidHouseDoorCount(usize),
|
||||
}
|
||||
|
||||
/// Represents the parsed data from a raw board layout.
|
||||
#[derive(Debug)]
|
||||
pub struct ParsedMap {
|
||||
/// The parsed tile layout.
|
||||
pub tiles: [[MapTile; BOARD_CELL_SIZE.y as usize]; BOARD_CELL_SIZE.x as usize],
|
||||
/// The positions of the house door tiles.
|
||||
pub house_door: [Option<IVec2>; 2],
|
||||
/// The positions of the tunnel end tiles.
|
||||
pub tunnel_ends: [Option<IVec2>; 2],
|
||||
}
|
||||
|
||||
/// Parser for converting raw board layouts into structured map data.
|
||||
pub struct MapTileParser;
|
||||
|
||||
impl MapTileParser {
|
||||
/// Parses a single character into a map tile.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `c` - The character to parse
|
||||
/// * `_x` - The x coordinate of the character (unused but kept for API consistency)
|
||||
/// * `_y` - The y coordinate of the character (unused but kept for API consistency)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The parsed map tile, or an error if the character is unknown.
|
||||
pub fn parse_character(c: char) -> Result<MapTile, ParseError> {
|
||||
match c {
|
||||
'#' => Ok(MapTile::Wall),
|
||||
'.' => Ok(MapTile::Pellet),
|
||||
'o' => Ok(MapTile::PowerPellet),
|
||||
' ' => Ok(MapTile::Empty),
|
||||
'T' => Ok(MapTile::Tunnel),
|
||||
c @ '0'..='4' => Ok(MapTile::StartingPosition(c.to_digit(10).unwrap() as u8)),
|
||||
'=' => Ok(MapTile::Wall), // House door is represented as a wall tile
|
||||
_ => Err(ParseError::UnknownCharacter(c)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a raw board layout into structured map data.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `raw_board` - The raw board layout as an array of strings
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The parsed map data, or an error if parsing fails.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the board contains unknown characters or if the house door
|
||||
/// is not properly defined by exactly two '=' characters.
|
||||
pub fn parse_board(raw_board: [&str; BOARD_CELL_SIZE.y as usize]) -> Result<ParsedMap, ParseError> {
|
||||
let mut tiles = [[MapTile::Empty; BOARD_CELL_SIZE.y as usize]; BOARD_CELL_SIZE.x as usize];
|
||||
let mut house_door = [None; 2];
|
||||
let mut tunnel_ends = [None; 2];
|
||||
|
||||
for (y, line) in raw_board.iter().enumerate().take(BOARD_CELL_SIZE.y as usize) {
|
||||
for (x, character) in line.chars().enumerate().take(BOARD_CELL_SIZE.x as usize) {
|
||||
let tile = Self::parse_character(character)?;
|
||||
|
||||
// Track special positions
|
||||
match tile {
|
||||
MapTile::Tunnel => {
|
||||
if tunnel_ends[0].is_none() {
|
||||
tunnel_ends[0] = Some(IVec2::new(x as i32, y as i32));
|
||||
} else {
|
||||
tunnel_ends[1] = Some(IVec2::new(x as i32, y as i32));
|
||||
}
|
||||
}
|
||||
MapTile::Wall if character == '=' => {
|
||||
if house_door[0].is_none() {
|
||||
house_door[0] = Some(IVec2::new(x as i32, y as i32));
|
||||
} else {
|
||||
house_door[1] = Some(IVec2::new(x as i32, y as i32));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
tiles[x][y] = tile;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate house door configuration
|
||||
let house_door_count = house_door.iter().filter(|x| x.is_some()).count();
|
||||
if house_door_count != 2 {
|
||||
return Err(ParseError::InvalidHouseDoorCount(house_door_count));
|
||||
}
|
||||
|
||||
Ok(ParsedMap {
|
||||
tiles,
|
||||
house_door,
|
||||
tunnel_ends,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The game map, responsible for holding the tile-based layout and the navigation graph.
|
||||
///
|
||||
/// The map is represented as a 2D array of `MapTile`s. It also stores a navigation
|
||||
@@ -38,38 +146,11 @@ impl Map {
|
||||
/// This function will panic if the board layout contains unknown characters or if
|
||||
/// the house door is not defined by exactly two '=' characters.
|
||||
pub fn new(raw_board: [&str; BOARD_CELL_SIZE.y as usize]) -> Map {
|
||||
let mut map = [[MapTile::Empty; BOARD_CELL_SIZE.y as usize]; BOARD_CELL_SIZE.x as usize];
|
||||
let mut house_door = [None; 2];
|
||||
let mut tunnel_ends = [None; 2];
|
||||
for (y, line) in raw_board.iter().enumerate().take(BOARD_CELL_SIZE.y as usize) {
|
||||
for (x, character) in line.chars().enumerate().take(BOARD_CELL_SIZE.x as usize) {
|
||||
let tile = match character {
|
||||
'#' => MapTile::Wall,
|
||||
'.' => MapTile::Pellet,
|
||||
'o' => MapTile::PowerPellet,
|
||||
' ' => MapTile::Empty,
|
||||
'T' => {
|
||||
if tunnel_ends[0].is_none() {
|
||||
tunnel_ends[0] = Some(IVec2::new(x as i32, y as i32));
|
||||
} else {
|
||||
tunnel_ends[1] = Some(IVec2::new(x as i32, y as i32));
|
||||
}
|
||||
MapTile::Tunnel
|
||||
}
|
||||
c @ '0'..='4' => MapTile::StartingPosition(c.to_digit(10).unwrap() as u8),
|
||||
'=' => {
|
||||
if house_door[0].is_none() {
|
||||
house_door[0] = Some(IVec2::new(x as i32, y as i32));
|
||||
} else {
|
||||
house_door[1] = Some(IVec2::new(x as i32, y as i32));
|
||||
}
|
||||
MapTile::Wall
|
||||
}
|
||||
_ => panic!("Unknown character in board: {character}"),
|
||||
};
|
||||
map[x][y] = tile;
|
||||
}
|
||||
}
|
||||
let parsed_map = MapTileParser::parse_board(raw_board).expect("Failed to parse board layout");
|
||||
|
||||
let map = parsed_map.tiles;
|
||||
let house_door = parsed_map.house_door;
|
||||
let tunnel_ends = parsed_map.tunnel_ends;
|
||||
|
||||
let mut graph = Graph::new();
|
||||
let mut grid_to_node = HashMap::new();
|
||||
@@ -169,10 +250,6 @@ impl Map {
|
||||
}
|
||||
}
|
||||
|
||||
if house_door.iter().filter(|x| x.is_some()).count() != 2 {
|
||||
panic!("House door must have exactly 2 positions");
|
||||
}
|
||||
|
||||
// Calculate the position of the house entrance node
|
||||
let (house_entrance_node_id, house_entrance_node_position) = {
|
||||
// Translate the grid positions to the actual node ids
|
||||
@@ -397,3 +474,60 @@ impl Map {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::constants::RAW_BOARD;
|
||||
|
||||
#[test]
|
||||
fn test_parse_character() {
|
||||
assert!(matches!(MapTileParser::parse_character('#').unwrap(), MapTile::Wall));
|
||||
assert!(matches!(MapTileParser::parse_character('.').unwrap(), MapTile::Pellet));
|
||||
assert!(matches!(MapTileParser::parse_character('o').unwrap(), MapTile::PowerPellet));
|
||||
assert!(matches!(MapTileParser::parse_character(' ').unwrap(), MapTile::Empty));
|
||||
assert!(matches!(MapTileParser::parse_character('T').unwrap(), MapTile::Tunnel));
|
||||
assert!(matches!(
|
||||
MapTileParser::parse_character('0').unwrap(),
|
||||
MapTile::StartingPosition(0)
|
||||
));
|
||||
assert!(matches!(
|
||||
MapTileParser::parse_character('4').unwrap(),
|
||||
MapTile::StartingPosition(4)
|
||||
));
|
||||
assert!(matches!(MapTileParser::parse_character('=').unwrap(), MapTile::Wall));
|
||||
|
||||
// Test invalid character
|
||||
assert!(MapTileParser::parse_character('X').is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_board() {
|
||||
let result = MapTileParser::parse_board(RAW_BOARD);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let parsed = result.unwrap();
|
||||
|
||||
// Verify we have tiles
|
||||
assert_eq!(parsed.tiles.len(), BOARD_CELL_SIZE.x as usize);
|
||||
assert_eq!(parsed.tiles[0].len(), BOARD_CELL_SIZE.y as usize);
|
||||
|
||||
// Verify we found house door positions
|
||||
assert!(parsed.house_door[0].is_some());
|
||||
assert!(parsed.house_door[1].is_some());
|
||||
|
||||
// Verify we found tunnel ends
|
||||
assert!(parsed.tunnel_ends[0].is_some());
|
||||
assert!(parsed.tunnel_ends[1].is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_board_invalid_character() {
|
||||
let mut invalid_board = RAW_BOARD.clone();
|
||||
invalid_board[0] = "###########################X";
|
||||
|
||||
let result = MapTileParser::parse_board(invalid_board);
|
||||
assert!(result.is_err());
|
||||
assert!(matches!(result.unwrap_err(), ParseError::UnknownCharacter('X')));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,18 +7,13 @@
|
||||
//! # Example Usage
|
||||
//!
|
||||
//! ```rust
|
||||
//! use crate::texture::text::TextTexture;
|
||||
//! use std::rc::Rc;
|
||||
//! use pacman::texture::text::TextTexture;
|
||||
//!
|
||||
//! // Create a text texture with 1.0 scale (8x8 pixels per character)
|
||||
//! let mut text_renderer = TextTexture::new(atlas.clone(), 1.0);
|
||||
//! let mut text_renderer = TextTexture::new(1.0);
|
||||
//!
|
||||
//! // Render text at position (100, 50)
|
||||
//! text_renderer.render(canvas, "PAC-MAN", glam::UVec2::new(100, 50))?;
|
||||
//!
|
||||
//! // Change scale for larger text
|
||||
//! // Set scale for larger text
|
||||
//! text_renderer.set_scale(2.0);
|
||||
//! text_renderer.render(canvas, "SCORE: 1000", glam::UVec2::new(50, 100))?;
|
||||
//!
|
||||
//! // Calculate text width for positioning
|
||||
//! let width = text_renderer.text_width("GAME OVER");
|
||||
|
||||
Reference in New Issue
Block a user