Один из популярнейших шахматных движков — 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.
Вот теперь все работает. В современном веб-контроле нельзя просто получить доступ к 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 — сколько миллисекунд дано на обдумывание.
После этого движок начинает перебор вариантов (вываливая в консоль много интересной информации, вроде оценки текущей позиции), и заканчивается он сообщением «bestmove XXXX» (найденный лучший ход). Который я и покажу в статусной строке. А потом поток и дочерний процесс можно закрыть.
if (!string.IsNullOrEmpty(bestMove)) {
_status?.Report("Best move: " + bestMove);
}
inputWriter.Close();
stockfish.Close();
Кодирование позиции для команды POSITION 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 быстро загнал в угол, и поставил ее на связку с его же слоном, полностью нейтрализовав материальный перевес. Потом, сделав размены, вытащил шахами короля противника в центр пешечной патовой сети. И заставил сделать троекратное повторение позиции, то есть ничью. Представляю, как недоумевал противник, имея лишнюю ладью, выигранную позицию, которая непонятно как становилась все хуже. Извини, бро, это был эксперимент. Уверен, проходная ничья не повлияла на твой рейтинг.
Побед вам!
Комментарии (20)
AppCrafter
26.12.2024 18:55не отказался бы от такой програмулины на телефоне. А то вот тоже бот обыгрывает, потому что зеваю.
ALexKud
26.12.2024 18:55Надо не читерством заниматься, а регулярно решать шахматные задачи если шахматы интересны и вы подсели на них. Это в какой-то мере избавляет от одноходовых зевков и играть не на телефоне а за компом и с нормальным экраном, что то же способствует избавлению от зевков, по крайней мере постоянных.
net_racoon
26.12.2024 18:55Год на это потратил, даже на 100ку не продвинулся. Просто кому-то это дано, а кому-то писос...
kanasero
26.12.2024 18:55Чем конкретно занимались? Если просто играли, то смысла в этом немного. Нужны систематические занятия - решение задач, анализ своих партий, чтение шахматных книг и пр. Если этим заниматься на регулярной основе, наверняка продвинулись бы.
Другое дело, какой в этом смысл? Если это просто хобби для души, то обычно так серьезно и основательно за изучение шахмат не берутся, лично мне просто жалко тратить на это много времени. Другое дело в детстве в детстве, тогда был смысл, интерес продвинуться, показать себя и пр., а сейчас интересы немного сместились, а шахматы только как хобби.
wmlab Автор
26.12.2024 18:55Это правда - у каждого есть свой лимит, связанный с особенностями мышления. Тренировками можно количественно вырасти, но ненамного. Качественных сдвигов не будет. Например, моя беда - туннельное зрение, я упускаю из вида кучу возможностей, фокусируясь на чем-то одном с самого начала. Это и в жизни заметно, а гениальные шахматы - это отражение реальной жизни.
serg_meus
26.12.2024 18:55У меня рейтинг 1900-2000, правда, на другой платформе -- Lichess. Я считаю, что создание такого бота -- это превосходный хобби пооект, вот только не верится, что от этого улучшится понимание шахмат и поднимется сила игры. Лучше уж по-старинке: читать Нимцовича и других классиков, решать тактические задачки, играть партии с длинным контролем времени, после чего разбирать свои ошибки и противника, поиграть немного ультра-короткие партии, чтобы, что называется, набить руку. Если реально хотите выйти на новый уровень, можно записаться на занятия с тренером. Мне слегка помогло участие в городских офлайн-турнирах
tkutru
26.12.2024 18:55Надо было делать скриншот области экрана, распознавать доску, переводя в FEN, и потом спрашивать у движка. Распознавание доски - интересная задача машинного обучения и компьютерного зрения. Чтоб работало на любых сайтах и досках.
Human_1988
26.12.2024 18:55Спасибо за статью. Воспользуюсь, чтобы оценить отобранные позиции стокфишем, для дальнейшего тюнинга этими позициями с адекватными оценками - оценочной функции своего движка.
Не совсем только понял про рыбку, ведь это совсем другой движок был, скорее уж вяленая рыбка. А также прошу пояснить причëм тут оценка Alfa Zero и lc0, насколько мне известно они совсем на других принципах работают нежели стокфиш, и оценка у них совершенно по-разному устроена. Да, и там и там за оценку отвечают нейросети, но они совершенно непохожи по архитектуре, насколько удалось понять. Если неправ, прошу поправить.
wmlab Автор
26.12.2024 18:55Архитектура Stockfish и LC0 разная. Разработчики Stockfish внедрили оценку позиции нейросетью, начиная с версии 12 (2020), как это сделано в LC0. Тоже двумя сетями, быстрой и медленной (nn-37f18f62d772.nnue и nn-1111cefa1111.nnue). Отличие в том, что LC0 делает упор на нейросети, играя сам с собой и постепенно улучшая веса сети, а Stockfish - на традиционном переборе вариантов и древнем альфа-бета отсечении (еще в "Кассандре" он работал), и только оценивая позиции нейросеткой.
Its-alright
26.12.2024 18:55Интересно, можно ли из этого сделать анализ сыгранных партий как на ческоме?
wmlab Автор
26.12.2024 18:55Только из этого - нет. Нужно задействовать еще и библиотеку дебютов. Анализ на chess.com говорит, какие ходы сделаны по теории, а какие - нет. Потом я бы задействовал еще не опробованную команду eval, чтобы получить оценку текущей позиции и предыдущей, чтобы понять по разнице, был ход хорошим или плохим. Потом надо найти лучший ход в предыдущей позиции, чтобы показать упущенные возможности. Надо считать вручную материальный баланс и сообщать об этом, как делает анализ на chess.com. А так - да, интересная задача.
wmlab Автор
26.12.2024 18:55Дополню. Можно вообще рисовать свою доску в другом веб-контроле, дублируя позицию с chess.com, но накладывать на нее свои элементы типа подсказок, анализа, комментариев от советчика. Думаю, это будет полезно для обучения.
dio-gen
26.12.2024 18:55Эрик (создатель и владелец chess.com; когда-то я для него делал некоторые работы для chess.com) очень не любит читерства. Там есть механизмы, которые проверяют партии. За прошлую неделю мне вернули 15 очков рейтинга за две партии, где меня обыграли нечестно.
wmlab Автор
26.12.2024 18:55Если бы мне поставили задачу выявить, жульничает ли мой противник, я бы сравнивал предлагаемые движком ходы на максимальном рейтинге с его ходами. При большом количестве совпадений - красный флаг. Только не на самых первых ходах и не в эндшпиле, когда многие по теории играют.
Если не секрет, что именно Вы для Эрика делали? Что-то связанное с шахматами или с сайтом?
click0
Это не мешает существованию сайтам типа "chess next move" и множеству плагинов к браузеру со словом "сhess".
Нужна обратная задача - в ходе партии наоборот находить компьютерные хода оппонента :)
GennPen
Обычно это вычисляется не в ходе партии, а по кривой рейтинга. Ну не может быть такого, что человек сидел на своем пределе 1500-1600, а потом резко пошел вверх.
Ну, можно еще вычислять кол-во идеальных ходов относительно своего рейтинга.
click0
Это один из многих показателей (красных флажков) :)