using UnityEngine; using System.Collections.Generic; using System.Linq; /// /// Holds all data for a generated maze /// This is the core representation that can be used for rendering, pathfinding, and AI /// public class MazeData { public int Width { get; private set; } public int Height { get; private set; } public MazeTile[,] Tiles { get; private set; } public List Rooms { get; private set; } = new(); public List StartPoints { get; private set; } = new(); public List ExitPoints { get; private set; } = new(); private int nextRoomId = 0; // O(1) room lookup by ID – avoids scanning Rooms list every call private readonly Dictionary _roomById = new(); // Cached typed-room lists – invalidated when a room is added private readonly Dictionary> _roomsByType = new(); public MazeData(int width, int height) { Width = width; Height = height; Tiles = new MazeTile[width, height]; // Initialize all tiles as walls for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { Tiles[x, y] = new MazeTile(x, y); } } } /// /// Sets a tile at the given position /// public void SetTile(int x, int y, MazeTile tile) { if (IsInBounds(x, y)) { Tiles[x, y] = tile; } } /// /// Gets a tile at the given position /// public MazeTile GetTile(int x, int y) { if (IsInBounds(x, y)) { return Tiles[x, y]; } return null; } /// /// Checks if coordinates are within maze bounds /// public bool IsInBounds(int x, int y) { return x >= 0 && x < Width && y >= 0 && y < Height; } /// /// Checks if a tile is walkable /// public bool IsWalkable(int x, int y) { var tile = GetTile(x, y); return tile != null && tile.IsWalkable(); } /// /// Fills a rectangular area with floor tiles and assigns to a room /// public int FillRoom(int minX, int minY, int maxX, int maxY, int roomId, MazeTile.TerrainType terrain = MazeTile.TerrainType.Normal) { int changedTiles = 0; for (int x = minX; x <= maxX; x++) { for (int y = minY; y <= maxY; y++) { if (IsInBounds(x, y)) { var tile = GetTile(x, y); if (tile.Type != MazeTile.TileType.Floor) { changedTiles++; } tile.Type = MazeTile.TileType.Floor; tile.Terrain = terrain; tile.RoomId = roomId; } } } return changedTiles; } /// /// Creates floor tiles along a path (for hallways/corridors) /// public int DrawPath(List path, int hallwayWidth, int roomId = -1) { int changedTiles = 0; foreach (var point in path) { for (int dx = -hallwayWidth / 2; dx <= hallwayWidth / 2; dx++) { for (int dy = -hallwayWidth / 2; dy <= hallwayWidth / 2; dy++) { int x = point.x + dx; int y = point.y + dy; if (IsInBounds(x, y)) { var tile = GetTile(x, y); if (tile.Type != MazeTile.TileType.Floor) // Don't overwrite existing floors { changedTiles++; tile.Type = MazeTile.TileType.Floor; tile.Terrain = MazeTile.TerrainType.Normal; if (tile.RoomId == -1) { tile.RoomId = roomId; } } } } } } return changedTiles; } /// /// Adds a room to the maze and registers it in the fast-lookup dictionary. /// public MazeRoom AddRoom(int minX, int minY, int maxX, int maxY, MazeRoom.RoomType roomType = MazeRoom.RoomType.Normal) { var room = new MazeRoom(nextRoomId++, minX, minY, maxX, maxY, roomType); Rooms.Add(room); _roomById[room.Id] = room; _roomsByType.Clear(); // invalidate type cache return room; } /// /// Gets all rooms of a specific type. Always queries fresh since room types /// can be changed after AddRoom() (e.g. exit rooms assigned later by generator). /// public List GetRoomsByType(MazeRoom.RoomType type) { return Rooms.Where(r => r.Type == type).ToList(); } /// /// Gets a room by its ID in O(1). /// public MazeRoom GetRoomById(int id) => _roomById.TryGetValue(id, out var r) ? r : null; /// /// Gets the room at a specific tile position. O(1) via dictionary lookup. /// public MazeRoom GetRoomAtTile(int x, int y) { var tile = GetTile(x, y); if (tile != null && tile.RoomId >= 0 && _roomById.TryGetValue(tile.RoomId, out var room)) return room; return null; } /// /// Adds a start point /// public void AddStartPoint(Vector2Int point) { if (IsWalkable(point.x, point.y)) { StartPoints.Add(point); } } /// /// Adds an exit point /// public void AddExitPoint(Vector2Int point) { if (IsWalkable(point.x, point.y)) { ExitPoints.Add(point); } } /// /// Gets all adjacent walkable tiles /// public List GetAdjacentWalkable(int x, int y) { var adjacent = new List(); Vector2Int[] directions = new[] { new Vector2Int(x + 1, y), new Vector2Int(x - 1, y), new Vector2Int(x, y + 1), new Vector2Int(x, y - 1), }; foreach (var dir in directions) { if (IsWalkable(dir.x, dir.y)) { adjacent.Add(dir); } } return adjacent; } /// /// Gets statistics about the maze /// public string GetStatistics() { int floorTiles = 0; int wallTiles = 0; for (int x = 0; x < Width; x++) { for (int y = 0; y < Height; y++) { if (GetTile(x, y).IsWalkable()) floorTiles++; else wallTiles++; } } return $"Maze Statistics:\n" + $" Size: {Width}x{Height}\n" + $" Rooms: {Rooms.Count}\n" + $" Floor Tiles: {floorTiles}\n" + $" Wall Tiles: {wallTiles}\n" + $" Start Points: {StartPoints.Count}\n" + $" Exit Points: {ExitPoints.Count}"; } }