Загрузка данных


using System;
using System.Collections.Generic;
using System.Linq;

namespace Tetris.Dto
{
    // ─── 1. Point / CellDTO ─────────────────────────────────────────────
    public readonly record struct Point(int X, int Y);

    // ─── 2. Piece ───────────────────────────────────────────────────────
    public class Piece
    {
        private readonly List<Point> _initialCells;

        public Piece(IEnumerable<Point> initialCells, char color, int rotationStates = 4)
        {
            _initialCells = initialCells.ToList();
            RelativeCells = new List<Point>(_initialCells);
            Color = color;
            RotationStates = Math.Max(1, rotationStates);
        }

        public Point Center { get; set; }
        public char Color { get; }
        public int RotationStates { get; }     // 2 для I, S, Z; 1 для O; 4 для T, J, L
        public int RotationIndex { get; private set; }
        public List<Point> RelativeCells { get; private set; }

        public IEnumerable<Point> GetAbsoluteCells() =>
            RelativeCells.Select(c => new Point(Center.X + c.X, Center.Y + c.Y));

        // Единая математическая операция поворота (X, Y) -> (-Y, X)
        public void Rotate()
        {
            RotationIndex = (RotationIndex + 1) % RotationStates;
            RecomputeCells();
        }

        public (List<Point> cells, int index) Snapshot() =>
            (new List<Point>(RelativeCells), RotationIndex);

        public void Restore((List<Point> cells, int index) snap)
        {
            RelativeCells = snap.cells;
            RotationIndex = snap.index;
        }

        private void RecomputeCells()
        {
            var cells = _initialCells;
            for (int i = 0; i < RotationIndex; i++)
                cells = cells.Select(p => new Point(-p.Y, p.X)).ToList();

            // Нормализация: минимальные X,Y сдвигаем к 0
            int minX = cells.Min(p => p.X);
            int minY = cells.Min(p => p.Y);
            RelativeCells = cells.Select(p => new Point(p.X - minX, p.Y - minY)).ToList();
        }
    }

    // ─── 3. Фабрика фигур ───────────────────────────────────────────────
    public static class PieceFactory
    {
        public enum PieceType { I, O, T, S, Z, J, L }

        private static readonly Dictionary<PieceType, (Point[] cells, char color, int rotations)> Templates = new()
        {
            [PieceType.I] = (new[] { new Point(0,0), new Point(1,0), new Point(2,0), new Point(3,0) }, 'I', 2),
            [PieceType.O] = (new[] { new Point(0,0), new Point(1,0), new Point(0,1), new Point(1,1) }, 'O', 1),
            [PieceType.T] = (new[] { new Point(0,0), new Point(1,0), new Point(2,0), new Point(1,1) }, 'T', 4),
            [PieceType.S] = (new[] { new Point(1,0), new Point(2,0), new Point(0,1), new Point(1,1) }, 'S', 2),
            [PieceType.Z] = (new[] { new Point(0,0), new Point(1,0), new Point(1,1), new Point(2,1) }, 'Z', 2),
            [PieceType.J] = (new[] { new Point(0,0), new Point(0,1), new Point(1,1), new Point(2,1) }, 'J', 4),
            [PieceType.L] = (new[] { new Point(2,0), new Point(0,1), new Point(1,1), new Point(2,1) }, 'L', 4),
        };

        public static Piece Create(PieceType type, int boardWidth)
        {
            var (cells, color, rotations) = Templates[type];
            int width = cells.Max(p => p.X) + 1;
            int startX = Math.Max(0, (boardWidth - width) / 2);
            return new Piece(cells, color, rotations)
            {
                Center = new Point(startX, 0)
            };
        }

        public static Piece CreateRandom(int boardWidth) =>
            Create((PieceType)Random.Shared.Next(0, 7), boardWidth);
    }

    // ─── 4. Board ───────────────────────────────────────────────────────
    public class Board
    {
        public int Width { get; }
        public int Height { get; }
        public char[,] Grid { get; }   // '\0' = пусто

        public Board(int width, int height)
        {
            Width = width;
            Height = height;
            Grid = new char[width, height];
        }

        public bool IsInside(int x, int y) => x >= 0 && x < Width && y >= 0 && y < Height;
        public bool IsEmpty(int x, int y) => Grid[x, y] == '\0';
        public bool IsOccupied(int x, int y) => IsInside(x, y) && Grid[x, y] != '\0';
        public void Set(int x, int y, char c) => Grid[x, y] = c;
    }

    // ─── 5. BoardProcessor / GameEngine ─────────────────────────────────
    public class BoardProcessor
    {
        public bool CanPlace(Board b, Piece p)
        {
            foreach (var c in p.GetAbsoluteCells())
                if (!b.IsInside(c.X, c.Y) || b.IsOccupied(c.X, c.Y))
                    return false;
            return true;
        }

        public bool TryMove(Board b, Piece p, int dx, int dy)
        {
            var old = p.Center;
            p.Center = new Point(old.X + dx, old.Y + dy);
            if (!CanPlace(b, p)) { p.Center = old; return false; }
            return true;
        }

        public bool TryRotate(Board b, Piece p)
        {
            var snap = p.Snapshot();
            p.Rotate();
            if (!CanPlace(b, p)) { p.Restore(snap); return false; }
            return true;
        }

        public void Lock(Board b, Piece p)
        {
            foreach (var c in p.GetAbsoluteCells())
                b.Set(c.X, c.Y, p.Color);
            ClearFullLines(b);
        }

        public int ClearFullLines(Board b)
        {
            int cleared = 0;
            for (int y = b.Height - 1; y >= 0; y--)
            {
                bool full = true;
                for (int x = 0; x < b.Width; x++)
                    if (b.IsEmpty(x, y)) { full = false; break; }

                if (!full) continue;

                for (int yy = y; yy > 0; yy--)
                    for (int x = 0; x < b.Width; x++)
                        b.Set(x, yy, b.Grid[x, yy - 1]);

                for (int x = 0; x < b.Width; x++)
                    b.Set(x, 0, '\0');

                cleared++;
                y++;
            }
            return cleared;
        }
    }
}