Grid/Base/GridMap.cs
using System;
using System.Collections.Generic;
using System.Numerics;
public sealed class GridMap
{
public int Width { get; }
public int Height { get; }
public char[,] Cells { get; }
public float GridSize { get; set; } = 128f;
public Vector3 CenterWorld { get; private set; }
public GridMap(string gridText)
{
string[] lines = gridText.Split(
new[] { "\r\n", "\n" },
StringSplitOptions.RemoveEmptyEntries
);
int width = 0;
foreach (var line in lines)
if (line.Length > width)
width = line.Length;
Width = width;
Height = lines.Length;
Cells = new char[Width, Height];
for (int y = 0; y < Height; y++)
{
var line = lines[y];
for (int x = 0; x < Width; x++)
Cells[x, y] = x < line.Length ? line[x] : ' ';
}
CenterWorld = GridToWorld(Width / 2, Height / 2, 0f);
}
// =========================
// COORDINATES
// =========================
public Vector3 GridToWorld(int x, int y, float z)
{
float half = GridSize * 0.5f;
float worldX = (x - Width * 0.5f) * GridSize + half;
float worldY = (y - Height * 0.5f) * GridSize + half;
return new Vector3(worldX, worldY, z);
}
public (int x, int y) WorldToGrid(Vector3 worldPos)
{
int x = (int)MathF.Floor(worldPos.x / GridSize + Width * 0.5f);
int y = (int)MathF.Floor(worldPos.y / GridSize + Height * 0.5f);
return (x, y);
}
// =========================
// BASIC
// =========================
public bool InBounds(int x, int y)
=> x >= 0 && y >= 0 && x < Width && y < Height;
public char GetCell(int x, int y)
=> InBounds(x, y) ? Cells[x, y] : '#';
public bool SetCell(int x, int y, char value)
{
if (!InBounds(x, y))
return false;
Cells[x, y] = value;
return true;
}
// =========================
// CELL TYPES (NEW MODEL)
// =========================
public bool IsOutside(int x, int y) => GetCell(x, y) == '.';
public bool IsInterior(int x, int y) => GetCell(x, y) == ',';
public bool IsWall(int x, int y)
{
char c = GetCell(x, y);
return c == '-' || c == '#' || c == 'B';
}
public bool IsOuterWall(int x, int y) => GetCell(x, y) == '#';
public bool IsInnerWall(int x, int y) => GetCell(x, y) == '-';
/// <summary>
/// Räjähdettävä heikko seinä – tuhoutuu yhdestä osumasta, ketjureaktio.
/// </summary>
public bool IsExplosiveWall(int x, int y) => GetCell(x, y) == 'B';
public bool IsDoor(int x, int y) => GetCell(x, y) == '+';
public bool IsTrap(int x, int y) => GetCell(x, y) == '^';
public bool IsEnemy(int x, int y) => GetCell(x, y) == 'X';
public bool IsSpawn(int x, int y) => GetCell(x, y) == '@';
// =========================
// 🔧 BACKWARD COMPATIBILITY
// =========================
// Vanha spawneri odottaa tätä
public bool IsButton(int x, int y)
=> GetCell(x, y) == '0';
// Vanha spawneri käyttää tätä seinien spawniin
public bool IsWallLike(int x, int y)
{
char c = GetCell(x, y);
return c == '#' || c == '-' || c == '+' || c == 'B';
}
// =========================
// WALKABILITY / GAMEPLAY
// =========================
public bool IsWalkable(int x, int y)
{
if (!InBounds(x, y))
return false;
char c = Cells[x, y];
return c == '.' || c == ',' || c == '+' || c == '@' || c == 'X';
// Huom: 'B' ei ole käveltävä – se on seinä
}
public bool IsIndestructible(int x, int y)
=> IsOuterWall(x, y);
// =========================
// HELPERS
// =========================
public bool TouchesWall(int x, int y)
{
return IsWall(x - 1, y) ||
IsWall(x + 1, y) ||
IsWall(x, y - 1) ||
IsWall(x, y + 1);
}
public bool AdjacentTo(int x, int y, params char[] targets)
{
foreach (var t in targets)
{
if (GetCell(x - 1, y) == t ||
GetCell(x + 1, y) == t ||
GetCell(x, y - 1) == t ||
GetCell(x, y + 1) == t)
return true;
}
return false;
}
public (int x, int y)[] GetAllCellsOfType(char cellType)
{
var list = new List<(int, int)>();
for (int y = 0; y < Height; y++)
for (int x = 0; x < Width; x++)
if (Cells[x, y] == cellType)
list.Add((x, y));
return list.ToArray();
}
public (int x, int y)[] GetAllSpawnPoints(char spawnChar = '@')
=> GetAllCellsOfType(spawnChar);
public List<(int minX, int minY, int maxX, int maxY)> FindRooms()
{
var visited = new bool[Width, Height];
var rooms = new List<(int, int, int, int)>();
for (int x = 0; x < Width; x++)
{
for (int y = 0; y < Height; y++)
{
if (visited[x, y] || !IsInterior(x, y))
continue;
int minX = x, maxX = x;
int minY = y, maxY = y;
var stack = new Stack<(int, int)>();
stack.Push((x, y));
visited[x, y] = true;
while (stack.Count > 0)
{
var (cx, cy) = stack.Pop();
minX = Math.Min(minX, cx);
maxX = Math.Max(maxX, cx);
minY = Math.Min(minY, cy);
maxY = Math.Max(maxY, cy);
foreach (var (nx, ny) in new (int, int)[]
{
(cx+1, cy),
(cx-1, cy),
(cx, cy+1),
(cx, cy-1)
})
{
if (nx < 0 || ny < 0 || nx >= Width || ny >= Height)
continue;
if (visited[nx, ny] || !IsInterior(nx, ny))
continue;
visited[nx, ny] = true;
stack.Push((nx, ny));
}
}
rooms.Add((minX, minY, maxX, maxY));
}
}
Log.Info($"Rooms found: {rooms.Count}");
return rooms;
}
}