Grid/Base/PrisonGenerator.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace Sandbox.Grid.Base
{
/// <summary>
/// PrisonGenerator – luo vankila-layoutin gridille.
/// Tuottaa soluhuonerivejä, käytäviä, yhteisötilan (recreational yard) ja räjähdettäviä heikkoja seiniä.
///
/// Grid-merkit:
/// ' ' = ei käytössä
/// '.' = ulkolattia / käytävä
/// ',' = soluhuoneen lattia
/// '-' = vankilan sisäseinä (tuhoutuva)
/// '#' = ulkoreunan seinä (tuhoutumaton)
/// '+' = ovi
/// '^' = ansa
/// '@' = spawn
/// 'X' = vihollinen
/// 'B' = räjähdettävä heikko seinä (tuhoutuu 1 osumasta, ketjureaktio)
/// </summary>
public class PrisonGenerator
{
const int W = 70;
const int H = 70;
char[,] grid = new char[W, H];
Random rng = new Random();
List<(int x, int y)> cellCenters = new();
public PrisonGenerator()
{
}
/// <summary>
/// Generoi vankila-layoutin ja palauttaa grid-taulukon.
/// </summary>
public char[,] Generate()
{
// 1. Alusta grid tyhjäksi
for (int y = 0; y < H; y++)
for (int x = 0; x < W; x++)
grid[x, y] = ' ';
// 2. Rakenna ulkoreunan seinät
BuildBoundaryWalls();
// 3. Rakenna soluhuonerivit (vasen ja oikea puoli)
BuildCellBlocks();
// 4. Rakenna keskelle yhteisötila (recreational yard)
BuildCentralYard();
// 5. Rakenna seinät soluhuoneiden ympärille
BuildCellWalls();
// 6. Täytä jäljellä olevat tyhjät ruudut käytävällä ('.')
FillCorridors();
// 7. Sijoita räjähdettävät heikot seinät satunnaisiin soluseiniin
PlaceExplosiveWalls();
// 8. Sijoita viholliset ja spawnit
PlaceEnemies(10);
PlaceSpawns(3);
return grid;
}
/// <summary>
/// Rakentaa ulkoreunan seinät ('#').
/// </summary>
private void BuildBoundaryWalls()
{
for (int x = 0; x < W; x++)
{
grid[x, 0] = '#';
grid[x, H - 1] = '#';
}
for (int y = 0; y < H; y++)
{
grid[0, y] = '#';
grid[W - 1, y] = '#';
}
}
/// <summary>
/// Rakentaa soluhuonerivit vasemmalle ja oikealle puolelle.
/// Kumpaankin puoleen tulee 2 rivitä soluhuoneita, jokainen solu 4x3 ruutua.
/// Rivien väliin jää käytävä (3 ruutua leveä).
/// </summary>
private void BuildCellBlocks()
{
// Soluhuoneen mitat
const int cellW = 4;
const int cellH = 3;
const int cellsPerRow = 6; // kuinka monta solua per rivi
const int cellRowsPerSide = 4; // kuinka monta soluriviä per puoli
// VASEN PUOLI – 2 riviä soluja
BuildCellBlockSide(startX: 3, startY: 3, cellW, cellH, cellsPerRow, cellRowsPerSide, 1);
// OIKEA PUOLI – 2 riviä soluja (peilattu)
BuildCellBlockSide(startX: W - 3 - cellsPerRow * cellW - 3, startY: 3, cellW, cellH, cellsPerRow, cellRowsPerSide, -1);
}
/// <summary>
/// Rakentaa soluhuonerivit yhdelle puolelle.
/// dir = 1 (vasen), dir = -1 (oikea, kasvaa vasemmalle)
/// </summary>
private void BuildCellBlockSide(int startX, int startY, int cellW, int cellH, int cellsPerRow, int cellRows, int dir)
{
int corridorWidth = 3; // käytävä solurivien välissä
for (int row = 0; row < cellRows; row++)
{
int yBase = startY + row * (cellH + 1 + corridorWidth);
if (yBase + cellH >= H - 2)
break;
for (int col = 0; col < cellsPerRow; col++)
{
int xBase = startX + col * (cellW + 1) * dir;
if (dir == -1)
xBase = startX - col * (cellW + 1);
if (xBase < 2 || xBase + cellW >= W - 2)
break;
// Täytä soluhuoneen lattia (',')
for (int y = yBase; y < yBase + cellH; y++)
{
for (int x = xBase; x < xBase + cellW; x++)
{
if (x > 0 && x < W - 1 && y > 0 && y < H - 1)
grid[x, y] = ',';
}
}
cellCenters.Add((xBase + cellW / 2, yBase + cellH / 2));
}
}
}
/// <summary>
/// Rakentaa keskelle suuren yhteisötilan (recreational yard).
/// Kooltaan noin 20x20 ruutua, lattia ','.
/// </summary>
private void BuildCentralYard()
{
int yardX = W / 2 - 10;
int yardY = H / 2 - 10;
int yardW = 20;
int yardH = 20;
for (int y = yardY; y < yardY + yardH; y++)
{
for (int x = yardX; x < yardX + yardW; x++)
{
if (x > 0 && x < W - 1 && y > 0 && y < H - 1)
grid[x, y] = ',';
}
}
}
/// <summary>
/// Rakentaa seinät ('-') kaikkien soluhuoneiden ja yhteisötilan ympärille.
/// Käy läpi gridin ja asettaa seinän tyhjien (' ') ruutujen viereen jotka koskettavat lattiaa (',').
/// </summary>
private void BuildCellWalls()
{
// Ensimmäinen passio: rakenna seinät lattian viereen
for (int y = 1; y < H - 1; y++)
{
for (int x = 1; x < W - 1; x++)
{
if (grid[x, y] != ' ')
continue;
// Jos viereisessä ruudussa on lattia (','), tee seinä
if (grid[x - 1, y] == ',' || grid[x + 1, y] == ',' ||
grid[x, y - 1] == ',' || grid[x, y + 1] == ',')
{
grid[x, y] = '-';
}
}
}
// Toinen passio: täytä seinien väliset kulmat
for (int y = 1; y < H - 1; y++)
{
for (int x = 1; x < W - 1; x++)
{
if (grid[x, y] != ' ')
continue;
// Vaakaseinät molemmin puolin
if (grid[x - 1, y] == '-' && grid[x + 1, y] == '-')
{
grid[x, y] = '-';
continue;
}
// Pystyseinät molemmin puolin
if (grid[x, y - 1] == '-' && grid[x, y + 1] == '-')
{
grid[x, y] = '-';
continue;
}
// L-kulmat
if ((grid[x - 1, y] == '-' && grid[x, y - 1] == '-') ||
(grid[x + 1, y] == '-' && grid[x, y - 1] == '-') ||
(grid[x - 1, y] == '-' && grid[x, y + 1] == '-') ||
(grid[x + 1, y] == '-' && grid[x, y + 1] == '-'))
{
grid[x, y] = '-';
}
}
}
}
/// <summary>
/// Täyttää jäljellä olevat tyhjät ruudut käytävällä ('.').
/// </summary>
private void FillCorridors()
{
for (int y = 0; y < H; y++)
for (int x = 0; x < W; x++)
if (grid[x, y] == ' ')
grid[x, y] = '.';
}
/// <summary>
/// Sijoittaa räjähdettäviä heikkoja seiniä ('B') satunnaisiin soluseiniin.
/// Heikot seinät tuhoutuvat yhdestä osumasta ja voivat aiheuttaa ketjureaktion.
/// </summary>
private void PlaceExplosiveWalls()
{
// Kerää kaikki '-' seinät jotka ovat soluhuoneiden välissä
var candidates = new List<(int x, int y)>();
for (int y = 2; y < H - 2; y++)
{
for (int x = 2; x < W - 2; x++)
{
if (grid[x, y] != '-')
continue;
// Vain seinät joilla on lattiaa molemmilla puolilla (väliseinät)
bool floorBothSides =
(IsFloor(x - 1, y) && IsFloor(x + 1, y)) ||
(IsFloor(x, y - 1) && IsFloor(x, y + 1));
if (floorBothSides)
candidates.Add((x, y));
}
}
// Muuta ~15% seinistä räjähdettäviksi
int explosiveCount = candidates.Count / 7;
for (int i = 0; i < explosiveCount && candidates.Count > 0; i++)
{
int idx = rng.Next(candidates.Count);
var (x, y) = candidates[idx];
candidates.RemoveAt(idx);
grid[x, y] = 'B';
}
}
/// <summary>
/// Sijoittaa viholliset ('X') satunnaisiin soluhuoneisiin ja yhteisötilaan.
/// </summary>
private 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, '@', '+', '^', 'B')) continue;
grid[x, y] = 'X';
placed++;
}
}
/// <summary>
/// Sijoittaa spawn-pisteet ('@') satunnaisiin soluhuoneisiin ja yhteisötilaan.
/// </summary>
private 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;
if (AdjacentTo(x, y, 'X', '^', 'B')) continue;
grid[x, y] = '@';
placed++;
}
}
/// <summary>
/// Onko ruudussa lattiaa (',' tai '.').
/// </summary>
private bool IsFloor(int x, int y) => grid[x, y] == ',' || grid[x, y] == '.';
/// <summary>
/// Onko 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;
}
/// <summary>
/// Sarjallistaa gridin tekstiksi (debug-tulostukseen).
/// </summary>
public string Serialize()
{
int w = grid.GetLength(0);
int h = grid.GetLength(1);
var sb = new StringBuilder(h * (w + 1));
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
sb.Append(grid[x, y]);
sb.AppendLine();
}
return sb.ToString();
}
}
}