Guest User

Untitled

a guest
Mar 6th, 2026
103
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 14.60 KB | None | 0 0
  1. """
  2. 3D-шахматы (Raumschach-стиль) — однопроходной генератор ходов.
  3.  
  4. Доска : N×N×N  (по умолчанию 8×8×8)
  5. Координата : (x, y, z) — колонка, ряд, уровень
  6.  
  7. Фигуры и направления движения в трёхмерном пространстве:
  8. ──────────────────────────────────────────────────────────
  9.  Ладья    (R) — 6 осевых направлений
  10.  Слон     (B) — 12 плоско-диагональных направлений
  11.  Единорог (U) — 8 пространственных диагоналей
  12.  Ферзь    (Q) — все 26 направлений (6 + 12 + 8)
  13.  Король   (K) — те же 26 направлений, но ровно 1 шаг
  14.  Конь     (N) — 24 L-образных прыжка (перестановки ±2,±1,0)
  15.  Пешка    (P) — вперёд по оси Z, взятия по диагоналям
  16.  
  17. Принцип однопроходности:
  18.  • Внешний цикл — по фигурам на доске (один раз каждая).
  19.  • Внутренний цикл — по направлениям/смещениям (конечный набор).
  20.  • Для скользящих фигур луч идёт строго вперёд по вектору,
  21.    при первом блокере — break, без отката.
  22.  • Для шаговых фигур — только проверка одного целевого поля
  23.    на каждое смещение.
  24. """
  25.  
  26. from __future__ import annotations
  27. from enum import Enum, auto
  28. from typing import Dict, List, Tuple, Optional
  29. from itertools import permutations
  30.  
  31.  
  32. # ──────────────────────────────────────────────
  33. #  Типы данных
  34. # ──────────────────────────────────────────────
  35.  
  36. Pos = Tuple[int, int, int]          # (x, y, z)
  37.  
  38.  
  39. class Color(Enum):
  40.     WHITE = auto()
  41.     BLACK = auto()
  42.  
  43.     @property
  44.     def opponent(self) -> Color:
  45.         return Color.BLACK if self is Color.WHITE else Color.WHITE
  46.  
  47.  
  48. class PieceKind(Enum):
  49.     KING   = "K"
  50.     QUEEN  = "Q"
  51.     ROOK   = "R"
  52.     BISHOP = "B"
  53.     UNICORN = "U"   # пространственная диагональ — классика Raumschach
  54.     KNIGHT = "N"
  55.     PAWN   = "P"
  56.  
  57.  
  58. class Piece:
  59.     __slots__ = ("kind", "color")
  60.  
  61.     def __init__(self, kind: PieceKind, color: Color):
  62.         self.kind = kind
  63.         self.color = color
  64.  
  65.     def __repr__(self) -> str:
  66.         c = "w" if self.color is Color.WHITE else "b"
  67.         return f"{c}{self.kind.value}"
  68.  
  69.  
  70. # ──────────────────────────────────────────────
  71. #  Предвычисленные таблицы направлений
  72. #  (создаются один раз, переиспользуются)
  73. # ──────────────────────────────────────────────
  74.  
  75. def _build_directions():
  76.     """
  77.    Генерирует все 26 единичных 3D-направлений и классифицирует их:
  78.      осевые           (6)  — ровно одна ненулевая координата
  79.      плоско-диагональные (12) — ровно две ненулевые координаты
  80.      пространственные   (8)  — все три координаты ненулевые
  81.    """
  82.     axial = []       # Ладья
  83.     planar = []      # Слон
  84.     spatial = []     # Единорог
  85.  
  86.     for dx in (-1, 0, 1):
  87.         for dy in (-1, 0, 1):
  88.             for dz in (-1, 0, 1):
  89.                 if dx == dy == dz == 0:
  90.                     continue
  91.                 nonzero = (dx != 0) + (dy != 0) + (dz != 0)
  92.                 d = (dx, dy, dz)
  93.                 if nonzero == 1:
  94.                     axial.append(d)
  95.                 elif nonzero == 2:
  96.                     planar.append(d)
  97.                 else:
  98.                     spatial.append(d)
  99.  
  100.     return axial, planar, spatial
  101.  
  102.  
  103. AXIAL_DIRS, PLANAR_DIRS, SPATIAL_DIRS = _build_directions()
  104.  
  105. ROOK_DIRS    = AXIAL_DIRS                                # 6
  106. BISHOP_DIRS  = PLANAR_DIRS                               # 12
  107. UNICORN_DIRS = SPATIAL_DIRS                               # 8
  108. QUEEN_DIRS   = AXIAL_DIRS + PLANAR_DIRS + SPATIAL_DIRS    # 26
  109. KING_DIRS    = QUEEN_DIRS                                 # 26 (шаг = 1)
  110.  
  111.  
  112. def _build_knight_offsets() -> List[Pos]:
  113.     """
  114.    Конь в 3D: все перестановки (±2, ±1, 0) с учётом знаков.
  115.    Множество устраняет дубли (знак на нулевой компоненте не меняет вектор).
  116.    Результат — 24 уникальных смещения.
  117.    """
  118.     offsets: set[Pos] = set()
  119.     for perm in permutations((2, 1, 0)):
  120.         for sx in (1, -1):
  121.             for sy in (1, -1):
  122.                 for sz in (1, -1):
  123.                     offsets.add((perm[0] * sx, perm[1] * sy, perm[2] * sz))
  124.     return list(offsets)
  125.  
  126.  
  127. KNIGHT_OFFSETS = _build_knight_offsets()   # 24 прыжка
  128.  
  129.  
  130. # ──────────────────────────────────────────────
  131. #  Доска и генератор ходов
  132. # ──────────────────────────────────────────────
  133.  
  134. class Board3D:
  135.     """Трёхмерная шахматная доска N×N×N."""
  136.  
  137.     def __init__(self, size: int = 8):
  138.         self.size = size
  139.         self.cells: Dict[Pos, Piece] = {}
  140.  
  141.     # ── работа с доской ──
  142.  
  143.     def place(self, pos: Pos, piece: Piece) -> None:
  144.         self.cells[pos] = piece
  145.  
  146.     def remove(self, pos: Pos) -> Optional[Piece]:
  147.         return self.cells.pop(pos, None)
  148.  
  149.     def in_bounds(self, pos: Pos) -> bool:
  150.         x, y, z = pos
  151.         s = self.size
  152.         return 0 <= x < s and 0 <= y < s and 0 <= z < s
  153.  
  154.     # ── главный метод — однопроходной сбор ходов ──
  155.  
  156.     def compute_all_moves(self) -> Dict[Pos, List[Pos]]:
  157.         """
  158.        Один проход по всем фигурам на доске.
  159.        Возвращает {позиция_фигуры: [список_допустимых_полей]}.
  160.  
  161.        Сложность: O(P · D · L)
  162.          P — число фигур на доске
  163.          D — макс. число направлений у фигуры (≤ 26)
  164.          L — макс. длина луча (≤ N-1)
  165.        Для шаговых фигур L = 1, для коня D = 24, L = 1.
  166.        """
  167.         result: Dict[Pos, List[Pos]] = {}
  168.  
  169.         # ── единственный проход ──
  170.         for pos, piece in self.cells.items():
  171.             result[pos] = self._moves_for(pos, piece)
  172.  
  173.         return result
  174.  
  175.     # ── диспетчер по типу фигуры ──
  176.  
  177.     def _moves_for(self, pos: Pos, piece: Piece) -> List[Pos]:
  178.         kind = piece.kind
  179.  
  180.         if kind is PieceKind.ROOK:
  181.             return self._ray_moves(pos, piece, ROOK_DIRS)
  182.  
  183.         if kind is PieceKind.BISHOP:
  184.             return self._ray_moves(pos, piece, BISHOP_DIRS)
  185.  
  186.         if kind is PieceKind.UNICORN:
  187.             return self._ray_moves(pos, piece, UNICORN_DIRS)
  188.  
  189.         if kind is PieceKind.QUEEN:
  190.             return self._ray_moves(pos, piece, QUEEN_DIRS)
  191.  
  192.         if kind is PieceKind.KING:
  193.             return self._jump_moves(pos, piece, KING_DIRS)
  194.  
  195.         if kind is PieceKind.KNIGHT:
  196.             return self._jump_moves(pos, piece, KNIGHT_OFFSETS)
  197.  
  198.         if kind is PieceKind.PAWN:
  199.             return self._pawn_moves(pos, piece)
  200.  
  201.         return []
  202.  
  203.     # ── скользящие фигуры (луч вперёд, break при блокере) ──
  204.  
  205.     def _ray_moves(self, origin: Pos, piece: Piece,
  206.                    directions: List[Pos]) -> List[Pos]:
  207.         """
  208.        Для каждого направления двигаемся строго вперёд.
  209.        Остановка при выходе за границу или встрече фигуры.
  210.        Фигуру противника можно взять (добавляем поле),
  211.        свою — нельзя (не добавляем).  Оба случая — break.
  212.        """
  213.         moves: List[Pos] = []
  214.         cells = self.cells
  215.         color = piece.color
  216.         size = self.size
  217.  
  218.         for dx, dy, dz in directions:
  219.             x, y, z = origin
  220.  
  221.             # ── луч вдоль одного направления ──
  222.             while True:
  223.                 x += dx
  224.                 y += dy
  225.                 z += dz
  226.  
  227.                 # выход за границу → конец луча
  228.                 if not (0 <= x < size and 0 <= y < size and 0 <= z < size):
  229.                     break
  230.  
  231.                 blocker = cells.get((x, y, z))
  232.  
  233.                 if blocker is None:
  234.                     # пустое поле — можно идти, луч продолжается
  235.                     moves.append((x, y, z))
  236.                     continue
  237.  
  238.                 if blocker.color is not color:
  239.                     # фигура противника — взятие, луч кончается
  240.                     moves.append((x, y, z))
  241.  
  242.                 # своя фигура или только что взятая чужая — break
  243.                 break
  244.  
  245.         return moves
  246.  
  247.     # ── шаговые фигуры (один прыжок по каждому смещению) ──
  248.  
  249.     def _jump_moves(self, origin: Pos, piece: Piece,
  250.                     offsets: List[Pos]) -> List[Pos]:
  251.         """
  252.        Проверяем каждое смещение ровно один раз.
  253.        Если поле в пределах доски и не занято своей фигурой — ход возможен.
  254.        """
  255.         moves: List[Pos] = []
  256.         cells = self.cells
  257.         color = piece.color
  258.         size = self.size
  259.         ox, oy, oz = origin
  260.  
  261.         for dx, dy, dz in offsets:
  262.             nx, ny, nz = ox + dx, oy + dy, oz + dz
  263.  
  264.             if not (0 <= nx < size and 0 <= ny < size and 0 <= nz < size):
  265.                 continue
  266.  
  267.             blocker = cells.get((nx, ny, nz))
  268.             if blocker is None or blocker.color is not color:
  269.                 moves.append((nx, ny, nz))
  270.  
  271.         return moves
  272.  
  273.     # ── пешка ──
  274.  
  275.     def _pawn_moves(self, origin: Pos, piece: Piece) -> List[Pos]:
  276.         """
  277.        Пешка идёт вперёд по оси Z (белые +z, чёрные -z).
  278.        Со стартового уровня — может шагнуть на 2.
  279.        Взятия — по диагоналям в плоскости (x,y) со сдвигом +z/−z.
  280.        """
  281.         moves: List[Pos] = []
  282.         cells = self.cells
  283.         size = self.size
  284.         ox, oy, oz = origin
  285.  
  286.         fwd = 1 if piece.color is Color.WHITE else -1
  287.         start_z = 1 if piece.color is Color.WHITE else size - 2
  288.  
  289.         # ── тихий ход на 1 клетку вперёд ──
  290.         z1 = oz + fwd
  291.         if 0 <= z1 < size and (ox, oy, z1) not in cells:
  292.             moves.append((ox, oy, z1))
  293.  
  294.             # ── двойной ход со старта ──
  295.             if oz == start_z:
  296.                 z2 = oz + 2 * fwd
  297.                 if 0 <= z2 < size and (ox, oy, z2) not in cells:
  298.                     moves.append((ox, oy, z2))
  299.  
  300.         # ── взятия: сдвиг по x или y (±1) + шаг по z ──
  301.         #    В 3D пешка может бить в 8 диагональных направлениях
  302.         #    с продвижением по z.
  303.         capture_dxdy = [
  304.             (-1, 0), (1, 0), (0, -1), (0, 1),    # осевые в плоскости
  305.             (-1, -1), (-1, 1), (1, -1), (1, 1),   # диагональные в плоскости
  306.         ]
  307.         for dx, dy in capture_dxdy:
  308.             cx, cy, cz = ox + dx, oy + dy, oz + fwd
  309.             if 0 <= cx < size and 0 <= cy < size and 0 <= cz < size:
  310.                 target = cells.get((cx, cy, cz))
  311.                 if target is not None and target.color is not piece.color:
  312.                     moves.append((cx, cy, cz))
  313.  
  314.         return moves
  315.  
  316.     # ── красивый вывод ──
  317.  
  318.     def print_moves(self, moves: Dict[Pos, List[Pos]]) -> None:
  319.         for pos in sorted(moves):
  320.             piece = self.cells[pos]
  321.             targets = moves[pos]
  322.             coords = ", ".join(str(t) for t in sorted(targets))
  323.             print(f"  {piece} @ {pos}  →  [{len(targets)} ходов] {coords}")
  324.  
  325.  
  326. # ──────────────────────────────────────────────
  327. #  Демонстрация
  328. # ──────────────────────────────────────────────
  329.  
  330. def demo():
  331.     board = Board3D(size=8)
  332.  
  333.     # Расставим несколько фигур для примера
  334.     board.place((3, 3, 3), Piece(PieceKind.QUEEN,   Color.WHITE))
  335.     board.place((3, 3, 6), Piece(PieceKind.ROOK,    Color.BLACK))
  336.     board.place((5, 5, 5), Piece(PieceKind.UNICORN, Color.WHITE))
  337.     board.place((0, 0, 0), Piece(PieceKind.KNIGHT,  Color.WHITE))
  338.     board.place((4, 4, 1), Piece(PieceKind.PAWN,    Color.WHITE))
  339.     board.place((5, 5, 2), Piece(PieceKind.PAWN,    Color.BLACK))
  340.     board.place((4, 4, 4), Piece(PieceKind.KING,    Color.BLACK))
  341.  
  342.     print("=" * 60)
  343.     print("  Трёхмерные шахматы — однопроходной генератор ходов")
  344.     print("=" * 60)
  345.     print(f"  Доска: {board.size}×{board.size}×{board.size}")
  346.     print(f"  Фигур на доске: {len(board.cells)}\n")
  347.  
  348.     # ── один вызов — все ходы ──
  349.     all_moves = board.compute_all_moves()
  350.  
  351.     board.print_moves(all_moves)
  352.  
  353.     total = sum(len(m) for m in all_moves.values())
  354.     print(f"\n  Всего допустимых ходов: {total}")
  355.     print("=" * 60)
  356.  
  357.  
  358. if __name__ == "__main__":
  359.     demo()
Advertisement
Add Comment
Please, Sign In to add comment