Привет, Хабр. Меня зовут Владимир, я бэкенд-разработчик. Это моя первая статья здесь — о том, как пет-проект для нишевого варианта шахмат прошёл путь от «а что, если...» до первого места в рейтинге на chess.com. Без нейронок. На чистом alpha-beta поиске, написанном на Rust.
Статья будет полезна тем, кто интересуется шахматным программированием, оптимизацией CPU-bound задач или связкой Python + Rust через PyO3.

#1 в рейтинге Minihouse на chess.com, 23 февраля 2026
О чём вообще речь
На chess.com есть раздел «Variants» — альтернативные шахматные режимы. Один из них — Minihouse (он же 6×6 Crazyhouse). Правила такие:
Доска 6×6 вместо 8×8 (нет ферзя, по одной пешке у каждого)
Crazyhouse-механика: съеденная фигура попадает к вам «в руку», и вы можете поставить её обратно на любую свободную клетку вместо обычного хода
Это создаёт совершенно другую динамику. В обычных шахматах материал теряется безвозвратно. Здесь каждый размен даёт обоим игрокам новые фигуры для сброса на доску. Тактика доминирует над стратегией. Партии часто заканчиваются за 5–10 ходов внезапным матом от сброшенного коня.
Для обычных шахмат существуют Stockfish, Leela Chess Zero и десятки других движков. Для стандартного Crazyhouse (8×8) есть специализированные варианты. А вот для 6×6 Crazyhouse — не было ничего. Пустота.
Я подумал: ну и ладно, напишу сам.
Хронология: 9 месяцев от первого коммита до #1
Девять месяцев звучит внушительно, но на деле это проект «шестого терминала» — так я называю тот терминал, до которого руки доходят, только когда выпадает свободная минутка от рабочих и приоритетных задач. Не full-time разработка, а урывками между основными проектами.
Первый коммит — на чистом Python. Вот как всё развивалось:
Этап 1. Наивный Python (май–июнь 2025)
Классика: minimax с alpha-beta отсечением, всё на Python. Игровую логику для Crazyhouse пришлось писать с нуля — готовых библиотек для 6×6 доски с правилами сброса не существует. Получилось около 860 строк в gamestate.py: генерация ходов, валидация, обработка шахов, промоушен пешек (на 6×6 они доходят быстро).
Движок играл. Плохо. Глубина поиска 3–4 хода, время на ход — секунды. Для Crazyhouse, где каждый ход ветвится в десятки вариантов из-за возможных сбросов, этого мало.
Этап 2. Оптимизации в Python (июль–октябрь 2025)
Добавил всё, что мог, не меняя язык:
Transposition table с Zobrist-хешированием
Null-move pruning (с нюансом: отключается, когда у противника есть фигуры в руке — иначе пропускаешь опасные сбросы)
Late Move Reduction (LMR)
Iterative deepening с aspiration windows
Move ordering: MVV-LVA, killer moves, history heuristic
Стало лучше. Глубина 5–6. Но Python не предназначен для перебора миллионов позиций.
Этап 3. Переписываем ядро на Rust (ноябрь 2025)
Ключевой коммит: «Rewrite chess engine core in Rust via PyO3 for ~50x speedup».
Я переписал весь поиск и функцию оценки на Rust, оставив Python-обёртку для совместимости с GUI и ботом. Связка через PyO3 — Rust компилируется в нативный Python-модуль, который вызывается как обычный import.
Результат: глубина 8–10 ходов, ~3.8 секунды на ход. Для 6×6 доски это серьёзная глубина — дерево поиска с учётом сбросов на маленькой доске очень «кустистое».
Rust-ядро — ~3300 строк:
Модуль | Назначение | LOC |
|---|---|---|
Alpha-beta + PVS + quiescence + null-move | ~1000 | |
Генерация ходов, make/undo | ~750 | |
Функция оценки позиции | ~440 | |
Типы данных (фигуры, ходы, доска) | ~330 | |
SQLite-кэш позиций | ~100 | |
Хеширование позиций | ~75 | |
PyO3-биндинги | ~600 |
Этап 4. Бот для chess.com (декабрь 2025)
Дальше нужен был способ играть против реальных людей. Написал автоматизацию на Playwright (~2000 строк): логин, поиск игры в Minihouse, распознавание доски, выполнение ходов, обработка результатов. Бот заходит на chess.com через обычный Chrome, находит партию и играет.
Важный момент: на chess.com я всегда открыто сообщал, что играет бот. Никакого обмана — соперники знали, с кем имеют дело.
Этап 5. Ночное обучение на сервере (январь–февраль 2026)
У меня есть выделенный сервер с 12 CPU-ядрами. Днём они заняты рабочими задачами. А ночью — свободны.
Написал systemd-сервис + таймер. Каждый день в 00:00 UTC запускается процесс. Он ждёт до 02:00 UTC (когда нагрузка от рабочих задач гарантированно падает), потом 8 часов играет сам с собой: AI за белых против AI за чёрных. Глубина поиска 6, 20% ходов — «исследование» (вместо лучшего хода берётся второй лучший). В 10:00 UTC — graceful shutdown, ресурсы возвращаются рабочим задачам.
# minichesstrain.timer [Timer] OnCalendar=*-*-* 00:00:00 Persistent=false
За ночь набирается несколько десятков партий, каждая позиция и лучший ход сохраняется в SQLite. К февралю 2026 — 27 519 закэшированных позиций. Когда движок видит знакомую позицию, он не тратит время на пересчёт — сразу берёт ответ из кэша.
Функция оценки: чем Crazyhouse отличается от обычных шахмат
Вот тут самое интересное с инженерной точки зрения. Нельзя просто взять стандартную шахматную eval-функцию — в Crazyhouse другие приоритеты.
1. Фигуры в руке стоят дороже, чем на доске
В обычных шахматах конь стоит ~320 очков. В Crazyhouse конь в руке стоит больше — он может быть мгновенно сброшен на любое свободное поле. Движок оценивает ручные фигуры с множителем 60–100% к базовой стоимости.
2. Прогрессивная угроза сброса
Чем больше фигур в руке, тем опаснее каждая следующая. Одна фигура в руке — это угроза. Три фигуры в руке — это мат через 2 хода. Формула нелинейная:
let drop_threat = (hand_total) * DROP_THREAT_BONUS + hand_total.max(1) * (hand_total - 1).max(0) * 10;
3. Безопасность короля — это всё
На доске 6×6 король никуда не спрячется. Пешечный щит (пешки перед королём) оценивается в +55 очков за каждую пешку. А вот голый король при фигурах у противника в руке — это штраф до –390 очков. Движок прямо паникует, если король открыт.
if pawn_shield == 0 && opponent_hand_pieces > 0 { score -= EXPOSED_KING_PENALTY * opponent_hand_pieces.min(3); // До -130 * 3 = -390 штрафа }
4. Конь-сброс с шахом
Конь в руке, который может быть сброшен ря��ом с вражеским королём — это отдельный бонус +40. Ладья, которая может сесть на линию короля — +25. Движок считает не только текущие угрозы, но и латентные — фигуры в руке, готовые к атаке.
5. Проходные пешки обесценены
В обычных шахматах проходная пешка — это сила. В Crazyhouse противник может просто сбросить фигуру на её пути. Поэтому бонус за проходные пешки существенно снижен по сравнению с классикой.
Нейронка? Пробовали. Не зашло.
В проекте есть папка nn/ с AlphaZero-подходом: CNN для policy + value (PyTorch), MCTS, RL-обёртка. Писать маленькие нейронки я умею, и экспериментов было много — разные архитектуры, разные подходы к обучению. Но ничего толкового для этой задачи не вышло. Возможно, плохо старался — но скорее дело в объективных ограничениях:
Маленький датасет. Для 6×6 Crazyhouse нет базы партий — всё нужно генерировать самостоятельно
Ограниченные ресурсы. У меня 12 CPU ядер по ночам, а не кластер GPU
Alpha-beta оказался достаточно хорош. На маленькой доске классический поиск на глубину 8–10 с хорошей eval-функцией покрывает большинство тактических ударов
В итоге ручная eval на 440 строках Rust оказалась эффективнее всех моих нейросетевых экспериментов. Для ниши, где нет конкуренции — хорошо настроенная классика работает.
Результаты: 228 партий на chess.com
Бот отыграл 228 партий на chess.com в режиме Minihouse. Общая статистика:
Метрика | Значение |
|---|---|
Побед | 212 (93.0%) |
Поражени�� | 12 |
Ничьих | 4 |
Мат | 139 партий |
Противник сдался / время | 65 партий |
Средняя длина победной партии | 8.1 ходов |
Рейтинг (пик) | ~2300 |

Победа над бывшим первым номером рейтинга
Средняя победная партия — 8 ходов. Большинство людей просто не успевают понять, что произошло: движок находит тактику со сбросами, которую человек не видит.
Ключевой этап: игры с топ-20 мира
Самый важный скачок в качестве движка случился не от оптимизации кода, а от правильных соперников. Self-play и случайные матчи не показывали слабости стратегии — движок стабильно побеждал, но не рос. Всё изменилось, когда я договорился об играх с игроками из топ-20 мира по Minihouse.
Один соперник из самого топа согласился рубиться с ботом регулярно — несмотря на то, что движок думал мучительно долго. Именно эти партии вскрывали дыры в eval-функции: тут неправильно оценён сброс, тут слабый пешечный щит, тут движок не видит позиционную угрозу. После каждого поражения я анализировал, правил оценку, и дело пошло семимильными шагами.
Ночь, которая всё решила
До 22 февраля первое место в рейтинге Minihouse на chess.com занимал другой игрок. Мой бот был вторым.
Вечером 22-го я запустил бот в режиме автоматического поиска игр. Он играл всю ночь — и к утру 23 февраля отыграл 138 партий, из которых выиграл 134. Но самое главное — движок уверенно побеждал игроков из топ-20, включая бывшего первого номера.
Поражения были в основном в начале, когда движок ещё не встречал стиль конкретного противника. Потом позиции из проигранных партий попали в кэш — и бот перестал наступать на те же грабли.
Утром я открыл chess.com и увидел, что мой аккаунт — #1 в Minihouse. Ощущение примерно такое: ты просыпаешься, а твой код за ночь сделал то, на что ты потратил 9 месяцев.
Побочный эффект: я сам стал играть сильнее
Забавная вещь: пока я писал и тестировал движок, я параллельно играл в Minihouse сам, без подсказок. До проекта я стабильно держался в топ-100, на хорошей полосе залезал в топ-50. Но после месяцев работы над eval-функцией и анализа тысяч партий бота мне в голову залезли все эти паттерны: сброс коня с шахом, пешечный щит, контроль центра на маленькой доске.
Результат — я ворвался в топ-20 по Minihouse. По-честному, своими руками. Оказалось, что писать шахматный движок — это лучший способ научиться играть в шахматы.
Стек целиком
Python 3.11 — клей, GUI, бот ├── Rust (PyO3) — поисковый движок, eval, хеширование ├── Pygame — локальный GUI для тестов ├── Playwright + Chrome — автоматизация chess.com ├── SQLite — кэш позиций (27.5K записей) ├── systemd timer — ночной self-play на сервере └── PyTorch — попытка нейронки (не используется)
Что я вынес из этого проекта
1. Rust через PyO3 — это магия. Переписывание узкого места с Python на Rust дало 50x ускорение при сохранении того же Python API. Если у вас CPU-bound задача в Python — серьёзно посмотрите на PyO3.
2. Domain-specific eval > Generic neural net (при ограниченных ресурсах). Нейронки побеждают, когда есть данные и GPU. Когда их нет — ручная настройка eval-функции под конкретный вариант шахмат работает лучше.
3. Ниша — это суперсила. В популярных шахматах мой движок не вошёл бы даже в топ-1000. Но в 6×6 Crazyhouse конкуренции почти нет — и небольшой, но хорошо настроенный движок может стать лучшим.
4. Self-play на рабочих серверах по ночам — рабочая стратегия. 8 часов * 12 ядер каждую ночь — и за пару месяцев движок накопил 27 тысяч позиций, которые работают как opening book.
5. Null-move pruning нужно отключать в Crazyhouse, когда у противника есть фигуры в руке. Это я выяснил после нескольких непонятных проигрышей — движок «пропускал» ход и не видел, что противник может сбросить коня с шахом.
Что бы сделал иначе
Оглядываясь назад:
Сразу писать ядро на Rust. Потратил месяцы на оптимизацию Python, а 50x ускорение всё равно пришло только с Rust. Прототипировать на Python — ок, но не стоило так долго в нём задерживаться.
Раньше начать играть с сильными игроками. Self-play и рандомные матчи не вскрывали слабости eval-функции. Реальный рост начался только после партий с топ-20.
Логировать подробнее. Первые месяцы я не записывал, почему движок выбрал конкретный ход. Когда начал логировать дерево поиска — отлаживать eval стало в разы проще.
Эпилог: бан
Через несколько дней после выхода на первое место chess.com забанил аккаунт бота. Формально — за использование компьютерной помощи. Технически они правы: это буквально компьютер. Хотя я открыто сообщал в профиле и в чатах, что играет бот, — для системы античита chess.com это не имеет значения. Fair play policy одинакова для всех.
Обидно? Немного. Но #1 в рейтинге зафиксирован на скриншоте, код и 27 тысяч позиций никуда не делись, а главное — опыт и знания остались. Да и если честно, попасть на #1 и тут же получить бан — это какой-то идеальный финал для пет-проекта.
Проект живёт дальше. Движок можно использовать локально, натренировать свой кэш, или просто поиграть через GUI. А можно написать бота для Lichess — там, кстати, бот-аккаунты официально поддерживаются.
Проект полностью open source: GitHub. Можно форкнуть, поменять eval-функцию под свой стиль, натренировать свой кэш позиций и запустить собственного бота.
А если не играли в Minihouse — попробуйте. Это шахматы на стероидах: быстрые, тактически дикие, и каждая партия непохожа на предыдущую. Доска маленькая, партии по 2–3 минуты. Идеальный формат, чтобы убить обеденный перерыв.
Буду рад вопросам в комментариях — особенно от тех, кто работал с шахматным программированием, PyO3 или оптимизацией перебора.
