
Шахматы – это не просто игра, а классический пример задачи с четкими правилами и сложной логикой. Реализация их на Python требует разбиения на конкретные компоненты: доска (8×8 клеток), фигуры (6 типов с уникальными ходами), проверка легальности ходов и обработка игрового состояния. В этой статье мы пройдем путь от пустого файла до работающей консольной версии без использования сторонних библиотек вроде python-chess или Pygame. Код будет написан на чистом Python 3.10+ с акцентом на читаемость и модульность.
Первый шаг – проектирование структуры данных. Доску удобно представить в виде двумерного списка (например, board = [[None for _ in range(8)] for _ in range(8)]), где каждая клетка хранит объект фигуры. Фигуры реализуются как классы с методами get_moves() и is_valid_move(), возвращающими возможные ходы с учетом правил (например, слон ходит по диагонали, но не может перепрыгивать через другие фигуры). Для проверки шаха и мата потребуется отдельный метод, анализирующий атакующие траектории всех фигур противника.
Третий шаг – реализация рокировки, взятия на проходе и превращения пешки. Эти правила требуют хранения дополнительных данных: флага первого хода ладьи и короля для рокировки, последнего хода пешки для взятия на проходе. Превращение пешки реализуется через запрос пользователю выбрать фигуру (ферзь, ладья, слон, конь) при достижении последней горизонтали. Для тестирования используйте заранее подготовленные позиции, например, Фишер – Спасский (1972), где рокировка встречается в 12% партий.
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜ 7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟ 6 | · · · · · · · · 5 | · · · · · · · · 4 | · · · · ♙ · · · 3 | · · · · · · · · 2 | ♙ ♙ ♙ ♙ · ♙ ♙ ♙ 1 | ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖ a b c d e f g h
Пятый шаг – расширение функционала. Добавьте поддержку FEN-нотации для загрузки позиций, возможность игры против простого ИИ (например, алгоритм minimax с глубиной 2–3 хода) и сохранение партий в PGN-формате. Для отладки используйте модуль unittest с тест-кейсами на базовые ходы, шахи и маты. Пример теста:
def test_pawn_move():
board = Board()
board.place_piece(Pawn("white"), "e2")
assert board.move("e2", "e4") is True
assert board.get_piece("e4").type == "pawn"
Настройка базовой структуры игрового поля и фигур

Шахматная доска в Python реализуется как двумерный список размером 8×8, где каждая ячейка содержит либо строку с обозначением фигуры, либо None. Для инициализации используйте вложенные списки: board = [[None for _ in range(8)] for _ in range(8)]. Это оптимально по памяти и позволяет быстро обращаться к клеткам по координатам [ряд][колонка]. Нумерация рядов начинается с 0 (белые фигуры) до 7 (чёрные), колонки – от ‘a’ до ‘h’.
Фигуры кодируются строками: ‘♙’ (белая пешка), ‘♟’ (чёрная пешка), ‘♖’ (ладья), ‘♘’ (конь), ‘♗’ (слон), ‘♕’ (ферзь), ‘♔’ (король). Альтернативный вариант – использовать классы (например, Piece с атрибутами color и type), но для базовой версии хватит строк. Разместите фигуры на доске вручную: board[0][0] = ‘♖’, board[0][1] = ‘♘’, board[1][0] = ‘♙’ и так далее для белых, аналогично для чёрных с 6 и 7 рядами.
Для проверки корректности расстановки используйте функцию validate_board(), которая проверяет количество фигур каждого типа (например, 8 пешек, 2 ладьи) и их позиции. Исключите дублирование кода: создайте словарь initial_positions = {‘♖’: [(0,0), (0,7)], ‘♘’: [(0,1), (0,6)]} и заполняйте доску циклом. Это упростит модификацию начальной расстановки.
Координаты на доске удобно представлять в виде кортежей (row, col), где row ∈ [0,7], col ∈ [0,7]. Для преобразования пользовательского ввода (например, «e2») в индексы используйте функции: row = 8 — int(input[1]), col = ord(input[0]) — ord(‘a’). Это избавит от необходимости работать с буквами в коде логики.
Храните состояние игры в классе Game с атрибутами board, current_player (‘white’ или ‘black’) и move_history. Метод make_move(self, from_pos, to_pos) должен проверять корректность хода, обновлять доску и переключать игрока. Для отмены ходов сохраняйте предыдущее состояние доски в move_history как список копий board (используйте deepcopy из модуля copy).
Оптимизируйте доступ к фигурам: создайте словарь piece_positions = {‘white’: {‘♙’: [(1,0), (1,1), …]}, ‘black’: {…}}, который обновляется при каждом ходе. Это ускорит проверку легальности ходов (например, для рокировки или взятия на проходе) и избавит от перебора всей доски. Обновляйте словарь в make_move() после изменения board.
Реализация правил перемещения для каждой шахматной фигуры

Каждая фигура в шахматах обладает уникальным набором правил перемещения, которые необходимо строго соблюдать. Для пешки реализуйте двумерный массив directions = [(0, 1)] для стандартного хода вперед, но добавьте условие: первый ход на две клетки (0, 2). Проверяйте наличие препятствий через функцию is_path_clear(board, start, end), где board – двумерный список с фигурами. Исключите взятие по прямой: для этого добавьте отдельную логику для диагональных ходов (±1, 1) только при наличии противника.
Ладья и слон используют общий шаблон перемещения по линиям, но с разными векторами. Для ладьи задайте directions = [(0, 1), (1, 0), (0, -1), (-1, 0)], для слона – [(1, 1), (1, -1), (-1, 1), (-1, -1)]. Реализуйте цикл, который итерирует по каждому направлению до границы доски или встречи с фигурой. Проверяйте цвет фигуры на конечной клетке: если свой – ход запрещен, если чужой – разрешен с взятием. Оптимизируйте проверку через abs(dx) == abs(dy) для слона и dx == 0 or dy == 0 для ладьи.
Конь – единственная фигура, игнорирующая препятствия. Задайте все возможные прыжки через список moves = [(2, 1), (1, 2), (-1, 2), (-2, 1), (-2, -1), (-1, -2), (1, -2), (2, -1)]. Для каждого хода проверяйте выход за пределы доски 0 ≤ x < 8, 0 ≤ y < 8 и цвет фигуры на целевой клетке. Избегайте дублирования кода: вынесите проверку координат в отдельную функцию is_on_board(x, y), возвращающую булево значение.
Ферзь объединяет правила ладьи и слона. Достаточно объединить их направления в один список directions = [(0, 1), (1, 0), (0, -1), (-1, 0), (1, 1), (1, -1), (-1, 1), (-1, -1)] и использовать ту же логику проверки пути. Король перемещается на одну клетку в любом направлении: directions = [(0, 1), (1, 1), (1, 0), (1, -1), (0, -1), (-1, -1), (-1, 0), (-1, 1)]. Добавьте проверку на рокировку отдельно: король не должен находиться под шахом, а ладья и король не должны двигаться ранее. Храните флаги has_moved_king и has_moved_rook в классе GameState.
Для всех фигур используйте единую структуру данных: словарь piece_rules = { ‘P’: pawn_rules, ‘R’: rook_rules, … }, где ключ – символ фигуры, а значение – функция, принимающая board, start, end и возвращающая True/False. Это упростит расширение логики и тестирование. Проверяйте цвет фигуры на стартовой позиции через board[start[0]][start[1]].color и сравнивайте с цветом на конечной клетке. Исключите ходы, ведущие к шаху собственному королю: после каждого потенциального хода вызывайте is_in_check(board, color) и отменяйте ход, если король под атакой.
Обработка ходов игроков и проверка допустимости хода

Структура данных для хранения доски критична. Используйте двумерный список board[8][8], где None обозначает пустую клетку, а объекты фигур содержат тип ('pawn', 'rook') и цвет ('white', 'black'). Пример проверки для ладьи:
- Если
start_x == target_xилиstart_y == target_y– ход потенциально допустим. - Определите шаг (
step_x = 1 if target_x > start_x else -1) и пройдите по всем клеткам между стартом и целью. - Если встретится непустая клетка – ход недопустим.
Особые случаи требуют отдельных проверок. Рокировка возможна только если король и ладья не двигались, между ними нет фигур, и король не находится под шахом. Взятие на проходе реализуйте через флаг en_passant_target, который хранит координаты пешки, допустившей двойной ход. После каждого хода обновляйте этот флаг: если пешка сдвинулась на две клетки, запишите её координаты, иначе сбросьте в None.
Шах и мат – финальные состояния, но проверка шаха должна выполняться после каждого хода. Найдите короля текущего игрока и проверьте, атакует ли его хоть одна фигура противника. Для этого временно переместите фигуру на целевую клетку, затем вызовите is_under_attack(king_position). Если король под ударом – ход недопустим. Мат проверяйте перебором всех возможных ходов: если ни один не спасает от шаха, игра окончена.
Оптимизируйте проверки с помощью предварительных фильтров. Например, пешка не может ходить назад, а король – на клетку, атакуемую противником. Храните возможные ходы фигур в словарях:
move_rules = {
'pawn': [(0, 1), (0, 2), (1, 1), (-1, 1)],
'knight': [(2, 1), (1, 2), (-1, 2), (-2, 1), ...]
}
Это сократит код и ускорит проверку базовых условий.
Тестируйте каждый сценарий отдельно. Напишите юнит-тесты для:
- Хода пешки через свою фигуру (должен быть запрещён).
- Рокировки при наличии фигур между королём и ладьёй.
- Взятия на проходе на следующем ходе после двойного хода пешки.
- Шаха от коня, когда король окружён своими фигурами.
Используйте библиотеку unittest или pytest для автоматизации проверок.
Создание логики для особых ситуаций: рокировка, взятие на проходе, шах и мат

Рокировка требует проверки четырёх условий: король и ладья не двигались, между ними нет фигур, король не под шахом и не проходит через атакованные поля. Реализуйте метод can_castle(), который проверяет эти условия для короткой и длинной рокировки отдельно. Для взятия на проходе добавьте в класс пешки флаг en_passant_vulnerable, который устанавливается в True при ходе на две клетки и сбрасывается после следующего хода противника. Храните координаты уязвимой пешки в состоянии игры и проверяйте их при ходе другой пешки на поле за ней.
- Шах: После каждого хода вызывайте метод
is_check(), который перебирает все фигуры противника и проверяет, атакуют ли они короля текущего игрока. Используйте генераторyieldдля оптимизации поиска. - Мат: Проверяйте мат только если король под шахом. Переберите все возможные ходы короля и фигур, блокирующих шах. Если ни один ход не устраняет угрозу – объявляйте мат. Для ускорения используйте ранний выход из цикла при нахождении хотя бы одного легального хода.
- Пат: Отдельная проверка – король не под шахом, но ни одна фигура не может сделать легальный ход. Исключите из проверки ходы, после которых король окажется под шахом.
Храните состояние игры в классе GameState с полями current_player, board, castling_rights, en_passant_target и halfmove_clock. Обновляйте их при каждом ходе, включая специальные случаи.
Визуализация игрового процесса с помощью библиотеки Pygame

Pygame – оптимальный выбор для визуализации шахмат из-за низкого порога входа и встроенных инструментов для работы с графикой. Установите библиотеку через pip: pip install pygame==2.5.2. Версия 2.5.2 стабильна и поддерживает все необходимые функции для рендеринга доски, фигур и обработки событий. Основной цикл игры строится на трех этапах: инициализация (pygame.init()), отрисовка (screen.blit()) и обновление экрана (pygame.display.flip()).
Для отрисовки доски создайте двумерный массив 8×8, где каждая клетка представлена координатами (x, y) и цветом. Используйте pygame.Surface((cell_size, cell_size)) для генерации клеток размером 64×64 пикселя – это стандарт для шахматных интерфейсов. Цвета задавайте в RGB: светлые клетки – (240, 217, 181), темные – (181, 136, 99). Рендерите доску в цикле, чередуя цвета по формуле (i + j) % 2 == 0, где i и j – индексы строки и столбца.
Фигуры загружайте из спрайтов или векторных изображений формата PNG с прозрачностью (альфа-канал). Оптимальный размер спрайтов – 56×56 пикселей, чтобы они помещались в клетку с отступом в 4 пикселя. Храните изображения в словаре, где ключи – названия фигур («w_pawn», «b_rook»), а значения – объекты pygame.image.load(). Для масштабирования используйте pygame.transform.scale() с сохранением пропорций. Пример загрузки пешки:
| Код | Описание |
|---|---|
pawn_img = pygame.image.load("assets/w_pawn.png").convert_alpha() |
Загрузка изображения с альфа-каналом |
pawn_img = pygame.transform.scale(pawn_img, (56, 56)) |
Масштабирование до нужного размера |
Обработка ходов реализуется через события мыши. Используйте pygame.mouse.get_pos() для получения координат клика и pygame.Rect.collidepoint() для проверки попадания в клетку. Храните выбранную фигуру в переменной selected_piece, а возможные ходы – в списке кортежей. При клике на клетку проверяйте условия: если клетка пуста – перемещайте фигуру, если занята вражеской – захватывайте. Для анимации перемещения используйте линейную интерполяцию (lerp) между стартовой и конечной позициями с шагом 0.1.
Звуковые эффекты добавляйте через pygame.mixer.Sound(). Для шахмат достаточно трех звуков: перемещение фигуры («move.wav»), захват («capture.wav») и шах («check.wav»). Загружайте их при инициализации и воспроизводите через sound.play() в соответствующих событиях. Оптимальная частота дискретизации – 44100 Гц, формат – WAV. Пример:
| Событие | Звук | Код |
|---|---|---|
| Ход фигуры | move.wav | move_sound.play() |
| Шах | check.wav | check_sound.play(loops=0, maxtime=1000) |
Для отображения подсказок используйте полупрозрачные поверхности. Создайте hint_surface = pygame.Surface((64, 64), pygame.SRCALPHA) и заполните ее цветом (0, 255, 0, 128) для возможных ходов или (255, 0, 0, 128) для шаха. Рендерите подсказки поверх доски после отрисовки фигур. Для текста (например, номера ходов) используйте pygame.font.Font(None, 24) и метод render(). Шрифт Arial с размером 20 пикселей обеспечивает читаемость без нагрузки на производительность.
