diff --git a/src/constants.rs b/src/constants.rs index f5e2317..bd4ca91 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -37,8 +37,6 @@ pub enum MapTile { Pellet, /// A power pellet. PowerPellet, - /// A starting position for an entity. - StartingPosition(u8), /// A tunnel tile. Tunnel, } @@ -68,7 +66,7 @@ pub const RAW_BOARD: [&str; BOARD_CELL_SIZE.y as usize] = [ "#............##............#", "#.####.#####.##.#####.####.#", "#.####.#####.##.#####.####.#", - "#o..##.......0 .......##..o#", + "#o..##.......X .......##..o#", "###.##.##.########.##.##.###", "###.##.##.########.##.##.###", "#......##....##....##......#", @@ -139,30 +137,12 @@ mod tests { fn test_map_tile_variants() { assert_ne!(MapTile::Empty, MapTile::Wall); assert_ne!(MapTile::Pellet, MapTile::PowerPellet); - assert_ne!(MapTile::StartingPosition(0), MapTile::StartingPosition(1)); assert_ne!(MapTile::Tunnel, MapTile::Empty); } - #[test] - fn test_map_tile_starting_position() { - let pos0 = MapTile::StartingPosition(0); - let pos1 = MapTile::StartingPosition(1); - let pos0_clone = MapTile::StartingPosition(0); - - assert_eq!(pos0, pos0_clone); - assert_ne!(pos0, pos1); - } - - #[test] - fn test_map_tile_debug() { - let tile = MapTile::Wall; - let debug_str = format!("{:?}", tile); - assert!(!debug_str.is_empty()); - } - #[test] fn test_map_tile_clone() { - let original = MapTile::StartingPosition(5); + let original = MapTile::Wall; let cloned = original; assert_eq!(original, cloned); } @@ -217,10 +197,10 @@ mod tests { #[test] fn test_raw_board_starting_position() { - // Should have a starting position '0' for Pac-Man + // Should have a starting position 'X' for Pac-Man let mut found_starting_position = false; for row in RAW_BOARD.iter() { - if row.contains('0') { + if row.contains('X') { found_starting_position = true; break; } diff --git a/src/map/builder.rs b/src/map/builder.rs index 6488b3d..fb62b12 100644 --- a/src/map/builder.rs +++ b/src/map/builder.rs @@ -19,11 +19,7 @@ pub struct NodePositions { pub clyde: NodeId, } -/// 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 -/// `Graph` that entities like Pac-Man and ghosts use for movement. The graph is -/// generated from the walkable tiles of the map. +/// The main map structure containing the game board and navigation graph. pub struct Map { /// The current state of the map. current: [[MapTile; BOARD_CELL_SIZE.y as usize]; BOARD_CELL_SIZE.x as usize], @@ -33,6 +29,8 @@ pub struct Map { pub grid_to_node: HashMap, /// A mapping of the starting positions of the entities. pub start_positions: NodePositions, + /// Pac-Man's starting position. + pacman_start: Option, } impl Map { @@ -51,6 +49,7 @@ impl Map { let map = parsed_map.tiles; let house_door = parsed_map.house_door; let tunnel_ends = parsed_map.tunnel_ends; + let pacman_start = parsed_map.pacman_start; let mut graph = Graph::new(); let mut grid_to_node = HashMap::new(); @@ -58,25 +57,7 @@ impl Map { let cell_offset = Vec2::splat(CELL_SIZE as f32 / 2.0); // Find a starting point for the graph generation, preferably Pac-Man's position. - let start_pos = (0..BOARD_CELL_SIZE.y) - .flat_map(|y| (0..BOARD_CELL_SIZE.x).map(move |x| IVec2::new(x as i32, y as i32))) - .find(|&p| matches!(map[p.x as usize][p.y as usize], MapTile::StartingPosition(0))) - .unwrap_or_else(|| { - // Fallback to any valid walkable tile if Pac-Man's start is not found - (0..BOARD_CELL_SIZE.y) - .flat_map(|y| (0..BOARD_CELL_SIZE.x).map(move |x| IVec2::new(x as i32, y as i32))) - .find(|&p| { - matches!( - map[p.x as usize][p.y as usize], - MapTile::Pellet - | MapTile::PowerPellet - | MapTile::Empty - | MapTile::Tunnel - | MapTile::StartingPosition(_) - ) - }) - .expect("No valid starting position found on map for graph generation") - }); + let start_pos = pacman_start.expect("Pac-Man's starting position not found"); // Add the starting position to the graph/queue let mut queue = VecDeque::new(); @@ -110,7 +91,7 @@ impl Map { // Skip if the new position is not a walkable tile if matches!( map[new_position.x as usize][new_position.y as usize], - MapTile::Pellet | MapTile::PowerPellet | MapTile::Empty | MapTile::Tunnel | MapTile::StartingPosition(_) + MapTile::Pellet | MapTile::PowerPellet | MapTile::Empty | MapTile::Tunnel ) { // Add the new position to the graph/queue let pos = Vec2::new( @@ -167,9 +148,10 @@ impl Map { Map { current: map, - grid_to_node, graph, + grid_to_node, start_positions, + pacman_start, } } @@ -183,14 +165,9 @@ impl Map { /// /// The starting position as a grid coordinate (`UVec2`), or `None` if not found. pub fn find_starting_position(&self, entity_id: u8) -> Option { - for (x, col) in self.current.iter().enumerate().take(BOARD_CELL_SIZE.x as usize) { - for (y, &cell) in col.iter().enumerate().take(BOARD_CELL_SIZE.y as usize) { - if let MapTile::StartingPosition(id) = cell { - if id == entity_id { - return Some(UVec2::new(x as u32, y as u32)); - } - } - } + // For now, only Pac-Man (entity_id 0) is supported + if entity_id == 0 { + return self.pacman_start.map(|pos| UVec2::new(pos.x as u32, pos.y as u32)); } None } @@ -401,7 +378,7 @@ mod tests { board[20] = "#............##............#"; board[21] = "#.####.#####.##.#####.####.#"; board[22] = "#.####.#####.##.#####.####.#"; - board[23] = "#o..##.......0 .......##..o#"; + board[23] = "#o..##.......X .......##..o#"; board[24] = "###.##.##.########.##.##.###"; board[25] = "###.##.##.########.##.##.###"; board[26] = "#......##....##....##......#"; diff --git a/src/map/parser.rs b/src/map/parser.rs index daf5954..fcde796 100644 --- a/src/map/parser.rs +++ b/src/map/parser.rs @@ -22,6 +22,8 @@ pub struct ParsedMap { pub house_door: [Option; 2], /// The positions of the tunnel end tiles. pub tunnel_ends: [Option; 2], + /// Pac-Man's starting position. + pub pacman_start: Option, } /// Parser for converting raw board layouts into structured map data. @@ -44,8 +46,8 @@ impl MapTileParser { '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 + 'X' => Ok(MapTile::Empty), // Pac-Man's starting position, treated as empty + '=' => Ok(MapTile::Wall), // House door is represented as a wall tile _ => Err(ParseError::UnknownCharacter(c)), } } @@ -68,6 +70,7 @@ impl MapTileParser { 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]; + let mut pacman_start: Option = None; 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) { @@ -92,6 +95,11 @@ impl MapTileParser { _ => {} } + // Track Pac-Man's starting position + if character == 'X' { + pacman_start = Some(IVec2::new(x as i32, y as i32)); + } + tiles[x][y] = tile; } } @@ -106,6 +114,7 @@ impl MapTileParser { tiles, house_door, tunnel_ends, + pacman_start, }) } } @@ -122,18 +131,11 @@ mod tests { 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('X').unwrap(), MapTile::Empty)); assert!(matches!(MapTileParser::parse_character('=').unwrap(), MapTile::Wall)); // Test invalid character - assert!(MapTileParser::parse_character('X').is_err()); + assert!(MapTileParser::parse_character('Z').is_err()); } #[test] @@ -154,15 +156,18 @@ mod tests { // Verify we found tunnel ends assert!(parsed.tunnel_ends[0].is_some()); assert!(parsed.tunnel_ends[1].is_some()); + + // Verify we found Pac-Man's starting position + assert!(parsed.pacman_start.is_some()); } #[test] fn test_parse_board_invalid_character() { let mut invalid_board = RAW_BOARD.clone(); - invalid_board[0] = "###########################X"; + invalid_board[0] = "###########################Z"; let result = MapTileParser::parse_board(invalid_board); assert!(result.is_err()); - assert!(matches!(result.unwrap_err(), ParseError::UnknownCharacter('X'))); + assert!(matches!(result.unwrap_err(), ParseError::UnknownCharacter('Z'))); } }