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