Skip to content
Barcha maqolalar
Sun’iy intellekt8 daq

Damka.uz uchun neyrotarmoqni shashka o‘ynashga qanday o‘rgatdik

Damka.uz bizning o‘z mahsulotimiz, rus shashkasi uchun onlayn platforma. Ikki yil ichida uning dvigatelini uch marta qayta yozdik. Nega shunday bo‘lgani va nimasi arziydi, nimasi arzimasligi haqida yozaman.

Azamat AbduyaminovAsoschi, yetakchi muhandis

Damka’ning birinchi versiyasida repozitoriyda TODO.md fayli bor edi va uning birinchi bandida taxminan shunday yozilgandi: bot minimaksdan foydalanadi, qidiruv chuqurligi 3, chunki Python sekin, va bu kuchli raqib emas. Keyin yechimning ikki varianti kelardi: C yoki Go’da yozilgan tayyor dvigatelni olish yoki o‘z-o‘zidan o‘rganadigan AI qilish.

Oxir-oqibat ikkalasini ham qildik. Faqat bunga ikki yil va uchta arxitektura ketdi.

Studiyaga o‘z shashkasi nima uchun kerak

O‘zbekistonda shashkani hamma joyda o‘ynashadi: hovlilarda, choyxonalarda, Telegram guruhlarida. Majburiy urish va istalgan masofaga yuradigan «damka»li 8×8 taxtadagi rus shashkasi. Shu bilan birga bizda o‘zbek tilida normal onlayn maydoncha yo‘q edi. Damka.uz’ni o‘z mahsulotimiz sifatida qilishga qaror qildik: onlayn, do‘st bilan havola orqali va botga qarshi o‘ynash.

Studiya uchun bu yana sinov maydoni ham. Mijoz loyihalarida WebSocket yuklamasi va Rust bilan tajriba qilishga hech kim ruxsat bermaydi. O‘zimiznikida mumkin.

Ikki yilda uchta versiya

  1. 2024
    v1: Django + Nuxt 3. Partiyalar Django Channels orqali WebSocket’da, qoidalar pydraughts kutubxonasidan, bot alfa-beta kesish bilan minimaksda, chuqurlik 3.
  2. 2026-yil boshi
    v2: Django 6 + Channels + Nuxt 4. Negamax’dagi o‘z qidiruv dvigatelimiz, matchmaking, ELO, kuzatuvchilar, tomoshabinlar uchun pozitsiya baholash chizig‘i.
  3. 2026
    Parallel ravishda: baholovchi neyrotarmoqni production’dan alohida, Go’dagi alohida dvigatelda o‘qitish.
  4. Hozir
    v3: ichida dvigatel va neyrotarmoq bo‘lgan Rust’dagi o‘yin serveri (actix-web, WebSocket), React’da front, Next.js’da admin, guruhlarda o‘ynash uchun @DamkaUzBot.

1-versiya: ishlaydi, lekin bot zaif

Birinchi versiya halol MVP edi. Partiya WebSocket orqali borardi, pydraughts kutubxonasi qoidalarni tekshirardi, bot yurishlarni minimaks bilan saralardi. 3 chuqurlik botning har ikki tomon uchun bir yarim yurish oldinga qarashini bildiradi. Hovlida bo‘lsa ham o‘ynab yurgan har kim uni yutardi. Python’da chuqurlikni 5–6 ga ko‘tarish botning javobini bir necha soniya kutish degani edi.

2-versiya: o‘z dvigatelimiz va birinchi yuklama

Ikkinchi versiyada yoqimsiz narsani aniqladik: kutubxonaning ichki dvigateli 8×8 rus taxtasida noto‘g‘ri ishlardi. Qidiruvni noldan yozishga to‘g‘ri keldi: alfa-beta kesishli negamax, baholash uchun bitbordlar, transpozitsiya jadvali, vaqt byudjeti bilan iterativ chuqurlashtirish. Bularning hammasi botli bitta partiya qolganlarini bloklamasligi uchun jarayonlar pulida bajarilardi.

Shu bilan birga Redis’dagi tartiblangan to‘plamda matchmaking, ELO reytingi va chess.com’dagidek jonli baholash chizig‘i bilan kuzatuvchi rejimi paydo bo‘ldi.

Keyin yuklama testini ishga tushirdik va shu versiyaning asosiy saboqi yuz berdi.

Aslida qayer sekin edi

Real ssenariyni sinab ko‘rdik: o‘yinchilar ulanadi, raqib topadi, yurish qiladi. Serverni 2 yadroli odatiy VPS hajmigacha cheklab qo‘ydik. 300 ta bir vaqtdagi partiyada yurishning median kechikishi 6,7 soniya edi va deyarli barcha partiyalar xatolar bilan yiqilardi.

Birinchi fikr: Python sekin, qayta yozamiz. Lekin protsessor 50% yuklangan edi. Server band emas edi, u navbatda turgan edi.

Profillash uchta sababni topdi. Bitta Python jarayoni GIL’ga tirab qolib, ikki yadrodan bittasini egallardi. ORM so‘rovlari bitta oqimda birma-bir bajarilardi. Baza bilan ulanishlar har bir so‘rovda qayta ochilardi, ustiga matchmaking Redis kalitlarini bloklovchi KEYS orqali skanerlardi.

6 700 ms
tuzatishlargacha yurishning median kechikishi (300 partiya, 2 yadro)
17 ms
uchta tuzatishdan keyin o‘sha kechikish
~100
2 vCPU’li server bemalol ko‘taradigan bir vaqtdagi partiyalar
~350
6 vCPU’da, o‘sish deyarli chiziqli

Bir nechta uvicorn worker’lari, bazaga parallel kirish va doimiy ulanishlar boshqa tilda birorta qator yozmasdan 17 ms kechikish berdi. Agar o‘shanda darhol Rust’ga ketganimizda, muammo tilda emasligini hech qachon bilmay qolardik.

Nega baribir Rust

Agar Python yuklamani uddalagan bo‘lsa, nega qayta yozdik? Bot sababli.

Python’dagi o‘yin serveri xabarlarni bemalol uzatadi. Lekin yurishlar daraxti bo‘yicha qidiruv soniyasiga millionlab pozitsiya degani va bu yerda interpretator kompilyatsiya qilinadigan tildan o‘nlab marta yutqazadi. «Dvigatel Rust’da, server Python’da, FFI orqali» variantini muhokama qildik. Ikki til, ikki deploy va ular orasida chegara chiqardi, undan har bir yurishni seriyalashtirib o‘tkazish kerak bo‘lardi.

Dvigatel va server bitta Rust binarida bo‘lishiga qaror qildik. Server dvigatelni oddiy Cargo-kreyt sifatida ulaydi va yurishni tekshirish funksiya chaqiruvi narxida turadi. Xuddi shu dvigatel WebAssembly’ga kompilyatsiya qilinadi va brauzer serverdan so‘ramasdan ruxsat etilgan yurishlarni o‘zi ko‘rsata oladi.

Dvigateldagi taxta 16 bayt egallaydi: ikkita u64. Ularga yurish navbati, har biri 3 bitdan 32 ta o‘yin katagi va durang qoidasi uchun faqat damkalar bilan qilingan yurishlar hisoblagichi sig‘adi.

Qo‘lda baholash o‘rniga neyrotarmoq

2-versiyada pozitsiyani baholash qo‘lda yozilgan edi: dona uchun shuncha ball, damka uchun shuncha, markaz va oldinga siljish uchun bonuslar. Bunday funksiyalar tezda shiftga tiraladi, chunki inson pozitsiyani yaxshi qiladigan hamma narsani ta’riflay olmaydi.

v3’da pozitsiyani kichik to‘liq bog‘langan neyrotarmoq baholaydi. U alohida, Go’dagi dvigatelda o‘qitilgan, Rust’ga esa tayyor og‘irliklarni ko‘chirdik.

// Kirish: 32 katak, har birida -K dan +K gacha qiymat
// (dona = ±1, damka = ±K, bo‘sh = 0).
// Yashirin qatlamlar tanh bilan, oxirgi qatlamga
// materialning oddiy yig‘indisi qo‘shiladi.
pub const LAYERS: [u16; 4] = [32, 40, 10, 1];
pub const WEIGHTS_LEN: usize = 1280 + 400 + 10;

Tarmoq ataylab kichik. U qidiruv daraxtining har bir bargida chaqiriladi va katta tarmoq yutilgan tezlikning hammasini yeb qo‘yardi. Dvigatelga tarmoqning o‘nta o‘qitilgan varianti tikilgan, jami taxminan 140 KB. Turli variantlar turlicha o‘yin xarakterini beradi va bu qiyinlik darajalari uchun qulay.

Endshpilda, taxtada faqat damkalar, beshtagacha qolganda, dvigatel umuman o‘ylamaydi: javobni oldindan hisoblangan jadvaldan qaraydi. Bitta yozuv bir bayt egallaydi.

206 KB
WebAssembly’dagi dvigatel hajmi
715
etalon bilan solishtirish uchun test to‘plamidagi pozitsiyalar
10
dvigatel ichidagi neyrotarmoqning o‘qitilgan variantlari
5
endshpil jadvalidagi damkalarning eng ko‘p soni

Hech narsani buzmaganimizni qanday tekshirdik

Dvigatelni bir tildan boshqasiga ko‘chirish qo‘rqinchli: qoidalardagi har qanday farq yurish tanlovini o‘zgartiradi, buni esa ko‘z bilan payqab bo‘lmaydi. Etalon dvigateldan to‘g‘ri javoblari bilan 715 ta pozitsiyadan iborat to‘plam yig‘dik va uni har bir o‘zgarishda ishga tushiramiz.

Bu to‘plam haqiqiy xatoni topdi, faqat yangi kodda emas, etalonda. Rus shashkasida agar urishni davom ettirish mumkin bo‘lsa, uni davom ettirish shart va bu oddiy donalarga ham, damkalarga ham tegishli. Etalon dvigatelda davom ettirish bayrog‘i yo‘qolib qolardi va damka uchun ba’zan qoidalar ruxsat bermaydigan qisqa urish generatsiya qilinardi. Rust versiyasida qoida to‘g‘ri ishlaydi, farq esa testlarda alohida yozib qo‘yilgan.

Telegram’da shashka

Alohida hikoya — @DamkaUzBot boti. Odamlar «o‘ynash uchun saytga kirishni» istamasligini payqadik. Ular guruh chatida o‘tirishadi va aynan o‘sha yerda o‘ynashni xohlashadi. Bot guruhda partiya boshlash va IDF qoidalari bo‘yicha guruh turnirlarini o‘tkazish imkonini beradi: juftliklar qur’asi, turlar, Buxgolts bo‘yicha tay-breyklar. Bunday botlarni mijozlar uchun ham qilamiz, batafsil Telegram-bot yaratish sahifasida.

v1 v2 v3
Server Django + Channels Django 6 + Channels Rust, actix-web
Front Nuxt 3 Nuxt 4 React
Pozitsiyani baholash Material va harakatchanlik Qo‘lda yozilgan funksiya Neyrotarmoq
Bot chuqurligi 3 Vaqt byudjeti bo‘yicha Vaqt byudjeti bo‘yicha
Endshpil Yo‘q Yo‘q 5 damkagacha jadval

Nimani boshqacha qilgan bo‘lardim

Birinchisi: ertaroq profillagan bo‘lardim. «Python tortmaydi» degan gaplarga bir necha hafta yo‘qotdik, holbuki u tortardi. Muammo bitta jarayonda va bazaga bitta oqimda edi.

Ikkinchisi: etalon pozitsiyalar to‘plamini darhol yozgan bo‘lardim. Avvaliga dvigatellarni ularga qarshi o‘ynab, ko‘z bilan solishtirardik va bu sekin hamda ishonchsiz edi.

Uchinchisi, va bu yerda hali ham ikkilanaman: balki butun serverni emas, faqat dvigatelni Rust’ga qayta yozish kerak edi. Hozir bizda bitta binar va uni deploy qilish qulay, bu afzallik. Lekin yillar davomida Django’da yozayotgan jamoa uchun Rust-serverga kirish ostonasi baland. Agar Damka yillab qo‘llab-quvvatlanadigan mijoz loyihasi bo‘lganida, serverni Python’da qoldirgan bo‘lardim. Umuman stekni qanday tanlashimiz haqida Nuxt, Next.js va Rust haqidagi maqolada batafsilroq yozganman.

O‘z mahsulotimiz uchun bunga imkonimiz bor edi. Va bot endi meni yutadi, bunga undan oldingilarning birortasi qodir emas edi.

Vazifa bormi? Yozing — muhokama qilamiz.

Telegram’da to‘g‘ridan-to‘g‘ri studiya asoschisiga yozing. Nima kerakligini ikki og‘iz so‘z bilan ayting — savollar va taxminiy baho bilan javob beramiz.