PlayBelote
Белот в реално време за много играчи: детерминиран модул с правилата, ботове по метода Монте Карло и формат за запис на игрите
- Роля
- Сам, ръководя екипи от агенти
- Статус
- Работи
- Код
- Частно хранилище
- Технологии
- TypeScriptNext.js 16React 19Phaser 3ColyseusPrismaPostgreSQL 16PgBouncerNginxPM2VercelHetznerVitestPlaywrightfast-check

В числа
13 123
игри между ботове в 20 първенства по 128 отбора
10/ 10
двойки игри със зададено начално число, при които новият клиент за масата визуално съвпада с основния клиент на Phaser
1.4
текуща версия на BGN, формата за запис на игри по образец на шахматния PGN; прегледът сменя гледната точка между местата
12
вида ботове, оценени с ELO в серията от 20 първенства
Проблемът
В белота картите на играчите са скрити, има обявяване, комбинации и точкуване с много изключения. Онлайн масата има нужда от модул с правилата, на който се доверяват и клиентът, и сървърът, и от достатъчно силни ботове, които да заемат празните места. Освен това играта трябва да издържа на повторно свързване и на превключване между раздели. Не пиша кода на ръка: задавам работата, агентите я изграждат, а аз проверявам резултата по числа.
Подходът
Кодът има три слоя: shared/ е чист детерминиран модул (генератор на случайни числа със задаваемо начално число, без входно-изходни операции), който ползват и двете страни, server/ е сървър за стаи на Colyseus, разделен на оркестратор и шест управляващи модула, а src/ е приложението на Next.js с платно (canvas) на Phaser. Проверката става в дълги изпълнения с договореност, записана в хранилището: оценка на ботовете в 20 турнира, изпълнения на Codex без надзор, преглед със 120 агента, при който втори агент се опитваше да опровергае всяка находка, преди тя да стане задача, и по-късен пълен преглед.

Как работи
Ботове, които разиграват различни варианти на скритите карти
Ботът по метода Монте Карло раздава невидимите карти на случаен принцип, но в съответствие с разкритото на масата: вече изиграните карти и боите, в които играч е показал, че няма карти. Той изиграва 60 симулации при всяко решение коя карта да пусне и 40 при всеки анонс, и обявява само когато печели над 55% от симулациите. В турнирите играят и дванадесет бота с различен характер, работещи по евристични правила, заредени предварително като 128 постоянни отбора от ботове.
13 123 игри в 20 първенства
Сървърът в продукция изигра 20 поредни първенства със 128 отбора: девет кръга по швейцарска система, после най-добрите 32 отиват в директни елиминации (най-добър от три, финалът най-добър от пет, игра до 151 точки). Така се стигна до 13 123 игри и 180 362 раздавания, оценени с ELO по вид бот. Първите два вида са наравно на 1531, а разликата между първия и дванадесетия е 55 точки ELO (от 58,9% до 42,1% победи). Серията откри и изтичане на памет: приключилите стаи, в които играят само ботове, никога не се освобождаваха, и след 11-ия турнир сървърът остана без памет при 1,8 GB. Сега тези стаи се затварят, когато играта свърши.
BGN, нотация за белот
Belote Game Notation е изградена по образеца на шахматния PGN и има версии, сега е 1.4. Сървърът записва всяка игра, играчите свалят .bgn файла и минават през него ход по ход в програма за преглед, която сменя гледната точка между местата. Прегледът откри, че записаните файлове съдържат грешна разбивка на точките, и версия 1.4 поправи формата.
Грешката с Alt+Tab, записана като седем инварианта
В един момент връщането към раздела направи играта неиграема: раздаванията ставаха мигновено, изчезваха карти, настъпваше срив в сцената. Веригата от причини: next-auth зареждаше отново сесията при фокус на прозореца и връщаше нов обект със същите данни; ефектът за свързване на страницата на играта имаше този обект в зависимостите си, затова се изпълняваше пак и вдигаше флаг за зареждане; условието за зареждане премахваше платното на Phaser. Всяко връщане към раздела унищожаваше играта и я изграждаше наново. Тестовете от край до край останаха зелени, защото помощната им функция създаваше отделна стая, която заобикаляше ефекта за свързване на страницата. Поправката идва със седем писмени инварианта, тоест условия, които трябва да важат винаги (платното остава в страницата през цялата сесия, ефектът за свързване зависи само от стабилни прости стойности, повторното свързване е слой върху масата, всеки await в обработчик на сцената е граница, на която сцената вече може да е разрушена), и със задължителна проверка през истинското лоби: скриптирано превключване с Alt+Tab веднъж, пет пъти и 90 пъти, без нито един запис за разрушаване в конзолата.
Изпълнения без надзор, оценявани по картината
По-дълбоката поправка беше втори клиент за масата, който се изчертава от състоянието на играта. Codex го изгради в изпълнения без надзор по договореност, записана в хранилището: входна проверка на изходното състояние, детерминирани тестове, пускания в истински браузър, после група агенти-съдии, които гледат картината и сравняват кадър по кадър една и съща игра с еднакво начално число в клиента на Phaser и в новия клиент, после група агенти-противници и накрая писмен доклад. Правилата в договореността: проверявай сам всяко число, не пипай модула с правилата, сървъра и базата данни, поправяй напред, без връщане назад, и дръж Phaser като клиент по подразбиране. Второто изпълнение завърши с 2 499 преминали теста и 10 от 10 двойки игри на съвпадение. Третото раздели списъка за проверка на качеството по групи файлове, защото двама агенти, които редактират един и същ файл, си пречат.
Как работи в продукция
Клиентът е във Vercel. Сървърът на играта, Postgres 16, PgBouncer, Nginx и PM2 работят на Hetzner VPS, след преместване от Render и Neon. Новите версии се пускат чрез webhook, всяка нощ се прави резервно копие на базата данни и копията се пазят 31 дни, а ограничител на заявките заедно със следене на невалидни действия прекъсва връзката на клиенти, които продължават да пращат невалидни ходове.

Какво избрах и какво отпадна
Избрах
Изчертаване на масата от състоянието (моментна снимка плюс пропуснатите събития) като втори клиент до този на Phaser
Пред
Още кръпки по клиента на Phaser, който работи по събития
Той предполагаше, че всяко събитие идва веднъж и по ред. Повторното свързване и връщането към раздела нарушават това предположение.
Избрах
Phaser остава клиентът по подразбиране, докато аз не го сменя, а новият клиент трябва да съвпада с него кадър по кадър в игри със зададено начално число
Пред
Смяна на клиента, щом тестовете станат зелени
Тестовете за коректност не бяха достатъчни: грешката с Alt+Tab беше минала през зелени тестове от край до край.
Избрах
v2 се гради изолирано от v1: агентите могат да четат правилата, заданието и новото проучване, но нищо от v1
Пред
Доразвиване на v1 или разрешение агентите на v2 да четат кода и спецификациите му
Ползвам правилата и оставям предположенията на v1 зад гърба си.
Резултатът
Работи на playbelote.vercel.app. Последното записано пълно изпълнение: 2 838 минаващи теста в 98 файла. По-късен преглед заведе 31 задачи (3 с критична и 10 с висока важност), като две от критичните находки, и двете от страна на сървъра, още са отворени. Мобилно приложение няма: Capacitor е подготвен, но проектът за Android никога не е добавен. v2 съществува, но не е пусната. Тя е 3D преработка за един играч, изградена изолирано от v1, с две теми (луксозна стая за карти и пародия на механа) и с ботове, които използват детерминизиран метод Монте Карло върху до 48 примерни разпределения на скритите карти и точен минимакс, когато останат две карти. Фази от 1 до 3 минаха проверките си със 717 теста, а проверката за производителност „без забавяне“ изпълнява 6 от 9-те си критерия. Нищо не е качено в хранилището, а при предаването на работата не бях играл готовата версия.
Какво следва
v2 е изградена така, че по-късно да поеме игра за много играчи: актьор за масата (table actor) във Web Worker държи състоянието и часовниците, клиентите говорят по протокол, проверяван със zod, с пореден номер на съобщенията и филтриране на скритата информация за всяко място, и ще се смени само транспортът, от postMessage към WebSocket.
Галерия
Публичната входна страница.
Анонсът е направен, ботовете пасуват и се изиграват две ръце.
Ръка в новия графичен модул за масата, с видими анонс и комбинации.
Игрите се изнасят в текстов формат за запис и могат да се възпроизведат отново.
Раздаване в графичния модул за масата: кандидатът в проверка за еднаквост.
Същото раздаване в базовия вариант с Phaser.