Untitled

 avatar
unknown
csharp
a month ago
24 kB
3
Indexable
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PolySpearAI
{
    [Serializable]
    public record Hex
    {
        public int Q { get; } // Column
        public int R { get; } // Row
        public Hex(int q, int r)
        {
            Q = q;
            R = r;
        }
    }

    [Serializable]
    public record PreMove
    {
        public Dictionary<Hex, Unit> UnitsPositions;
        public HashSet<Unit> AllUnits;

        public PreMove(Dictionary<Hex, Unit> positions, HashSet<Unit> allUnits)
        {
            UnitsPositions = positions;
            AllUnits = allUnits;
        }
    }

    public class HexGrid
    {
        public int Width { get; private set; }
        public int Height { get; private set; }
        private readonly Dictionary<(int, int), Hex> _hexes = new();

        public static readonly int[][][] OddrDirectionDifferences =
        {
             // Even rows
             new int[][]
             {
                 new[] { 0, -1 }, new[] { 1, 0 }, new[] { 0, 1 },
                 new[] { -1, 1 }, new[] { -1, 0 }, new[] { -1, -1 }
             },
             // Odd rows
             new int[][]
             {
                 new[] { 1, -1 }, new[] { 1, 0 }, new[] { 1, 1 },
                 new[] { 0, 1 }, new[] { -1, 0 }, new[] { 0, -1 }
             }
        };

        private readonly Dictionary<Hex, Unit> _unitsPositions = new();
        private HashSet<Unit> _allUnits = new();

        public Stack<PreMove> Moves = new();

        public HexGrid(int width, int height)
        {
            this.Width = width;
            this.Height = height;

            for (int r = 0; r < height; r++)
            {
                for (int q = 0; q < width - (r % 2 == 0 ? 0 : 1); q++)
                {
                    _hexes[(q, r)] = new Hex(q, r);
                }
            }
        }

        public Hex GetHex(Unit unit)
        {
            return GetHex(unit.Q, unit.R);
        }

        public Hex GetHex(int q, int r)
        {
            _hexes.TryGetValue((q, r), out Hex? hex);
            return hex;
        }

        public Unit GetUnitAtHex(Hex hex)
        {
            _unitsPositions.TryGetValue(hex, out Unit? unit);
            return unit;
        }

        public Side DirectionTo(Hex start, Hex neighbor)
        {
            int parity = start.R & 1;
            int dq = neighbor.Q - start.Q;
            int dr = neighbor.R - start.R;

            for (int i = 0; i < HexGrid.OddrDirectionDifferences[parity].Length; i++)
            {
                var diff = HexGrid.OddrDirectionDifferences[parity][i];
                if (dq == diff[0] && dr == diff[1])
                {
                    return (Side)i;
                }
            }

            throw new ArgumentException("Hexes are not adjacent");
        }

        public Dictionary<Side, Hex> GetNeighbors(Hex hex)
        {
            int parity = hex.R & 1;
            Dictionary<Side, Hex> neighbors = new();

            for (int i = 0; i < OddrDirectionDifferences[parity].Length; i++)
            {
                var diff = OddrDirectionDifferences[parity][i];
                Hex neighbor = GetHex(hex.Q + diff[0], hex.R + diff[1]);
                if (neighbor != null) neighbors[(Side)i] = neighbor;
            }

            return neighbors;
        }

        public Hex GetNeighbor(Hex hex, Side side)
        {
            GetNeighbors(hex).TryGetValue(side, out Hex result);
            return result;
        }

        public List<Hex> AllowedMoves(Unit unit)
        {
            var moves = new List<Hex>();

            if (unit == null)
                return moves;

            int currentRotation = (int)unit.Rotation;

            var directions = Enum.GetValues(typeof(Side));
            var neighbors = GetNeighbors(GetHex(unit));
            foreach (int dirValue in directions)
            {
                Side side = (Side)dirValue;
                if (neighbors.TryGetValue(side, out Hex neighborHex))
                {
                    Unit neighborUnit = GetUnitAtHex(neighborHex);
                    if (neighborUnit == null || neighborUnit.Player != unit.Player)
                    {
                        moves.Add(neighborHex);
                    }
                }
            }
            return moves;
        }

        private bool IsAdjacent(Hex a, Hex b)
        {
            try
            {
                DirectionTo(a, b);
                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool MoveUnit(Unit unit, Hex to)
        {
            if (!AllowedMoves(unit).Contains(to))
            {
                return false;
            }

            Hex from = GetHex(unit);

            Side moveDirection = DirectionTo(from, to);

            return PerformMovement(unit, from, to, moveDirection);
        }

        private bool PerformMovement(Unit unit, Hex currentPosition, Hex destination, Side moveDirection)
        {
            unit.SetRotation(moveDirection);

            if (IsVulnerableToSpear(unit, currentPosition))
            {
                KillUnit(unit);
                return true;
            }

            // Check if destination has an enemy unit that can be attacked
            if (_unitsPositions.TryGetValue(destination, out Unit targetUnit) && targetUnit.Player != unit.Player)
            {
                // Can only move to an occupied hex if the weapon in the move direction would kill the target
                if (!CanKillUnit(unit, targetUnit, moveDirection))
                    return false;

                // Kill the target unit
                KillUnit(targetUnit);
            }
            else if (_unitsPositions.ContainsKey(destination))
            {
                // Can't move to a hex occupied by a friendly unit
                return false;
            }

            ActivateWeaponEffectsBeforeMovement(unit, currentPosition, destination, moveDirection);

            _unitsPositions.Remove(currentPosition);

            _unitsPositions[destination] = unit;
            _unitsPositions.Remove(currentPosition);
            unit.SetPosition(destination);

            if (IsVulnerableToSpear(unit, destination))
            {
                KillUnit(unit);
                return true;
            }

            ActivateWeaponEffectsAfterMovement(unit, destination);

            return true;
        }

        private void ActivateWeaponEffectsBeforeMovement(Unit unit, Hex currentPosition, Hex destination, Side moveDirection)
        {
            Weapon weaponInMoveDirection = unit.GetItemOnSide((int)moveDirection);

            if (weaponInMoveDirection == Weapon.AXE || weaponInMoveDirection == Weapon.STRONG_AXE)
            {
                KillAdjacentEnemies(unit, destination);
            }

            if (weaponInMoveDirection == Weapon.BOW)
            {
                FireBow(unit, currentPosition, moveDirection);
            }

            if (weaponInMoveDirection == Weapon.PUSH)
            {
                PushUnit(unit, destination, moveDirection);
            }
        }

        private void ActivateWeaponEffectsAfterMovement(Unit unit, Hex position)
        {
            foreach (Side side in Enum.GetValues<Side>())
            {
                if (unit.GetItemOnSide((int)side) == Weapon.SPEAR)
                {
                    try
                    {
                        Hex targetPos = GetNeighbor(position,side);
                        if (_unitsPositions.TryGetValue(targetPos, out Unit targetUnit) &&
                            targetUnit.Player != unit.Player)
                        {
                            // Check if target has a shield
                            Side defendDirection = (Side)(((int)side + 3) % 6);
                            Weapon defendWeapon = targetUnit.GetItemOnSide((int)defendDirection);

                            if (defendWeapon != Weapon.SHIELD && defendWeapon != Weapon.STRONG_SHIELD)
                            {
                                KillUnit(targetUnit);
                            }
                        }
                    }
                    catch
                    {
                        // Ignore if hex is outside the board
                    }
                }
            }
        }

        private void KillAdjacentEnemies(Unit unit, Hex position)
        {
            List<Unit> unitsToKill = new List<Unit>();

            // Check all adjacent hexes for enemies
            foreach (Side side in Enum.GetValues<Side>())
            {
                try
                {
                    Hex neighborPos = GetNeighbor(position,side);
                    if (_unitsPositions.TryGetValue(neighborPos, out Unit neighborUnit) &&
                        neighborUnit.Player != unit.Player)
                    {
                        unitsToKill.Add(neighborUnit);
                    }
                }
                catch
                {
                    // Ignore if hex is outside the board
                }
            }

            // Kill all marked units
            foreach (var unitToKill in unitsToKill)
            {
                KillUnit(unitToKill);
            }
        }

        private void FireBow(Unit unit, Hex position, Side direction)
        {
            // Start at the unit's position
            Hex currentPos = position;

            // Keep going in the direction until we hit something or reach the edge
            while (true)
            {
                try
                {
                    // Move to the next hex in the direction
                    currentPos = GetNeighbor(currentPos,direction);

                    // Check if there's a unit there
                    if (_unitsPositions.TryGetValue(currentPos, out Unit targetUnit))
                    {
                        // If it's an enemy unit, check for shield and kill if not shielded
                        if (targetUnit.Player != unit.Player)
                        {
                            Side defendDirection = (Side)(((int)direction + 3) % 6);
                            Weapon defendWeapon = targetUnit.GetItemOnSide((int)defendDirection);

                            if (defendWeapon != Weapon.SHIELD && defendWeapon != Weapon.STRONG_SHIELD)
                            {
                                KillUnit(targetUnit);
                            }

                            // Arrow stops regardless of whether it killed or not
                            break;
                        }
                        else
                        {
                            // Friendly units block arrows
                            break;
                        }
                    }
                }
                catch
                {
                    // Reached the edge of the board
                    break;
                }
            }
        }

        private void PushUnit(Unit pusher, Hex pushPosition, Side pushDirection)
        {
            // Check if there's a unit at the push position
            if (_unitsPositions.TryGetValue(pushPosition, out Unit targetUnit) &&
                targetUnit.Player != pusher.Player)
            {
                // Calculate the position the target will be pushed to
                Hex pushDestination;
                try
                {
                    pushDestination = GetNeighbor(pushPosition, pushDirection);

                    // If the push destination is occupied or off the board, the target unit dies
                    if (_unitsPositions.ContainsKey(pushDestination))
                    {
                        KillUnit(targetUnit);
                    }
                    else
                    {
                        // Move the target to the push destination
                        _unitsPositions.Remove(pushPosition);
                        _unitsPositions[pushDestination] = targetUnit;
                        targetUnit.SetPosition(pushDestination);

                        // Check if the pushed unit is now vulnerable to a spear
                        if (IsVulnerableToSpear(targetUnit, pushDestination))
                        {
                            KillUnit(targetUnit);
                        }
                    }
                }
                catch
                {
                    // Pushed off the board, unit dies
                    KillUnit(targetUnit);
                }
            }
        }

        private bool CanKillUnit(Unit attacker, Unit target, Side attackDirection)
        {
            Weapon attackWeapon = attacker.GetItemOnSide((int)attackDirection);

            // The opposite direction from the attack direction
            Side defendDirection = (Side)(((int)attackDirection + 3) % 6);

            // Get the weapon used for defense
            Weapon defendWeapon = target.GetItemOnSide((int)defendDirection);

            // If defender has a shield in the right direction, they're protected
            if (defendWeapon == Weapon.SHIELD || defendWeapon == Weapon.STRONG_SHIELD)
                return false;

            // AXE/STRONG_AXE can kill if not blocked by shield
            if (attackWeapon == Weapon.AXE || attackWeapon == Weapon.STRONG_AXE)
                return true;

            // PUSH can always move to occupied hex (it will push the enemy)
            if (attackWeapon == Weapon.PUSH) //todo
                return true;

            return false;
        }

        private bool IsVulnerableToSpear(Unit unit, Hex position)
        {
            // Check all adjacent hexes for enemy units with spears pointing at this unit
            foreach (Side side in Enum.GetValues<Side>())
            {
                try
                {
                    Hex neighborPos = GetNeighbor(position, side);
                    if (_unitsPositions.TryGetValue(neighborPos, out Unit neighborUnit) &&
                        neighborUnit.Player != unit.Player)
                    {
                        // The side pointing at our unit
                        Side pointingDirection = (Side)(((int)side + 3) % 6);

                        // Check if neighbor has a spear pointing at our unit
                        Weapon neighborWeapon = neighborUnit.GetItemOnSide((int)pointingDirection);
                        if (neighborWeapon == Weapon.SPEAR)
                        {
                            // Check if our unit has a shield in that direction
                            Side defendDirection = side;
                            Weapon defendWeapon = unit.GetItemOnSide((int)defendDirection);

                            if (defendWeapon != Weapon.SHIELD && defendWeapon != Weapon.STRONG_SHIELD)
                                return true; // Unit is vulnerable to spear
                        }
                    }
                }
                catch
                {
                    // Ignore if hex is outside the board
                }
            }
            return false;
        }

        private void KillUnit(Unit unit)
        {
            _unitsPositions.Remove(GetHex(unit));
            _allUnits.Remove(unit);
        }

        public HashSet<Unit> GetUnitsByPlayer(PLAYER player)
        {
            return _allUnits.Where(x => x.Player == player).ToHashSet();
        }

        public void PlaceUnit(int q, int r, Unit unit, Side facing)
        {
            Hex hex = GetHex(q, r);
            if (hex == null)
            {
                Console.WriteLine($"Hex ({q},{r}) does not exist.");
                return;
            }
            Unit occupant = GetUnitAtHex(hex);
            if (occupant != null)
            {
                Console.WriteLine($"Hex ({q},{r}) is already occupied by {occupant}.");
                return;
            }
            _allUnits.Add(unit);
            _unitsPositions[hex] = unit;
            unit.SetRotation(facing);
            _unitsPositions[hex] = unit;
            unit.SetPosition(hex);
        }

        public void PrintGrid()
        {
            for (int r = 0; r < Height; r++)
            {
                if (r % 2 == 1) Console.Write("   ");

                int currWidth = Width - (r % 2 == 0 ? 0 : 1);
                for (int q = 0; q < currWidth; q++)
                {
                    Unit unit = GetUnitAtHex(GetHex(q, r));
                    string unitId = "  ";
                    ConsoleColor originalColor = Console.ForegroundColor;
                    ConsoleColor playerColor = ConsoleColor.White;
                    if (unit != null)
                    {
                        unitId = unit.ID;
                        playerColor = unit.Player == 0 ? ConsoleColor.Green : ConsoleColor.Red;
                    }

                    if (unit != null)
                    {
                        switch (unit.Rotation)
                        {
                            case Side.UP_RIGHT:
                                Console.ForegroundColor = originalColor;
                                Console.Write($@" /{q}");
                                Console.ForegroundColor = playerColor;
                                Console.Write(@"\  ");
                                break;
                            case Side.UP_LEFT:
                                Console.ForegroundColor = playerColor;
                                Console.Write($@" /{q}");
                                Console.ForegroundColor = originalColor;
                                Console.Write(@"\  ");
                                break;
                            default:
                                Console.Write($@" /{q}\  ");
                                break;
                        }
                    }
                    else
                    {
                        Console.Write($@" /{q}\  ");
                    }
                    Console.ForegroundColor = originalColor;
                }
                Console.WriteLine();

                if (r % 2 == 1) Console.Write("   ");
                for (int q = 0; q < currWidth; q++)
                {
                    Unit unit = GetUnitAtHex(GetHex(q, r));
                    string unitId = "  ";
                    ConsoleColor originalColor = Console.ForegroundColor;
                    ConsoleColor playerColor = ConsoleColor.White;
                    if (unit != null)
                    {
                        unitId = unit.ID;
                        playerColor = unit.Player == 0 ? ConsoleColor.Green : ConsoleColor.Red;
                    }

                    if (unit != null)
                    {
                        switch (unit.Rotation)
                        {
                            case Side.LEFT:
                                Console.ForegroundColor = playerColor;
                                Console.Write(@$"|");
                                Console.ForegroundColor = originalColor;
                                Console.Write(@$" {unitId}| ");
                                break;
                            case Side.RIGHT:
                                Console.ForegroundColor = originalColor;
                                Console.Write($@"| {unitId}");
                                Console.ForegroundColor = playerColor;
                                Console.Write(@"| ");
                                break;
                            default:
                                Console.Write($@"| {unitId}| ");
                                break;
                        }
                    }
                    else
                    {
                        Console.Write($@"| {unitId}| ");
                    }
                    Console.ForegroundColor = originalColor;
                }
                Console.WriteLine();

                if (r % 2 == 1) Console.Write("   ");
                for (int q = 0; q < currWidth; q++)
                {
                    Unit unit = GetUnitAtHex(GetHex(q, r));
                    ConsoleColor originalColor = Console.ForegroundColor;
                    ConsoleColor playerColor = ConsoleColor.White;
                    if (unit != null)
                    {
                        playerColor = unit.Player == 0 ? ConsoleColor.Green : ConsoleColor.Red;
                    }

                    if (unit != null)
                    {
                        switch (unit.Rotation)
                        {
                            case Side.DOWN_RIGHT:
                                Console.ForegroundColor = originalColor;
                                Console.Write(@" \");
                                Console.ForegroundColor = playerColor;
                                Console.Write($@"{r}/");
                                Console.ForegroundColor = originalColor;
                                Console.Write(@"  ");
                                break;
                            case Side.DOWN_LEFT:
                                Console.ForegroundColor = playerColor;
                                Console.Write(@" \");
                                Console.ForegroundColor = originalColor;
                                Console.Write($@"{r}/");
                                Console.Write(@"  ");
                                break;
                            default:
                                Console.Write($@" \{r}/  ");
                                break;
                        }
                    }
                    else
                    {
                        Console.Write($@" \{r}/  ");
                    }
                    Console.ForegroundColor = originalColor;
                }
                Console.WriteLine();
            }
        }

        public HexGrid Clone()
        {
            return new HexGrid(this);
        }

        public HexGrid(HexGrid other)
        {
            Width = other.Width;
            Height = other.Height;

            _allUnits = new();
            _hexes = new Dictionary<(int, int), Hex>();
            foreach (var kvp in other._hexes)
            {
                Unit unit = GetUnitAtHex(kvp.Value);
                if(unit != null)
                {
                    Hex newHex = new Hex(kvp.Value.Q, kvp.Value.R);
                    Unit unitClone = unit.Clone();
                    _allUnits.Add(unitClone);
                    _unitsPositions[newHex] = unitClone;
                    unitClone.SetPosition(newHex);
                    _hexes[kvp.Key] = newHex;
                }
                else
                {
                    _hexes[kvp.Key] = new Hex(kvp.Value.Q, kvp.Value.R);
                }
            }

            _unitsPositions = new Dictionary<Hex, Unit>();
            foreach (var kvp in other._unitsPositions)
            {
                Hex hex = GetHex(kvp.Key.Q, kvp.Key.R);
                _unitsPositions[hex] = kvp.Value;
            }
        }
    }
}
Editor is loading...
Leave a Comment