Как стать автором
Обновить

Рыбка-советник для chess.com

Уровень сложностиСредний
Время на прочтение6 мин
Количество просмотров1.7K

Один из популярнейших шахматных движков — StockFish. Он бесплатный, у него репозитарий на Github, очень высокий рейтинг 3000+ (больше, чем у любого гроссмейстера), передовые наработки оценки позиции, разработанные в Google для нашумевшего AlphaZero и воспроизведенные позже сообществом в Leela Chess Zero... И у него простой интерфейс взаимодействия через консоль.

То есть можно запустить Stockfish, как дочерний процесс, в консоли передать закодированную позицию, настройки (в том числе, сколько у него времени на обдумывание) и... прочитать ответ. Как же получить позицию?

Некоторое время я обдумывал варианты с машинным зрением. Но на chess.com десятки вариантов досок и фигур в настройках. Да и размер доски и ее расположение может быть разное. Потом я стал думать, как подключиться к браузеру. Но браузеров много, плюс заморочки с чтением чужого процесса... Аддон к браузеру? У меня нет опыта их написания. Решение пришло само — я встрою браузер в мое приложение. Тогда с чтением текущей веб‑страницы проблем не будет.

Окончательный вариант выглядит так - простое WPF приложение с единственным окном и веб-контролом, запускающее StockFish дочерним процессом и взаимодействующее с ним через консольные потоки ввода-вывода. За пять минут создаю новый WPF .NET 8 проект в Visual Studio, накидываю WebBrowser основным контролом, в его настройках даю домашнюю страницу chess.com, запускаю... и ошибки JavaScript, ничего не работает.

Древний WebBrowser до сих пор обращается к библиотекам Internet Explorer, несовместимого с современными веб‑страницами. Нужно что‑то поновее — Chromium или Edge (это тоже Chromium, но другая оболочка). Для обеих вариантов есть библиотеки. Мой основной браузер — Microsoft Edge, поэтому через NuGet ставлю компонент от самого Microsoft — Microsoft.Web.WebView2.

добавляем компонент Microsoft Edge
добавляем компонент Microsoft Edge

Вот теперь все работает. В современном веб-контроле нельзя просто получить доступ к HtmlDocument и DOM, как в старые добрые времена. Мир изменился, страницы динамические, поэтому смотрим содержимое иначе:

const string script = "document.documentElement.outerHTML";
        var result = await webView2.CoreWebView2.ExecuteScriptAsync(script);
        var decodedHtml = Regex.Unescape(result.Trim('"'));

Осталось понять, как вытащить из страницы доску и фигуры на ней. Это оказалось просто. В текущей имплементации доска закодирована набором <div>. Как-то так:

<div class="piece bb square-55" style=""></div>
<div class="piece square-78 bk" style=""></div>
<div class="piece square-68 br" style=""></div>
...
<div class="piece square-61 wk" style=""></div>
<div class="element-pool" style=""></div>
<div class="piece wq square-41" style=""></div>
<div class="element-pool" style=""></div>

Класс фигуры всегда начинается с «piece», первый символ двухбуквенного класса всегда «w» или «b» (белая или черная фигура), второй — сама фигура («r» — ладья, «p» — пешка, «q» — ферзь и т. п.), а «square‑XY» указывает на клетку. 11 — это a1, 88 — h8. Но доска может быть перевернутой, если мы играем за черных. За это отвечает еще один элемент, чуть раньше:

<wc-chess-board class="board">
<!-- или -->
<wc-chess-board class="board flipped">

Наличие «flipped» и говорит о том, перевернута ли доска.

Работаю со StockFish

Попробую поработать с ним сначала вручную. Я скачал бинарный вариант stockfish-windows-x86-64-avx2.exe отсюда. Он не требует установки, можно положить в любую папку. Запускаем, видим пустое консольное окно.

Диалог всегда начинаем с команды «uci». Движок сообщает информацию о себе и завершает вывод словом‑маркером «uciok». Сейчас мы можем задать параметры движка. Зачем? По умолчанию движок работает, как слабый игрок с минимальным рейтингом. Этот факт я обнаружил в процессе проверочных игр с ботами. «Гениальная рыбка» предлагала явно слабые ходы и «зевала» фигуры даже лучше меня. Команда «setoption name UCI_Elo value XXXX» указывает силу игры, где XXXX рейтинг от 1320 до 3190 (напомню, что у гроссмейстеров он в районе 2600–2800). Далее, желательно указать количество тредов, чтобы повысить производительность (по умолчанию задействуется один тред) командой «setoption name Threads value XXXX».

Закончив с настройками, передаю команду «ucinewgame», означающую новую игру. Далее, потребуется указать позицию для анализа, «position fen XXXX», где XXXX — закодированная позиция. Формат я разберу ниже. И попросить принять ее командой «isready». Если все в порядке, движок ответит «readyok». И нам останется запустить анализ командой go с параметрами. Я использовал «go movetime XXXX», где XXXX — сколько миллисекунд дано на обдумывание.

пример диалога со StockFish в ручном режиме
пример диалога со StockFish в ручном режиме

После этого движок начинает перебор вариантов (вываливая в консоль много интересной информации, вроде оценки текущей позиции), и заканчивается он сообщением «bestmove XXXX» (найденный лучший ход). Который я и покажу в статусной строке. А потом поток и дочерний процесс можно закрыть.

if (!string.IsNullOrEmpty(bestMove)) {
    _status?.Report("Best move: " + bestMove);
}

inputWriter.Close();
stockfish.Close();

Кодирование позиции для команды POSITION FEN

Я нарисовал эту диаграмму с моей позицией и пояснениями.

разбор нотации FEN
разбор нотации FEN

FEN состоит из описания восьми горизонталей, разделенных слешами. Например, третья горизонталь выглядит так «2P2N2». «2» — сначала два пустых поля, потом белая пешка («P» в верхнем регистре), потом опять два пустых поля, белый конь («N» верхнем регистре), потом опять два пустых поля. Черные фигуры пишутся в нижнем регистре.

Затем идет информационный блок из шести полей. Зачем они нужны, ведь позиция ясна? На самом деле, нет. В позиции, выдернутой из середины игры, не хватает дополнительной информации. Первое поле «w» — ход белых, или «b» — ход черных. Потом мы должны перечислить возможные рокировки (их четыре — «KQkq», по две за обе стороны) или «», если все рокировки недоступны. Например, король до рокировки может переместиться, а позже вернуться на свое поле. Позиция будет выглядеть «невинно», но возможность рокировки уже потеряна, раз король двигался. Далее, мы должны сообщить, есть ли взятия на проходе — это тоже из позиции не всегда ясно. Предпоследнее поле — счетчик «пустых» ходов. Это нужно для определения ничьи, если долго не было движения пешек и взятий. Последнее поле нужно для определения троекратного повторения позиции. Тут я не заморачиваюсь и сообщаю всегда, что «- - 0 1». Это не совсем точно, рокировка никогда предложена не будет, зато надежно и годится для первой версии советника. За деталями отсылаю к описанию FEN.

После нескольких партий

Я вынес некоторые переменные в App.config для удобства. Можно менять силу игры, время на ход и т.п.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
	<appSettings>
		<add key="StockFishPath" value="D:\Users\Murad\StockFish\stockfish-windows-x86-64-avx2.exe" />
		<add key="Elo" value="1500"/>
		<add key="Timeout" value="5000"/>
		<add key="Threads" value="8"/>
	</appSettings>
</configuration>

Потратил час на странные вылеты StockFish. Оказалось, из‑за ошибки в регулярном выражении в передаваемой StockFish позиции... отсутствовали короли. Иногда движок это проглатывал (особенно, в дебюте), но чаще — завершался. Всегда пишите юнит‑тесты!

Может, в дальнейшем я усовершенствую приложение, добавив оценку позиции, боковую панель с настройками, кнопочки, свистелки и пищалки, но для начала уже неплохо.

Исходники доступны в моем репозитарии. Экспериментируйте!

Заключение

Напомню, что жульничать некрасиво и нечестно по отношению к противнику. Но, признаюсь, я не удержался от соблазна проверить игру с человеком один раз. Для интереса я провел дебют вручную, потом «зевнул» ладью и только потом врубил советы от боженьки на максималках, выставив рейтинг 3190. Это непередаваемое ощущение, когда тебя ведут за руку по игре, делая немыслимые, нечеловеческие комбинации. Лишнюю ладью противника StockFish быстро загнал в угол, и поставил ее на связку с его же слоном, полностью нейтрализовав материальный перевес. Потом, сделав размены, вытащил шахами короля противника в центр пешечной патовой сети. И заставил сделать троекратное повторение позиции, то есть ничью. Представляю, как недоумевал противник, имея лишнюю ладью, выигранную позицию, которая непонятно как становилась все хуже. Извини, бро, это был эксперимент. Уверен, проходная ничья не повлияла на твой рейтинг.

Побед вам!

Теги:
Хабы:
+8
Комментарии9

Публикации

Истории

Работа

.NET разработчик
54 вакансии

Ближайшие события