Grid/Base/GridEntityPlacer.cs
using System;
using System.Collections.Generic;
namespace Sandbox.Grid.Base
{
/// <summary>
/// GridEntityPlacer – vastaa vihollisten, spawnien, ovien ja ansojen sijoittelusta gridille.
/// Sisältää ovi- ja käytävähelperit sekä ansa-algoritmin.
/// </summary>
internal class GridEntityPlacer
{
private readonly char[,] grid;
private readonly Random rng;
private readonly int W;
private readonly int H;
public GridEntityPlacer(char[,] grid, Random rng, int w, int h)
{
this.grid = grid;
this.rng = rng;
this.W = w;
this.H = h;
}
// --------------------------------------------------
// Viholliset ja spawnit
// --------------------------------------------------
/// <summary>
/// Sijoittaa annetun määrän vihollisia ('X') satunnaisiin huoneisiin.
/// </summary>
public void PlaceEnemies(int count)
{
int placed = 0;
int safety = 0;
while (placed < count && safety++ < 10_000)
{
int x = rng.Next(2, W - 2);
int y = rng.Next(2, H - 2);
if (grid[x, y] != ',') continue;
if (AdjacentTo(x, y, '@', '+', '^')) continue;
grid[x, y] = 'X';
placed++;
}
}
/// <summary>
/// Sijoittaa annetun määrän spawn-pisteitä ('@') satunnaisiin huoneisiin.
/// </summary>
public void PlaceSpawns(int count)
{
int placed = 0;
int safety = 0;
while (placed < count && safety++ < 10_000)
{
int x = rng.Next(2, W - 2);
int y = rng.Next(2, H - 2);
if (grid[x, y] != ',') continue; // 🔑 MUUTOS
if (AdjacentTo(x, y, 'X', '^')) continue;
grid[x, y] = '@';
placed++;
}
}
// --------------------------------------------------
// Ovet
// --------------------------------------------------
/// <summary>
/// Sijoittaa ovet ('+') talojen seiniin, joissa toisella puolella on lattia ja toisella ulkolattia.
/// Luo myös oven molemmin puolin laatan ('0').
/// </summary>
public void PlaceHouseDoors()
{
for (int y = 1; y < H - 1; y++)
{
for (int x = 1; x < W - 1; x++)
{
if (grid[x, y] != '-')
continue;
bool horizontal =
(grid[x - 1, y] == ',' && grid[x + 1, y] == '.') ||
(grid[x + 1, y] == ',' && grid[x - 1, y] == '.');
bool vertical =
(grid[x, y - 1] == ',' && grid[x, y + 1] == '.') ||
(grid[x, y + 1] == ',' && grid[x, y - 1] == '.');
if (!(horizontal || vertical))
continue;
if (rng.NextDouble() > 0.35)
continue;
grid[x, y] = '+';
// 🔥 tee oven molemmille puolille laatat
if (horizontal)
{
grid[x - 1, y] = '0';
grid[x + 1, y] = '0';
}
else if (vertical)
{
grid[x, y - 1] = '0';
grid[x, y + 1] = '0';
}
}
}
}
/// <summary>
/// Sijoittaa ovet huoneiden ja käytävien välissä oleviin seiniin ('#').
/// </summary>
public void PlaceDoorsBetweenRoomsAndCorridors()
{
for (int y = 1; y < H - 1; y++)
{
for (int x = 1; x < W - 1; x++)
{
if (grid[x, y] != '#')
continue;
bool roomLeft = grid[x - 1, y] == ',';
bool roomRight = grid[x + 1, y] == ',';
bool corLeft = grid[x - 1, y] == '.';
bool corRight = grid[x + 1, y] == '.';
bool roomUp = grid[x, y - 1] == ',';
bool roomDown = grid[x, y + 1] == ',';
bool corUp = grid[x, y - 1] == '.';
bool corDown = grid[x, y + 1] == '.';
// vaakasuuntainen ovi
if ((roomLeft && corRight) || (corLeft && roomRight))
{
grid[x, y] = '+';
continue;
}
// pystysuuntainen ovi
if ((roomUp && corDown) || (corUp && roomDown))
{
grid[x, y] = '+';
}
}
}
}
/// <summary>
/// Yrittää sijoittaa oven ruutuun, jos siinä on käytävää ('.').
/// </summary>
public void TryDoor(int x, int y)
{
// ovi = huoneen vieressä käytävä
if (grid[x, y] == '.')
{
grid[x, y] = '+'; // ovi
}
}
/// <summary>
/// Luo oven satunnaisesti valittuun validiin paikkaan ja asettaa laatan molemmille puolille.
/// Heittää poikkeuksen, jos yhtään paikkaa ei löydy.
/// </summary>
public void CreateDoorsWithTiles()
{
var candidates = FindDoorCandidates();
if (candidates.Count == 0)
throw new Exception("Ei yhtään validia ovipaikkaa");
var chosen = candidates[rng.Next(candidates.Count)];
if (chosen.horizontal)
PlaceDoorHorizontalSafe(chosen.x, chosen.y);
else
PlaceDoorVerticalSafe(chosen.x, chosen.y);
}
/// <summary>
/// Etsii kaikki validit ovipaikat seinistä, joilla on lattia molemmin puolin.
/// </summary>
public List<(int x, int y, bool horizontal)> FindDoorCandidates()
{
var list = new List<(int, int, bool)>();
for (int y = 1; y < H - 1; y++)
for (int x = 1; x < W - 1; x++)
{
if (!IsWall(x, y)) continue;
// vaakasuuntainen ovi
if (IsFloor(x - 1, y) && IsFloor(x + 1, y))
list.Add((x, y, true));
// pystysuuntainen ovi
else if (IsFloor(x, y - 1) && IsFloor(x, y + 1))
list.Add((x, y, false));
}
return list;
}
/// <summary>
/// Sijoittaa vaakasuuntaisen oven turvallisesti (tarkistaa seinän ja lattian).
/// </summary>
public void PlaceDoorHorizontalSafe(int x, int y)
{
// oven täytyy olla seinässä
if (!IsWall(x, y))
throw new Exception("Ovi ei ole seinässä");
// oven molemmilla puolilla täytyy OLLA lattia
if (!IsFloor(x - 1, y) || !IsFloor(x + 1, y))
throw new Exception("Oven molemmilla puolilla ei ole lattiaa");
grid[x, y] = '+';
grid[x - 1, y] = '0';
grid[x + 1, y] = '0';
}
/// <summary>
/// Sijoittaa pystysuuntaisen oven turvallisesti (tarkistaa seinän ja lattian).
/// </summary>
public void PlaceDoorVerticalSafe(int x, int y)
{
if (!IsWall(x, y))
throw new Exception("Ovi ei ole seinässä");
if (!IsFloor(x, y - 1) || !IsFloor(x, y + 1))
throw new Exception("Oven molemmilla puolilla ei ole lattiaa");
grid[x, y] = '+';
grid[x, y - 1] = '0';
grid[x, y + 1] = '0';
}
/// <summary>
/// Varmistaa, että ruudussa on lattia ('.'). Heittää poikkeuksen, jos ruudussa on seinä.
/// </summary>
public void EnsureFloor(int x, int y)
{
if (grid[x, y] == '#')
throw new Exception("Laatta yrittää mennä seinään");
grid[x, y] = '.';
}
/// <summary>
/// Onko ruudussa lattia ('.').
/// </summary>
public bool IsFloor(int x, int y) => grid[x, y] == '.';
/// <summary>
/// Onko ruudussa seinä ('#'). Huom: eri kuin IsWall(char), joka tarkistaa myös '-'.
/// </summary>
public bool IsWall(int x, int y) => grid[x, y] == '#';
// --------------------------------------------------
// Käytävät
// --------------------------------------------------
/// <summary>
/// Kaivaa vaakasuuntaisen käytävän fromX:stä toWallX:ään (jättää toWallX seinäksi).
/// </summary>
public void DigCorridorToWallHorizontal(int fromX, int toWallX, int y)
{
int step = fromX < toWallX ? 1 : -1;
for (int x = fromX; x != toWallX; x += step)
{
if (grid[x, y] == '#')
grid[x, y] = '.';
}
// HUOM: toWallX jää '#':ksi
}
/// <summary>
/// Kaivaa pystysuuntaisen käytävän y1:stä y2:een annetussa x-sarakkeessa.
/// </summary>
public void DigVerticalCorridor(int y1, int y2, int x)
{
int from = Math.Min(y1, y2);
int to = Math.Max(y1, y2);
for (int y = from; y <= to; y++)
{
if (grid[x, y] == '#')
grid[x, y] = '.';
}
}
// --------------------------------------------------
// Ansat
// --------------------------------------------------
/// <summary>
/// Sijoittaa ansasiemeniä ovien ('+') viereisiin ruutuihin.
/// </summary>
public void PlaceTrapSeeds()
{
for (int y = 1; y < H - 1; y++)
{
for (int x = 1; x < W - 1; x++)
{
if (grid[x, y] != '+')
continue;
TryTrap(x + 1, y);
TryTrap(x - 1, y);
TryTrap(x, y + 1);
TryTrap(x, y - 1);
}
}
}
/// <summary>
/// Yrittää sijoittaa ansan ('^') huoneen lattialle (',') satunnaisella todennäköisyydellä.
/// </summary>
public void TryTrap(int x, int y)
{
if (grid[x, y] != ',')
return;
if (rng.NextDouble() < 0.35)
grid[x, y] = '^';
}
/// <summary>
/// Levittää ansia olemassa olevista ansista viereisiin huoneiden lattioihin.
/// Toistaa maxIterations kertaa.
/// </summary>
public void SpreadTrapsFromTraps()
{
const int maxIterations = 3;
for (int i = 0; i < maxIterations; i++)
{
bool any = false;
for (int y = 1; y < H - 1; y++)
for (int x = 1; x < W - 1; x++)
if (grid[x, y] == ',' && AdjacentTo(x, y, '^'))
{
if (rng.NextDouble() < 0.25)
{
grid[x, y] = '^';
any = true;
}
}
if (!any) break;
}
}
/// <summary>
/// Yrittää sijoittaa ansan ulkolattialle ('.'), jos ei koske seinään
/// ja on viereisessä ruudussa ovi/laatta/ansa.
/// </summary>
public void TryPlaceTrap(int x, int y)
{
if (grid[x, y] != '.') return;
if (TouchesWall(x, y)) return;
if (!AdjacentTo(x, y, '0', '+', '^')) return;
grid[x, y] = '^';
}
// --------------------------------------------------
// Apumetodit
// --------------------------------------------------
/// <summary>
/// Onko merkki seinä ('-' tai '#').
/// </summary>
private bool IsWall(char c) => c == '-' || c == '#';
/// <summary>
/// Koskeeko ruutu seinää neljänsuunnassa.
/// </summary>
private bool TouchesWall(int x, int y)
{
return IsWall(grid[x - 1, y]) ||
IsWall(grid[x + 1, y]) ||
IsWall(grid[x, y - 1]) ||
IsWall(grid[x, y + 1]);
}
/// <summary>
/// Onko ruutu viereisessä ruudussa jokin annetuista merkeistä.
/// </summary>
private bool AdjacentTo(int x, int y, params char[] targets)
{
foreach (var t in targets)
{
if (grid[x - 1, y] == t ||
grid[x + 1, y] == t ||
grid[x, y - 1] == t ||
grid[x, y + 1] == t)
return true;
}
return false;
}
}
}