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();
        }
    }
}