Her resmî kuralı uygulayan, dünyanın en küçük satranç hakemi.
Kural motoru 1.527 bayt düz kaynak. Kural doğruluğu ve FIDE
uyumluluğu bakımından dünyanın en eksiksiz motorlarından biri; bayt başına işlev
oranında ise açık ara birinci — projenin temel iddiası da bu. Tarayıcıda çalışır, JavaScript ile yazılmıştır; kütüphane,
kurulum ve sunucu istemez. İddianın sergi sürümü2,4 KB'lık tek bir
HTML dosyası: bütün kurallar, tıklanabilir tahta ve canlı saatle tarayıcısı olan her cihazda çalışır —
aşağıda oynanan da indirilen de o.
Başlangıç pozisyonunun tamamı, motorun dizisinde: 64 onaltılık basamak (0–9 ve a–f ile yazılan, 16 tabanlı sayı), a1'den h8'e sırayla; yatayların arasında 8'er boş göz. Her basamak bir kare; şeritteki her renk bir taş türünü gösteriyor; beyaz taşın kodu siyahınkinin 8 fazlası. Motorun
tahta temsili bu.
Boş 0Piyon 1/9Şah 2/aVezir 3/bAt 4/cKale 5/dFil 6/e
a85♜70
b84♞71
c86♝72
d83♛73
e82♚74
f86♝75
g84♞76
h85♜77
78∅7f
a71♟60
b71♟61
c71♟62
d71♟63
e71♟64
f71♟65
g71♟66
h71♟67
68∅6f
a6050
b6051
c6052
d6053
e6054
f6055
g6056
h6057
58∅5f
a5040
b5041
c5042
d5043
e5044
f5045
g5046
h5047
48∅4f
a4030
b4031
c4032
d4033
e4034
f4035
g4036
h4037
38∅3f
a3020
b3021
c3022
d3023
e3024
f3025
g3026
h3027
28∅2f
a29♟10
b29♟11
c29♟12
d29♟13
e29♟14
f29♟15
g29♟16
h29♟17
18∅1f
a1d♜00
b1c♞01
c1e♝02
d1b♛03
e1a♚04
f1e♝05
g1c♞06
h1d♜07
08∅0f
Her karede dört şey var: kare adı, taşın onaltılık kodu, glifi
(taşın Unicode simgesi — ♞, ♛)
ve motorun kullandığı indeks.
İndeks onaltılık okunuyor: üst hane yatay, alt hane dikey —
a1 00, h8 77. Alt hanesi 8–f olan gözler tahtanın dışında; sağdaki
soluk sütun her yatayın bu sekiz gözünü tek hücrede gösteriyor. Bu düzenin motora ne
kazandırdığı “Motorun kendisi” sekmesinde.
“Mükemmelliğe, ekleyecek bir şey kalmadığında değil,
çıkarılacak bir şey kalmadığında ulaşılır.”Antoine de Saint-Exupéry
Golfstack
Kodun kendisi, değeri ve Golfstack sanatı
Turnuva standartlarında eksiksiz bir satranç hakemi, 1.527 baytlık tek bir JavaScript
dosyasına sığdı. Oynanabilir tek bir HTML dosyası olarak boyutu 1,7 ile 2,7 KB arasında
(KB, dosya boyutu birimi; 1 KB = 1024 bayt). Bu fark,
arayüzün neler sunduğundan ve HTML yazımının eski mi modern mi olduğundan geliyor. FideLite, en küçük boyutta en çok işlevi sunmayı hedefleyen bir
mühendislik çalışması; code golf ile yazıldı — işleve dokunmadan, gereksiz dolgu olan her
şeyi yontarak. Bu çalışma biçimine Golfstack diyorum.
Ana sürümlerde iki oyuncu aynı cihazda karşılıklı oynar. Motor hamle önermez, oyunu
yönetir; harici hiçbir kütüphaneye de ihtiyaç duymaz.
Kural tarafında kestirme yok. Hangi maddenin nasıl işlediğini
“Kurallar ve uygulanışı” sekmesinde madde madde bulabilirsin. 50 hamle
ve üçlü tekrarın talep akışı, anlaşmalı beraberliğin teklif–kabul–düşme mekaniği
kitapçıktaki gibi işliyor: talep oynanacak hamle üzerinden kurulabiliyor, teklif, rakip
kabul etmeden hamlesini yaptığında düşüyor, mat bütün otomatik beraberlik koşullarının önüne geçiyor. Terk ve süre
bitiminin, rakibin mat edemediği pozisyonlarla karşılaştığı çetrefil senaryolarda karar
5.1.2 ve 6.9'un sorduğu soruya göre veriliyor. Büyük platformların çoğu bu maddelerde kendi kısayolunu uyguluyor.
Teknik özellikler
Modern bir siteye yakın arayüz. Varsayılan sürümlerde seçili kare ve geçerli
hedefler işaretlenir, terfi seçici açılır, şah ve bekleyen beraberlik teklifi göstergelerden
okunur; her hamleden sonra tahta sıradaki oyuncunun bakış açısına döner. Taş glifleri, saat
işareti ve göstergeler Unicode'dan geliyor — tek bir görsel dosyası, font indirmesi ya da dış sunucu isteği olmadan. Daha yalın sürümler tahtayı metin karakterleriyle çiziyor, bir kısmı
hiç çizmiyor.
Fischer zaman kontrolü ve okunaklı saat. Saat taşıyan sürümlerde varsayılan tempo
10+5: oyuncu başına on dakika, artı her hamlede oyuncunun süresine eklenen beş saniye. İki saat ekranda emojisiyle birlikte
DAKİKA : SANİYE biçiminde işler, artış her hamlede görünür biçimde eklenir. Bayrak
düştüğü anda sonuç kendiliğinden belirlenir.
Hamle üretimi ve sağlamlık. Motor satranç mantığı bakımından tuhaflık yapmaz:
geçerli olmayan hamle geçmez, geçerli olan reddedilmez, sonuç kodu yanlış pozisyonda üretilmez.
İddia ölçüme dayanıyor: motor, satranç programlamanın standart test
takımlarını makul derinliklere kadar hatasız geçiyor.
Kural doğruluğu ve bitişler. Motor oyun bittiğinde “berabere” deyip geçmez; on beş sonucun hangisi geldiyse onu adıyla verir. Kodlar iki karakterlik olmasına rağmen akılda kalıcı — hangi harfin neye karşılık geldiği bir bakışta
oturuyor — ve on beşi FIDE kitapçığındaki maddelerle birebir örtüşüyor. Pat mı, üçlü tekrar mı, ölü pozisyon mu: belirsiz kalmaz.
Beraberliğin zor yarısı. Federasyonların kitabında çoğu uygulamanın atladığı ya
da en basit hâliyle geçiştirdiği maddeler var; motor onları olduğu gibi uygulamayı deniyor.
Özellikle FIDE'nin meşhur 6.9 ve 5.2.2 maddeleri es geçilmeden, olabildiğince
algoritmaya dahil edildi. Kazancı somut: haksız sonuçların büyük bölümünün önüne geçiliyor.
Ayrıntısı burada.
Modüler yapı. Kural katmanı bir yanda durur, arayüzün yaptığı işler öbür yanda.
Beraberlik teklifini, saati ya da otomatik beraberlik sınırlarını çıkarırsan geri kalan aynen
çalışmaya devam eder; başlangıç dizilişi — istersen bir FEN'den — ve süre ayarlanabilir.
Code golf satranç projelerinde bu ayrım genellikle yoktur: kuralın nerede bitip oyun oynamanın
nerede başladığı okunamaz hâle gelir.
Okunabilirlik ve akılda kalıcılık. Kaynak okunmak için değil çalışmak için
yazıldı: tek harfli isimler, tek satır, gömülmüş fonksiyonlar. Yine de isimlendirme rastgele
değil — bayt bütçesinin izin verdiği her yerde adlar İngilizce karşılıklarını çağrıştırıyor.
Ne olup bittiği satır satır ayrı bir bölümde açılıyor.
Hız ve derinlik. Hakemin işinin büyük kısmı tek bir yarım hamlede biter, ama
beraberliğin zor yarısında motor gerçekten ileriye bakıyor — üstelik yalnızca testte değil, oyunun
içinde de, bayrak düştüğü ya da oyuncu terk ettiği anda. Hız bu yüzden süs değil. Hızın önem taşıdığı ikinci yer testler: perft
(bir pozisyondan çıkan, belirli uzunluktaki bütün hamle dizilerini sayan standart doğruluk
sınaması) milyonlarca düğüm sayıyor. İkisine de motorun
hızlı varyantı hizmet ediyor: aynı kurallar, 117 bayt daha uzun,
mantıksal olarak birebir aynı. Ölçümler
ayrı bir başlıkta. Hızın önem taşıdığı üçüncü yer bot: motorun üstüne kurulan arama
onu her düğümde yeniden çağırıyor, bu yüzden botun aynı sürede ne kadar derine bakabileceğini motorun hızı belirliyor.
Bot ayrı bir katman. İddia bayt başına düşen işlev üzerine kurulu; bot şart değil ve üstüne eklenmesi kural eşdeğerliğini bozmuyor. Botlu sürümler iddianın dışında; gerekçesi aşağıda, ölçümleri Varyantlar sekmesinde.
Asıl iddia tek tek bu maddeler değil, oranın kendisi: bayt başına düşen işlev.
Kuralların tamamı tek bir dosyada duruyor — elmas kadar sert, kara delik kadar yoğun. Bu
hâliyle zanaatkâr ve sanatkâr egomun da bir yansıması. ;)
Motor geliştirme aşamasında uç durumlar dahil binlerce kez sınandı ve bilinen hataların
tamamı ayıklandı. Yine de kendi prensiplerimi bozmadan daha da sertleştirmeye her zaman
açığım: bir baytı daha nasıl söküp atacağıma dair bir fikrin varsa, ya da daha tehlikelisi
bir hata, çökme veya yanlış hüküm gördüysen
cuneytinan37@gmail.com
üzerinden yaz.
Canlı sergi
Sürümü seç, oyna
Tek oyun değil, bir sergi. Depoda doğrudan tarayıcıda oynanabilen 36 dosya var ve çeşitlilik
üç yönde açılıyor: kurallarda yalnızca geçerliliği bilen sürümden tam FIDE hakemine,
arayüzde tıklanabilir tahtadan UCI notasyonuna ve hiç tahta çizmeyen körleme sürümlere, özel
sürümlerde de botlara, daha özenli arayüzlere ve Lichess ile Chess.com kurallarını uygulayan eşdeğerlere kadar. Bu çeşitlilik Theseus'un Gemisi'nden esinlendi: tam satrancın nerede
bitip daha azının nerede başladığı tartışılsın ve düşünülsün diye. Kuralları dinamik olarak
ekleyip çıkarabilen üreticiye de bu yüzden Chesseus diyorum!
Her sürüm kendi çerçevesinde çalışır, saatler ancak oyunu başlattığında işlemeye
başlar. Sürüm değiştirmek çalışan oyunu kapatır.
Önce kural seviyesini, sonra sürümü seç. Her seviyenin ilk sürümü, default,
o seviyenin ana dosyası. L3 tam hakem: bütün sürümlerinde kural tarafı aynı,
değişen yalnızca hamleyi nasıl girdiğin, tahtanın nasıl çizildiği ve hangi göstergelerin
bulunduğu. L1 ve
L2 kural seviyesi daha düşük olanlar; ikisi de iddianın dışında. special ayrı bir katman: çoğu bu seviyelerden türemiş ama
hiçbirine tam uymayan, kimi zaman projenin kendi ilkelerine ters düşen çok çeşitli sürümler.
Ayrıntısı Varyantlar sekmesinin sonundaki
Özel sürümler bölümünde. L1,
L2 ve L3'teki nonbrowser
bir terminal sürümü: tarayıcıda çalışmadığı için burada oynanamaz, ama kodunu gösterebilir ya da indirip
node ile çalıştırabilirsin.
Tahta çizen sürümlerde tahta küçük geliyorsa tarayıcının yakınlaştırmasını kullan —
Ctrl+, macOS'ta ⌘+; Ctrl0 (macOS'ta ⌘0) eski hâline döndürür. prompt() penceresi açan sürümlerde
tarayıcın iletişim kutularını engelliyorsa hiçbir şey açılmaz; dosyayı indirip aç ya da
iletişim kutularına izin ver.
Seçili sürüm L3. Oyunu başlat ya da kodunu göster.
Code golf, bir programı işlevinden, kurallarından ve mantıksal doğruluğundan
hiçbir ödün vermeden mümkün olan en az baytla yazma sanatı. Kısıt tek: davranış
aynı kalacak, hacim küçülecek.
Bu proje baştan sona tek bir sorunun peşindeydi:
Baştan sona hiçbir kuralı atlamayan, %100 FIDE uyumlu, eksiksiz bir satranç oyunu en az kaç bayta sığabilir?
Cevabı aşağıda. Ama bu soruyu sorabilmek için önce neyin “eksiksiz”
sayılacağına karar vermek gerekiyordu.
Neyi çıkarırsak hâlâ satranç kalır? (Chesseus problemi)
Motoru yazarken kendimi bir anda Theseus'un Gemisi paradoksunun ortasında buldum:
bir satranç oyunundan neleri söküp atabiliriz ki geriye yine satranç kalsın?
Burada kaçınılmaz bir kimlik sorusu doğuyor. Ortaçağ satrancını
başlangıç noktası almadım — o oyunda fil çapraz iki kare sıçrıyor, vezirin atası tek kare
çapraz gidiyor; rok ve piyonun çift adımı henüz yerleşik değil. Aynı adı taşısalar da o oyunla bugün oynadığımız
oyun temelde iki ayrı kimlik; onu taban kabul etmek, sorunun cevabını başka bir oyun
için vermek olurdu.
Başlangıç noktası olarak şunu aldım: bugünkü taş hareketleri (rok hariç), şah çekme, mat ve pat,
piyonun tek kare ilerlemesi, otomatik vezir terfisi. Çıkarılabilecek ne varsa
çıkarıldığında geriye kalan çekirdek bu. Buradan bir madde daha eksiltmek oyunu satranç
olmaktan çıkarıyor.
Çekirdek eldeyken tek yön kalmıştı: eklemek.
Çekirdekten tam hakemliğe
Motor tek seferde doğmadı, üst üste binen turlarla büyüdü.
Önce alt terfi geldi; piyon vezire mahkûm olmaktan çıktı, at, fil ve kale de
birer seçenek oldu. Ardından piyonun iki kare ilerlemesi ve onunla birlikte gelmek
zorunda olan geçerken alma — ikisi ayrılamaz, çünkü ikincisi birincisinin açtığı
boşluğu kapatır. Sonra rok. Bu noktada tahtada olup biten artık bugünkü satrancın
kendisiydi.
Sonrasında tahtanın dışına çıktım. Oyunun sonsuza kadar sürmesini teoride hamle sayacı, pratikte
saat engelliyor; ikisi de motora bu aşamada girdi.
Normalde orada bırakacaktım. Ama bu kadar ilerlemişken kural kitabının geri kalanını
da tamamlamak istedim: pozisyon tekrarı, yetersiz materyal, beraberlik teklifi ve
talebi, bayrak düşmesi, terk. Tahtadaki hamlelerden değil, oyunculardan ve saatten gelen
hakem kararları da böylece işin içine girdi.
Bu yolun düz bir çizgi olduğunu söyleyemem. Sık sık, daha önce eklediğim bir kuralın
FIDE'ye tam uymadığını ya da belirli bir pozisyonda yanlış davrandığını fark edip geri
dönmem gerekti. Bazen yeni bir keşif, o ana kadar yazdığım çözümün tamamını çöpe
attırdı. Her geri dönüş yapısal bir revizyon, arkasından da yeni bir bayt optimizasyonu
demekti; kod büyüdü, sonra yeniden küçüldü.
Uzun ve öğretici bir süreç oldu, hem kodlama hem satranç mantığı tarafında. Yapım
sürecinde karşılaştığım dönüm noktalarını — hangi kuralın neyi kırdığını, hangi keşfin neyi
baştan yazdırdığını — Ayrıntılı teknik sekmesinde anlatıyorum.
Yontma nerede biter
Michelangelo'ya heykellerini nasıl yaptığı sorulduğunda şöyle cevap verdiği anlatılır:
Heykel zaten mermer bloğun içinde tamamlanmış hâldedir. Ben sadece
gereksiz kısımları yontup atıyorum.Michelangelo
Golfstack'te yaptığım da buydu. Kuralların ne olduğu baştan belliydi; iş, o kuralları
anlatan koddaki her fazlalığı yontmaktı.
Bu yontmanın nerede biteceğini söyleyen kavramın adı Kolmogorov karmaşıklığı: bir nesneyi
üretebilen en kısa programın uzunluğu. Code golf tam olarak o sınıra doğru yürümektir; sınırın kendisi ise kanıtlanmış biçimde hesaplanamaz — yani nerede duracağını kimse önceden
söyleyemez, ölçerek yürürsün. Buradaki ek kısıt, yontmanın kayıpsız olmak zorunda olması:
kuralların gerektirdiği mantıktan tek bir madde bile düşerse elde kalan şey daha
küçük bir satranç olmaz, eksik bir satranç olur.
Sorunun cevabı bu yüzden tek bir sayı: 1.527 bayt.engine.js, iki
insan oyuncu arasındaki resmî bir oyunu baştan sona yönetiyor; bir arayüzle birleşip
oynanabilir tek bir dosya olduğunda, arayüzüne ve eski ya da modern HTML yazımına göre
1.715 ile 2.744 bayt arasında kalıyor.
Neden tarayıcı ve JavaScript
Akla gelen ilk sorulardan biri şu: neden tarayıcı, neden JavaScript?
Cevabın ilk yarısı erişimle ilgili. Hangi cihazda, hangi işletim sisteminde, hangi
ortamda açılabildiğine bakılırsa tarayıcı açık ara birinci sırada. Derleyici yok,
çalışma zamanı kurulumu yok, paket yöneticisi yok; indirilen dosyaya çift tıklamak
yetiyor. Üstelik HTML görsel katmanı neredeyse bedavaya veriyor — tahta, saat ve
göstergeler için ayrı bir arayüz altyapısı yazmak gerekmiyor, dahası
<html>, <head>, <body> hiç
yazılmasa bile dosya açılıyor. Motorun oyuncuya ulaşmak için kat etmesi gereken mesafe
bu kadar kısa olmasaydı, boyut iddiasının pratikte bir karşılığı olmazdı.
İkinci yarısı dilin kendisiyle ilgili. JavaScript Golfstack'e beklenenden çok daha
elverişli: değişken bildirim anahtar kelimesi istemiyor, giriş noktası ve içe aktarma
gibi zorunlu bir iskeleti yok, örtük tür dönüşümü sayesinde tahtanın sayısal kodlaması hiçbir yerde açık bir dönüşüm yazdırmıyor. Ok fonksiyonu, üçlü operatör ve virgül operatörü birlikte
çalışınca bütün bir fonksiyon süslü parantez ve return olmadan tek ifadeye
sığıyor. Dil, kendi kendini açan paketleyicilere de doğal olarak elverişli; motorun paketlenmiş hâli
doğrudan buna dayanıyor.
Buradan bir itiraz doğuyor: tarayıcı yüzlerce megabayt, JavaScript motorunu da
çalışma zamanını da o veriyor. Öyleyse 1.527 bayt kelime oyunu değil mi?
Sıfır taban diye bir şey yok. C ile yazsam libc ve bir işletim sistemi gerekirdi,
assembly ile yazsam bir komut kümesi ve bir makine. Çalışma zamanını da sayan ölçünün
duracağı bir yer yok; öyle ölçülürse hiçbir dilde boyut iddiası kurulamaz. Code golf
bu yüzden çizgiyi hep aynı yere çeker: yazılan kod ile dışarıdan eklenen kütüphaneler
sayılır, ortam ve taban sayılmaz. Tarayıcı da işletim sistemi de işlemci de kimsenin
hanesine yazılmaz. Diller arası karşılaştırma bu ölçüyle yapılır ve rekor denemeleri her dile açıktır.
Son kullanıcının gözünden bakıp daha sıkı bir ölçü isteyen de haklıdır: aynı ortam,
aynı dil, sıfır kütüphane. İkisi de meşru ve ikisinde de çizginin bu yanında tek bir
dosya duruyor — indirilen, pakete katılan ya da eklenen hiçbir kütüphane yok.
“Dünyanın en küçüğü” iddiası bu iki ölçüye dayanıyor. Uzunca bir süredir arıyorum ve
her resmî kuralı uygulayan daha küçüğüne rastlamadım, ne JavaScript'te ne başka bir
dilde. İddia çürütülebilir, öyle de olmalı: biri daha küçüğünü çıkarırsa düşer.
Çıkana kadar duruyor.
Kural katmanı ayrı, bot ayrı
İkinci soru daha zor: bu sayfada botlu sürümler duruyorken, iddia neden onları hiç
saymıyor?
Alışılmış yol belli. Ekrana bir tahta çizilir, taşlar dizilir, oyuncu rastgeleden
biraz daha iyi oynayan bir botla kapışır; bugüne kadarki denemelerin büyük çoğunluğu bu
çizgide ilerledi. Code golf dünyasında birkaç yüz bayta sığdırılmış satranç botları var,
ama yer açmak için rok, geçerken alma, 50 hamle, üçlü tekrar gibi oyunun belkemiğini
oluşturan kuralların neredeyse hepsini feda ediyorlar. Bot mantığı kodu ciddi biçimde
şişirir; iki insanın oynadığı yapı bayt bütçesi açısından çok daha ucuz. Üstelik bir bot
eklemek FIDE uyumluluğuna hiçbir şey katmıyor.
Asıl mesele ölçülebilirlik. Botun tanımı belirsiz: 100 baytın altına sığan, rastgele
ama geçerli hamle yapan şey bot mudur? Materyal sayan bot mudur, yoksa eşik daha yukarıda
mı? Bunu kim söyleyecek? Kurallar öyle değil, kesin ve net. Bir kodun kuralı uygulayıp
uygulamadığı bakıldığında görünüyor, tartışmaya yer bırakmıyor. Ölçü eksik kapsamada bile
net: motorun ilan ettiği her beraberlik gerçek (soundness), kaçırdığı gerçek beraberlikler olabilir
(completeness) — ve kaçırdıkları, pratik tabloda kabaca 2,2 milyonda bir oyuna
düşüyor; kaçırılan bir beraberlik sonucu yalnızca bayrak düştüğünde ya da terkte değiştirebiliyor. Birkaç maddede kapsamayı %100'e çıkarmak zor, ama bu bile bayt başına getirisi
hesaplanabilen kesin bir kavram; botun tanımı değil. Süreç boyunca ben de bot
yazmayı denedim; sonra bütün enerjimi ölçülebilir olan tarafa verdim.
İkisi kodda da ayrı duruyor ve bu tesadüf değil. Motor çekirdeği sürücüden hiçbir şey
bilmiyor; üstüne bir arama koymak ya da onu aramayla iç içe geçirmek kural eşdeğerliğini
bozmuyor. Bu motorun üstündeki botlu sürümler tam olarak böyle çıktı: aynı kurallar, saat ve oyuncular arası
etkileşimler dışarıda, üstünde arama.
Bitmiş saymıyorum, çünkü bir botu iddianın içine almak için kendime koyduğum ölçüt hakem
motorununkiyle aynı: kalite/bayt oranında bir şaheser olması. Bu daha uzun sürecek bir iş, ama
çok keyif aldığım bir hobi olduğu için o tarafta da somut bir rekor denemesi yapacağım.
Geriye ölçülebilir tarafın ne kazandırdığı kalıyor: bu motor beraberliğin zor yarısında
büyük platformların çok ilerisinde. Bu konuda iddialıyım — en titiz duranlar bile
kitapçığa birebir bağlanmak yerine materyal testiyle yetiniyor: tahtada mat etmeye
yetecek taş var mı. Pozisyonun kendisine bakan taraf, yani bayrak düştüğünde mat kurulup
kurulamayacağı ve kilitli pozisyonların tespiti, yaygın değil. Bunun bir
de ölçüsü var: Miguel Ambrona'nın
CHA-Solver'ı Lichess
açık veritabanında haksız sonuçlanmış 204.060 oyun saydı; bu motor onların
%98'inden fazlasını doğru karara bağlıyor. Chess.com'un
FIDE–USCF karması gibi bilinçli tercihler de var; buradaki tercih
başka: tek kitap, tek ölçü.
Baytların ötesinde
Code golf dünyasındaki satranç projeleri genellikle 50 hamle sınırından, üçlü tekrardan, yetersiz materyalden, çoğu zaman rok ve geçerken almadan taviz verip yalnızca
temel çekirdeğe odaklanıyor. Ben kimsenin pek uğramadığı tarafa baktım; projenin
özgünlüğü de galiba tam olarak oradan doğdu. Bildiğim kadarıyla FIDE kitabının
tamamını FideLite'tan daha az bayta sığdıran bir hakem motoru yok — ve FideLite,
büyük platformların hiçbirinde rastlamadığım kilitli pozisyon tespitini de taşıyor.
Ticari bir ürün değil, öyle bir iddiası da yok. Ama satrancın bütün kurallarını bu
şekilde katmanlandırıp en az bayta sığdırmak, kod tarafında sınır zorlayan katıksız bir
mühendislik işi.
Bu noktaya gelirken Claude ve Gemini gibi yapay zekâ modellerinden ciddi destek aldım. Normalde
aylar sürecek hata ayıklama, kod optimizasyonu ve tarayıcı davranışlarını didikleme
işini bu araçlarla inanılmaz ölçüde kısalttım. Kararlar bana ait kaldı, kazandığım şey hız
oldu.
Çehov kardeşine yazdığı mektupta “kısalık yeteneğin kardeşidir” diyor. Ben de bu projede
satrancın o devasa dünyasını, işlevinden hiçbir şey kaybetmeyen en kısa hâline
dönüştürmeye çalıştım.
FideLite — adın ve logonun anlamı
Ad iki parçadan oluşuyor: FIDE + Lite. Birleşince Fransızca
fidélité okunuşuna düşer — sadakat, bağlılık. Anlattığı da bu:
dosya küçülürken kural kitabına bağlılığın küçülmemesi. “Lite” hafiflik,
“Fide” federasyon; ikisi bir arada, hafiflik uğruna hiçbir maddenin atılmadığı
bir hakem.
Logo tek bir karenin etrafına kurulu. Ortadaki kare
başlangıç, geri kalan yirmi dört kare o kareden hareket eden taşların
gidebileceği yerler.
Başlangıç — ortadaki tek kare
At — sekiz nötr kare, L sıçramaları
Kale — sekiz turkuaz kare, dört düz ışın
Fil — sekiz leylak kare, dört çapraz ışın
Vezir — kale ve fil karelerinin tümü
Şah — bordosarı çerçeve, bir adımlık erim
Piyon — tek karelik ilerlemesinin düştüğü kare: FIDElite yazısı
Kurallar
Kuralların okunuşu
Bu sekme kitabın kendisiyle ilgili: hangi madde ne diyor, onu nasıl okuduğum ve yorumun tartışmalı olduğu yerler. Kod yok; motorun bunları nasıl yaptığı
“Motorun kendisi” sekmesinde.
Bunun yerine her maddenin altında bir sınama var: bir pozisyon, doğru hüküm ve
maddeyi yüzeysel uygulayan bir sistemin verdiği yanlış cevap. Kuralı okumak bir şey,
onu her pozisyonda doğru uygulamak başka bir şey; aradaki farkı ancak pozisyon
gösterir.
Kural bölümleri (1–7) aynı parçaları aynı sırayla kullanıyor, ama her parça her bölümde yok: kitap ne diyor, algoritmada nasıl
okunur, varsa FideLite'ta ne yapıldığı ve uygulama maliyeti. 8. bölüm FIDE'yi
USCF ve platformlarla karşılaştırıyor, 9. bölüm önerdiğim karma sistemi anlatıyor, 10. bölüm
ise oyunu değil çerçevesini değiştiren kurallara bakıyor.
Madde numaraları, FIDE'nin 1 Ocak 2023'te yürürlüğe giren Satranç Kuralları'na göre — FIDE Handbook ↗️.
1. Taş hareketleri ve geçerlilik
Kitap ne diyor
Piyon — 3.7. Düz ileri, boş bir kareye bir kare gider; ilk hamlesinde, önündeki iki kare boşsa iki kare de gidebilir. Çapraz ileri bir kareye yalnızca taş alarak gider; düz giderken alamaz. Geçerken alma ve terfi, özel hamleler bölümünde.
Fil — 3.2. Çaprazlarda, önü açık olduğu sürece istediği kadar ilerler.
Kale — 3.3. Dikeylerde ve yataylarda, önü açık olduğu sürece istediği kadar ilerler.
Vezir — 3.4. Fil ile kalenin birleşimi: sekiz doğrultuda, önü açık olduğu sürece istediği kadar ilerler.
At — 3.6. “L” çizer: bir doğrultuda iki kare, ona dik bir kare. Aradaki taşların üstünden atlayabilen tek taş.
Şah — 3.8. Kısa menzilli: her yöne bir kare. Rok, özel hamleler bölümünde.
Bir hareketin geçerli hamle sayılması için dört koşul birlikte sağlanmalı:
Hareket biçimi. Hamle, taşın yukarıdaki hareket biçimine uymalıdır.
Yolun açıklığı. At dışındaki bütün taşlar için aradaki karelerde hiçbir taş bulunmamalıdır. At bu şarta bağlı değil; bu bir istisna değil, atın hareketinin tanımı.
Hedef kare. Boş olmalı ya da orada rakip taş bulunmalıdır; kendi taşının durduğu kareye hamle yapılamaz.
Şah güvenliği. Hiçbir hamle kendi şahını tehdit altında bırakamaz. Diğer üçü hamlenin şekliyle ilgili; bu dördüncüsü sonucuyla.
Algoritmada nasıl okunur
Bu dördü birlikte geçerli hamleyi tanımlıyor ve kitabın geri kalanı baştan sona bu
tek tanıma yaslanıyor. Mat, pat, ölü pozisyon, süre bitimi — hepsi “geçerli hamle” ya da
“geçerli hamle dizisi” diyor. Tanım kayarsa ona dayanan her madde de kayar.
Uygulama maliyeti. Dördüncü koşul ilk üçünden pahalı: bir hamlenin kendi şahını tehdit altında bırakıp bırakmadığı, hamle yapılmadan anlaşılmıyor. Bir hakem algoritmasında en sık çalışan kontrol de bu.
2. Özel hamleler
Rok — 3.8.2
Kitap ne diyor
Kitap rokta iki ayrı şeyi ayrı alt maddelere koyuyor ve bu ayrım ilk bakışta göründüğünden daha önemli. 3.8.2.1 hakkın kaybı: şah ya da o kale oynadıysa hak gider ve bir
daha geri gelmez. 3.8.2.2 geçici engel: şahın durduğu, geçtiği veya vardığı karelerden
biri tehdit ediliyorsa ya da şahla kale arasında taş varsa rok o an yapılamaz — ama hak
yerinde durur.
Üç kare, iki değil. Şahın durduğu, geçtiği ve vardığı kare — üçü de tehdit edilmemelidir. Vardığı kare çoğu özette atlanıyor; oysa üçü de aynı alt maddede.
Kale muaf. Bu üç kare şahın kareleri. Kalenin geçtiği kare tehdit altında olabilir: uzun rokta b1 tehdit ediliyorken rok yine geçerlidir. Kalenin yolunda istenen tek şey boş olması.
Şah oynarsa ikisi birden gider. Şah hareket ettiğinde iki kanattaki rok hakkı da düşer; kale hareket ettiğinde yalnızca o kanattaki.
♜♚♜♚♚
Uzun rok, b1 tehdit ediliyorken. Rok geçerli — üç kare şartı şahın kareleri için (e1, d1, c1), kalenin geçtiği kare için değil. Şah e1'den d1 üzerinden c1'e gidiyor: yeşil çerçeveler çıkış ve varış karesi, ok geçtiği kare, soluk şah varış. Kırmızımsı kareler siyah kalenin tehdit ettikleri.
Uygulama maliyeti. Rok ancak dört ayrı koşulun kesişiminde geçerli: hakkın hâlâ durması, şahla kale arasının boş olması, şahın o an tehdit altında olmaması ve geçeceği ile varacağı karelerin tehdit edilmemesi. Maddeyi ötekilerden pahalı yapan, bu tehdit sorguları.
Geçerken alma — 3.7.3.1
Kitap ne diyor
Bir piyon ilk hamlesinde iki kare ilerleyip rakip bir piyonun yanına gelirse, o rakip piyon
onu tek kare ilerlemiş gibi alabilir: çapraz olarak, atlanan kareye gider ve iki kare
ilerleyen piyon tahtadan kalkar. Hak yalnızca hemen ardından gelen hamlede geçerlidir. O hamlede kullanılmazsa kalıcı
olarak düşer — bir hamle sonra geri gelmez.
Algoritmada nasıl okunur
Geçerken alma ayrı bir kural değil, başka bir kuralın yaması. Piyonun ilk hamlesinde
iki kare ilerleyebilmesi açılışı hızlandırmak için getirildi; ama bununla birlikte piyon, rakip piyonun tehdit ettiği kareyi atlayıp geçebilir hâle geldi. Geçerken alma tam olarak o deliği
kapatıyor: iki kare ilerleyen piyon, tek kare ilerlemiş gibi alınabiliyor.
Uygulama maliyeti. Alışın kendisi ucuz: alan piyonun indiği kareyle, yani atlanan kareyle ifade edildiği için ayrı bir kural gerektirmiyor. Pahalı olan, hakkın doğup doğmadığına karar vermek — buna 4. bölümde dönüyoruz.
Terfi — 3.7.3.3
Kitap ne diyor
Zorunludur. Son yataya ulaşan piyon piyon kalamaz. Terfi bir seçenek değil, hamlenin tamamlanma koşulu.
Anındadır. Ayrı bir hamle değil, aynı hamlenin parçası. Yeni taş hemen oyundadır ve aynı hamlede şah çekebilir.
Alınmış taşlarla sınırlı değildir. Tahtada zaten vezir varken ikinci bir vezir doğabilir; kitap sayı sınırı koymuyor.
Alt terfi yalnızca estetik bir imkân değil. Vezire terfinin rakibi pata soktuğu, kaleye terfinin ise kazandırdığı pozisyonlar var — Saavedra etüdünün bütün konusu budur. Ata terfi de şah çekerek ya da çatal kurarak kazanabilir.
Algoritmada nasıl okunur
Sonucu şu: vezir her zaman en iyi seçim değildir. Terfiyi otomatik olarak vezire çeviren bir oyun daha basit bir oyundur ve bence bunun kaybı sanıldığından büyüktür. Alt terfi, tahtadaki
hiçbir taşın koşulsuz üstün olmadığı bir oyun yaratıyor; oyunun derinliğinin ciddi bir
kısmı buradan geliyor.
Uygulama maliyeti. Terfi bir seçim olduğu için arayüzün bir soru sorması, algoritmanın da dört seçeneği birden hesaba katması gerekiyor. Terfi otomatik olarak vezire yapılsaydı bu maddenin maliyeti neredeyse sıfır olurdu.
Özel hamleler oyuna ne katıyor
Rok ve iki adım tahtada yeni bir şey yaratmıyor: şahla kalenin rokla vardığı karelere
de, piyonun iki adımda vardığı kareye de sıradan hamlelerle ulaşılabiliyor. Katkıları
tempo: oyunu hızlandırıyorlar, yan etki olarak da bazı özel durumlarda oyuna estetik
katıyorlar. Alt terfi öyle değil. Her terfide dal sayısını dörde katlıyor ve terfiler
çoğaldıkça oyunun uzayı üstel biçimde büyüyor. Bu yüzden bana göre taş hareketleri ve geçerlilikten sonra kurallara yapılmış en önemli ekleme bu. Ekleme diyorum, çünkü uzun
süre terfinin tek bir yönü vardı: ortaçağ satrancında piyon yalnızca vezirin atası fers'e
dönüşebiliyordu.
3. Şah, mat ve pat
Kitap ne diyor
Şah çekme — 3.9. Şah, rakibin bir ya da birden fazla taşı tarafından tehdit ediliyorsa şah çekilmiş olur. Tehdit için o taşın şahın karesine gerçekten gidebilmesi gerekmiyor: kendi şahını açıkta bırakacağı için yerinden oynayamayan, açmazdaki bir taş da şah çeker. Açmaz taşın hamlesini engeller, tehdidini değil.
Şah tahtadan hiç kalkmaz. Oyun matla biter, şahın alınmasıyla değil. Bu bir üslup tercihi değil, bütün geçerlilik tanımının dayandığı yer: kendi şahını tehdit altında bırakan hamle kötü bir hamle değil, var olmayan bir hamledir.
Mat — 5.1.1. Şah tehdit altında ve tehdidi kaldıracak hiçbir geçerli hamle yok. Mat eden kazanır ve oyun o anda biter.
Pat — 5.2.1. Sırası gelen tarafın şahı tehdit altında değil ama hiçbir geçerli hamlesi yok. Oyun yine o anda, berabere biter.
İkisini tek bir şey ayırıyor. Ortak noktaları, sırası gelenin oynayabileceği geçerli bir hamlesinin olmaması. Sonucu belirleyen tek şey şahın o anda tehdit altında olup olmadığı.
Uygulama maliyeti. Hemen çözülür. Sıradaki tarafın bir geçerli hamlesi var mı ve şahı tehdit altında mı — ikisi de mevcut pozisyondan cevaplanır; o tarafın kendi hamlelerinin ötesine bakan bir arama gerekmez.
Çifte şah her biçimde olmaz
Bir taş üç biçimde şah çekebilir:
Sıçrayışla. At, bir eksende iki, öbüründe bir kare atlar: (±1, ±2) ve (±2, ±1). At, taşların üstünden atladığı için aradaki taşlar onu durdurmaz; atın çektiği şaha araya taş koyarak karşılık verilemez.
Çapraz ışınla. Taş iki eksende eşit ilerler, (±1, ±1) yönünde. Bu ışından fil ve vezir şah çeker; piyonun alışı da bunun tek adımlık biçimidir.
Düz ışınla. Taş yalnız bir eksende ilerler, (±1, 0) ve (0, ±1) yönünde. Bu ışından kale ve vezir şah çeker.
Çifte şah tek bir hamleyle olur: bir taş hattan çekilip arkasında kalan ışını açar — açarak
şah — ve kendisi de şah çeker. Terfi ve geçerken alma da bu kalıbın özel hâlleridir. Geçerken almanın bir
ayrıcalığı daha var: iki kareyi birden boşalttığı için iki ışını birlikte açabilir; bu durumda iki
şahı da açılan ışınlar çeker, piyonun kendisi şah çekmez.
Rokla da şah çekilebilir, ama yalnızca kaleyle. Rokta boşalan kareler ilk yatayda, yani tahtanın
kenarındadır; bu yatayda açılabilecek her ışının önünde de rok yapan kalenin kendisi durur. Bu
yüzden rok şaha giden bir ışın açamaz ve çifte şah çekemez.
Açılabilen tek şey ışındır; bu yüzden her çifte şahta şahlardan en az biri bir ışından gelir.
Atın sıçrayışı da piyonun tek adımlık şahı da açılamaz: iki at, iki piyon ya da bir at ile bir piyon aynı anda
şah çekemez.
Açılan ışının önünde duran taş o ışının ailesinden olamaz; olsaydı, hamleden önce o ışından
kendisi şah çekiyor olurdu. Çapraz bir ışının önünde ancak kale, at, piyon ya da şah durabilir;
düz bir ışının önünde ancak fil, at, piyon ya da şah.
Buradan üç sonuç çıkıyor:
Farklı aileler. İkinci şah açılan ışından farklı bir aileden geliyorsa çifte şah olağandır: at ile bir ışın, çapraz ışın ile düz ışın, piyon ile düz ışın.
Çapraz ile çapraz. Çapraz aileden iki taş hiçbir zaman aynı anda şah çekemez. Çapraz bir ışının önünde durabilen taşlardan çaprazdan şah çekebilecek olan yalnızca piyon, onun da iki yolu kapalı: ilerlerken tek bir düz adım atar, bu adım karenin rengini değiştirir ve piyon şahın köşegenine düşemez; çapraz alırsa ya aynı köşegende kalır ya da kesişen köşegene geçer. Geçerken almada boşalan iki kare de farklı renktedir.
Düz ile düz. Düz aileden iki taş yalnızca tek bir yolla aynı anda şah çekebilir: piyon çapraz alarak kale ya da vezir olarak terfi eder. Bu çapraz adım onu kapattığı hattan çıkarıp şahın üzerinde durduğu bir hatta taşır: diyagramdaki gibi dikeyden şahın yatayına ya da yataydan şahın dikeyine.
♚♜♜♚
e7'deki piyon f8'deki atı alıp kale olarak terfi etti. e4'teki kale dikeyden, f8'deki yeni kale yataydan şah çekiyor. Piyon vezir olarak terfi etseydi de aynı çifte şah olurdu; bu kez ikinci şahı vezir, düz ışınından çekerdi. Yeşil çerçeveler piyonun çıkış ve varış kareleri.
Aynı aileden iki ışının birlikte şah çekebildiği tek durum bu: düz ışınlarda tek bir istisna
var, çapraz ışınlarda hiç yok.
4. Sayaçlar ve tekrar — 9.2 · 9.3 · 9.6
Kitap ne diyor
Bir oyuncunun hamlesi bir yarım hamle, iki tarafın karşılıklı hamlesi bir tam hamle.
50 ve 75 hamle sayacı. İki sınır da aynı sayaca bakar: piyon oynanmadığı ve taş alınmadığı sürece ilerler; ikisinden biri olduğunda sıfırlanır.
Pozisyon tekrarı — 9.2.3. İki pozisyonun aynı sayılması için sıra aynı tarafta olmalı, aynı tür ve renkteki taşlar aynı karelerde durmalı ve iki tarafın mümkün hamleleri aynı olmalıdır.
Tekrarların ardışık olması gerekmez. Aynı pozisyon oyunun üç ayrı yerinde doğmuş olabilir; önemli olan tekrarın kaç kez olduğu, aralarındaki mesafe değil.
Talebe bağlı olanlar — 9.2, 9.3. Üçlü tekrar ve 50 tam hamle (100 yarım hamle) doğru talep edilirse beraberlik verilir.
Kendiliğinden işleyenler — 9.6. Beşli tekrar ve 75 tam hamle (150 yarım hamle) dolduğunda oyun, talep beklenmeden berabere biter.
Matın üstünlüğü — 9.6.2. Bir hamle mat ediyorsa, aynı hamlede 50 ya da 75 hamle sınırı dolsa bile mat geçerlidir: hamleye iliştirilen 50 hamle talebi yok sayılır, 75 hamlenin otomatik beraberliği doğmaz. Uzun oyun sonlarında rastlanan bir çakışma bu. Tekrarla çakışma ise mümkün değil: mat edilmiş bir pozisyon daha önce görülmüş olamaz — görülseydi oyun orada biterdi. Kitap bunu iki yerden söylüyor: 75 hamle için 9.6.2'nin kendi metninde açıkça yazılı, 50 hamle talebi içinse 5.1.1 yetiyor — mat oyunu o anda bitirir.
“Mümkün hamleler” ne demek
Tekrarın bütün ağırlığı bu ifadede. Kitap iki durumu açıkça ayrı ayrı belirtiyor ve ikisini
farklı ölçüyor:
9.2.3.1 — geçerken alma: geçerlilik (imkân) bazlı. Dizinin başında bir piyon geçerken
alınabiliyorduysa, sonradan alınamadığında pozisyonlar aynı sayılmaz. Ölçülen şey hakkın
kullanılabilirliği.
9.2.3.2 — rok: hak bazlı. Rok hakkı ancak şah ya da kale oynadığında
kaybolur. Yolda taş olduğu ya da kareler tehdit edildiği için rokun o an yapılamıyor olması
pozisyonu değiştirmez.
Algoritmada nasıl okunur
♜♚♟♟♚
Siyah az önce g7–g5 oynadı ve geçerken alma karesi g6. Ama f5'teki beyaz piyon, f8'deki kale yüzünden açmazda: fxg6 oynanırsa f dikeyi açılır ve
beyaz şah tehdit altında kalır. Alış geçerli olmadığı için imkân da doğmaz.
9.2.3.1'deki şart, alışın gerçekten geçerli olmasını istiyor. Yandaki
pozisyonda siyah az önce bir piyonunu iki kare ilerletti, ama alacak piyon açmazda: alış oynanamaz, dolayısıyla imkân hiç doğmaz.
Ayrım kritik, çünkü almayı yalnızca oynanma anında reddetmek yeterli değil. Geçerken
almanın kendisi açısından ikisi aynı sonucu verir — hamle her hâlükârda oynanamaz. Fark
tekrar sayımında ortaya çıkıyor: imkân, pozisyonun tanımının bir parçası. İmkân varmış gibi sayılırsa
FIDE'nin aynı saydığı iki pozisyon farklı sayılır ve üçüncü tekrar geç gelir ya da hiç
gelmez.
Bedeli somut: berabere bitmesi gereken bir oyun yenilgiyle bitebilir. Alış zaten oynanamadığı için bu hata gözle görülmez — yalnızca tekrar sayımında yaşar.
Üç ölçü, iki hak
Geçerken alma ve rok için tekrar sayımında üç ayrı ölçü kullanılabiliyor — hak izi, ham-geçerli (şah güvenliğine bakılmadan, yalnızca geometrik olarak mümkün olan hareket) ve geçerli — ve her biri aynı tahtaları farklı bir pozisyon kümesine ayırıyor. Tablonun üst satırı pozisyonları en çok
ayıran ölçü, alt satırı en az ayıran; ayrım arttıkça tekrar da o kadar geç geliyor.
Ölçü
Geçerken alma
Rok
Hakiz
İki kare ilerleme yetiyor; alacak piyon olmasa da iz kalıyor
Kalıcı hak: şah ya da o kale oynamadıkça duruyor
Ham-geçerli
Yanında alabilecek bir rakip piyon var; açmazda olup olmadığına bakılmıyor
Hak var ve şahla kale arasındaki kareler boş; tehdit edilen karelere bakılmıyor
Geçerli
Alış şu an gerçekten oynanabiliyor
Rok şu an oynanabiliyor: hak var, yol boş, şah tehdit edilmiyor, geçtiği ve indiği kareler tehdit edilmiyor
FIDE'nin seçtiği
Geçerli9.2.3.1
Hak9.2.3.2
FIDE iki hakkı iki ayrı satırdan ölçüyor: geçerken almayı imkân, roku hak olarak sayıyor ve bunu 9.2.3.1 ile 9.2.3.2'de açıkça yazıyor. İlk bakışta tutarsız görünüyor — ikisi de “şu an oynanabilir mi” sorusuna bağlıymış gibi duruyor — ve konu tartışmasız da değil. Kitabın gerekçesi, 2. bölümde gördüğümüz hak kaybı ile geçici engel ayrımı: geçerken almada geçici engel diye bir şey yok — hak ya vardır ya yoktur ve bir hamle sonra kalıcı olarak düşer; rokta ise ikisi ayrı şeyler ve kitap yalnızca kalıcı olanı sayıyor. Yaygın görüş de bu yönde: roku hak olarak saymak hem daha ucuz hem de pozisyonları daha isabetli ayırıyor. Bu ayrımın gerçek bir
oyunda bedeli de olmuştur:
Karpov–Miles, 1986'da Karpov 26. hamlesinde üçlü tekrar talep etti; 22. hamledeki pozisyonda
siyahın rok hakkı hâlâ durduğu gösterildi ve talep geçersiz sayıldı. O dönemin kurallarına göre Karpov'a saatinden beş
dakika ceza verildi; Miles ise bir süre düşündükten sonra beraberliği yine de kabul etti,
çünkü yanlış bir talep de bir beraberlik teklifidir. Aradaki fark tahtaya bakınca görünmüyordu.
♚♞♚♞♜
X ile Y: tahta birebir aynı. X'te şah hiç oynamadı, Y'de f1'e gidip geri döndü. İkisinde de g1'deki at rokun yolunu tıkıyor. Ag1–f3 yolu açınca X'te rok oynanabilir hâle geliyor, Y'de gelmiyor. Yeşil çerçeveler atın çıkış ve varış karesi, soluk at varış.
Neden o anki geçerlilik değil de hak. Bir durum makinesinin (her adımda bir durumdan ötekine geçen sistem) temel şartı
hafızasızlıktır: bir sonraki durum yalnızca şimdiki duruma ve yapılan hamleye bağlı
olmalı, geçmişe bakmaya gerek kalmamalı. Rok o anki geçerlilikle sayılsaydı bu şart
bozulurdu. Yandaki tahtayı iki ayrı oyunda düşünelim. X'te beyaz şah hiç oynamadı; Y'de
f1'e gidip geri döndü. İkisinde de g1'deki at yolu tıkadığı için rok şu an oynanamıyor.
Taşlar aynı karelerde, sıra aynı tarafta ve iki pozisyonun geçerli hamle listesi birebir
aynı; geçerli ölçüyle X ile Y aynı pozisyon sayılır. Şimdi ikisinde de aynı hamleyi
oynayalım: Ag1–f3. X'ten çıkan pozisyonda beyaz bir sonraki hamlesinde rok yapabilir,
Y'den çıkanda yapamaz. Aynı pozisyona aynı hamle uygulanınca iki farklı pozisyon
çıkıyorsa, başta aynı oldukları varsayımı yanlıştır. Demek ki pozisyonun içinde tahtada
görünmeyen bir hafıza var: rok hakkı. FEN onu KQkq alanında tutuyor.
Aynı sınama geçerken almada geçerli ölçünün neden yettiğini de gösteriyor. Hak
yalnızca bir yarım hamle yaşıyor: alış o an geçerli değilse, hangi hamle oynanırsa
oynansın bir sonraki pozisyonda hak zaten yok. Geçerli ölçüyle aynı sayılan iki pozisyonun
geleceği ayrışamıyor, yani gizli bir hafızaya gerek kalmıyor. Kitabın iki hakkı iki ayrı
satırdan ölçmesinin nedeni bu: her hak, en az ayıran ama geleceği farklı olan pozisyonları
yine de ayrı tutan ölçüyle sayılıyor.
Bu hafızaya yalnızca tekrar sayımı ihtiyaç duyuyor. Tekrar sayımı olmayan
bir hakemde rokta da geçerken almadaki gibi kolay bir yol seçilebiliyor: hak tutulmuyor,
yalnızca şahın ve kalenin karesine dokunulup dokunulmadığına bakılıyor. Rokta iki yol
arasındaki zorluk ve maliyet farkı küçük; ayrıntısı Ayrıntılı teknik
sekmesinde.
Uygulamalar geçerken alma karesini farklı biçimlerde yazıyor. Hepsinde alışın kendisi
eninde sonunda geçerlilik süzgecinden geçtiği için fark tahtada görünmüyor, yalnızca tekrar
sayımında çıkıyor. En basit hâlinde kare, iki kare ilerleyen her piyon için yazılıyor — klasik FEN de böyle
yazar. Gösterim olarak doğru; ama tekrar anahtarı (pozisyonun tekrar sayımındaki kimliği: tahta, sıra, rok hakları ve geçerken alma karesi) olarak kullanıldığında açıkça yanlış ve
pozisyon tekrarı tanımına ters düşüyor. Geçerken alma
özel bir hamle; kareyi her çift adımda yazmak kural doğruluğu açısından yeterince hassas
değil. Tanım iki pozisyonda geçerken alma imkânlarının aynı olmasını istiyor, oysa
oynanamayacak bir alış kaydedildiğinde bu imkân kâğıt üzerinde var, tahtada yok: aynı
pozisyon iki ayrı pozisyon gibi sayılıyor ve kare artık pozisyonlar arasındaki gerçek bir
farkı göstermiyor. Chess.com gibi
bazı platformlar maddeyi farklı yorumlayıp tekrarı ham-geçerli ölçüyle sayıyor. Genel kabul
gören FIDE yorumu ise Lichess'in de yaptığı gibi kareyi yalnızca alış geçerliyse saymak.
FideLite'ta. Geçerken alma karesi yalnızca alış geçerliyse yazılıyor — yanında bir rakip piyon var ve alış, aynı yataydaki iki kareyi birden boşalttığı hâlde alan tarafın şahını tehdide açmıyor. Rok ise kalıcı hak olarak tutuluyor: tahta görüntüsü, sıra bilgisi ve geçerken alma karesiyle birlikte rok hakları da tekrar anahtarının parçası. Yani FideLite, FIDE'nin iki satırını birebir uyguluyor.
Oyunu sonlu kılan iki sınır
Bu bölümdeki kendiliğinden işleyen iki sınır — 75 hamle ve beşli tekrar — oyunun
sonsuza kadar sürmesini tek başına engelleyebiliyor; her biri ayrı bir sonluluğa dayanıyor.
Sayacın dayanağı şu: piyon hamlesi ile alma geri alınamaz. Bir piyon geri gitmez, alınan taş
geri gelmez. İkisi de bir oyunda ancak sonlu sayıda olabilir — piyon da taş da sayılı.
Ve sayacı sıfırlayan tek şey bu ikisi. Öyleyse ikisi de tükendikten sonra sayaç durmadan
artar ve 150 yarım hamlede oyunu kendiliğinden bitirir. Kitap sayacı yalnızca bu ikisine bağlıyor;
aynı sınıftan iki olayın daha, rok hakkının ve geçerken alma hakkının düşmesinin de sayacı
sıfırlaması gerektiğini 9. bölümde savunuyorum.
Tekrarın dayanağı ise pozisyon uzayının sonlu olması: taşların yeri, sıra, rok hakları ve
geçerken alma karesi sonlu sayıda birleşim veriyor; bitmeyen bir oyun bir pozisyona er geç
beşinci kez dönmek zorunda.
Bu yüzden ölü pozisyon tespiti olmasa bile ölü bir pozisyonu iki sınırdan biri mutlaka
bitirir; oyunu olabildiğince uzatmaya çalışan taraflar bile bunu engelleyemez. Teoride 75 hamle
sayacı çok daha erken bitirir: k7/8/8/1p2p2p/1P2P2P/8/8/K7 w - - 0 1 pozisyonunda şahlar kendi üç
yataylarına kapalı; 576 dizilişin sıra bilgisiyle 1.152 pozisyonu var ve hiçbiri beşinci kez
gelmeden oynanabilecek en uzun dizi 4.608 yarım hamle, sayaç ise 150'de doluyor. Pratikte ise
tekrarın çok daha erken kestiği pozisyonlara sık rastlanıyor: kilitlilik hareketliliği düşürdüğü
için aynı pozisyon çabuk geri geliyor — 6. bölümdeki fil örneğinde beşli
tekrar 16. yarım hamlede geliyor.
Ölü pozisyon tespiti ise oyunu bitiren değil, erken bitiren kural:
ölü olmayan bir pozisyonda hiç devreye girmediği için tek başına sonluluğu garanti etmez, ama ölü olanı
ilk hamlede tanır.
Aynı akıl yürütme tersten okunduğunda, sonluluğun ne kadar az kuralla garanti edildiğini gösteriyor. Geçerlilik, mat, pat ve bu iki sınırdan biri yeter: bunları uygulayan bir
oyun sonsuza kadar süremez — imkânı yoktur. İkisi birden gerekmiyor; ölü pozisyon ise
sonluluk için hiç gerekli değil. O, oyunu doğru bitirmek için var, bitirmek
için değil.
Bu ayrım seviyelerde doğrudan görünüyor: her seviye oyunu bitiren
yeni hükümler ekliyor. En yalın seviyede ne sayaç ne tekrar sayımı var, dolayısıyla sonluluk da garanti değil —
tahtada yalnızca iki şah kaldığında oyun hiç bitmez. Garanti kendiliğinden işleyen sınırlarla doğuyor: 75 hamle de
beşli tekrar da tek başına yeter. Ölü pozisyon ve tekrar ise oyunun akışını hızlandıran,
makul ve pratik kurallar: sonucu çoğu zaman 150 yarım hamleyi beklemeden veriyorlar. İkisi ayrı bir kesinlikten besleniyor:
ölü pozisyon geometrik kesinlikten — matın gelmeyeceğini tahtanın kendisi söylüyor;
tekrar ise niyetin kesinliğinden — aynı pozisyona üçüncü kez dönmek kimsenin
ilerlemek istemediğini gösteriyor ve oyuncu talep edebiliyor, beşincide artık kimseye sormaya
gerek kalmıyor. Günlük oyunda da resmi turnuvada da bu kuralların oyuncuya kazandırdığı zaman
paha biçilmez. En üst seviyenin eklediği saat, terk, talep ve teklif ise ayrı bir boyut:
tahtanın değil insan etkileşiminin kuralları; onlar olmadan turnuva satrancı olmaz.
Geri alınamaz hamle geçmişi budar
Yarım hamle sayacını sıfırlayan bir hamle (piyon hamlesi ya da alış) ve bir rok hakkının
düşmesi, tekrar tablosunu o andan itibaren budar: öncesindeki pozisyonların hiçbiri bir daha
ortaya çıkamaz. Piyon geri gitmez, alınan taş geri gelmez; rok hakkı da pozisyonun tanımının
parçası olduğu için, hak düştükten sonra hakkın durduğu pozisyonlar yeniden doğamaz.
Tablonun o ana kadarki kısmı artık kesin olarak ölü veri.
Bu budamanın asıl işe yaradığı yer arama yapan motorlar. Bot tekrar kontrolünü aramanın
her düğümünde, saniyede milyonlarca kez yapıyor; geçmişi yalnızca son geri alınamaz hamleye
kadar taramak orada doğrudan hız demek. Hakemde ise kontrol yarım hamlede bir kez yapılıyor
ve pozisyonlar bir karma tablosunda (hash tablosu: kayda anahtarıyla doğrudan ulaşılan yapı) tutulduğu için bir bakış sabit sürede yapılıyor, tablonun boyuna bağlı
değil.
Tablonun büyümesinin kesin bir üst sınırı da var: 75 hamle kuralı kendiliğinden işlediği
için bir oyun en fazla 17.697 yarım hamle, yani 8.848,5 hamle ↗️ sürebilir.
Yukarıdaki sonluluk argümanının somut sayısı bu; tablo bundan fazla pozisyon tutamaz.
FideLite'ta. Motor kitaba uyuyor: sayaç yalnızca piyon hamlesinde ve alışta
sıfırlanıyor. Tekrar tablosu da hiç budanmıyor, oyun boyunca büyüyor. Pozisyonu 190 karakter
civarında bir metin anahtarıyla tutmasına rağmen en uzun oyunun sonunda bile yaklaşık
4,5 MB yer kaplıyor. Hissedilmeyecek bir yük; budamayı yazmak ise boşuna bayt harcamak
olurdu.
Hamle listesi yerine sayaçlar 8.1 · dijitalde sayaçlara dönüşür
Bu sayaçların tahta başındaki karşılığı oyuncunun kendi tuttuğu kayıt. FIDE oyuncudan
kendi hamlelerini ve rakibininkileri, hamle hamle ve okunaklı biçimde cebirsel
notasyonla kaydetmesini istiyor (8.1.1): elle kâğıt kayıt formuna ya da FIDE onaylı
elektronik kayda. FIDE'nin tanıdığı tek notasyon sistemi bu (Ek C ↗️); bilgisayar
satrancında SAN (Standard Algebraic Notation) adıyla bilinen, Nf3,
exd5, O-O diye yazılan biçim. Taş harfleri oyuncunun kendi dilinde
olabiliyor — Türkçe bir kayıtta A, F, K, V, Ş — SAN ise İngilizce harfleri sabitliyor.
Talebin dayanağı o kayıt. 50 hamlenin dolup dolmadığını, bir pozisyonun kaçıncı kez geldiğini
oyuncu yalnızca kayıttan çıkarabiliyor; onun dışında not tutmak bile yasak (11.3.1). Kural
oldukça katı. Hamleyi oynamadan önce kayda yazmak da yasak; istisnalardan biri 9.2 ve 9.3
talebi (8.1.2): talep hamleyi kayda yazarak yapılıyor, hakem de kayıttan doğruluyor. Kayıt
orada bir delil, çünkü masa başında geçersiz hamle de yanlış talep de mümkün; ikisinin cezası
da ona bakılarak veriliyor (7.5, 9.5).
Dijitalde bu yükün hiçbiri kalmıyor. Geçersiz hamle yapmak imkânsız, dolayısıyla cezası
da yok; yanlış bir talep de anında denetleniyor ve kimseye bir şey kaybettirmiyor. Kaydın delil olarak
taşıdığı iş kalkınca, oyuncunun ondan çıkarmak zorunda kaldığı iki sayı kalıyor: yarım hamle
sayacı ve tekrar sayısı.
Bu hesap otomatikleştirilmeli: iki sayı her hamlede ekranda güncel durmalı. Sayaç tam hamle değil yarım hamle saymalı; bence doğrusu bu. Talep oynanacak hamle
üzerinden de kurulabildiği için sınırı hangi yarım hamlenin doldurduğunu bilmek kritik: tam
hamleyle yazılsaydı ekrandaki “49” hem 98 hem 99 yarım hamle olabilirdi ve oyuncu sıradaki
hamlesinin elliyi tamamlayıp tamamlamadığını bilemezdi. Hassasiyet tek bir yarım hamleye kadar inmeli.
Pozisyonun kaçıncı kez görüldüğü de aynı şekilde sürekli bildirilmeli; böylece oyuncu hakkın doğduğunu sonradan öğrenmez, yaklaştığını görür. Bence bu, hakkı ancak doğduktan sonra
bir düğmeyi yakarak bildiren yaklaşımlardan daha tutarlı: kitap talebi oynanacak hamle
üzerinden de tanıyor, düğme ise yalnızca oynanmış olanı görüyor.
FIDE'yi, hatta USCF'yi baz alan sistemleri dijitale uyarlamanın en mantıklı yolu da bence
bu. Geçersiz hamlenin imkânsız, yanlış talebin bedelsiz olduğu bir ortamda sayıları otomatik veren
sayaçlar, yukarıdaki uyarlamalarla tamamen tutarlı: kaydın yükü kalktıysa, kayıttan
çıkarılan hesabın yükü de kalkmalı.
Not yasağı düşünüldüğünde bu FIDE'ye aykırı sayılabilir mi? Sayılırsa motor en baştan
aykırı olur: yalnızca geçerli hamleye izin veriyor, geçersiz hamleye 7.5'in cezasını hiç
uygulamıyor, talebi de kayıt yerine anında denetliyor. Kayıt, not yasağı ve ceza maddeleri
oyunun değil masa başının kuralları; delil üretmek ve dürüstlüğü korumak için varlar ve
dijitalde o işi platform kendisi görüyor. FIDE'nin kendi çevrimiçi yönetmeliği ↗️
de aynı yönde: oyun alanı yalnızca geçerli hamleleri kabul ediyor, bütün hamleler ve saatler
otomatik kaydedilip iki oyuncuya da gösteriliyor (3.1.2, 3.1.6). Not yasağı orada da geçerli
(9.6), ama platformun iki tarafa birden gösterdiği kayıt not sayılmıyor; sayaçlar da o
kaydın özeti.
Bunun platformlar arasında standart hâle gelmesi iyi olur. Hakkı olan bir oyuncu, o hakkı
kullanabilmek için hamle listesini geriye doğru saymak zorunda kalmamalı.
FideLite'ta. İki sayı durum satırında her hamlede güncel duruyor; göstergeler bölümü nasıl okunacaklarını anlatıyor.
FIDE'nin çevrimiçi yönetmeliği dijitalde talep kendiliğinden
FIDE'nin tahta başı kurallarının yanında bir de çevrimiçi yönetmeliği ↗️ var:
resmî çevrimiçi turnuvalar ve oyuncuların bir hakemin gözetiminde bir arada ama ekranda
oynadığı hibrit turnuvalar için yazılmış. Hamle kurallarını (1–3. maddeler) olduğu gibi
alıyor; farklar az ve neredeyse hepsi arayüz tarafında.
Oyun alanı yalnızca geçerli hamleyi kabul ediyor; bu FideLite'ta da böyle. Hamlelerin ve
saatlerin kendiliğinden kaydedilmesi, ön hamle ve otomatik vezir terfisi ise oyuncunun ya da
arayüzün ayarı, kuralın değil. Projenin iddiası motorun kural doğruluğu üzerine, arayüzün
konforu üzerine değil.
Asıl fark talepte. Yönetmelik üçlü tekrarı ve 50 hamleyi kimsenin talebini beklemeden,
kendiliğinden beraberlik sayıyor (5.4). Bu, sanki iki oyuncunun da otomatik talebi
birbirine karşı açıkmış gibi; amaç sadelik. Bu da özünde bir sürücü tercihi: eşik
dolduğu anda talebi oyuncu adına kendisi yapan bir sürücü, motora dokunmadan aynı davranışı
verir. Sürümler arasında bunu yapanlar da var. Chess.com eşdeğeri,
platformun kendisi gibi, üçlü tekrarda ve 50 hamlede oyunu her zaman kendiliğinden bitiriyor.
Her türlü esnek ayarı sunan, FideLite motoruna dayanan Chess LUX sürümünde
ise bunu otomatik yapma seçeneği var.
Motorun kendisine dokunan tek fark terkte. Yönetmelikte terk eden her durumda kaybediyor (5.2). Tahta
başında ise 2023'ten beri, rakip hiçbir geçerli hamle dizisiyle mat edemiyorsa terk
beraberlikle bitiyor (5.1.2).
FideLite'ta. İlke, ana federasyonun kurallarını dijitale olabildiğince aslına sadık
taşımak; tahta başı kitabı bu yüzden uygulanıyor. Adı da bu ilkeyi taşıyor: fidélité,
sadakat. Talep ile kendiliğinden beraberlik bilerek ayrı tutuluyor: 50 hamle ve üçlü tekrar
taleple, 75 hamle ve beşli tekrar kendiliğinden. Biri oyuncunun hakkı, öbürü kuralın hükmü;
kitap da ikisini ayrı maddelere yazıyor. FideLite bu ayrımın dijitale sürtünmesiz
taşınabildiğini savunuyor: talep hamlenin kendi kanalıyla gidiyor,
sayılar ekranda duruyor; oyuncu ne hakkını kaçırıyor ne de onu kullanmak için hamle listesini
geriye doğru sayıyor.
Uygulama maliyeti. Tekrar, kitabın tahtanın dışında bilgi tutmayı gerektiren ilk maddesi. Pozisyonu tanımlayan şey yalnızca taşların yeri değil: sıra, rok hakları ve geçerken alma imkânı da o tanımın parçası. Bunlardan birini yanlış saymak, tekrar sayımını sessizce bozar.
5. Ölü pozisyon — 5.2.2
Kitap ne diyor
Madde şunu söylüyor: hiçbir oyuncunun, herhangi bir geçerli hamle dizisiyle rakibin
şahını mat edemeyeceği bir pozisyon doğduğunda oyun berabere biter. Böyle bir pozisyona
ölü pozisyon denir.
Üç noktanın baştan altını çizmek gerek, çünkü maddenin özü bunlarda.
İki taraflı. İki tarafın da mat edememesi gerekir; taraflardan biri hâlâ mat edebiliyorsa pozisyon ölü değildir. Bir sonraki bölümdeki 6.9 ve 5.1.2 ise yalnızca bir tarafı sorar — iki taraflı soruyla tek taraflı soruyu karıştırmak, bu sekmede en kolay düşülen hata.
Anında biter. Oyun, pozisyon ölü hâle geldiği anda bitmiştir. Ne bir talep beklenir ne de hakemin kararı. Sonrasında oynanan hamleler yok hükmündedir — kitap bunu, sonucu geriye dönük işletecek kadar ciddiye alıyor.
Üç kolu var. Mat imkânsızlığı materyalden doğabilir: tahtada mat edebilecek taş kalmamıştır. Yapıdan doğabilir: taşlar fazlasıyla yeterlidir ama pozisyon kilitlenmiştir. Ya da zorunlu varıştan: tahta şu an ne materyal ne yapı bakımından ölüdür, ama her geçerli devam onu matsız bir sona götürür. Aynı madde, üç ayrı soru.
Algoritmada nasıl okunur
Her kolu farklı hamleler doğurabilir; bir algoritmanın hangi kola ne zaman bakacağı da buradan çıkıyor:
Kol
Onu doğurabilen hamle
Neden
Materyal
Taş alımı. Tek istisna, tahtadaki son piyonun terfi edip yetersiz materyal bırakması: tek bir at ya da hepsi aynı renkte filler.
Materyal yalnızca alımla azalır. Terfi piyonu başka bir taşa çevirir; seçilen taş mat edemeyecek kadar zayıf olabilir.
Yapı
Taş alımı ya da piyon sürmesi. İstisna: hamle mat veriyorsa ya da sürülen piyon geçerken alma hakkı doğuruyorsa.
Sessiz hamle geri oynanabilir; ardından bir kilit görünüyorsa, o kilit önceki pozisyonda da vardı. Mat oyunu zaten bitirir. Geçerken alma hakkı ise başlı başına bir kilit kırıcı; kilit ancak hak düşünce doğar.
Zorunlu varış
Her hamle, sessiz olanlar dahil.
Sessiz bir hamle yarım hamle sayacını ilerletir; mat için yeterli hamle kalmamış olabilir.
Birinci kol: materyalden
Aşağıdaki tablo iki tarafın taşlarına birden bakar ve şunu sorar: bu tahtada mat diye bir şey var mı? Bir sonraki bölümdeki tabloyla karıştırılmamalı; o tablo tek tarafa bakar ve başka bir maddeye aittir. İkisi arasındaki bağ o bölümün sonunda kuruluyor.
Materyal
Hüküm
Çıplak şah
Yetersiz
Tek fil
Yetersiz
Hepsi aynı renkte filler
Yetersiz
Tek at
Yetersiz
Farklı renklerde filler
Yeterli
Fil + at
Yeterli
İki at
Yeterli
Herhangi bir kale, vezir ya da piyon
Yeterli
Atlar toplanır, filler birleşir
Ölü pozisyonda taşlar taraf ayrımı yapılmadan, tahtanın bütünü üzerinden sayılıyor; bu sayımda
at ile fil aynı terazide tartılmıyor. Atta sayı belirleyici: tahtada tek at varsa pozisyon
ölüdür; ikinci bir at, hangi tarafa gelirse gelsin, pozisyonu ölü olmaktan çıkarır. Filde öyle
değil. Tahtadaki bütün filler aynı renkteyse, kaç tane olurlarsa olsunlar ve kimin elinde bulunurlarsa bulunsunlar pozisyon ölüdür. O renkte gelen her yeni fil hükmü değiştirmez; pozisyon ancak tahtaya öbür renkte bir fil ya da filden başka bir taş geldiği anda ölü olmaktan çıkar.
♝♚♝♝♝♝♝♝♝♝♝♝♝♝♝♝♝♚
On altı fil, hepsi koyu karelerde. İşaretli e8, g8 ve f7 siyah şahın yatay ve dikey komşuları: açık kare oldukları için tahtadaki fillerin hiçbiri bu karelere ulaşamaz. Beyaz şah ise açık karede duruyor; orada kaldıkça ona şah bile çekilemez.
Yandaki tahtada on altı fil var: dokuzu beyazın, yedisi siyahın, hepsi koyu karelerde.
Beyazın dokuz fili ulaşılabilecek en yüksek sayı: başlangıçtaki koyu kareli fil ile sekiz terfi.
Doğru hüküm: ölü pozisyon, oyun berabere biter. Fil sayan bir sistem on altı fili
görür, materyali fazlasıyla yeterli sayar ve oyunu sürdürür. Oysa bu fillerin on beşinin yerinde
öbür renkte tek bir fil olsaydı, tahtada yalnızca iki fil kalırdı ve pozisyon ölü olmazdı.
Bu bir rastlantı değil, satrancın geometrisinden çıkan bir zorunluluk.
Fil rengini değiştirmez. Çapraz bir adım hem dikeyi hem yatayı birer kare değiştirir; bu yüzden karenin rengi hiç değişmez. Terfiyle doğan fil de doğduğu karenin renginde kalır. Koyu kareli bir
filin bütün etkisi koyu karelerdedir: saldırdığı, üzerinde durduğu ve başka taşların yolunu
kestiği kareler hep koyudur. Açık kareler onun için yok hükmündedir.
Kaçış karesi hep açıkta kalır. Tahtada şahlardan başka yalnızca koyu kareli filler varken şah altındaki bir şah mutlaka koyu karededir; yatay ve dikey komşuları ise açık karedir. Bu kareleri iki
tarafın hiçbir fili ne tehdit edebilir ne de üzerlerine çıkabilir; onları ancak karşı şah
tutabilir. Mat edilecek şah en elverişli yerde, köşede olsa bile karşı şah bunu başaramaz: köşedeki şahın iki açık
komşusu vardır ve ikisini birden tutmak için karşı şahın, mat edilecek şaha bitişik bir kareye
girmesi gerekir. Kenardaki şahın böyle üç, ortadakinin dört komşusu vardır. Dolayısıyla hiçbir taraf mat edemez.
Fillerin sayısı da hangi tarafa ait oldukları da bu ispatın hiçbir adımına girmiyor.
Koşul kalıcı. Tahtada piyon olmadığı için yeni bir taş türü doğamaz; alımlar yalnızca fil eksiltir. Öbür renkte bir fil tahtaya hiçbir zaman giremez. Koşul bir kez oluştuysa hiç bozulmaz
ve 5.2.2'nin “herhangi bir geçerli hamle dizisi” sorusu ileriye bakmadan, tek bakışta
cevaplanır.
Atta böyle bir değişmez yok: at her adımda renk değiştirir ve iki at, ister aynı tarafta ister ayrı taraflarda olsun, bir mat pozisyonunun kurulmasına imkân verir. Atta hükmü sayı verir, filde
renk.
FideLite'ta. Materyal yoklaması filleri tek tek saymaz; yalnızca
hangi renklerde fil bulunduğuna bakar.
Kapsam ve doğruluk
Bir ölü pozisyon algoritmasının doğruluğu iki ayrı soruya ayrılıyor; mantıktaki adlarıyla
sağlamlık (soundness) ve tamlık (completeness):
Sağlamlık: “ölü” dediği ölüdür. Beraberlik ilan ettiği her pozisyonda gerçekten hiçbir taraf mat edemez.
Tamlık: ölü olan her pozisyonu tanır. Hiçbir ölü pozisyon “ölü değil” cevabıyla oyunda kalmaz.
Bu ikisinin ağırlığı farklı: sağlamlık pazarlık konusu değil, tamlık ise ölçülen bir hedef.
Hata yalnızca tek yönde olabilir: ölü bir pozisyon kaçırılabilir, ama ölü olmayan bir
pozisyona hiçbir zaman “ölü” denmez.
Neden bu yön. İki hata eşit değil. Kaçırmada oyun sürer, hamleler yinelenir
ve iki sayaçtan biri beraberliği eninde sonunda getirir; sonuç yalnızca
geç gelir. Kaçırmanın yanlış sonuca dönüştüğü tek durum, oyunun sayaçlar dolmadan
kesilmesi: bir bayrak düşer ya da bir oyuncu terk eder ve beraberliğe hakkı olan taraf
kaybeder. Ağır bir hata, ama tek bir oyunla
sınırlı. Yanlış pozitif ise oyunu bitirir; ilan edilmiş bir beraberliği hiçbir kural
geri almaz. Bu yüzden ispatlanamayan bir imkânsızlık beraberlik sayılmaz: kuşku varsa cevap “ölü değil”.
Sağlamlık ispatlanabilir. Üç kolun her biri “ölü” derken bir ispata dayanıyor.
Materyal kolunda ispat tablonun kendisi. Kilitte desenin her koşulu, kilidi kırabilecek bir
hamle türünü dışlıyor; koşulların hepsi birden sağlandığında öyle bir hamle kalmıyor. Zorunlu
varışta ise ispat sonuna kadar tüketilmiş ağaç: her dal mattan önce bir sona çarpmış.
Tamlık ise yalnızca yetersiz materyalde ispatlanabiliyor. Materyal tablosu kapalı:
materyalin yetersiz olduğu her pozisyonu yakalıyor ve bu pozisyonların hepsi ölü. Ama yeterli
materyal pozisyonun canlı olduğunu göstermez; materyal yeterliyken pozisyon ölü de olabilir,
olmayabilir de. Bunu taşların sayısı değil, yapısı belirler: bir kilit ya da zorunlu bir
hat.
Ölü pozisyonda tamlığın asıl sorusu da bu yüzden yapıda. Kilitte tamlığın ispatı, iki ile
otuz iki taş arasındaki bütün geçerli pozisyonların kaba kuvvetle taranmasını gerektirir;
böyle bir tarama henüz yapılmadı. Zorunlu varışta tamlık bütçeye bağlı; sınırlı bir bütçe
bazı dalları yarıda bırakır. Bir algoritmanın “bütün ölü pozisyonları yakalar” demesi bu
yüzden fazla iddialı olur. Yine de pratikte karşılaşılan ölü pozisyonların neredeyse tamamı
yakalanabiliyor; kaçırma oranı da ölçülebilir.
Kapsam: ulaşılabilir tahtalar. Yapı ve zorunlu varış kollarının algoritmaları, standart dizilişten ya da Chess960 dizilişlerinden biriyle 32 taşla başlayan bir oyunda geçerli hamlelerle ulaşılabilen tahtalar için yazılır. Hiçbir oyunda doğamayacak tahtaların en azından bir kısmı aritmetikle tanınır ve baştan elenir: başlangıç kadrosunu aşan her taş bir terfiden gelir, her terfi bir piyona mal olur; fazla kale, at, fil ve vezirlerin toplamı eksik piyon sayısını aşamaz. Fillerde renk de sayılır: bir taraf aynı renkte en fazla dokuz fil bulundurabilir, başlangıçtaki fil ile sekiz terfi. Bunun ötesindeki tahtalar
algoritmaların konusu değil; hiçbir oyunda doğmadıkları için hakemin önüne de gelmezler.
FideLite'ta. Kaçırma oranı ölçülüyor. CHA-Solver'ın Lichess açık veritabanında
süreden haksız sonuçlanmış bulduğu 204.060 oyunun 200.384'ünde motor doğru
hükmü veriyor. Kilitli sınıfı motorun kendi kilit taraması ayırıyor: FideLite'ınki bu
oyunların 48.135'ini kilitli olarak tanıyor, filleri de kapsayan Chess LUX'unki
52.146'sını. LUX'un ayırdığı bu geniş sınıfta FideLite 48.483 oyunda doğru
hükmü veriyor (%92,98), Chess LUX hepsinde. Motorun
kaçırdığı 3.676 oyunun 3.638'inde tahtada fil var; motorun kilit taraması yalnızca şahları ve
piyonları kapsadığı için bu oyunlar aramaya kalıyor. Bu kaçırmalar, yaklaşık 8,22 milyar
oyunluk kaydın bütününde kabaca 2,2 milyonda bir oyuna denk geliyor. Ölçüm
Arbiter ile yapıldı.
İkinci kol: yapıdan — kilitli pozisyonlar
Materyal fazlasıyla yeterliyken de mat imkânsız olabilir. Piyonlar birbirini kilitlemiştir,
hiçbiri ilerleyemez, hiçbir taş alınamaz; iki taraf iş birliği yapsa bile bir mat pozisyonu
kurulamaz. Madde bu hâli de kapsıyor, çünkü “herhangi bir geçerli hamle dizisi” diyor —
taş saymıyor.
Kilit bir değişmezdir. Kilitli bir pozisyon, devamlar tek tek denenerek değil, tahtadan okunan bir desenle tanınır. Desen bir kez kurulduysa hiçbir geçerli hamle onu bozamaz. Bozamadığı
için bir sonraki pozisyonda da kuruludur, ondan sonrakinde de. Hamle hamle işleyen bu
tümevarım sayesinde tek bir pozisyona bakarak oyunun bütün geleceği hakkında hüküm
verilebilir.
Aranan şey kazanan değil, kilit kırıcı. İki taraf iş birliği yapsa bile ne geri alınamaz bir hamleye (taş alımı ya da piyon sürmesi, terfi dahil) ne de bir mata yol açılabiliyorsa pozisyon ölüdür. Geriye yalnızca geri alınabilir hamleler kalır, onlar da matsız bir mekik
dokur. Algoritma bu yüzden “kim kazanır” diye sormaz; kilidi kıracak bir hamle ya da bir mat
arar.
Kapsam içindeki bir tahtada kilit deseni beş koşulu birden
sağlar. Her biri yukarıdaki ilkenin doğal sonucu: biri bozulduğu an bir kırıcı doğar.
At, kale ve vezir gömülü olmalı. Gidebilecekleri her karede kendi taşları durmalı. Hareket edebilen bir at duvarın üstünden atlar, kale ve vezir açık bir hatta kayar; algoritma hareket edebilen her birini kırıcı sayar. Vezir de gömülebilir: a8'de kendi piyonları ve atıyla kapatılmış bir vezir bu koşulu sağlar. Rok hakkı olan bir kale, ancak rokun geometrisi de kapalıysa gömülüdür; Chess960 dizilişleri de buna dahil.
Piyon dikey değiştirememeli. Piyon ya donmuştur ya da alacak taşı olmadan yalnızca kendi dikeyinde ilerleyebilir. Çapraz alım dikeyi, terfi taşın türünü değiştirir; ikisi de geri alınamaz. Geçerken alma hakkı dururken pozisyon kilitli sayılmaz, çünkü o alım da bir dikey değişimidir.
Şahın yaptığı alım kilidi açmamalı. Şah serbestçe dolaşabilir; duvarın öbür yanına geçmesi tek başına bir şey değiştirmez. Ama bir taş alabiliyorsa, desen o alımdan sonra da kurulu olmalı.
Fil etkisiz kalmalı. Fil dolaşabilir, ama hiçbir taş alamamalı ve bir piyonun alabileceği kareye çıkamamalı. Çıkabilseydi kendini aldırıp piyona dikey değiştirterek kilidi kırardı. Filin şah çekebilmesi ise tek başına bir şey söylemez: kimi yapıda etkisizdir, kimi yapıda tek taraflı bir mata kapı açar.
Gömülü olan gömülü kalmalı. Şah, fil ya da piyon nereye giderse gitsin, at, kale ve vezir gömülü kalmalı. Bir piyonun ilerlemesi, şahın ya da filin yer değiştirmesi gömülü bir taşın önünü açıyorsa, o hamle de kırıcıdır.
Koşulların tahtada nasıl göründüğünü üç örnek gösteriyor. En yalını yalnızca şahlardan ve
piyonlardan oluşuyor:
♚♟♟♟♟♟♟♚
Altı piyon karşılıklı kilitli: hiçbiri ilerleyemiyor, çaprazlarında alacak taş yok. Şahlar aradaki açık dikeylerde serbestçe dolaşabiliyor — ama iki şah birbirini mat edemez ve tahtada başka taş yok. Doğru hüküm: ölü pozisyon.
Benzer bir kilidin üstüne iki fil koy: hüküm değişmiyor — pozisyon yine ölü, çünkü filler de
kendi yarılarına hapsolmuş ve hiçbir taş alınamıyor.
♚♝♟♟♟♟♟♟♟♟♚♝
Fillerin karşıya geçememesinin sebebi kare renkleri: beyazın piyonları 4. yatayın bütün açık karelerinde, siyahınkiler 5. yatayın bütün koyu karelerinde. Açık kareli beyaz fil 4. yatayı, koyu kareli siyah fil 5. yatayı hiçbir zaman aşamıyor — iki yatayın kalan karelerini de karşı piyonlar tehdit ediyor.
♝♛♜♞♚♟♟♟♟♝♟♝♟♝♟♝♟♟♟♟♟♟♟♟♟♚
Dördüncü yatay baştan sona siyah taşlarla dolu; serbest vezir, kale ve at bu duvarı aşamıyor, beyaz şah da birinci yatayda kapalı. Kilitli, ama ulaşılamaz: siyahın on iki piyonu var.
Birinci koşulun bedeli. Yandaki tahtada siyahın veziri, kalesi ve atı serbest;
pozisyon yine de kilitli ve algoritma onu tanımaz. Ama bu duvarı kurmak için siyahın on iki
piyonu gerekiyor: tahta kapsamın dışında, hiçbir oyunda doğamaz.
Bu koşullar nereden geliyor. Bir kısmı tahtanın geometrisinden zorunlu olarak
çıkıyor: terfi aritmetiği, piyonun dikey değiştirmesi. Kalanı, algoritmanın bilerek temkinli
durduğu yerler; hareket edebilen bir atı, kaleyi ya da veziri kırıcı saymak gibi. On iki
piyonlu tahta bu temkinin bedelini gösteriyor. Şah güvenliğine, sıraya ve açmazlara bakmamak da aynı işi görüyor: taraflara gerçekte olduğundan fazla imkân tanıyor. Fazla imkân
daha çok kırıcı, daha çok kırıcı da “kilit değil” demek; temkin bir hataya yol açarsa, o hata güvenli yönde olur. Gerçek oyunlarda karşılaşılan kilitlerin neredeyse tamamı bu desenlere uyuyor.
Taramanın ne zaman çalışacağı da kuraldan çıkıyor. Kilit ancak geri alınamaz bir
hamleyle doğar: taş alımı ya da piyon sürmesi. Sessiz bir hamle — şahın ya da
uzun menzilli bir taşın yer değiştirmesi — kilit kuramaz, çünkü tersi de oynanabilir: ardından bir kilit görünüyorsa, o kilit bir önceki pozisyonda da vardı. Alım da piyon sürmesi de yarım hamle sayacını
sıfırlıyor; taramanın anı bu.
Rok hakkının düşmesi kilidi doğurmaz.Tekrar sayımında geri
alınamaz bir olaydır, çünkü pozisyonun kimliğini değiştirir; ama kilit rok hakkına değil,
rokun geometrisine bakar. Rok bir gün yapılabilecekse kale gömülü sayılmaz ve kilit zaten
yoktur. Hiç yapılamayacaksa hakkın düşmesi tahtada hiçbir şeyi değiştirmez.
Kuralın iki istisnası var. Birincisi mat: kilidi doğuran hamle aynı anda mat da
verebilir.
♚♟♟♟♟♟♟♚♟♟♟♟
Siyah oynuyor ve f5 sürüyor. Piyon e4'teki şaha şah çekiyor; piyonu da e6'daki şah koruyor. e3'ü d4, f3'ü g4 tutuyor; d4'ü c5 koruyor; d5 ve e5 siyah şaha bitişik: mat. Aynı hamle yapıyı da kilitledi: artık hiçbir piyon ne ilerleyebiliyor ne bir şey alabiliyor.
Mat önce gelir. Şahlar başka yerde olsa, aynı f5 yalnızca kilidi kurardı: pozisyon
ölü, oyun berabere.
8/2k5/5p2/p1p5/P1Pp1Pp1/3P2P1/8/4K3 b - - 0 1
Yandaki tahtada ise f5 hem kilidi doğuruyor hem mat ediyor. Desene bakan bir tarama donmuş piyonları ve hiç alım kalmadığını görür, “ölü” der. Ama oyun f5 oynandığı anda matla
bitti; mat edilmiş bir pozisyon ölü pozisyon sayılmaz. Bu yüzden önce mata bakılır, kilide
ancak mat yoksa. Doğru hüküm: siyah kazandı.
İkincisi geçerken alma. Piyonun çift adımı kilidi doğurmuş olabilir, ama aynı hamle bir geçerken alma hakkı da doğurabilir ve o hak dururken pozisyon ölü sayılamaz. Tarama tam o anda çalışırsa “kilitli değil” der; ardından geri alınamaz bir hamle gelmeyeceği için bir daha hiç çalışmaz ve pozisyon kalıcı olarak kaçırılır. Taranması
gereken, hak düştükten sonraki, yani bir yarım hamle sonraki pozisyon. Kural dört satıra
sığıyor:
Hamleden sonra
Tarama
Neden
Hamle mat verdi
Bakılmaz.
Oyun matla bitti; desen kurulmuş olsa da sonuç mat.
Yarım hamle sayacı sıfırlandı, geçerken alma hakkı yok
Bakılır. Sonra sayaç yeniden sıfırlanana kadar bakılmaz.
Kilit ancak geri alınamaz bir hamleyle doğar; aradaki sessiz hamleler geri oynanabildiği için yeni kilit kuramaz.
Yarım hamle sayacı sıfırlandı, geçerken alma hakkı var
Bakılmaz.
Hak zaten bir kilit kırıcı; pozisyon bu hâliyle ölü olamaz.
Sayaç sıfırlanmadan bir yarım hamle geçti, geçerken alma hakkı düştü
Bakılır. Sonra sayaç yeniden sıfırlanana kadar bakılmaz.
Kırıcı ortadan kalktı; kilit çift adımla doğduysa şimdi görünür. Hakkı düşüren hamle sessiz olduğu için sayaç sıfırlanmaz; bu satır olmasa tarama bir daha hiç çalışmaz.
FideLite'ta. Motor bu algoritmanın yalnızca şah ve piyondan oluşan kısmını
uyguluyor ve doymadan hüküm vermiyor; pratikte karşılaşılan
kilitlerin çoğu bu kısımda. Tahtada şah ve piyondan başka bir taş varsa — bir fil, ya da
gömülü bile olsa bir at, kale veya vezir — tarama çalışmıyor; o kilitleri
6.9'un araması alan darsa kapatıyor, genişse kaçırıyor. Filleri ve gömülü
taşları da tanıyan genişletme kardeş proje Chess LUX'ta. Dar bir örnek:
♝♚♟♟♟♟♟♟♟♟♟♟♟♟♟♟♚♞
At d1'de kendi piyonlarının arasına gömülü: gidebileceği dört kare — b2, c3, e3 ve f2 — kilitli beyaz piyonlarla dolu. Beyaz şahın a1–c1 arasında, siyah şahın h8–g8 arasında yeri var; siyah fil de e7 ile g7'nin arkasında kapalı.
Tarama susuyor, ama beyazın iki, siyahın tek hamlesi var ve arama bütün devamları
yedi denemede tüketiyor: hüküm doğru, yalnızca başka bir kapıdan geliyor.
Kilit bazen tahtada henüz kurulmuş değildir. Zorunlu olarak alınacak bir vezir, başka
hamlesi kalmamış bir piyon ya da dolmak üzere olan bir sayaç onu birkaç hamle sonra kurar, ya
da oyunu ondan önce matsız bitirir. Bu pozisyonlar da ölüdür, ama tanınmaları ileriye bakmayı gerektirir; bunlar üçüncü kolun konusu.
Üçüncü kol: zorunlu varış — tahtanın neye dönüşeceği
Materyal yeterliyken ve yapı kilitli değilken de mat imkânsız olabilir. Madde tahtadaki
taşları değil, herhangi bir geçerli hamle dizisini soruyor ve bazı pozisyonlarda bütün
diziler aynı yere akıyor: hamleler zorunlu ya da seçenekler hiçbir şeyi değiştirmiyor. Hat
nereye giderse gitsin matsız bir sona varıyor. Tahtanın şu anki hâli yanıltıcı;
hüküm, o tahtanın neye dönüşmek zorunda olduğundan çıkıyor.
Aranan şey yine bir mat, ama ileride. Algoritma iki tarafın iş birliği yaptığını
varsayar ve önündeki bütün geçerli hamle dizilerine bakar. Bir dizi bile mata varıyorsa
pozisyon ölü değildir. Her dizi mattan önce bir sona çarpıyorsa ölüdür. Beş son var: ilk iki kol, pat, dolan yarım hamle sayacı ve beşli tekrar. Zorunlu hat tahtayı çoğu
zaman ilk iki koldan birine taşıyor; bu kol, onların birkaç hamle öteden görünüşü. Sayacı
dolduran hamle mat veriyorsa sonuç mattır.
Ağaç bir bütçeyle taranır. Sayaç ve tekrar ağacı kuramsal olarak sonlu kılıyor, ama
pratikte tüketilemeyecek kadar büyük bırakabiliyor. Algoritma bu yüzden bir bütçeyle çalışır:
ağacı bütçe içinde sonuna kadar tükettiyse hüküm “ölü”, tüketemediyse “ölü değil”, yani
güvenli yöndeki cevap. Zorunlu hamlelerden ya da birkaç seçenekten
oluşan dar bir hat bir avuç denemede tükenir; seçenekler çoğaldıkça bütçe yetmez ve kaçırma
başlar.
Burada da iki tarafın mat edememesi gerekiyor; zorunlu hattın yalnız bir taraf için
matsız olduğu pozisyonlar ise 6.9'un konusu; onlar bir sonraki bölümde. Aşağıdaki beş biçim, beş sonun her birine bir örnek.
♚♛♚
Beyaz şahta ve tek hamlesi Şxb2: alım yapılınca tahtada iki şahtan başka bir şey kalmıyor.
Zorunlu yetersiz materyal. Tek geçerli hamle, mat edebilecek tek taşı alıyor: beyazın zaten şahından başka taşı yok, siyahın veziri de zorunlu olarak gidiyor. Hiçbir taraf mat edemez: pozisyon, vezir şah çektiği anda ölü. Doğru hüküm: beraberlik. Statik bakan bir sistem ise tahtadaki veziri görüp oyunu sürdürür. Kolun en sık rastlanan hâli de bu.
♚♟♟♟♟♟♟♚♟
Siyahın şahı kendi piyonları ve rakip piyonlarla hapsolmuş: g7 piyonu f8 ile h8'i tehdit ediyor. Geriye yalnız a7 piyonunun iki hamlesi kalıyor; tükendiğinde pat.
Zorunlu pat. Aynı kalıp, başka bir varış. Zorunlu hat mata değil pata çıkıyor ve oyun orada bitiyor. Beyaz da mat edemez: siyah şaha şah çekebilecek bir taşı yok, a piyonu da karşısındaki piyona takılıyor. Doğru hüküm: beraberlik. Burada tahtadaki materyal fazlasıyla yeterli olabilir — yeterlilik meselenin dışında.
♛♚♟♟♟♟♟♟♟♟♚
Siyah şahta ve tek hamlesi Şxb7. Vezir gidince geriye kilitli piyon duvarı kalıyor: hiçbir piyon ilerleyemiyor, hiçbir şah karşıya geçemiyor.
Zorunlu kilitli pozisyon. Zorunlu hattın sonunda ikinci kol var: materyal yeterli, mat yine de imkânsız. Tahtadaki vezir yanıltıcı; tek işi alınmak. Doğru hüküm: beraberlik. İki kolun birleştiği yer burası — ve taş saymakla asla bulunamayacak olan da bu.
Üçünde de hat zorunlu, değişen yalnızca varış; üçünde de tahtaya bakan biri doğru cevabı bulabilir. Kalan ikisi ise kitabın otomatik sınırlarına varıyor: biri yarım hamle sayacına, öbürü tekrara bağlı. Yani tahtaya değil, tahtanın geçmişine.
♚♞♚♞
Simetrik tahta: iki şah, iki at.
Zorunlu 75 hamle. Bu pozisyon diyagramdan okunamaz — hükmü belirleyen şey tahtadaki
taşlar değil, sayacın kaçta olduğu. O yüzden FEN'i (pozisyonu sıra, rok hakları, geçerken alma karesi ve sayaçlarla birlikte tek satırda yazan standart gösterim) de yanına koymak gerekiyor:
4k1n1/8/8/8/8/8/8/4K1N1 w - - 148 250
Sondan ikinci alan yarım hamle sayacı: 148. Sayaç 150'de dolduğu için geriye 2 yarım hamle kalıyor. Materyal yeterli; şah ile at yardımlı mat kurabilir. Ama iki yarım hamlede kurulamıyor: sayaç önce doluyor, oyun orada
kendiliğinden berabere bitiyor ve mat hiçbir zaman gerçekleşemiyor. Taşlar simetrik olduğu
için bu iki taraf için de geçerli. Doğru hüküm: beraberlik.
♝♚♟♟♟♟♟♟♟♟♚♝
İki tarafın da tek hamlesi var: şah mekiği. Filler kendi köşelerinde kilitli, piyonlar karşılıklı tıkalı. Pozisyon iki tam hamlede geri geliyor.
Zorunlu beşli tekrar. Zorunlu hat aynı pozisyona dönüp duruyor ve hiçbir taraf mat edemiyor. Oyun en geç beşli tekrarda kendiliğinden bitiyor. Doğru hüküm: beraberlik. Tekrarın kendisi oyunun geçmişine bağlı — hangi pozisyonun kaç kez görüldüğü tahtanın dizilişinden çıkarılamaz — ama bu hat sonsuza kadar döndüğü için geçmiş ne olursa olsun varış aynı.
Burada durup bir şeyin altını çizmek gerek: ölü pozisyonu tanıyan algoritma, satranç programlamanın en zorlarından biri — bana göre en zoru. Mat, pat, tekrar,
sayaç — hepsinin tanımı kapalı: mat ve pat hamle üretiminden çıkıyor ve perft (bir
pozisyondan çıkan bütün hamle dizilerini sayan standart doğruluk sınaması) ile sınanabiliyor;
tekrar ve sayaçlar test pozisyonları ve referans uygulamalarla birebir doğrulanabiliyor. Ölü pozisyonun tanımı da net, ama her pozisyonda onu ispatlayan tanımlı bir yöntem yok. Maddeyi eksiksiz çözdüğünü iddia eden biri bile bunu ispatlayamaz: ispat, iki ile otuz iki taş arasındaki
yaklaşık 4,8×1044 geçerli pozisyonun tek tek sınanması demek; bunu bitirebilecek
bir bilgisayar da, onay verecek bir külliyat ya da otorite de yok.
Bu yüzden bir uygulamanın söyleyebileceği en güçlü şey “%100'e yakın”; kaçırdığı oyun
sayısı ölçülür, ispatlanmaz — ölçüm bir sonraki bölümde. Kesin
olabilecek tek şey yön: ölü denen her pozisyon gerçekten ölü olmalı,
tanınmayan ise oynanmaya devam etmeli.
FideLite'ta. Her hamleden sonraki yoklama ileriye bakmıyor; bakması da gerekmiyor,
çünkü hat zorunlu ve oyun birkaç yarım hamle sonra aynı sonuca zaten varıyor.
6.9'un araması bu yüzden bir kapanış yoklaması: yalnızca oyun o
varıştan önce kesildiğinde — bayrak düştüğünde ya da biri terk ettiğinde — bir kez çalışıyor
ve hattı oyunun yerine yürüyor.
İki sınırı var: toplam 2.000 hamle denemesi ve tek bir hat için 9 yarım
hamle. Hangisi önce biterse cevap “mat mümkün”; sınır beraberlik uydurmuyor. Sıradan bir
pozisyonda hüküm milisaniyenin çok altında geliyor; gerçek oyunlardan gelen en ağır pozisyonda
56 milisaniye, bütçeyi tüketen bir aramada bile bir saniyenin altında: oyun başına en fazla
bir kez ödenen bir sürtünme.
Dar hatlarda bütçe fazlasıyla yetiyor: beşli tekrar örneği dört, sayacı 148'deki
iki atlı örnek 72 denemede, onlarca milisaniyede kapanıyor. Sayaç geriye çekildikçe hüküm 147 ile 146 arasında tersine dönüyor — mat yüzünden değil, bütçe yüzünden: 147'de ağaç 688
denemede tükeniyor, 146'da 2.000'e sığmıyor.
Sınırları büyütmek tamlığı biraz artırır, ama derinliği artırmak her sıradan pozisyonu, bütçeyi artırmak da tavana dayanan her pozisyonu aynı oranda yavaşlatır. CHA-Solver da aynı yerde duruyor:
sınırsız bütçeyle hep doğru hükmü verdiğini söylüyor, ama 23 kat daha hızlı, kuramsal olarak tam
olmayan sürümünü öneriyor. Ben de aynı fikirdeyim: birkaç uç istisna için sıradan pozisyonlar
yavaşlatılmamalı.
Motor bu yönü koruyor: ölü dediği her pozisyon ölü, tanımadığını oynatmaya devam ediyor.
Çevrimiçi satranç platformları da aynı yönde; fark tamlıkta: onlarda bu maddeyi uygulayan bir kontrol hiç yok ve
kilitli pozisyonların tamamını kaçırıyorlar.
Uygulama maliyeti. Kitabın hem kod boyutu hem hız bakımından en pahalı maddesi. Materyal kolu taş saymaktan ibaret ve ucuz. Yapı kolu tahtanın bütününe bakmayı gerektiriyor: kilidin kalıcı olduğunu, hiçbir hamle dizisinin onu açamayacağını göstermek. Zorunlu varış ise mat ve patın tersine mevcut pozisyondan cevaplanamıyor; ileriye bakan bir arama istiyor.
6. Süre bitimi, terk ve mat imkânı — 5.1.2 · 6.9
Kitap ne diyor
Bu iki madde aynı kalıba dayanıyor. Terk eden ya da süresi biten taraf kaybeder — ancak
rakibi, hiçbir geçerli hamle dizisiyle onun şahını mat edemeyecek durumdaysa sonuç
beraberliktir.
Süre bitimi için bu istisna kitapta çoktandır var. Terk için 2023'te eklendi:
önceki metin, terk eden tarafın her hâlükârda kaybettiğini söylüyordu. Yani aynı pozisyonda terk eden oyuncu 2022'de kaybediyordu; 2023'ten itibaren beraberlik alıyor.
Terk sıraya da bağlı değil. Teklif ve talebin aksine oyuncu, rakibin saati işlerken de dahil, her an terk edebilir (5.1.2).
6.9'un açılış cümlesi de gözden kaçırılmamalı: madde, 5.1.1, 5.1.2, 5.2.1,
5.2.2 ve 5.2.3'ten biri geçerliyse hiç devreye girmiyor. Mat, terk, pat, ölü pozisyon ve
anlaşmalı beraberlik bayrak düşmesinin önüne geçiyor. Matın bayrağa üstünlüğü de buradan
geliyor: mat oyunu o anda bitirir, ardından düşen bayrak sonucu değiştirmez.
Tek taraflı. 5.2.2 iki tarafı birden sorar; bu iki madde ise yalnızca terk eden ya da süresi biten tarafın rakibini sorar. Senin mat edebiliyor olman seni kurtarmaz — soru senin ne yapabildiğin değil, rakibinin ne yapabildiği.
“Zorlayabilir mi” değil, “kurulabilir mi”. Madde zorlamalı mat (rakip ne yaparsa yapsın gelen mat) aramıyor; kaybeden taraf en kötü hamleleri oynarsa ortaya çıkabilecek bir mat olup olmadığını soruyor. Yani yardımlı mat. Kaybeden taraf kendi şahının kaçışını kendi taşlarıyla kapatarak yardım edebildiği için, savunanın taşları da hesaba girer. Gerçek bir örnek: Soćko–Foişor, 2008.
Algoritmada nasıl okunur
dijitalde yorum payı yok İnce nokta, matla bayrağın aynı ana denk geldiği durum. Kitap bunun dayanağını hamlenin tanımında veriyor: hamle, oyuncu saate bastığında tamamlanır; oyunu bitiren bir hamle — mat gibi — ise saate basılmasını beklemeden, yapıldığı anda tamamlanmış sayılır (6.2.1.1). Yani mat ile bayrak arasındaki soru, matın bayraktan önce tahtaya konup konmadığı. Tahta başında mat hamlesi süre bitimine
denk gelir ve hangisinin önce olduğu şüpheliyse, şüphe genellikle mat eden lehine yorumlanır.
Bu esnekliğin sebebi insanın kendisi: hamleyi tahtaya koymak da saate basmak da zaman alır,
bayrağı görmek de bir göz işidir. Belirsizlik bu kısıtlardan doğar ve tahta başındaki yorum payı onu gidermek için vardır. Dijitalde bu kısıtların hiçbiri yok. Sınır keskin, bayrak her
an düşebilir ve hangi olayın gerçekten önce geldiğine algoritma karar verir: mat tahtada,
bayrak saatte, iki ayrı katmanda tetiklenir ve doğru kabul edilmesi gereken hangisi önce
tetiklendiyse o. Dijital ortam, bu maddenin yoruma en kapalı olduğu yer.
Bir hakem algoritmasını çevrimiçi bir uygulamaya taşıyan, sıra konusuna da dikkat etmeli:
teklif ve talep sıraya bağlı kalmalı, terk bağlanmamalı.
FideLite'ta. Hamle işlenmeden önce saat güncelleniyor; süre o anda bitmişse hüküm
bayraktan, bitmemişse tahtadan geliyor. Terkin sıraya bağlı olmaması ise tam sağlanamıyor:
sürümler tek ekranda oynanan simülasyonlar, terk de hamlenin kanalından geçiyor ve terk düğmesine basan, sırası gelen taraf sayılıyor. Amaç kural doğruluğunu sergilemek olduğu için taviz buradan
verildi; ayrıntısı Tuzaklar'da.
Ucuz yarı: tahtadaki taşlar
İlk soru taş saymakla cevaplanır: rakibin elindeki taşlarla mat diye bir şey var mı? Aşağıdaki tablo cevabı durum durum veriyor. Beşinci bölümdeki tablodan farkı şu: orada iki tarafa birden
bakılıyordu, burada mat edecek tarafa bakılıyor — savunanın taşları ise yalnızca
yardım edebildiği için hesaba giriyor.
Tek atla mat ancak savunan tarafın, kendi şahının kaçış karesini kapatacak bir taşı varsa kurulabilir:
fil, at, piyon, hatta kale. Savunan vezir bu işi göremez, çünkü köşedeki kaçışı kapatabileceği
her kareden aynı anda mat eden atı da tehdit eder. Savunan piyon iki yoldan yeter. Piyon a ya da h dikeyindeyse köşedeki şahın kaçış karesini terfiden önce de kendisi kapatabilir; başka bir dikeydeyse terfi eder: madde herhangi bir geçerli hamle dizisine izin verdiğinden, piyon
sıkıştırmayı yapacak taşa dönüşebilir. Piyon için söylenenler tek fille mat için de geçerli.
Mat eden
Savunan
Mat
Kurulabilen pozisyon
Ş
Ş + herhangi bir taş
Yok
kurulamaz
Ş + Fil
Ş
Yok
kurulamaz
Ş + Fil
Ş + Fil aynı renk
Yok
kurulamaz
Ş + Fil
Ş + Kale
Yok
kurulamaz
Ş + Fil
Ş + Vezir
Yok
kurulamaz
Ş + At
Ş
Yok
kurulamaz
Ş + At
Ş + Vezir
Yok
kurulamaz
Ş + At
Ş + Kale
Var
♚♜♞♚
Ş + At
Ş + Fil
Var
♚♝♚♞
Ş + At
Ş + At
Var
♚♞♞♚
Ş + At
Ş + Piyon a ya da h dikeyinde
Var
♟♞♚♚
Ş + At
Ş + Piyon diğer dikeylerde
Var
Terfiden sonrapiyonken kurulamaz; piyon terfi edip sıkıştırmayı yapacak taşa dönüşünce mümkün
Ş + Fil
Ş + At
Var
♚♞♝♚
Ş + Fil
Ş + Fil zıt renk
Var
♚♝♚♝
Ş + Fil
Ş + Piyon a ya da h dikeyinde
Var
♝♟♚♚
Ş + Fil
Ş + Piyon diğer dikeylerde
Var
Terfiden sonrapiyonken kurulamaz; piyon terfi edip sıkıştırmayı yapacak taşa dönüşünce mümkün
Ş + At + At
Ş
Var
♞♚♞♚
Bunlar kurulabilen pozisyonlar, zorlanabilen sonuçlar değil. Hiçbiri bir kazanma
yöntemi değil; yalnızca “bu materyalle mat diye bir şey var mı” sorusunun cevabı — ve 6.9
ile 5.1.2 tam olarak bunu soruyor.
Hani kurulamıyordu?
Tabloya göre Ş+F, Ş+V'ye de Ş+K'ye de karşı mat kuramaz. Beşinci
bölümdeki sonuç burada da geçerli: aynı renkte ikinci bir fil, kimin elinde olursa olsun, bu
satırları değiştirmez; savunanın taşı her şaha ya araya girerek ya da şah çeken fili alarak
karşılık verir.
♛♚♜♝♚♝
b6 ve g5'teki koyu kareli filler d8'deki şaha aynı anda şah çekiyor. İşaretli c7 ve e7, tek bir şaha karşı ağır taşların araya gireceği kareler; çifte şaha karşı yalnızca şah hamlesi yapılabildiği için ikisi de kullanılamıyor.
Yine de tahtada böyle bir mat duruyor. Siyah şah d8'de; yanında kendi ağır taşları var: c8'de
vezir, e8'de kale. Beyaz şah d6'da, b6 ve g5'teki filler aynı anda şah çekiyor. Tek fil olsaydı
mat olmazdı: b6'daki filin çektiği şaha karşı c8'deki vezir c7'ye, g5'tekinin çektiğine karşı e8'deki kale e7'ye girerdi. Her ağır taş bir şahı karşılayabiliyor; ikisini birden ancak çifte şah aşıyor.
Ama iki çapraz ışın aynı anda şah çekemez: öndeki fil, hamleden önce zaten şah çekiyor
olurdu. Gerekçenin tamamı üçüncü bölümde. Bu tahta çizilebiliyor ama
oynanamıyor.
♚♚
İki şah yan yana; siyah şahın kaçabileceği her kare beyaz şahın yanında.
Kurulabilseydi, bunu da mat saymamız gerekirdi: siyah şah tehdit altında ve gidecek karesi yok. Tahtada yalnızca iki çıplak şah var, ama tanımın lafzına göre bu bir mat.
Bunu mat saymak oyunun önkabullerini çiğner ve o zaman hiçbir sınır kalmaz: çıplak şah bile
mat edebilir, materyal tablolarının hepsi çöker. İki tahta eşdeğer: ikisi de geçerli hamlelerle hiçbir zaman ortaya çıkamayan birer çizim. Sınırı nereye çektiğimiz bize kalmış, ama ikisi için
aynı yerden çekmeliyiz; birini mat sayan öbürünü de saymak zorunda kalır. Kural yalnızca oynanabilen pozisyonlardan söz ediyor.
Pahalı yarı: tahtanın zorunlu olarak neye dönüşeceği
Taş saymak sorunun yalnızca yarısını çözüyor, üstelik ucuz yarısını. Rakibinin tahtada veziri duruyor olabilir; statik bakan bir sistem “yeterli” deyip geçer. Oysa madde
tahtadaki taşları değil, herhangi bir geçerli hamle dizisini soruyor. Ve bazı pozisyonlarda bütün diziler tek bir hatta iner: başka hamle yoktur.
Tipik örnek şu: rakip vezirle şah çekmiştir, senin tek geçerli hamlen o veziri almaktır — ama almaya vakit kalmadan süren biter. Tahtada vezir görünüyor; yine de
rakibin seni mat etmesine giden hiçbir hamle dizisi yok, çünkü oyunun devam edebildiği tek
hat vezirin alınmasından geçiyor. Doğru hüküm beraberlik.
Pratikte karşılaşılan hâl çoğunlukla bu kadar yalın ve arama böyle bir dalı tek denemede
kapatıyor. Talep edilmek üzere olan bir üçlü tekrar ise burada işe yaramıyor: talebe bağlı
olduğu için hattı bitirmiyor; hattı kendiliğinden bitirenler yalnızca beşli tekrar ve 75
hamle. Zorunlu hat iki taraf için de matsız bir yere varıyorsa pozisyon zaten ölüdür (5.2.2). Yalnız bir taraf için
matsızsa hüküm asimetrik olur; o durumlar aşağıda.
Fark tek bir cümlede: taş saymak bayrak düştüğü andaki tahtaya bakar, kitap ise o
tahtanın zorunlu olarak neye dönüşeceğini sorar. Tahta başında bu sorun büyük ölçüde yok;
hakem zaten ileriye bakıyor. Haksız hükümler sunucularda birikiyor.
FideLite'ta. Ölçüt Miguel Ambrona'nın
CHA-Solver'ı: bu pozisyon sınıfını ilk kez sistematik olarak tarayan, hakemli
bir makaleyle yayımlanmış açık kaynak bir çözücü. Lichess açık veritabanında, 30 Eylül
2026'ya kadarki oyunlarda haksız sonuçlanmış 204.060 oyun saydı. Sınıfı motorun
kendisi ayırıyor: FideLite'ın kilit taraması bunların 48.135'ini kilitli pozisyon
olarak tanıyor; kalan 155.925'i aramaya ve malzeme tablosuna kalıyor.
Motor bu oyunların 200.384'ünü doğru karara bağlıyor: tanıdığı 48.135 kilidin
hepsini, kalanların 152.249'unu.
Kaçırdığı 3.676 oyun ölçülmüş bir sayı, ispatlanmış bir sınır değil; hepsinde arama
sınırına takılıyor ve 3.638'inde tahtada fil var, yani kilit taramasının kapsamı dışında. CHA-Solver yarım hamle sayacını ve tekrarı hesaba katmadığı için, sayacın mattan
önce dolduğu pozisyonları kazanılabilir sayıyor; bu motor beraberlik diyor. Aynı veritabanı
— yaklaşık 8,22 milyar oyun — bu motorla taransa, CHA-Solver'ın saymadığı yanlış sonuçlanmış
başka oyunların da çıkması muhtemel. Ölçümü aynı veriyle kendin de yineleyebilirsin:
Arbiter.
Uygulama maliyeti. Bu maddeyi uygulamamanın gerekçesi hep aynı olmuştur: hesaplanamaması. Gerekçe sanıldığı kadar sağlam değil. İleriye bakmak gerekiyor, ama zorunlu dallar tanım gereği dar ve soru oyun başına en fazla bir kez, oyun kesilirken soruluyor.
Arama gerektiren tek taraflı kazançlar
6.9 yalnızca tek tarafı sorduğu için hüküm asimetrik olabilir: aynı tahtada mat yalnızca bir taraf için mümkün olabilir ve sonucu bayrağın kimde düştüğü belirler.
5.2.2 böyle çalışmaz; orada soru iki taraf için birden sorulur ve cevap
tek olur. Bunu taş sayarak görmenin yolu yok; tahtanın neye dönüşebileceğine bakmak
gerekiyor. İki örnek: biri zorunlu bir hattan, biri kilitli bir yapıdan.
♚♚♜♟♟♛
Beyaz şahta ve tek hamlesi Şxe7 — o hamle kalenin yolunu açarak siyaha koridor matı veriyor. Ama oynayacak vakit yok.
Zorunlu karşı mat. Bayrağı düşen tarafın tek geçerli hamlesi rakibini mat ediyor. Ama o hamleyi oynayacak vakit kalmamıştı ve oyun bayrak düştüğü anda bitti; kazandı denemez. Rakip de sıra alamayacağı için onu hiçbir diziyle mat edemez. Doğru hüküm: beraberlik. 6.9, “ben mat edebilir miyim” diye değil, “rakibim beni mat edebilir mi” diye soruyor.
♝♟♚♝♟♟♟♟♟♟♟♟♟♚
Piyonlar donmuş: beyazınkiler açık karelerde, siyahınkiler koyu karelerde, filler zıt renkte — tek mümkün alım, siyah şahın korumasız beyaz fili alması; o da pozisyonu ölü yapar. Şahlar da karşı tarafa geçemiyor. Ama şu hâliyle pozisyon ölü değil: beyaz hâlâ mat edebiliyor, siyah edemiyor.
Asimetrik kilit. Piyon yapısı donduğu için ulaşılabilir durum sayısı sonlu ve
tamamı sayılabiliyor: beyaz fil tahtada kaldığı sürece 68.672 durum. Bunların hiçbirinde
beyaz mat değil; 58'inde siyah mat. Örneğin:
♚♝♟♟♟♝♟♟♟♟♟♟♟♚
Aynı kilitten çıkan 58 mat pozisyonundan biri. Beyaz fil c6’dan şah çekiyor, a6 piyonu b7’yi tehdit ediyor ve siyahın kaçacağı iki kareyi kendi fili ile kendi piyonu kapatıyor. Beyaz şah hâlâ d1’de — mata hiç karışmıyor.
Hüküm yine asimetrik: beyazın bayrağı düşerse beraberlik, siyahınki düşerse
kayıp. Pozisyon ölü olmadığı için oyun da durmuyor ve durmaması gerekiyor. Pratikte
milyarda bir görülecek bir yapı, ama 6.9'un 5.2.2'den neden ayrı bir madde olduğunu tek bir
tahtada gösteriyor.
FideLite'ta. İki örnekte de hüküm doğru: bayrağı düşen ya da terk eden taraf mat
edilemiyorsa beraberlik, edilebiliyorsa kayıp. Kendin denemek için
Chess LUX ↗️
sayfasını aç, FEN'i girip Play ile başlat ve hamle yapmadan ⚐ ile terk et; terk eden,
sırası gelen taraf.
k3K2R/pp2q3/8/8/8/8/8/8 w - - 0 1
5b2/p2k1B2/Pp1p1p2/1P1P1Pp1/6P1/8/8/3K4 w - - 0 1
5b2/p2k1B2/Pp1p1p2/1P1P1Pp1/6P1/8/8/3K4 b - - 0 1
İlk ikisinde beyaz terk ediyor ve oyun berabere bitiyor (½‑½); üçüncüsünde siyah terk
ediyor ve kaybediyor (1‑0).
Tek at, karşısında vezirden başka taş yoksa mat kuramaz. At bir tane olmalı; vezir birden fazla olabilir.
Hepsi aynı renkteki filler, karşısında kale, vezir ve aynı renkte filden başka taş yoksa mat kuramaz. Fillerin sayısı fark etmez; hepsi aynı renkte kaldığı sürece rakibin elinde bile olabilirler.
Bu tek taraflı kuralların kesişimini, yani iki tarafın da birbirine mat kuramadığı
bileşimleri alınca beşinci bölümdeki ölü pozisyonun materyal
tablosu çıkıyor: çıplak şaha karşı çıplak şah, tek at ya da hepsi aynı renkte filler;
aynı renkteki fillere karşı yine o renkteki filler. Başka her bileşimde en az bir taraf mat
kurabilir. Yani 5.2.2'nin materyal kolu, 6.9'un sorusunun iki taraf için birden sorulmuş
hâli.
İki mat imkânı
uygulayana öneri Mat edebilirlik tek bir soru değil, iki: beyaz
siyahı mat edebilir mi, siyah beyazı mat edebilir mi? Her pozisyon için iki imkân ayrı ayrı çıkarılabilir. Tıpkı rok hakları gibi bunlar da pozisyonun taşıdığı iki küçük bilgidir ve onlar gibi yalnızca düşebilir: bir pozisyondan hiçbir hamle dizisi mata varmıyorsa,
ondan sonraki pozisyonlardan da varamaz. Düşen imkân oyunun sonuna kadar bir daha dönmez.
Burada da yön aynı: imkân, düştüğü ispatlanana kadar duruyor
sayılır.
Bayrak düştüğünde (6.9) ya da biri terk ettiğinde (5.1.2) hükmü yalnızca rakibin imkânı
belirler: duruyorsa kayıp, düştüyse beraberlik. Üç sonuç çıkıyor:
İki imkân da duruyorsa süre bitimi ve terk doğrudan kayıp ilan edilir.
Bir tarafınki düştüyse o taraf artık kazanamaz; en fazla beraberlik alabilir. 6.9 ve 5.1.2'nin sorduğu tam olarak bu.
İkisi de düştüyse pozisyon ölüdür, yani bildiğimiz 5.2.2; oyun o anda berabere biter.
İmkânlar her hamle oynanır oynanmaz güncellenebilir. O zaman süre bitiminde ve terkte yapılacak iş kalmaz: hüküm hazırdır, yalnızca okunur. Bunun maliyeti de sanıldığı kadar
yüksek değil, çünkü daha önceki çıkarımlar işi hafifletiyor:
Düşmüş bir imkân bir daha sorulmaz.
Materyal ve kilit yalnızca alım ya da piyon sürmesiyle değişebildiği için (beşinci bölümün başındaki tablo), sessiz hamlelerden sonra yeniden okunmaz.
Böylece her hamlede iki taraf için de tam kontrol gerekmez. İş, imkânı gerçekten
değiştirebilecek hamlelere kalır; hem bütçe hem işlemci rahatlar.
Hangi yolun seçileceği tercih ve önceliklere bağlı: imkânları oyun boyunca güncel tutmak
ya da ileriye bakan aramayı bayrağa ve terke bırakıp yalnızca o an, tek taraflı yapmak.
FideLite'ta. Motor ikincisini seçiyor. Bu bölüm, kuralları ya da CHA-Solver'ı
okuyup kendi hakemini yazacak biri için bir öneri.
Tek bir aramanın kısa devreleri
Her ölü pozisyon aslında kendiliğinden gelecek bir beraberliktir. Mat imkânı yoksa
oyunu, sonsuza kadar sürmesini engelleyen iki sınırdan biri er geç
bitirir; bazı pozisyonlarda ise zorunlu bir pat daha da erken bitirir. 5.2.2 o kaçınılmaz sonu yalnızca
beklemeden ilan ediyor.
Buradan bir sonuç daha çıkıyor: yeterli bütçesi olan bir arama hepsini kendisi
bulurdu. Önündeki her geçerli hamle dizisini bu sonlara kadar yürüyen bir arama, materyal tablosuna da kilit desenine de ihtiyaç duymadan her ölü pozisyonu tanır; 6.9'un tek
taraflı sorusunu da aynı yoldan cevaplar. Aynı iki sınır ağacı sonlu kıldığı için bu kuramsal
olarak mümkün. Ama o ağacı tüketmek tek bir pozisyon için bile bir oyuncunun bekleyebileceği
sürenin çok ötesine taşabiliyor; oysa 5.2.2 her hamleden sonra, 6.9 ile 5.1.2 ise oyun
kesildiği anda bir hüküm istiyor.
Bu yüzden 5.2.2'nin kolları ve 6.9'un materyal tablosu gereksiz değil; o aramanın kısa
devreleri. Her biri ağacın bir bölümünü dal dal gezmeden kapatıyor:
Materyal tablosu bir çıkarım. Belli taşlarla mat kurulamayacağı bir kez gösterildiyse, o taşlarla oynanabilecek bütün diziler birden kapanır.
Kilit bir tümdengelim. Desenin hiçbir hamleyle bozulamayacağı, hamle kurallarından çıkan kesin bir bilgi. Bozulamıyorsa bir sonraki pozisyonda da vardır, ondan sonrakinde de; tek bir tahta bütün geleceği söyler.
Zorunlu varış bir domino. Hamleler zorunluysa hat dallanmaz: her hamle bir sonrakini domino taşı gibi devirir ve hat matsız bir sona varır. Arama yalnızca o hattı yürür; ağacın gerisi hiç açılmaz.
Bu kısa devreler bir yazılımın değil, problemin kendisinin özelliği. Hangi dilde, hangi
motorla yazılırsa yazılsın, kuralı makul sürede uygulamak isteyen her hakem bu türden kısa
devrelere başvurmak zorunda. Hepsi aynı soruya — herhangi bir geçerli hamle dizisi mata varır
mı — daha ucuz bir yoldan cevap veriyor; hiçbiri soruyu değiştirmiyor.
7. Talep ve anlaşma — 9.1 · 9.2 · 9.3 · 9.5
Kitap ne diyor
Mevcut pozisyon üzerinden talep. Eşik tahtada zaten dolmuşsa hamle yapmadan talep edilir.
Hamleyle talep. Oyuncu oynayacağı hamleyi bildirir ve o hamle oynandığında kuralın sağlanacağını beyan eder. Hamle bildirildikten sonra değiştirilemez.
Yanlış talebin bedeli — 9.5.dijitalde karşılığı yok Talep doğru çıkmazsa rakibe iki dakika ek süre verilir. Bu, masa başındaki sürtünmeyi telafi ediyor: talep saati durdurur, hakemi masaya çağırır, rakibin temposunu ve dikkatini böler. Dijitalde talep anında ve otomatik doğrulanır; saat durmaz, kimse beklemez, rakip hiçbir şey kaybetmez. Telafi edilecek bir kayıp olmadığı için kuralın dijitalde karşılığı da yok — geçersiz hamlenin cezasıyla aynı yolu izliyor.
Algoritmada nasıl okunur
Talep penceresi
Kitap talebi hamleye bağlı kuruyor: ya oynayacağın hamleyi bildirip talep
edersin ya da pozisyon doğduğunda, sıra sendeyken. Tahta başında bu pencere düşünme süren
kadar geniştir; rahatça yeter.
Sıra sendeyken, tahtada zaten dolmuş bir eşik için talep etmekte sorun yok. Sürtünme, hak
yapacağın hamleyle doğacaksa başlıyor. Kitap bunu hamleyi önceden bildirip talep etmeye
izin vererek çözüyor. Dijitalde hamle bu bilgiyi kendi içinde taşımıyorsa ve otomatik bir
tetik de açık değilse, oyuncu önce hamlesini oynamak zorunda kalıyor — ama hamle oynandığı an
sıra rakibe geçiyor ve hak artık onun. Rakip bir alış ya da piyon hamlesiyle sayacı
sıfırlayabilir, pozisyonu değiştirebilir; ön hamlede (premove — sırası gelmeden girilip
sırası gelir gelmez otomatik oynanan hamle) bu milisaniyeler içinde oluyor. Hak, oyuncu talep
edemeden, istemediği biçimde elinden gidiyor. Kural ihlal edilmiş olmuyor — kullanılmamış bir
hak zaten düşer — ama tahta başında hamleyi bildirerek kullanılan hak, ekranda fiilen
kullanılamaz hâle geliyor. Bu belirli bir siteye özgü değil; hamleye talep iliştirmeye izin
vermeyen her platformda aynı; ön hamle durumu yalnızca ağırlaştırıyor.
Buradan çıkan tasarım sonucu net: talep kanalı hamlenin kendisiyle birlikte
gitmelidir — ya hamle talebi kendi içinde taşır ya da hak otomatik tetiklenir. Ayrı bir
düğmeye bırakılırsa pencere yeniden kapanır.
Teklif de hamleye bağlıdır
Kitap talep ile teklifi birbirine bağlıyor: 9.2 ya da 9.3'e göre yapılan her talep aynı
zamanda bir beraberlik teklifidir (9.1.2.3). Şartı tutmayan bir talep boşa gitmez; rakibin
önünde teklif olarak kalır ve rakip kabul ederse oyun berabere biter. Teklifin kendi kuralları da talebinkine paralel:
Anlaşmalı beraberlik — 9.1.dijitalde hamleye gömülür Masa başında teklif, hamle tahtada yapıldıktan sonra ve saate basılmadan önce yapılır: teklif hamleye iliştirilir, ayrı bir mesaj değildir. Dijitaldeki karşılığı, teklifi taleple aynı kanaldan hamlenin içine gömmek.
Teklifin düşmesi. Teklif, rakip onu açıkça reddettiğinde ya da kabul etmek yerine hamlesini oynadığında geçerliliğini yitirir. Teklif veren taraf teklifini geri alamaz.
Başka bir anda yapılan teklif yine geçerli sayılıyor, ama rakibi rahatsız etmek 11.5'in
kapsamına giriyor — yani rakip düşünürken teklif, kitabın öngördüğü yol değil.
Platformlarda ise teklif bu pencereyle hiç sınırlı değil: her an gönderilebiliyor ve tekrarlandığında
rakibin düşünmesini bölen bir araca dönüşüyor. Kitabın teklifi sıraya bağlayarak dışarıda
bırakmak istediği davranış tam olarak bu.
Terk bu şartın dışında: kitap terki sıraya bağlamıyor; ayrıntısı 6. bölümde.
FideLite'ta. Pencere uygulanıyor: teklif de talep de yalnızca sırası gelen taraftan
gelebiliyor, çünkü ikisi de hamlenin kendi kanalından gidiyor.
8. Federasyon farkı: FIDE, USCF ve platformlar
Hamle kuralları, taşların hareketi, mat ve pat iki federasyonda birebir aynı. Ayrışma
tahtanın dışında başlıyor — saatte, terkte, beraberlik talebinde. İki kitap aynı oyunu
yönetiyor; oyuncunun, saatin ve hakemin devreye girdiği yerlerde ayrışıyor.
Usul farkları
50 hamle ve üçlü tekrar — ortak. İki kitapta da oyun kendiliğinden bitmiyor; beraberlik için oyunculardan birinin talep etmesi gerekiyor.
75 hamle ve beşli tekrar — FIDE'de kendiliğinden, USCF'de takdire bağlı. FIDE 9.6 talep beklemeden oyunu bitiriyor. USCF'de aynı eşikler var ama beraberliği yönetici ilan edebilir, etmek zorunda değil (14K); beşli tekrar da yalnızca art arda gelen tekrarları sayıyor. Yönetici karışmazsa oyun sürüyor.
Terk. USCF'de terk her zaman kayıp, pozisyona bakılmıyor. FIDE'de 2023'ten beri 5.1.2 gereği, rakip hiçbir geçerli hamle dizisiyle mat edemiyorsa terk beraberliğe dönüyor.
Ölü pozisyon — ortak. FIDE 5.2.2 ile USCF 14D aynı ilkeyi koyuyor: iki taraf için de mat imkânı kalmadıysa oyun o an biter, kilitli pozisyonlar dahil.
Asıl ayrım: yardımlı mat ve zorlamalı mat
Bayrak düştüğünde iki kitap da aynı soruyu soruyor — rakip bu oyuncuyu mat edebilir mi —
ama “edebilir”i farklı okuyor. İkisi de pozisyona bakıyor, ikisi de bir arama gerektiriyor;
ayrıldıkları yer, aramanın neyi aradığı.
FIDE: yardımlı mat — 6.9. Kaybeden tarafın en kötü hamleleri oynadığı, kendi şahının kaçış karelerini kendi taşlarıyla kapattığı bir dizi yetiyor. Soru “mat kurulabilir mi”. Zor hâlleri 6. bölümde.
USCF: zorlamalı mat — 14E. Rakipte yalnızca çıplak şah, tek at, tek fil ya da — süresi bitenin piyonu yokken — iki at kaldıysa, oyuncu süreden kaybetmiyor; rakibin zorlamalı bir matı yoksa. Soru “her savunmaya karşı mat zorlanabilir mi”. Liste nereye bakılacağını söylüyor; istisnayı görmek için pozisyonun kendisine bakmak gerekiyor.
Zorlamalı matı aramak sanıldığından hafif. Zorlamalı mat ya vardır ya yoktur;
kurala bir derinlik ya da eşik yazmaya gerek yok. Bu sınıflarda mat etmeye çalışan tarafın elinde bir
ya da iki hafif taş var, savunan tarafın tek bir kurtaran hamlesi bütün bir dalı kapatmaya
yetiyor ve ağaç çok küçük kalıyor. Mat varsa kısa — pratikte beş hamle civarını geçmiyor.
Teorik sınır daha uzun olabilir, ama bugünün makineleri onu da zorlanmadan buluyor.
İki kitap çizgiyi de farklı yerlerden çekiyor. Örneğin USCF, süresi biten tarafa daha cömert: süresi
bitenin elinde yalnızca şahı kaldıysa ve rakipte iki at varsa FIDE kazanç verir, çünkü yardımlı mat
kurulabilir; USCF beraberlik verir, çünkü mat zorlanamaz.
Aşağıdaki tabloda “kazanç”, süresi bitmeyen tarafın kazancı.
Süresi biten
Rakip
FIDE
USCF
Ş+K
Ş+A
Kazançyardımlı mat kurulabiliyor
Beraberlikzorlamalı mat yoksa
Ş+F
Ş+F (zıt renk)
Kazançyardımlı mat kurulabiliyor
Beraberlikzorlamalı mat yoksa
Ş+V
Ş+A
Beraberlikmat kurulamıyor
Beraberlik
Ş
Ş+A+A
Kazançyardımlı mat kurulabiliyor
Beraberlikiki at matı zorlayamıyor
Ş+P
Ş+A+A
Kazanç
Kazançsüresi bitenin piyonu var
Platformlar
♟♚♞♚
Beyaz Af1 oynadı. Siyahın tek hamlesi h3–h2, ardından Ag3 mat: şahı
köşeye kilitleyen taş kendi piyonu.
Tek tahta, üç ölçü, iki sonuç. Yandaki pozisyonda beyazın elinde yalnız bir at var,
siyahın ise h dikeyinde bir piyonu. FIDE'de soru önce materyalle cevaplanıyor:
6. bölümdeki tabloya göre şah ve at, a ya da h dikeyinde piyonu olan
şaha karşı yardımlı mat kurabiliyor. USCF'nin listesinde tek at yetersiz sayılıyor, ama 14E
rakibin zorlamalı bir matı olduğunda istisna tanıyor ve bu pozisyon tam o istisna. İki
federasyon farklı yollardan aynı sonuca varıyor: siyahın bayrağı düşerse beyaz kazanır.
Chess.com ise pozisyona bakmıyor, yalnızca materyali sayıyor — bu materyal çıplak
bir şahı mat edebilir mi — ve tek at edemediği için
beraberlik veriyor.
Arama yapmayan bu ölçü USCF'nin listesini izliyor ama istisnasını göremiyor. Böyle kenar
durumlarda ortaya çıkan sonuç iki federasyona göre de haksız: kaybedeceğini gören taraf
süresini bilerek bitirip beraberliğe ulaşabiliyor.
FideLite'ta. Motor, Lichess gibi FIDE'ye sadık kalıyor; ama 6.9'u yalnızca materyal
karşılaştırmasıyla değil, gerektiğinde pozisyonun kendisine inen aramayla uyguluyor.
Karşılaştırma için iki platformun davranışını izleyen eşdeğer sürümler de var, Varyantlar
sekmesindeki Özel sürümler bölümünde: USCF ölçüsünü yarım uygulayan —
listesini alıp istisnasını bırakan — Chess.com eşdeğeri ve 6.9'un yardımlı mat koşulunu
yalnızca materyal karşılaştırmasıyla uygulayan Lichess eşdeğeri. Aynı pozisyonu üçünde yan
yana oynamak mümkün.
9. Önerilen karma sistem
Dijital bir platformda iki kitaptan birini olduğu gibi uygulamak her zaman en iyi sonucu
vermiyor. USCF'de 75 hamle ve beşli tekrar yöneticinin takdirine bırakıldığı için, sunucuda bu
kararı veren biri yoksa oyun net bir sınırda bitmeden sürüp gidebiliyor. FIDE'de ise 50 hamle ve üçlü tekrar talebi, kazanılmış
teknik oyun sonlarını tek bir hatayla beraberliğe çevirebiliyor.
Aşağıdaki karma sistem iki kitabın en adil görünen parçalarını birleştiriyor. Bu bir
öneri: FideLite'ın kendisi FIDE'yi uyguluyor ve bu sekmedeki geri kalan her şey o kitaba göre yazıldı.
Ancak iki kitabın ayrıştığı, tartışmalı noktalarda bana en makul ve adil gelen sınırlar
bunlar. Hangi maddenin hangi kitaptan geldiği de tabloda açıkça duruyor; Chess.com'un bazı
maddelerde yaptığı karmadan farkı da bu. Bu sınırlar zaten federasyondan federasyona, hatta
turnuvadan turnuvaya değişebiliyor; satrancın çekirdeği ya da gövdesi değiller. Bir önerim ise
doğrudan gövdeye dokunuyor: sayacı neyin sıfırlaması gerektiği. Bu,
kuralın kendisinde köklü bir değişiklik; gerekçesini ayrı bir başlıkta temellendiriyorum.
Parametre
FIDE
USCF
Karma
50 hamle · üçlü tekrar talebi
Açık
Açık
Kapalı
Kendiliğinden beraberlik
75 hamle · beşli tekrar
Yokyönetici 75 hamlede ya da art arda beşli tekrarda ilan edebilir
60 hamle · dörtlü tekrar
Anlaşmalı beraberlik
Açıkiki taraf da en az bir hamle yaptıktan sonra
Açıkasgari hamle şartı yok
Açıkilk hamleden itibaren
Ölü pozisyon
Hemen biter
Hemen biter
Hemen biter
Tekrarda geçerken alma
Geçerli
Geçerli
Geçerli
Tekrarda rok
Hak
Hak
Hak
Sayacı sıfırlayan
Piyon hamlesi, alış
Piyon hamlesi, alış
Her geri alınamaz olay+ rok ve geçerken alma hakkının düşmesi
Bayrak düşmesinde ölçüt
Yardımlı mat
Zorlamalı mat
Yardımlı mat
Yetersiz materyal sayılan filler
Tahtadaki bütün filler tek renkteyse
Tek fil
Tahtadaki bütün filler tek renkteyse
İki at, süresi bitenin piyonu varken
Kazanç
Kazanç
Kazanç
İki at, piyonsuz tarafa karşı
Kazançyardımlı mat varsa
Beraberlikzorlamalı mat yoksa
Kazançyardımlı mat varsa
Terk
Yardımlı matrakip mat edemiyorsa beraberlik
Her zaman kayıp
Yardımlı matbayrakla aynı ölçü
50 hamle ve üçlü tekrar talebi — kapalı. Bu eşiklerde taleple oyun bitmiyor, anlaşmalı beraberlik açık kalıyor; sınır 60'ta kendiliğinden çiziliyor. Fark teknik oyun sonlarında kritik: fil ve atla mat en iyi oyunla bile 33 hamleye kadar sürebilir, vezirin kaleye karşı Philidor pozisyonu da uzundur. 50 hamle bunun üstüne yalnızca 17 hamle pay bırakıyor ve tek bir hata kazanılmış oyunu beraberliğe çevirebiliyor; 60 payı 27'ye çıkarıyor, 75 ise 42'yle bitmiş oyunu uzatıyor. Bu üstün tarafa imtiyaz değil, tek bir hatanın sonucu belirlemesine engel; savunan taraf dörtlü tekrar ve 60 hamleyle yine korunuyor. Talep suistimal edilemiyor, 7. bölümdeki ön hamle sorunu da kökünden kalkıyor.
60 hamle ve dörtlü tekrar — kendiliğinden. Sayaç 60 hamleye varınca ya da pozisyon dördüncü kez doğunca oyun berabere biter. Eşikler iki kitabınkilerin harmonik ortalaması: 50 ile 75'inki tam 60, 3 ile 5'inki 3,75, yuvarlanınca 4. Üçlü tekrar erken, beşli tekrar geç kalıyor; harmonik ortalama ikisinin arasına, küçük değere daha yakın bir yere düşüyor: sınır genişliyor ama ölçüsüzce değil. Oyunun sonlu kalmasını garanti eden en dış ağ bu.
Anlaşmalı beraberlik — ilk hamleden itibaren. FIDE iki tarafın da en az bir hamle yapmasını şart koşuyor (5.2.3); USCF hamle şartı koymuyor, yalnızca teklifi hamle belirlendikten sonra, saate basmadan önceye bağlıyor (14B1). Karma sistem de hamle şartı koymuyor; teklif hamleyle gittiği için zamanlama aynı kalıyor.
Ölü pozisyon — hemen biter. 5.2.2'deki gibi, kilitli pozisyonlar dahil.
Yarım hamle sayacı — her geri alınamaz olayda sıfırlanır. Piyon hamlesi ve alışın yanında rok ve geçerken alma hakkının düşmesi de sayacı sıfırlıyor; gerekçesi aşağıda.
Bayrak düşmesi — yardımlı mat, FIDE'deki gibi. Süre bitince sorulan, rakibin matı zorlayıp zorlayamayacağı değil, herhangi bir geçerli hamle dizisiyle mat edip edemeyeceği (6.9). Süre oyunun parçası; saati yönetmek de tahtayı yönetmek gibi oyuncunun gücünün bir ölçüsü. Ağır taşlara karşı tek atla ya da tek fille kazanmak ilk bakışta adil görünmeyebilir, ama o galibiyeti at değil, rakibin süresini yönetememesi getiriyor; oyuncular süreyi de hesaba katmayı öğrenmeli. USCF'nin zorlamalı mat ölçüsü süresini tüketeni koruyor, karma sistem korumuyor.
Terk — yardımlı mat, bayrakla aynı ölçü. FIDE 2023'ten beri terkte de bu ölçüyü kullanıyor (5.1.2), USCF'de ise terk her zaman kayıp. Karma sistem FIDE'yi izliyor: terk de bayrak gibi oyunu tahtada bitmeden kapatıyor, rakip mat edemiyorsa terk eden kaybetmiyor.
Uygulama tarafında bir not: yardımlı mat araması (6.9) bayrak düşmeyi beklemek zorunda değil.
Hafif taşlı her pozisyonda arka planda çalışabilir; bayrak düştüğünde sonuç hazırdır ve
oyuncular için bekleme olmaz.
Sayacı ne sıfırlamalı
9.3 ve 9.6.2 sayacı yalnızca iki olaya bağlıyor: piyon hareketi ve taş alma. İkisinin ortak
noktası geri alınamaz olmaları; sayacın bütün anlamı da buradan geliyor. Ama kitabın kendi
mantığı harfinden ileri gidiyor. Aynı sınıfa iki olay daha giriyor: bir rok hakkının düşmesi
ve geçerken alma hakkının düşmesi. İkisi de geri gelmiyor ve ikisini de pozisyonun kimliğine
9.2.3'te kitabın kendisi katıyor. Bence sayaç dördünde de sıfırlanmalı. Bu bir okuma değil,
kural değişikliği: sayaç 140'tayken bir kale hamlesiyle rok hakkı düşerse FIDE oyunu on yarım
hamle sonra berabere bitirir, bu sayaçla oyun sürer.
Geçerken almanın düşmesi pratikte nadiren fark yaratır: hak ancak sayacı zaten sıfırlayan
bir çift adımla doğuyor, düştüğünde de sayaç yalnızca bir yarım hamle geç sıfırlanmış olur.
Fark küçük, ama onu saymak kuralı kesin tutuyor. Hak burada 9.2.3.1'in ölçüsüyle sayılıyor: alış
gerçekten geçerli değilse düşecek bir hak da yok.
Bu kadar ince düşünmemin nedeni üç kuralın birbirine oturması. Böyle tanımlanan sayaç
“son geri alınamaz olaydan bu yana geçen yarım hamle” demek olur ve üç kural aynı sinyalde
buluşur: 75 hamle onu sayar; beşli tekrara yalnızca o
pencerenin içine bakmak yeter, çünkü öncesi bir daha doğamaz; kilitli pozisyon taraması da onun
sıfırlandığı anda çalışır. 5. bölümdeki iki parçalı koşul — sayaç
sıfırlandıysa ve geçerken alma karesi yoksa, ya da hak az önce düştüyse — tek parçaya iner:
sayaç sıfırlandı ve geçerken alma karesi yok. Çift adımın bir yarım hamle gizlediği kilit hak
düştüğü anda yakalanır, çünkü o an da bir sıfırlanma. Sonluluk korunur: rok hakkı dört, geçerken alma
hakkı en fazla on altı tane; en uzun oyun en fazla birkaç yüz yarım hamle uzar.
10. Çerçeveyi değiştiren kurallar
İlk dokuz bölüm standart oyun üzerineydi. Bu bölüm oyunun kendisini değil çerçevesini
değiştiren kurallara bakıyor. Kitap bunların bir kısmını ana maddelerden sonra, ek ve
kılavuzlarda veriyor; bir kısmını da turnuva yönetmeliğine bırakıyor. Ortak noktaları şu:
tahtadaki kurallar olduğu gibi kalıyor, değişen onların etrafı.
Chess960 — Kılavuz II
Kitap ne diyor
♛♜♚♝♝♞♜♞♟♟♟♟♟♟♟♟♟♟♟♟♟♟♟♟♛♜♚♝♝♞♜♞
777 numaralı diziliş, QRKBBNRN. Şah c1'de, b1 ve g1'deki kalelerin
arasında; filler d1 ve e1'de, yani zıt renk karelerde. Piyonlar yerinde, siyah
beyazın aynası.
Chess960'ta oyun standart satrançla aynı biçimde oynanıyor: taşlar aynı hareket ediyor, amaç
yine mat; geçerlilik, pat, tekrar ve sayaçlar değişmiyor. Farklı olan iki şey var:
başlangıç dizilişi ve rokun yapılışı. Kitap ikisini de Kılavuz II'de
veriyor. FIDE kılavuzları Satranç Kuralları'nın parçası saymıyor, ama uygulanabildiği her etkinlikte
kullanılmalarını şiddetle öneriyor.
Diziliş. Yalnız arka sıra değişiyor: piyonlar yine ikinci ve yedinci yatayda,
birinci yataydaki sekiz taşın sırası oyundan önce rastgele belirleniyor ve siyah aynı
sırayla karşısına diziliyor. İki şart var: şah iki kalenin arasında durmalı, filler zıt
renk karelerde olmalı. Bu iki şart tam 960 dizilişe izin veriyor: filler 4 × 4 yolla,
vezir kalan altı kareden birine, atlar kalan beşten ikisine (10 yol) yerleşiyor; geriye
kalan üç kareye kale, şah, kale sırası zorunlu. 16 × 6 × 10 = 960. Dizilişler 0–959
arası numaralanıyor (Scharnagl numarası); klasik başlangıç 518, yandaki 777.
Ad kanattan, uzunluktan değil. Standartta rok kısa ya da uzundur; 960'ta şahın gideceği mesafe dizilişe göre değiştiği için bu adlar anlamını yitiriyor. Kitap roku şahın vardığı dikeyle adlandırıyor: c kanadı roku (0-0-0) ve g kanadı roku (0-0). Programlamada aynı ayrım, kalenin durduğu tarafa göre a kanadı ve h kanadı diye de geçiyor.
Varış kareleri standarttakiyle aynı. Şah ve kale nereden başlarsa başlasın, standart rokun son karelerine gidiyor: c kanadında şah c1'e, kale d1'e; g kanadında şah g1'e, kale f1'e. Siyahta aynısı sekizinci yatayda.
Bu yüzden rokun dört biçimi var. Kitap onları ayrı ayrı sayıyor (II.3.2): iki taşın da gittiği çift hamleli rok, iki taşın yer değiştirdiği rok, yalnız şahın gittiği rok ve yalnız kalenin gittiği rok. Şah c dikeyinde başladıysa c kanadı rokunda yerinden kıpırdamıyor, yalnızca kale gelip yanına oturuyor (yandaki 777: QRKBBNRN); şah g'de başladıysa aynısı g kanadında oluyor (34: BNNQRBKR). Tersine, rok yapılacak kale zaten varış karesindeyse, yani c kanadının kalesi d'de ya da g kanadınınki f'de başladıysa, bu kez yalnızca şah gidiyor (295: QNBRKRNB, iki kanatta da). İkisi birden yerinde kalamıyor: şah c'deyse c kanadının kalesi onun solunda, a ya da b'dedir ve d'ye gelmek zorundadır; kale d'deyse şah onun sağındadır ve c'ye gitmek zorundadır. 960 dizilişin 276'sında şah, 258'inde kale en az bir kanatta yerinde kalıyor.
Mesafe de dizilişe bağlı. Standartta şah rokta iki yöne de iki kare gider. 960'ta sıfır ile beş kare arasında gidiyor: b dikeyinde başlayan şah g kanadı rokunda beş kare birden yol alıyor (741: RKBBNNQR). Diziliş serbest olduğu için rok ilk hamlede bile yapılabiliyor: 469'da, RNBBNKRQ'de, şah f1 ile kale g1 yan yana ve boşalması gereken kare yok; g kanadı rokunda ikisi yer değiştiriyor. 960 dizilişin 162'sinde rok ilk hamlede mümkün.
Boş kalması gereken kareler dizilişe bağlı. Şahın başlangıç ve varış karesi arasındaki bütün kareler ile kalenin başlangıç ve varış karesi arasındaki bütün kareler, varış kareleri dahil, şah ve rok yapan kale dışında boş olmalı. Standartta boş olması gereken bir kare burada dolu kalabiliyor, standartta ilgisiz bir kare burada boşalmak zorunda kalabiliyor. Tehdit şartı ise aynı: şahın durduğu, geçtiği ve vardığı kareler tehdit altında olmamalı, kale yine muaf (3.8.2.2).
Yandaki 777'de iki kanat iki ayrı biçimi gösteriyor. c kanadı rokunda şah c1'den
kıpırdamıyor, b1'deki kale şahın üstünden d1'e geçiyor; bunun için yalnızca d1'deki filin
çekilmesi yetiyor. g kanadı rokunda ise iki taş da gidiyor: şah c1'den g1'e dört kare,
g1'deki kale f1'e; bunun için d1, e1 ve f1'in üçü de boşalmalı.
Algoritmada nasıl okunur
Rok artık şahın iki kare gitmesiyle yazılamıyor. Standartta rok, şahın iki kare
yana gitmesi olarak kodlanabiliyor (e1g1); başka hiçbir şah hamlesi iki kare
gitmediği için belirsizlik yok. 960'ta şah sıfır ile beş kare arasında gidebiliyor: yerinde
kalan şahın kaynak ve hedef karesi aynı çıkıyor, tek kare giden şahın rok hamlesi ise sıradan
bir şah hamlesiyle karışıyor. Motorların ortak çözümü roku şahın kendi kalesini alması
gibi yazmak: UCI'nin Chess960 kipinde yandaki dizilişte c kanadı roku c1b1, g
kanadı roku c1g1. Kaynakla hedef hep farklı ve hiçbir geçerli şah hamlesi kendi
taşının karesine gidemediği için belirsizlik kalmıyor.
Rok hakkı kaleyi dikeyiyle tanımalı. FEN'in KQkq alanı kalelerin a ve h
dikeyinde durduğunu varsayıyor. 960'ta hakkın hangi kaleye ait olduğu da yazılmalı:
Shredder-FEN her zaman kalenin dikey harfini kullanıyor (yandaki diziliş için
GBgb); X-FEN ise KQkq'yu koruyup o taraftaki en dıştaki kaleyi kastediyor, harfe
ancak rok yapılacak kale en dıştaki değilse geçiyor. Yandaki dizilişin FEN'i bu yüzden X-FEN
olarak şöyle yazılabiliyor:
qrkbbnrn/pppppppp/8/8/8/8/PPPPPPPP/QRKBBNRN w KQkq - 0 1.
Geçerlilik aynı soruların genellemesi. Rok yine 2. bölümdeki dört koşula bakıyor:
hak duruyor mu, yol boş mu, şah şu an tehdit altında mı, geçeceği ve varacağı kareler tehdit
altında mı. Standart kod bunları sabit karelerle soruyor (g kanadında f1 ve g1); 960'ta aynı
sorular şahın ve kalenin başlangıç dikeylerine bağlı aralıklarla soruluyor. Rok yalnızca şahın ve
iki kalenin dikeylerine baktığı için 960 diziliş rok açısından 56 iskelete iniyor; hepsi
Chess960_Skeletons simülasyonunda.
dijitalde karşılığı yok Masa başında aynı belirsizliği kitap el sırasıyla gideriyor: önce şah tahtanın dışına,
varış karesinin yanına alınıyor, sonra kale yerine konuyor, en son şah bırakılıyor; yanlış
anlaşılmasın diye önceden “rok yapıyorum” demek de öneriliyor. Dijitalde bu işi hamlenin
kodlanışı görüyor.
FideLite'ta. Sürümler standart satrancı oynuyor; 960 Özel
sürümler'deki LUX'ta var: başlarken 960 kutusu işaretlenip diziliş numarası (0–959)
yazılıyor ya da zarla rastgele seçiliyor. Bölümün başında gösterilen dizilişi
(777, QRKBBNRN) açmak için 777 yazmak yeterli.
Uygulama maliyeti. Diziliş kural değil veri: oyunu başka bir FEN'le başlatmak kadar ucuz. Maliyetin tamamı rokta: sabit karelerle yazılmış kontroller dikeylere bağlı aralıklara genelleşiyor, hamle kodlaması şahın kendi kalesini alması biçimine geçiyor ve rok hakkı kaleyi dikeyiyle tanıyor. Hamle üretimi, şah kontrolü, mat, pat, tekrar ve sayaçlar olduğu gibi kalıyor.
Zaman türleri ve çoklu periyot — 6 · Ek A · Ek B · Kılavuz III
Kitap ne diyor
Kitap süreyi sabitlemiyor: nasıl dağıtılacağını tanımlıyor, sayıları önceden ilan
edilecek turnuva yönetmeliğine bırakıyor (6.3.1). Bir zaman kontrolünü üç şey belirliyor:
süre, hamle başına ek süre ve sürenin kaç periyoda bölündüğü.
Üç tür. Türü tek bir hesap veriyor: süre + 60 × ek süre, yani oyun 60 hamle
sürecekmiş gibi. Sonuç 10 dakika ya da daha azsa yıldırım (Ek B), 60 dakikadan azsa
hızlı (Ek A), en az 60 dakikaysa klasik; kitabın deyişiyle standart satranç.
“3 + 2”, 3 dakika ve hamle başına 2 saniye demek.
Kontrol
Süre + 60 × artış
Tür
3 + 2
3 + 2 = 5 dk
Yıldırım
5 + 5
5 + 5 = 10 dk
Yıldırımsınır dahil
10 + 1
10 + 1 = 11 dk
Hızlı
10 + 5
10 + 5 = 15 dk
HızlıFideLite
90 + 30
90 + 30 = 120 dk
Klasik
Ani ölüm. Bütün hamleler sabit bir sürede; ek süre yok.
Fischer — birikimli. Her hamleden önce sabit bir ek süre ekleniyor, kullanılmayanı birikiyor. Kitap ek süreyi bu kipte de gecikmede de 2 ile 60 saniye arasında tanımlıyor ve ilk hamleden itibaren veriyor.
Gecikme — 6.3.2. Ek süre birikmiyor: ana süre, ek süre dolana kadar hiç akmıyor. Saat üreticileri buna basit gecikme diyor. Sözlük aynı kipe Bronstein adını da veriyor, ama Bronstein'da süre baştan akıyor ve harcananın yalnızca ek süre kadarı geri veriliyor; fark aşağıda.
Çoklu periyot — 6.3.1 · 6.3.2 · 6.4. Süre periyotlara bölünebiliyor: her periyotta belirli sayıda hamle, sonuncusunda kalan bütün hamleler yapılmalı; harcanmayan süre sonrakine geçiyor. Sözlükteki örnek, yaygın klasik kontrolün kendisi: 40 hamleye 90 dakika, kalanına 30 dakika, ilk hamleden itibaren hamle başına 30 saniye. Masa başında periyot sonunda düşen bayrak, hamle şartı yerine gelmişse kayıp sayılmıyor (6.4); dijitalde süre, hamle numarası sınıra geldiği an eklendiği için düşen bayrak her zaman gerçek bir aşım.
Hızlı bitişte beraberlik talebi — Kılavuz III.dijitalde karşılığı yok Artışsız oyunun son iki dakikasında oyuncu, rakibinin normal yollarla kazanamayacağını öne sürüp beraberlik isteyebiliyor; karar hakemin takdirinde ve bir algoritma bunu veremez. Dijitalde yerini artış ve 6.9'un nesnel ölçüsü alıyor (6. bölüm).
Algoritmada nasıl okunur
Saat küçük bir durum makinesi. Her taraf için kalan süre, kip, periyot tablosu ve
hamle numarası yetiyor. Saate her basışta harcanan süre düşülüyor, kipin eki uygulanıyor,
hamle numarası periyodun sınırına geldiyse sonraki periyodun süresi ekleniyor.
Artış hamleden önce mi, sonra mı? Kitap önce veriyor; birçok çevrimiçi saat
hamleden sonra ekliyor. Hamle bitince kalan süre aynı, fark hamlenin içinde: önce verilen
artış o hamlede de harcanabiliyor, yani her hamlenin içinde bayrak bir artış kadar geç düşüyor.
Bronstein ve basit gecikme. 6.3.2'nin tarif ettiği davranış, saat üreticilerinin
basit gecikme dediği kip: ana süre gecikme boyunca donuk. Bronstein'da süre baştan
akıyor, hamleden sonra harcananın gecikme kadarı iade ediliyor. Çoğu hamlede sonuç aynı; fark
zaman sıkıntısında çıkıyor: basit gecikmede oyuncu o hamlede ana süresine ek olarak
gecikmenin tamamını kullanabiliyor, Bronstein'da bayrak ana süre bittiği an düşüyor. Sözlük
iki adı tek kip için kullandığından, ikisini ayrı sunan bir yazılım farkı kendisi tanımlamak
zorunda.
FideLite'ta. Oyun 10 + 5 oynanıyor: Fischer kipinde, tek periyot. Kitabın
hesabıyla 15 dakika, yani hızlı oyun. Özel sürümler'deki LUX'ta bu
kontrol başlarken değiştirilebiliyor: Fischer, Bronstein ya da basit gecikme; beyaza ve
siyaha ayrı süre; iki ek periyot.
Uygulama maliyeti. Saat kural motorunun dışında: birkaç sayı ve saate her basışta birkaç toplama. Zamanın pahalı sorusu tek: bayrak düştüğünde rakip mat edebilir mi (6.9). Tür ve kip ne olursa olsun bayrak düşmesi bu soruya varıyor.
Beraberlik teklifi periyodu — 9.1.1 · 11.5
Kitap ne diyor
Hamle eşiği — 9.1.1. Turnuva yönetmeliği, hakemin onayı olmadan beraberlik teklif etmeyi ve kabul etmeyi belli bir hamleden önce ya da oyunun tamamında yasaklayabiliyor; sık görülen eşikler 30 ve 40. hamle. Yasak yalnızca teklif ve anlaşmayı kapsıyor: 50 hamle ya da üçlü tekrar talebi doğruysa oyunu yine bitiriyor.
Rahatsız edici teklif — 11.5.dijitalde kurala dönüşür Kitap teklifin sıklığına bir sayı koymuyor; rakibi rahatsız eden teklif 11.5'e giriyor ve hüküm hakemin takdiri. Dijitalde bu takdiri verecek hakem yok, o yüzden sınır önceden kurala yazılmak zorunda.
Algoritmada nasıl okunur
Teklif, talep gibi sessiz değil. Doğru bir talebi sistem kendisi doğrulayıp sonuçlandırıyor;
rakipten bir şey istenmiyor, ona yalnızca tutmayan talep ulaşıyor, o da teklif olarak. Teklif ise ancak rakip görüp karar verirse sonuç doğuruyor: her
teklif rakibin ekranına düşen bir sinyal, tekrarlandığında da düşünmesini bölen bir araç
(7. bölüm). Platformlar ve uygulamalar bu yüzden teklifi kendi
kurallarıyla sınırlayabiliyor: belli bir hamleden önce kapalı tutmak, bir periyoda bağlamak
(örneğin on hamlede bir) ya da reddedilen tekliften sonra bir süre yenisini almamak gibi.
Hepsi hamle numarasına bakan birer karşılaştırma; tutmayan bir talepten doğan teklif de aynı denetimden geçiyor.
FideLite'ta. Bununla ilgili bir sınırlama yok. Kural daha çok oyunu yürüten
tarafı, yani organizatörü ya da platformu ilgilendiriyor; kitabın değil yönetmeliğin seçeneği
olduğu için karar ona bırakıldı. Bazı özel sürümlerde bu şekilde ayarlanabiliyor.
Armageddon — dengeyi bozan oyun
Üst düzeyde satranç beraberliğe doğru akıyor: iki taraf da hata yapmadıkça sonuç çoğu
zaman eşitlikte kalıyor. Armageddon bu dengeyi kesin olarak alt üst etmek için var; bir
sonuç çıkması gereken yerde oynanan son oyun. Süper GM düzeyinde beyazla başlamak zaten
başlı başına ciddi bir avantajken, kısa bir yıldırım oyununda beyaza bir de kayda değer
farkla daha fazla süre veriliyor. Karşılığında siyah beraberliği galibiyet sayıyor. Beyaz
kazanmak zorunda; siyahın kaybetmemesi yetiyor.
Yönetmelik ne diyor
Armageddon kitapta yok; eşitlik bozma ve turnuva yönetmeliklerinde tanımlanıyor. Genel
biçimi:
Beraberlik siyahın. Berabere biten her oyun siyahın galibiyeti sayılıyor: pat, ölü pozisyon, tekrar, 50 hamle.
Süre farkı yönetmelikte yazılı. En yaygın biçim 5'e 4 dakika ve iki periyot: ikinci periyot 61. hamlede başlıyor, süre eklemiyor, yalnızca küçük bir artışı devreye sokuyor. Örneğin 2021 Dünya Kupası'nda beyaz 5, siyah 4 dakika aldı ve 61. hamleden itibaren hamle başına 2 saniye eklendi. Norveç Satrancı'nda beyaz 10, siyah 7 dakikayla oynuyor; 41. hamleden itibaren ikisine de hamle başına 1 saniye ekleniyor.
Renk kurayla ya da kapalı artırmayla. Genelde kurayı kazanan rengini seçiyor; kapalı artırma usulünde siyahla oynamak için en az süreye razı olan siyahı alıyor.
Algoritmada nasıl okunur
Beyaz neyle kazanır. Mat, siyahın terki ya da siyahın bayrağıyla. Son ikisinde 5.1.2 ve 6.9 aynen işliyor: beyaz hiçbir geçerli hamle dizisiyle mat edemiyorsa sonuç beraberlik, yani yine siyah.
Talep formaliteye dönüşüyor. 50 hamle ve üçlü tekrar normalde talep ister. Armageddon'da siyahın talep etmemek için, beyazın talep etmek için hiçbir sebebi yok; eşik dolduğu an siyah her durumda talep edebiliyor (9.2, 9.3). Dijitalde oyunu 50. hamlede ve üçüncü tekrarda kendiliğinden bitirmek bu yüzden kuralı değiştirmiyor, aynı sonucu hakemi beklemeden veriyor.
Tek taraflı ölü pozisyon. 5.2.2 iki tarafa birden bakar: kimse mat edemiyorsa oyun biter. Armageddon'da yalnızca beyaza bakmak yetiyor. Beyaz artık hiçbir geçerli hamle dizisiyle mat edemiyorsa oyun nasıl biterse bitsin sonuç siyahın: siyah mat eder, beyazın bayrağı düşer ya da siyahın bayrağı düşer ve 6.9 bunu beraberliğe çevirir. Bunu söyleyen yazılı bir kural yok; ama sonuç artık değişmediği için bir hakem algoritması oyunu burada bitirebilir. Gereken arama da zaten var: 6.9'un “rakip mat edebilir mi” sorusu, bu kez her hamlede beyaz için soruluyor.
Uygulama maliyeti. Sonuç kuralı tek satır: beraberlik siyaha yazılıyor. Tek taraflı ölü pozisyon ise 6.9 aramasını bayrağı beklemeden, hafif taşlı pozisyonlarda her hamleden sonra arka planda çalıştırmak demek.
FideLite'ta. Ana sürümlerde armageddon yok; LUX'ta başlangıç
penceresindeki Armageddon kutusuyla açılıyor ve buradaki üç maddenin üçünü de uyguluyor.
Her şey otomatik: talepler kendiliğinden işliyor, beraberlik teklifi yok ve her hamleden sonra,
yalnızca armageddona özgü tek yönlü bir arama beyazın hâlâ mat edip edemeyeceğine bakıyor;
edemiyorsa oyun o anda siyahın galibiyetiyle bitiyor. Oyun yalnızca kanıtla erken bitiyor:
materyal testi, kilit dedektörü ya da bütün yolları matsız kapatan bir arama. Aramanın bütçesi
dolarsa oyun sürüyor. Aşağıdaki Soćko–Foişor pozisyonunda oyun bitmiyor:
siyahın atı kendi şahının kaçış karesini kapatabildiği için mat hâlâ kurulabiliyor. Siyahın
bayrağı düştüğünde hakem aynı ölçüte bakıyor (6.9) ve oyunu beyaza veriyor, tıpkı itiraz
kurulu gibi.
Gerçek bir örnek: Soćko–Foişor, 2008
♚♞♞♚
Kurulabilen mat: siyahın atı b8'de kendi şahının kaçış karesini kapatıyor, c7'deki at şah çekiyor, a7 ile b7'yi beyaz şah tutuyor.
2008 Kadınlar Dünya Şampiyonası'nın ilk turunda Monika Soćko (beyaz) ile Sabina-Francesca
Foişor (siyah) arasındaki armageddon oyununda tahtada yalnızca iki şah ve iki at kaldı; piyon
yoktu. Foişor'un süresi bitti. Hakem, bu malzemeyle mat kurulamayacağı gerekçesiyle o dönemin 9.6
maddesine dayanarak beraberlik verdi; armageddonda bu, siyahın turu geçmesi demekti. Soćko itiraz
etti ve itiraz kurulu kararı bozup oyunu beyaza verdi: at ve şah karşı at ve şahta mat
kurulabiliyor. Ölçüt matın zorlanabilmesi değil, herhangi bir geçerli hamle dizisiyle ortaya
çıkabilmesi (6. bölüm). Kaynaklar: ChessBase ↗️,
Chess Express ↗️.
Varyantlar
Üç eksen: arayüz, kural ve bot
Sürümler üç ayrı eksende çoğalıyor; bu eksenleri birbirine karıştırmamak gerekiyor.
Arayüz ekseni. Kurallar aynı, arayüz değişiyor: tıklanan tahta, onun yalın hâli, sayısal giriş,
UCI, körleme, harf dizgisi, terminal. Bir seviyenin bütün arayüzleri aynı hükmü verir; değişen
yalnızca hamlenin nasıl girildiği, tahtanın nasıl çizildiği ve saatin nasıl gösterildiği. Bu eksen
her seviyede var: L3'te on bir, L2 ve L1'de yedişer arayüz.
Kural ekseni. Arayüz aynı, kural katmanı değişiyor: L3 → L2 → L1.
L3 tam hakem ve iddianın kendisi. L2 ile L1iddianın dışında,
ama kuralların katman katman nasıl eklendiğini görmenin en açık yolu onlar.
Bot ekseni. Burada ne arayüz ne de kurallar ölçülüyor; ölçülen, kuralların üstüne kurulan arama:
L1_1250elo, L2_aybars_2500 ve Toledo'nun programına rakip olarak yazılan
ToledosOpponentbyMe. Bu sürümler özel sürümlerin içinde, ayrı bir grup olarak duruyor.
Hiçbir eksene sığmayanlar özel sürümler arasında. L3'ün
paketli hâli ise eskimiş yöntemler kullansa da iddianın parçası.
L3 ailesi — tam hakem
On bir sürüm, aynı kurallar. Hepsi şunları eksiksiz uyguluyor: rok, geçerken alma, terfide taş
seçimi, pat, ölü pozisyon, üçlü ve beşli tekrar, 50 ve 75 hamle kuralları, anlaşmalı beraberlik,
beraberlik talebi, bayrak düşmesi ve terk. Hepsi aynı
on beş sonuç kodunu üretiyor.
Sürüm
Giriş
Tahta
Saat
Süre
Baytdüz kaynak
default builds/L3.html
Tıklama
Tam tahta, taş glifleri
⏱ D:SScanlı
600 sn + 5 sn
2.744
light builds/L3_light.html
Tıklama
Tam tahta, taş glifleridurum satırı, koordinatlar, şah uyarısı ve hedef kare işaretleri yok; sonuç tahtadan ve saatlerden okunuyor
L3_packed (2.423 bayt), default'un RegPack ile paketlenmiş
hâli; iddianın asıl parçalarından biri ve seviye olarak L3'le eş. Buradaki
paketleme bir sıkıştırma değil, kendini açan bir sarmalayıcı: dosya açılınca asıl kaynak yeniden
kuruluyor ve oyun eşdeğer biçimde çalışıyor. Ama bunu eskimiş yöntemlerle yapıyor: with
katı kipte (strict mode) sözdizimi hatası veriyor, eval ise unsafe-eval
izni vermeyen bir içerik güvenlik politikası altında engelleniyor. Tarayıcılar uyarı veriyor; bazı
kiplerde dosya hiç açılmıyor. İçinde görünmez kontrol karakterleri de bulunduğu için
kopyala-yapıştırla taşınan bir kod olarak değil, paylaşılan bir dosya olarak işe yarıyor.
Oynatıcıda L3_packed, L3 seviyesinin son sürümü
olarak açılıyor. Kalan sürümler düz kaynak olarak duruyor, çünkü paketleme
her sürümde kazanç sağlamıyor.
Aynı satırı paylaşan üç sürümün farkı ekranda değil, içeride.prompt_string_a1 ile
prompt_string tahtayı sayı dizisi olarak değil harf dizgisi olarak
tutuyor: boş kare '-', taşlar RNBQKP ve bunların küçük harfli karşılıkları;
renk sınaması da bir harf karşılaştırması. Aynı kuralları uyguluyorlar, ama
tahta temsili farklı. Yani bunlar arayüz varyantları değil, mimari
varyantları ve elenen mimarilerin çalışan örnekleri.
prompt_string bir adım daha ileri gidiyor: indeks sırasını ters çeviriyor, yani
0 indeksi a1'e değil a8'e karşılık geliyor. Piyonun yön sınaması f<i yerine f>i oluyor
ve kodun geri kalanı hiç değişmiyor. Bu da oyun yönünün tek bir karşılaştırmaya bağlı olduğunu gösteriyor.
Saat taşıyan bütün sürümler süreyi aynı biçimde ölçüyor; fark yalnızca gösterimde. default ve
light kalan süreyi yukarı yuvarlıyor, bu yüzden 0:00 ancak bayrak
düştüğünde görünüyor. Saniye gösteren öbür sürümler ise aşağı kırpıyor, yani ekrandaki değer gerçek
süreden bir saniyeye kadar düşük olabiliyor. Dolayısıyla tıklamalı sürümler ekranda biraz daha hassas,
ama kural doğruluğu etkilenmiyor: bayrak her sürümde yuvarlanmamış süreye göre düşüyor ve ekrandaki
değerle gerçek süre arasındaki fark bir saniyenin altında kalıyor.
Sonuç kodları
On beş sonuç
Oyunun bitebileceği on beş yol var: altısı galibiyetle, dokuzu beraberlikle sonuçlanıyor. Motorun iki
kodlaması var ve ikisi de aynı on beş sonucu adlandırıyor: numerical sürümleri ile engine.js bir tam sayı döndürüyor, geri kalan bütün sürümler ise iki harfli bir kısaltma. Aşağıdaki her hücrede ikisi birlikte gösteriliyor.
Bu da benim periyodik tablom :) Tıpkı elementler gibi, her kodun bulunduğu satıra ve
sütuna göre, bazen de hangi harfle başlayıp bittiğine göre bir anlamı ve ötekilerle bir
ilişkisi var!
W#1
Beyaz mat etti
B#2
Siyah mat etti
SM3
Pat
WT10
Beyaz süreden kazandı
BT11
Siyah süreden kazandı
TM14
Süre bitti, mat imkânsız
WR12
Beyaz, rakibin terkiyle kazandı
BR13
Siyah, rakibin terkiyle kazandı
RM15
Terk edildi, mat imkânsız
754
75 hamle kuralı
5R5
Beşli tekrar
DP6
Ölü pozisyon
507
50 hamle talebi
3R8
Üçlü tekrar talebi
DA9
Anlaşmalı beraberlik
Beyaz kazanırSiyah kazanırBerabere
Kısaltmalar kendilerini açıklıyor: W ile başlayanlarda beyaz,
B ile başlayanlarda siyah kazanmıştır. D ile başlayanlar,
M ile bitenler ve içinde rakam geçenler beraberedir. On beş kodun
on beşi de bu kalıplara uyuyor.
Satırlar da anlamlı: birinci satır mat ve pat, ikincisi süre bitimi, üçüncüsü
terk ile ilgili. Dördüncü satırda kendiliğinden tetiklenen beraberlikler, beşincisinde ise
oyuncunun istemesiyle gelenler yer alıyor. Satırların nereden geldiği ve aralarındaki öncelik sırası
bitişlerin boru hattında anlatılıyor.
Bir kodun adlandırılması ayrıca anlatılmaya değer. DP, özel bir durumun değil,
maddenin kendi adını taşıyor: 5.2.2 ölü pozisyonun hem yetersiz materyalden doğan hâlini hem de
kilitli pozisyonları kapsıyor ve motor da ikisini birden bildiriyor (kilitli tarafta ölçülen
kapsama %93). Kodu dar olan yarıya göre adlandırmak kolay yoldu; maddeye göre adlandırmak ise
kapıyı açık bıraktı: kilitli pozisyonlar eklendiğinde gerçekten aynı kodla
bildirilmeye başlandı ve liste on beşte kaldı.
Bu on beş kod FIDE kurallarına göre; başka bir federasyonun kuralları başka bir liste
üretir. Örneğin USCF'de RM bitişi yoktur, 75 ve 5R kendiliğinden işlemez (turnuva yöneticisi isterse ilan eder), TM ise oldukça farklı çalışır. Motoru USCF'ye uyarlamak
mümkündür.
Oynanış
L3 sürümleri nasıl oynanır
Bu bölüm L3 ailesinin nasıl oynandığını anlatıyor. L1 ve
L2'de oynanış değişmiyor, yalnızca yalınlaşıyor; birkaç küçük fark seviyeler bölümünde anlatılıyor. Oynanışın
gerçekten değiştiği tek yer özel sürümler; onlar kendi bölümlerinde ele alınıyor.
Hamle nasıl girilir
Tıklama — default ve light
Her şey tıklanarak yapılır: önce taşa, sonra varış karesine basarsın. Piyon terfi karesine ulaştığında bir seçim paneli açılır; taşı seçene kadar hamle tamamlanmaz ve saat bu
sırada işlemeye devam eder.
Beraberlik radyo düğmesi hamleden bağımsız çalışır. Rakip beraberlik teklif etmişse
düğmeyi seçmek teklifi kabul eder ve oyun anlaşmayla biter; tahtada bir talep eşiği dolmuş
olsa da sonuç anlaşmadır, çünkü her talep aynı zamanda bir tekliftir. Bekleyen teklif yoksa ve
tahtada bir talep eşiği zaten dolmuşsa düğmeyi seçmek beraberliği hemen tetikler. İkisi de
yoksa düğme seçili kalır ve hamleni oynadığında değerlendirilir: hamle eşiği dolduruyorsa
talep, doldurmuyorsa rakibe teklif sayılır. Hamleni oynamak rakibin bekleyen teklifini de
reddeder.
UCI — prompt, input ve nonbrowser aileleri
Sekiz sürüm aynı biçimi kullanıyor: üç prompt sürümü (prompt, prompt_string_a1,
prompt_string), prompt_blindfold, input,
input_blindfold, nonbrowser ve nonbrowser_blindfold.
Biçim şöyle: <başlangıç><varış><terfi><beraberlik>.
Büyük-küçük harf ayrımı yapılmaz: E2E4, e2e4 ve e2E4
aynı hamledir.
Terfi harfi r, b ya da n. Bu üçü dışındaki
her harf (q dahil) ya da harf olmaması vezir demektir, yani
e7e8 yeter. Rengi motor kendisi belirliyor; sen yalnızca taşın türünü seçiyorsun.
Rok, şahın gideceği kareyle yazılır: beyaz için e1g1 ve
e1c1, siyah için e8g8 ve e8c8. Şahın kendi kalesini alıyormuş gibi yazıldığı bir biçim yok. Geçerken alma ise piyonun gerçekten varacağı kareyle yazılır, o
yüzden ayrı bir yazım kuralı gerektirmez.
Beraberlik, hamlenin sonuna eklenen = ile istenir. b2b3= şu anlama gelir:
bu hamleyi oynadığımda eşik dolacaksa talep ediyorum, dolmayacaksa rakibe teklif
ediyorum. Motor kararını hamle oynandıktan sonraki duruma bakarak verir; hamle, sayacı
sıfırlıyorsa ya da daha önce görülmemiş bir pozisyon getiriyorsa talep gerçekleşmez ve istek
teklif olarak kalır. Hamleli bir istek rakibin bekleyen teklifini kabul etmez: hamle oynandığı
anda o teklif düşer. Tek başına = ise bekleyen bir teklif varsa onu kabul eder;
yoksa mevcut pozisyonda beraberlik talep eder, talep tutmazsa teklif olur.
Terk etmek için r yazılır. prompt'ta pencereyi iptal etmek de terk sayılır; boş satırda Enter'a basmak ise sayılmaz: bu geçersiz bir hamledir ve saat işlemeye devam eder. nonbrowser'da girdiyi kapatmak (Unix'te Ctrl+D) terk sayılır; boş Enter orada da geçersiz bir hamledir.
e7e8=N girişinde = beşinci karakter, yani terfi
yuvasında; r, b ya da n olmadığı için sonuç vezir olur. Beraberlik eki
dizginin sonunda aranır; orada N durduğu için beraberlik de istenmiş
olmaz. SAN alışkanlığı burada iki yönden birden ters teper. Ata terfi etmek için
e7e8n, ata terfi edip beraberlik istemek için e7e8n= yazılır.
Bir hamle mat ediyorsa sonundaki = yok sayılır. Mat, bütün otomatik beraberlik koşullarının önüne geçtiği gibi talebin de önüne geçer.
Sayısal — numerical
Dizilim aynı, ama kareler iki basamaklı sayılarla yazılır: ilk basamak yatay, ikincisi
sütun, ikisi de 0'dan 7'ye kadar. Bu, motorun kendi indeksinin onaltılık
yazılışı: a1 = 00, h8 = 77. Sürücü
yalnızca ilk basamağı dört bit kaydırıp ikinciyle birleştiriyor; başka bir dönüşüm yok.
Sayısal girişte her karenin kodu: ilk basamak yatay, ikinci basamak sütun. e2e4 = 1434.
Terfi taşı bir rakamla seçilir: 0 fil, 1 kale, 2 at;
3 ve geri kalan her şey vezir. Harf yok; bu sürümde hiçbir yerde harf kullanılmıyor.
Rok yine şahın kareleriyle yazılır: beyaz için 0406 ve 0402, siyah için
7476 ve 7472.
Beraberliğin ayrı bir düğmesi yok; istek girdinin kendisiyle iletiliyor.
Sayısal olmayan her girdi bir beraberlik isteğidir. Rakibin bekleyen bir teklifi
varsa hamlesiz bir istek onu kabul eder. Yoksa istek o anda geçerli bir talebe karşılık
geliyorsa (üçlü tekrar ya da 50 hamle kuralı) oyun orada biter; gelmiyorsa istek rakibe teklif
olarak gider. Ortaya dört durum çıkıyor:
Hamleyle birlikte istek.6777asd ya da 6777? — eklenen karakterlerin ne olduğu fark etmez; hamle normal biçimde oynanır ve isteği de beraberinde taşır. Hamle rakibin bekleyen teklifini düşürür; istek, hamleden sonraki pozisyon için bir talep, talep tutmazsa yeni bir teklif olur. Hamle mat ederse istek hiç işlenmez: mat oyunu o anda bitirir.
Hamlesiz istek.asd, hatta tek karakterlik g —
bekleyen teklif yoksa o anki pozisyon için doğrudan bir talep; talep geçerli değilse
teklif.
Bekleyen teklifin kabulü. Rakip de sayısal olmayan bir şey yazar, ama hamle
yapmadan; karakterlerin ne olduğu fark etmiyor: dssdf teklifi hemen kabul ediyor.
İki teklif biti birden yandığı anda oyun karşılıklı anlaşmayla biter; o sırada bir talep eşiği
dolmuş olsa da sonuç anlaşmadır, çünkü her talep aynı zamanda bir tekliftir.
1434x gibi hamleli bir girdi ise teklifi kabul etmez: hamle oynandığı anda teklif
düşer.
Sayısal ama geçersiz girdi.6500 — ne talep ne teklif; hamle
reddedilir ve aynı soru yeniden sorulur.
İsteğin hamleye iliştirilebilmesi keyfi bir kolaylık değil, FIDE kurallarının gereği.
“Sayaç 99 yarım hamlede ama bu hamleyi oynarsam 100 olacak” ya da “pozisyon iki kez
görüldü, bu hamle üçüncüsünü getirecek” durumlarında talep, oynanacak hamle
üzerinden yapılır. İsteğin hamleyle birlikte gidebilmesinin sebebi bu; bu davranış 9.2 ve
9.3. maddelere birebir uyuyor.
Dördüncü durumun bir yan faydası var. Bu sürümde saat canlı değil, yalnızca
prompt() penceresinin başlığında duruyor. Sayısal ama geçersiz bir girdi
hiçbir şey tetiklemeden pencereyi yeniden açtığı için, kalan süreyi görmenin tek yolu
da bu.
Terkin kendi kanalı var: İptal, Esc, boş Enter ve sekmeden çıkmak terk sayılır.
0, 00 ve boşluk ise terk sayılmaz: üçü de sayısal olarak sıfır, yani
yalnızca geçersiz bir hamle. r yazmak da terk sayılmaz; harf içerdiği için beraberlik
kanalına düşer.
Bu arayüzün kullanışsızlığı kasıtlı: amaç, iyi niyetli girdiyle öbür sürümlerin yaptığı her şeyi en az baytla yapabilmek. Bu, motoru bozmadan oyunun oynanabildiği en alt basamak.
Terfi, terk ve beraberlik
Hamle dışındaki her eylem sürümden sürüme farklı yapılıyor. default ve light'ta her birinin ayrı bir arayüz ögesi var; öbür sürümlerde hepsi hamlenin girildiği kanaldan geçiyor.
Sürüm
Terfi
Terk
Beraberlik
default · light
Seçim paneli açılır; taş seçilene kadar hamle tamamlanmaz
⚐ düğmesi
Beraberlik radyo düğmesi
UCI ailesi
Hamlenin 5. karakteri: r b n, diğer her şey vezir
r; prompt'ta ayrıca İptal, nonbrowser'da girdiyi kapatmak
Hamlenin sonuna =; tek başına = kabul ya da talep
numerical
Hamlenin 5. karakteri: 0 fil, 1 kale, 2 at, diğer her şey vezir
İptal, Esc, boş Enter, sekmeden çıkmak
Sayısal olmayan karakter içeren her girdi
Göstergeler
Motor hiçbir sürümde oyunun durumunu kendisi anlatmaz; ne gösterileceği tamamen sürücünün
tercihidir ve bayt bütçesi büyüdükçe göstergeler de çoğalır. Altı sürüm durumu yazıyla bildiriyor: default, input, nonbrowser ve üç prompt sürümü. light yalnızca saatleri ve beraberlik radyo düğmesini gösteriyor; üç körleme sürüm ile numerical ise neredeyse hiçbir şey söylemiyor.
W:600s B:600ssabit sıra · input'ta canlı, nonbrowser'da hamlede
[e2e4]
prompt prompt_string_a1 prompt_string
a8 / h1yalnızca köşe karesi
Var
Var
Var
W: 600shamlede · sıra alttaki tarafta
—
prompt_blindfold
—
—
—
—
W:600s < B:600shamlede · sabit sıra, ok sıranın kimde olduğunu gösterir
[e2e4]
numerical
—
—
—
—
900000 900000hamlede · ham ms, etiketsiz
Ham girdi
Gösterge taşıyan sürümlerde alanlar hep aynı sırayla dizilir. default'un durum satırı a8-h8 C! M=0 R=1 biçimindedir; input, nonbrowser ve prompt sürümleri aynı alanları tahtanın üstüne yazar.
M= yarım hamle sayacı — FEN ve motor dilindeki adıyla halfmove clock. Bir taş alındığında ya da bir piyon oynandığında
sıfırlanır. M=100'de 50 hamle beraberliği talep
edilebilir, M=150'de ise beraberlik talep beklenmeden ilan edilir. Pratikte
şöyle okunur: ekranda M=99 görüyorsan, oynayacağın hamle taş almıyor ve piyon hamlesi değilse eşik onunla dolar. UCI'deki b2b3= tam olarak bunun için var.
R= tekrar sayısı. Mevcut pozisyonun kaçıncı kez görüldüğünü gösterir.
R=3 üçlü tekrar talebini açar, R=5 ise otomatik beraberliktir.
Bu bir geri sayım değil, her hamleden sonra yeniden hesaplanan bir değer: yeni bir pozisyon ortaya çıktığında R=1'e düşer.
İki sayının da iki eşiği var: biri talebi açıyor, öbürü oyunu kendiliğinden bitiriyor.
Talebin olmadığı seviyelerde yalnızca ikincisi işliyor; ayrıntılar
seviyeler bölümünde.
C! sırası gelen tarafın şah altında olduğunu söyler; yani sıra sendeyken her zaman kendi şahını gösterir. D? ise açık bir beraberlik
teklifi olduğunu gösterir. input, nonbrowser ve prompt sürümlerinde ikisi aynı yuvayı
paylaşır ve D? öncelik kazanır; default ise onu hiç yazmaz.
Oyun bittiğinde default durum satırının tamamını sonuç koduyla değiştirir;
input ile nonbrowser ise sonucu yalnızca bu yuvaya yazar, M= ve
R= yerinde kalır. Körleme sürümlerden input_blindfold ile
nonbrowser_blindfold sonucu tek satırlarının sonuna ekler; prompt sürümleri,
prompt_blindfold ve numerical ise sonucu bir alert()
penceresinde gösterir.
D?, teklif bitlerinin sıfırdan farklı olduğunu gösterir; teklifi kimin yaptığını
söylemez. Normal akışta gördüğün teklif rakibinkidir, ama hamlesiz
= yazarsan kendi teklifini de D? olarak görürsün.
Ayrımı yalnızca default yapar ve orada tek işaret de budur: beraberlik radyo
düğmesi yalnızca rakibin açık bir teklifi varken sarı çerçeve alır.
Göstergesiz sürümlerde ne yapmalı? Dördünde de şahı, sayaçları ve tekrarı takip etmek
tamamen sana kalmış; motor hiçbir şey hatırlatmaz. Geriye kalan kanallar son hamle kaydı ile saat; ikisi de sandığından fazla bilgi taşır.
Körleme sürümlerde beraberlik eki kayda giriyor: rakip e7e5=
oynadıysa köşeli parantez içinde [e7e5=] görürsün; teklif mi yoksa talep mi ettiğini oradan okuyabilirsin. Terfi için de aynısı geçerli: e7e8n kayda [e7e8N]
olarak düşer; böylece rakibin gerçekten neye terfi ettiğini tahtayı kafanda tutmadan
doğrulayabilirsin. Sırayı input_blindfold'da işleyen saatten, prompt_blindfold'da iki saatin arasındaki oktan okursun; nonbrowser_blindfold'da ise saatler yalnızca hamleden sonra güncellendiği için sırayı en son azalan saat söyler.
numerical'da kayıt ham girdinin kendisi, o yüzden rakibin beraberlik
isteği oradaki sayısal olmayan karakterlerden okunur; bu karakterlerin ne olduğu fark etmez: 1434asd de 1434x de hem e2e4 hamlesi hem de beraberlik isteğidir. Hamlesiz istek iz bırakmaz. Saat, etiketsiz iki
milisaniye sayısından oluşur (soldaki beyazın, sağdaki siyahın); hangisinin azaldığı sırayı
söyler.
Tahtayı görmek istersen.L3'ün prompt() ile
oynanan sürümlerinde oyun bitince tahta gösterilmez, yalnızca bir sonuç bildirisi
(alert) çıkar. Son durum yine de bellekte: geliştirici konsolunda
“Motorun kendisi” sekmesinde anlatılan değişkenleri yazarak bakabilirsin.
z sonucu, b ise tahtayı kendi temsiliyle verir; t,
e, c, n, R ve o da aynı
şekilde okunur. Oynatıcıda sürüm bir çerçevenin içinde açıldığı için konsolda o çerçeveyi seç.
L1 ve L2'de buna gerek yok:
pencere hiç kapanmıyor ve sonuç aynı pencerenin içinde kalıyor.
light'ta sonuç nasıl okunur
light sonucu yazmıyor; ekranda zaten duran tahta, saatler ve beraberlik radyo düğmesi yetiyor.
Sonuç için fazladan tek bir işaret bile eklenmedi. Galibiyetle beraberlik saatten kesin olarak ayırt ediliyor. Hangi tür
galibiyet ya da beraberlik olduğunu ise oyunu takip ederek, art arda dizilmiş huniler gibi adım adım
eleyerek kendin çıkarırsın. Tahta ve saatler sırası gelen tarafa göre döndüğü için tablodaki “sıra”,
oyunun bittiği anda altta duran taraftır. Her saatin başında kendi renginin şahı durur (♔ beyaz, ♚ siyah);
alttaki saatin şahı sıranın kimde olduğunu söyler.
Saat
Ne oldu
Sıra beyazda
Sıra siyahta
0:00'da durdukayıp
Geçerli hamle yok, şah tehdit altında
B#
W#
⚐'ye basıldı
BR
WR
Süre bitti
BT
WT
Eksiye akıyorberaberlik
Geçerli hamle yok, şah tehdit altında değil
SM
⚐'ye basıldı
RM
Süre bitti
TM
İki saat de durduberaberlik
Radyo düğmesine basıldı
3R50DA
Hamleden sonra; son hamle geri alınamaz
DP
Hamleden sonra; son hamle geri alınabilir
5R75
Son iki satırda eleme şöyle ilerler. Son hamle taş aldıysa ya da piyon sürdüyse sonuç ölü pozisyondur
(DP): geri alınamaz bir hamleden hemen sonra ne tekrar ne de 75 hamle kuralı gerçekleşebilir. Son
hamle geri alınabilir bir hamle olsa bile, bir önceki hamlenin doğurduğu geçerken alma hakkını düşürdüyse sonuç
yine ölü pozisyon olabilir: iki kare ilerleyen piyon tahtayı kilitlemiş, kilit ancak geçerken alma hakkı düşünce
tanınmıştır. Son hamle bir rok hakkını düşürdüyse tekrar zinciri kırılmıştır; sonuç 75 hamle kuralıdır. Bunların
hiçbiri geçerli değilse sonuç 5R ya da 75'tir. Yani sonucu, ne olduğunu görerek değil,
neyin asla olamayacağını eleyip ihtimalleri daraltarak buluyoruz. Radyo düğmesiyle biten oyunlarda ise: sıra
sana geldiğinde düğme zaten seçiliyse sonuç anlaşmadır (DA), değilse taleptir (3R ya da
50).
3R ile 50'yi, 5R ile 75'i ayırmak her zaman mümkün değil:
her ikisi de birer sayaç eşiği ve aynı hamlede birlikte dolabiliyor. light'ın amacı açısından bu önemsiz;
önemli olan sonucun beraberlik olduğunu bilmek. Merak edersen tarayıcıda F12 ile konsolu açıp n (yarım hamle
sayacı: 100'de 50 hamle, 150'de 75 hamle) ve R (pozisyonun kaçıncı kez görüldüğü: 3'te üçlü, 5'te beşli
tekrar) değerlerine bakabilirsin; oynatıcıda sürüm bir çerçevenin içinde açıldığı için konsolda o çerçeveyi seç.
Mat ve patta saat anında sıfırlanır. Hamlesi kalmayan tarafın saati hemen 0'a iner;
bu taraf mat edildiyse saat 0:00'da durur, pat olduysa eksiye akar. Mat bütün beraberliklerin önüne geçer: son
hamle hem mat etse hem de 75. hamleyi doldursa bile sonuç mattır.
Beraberlik radyo düğmesi aynı zamanda teklif göstergesi. Sıra sana geldiğinde düğme seçiliyse
rakip beraberlik teklif etmiş demektir: düğmeye basarsan kabul edersin, hamle yaparsan reddetmiş
olursun. Kendi teklifini, hamleni yapmadan önce düğmeye bir kez daha basarak geri çekebilirsin.
Eksi süre0:-3 ya da -1:-14 biçiminde yazılıyor; saat
yukarı yuvarlandığı için bayrak düştükten sonra bir iki saniye daha 0:00
görünür, sonra eksiye geçer. M= ve R= sayaçları yok; onları
takip etmek, göstergesiz sürümlerde olduğu gibi oyuncuya kalıyor.
L1 ve L2'nin light sürümlerinde saat yok; orada tek bilgi kanalı tahta. Sırası gelen tarafın hamlesi
yoksa: şahı tehdit altındaysa mat, değilse pattır. L2'de hamle varken tahta donduysa sonuç tahtadan doğan bir
beraberliktir; hangisi olduğu gösterilmez.
Üç light sürümü de DOCTYPE ve <head> etiketleri olmadan
yazıldı; sayfa quirks modunda açılıyor. Stil yalnızca tahtaya verildi: kare rengi, kare boyutu
ve büyütme oranı (L1–L2'de üç kat, L3'te iki kat).
L3'teki stil etiketi ayrıca tahtayı ve yazıları ortalıyor, terfi düğmelerini de
büyütüyor. Düğmeler, radyo düğmesi ve yazılar tarayıcının varsayılan görünümünde kalıyor.
Seviyeler
L1 ve L2 — alt kural seviyeleri
Bunlar, kural ekseninde L3'ün altındaki iki basamak. Arayüzleri, L3'ün on bir arayüzünden
yedisi: tıklanan tahta, onun yalın hâli, sayısal giriş, UCI, körleme, harf dizgisi ve terminal.
Karşılaştırılan şey, kural katmanının nereye kadar götürüldüğü.
Seviyeler birbirinin üstüne kuruluyor. L1 yalnızca oynanabilirliği
biliyor: hamle geçerli mi, oyun matla ya da patla bitti mi? L2 bunun üstüne
tahtadan doğan beraberlikleri ekliyor. L3 ise tahtanın dışına çıkıyor:
saat, terk, teklif ve talep.
Kural / sürücü parçası
L1
L2
L3
Hamle üretimi ve şah güvenliği
✓
✓
✓
Rok, geçerken alma, terfide taş seçimi
✓
✓
✓
Mat · Pat
✓
✓
✓
Yarım hamle sayacı n
—
✓
✓
Tekrar sayımı R
—
✓
✓
Geçerken almanın geçerlilik sınaması
—
✓
✓
Rok haklarının bit maskesi c · C
—dokunulan kareler defteri
✓
✓
Ölü pozisyon — materyal ve kilitli
—
✓
✓
Beşli tekrar · 75 hamle
—
✓
✓
Saat ve bayrak düşmesi
—
—
✓
Terk
—
—
✓
Üçlü tekrar · 50 hamle talebi
—
—
✓
Anlaşmalı beraberlik
—
—
✓
6.9 araması — mat imkânsızken bayrak düşmesi ya da terk
—
—
✓
Ürettiği sonuç kodu sayısı
3
6
15
Durum satırı
koordinatlar, C!
+ M=R=
+ saat, D?
Tablodaki dört soluk satır, kuralların kendisi değil, onları taşıyan parçalar. Bunlar
L1'de eksik değil, gereksiz: tekrar sayılmıyorsa ne tabloyu tutmanın ne de
geçerken alma imkânının gerçekten geçerli olup olmadığını sınamanın bir anlamı var; o
sınamanın tek amacı tekrar sayımını doğru tutmak. Sayaç yoksa sınamaya da gerek kalmıyor. Rok hakları da aynı zincire bağlı: tekrar anahtarı olmayınca hakların
kanonik tutulmasına gerek kalmıyor. L1 hamlenin dokunduğu kareleri bir deftere işaretliyor; rok için
şahın ve kalenin karelerine hiç dokunulmamış olması gerekiyor. L1'in 1.404 baytta kalabilmesinin sebebi bu zincir.
Kod tarafında fark daha da keskin. L1'de dört fonksiyon var — G,
T, U, L — ve hüküm ayrı bir fonksiyon
bile değil, hamleyi işleyen ifadenin içine gömülü. L2 bunlara
C'yi, kilitli pozisyon dedektörünü ve satır içine gömülü bir materyal testini
ekliyor. Ayrı birer M, V, O, Z,
D, F, A fonksiyonu ve 6.9 araması H ise yalnızca
L3'te ortaya çıkıyor.
İki seviyenin sürümleri yan yana. Dosyalar builds/L1/ ve
builds/L2/ altında; son iki sütun her seviyedeki boyutu bayt olarak veriyor, böylece
her arayüz için kural katmanının bedeli kendi satırı boyunca okunabiliyor.
Aynı arayüzde L1'den L2'ye çıkmanın bedeli 486 ile 579 bayt arasında.
L1 kurallarının paketli bir uygulaması olan DOM_911, özel sürümler arasında; oyun bittiğinde tahta tek renge bürünüyor.
Oynanış
Oynanış L3'teki gibi; farklar kurallardan geliyor ve yukarıdaki
tablodan okunabiliyor. Oyuncu katmanı olmadığı için saat, terk, teklif ve talep yok; dolayısıyla
onların düğmeleri ve göstergeleri de yok. Hamle aynı biçimde giriliyor, terfi aynı biçimde
seçiliyor. Saat olmadığı için input_blindfold'da sıra, satırın başındaki
harften okunuyor: W ya da B. Kurallardan kaynaklanmayan tek fark, prompt() kullanan iki sürümde.
Pencere hiç kapanmıyor.numerical ve prompt_string'de İptal, Esc,
boş Enter ya da sekme değiştirmek pencereyi kapatsa bile döngü onu hemen yeniden açar. Oyun
bittiğinde de alert() açılmaz; sonuç aynı pencerenin içine yazılır:
prompt_string'de şah yuvasına, C!'nin yerine; numerical'da son
hamlenin yerine, sayı olarak. Pencere aynı ekranla açılmaya devam eder, girilen hamleler
işlenmez ve oyun ancak sekme kapatılınca sona erer. Bu bilinçli bir tercih. Aynı davranış L3'te de
istenmişti, ama orada saati bozuyordu: pencere kapanmadığı için saat donup kalıyordu; süreli bir
oyunda bu bir kural ihlali demek. Bu yüzden L3'te pencereyi kapatmak terk sayılıyor;
hiç kapanmayan pencere yalnızca saati olmayan L1 ve L2'de kaldı.
Özel sürümler
Merdivenin dışında
builds/special/ klasörü, merdivenin dışında, basamaklarının arasında ya da üstünde duran
dosyaları tutuyor. Dosyaların buraya düşmesinin üç sebebi var. Bazılarının arayüzü ya da kuralları
merdivenden başka bir yöne ayrılıyor (tavanı L3'ün üstüne taşıyan LUX, platform
eşdeğerleri, dedektörsüz ChessMiniature, 1 KB'nin altındaki chessinbytes). Bazıları arayüz ya da kural
yerine başka bir şeyi ölçüyor; bunlar bot ekseni. Kalanlar ise projenin genel
ilkelerine uymuyor: eskimiş yöntemlerle yazılmış, tarayıcıda uyarı ya da hata veren dosyalar. Çoğu
kendi başına bir proje: altı ayrı GitHub deposunun dosyaları burada da duruyor ve her biri kendi
deposuna bağlantı veriyor. Hiçbiri iddianın parçası değil; karşılaştırma ve kayıt için duruyorlar.
Oyun dosyalarının hepsi oynatıcıda special seviyesinden açılabiliyor; her deponun ayrıntıları kendi README dosyasında.
Chess LUX, FideLite'ın daha gevşek bir bayt bütçesiyle ve daha yüksek kural doğruluğuyla
yazılmış hâli. Motoru engine_4x; kuralları L3'le aynı, ama iki
noktada daha ileri gidiyor. Kilitli pozisyon dedektörü şahların, piyonların ve fillerin
yanında, hiç kıpırdayamayan gömülü at, kale ve vezirleri de hesaba katıyor; dedektör 432 bayttan
961 bayta çıkıyor. 6.9 araması da çok daha bol bir bütçeyle çalışıyor: 9 yarım hamle ve
2.000 düğüm yerine 300 yarım hamle ve 400.000 düğüm; her hamleden sonra da iki taraf için kısa
bir ölü pozisyon araması yapılıyor. Bunlara
Chess960 de ekleniyor: başlangıç pozisyonu resmî numarasından (0–959) kuruluyor; bu oyunda rok,
şahı kendi kalesinin üstüne sürerek yapılıyor.
Sonuç ölçülebilir. CHA-Solver'ın süreden yanlış sonuçlandığını bulduğu 204.060 oyunun
204.058'inde doğru hükmü veriyor: %99,999. FideLite'ın
kaçırdığı 3.676 oyunun 2'si dışında hepsini yakalıyor: 3.663'ünü kilit
taramasıyla, 11'ini aramayla. Kilit taraması 52.146 oyunu kilitli olarak tanıyor —
FideLite'ınkinden 4.011 fazla — ve bu sınıfta kapsama %92,98'den %100'e çıkıyor. Kalan 2 oyun
yanlış hüküm değil, kanıtlanamamış beraberlik: kanıt bulunamayınca, FideLite'ta olduğu gibi,
tedbiren galibiyet veriliyor. Depoda ayrıca motoru tek başına sınamak için bir kıyaslama sayfası
ve CHA-Solver verileri de bulunuyor.
Arayüz tarafında da gerçek bir platforma daha yakın. Saat performance.now ile işliyor, yani
sistem saatinin ileri ya da geri alınmasından etkilenmeyen monoton bir sayaçla. Başlarken bir FEN ve
süre girilebiliyor; beyaza ve siyaha ayrı süre vermek, iki ek periyot tanımlamak, tahtanın
yönünü seçmek ve üçlü tekrar ile 50 hamle kuralını talep beklemeden işletmek de mümkün. FEN bir geçerlilik
denetiminden geçiyor: her renkten tek şah olmalı, birinci ve sekizinci yatayda piyon bulunmamalı, sırası
olmayan taraf şah altında olmamalı. Tahtayla uyuşmayan rok hakları ve geçerken alma karesi yok sayılıyor;
geçersiz bir girdiyle oyun başlamıyor.
Zaman kontrolünün türü de seçilebiliyor: Fischer artışı, Bronstein gecikmesi
ya da basit gecikme; artış sıfır olduğunda sudden death. FIDE 5.2.3 de uygulanıyor: iki taraf da en az
bir hamle yapmadan anlaşmalı beraberlik olmuyor; teklifin hangi hamleden itibaren yapılabileceği de
ayarlanabiliyor (½ Offer, varsayılan değeri 1). Beraberlik radyo düğmesi yalnızca rakibin açık bir teklifi
olduğunda değil, 50 hamle ya da üçlü tekrar talebi geçerli olduğunda da yeşile dönüyor.
Farenin sağ tuşunu basılı tutup sürükleyerek tahtaya çemberlerle desenler çizilebiliyor: sürükleme düz ya da
çapraz bir hat boyuncaysa hattaki bütün kareler, değilse yalnızca iki uç kare işaretleniyor; tek bir kareye sağ
tıklamak yalnızca o kareyi işaretliyor. Taşın gidebileceği kareler, Lichess ve Chess.com eşdeğerlerindeki gibi iki işaretle gösteriliyor: boş karede ortada bir nokta, rakip taşın durduğu karede kenarda bir
halka. Şah çekildiğinde, eşdeğerlerde olduğu gibi, şahın karesi kırmızı bir ışıkla
vurgulanıyor. Alınan taşlar ve materyal farkı saatlerin yanında duruyor. Bitiş göstergeleri ve
bütün yerleşim daha ferah.
Armageddon. Başlangıç penceresindeki Armageddon kutusu oyunu armageddona
çeviriyor:
Süre farkı şart. Kutu işaretlenince ⚖ kendiliğinden kalkıyor ve siyahın süre kutusu açılıyor; ilk kutu beyazın süresi oluyor. Beyazın süresi siyahınkinden fazla olmalı, örneğin beyaza 5, siyaha 4 dakika; eşitse ya da azsa giriş geçersiz sayılıyor ve oyun başlamıyor. Armageddon işareti kaldırılınca ⚖ geri geliyor.
Talep ve teklif yok.Auto 3R/50 kendiliğinden işaretlenip kilitleniyor: beraberlik zaten siyahın galibiyeti sayıldığı için üçlü tekrar ve 50 hamle kuralı talep beklemeden oyunu bitiriyor. ½ düğmesi de gizleniyor; teklif edilecek bir şey kalmıyor.
Tek taraflı ölü pozisyon. Her hamleden sonra “beyaz hâlâ mat edebilir mi?” diye soruluyor; edemiyorsa oyun bitiyor ve sonuç DP ⟹ BA. Ayrı bir kod yok: DP'nin normal oyunda iki taraf için sorduğu soru, armageddonda yalnız beyazın mat imkânı için soruluyor. Soruyu materyal testi, geniş kilit dedektörü ve 9 yarım hamlelik arama cevaplıyor. Arama “mat imkânsız” hükmünü yalnızca bütün yolları kapattığında veriyor; bütçesi dolarsa oyun sürüyor.
Bayrak ve terk. FIDE'deki gibi işliyor: 6.9 araması her zamanki gibi 300 yarım hamle ve 400.000 düğümle soruluyor. Rakip mat edebiliyorsa bayrağı düşen ya da terk eden kaybediyor (WT, WR, BT, BR), edemiyorsa sonuç TM ya da RM. Armageddonda TM ile RM de siyahın galibiyeti: beyazın bayrağı ya da terki her durumda BA.
Skor. Sonuç satırı kodla birlikte skoru gösteriyor: W# ⟹ WA, 3R ⟹ BA. Beyazın galibiyetleri W ile başlayan üç kod; geri kalan her kod siyahın.
Armageddonda genelde iki periyot olur: ikinci periyot süre eklemez, yalnızca artışı devreye
sokar. 61. hamleden itibaren hamle başına 2 saniye artış için ana artış 0 bırakılır ve
per2 alanına # 60 +0 min +2 s girilir. Armageddon, Chess960 ve FEN'le birlikte de
çalışıyor. Kuralların nasıl işlediği Kurallar sekmesinin 10. bölümünde
anlatılıyor.
Siyah kuralı bildiğinden emin olmalı. Beyazda yalnızca şah ve at, siyahta şah ve kale
kalmışken bile yardımlı mat kurulabiliyor: siyahın kendi kalesi, şahının kaçış karesini
kapatıyor. Bu yüzden oyun orada kendiliğinden bitmiyor; o anda terk eden ya da süresini bitiren
siyah da gerçekten kaybediyor (6. bölümdeki tablo). Benzeri 2008'de bir dünya
şampiyonasında, at ve şah karşı at ve şahta yaşandı: Soćko–Foişor. Hakemin de
beraberliği kanıtlayabilmesi gerekiyor: kanıt bulunamazsa tedbiren beyaza galibiyet veriliyor.
LUX'ta bu çok nadir, ama sıfır değil.
Sürüm
Ne
Giriş
Bayt
LUX builds/special/LUX.html depoda: index.html
Zengin arayüzlü tam hakem: sürükle-bırak, sağ tıkla işaretleme; başlangıçta FEN, süre, saat modu (Fischer, Bronstein ya da basit gecikme), Chess960 ve armageddon seçimi.
Lichess'in kurallarını uygulayan bir hakem: tıklanan tahtayla 2.804 bayt, tahtası çıkarılmış sayısal hâliyle 1.088 bayt. Tıklamalı sürümün oynanışı da Lichess'e benziyor: platformun renkleri, gidilebilecek karelerin işaretleri, son hamlenin ve tehdit altındaki şahın vurgulanması, tahtanın üstünde açılan terfi seçimi; saat L3'teki gibi 600 + 5 saniye. Sayısal hâli L3_numerical'ın arayüzünü kullanıyor ve paketli.
Platformların kuralları neden böyle uyguladığı federasyonlar bölümünde anlatılıyor; iki eşdeğerin farkları kısaca şöyle:
Kural
FIDE · L3
Lichess
Chess.com
Üçlü tekrar
Taleple
Taleple
Kendiliğinden
50 hamle
Taleple
Kendiliğinden
Kendiliğinden
Beşli tekrar
Kendiliğinden
Kendiliğinden
—üçlüde biter
75 hamle
Kendiliğinden
—50'de biter
—50'de biter
Ölü pozisyon
Materyal ve kilitlikod DP
Yalnızca materyalkod IM
Yalnızca materyal, ama USCF'ye yakınkod IM
Süre bitiminde
Kapsamlı 6.9 aramasıyardımlı mat
6.9, ama yalnızca materyal
Yalnızca rakibin taşları sayılıyor
Terkte
Kapsamlı 6.9 aramasıyardımlı mat
6.9, ama yalnızca materyal
Her zaman kayıp
Tekrarda geçerken alma
Geçerli
Geçerli
Ham-geçerli
İki eşdeğerde ölü pozisyonun yalnızca materyal kolu var. Bu yüzden iki harfli sonuç kodu, kısaltmanın
anlamı doğru kalsın diye DP değil IM (insufficient material, yani yetersiz
materyal). Bu kodu tetikleyen, ana sürümlerde DP veren materyal testinin ta kendisi.
Satrancın temel kuralları, satır satır. Bu depo ders malzemesi olarak hazırlandı.
Sayfada sağ tıklayıp “Sayfa kaynağını görüntüle” seçeneğine tıklamak yetiyor: tahtanın kendisinden doğan
kuralların neredeyse hepsi (hamle üretimi ve şah güvenliği, rok, geçerken alma, terfi, mat, pat,
beşli tekrar, 75 hamle kuralı ve materyalden tanınan ölü pozisyon) tek bir HTML dosyasında, iki bin
baytın altında, baştan sona okunabiliyor. Amaç, satrancın ne kadar sadeleşebildiğini ve
kurallarının kaç karaktere sığdığını sezgisel olarak kavratmak; depo, yazılım ve algoritma derslerinde
kaynak olarak kullanılabilmesi umuduyla yazıldı.
Tek HTML dosyasında 1.893 baytlık iki kişilik bir satranç hakemi ve 1.815 baytlık ikizi. İkisi de aynı kuralları uyguluyor: L2 seviyesinin kilitli pozisyon dedektörü olmayan hâli; yani ölü pozisyon yalnızca materyalden tanınıyor, beşli tekrar ve 75 hamle kuralı ise kendiliğinden ilan ediliyor. Aralarındaki fark, tahtanın içeride nasıl tutulduğu: biri FEN gibi harflerle, öbürü sayılarla. Yan yana konduklarında bir temsil seçiminin neye mal olduğunu gösteriyorlar: harfler 78 bayt daha pahalı. İkisi de paketlenmemiş; kaynak olduğu gibi okunabiliyor. Oynanış L2'yle aynı.
Okunabilirlik için iki sürüm birbirine paralel yazıldı: fonksiyon ve değişken adları aynı
sırada tanımlanıyor ve satırlar aynı yerlerden kırılıyor; böylece iki dosya satır satır, yan yana
okunabiliyor. Bu yüzden tek satır hâlinde duran öbür sürümlere göre çok daha rahat okunuyorlar. Kilitli
pozisyon dedektörü bilerek çıkarıldı: yaygın bilinen bir algoritma değil ve satranç bilmeyen biri için
okunması zor. Kalan kurallar hemen hemen her satranç motorunda bulunduğu için kısa sürede
kavranabiliyor.
Bayt bayt iki kişilik satranç: her biri tek bir HTML dosyası olan ve 1 KB'nin altında kalan üç sürüm. İkisi paketli; en küçüğü düz kaynak, çünkü onda paketleme kazanç sağlamıyor. Üçü de aynı kuralları uyguluyor: tam geçerlilik, bütün şartlarıyla rok, geçerken alma ve her taşa terfi. Üçü de L1'in 0x88 kural koduyla çalışıyor; oynanışları ve göstergeleri ise farklı. Saat, sayaç, tekrar, materyal testi, teklif ve sonuç kodu yok; mat ile pat ayırt edilmiyor. Adını bayt bütçelerinden alıyor: bütçe 911 baytlık ana sürümden 722 baytlık ara sürüme, oradan da 650 baytlık en küçük sürüme iniyor.
Sayısal, prompt penceresikareler yatay ve sütun rakamlarıyla: e2e4 = 1434
Yok, yalnızca son hamleilk hamleden önce 63
Tanınmıyorartık hiçbir hamle kabul edilmiyor; pencerede son hamle yazılı kalıyor
650
Bot ekseni
Üçüncü eksen: kuralların üstüne kurulan arama. Bu bölümde iki seviyenin botlu sürümleri ve Toledo'nun programına rakip olarak yazılan bot yer alıyor. L1_1250elo ile L2_aybars_2500 tıklanan tahtada, bota karşı oynanıyor; durum satırında koordinat yok. Aybars'ın kendi deposu da var: Aybars_ChessAI ↗️. Bot orada index.html adıyla duruyor; siyahla oynanan ikizi black.html, Stockfish'e karşı güç ölçümü ve botun nasıl kurulduğunun anlatımı da o depoda.
Seviye
Botsuz
Botlu
Botun maliyeti
L1
1.404builds/L1/L1.html
2.051builds/special/L1_1250elo.html
647
L2
1.922builds/L2/L2.html
7.927builds/special/L2_aybars_2500.html
6.005
L1_1250elo, L1 ile aynı yeni tahta temsilinde, 0x88 tahtada ve engine_4x'in çekirdeği üstünde çalışıyor; ilk satırdaki farkın neredeyse tamamı bot, kalan 35 bayt kadarı kural çekirdeğinin engine_4x'ten aldığı hız ayarları. L2_aybars_2500 ise hâlâ önceki tahta temsilinde, 64 karelik düz bir dizide çalışıyor; L2 yeni tahta temsiline geçtiği için ikinci satırın son sütunu yalnızca botun maliyetini değil, iki tahta arasındaki farkı da içeriyor. Üstelik bu iki fark aynı şeyin iki ölçümü değil: iki seviyede farklı botlar var. Adlarındaki 1250 ile 2500'ün de işaret ettiği gibi L1'deki bot hafif; L2'deki Aybars'ın araması ve değerlendirmesi ise çok daha ileri gidiyor. Her seviyede kurallar botlu ve botsuz dosyada aynı olduğu için fark büyük ölçüde botun kendisinden geliyor; iki fark arasındaki yaklaşık dokuz katlık açık da kurallardan değil, botların gücünden kaynaklanıyor. L2'nin botlu sürümü oyunu üçlü tekrarda ve 50 hamlede de bitiriyor; bu da farkın içinde, ama küçük bir parçası.
Seviyelerin iki botu iddianın dışında ve bu bir çekince değil, tasarımın bir sonucu. Bot
öncelikli olsaydı tahta temsili baştan başka türlü yazılırdı: kare merkezli
düz bir dizi “bu karede ne var?” sorusunu ucuzlatıyor ama küme işlemlerini pahalılaştırıyor; bir arama ağacı ise tam tersini ister. Burada seçim kural yoğunluğundan yana yapıldı;
bot bu seçimin üstüne sonradan eklendi.
Kural katmanının ayrı durması da bunu mümkün kıldı: motor çekirdeği sürücü hakkında hiçbir şey
bilmediği için üstüne arama koymak ya da onu aramayla iç içe geçirmek kural eşdeğerliğini
bozmuyor. Botlu sürümlerde saat ve oyuncular arası etkileşimler dışarıda kalıyor; kurallar ise
aynı.
L2 · bot'ta bilerek bırakılmış bir istisna. Oyuncu katmanı olmadığı için talebe
bağlı eşikler normalde hiçbir şey tetiklemezdi; yine de bu sürüm bilerek R=3 ve
M=100'de bitiyor. Botun araması bu iki eşiği kendi içinde de kullanıyor; oyunun
eşiklerini yukarı çekip aramanınkileri olduğu gibi bırakmak ikisini birbirinden koparırdı.
Botsuz L2'de ise aynı eşikler gerçekten hiçbir şey tetiklemiyor: orada oyun yalnızca
kendiliğinden gelen R=5 ve M=150 ile bitiyor.
Toledo
Oscar Toledo G.'nin programı ve ona rakip olarak yazılmış bot, “aynı kural seviyesinde, Toledo'nunkine yakın bir bayt sayısıyla daha işlevsel — daha güçlü botlu, daha iyi arayüzlü — bir sürüm yazılabilir mi acaba?” sorusunu sınamak için burada. Bu ikisinin ayrı bir deposu yok; FideLite deposunun builds/special/ klasöründe duruyorlar.
İkisine de aynı kural eklendi: 50 hamle kuralı. 100 yarım hamle boyunca ne alış ne de piyon hamlesi olursa oyun, mat ve patta olduğu gibi sessizce donar: ne tahtaya yeni bir hamle yazılır ne de ekranda bir şey gösterilir. Amaç, iki bot arasındaki karşılaştırma maçlarının sonsuza kadar sürmesini ya da bir döngüye takılmasını önlemek. Sayaç yalnızca oynanan hamlenin tahtaya yazıldığı yerde güncelleniyor ve denetleniyor; arama ona hiç bakmıyor, dolayısıyla iki botun da gücü değişmedi.
Oscar Toledo G.'nin programı, camiada en çok konuşulan küçük satranç programlarından biri: 2 KB'nin biraz üzerinde L1 seviyesinde kurallar ve sabit, sığ bir derinliğe kadar arayan, yaklaşık 1200 Elo'luk bir bot. Eşdeğerliğini koruyarak ES6 yöntemleriyle biraz daha küçülttüm, arayüzünü de hafifçe yeniden yorumladım.
Tıklama, bota karşıterfi tahtanın altındaki listeden, hamleden önce seçilir
Toledo'nun programına rakip olarak bu motorla yazdığım bot: L1 seviyesinde kurallar artı 50 hamle kuralı, yinelemeli derinleştirmeli alfa-beta araması ve hamle başına 50 milisaniye civarında bir zaman bütçesi. Bot beyazla oynuyor.
Tıklama, bota karşı
1.904
ToledosOpponentbyMe, L1_1250elo'yla aynı arama ve değerlendirmeyi kullanıyor; farkları şunlar:
Bu sekme motorun kendisini anlatıyor. Motorun uyguladığı kurallar Kurallar ve
uygulanışı sekmesinde; tasarım geçmişi, sınır durumları ve engine_4x.js ile
engine_string.js'in kaynakları Ayrıntılı teknik sekmesinde.
Motor
engine.js — arayüzsüz çekirdek
Motorun arayüzsüz hâli: saf durum ve fonksiyonlar — DOM yok, bağımlılık yok, derleme adımı
yok. Tek satır, 1.527 bayt. Aşağıdaki kaynak builds/ dizinindeki dosyanın
kendisi; sayfa açılırken yükleniyor. Bir çipe basınca o kavrama ait parçalar kaynakta
vurgulanıyor; aynı anda tek kavram vurgulanıyor, aynı çipe yeniden basınca vurgu kalkıyor.
Pencerenin üstündeki En basit sürücüyle düğmesi aynı pencerede
L3_numerical'ı açıyor: bu motorla onu oynanabilir kılan en kısa sürücü, 1.715
baytlık tek bir HTML dosyasında. Sürücü 283 baytlık bir döngü. Hamleyi bir prompt
penceresinden rakamlarla okuyor, her kare için iki rakam: önce yatay, sonra sütun, ikisi de
0'dan 7'ye — yani karenin onaltılık indeksi. Böylece e2 14,
e4 34, 1.e4 de 1434 oluyor. İsteğe bağlı beşinci rakam alt terfiyi
seçiyor (0 fil, 1 kale, 2 at). Aynı pencere iki saati ve son hamleyi gösteriyor; oyun bitince
sonuç kodu alert ile bildiriliyor. Boş girdi terk anlamına geliyor; sayı olmayan
bir girdi bekleyen teklifi kabul ediyor, teklif yoksa beraberlik talep ya da teklif ediyor;
hamlenin ardına yazılan rakam dışı bir karakter ise önce hamleyi oynatıyor — bu, bekleyen
teklifi düşürüyor — sonra talep ya da teklif ediyor. Bu görünümde yalnızca sürücünün kodu
vurgulanıyor; bir de sürücünün motora eklediği kendi taşı sınaması.
Motorun gövdesi iki dosyada neredeyse aynı; üç fark var. Sürücüde L, çıkış
karesindeki taşın sırası gelen tarafa ait olduğunu kendisi sınıyor
(~b[i]&8^t&&); bu yüzden O'nun renk süzgeci
(p&8^t||) kalkıyor. D, F ve A ise ayrı
fonksiyon olarak yok; işleri döngünün içine gömülü.
İmza başlıkta: b=[c=un=15,e=y=t=8,n=o=0] — başlığın atadığı adlar sırayla
okununca c·un·e·y·t çıkıyor. Çoğunun bir görevi var: c dört rok hakkının
maskesi (15), e geçerken alma karesi (8: yok), t sıra (8: beyaz),
n ile o da yarım hamle sayacı ve teklif bitleri. İkisi yalnızca imza
için orada: un hiçbir yerde okunmuyor; y'yi de motor hiç okumuyor —
sürücüler onu son hamleyi ya da seçilen terfi taşını tutmak için kullanıyor. İmzanın bedeli
beş bayt: un= üç, y= iki.
engine.js—
Tahta nasıl duruyor
Tahta düz bir dizi, ama 64 elemanlı değil: 0x88 düzeninde. Her yatay dizide 16 hücre
yer tutuyor; ilk sekizi gerçek kareler, son sekizi tahtanın dışında kalan bir boşluk. İndeks
16 × yatay + sütun; ikisi de 0'dan sayılıyor: a1 0, h1 7, a2 16, e4 52, h8 119. Onaltılık
yazılınca iki basamak doğrudan koordinat oluyor — üst basamak yatay, alt basamak sütun: e4
0x34, h8 0x77. Sayısal sürücülerin bir hamlenin her karesini iki
rakamla okuması bundan.
Dizinin uzunluğu 120: h8'in indeksi 119 olduğu için son yatayın sağındaki boşluk hiç
oluşmuyor. 120 hücrenin 64'ü kare, 56'sı delik. Delikler hiç atanmamış: dizi seyrek
(sparse), bir delik okununca undefined dönüyor.
0x88 tahtası; her hücrede indeksin onaltılık yazımı. Soldaki sekiz sütun kareler (a1 = 00 sol altta, h8 = 77), sağdaki sekiz sütun delikler. En üst sıranın delikleri dizinin sonunun da ötesinde: b.length 120, yani 0x78.
Düzenin adı klasik tahta dışı testinden geliyor: 0x88 motorları bir indeksin tahtada olup
olmadığını i&0x88 ile sınar. h sütununu aşan bir indeks 0x08
bitini, 8. yatayın ötesine geçen ya da 1. yatayın altına düşen bir indeks 0x80
bitini yakar. Bu motor o maskeyi hiç kullanmıyor. Deliğin değeri undefined ve
undefined motorun yaptığı hiçbir sınamayı geçmiyor: ışın yürüyüşünde
b[i]<1 yanlış çıkıyor ve yürüyüş duruyor; hedef süzgecinde
b[x]+~t>>3 0 çıkıyor ve hedef eleniyor. Dizinin başından önceye ya da
sonundan sonraya taşan indeksler de aynı şekilde undefined okunuyor. Dizi
metotları da (map, some, every, indexOf)
delikleri atlıyor; tahta üzerindeki her döngü yalnızca 64 kareyi geziyor.
Aradaki boşluğun asıl kazandırdığı şey yön. İki kare arasındaki fark d = f − i,
16 × yatay farkı + sütun farkı; sütun farkı −7 ile 7 arasında kaldığı için her (yatay farkı,
sütun farkı) çiftine ayrı bir değer düşüyor. 64 elemanlı bir dizide bu böyle değil: +7 hem
aynı yatayda yedi kare sağı hem de bir yukarı bir solu gösterebilir. 0x88'de hattı fark tek
başına söylüyor: 15'in katıysa bir çapraz, 16'nın katıysa sütun, 17'nin katıysa öbür çapraz;
hiçbiri değilse ya aynı yatay ya da hiçbir hat. Ayrıntısı Geometri
bölümünde.
Karenin değeri o karedeki taşın kodu: 0 ile 14 arasında, dört bite sığan bir sayı. Dört
bitlik bu birime nibble, yani yarım bayt denir ve tek bir onaltılık basamakla yazılır:
0–9 rakamla, 10–14 a–e harfleriyle. Başlangıç dizilişi bu sayede
onaltılık bir dizgi olarak yazılabiliyor — beyazın ilk yatayı dcebaecd — ve her
kareye tek bir karakter düşüyor. Dört bit iki parçaya ayrılıyor:
8 biti: renk. Beyazda 1, siyahta 0. Sıra değişkeni t de aynı değeri
taşıyor: beyaz için 8, siyah için 0. Taşın rengini sırası gelen tarafla kıyaslamak böylece tek
bir XOR'a iniyor: p&8^t.
Alttaki üç bit: tür, 1'den 6'ya. 1 piyon, 2 şah, 3 vezir, 4 at, 5 kale, 6 fil.
Türü p&7 veriyor (motorda P); kod = tür + renk, yani beyaz bir
taşın kodu siyahınkinin 8 fazlası. Boş kare 0; 7, 8 ve 15 hiçbir taşa ait
değil.
Taş
Tür P
Siyah
Beyaz
Kısa menzilli taşlar (1, 2, 4)
Piyon
1
10001
91001
Şah
2
20010
10 (a)1010
At
4
40100
12 (c)1100
Kayan taşlar (3, 5, 6)
Vezir
3
30011
11 (b)1011
Kale
5
50101
13 (d)1101
Fil
6
60110
14 (e)1110
Kayan taşlar için motorun tek bir ifadesi var: P+P*4%x&S(). x
adım büyüklüğü (yatayda 1, sütunda 16, iki çaprazda 15 ve 17), S() de yolun açık
olup olmadığını söylüyor. Yönü ifadenin en alt biti, yani tekliği süzüyor. Düz hatta
(x 1 ya da 16) P*4%x hep çift çıkıyor ve teklik P'nin
kendisinden geliyor: tek türler, vezir (3) ve kale (5), geçiyor. Çaprazda (x 15
ya da 17) 4P — üç kayan taş için 12, 20 ve 24 — yalnızca 5 ile 6'da 15'i ve 17'yi
aşıyor; o zaman tek olan bölen bir kez çıkarılıyor ve teklik dönüyor. Sonuç 3 ile 6'da tek
(vezir, fil), 5'te çift (kale) çıkıyor. Bu en alt bit & ile
S()'nin sonucuna bağlanıyor: yol kapalıysa hamle geçmiyor. Vezir için ayrı bir
dal yazılmamış: her iki durumda da tek çıkıyor.
Kısa menzilli üç taşın her birinin G'de kendi dalı var; G onları
türün kendisiyle ayırıyor: P-4 sıfırsa at, P-2 sıfırsa şah,
P-1 sıfırsa piyon; hiçbiri sıfır değilse taş kayan bir taş.
Kodların sırası gelişigüzel değil; her yerleşim motorun bir yerinde kısayol olarak geri
dönüyor. Piyonun 1 olması, piyon kodlarında (1 ve 9) hem 2 bitinin hem 4 bitinin sıfır olması
demek; öbür bütün taşlarda bu ikisinden en az biri 1. M'nin yarım hamle sayacı
buna dayanıyor: !b[x]*b[i]&6 sıfırsa ya bir taş alınmıştır ya da piyon
oynamıştır ve sayaç sıfırlanır. Kayan taşların 3, 5 ve 6 olması yukarıdaki teklik ifadesinin
şartı. Şahın 2'de, atın 4'te durması da V'nin materyal sayımına hizmet ediyor:
p%8>2 vezir, at ve kaleyi (3, 4, 5) ağırlıklarıyla sayıp şahı ve piyonu
dışarıda bırakıyor; fil (6) ise daha önce p%8>5 ile kendi dalına ayrılmış
oluyor.
Temsil kare merkezli: bilgi kareye bağlı, taşa değil. Karşıt yaklaşım taş merkezli
temsil: bitboard kullanan bir motorda bilgi her taş türü için ayrı bir bit kümesine dağılır;
“bu karede ne var” sorusu pahalılaşır, küme işlemleri ucuzlar. Burada tersi geçerli: kare
sorgusu tek bir dizi erişimi, b[i], dört karakter. Tek istisna V.
Kilitli pozisyon taramasında piyonları ve şahları BigInt bitboard'lara çeviriyor, çünkü orada
soru “bu karede ne var” değil, “bu küme nereye kadar büyüyebilir” — tam da küme işlemlerinin
işi. O bitboard'lar 64 bitlik: map delikleri atladığı için bit sırası 0x88'inki
değil, düz tahtanınki, 8 × yatay + sütun.
Sürücü yazmak için altı çağrı
Bir sürücünün satranç bilmesi gerekmiyor: tıklamayı ya da yazılan hamleyi L ile
doğrula, A ile oyna, sonra ekranı yeniden çiz. Kalan dördü sonuç ve gösterge için:
Z tahtada oluşan bitişleri, D ile F oyuncunun talep
ettiği ya da saatin getirdiği bitişleri bildiriyor; T de şah göstergesini
besliyor.
L(çıkış, varış)
Kareler 0x88 indeksi. Hamle geçerliysetrue, değilse false; hamleyi yapan tarafın şahını şah altında bırakan hamle de geçersiz. Tek bir hamleyi sınıyor: bir taşın bütün varış karelerini göstermek isteyen sürücü her kareyi ayrı ayrı sormalı. Çıkış karesindeki taş sırası gelen tarafın olmalı; engine.js bunu sınamıyor, sürücü sınıyor. Rakip bir taş için sorulursa cevap anlamsız: piyonun yönü sırası gelen tarafa göre alınır, hedef süzgeci de taşın kendi rengindeki bir taşı “almasına” izin verir. A'dan hemen önce çağrılmalı: rokta kalenin karesini bu çağrı kaydediyor.
A(çıkış, varış, terfi)
Hamleyi gerçekten oynar: yarım hamle sayacını ve rok haklarını günceller, geçerken alma karesini yazar, sırayı değiştirir, pozisyonu tekrar tablosuna işler ve sonuç kodunu döndürür. Terfi türü 3 vezir, 5 kale, 6 fil, 4 at; verilmezse piyon vezire terfi eder. Geçerliliği sınamaz; o L'nin işi.
Z()
Yalnızca pozisyondan doğan sonuç: mat, pat, ölü pozisyon, beşli tekrar, 75 hamle kuralı. Oyun sürerken 0. A dönmeden önce onu zaten çağırır; ayrıca çağırmak gerekmez.
D(taraf)
Verilen taraf beraberlik teklif eder ya da bekleyen teklifi kabul eder; anlaşma çıkmazsa talep edilen beraberlikler (üçlü tekrar, 50 hamle kuralı) de buradan döner. Argümansız D() teklif bitlerine dokunmadan yalnızca değerlendirir.
F(taraf, tür)
Tür 0: o tarafın bayrağı düştü. Tür 1: o taraf terk ediyor. Rakibin mat etmesi hiçbir şekilde mümkün değilse sonuç beraberliğe döner — FIDE 6.9 ve 5.1.2.
T()
Sırası gelen tarafa şah çekilmiş mi? Şahın karesini kendisi bulup onu sınar. T(kare) aynı soruyu herhangi bir kare için sorar: o kare, sırası gelen tarafın rakibi tarafından tehdit ediliyor mu? Yalnızca gösterge için; geçerlilik L'nin işi.
Taraf her yerde taş kodunun renk bitiyle verilir: 8 beyaz,
0 siyah; t de aynı değeri taşıyor. Saat tamamen sürücünün işi —
motor süreyi ne tutar ne ölçer; sürücü kendi saatini her tıkta azaltır, biri sıfıra ulaşınca
F(taraf,0) çağırır.
Üç şeyi daha bilmekte yarar var. Motor kendi kendini kuruyor: dosya yüklendiği anda tahta,
sıra, rok hakları, sayaçlar ve tekrar tablosu başlangıç pozisyonunda; ayrı bir kurulum
çağrısı yok.
Geri alma yok:A yalnızca ileri gidiyor. L kendi deneme
hamlesi için tahtayı saklayıp geri koyuyor, ama bunu dışarıya açmıyor. Geri alma isteyen
sürücü hamleden önce b, e, n, t,
c, k ve R'yi saklamalı; geri alırken de önce yeni
pozisyonun tekrar sayısını bir azaltmalı (G[k]--), sonra saklanan değerleri geri
yazmalı. Azaltma atlanırsa sessiz bir hata doğar: geri alınan hamlenin tabloya eklediği sayım
orada kalır, aynı hamle yeniden oynandığında pozisyon bir kez fazla sayılır ve R
gerçekte olduğundan yüksek çıkar — üçlü tekrar erken talep edilebilir hâle gelir.
FEN ve PGN yok: motor pozisyon okumuyor, hamle kaydı da tutmuyor. Belirli bir
pozisyondan başlamak isteyen sürücü tahtayı b'ye 0x88 indeksleriyle, delikleri
hiç atamadan yazar; sırayı t'ye, rok haklarını c'ye, geçerken alma
karesini e'ye ve yarım hamle sayacını n'ye de kendisi yazar. Tekrar
tablosunu da yeni pozisyona göre baştan kurmalıdır: önce G'ye yazılmış eski
anahtarları siler (for(K in G)delete G[K]; fonksiyonun kendi özellikleri
sayılabilir olmadığı için döngü yalnızca pozisyon anahtarlarını görür), ardından
R=G[k=b+e+t+c]=1 yazar. Teklif bitlerini de sıfırlamalıdır (o=0).
Bitler kalırsa önceki oyundaki bir teklif yeni pozisyona taşınır ve karşı taraftan gelen tek
bir teklif oyunu anlaşmalı beraberlikle (9) bitirir. Delikler doldurulursa, örneğin 0
yazılırsa, tahtanın dışı boş kareye döner: kayan taşlar bir yatayın sonundan ötekinin başına
sarar ve h1'deki kale a2'ye gidebilir hâle gelir. Geçerken alma karesi de ancak komşu
sütundaki bir piyon almayı gerçekten yapabiliyorsa yazılır; yoksa 8.
M de e'yi aynı kuralla tutuyor; FEN'deki kare olduğu gibi
kopyalanırsa anahtar, motorun kuracağı anahtardan ayrılır ve pozisyonun tekrarı bir eksik
sayılır.
Durum değişkenleri
Sürücünün ekranı çizmek için okuduğu her şey burada; son üçü 6.9 aramasına ait. Saat motorda yok: süreyi sürücü tutuyor, bir bayrak düştüğünde ya da oyuncu terk
ettiğinde F'yi çağırıyor.
Ad
Ne tutar
Ayrıntı
b
Tahta
120 elemanlı seyrek dizi: 64 kare, 56 delik (temsil yukarıda). Sürücü doğrudan okuyabilir; yazması hiç gerekmez.
t
Sıra
8 beyaz, 0 siyah. M her hamlede değiştirir (t^=8).
z
Oyun sonucu
Sonuç kodu; oyun sürerken 0. engine.js'te geçmiyor; sürücünün değişkeni: sürücü A, D ya da F'nin dönüş değerini buraya yazar.
n
Yarım hamle sayacı
FEN'in halfmove clock alanı. Her taş alımında ve piyon hamlesinde sıfırlanır. n>99 olunca 50 hamle kuralıyla beraberlik talep edilebilir; n>149 olunca 75 hamle kuralı oyunu kendiliğinden berabere bitirir.
R
Tekrar sayısı
Mevcut pozisyonun kaçıncı kez oluştuğu. R>2 olunca üçlü tekrar talep edilebilir; R>4 olunca oyun kendiliğinden berabere biter (beşli tekrar).
o
Teklif bitleri
1 biti beyazın, 2 biti siyahın bekleyen teklifi. İkisi de 1 olunca oyun anlaşmalı beraberlikle biter.
c
Rok hakları
Dört bitlik maske: 1 beyaz kısa, 2 beyaz uzun, 4 siyah kısa, 8 siyah uzun; başlangıçta 15. Tutulan şey hak; rokun o an yapılabilir olup olmadığı değil.
e
Geçerken alma karesi
Yoksa 8: tahtada karşılığı olmayan bir delik indeksi. Yalnızca komşu sütundaki bir piyon almayı gerçekten yapabiliyorsa yazılır — tekrar sayımının doğruluğu buna bağlı.
k
Pozisyon anahtarı
b+e+t+c dizgisi: tahta, geçerken alma karesi, sıra ve rok maskesi art arda eklenmiş. M her hamlede yeniden kurar; tekrar tablosunun da arama tablosunun da anahtarı bu.
G[k]
Tekrar tablosu
Pozisyon anahtarı → kaç kez oluştuğu. Ayrı bir nesne bildirilmiyor: fonksiyon da bir nesne olduğu için tablo G'nin kendi özelliklerine yazılıyor.
N
Düğüm bütçesi
6.9 aramasının deneyebileceği hamle sayısı; terfide her taş ayrı bir hamle sayılıyor. F her çağrıda onu 2e3'e kuruyor. Bütçe tükenince arama kesiliyor ve mat mümkün diyor: kanıtlanamayan bir imkânsızlık beraberlik sayılmıyor.
$
Arama tablosu
Aranmış pozisyon → ziyaret edildiği anda n'nin değeri. Aynı pozisyon aynı ya da daha yüksek bir sayaçla yeniden gelirse, 75 hamle sınırına kadar önünde ilk seferkinden fazla yol kalmamış demektir; önceki ziyaret onu zaten kapsıyor ve arama oraya yeniden inmiyor.
I
Arama tarafı
Bayrağı düşen ya da terk eden taraf; harf, insufficient material'ın baş harfi. F kuruyor; H ile V(I) okuyor. Arama boyunca soru hep aynı: I mat edilebilir mi?
Fonksiyonlar
Altısı yukarıda tanıtıldı. Kalan yedisi iç mekanizma: sürücünün onları çağırması hiç
gerekmez, ama motoru okumak için bilinmeleri gerekir. Tablo kaynaktaki sırayı izliyor; her
fonksiyon yalnızca kendinden önce tanımlananları çağırıyor, iki istisnası
kat haritasında. Bayt sütunu her fonksiyonun boyunu veriyor — bayt
bütçesinin nereye gittiğini en hızlı bu gösteriyor.
Ad
İmza
Bayt
İşi
C
C(i)
28
Kare → o kareye bağlı rok hakları, bit maskesi olarak. Şahın ve kalelerin başlangıç kareleri dışında her karede 0. M hakları bununla siliyor, G de rok hakkını bununla soruyor.
G
G(i, f, m)
236
Geometri. i'deki taş f'ye gidebilir mi, şah güvenliğine bakmadan (sözde geçerli, pseudo-legal bir sınama)? m tehdit modu: açıkken piyon rakibin yönünde hareket eder, çapraz adımı hedef boş olsa da sayılır ve şah rok yapamaz.
T
T(x)
52
x karesi sırası gelen tarafın rakibi tarafından tehdit ediliyor mu? x verilmezse sırası gelen tarafın şahının karesi kullanılır, yani T() doğrudan şah testidir.
U
U(i, f, p)
134
Ham hamle: tahtayı günceller. Tahtanın kopyasını çıkarır, taşı oynatır, terfi ettirir (p, verilmezse vezir), geçerken alınan piyonu kaldırır, rokta kendini çağırıp kaleyi oynatır. Yeni geçerken alma adayını döndürür: piyonun çift adımında atlanan kare, yoksa 8. Sıraya, sayaçlara ve haklara dokunmaz — çünkü L'nin deneme hamlesi olarak da kullanılır.
L
L(i, x)
54
Tek bir hamlenin geçerliliği. Hedef kare boş mu ya da rakip taş mı, G hamleye izin veriyor mu, U ile oynanınca şah güvende kalıyor mu (T)? Tahtayı geri koyar ve true ya da false döndürür.
M
M(i, x, p)
109
Hamleyi oynar ve durumu günceller: yarım hamle sayacı, rok hakları, U, sıra, geçerken alma karesi — yalnızca alma gerçekten mümkünse kalır — ve yeni anahtar k. Tekrar tablosuna dokunmaz — çünkü H'nin arama ağacında da çalışır.
V
V(s)
405
Hüküm: s'nin rakibi tarafından mat edilmesi, materyal ya da kilitli piyon yapısı yüzünden imkânsız mı? Tek taraflı bir soru: Z ölü pozisyonu bulmak için iki tarafa da soruyor (V(0)&V(8)), H yalnızca I için. Kilitli yarısının kapsamı şahlar ve piyonlar; tahtada başka bir taş varsa ya da geçerken alma mümkünse o yarı hiç çalışmaz.
O
O(h)
59
Yokluk testi. Sırası gelen tarafın her geçerli hamlesi için h(i, x)'i çağırır; h yanlış bir değer döndürdüğü anda durur. O(_=>0) yalnızca geçerli hamle yoksa doğru döndürür: mat ya da pat sorusu bu.
H
H(d)
149
6.9'un arama yarısı. I, herhangi bir geçerli hamle dizisiyle — I'nın kendisi de her hamlede yardım etse — d yarım hamle içinde mat edilebilir mi? H doğru döndürürse edilemez demektir. Yardımlı matın bile imkânsız olduğunu ispatlamaya çalışır; ispat bulamazsa mat mümkün sayar.
Z
Z()
51
Pozisyonun sonucu. Önce geçerli bir hamle var mı diye bakar (O); yoksa matı pattan ayırır. Varsa V'ye ölü pozisyonu sorar, ardından tekrar sayısına ve yarım hamle sayacına bakar.
D
D(s)
36
Beraberlik talebi, teklifi ve kabulü.
F
F(s, r)
44
Bayrak düşmesi ve terk. I, $ ve N'yi kurar, H(9)'u çağırır ve sonucu onun cevabına göre belirler.
A
A(i, f, p)
34
Gerçek hamle — L'nin yalnızca denediğini uygular: M'yi çağırır, pozisyonu tekrar tablosuna işler, R'yi günceller, Z'nin sonucunu döndürür.
On üç fonksiyon 1.391 bayt tutuyor, yani dosyanın %91,1'si. Geri kalan 136 bayt
başlangıç durumuna ve parçalar arasındaki ayraçlara gidiyor: imzalı başlık 25, tahtayı
dolduran döngü 80, tekrar tablosunun ilk kaydı 16, on beş virgül ve noktalı virgül 15.
Dağılım eşit değil ve bu eşitsizlik bir şey anlatıyor. V tek başına
405 bayt — dosyanın yaklaşık %27'si ve en uzun fonksiyonu. Onu G
(236), H (149) ve U (134) izliyor. Yani motorun en pahalı parçaları
sırasıyla mat imkânsızlığının statik tespiti (materyal ve kilitli piyon
yapısı), taş geometrisi, 6.9 araması ve hamlenin tahtada yapılması. Gruplara bölünce:
geometri, tehdit ve geçerlilik (C, G, T,
L) 370 bayt; hamlenin yapılması (U, M) 243; hamlelerin
gezilmesi ve arama (O, H) 208; hüküm, talepler, süre ve işlem
(Z, D, F, A) toplam 165.
Adlar
Dosya on altı parçadan oluşuyor: imzalı başlık, tahtayı dolduran döngü, on üç
fonksiyon ve tekrar tablosunun ilk kaydı. İlk ikisinin ardında noktalı virgül, geri
kalanların arasında virgül var, çünkü tahtayı dolduran parça bir ifade değil, bir
for…in deyimi. Başlık tek başına sekiz ad tanımlıyor — b ve dizinin
içinde atanan c, un, e, y,
t, n, o —, son parça da iki ad: R ve
k. Döngü de j ile s'yi global bırakıyor. Bunlara bir
fonksiyonun içinde doğan üç durum adı — F'nin kurduğu $,
I ve N — ile sekiz geçici global ekleniyor. Geri kalan her harf
fonksiyon parametresi, yani sürücünün yeniden kullanmasına açık. Durum değişkenleriyle
fonksiyonlar yukarıdaki tablolarda, geçici değerlerle parametreler aşağıda.
Kaynakta hiç geçmeyen yedi harf var: E, J, K,
Y, l, w ve z; sürücüler bunları çakışma
kaygısı olmadan kullanabiliyor. L3_numerical'ın döngüsü de tam bunu yapıyor:
girdiyi K'ye, varış karesini Y'ye, saatin başlatıldığı anı
w'ye, sonucu z'ye yazıyor. Alfabenin geri kalanı dolu — bayt
bütçesinin ne kadar zorlandığının en açık ölçüsü.
İsimlendirme rastgele değil. Çoğu ad İngilizce bir kelimenin baş harfi. Fonksiyonlarda
A apply, C castling, D draw, F flag,
G geometry, H helpmate, L legal, M move,
T threat, U update, V verdict; durumda b
board, c castling, e en passant, I insufficient
material, k key, N node budget, o offer,
R repetition, t turn; parametrelerde i initial, f final,
d difference, p piece, r rook. Sayfanın İngilizce
sürümünde bu harfler, geldikleri kelimenin içinde kalın yazılı. Birkaçı başka bir mantığa
uyuyor: n saymanın geleneksel harfi; W ile B beyaz ve siyah
piyonlar; z ile Z oyunun sonunu taşıdıkları için alfabenin sonunda
duruyor (z yalnızca sürücülerde). O bir yokluk testi:
O(_=>0) geçerli hamle kalıp kalmadığını soruyor. X hiçbir kelimeden
gelmiyor; bir çarpı, yani “değil”: hamlenin terfi olmadığını söyleyen bayrak.
$ da biraz # işaretine, yani hash işaretine benziyor — ve gerçekten de bir hash tablosu tutuyor.
Harfin büyük ya da küçük olması da kendi başına bir düzen taşıyor: fonksiyonlar genelde büyük
harfi, durum değişkenleriyle parametreler küçüğü alıyor. Bu bir kural değil, eğilim —
R büyük harfli bir sayaç; W, B, S,
X büyük harfli geçici değerler — ama harfin büyüklüğü çoğu zaman adın ne tür bir
ad olduğunu ele veriyor. Bir harfin iki hâli birden kullanılıyorsa çoğu kez aynı fikrin iki
düzeyini adlandırıyor: p taş kodu ve P onun türü, c rok
hakları ve C onları veren fonksiyon, s adım ve S o
adımla ilerleyen ışın tarayıcı, z saklanan sonuç ve Z onu üreten
fonksiyon. V'nin içinde ise düzen FEN'inkine dönüşüyor: S ile
s kilitli beyaz ve siyah piyonlar, A ile a beyaz ve
siyah piyonların ulaşabildiği kareler — büyük harf beyaz, küçük harf siyah.
Geçici değerler ve parametreler
Sekiz harf, bir fonksiyonun içinde doğan global geçici değişken. Altısı çağrı bitince
anlamını yitiriyor ve hepsi V'nin: W ile B beyaz ve
siyah piyonların bitboard'u; _ ile f beyaz ve siyah şahın bitboard'u,
doyma döngüsünde de ulaşabildikleri kareler; S kilitli beyaz piyonlar;
a siyah piyonların ulaşabildiği kareler. Kalan ikisi bir fonksiyondan ötekine
bilgi taşıyor: r rok kalesinin karesi — G'nin rok dalı yazıyor,
U okuyor; X hamlenin terfi olmadığını söyleyen çarpı —
U yazıyor, H okuyup terfide dört taşı ayrı ayrı deniyor.
f ile S başka fonksiyonlarda parametre: G,
U ve A'da f varış karesi, G'de
S ışın tarayıcı; _ da üç yerde hiç okunmayan bir parametre.
V bu harfleri global olarak yazıyor. Çakışma doğurmuyor, çünkü parametre globali
gölgeliyor ve ikisi aynı anda canlı değil; ama bu harflerden birini bilmeden kendi globali
olarak kullanan bir sürücü burada tökezler.
Kalan harflerin hepsi fonksiyona yerel; sürücü aynı adları global olarak güvenle
kullanabilir. j ile s de öyle: tahtayı dolduran döngü ikisini global
bırakıyor — j"63", s 64 karakterlik diziliş dizgisi
—, ama motor onları bir daha global olarak okumuyor. Nerede sütunu, o harfin hangi fonksiyonların içinde geçtiğini
gösteriyor; V'nin kendi alfabesi ayrıca aşağıda.
Ad
Nerede
Rol
i
C G U L M A
Başlangıç karesi; C'de sorulan kare.
f
G U A
Hedef kare. G'nin rok dalı onu kalenin karesine kaydırıyor.
x
G T L M O H
G'de adım büyüklüğü: 1, 15, 16 ya da 17. T'de sorulan kare. L ve M'de hedef kare — L sonra aynı harfe T'nin cevabını yazıyor. O ile H'de taranan hamlenin hedefi.
p
T U M O H A
T, M ve O'nun taramalarında taş kodu — b'den okunan ham değer. U, M ve A'da terfi türü (U'da varsayılanı 3, yani vezir). H'de terfi döngüsünün denediği tür.
P
G U
Taş türü. G'de b[i]&7; U'da b[i]-t, sırası gelen tarafın taşı için aynı tür. İkisinde de P-1 “piyon mu” testi.
d
G U H
G'de indeks farkı f-i. U'da P*(i-f)**2: rokta 8, piyonun çift adımında 1024 — iki özel hamle bununla tanınıyor. H'de kalan arama derinliği.
m
G
Tehdit modu bayrağı; T ona 1 veriyor. Açıkken piyon rakibin yönünde hareket eder, çapraz adımı hedef boş olsa da sayılır ve şah rok yapamaz.
s
G D F H
G'de işaretli adım (±x). D ile F'de taraf: 8 beyaz, 0 siyah. H'de hamleyi geri almak için saklanan altı elemanlı yedek: [b,e,n,t,c,k].
S
G
Işın tarayıcı. Kayan taşın yolunu — ya da piyonun çift adımını, ya da rokta şahla kale arasını — adım adım yürüyüp yolda bir engel olup olmadığına bakar. Her adımda kendini çağırır ve boş olmayan ilk hücrede durur: bir taşta ya da bir delikte.
j
T M O H
Tahta taramasında indeks.
q
L
Deneme hamlesinden önce alınan tahta yedeği.
Q
U
İki karenin ortası: rokta kalenin ineceği kare, piyonun çift adımından sonra geçerken alma karesi.
Bitişin türü: 0 bayrak düşmesi, 1 terk. Global r'yi gölgeliyor; F'nin ona ihtiyacı yok.
_
G O Z
Hiç okunmayan parametre: S'nin, O'nun iç geri çağrısının ve Z'nin. Boş parantezden (()=>) bir bayt kısa.
V kendi alfabesini kuruyor
Yukarıdaki tablo V'yi içermiyor ve bunun bir sebebi var: V tek
başına on parametre taşıyor — ikisi bir yapı çözme (destructuring) kalıbından
geliyor —, iç döngüsünün parametresiyle birlikte on bir ad. Bunların sekizi motorun başka
yerinde global olan harfler.
V = ( sjA [ m o ] u cHN g = n => … )
koyu: globali gölgeliyor
Bunların arasında biri özellikle dikkat çekiyor: H. Motorun geri kalanında
H 6.9 araması, üstelik V'yi her düğümde çağıran fonksiyon;
V'nin içinde ise bir bit kümesini bir sütun sağa ve sola yayan yardımcı. Öbürleri
de aynı yolu izliyor: c rok hakları değil, kaydırmanın tahta kenarından taşmasını
önleyen maske; N arama bütçesi değil, şahın bir adımı; s diziliş
dizgisi değil, önce sorulan taraf, döngüde de tıkalı siyah piyonlar; j tahtayı
dolduran döngünün indeksi değil, bit sayacı; o teklif bitleri değil, s'nin kendi
materyali; A hamleyi uygulayan fonksiyon değil, önce materyal sayacı,
sonra beyaz piyonların ulaşabildiği kareler; döngünün parametresi n de yarım
hamle sayacını değil, kalan tur sayısını tutuyor. Tahta taraması bile parametre listesinde
koşuyor: [m,o]'nun varsayılan değeri b.map(…)'in kendisi.
Gölgeleme burada bir tasarım tercihi değil, bayt bütçesinin doğal sonucu: boşta yalnızca
yedi harf kaldığı için V adlarının çoğunu kullanımdaki harflerden seçiyor.
Parametre listesinde doğanlar dışarıya sızmıyor — fonksiyon bittiğinde global değerler olduğu
gibi duruyor. Altı atama bu kuralın dışında kalıyor: W, B,
_, f, S ve a; hepsi
doyma döngüsünün açıklamasında. Bunun nerede
kırılgan hâle geldiği de Tuzaklar bölümünde.
Akış
engine.js — satır satır ne oluyor
Harflerin ne anlama geldiği yukarıdaki tablolarda. Burada anlatılan,
o harflerin hangi sırayla ne yaptığı: hangi ifade hangi kuralı uyguluyor, hangi
operatör neyi neye çeviriyor, bir hamle tahtada yapılırken hangi fonksiyonlar hangi sırayla
çalışıyor.
On altı parçanın sırası keyfî değil — her fonksiyon kendinden
öncekilerin üzerine kuruluyor. Kaynak yalnızca bir yerde ileriye bakıyor: G, hemen
ardından gelen T'yi çağırıyor. Bu gerçek bir karşılıklı özyineleme:
T her taş için G'yi çağırırken G de rok dalında
T'yi çağırıyor. Geri kalan her çağrı geriye bakıyor: bir fonksiyonu okurken,
çağırdığı her şey daha önce, yukarıda okunmuş oluyor.
Aşağıdaki bölümler kat haritasını izliyor: aşağıdan yukarıya
geometri, tehdit, geçerlilik, hüküm ve işlem. Bu motorda kat sırası bildirim sırasıyla aynı;
kaynağı soldan sağa okumak katları aşağıdan yukarıya tırmanmak demek.
Kat haritası
Motor beş kattan oluşuyor. En altta saf geometri: bir taş şu kareden şu kareye
gidebilir mi — hedefte ne olduğunu ve şahın şah altında kalıp kalmadığını hiç sormadan
(C, G)? Üstünde tehdit: bir kare tehdit ediliyor mu
(T)? Sonra geçerlilik: hamleyi gerçekten yapan, şahın güvenliğini sınayan
ve tahtayı eski hâline getiren kat (U, L, M). Sonra
hüküm: oyun bitti mi, bittiyse nasıl; mat hâlâ mümkün mü (V,
O, H, Z)? En üstte işlem: üç fonksiyon —
A hamleyi kalıcı hâle getirip hükmü döndürüyor, D talebi ve teklifi,
F bayrak düşmesini ve terki karara bağlıyor. Rok haklarını silmek, yarım hamle
sayacını ilerletmek ve sırayı değiştirmek onların değil, geçerlilik katındaki
M'nin işi. Sürücü oyunu bu en üst kattan yürütüyor; aşağıya yalnızca tıklamayı
doğrulamak için L'ye, şahı göstermek için T'ye iniyor.
Düz oklar kat sırasını izliyor: çağrılar aşağı iner ya da kendi katında kalır,
bazen bir katı atlayarak (L → G, Z → T,
A → M). Kesikli altın oklar bağımlılığın yukarıya döndüğü iki yeri
gösteriyor — ikisi de satranç kurallarının kendisinden doğuyor.
Bağımlılıklar neredeyse tamamen tek yönlü. Bu kural iki yerde bozuluyor:
G → T. Geometri katı rokta bir üst kata uzanıyor:
şaha şu an şah çekilip çekilmediğini ve şahın geçtiği karenin tehdit edilip
edilmediğini sormadan rokun geçerli olup olmadığına karar veremez. Bu yüzden G
ile T karşılıklı özyinelemeli — T her taş için G'yi
çağırırken G de rok dalında T'yi çağırıyor. Özyineleme sonsuza
gitmiyor, çünkü T'nin çağırdığı G tehdit modunda ve tehdit modunda
rok dalına hiç girilmiyor.
M → L. Hamleyi tahtada yapan M, geçerken
alma karesini kurarken geçerlilik katına geri dönüyor: kareyi ancak artık sırası gelen
taraftaki bir piyon o almayı geçerli olarak yapabiliyorsa tutuyor.
Tek bir katın içinde kalan özyinelemeler de var. G'nin ışın tarayıcısı
S kendini çağırıyor: kayan taşın yolu, piyonun çift adımı ve rokta şahla kale
arasındaki yol bu yürüyüşle sınanıyor. U rokta kaleyi taşımak için kendini bir
kez daha çağırıyor. H arama ağacında, V'nin iç döngüsü
g de doyma turlarında kendini çağırıyor.
Fonksiyon
Çağırdıkları
Kat
C
—
Geometri
G
S · C · T
Geometri
T
G
Tehdit
U
U
Geçerlilik
L
G · U · T
Geçerlilik
M
C · U · L
Geçerlilik
V
—yalnızca kendi yerel yardımcıları
Hüküm
O
L
Hüküm
H
T · V · O · M · H
Hüküm
Z
O · T · V
Hüküm
D
Z
İşlem
F
H
İşlem
A
M · Z
İşlem
Tahtanın kurulması
Tahtayı iki parça kuruyor, bir ifade ve bir döngü:
b=[c=un=15,e=y=t=8,n=o=0];
for(j in s=`dcebaecd${10n**40n-10n**32n}1111111154632645`)b[j*2-j%8]='0x'+s[j]-0;
Başlık bir bildirim listesi. İlk ifade bir dizi değişmezi (array literal),
ama elemanları atama: c=un=15, e=y=t=8, n=o=0. Değişmez
değerlendirilirken bütün başlangıç durumu yazılıyor — t=8 ile sıra beyazda,
c=15 ile dört rok hakkının hepsi açık, e=8 ile geçerken alma karesi
yok, n=o=0 ile yarım hamle sayacı ve teklif bitleri sıfır — ve ortaya çıkan
[15,8,0] dizisi b oluyor. Böylece durum değişkenleri ile tahtanın
dizisi aynı köşeli parantezi paylaşıyor; ayrı bir b=[] yazılmıyor. Üç eleman
yalnızca yer tutucu: ardından gelen döngü a1, b1 ve c1'i 0, 1 ve 2 indekslerine yazıp onları
siliyor. Saat motorda yok; süreyi sürücü tutuyor.
Dizilişin dizgisi. Şablon dizgisi 64 karakter üretiyor: beyazın birinci yatayı
dcebaecd, ortada 40 basamak, sonra siyahın piyonları 11111111 ve
siyahın sekizinci yataydaki taşları 54632645. Ortadaki 40 karakter kaynakta
yazılı değil; çalışma anında 10n**40n-10n**32n ile üretiliyor. Bu fark
10n**32n * (10n**8n - 1n)'e eşit: sekiz tane 9 (beyazın piyonları) ve
arkasından otuz iki tane 0 (dört boş yatay), tam 40 basamak. BigInt olması şart:
aynı sayı sıradan bir Number olsaydı kayan nokta yuvarlaması basamaklarını
bozardı.
Düz indeksten 0x88'e.for…in dizginin indekslerini geziyor: 0'dan
63'e, ama sayı olarak değil, "0"–"63" dizgileri olarak; aritmetik
onları kendiliğinden sayıya çeviriyor. j düz tahtanın indeksi, 8 × yatay +
sütun; j*2-j%8 onu 0x88 indeksine çeviriyor: j*2 16 × yatay +
2 × sütun, sütun bir kez çıkarılınca 16 × yatay + sütun kalıyor. s[j] de o
indeksteki karakteri veriyor. Yazılmayan hücreler — her yatayın sağındaki sekizer hücre — hiç
atanmıyor; delikler buradan doğuyor. Dizgiyi yayma operatörüyle karakterlere bölüp
map ile gezen bir ifade 83 bayt tutardı; döngü 80. Bedeli iki ad: j
ile s global kalıyor. Döngü bir deyim olduğu için de önceki ve sonraki parçadan
virgülle değil noktalı virgülle ayrılıyor.
Değer dönüşümündeki incelik operatör önceliğinde: + ile
- aynı öncelikte ve soldan sağa işliyor, yani önce '0x'+s[j]
birleşiyor — 'd' için '0xd' — sonra -0 o dizgiyi sayıya
dönüştürüyor ve JavaScript 0x önekini onaltılık okuyup 13 veriyor.
+0 yazılsaydı birleştirme sürer ve ortaya '0xd0' çıkardı.
Son ifade tekrar tablosunu, başlangıç pozisyonu bir kez sayılmış olarak kuruyor:
R=G[k=b+e+t+c]=1
Anahtar dört parçadan oluşuyor: dizi metne çevriliyor — 120 hücre, virgülle ayrılmış;
delikler boş dizgiye dönüştüğü için her yatayın ardındaki sekiz delik art arda virgüller
olarak görünüyor — ve arkasına geçerken alma karesi, sıra ve rok maskesi ayraçsız
ekleniyor. Tablo ayrı bir nesne değil, G fonksiyonunun kendisi; anahtarlar onun
özellikleri oluyor.
Ayraç olmaması ilk bakışta çakışma riski gibi görünüyor ve bu kez gerçekten de tek bir
ayrıştırma garanti değil: son virgülden sonra h8'in kodu (bir ya da iki basamak),
e (8 ya da 32–39 / 80–87, bir ya da iki basamak), t (8 ya da 0) ve
c (0–15) art arda duruyor. Bütün olası değerleri tek tek denemek tek bir çakışma
ailesi buluyor: h8'de siyah piyon (1) ve e g3 (38) ile
h8'de beyaz kale (13) ve e yok (8) — ikisi de
138… diye yazılıyor. Bu durumu satranç kuralının kendisi dışarıda bırakıyor:
siyah piyon h8'de hiçbir zaman duramaz, çünkü siyah piyonlar 7. yatayda başlar ve yalnızca
1. yatay yönünde ilerler. Geriye kalan her değer bileşimi tek bir biçimde okunuyor.
Geometri — G
G(i,f,m) tek bir soruya cevap veriyor: i karesindeki taş
f karesine tahtanın geometrisi açısından gidebilir mi? Hedefte kendi
taşının olup olmadığına da, hamlenin şahı şah altında bırakıp bırakmadığına da bakmıyor.
İkisi de üst katlara bırakılmış. 236 bayt — V'den sonra dosyanın en uzun
ikinci fonksiyonu.
Üçüncü argüman mtehdit modu. Açıkken üç şey değişiyor: rok devre dışı kalıyor
(rok bir tehdit değildir), piyonun çapraz karesi üzerinde taş olmasa bile sayılıyor (piyon o
kareyi tehdit eder) ve piyonun yönü ters çevriliyor, çünkü tehdit eden piyon sırası
gelen tarafın değil, rakibinin piyonu. Yani G(j,x,1) “j'deki rakip
taş x karesini tehdit ediyor mu” demek.
Parametre listesi bir hesap tablosu
Varsayılan parametreler gövdeden önce ve soldan sağa değerlendirildiği için
G'nin başlığı, anahtar kelime harcamadan bir let bloğu işi
görüyor. Gövde çalışmaya başladığında geometrinin tamamı çıkarılmış oluyor:
İlk dikkat çeken şey taşın renginin hiç okunmaması: tür başlıkta P=b[i]&7
ile çıkarılıyor, piyonun yönü ise taşın renginden değil sıradan, t'den geliyor.
Bu, L'nin yalnızca sırası gelen tarafın taşı için çağrılması şartına
yaslanıyor; rengi okumak bayt harcardı.
Geri kalanı 0x88'in sağladığı geometri. Çıkarılan değerlerden üçü daha yakından bakmayı
hak ediyor:
d=f-i. İki karenin farkı. 0x88'de yatay ve sütun
farklarının her bileşimine ayrı bir değer düştüğü için hattı bu tek sayı belirliyor; yatay ve
sütun mesafesini ayrı ayrı hesaplamaya gerek yok.
x: adım büyüklüğü. İç içe üç kalan testi: fark 15'e bölünüyorsa
bir çaprazdır ve adım 15; 16'ya bölünüyorsa sütundur ve adım 16; 17'ye bölünüyorsa öbür
çaprazdır ve adım 17; hiçbiri değilse adım 1. Sonuncusu hem yatayı hem de hiçbir hatta
düşmeyen bütün farkları kapsıyor. s aynı adımın işaretlisi: yön tablosu yok,
ofset dizisi yok — üç kalan ve bir işaret.
S. Işın tarayıcı. i'yi s kadar
ilerletir; f'ye vardıysa doğru döndürür, varmadıysa ve hücre boşsa
(b[i]<1) kendini yeniden çağırır. Taşa rastlayınca durur, deliğe rastlayınca
da: undefined<1 yanlıştır. Aradaki bütün karelerin boş olması bu tek satırla
sınanıyor. Piyonun çift adımı ve rok da aynı tarayıcıyı kullanıyor.
Hiçbir hatta düşmeyen bir fark, örneğin a1'den b3'e, adım 1 alır. Böyle bir çiftte
tarayıcı yatay boyunca yürür ve f'ye varamadan ilk delikte durur — 0x88'de her
yatayın iki ucunda bir duvar var. 64 kareli bir tahtada bu geometrilerin dal koşullarıyla
önceden elenmesi gerekirdi; burada onları delikler eliyor. Aynı duvarlar bozuk girdileri de
zararsızlaştırıyor. Aynı kare iki kez verilirse d sıfır, adım 15 olur ve yürüyüş
ilk taşta, ilk delikte ya da dizinin sonunda biter. Tahtada şah yoksa T-1 karesini sorar ve yürüyüş yine dizinin dışında biter. Cevaplar anlamsız, ama
hiçbir yürüyüş sonsuza kadar sürmüyor.
Dört dal, altı taş
Gövde tek bir üçlü operatör zinciri. Her halka türden bir sayı çıkarıp sıfırı arıyor:
P-4 sıfırsa at, P-2 sıfırsa şah, P-1 sıfırsa piyon.
Zincirin son dalı kalan üç taşı — vezir, kale ve fil (3, 5, 6) — birlikte ele alıyor.
At.d*d%765%128==68. 0x88'de atın sekiz hamlesi dört farka iniyor:
±14, ±18, ±31, ±33. Karesi alınınca işaret düşüyor (196, 324, 961, 1089); 765'e göre kalan,
büyük ikisini küçük ikisinin üstüne katlıyor (961 → 196, 1089 → 324); 128'e göre kalan da 196
ile 324'ü aynı sayıya, 68'e indiriyor. Tahtada iki kare arasında oluşabilecek her farkı tek
tek denemek, yalnızca bu sekiz farkın 68 verdiğini gösteriyor. Sekiz yönlü bir tablo yerine
bir çarpma ve iki kalan. Hedef bir delikse atın hamlesi yine “geometrik olarak uygun” çıkar,
ama L'nin hedef süzgeci delikleri zaten eliyor.
Şah. İki dal; onları ~m+3%d ayırıyor. ~m normal modda −1,
tehdit modunda −2; 3%d ise yalnızca fark ±2 iken 1 (fark ±1 ya da ±3 iken 0, daha
büyükse 3). Toplam yalnızca normal modda ve yatay boyunca iki karelik bir harekette sıfır
oluyor; rok dalına ancak o zaman giriliyor, tehdit modunda hiç girilmiyor. Öbür dal
d==s: fark tek bir adıma eşit mi, yani hamle herhangi bir yöne tek kare mi?
Rokun bütün şartları tek zincirde. Önce hedef kalenin karesine çevriliyor:
f+=s-!~s — kısa rokta s 1'dir, ~1 −2, !−2
yanlış, dolayısıyla f1 ilerliyor: g1'den bir kare ileri, h1'e. Uzun rokta
s −1, ~−1 0, !0 doğru, dolayısıyla f−2 kayıyor: c1'den iki kare geri, a1'e. Asimetri üçlü operatör kullanmadan çözülüyor.
Kalenin karesi aynı anda global r'ye yazılıyor; kaleyi taşıyacak olan
U onu oradan okuyacak. c&C(r) o kaleye bağlı rok hakkının hâlâ
maskede olduğunu sınıyor. T()+T(i+s)<S() kalan üç şartı tek bir
karşılaştırmada soruyor. T() şaha şu an şah çekilip çekilmediğini,
T(i+s) şahın geçtiği karenin tehdit edilip edilmediğini söylüyor;
T'nin varsayılan karesi sırası gelen tarafın şahı, yani i. İkisi de
yanlışsa toplam 0. Sağdaki S() şahtan kaleye yürüyüp aradaki karelerin boş olduğuna
bakıyor; uzun rokta b1 de bu yürüyüşün içinde. Yol açıksa 1. Karşılaştırma yalnızca
0 < 1 iken doğru. İfade soldan sağa değerlendirildiği için S yine tehdit
testlerinden sonra çalışıyor; yürüdükçe i'yi değiştirdiği için buna mecbur. Kalenin yerinde
durduğunu ayrıca sınamak gerekmiyor: hak, kale oynadığı ya da alındığı anda M'de
siliniyor. Şahın varacağı kare burada sınanmıyor, çünkü o karedeki tehdidi
zaten L'nin deneme hamlesi yakalıyor.
Piyon. Önce yön: m^d<d*t. Beyaz sıradayken t 8'dir ve
d<8d ancak d pozitifse doğru — beyaz indeksi büyütmek zorunda.
Siyah sıradayken t 0'dır ve d<0 doğrudan “indeks küçülüyor mu”
sorusu. Sıranın değerini bir çarpan gibi kullanmak, iki rengi tek bir karşılaştırmaya
sığdırıyor. Tehdit modunda m^ cevabı ters çeviriyor: soru artık rakibin piyonu
hakkında. Yön tutmazsa üçlü operatör 0 döndürüyor ve dallara hiç girilmiyor. Tutarsa iki dal
var; onları x-16 ayırıyor: hamle sütun boyuncaysa itiş, değilse alma.
Alma.1&~x%d hamlenin tam olarak tek bir çapraz adım olup olmadığını
soruyor. ~x%d, (-x-1)%d demek; fark tek bir adımsa (±15 ya da ±17)
kalan −1, yani tek; iki adım ve fazlasında −16 ya da −18, adım 1 ise 0 ya da −2 — hep çift.
Ardından m|b[f]|f==e üç koşuldan birini kabul ediyor: tehdit modu, hedefte bir
taş (rakibin taşı olduğunu L'nin süzgeci zaten sağladı) ya da hedefin geçerken
alma karesi olması. Üçünün veya ile bağlanması, geçerken almayı ayrı bir dal
açmadan sıradan bir almaya çeviriyor.
İtiş. Önce mesafe: d/s<2+(i%96<24) — adım sayısı başlangıç
yatayında üçten, başka her yerde ikiden küçük olmalı; yani başlangıç yatayından en çok iki
kare, başka yerden tek kare. Karşılaştırmanın sonucu sayı olarak 1 ya da 0;
>b[f] onu hedefteki taşla kıyaslıyor ve ancak hedef boşsa (b[f]
0) 1, 0'dan büyük çıkıyor. Tek bir işleç hem mesafeyi hem boş hedefi soruyor; hedef bir
delikse b[f]undefined ve kıyas yanlış. Kalanı
S() yapıyor: tek kare ilerlemede ilk adımda hedefe varıp doğru döndürüyor; çift
adımda önce aradaki kareye bakıyor ve o boşsa hedefe varıyor.
İtiş dalında tehdit modunu soran bir terim yok; tehdit modunda bir piyon, önündeki boş
kareyi “tehdit ediyor” görünebilir. T bunu yalnızca roktaki geçiş karesi için
sorduğunda görebilir — sorgulanan kareler arasında boş olan tek kare o. Ama f2'deki siyah bir
piyonun f1'i “tehdit etmesi” için zaten e1'deki şaha şah çekiyor olması gerekir ve rok, şah
altındayken zaten reddediliyor. Bu yanlış pozitif hiçbir cevabı değiştirmiyor.
i%96<24 iki yatayı birden nasıl seçiyor? Bir yatayın indeksleri
16 × yatay ile başlıyor. 96'ya bölümünden kalanı 24'ten küçük olanlar 0–23 ve 96–119, yani
birinci, ikinci, yedinci ve sekizinci yataylar. Piyon birinci ve son yatayda hiç durmadığı
için bunlardan geriye yalnızca ikinci ve yedinci yatay kalıyor — beyazın ve siyahın piyon
yatayları. Karşılaştırma true ya da false verdiği için
2+(i%96<24) başlangıç yatayında 3, başka her yerde 2 oluyor.
Kayan taşlar. Zincirin son dalı vezir, kale ve fili birlikte ele alıyor ve bunu
tür numarasının tekliğini okuyarak yapıyor. P+P*4%x tek çıkarsa taş o
yönde hareket edebiliyor:
Taş
P
yatay (x 1)
sütun (16)
çapraz (15)
çapraz (17)
Vezir
3
3
15
15
15
Kale
5
5
9
10
8
Fil
6
6
14
15
13
Düz hatta 4P, 1'e ya da 16'ya bölününce kalan hep çift; teklik
P'nin kendisinden geliyor ve tek türler, vezirle kale, geçiyor. Çaprazda
4P yalnızca 5 ile 6'da 15'i ve 17'yi aşıyor; o zaman tek olan bölen bir kez
çıkarılıyor ve teklik dönüyor. 3'teki vezir bu eşiğin altında kalıp tek kalıyor; 5 ile 6 ise
tekliklerini değiştiriyor: kale çifte, fil teke dönüyor. Bu en alt bit & ile
S()'nin sonucuna bağlanıyor: yol kapalıysa hamle geçmiyor. Vezir için ayrı bir dal
yazılmamış; dört sütunun dördünde de tek çıkıyor.
&& yerine & kullanılması bir bayt kazandırıyor ama kısa
devreyi kaldırıyor: S() teklik tutmasa da çalışıyor. Bedeli boşa giden bir yürüyüş;
sonuç değişmiyor. Boş bir kare de bu dala düşer — P 0'dır, ifade 0 kalır — ve
T ile O'nun süzgeçlerinden sızan boş kareler zararsız biçimde burada
eleniyor.
Tehdit — T
Geometri katı tek bir taşla ilgileniyor; tehdit katı bütün tahtayı yokluyor — 52
baytta.
T(x) şunu soruyor: x karesi, sırası gelen tarafın
rakibi tarafından tehdit ediliyor mu? Süzgeç p&8^t: taşın renk biti
(8 biti) ile sıranın özel veyası; renkler farklıysa 8. Ardından G(j,x,1), tehdit
moduyla. some bulduğu ilk saldırganda duruyor; tam bir tarama yalnızca kare
gerçekten güvenliyse yapılıyor ve some delikleri atladığı için tarama 64 kareyle
sınırlı.
Asıl incelik parametrenin varsayılanında: x=b.indexOf(2+t) şahı arayıp
buluyor — şahın türü 2, kodu da 2 + renk; t rengin kendisi olduğu için
2+t doğrudan sırası gelen tarafın şahı. Yani x verilmezse soru
kendiliğinden “sırası gelen tarafa şah çekilmiş mi” sorusuna dönüşüyor.
Bir taraf parametresi yok; soru hep sırası gelen taraf için. Bu yetiyor, çünkü motorda
sorulan her şah o tarafın şahı: L'nin denemesinde sıra henüz değiştirilmemiştir,
hamleyi yapan hâlâ t'dir; Z ile H'nin yaprak testi de
oynayacak tarafın şahını soruyor.
Şahın yeri hiçbir yerde saklanmıyor; her çağrıda yeniden aranıyor. Bir önbelleğin her
hamlede güncellenmesi, üstelik L'nin geri aldığı hamlelerde de doğru kalması
gerekirdi. Aramak daha ucuz.
Boş kareler süzgeçten sızıyor: boş karede p&8 sıfırdır ve
t 8 iken özel veya 8 verir. Yani beyaz sıradayken T, siyah
taşlarla birlikte bütün boş kareleri de G'ye soruyor; siyah sıradayken boş kareler
süzülüyor. Zararsız olmasının sebebi bir kat aşağıda: boş karede P sıfır olduğu
için G kayan taş dalına düşer ve ifade 0 kalır. Ama bedava değil: o dal
S()'yi yine de yürütüyor. Aynı sızıntının aynası O'da,
bu kez siyahın sırasında.
Geçerlilik — L, U ve M
L — süz, dene, geri koy
Geometri bir hamlenin şekline bakıyor, geçerlilik sonucuna. Aradaki fark tek bir kural:
kendi şahını şah altında bırakan hamle geçersizdir. Bunu statik olarak sınamanın kısa bir yolu
yok, o yüzden L hamleyi gerçekten yapıyor, şahı soruyor ve tahtayı geri
koyuyor. Tek bir hamle hakkında evet ya da hayır diyor; bir taşın bütün hamlelerini isteyen
her kareyi ayrı ayrı sormalı — O'nun yaptığı gibi. 54 bayt.
Süzgeç tek bir kaydırma. Toplamanın önceliği kaydırmadan yüksek olduğu için ifade
(b[x]+~t)>>3. ~t beyazın sırasında −9, siyahınkinde −1.
Beyazın sırasında boş kare −9'u, siyah taş −8 ile −3 arasını verir; sağa üç kaydırınca ikisi de
negatif kalır, yani doğru sayılır. Beyaz taş 0 ile 5 arasını verir ve kaydırınca 0 olur.
Siyahın sırasında boş kare −1'i, beyaz taş 8 ile 13 arasını verir (kaydırınca −1 ve 1); siyah
taş 0 ile 5 arasını verir ve 0'a iner. Delik undefined'dır, toplam
NaN, kaydırma 0. Tek bir ifade boş kareyi ve rakip taşı geçiriyor, kendi taşını ve
tahta dışını eliyor — G hiç çalışmadan. Çıkış karesinin kendisi de bu yüzden
eleniyor: orada kendi taşı duruyor.
Yedek bir referans.q=b varsayılan parametre olarak doğuyor, bir
let harcanmıyor. Tahtayı L kopyalamıyor: kopyalama
U'nun ilk adımı. U yeni bir dizi üretip b'yi ona
bağlıyor, eski dizi nesnesine hiç dokunmuyor; geri koyma da bu yüzden tek bir atama:
kopyalama değil, eski referansı yerine takma.
Harf yeniden kullanılıyor. Deneme bitince hedef kare artık gerekmiyor;
bu yüzden x artık T()'nin cevabını tutuyor. Virgüllü dizide önce tahta geri
konuyor, sonra !x döndürülüyor: şah tehdit altında değilse hamle geçerli.
U(i,x) terfi türünü belirtmeden çağrılıyor, yani varsayılan vezir
kullanılıyor — geçerlilik açısından fark etmez, çünkü şahın şah altında kalıp kalmadığı varış
karesinin dolu olmasına bağlı, üzerindeki taşın türüne değil.
L'nin iki yan etkisi var ve ikisi de bilerek bırakılmış: rok dalında
G kalenin karesini r'ye, deneme hamlesinde U terfi
bayrağını X'e yazıyor. Böylece L(i,f) bir sonraki
A(i,f)'yi hazırlamış oluyor; sürücünün ikisini art arda çağırması bundan.
U — ham hamle
U hamleyi tahtada yapıyor, başka hiçbir şey yapmıyor: sırayı değiştirmiyor,
sayaçlara ve haklara dokunmuyor. L'nin deneme hamlesi de, M'nin
gerçek hamlesi de bu. Geri alma işi çağıranın. 134 bayt.
Başlık yine bir hesap tablosu. P=b[i]-t taşın türü: sırası gelen tarafın
taşından renk çıkarılınca geriye tür kalıyor (beyaz kod − 8, siyah kod − 0).
d=P*(i-f)**2 iki özel hamlenin parmak izi: rokta şah (2) iki kare gider,
2 × 4 = 8; piyonun (1) çift adımında fark 32'dir, 1 × 1024 = 1024. Bir taşın
yapabileceği başka hiçbir hamle bu iki sayıdan birini vermiyor. Q iki karenin
ortası: rokta kalenin ineceği kare, çift adımda piyonun üzerinden atladığı kare.
Kopya, varış karesi ve terfi.b.slice() tahtayı kopyalıyor ve
delikleri olduğu gibi bırakıyor; b yeni diziye bağlanıyor. Terfi testi
X=P-1|f%112>7: yalnızca taş piyonsa (P-1 sıfır) ve
f%112 en çok 7 ise, yani f birinci ya da sekizinci yataydaysa sıfır.
O zaman seçilen tür yazılıyor — p|t, yani tür artı sırası gelen tarafın
rengi; terfi değilse taşın kendisi. Bayrak global X'te kalıyor;
H terfide dört taşı ayrı ayrı denemek için onu okuyor.
Çıkış karesi ve geçerken alınan piyon. Zincirleme atama sağdan sola işliyor.
Sıradan bir hamlede çıkış karesi iki kez sıfırlanır. Piyon geçerken alma karesine vardıysa
(e-f ve P-1 ikisi de sıfır) silinen kare f^16 olur ve
bu ifade yönü kendiliğinden buluyor: geçerken alma karesi ya üçüncü yatayda (indeks
0x2_) ya altıncıda (0x5_) durur; 16 ile özel veya, yatay
basamağının en düşük bitini çeviriyor: 2'yi 3'e, 5'i 4'e — iki durumda da alınan piyonun
durduğu yataya: e3 → e4, e6 → e5.
Geçerken alma adayı ve rok kalesi. Son satır iç içe bir üçlü operatör. Parmak izi
8 değilse dönüş değeri geçerken alma adayı: parmak izi 1024 ise atlanan kare, değilse
8. Karenin gerçekten tutulup tutulmayacağına M karar veriyor.
Parmak izi 8 ise U kendini bir kez daha çağırıp kaleyi r'den
ortadaki kareye taşıyor — f1, d1, f8 ya da d8; r'yi az önceki L
çağrısında G'nin rok dalı yazmıştı. Dönüş değeri de o iç çağrınınki: taş kale
olduğu için parmak izi 20 ya da 45 çıkıyor, özyineleme orada bitiyor ve 8
dönüyor — rokta geçerken alma adayı yok.
M — altı adım, sabit bir sırayla
M hamleyi oynuyor ve durumu güncelliyor: yarım hamle sayacı, rok hakları, tahta,
sıra, geçerken alma karesi ve anahtar. Tekrar tablosuna yazmıyor. Rok hakları ve sayaç burada,
hamlenin yapıldığı yerde güncelleniyor; çünkü arama da hamle oynuyor ve c
güncellenmeseydi dallarda çoktan kaybedilmiş bir rok hakkı hâlâ geçerli görünürdü. Geri alma
işi çağıranın: arama [b,e,n,t,c,k]'yı saklıyor. Kalıcı olan son etki, tekrar
sayımı, bir kat yukarıda, A'da. 109 bayt.
Yarım hamle sayacı.!b[x] hedef boşsa 1, doluysa 0; çıkış taşıyla
çarpınca alımda 0, sessiz hamlede taşın kodu kalıyor. &6 piyonda (1 ve 9)
sıfır, öbür bütün taşlarda sıfırdan farklı. Sonuç sıfırsa — bir taş alındı ya da piyon
oynadı — && o sıfırı döndürüp sayacı sıfırlıyor; değilse sayaç bir
artıyor. Geçerken almada hedef boş ama taş piyon: sayaç yine sıfırlanıyor. Kuralın tamamı
tek ifade.
Rok hakları. İki ayrı kural tek ifadede toplanıyor. C(i) çıkış
karesine bakıyor: şah kendi karesinden ya da kale kendi köşesinden ayrıldıysa ilgili hak
silinir. C(x) varış karesine bakıyor: bir kale kendi köşesinde
alındıysa o hak da silinir. Şahta iki hak tek bir karede toplanıyor:
C(e1) 3 döndürüyor, bu yüzden rok da dahil her şah hamlesi beyazın iki hakkını
birden siliyor.
Tahta.U hamleyi tahtada yapıyor ve geçerken alma adayını döndürüyor.
Sıra.t^=8.
Geçerken alma karesi. Aday ancak yeni sırası gelen taraftaki bir piyon
o almayı gerçekten yapabiliyorsa kalıyor: p==1+t o tarafın piyonlarını seçiyor
(siyah için 1, beyaz için 9), L(j,e) de almayı eksiksiz geçerlilik denemesiyle
soruyor — açmazdaki bir piyon bu yüzden geçerken alma hakkı doğurmuyor. Aday yoksa
(e 8 ise) L daha süzgeçte, deliği görüp duruyor; tarama her hamlede
koşuyor ama en çok sekiz piyona bakıyor.
Anahtar.k=b+e+t+c yeni pozisyonun dizgisi; M onu
döndürüyor, A da pozisyonu tekrar tablosunda onunla sayıyor.
Sıra sabit. Sayaç (1) hedef karedeki taşa bakıyor, o yüzden U'dan (3) önce
gelmeli. Geçerken alma denetimi (5) bitmiş tahtada ve yeni sırayla bir geçerlilik sorusu
soruyor, o yüzden U'dan ve sıranın değiştirilmesinden (4) sonra gelmeli. Anahtar
(6) her şeyden sonra. Yeri değiştirilebilecek tek adım rok hakları (2); anahtardan önce kaldığı
sürece hiçbir şey değişmiyor.
Beşinci adımın bu kadar titiz olmasının sebebi tekrar sayımı.
Pozisyonun tanımı geçerken alma imkânını da içerdiği için, kimsenin kullanamayacağı bir kare
yazılsaydı aynı pozisyon iki farklı pozisyon gibi sayılır ve üçlü tekrar talebi sessizce
kaçırılırdı.
Rok hakkı — C
Dört rok hakkı c'de dört bit. Hangi karenin hangi bite bağlı olduğunu
söyleyen fonksiyon 28 bayt — dosyanın en kısası.
C=i=>'20003001'[i%112]<<i/56
İki adım. i%112 birinci ve sekizinci yatayları aynı sekiz indekse katlıyor:
a1 (0) ve a8 (112) sıfıra, e1 ve e8 dörde, h1 ve h8 yediye. Aradaki altı yatay 16–111
aralığında kalıyor, yani sekiz karakterlik dizginin dışında — orada undefined
okunuyor. Dizginin kendisi tablo görevi görüyor.
Sonra <<i/56. Kaydırma operatörü iki işleneni de tam sayıya çeviriyor:
'2' 2'ye, undefined 0'a dönüyor; sağ işlenen ise birinci yatayda 0,
sekizinci yatayda 2 çıkıyor (112–119 bölü 56, 2 ile 2,125 arası).
Kare
i%112
Karakter
Kaydırma
Bit
h1
7
1
0
1
a1
0
2
0
2
h8
7
1
2
4
a8
0
2
2
8
e1
4
3
0
3
e8
4
3
2
12
diğer 58
—
'0' / yok
—
0
Bir şah hamlesinin iki biti birden silmesi dizgideki '3' sayesinde.
C'nin iki kullanıcısı var. M'nin ikinci adımı hak silme işinin
tamamını yapıyor: c &= ~C(i) & ~C(x). Çıkış karesi şahın ya da kalenin
oynadığını, varış karesi ise bir kalenin kendi köşesinde alındığını gösteriyor. İki
ayrı kural, tek ifade. Diğer bütün kareler sıfır döndürdüğü için tümleyenleri -1
olur ve maskeyi hiç değiştirmez. G'nin rok dalı ise c&C(r) ile
aynı tabloyu ters yönden okuyor: o kaleye bağlı hak hâlâ duruyor mu?
Burada saklanan şey hak, rokun o anki yapılabilirliği değil — ayrım
da kural kitabının kendisinden geliyor. Yolda bir taş durduğu için rok o an yapılamıyorsa bit
yerinde kalır.
Mat edilemezlik — V
Ölü pozisyonun da (5.2.2), bayrak düşmesi ve terkteki istisnanın da
(6.9 ve 5.1.2) dibinde aynı soru var: bir taraf, rakibi tarafından mat
edilebilir mi? V bu soruyu statik olarak, yalnızca tahtaya bakarak cevaplıyor —
tek bir hamle oynamadan. 405 bayt ile dosyanın en uzun fonksiyonu.
Soru tek taraflı: V(s) yalnızca s'nin mat edilmesinin imkânsız
olup olmadığını soruyor. Doğru döndürürse imkânsız; yanlış döndürürse mümkün ya da statik olarak
karar verilemiyor. İki yerden, iki farklı iş için çağrılıyor.
Z her hamleden sonra iki taraf için de soruyor:
V(0)&V(8) — iki taraf da mat edilemiyorsa pozisyon ölüdür, DP.
H ise arama sırasında, her düğümde ve yalnızca
I için soruyor: bu hatta I artık mat edilemiyorsa hat kapanır. Yani
V, aynı kattaki H'nin yalnızca komşusu değil; H'nin
işinin de bir parçası, onun içinde çağrılıyor.
Gövde üç terimden oluşuyor. İlk ikisi materyale bakıyor, üçüncüsü yapıya; sayımı yapan tahta
taraması ise gövdeden önce, parametre listesinde koşuyor:
V=(s, … sayım ve yardımcılar …)=>
!(s?B:W) > m rakipte piyon da taş da yok: çıplak şah
| !(W|B) > (4%m | o&(m>3?15423:63^m)) piyon yok; tek renkte filler ya da tek at, yardımcı yok
| !(o|m|e-8 || g(99,A=S=W,a=s=B)) yalnız şah ve piyon: kilitli yapı
Yetersiz materyal — V'nin materyal yarısı
Sayım tahtayı bir kez tarıyor ve iki şeyi birden hazırlıyor: iki tarafın materyal
toplamlarını ve kilitli yarının kullanacağı piyon ve şah bitboard'larını. Bütün bunlar
[m,o]'nun varsayılan değerinin içinde gerçekleşiyor:
[m,o]=(W=B=0n, b.map((p,i)=>(
p%8>5 ? A[p>7^!s] |= i%15%2+1 fil: kare rengini maskeye yaz
: p%8>2 ? A[p>7^!s] += 4<<p*8 vezir 2²⁶, at 4, kale 1024
: p&8 ? p>9 ? _=j : W|=j beyaz şah ve piyonlar: bitboard
: p>1 ? f=j : p ? B|=j : 0, siyah şah ve piyonlar: bitboard
j+=j)), A)
İki tarafın hesabı.A=[0,0] iki ayrı toplam tutuyor; eklemenin
hangisine yapılacağını p>7^!s seçiyor. p>7 taş beyazsa
doğru; !s ile alınan özel veya, sorulan taraf siyahsa bu sonucu ters çeviriyor.
Böylece indeks 1 sorulan tarafın kendi taşını, 0 rakibin taşını gösteriyor. Tarama bitince
[m,o]=A iki toplamı iki ada dağıtıyor: m rakibin materyali,
o sorulan tarafınki.
Filler kare rengiyle. Önemli olan fillerin sayısı değil, bulundukları karelerin
rengi. 0x88'de i 16 × yatay + sütun; 16'yı 15'e bölünce kalan 1 olduğu için
i%15 yatay + sütuna eşit (en çok 14 olduğundan 15'e hiç ulaşmıyor). Bu toplamın
tekliği karenin rengini veriyor: i%15%2+1 koyu karede 1, açık karede 2.
|= bu değeri bit düzeyinde veya ile ekliyor: aynı renkteki beş fil de yalnızca
tek bit yakıyor, iki renkte birer fil ise 3 yapıyor. Ölçüldü: f1'deki fil 2'yi yakıyor,
c8'deki fil de 2'yi — ikisi de açık karede —; d8'deki fil ise 1'i. Beyaz fil f1'de, siyah fil
c8'deyken Z 6 döndürüyor; siyah fil d8'e geçince 0.
Ağır taşlar ve atlar sayıyla.4<<p*8 her türü kendi bit alanına
ekliyor. JavaScript, kaydırma miktarının 32'ye göre kalanını kullanıyor: vezirde
3*8 24 ediyor, sonuç 2²⁶; atta 4*8 32 ediyor, kalan 0, sonuç 4;
kalede 5*8 40 ediyor, kalan 8, sonuç 1024. Beyaz taşların kodları 8 fazla olduğu
için çarpım 64 fazla çıkıyor; 64'ün 32'ye göre kalanı sıfır olduğundan renk biti
kendiliğinden düşüyor. += toplayarak saydığı için atlar 2–9., kaleler 10–25.
bitlerde, vezirler de 2²⁶'dan yukarıda birikiyor; tahtada bulunabilecek hiçbir taş sayısı
bir alanı komşusuna taşırmıyor.
Piyonlar ve şahlar bitboard'a. Kalan kodlar, yani piyon ve şah, BigInt kümelere
yazılıyor: W ile B beyaz ve siyah piyonlar, _ ile
f beyaz ve siyah şah. j her karede ikiye katlanıyor ve
map delikleri atladığı için bit sırası düz tahtanınkiyle aynı: 8 × yatay +
sütun. Tarama bitince j 2⁶⁴ oluyor; kilitli yarı bu değeri bir sınır olarak
kullanıyor.
Ardından iki terim geliyor. Birincisi!(s?B:W)>m: rakibin
piyonu yok (sorulan taraf beyazsa siyah piyonlar, siyahsa beyaz piyonlar) ve m
sıfır, yani rakipte şahtan başka taş yok. Değil işlecinin sonucu sayı olarak 1 ya da 0; 1
ancak m sıfırsa ondan büyük. &!m yerine >m, bir
bayt. Çıplak şah, pozisyon ne olursa olsun mat edemez;
sorulan tarafın kendi piyonları burada bir şey değiştirmiyor.
İkincisi, tahtada hiç piyon yokken rakibin tek başına mat kuramadığı iki materyal
durumunu yakalıyor. 4%m yalnızca m 1, 2 ya da 4 iken sıfır: tek
renkte filler ya da tek bir at. İki renkte fil (3) 1 veriyor; iki at (8), fil ve at (5, 6),
kale ya da vezir 4 veriyor — hepsi “mat mümkün” demek. Asıl incelik bundan sonra geliyor: bu
materyalle mat, ancak sorulan taraf kendi şahının kaçış karesini kendi taşıyla kapatırsa
kurulabilir; yani bir yardımcı gerekiyor. o&(…) bu yardımcıyı
arıyor:
Rakipte tek at varsa (m>3) maske 15423, yani
onaltılık tabanda 0x3C3F: fil bitleri (0–1), at alanı (2–5) ve kale alanı
(10–13). Fil, at ya da kale kaçış karesini kapatabilir; vezir hiçbir zaman işe yaramadığı
için maskede yer almıyor.
Rakipte tek renkte filler varsa maske 63^m: 63 iki fil bitini ve at
alanını kapsıyor, özel veya da bunlardan rakibin fillerinin rengini çıkarıyor. Geriye kalan
yardımcılar karşı renkte bir fil ya da bir at. Aynı renkteki fil işe yaramıyor; kale ve
vezir de yaramıyor.
Yardımcı yoksa 4%m|o&… sıfır oluyor ve !(W|B) — tahtada
piyon yoksa 1 — ondan büyük çıkıyor: mat imkânsız. Kuralın kendisi ve vakaların tek tek dökümü
kurallar bölümünde.
Rakibin elindeki (tahtada piyon yok)
m
Matı mümkün kılan yardımcı
Yardımcı yoksa
Yalnız şah
0
—
İmkânsız (piyon olsun olmasın)
Tek renkte filler
1 / 2
Karşı renkte fil ya da at
İmkânsız
Tek at
4
Fil, at ya da kale
İmkânsız
İki renkte fil
3
—
Mümkün
İki at; fil ve at
8; 5 / 6
—
Mümkün
Kale ya da vezir
1024+
—
Mümkün
Tek taraflı soru, Z'de tek bir & ile iki taraflı maddeye
dönüşüyor. Beyazda şah ve at, siyahta yalnız şah varsa V(0) de
V(8) de doğru; pozisyon ölü. İki tarafta da şah ve at varsa siyahın atı kendi
şahına yardımcı olabildiği için V(0) yanlış; oyun sürüyor. Beyazda şah ve iki
at, siyahta yalnız şah varsa V(0) yanlış — mat mümkün —, V(8)
doğru; bu ayrım ölü pozisyon için değil, bayrak düştüğünde önem taşıyor. Beyazda şah ve at,
siyahta şah ve vezir varsa V(0) doğru, çünkü vezir yardımcı sayılmıyor;
V(8) yanlış.
Ölü pozisyon — V'nin kilitli yarısı
Ölü pozisyonun bir kolu materyal, öbür kolu yapı: taşlar yeterli olsa da pozisyon kilitli
olabilir. V'nin üçüncü terimi bu durumu tarıyor:
!(o|m|e-8||g(99,A=S=W,a=s=B)). Bu yarıya özgü kısım — yardımcılar, döngü,
çıkış ve kapı — 221 bayt tutuyor; tahta taraması ise iki yarının ortak işi.
Kapı. Döngü ancak üç koşul birden sağlanırsa başlıyor. o|m sıfır
olmalı: iki tarafta da şah ve piyondan başka taş bulunmamalı. Tek bir at, kale, vezir ya da
fil kapıyı kapatıyor ve doyma döngüsü hiç başlamıyor; filli kilitli
pozisyonların gözden kaçmasının tek satırlık nedeni bu: sorun filde değil, kapsamda.
e-8 de sıfır olmalı: bekleyen bir geçerken alma hakkı bulunmamalı. Bunun
gerekçesi koddan değil, satrançtan geliyor. Geçerken alma hakkı varken pozisyon ölü
olamaz: alan piyon, alınan piyonun az önce boşalttığı dikeye geçiyor ve orada önü
açılıyor. Bu, modelin göremediği bir yan sıçrama, çünkü ilerleyiş kümeleri yalnızca dikey
yönde büyüyor; bu yüzden modelin böyle bir pozisyonda hüküm vermesine hiç izin
verilmiyor.
Yardımcılar parametre listesinde tanımlanıyor ve hepsi BigInt. u=256n
bir yatay: 8 bit. c=~j/~-u bir sütun maskesi. Taramadan sonra j
2⁶⁴ olduğu için bu ifade −(2⁶⁴+1)/255 ediyor; tam sayı bölmesi sıfıra doğru kestiği için
sonuç −0x0101010101010101. İkiye tümleyen gösterimde bu, a sütununun ikinci ile
sekizinci yatay arasındaki bitleri dışında her biti yanık bir sayı — ilk 64 bitin
ötesindekiler de dahil. H=s=>s+s&c|(s&c)/2n bir kümeyi bir sütun
sağa ve sola yayıyor: sağa kaydırdıktan sonra maskelemek h sütunundan bir üst yatayın a
sütununa, sola kaydırmadan önce maskelemek de a sütunundan bir alt yatayın h sütununa
taşmayı engelliyor. N=s=>H(s|=s*u|s/u)|s kümeye önce bir yatay yukarısındaki
ve aşağısındaki kareleri ekliyor, sonra sonucu iki yana yayıp kümenin kendisini de geri
katıyor: tam bir şah adımı.
Kümeler renge bağlı. Motor tahtayı hep a1'den saydığı için beyaz piyonlar her zaman
yukarı (*u), siyahlar aşağı (/u) yürüyor. Döngü altı kümeyi
güncelliyor: S/s önü kapalı (tıkalı) beyaz ve siyah piyonlar,
A/a piyonların ilerleyebildiği kareler,
_/f şahların ulaşabildiği kareler. Tıkalı kümeler yalnızca
küçülüyor, öbür dördü yalnızca büyüyor. Her turda kümeler birbirini besliyor: şah ne kendi
tıkalı piyonunun karesine ne de rakibin tıkalı piyonlarının tehdit ettiği karelere
girebiliyor; piyon da rakip piyonun durduğu kareye ilerleyemiyor. Piyon kümeleri şahlara
hiç bakmıyor; şahın bir piyona ulaşıp ulaşamadığı çıkışta soruluyor. Hiçbir küme artık
değişmediğinde doyma noktasına ulaşılmış oluyor.
Çıkışta altı soru kalıyor. İkisi terfiyle ilgili: beyaz sekizinci yataya, siyah birinci
yataya ulaşabiliyor mu? Dördü temasla ilgili: iki taraftan herhangi birinin piyonu rakip
piyonu alabiliyor ya da rakip şaha şah çekebiliyor mu, herhangi bir şah rakip piyona
ulaşabiliyor mu? Altısının da cevabı hayırsa pozisyon nefes alamıyor demektir: ölü.
Doyma döngüsü, satır satır
Döngünün gövdesi altı atamadan ve bir özyinelemeden oluşuyor. İşin inceliği yazılış
biçiminde: g yalnızca n'yi alıyor ama kendini altı fazladan
argümanla çağırıyor. Argümanlar çağrıdan önce soldan sağa değerlendiriliyor, sonra
kullanılmadan atılıyor; yani argüman listesinin kendisi döngünün gövdesi. Ne noktalı virgül
var ne blok ne de return. n sıfıra inince çıkış ifadesinin değeri
döndürülüyor. Başlangıç değerleri de aynı yoldan veriliyor:
g(99,A=S=W,a=s=B).
g=n=>n?g(n-1,
S&=(B|S)/u, önü kapalı beyaz piyonlar
s&=(W|s)*u, önü kapalı siyah piyonlar
A|=A*u&~B, beyazın ilerleyişi: bir yatay yukarı
a|=a/u&~W, siyahın ilerleyişi: bir yatay aşağı
_|=N(_)&~S&~H(s/u), beyaz şah: bir adım daha
f|=N(f)&~s&~H(S*u)) siyah şah: bir adım daha
:A*u/j | a%u bir piyon hâlâ terfi edebilir mi
|H(A)*u&(a|f) beyaz piyon alabilir ya da şah çekebilir mi
|H(a)/u&_ siyah piyon şah çekebilir mi
|_&B | f&W bir şah rakip piyona ulaşabilir mi
Tıkanıklık.S&=(B|S)/u: /u öndeki kareleri bir yatay
aşağıya, piyonların kendi karelerine indiriyor. Bir beyaz piyon, önündeki karede bir siyah
piyon ya da zaten tıkalı sayılan bir beyaz piyon varsa tıkalı kalıyor; ikinci koşul aynı
dikeyde üst üste duran piyonları zincir hâlinde yakalıyor. S bütün beyaz
piyonlarla başlıyor ve yalnızca daralıyor: tıkalı kümeden düşen bir piyon, bir sonraki turda
arkasındakini de düşürüyor. s bunun siyah için aynası; siyah tahtada aşağı
yürüdüğü için kaydırma *u. Önündeki karede bir siyah piyonun durması kalıcı bir
engel: o siyah piyonun önünü de bu beyaz piyon kesiyor ve bu ikiliyi ancak bir alma
çözebilir — almaları da çıkış soruyor.
İlerleyiş.A|=A*u&~B: beyazın ilerleyiş kümesi piyonların kendi
karelerinden başlayıp her turda bir yatay daha yukarı uzanıyor, ama bir siyah piyonun
durduğu kareye giremiyor. a aynı işi siyah için aşağı doğru, beyaz piyonlarda
durarak yapıyor. Bu kümeler bir piyonun oraya gerçekten gidebileceğini değil, yalnızca
yolunda bir piyon bulunmadığını söylüyor.
Şahlar._|=N(_)&~S&~H(s/u): beyaz şahın erişimi kendi
karesinden başlıyor ve her turda bir şah adımı büyüyor. İki tür kareye giremiyor: kendi
tıkalı piyonlarının karelerine ve siyahın tıkalı piyonlarının tehdit ettiği karelere.
s/u bu piyonları siyahın alış yönünde bir yatay aşağı indiriyor, H
de bir sütun iki yana yayıyor; sonuç çapraz alış kareleri. f siyah şah için
aynı hesabın ayna görüntüsü. Yalnızca tıkalı piyonların tehdidi hesaba katılıyor; hareketli
piyonların tehdit ettiği kareler açık bırakılıyor ve bu, erişimi olduğundan büyük
gösterebilir — yanılgı her zaman “hayat var” tarafına düşüyor.
Çıkıştaki altı terim.A*u/j beyazın ilerleyişinin sekizinci yataya
değip değmediğini soruyor: o yatayda bir bit varsa A*u en az 2⁶⁴, yani
j oluyor ve bölüm sıfırdan farklı çıkıyor. a%u siyahınkinin
birinci yataya (0–7. bitler) değip değmediğini soruyor. H(A)*u&(a|f), bir
beyaz piyonun ilerleyişi boyunca herhangi bir kareden çapraz vurabileceği kareleri buluyor
ve bunlardan birinin bir siyah piyonun bulunabileceği bir kare (alma) olup olmadığını ya da
siyah şahın erişimine (şah) girip girmediğini soruyor. H(a)/u&_ aynı soruyu
siyah piyon ile beyaz şah için soruyor; siyah piyonun beyaz piyonu alması ise aynı çiftin
öbür taraftan görünüşü, önceki terimde zaten var. _&B ile
f&W bir şahın rakip bir piyonun karesine ulaşıp onu alıp alamayacağını
soruyor. Yalnız şahların ve piyonların kaldığı bir tahtada matı ancak bu temaslar
doğurabilir; hiçbiri yoksa pozisyon ölü.
Hiçbir küme kanıtlanmamış bir engeli varsaymıyor. Piyonların ilerleyişi yalnızca
piyonlarda, şahların erişimi yalnızca tıkalı piyonlarda ve onların tehdit ettiği karelerde
duruyor; şüpheli kalan her şey “mümkün” tarafına düşüyor. “Ölü” hükmünün tek yönlü
güvencesi buradan geliyor — ama yalnızca kümeler doymuşsa. Aşağıdaki 99 turun işi de bunu
sağlamak.
Başlangıç değerleri taramadan geliyor: W, B,
_ ve fsayımın içinde dolduruldu.
g(99,A=S=W,a=s=B) ilerleyiş ve tıkalı kümelerini piyonların kendisinden
başlatıyor. İki harf yeniden kullanılıyor: A az önce materyal toplamlarının
dizisiydi, artık beyazın ilerleyişi; s sorulan taraftı — birinci terim onu
çoktan okudu —, artık siyahın tıkalı piyonları. İki şahın da tahtada olup olmadığı ayrıca
sorulmuyor: geçerli her pozisyonda ikisi de var.
Altı sızıntı.W, B, _, f,
S ve a hiçbir yerde bildirilmiyor; bu yüzden global değişkenlere
yazılıyor. Bu zararsız: o global değişkenleri V'den başka okuyan yok;
G, U ve A'daki f ile G'deki
S o fonksiyonların kendi parametreleri. Tahta dışı bitler de var: sağa
kaydırma h8'i, *u da bütün sekizinci yatayı ilk 64 bitin ötesine taşıyor; şah
erişimi orada her turda bir yatay daha büyüyor. Bunlar hiçbir çıkış terimini tetiklemiyor:
piyon kümeleri 64 bitin içinde kalıyor ve beyazın ilerleyişi oraya ancak sekizinci yataydan
geçerek ulaşabiliyor — o durumda da ilk terim zaten doğru.
99 tur. Piyon kümeleri şahlara bağlı değil; bir dikeyde en çok altı piyon üst üste
durabildiği ve bir piyon en çok altı yatay ilerleyebildiği için tıkalı ve ilerleyiş kümeleri
en geç altı turda sabitleniyor. O andan sonra şahların giremediği kareler de değişmiyor ve
erişim tamamlanana kadar her tur en az bir kare ekliyor; 64 karelik bir tahtada bu en çok
63 tur sürüyor. Toplam 70 turu geçemiyor; döngü ise 99'da duruyor. Yani sınır ölçülmüş
değil, ispatlı. 99 da iki basamağa sığan en büyük sayı. Pratikte doyma çok daha
erken geliyor: ölçtüğüm kilitli pozisyonlarda dört ile sekiz tur arasında.
Sınırın asıl işi güvenceyi korumak. Kümeler doymadan çıkış sorularına geçilirse şahların
erişimi eksik kalır ve motor ölü olmayan bir pozisyona “ölü” diyebilir; yani yanlış
yönde yanılır. Motorun bu yarıdaki tek ispatlı özelliği, yani “ölü dediği her pozisyon
gerçekten ölüdür” güvencesi, doğrudan bu sınıra dayanıyor.
Güvenliği ölçüldü. Kilitli yarı tek yönlü çalışıyor: emin olmadığı bir pozisyona
ölü demiyor, çünkü yanlış ilan edilmiş bir beraberlik geri alınamaz. CHA-Solver kaydında
hükmü kilitli taramadan gelen 48.135 şah-piyon pozisyonu var. Bunların her birinden
erişilebilen bütün pozisyonlar, motorun kendi fonksiyonlarını hiç kullanmayan bağımsız bir
aramayla tarandı: almalar ve geçerken alma dahil, toplam 46,6 milyon pozisyon. Hiçbirinde
terfi ya da mat mümkün değil; bir alma bile mümkün değil: hiçbir şah rakip bir piyonu,
hiçbir piyon da başka bir piyonu alamıyor. Kilitli taramanın ölü dediği her pozisyon
gerçekten ölü.
Maliyeti.V tek başına 405 bayt — motorun yaklaşık %27'si;
kural kitabının en pahalı maddesi bu. Kilitli yarı yalnızca piyonları ve şahları kapsıyor;
oysa tahtada fil de bulunan, tamamen ölü kilitli pozisyonlar da var. Bunların neden dahil
edilmediği Ayrıntılı teknik sekmesinde anlatılıyor.
Hamle gezgini — O
Hükmün iki sorusu da bütün geçerli hamleleri gezmeyi gerektiriyor: Z hiç
geçerli hamle olmadığını, H her hattın kapandığını göstermek zorunda. İkisi de
aynı gezgini kullanıyor — 59 bayt.
İç içe iki every var ve ikisi de delikleri atlıyor. Dıştaki çıkış karelerini
geziyor; p&8^t sıfır değilse — taş rakibinse ya da beyazın sırasında kare
boşsa — kare atlanıyor ve || doğru döndürerek taramayı sürdürüyor. İçteki varış
karelerini geziyor: !L(j,x) geçersiz hamleyi atlıyor, geçerli hamlede
h(j,x) çağrılıyor. h yanlış sayılan bir değer döndürürse iki
every de orada duruyor ve O yanlış döndürüyor; döndürmezse bütün
hamleler gezildikten sonra doğru döndürüyor.
Böylece O(_=>0) tam olarak “mat ya da pat mı?” sorusu, yani bir yokluk
testi: ilk geçerli hamlede h sıfır döndürüyor ve gezinti duruyor; dolayısıyla
O(_=>0) ancak hiç geçerli hamle yoksa doğru. H'de ise
h arama adımının kendisi ve every'nin anlamı “bütün hatlar
kapandı”.
h'ye çıkış ve varış kareleri veriliyor, ama asıl önemli olan bir yan etki:
h çağrıldığı anda L(j,x) az önce çalışmış, rok kalesinin karesini
r'ye, terfi bayrağını da X'e yazmış oluyor. H hamleyi
oynarken ikisine de dayanıyor.
Süzgeç p&8^t siyahın sırasında boş kareleri de geçiriyor: boş karede
p&8 sıfır, t de sıfır. Bu, T'deki
sızıntının ayna görüntüsü; bu kez öbür renkte. Zararsız, çünkü boş kareden hiçbir hamle
geçerli olamaz; ama bedava da değil: her boş kare için L 64 kez çağrılıyor.
Ölçümü aşağıda.
Hüküm — Z
Tahtada oyunu bitiren her durum tek bir soruya bağlı: sırası gelen tarafın
oynayabileceği geçerli bir hamle var mı? Cevap hayırsa mat ya da pat olmuştur; evetse oyun
yalnızca otomatik beraberliklerle bitebilir. Z tam olarak bu ayrımın şeklini
taşıyor — 51 baytta.
Z=_=>O(_=>0)
? T() ? 2-!t : 3 hamle yok → mat ya da pat
: V(0)&V(8) ? 6 : R>4 ? 5 : n>149 ? 4 : 0 hamle var → yalnızca otomatik beraberlikler
Mat kodu 2-!t. Sıra beyazdaysa (t=8) mat edilen taraf beyazdır:
!8 yanlış, kod 2 çıkar — siyah mat ederek kazandı; sıra siyahtaysa
!0 doğru ve kod 1. Tek bir değil işleci iki sonucu birden adlandırıyor. Şah
çekilmemişse aynı “hamle yok” durumu pattır: 3.
Ölü pozisyon tek bir & ile kuruluyor: V tek taraflı soruyor,
5.2.2 ise iki taraflı — iki taraf da mat edilemiyorsa pozisyon ölü, kod
6. Materyal yarısı ile kilitli yarı aynı V'nin içinde; hangisi doğru
döndürürse döndürsün kod aynı, çünkü madde de aynı.
Dalların sırası da başlı başına bir kural. FIDE 9.6, beşli tekrarın
ve 75 hamle kuralının oyunu berabere bitirdiğini söyler — son hamle mat etmediyse.
Burada mat, “hamle yok” dalının içinde yaşadığı için bu istisna hiç yazılmamış: yarım hamle
sayacı 75 hamle sınırına dayanmışken gelen bir matta motor sayaca bakmaz bile, çünkü o dala
hiç girmez. Öncelik ayrıca kodlanmış değil; şeklin bir sonucu.
Ayrımın maliyeti asimetrik. “Oyun sürüyor” demek için ilk geçerli hamleyi bulmak yeter;
“mat” demek ise bütün hamleleri deneyip hiçbirinin geçerli olmadığını kanıtlamayı
gerektirir. Üstelik O'nun süzgeci siyahın sırasında boş kareleri de geçirdiği
için maliyet, sıranın kimde olduğuna da bağlı — ölçümü aşağıda.
Mat imkânsızlığı — H
V tahtanın statik resmine bakıyor: bu materyalle, bu yapıyla hiç mat
kurulabilir mi? 6.9 daha dar bir şey soruyor: bu pozisyondan mat
kurulabilir mi? Materyal fazlasıyla yeterli olduğu hâlde pozisyon matı tamamen dışlayabilir.
H bu boşluğu kapatan arama — 149 bayt.
H=(d,v=t^I|!T(),s=[b,e,n,t,c,k])=>
$[k]<=n || V(I) iki kapı
|| O((j,x)=> v = n>149 75 hamle sınırı dolduysa zaten berabere
|| d derinlik
&& (X?[3]:[3,5,6,4]).every(p=> terfi değilse tek tur
N-- bütçe: denenen her taş bir birim; 0'da durur
&& (M(j,x,p),
H(d-1)|([b,e,n,t,c,k]=s))), özyinele, sonra geri al
$[k]=n) ziyaret: O'nun okunmayan ikinci argümanı
&& v yaprak: I mat edilmiş mi
H'nin sorduğu soru tam olarak maddenin sorduğu soru: bayrağı düşen ya da terk
eden taraf, rakibi tarafından hiçbir geçerli hamle dizisiyle mat edilemez mi?
Zorlamalı matı değil, yardımlı matın bile imkânsızlığını sınıyor. Sorulan taraf bir
parametre değil, F'nin kurduğu global I. H(d) doğru
döndürürse mat imkânsızdır ve sonuç beraberliktir; yanlış döndürürse ya mat mümkündür ya da
imkânsızlığı ispatlanamamıştır. Motorun içinde onu dışarıdan çağıran tek fonksiyon
F.
İki kapı var. $[k]<=n, pozisyon daha önce aynı ya da daha küçük bir sayaçla
ziyaret edildiyse çıkıyor — tabloda ne evet/hayır cevabı ne de derinlik, ziyaret anındaki
sayaç duruyor; nedeni aşağıda. Anahtar k, tekrar tablosununkiyle aynı.
F her çağrıda tabloyu sıfırlıyor; bir aramanın öğrendiği bir sonrakine
taşınmıyor. V(I) ise I bu pozisyonda materyal ya da yapı yüzünden
mat edilemiyorsa çıkıyor; bu denetim her düğümde yapılıyor.
Ziyaretin kaydedildiği yer de ayrı bir incelik: $[k]=n, O'ya
ikinci argüman olarak veriliyor. O yalnızca h'yi okuyor, ama
argümanlar çağrıdan önce değerlendirildiği için pozisyon, çocukları aranmadan önce
işaretlenmiş oluyor. Ne ayrı bir deyim var ne de fazladan bir virgül; döngüye girip aynı
pozisyona dönen bir dal ilk kapıda kesiliyor.
En zarif yeri ikinci parametre. v=t^I|!T() bir yaprak testi: sıra
I'da değilse ya da I'ya şah çekilmemişse burada mat yoktur. İki
koşul da yanlışsa ve tahtada hiç geçerli hamle yoksa — O geri çağrıyı hiç
çağırmadan doğru döndürür — v varsayılan değerinde, yani sıfırda kalıyor: mat
bulunmuş demektir. Mat tespiti ayrı bir dal olarak hiç yazılmamış, parametre varsayılanına
katlanmış. Aynı ifade patı da ayırt ediyor.
Geri alma da ifadenin içinde. Üçüncü parametre s=[b,e,n,t,c,k] düğüme girerken
altılı yedeği alıyor; H(d-1)|([b,e,n,t,c,k]=s)'de dizi ataması |
işleneni olarak NaN'a, yani sıfıra dönüşüyor ve hiçbir katkı yapmıyor: özyinelemenin sonucu
bozulmadan geçiyor. Terfi türleri X'e bakılarak seçiliyor: O geri
çağrıyı çağırmadan hemen önce L(j,x) çalışmış ve bayrağı yazmış. Hamle terfi
değilse dizi [3], yani tek tur; terfiyse [3,5,6,4]: vezir, kale, fil
ve at. Dizi döngü başlamadan kurulduğu için daha derindeki çağrıların X'in
üzerine yazması sonucu değiştirmiyor.
Aradığı şey mat değil, matın imkânsızlığının ispatı.H yardımlı bir mat
bulmaya çalışmıyor; yardımlı matın bile kurulamayacağını ispatlamaya çalışıyor, yani mata
karşı bir kanıt arıyor. Önce statik kapı geliyor: V(I). O karar veremezse arama
başlıyor. Kapılardan geçen her pozisyonda sırası gelen tarafın bütün geçerli hamleleri
deneniyor — iki taraf için de, çünkü mat iş birliğiyle de kurulabilir. Terfide dört taşın
dördü de deneniyor. Bir hat ancak mat olmadan biten bir sonuca vardığında kapanıyor: pat,
karşı mat (I'nın rakibini mat etmesi), I'nın artık mat edilemediği
bir pozisyon (V(I); materyal ya da kilit yüzünden ölü pozisyonlar da burada
kapanır), daha önce görülmüş bir pozisyona dönüş ($; kilitli bir
pozisyonda aramayı bitirebilen tek şey) ya da 75 hamle sınırı (n>149).
Bunların hepsi mantıksal kesme: kestikleri dalda gözden kaçan bir mat olamaz.
N bütçesi ile 9 yarım hamlelik derinlik ise yapay sınır. O da
every de “hepsi” demek: H ancak bütün hatlar mantıksal olarak
kapandığında doğru döndürüyor. I mat edilirse ya da bir hat yapay sınırlardan
birine dayanırsa gezinti orada durup yanlış döndürüyor: mat mümkün sayılıyor ve hüküm
kayıp oluyor (WT, BT, WR, BR).
Bu tek yönlü bir güvence. H'nin “mat imkânsız” dediği her pozisyonda
mat gerçekten imkânsız, çünkü ispatsız beraberlik yok. Tersi garanti edilmiyor: ispat bütçeye
sığmazsa, gerçekte matın imkânsız olduğu bir pozisyon “mümkün” sayılabilir. Yanılgı her zaman
kuralın varsayılan hükmüne, yani bayrak düşmesinde ve terkte kayba düşüyor.
Arama O'nun süzgecinden geçtiği için siyahın sırasında boş kareler de
geziliyor; yaprak testindeki T() da beyazın sırasında boş kareleri yokluyor.
İkisi de zararsız, ama ikisi de bedava değil.
$ bir hızlandırma değil
Arama tablosu ilk bakışta sıradan bir transpozisyon tablosu gibi görünüyor: amacı aynı
pozisyonu iki kez aramamak. Ama kilitli bir pozisyonda işi bu değil — aramanın
bitebilmesini sağlayan şey o.
Kilitli bir pozisyonda oynanabilen hamleler yalnızca şahın ve filin mekik hareketlerinden
ibaret ve hepsi er ya da geç daha önce görülmüş bir pozisyona dönüyor. Tablo olmadan arama bu
döngüyü fark etmez; derinlik dolar, hat kapanmadan kalır ve cevap “mat mümkün” olur.
Tablo varken geri dönen her dal kesilir, bütün hatlar kapanır ve imkânsızlık ispatlanmış
olur.
Aradaki fark ölçülebiliyor. Aşağıdaki pozisyonda iki tarafın piyonları da karşılıklı
kilitli, filler kendi yarılarında hapis, şahlar dar bir alana sıkışmış:
1kb5/1p1p4/1P1P4/8/8/4p1p1/4P1P1/5BK1 w - - 0 1
F(0,0) — siyahın bayrağı düşüyor
Düğüm
Deneme
Tablo girdisi
Sonuç
Motorun kendisi
5
4
4
14 · berabere
$ kapısı devre dışı
10
9
—
10 · beyaz kazanır
Beş düğüm, dört deneme ve dört tablo girdisi; beşinci düğüm, başlangıç pozisyonuna dönüp
ilk kapıda kesilen ziyaret. Tablo kapatıldığında arama iki kat çalışıyor ve
yanlış hüküm veriyor: tek bir mekik hattında 9 yarım hamle aşağı iniyor ve derinlik
dolunca pes ediyor. Kilitli tarama burada devrede bile değil — tahtada
fil olduğu için kapsam dışı — ve materyal fazlasıyla yeterli, yani V(I) de bir
şey söylemiyor. Kararı tek başına arama veriyor; verebilmesini de tablo sağlıyor.
Tabloda neden sayaç duruyor? Üç gözlem yetiyor. Birincisi, arama yalnızca tek bir
yoldan erken bitebiliyor: herhangi bir dalda “mat mümkün” çıkarsa — ister mat bulunsun, ister
derinlik ya da bütçe bitsin — bütün arama orada duruyor ve F kaybı ilan ediyor.
Demek ki tabloda kaydı olan her ziyaret ya “kapandı” diye bitmiş ya da hâlâ şu anki yolun bir
atası. Derinliği saklamaya gerek kalmıyor: derinlik sınırına dayanan bir ziyaret asla
“kapandı” diye bitmez. İkincisi, bir pozisyonun önündeki ağaç sayaca bağlı: 75 hamle sınırı
hattı n>149'da kapatıyor ve sayaç ne kadar küçükse önündeki yol o kadar uzun.
Daha küçük ya da eşit bir sayaçla kapanmış bir pozisyon, daha büyük bir sayaçla da kapanır.
Üçüncüsü ata durumu için: yol aynı pozisyona döndüyse arada ne alma ne de piyon hamlesi
olmuştur, yani sayaç büyümüştür; buradan varılabilecek her yer atanın kendi aramasında zaten
deneniyor.
Anahtarda (b+e+t+c) yarım hamle sayacı yer almıyor; sayacın kendisi tabloya
değer olarak yazılıyor ve kapı onu <= ile karşılaştırıyor. Aynı tahtaya daha
küçük bir sayaçla — örneğin bir alma ya da piyon hamlesinden sonra — varan bir ziyaret
yeniden aranıyor, çünkü önünde daha uzun bir yol var.
Bütçe — 2.000 deneme, 9 yarım hamle
F, H'yi her çağırışında iki sınır çiziyor: N=2e3 ve
H(9). İlki bütün ağacın ortak bütçesi: denenen her hamle N-- ile
bu bütçeden bir birim düşüyor (terfide her taş ayrı bir hamle sayılıyor, yani dört terfi
dört birim tutuyor); 2.000 de dal başına değil, aramanın tamamı için. İkincisi tek bir
hattın derinliğini sınırlıyor: dokuz yarım hamle — tek basamağa sığan en büyük sayı.
Hangisi önce biterse o hat kapanmadan kalıyor ve cevap mat mümkün oluyor. Bir sınır
hiçbir zaman beraberlik uydurmuyor; yalnızca beraberliği ispatlama şansını azaltıyor.
İki sınırın işi farklı. Derinlik sıradan pozisyonların maliyetini belirliyor.
Taşların bol, oyunun açık olduğu bir pozisyonda matın mümkün olduğu belli, ama
H bunu ispatlamaya çalışmıyor; bunu, imkânsızlığı ispatlayamadığında
bildiriyor. Tek bir hat boyunca aşağı iniyor; yolda bir yardımlı mata rastlasa da 9 yarım
hamlenin dibinde hattı kapatamasa da gezinti ilk yanlışta duruyor ve bu cevap yukarı
taşınıyor. Başlangıç pozisyonunda bu iş, hangi taraf sorulursa sorulsun 9 deneme tutuyor.
Bütçe ise uç durumların tavanı: zorunlu hamlelerden oluşan dar ama uzun ağaçlarda
aramanın en fazla ne kadar iş yapacağını sınırlıyor.
Pratikte iki sınıra da nadiren dayanılıyor. Zorunlu bir alma tek denemede kapanıyor;
rakibin bir yarım hamle sonra tekrara ya da sayaç sınırına ulaşacağı pozisyonlar da bir avuç
denemede kapanıyor. Ölçüm Ayrıntılı teknik sekmesinde:
CHA-Solver kaydının 31 Temmuz 2026'ya kadarki 201.060 pozisyonunun tamamında, oyunun kendi sınırlarıyla,
engine.js hükmü medyanda 0,1 milisaniyede veriyor; %99'u en fazla 0,7
milisaniye tutuyor, en ağırı 56 milisaniye sürüyor. Kayıttaki pozisyonların çoğu tek düğümde
kapanıyor. Aynı kayıtta engine_4x yaklaşık 6 kat hızlı — medyanı saatin 0,1
milisaniyelik adımının altında, en kötü durumda 6,9 milisaniye. Kapsam da bu sınırlarla
uyumlu: 30 Eylül 2026'ya uzayan kayıtta 204.060 pozisyonun 200.384'ünde beraberlik
bu sınırlar içinde ispatlanıyor.
Kanıtsız kalan 3.676 pozisyonun hepsinde arama derinlik sınırına dayanıyor, bütçe hiçbirinde
tükenmiyor; 3.638'inde tahtada fil var, yani kilitli taramanın kapsamı
dışında. Bu, kaydın bütününde kabaca 2,2 milyon oyunda bire denk geliyor. Yani bayrak düşmesinde ve terkte karşılaşılan durumların %99,9'undan
fazlasında bu sınırlar yetiyor ve hüküm milisaniyeler içinde geliyor.
Tavana dayanan örnekler ise seyrek. Kurallar sekmesindeki iki atlı
pozisyonda beraberlik, sayaç 148'deyken 72 denemede, onlarca milisaniyede ispatlanıyor;
147'deyken 688 deneme gerekiyor, 146'da ise bütçe tükeniyor ve cevap “mat mümkün” oluyor.
Tavana dayanan bir arama bile tek bir pozisyon için ve oyun başına en fazla bir kez koşuyor.
Bütçenin 2.000'de durmasının nedeni bir denge: daha büyük bir bütçe seyrek görülen şeridi biraz daha daraltırdı, ama tavana
dayanan her pozisyonda bekleme de aynı oranda uzardı. Bayt bütçesi daha gevşek olan
Chess-LUX 300 yarım hamle derinliğiyle ve 400.000 denemelik bir bütçeyle
çalışıyor.
İşlem — A, D, F
A — üç adım, hepsi tek bir argümanın içinde
A=(i,f,p)=>Z(R=G[M(i,f,p)]=-~G[k])
1 M(i,f,p) hamleyi uygula — haklar, sayaç, sıra içeride; yeni anahtarı döndür2 R=G[…]=-~G[k] yeni pozisyonu say3 Z(…) hükmü döndür
Rok haklarını, sayacı ve sırayı A değil M
güncelliyor, çünkü arama da hamle oynuyor ve bu bilgilerin dallarda da güncel kalması
gerekiyor. A'nın tahtayla ilgili tek işi M'yi çağırmak; geriye kalan
iki adım sayımdan ve hükümden ibaret.
Adımların sırası, değerlendirme sırasının ta kendisi. Atamanın sol tarafı sağ tarafından önce
değerlendirilir: G[M(i,f,p)] ifadesindeki özellik anahtarı hesaplanırken
M çalışıyor, hamleyi uyguluyor ve k'yi yeni pozisyona göre kuruyor.
Sağ taraf, -~G[k], bundan sonra değerlendiriliyor; yani yeni pozisyonun sayısını
okuyor. ~undefined eksi bir eder, işareti çevrilince bir olur: ilk kez görülen bir
pozisyon, başlangıç değeri atanmadan doğrudan 1'e oturuyor. Zincirleme atama aynı değeri
R'ye de yazıyor. Bütün bunlar Z'nin argümanının içinde oluyor;
Z argümanını hiç okumuyor, argüman yalnızca işlerin sırasını belirliyor. Ne
virgüle ne de parantez içinde bir diziye gerek var.
A'nın yapmadığı üç şey de tanımının parçası: geçerliliği sınamıyor,
saatlere dokunmuyor ve beraberlik teklifini temizlemiyor. Birincisi L'nin işi —
rokta kalenin karesi de o çağrıdan geliyor; öbür ikisi kural değil, sürücünün
politikası.
D — talep ve teklif
D=s=>(o^=2-s/8)>2?9:R>2?8:n>99?7:Z()
Önce teklif: bitler o'da duruyor; beyazınki 1 biti, siyahınki 2 biti.
2-s/8 doğru biti tek ifadeyle seçiyor: beyaz için 2 − 1, siyah için 2 − 0. Özel
veya biti tersine çeviriyor; yani aynı çağrı teklifi hem yapıyor hem geri çekiyor. İki bit
birden yanıksa o 3'tür ve >2 anlaşmalı beraberliği (9) veriyor.
Sonra talepler: pozisyon üçüncü kez görüldüyse üçlü tekrar (8), yarım hamle sayacı 99'u
geçtiyse 50 hamle kuralı (7); hiçbiri tutmazsa Z'nin hükmü döndürülüyor.
Sıranın gerekçesi kuralın kendisi: 9.2 ya da 9.3'e göre yapılan her talep aynı zamanda bir
beraberlik teklifidir (9.1.2.3). Bu yüzden bit, talep sınanmadan önce
ve her çağrıda çevriliyor. Rakibin bekleyen bir teklifi varsa talep onu kabul ediyor ve oyun
anlaşmayla (9) bitiyor; talebin kendisine bakmaya gerek kalmıyor. Şartı tutmayan bir talep de
boşa gitmiyor: bit yanık kalıyor ve rakibin önünde teklif olarak duruyor.
Argümansız çağrıldığında 2-undefined/8 ifadesi NaN olur,
o^=NaN da o'yu olduğu gibi bırakır — böylece aynı fonksiyon bitleri
bozmadan yalnızca hükmü okumak için de kullanılabiliyor.
Eşiklerin Z'dekilerden farklı olması tesadüf değil. 50 hamle kuralı ve üçlü
tekrar talep edilir; 75 hamle kuralı ve beşli tekrar kendiliğinden devreye
girer. FIDE bu ikisini ayrı maddelere koyuyor; motor da ayrı
fonksiyonlara. İsteğe bağlı olan D'de, zorunlu olan Z'de.
F — bayrak ve terk
F=(s,r)=>($=[I=s],N=2e3,H(9))?14+r:11+2*r-!s
s bayrağı düşen ya da terk eden taraf; r ise bayrak düşmesinde 0,
terkte 1. Kurulum tek bir parantezin içinde: $=[I=s] hem sorulan tarafı
I'ya yazıyor hem de yeni, boş bir arama tablosu veriyor — dizinin tek elemanı,
yani I'nın değeri, 0 indeksinde duruyor; pozisyon anahtarları ise uzun dizgiler
ve hiçbiri "0" değil, yani bu eleman tablonun girdilerine karışmıyor.
N=2e3 bütçeyi kuruyor, H(9) da soruyu soruyor.
Cevap doğruysa rakibin mat etmesi imkânsızdır ve sonuç beraberlik olur — 14 ya da 15.
Değilse rakip kazanır ve tek bir aritmetik ifade dört sonucu birden adlandırır:
11+2*r-!s. r bayrak düşmesini terkten ayırır, !s tarafı
seçer — beyaz kaybettiyse !8 yanlış ve sonuç 11 ya da 13 (siyah kazanır), siyah
kaybettiyse !0 doğru ve sonuç 10 ya da 12 (beyaz kazanır). 10 ve 11 bayrak
düşmesinden, 12 ve 13 terkten geliyor.
Kilitli bir pozisyonda (k7/8/8/1p2p2p/1P2P2P/8/8/K7) ölçüldüğünde
F(0,0) 14, F(8,1) 15 döndürüyor: bayrak düşmesi ve terk için iki ayrı
kod; yani 6.9 ile 5.1.2 ayrı ayrı bildiriliyor.
Bir hamlenin tam izi
Aşağıdaki sayılar motor çalıştırılarak ölçüldü: her fonksiyona bir sayaç takıldı ve tek bir
üst düzey çağrı boyunca her birine kaç kez girildiği kaydedildi. Kareler 0x88 indeksleriyle
veriliyor: e2 20, e4 52, e1 4, g1 6.
Sıradan bir hamle — e2-e4
Sürücü önce tıklanan ya da yazılan hamleyi L(20,52) ile sınıyor: sonuç doğru.
Taşın gidebileceği bütün kareleri işaretlemek isteyen bir sürücü 64 karenin hepsini sorar; o
zaman L 64 kez çalışıyor ve yalnızca 36 ile 52 için, yani e3 ve e4 için doğru
döndürüyor. Ardından A(20,52) çağrılıyor. İçeride M'nin altı adımı
çalışıyor: piyon oynadığı için sayaç sıfırlanıyor; rok maskesi değişmiyor (C(20)
ile C(52) sıfır); U piyonu 52'ye yazıp aday kareyi, 36'yı
döndürüyor; sıra siyaha geçiyor; aday süzülüyor; anahtar kuruluyor. Sonra yeni pozisyon bir
kez sayılıyor ve Z sıfır döndürüyor.
Geçerken alma karesi tutulmuyor. Hamle bir çift adım olsa da siyahın sekiz
piyonundan hiçbiri 36'da geçerli bir alma yapamadığı için e yeniden 8 oluyor. Aynı
hamle, d4'te bir siyah piyon varken kareyi tutardı (e=36) — ikisi de
ölçüldü.
Çağrı
G
T
U
L
M
C
V
O
Z
L(20,52)
49
1
1
1
—
—
—
—
—
L(20,·) 64 hedef
144
2
2
64
—
—
—
—
—
A(20,52)
1.593
1
2
2.089
1
2
2
1
1
Dağılım motorun şeklini özetliyor. Tek bir L 49 geometri sorusu soruyor: biri
piyonun kendisi için, 48'i T'nin şah taramasında — beyazın sırasında süzgeçten
16 siyah taşla birlikte 32 boş kare de geçiyor. Hamlenin kendisi neredeyse bedava: bir
M, bir U, iki C; geçerken alma süzgeci sekiz
L daha ekliyor, ama hiçbir siyah piyon geometriyi geçemediği için hepsi ucuz.
1.593 geometri sorusunun neredeyse tamamı son adımdan geliyor: Z oyunun
bitmediğini kanıtlamak için siyahın geçerli bir hamlesi olduğunu göstermek zorunda;
O da böyle bir hamle bulana kadar iki binden fazla hamleyi sınıyor. Motor hamleyi
yapmak için değil, hakemlik etmek için çalışıyor.
L çağrılarının 2.048'i boş karelerden geliyor. O tahtayı
a1'den tarıyor ve süzgeci p&8^t siyahın sırasında boş kareleri de geçiriyor;
ilk siyah taşa, a7'deki piyona varana kadar 32 boş karenin her biri için 64 varış karesi
soruluyor. Boş kareden hamle sınamak ucuz — G kayan taş dalına düşüp sıfır
döndürüyor, ne U ne T çalışıyor — ama bedava değil. Sonunda bulunan
ilk geçerli hamle a7-a5. Aynı Z çağrısı iki renk için ayrı ayrı ölçüldü:
Z() başlangıç pozisyonunda
G
L
Oran (G)
Sıra beyazda
97
81
—
Sıra siyahta
1.585
2.081
16,3×
Aynı pozisyon, aynı hüküm, on altı kattan fazla iş. Beyazın sırasında O a1'deki
kaleden başlıyor; kale 64 hedefin hiçbirine gidemiyor ve O, b1'deki atın ilk
geçerli hamlesini 17. denemede buluyor: toplam 81 L çağrısı. Bu asimetri bir
yavaşlık değil, kodun şeklinden doğan bir çarpıklık; bu farkın nasıl kapatıldığı
hız bölümünde anlatılıyor.
Kısa rok
1.e4 e5 2.Af3 Ac6 3.Fc4 Fc5 ile yol açıldıktan sonra şahın gidebileceği kareleri soran bir
sürücü, L(4,·) çağrısını 64 karenin hepsi için yapıyor; doğru döndürenler 5, 6 ve
20, yani f1, g1 ve e2. g1 listede sıradan bir hedef gibi duruyor; sürücü bunun bir rok
olduğunu hiç öğrenmiyor, yalnızca bir kareye tıklıyor. Rokun bütün koşulları G'nin
içinde sınanmış oluyor.
Bu taramada C bir kez çalışıyor (G'nin rok dalında, g1 için),
T ise beş kez. Bu beş çağrının ikisi doğrudan G'nin rok dalından
geliyor — şaha o anda şah çekilmediğini ve şahın üzerinden geçtiği karenin tehdit altında
olmadığını sınayan iki çağrı —; üçü de L'nin f1, g1 ve e2 denemelerinden.
U dört kez çalışıyor: üç deneme ve g1 denemesinde kaleyi taşıyan iç çağrı. Uzun
rok hiç gündeme gelmiyor bile: c1'de hâlâ fil durduğu için L'nin süzgeci bu
hedefi geometriye gelmeden eliyor.
Ardından L(4,6) ve A(4,6) geliyor. L rok dalında
r'ye 7'yi, yani h1'i yazıyor. A'nın içinde C(4) üç
döndürüyor ve maske 15'ten 12'ye iniyor — beyazın iki hakkı da tek adımda gidiyor.
U'da parmak izi 8 çıkıyor; iç çağrı kaleyi r'den aradaki kareye,
f1'e taşıyor ve h1 boşalıyor. Ölçülen son durum: f1'de beyaz kale (13), g1'de beyaz şah (10),
h1 boş.
Çağrı
G
T
U
L
M
C
V
O
Z
L(4,·) 64 hedef
288
5
4
64
—
1
—
—
—
L(4,6)
145
3
2
1
—
1
—
—
—
A(4,6)
893
1
3
1.174
1
2
2
1
1
Geçerken alma
1.e4 a6 2.e5 d5 dizisinde asıl iş siyahın çift adımında, A(99,67)'de oluyor.
U'da parmak izi 1024 çıkıyor ve aday kare — 99 ile 67'nin orta noktası —
e=83 (d6) olarak yazılıyor. Sıra beyaza geçiyor ve M'nin beşinci
adımı beyazın piyonlarını tek tek sınıyor. 68'deki (e5) piyon için L(68,83)
gerçekten doğru döndürüyor: G'nin piyon dalı çapraz adımı görüyor ve
f==e koşulu sağlanıyor. Bu ancak aday kare e'ye zaten yazılmış olduğu
için mümkün. Sonuç: e=83 korunuyor.
Aynı hamle, e5'te beyaz piyon yokken e=8 ile bitiyor. İkisi de ölçüldü.
Beyaz A(68,83) ile aldığında U'nun ikinci adımı devreye giriyor:
piyon geçerken alma karesine vardığı için silinecek kare 83^16, yani 67 — d5.
Ölçülen sonuç: d5 boş, d6'da beyaz piyon var, e5 boş. Bu hamle çift adım olmadığı için aday
8 oluyor ve e yeniden 8'e dönüyor.
Çağrı
G
T
U
L
M
C
V
O
Z
A(99,67) d7-d5
154
2
3
89
1
2
2
1
1
A(68,83) exd6
1.274
1
2
1.640
1
2
2
1
1
Bitişlerin boru hattı
On beş sonuç birbirinden bağımsız değil; aynı boru hattının ayrı çıkışları.
Sonuç tablosu bu yüzden periyodik tablo gibi okunuyor: satırlar bitişin
hangi aşamada belirlendiğini, sütunlar kimin kazandığını gösteriyor. Matı, patı ve
kendiliğinden gelen beraberlikleri Z; talep ve teklifi D; bayrak
düşmesini ve terki F ile H üretiyor.
Aşamaların sırası aynı zamanda bir öncelik sırası ve kural kitabından geliyor.
Z önce geçerli bir hamle olup olmadığına bakıyor; mat bu yüzden 75 hamle
kuralının ve beşli tekrarın önüne geçiyor — 9.6'nın “son hamle mat
etmediyse” kaydı da budur. Aynı aşamanın içinde de ilk sağlanan koşul kazanıyor. Ölü pozisyon
öbür iki otomatik beraberlikle hiçbir zaman çakışamaz: bir pozisyon ancak bir alma ya da bir piyon
hamlesiyle ölü hâle gelir ve ikisi de sayacı sıfırlar. Sessiz bir hamle taramanın cevabını
yalnızca bir çift adımın yarattığı geçerken alma hakkını düşürerek değiştirebilir; o an sayaç
1'dedir ve pozisyon yenidir. Beşli tekrar ile 75 hamle kuralı ise aynı
hamlede gerçekleşebilir; o zaman etiket 5R olur. İkisi de beraberlik; fark yalnızca adda.
Talep ve teklif ancak oyun kendiliğinden bitmediyse ve bir istek biti yanıksa
değerlendiriliyor. Sürücünün bunu hamleden sonra nasıl çağırdığı sürücü
bölümünde anlatılıyor. D'nin içinde sıra yine sabit: anlaşma, üçlü tekrar,
50 hamle. Her talep aynı zamanda bir teklif olduğu için (9.1.2.3)
rakibin bekleyen teklifi varken gelen talep anlaşmayla bitiyor; 3R ile 50 hamle koşulları
birlikte sağlanırsa 3R kazanıyor.
Bayrak düşmesi ve terk ayrı bir koldan, yalnızca oyun sürerken devreye giriyor: ikisi de
F'ye gidiyor, F de H'ye soruyor. H mat
imkânsız derse sonuç TM ya da RM oluyor, demezse rakip kazanıyor. Bu, 6.9'un
açılış cümlesinin kendisi: mat, pat, ölü pozisyon ve anlaşma bayrak düşmesinin önüne
geçer.
Soldaki kutu her aşamanın sorusunu, sağdaki kartlar o aşamanın çıkışlarını
gösteriyor. › işareti, aynı aşamada önce gelenin öncelikli olduğunu belirtiyor; kartların
renkleri sonuç tablosundakilerle aynı.
Kısa devre merdiveni
Dört ayrı test aynı sonuca varıyor: V'nin iki yarısı — materyal ve kilit —,
tekrar sayacı ve yarım hamle sayacı. Hepsi oyunun berabere bittiğini söylüyor. Aralarındaki
fark, bunu ne kadar erken ve ne kadar ucuza söyledikleri.
En dipte iki güvenlik ağı var: 75 hamle kuralı ve beşli tekrar. Kilitli her pozisyon eninde
sonunda bunlardan birine düşer (4. bölüm); kilitli bir pozisyonda
hareketlilik çok azaldığı için tekrar çoğu zaman önce gelir. V'nin iki yarısı bu
ikisini de kısa devre ediyor: pozisyonu ortaya çıktığı anda tanıyor. Aynı V,
I için sorulduğunda H'yi de kısa devre ediyor: materyal ya da yapı
mata izin vermiyorsa arama o dalda hiç açılmıyor — üstelik bu her düğümde denetleniyor.
İlke olarak H hepsini kapsayabilirdi. Sınırsız derinlik ve bütçeyle erişilebilir
durum uzayını tüketir, imkânsızlığı da kendisi ispatlardı. Ama bu ölçüldü ve sonuç iç açıcı
değil.
k4b2/8/8/p1p1p1p1/P1P1P1P1/8/8/K4B2 w kilitli
Bu pozisyonda hiçbir taş karşı tarafa geçemiyor. Piyonlar karşılıklı kilitli ve
çaprazlarında alınabilecek bir taş yok; iki fil de kendi yarısındaki karelerde hapis; iki şah
da rakip piyonların tuttuğu yatayı aşamıyor. Pozisyon ölü. Ama tahtada fil olduğu için
V'nin kilitli yarısı devreye girmiyor; materyal de fazlasıyla yeterli olduğu için
materyal yarısı da bir şey söylemiyor. Geriye aramanın kendisi kalıyor. F(0,0)
derinlik değiştirilerek ölçüldü:
Derinlik
Cevap
Deneme
9 motorun derinliği
mat mümkün
10
15
mat mümkün
19
23
mat mümkün
31
35
mat mümkün
43
99
mat mümkün
178
149
mat mümkün
255
150
mat mümkün bütçe tükendi
2.001
Deneme sayısı 149'a kadar derinlikle üstel değil, doğrusal artıyor — ve cevap hiç
değişmiyor. Nedeni
H'nin tasarımında: O ile every ilk yanlışta duruyor;
derinlik bitince de hat kapanmadan kalıyor ve cevap “mat mümkün” oluyor. Arama tek bir hat
boyunca dibe kadar iniyor ve orada pes ediyor. Bir kardeş hamleye ancak önceki hamle daha önce
görülmüş bir pozisyona dönüp kapandığında geçiyor; bir hat derinlik sınırına dayandığı anda
“yanlış” yukarı taşınıyor ve başka hiçbir dal denenmiyor.
Yani H imkânsızlığı ancak bütün hatlar derinlik dolmadan kapanırsa
ispatlayabiliyor. Şahlar ve filler serbestçe dolaşabiliyorsa, hep yeni pozisyonlardan geçerek
derinlik sınırına dayanan bir hat bulunur. 75 hamle sınırı ancak derinlik 150'ye çıkınca
devreye giriyor ve her hattı en geç orada kapatıyor; ama o zaman ağaç şah ve fil konumlarının
çarpımı kadar büyüyor ve bütçe 2.000 denemede tükeniyor. Kısa devreler tam da bu yüzden var:
V'nin kilitli yarısı aynı kararı — tahtada fil olmadığında — taramayla, arama
hiç açılmadan veriyor.
Bu da iki fonksiyonun neden ayrı durduğunu açıklıyor. V ile H aynı
soruyu soruyor ama aynı yoldan gitmiyor: biri tahtanın statik resmine bakarak, öbürü ileriyi
arayarak. Birinin bittiği yerde öbürü başlıyor — H'nin her düğümü önce
V'ye soruyor — ve ikisi de aynı maddeye varıyor.
Doğrulama
Geçtiği testler
Motorun kuralları doğru uygulayıp uygulamadığı perft ile sınandı: bir pozisyondan belirli
bir derinliğe kadar üretilen geçerli hamle dizilerinin sayısı, yayımlanmış referans
değerlerle karşılaştırıldı. Tek bir düğümlük sapma bile kurallarda bir hata olduğu anlamına
gelir; perft'i bir hakem motoru için en sert test yapan da bu — hamle üretiminin tamamını,
şah güvenliğini, rok haklarını ve geçerken alma imkânını aynı anda yokluyor.
Karşılaştırma dört kaynağa dayanıyor: Chess Programming Wiki'nin yayımlanmış düğüm
sayıları, van Kervinck'in zor pozisyon listesi, 6.838 pozisyonluk Vajolet külliyatı ve
Stockfish. Yayımlanmış değerin bulunmadığı sığ derinliklerde ise motorla hiç kod
paylaşmayan, kurallardan doğrudan yazılmış naif bir 0x88 uygulaması kıyas noktası
oluyor.
Aşağıdaki tablolarda derinlikler birleştirildi: bir pozisyon birden fazla koşuya
bölünmüşse (önce d5'e kadar, sonra ayrı bir koşuda d6 ve d7), tabloda tek satırda, ulaşılan
en büyük derinlikle yer alıyor. Her pozisyon d1'den o derinliğe kadar bütün ara
derinliklerde de doğrulandı.
CPW standart pozisyonları
Yedi pozisyon; aralarında dördüncünün renkleri aynalanmış hâli de var. Ayna tesadüfen orada
değil: renk simetrisini bozan bir hata ancak orada görünür.
Pozisyon
Derinlik
Düğüm
Karşılaştırma
Başlangıç
d6
119.060.324
CPW yayımlanmış değerleri
Kiwipete
d5
193.690.690
CPW yayımlanmış değerleri
Pozisyon 3
d7
178.633.661
CPW yayımlanmış değerleri
Pozisyon 4
d5
15.833.292
CPW yayımlanmış değerleri
Pozisyon 4 aynası
d5
15.833.292
CPW yayımlanmış değerleri
Pozisyon 5
d5
89.941.194
CPW yayımlanmış değerleri
Pozisyon 6
d5
164.075.551
CPW yayımlanmış değerleri
van Kervinck zor pozisyonları
Geçersiz geçerken alma, şah altında rok, geçerken almanın açtığı keşif şahı, şah çeken terfi
ve alt terfi, pat–mat ayrımı, fil açmazından kaçınma. On beş pozisyonun onunda yayımlanmış
derinliğin ötesine geçildi; bu derinliklerde karşılaştırılacak yayımlanmış değer
bulunmadığı için doğrulama Stockfish'e karşı yapıldı.
Pozisyon
Yayımlanmış
Ulaşılan
Doğrulama
Geçersiz geçerken alma #1
d6 · 1.134.888
d6
Yayımlanmış değer, Stockfish teyitli
Geçersiz geçerken alma #2
d6 · 1.015.133
d6
Yayımlanmış değer, Stockfish teyitli
Geçerken alma rakibe şah çekiyor
d6 · 1.440.467
d6
Yayımlanmış değer, Stockfish teyitli
Kısa rok şah çekiyor
d6 · 661.072
d7
Stockfish
Uzun rok şah çekiyor
d6 · 803.711
d7
Stockfish
Rok hakları
d4 · 1.274.206
d5
Stockfish
Rok engellenmiş
d4 · 1.720.476
d5
Stockfish
Şahtan terfiyle çıkış
d6 · 3.821.001
d6
Yayımlanmış değer, Stockfish teyitli
Keşif şahı
d5 · 1.004.658
d6
Stockfish
Şah çeken terfi
d6 · 217.342
d8
Stockfish
Şah çeken alt terfi
d6 · 92.683
d8
Stockfish
Kendi kendine pat
d6 · 2.217
d8
Stockfish
Pat ve mat ayrımı I
d7 · 567.584
d8
Stockfish
Pat ve mat ayrımı II
d4 · 23.527
d4
Yayımlanmış değer, Stockfish teyitli
Fil açmazından kaçınma
d5 · 1.063.513
d6
Stockfish
Toplu koşular
Tek tek seçilmiş pozisyonlar uç durumları yakalar; toplu koşular ise sıradan oyunda
karşılaşılanları.
Koşu
Kapsam
Yöntem
Vajolet külliyatı gerçek oyun pozisyonları
100 pozisyon × d4 kapsama için, ilk 10 pozisyon × d5 derinlik için — 450 perft kontrolü
Külliyatın kendi referans değerleri
Rastgele oyun perft değil
Kiwipete'ten 20 deterministik-rastgele oyun, oyun başına en çok 80 yarım hamle
Her pozisyonda geçerli hamle listesinin tamamı, sıralı hâlde Stockfish ile birebir
Rastgele oyun koşusu düğüm toplamına bakmaz, listeyi karşılaştırır. İki hata birbirini
götürdüğünde de toplam tutabilir; liste karşılaştırması tam da bu olasılığı elemek için
var.
Testler varsayılan olarak engine_4x.js üzerinde
çalışıyor. engine.js dosyasının kendisini sınamak için
ENGINE=./engine.js ayarlamak yeterli.
Kendin çalıştır
Yukarıdaki sayıların hepsi tek bir iddiaya varıyor: motor bu pozisyonlarda bu düğüm
sayılarını üretiyor. Bana güvenmen gerekmiyor; kendin kontrol edebilirsin.
Repo herkese açık:
github.com/cuneytinann/FideLite.
Test klasörünün tamamını tek dosya hâlinde indirmek için:
test.zip · 251 KB. İçinde süit, beş motorun
kopyası, Vajolet pozisyon listesi, çift tıklanarak çalışan sekiz test .bat dosyası ve
hız karşılaştırması (bench.js ile speed-comparison.bat) var; başka bir şey indirmen gerekmiyor. Windows'ta bir .bat dosyasına çift
tıklamak yetiyor; başka platformlarda node test.js <komut> komutunu
çalıştırman yeterli.
Sekiz koşunun beşi yalnızca Node istiyor. CPW standart pozisyonları, Vajolet süiti ve
derin tek pozisyon koşuları yayımlanmış değerlerle karşılaştırma yapıyor; dışarıdan bir
motora gerek yok. En hızlısı 1-sanity-test.bat.
İkisi Stockfish gerektiriyor: rastgele oyun simülasyonu ve yayımlanmış derinliğin ötesine
geçen doğrulamalar. Biri de Stockfish'i isteğe bağlı kullanıyor: zor pozisyonlar koşusu,
Stockfish varsa çapraz kontrol ekliyor, yoksa referans değerlerle yetiniyor.
Stockfish pakette yok; tek başına 100 megabaytı aşıyor. Kendi ikili dosyanı test
klasörüne koyabilir ya da PATH'e ekleyebilirsin; süit sırayla STOCKFISH ortam
değişkenine, Windows'ta stockfish.exe'ye, Linux ve macOS'ta olağan kurulum
yollarına bakıyor.
Ne yayımlanmış bir değerin ne de Stockfish'in bulunduğu durumlar için pakette
ref.js var: kurallardan doğrudan yazılmış, motorla tek satır kod paylaşmayan
naif bir referans. İkisinin aynı sayıya varması ortak bir hatanın değil, gerçek bir
mutabakatın kanıtı. Onun tahtası da tıpkı motorunki gibi 0x88, ama
ikisi bunu bambaşka biçimlerde uyguluyor: ref.js tahta dışındaki kareleri klasik
i&0x88 maskesiyle tespit ediyor, yönleri tablolardan okuyor, taşları nesne
olarak tutuyor. Motorun deliklere, kalan testlerine ve tek basamaklı kodlara dayandırdığı her
şey orada açıkça yazılmış.
Doğrulama
Hükümlerin testi: Arbiter
Perft hamle üretimini sınıyor; bir oyunun nasıl bittiği hakkında tek söz söylemiyor. Bir
hakem motorunun en zor hükmü ise orada: bayrak düştüğünde ya da oyuncu terk ettiğinde rakip
herhangi bir geçerli hamle dizisiyle mat edebilir mi (6.9 ve 5.1.2)?
Arbiter bu soruyu toplu hâlde soruyor: her pozisyonu motorun kendi F
fonksiyonuyla karara bağlıyor, kararı hangi testin verdiğini gösteriyor ve sonucu bir
referansla karşılaştırıyor.
Sürümler gibi tek bir HTML dosyası: kurulum yok, sunucu yok, hiçbir şey bir yere
gönderilmiyor; pozisyonlar tarayıcının içinde karara bağlanıyor. Arayüzü Türkçe ve
İngilizce. Yeni sekmede açılıyor:
fidelite.art/arbiter.
Hüküm nasıl veriliyor
Bayrağı düşen taraf, FEN'de sırası gelen taraf: oyunda yalnızca onun saati işliyor.
İstenirse her pozisyon iki tarafın bayrağı için ayrı ayrı da karara bağlanıyor. Dört test bu
sırayla çalışıyor; soruyu ilk kapatan hükmü veriyor, ondan sonrakiler yalnızca bilgi için
gösteriliyor.
Test
Gösterdiği
Hüküm
1. Malzeme V
Rakibin mat edecek malzemesi kalmamış: V'nin malzeme yarısı, kilit kapalıyken.
TM
2. Ölü pozisyon V(0)&V(8)
Yapı iki taraf için de kilitli; hiçbiri mat edemez. Oyunda Z'nin sorduğu soru.
DP
3. Rakibe karşı kilit V(I)
Yapı yalnızca rakibe karşı kilitli. H bunu kökünde soruyor; tek düğüm harcanmadan.
TM
4. Yardımlı mat araması H
Her yol mat imkânsızlaşana kadar izleniyor ya da bir mat bulunuyor. Kökte hamle kalmamışsa ya da 75 hamle dolmuşsa arama hiç başlamadan dönüyor.
TM, WT ya da BT
Sınırlar oyundakiyle aynı: F 9 yarım hamle derinliğe ve 2.000 düğüme kadar
arıyor; sayfada ikisi de değiştirilebiliyor. Arama sınıra kanıtsız takılırsa hüküm kazanca
düşüyor, WT ya da BT: bir beraberlik kaçabilir, ama yanlış bir
beraberlik asla ilan edilmez. Bu, 5.2.2'de anlatılan güvenli
yön.
Referansla karşılaştırma
Referans ya satırın başındaki CHA-Solver etiketi (WB, W-,
-B, --: hangi tarafın yardımlı mat kurabildiği) ya da her satırı
berabere olan bir dosya. Referansı olan her hüküm dört sınıftan birine düşüyor:
Uyuşuyor. Kanıtla ya da varsayılan kazançla.
Kaçan beraberlik. Referans berabere diyor, ama sınırlar içinde kanıt
bulunamamış. Tamlıktan yiyor, sağlamlıktan değil.
Yanlış TM. Motor beraberlik ilan etmiş, referans ise rakibin hâlâ mat
edebileceğini söylüyor. Sağlamlığı bozan tek hata bu; sıfır olmalı.
Çelişki. Arama, referansın imkânsız dediği bir mat bulmuş. Ya referans ya hamle
üretimi yanlış; her biri tek tek okunmaya değer.
Bu ölçümün sonuçları kuralların yanında duruyor: 6.9 için pahalı
yarı, kilitli pozisyonlar için 5.2.2'nin kapsamı ve
doğruluğu.
Sayfada başka neler var
Sürüm karşılaştırması.engine.js ile engine_4x.js
sayfanın içinde bayt bayt duruyor ve her girdide aynı hükmü veriyor. “Öteki sürümü de
çalıştır” seçeneği farkları ve hız oranını raporluyor; sayfa ikisini tanım tanım ayıran
farkı da açılırken iki kaynaktan hesaplayıp gösteriyor. Harf tahtalı
engine_string.js ile LUX'un motoru (300 yarım hamle, 400.000 düğüm) da
seçilebiliyor.
Ayrıntı görünümü. Her pozisyon için dört testin sonucu, harcanan düğüm, süre,
referans ve pozisyonu Lichess'in tahta düzenleyicisinde açan bir bağlantı.
FEN denetimi. Gerekli koşullar: her tarafta tek şah, ilk ve son yatayda piyon
yok, fazla taşlar terfiyle açıklanabiliyor, sırası olmayan taraf şahta değil, şah çekmesi
mümkün, geçerken alma karesi ve yarım hamle sayacı tutarlı. Birini geçemeyen satır hüküm
almıyor ve karşılaştırmanın dışında kalıyor.
PGN testi. Oyunları hamle hamle yeniden oynatıyor; kuralların oyunu bitirdiği
yerden sonra oynanan hamleleri ve süreden ya da terkten kaybedilmiş ama berabere olması
gereken oyunları buluyor.
Dışa aktarma. Sonuçlar sınıf sınıf CSV olarak iniyor.
Kendin dene
fidelite.art/arbiter adresini aç.
FEN'leri satır satır yapıştır (istersen başına CHA-Solver etiketiyle) ya da CSV ve TXT
dosyalarını sayfaya bırak; Ctrl+Enter karara bağlar.
Veri sitede barındırılmıyor; ölçüm kümesi repoda:
chasolver-data-until-10-2026.csv · 30 MB.
CHA-Solver'ın Lichess açık veritabanında haksız sonuçlandığını bulduğu oyunlar, her oyun
için son pozisyonun FEN'iyle bir satır. Sayfa bu dosyayı tanıyor ve her satırını
kendiliğinden berabere işaretliyor.
Aynı arbiter/ klasöründe CHA-Solver'ın kendi etiketli test kümeleri de var:
Lichess oyunlarından örneklenmiş 65.536 pozisyonluk chasolver-lichess.txt
ve 2021'den beri chasolver.org'a gönderilen sorgulardan derlenmiş 3.414 pozisyonluk
chasolver-positions.txt. Sayfa ikisinin biçimini de doğrudan okuyor.
Bu sekme motorun bugünkü hâline nasıl gelindiğini ve sınır durumlarını anlatıyor. Önce
ana sürümün dışında kalan motorlar: engine_4x: aynı kurallar,
biraz daha fazla baytla çok daha hızlı. Önceki nesil: tahtayı harf
dizgisi olarak tutan sürüm. Yalnız hamle üretici: testlerin sınadığı
çekirdeğin tek başına ne kadar küçük olabileceğini gösteren 653 baytlık dosya. LUX'un motoru:
kilit dedektörü, roku ve aramaları genişletilmiş engine_4x; motordan çıkarılan ve
hiç yazılmayan katmanların bayt hesabıyla. Denenip elenen yollar: ölçülüp
bırakılan tahta temsilleri ve kısayollar. Sonra motorun dışı:
Sürücünün ayrıştırıcıları: tıklama, UCI ve sayısal girdi; bir de
sürücünün motora nerede bağlandığı. L1'in rok defteri ve
FEN'den pozisyon kurmak: L1'in rok haklarını neden farklı
biçimde tuttuğu ve bir pozisyonun ona nasıl yükleneceği. Paketleme: RegPack, takma adlar,
eskimiş teknikler, quirks modu ve sürüm sürüm uyumluluk. En sonda
Tuzaklar: motorun kırılgan yerleri ve değişiklik yaparken dikkat
edilmesi gerekenler.
Hız
engine_4x.js — aynı kurallar, +117 bayt, hamle üretiminde 3,5 kat, hükümde yaklaşık 6 kat hız
Aşağıdaki kaynak engine_4x.js'in kendisi; engine.js'te olmayan
her parça boyalı. Bu bölümde anlatılan değişikliklerin her biri kodda bir bakışta
görünüyor.
engine_4x.js—
İki motor mantıksal olarak aynıdır: her derinlikte aynı hamleler, aynı düğüm sayıları,
aynı sonuç kodları. Başlangıç pozisyonundan perft d1–d4 ikisinde de
20 / 400 / 8.902 / 197.281 veriyor; CHA-Solver kaydının 31 Temmuz 2026'ya kadarki 201.060 pozisyonunda da
ikisi pozisyon pozisyon aynı hükmü veriyor. Fark yalnızca aynı cevaba kaç işlemle
varıldığında — ve bu nesilde o fark hamle üretiminde 3,5 kat, hükümde yaklaşık 6 kat.
117 bayt nereye gidiyor
İki dosya yedi fonksiyonda ayrılıyor. Farkların hepsi bir kısa devre ya da daha dar bir
tarama; yeni bir fonksiyon, tablo ya da durum eklenmedi, kural metni aynı.
Fonksiyon
Ne değişiyor
Bayt
G
Kale ya da vezir ışınını ancak hedef o ışının üstünde olabiliyorsa yürüyor: d/s<8, başka bir yataydaki hedefi yatay yürüyüş başlamadan eliyor. Piyonun düz adımı da yalnız hedef boşsa yürüyor (&S() yerine &&S()).
+8
T
(p^t)>8 yalnız rakip taşları bırakıyor — boş kareler ve kendi taşları tek karşılaştırmada çıkıyor — ve şahtan indeks olarak 33 kareden uzaktaki piyon, at ya da şah G'ye hiç sorulmuyor: (22>>p%8&(x-j)**2>1089).
+26
M
e-8&& — geçerken alma yasallık taraması yalnız iki kare ilerlemeden sonra çalışıyor.
+5
V
Kilit döngüsü 99 tur yerine sabit noktada duruyor: her turda kümelerin toplamı, A+a+_+f-S-s, karşılaştırılıyor; toplam değişmeyince döngü bitiyor. Sabit noktaya varılabilsin diye yukarı doğru büyüyen üç küme — beyazın ilerleyişi ile iki şahın erişimi — 64 bitte kesiliyor (&F, iki yerde); yoksa tahtanın dışına taşan bitler her turda büyür ve toplam hiç durmazdı. Bir |, || oluyor: malzeme yarısı cevabı zaten verdiyse kilit taraması hiç başlamıyor.
+27
O
p+~t>>3 boş kareleri ve rakip taşları tek testte atlıyor; piyon, at ve şahta L'ye yalnız çevresindeki 5×5 kutu ulaşıyor: x-j+34&136|j-x+34&136.
+36
Z
(n+e<10|_)&& — ölü pozisyon testi yalnız bir piyon hamlesi ya da almanın yapıldığı yarım hamlede ve ondan sonrakinde, açık bir geçerken alma karesi yokken çalışıyor; bir de Z(1) ile istendiğinde.
+12
A
Z()'yi argümansız çağırıyor, tetik bu yüzden geçerli.
+3
Toplam
engine.js 1.527 → engine_4x.js 1.644
+117
117 baytın 38'i son turda geldi ve hepsi hamle üretimine gitti: 24'ü T'ye,
9'u O'ya, 5'i G'ye; kalan 79'u o turun dışında. Altı standart pozisyonun derinlik 4'te ölçümünde yeni dosya önceki
dosyanın 1,259 katı hızda; o 38 baytın karşılığı bu. Adayların tek tek nasıl ölçüldüğü
aşağıda.
Hamle üretimi: perft
Aşağıdaki tablo her hamle dizisini motorun kendi O()'su üzerinden sayıyor,
motorun üstüne kurulan bir aramanın yapacağı gibi: L() her adayı
G(), U() ve T() ile süzüyor, hamle yalın
c, U, t ile (harf tahtasında X() ile)
oynanıyor; M()'nin tekrar anahtarı sayıma girmiyor. Pozisyonlar ve derinlikler
bu tablonun önceki baskısındakilerle aynı. Her ölçüm ayrı bir süreçte çalışıyor, motorların
sırası her turda dönüyor, sayılmayan bir ısınma koşusundan sonra beş turun medyanı alınıyor
ve her düğüm sayısı bilinen değerle karşılaştırılıyor. G sütunu makineden
bağımsız.
Perft
engine.js
engine_string.js
engine_4x.js
G çağrısı
Başlangıç d4
15.248 ms
11.989 ms 1,27×
3.368 ms 4,53×
5,97×
Kiwipete d3
2.765 ms
4.210 ms 0,66×
1.214 ms 2,28×
5,15×
CPW 3 d5
30.992 ms
25.990 ms 1,19×
8.923 ms 3,47×
7,52×
CPW 4 d4
17.806 ms
17.379 ms 1,02×
5.269 ms 3,38×
4,66×
CPW 5 d3
1.915 ms
2.656 ms 0,72×
761 ms 2,52×
4,85×
CPW 6 d3
2.586 ms
3.904 ms 0,66×
1.139 ms 2,27×
4,96×
Toplam
72,07 sn
66,21 sn 1,09×
20,53 sn 3,51×
6,05×
Ryzen 7 8845HS, Node 24.14. Satırlar o pozisyonun medyanı; toplam, turların toplam
sürelerinin medyanı, sütunun toplamı değil. Oranlar engine.js'e göre;
G sütunu engine.js'in G çağrı sayısının engine_4x'inkine oranı.
4x toplamda 3,5 kat, pozisyon pozisyon 2,3 ile 4,5 kat arasında hızlı ve
G'ye altı kat daha az giriyor. Bunun nereden geldiğini iki sayaç daha
söylüyor. T() ve U() üç motorda da tıpatıp aynı sayıda
çağrılıyor — plan boyunca 1.719.023 ve 1.760.215 kez: her motor aynı adaylar için tahtayı
kopyalıyor ve onlara aynı şah testini soruyor. Değişen, oraya varana kadar retlerin ne kadara
mal olduğu. engine.js'te O() ile T()'nin tarama
koşulları boş kareleri içeri alıyor — beyaz sıradayken T() her boş kareye
soruyor, siyah sıradayken O() boş kareleri siyahın taşı sayıyor — ve uzun
menzilli dal &S() ile bittiği için ışın her çağrıda yürünüyor, önceki
testler ne derse desin: dikeye sorulan file, başka yataydaki kareye sorulan kaleye, yukarıda
içeri giren boş karelere bile. 4x ışını ancak taş o yönde gidebiliyorsa ve hedef o hattın
üstünde olabiliyorsa yürüyor. G'nin içindeki ışın adımları 262 milyondan 5,7
milyona, 45,7 kat iniyor.
Harf tahtası ikisinin arasında: toplamda engine.js'in 1,09 katı, üç
pozisyonda önde (Başlangıç, CPW 3, CPW 4), üçünde geride (Kiwipete, CPW 5, CPW 6). Onun
G'si başka bir yapıda, bu yüzden G sayısı (49,5 milyon) tahtalar
arasında karşılaştırılamaz; T() ve X() sayıları ise yukarıdakilerle
aynı.
Aynı asimetri tek bir Z() çağrısında da görünüyor; oyunda her hamleden sonra
çalışan test bu. Başlangıç pozisyonunda sayıldığında:
Başlangıç pozisyonunda Z()
Beyaz sırada
Siyah sırada
Oran
engine.js geometri çağrısı
97
1.585
16,3×
engine_4x.js geometri çağrısı
54
6
0,11×
Sayılar mikrosaniye değil çağrı sayısı: aynı Z() çağrısı boyunca
G'ye kaç kez girildiği; makineden bağımsız oldukları için tekrarlanabilirler.
engine.js boş kareleri siyahın taşı saydığı için siyah sıradayken
Z() ilk hamlesini bulmadan önce 32 boş karenin her biri için hedef taraması
yapıyor. 4x boş kareleri dışarıda bırakıyor ve Z() bulduğu ilk yasal hamlede
duruyor: tarama sırasında siyahın ilk taşı a7'deki piyon ve hamlesi hemen orada; beyazın
taraması ise a1'de kapalı duran kaleyle başlıyor. Asimetri kaldırılmakla kalmıyor, tersine
dönüyor.
Hüküm: 201.060 pozisyon
Perft V()'ye de 6.9 araması H()'ye de hiç uğramaz. İkinci ölçüm
bayrak düştüğündeki hükmün kendisi: CHA-Solver kaydının 31 Temmuz 2026'ya kadarki tamamı
(201.060 pozisyon; güncel CSV'de source sütunu chasolver olan
satırlar), hakem sayfasında, oyunun kendi sınırlarıyla — 9 yarım hamle ve 2.000 düğümlük bütçe.
engine.js
engine_string.js
engine_4x.js
Kaydın tamamı
53,5 sn
31,7 sn
8,7 sn
Saniyede pozisyon
3.762
6.335
23.074
Pozisyon başına, ortalama
0,13 ms
0,08 ms
0,02 ms
Medyan
0,10 ms
0,10 ms
< 0,10 ms
%99
0,70 ms
0,40 ms
0,10 ms
En kötü
56,1 ms
108,4 ms
6,9 ms
Üçü de aynı hükmü veriyor: 197.464 beraberlik kanıtlanıyor, 3.596 pozisyon yarım
hamle tavanında kanıtsız kalıyor, bütçe hiçbirinde tükenmiyor, hiçbir YANLIŞ TM yok. İki 0x88
motorunda kanıtlar da aynı dağılıyor: 8.139'u malzemeden, 47.047'si kilitten, 141.814'ü
aramadan, 464'ü kökte bitmiş oyundan. Kayıt 30 Eylül 2026'ya uzayınca (204.060 pozisyon)
engine_4x 200.384 beraberlik kanıtlıyor; 3.676 pozisyon yine yarım hamle
tavanında kalıyor, bütçe yine hiçbirinde tükenmiyor; kanıtlar 8.139 malzemeden, 48.135
kilitten, 143.646 aramadan, 464 kökten. engine_4xengine.js'ten
yaklaşık 6 kat, harf tahtasından 3,6 kat hızlı. Süreler tarayıcının 0,1
milisaniyelik saat adımıyla ölçülüyor; medyan ve %99 bu yüzden kaba, karşılaştırma toplam
süreden yapılıyor.
Hükümde perft'in hiç görmediği bir maliyet var: V() çağrıları.
Z her hamleden sonra V'yi iki kez, H her düğümde
V(I)'yı bir kez soruyor ve iki motorun ayrıldığı yerlerden biri de orası.
engine.js'te V() terimlerini | ile bağlıyor, yani her
çağrıda üçü de hesaplanıyor; kilit taraması ise yalnızca tahtada şah ve piyondan başka taş
yokken ve geçerken alma mümkün değilken başlıyor ve o zaman sabit 99 tur dönüyor.
engine_4x kilit terimini || ile bağlıyor: malzeme yarısı cevabı
zaten verdiyse tarama hiç başlamıyor, başladığında da sabit noktada, çoğu zaman birkaç turda
duruyor. V() çağrısı ve arama düğümü sayısı iki motorda aynı. Kayıttaki
pozisyonların çoğu tek düğümde kapanıyor — aramanın medyanı bir düğüm —, yani bir hükmün
maliyetinin büyük kısmı o düğümün hamle üretimi ve V()'si. Fark bu yüzden
perft'teki 3,5 kattan büyük: hamle üretimindeki kazancın üstüne V()'nin bu iki
erken çıkışı ekleniyor.
Oyunda karşılığı: bayrak düştüğünde hüküm medyanda saatin 0,1 milisaniyelik adımının
altında, en kötü durumda 6,9 ms sürüyor; engine.js'te 0,1 ve 56,1 ms. Bütçenin kendisi motor
sekmesinde, ayrı bir başlıkta.
Bu tur: 38 bayt, 1,26 kat
Bu turun adayları önceki engine_4x.js'e karşı tek tek, her biri kendi
dosyasında ölçüldü; plan üç ağır pozisyonu derinlik 3'te tutuyordu (Başlangıç, CPW 3 ve
CPW 4 derinlik 4'te). Seçilen birleşim sonra altı pozisyonun hepsinde derinlik 4'te bir kez
daha ölçüldü. Kazançlar önceki motora göre duvar saati; çağrı sayıları adayın gerçekte neyi
kaldırdığını söylüyor.
Aday
Bayt
Ne kaldırıyor
Ölçülen
Karar
T: saldıran süzgeci
+24
G çağrılarının %40'ı (d3'te 7,1 → 4,3 milyon)
1,16×
alındı
O: 5×5 kutu
+9
G çağrılarının %7'si ve onlardan önceki L çağrıları
1,11–1,19×
alındı
G: kale/vezir hattı (d/s<8)
+4
ışın adımları 2,8 kat az; çağrı yok
tek başına 0,96×
ikisi birlikte, T ve O'nun üstünde: 1,06× — alındı
G: piyon itişinde &&
+1
ışın adımları
—
Rok: önce yol, sonra şah testi
+5
T çağrılarının %0,3'ü
eşit
elendi
Birlikte, altı pozisyonun hepsinde derinlik 4'te: önceki motor 180,8 sn, yenisi 143,7 sn
— 38 bayta 1,259 kat, bayt başına %0,68.
İkisi ilk ölçüldükleri hâlden kısa. T'deki bekçinin &1'i
gereksiz: sağındaki karşılaştırma zaten 0 ya da 1 ve & ondan yalnız en
düşük biti alıyor; 26 bayt 24 oldu. O'daki kutu önce
(x-j+34&15)>4|x-j+34>>>0>68 diye yazılmıştı; 0x88 tahtasının
tahta dışı testi iki yönden uygulanınca, x-j+34&136|j-x+34&136, aynı
5×5 kutu 5 bayt daha ucuza geliyor. İki yazımın sayaçları çağrısı çağrısına aynı; boşta bir
makinede yedi turluk koşuda süreleri de aynı çıktı.
Hüküm tarafında, yeni hamle üretiminin üstüne beş aday daha Temmuz 2026'ya kadarki 201.060 pozisyonluk kayıtta
ölçüldü; hiçbiri alınmadı. Dördü hızlandırmadı: 6.9 aramasındaki şah testini yalnız yaprakta
hesaplamak (+3), aramanın içinde V()'yi Z()'deki
n+e<10 tetiğiyle kapılamak (+14) — önceki motorda gözle görülür hızlandırıyordu
—, Z()'nin iki V() çağrısı arasında || (+1) ve
V()'de map yerine forEach (+4). V()'nin
BigInt bölenini yalnız kilit taranacaksa hesaplamak (+4) bir şey değiştirmedi.
Ölçü ve tuzakları
Perft sayıları test paketindekibench.js'ten
(node bench.js --site --rounds=5), hüküm sayıları hakem sayfasından.
Hız ölçümünü şişiren iki hata zaten biliniyordu: medyan yerine en iyi koşuyu almak ve
hamle üretimini sabit pozisyonlarda döngüye sokup gerçek bir sayımın durum yönetimini dışarıda
bırakan yapay tezgâh. Bu tur üç ders daha ekledi.
Aynı iş tek koşuda farklı ölçülebiliyor.O'daki kutunun iki yazımı
motora tıpatıp aynı işi yaptırıyor — bütün sayaçlar çağrısı çağrısına aynı — ama tek bir
koşu birine 1,19×, ötekine 1,03× gösterdi. Boşta bir makinede yedi turluk koşuda 12,82 ve
13,00 sn çıktılar. Tek koşu hiçbir şeye karar vermiyor; sayaçlar iki adayın aynı işi yaptığını
söylüyorsa sürenin farklı olup olmadığını ancak çok tur söyleyebilir.
Meşgul makine her sayıyı oynatıyor. Dizüstünde başka bir iş çalışırken —
tarayıcıda bir hüküm koşusu gibi — aynı motorun turları ±%20–30 açıldı; boşta, yüksek
öncelikle ±%1–2. Sıranın dönmesi rahatsızlığı bütün motorlara yayıyor ama ortadan
kaldırmıyor.
Bir değişikliğin değeri, içeride ne olduğuna bağlı.G'deki kale/vezir
kontrolü ışın adımlarını 2,8 kat azalttı ve tek başına ölçüldüğünde %4 yavaş çıktı: yürüyüşler
kısa ve ucuzdu, darboğaz değildi. T ve O'nun bekçileri
G çağrılarını neredeyse yarıya indirdikten sonra aynı kontrol, piyonun
&&'iyle birlikte %6 getirdi. Hüküm tarafında tersi oldu:
V()'yi aramada kapılamak önceki motorda hızlandırıyordu, yenisinde
hızlandırmadı. Bir ölçümün bir değişikliğe yazdığı şey, değişikliğin o andaki darboğazdaki
payıdır.
Önceki nesilde ölçülen dersler hâlâ geçerli: bir hamlenin dokunduğu kareleri geri koyan
gerçek bir unmake, tahtayı kopyalamaktan %13 yavaştı ve çöp toplayıcıyı besliyordu;
forEach yerine elle yazılmış bir for döngüsü %10 yavaştı. C'den
taşınan sezgiler — toplu kopya pahalıdır, elle döngü hızlıdır — JavaScript'te geçerli
değil.
Neden burada durduk
Kalan işin nerede olduğunu sayaçlar gösteriyor. T() ve U()engine_4x'te engine.js'tekiyle aynı sayıda çağrılıyor:
G'den geçen her aday bir kez kopyalanıyor ve ona bir kez şah testi soruluyor.
Bunu kısmak yapıyı değiştirmek demek — hamleyi yerinde yapıp geri almak ya da saldırı
tabloları — ve iki yol da ölçülüp bırakılmıştı: unmake yavaştı,
hamle tabloları kilobaytlar tutuyor. Aynı yapının içinde kalan,
zaman payı artık küçük olan bekçiler; son ölçülenler ya karşılığını vermedi ya da baytını
hak edecek kadar vermedi.
Hangi değişikliğin hangi dosyaya gireceği tek bir kurala bakıyor: engine.js en
kısa hâl, engine_4x en hızlısı; 117 baytın hiçbiri engine.js'te yok.
engine_4x.jsL1–L3 sürücülerinin hiçbirine gömülü değil: oyunda motorun mikrosaniyeleri
oyuncunun düşünme süresinin yanında görünmüyor. Yeri sıcak yolun açık kaldığı yerler — bir
veritabanını doğrulamak ve motoru her düğümde çağıran bir arama; builds/special/'daki
botlu sürümler ile LUX da onun çekirdeğini taşıyor. Kaynağı
builds/ altında, bir kopyası da test paketinde.
Harf tahtası
Önceki nesil — tahtayı harf dizgisi olarak tutmak
Motor ilk hâlinde tahtayı sayı dizisi olarak tutmuyordu. Tahta bir harf dizgisiydi:
boş kare -, taşlar RNBQKP ve küçük harfli karşılıkları. Satrancın
kendi notasyonunun doğrudan tahtaya yazılmış hâli.
Sayısal kodlamaya geçiş baştan planlanmıştı; geçiş yapılınca iki şey birden oldu: dosya
küçüldü, motor hızlandı. Ama harf sürümleri silinmedi — beşi hâlâ depoda duruyor ve
karşılaştırmayı mümkün kıldıkları için burada yer alıyorlar.
Sürüm
Arayüz
Düzen
Bayt
L1_prompt_string builds/L1/
prompt
a8=0
1.039
L2_prompt_string builds/L2/
prompt
a8=0
1.618
prompt_string_a1 builds/
prompt
a1=0
2.202
prompt_string builds/
prompt
a8=0
2.205
L2_string_base builds/special/
tıklama
a8=0
1.893
Bu kodlamanın çekirdeği engine_string.js: kuralları engine.js'le
aynı, tahtası harf dizgisi; 1.721 bayt. Aşağıdaki kaynak builds/
dizinindeki dosyanın kendisi.
engine_string.js—
Kodlama nasıl işliyor
Tahta tek bir dizgiden doğuyor ve boş kareler padEnd ile dolduruluyor:
b = [...'RNBQKBNRPPPPPPPP'.padEnd(48, e = _ = '-') + 'pppppppprnbqkbnr']
_ = '-' E = 'a' j = p => p?.toUpperCase()
İki sabit bütün mantığı taşıyor. _ boş kareyi, E ise renk
eşiğini tutuyor: büyük harfler sıralamada 'a''dan önce, küçük harfler sonra
geliyor. Yani renk sınaması bir karşılaştırma — p < E beyaz demek. Tür
sınamasında da doğrudan harfin kendisi kullanılıyor: j(b[i]) taşı büyük harfe
çevirip 'N', 'K', 'P' ile karşılaştırılabilir hâle
getiriyor.
Kayan taşlarda iş daha ince. Sayısal motorda karar tür numarasının aritmetiğine kalıyor:
P+P*4%x'in tekliğine; önceki neslin sayısal motorunda ise bu
bir bit alanıydı: (h*v?h==v:2)&P. Harf sürümünde ikisi de yok, ama
alfabetik sıra var: B < Q < R. Çapraz
sınama h==v & P<'R' fil ile veziri geçiriyor, düz sınama
P>'B' kale ile veziri. Aynı iki soru, aritmetik yerine harf sırasıyla
cevaplanıyor.
Yukarıdaki kod prompt_string_a1'e ait; orada tahta a1'den başlıyor.
Ters indeksli dört sürüm — L1, L2 ve
L3'teki prompt_string ile
L2_string_base — tahtayı a8'den kuruyor. Beşinde de boş kare harfi
_ adıyla tahtanın içinde doğuyor, ama iki ayrı yazımla.
prompt_string_a1'de padEnd(48,e=_='-') otuz iki boş
kareyi dolduruyor, harfe adını veriyor ve geçerken alma karesini aynı argümanda ona
eşitliyor. a8'den başlayan dört sürümde — ve engine_string.js'te — tahta bir
şablon dizgisi: ${(_='-').repeat(32)} harfe adını verip otuz iki kareyi
dolduruyor, geçerken alma karesi de ardından e=_ ile kuruluyor. Sonra sıra, rok
hakları ve yarım hamle sayacı geliyor; L1'de rok maskesi ve yarım
hamle sayacı olmadığı için orada tahtadan sonra yalnızca sıra ve geçerken alma karesi
geliyor.
Aynı arayüz, aynı kural katmanı, iki tahta temsili. Ölçüm:
prompt ailesi
Tahta
Bayt
Tam tarama
prompt
Sayı dizisi
2.089
1.210 µs
prompt_string_a1
Harf dizgisi
2.202 +113
1.640 µs 1,36×
Fark yüz on üç bayt ve üçte birden fazla yavaşlama. Nedeni tek bir noktada toplanıyor:
sıfır doğal bir “hiçbir şey” değeri, - değil.
Sayısal kodlamada boş kare 0 ve sıfır bütün testlerden aritmetiğin kendisi
sayesinde düşüyor: p&7 sıfır veriyor, hiçbir tür dalına uymuyor, hiçbir
maskeden geçmiyor. Harf kodlamasında - harflerin arasında bir yerde duruyor ve
hiçbir şeyden kendiliğinden düşmüyor — her testte ayrıca elenmesi gerekiyor. Bu, kaynağın
her yerine dağılmış küçük bir vergi.
Farkın ne kadar somut olduğunu şu gösteriyor: harf motorunda L(i) hâlâ bir
hamle listesi döndürüyor ve boş bir karenin hamleleri sorulduğunda liste boş gelmiyor.
Başlangıç pozisyonunda e4'ten sekiz hayalet hamle çıkıyor; otuz iki boş karenin her
birinden de dört ile sekiz arası — - kayan taş dalının çapraz yarısından
geçiyor, çünkü '-' < 'R' doğru. Sayısal motorun L(i,f)'si ise boş
bir kareden yapılan hiçbir hamleye evet demiyor: tür sıfır, P+P*4%x de sıfır.
Bu oyunda görünmüyor, çünkü sürücü boş bir kareyi hiç sormuyor; ama bedelin nereden
geldiğini tek satırda anlatıyor.
Motorun kendisinde de hesap aynı sonucu veriyor. engine_string.js — yukarıdaki
kod penceresinde açılan dosya — 1.721 bayt, engine.js'ten 194 bayt
uzun. Kurallar aynı; değişen, tahta ve onu okuyan kod: b[i] artık
9 değil 'P', boş kare 0 değil '-', renk
testi de bit testi değil karakter karşılaştırması (p<'a' beyaz demek).
Baytların nereye gittiği ilginç: başlangıç dizilişi bu temsilde on dört bayt ucuz —
boş kare harfini adlandıran şablon ile geçerken alma karesini kuran e=_ birlikte
66 karakter, sayısal motordaki onaltılık hile ile onu tahtaya yayan for döngüsü ise 80;
prompt_string_a1'in padEnd'li yazımıyla fark on yedi bayt —
ama tahtayı okuyan her test pahalı. Bu sürüm, motorun harf dizgisi üzerinde de aynı hükümleri
verdiğini göstermek için duruyor: sayısal temsil bir zorunluluk değil, bir tercih.
Kazanılan şey okunabilirlik. P=='K' ile P==2 aynı soruyu
soruyor ama birincisi satranç gibi okunuyor. Golfstack'te amaç okunabilirlik değil; yine de
bu, kodlamanın neden önce bu hâliyle yazıldığını açıklıyor: önce doğru olanı anlaşılır
biçimde yaz, sonra ölçüp yont.
Bir ayrıntı daha: harf sürümlerinde j, taşı büyük harfe çeviren yardımcı.
Sayısal motorda toUpperCase gerektiren hiçbir şey yok; aynı harf de başka bir işe
geçti: tahtayı kuran for…in döngüsünün anahtarı ve T'nin taramasının indeksi oldu.
Kodlama değişince yalnızca baytlar değil, harfler de serbest kalıyor — ve serbest kalan bir
harf uzun süre boş durmuyor; güncel durum boşta harfler
tablosunda.
Arayüzde ne kazanılıyor — üç prompt sürümü
Yukarıdaki fark kural katmanının bedeli; arayüzde hesap tersine dönüyor. Üç sürüm aynı
pencereyi çiziyor ve aynı UCI girdisini okuyor: prompt sayı tahtasında
(0x88, a1=0), prompt_string_a1 harf tahtasında a1'den,
prompt_string harf tahtasında a8'den sayıyor. Arayüze ait dört yuva yan
yana, bayt olarak:
Yuva
prompt
prompt_string_a1
prompt_string
Glif ve döndürme
68
51
50
Satır sonu
10
9
9
UCI ayrıştırıcısı
43
33
32
Terfi taşı
19
24
24
Arayüz toplamı
140
117−23
115−25
Dosya
2.089
2.202 +113
2.205 +116
glif ve döndürme— hepsi String.fromCharCode(…+65248) içinde
prompt '-pkqnrb..PKQNRB'.charCodeAt(b[w^7<<t/2])
prompt_string_a1 b[w^t*49+7].charCodeAt()
prompt_string b[w^!t*63].charCodeAt()
satır sonu
prompt w^7?w-119?
prompt_string_a1 w^7?w-63?
prompt_string w-7?w-63?
UCI ayrıştırıcısı
prompt J=V=>(V=K.charCodeAt(V)+8*K[++V]-105)*2-V%8
prompt_string_a1 J=V=>K.charCodeAt(V)+8*K[++V]-105
prompt_string J=V=>K.charCodeAt(V)-8*K[++V]-33
terfi taşı
prompt {r:5,b:6,n:4}[K[4]]
prompt_string_a1 ~'rbn'[U](K[4])?K[4]:'q'
prompt_string ~'rbn'[U](K[4])?K[4]:'q'
Harf tahtası arayüzde 23 ile 25 bayt kazandırıyor ve kaynağı tek: tahtanın kendisi zaten
ekrana yazılacak harf. Sayı tahtası her kareyi bir tablodan, '-pkqnrb..PKQNRB''den,
harfe çevirmek zorunda; harf tahtası karakterin kodunu doğrudan okuyor. Ayrıştırıcıda da aynısı:
sayı tahtası düz indeksi 0x88'e çeviren *2-V%8'i taşıyor, harf tahtasında düz indeks
zaten tahtanın indeksi. Ters yönde işleyen tek yuva terfi. Sayı tahtasında tanınmayan bir harf
nesne aramasında undefined'a, oradan da U'nun varsayılan veziri
p=3'e düşüyor (ayrıştırıcılar); harf tahtasında yazılan harf
olduğu gibi tahtaya gideceği için önce süzülmesi gerekiyor, yoksa e7e8k tahtaya bir
şah koyardı. Süzgeç beş bayt tutuyor. Dosyanın toplamında harf tahtası yine 113 bayt geride:
kurulumda ve arayüzde kazandığını kural katmanının her testinde geri ödüyor.
Düz ve ters tahta.prompt_string ile
prompt_string_a1 aynı motorun iki düzeni ve kural katmanındaki fark sıfır
bayt: piyonun yön sınaması f<i yerine f>i, kilitli taramada iki ad
yer değiştiriyor (a8=0 indeksleme). Arayüzde ise ters tahta iki
bayt önde. a8'den sayan tahtada dizi beyazın gözünden çizilmiş hâliyle duruyor: beyazın sırasında
olduğu gibi yazılıyor, siyahın sırasında tek bir XOR, w^!t*63, tahtayı 180 derece
çeviriyor. a1'den sayan tahta beyaz için de yatayları çevirmek zorunda ve iki tarafı
w^t*49+7 ile ayırıyor — beyazda w^56, siyahta w^7 —: bir bayt
fazla. Ayrıştırıcının sabiti de a8'de kısa: kare = harf kodu − 8 × yatay − 33; a1'de
+ 8 × yatay − 105. Bayta yansımayan bir kazanç daha var: a8'den sayan tahta FEN'in kendi sırasında,
yani bir pozisyonu yüklerken yatayları çevirmek gerekmiyor (FEN bölümü).
Dosyaların toplamında ise ters tahta üç bayt geride: 2.205'e karşı 2.202. Farkı tahtanın
kurulumu yapıyor. prompt_string_a1 boş kare harfini ve geçerken alma
karesini tek argümanda kuruyor, padEnd(48,e=_='-'); prompt_string'in
şablon dizgisi, ${(_='-').repeat(32)}, ondan bir bayt kısa ama geçerken alma karesini
kurmuyor, e=_ başlıkta ayrıca yazılıyor ve başlık altı bayt uzuyor: net beş bayt.
Arayüzün iki baytı bunun ikisini geri alıyor. Kalan üç bayt bilinçli bir tercihin bedeli: şablon
otuz iki boş kareyi sayısıyla gösteriyor, başlık da FEN alanlarının sırasını — tahta, sıra, rok,
geçerken alma, sayaç — bozmadan izliyor (imza).
Harf kodlamasının tam anlatımı ayrı bir
depoda: ChessMiniature.
Burada yalnızca özeti var — elenen mimariler arasında sayılmıyor,
çünkü elenmedi: yerini halefine bıraktı.
Çekirdek
Yalnız hamle üretici — 653 bayt
builds/special/engine_onlyMoveGenerator.js motorun hamle üreten çekirdeği,
tek başına: C, G, T, U, L ve
M. Rok, geçerken alma ve terfi dahil bütün kurallara göre bir hamlenin geçerli
olup olmadığını söylüyor ve hamleyi oynuyor; hüküm vermiyor. Sonuç kodu, sayaçlar, tekrar,
materyal testleri, ölü pozisyon ve 6.9 araması yok. Hamle listesi motorun kendisindeki gibi
iç içe iki döngüyle çıkıyor: her çıkış karesinin her varış karesi için L(i,f)
soruluyor.
Dosya bir kanıt olarak duruyor: doğruluk testlerinin — perft, CPW
pozisyonları, tricky süiti, Vajolet külliyatı — sınadığı şey yalnızca bu çekirdek. Perft bir
pozisyondan çıkan bütün hamle dizilerini sayar; sonuç kodlarına, sayaçlara ya da beraberlik
kurallarına hiç dokunmaz. Test süiti de bu yüzden yalnızca L, G,
C ve ham hamleyi yapan U'yu çağırıyor — 0x88 motorlarında M'yi hiç
kullanmıyor (harf tahtasında ham hamle M'den geçiyor), rok maskesini ve geçerken alma karesini kendisi yazıyor — ve bu dosyayı üçüncü
motor olarak çalıştırabiliyor (node test.js --engine=3); sanity, tricky ve CPW
sayıları ana motorla birebir aynı. Yani testlerin sınadığı çekirdek 653 bayta sığıyor.
engine.js'in kalan 874 baytı bu çekirdeği bir hakeme dönüştüren kısım: motorun
%57'si hamle üretmeye değil, hüküm vermeye gidiyor.
Dosya engine.js'in kısaltılmış hâli, ayrı bir uygulama değil. Başlıktan
L'nin sonuna kadar metin harfi harfine aynı; başlıktan yalnızca yarım hamle sayacı
ile teklif bitleri (n=o=0) çıkarılmış. Gövdelerdeki tek fark M'de:
yarım hamle sayacı ve tekrar anahtarı k yok, geçerken alma karesi de her çift
adımdan sonra olduğu gibi kalıyor. engine.js onu yalnızca alma gerçekten
mümkünse tutuyor, çünkü orada kare tekrar anahtarının parçası (9.2.3.1). Bu çekirdek tekrar
saymadığı için fark hamle listesine yansımıyor.
engine_onlyMoveGenerator.js—
Kapsamlı motor
LUX'un motoru — engine_4x'in kapsamlı hâli, 2.387 bayt
Chess LUX
FideLite'ın kardeş projesi: engine_4x'in üstüne kurulu, kurulumu, saatleri ve
Chess960'ıyla eksiksiz bir oyun sayfası, tek bir 9.553 baytlık HTML dosyası. Oynanabilir hâli
special seviyesindeki LUX. Dosyanın motor satırı
2.387 bayt; aşağıdaki kaynak o satırın kendisi ve engine_4x.js'te olmayan her parça
boyalı.
Satır arayüzle iç içe yazılmış. Başlığında arayüzün birkaç değişkeni de duruyor
(Y, z, dg, m, r, Q);
hamleyi oynatan A ile tekrar sayımı arayüzde, otomatik 3R/50 ayarı Ac ile
armageddon ayarı Ag de oradan geliyor. Bu yüzden builds/special/engine_lux.js tek başına oyun yönetecek bir
motor değil; LUX'un motorunu ayrı okumak için ayrılmış hâli. Hamle üretimi ise tam:
test süiti onu beşinci motor olarak koşuyor (node test.js --engine=5)
ve perft sayıları engine_4x'inkilerle birebir aynı. Perft kilit dedektörüne hiç
uğramadığı için bu, geniş dedektör hakkında bir şey söylemiyor; onun kapsamı CHA-Solver'ın
oyunlarıyla ölçüldü.
builds/special/engine_lux.js—
743 bayt nereye gidiyor
Kalem
Fonksiyon
Bayt
Geniş kilit dedektörü — aşağıda katman katman
V
+529
Şahın kendi kalesinin üstüne giderek rok yapması (Chess960)
G +78, U +57, L +23, M +7, C ile tablosu y +4
+169
Her hamleden sonra iki taraf için 9 yarım hamlelik ölü pozisyon araması; otomatik 3R/50
Z
+42
Armageddon: her hamleden sonraki ölü pozisyon sorusu yalnız beyazın mat imkânı için ([8*!Ag,0]); kod yine DP
Z
+4
Aramada kilit dedektörünü yalnız almadan ya da piyon hamlesinden sonraki iki yarım hamlede sormak (n+e<10); otomatik 3R/50
H
+14
Bayrak düşmesinde 300 yarım hamle ve 400.000 düğüm
F
+8
Teklif bitini arayüz veriyor; kendiliğinden gelen sonuçlar anlaşmadan ve taleplerden önce soruluyor
D
+3
5×5 kutu yerine T'deki uzaklık süzgeci: rok kalesi altı sütun uzakta olabilir
O
−11
Hamleyi oynatmak ve tekrar sayımı arayüzde; arayüzün başlıktaki değişkenleri
A, başlık
−15
Toplam — engine_4x.js 1.644 → builds/special/engine_lux.js 2.387
+743
Kilit dedektörü katman katman
LUX'un V'si engine_4x'inkinden 529 bayt uzun ve fark üç katmana
ayrılıyor. Aynı katmanlar engine.js'in yazımıyla da sayılabiliyor:
Katman
engine.js'te V
engine_4x ve LUX'ta V
Bugünkü dedektör: şah ve piyon
405
432
+ filler, şahın alabileceği piyonlar, mat edemeyen şahlar, hiç kıpırdayamayan şah motorda vardı, çıkarıldı
+333 → 738
+334 → 766
+ kutulu at, kale ve vezir yalnız LUX için yazıldı
+169 → 907
+168 → 934
+ çıkabilen şah kısa yolu
—
+27 → 961
Motorun tamamı
1.527 → 1.860 → 2.029
Bugünkü dedektör. Duvar kıpırdayabilir: bir piyon kendi dikeyinde ilerleyebiliyor,
yeter ki hiçbir yol bir almaya, son yataya ya da şaha çıkmasın
(kilitli pozisyonun koşulları). Şahın bir piyonu alabilmesi de, ilerleyen bir
piyonun şah çekebilmesi de hayat sayılıyor; tahtada şah ve piyondan başka tek bir taş varsa
tarama hiç başlamıyor.
Birinci katman. LUX'un dedektörü bunların dördünü ölü pozisyonun içine alıyor.
Filler: fil duvarın arkasında dolaşabilir, yeter ki bir piyonu alamasın ve bir piyonun
alabileceği kareye çıkamasın. Şahın alabileceği piyonlar: şahın bir piyona ulaşması tek
başına hayat sayılmıyor; piyon duvardan çıkarılıyor ve arkasında bir şeyin açılıp açılmadığına
bakılıyor — tarama her şah için ayrıca koşuyor. Mat edemeyen şahlar: ilerleyen bir piyonun
ya da bir filin çekebileceği şah kilidi tek başına bozmuyor; ancak şahın kaçacak karesi
kalmayabiliyorsa sayılıyor. Kilit bu yüzden bir taraf için tutup öbürü için tutmayabiliyor; aynı
tahtada bir taraf hâlâ mat edebilir. Hiç kıpırdayamayan şah: duvarın parçası sayılıyor ve
önündeki rakip piyonu donduruyor.
Bu katman engine.js'te bir süre durdu: 0x88 neslinde motor 1.860 bayta çıktı
(o günkü engine.js ↗️),
engine_4x 1.978'e; sonra katman çıkarıldı ve iki motor bugünkü boyuna indi. Gerekçe
askıdaki genişletme başlığındakiyle aynı: bayt başına getiri ve motorun
tek ispatlı özelliği. LUX onu tutuyor.
İkinci katman: kutulu at, kale ve vezir. Tahtada tek bir at, kale ya da vezir dedektörü
kapatıyor; LUX böyle bir taşın kutulu olduğu durumda kalmasına izin veriyor. Kutulu taşın
gidebileceği her karede kendi sabit adamı duruyor: atta at kareleri, kalede dört dik komşu,
vezirde sekiz komşu. Sabit adam ya rakip şahın alamayacağı kalıcı tıkalı bir piyon ya da başka bir
kutulu taş. Kutulu taş önündeki piyonu da durduruyor; rakip şah ya da bir fil ona ulaşabiliyorsa
“düşmüş” sayılıyor ve duvar olmaktan çıkıyor; bir piyon alabiliyorsa hiç sertifika çıkmıyor.
Standart satrançta neredeyse hiç görülmüyor — kayıttaki 204.060 oyunun yalnızca birinde var:
1n6/2Bp4/p1pPp1k1/PpP1Pp1p/1P3P1P/2K5/8/8 w - - 7 53
Siyahın b8'deki atı a6, c6 ve d7'deki piyonlarının arasında kutulu. Beyaz fil onu alabilir ama
almak hiçbir şeyi açmıyor ve siyah hiçbir hamle dizisiyle mat edemiyor: doğru sonuç beraberlik.
LUX bunu arama yapmadan, dedektörle veriyor. Bu katman engine.js için hiç yazılmadı;
LUX için yazıldı, çünkü Chess960'ta kale, at ve vezir başka karelerden başlıyor ve kendi piyonlarının
arkasında kalan bir taş çok daha olası.
engine.js'e eklenseydi kaç bayt tutardı? LUX'un kutulu taş kodunu
engine.js'in yazımına — sabit nokta yerine 600 tur, kısa devre yerine | —
taşıyınca V 738'den 907'ye çıkıyor: 169 bayt; motor 2.029 bayt, bugünkünden 502
bayt uzun. Bu ara sürüm hiç yayımlanmadı. LUX'un dedektörüyle yan yana çalıştırıldı: Arbiter'in veri dosyasındaki 204.060 pozisyonun her birinde, iki taraf için de aynı hükmü veriyor.
Üçüncü katman: çıkabilen şah. Dedektör bir şah için bütün çözümlemeye girmeden önce o şahın
hiçbir piyonun tutmadığı ya da tehdit etmediği bir kareye adım atıp atamayacağını soruyor; atabiliyorsa
o şahın durumu hemen kapanıyor. Hükmü değiştirmiyor, yalnız hızlandırıyor. Aramadaki
n+e<10 kapısı gibi o da baytını ancak uzun bir aramada karşılıyor: kapı
engine_4x için de ölçüldü ve 9 yarım hamle, 2.000 düğümlük aramada karşılığını vermedi
(bu tur). LUX'un aramaları çok daha uzun olduğu için ikisini de tutuyor.
Rokun tek bekçisi L — b dikeyindeki uzun kale
LUX'ta rok, şahın kendi kalesinin üstüne gitmesiyle oynanıyor ve G'nin şah dalı bu
hamleyi tahtaya dokunmadan yanıtlıyor: hak duruyor mu, şahın ve kalenin geçeceği kareler boş mu,
şahın yolundaki karelerden biri tehdit altında mı. Son soru tahta olduğu gibiyken, yani kale daha
yerindeyken soruluyor. Chess960'ta bunun yetmediği tek bir durum var.
Uzun kale b1'de, a1'de rakip bir kale ya da vezir dursun. Uzun rokta şah c1'e, kale d1'e
gidiyor ve b1 boşalıyor; a1'deki taş artık c1'i görüyor, şah rokun kendisiyle çekmeye giriyor.
G sorduğu anda b1'de kale durduğu için a1'in hattı kapalı, c1 tehdit altında
görünmüyor ve G roka izin veriyor. Rokun kuraldışı olduğunu söyleyen L:
her hamlede yaptığı gibi hamleyi kopya bir tahtada U ile oynatıyor ve şahı
T() ile soruyor. Ölçüldü: Şd1 ve Kb1 ile a1'de siyah kale ya da vezir varken
G(3,1) evet, L(3,1) hayır diyor; a1 boşken ya da orada fil veya at
dururken ikisi de evet.
Boşluk yalnızca bu. 960 dizilişin hepsinde, iki yöne rok için, tahtada yalnızca yerindeki
beyaz şah ile iki kalesi ve kalesiz bir dikeydeki siyah şah varken her boş kareye tek bir rakip
kale, vezir, fil, at ya da piyon kondu: 552.960 konum. G ile L yalnızca 468'inde ayrışıyor ve
hepsi aynı kalıpta: uzun rok, kale b1'de, rakip kale ya da vezir a1'de. a1 ile c1 arasındaki tek
kare b1; hat ancak o boşalınca açılıyor. Kısa yanda tarama tek bir ayrışma bulmadı. Uzun kalesi b
dikeyinde olan 252 diziliş var; 234'ünde boşluk ilk hamleden açık, kalan 18'inde kısa kale d1'de
durduğu için uzun rok ancak o kale yerinden oynadıktan sonra mümkün. Başlangıçta a1'de kendi
taşın duruyor; tehlike o taş gidip yerine rakip bir kale ya da vezir gelince doğuyor.
Kural şu: rok L'nin geçerlilik denemesinden geçmeli. Bayt ya da hız uğruna rokun
bu denemeyi atlayıp G'nin hükmüyle yetinmesi açığı geri getirir. Rokun 56 iskelete
göre sabit kodlandığı eski 960 motoru tam olarak bunu yapıyordu: rok
hamlesini kendi durağan testlerinden sonra doğrudan listeye ekliyor, öbür her hamlenin geçtiği
yap–dene–geri al süzgecine sokmuyordu. Açık orada bu 252 diziliş için yazılmış ayrı bir a1
bekçisiyle kapatılmıştı, +26 bayt; roku genel süzgeçten geçirmek +59 tutuyordu. LUX'ta bekçiye
gerek yok, çünkü rok zaten her hamle gibi L'den geçiyor: tıklanan hamle de, hamle
sorguları da. Test süitinin perft koşuları standart tahtada ve bu konuma uğramıyor; bu denetim
ayrıca yapıldı.
Alternatifler
Denenip elenen yollar
Motorun tahta temsili bugün 0x88: on altı sütunlu, kare merkezli
seyrek bir dizi; yatayların arasında sekizer boş hücre var. Önceki neslin tahtası düz, 64
elemanlı bir diziydi — dolgu yok, 0x88 yok, bitboard yok — ve bu bölüm ilk yazıldığında o
yapı “geriye kalan” diye anlatılıyordu. Aşağıdakilerin hepsi ya yazıldı ya da ölçüldü.
Bu bölüm bir kayıt: ne denendi, neden tutmadı. Sayılar eskimiyor, çünkü her karar kendi
gününün ölçümüne ait; çoğu önceki neslin motorunda ölçüldü ve öyle bırakıldı. Ama “elendi”
her zaman “kullanılmadı” demek değil. En büyük örnek tahtanın kendisi: dolgulu
tahta önceki nesilde gerekçeleriyle elenmişti; yeni motor ise onun üstüne kuruldu.
Taş alfabesi de öyle: eski motorda hiçbir aday kazanmadı, 0x88'de
arama yeniden açıldı ve bugünkü alfabe oradan çıktı. Bir fikri bir motorda elemek onu
bitirmiyor: bayt farkı motora ve dosyaya göre değişiyor.
Öklid uzaklığı — ilk tasarım, iki karede çöktü
İlk fikir hareketi mutlak uzaklıkla tanımlamaktı: kesikli düzlemde bir kareden ötekine
Pisagor uzaklığı. Boş bir 8×8 tahta çizip seçilen kareye göre bütün uzaklıkları ölçen küçük
bir simülasyon yazıldı ve formüller ilk bakışta kusursuz duruyordu — at hep √5'te, şah 1 ile
√2 arasında, kale {1…7} tam sayılarında, fil √2'nin katlarında {1√2…7√2}, vezir de son
ikisinin birleşiminde.
At ve şah gerçekten temiz. Sekiz kareli uzayda d²=5 veren tek şekil (1,2); √5 tek başına
ata ait. d²≤2 veren şekiller (0,1), (1,0), (1,1) — şahın da rakibi yok.
Kale ve fil için durum farklı. Bütün uzaklık uzayında tam iki çarpışma var:
Uzaklık
Şekil
Çarpışan şekil
5
(0,5) — kalenin beş karelik hamlesi
(3,4) — hiçbir taşın hamlesi değil
5√2
(5,5) — filin beş karelik hamlesi
(1,7) — hiçbir taşın hamlesi değil
3-4-5 üçgeni kalenin beşinci karesini, (1,7) filin beşinci karesini taklit ediyor.
Vezir ikisini birden miras alıyor. Uzaklık tek başına geçerli hamleyi tanımlayamıyor.
İkinci sorun daha da ölümcül: uzaklık yönsüz. Karekök işareti yutuyor, piyon ise
tahtanın tek yönlü taşı. Öklid yaklaşımında piyonu hiçbir yama kurtaramazdı.
Çözüm karekökü atmak oldu: yatay ve dikey farkları ayrı tutmak. Önceki neslin
h ve v'si buradan geliyor ve tek adımda iki sorun birden çözüldü —
çarpışma, çünkü (3,4) ile (0,5) artık farklı iki çift; yön, çünkü
f<i^b[i]&1 işaretli bir karşılaştırma taşıyordu. Vazgeçilen yaklaşımın
kalıntısı orada açıkça görünüyordu: h*v==2 at, h|v<2 şah,
h==v fil, h*v==0 kale. Uzaklık gitti, şekil kaldı. Harf tahtalı
engine_string.js bu şekil testini bugün de taşıyor; yeni motor şekli de bıraktı
ve hamleyi indeks farkından okuyor — ayrıntısı bir sonraki
başlıkta.
Çöküşün nerede olduğunu kareye tıklayarak görmek için o günkü simülasyon hâlâ duruyor:
Board_Delta_Coordinate.html
— bir kare seç, bir taşın uzaklıklarından birini tutan bütün kareler yanıyor; çakışanlar
taralı çıkıyor ve tek bakışta görünüyor.
Dolgulu tahta — önce elendi, sonra alındı
Uzaklık şekle dönüştükten sonraki soru şuydu: şekil testinden de vazgeçilebilir mi?
Geçerliliği i ile f arasındaki ham indeks farkından okumak düz 64
karelik tahtada mümkün değil, çünkü fark kenarda belirsiz: +1 bir sağa demek ama
h sütununda bir üst yatayın a sütununa sarıyor; çaprazların +7 /
+9'u ve atın +6/+10/+15/+17'si de aynı şekilde. Belirsizliği
kaldırmak için |i%8-f%8| eklemek gerekiyor, bu da zaten h'nin
kendisi; yani düz tahtada fark yolu şekil testine geri düşüyor. Fark ancak dolgulu bir
tahtada tek anlamlı oluyor: 0x88'in boş sütunları ya da 10×12 mailbox'ın gözcü kenarı sarmayı
emiyor.
Önceki nesil burada durdu ve dolguyu eledi. Gerekçe şuydu: dolgunun sattığı bedava
kenar testi, hedefi f = i + ofset ile üreten bir hamle üreticisinde alıcı bulur.
Bu motor hamle üretmiyor, geziniyor: G'ye başlangıç ve varış karesi
birlikte geliyor, varış da zaten var olan bir kare. Üstüne iki maliyet öngörüldü: dizi 128
hücreye çıkınca hedef taraması yarısını ayrıca elemek zorunda kalacak, başlangıç literali de
iki katına çıkacaktı. Yatay ve dikey farkla kurulan şekil testi en ucuz yol sanılıyordu.
Fark yolu önce düz tahtada denendi. L1'in light sürümünde aynı
arayüz ve aynı şablonla iki 8×8 dosya kuruldu: biri h / v'li,
öbürü 0x88 farkını düz indekslerden hesaplayıp (X=a(F+F-h)) yön seçimini onun
üstüne kuran. Birincisi 1.105, ikincisi 1.121 bayt. Farkı hesaplamanın bedeli kazancı
yiyordu.
Tahta gerçekten on altı sütuna genişletilince fark bedavaya geldi: d=f-i. Yön
tek bir ifadeyle seçiliyor: x=d%15?d%16?d%17?1:17:16:15; at
d*d%765%128==68, şah d==s. Hamle geometrisinden Math.abs,
i%8 ve i>>3 tamamen çıktı. Önceki neslin G'si 247
bayttı ve a=Math.abs, takma adına (11 bayt) dayanıyordu — üç a(
çağrısının ikisi G'deydi. 0x88 motorunun G'si 236 bayt. Geçiş önce
L1'in light sürümünde yapıldı, sonra L2 ve
L3'te, en son da motorun kendisinde.
Öngörülen iki maliyet gerçekleşmedi. Hedef taraması delikleri hiç görmüyor:
b seyrek bir dizi — değer yalnızca 64 gerçek kareye yazılıyor, aradaki 56 hücre
hiç atanmamış delik olarak kalıyor — ve map, some, every
deliklerde geri çağrıyı hiç çalıştırmıyor. Dizinin yarısını eleyen ek bir test yazılmadı.
Literal de iki katına çıkmadı: aynı 64 haneli dizgi j*2-j%8 ile yayılıyor ve
kurulum 70 bayttan 83'e çıktı (+13); bugünkü for…in yazımıyla 80 bayt (+10). Yan kazanç olarak kare rengi i*9/8%2+1
yerine i%15%2+1 ile hesaplanıyor, çünkü 16 ≡ 1 (mod 15).
Dolgunun kenar testi de alıcı buldu — üreticide değil, yürüyüşte. Kayan taşlar için adım
zincirinin son dalı 1: düz bir doğru üzerinde olmayan her fark yatay adım
sayılıyor. S o yönde yürüyor ve varışa ulaşamadan, en geç ilk delikte duruyor.
Bunun için yürüyüş koşulunun b[i]<1 olması şart: undefined<1
yanlış ve yürüyüşü durduruyor, !undefined ise doğru ve yürüyüşü sürdürüyor.
Önceki nesil burada bir bayt kısa olan !b[i]'yi taşıyordu; yeni motorda o
kısaltma bir hata. Tuzaklar bölümünde anlatılıyor.
Önceki neslin bir gerekçesi bugün de doğru: yolun kapalı olup olmadığı temsilden bağımsız
bir soru; kayan taşın yolu yine yürüyerek sınanıyor. 0x88 S'yi kaldırmadı,
yalnızca h ile v'yi kaldırdı. Motor da hâlâ ofset üretmiyor,
geziniyor; dolgu kenar için değil, farkı tek anlamlı kılmak için orada. Geriye kalan yapı:
0x88 + G'nin fark testi + S'nin yürüyüşü.
İndeks farkının düz tahtada neden yanıltıcı olduğunu kareye tıklayarak görmek için o
günkü ikinci simülasyon duruyor:
Board_Delta_Index.html
— bir kare seç, her hedef karenin üstüne o kareye giden indeks farkı yazılıyor. Aynı farkın
tahtanın iki ucunda iki ayrı hamle anlamına geldiği ve kenardan taşan farkların nereye
düştüğü orada tek bakışta görünüyor. Düz 8×8, 0x88 ve 10×12 düzenleri arasında geçiş
yapıldığında taş aynı karede kalıyor; düz tahtada yanlış kareye sızan farkın dolgulu tahtada
nasıl emildiği yan yana izlenebiliyor.
Tahtanın eni — neden on altı
Dolgu kabul edilince sıradaki soru genişlikti; on altı tek seçenek değildi. 10, 12, 16 ve
17 sütunlu tahtalar aynı L1 light şablonundan üretildi, her biri
perft ve tarayıcı senaryolarıyla sınandı — en yakınları ayrıca bağımsız bir hakeme karşı
rastgele oyunlarla — ve bayt bayt ölçüldü. Tahtaya bağlı her yuvanın — başlangıç eşlemesi,
geçerken alınan piyonun karesi, kare rengi, kayan taş testi, ekranı döndürme, yön zinciri, at
testi, çift adım ve son yatay sabitleri — en iyi yazımı her tahta için ayrıca arandı. Sayılar
o günkü sıkı arayüzle ölçüldü; sitenin arayüzü her dosyaya aynı 24 baytı eklediği için
sıralama değişmiyor.
Tahta
En iyi düzen
Bayt
16×8'e göre
16×8
normal
909
—
17×8
ters sıralı
912
+3
10×8
ters sıralı
913
+4
16×12
ters sıralı
919
+10
12×10
ters sıralı
921
+12
Asıl bulgu ekranın döndürülmesinde çıktı. Tahta her zaman sırası gelen tarafın
gözünden çiziliyor ve şablon ekran sırasını gerçek kareye çevirmek zorunda. 10 genişlikte
normal düzende bu 23 karakter tutuyordu (u=w+7-w%10*2,t?u=77-u:u); yataylar 8'den
1'e doğru saklanınca 10 karaktere indi (u=t?w:77-w) ve başka hiçbir yerde bedel
çıkarmadı. 10×8 için tahmin 926–928'di; normal düzen 926, ters sıralı düzen 913 bayt ölçüldü.
16 genişlikte ise XOR zaten 10 karakter (u=w^7<<t/2); ters düzen de orada
u=w^119>>t ile aynı uzunluğa iniyor.
10×8 ile 16×8 yuva yuva karşılaştırılınca 10 genişlik yön zincirinde
(x%9?x%10?x%11?1:11:10:9, −2), at testinde (x*x%257%40==24, −1),
çift adım ve son yatay sabitlerinde (birer bayt) kazanıyor; satır başında, kare renginde ve
kayan taş testinde birer bayt kaybediyor. Farkı iki yuva belirliyor: başlangıç eşlemesi
(j*5-j%8>>2, +3) ve geçerken alınan piyonun karesi
(30+f%20, +3). 16'da ikisi de tek işlem: j*2-j%8 ve
f^16. Net fark +4.
Tek genişlikler kâğıt üstünde rekor kırdı ama geçersizdi. 11×8 908, 13×8 909 bayt
verdi. Genişlik tekken iki çapraz adım (W−1 ve W+1) çift sayı oluyor ve ortak katları tahtanın
içine düşüyor: 11 genişlikte 60, hem (5,+5) hem (6,−6) çaprazına karşılık geliyor. Zincir
farktan tek bir yön seçtiği için öbür hamle kayboluyor — 11×8'de perft dokuz hata verdi ve
rastgele oyunlarda Fc6xh1 hiç üretilmedi; 13×8'de aynı sorun Fh2-b8'i götürdü. 17'de ortak kat
tahtanın dışında kalıyor ve 17×8 geçerli: 912 bayt. Tek genişlik orada iki yuvayı kısaltıyor —
kare rengi w&1 (−3), kayan taş testi P>>d%2 — ama eşleme ve
geçerken alma yine pahalı. 12, 14, 15 ile 19 ve üstü genişlikler kurulmadı; tahminleri en az
913 bayt. 9'da hem aynı çapraz sorunu hem de atın belirsizliği var.
Kurallar arttıkça sıralama biraz değişti.L2'de 17×8, ölü
pozisyon tespitinde kare rengini i%2+1'e indirip 16×8'e yetişti: ikisi de 1.540.
L3'te de eşit kaldılar: 2.325. 10×8 iki seviyede de dört bayt
geride. Eşitlikte 16×8 seçildi, çünkü yalnızca onda 6.9 araması hamleleri önceki motorla aynı
sırada, a1'den h8'e geziyor ve bütçeyi birebir aynı harcıyor. Ters sıralı tahtalarda hüküm
hiçbir testte değişmedi ama bütçe farklı yerlerde tükendi: CHA-Solver'ın Temmuz 2026'ya kadarki 201.060 oyunluk
listesinde 2.707 pozisyonda.
Toledo'nun JavaScript satrancı 10×12'lik, 120 hücreli bir tahta kullanıyor; hamleleri yön
tablosuyla üreten bir motor için doğal seçim bu. Burada ise motor hamle üretmiyor, hamleyi
farktan doğruluyor; fazladan dolgu sıraları yalnızca indeks kaydırma bedeli getiriyor.
16×12 ile 12×10'un fazladan sıraları bu yüzden hiçbir şey kazandırmadı.
Tahtayı sayı olarak tutmak — iki deneme, iki eksen
Bu bölümün tezi iki kelimeye dayanıyor: kare merkezli ve dizi. Denenen iki alternatifin
her biri bunlardan birini reddediyor.
Bitboard kare merkezliliği bırakır: bilgi taş türüne göre ayrılmış 64 bitlik
kelimelere dağılır, her bit “bu karede beyaz piyon var mı?” sorusunu cevaplar. Küme işlemleri
ucuzlar — bütün beyaz piyonların tehdit ettiği kareleri tek maskede alırsın. Karşılığında
“12. karede ne var?” sorusu pahalılaşır; her kelimeyi ayrı ayrı sınaman gerekir.
Paketlenmiş tam sayı ise kare merkezliliği korur, yalnızca kabı değiştirir: aynı 64
basamak, dizi yerine tek bir sayının içinde. Veri modeli birebir aynı, erişim biçimi
farklı.
İkisi de aynı araca mecbur: BigInt. 64 kare × 4 bit 256 bit eder; bu,
Number'ın 53 bitlik tam sayı hassasiyetinin çok ötesinde. Yani iki yol da
JavaScript'in BigInt bedelini ödüyor — her literale n eki, karışık her ifadeye
açık bir dönüşüm ve BigInt ile Number'ı + ile
karıştırdığın anda bir TypeError.
Bedelin nerede biriktiği ölçülebilir. engine.js tahtaya köşeli parantezle
on üç yerde dokunuyor — dokuzu okuma, dördü atama — ve ayrıca indexOf,
some, map ve every ile altı kez geziyor. Dizi hâlinde
b[i] dört karakter; paketlenmiş hâlde aynı okuma bir kaydırma ve bir maske ister,
kabaca 25 karakter. Yalnızca okumalarda fark 180 baytı aşıyor; gezinmelerin ise doğrudan bir
karşılığı yok — altısı da elle yazılmış döngüye dönüşür. Bitboard'da bunun üstüne bir de
saldırı tabloları eklenir.
C ya da Assembly'de bu modelleri ucuz kılan şey, işlemci düzeyinde bedava olan 64 bitlik
aritmetik. JavaScript'te o zemin yok; kazandırmak bir yana, iki deneme de yüzlerce bayt
ekledi. Dahası ikisi de motorun bütün iskeletini değiştirecek, karşılığında yönetilmesi zor
bir algoritmik bilinmezlik yığını getirecekti — hızı boyuta takas eden bir projede yanlış
yönde bir takas.
İşin ironisi, BigInt motorda kaldı; yalnızca bambaşka bir işte. Başlangıç
tahtası 10n**40n-10n**32n ile kuruluyor: ifade 40 basamaklı bir sayı üretiyor —
sekiz adet 9, ardından otuz iki adet 0 — yani beyaz piyon sırası ve
ortadaki dört boş yatay. Kaynakta 20 karakter tutuyor, ürettiği dizgi 40 karakter. 20 bayt
kazanç. BigInt tahta temsili olarak elendi, literal sıkıştırıcı olarak kaldı — bir de
kilitli taramada: V piyonları ve şahları 64 bitlik
BigInt kümelerinde tutuyor. Tahtanın tamamı için pahalı olan model, tek bir sorunun içinde ucuz.
İki kare bir karakterde — kaynakta bayt bellek değil
Tahta sayıya dönünce sıradaki adım kendiliğinden akla geliyor: her kare
dört bit, bir bayta iki kare sığar; 64 karelik başlangıç tahtası 32 bayta
inmeli. Bu, bellek için doğru bir cümle. Küçültülen ise bellek değil, kaynak metin; orada
bir bayt tek bir ASCII karakteri demek ve iki kare o karaktere sığmıyor.
Sayı basit. Bir kare on üç değer alabiliyor: boş kare ve on iki taş. İki karelik bir çift
13 × 13 = 169 değer eder. UTF-8'in tek baytlık aralığı 0–127, toplam 128 karakter; tırnak, ters
bölü, satır sonu ve HTML'in sorun çıkardığı denetim karakterleri düşülünce daha da az. Çiftlerin
bir kısmı ister istemez 127'nin üstüne taşıyor ve UTF-8 bunların her birini iki baytla yazıyor.
Beyazın kodları siyahınkinden 8 fazla olduğu için taşanlar çoğunlukla beyaz taşlı çiftler:
başlangıç pozisyonunda beyaz arka sıranın ve beyaz piyonların sekiz çiftinin sekizi de iki
baytlık karaktere düşüyor.
Asıl sıkıştırma zaten yapılmış. Yukarıdaki 10n**40n-10n**32n ortadaki 40 kareyi
20 karakterde yazıyor: kare başına yarım karakter, çiftlemenin vaat ettiği yoğunluğun ta kendisi
— üstelik ayrı bir çözücüye gerek kalmadan. Çiftlemenin kazandırabileceği yer, geriye kalan 24
kare.
Bugünkü engine.js'te kurup ölçtüm. Çiftleri 16a+b+32 diye kodlayınca
boş çiftler boşluk karakterine düşüyor ve padEnd(24)'ün varsayılan dolgusuyla
bedavaya geliyor; dizgi 46 bayttan 41'e iniyor, −5. Ama her karakteri iki kareye açan çözücü
pahalı: bugünkü '0x'+s[j]-0 yerine s.charCodeAt(j>>1)-32>>4-j%2*4&15
ve onu 64 kez döndüren döngü, +27. Bulabildiğim en kısa yazımda motor 1.527 bayttan 1.549'a çıktı:
+22. Tahta da sıraların arasındaki delikler de birebir aynı kaldı; sanity, CPW ve zor
pozisyonlar geçti. Her çift tek bayta sığsaydı bile dizgi ancak 32 bayta inerdi ve motor yine 13
bayt uzardı. Önceki nesilde, düz 64'lük tahtada aynı deneme +16 çıkmıştı.
Ders, taş alfabesindeki “neyi küçültüyoruz?” sorusunun aynısı: dört
bitlik kod tahtanın bellekteki boyutunu anlatır, programınkini değil. Kaynakta sayılan
karakterdir.
Tahtayı dedektöre göre kurmak — bitboard ikinci kez
Yukarıdaki deneme bitboard'u bütün motor için tarttı. Bu ikincisi daha dar bir soruydu ve
önceki nesilde soruldu; o gün kilitli pozisyon taraması l, yetersiz materyal
kontrolü ise I ile Im'di; bugün ikisi de
V'nin içinde. Kural katmanının en pahalı parçası kilitli
pozisyon taraması ve bu tarama zaten 64 bitlik kümelerle çalışıyor: tahta dizisini okuyup
kümelere çeviren bir köprüyle açılıyor. Tahta baştan küme olarak tutulursa köprü kalkar.
Öbür kurallar pahalılaşsa bile l'nin ucuzlaması bunu karşılar mıydı?
Bu üç yoldan ölçüldü. Melez: tahta dizi olarak kalır, kümeler her hamlede yanında
yeniden kurulur, l ile Im onlardan okur. Motor o günkü hâlinden 15 bayt
kadar uzun çıktı; kümeleri her hamlede kurmak onu üçte bir kadar da
yavaşlattı. Tam dönüşüm: tahta baştan küme olarak tutulur; G,
V, M ve L küme tarzında yazılır. Kural çekirdeği
neredeyse iki katına çıktı; G'nin tek bir ifadede çözdüğü geometri adım
tablolarına, ışın döngülerine ve bir “bu karede ne var?” yardımcısına bölündü.
Tavan: tahta zaten küme olsaydı ölü pozisyon tarafı ne kazanırdı? Sıfıra yakın.
Tavanı sıfıra indiren, yetersiz materyal kontrolü I. Dizide bir filin kare
rengi karenin indeksinden bedava çıkıyor (düz 64'te i*9/8%2+1, 0x88'de
i%15%2+1); kümelerde aynı bilgi 64 bitlik bir renk maskesi istiyor.
l'nin kazandığını I geri veriyor. Ölü pozisyon tarafı öbür
kurallara harcanacak bir bütçe üretmiyor.
Denemenin asıl bulgusu başka yerde çıktı: kazanç temsilden değil, köprünün kendisinden
geliyor. Köprüyü genel bir döngüyle — her taş kendi kümesine, gerekenler yapı çözmeyle
(destructuring) — yeniden yazmak tahta dizi olarak kalırken de işe yarıyor; önceki nesilde
motor bu yolla on bayt kadar kısalıyordu, tarama da hızlanıyordu. Filli
genişletme de aynı sonucu verdi. Yeni motorda köprü tam bu biçimde: V tahtayı
tek bir b.map ile geziyor, piyonlar ve şahlar kümelere, öbür taşlar sayaçlara
gidiyor; materyal kolu ile kilitli tarama aynı geçişi paylaşıyor. 0x88'den kümeye geçiş de
bedava: b.map delikleri atladığı için yalnızca 64 gerçek kare a1'den h8'e sırayla
geziliyor ve her karede biti ikiye katlamak (j+=j) doğrudan 8×8'in bit düzenini
veriyor.
Tahtada boşta kalan değerler — üç deneme
Tahtanın her karesi tek bir onaltılık basamak ve on altı değerin hepsi kullanılmıyor: önceki
nesilde 1, 14 ve 15 tahtada hiç geçmiyordu, bugünkü
alfabede de tür 7 (7 ve 15) boşta. Bu boş değerlerin iş görüp
göremeyeceği önceki nesilde üç kez denendi; üçü de elendi.
Geçerken alma karesini tahtaya taşımak. Motorda e ayrı bir global
değişken. Onun yerine atlanan kareyi boş bir kodla tahtanın kendisinde işaretlemek denendi:
bir “hayalet”. Fikir temiz — pozisyon tek doğruluk kaynağı oluyor, tekrar anahtarından bir
alan düşüyor, bir harf serbest kalıyor; hayalet tahtadayken kilitli pozisyon taraması da
kendiliğinden susuyor. Kaybettiren şey tek cümleye sığıyor: e bir değişkenken
geçerken alma karesinin yeri bedavaya biliniyordu; tahtaya gömülünce her hamlede aranması
gerekiyor. Eski hayaleti silen zincir, taramayı yeniden zorlayan satır ve koruma koşulları,
dağınık küçük kazançları aştı: sayaçlı motorda net +5 bayt (indexOf takma adıyla
+2), yalnızca matı ve patı bilen sürümde +2, perft %25 daha yavaş. Elenmesinin nedeni bir hata
değil, bedeldi — yedi pozisyonda perft sayıları birebir tuttu. İşareti atlanan kareye değil
piyonun kendisine yazan ikinci tasarım daha iyi çıktı, ama o da kaybettirdi. Hayalet
kodlamanın ayrıntılı anlatımı ve üstüne kurulu 996 baytlık sürüm ayrı bir depoda:
chessinbytes.
Boş kareleri tehdit durumuna göre sınıflandırmak. Boşta kalan değerlerle boş kareler
üzerinde iki bitlik bir tehdit haritası tutulup şah güvenliği ve rok denetimi kısaltılabilir
miydi? Üç ayrı nedenle hayır. Birincisi ve öldürücü olanı: haritadaki işaret hamleden
önceki pozisyonu anlatıyor, oysa şahın kendi gövdesi ışını kesiyor — “şah, kendisine
şah çeken taşın ışını boyunca geri kaçamaz” kuralı önbellekte bozuluyor. Ölçüldü: aday şah
hamlelerinin %0,2'sinde harita, yapılmaması gereken bir hamleye izin veriyor. Motor aynı işi,
geçerlilik denemesindeki yap–dene–geri al sayesinde bedavaya ve doğru yapıyor. İkincisi:
şahın kendi karesi dolu, yani şah testinin ve rokun ihtiyaç duyduğu kareler haritanın
kapsadığı kareler değil. Üçüncüsü: boş kare artık 0 olmayınca boşluk testleri
bozuluyor — önceki nesilde sekiz ayrı test; harita bakım kodu yazılmadan önce bile 16 bayt.
0x88'de bu daha da kesin: yürüyüş b[i]<1 ile boş karede sürüp delikte duruyor;
boş kareye başka bir değer yazmak bu ayrımı bozar.
Rok haklarını tahtaya taşımak. Aynı fikrin c'ye uygulanmış hâli.
Önceki nesilde duvara daha erken çarpıldı: >>1 = 2 veren, yani kaleyle aynı
dala düşen serbest bir değer yoktu. Geriye kale karelerini işaretlemek kalırdı, ama hakları
kırpan fonksiyon o kadar sıkı ki dizginin kendisi tablo görevi görüyor — sınırın dışına düşen
kareler undefined → NaN → 0 yoluyla bedavaya sıfır veriyor. Bugünkü
C de aynı yapıda: '20003001'[i%112]<<i/56. Tahtaya taşımak o
bedava davranışı kaybettirirdi.
Piyon yönü için işaretli değişken — denendi, kazandırmadı
Renk başına ±1'lik bir yön değişkeni tutup piyon hamlesini o çarpanla yazmak önceki
nesilde denendi. Kazandırmadı. Nedeni, seçilen temsilin daha ucuz olması değil, daha çok
iş görmesi: renk bilgisi tek bir değer olarak duruyor ve motor ile sürücüleri onu aynı
anda birçok rolde kullanıyor. Önceki nesilde t 0 ya da 1'di; doğruluk değeri
(p%2==g), XOR işleneni (t^=1), toplanan (V(t)?t+1:3 mat
kodunu, 10+2*k+g sonuç kodunu verir), indeks ('WB'[t]) ve çarpan
(t*49+7, ekranı döndürme) olarak kullanılıyordu. ±1 bunlardan yalnızca
sonuncusunu yapabiliyordu; üstelik işaretli yön yalnızca tek bir taş için anlamlı. Piyon ayrı
bir değişken taşımıyordu: f<i^b[i]&1 varış indeksinin kaynaktan küçük olup
olmadığını sorup bunu renk bitiyle XOR'luyordu; tek ifadede hem yön hem renk.
Yeni motorda t 8 ya da 0 ve aynı çok yönlülük sürüyor: maske
(p&8^t), XOR işleneni (t^=8), toplanan (2+t şahın,
1+t piyonun kodu), çıkan (b[i]-t taşın türü), kaydırma
(w^7<<t/2, ekranı döndürme). Piyonun yönü yine ayrı bir değişken
istemiyor: d<d*t, t 8 iken yalnızca d pozitifse, 0
iken yalnızca negatifse doğru; farkın işaretini rengin değeri seçiyor. Önündeki
m^ da tehdit sorusunda yönü ters çeviriyor.
Taş alfabesi — onaltılık kodların daha iyisi var mı?
Bu arama önceki neslin motorunda, düz 64 karelik tahtada yapıldı; metindeki “bugünkü” sözü
o günün alfabesini ve motorunu anlatıyor. Bugünkü alfabe ise bu başlığın sonundaki
ikinci aramadan çıktı.
Motorun önceki nesli tahtayı FEN harfleriyle tutuyordu. Harflerden onaltılık sayılara geçmek
yüz baytın üzerinde kazandırdı: taşın türü, rengi ve menzili artık bir karşılaştırma
zinciriyle değil, bit işlemleriyle okunuyordu (tahta). Ardından doğal bir
soru doğdu: bugünkü dizilim, dört bitlik bu alfabenin en iyi hâli mi? Kodlar başka türlü
dizilseydi motor kısalır mıydı?
Bugünkü alfabede her taş türü bir kod çifti: fil 2/3, kale 4/5, vezir 6/7, piyon 8/9, şah
a/b, at c/d. Tek sayı beyaz, 0 boş kare; 1, e ve f tahtada hiç geçmiyor. Ama hiç geçmemek işsiz
kalmak demek değil. Kilitli taramanın eski hâli 1 ve e kodlarının bitboard'larını sıfırdan
başlayan akümülatörler olarak kullanıyordu; hayalet geçerken alma ve rok hakları da birer boş
koda talip. Yani on altı kodun on altı ayrı rolü var: on iki taş, boş kare ve üç boş kod.
Arama bu uzayı adım adım daralttı. Daraltan kuralların bir kısmı aksiyom: motorun nasıl
yazıldığından gelen, vazgeçilmesi motoru baştan yazmak demek olan kurallar. Öbür kısmı aksiyom
değil, birer hareket noktası: aramayı ucuz ve anlaşılır bir yerden başlatmak için konmuş
varsayımlar. Önce aksiyomlar geliyor.
İlk aksiyom tanımın kendisi: her kare dört bitlik tek bir kod tutuyor ve on altı rol on altı
koda dağılıyor. Bu, 16! = 20.922.789.888.000 dizilim demek. İkincisi boş karenin 0
olması; uzay 15!'e, 1,3 trilyona iniyor. Üçüncüsü kayan taşlarla ilgili: üç kayan taş tek bir
ifadeyle yürüdüğü için kodda bir çapraz, bir de düz bit bulunmalı; fil yalnızca birinde, kale
yalnızca ötekinde, vezir ikisinde birden. Altı kayan taş kodunun on beş koda dizilişlerinin
yalnızca 20.544'ü buna uyuyor ve uzay 7,5 milyara iniyor.
Aksiyomlar burada bitiyor; geri kalan daralmanın tamamı hareket noktalarından geliyor.
Boştaki üç kodu birbirinin aynı saymak uzayı altıda birine indirdi. Asıl kırılma, rengin tek
bir bit olduğu ve bir türün iki renginin aynı deseni paylaştığı varsayıldığında geldi: kodun
geri kalan üç biti altı türe dağıtılacak yedi desenden ibaret kaldı ve milyarlar sekiz bine
indi. Renk bitinin bir uçta durması, çapraz bitin türün en alt bitinde olması, renk bitinin en
altta olması ve iki yön bitinin yan yana durması kalanı 384'e indirdi.
Adım
Daraltma
Tür
Kalan dizilim
0
her kare dört bitlik bir kod: on altı rol, on altı kod
aksiyom
20.922.789.888.000
1
boş kare 0
aksiyom
1.307.674.368.000
2
kayan taşlar çapraz ve düz bitle yürür: vezir = fil ∪ kale
aksiyom
7.455.006.720
3
boştaki üç kod birbirinin aynı
hareket noktası
1.242.501.120
4
renk tek bir bit; bir türün iki rengi aynı desende
hareket noktası
8.064
5
renk biti bir uçta
hareket noktası
4.032
6
çapraz bit, türün en alt bitinde
hareket noktası
1.440
7
renk biti en altta
hareket noktası
720
8
iki yön biti yan yana
hareket noktası
384
Kalan 384 dizilim, renkleri birbirinin tersi olan 192 çift; çiftin iki üyesi aynı kodu
veriyor. Her çiftin biri için koda bağlı her ifadenin en kısa hâli kaba kuvvetle arandı:
G'nin dağıtımı, materyal testinin fil testi ve ağırlığı, M'nin piyon
ve rok testleri, 6.9 aramasının piyon testi ve kilitli taramanın eşlemesi. En iyi adaylar
sonra gerçek motor dosyası olarak kuruldu.
Budamanın gücü. Taranan 192 dizilimde tek bir kural bile uzayı dörtte bire, sekizde
bire indiriyor. Tahta hilesini korumak (beyaz piyon 9 olmalı, yani piyon tür 4) 48 dizilim
bırakıyor; materyal ağırlığını tek bir XOR'a indirmek (şah ile at tek bitlik bir çift olmalı)
32; ilk turun biçimiyle tek maskeli kilitli tarama kapısı 24. İlk turun hesabıyla kazandıran
48 dizilim var; 8 bayt ya da daha fazla kazandıran 11, tahtayı da koruyarak kazandıran 21. En
kötüsü +18: fil 5, kale 2, vezir 7, piyon 3, şah 6, at 1; hiçbir mekanizmayı
tutturamıyor.
Aksiyomlar. Bunlar aramanın tartışmadığı, motorun kendisinden gelen kurallar.
Dört bitlik kod. Tahta dizgisi kare başına bir onaltılık basamak tutuyor. Bir kare on
üç durumdan birinde olabiliyor: on iki taş ya da boş. Bunun bilgi içeriği log₂13 ≈ 3,7 bit;
dört bitlik kod bunu taşıyan en kısa sabit uzunluklu kod, artan üç kod da boştaki
yuvalar.
Küçültülen ne? Bu hesap tahtanın bellekteki boyuyla ilgili; küçültülen şey ise
programın boyu. Onun kuramsal çerçevesi Shannon değil, bir çıktıyı üreten en kısa programı
soran Kolmogorov karmaşıklığı. Kolmogorov karmaşıklığı hesaplanamaz: bir programın en kısa
olduğunu genel olarak kanıtlamanın yolu yok. Bu yüzden yöntem, uzayı kurallarla daraltıp kalan
adayları tek tek ölçmek. Aranan şey bir ispat değil, ölçülmüş en iyi sonuç.
Boş kare 0. JavaScript'te yanlış sayılan tek tam sayı 0. Motorun boş kare
sorgularının hepsi birer doğruluk testi: !p, !b[i],
p&&, T|b[f], P|b[f]. Boş kareye başka bir değer
veren her alfabe bunların her birini bir karşılaştırmaya çevirir. Tahtayı üreten dizgi hilesi
de boş kareleri zaten 0 olarak üretiyor.
Kayan ve kısa menzilli taşlar. Üç kayan taş tek bir ifadeyle yürüyor:
(h*v?h==v:2)&P. Çapraz bir hamlede ifade h==v'nin sonucunu, düz
bir hamlede sabit 2'yi veriyor; sonra taşın türüyle & alınıyor. Yani türün bir
biti “çapraz yürür”, bir biti “düz yürür” demek: fil yalnızca çapraz bitte, kale yalnızca düz
bitte, vezir ikisinde birden. Şah, at ve piyon kısa menzilli; G'nin dağıtımı
onları kendi testleriyle ayırıyor. Bu ayrım ve vezir = fil ∪ kale eşitliği birer aksiyom:
kayan taş ifadesinin üç taşı birden yürütmeye devam etmesi için. Hangi bitin çapraz,
hangisinin düz olduğu ise aksiyom değil; o, aşağıdaki tabloda.
Taraf bilgisi. Her taş hangi tarafın olduğunu söylemek zorunda. Bunu nasıl
söylediği — bir bitle mi, işaretle mi — serbest.
Yalnızca geçerli pozisyonlar. Hiç oluşmayan bir durum serbesttir. Piyon 1. ve 8.
yatayda hiç durmadığı için çift adım testi y%5==1 yerine y%6<2
olabiliyor: ikincisi 1., 2., 7. ve 8. yatayları seçiyor, ama piyon bunların yalnızca ikisinde
bulunabiliyor.
Sabit geometri. a1 = 0, doğrusal 64 kare. Bu bir varsayımdı: kenar boşluklu
tahtaların (0x88, 10×12) bir “tahta dışı” kodu isteyeceği ve boştaki yuvalara talip olacağı
düşünülüyordu; o gün ayrıca elenmişlerdi. 0x88 sonradan
alındığında böyle bir kod gerekmedi: aradaki hücreler hiç atanmamış delik olarak kalıyor ve
undefined kendi başına “tahta dışı” demek.
Hareket noktaları. Huniyi asıl daraltanlar bunlar ve hiçbiri zorunluluk değil. Her biri
aramayı bir yerden başlatmak için kondu ve her birinin durumu sonradan sınandı:
Hareket noktası
Neden oradan başlandı
Durumu
boştaki üç kod birbirinin aynı
işleri alfabeye bağlı; ayrı ayrı incelemek daha kolay
rolleri ayrı incelendi: hayalet, rok hakları, kilitli taramanın sıfırları. Hepsi birden istenirse durum sayısı 18'e çıkıyor, dört bit yetmiyor; beş bitle 32!/14! ≈ 3·10²⁴
renk tek bir bit
renk testi ve tür okuma tek işlem: p&1, p>>1
zorunlu değil: taraf sayının işaretinden de okunabiliyor. İşaretli alfabe kuruldu ve ölçüldü (aşağıda)
bir türün iki rengi aynı desende
tür, renge bakmadan okunuyor
eşikle okunan ayna dizilim (p>c?K-p:p) tür okumayı pahalılaştırıyor. Bedava tek ayna sıfır etrafındaki, yani işaret
renk biti bir uçta
ortadaki bir bit hem renk testini hem tür okumayı iki işleme çıkarıyor
çıkarımla elendi: ortadaki iki konum, 8.064 dizilimin yarısı
çapraz bit, türün en alt bitinde
h==v yalnız 0 ya da 1 veriyor ve doğrudan &P'ye giriyor
öteki dört rol dağılımı ifadeyi yaklaşık 3 bayt uzatıyor (hesap)
renk biti en altta
bugünkü kodla uyumlu
en üst bit denenmedi; o da aynı sayıda dizilim getiriyor. Piyonun türü 1 olursa tahta hilesi korunuyor
iki yön biti yan yana
bugünkü ifade (h*v?h==v:2)&P
zorunlu değil: düz bit 4'te, serbest bit ortada durunca ifade (h*v?h==v:4)&P, aynı uzunluk. Getirdiği 7 kayan taş yerleşimi taranmadı. İki biçimin ortak tek yerleşimi (fil 1, kale 6, vezir 7) “kale ve vezir üstte” alfabesininki
serbest bit üç kayan taşta aynı
anlatımdaki “sekizler basamağı menzildir” okuması
taramada uygulanmadı: serbest bit taş başına serbest. “Kale ve vezir üstte” alfabesi bu katı ağacın dışında kalıyor
Fil için taraf değil, maske önemli. “Renk tek bir bit” kuralının iki gerekçesi var:
renk testi de tür okuma da tek işlemde kalsın. Fil için bunlardan biri zayıf. Materyal
testlerinde bir filin sayısı da tarafı da sonucu değiştirmiyor; değiştiren tek şey filin hangi
renk karede durduğu. Tek renkteki bütün filler, kaç tane ve hangi tarafın olursa olsun, mat
kuramaz. Motor da filleri böyle okuyor: I(2) iki tarafı birden tarıyor ve iki
tarafın fillerini aynı maskeye, m'ye atıyor. Im'in fil kısmı rakibin
maskesini kendi maskesiyle karşılaştırıyor, !(B&~m); taraf orada yalnızca iki
maskeyi birbirinden ayırmaya yarıyor.
Bu gözlem bir aile doğurdu: tarafı bitle değil eşikle ayıran ayna dizilimler. Kodlar bir
dikişin iki yanına simetrik diziliyor, taraf tek bir karşılaştırmayla okunuyor
(p>6) ve bir taşın öbür rengi 13-p. Örnek düzen:
Kod
1
2
3
4
5
6
7
8
9
10
11
12
Taş
şah
piyon
at
vezir
kale
fil
fil
kale
vezir
at
piyon
şah
Bu düzende 1–6 beyaz, 7–12 siyah; iki tarafı birlikte soran testler de iç içe simetrik
aralıklara iniyor: fil dikişteki iki kod (6, 7), kayan taş 4–9, “şah ya da piyon” iki uçtaki
bloklar. Materyal testlerinin fil kısmı ve kilitli taramanın kapısı tam bu tür sorular. Ama
bedel motorun en sık çalışan yerinde çıkıyor: türler artık tekrar eden bir desen olmadığı için
G'nin dağıtımı ve kayan taş ifadesi tür okumayı p>6?13-p:p gibi
bir ifadeyle ödüyor. Bu aile ikinci turda iki gerekçeyle kapatıldı: filin maskesi zaten koddan
değil karenin renginden okunuyor; ayrıca sıfır etrafındaki ayna, yani işaret, tür okumayı
bedava bırakan tek ayna (a(p)). O ayna aşağıda işaretli alfabe olarak
ölçüldü.
Neden kazanç bu kadar az? Harf tahtasından sayılara geçerken bütün taş testleri
değişmişti. Sayısal motorda ise taş koduna dokunan yer az: tahta dizgisi, G'nin
dağıtımı, V'nin şah araması, M'nin piyon, rok, terfi ve karşı renk
piyon ifadeleri, materyal testleri, 6.9 aramasının piyon testi ve terfi döngüsü, kilitli
taramanın indeksi ve kapısı. Hepsi birlikte kabaca 200 bayt, bunun 70'i tahta dizgisi. Hamle
geometrisi, kilitli taramanın taşkın dolgusu (flood fill) ve 6.9 araması taş kodunu hiç
okumuyor. En iyi alfabe bile ancak bu küçük yüzeyin bir kısmını kısaltabilir ve kısalttığının
bir kısmını tahtada geri öder.
Adil karşılaştırma. Alfabeden bağımsız her iyileştirme önce bugünkü motora verildi;
adaylar ancak ondan sonra, aynı tabana göre karşılaştırıldı. Aşağıdaki farkların hepsi
yalnızca alfabeden geliyor.
Adaylar. Her aday bu tabanda, kendi en iyi kilitli tarama kapısıyla kuruldu.
Alfabeler şunlar:
Alfabe
Fil
Kale
Vezir
Piyon
Şah
At
bugünkü
2/3
4/5
6/7
8/9
a/b
c/d
kayanlar üstte, piyon en altta
a/b
c/d
e/f
2/3
4/5
6/7
at ile piyon yer değiştiriyor
2/3
4/5
6/7
c/d
a/b
8/9
şah ve at bir tür yukarı
2/3
4/5
6/7
8/9
c/d
e/f
kale ve vezir üstte
2/3
c/d
e/f
8/9
4/5
6/7
işaretli: taraf sayının işaretinde
±1
±2
±3
±6
±5
±4
Çekirdekte, yani tek başına motorda, bugünkü alfabeye göre sonuçlar:
Alfabe
Fark
Kazancın kaynağı
Bedeli
kayanlar üstte
−6
piyon testi p<4, şah araması 4+s, karşı renk piyon 5-p, en ucuz kilitli tarama kapısı p>5?1:p
tahta dizgisi +5, G'nin dağıtımı +3
at ile piyon
−4
piyon testi p>11, tek XOR'luk ağırlık p>>1^5, kilitli tarama kapısı p>9
tahta +5, 6.9 aramasının piyon testi +1
işaretli
−4
p*t ile kısalan renk süzgeçleri, terfi u*t, karşı renk piyon -p, ağırlık a(p)^5
tahta +4, sıranın ±1 olması
şah ve at yukarı
−3
ağırlık p>>1^6; tahta dizgisi korunuyor
kilitli tarama kapısı +1: şah ve piyon artık bitişik değil
kale ve vezir üstte
+2
ağırlık, şah araması
G'nin dağıtımı +3; tek maskeli kapı kurulamıyor
Çekirdekteki en büyük adil kazanç 6 bayt. Asıl soru ise bu kazancın bütün sürümlere yayılıp
yayılmadığıydı, çünkü sitedeki bütün sürümler aynı alfabeyi kullanıyor:
Alfabe
Çekirdek
L3 sürümleri
L2 sürümleri
L1 sürümleri
kayanlar üstte
−6
−4…−9
−4…−9
0…+5
at ile piyon
−4
−4…−7
−5…−8
−1…+2
işaretli
−4
kurulmadı
kurulmadı
L1 −7 (hesap)
şah ve at yukarı
−3
−1…−3
−1…−3
0…+2
kale ve vezir üstte
+2
+2…+4
+2…+4
+2…+4
Çekirdek ve L1 sütunları ölçüldü. L3 ve L2 aralıkları, ilk turun ölçülmüş sürüm
değerlerinden kilitli tarama kapısının yeni farkıyla düzeltilerek türetildi; çekirdekte bu
düzeltme her adayda ölçümle birebir tutuyor. L2_light kendi materyal ve kilitli
tarama zincirini taşıdığı için motorun kazançları ona ulaşmıyor; aralıkların dışında
kalıyor.
L1 neden ödüyor? L1'de materyal testleri, 6.9 araması ve kilitli tarama yok.
Adayların kazancının çoğu tam bu yerlerden geliyor. L1'e kalan ise tahtanın, G'nin
dağıtımının ve sürücü tablolarının bedeli. Kazanç çıkabilecek yerler yalnızca şah araması ve
piyon testleri.
İşaretli alfabe. İkinci turda kuralların kendisi de tartışıldı ve biri gevşetildi:
taraf bilgisi zorunlu, ama taşın bitlerinden biri olması zorunlu değil. Taraf sayının
işaretinden okunabilir; o zaman dört bitin dördü de türe kalır: on altı desenin biri boş,
altısı taş, dokuzu boşta. Dizgide yalnızca türün mutlak değeri duruyor, işaret karenin
yerinden geliyor:
bugün b=[...`5d37b3d5${10n**40n-10n**32n}888888884c26a2c4`].map(u=>'0x'+u-0) 70 bayt
işaretli b=[...`24135142${(10n**40n+1n)*66666666n}24135142`].map((u,i)=>i>31?-u:+u) 74 bayt
(10n**40n+1n)*66666666n iki piyon sırasını ve aradaki 32 boş kareyi tek bir
çarpımla üretiyor; bu ancak iki renk aynı rakamı paylaştığında mümkün. Sıra t =
±1 oluyor ve p*t “kendi taşım mı?” sorusunu tek bir karşılaştırmaya indiriyor;
t 1/0 kalsaydı çekirdek 13 bayt uzardı. Bedeli bit hileleri ödüyor: negatif
sayılarda bit işlemleri beklendiği gibi çalışmıyor; 6&4 4 iken
-6&4 0. Türe bakan her işlem a(p) ya da kare almayla yeniden
yazılıyor: piyon testi p*p>25, rok tanıma p*p>9, fil testi
p*p<2. Çekirdekteki 4 baytın biri, mat kodunu ~t ile serbest
bırakmaktan geliyor; bugünkü alfabe de V(t)?~t:3 ile aynı baytı kazanabilirdi,
ama sonuç kodlarının 1–15 düzeni bozulmasın diye bu yola gidilmedi.
t = ±1'in bir yan kazancı daha var: ~t onu bedavaya doğru ya da
yanlış bir değere çeviriyor: ~1 −2, ~-1 0. Sonuç kodları da yalnızca
birbirinden farklı olmaları gerektiği için serbest bırakıldı: W# −1, B# 1, WT −10, BT 10, WR
−11, BR 11; bir hükmün işareti, kaybeden tarafın işareti. Tahta için takma adlar da denendi ve
hepsi kaybettirdi: sekiz haneli bir dizgi yalnızca iki kez geçtiği için takma adın tanımı
tekrarın kazancını aşıyor (+1 ile +10 arası). Kurulumda boş kareler −0 da olabiliyor; −0 yanlış
sayılıyor ve ==0 karşılaştırması doğru veriyor, ama Object.is ya da
1/p onu 0'dan ayırıyor.
Kapanan sorular.Hayalet geçerken almanın ikinci
tasarımı, yani işareti atlanan kareye değil piyonun kendisine yazmak, birincisinden iyi çıktı
ama yine kaybettiriyor: L1'de yaklaşık +8, L2'de +10, L3'te +4. Yarım hamle sayacının
sıfırlama koşulu P|b[f] iki ayrı kareyi okuyor ve hiçbir dizilim bunu teke
indiremiyor. Rokta ve çift adımda aradaki karelerin boşluğunu yoklayan S taş
kodunu hiç okumuyor; bu testler alfabeden bağımsız. Materyal testleri fili kodundan değil
durduğu karenin renginden okuduğu için fil için ayrı kod gereksiz. 6.9 aramasının terfi döngüsü
vezir, kale, fil, at sırasını korumak zorunda: sıra değişirse bütçe sınırına dayanan
aramalarda hüküm değişebilir. Terfi taşlarının kodlarının ardışık olması ise çekirdekte
kazandırmıyor; yalnızca sürücüde, kör sürümlerin harf tablosunda 2 bayt kazandırıyor. Beyaz
piyonun 9 olma zorunluluğu da yalnızca bugünkü tahta hilesine özgü:
10n**40n-10n**32n yalnızca 9 ve 0 üretebiliyor. Rok haklarını tahtaya gömmek ise
denenmedi: bit ailelerinde boşta yalnızca üç kod var ve bu üç kod hayalete, hakkı olan
kalelere ve şahlara yetmiyor; işaretli alfabenin dokuz boş deseni ise yeterdi. Tahtaya konan
her işaretin bir bedeli daha var: tekrar anahtarı b+t+e+c tahtanın kendisini
içeriyor, yani hayalet ya da rok hakkı tahtaya girerse tekrar tespiti de onlarla birlikte
değişir.
Bazı fikirler dosya kurulmadan, kodun yapısından hesaplanarak elendi:
Fikir
Fark
şahı kayan taş dalına katmak: şah = “menzili 1 olan vezir”
+13 / +16
fil ile kalenin yer değiştirmesi: çapraz bit en alttan çıkıyor
en az +6
materyal testindeki üç aramayı tek taramaya indirmek
+4…+16
filin kare rengini koda gömmek: açık kare fili ayrı kod
+21, asıl bedel terfi
materyal testinin taradığı türleri kaydedip ikinci teste vermek
+8
Ölçülüp kaybettirenler de var:
Deneme
Fark
L2 ve L3'te at tür 4 (piyon 5, şah 6)
çekirdekte +10
L1'de şah tür 7
0…+2
L1'e özel at tür 4
yalnız L1_numerical'da −2; L2 ve L3'te +3…+10
Açık kalanlar. Tahtanın +5'lik bedeli bugünkü hileden ucuza ödenebilseydi “kayanlar
üstte” alfabesinin kazancı −11'e, “at ile piyon”unki −9'a çıkardı. Denenmemiş yollar belli.
Tek bir bölme yalnızca 9'un bölenlerinde (1, 3, 9) işliyor; öbür rakamlar çarpma da ister.
Ayrıştırıcı '0x'+u-0 başka bir çözümlemeyle değiştirilebilir, ama boş karelerin 0
kalması şart olduğu için sabit bir kaydırma doğrudan işlemiyor. 10n**32n'nin
ürettiği baştaki “1” anlamlı bir koda denk getirilebilirse dddddddd${10n**32n}
gibi bir biçim mümkün olabilir. Renk en üst bite alınır ve piyonun türü 1 olursa beyaz piyon
yine 9 olur; bugünkü hile de aynen çalışır. Taranmayanlar da kayıtlı: rengin en üst bitte
olduğu dizilimler, yön bitlerinin ayrık durduğu biçim, paketlenmiş sürümler ve
engine_4x; orada yalnızca “şah ve at yukarı” ölçüldü (ilk turda −8). İfade
aileleri de genişletilebilir: aranmayan bir ifade biçimi, bulunmayan bir kazanç demek.
Nasıl doğrulandı. Her aday gerçek motor dosyası olarak kuruldu. Perft, başlangıç
pozisyonunda, Kiwipete'te ve CPW 3–6 pozisyonlarında 3. ve 4. derinlikte yayımlanmış
değerlerle tuttu. Sonra her aday bugünkü motorla yan yana oynatıldı: taş kodları, sıra, terfi
ve sonuç kodları birbirine çevrildi; her yarım hamlede tahta, sıra, geçerken alma karesi, rok
hakları, sayaç ve tekrar sayısı, bütün geçerli hamle listeleri, materyal testleri, kilitli
tarama, beraberlik talebi, bayrak düşmesi ve terk hükümleri ve hamle sonrası sonuç
karşılaştırıldı. Girdiler rastgele oyunlar, terfiye yakın piyonlu oyunsonları, sayacı 75 hamle
sınırına yakın pozisyonlar ve kilitli piyon duvarlarıydı. Testin duyarlılığı, motor kasıtlı
olarak bozularak sınandı: materyal ağırlığı ya da 6.9 bütçesi bozulunca farklar hemen
yakalandı.
Karar. Hiçbir alfabe alınmadı. Çekirdekte en büyük adil kazanç 6 bayt, en az şeyi
değiştiren adayın kazancı 4 bayt. Buna karşılık hero'daki tahta şeridi, kod tabloları,
tahtanın anlatımı, terfi API'si, test süiti ve bütün sürümler değişirdi; yeniden doğrulama ve
yeniden paketleme de cabası. Kazanç da bütün sürümlere yayılmıyor: L1 ödüyor. Adaylar bir gün
bir rekor denemesi ya da sitenin baştan yazılacağı büyük bir revizyon için duruyor: o gün en
çok kazandıran “kayanlar üstte”, en az değiştiren “at ile piyon”.
İkinci arama — 0x88'de, bu kez alındı
O büyük revizyon geldi, ama yeni alfabe bu adaylar arasından seçilmedi. Tahta 0x88'e geçince
alfabenin dayandığı ifadelerin çoğu değişti — kayan taşları yürüten
(h*v?h==v:2)&P artık yoktu — ve arama baştan başlatıldı, bu kez
L1'in light sürümünde. Düzen de değişti: renk en üst bitte (beyaz = tür
+ 8), tür alttaki üç bitte. Rengi en alt bite koyan eski düzen sabit bir +7 bayt farkla elendi:
iki yerdeki tür çıkarma, son yatay testi, terfi kodu ve kendi taşı testi uzuyordu.
Aksiyom listesi kısaldı. Boş kare 0: yürüyüş b[i]<1 ile boş
karede sürüyor, delikte duruyor. Kodlar 1–15: başlangıç dizgisi kare başına tek bir onaltılık
basamak okuyor. Sıra t rengin değerinin kendisi, 8 ya da 0: b[i]-t,
+~t, 2+t gibi kısaltmalar buna dayanıyor. Piyonun türü 1, çünkü
beyaz piyonun 9 olması tahta hilesinin şartı: 10n**40n-10n**32n yalnızca 9 ve 0
üretiyor. Bu kurallarla huni 16!/3! ≈ 3,49 trilyon atamadan 120 (fil, kale, vezir) üçlüsüne
indi; şahın ve atın yerleşimi her üçlüde en iyi seçeneğe sabitlendi.
İlk turlarda en iyisi 910 baytla HB8'di. Sonra arama, kendi koyduğu bir aksiyomun fazla dar
olduğunu gösterdi: “vezir, kale ve fil tek bir kaydırmayla karar verir.” Kayan taş testi her
üçlü için serbest bir ifade olarak arandığında iki alfabe 909'a indi:
Alfabe
Piyon
Şah
Vezir
At
Kale
Fil
Boş tür
Kayan taş testi
Bayt
H5
1
2
3
4
5
6
7
P+P*4%x
909
H4
1
2
5
6
3
4
7
P/x*9*9
909
HB8
1
3
7
4
2
5
6
P>>x%3
910
İkisinde de tür 7 boş, bu yüzden glif dizgisi bir karakter kısa; şah 2 olduğu için rok
sabiti tek basamaklı (d-8). H5 seçildi, çünkü kayan taş testi tamamen tam sayı
aritmetiğinden oluşuyor. Bugünkü alfabe bu: beyazın ilk yatayı dcebaecd. Bu
şablonda daha aşağısı yok: 908 bayt için, şahın 2 olduğu ve tür 7'nin boş kaldığı bir
alfabede altı karakterlik bir kayan taş testi gerekirdi; aranan biçimlerin hiçbirinde böyle bir
test çıkmadı.
Buradan çıkan ders, eski aramadaki aksiyom tartışmasına bağlanıyor. Bir zorunluluğu değil de
belli bir kod biçimini dile getiren bir aksiyom kazananı eleyebilir; bu iki kez oldu: önceki
nesilden gelen “bir bit çapraz, bir bit düz” kuralı HB8'i elerdi; ikinci aramanın kendi “tek
kaydırma” kuralı da H4 ile H5'i eledi.
En yakın beş alfabe L2 ve L3'te de kuruldu.
O günkü light sürümlerin site arayüzüyle bayt cinsinden boyları:
Seviye
H5
H4
HB8
H2
H1
H3
L1
933
933
934
934
935
935
L2
1.540
1.540
1.540
1.541
1.544
1.543
L3
2.325
2.328
2.325
2.326
2.329
2.330
Hiçbir seviyede H5'i geçen yok. HB8 L2'de yetişiyor: şah ve piyon
kodları 1, 3, 9 ve 11; dördü de 99'u tam bölüyor, bu yüzden “şah ya da piyon değil” testi
99%p'ye iniyor. H4 L3'te geride kalıyor: materyal sayacında
at sıfırıncı kaydırmaya düşmeli — “rakipte tek at” testi 4%m buna dayanıyor — ve
at 6 olunca kaydırmanın bedeli ödeniyor. H5'te 4<<p*8 atı kendiliğinden
oraya koyuyor, çünkü kaydırma miktarı 32'de başa dönüyor. H5 tam perft'i, tarayıcı
senaryolarını ve 300 rastgele oyunda bağımsız bir hakemle karşılaştırılan 1.969.237 geçerli
hamleyi tek bir fark çıkmadan geçti.
a8=0 indeksleme — render için seçildi, motor için bırakıldı
Tahta bir dönem sekizinci yataydan başlıyordu: siyah üstte, a8=0,
b8=1, … h1=63. Nedeni render'dı — bu düzende diziyi baştan sona
çizmek doğrudan beyazın gözünden bir tahta veriyor, ilk döndürme için bedel ödenmiyordu.
Ama bu motorda aranan şey yalnızca kısalık değildi; anlaşılır ve evrensel olması da
öncelikti. Üstelik her sürümde çizilen bir tahta yok — biri körleme, biri sayısal. Render
kaygısı yedi dosyanın hepsini bağlamıyor.
Ölçüldüğünde terazi zaten dengede çıktı. Motor tarafında a8=0'ın maliyeti
tam sıfır; sürücülerde kimi dosyada sıfır, kimi dosyada bir iki bayt kazandırıyor. Ama
başlangıç tahtasının sıkıştırılabilir bloğu bu düzende yer değiştiriyor ve sayı
sıkıştırması aynı kadarını geri alıyor. Render'da kazanılan literalde kaybediliyor.
Geriye tek gerekçe kalıyor ve o bedelsiz: a1=0 standart satranç
indekslemesi. Yatay ve sütun numaraları ikisi de beyazın yönünde
arttığı için motorun her ifadesi altmış dört karenin tamamında doğal okunuyor. Motorun
evrenselliği sıfır bayt farkla tercih edildi. İddia render'la değil, motorla ilgili.
0x88'de soru yeniden soruldu. Ters sıralı 16×8 — yataylar 8'den 1'e, yani a8'den
başlayan tahta — L1, L2 ve
L3'ün light sürümlerinde düz 16×8 ile bayt bayt aynı çıktı: ekranı
döndüren w^7<<t/2 yerine w^119>>t, aynı uzunluk.
Eşitliği bozan 6.9 araması oldu: a1'den sayan tahtada hamleler
önceki motorla aynı sırada geziliyor ve bütçe aynı yerde bitiyor. a1=0
kaldı.
Motorda bırakıldı, harf tahtasında alındı. Fark sıfır bayt olduğu için karar
dosyaya göre değişebiliyor. Bugün tahtayı a8'den sayanlar harf tahtalı önceki nesil:
prompt_string ailesi ve
L2_string_base; prompt_string_a1 ise aynı
motorun a1'den sayan eşi. Ters indeksin, tahta temsilini baştan sona değiştirdiği hâlde koda
ne kadar az yansıdığı orada görülebiliyor: piyonun yön sınaması f<i yerine
f>i oluyor; kural gövdesinde bundan başka yalnızca kilitli taramanın ad bağlaması
değişiyor (aşağıda). Arayüzdeki farkın dökümü harf tahtası bölümünde.
Ters tahtada kilitli tarama. Tahtayı a8'den sayan harf tahtalı sürümler —
engine_string.js ile L2 ve L3'ün
prompt_string'i — kilitli taramayı da taşıyor. Doyma döngüsü ile çıkıştaki altı
soru engine.js'tekiyle aynı; tek fark bit sayacının adı: j yerine
y, çünkü harf motorunda j, harfi büyük harfe çeviren yardımcı. Tahtanın ters
sayılmasının tek bedeli adların bağlanmasında: döngü piyonları renge göre değil yöne göre
ayırdığı için (W, indeksi artıran yönde yürüyenler), a8'den sayılan tahtada
W ile B, _ ile f karşı renge bağlanıyor —
harfler '-pPkKbB' içinde aranıyor, siyah önce; a1'den sayan eşi
'-PpKkBb' kullanıyor — ve birinci terimdeki taraf seçimi ters yazılıyor:
!(s?W:B). Öbür farklar harf tahtasından geliyor. Bitboard'lar tek bir dizide
kuruluyor ve [,,W,B,_,f,a] ile dağıtılıyor; filin kare rengi düz 64'te
i*9/8%2+1. _ ile a harf motorunda boş kare ve
Math.abs olduğu için orada parametre; bu yüzden oradaki V globale
yalnızca S'yi yazıyor. Geçerken alma yokken e'-' olduğu
için e-8NaN veriyor ve bit işleminde sıfıra dönüyor; kapı aynı
yazılışla çalışıyor. L2'nin prompt_string'i aynı döngüyü
ve çıkışı daha kısa bir gövdeyle taşıyor: pozisyonu iki taraf için birden soruyor ve materyali
tek bir sayıda topluyor.
n<2 penceresi — kanıtlanmış bir kısayolun geri alınması
Uzun süre kilitli pozisyon taraması bir kapının ardında durdu:
~e|n>1?0:. Geçerken alma karesi varsa ya da yarım hamle sayacı birden büyükse
tarama hiç başlamıyordu. L3 ailesinde kapının ikinci yarısı bir bayrak
da taşıyordu — ~e|n>1&Y?0: — çünkü 6.9 yardımlı mat
araması kendi düğümlerinde sayaca güvenemediği için kapıyı l(0) ile zorla
açması gerekiyordu.
Kapı kayıpsızdı ve bu deneyle değil, tümdengelimle gösterilebiliyor.
n≥2 bölgesinde oynanabilen tek hamle türü sessiz şah hamlesidir: piyon hamlesi
de alma da sayacı sıfırlar. Sessiz bir şah hamlesi taramanın cevabını değiştiremez, çünkü
tarama şahın bölgesini okur ve şah kendi bağlantılı bileşeninin dışına çıkamaz. Şah
çekme de kapıdan kaçamaz: bu kapsamda şahı ancak piyon çekebilir, piyon hamlesi sayacı
sıfırlar, şahın cevabı da n≤1'de kalır. Geçerken alma karesinin bir yarım hamle
boyunca susturduğu hükmü yakalayan da pencerenin ikinci yuvasıydı. Dört durum, dördü de
pencerenin içinde — yani kapı hiçbir zaman gerçek bir hükmü kaçırmadı.
Kapının bırakılmasının nedeni doğruluk değildi. Kapı bir kısayoldu ve kısayolun
kazandırdığı şey insan hızında görünmüyordu: tahtada şah ve piyondan başka taş varken tarama
zaten hiç başlamıyor, yani kapının tasarrufu yalnızca saf piyon oyunsonlarında ortaya
çıkıyordu ve hamle başına yarım milisaniyenin altında kalıyordu. Buna karşılık kapının bedeli,
ödendiği her yerde görünüyordu: yirmi dosyada bir koşul,
L3'te bir parametre, iki çağrı yerinde birer argüman ve doğru yazılması
şart olan bir mekanizma. &Y yerine &!Y yazılsaydı 6.9 araması
derin düğümlerde kilitli pozisyon taramasını sessizce kaybederdi — yirmi dosyanın hiçbirinde
düşülmemiş bir tuzak, ama her yeni sürümde yeniden düşülebilecek bir tuzak.
Kapı kalkınca zorla açılacak bir şey de kalmadı: Y parametresi düştü, iki
çağrı yeri de l() oldu.
Kapının ilk yarısı ise yerinde duruyor. ~e bir kısayol değil, hükmü
değiştiren bir koşul: geçerken alma mümkünse piyon hamlesi mümkündür, yani kilit yoktur —
ve taramanın bunu kendi başına bulması gerekmez. Bu hikâye önceki nesilde geçti; bugün tarama
V'nin kilitli yarısı; “geçerken alma yok” değeri de 8 olduğu
için aynı koşul e-8 diye yazılıyor: !(o|m|e-8||g(99,…)).
Kaldırılmanın bilinen bedeli tek bir yerde toplanıyor ve gizlemeye değmez: toplu
doğrulamada. Tarayıcıda görünmeyen yarım milisaniye, bir sunucuda milyon oyunluk bir koşuda
bir saatin bir bölümüne çıkar. Motoru orada çalıştıracak olan kapıyı geri koyabilir; bu sayfa
kapının nasıl yazıldığını ve neden doğru olduğunu kayıt altında tutuyor.
Kalem
Bayt
L2 ailesi, altı dosya × −4
−24
L3 ailesi, on iki düz dosya × −10
−120
Düz dosyalarda toplam
−144
Saf piyon oyunsonunda hamle başına maliyet
+0,45 ms
Taşlı pozisyonda hamle başına maliyet
+0,004 ms
engine_4x'te kapı geri döndü, ama başka bir yerde ve başka bir biçimde.
Hızlı motorun Z'si ölü pozisyon testini (n+e<10|_)&& ile kapılıyor
(117 bayt nereye gidiyor). Kapı artık taramanın içinde değil Z'de:
6.9 araması V'yi doğrudan çağırdığı için zorlanacak bir şey yok, oyunun dışında hüküm
isteyen de kapıyı Z(1) ile açıyor. Yerine 3 bayt kısa n<2 denendi ve
bırakıldı: iki koşul eşdeğer değil. Geçerken alma karesi yokken e 8 olduğu için
n+e<10, n<2'nin ta kendisi; bir kare varsa e en az 32 ve
toplam hiçbir zaman 10'un altına inmiyor. Yani n+e<10, n<2ve
geçerken alma yok demek. Fark hükme yansımıyor: geçerken alma mümkünken V'nin kilitli
yarısı e-8'de duruyor, materyal yarısı da iki tarafta piyon varken ölü demiyor. Yansıdığı
yer iş: n<2 ile Z, geçerken almanın açık olduğu her yarım hamlede
V'yi iki kez boşuna çağırırdı — tahtayı tarayıp kümeleri kurduktan sonra cevabı
e-8'de öğrenmek için.
Filli kilitli pozisyonlar — askıdaki genişletme
Kilitli pozisyon taraması — bugün V'nin kilitli yarısı,
önceki nesilde l — yalnızca piyonları ve şahları kapsıyor. Tahtada fil de bulunan
tamamen ölü, kilitli pozisyonlar var ve bunlar bu kapsamın dışında kalıyor. Bu boşluk
ölçüldü: CHA-Solver'ın 204.060 oyunluk listesinde — hepsi yapı gereği beraberlik —
motorun kanıtlayamadığı 3.676 pozisyonun 3.638'inde fil var. Hepsinde arama derinlik
sınırına dayanıyor; bütçe hiçbirinde tükenmiyor. O boşluğu kapatacak bir genişletme önceki
nesilde tasarlandı ve ölçüldü: 782 bayt daha tutuyordu, yani bayt başına getiri yirmi dört
kat düşüyordu. Hızı da düşürüyordu. Asıl engel ise güvence: fil soruya üçüncü bir bilinmeyen
eklediği için motorun tek ispatlı özelliğini — ölü dediği her pozisyonun gerçekten ölü
olmasını — riske atıyor. Genişletme askıda, kaybolmuş değil: 0x88 neslinde yeniden yazılıp bir süre
motorda durdu (+333 bayt), sonra çıkarıldı; bugün LUX'ta yaşıyor. Katmanları ve baytları
LUX'un motoru bölümünde.
Ayrıştırma
Sürücünün üç ayrıştırıcısı
Motor altı çağrı sunuyor ve hepsi kare indeksleriyle çalışıyor:
L(i,f) ile A(i,f,p) için i ve f0x88 indeksi olmalı — 16 × yatay + sütun. Kullanıcı ise bir tıklama,
harfler ya da rakamlar giriyor. Aradaki dönüşüm sürücünün işi.
Üç ayrı ayrıştırıcı var — tıklama, UCI ve sayısal — ve üçü de tek satır. Ortak noktaları
şu: hiçbiri doğrulama yapmıyor. Geçersiz bir girdi bir hata dalına değil,
L'nin yanlış döndürdüğü bir kare çiftine dönüşüyor ve orada sessizce eleniyor;
tahtanın dışına düşen bir kare bile, çünkü L'nin süzgeci delikleri geri
çeviriyor. Ayrıştırıcının işi doğru girdiyi çevirmek; yanlış girdiyi eleyen zaten motorun
kendisi.
Kullanıcının ne yazdığı Notasyon bölümünde anlatılıyor. Burada kodun
kendisi var, çünkü bu üç satırın üçü de golf tekniği taşıyor; karşılığında dönen
sonuç kodları ise Varyantlar sekmesinde. Örnekler
L3 sürümlerinden; prompt_string ailesi önceki
nesilden ve kendi ayrıştırıcısını harf tahtasıyla birlikte
taşıyor.
Tıklama — bir durum makinesi
Tıklamalı sürümde ayrıştırılacak metin yok. default'ta altmış
dört karenin her biri, kendi tahta karesini geçiren bir tıklama işleyicisi taşıyor ve bütün
seçim mantığı tek bir ifadede:
K = u => z || p.hidden && (
L(i,u) ? (Y = u, X ? E(X) : p.hidden = 0)
: b[u] + ~t >> 3 || F(i = Y = u))
Ekran karesi tahta karesine render'da çevriliyor. Tahta her hamleden sonra sırası
gelen tarafın bakış açısına dönüyor, bu yüzden ekrandaki bir hücre her zaman aynı kare değil.
Render her hücreyi D=w^7<<t/2 ile yazıyor ve tıklama işleyicisine
w'yu değil D'yi veriyor. w karenin 0x88 indeksi;
7<<t/2 beyazın sırasında 7<<4, yani 0x70,
siyahın sırasında 7. 0x88'de iki onaltılık basamak yatay ile sütun olduğu için
ilki yatayları çeviriyor (sekizinci yatay üstte), ikincisi sütunları (tahta siyahın
gözünden görünüyor). Tek bir XOR, iki ayrı döndürme; işleyiciye gelen u zaten tahta
karesi.
Seçim bayrağı yok. Seçim yokken i-1 ve
L(-1,u) her zaman yanlış: b[-1]undefined, tür sıfır,
hiçbir geometri geçmiyor. İlk tıklama bu yüzden kendiliğinden seçim dalına düşüyor;
i hem seçili kareyi hem de “seçim var mı?” sorusunu birden taşıyor.
Seçim i = Y = u ile kuruluyor, ikisi birden. Nedeni render: yeşil
çerçeve Y'yi işaretliyor, yani seçim anında seçilen kareyi, hamleden sonra
varış karesini gösteriyor. Tek değişken iki anlam taşıyor ve ikisi de doğru.
Hata dalı yok. Seçim dalının bekçisi L'nin kendi süzgeci:
b[u]+~t>>3 boş kare ve rakip taş için doğru, kendi taşı için sıfır. Doğruysa
|| orada duruyor ve hiçbir şey olmuyor; sıfırsa seçim o kareye taşınıyor. Üç
sonuç — hamle yap, seçimi taşı, yok say — tek bir üçlü operatör zincirinden çıkıyor.
Geçerli hedefler render'da çıkıyor. Seçili taşın gidebileceği kareler altın rengine
dönüyor, ama bunu ayrı bir mekanizma yapmıyor: render her hücre için L(i,D)'yi
soruyor. L tek bir hamleyi sınadığı için çağrı ister istemez altmış dört karelik
döngünün içinde; seçim yokken hepsi yanlış ve hiçbir kare boyanmıyor. Render saatle birlikte
her 99 milisaniyede bir yeniden kuruluyor, ama tahtayı ekrana yalnızca dizgisi değiştiyse
yazıyor: m!=(m=…).
Terfi hamleyi askıya alıyor. Hamle geçerliyse karar X'te:
L(i,u)'nun deneme hamlesinde U onu az önce yazdı. Sıfır değilse
hamle terfi değil ve E(X) onu oynuyor; sıfırsa panel açılıyor ve hamle orada
bekliyor. Y zaten yazılmış; panelin düğmeleri taş türünü y'ye yazıp
hamleyi tamamlıyor — bu sürümlerin U'su terfi taşını parametreden değil
y'den okuyor. Askıya alma ayrı bir durum değişkeni istemiyor ama tahtanın
kilitlenmesini gerektiriyor — ifadenin başındaki p.hidden && koruması bunun
için var.
Hamleden sonra talep ve teklif.default'un hamle adımında
o&=t?1:8 var: bu sürümde teklif bitleri beyaz için 8, siyah için 1
(o^=t||1); hamleden sonra da t çoktan çevrilmiş oluyor. Maske hamleyi
yapanın bitini tutup rakibin bekleyen teklifini düşürüyor, çünkü hamle yapmak teklifi
reddetmek demek. Oyun kendiliğinden bitmediyse ve bir istek biti hâlâ yanıksa
o&&J() talebi değerlendiriyor: üçlü tekrar ya da 50 hamle kuralı.
light aynı makineyi biraz daha kısaltıyor. Hedef boyaması
ve yeşil çerçeve olmadığı için seçim yalnızca i=u; Y her tıklamada
L(i,Y=u)'nun içinde yazılıyor. i başta -1 değil 0, yani a1 — ama
oradaki kale ilk hamlede kıpırdayamadığı için bu da “seçim yok” demek. Hamleden sonra
i sıfırlanmıyor, çıkış karesinde kalıyor; orası artık boş ve L
yine yanlış döndürüyor. Terfi paneli de gizli bir öge değil, bir dizgi: p doluyken
tahta tablosunun başlığı olarak yazılıyor, z||p|| koruması tahtayı kilitliyor ve
düğmeler p='' ile onu boşaltıyor.
UCI — iki karakteri bir çağrıda okumak
UCI sürümlerinde bir kare iki karakter: sütun harfi ve yatay rakamı. Dönüşümün tamamı
bir satır.
J = V => (V = K.charCodeAt(V) + 8 * K[++V] - 105) * 2 - V%8
Dört püf noktası üst üste binmiş.
Tür karışımı.K.charCodeAt(V) sayı döndürüyor,
K[++V] ise tek karakterlik bir dizgi. 8* ile çarpılınca JavaScript
o dizgiyi sayıya zorluyor — ayrı bir parseInt gerekmiyor ve çarpanın kendisi
zaten yatayın düz tahtadaki ağırlığı. Bir işlem iki iş görüyor.
Yan etki.++V parametreyi yerinde artırıyor, böylece tek çağrı iki
karakteri birden okuyor. Çağıran taraf yalnızca çiftin başlangıcını veriyor:
J(0) çıkış karesi, J(2) varış karesi. Okuma bitince aynı harf bir
daha atanıyor ve artık karakter konumunu değil karenin düz indeksini tutuyor.
Sabit.105 rastgele seçilmiş bir sayı değil: 'a' karakterinin kodu 97,
birinci yatayın katkısı 8*1. İkisinin toplamı a1'i sıfıra oturtuyor; parantezin
içi düz tahtanın indeksi, 8 × yatay + sütun.
0x88'e geçiş.*2-V%8 düz indeksi 0x88 indeksine çeviriyor — tahtayı
kuran ifadedeki j*2-j%8'in aynısı. Ayrıştırıcı tanıdık
aritmetiğiyle düz tahtada çalışıyor ve sonucu motorun düzenine tek bir ifadeyle
taşıyor.
Girdi önce toLowerCase()'den geçtiği için E2E4 ile
e2e4 aynı kareleri veriyor. Büyük-küçük harf duyarsızlığı ayrı bir kural değil,
tek bir çağrının yan ürünü.
Sayısal — iki rakam, iki onaltılık basamak
Sayısal sürümde J yok, çünkü buna gerek yok. Kullanıcı kareyi zaten motorun
indeksiyle yazıyor ve dönüşüm satır içinde bitiyor.
i = K[0] << 4 | K[1]
Y = K[2] << 4 | K[3]
İki rakam, 0x88 indeksinin iki onaltılık basamağı: önce yatay, sonra sütun, ikisi de
0'dan 7'ye. <<4 yatayı üst basamağa kaydırıyor, | sütunu alt
basamağa koyuyor; e2 için '1'<<4|'4', yani 20. Kaydırma da bit düzeyinde
veya da işlenenlerini kendiliğinden sayıya çeviriyor; tek karakterlik dizgiler için ayrı bir
dönüşüm yazılmıyor. 8 ya da 9 gibi bir sütun rakamı bir deliğe düşüyor ve L
onu geri çeviriyor.
Kaydırılacak başka bir şey yok, çünkü yazılan sayı motorun kendi indeksi;
L1 ve L2'nin sayısal sürümleri de aynı düzeni
kullanıyor.
Terfi harfi yazılmazsa vezir çıkıyor — ve bunu hiçbir denetim yapmıyor.
Metin ayrıştırıcıları terfi karakterini bir nesne aramasından
({r:5,b:6,n:4}[K[4]]) ya da bir dizgi indeksinden ('654'[K[4]])
geçiriyor; tanımadıkları karakterde ikisi de undefined döndürüyor. Bir argüman
olarak geçirilen undefined ise JavaScript'te varsayılan parametreyi
devreye sokuyor ve U'nun varsayılanı p=3: vezir. Yani bu davranış bir denetimden değil, bir dil kuralından geliyor ve metin
ayrıştırıcılarının hiçbirinde bayt tutmuyor. Tıklamalı sürümlerde terfi her zaman panelden seçiliyor.
Ters yön — sonucu yazmak
Motor bir sayı döndürüyor: 1–15 arası bir kod. Ekrana ne yazılacağı
sürücünün işi ve burada iki yol var.
Sayısal sürüm hiç çeviri yapmıyor, sayının kendisini yazdırıyor; baştan sona sayısal kalan
bir arayüzde kodu harfe çevirmek tutarsızlık olurdu. light ise hiçbir
şey yazmıyor: sonuç tahtadan ve saatlerden okunuyor. Geri kalan
bütün sürümler — default ve metin sürücüleri — hiç sayı üretmiyor:
hükmü kendi satırlarında yeniden yazıyorlar ve iki harfli kod doğrudan çıkıyor.
prompt'tan üç satır:
Şekil motordakiyle aynı; değişen yalnızca döndürülen değişmezler. Hiçbir dal yeniden
yazılmıyor, öncelik sırası da olduğu gibi kalıyor.
Bayrak düşmesinde ve terkte iki harf, dizgi birleştirmeyle kuruluyor. F
sürücünün tuttuğu harf: bayrak düştüyse 'T', terkse 'R'. Rakibin mat
etmesi imkânsızsa arkasına 'M' ekleniyor — TM ya da RM. Değilse önüne kazananın
harfi geliyor: bayrağı düşen ya da terk eden taraf sırası gelen taraf olduğu için kazanan onun
rakibi; 'WB'[t/8] onu tek bir indeksle seçiyor — beyazın sırasında
t/8 1, yani 'B'. WT, BT, WR, BR: dört sonuç, bir ifade.
Oyun sürerken döndürülen değer 0; z yanlış kalıyor ve döngü
sürüyor. “Devam ediyor” hâli için ayrı bir dal harcanmıyor.
İki kodlama artık yuva yuva örtüşüyor. Anlaşmalı beraberlik artık motorda da 9,
harfli sürümlerde ise DA; sonuç tablosunda her hücrenin sayısı ve
kısaltması aynı sonucu adlandırıyor. Önceki nesilde öyle değildi: motor anlaşmalı beraberliği
15'te tutuyordu, UCI sürümleri ise kodu aynı indeksle okunan iki paralel dizgiyle
(' WBS75D53D' ve ' ##M5RP0RA') harfe çeviriyordu ve on karakterlik o
tabloya sığmak için anlaşmalı beraberlik orada 9'du. Yeni nesilde ne çeviri tablosu kaldı ne
de ayrışma.
Üç döngü biçimi
Ayrıştırıcıyı kimin çağırdığı da sürümden sürüme değişiyor ve bu seçim saatin nasıl
tutulacağını doğrudan belirliyor.
Döngü
Kim çağırıyor
Saat
Bloklayan modal pencere
Sürücü döngüsü, hamle beklerken duruyor
Pencere kapanınca geçen süre ölçülüp düşülüyor
Olay tuş ya da tıklama
Tarayıcı, kullanıcı hareket edince
Ayrı bir zamanlayıcı, sürekli işliyor
Akış terminal
Okuma akışı, satır geldikçe
Hamle geldiğinde ölçülüyor
Bloklayan biçimde saat aslında durmuyor; hiç işlemiyor — bir modal pencere açıkken
zamanlayıcı zaten çalışamaz. Onun yerine pencere kapanınca aradaki fark ölçülüp düşülüyor.
Sonuç aynı, ama sayısal sürümün saatinin milisaniye tutmasının nedeni bu: ölçüm doğrudan bir
zaman farkı olduğu için saniyeye çevirmenin bir kazancı yok.
Üçü de aynı motora aynı çağrıyı yapıyor. Değişen yalnızca çağrının ne zaman geldiği — ve
motor saati hiç okumadığı için bu seçim kural katmanına hiç
dokunmuyor.
Sürücü sınırı
Motorun kural katmanı — geometri G, tehdit T, rok
C, hüküm V, arama H ve hamleyi uygulayan
U ile M — dokuz L3 sürücüsünde de aynı
mantığı taşıyor. Değişen en üst kat ve motorun iki küçük kenarı.
En üst kat. Yalnızca prompt ile
prompt_blindfoldA'yı olduğu gibi çağırıyor —
A(i,f,terfi), harfli Z ve D ile. Geri kalan yedi
sürücü A'nın adımlarını kendi satırlarına gömüyor: M'yi çağırıyor,
yeni pozisyonu sayıyor ve hükmü aynı satırda soruyorlar. Saat artışı da oraya yerleşiyor:
tıklamalı sürümler u[t]+=5'i M sırayı çevirmeden hemen önce
yazıyor; metin sürücüleri ise artışı hamleden önce g=t ile sakladıkları tarafa
ekliyor — u[g]+=5, prompt ailesinde G[g]+=5;
numerical'da artış yok.
Saatler ve sayımlar nereye yazılıyor? Motor tekrar tablosu için ayrı bir nesne
oluşturmuyor, sayımları G'nin kendi özelliklerine yazıyor (G[k]);
prompt ailesi saatleri de oraya asıyor (G[0],
G[8]). Tıklamalı sürümler ile input ailesi ikisini birden
ekrandaki ögeye, yani tahtanın tablosuna ya da <pre>'sine (u)
yazıyor; nonbrowser ailesi saatleri satır okuyan fonksiyona asıyor (o
da u), numerical'ın saatleri ise düz bir nesnede duruyor.
Saat anahtarları 0 ile 8, pozisyon anahtarları uzun dizgiler; ikisi hiçbir zaman
çakışmıyor.
İki kenar. İki tür sürücü motoru iki ayrı yerden kesiyor, çünkü girdilerine farklı
ölçüde güvenilebiliyor. Tıklamalı sürümlerde seçim zaten yalnızca kendi taşına
yapılabiliyor; buna karşılık terfi bir panelden geliyor, bu yüzden U terfi taşını
parametreden değil y'den okuyor (y+t) ve H her
denemeden önce y=p yazıyor. Metin sürücülerinde yazılan girdi herhangi bir kareyi
adlandırabilir; bu yüzden L'ye kendi taşı sınaması ekleniyor
(~b[i]&8^t&&) ve L artık çıkış karesini kendisi süzdüğü
için O'nun renk süzgeci düşüyor.
prompt_string ile prompt_string_a1 önceki
nesilden; tahtayı harf dizgisi olarak tutuyorlar ve adımları kendi döngülerinde açık açık
yazıyorlar.
Rok defteri
L1'in rok defteri — hak yerine dokunulan kareler
L2 ve L3 rok haklarını dört bitlik bir maskede tutuyor:
c=15 ile dört hak açık başlıyor, hamle oynandıkça C tablosu hangi bitin
düşeceğini söylüyor. L1 bunu yapmıyor; onda ne bir hak maskesi var ne
de C tablosu. Onun yerine bir defter tutuyor: oynanan her hamle kendi çıkış ve
varış karesini işaretliyor. Defter ayrı bir değişken bile değil — işaretler G'nin kendi
özelliklerine yazılıyor, yani L3'te tekrar tablosunun durduğu yere; tekrar
saymayan L1'de orası boşta. Tıklamalı iki sürümde
(default ve light) aynı işi tahtanın tablosu,
u, görüyor.
Rok için sorulan tek şey, şahın ve kalenin karesine oyun boyunca hiç dokunulmamış olması; aynı
defter piyonun çift adım sorusunu da cevaplıyor. İki yazım beş yerde ayrılıyor:
Hamle gerçekten oynandığında G[i]=G[Y]=1 iki kareyi işaretliyor. Rok dalında
G[i]|G[r=f+=s-!~s]|T()|T(i+s)?0:S() dört soruyu tek bir koşulda
topluyor: şahın bulunduğu kareye ya da hesaplanan kale karesine hiç dokunuldu mu, şaha şu anda
şah çekilmiş mi, şahın üzerinden geçeceği kare tehdit altında mı? Biri bile doğruysa cevap 0. Kale karesinin hesabı
L2 ve L3'tekiyle aynı: s-!~s kısa
rokta 1, uzun rokta -2; yani kısa rokta şahın varacağı karenin bir sağı, uzun rokta iki solu.
f artık kale karesi olduğu için ardından gelen S() şahtan kaleye
kadar aradaki karelerin boş olduğunu yürüyerek doğruluyor. Hakkın düşmesinin iki yolu da tek
bir işarete iniyor: şah ya da kale bir kez oynadıysa karesi işaretli; kale kendi köşesinde
alındıysa da işaretli, çünkü alan taşın varış karesi o köşe.
Deneme hamleleri defteri kirletmiyor. L'nin geçerlilik denemesi yalnızca
U'yu çağırıyor; U tahtanın bir kopyasında çalışıyor, L
de dönerken b=q ile eski tahtayı geri koyuyor. İşaret U'da değil,
sürücünün hamle satırında — hamlenin gerçekten oynandığı yerde — konuyor.
L3'te maske de aynı nedenle U'da değil M'de
güncelleniyor: LM'yi hiç çağırmıyor ve bu yüzden deneme öncesinde
yalnızca tahtayı saklıyor.
Aynı defter çift adım sorusunu da cevaplıyor.L2 ve
L3 piyonun çift adım hakkını yatayına bakarak soruyor:
(i%96<24), piyonun durabileceği yataylar arasında ikinci ve yedinci yatayda
doğru; yanlış yöndeki çift adım zaten tahtanın dışına düşüyor. L1 aynı
soruyu deftere soruyor: !G[i] — piyonun durduğu kareye hiç dokunulmamışsa piyon
başlangıç karesinde. Başlangıç pozisyonundan oynanan bir oyunda ikisi denk: beyaz piyon ikinci
yataya sonradan gelemez, oradaki piyon hep oradaydı ve kareye başka bir taş ancak onu alarak
dokunabilirdi; ilerlemiş bir piyonun karesi ise oraya vardığı hamlede işaretlendi. Siyah için
aynısı yedinci yatayda geçerli. Kazanç dört bayt: !G[i] beş karakter,
(i%96<24) dokuz — ikincisi < yüzünden parantez istiyor.
Defter üç şeyi hiç sormuyor: kale karesinde gerçekten kendi kalesinin durup durmadığını, şahın
e1 ya da e8'de olup olmadığını ve çift adım atacak piyonun gerçekten başlangıç yatayında olup
olmadığını. Başlangıç pozisyonundan oynanan oyunda üçü de kendiliğinden doğru — dokunulmamış bir
köşede kale, dokunulmamış bir şah karesinde şah, dokunulmamış bir piyon karesinde yerinden hiç
oynamamış piyon durur. Pozisyon FEN'le kurulduğunda bunları yükleyicinin sağlaması
gerekiyor; bir sonraki bölüm bunun nasıl yapıldığını anlatıyor.
Neden L2 ve L3'te maske şart
Bu kestirme yalnızca tekrarın sayılmadığı bir seviyede mümkün. Rok hakları tekrar
anahtarının parçası (9.2.3.2) ve anahtar kanonik olmak zorunda: aynı haklara sahip iki
pozisyon aynı yazılmalı. Defter kanonik değil; oyunun geçmişini taşıyor. a2–a3 bile a2 ile
a3'ü işaretliyor; rokla hiçbir ilgisi olmayan kareler defterde birikiyor. Aynı taşların aynı
karelerde, aynı haklarla durduğu iki pozisyon, farklı yollardan gelinmişse farklı defterler
yüzünden ayrı pozisyonlar olarak sayılırdı. Maske ise yalnızca dört hakkı tutuyor;
C ilgisiz karelerde sıfır döndürdüğü için onları hiç yazmıyor; maske de
k=b+e+t+c anahtarına tek bir sayı olarak giriyor.
Geçerken alma karesinde de aynı ayrım var. L1 kareyi her çift adımda
yazıyor — e=U(…) çift adımda aradaki kareyi döndürüyor — ve almanın geçerli olup
olmadığına bakmıyor; geçersiz bir almayı zaten L reddediyor.
L2 ve L3'te M kareyi ancak sırası
gelen tarafın bir piyonu orada gerçekten alma yapabiliyorsa tutuyor
(b.some(…L(j,e))?e:8), çünkü e de tekrar anahtarının parçası
(9.2.3.1). İki hak da aynı nedenle iki ayrı yazımla tutuluyor: kuralın doğruluğu değişmiyor,
değişen yalnızca tekrar sayımının ihtiyacı.
Ne kadar tutuyor
Fark küçük. Yeni nesilde dört yerin hesabı şöyle: C tablosu virgülüyle 29 bayt ve
L1'de hiç yok; hamledeki işaret 3 bayt, çift adım testi 4 bayt kısa; rok
testi ise 4 bayt uzun. Toplam 32 bayt. Maskenin başlangıç değeri de ayrı bir atama istiyor:
L3'ün başlığındaki 15, üç bayt daha. Algoritma olarak ikisi
aynı ağırlıkta: biri dört biti kırpıyor, öbürü iki kareyi işaretliyor.
Defter L1'e önceki nesilde
tek bir değişiklikle ↗️
girmişti ve dosya 1.567 bayttan 1.541 bayta inmişti — 26 bayt. O zaman kazancın tamamı
C tablosundan geliyordu (virgülüyle 28 bayt); hamledeki işaret 3 bayt kısalıyor, rok
testi 5 bayt uzuyordu ve çift adım henüz deftere sorulmuyordu.
L1 seviyesindeki iki bot da, L1_1250elo ve
ToledosOpponentbyMe, defteri kullanıyor. Arama her düğümde hamlenin
dokunduğu iki karenin eski işaretini saklıyor, kareleri işaretliyor ve dönüşte eski işaretleri
geri koyuyor; defteri klonlamak gerekmiyor. Ölçümde maskeye göre hız farkı çıkmadı, arama
değerleri de birebir aynı. İkisi de 0x88 tahtada, engine_4x'in çekirdeği üstünde
çalışıyor.
FEN
FEN'den pozisyon kurmak — L1
L1 motoru bir pozisyonu üç değişken ve bir defterle tutuyor: tahta
b, sıra t, geçerken alma karesi e ve
rok defteri — G'nin özellikleri. FEN okuyan bir yükleyici
yok; motor bunların hepsine güveniyor ve hiçbirini denetlemiyor. Bir FEN'i bunlara çevirmek
sürücünün işi. Aşağıdaki bölümler bunun nasıl yapılacağını ve özellikle rok ile geçerken almada
nelere dikkat edilmesi gerektiğini anlatıyor.
L1'de iki tahta biçimi var. Bot dışındaki altı sürüm tahtayı
0x88 düzeninde sayılarla tutuyor: default,
light, input, input_blindfold,
numerical ve nonbrowser.
prompt_string önceki nesilden ve tahtayı harflerle tutuyor. Kurallar ikisinde
de aynı, değişen yalnızca taşların ve karelerin yazılışı. Önce sayısal biçim geliyor; harf dizgili
sürümün farkları ise sonda anlatılıyor.
Dokunulan kareler defteri; default ve light'ta u[…]
Aynı defter, kare indeksleri aynalı
Geçerken alma
e
Atlanan karenin indeksi ya da 8
Atlanan karenin indeksi ya da '-'
Yarım hamle, hamle numarası
—
L1'de sayaç yok, okunmaz
Aynı
Oyun sonucunu tutan z de sıfıra çekiliyor. light'ta
z diye bir değişken yok; orada bu atama zararsız, yalnızca gereksiz.
Sıra 8 ya da 0 olmalı.t bir bayrak değil, bir renk biti: beyaz taşların
kodu 8 bitini taşıyor ve motor sırayı her yerde taşın aynı bitiyle karşılaştırıyor —
p&8^t, ~b[i]&8^t, şahı ararken b.indexOf(2+t),
terfide p|t. t=1 ya da t=true yazılırsa 2+t
3 ediyor ve motor şah yerine siyah veziri arıyor. Motorun kendisi t^=8 ile hep 8 ya
da 0 üretiyor; FEN'den yüklerken de bunlardan biri yazılmalı.
Tahta. FEN yatayları 8'den 1'e doğru yazıyor, sayısal b ise a1'den
başlıyor. Kare indeksi 16 × (yatay − 1) + sütun; sütunlar a=0 … h=7; iki yatay
arasında sekizer delik var. Delikle boş kare aynı şey değil. Delikler dizide hiç
yazılmamış yuvalar olarak kalmalı, boş kareler ise 0 olmalı: kayan taşın yolunda
motor boş kareyi b[i]<1 ile soruyor ve undefined<1 yanlış;
0 yazılmayıp boş bırakılan bir kare kayan taşı duvar gibi durduruyor,
b.every ile b.some da onu hiç görmüyor. Yükleyici bu yüzden önce
altmış dört kareyi motorun kendi formülüyle sıfırlıyor (b[j*2-j%8]=0), taşları
ondan sonra yazıyor. Taş kodları motorun tahta temsiliyle aynı: 1 piyon,
2 şah, 3 vezir, 4 at, 5 kale, 6 fil, beyazda 8 fazlası; boş kare 0. Sık gereken indeksler:
a1=0, e1=4, f1=5, h1=7, a8=112, e8=116, h8=119.
Rok
Bir hak ancak üç koşul birden sağlanırsa geçerli: hak FEN'de yazılı (K,
Q, k, q), şah e1'de (siyah için e8'de) ve kendi kalesi o
köşede (K → h1, Q → a1, k → h8, q → a8).
Yükleyici defteri boşaltarak başlıyor, geçersiz her hakkın köşesini işaretliyor ve hiç geçerli
hakkı kalmayan rengin şah karesini de işaretliyor. Böylece dokunulmamış kalan kareler
yalnızca gerçekten roka yarayan şah ve kale kareleri oluyor. Kuşkulu bir hakkı yazmamak her
zaman güvenli: en kötü ihtimalle o hak kaybolur.
Piyonlar da işaretleniyor.L1 çift adımı da deftere soruyor
(!G[i]), bu yüzden başlangıç yatayında durmayan her piyonun karesi işaretlenmeli —
beyaz için ikinci, siyah için yedinci yatayın dışındakiler. Başlangıçtan oynanan bir oyunda o kareler
piyonun vardığı hamlede zaten işaretlenmiş olurdu; FEN'de bu geçmiş yok.
Yanlış girişlerin sonuçları, defterin üç şeyi hiç sormamasından
kaynaklanıyor:
Giriş
Sonuç
Hak varken yazılmamış
Zararsız, yalnızca hak kaybolur
Hak yazılmış, şah e1'de, köşe boş
Kalesiz rok: şah iki kare gider, kale gelmez
Hak yazılmış, şah e1'de, köşede başka bir taş
O taş şahın geçtiği kareye taşınır (h1 → f1, a1 → d1); rakip taşı bile olsa
Hak yazılmış, şah e1/e8'de değil
Sahte rok: şah iki kare gider; “kale karesi” diye hesaplanan kare tahtadaysa oradaki ilgisiz taş şahın geçtiği kareye taşınır (şah d1'deyse g1'deki at e1'e), bir deliğe düşerse tahta bozulur
Piyon başlangıç yatayında değil, karesi işaretlenmemiş
Çift adım açılır: e3'teki beyaz piyon e5'e gidebilir
Deliğe düşen sahte rokun bir örneği: beyaz şah f1'de, K yazılmış ama işaretlenmemiş,
g1 ile h1 boş. Motor f1–h1 hamlesini rok sayıyor ve “kale karesi” 8 çıkıyor — 0x88'de bir delik.
U oradaki “taşı” g1'e taşırken türü undefined-8, yani NaN
buluyor; X sıfıra düştüğü için hamle terfi sayılıyor ve g1'e beyaz bir vezir — metin sürümlerinin varsayılan terfi taşı — yazılıyor,
delik de 0 olup tahtanın altmış beşinci karesine dönüşüyor. d de
NaN olduğu için yanlış sayılıyor ve kaleyi taşıyan özyineleme bir kez daha çalışıyor: bu
yeni boş kareyi h1'e, şahın üstüne yazıyor. Şah tahtadan siliniyor.
L2 ve L3'te sahte rok oluşmuyor, çünkü
C yalnızca köşe karelerini bir hakka çeviriyor — 8 için de g1 için de sıfır döndürüyor.
Ama kalenin köşede durup durmadığını onlar da denetlemiyor, yani kalesiz rok orada da mümkün.
Geçerken alma
e, piyonun atladığı kare, durduğu kare değil: siyah d7–d5 oynadıysa
e d6'dır (indeks 83). Kare ancak şu koşulların hepsi sağlanırsa yazılır, yoksa
e=8: sırası gelen taraf beyazsa kare 6. yatayda, siyahsa 3. yatayda; karenin
kendisi boş; bir arkasında rakip bir piyon duruyor (d6 için d5'te siyah piyon); piyonun çıktığı
kare boş (d6 için d7). 8 bir delik, yani tahtadaki hiçbir kareye eşit olmuyor; motor da
“geçerken alma yok” için onu yazıyor. Tahtanın dışında kalan başka bir sayı da iş görür —
default başlangıçta -1 kullanıyor.
L1 almanın ayrıca geçerli olmasını istemiyor. Tekrar sayılmadığı için
yazılı kalan kare hiçbir hükmü değiştirmiyor; geçersiz bir almayı da L zaten
reddediyor. L2 ve L3'te durum farklı:
e tekrar anahtarının parçası olduğu için almanın gerçekten geçerli olması da
gerekiyor (9.2.3.1).
Motor e'ye tamamen güveniyor: piyonun e'ye çapraz hamlesine izin
veriyor ve almada bir arkadaki kareyi koşulsuz temizliyor — b[e-f|P-1?i:f^16]=0.
Bir arkadaki kare f^16: 0x88'de yatay üst onaltılık basamakta durduğu için 16 biti
altıncı yatayı beşinciye, üçüncüyü dördüncüye çeviriyor; iki renk için tek bir XOR. İki tahta
biçiminde de davranış aynı:
Giriş (beyaz d5 piyonu, e = e6)
Sonuç
e5'te piyon değil at var
d5xe6 geçerken alma sayılır, e5'teki at silinir
e6 boş değil (siyah fil), e5'te at
dxe6 fili alır, e5'teki atı da siler: tek hamlede iki taş
Yanlış bir e yalnızca ilk hamlede zarar verebilir: her hamlede e yeniden
hesaplanıyor ve FEN'den gelen değer kayboluyor.
Genel geçerlilik. Motor pozisyonun geçerli olduğunu varsayıyor. Rok ve geçerken
almanın dışında yükleyicinin sağlaması gerekenler: her renkte tam olarak bir şah; birinci ve
sekizinci yatayda piyon olmaması; sırası gelmeyen tarafa şah çekilmemiş olması. Motor
yalnızca sırası gelen tarafın şahını soruyor; öbür tarafa şah çekiliyorsa o şah alınabilir ve
motorun şah araması bozulur.
Tahta FEN'in kendi sırasında.b a8'den başlıyor ve FEN'in yazıldığı
sırayla ilerliyor: a8=0, h8=7, a7=8 … a1=56, h1=63. Yatayları ters çevirmek gerekmiyor;
FEN'deki harfler olduğu gibi sıralanıyor, her rakam o sayıda '-''ye açılıyor. Büyük
harf beyaz, küçük harf siyah, boş kare '-'. Delik yok. Kare indeksi
(8 − yatay) × 8 + sütun; sık gerekenler a8=0, e8=4, h8=7, a1=56, e1=60, f1=61,
h1=63.
Rok. Defter burada da G'nin özelliklerinde; model aynı, kare indeksleri
aynalı: K → e1=60 ve h1=63, Q → e1=60 ve a1=56, k → e8=4
ve h8=7, q → e8=4 ve a8=0. Geçerlilik koşulları ve işaretleme kuralı birebir aynı.
Piyonlar için işaret gerekmiyor: bu sürüm çift adım hakkını hâlâ yataya bakarak soruyor
(y%6<2). Yanlış bir girişin sonucu da farklı: beyazın sahte roku burada
tahtanın dışına taşıyor. Şah f1'de (61) iken “kale karesi” 64 çıkıyor; oradaki taşın türü
sorulurken undefined büyük harfe çevrilmeye çalışılıyor, bir TypeError
fırlatılıyor ve döngü duruyor — hamle daha L'nin denemesinde çöküyor.
Geçerken alma. Geçerken alma yoksa e, 8 değil
'-'. Koşullar aynı, ama indeksler aynalı olduğu için yönler tersine dönüyor: sırası
gelen taraf beyazsa kare 2. satırda (e>>3 == 2), arkasındaki kare
e+8, piyonun çıktığı kare e-8; siyahsa kare 5. satırda ve işaretler
yer değiştiriyor. d6'nın indeksi burada 19.
Sıra. Bu sürümde sıra 8 ile 0 değil, 1 ile 0: şahı 'kK'[t] ile arıyor, bu
yüzden t'nin sayı olması burada da zorunlu.
Örnekler
Aşağıdaki pozisyonların hepsi hem input'a hem
prompt_string'e aşağıdaki yükleyicilerle yüklendi. Motorun izin verdiği
roklar ve geçerken almalar, her sürümde motorun kendi L'sine sorularak denetlendi;
iki sürüm de aynı cevabı verdi.
FEN
Durum
Sayısal yazım
Harf dizgili yazım
Motorun izin verdiği
r3k2r/pppq1ppp/2nbbn2/3pp3/3PP3/2NBBN2/PPPQ1PPP/R3K2R w KQkq - 0 10
Son iki satırda yükleyici geçersiz hakların köşelerini de işaretliyor. Şah kareleri zaten işaretli
olduğu için bunların sonuca etkisi yok; tabloda yalnızca belirleyici işaretler var. Sayısal yazımdaki
piyon işaretleri ise ilerlemiş piyonların çift adımını kapatıyor.
Yükleyiciler
Örneklerde kullanılan iki yükleyici aşağıda. İkisi de L1'in global
değişkenlerini doğrudan yazıyor. Önce sayısal sürümler için olanı:
loadFEN=fen=>{
const [pos,side,rights,ep]=fen.split(' '), K={P:9,p:1,K:10,k:2,Q:11,q:3,N:12,n:4,R:13,r:5,B:14,b:6};
b=[]; for(let j=0;j<64;j++) b[j*2-j%8]=0;
pos.split('/').forEach((row,r)=>{let x=(7-r)*16;for(const ch of row)ch>'0'&&ch<='8'?x+=+ch:b[x++]=K[ch]});
t=side=='w'?8:0; z=0; Object.keys(G).forEach(k=>delete G[k]);
// hak ancak FEN'de yazıyorsa, şah e1/e8'deyse ve kendi kalesi o köşedeyse geçerli
const R=[['K',4,7,10,13],['Q',4,0,10,13],['k',116,119,2,5],['q',116,112,2,5]]
.map(([h,s,r,kk,rr])=>({h,s,r,ok:rights.includes(h)&&b[s]==kk&&b[r]==rr}));
for(const x of R) if(!x.ok) G[x.r]=1; // geçersiz hakkın köşesi
for(const w of [8,0]){ if(!R.some(x=>x.ok&&(x.s==4)==!!w)) G[b.indexOf(2+w)]=1 } // hiç hakkı olmayan rengin şahı
b.forEach((p,j)=>p%8==1&&j>>4!=(p>8?1:6)&&(G[j]=1)); // başlangıç yatayında durmayan piyon// geçerken alma: kare doğru yatayda ve boş, arkasında rakip piyon, piyonun çıktığı kare boş
e=8; if(ep&&ep!='-'){const s=ep.charCodeAt(0)-97+(ep[1]-1)*16, d=t?-16:16;
if(!b[s]&&s>>4==(t?5:2)&&b[s+d]==(t?1:9)&&!b[s-d]) e=s}
}
Tıklamalı sürümlerde üç şey değişiyor. Defter G'de değil, tahtanın
tablosu u'da: yükleyicide G yerine u yazılıyor; silerken
Object.keys yine yalnızca işaretleri buluyor, çünkü bir ögenin kendi sayılabilir
özellikleri sonradan eklenenlerden ibaret. Seçim i=Y=-1 ile temizleniyor —
özellikle light'ta, çünkü orada başlangıçtaki seçim a1'e ve oradaki
kalenin hareket edemiyor olmasına dayanıyor. Son olarak light'ın
render'ı, bekleyen bir terfi yoksa, her çağrıda sırayı çeviriyor: bu yüzden orada
t ters yazılıp F() çağrılıyor ve sırayı doğru tarafa render
getiriyor.
Aynı kurallar, prompt_string'in tahta biçimiyle. Tahta FEN sırasında
olduğu için yatayları çevirmeye gerek yok:
loadFEN=fen=>{
const [pos,side,rights,ep]=fen.split(' ');
b=pos.split('/').flatMap(row=>[...row].flatMap(ch=>ch>'0'&&ch<='8'?Array(+ch).fill('-'):ch));
t=side=='w'?1:0; z=0; Object.keys(G).forEach(k=>delete G[k]);
// aynı geçerlilik kuralı, aynalı indekslerle: e1=60, h1=63, a1=56, e8=4, h8=7, a8=0
const R=[['K',60,63,'K','R'],['Q',60,56,'K','R'],['k',4,7,'k','r'],['q',4,0,'k','r']]
.map(([h,s,r,kk,rr])=>({h,s,r,ok:rights.includes(h)&&b[s]==kk&&b[r]==rr}));
for(const x of R) if(!x.ok) G[x.r]=1; // geçersiz hakkın köşesi
for(const w of [1,0]){ if(!R.some(x=>x.ok&&(x.s==60)==!!w)) G[b.indexOf(w?'K':'k')]=1 } // hiç hakkı olmayan rengin şahı// geçerken alma: yönler aynalı; boşken '-'
e='-'; if(ep&&ep!='-'){const s=ep.charCodeAt(0)-97+(8-ep[1])*8, d=t?8:-8;
if(b[s]=='-'&&(s>>3)==(t?2:5)&&b[s+d]==(t?'p':'P')&&b[s-d]=='-') e=s}
}
İki yükleyici de genel geçerlilik koşullarını denetlemiyor; onları sağlamak FEN'i girenin
sorumluluğunda. Başlangıçta FEN alıp onu tam denetimden geçiren bir arayüz
Chess LUX'ta var.
Paketleme
Paketlenmiş dosya
Tam hakemi taşıyan oynanabilir dosyalar, arayüzlerine ve eski ya da modern işaretleme kullanmalarına göre
1,7 KB ile 2,7 KB arasında değişiyor
(1 KB = 1024 bayt).
Paketlenmiş tek bir sürüm var: L3_packed, dosyası FideLite.html, 2.423 bayt. Kendi kendini açan
bir RegPack çıktısı — dosya bir UTF-8 imi (BOM) ve tek bir <script>'ten
ibaret, önünde tek bir işaretleme karakteri bile yok. Script'in gövdesi bir açma döngüsü; son
adımı eval(_). eval(_) yerine bir atama konduğunda dosya kendi
kaynağını geri veriyor:
vm.runInContext(scriptBody.replace(/eval\(_\)$/, '__out=_'), c);
// c.__out → paketlenen kaynak
Geri gelen kaynak default'un kendisi: script'i
L3.html'inkiyle bayt bayt aynı. Tek fark, işaretlemenin yerinde. Düz
dosyada <script>'ten önce statik duran HTML, pakette script'in içine, tek bir
document.write çağrısına taşınmış — R=u[…]'dan hemen önce, yani kod
u'ya ilk kez dokunmadan önce. Paketlenen kaynak 2.679 karakter, düz dosya 2.710.
Aradaki 31 karakterin hesabı: <!DOCTYPE html> (15) ile
<meta charset=utf-8> (20) yok; buna karşılık terfi paragrafını kapatan bir
</p> (4) eklenmiş; document.write`…`, sarmalayıcısının 17
karakteri de düz dosyadaki <script> etiketlerinin 17 karakterini
dengeliyor.
Paketin kendi kaynağı
Yeni nesilde paketin ayrı bir kaynağı yok. RegPack'e verilen girdi,
default'un script'i ile onun işaretlemesini yazdıran tek bir
document.write çağrısından ibaret; kural katmanına giren her değişiklik pakete
kendiliğinden geçiyor, taşınacak ya da ayrıca ölçülecek bir şey kalmıyor. Adlandırma da imza
da düz kaynaktaki gibi.
İşaretlemeyi script'e almak kendi başına kazandırıyor. Aynı işaretlemeyi
L3.html'deki gibi script'in dışında statik bırakan paket, RegPack'in en ucuz
ayarıyla 2.489 bayt: 387 bayt statik işaretleme, 17 bayt etiket, 2.085 bayt paketlenmiş
script. Yayımlanan paket ise 2.423 bayt, yani 66 bayt kısa. Bunun 28 baytı DOCTYPE ile
meta'nın düşmesinden geliyor — 35 bayt gidiyor, yerine UTF-8 imi (3) ile
</p> (4) geliyor. Kalan 38 bayt
paketleyicinin işi: işaretleme artık onun gördüğü metnin parçası ve içindeki tekrarlar —
<p id=, onclick=E(, background: — script'tekilerle
birlikte sıkıştırılıyor. Statik işaretlemeyi DOCTYPE'sız ve meta'sız bırakan aynı dosya 2.454
bayt tutardı.
Önceki nesilde durum tersti. Paketlenen kaynak hem düz kaynaktan uzundu hem de ondan
ayrıydı: düz kaynak bayt için, paket kaynağı tekrar için ayarlanmıştı (2.713 bayt yerine
3.266). Aradaki fark dört başlıkta toplanıyordu: açılan takma adlar, gömülen fonksiyonlar,
uzatılan operatörler ve eskimiş işaretleme. İkisini aynı dosyadan üretmek işe yaramıyordu,
çünkü golf her tekrarı siler, RegPack ise tam olarak tekrardan beslenir.
O yüzden yol ters çevrilmişti: paket kaynağı yerinde tutuldu — açılan takma adlar, gömülen
fonksiyonlar, uzatılan operatörler, adlandırma — ve düz kaynakta kural katmanına giren her
değişiklik bu kaynağa tek tek taşındı. Her birinin pakete etkisi ayrı ölçüldü:
Değişiklik
main
Piyon itişi S() ile
−7
Rokta S() ve !V(t)
−6
l: N yeniden yazımı
−1
l: w|=p
−2
l: V*v kapısı
−8
I: kare rengi
−1
I süzgeci ve Im takası
−4
Geçerken alma hilesi
−3
G: g parametresi
−2
x=T|b[f]alınmadı
0
Hepsi birliktegövde
2.682 → 2.648
Dosya
2.702 → 2.668
Satırlar her değişikliğin tek başına etkisini gösteriyor (RegPack 5.0.1, 90 ayar); son iki
satır ise hepsinin birlikte, 5.0.4 ve 350 ayarla vardığı sonuç. “Birini çıkar, kalanlarla
yeniden paketle” sınamasında hiçbir değişikliğin çıkarılması paketi küçültmedi.
x=T|b[f] düz 1x'te bir bayt kazandırıyor, burada nötr — alınmadı.
Ölçüm kalibre edilmiş: önceki paket açılıp kendi ayarlarıyla yeniden paketlendiğinde
yayımlanan dosyanın bayt bayt aynısı çıktı — yani farkın tamamı kaynaktan ve
ayardan geliyor.
Değişmeyen: adlandırma ve imza; paket kaynağının yapısı (Math.abs ile
indexOf açık, Z gömülü, renk %2 ile okunuyor).
Değişen: o paket açıldığında elde edilen kaynak, kural katmanında düz sürümle aynı
nesildendi — aynı hamleler, aynı hükümler, aynı geçerken alma kareleri ve tekrar
anahtarları.
l'nin yeni taraması. Tablodan sonra kural katmanına bir değişiklik daha
girdi: taşları kodlarına göre tek geçişte bir bitboard dizisine (h) toplayan ve
materyal kapısını !w yerine !(f^j^W^B^V^v) ile kuran yeni bir
l. Bu da aynı yolla taşındı, iki harf farkıyla: pakette boş karelerin bitboard'u
j yerine d, h'yi kuran geri çağrının parametresi
_ yerine p; böylece _ kaynağa girmiyor, token havuzunda
kalıyor. l'nin öbür yerel adı eski paketteki gibi: Z yerine
E. Sonuç 5.0.4 ve 350 ayarla: gövde 2.648 → 2.632; dosya 2.668 →
2.652.
O neslin son adımı: ekim kısaltmaları. Kural katmanına sonra sekiz değişiklik daha
girdi ve hepsi aynı yolla taşındı: kilitli taramada taşları kümelere ayıran
h[p>>2^2?!p*6:p-6] eşlemesi ile !q kapısı, 6.9 aramasının
p&6 testi, rok kalesinin karesi f+=k>0||-2, başlangıç
maskelerinde C=P=W, kare rengi i*9/8%2+1, materyal ağırlığı
p>9?p>11:9, çift adım testi y%6<2 ve terfide her taşı ayrı
sayan düğüm bütçesi. Yeni kapı sayesinde eski d de yapı çözmeden (destructuring)
düştü. Her birinin pakete etkisi, öbürleri yerindeyken yalnızca o geri alınarak ölçüldü:
kilitli tarama kapısı −14, p&6 −3, rok −2, C=P=W −2, kare rengi ve
ağırlık birer bayt; çift adım ile bütçe nötr. Hiçbiri paketi büyütmüyor. Sonuç 5.0.4 ve 350
ayarla: gövde 2.632 → 2.615; dosya 2.652 → 2.635. Önceki neslin paketi bu
boyutta kaldı.
Takma adlar ve gömülen fonksiyonlar
Yeni pakette bu başlığın karşılığı boş: takma ad açılmıyor, fonksiyon gömülmüyor, operatör
uzatılmıyor; renk de motordaki gibi &8 ile okunuyor. Fonksiyon olarak kalanlar
default'unkilerin ta kendisi. Aşağıdaki tablo ve onu izleyen ölçümler
önceki neslin paketine ait; takma ad kuralı ise paketleyiciye değil token ekonomisine ait bir
kural ve bugün de geçerli.
Dosya
Açılan takma ad
Gömülen fonksiyon
Fonksiyon kalan
L3_packed.htmlönceki nesil
a=Math.abs3 yerN='indexOf'5 yerN='innerText' açılmayan tek takma ad
ZIm
GVLCMIHlAFSd
H ile l fonksiyon olarak kalıyordu; ikisi de özyinelemeliydi, yani
gömülmeleri mümkün değildi. Şah testi, tehdit taramasıyla birleştikten sonraki
yapıdaydı: iki iş tek bir V'de toplanmıştı ve bu V sekiz çağrı yeriyle
fonksiyon olarak duruyordu.
Paketleyicinin bir zamanlar bulduğu kazanç — eski şah testi sarmalayıcısının, o zamanki
l'nin, gövdesi tek bir tehdit çağrısı olduğu için gömülebilmesi — sonradan
kaynağa terfi etti: sarmalayıcı silinip V'nin ikinci parametresinin
varsayılanına katlandı ve boşa çıkan l harfi kilitli pozisyon taramasına geçti. Aynı
desen hız bölümünde iki kez daha görülüyor. Paketleyici ile hız varyantı,
kaynağa neyin ait olduğunu bulan iki ayrı arama; ikisinin bulduğu şey de er ya da geç kaynağa
iniyor. Yeni nesil bunun son halkası: paketin bulduğu her şey kaynağa indikten sonra ayrı bir
paket kaynağına gerek kalmadı.
Yalnızca doğruluk değerinin kullanıldığı yerlerde operatörler uzatılıyordu:
&& sayısı 28'den 39'a, || sayısı 15'ten 19'a çıkıyordu. Motor
tarafında renk, &1 yerine %2 ile çıkarılıyordu. Adlandırma
korunuyordu — reassignVars kapalı, globaller düz kaynaktakiyle aynı
harflerde.
Bir takma adı açmanın kazandırması Math.abs'e özgüydü; genel bir kural değildi.
Ters yön de aynı sonuca varıyordu: Math.abs'i yeniden takma adla yazmak +3 bayt,
l'yi açmak +11, C'yi açmak +21, b.indexOf'u takma adla
yazmak +15.
Nedeni token ekonomisi. Elle yazılmış bir takma ad bedava bir kısaltma: kaynakta zaten kısa
duruyor ve paketleyicinin tek karakterlik token havuzundan hiçbir şey eksiltmiyor. Takma ad
açıldığında ise kaynağa uzun bir metin giriyor, paketleyici o metni sıkıştırmak için bir token
harcıyor ve harcanan token başka bir kalıptan eksiliyor. Math.abs istisnaydı,
çünkü yalnızca üç yerde geçiyordu ve açılmış hâli komşu metinle birleşip —
Math.abs( — tek başına üreteceğinden daha uzun bir ortak kalıp veriyordu.
Kural şu: bir takma ad ancak açılmış hâli komşu metinle birleşip daha uzun bir ortak
kalıp üretiyorsa açılır. Farklı bağlamlarda çok kez geçen, yalın bir değerin yerini tutan
takma adlar zaten en iyi hâlindedir; onlara dokunmak her seferinde kaybettirir.
Kaynağa uygulanan zorunlu üç şey
Literal içindeki satır sonları \n kaçışına çevrilmiş olmalı.
RegPack girdiyi temizlerken [\r\n]+\s* desenine uyanları siliyor ve satır sonunun
bir literalin içinde olup olmadığına bakmıyor. Bu adım atlanırsa hata çıkmaz, çıktı geçerli JS
kalır — yalnızca tahta çizen sürümlerin render'ı kayar. default'un
kaynağında hiç satır sonu yok, tahta <tr>'lerle bölünüyor; kural, render'ı
satır sonlarıyla kuran input ya da nonbrowser gibi
bir sürüm paketlenirse devreye giriyor.
Şablon dizgisindeki anlamlı boşluk \x20 kaçışıyla yazılmalı — önünde
satır sonu varsa. Aynı temizleme, önceki neslin dom kaynağındaki şu boşluğu
yiyordu: onclick=S(id) id=${w} → onclick=S(id)id=${w}. Tırnaksız
onclick değeri S(id)id=0 hâline gelip id özniteliğini
yutuyordu; altmış dört karenin hiçbirinde id kalmıyor, this[w]
tanımsız oluyor ve tahta hiç çizilmiyordu. Node'daki testlerde document.write
bir taklit (stub) olduğu için hata hiç görünmüyordu; yalnızca tarayıcıda ortaya çıkıyordu.
Çözüm boşluğu kaynağa kaçış olarak yazmaktı:
<th width=54 onclick=S(id)\x20id=${w}>. RegPack kaçış dizisini boşluk
saymıyor. Bu kaçış kaynağı üç karakter uzatıyor, pakete birkaç bayt yansıyor ve üretilen HTML
düz sürümdekiyle birebir aynı kalıyor; reassignVars'ın açık ya da kapalı olması
fark etmiyor. Yeni pakette böyle bir boşluk yok: kaynakta hiç satır sonu bulunmadığı için
işaretlemedeki boşlukların hiçbirinin — öznitelikleri ayıranlar da,
</label> ile <button arasındaki iki EM SPACE de — önünde satır
sonu yok ve hepsi temizlemeden zarar görmeden çıkıyor. EM SPACE bu kuraldan muaf değil:
desendeki \sU+2003'ü de kapsıyor.
Paketleyiciye yalnızca JS girmeli. Ham HTML paketlenirse açılan kaynak
eval edilebilir bir JS olmaktan çıkıyor. İşaretleme için iki yol var: ya
paketin dışında, <script>'ten önce statik kalıyor —
DOM_911 ile DOM_minimum böyle — ya da JS'e
çevrilip içeri alınıyor: L3_packed onu bir document.write
ile yazdırıyor.
Boşta harfler
Motor sekmesindeki dört tablo hangi harfin kullanıldığını anlatıyor.
Bu tablo tersini gösteriyor: her dosyada tanımlayıcı olarak hiç geçmeyen harfleri. Düz
dosyaların kaynağı ayrıştırılıp tek harfli bütün tanımlayıcılar toplandı — işaretlemedeki
id'ler de, çünkü adla erişim sayesinde onlar da birer global değişken; aşağıdakiler
geriye kalanlar.
Dosya
Boşta harfler
Sayı
engine.js
lwzEJKY
7
engine_4x.js
lwzEJKY
7
numerical
lDFJ
4
prompt
l
1
input
lZ
2
input_blindfold
lDZ
3
default
lZ
2
Sayı, arayüzsüz motorlardan sürücülere geçince düşüyor: iki motorun da arayüzü yok,
ikisinde de yedi harf boşta; sürücülerde bir ile dört arası kalıyor, en azı
prompt'ta. Ölçülen yedi dosyanın hepsinde boşta olan tek bir harf var:
l.
Tanımlayıcı olarak boşta olmak tek başına yetmiyor ve nedeni
paketleyicide. RegPack açma döngüsünün sarmalayıcısı için
bir harf istiyor, ama token karakterlerini de kaynakta hiç geçmeyen
karakterlerden seçiyor. Yani iki ayrı test var: harf tanımlayıcı olarak boşta
olacak ve dosyada karakter olarak da hiç bulunmayacak. İkisi aynı şey
değil.
Harf
Kaç dosyada boşta
Boşta olduğu dosyalarda karakter olarak
l
7
Hepsinde var — slice, every, replace …
Z
3
Hiçbirinde yok — default, input, input_blindfold
J
3
Hiçbirinde yok — engine.js, engine_4x.js, numerical
D
2
İkisinde de var — new Date
wzEKY
2
Hiçbirinde yok — engine.js, engine_4x.js
Yedi dosyanın hepsinde iki testi birden geçen bir harf yok. l her yerde
tanımlayıcı olarak boşta, ama karakter olarak da her yerde geçiyor: sarmalayıcı için iş görür,
token olamaz. İki testi birden geçenler arasında en geniş kapsamlıları Z ile
J, üçer dosyada: Z tarayıcıda tahta çizen üç sürücüde —
default, input, input_blindfold —,
J iki motorla numerical'da ne tanımlayıcı ne de karakter olarak
geçiyor. Paket de bunu görüyor: default'tan üretilen
L3_packed'in token sınıfında Z var.
Önceki nesilde bu harf J'ydi: yedi dosyanın yedisinde de iki testi birden
geçiyordu. Yeni nesilde J sürücülere geçti — default'ta talep
fonksiyonu, metin sürücülerinde UCI ayrıştırıcısı — ve yalnızca iki motorla
numerical'da boşta kaldı.
Token karakterleri
RegPack token'larını kaynakta hiç geçmeyen karakterlerden seçiyor. Kontrol
karakterleri neredeyse bedava, çünkü regexp sınıfında tek bir
\u0001-\u001f aralığına sığıyorlar; yazdırılabilir olanlar tek tek
sayılıyor. Kaynağa giren her yeni karakter havuzdan bir aday eksiltiyor, çıkan her
karakter bir aday ekliyor.
Yeni paketin havuzunda dört yazdırılabilir aday var ve RegPack üçünü kullanıyor.
" dışarıda kalıyor, çünkü paket dizgisini çevreleyen tırnak o. \ içeride:
default'un kaynağında hiç kaçış dizisi yok, bu yüzden ters bölü havuzda
kalıyor — regexp sınıfında kaçış gerektirdiği için iki bayt tutan, en pahalı aday olmasına
rağmen.
Önceki nesilde ters yönün canlı örneği dom'daydı.
Şablon dizgisine yazılan \x20 kaynağa bir ters bölü
sokmuş ve \ o anda havuzdan düşmüştü. Üç karakterlik bir düzeltmenin bedeli baytla
bitmiyor: token alfabesinden de bir karakter götürüyor. Aynı nesilde
default'ta render'ın parametresinin ve yazı tipi ailesinin
_'den çıkması da _'yi token yapmıştı; yeni default_'yi ikisi için de kullanıyor.
Dosya
Kaynakta geçmeyen yazdırılabilirler
Token sınıfı
FideLite.html
" @ Z \
[\u0001-\u001fZ@\\]dördünden üçü
L3_packed.htmlönceki nesil
" @ J Z _
[\u0001-\u001f _ Z J @]beşten dördü
Hangi ayarlarla paketleniyor
Beş paketin beşinde de RegPack 5.0.1 ile 5.0.4 birebir aynı dosyayı veriyor.
reassignVarskapalı: açık bırakılırsa adları RegPack kendisi seçiyor;
L3_packed'in kaynağında bu hiçbir şey kazandırmıyor, açılan kaynak ise
default'un metni olmaktan çıkıyor. Önceki nesilde ilk kurban imza
harfleriydi. withMath ile wrapInSetInterval da kapalı —
with(Math) gövdeyi 10 bayt uzatıyor —, useES6 açık. Değişen tek şey crush dörtlüsü ve her dosya kendi dörtlüsünü istiyor: aşağıdaki değerler
ilgili dosyayı bayt bayt yeniden üretiyor.
Dosya
gain
length
copies
tiebreaker
FideLite.html
1,5
0,5
0,25
−1
DOM_911.html
0,5
1
0
−1
DOM_minimum.html
0
1
0,5
−1
Lichess-eqv_min.html
0,5
2
1
0
Chesscom-eqv_min.html
0,5
1
0,5
0
İlk üç sütun, tek bir puanın ağırlıkları (gain × kazanç + length × kalıp uzunluğu +
copies × kopya sayısı); bu yüzden üçünü aynı oranda büyütmek sıralamayı değiştirmiyor:
0 / 1 / 0,5 ile 0 / 2 / 1 aynı dosyayı veriyor. Dörtlü aslında bir
orandan ve bir eşitlik bozma kuralından ibaret. Lowest.html artık listede yok: yeni
motorla düz, paketsiz bir dosya oldu. DOM_996'nın yerini de
DOM_911 aldı.
L3'ün paketi için gain, length, copies ve tiebreaker katsayılarının 2.187
kombinasyonu, ardından en iyi sonucun çevresinde daha ince bir ızgara tarandı. En kısa
gövde 2.403 bayt ve 1,5 / 0,5 / 0,25 / −1 ayarında geliyor; L3_packed için
seçilen ayar bu. RegPack 5.0.4 de 2.403'ün altına inmiyor ve bu ayarda iki sürüm birebir
aynı gövdeyi veriyor. Bir önceki paketin ayarı (1 / 0 / 0,25 / −1) önceki dosyayı bayt bayt
yeniden üretiyor, bu kaynakta ise 2.413 veriyor: 10 bayt geride.
Dosya
5.0.1 en iyi
5.0.4 en iyi
Ayar
FideLite.html
2.403
2.403
1,5 / 0,5 / 0,25 / −1
Sayılar paketlenmiş gövdenin boyu; dosyaya ayrıca <script> etiketleri (17
bayt) ve UTF-8 imi (3 bayt) ekleniyor: 2.403 + 17 + 3 = 2.423. Sürüm en kısa sonucu
değiştirmiyor.
Dörtlünün neden dosyadan dosyaya değiştiğini gösteren en temiz örnek önceki nesilden: motor
tarafındaki p%2 → p&1 değişikliği. Düz sürümlerde bedavaydı: aynı
uzunluk ve ölçülmüş %3,6 hız kazancı. Paketlenmiş sürümlerde ise pahalıydı, çünkü
p%2 o kaynaklarda sekiz ile on dört kez arasında geçen bir tekrardı ve RegPack onu
tek bir token'a sıkıştırıyordu; tek bir geçişi değiştirmek o token'ı ortadan kaldırıyordu.
Ölçüm: L3_packed+3, DOM_996+2,
Lichess-equivalent_minimum+10,
Chesscom-equivalent_minimum+9. Hiçbir crush dörtlüsü bu kaybı
geri getirmedi — dosya başına 360 kombinasyon denendi; paketlenmiş sürümlerde renk o nesil
boyunca %2 ile çıkarılmaya devam etti. Aynı etkinin ters yönde bir örneği de
vardı: DOM_minimum'da sürücüdeki b[f]%2^t →
b[f]&1^tbir bayt kazandırdı, ama ancak crush dörtlüsü
1 / 0 / 1 / 0'dan 0,5 / 0 / 1 / 0'a çekilince. Yeni motorda renk her
yerde &8 ile okunuyor ve sorun kendiliğinden ortadan kalktı.
Geçerken alma karesindeki ve aramanın dönüşündeki iki kısa devre de o nesilde paketlenmiş
tarafta bayt açısından nötr çıkmıştı; düz sürümlerle tutarlılık bedavaya geldi ve ikisi
de alındı.
Paketten sonra ne doğrulanıyor
Paketleme kaynağı okunamaz hâle getirdiği için çıktının doğruluğuna gözle karar vermek
mümkün değil. Yeni pakette denetimlerin çoğu tek bir karşılaştırmaya iniyor, çünkü
açılan kaynağın düz bir karşılığı var:
Açılma.eval(_) yerine bir yakalama konularak kaynak çıkarıldı ve
tablodaki ayarla yeniden paketlendi: gövde bayt bayt aynı, hem 5.0.1 hem 5.0.4 ile.
Düz sürümle özdeşlik. Açılan kaynaktan document.write ifadesi
çıkarılınca kalan, L3.html'in script'iyle bayt bayt aynı. Kural katmanı,
sürücü, saat ve render default'un kendisi; ayrı bir denklik sınamasına
gerek kalmıyor.
Üretilen işaretleme.document.write'ın yazdırdığı işaretleme,
L3.html'in statik işaretlemesinden üç yerde ayrılıyor:
<!DOCTYPE html> yok, <meta charset=utf-8> yok — işini UTF-8
imi görüyor — ve terfi paragrafı </p> ile kapanıyor; nedeni
quirks modu.
Tarayıcı. Chromium'da belge modu BackCompat; konsolda hata ya da uyarı
yok.
Önceki neslin paketinin ayrı bir kaynağı olduğu için orada denkliğin ayrıca kanıtlanması
gerekiyordu. O paket için çalıştırılan sınamalar şunlardı:
Açılma.eval(_) yerine bir yakalama konularak açılmış kaynak
çıkarıldı ve paketleyiciye verilen girdiyle karşılaştırıldı: bayt bayt aynı.
İmza. Kaynağa gömülü c U N e Y t altılısının altısı da yerindeydi,
imza harflerinin hiçbiri kaymamıştı.
perft. CPW'nin yedi pozisyonu dördüncü derinliğe kadar sınandı
(perft: bir pozisyondan çıkan bütün hamle dizilerini sayan standart
doğruluk sınaması); hepsi geçti.
Kural katmanı. Eski ve yeni paket kaynakları yan yana çalıştırıldı: hamle listeleri (54 oyun,
8.269 pozisyon), rok zorlama testi (241.920 G çağrısı), l,
I ve Im (4.000 pozisyon) ve H araması — harcanan bütçe
dahil, 2.401 pozisyonda iki taraf için: 0 fark.
Eş adımlı denklik. Yeni paketle bir önceki paket aynı girdi ve tıklama dizisiyle
çalıştırıldı; her adımda b, e, c, t,
n, $, o, z — ayrıca saatler, seçim, terfi
panelinin hidden durumu ve durum satırı — karşılaştırıldı. 38 oyunda 5.969 adım:
0 ayrılma.
Üretilen işaretleme.default'un document.write çıktısı
bir önceki paketle birebir aynıydı — <th width=54 onclick=S(id) id=0>
içindeki boşluk dahil.
Dosyayı yeniden paketleyecek olan şu üç denetimi mutlaka yapmalı: imza, üretilen
işaretleme ve açılan kaynağın düz default'la karşılaştırması.
Boyutun düşmüş olması tek başına hiçbir şey söylemiyor — imzası uçmuş bir dosya da birkaç bayt
küçük çıkar.
Esnetmeler — eskimiş teknikler ve quirks modu
Motor tarafı modern: BigInt değişmezleri, şablon dizgileri, varsayılan parametreler. Eskimiş
olan her şey paketlenmiş tarafta ve builds/special/'daki iki Toledo dosyasında — ve
orada da nedeni tek kelimeyle bayt. HTML'in terk
edilmiş sunum nitelikleri CSS karşılıklarından kısa ve tarayıcılar onları desteklemeyi
bırakamıyor: spesifikasyon bugün de nasıl işleneceklerini tanımlıyor, çünkü webin büyük bölümü
onlara bağlı. Eskimiş olmak çalışmamak demek değil; doğrulayıcının şikâyet etmesi demek.
L1–L3 merdiveninde eskimiş öge ya da
nitelik kalmadı. Düz sürümlerin hiçbirinde zaten yoktu; tahta çizenler işaretlemelerini
statik yazıyor ve ölçüleri CSS'e bırakıyor. Önceki nesilde bu yükün tamamı tek bir dosyada,
L3_packed'te toplanmıştı; yeni paket default'un modern
işaretlemesini olduğu gibi yazdırıyor ve geriye yalnızca paketlemenin kendi araçları kalıyor:
document.write, with, eval. Eskimiş öge ve nitelikler
artık yalnızca builds/special/'da. Eskimiş yöntemler bir tercih değil; yalnızca
yer kalmadığında başvurulan çareler — yer açılır açılmaz önce düz sürüm, şimdi de paket
onların hepsini bıraktı.
Eskimiş olanlar
Ne
Nerede
Durumu
Modern karşılığı
<center>
DOM_911'ün statik öneki ve ürettiği hücreler; iki Toledo dosyası
L3_packed'te bütün işaretlemeyi yazdırmak; Toledos-ES6-optimized
Kullanımdan kaldırılmış
Statik işaretleme + innerHTML
with
yalnızca paketlenmiş dosyalarda: RegPack'in açıcısı
Katı kipte (strict mode) yasak
yok
Önceki neslin paketinde tablonun ilk beş satırının beşi de L3_packed'teydi —
<center>, <body bgcolor=#ccc>,
<table cellspacing=0>, <th width=54>,
p.bgColor='#c91' — ve bunların kaynağa yazılacak en kısa modern karşılıklarının
bedeli, paketlemeden önce, 18 bayttı: <center> +10,
bgcolor +2, cellspacing +3, width +2,
.bgColor +1. Pakete yansıması doğrusal değildi: değişiklik paketleyicinin gördüğü
tekrar dizilerini bozuyordu; üstelik kaynağa giren her yeni karakter
token havuzundan bir yuva eksiltiyordu. O gün düz L3.html'in
ölçülen en ucuz paketlenmiş hâli 2.808 bayttı2.373 paketlenmiş script +
418 statik işaretleme + 17 etiket; kendi kaynağından üretilen yayımlanmış paket ise 2.635
bayttı.
Yeni pakette bu bedel sıfır, çünkü modern işaretleme zaten içeride. Geriye kalan tek
esnetme DOCTYPE: <!DOCTYPE html>'in 15 baytı paketin dışında,
<script>'ten önce durmak zorunda — document.write ile yazdırılan
bir DOCTYPE belge modunu değiştirmiyor — ve dosyaya doğrudan ekleniyor: standart modda açılan bir
paket 2.452 bayt tutardı. Standart modda gereksizleşecek </p>'yi çıkarmak ise
kazandırmıyor: RegPack'in en ucuz ayarında bile paket 4 bayt uzuyor (2.456).
document.write, with ve eval
document.write() üçü arasında en tartışmalısı. MDN onu açıkça “kullanımdan
kaldırılmış” olarak işaretliyor; spesifikasyonun kendisi de bir uyarı kutusu taşıyor: metot,
ayrıştırıcı çalışırken belgenin durumunu değiştirebiliyor, kaynağa karşılık gelmeyen bir DOM
üretebiliyor ve sayfa yüklendikten sonra çağrılırsa belgeyi siliyor. Chrome'un 2016'dan beri
uyguladığı müdahale ise dar kapsamlı: yalnızca yavaş bağlantıdaki kullanıcıda, üst düzey
belgede, document.write() ile enjekte edilmiş ayrıştırıcıyı bloklayan
<script> ögelerini çalıştırmıyor. FideLite.html script
değil işaretleme yazdırıyor ve bunu ayrıştırma sırasında yapıyor, yani müdahalenin dışında
kalıyor. Yine de çağrı ayrıştırıcıyı bloklamaya devam ediyor. Çağrının kendisi de bir golf
hilesi: document.write`…` etiketli bir şablon. Metot tek elemanlı bir dizi alıyor;
dizi dizgiye çevrilince içeriğin kendisini veriyor. Parantez ve tırnak yerine yalnızca iki ters
tırnak gerekiyor. Bugünkü L3.html ise document.write'ı hiç çağırmıyor:
işaretlemesinin iskeleti statik; tahta, terfi düğmeleri, iki saat ve durum satırı
innerHTML ile yazılıyor.
with paketlenmiş dosyada var, ama bizim yazdığımız kod
değil: RegPack'in açma döngüsü with(_.split(…)) kalıbını kullanıyor.
Pratik sonucu şu: with katı kipte (strict mode) sözdizimi hatası olduğu için
paketlenmiş dosyalar ES modülü olarak yüklenemez ve başlarına
'use strict' konamaz. Düz sürümlerde böyle bir kısıt yok.
eval eskimiş değil, ama açma döngüsünün son adımı ve bunun bir uyumluluk bedeli
var: unsafe-eval içermeyen bir içerik güvenlik politikası (Content Security Policy)
altında paketlenmiş sürümler çalışmaz. Bu sayfadaki başlatıcı bunu varsaymak yerine sınıyor —
iframe aynı kaynaktan yüklendiği için aynı politikayı alıyor, dolayısıyla sayfanın kendi
testinin cevabı iframe için de geçerli.
Quirks modu
L1–L3 merdiveninde 22 HTML dosyası var ve iki
gruba ayrılıyorlar. Üçü — L1.html, L2.html ve L3.html —
<!DOCTYPE html> ile başlıyor, standart modda açılıyor ve W3C Nu Html
Checker'dan tek bir hata ya da uyarı almadan geçiyor: taşları ve gidilecek kareleri boyayan,
modern DOM'lu sürümler bunlar. Kalan on dokuzunda DOCTYPE yok, dolayısıyla hepsi quirks modunda
açılıyor. Bu bir gözden kaçırma değil, bir bayt tercihi; doğrulayıcı onlara DOCTYPE,
<title> ve kapanmamış ya da yerinde olmayan ögeler için hata veriyor,
L3'ün paketlenmiş hâline de paket dizgisindeki kontrol karakterleri için
yüzlerce hata daha ekleniyor. Merdivende eskimiş öge ya da nitelik yok; önceki nesilde
yalnızca L3_packed'te, o da çalışırken ürettiği işaretlemede vardı. Üç
motor dosyası — engine.js, engine_4x.js ve
engine_string.js — ile dört terminal sürümü (.cjs) için ne DOCTYPE'ın ne de render modunun bir karşılığı var.
builds/special/ klasöründeki dosyalar bu sayımın dışında: çoğu kendi başına bir
proje, GitHub'daki altı
ayrı deponun dosyaları; ayrıntıları her deponun README'sinde. Paketlenmiş hâlin, yani
L3_packed'in ayrıntıları ise bu sayfada, paketleme
bölümünde.
Burada yaygın bir yanılgıyı düzeltmek gerekiyor: eskimiş sunum nitelikleri quirks
moduna ait değil.bgcolor, width ve
cellspacing, spesifikasyonun sunum ipucu olarak tanımladığı niteliklerdir ve
standart modda birebir aynı çalışırlar. <!DOCTYPE html> eklemek
yukarıdaki tablonun hiçbir satırını düzeltmez; ikisi bağımsız konular.
Sık anılan iki klasik quirks davranışından ilki kutu modeli. Eski IE modelinin
quirks'te hâlâ yürürlükte olduğu yaygın bir inanış; oysa modern tarayıcılarda tablo hücresinin
genişliği iki modda da içerik kutusuna göre hesaplanıyor. Ölçtüm:
<th width=54> ve 1px varsayılan dolguyla hücre 56 piksel genişliğinde
çıkıyor — 54 içerik artı iki yandan birer piksel, tam olarak standart modun vereceği sayı.
Yükseklikte ise quirks gerçekten farklı: hücreye verilen height dolguyu da
kapsıyor. light sürümlerinde bunu yaşadık. height:22
içeriğe yalnızca 20 piksel bıraktığı için, taşların satır yüksekliği 20'yi aşan bir yazı tipinde
(Windows'un sembol yazı tipi gibi) taşlı yataylar büyüyor ve tahta her hamlede azıcık uzayıp
kısalıyordu. Çözüm height:24: dolguyla 24, içerik 22 — standart modun eski ölçüsü,
ek bayt olmadan. İkinci klasik davranış, tabloların yazı tipini gövdeden miras almaması; bunun da
burada etkisi yok: standart dosyalar hücrelere kendi kuralını veriyor,
light sürümleri ise gövdeye hiç yazı tipi vermediği için miras alınacak
farklı bir değer yok.
Quirks moduna gerçekten dayanan dosyalar, üç light sürümü. İki
quirks davranışına dayanıyorlar: px'siz yazılan uzunluklar
(width:22;height:24) ve yukarıdaki yükseklik kuralı; DOCTYPE eklenirse ikisi de
bozulur. Ayrıştırmada da bir fark var: standart modda <table> açık kalan bir
<p>'yi kendiliğinden kapatır, quirks'te kapatmaz. Bu fark yüzünden iki
dosyada </p> elle yazılıyor. L3_light'ta saat
paragrafı <p id=h></p>: kapatılmazsa tahta onun içine düşüyor ve saat
her yazıldığında siliniyor. L3_packed'te terfi paragrafı
<p id=p hidden></p>: kapatılmazsa tahta gizli bir paragrafın içinde
kalıp hiç görünmüyor, terfi düğmeleri yazılırken de siliniyor. Aynı işaretleme
L3.html'de, standart modda, bu dört karaktere ihtiyaç duymuyor. Bunun dışında
L3_packed'te iki mod arasında görünür bir fark yok: CSS'indeki uzunlukların hepsi
birimli, hücrelerin yazı tipi kendi kuralında. Öbür quirks dosyalarında da görünür bir fark
yok: numerical ve prompt aileleri script dışında
hiç işaretleme taşımıyor, input ailesinin ise CSS'i yok.
Sürüm sürüm: mod, doğrulayıcı ve eskimiş yapılar
Yukarıdaki sayımı builds/special/ ile birlikte bütün dosyalara genişletince
ortaya şu tablo çıkıyor. Hata ve uyarı sütunları, W3C Nu Html Checker 26.9.27'nin CSS denetimi
açıkken verdiği sayıları gösteriyor.
Doğrulayıcının saydığı hatalar biçimsel: 36 dosyanın hiçbiri Chromium'da hata fırlatmıyor
ya da konsola uyarı düşürmüyor. Belge modunu DOCTYPE belirliyor;
document.compatMode'un okunabildiği 31 dosyada değer buna birebir uyuyor; kalan
beşinde diyalog her kapatılışında yeniden açıldığı için sayfa kilitli kalıyor ve değer
okunamıyor. Paketlerdeki yüzlerce hata, RegPack'in token olarak kullandığı kontrol
karakterlerinden geliyor; HTML bu kod noktalarını yasaklıyor, tarayıcı yine de okuyor. Eskimiş
öge ve nitelikler yalnızca builds/special/'da, çoğu da doğrulayıcının göremediği
yerde: paketin ya da document.write'ın çalışırken ürettiği işaretlemede. Toledo'da
doğrulayıcı ikinci <body>'den sonra durduğu için bgcolor'a hiç
ulaşmıyor. Çalışma ortamını gerçekten ilgilendiren iki şey var: with katı kipte
sözdizimi hatası; eval ile dizgiyle setTimeout ise
unsafe-eval izni vermeyen bir içerik güvenlik politikası altında
engelleniyor.
Eskimiş sanılanlar
Denetimlerde sık sık işaretlenen ama aslında güncel olan kullanımlar da var; bunları listeye
almamın nedeni, bir daha sorulmamaları.
innerHTML, setInterval, charCodeAt. Üçü
de eskimiş değil. codePointAtcharCodeAt'in yerine geçen değil,
ona eklenen bir metot; vekil çiftleriyle (surrogate pair) uğraşmayan bir kodda charCodeAt
doğru seçim.
E.key. Klavye olaylarında eskimiş olanlar keyCode ve
which; ikisi de hiçbir sürücüde geçmiyor. Bu bir tesadüf değil, bilerek ödenmiş
bir bedel: onkeyup=E=>E.which!=13 yazmak E.key!='Enter' yazmaktan
dosya başına 3 bayt kısa. Kanca yalnızca input ailesinde, her seviyede iki dosyada
olduğu için masada seviye başına 6 bayt bırakıldı — prompt ile
numerical girdiyi bloklayan bir pencereden aldıkları için olay kancasına
hiç ihtiyaç duymuyor.
Adla erişim (named access). Element id'sinin global değişkene dönüşmesi
spesifikasyonda tanımlı, ama açıkça “uyumluluk için” kaydıyla. Kaldırılmayacak da, çünkü
kaldırmak sayısız sayfayı bozar. Burada bilinçli bir kaldıraç: default'ta
tahta u, durum satırı x, terfi paneli p, saatler
g ile h, beraberlik radyo düğmesi A; input
ailesinde u ile x.
innerHTML ve innerText. İkisi de 1997'de IE4'ün tescilli
uzantısı olarak doğdu ve sonradan standarda alındı — innerText on sekiz yıl
standart dışı kaldıktan sonra, 2016'da. Bugün ikisi de güncel; hiçbiri eskimiş değil. Baştan
beri standart olan textContent ise kullanılmayan tek seçenek, çünkü 2 bayt daha
pahalı. Denetimler innerHTML'i refleksle işaretler ve haklıdırlar:
innerHTML işaretlemeyi ayrıştırır, yani güvenilmeyen bir metin oraya ulaşırsa XSS
açığı doğar. Burada ulaşmıyor. Kullanıcının yazdığını ekrana geri yazdıran tek sürücü
input_blindfold ve o zaten ayrıştırma yapmayan innerText'i
kullanıyor. default yalnızca tıklama alıyor;
input'un render şablonuna da girdi metni girmiyor.
DOM0 olay ataması.onclick= nitelikleri ve
onkeyup= ataması addEventListener'ın öncülü, ama eskimiş
değil. Tek kısıtı tek dinleyiciye izin vermesi — ikinci bir atama birincinin üzerine
yazar.
Atılmayan baytlar
Düz sürümlerin işaretlemesinde ilk bakışta fazlalık gibi duran üç yer var ve üçü de
ölçüldü. İkisi bayt kazandırıyor ama standart moddaki sürümlerin doğrulayıcıdan uyarısız geçme
özelliğini bozuyor. Üçüncüsü hiç kazandırmıyor, çünkü dosya çalışmaz hâle geliyor.
</script>: 9 bayt, ama script hiç çalışmıyor. Script dosyanın son
öğesi olduğu için kapanış etiketi gereksiz görünüyor: tarayıcı dosya sonunda açık kalan
öğeleri zaten kapatıyor. Kapatıyor da, ama çalıştırmıyor. HTML ayrıştırıcısı bir script'in
içindeyken dosya sonuna gelirse o öğeyi “zaten başlamış” (already started) diye
işaretliyor ve böyle işaretlenen bir script hiç yürütülmüyor. Ölçüldü:
L3.html'nin ve paketin son 9 baytı silindiğinde Chromium'da sayfa boş kalıyor;
tahtanın tek hücresi bile çizilmiyor, konsolda hata da yok. Önceki projenin notlarında bu
kısaltma “doğrulandı, çalışıyor” diye geçiyordu; yanlıştı. Paketin 17 baytlık etiket çiftinin
9 baytı bu yüzden pazarlık konusu değil.
<meta charset=utf8>: 1 bayt, doğrulayıcı hata veriyor. Encoding
Standard utf8'i UTF-8'in geçerli bir etiketi sayıyor; Chromium sayfayı UTF-8
olarak okuyor ve taşlar doğru çiziliyor. Ama HTML, meta'nın charset
niteliğinde yalnızca utf-8'e izin veriyor ve Nu Html Checker bunu hata
sayıyor.
</label>: 8 bayt, doğrulayıcı üç hata ve bir uyarı veriyor.
Silinince ⚐ düğmesi ½'nin etiketinin içine düşüyor. Terk bozulmuyor: tıklanan hedef etiketin
içindeki etkileşimli bir öğeyse HTML tıklamanın radyo düğmesine aktarılmasını iptal ediyor ve
⚐ yine terk ettiriyor. Ama iki bağımsız denetim tek öğe oluyor ve aradaki iki EM SPACE de
etikete ait hâle geliyor: aralığa tıklamak beraberlik teklifini açıyor. Ölçüldü; bugünkü
dosyada aynı noktaya tıklamak hiçbir şey yapmıyor. Doğrulayıcının itirazı da aynı yerden: bir
label birden fazla etiketlenebilir öğe taşıyamaz.
Tuzaklar
Tuzaklar ve dikkat edilmesi gerekenler
Golf edilmiş kodun her satırı bir taviz; tavizlerin çoğu da görünmüyor. Aşağıdakiler
motorda ya da sürücülerde değişiklik yapacak birinin bilmesi gereken yerler: dokunulduğunda
sessizce bozulan ya da ilk bakışta yanlış okunan noktalar. Hepsi bugünkü 0x88 motoru ve
sürücüleri için geçerli; gösterilen sonuçların hepsi motorun kendisi üzerinde ölçüldü.
Motorun kırılgan yerleri
S'deki b[i]<1!b[i]'ye kısaltılmamalı. Işın
tarayıcı şu biçimde: S=_=>(i+=s)==f||b[i]<1&&S(). !b[i]
bir bayt kısa ve önceki nesilde bu kısaltma alınmıştı; orada iki küçük açığı vardı. 0x88'de ise
hamle kuralının kendisini bozuyor, çünkü tahtanın kenarını tutan şey tam bu fark: delik
undefined, undefined<1 yanlış ve yürüyüş orada duruyor;
!undefined ise doğru ve yürüyüş deliğin içinden bir sonraki yataya geçiyor.
Ölçüldü: kısaltılmış motorda h1'deki kale, a2'den h8'e uzanan kırk dokuz kareye gidebiliyor;
normalde dokuz kareye.
Delikler delik kalmalı. Tahta 120 uzunluğunda seyrek bir dizi; altmış dört kare bir
kod taşıyor, aradaki 56 yuva hiç yazılmamış. Buna iki şey bağlı. Birincisi yukarıdaki duvar:
tahtayı Array(120).fill(0) ile kuran ya da JSON'dan geri okuyan bir
sürücüde delikler 0 ya da null oluyor, ikisi de <1'den
geçiyor ve h1'deki kale yine a2'ye, hatta deliklerin kendisine gidebiliyor. İkincisi daha
sinsi: tahtayı [...b] ya da Array.from(b) ile kopyalamak delikleri
gerçek undefined elemanlara çeviriyor. Işın yine duruyor, çünkü değer aynı; ama
map, some ve every artık o yuvalara da uğruyor.
V'nin bitboard'daki bit konumu her ziyarette bir ilerlediği için yataylar kayıyor ve
kilitli yarı çöküyor: denenen üç kilitli pozisyonun üçünde de V(0)&V(8) 1'den
0'a düştü — ölü pozisyon artık tanınmıyor. U'nun kullandığı b.slice()
delikleri koruyor; güvenli kopya o. Sürücü yazmak için altı çağrı da tahtanın
nasıl kurulacağını anlatıyor.
L ile A arasına başka bir rok sorusu girmemeli. Rok
kalesinin karesi bir parametreyle değil, global r ile taşınıyor: G'nin
rok dalı onu yazıyor (r=f+=s-!~s), U'nun özyinelemesi okuyor
(U(r,Q)). Arada başka bir rok hamlesi sorulursa r o hamleye ait olur.
Ölçüm: iki rok da mümkünken önce e1–c1, sonra e1–g1 sorulup ardından e1–c1 oynanınca motor
şahı c1'e götürüyor ama h1'deki kaleyi d1'e taşıyor; a1'deki kale yerinde kalıyor. Aynı kanal
terfi için de var: U, L'nin denemesinde global X'e
hamlenin terfi olup olmadığını yazıyor ve default ile H onu
hemen ardından okuyor (X?E(X):…, X?[3]:[3,5,6,4]). Kural ikisi için
de aynı: oynanacak hamle için yapılan L çağrısı, en son L çağrısı
olmalı. Önce bütün hamleleri doğrulayıp sonra sırayla oynayan bir sürücü — örneğin bir PGN
okuyucusu — tam bu tuzağa düşer.
U tahtayı kopyalayarak değiştirmeli. İlk işi
(b=b.slice())[f]=… oluyor. Hızlandırmak için kopyayı atıp diziyi yerinde
değiştiren bir U, L'nin geri alma işini boşa çıkarır: L
yedek olarak yalnızca diziye bir referans tutuyor (q=b) ve dönerken b=q
yazıyor; referans aynı diziyi gösterdiği için deneme hamlesi tahtada kalıyor. Ölçüldü: yerinde
değiştiren bir U'yla a1–a2'nin geçerliliğini sormak kaleyi a2'ye götürüyor.
H'nin [b,e,n,t,c,k]=s geri yüklemesi de aynı varsayıma dayanıyor.
Çift adım testi birinci ve son yatayı da geçiriyor.G'nin piyon dalı
başlangıç yatayını (i%96<24) ile tanıyor; bu, ikinci ve yedinci yatayın yanında
birinci ve sekizinciyi de geçiriyor. Geçerli bir oyunda piyon o iki yatayda hiç durmadığı için
fark görünmüyor. Ama birinci ya da son yatayda piyon bulunan bir pozisyon kurulursa, beyazın 1.
yataydaki ve siyahın 8. yataydaki piyonu, önü boşsa iki kare ilerleyebiliyor — ölçüldü: a1–a3
ve a8–a6 geçerli sayılıyor — ve bu hamle bir geçerken alma karesi de açabiliyor. Yanlış yöndeki
çift adım tahtanın dışına düştüğü için sorun yok. Böyle bir pozisyon zaten motorun geçerli
pozisyon varsayımının dışında: birinci ve sekizinci yatayda piyon olmamasını sağlamak pozisyonu
kuranın sorumluluğunda. L1'in defteri çift adım hakkını yatay yerine
kareye sorduğu için bu açığı taşımıyor; onun FEN'deki karşılığı
piyon işaretleri.
V'nin kilitli yarısı yalnızca oyunda karşılaşılabilecek pozisyonlar için
kuruldu. Motorun geri kalanı — hamle üretimi, mat ve pat, sayaçlar, V'nin
materyal yarısı — taş sayısından bağımsız. Her tarafta tek şah olduğu, birinci ve son yatayda
piyon bulunmadığı, rok haklarıyla geçerken alma karesi pozisyonla tutarlı olduğu ve sırası
gelmeyen tarafa şah çekilmemiş olduğu sürece 64 kareye kadar her dolulukta doğru çalışıyor.
Kilitli yarı bundan ayrılıyor. Taramanın ilerleyiş, erişim ve doyma geometrisi de materyal
saymıyor, ama doğruluğu oyunda çıkabilecek yapılar için kuruldu. FEN'le kurulmuş, hiçbir oyunda
ortaya çıkamayacak yapay pozisyonlarda — örneğin 32'den fazla taş ya da bir tarafta sekizden
fazla piyon — doğru çalıştığı garanti edilemez. Bu, bilinen bir yanlış hüküm olduğu anlamına
gelmiyor; yalnızca böyle pozisyonların kapsam dışında kaldığını söylüyor.
H, V'nin sınırını devralıyor. 6.9 aramasının kendisi taş
sayısından bağımsız ve temkinli: bütçe ya da derinlik bitince beraberlik demiyor, her dalın
kapandığını görmeden “imkânsız” demiyor. Ama her düğümde önce kapıya soruyor —
$[k]<=n||V(I)||O(…) — ve kapı mat imkânsız derse o dal orada kapanıyor. Bu yüzden
oyunda çıkamayacak yapay pozisyonlarda H'nin hükmü V'ninkinden daha
güvenilir olamaz.
H yalnızca F'nin içinden çağrılmalı.F
aramadan önce üç şeyi kuruyor: $=[I=s] hem arama tablosunu boşaltıyor hem de
sorulan tarafı yazıyor, N=2e3 de bütçeyi dolduruyor. H'yi doğrudan
çağıran bir sürücü, bir önceki aramanın tablosunu — başka bir tarafın sorusuna verilmiş
cevapları — ve bütçeden kalanı devralır. Kalan sıfırsa N-- ilk denemede sıfır
döndürüyor, hiçbir dal açılmıyor ve kapıda kapanmayan her pozisyonda cevap “mat mümkün”
oluyor: beraberlik olması gereken yerde galibiyet. Bütçesini tüketen bir aramadan sonra ise
N −1'de kalıyor; N-- bundan sonra hep negatif bir sayı döndürüyor,
negatif sayı doğru sayıldığı için bütçe hiç bitmiyor ve aramayı yalnızca derinlik sınırlıyor.
Ölçüldü: sayacı 146'daki iki atlı pozisyonda F 2.001 denemede pes ediyor. Hemen
ardından doğrudan çağrılan H(9) tabloyu da devralırsa, kök pozisyon tabloda kayıtlı
olduğu için ilk kapıda, tek düğümde ve hiçbir şey aramadan “beraberlik” diyor. Yalnızca tablo
boşaltılırsa, −1'de kalan bütçe yüzünden sınırsız çalışıyor ve 3.654 düğüm gezip beraberliği
ispatlıyor.
V altı global sızdırıyor. Adlarının çoğu parametre listesinde doğuyor ve
dışarı sızmıyor; ama W, B, _, f,
S ve a global olarak yazılıyor ve V her hamlede
Z'nin, aramada da her düğümde H'nin içinden çağrılıyor. Bugünkü
sürücülerin hiçbiri bu altı harfi kendi globali olarak kullanmıyor — acorn ile tarandı —, ama
kullanan yeni bir sürücü olursa değeri her hamlede ezilir. Aynı sızıntının ikinci bir yüzü
var: _ ile f şahların bitboard'u ve V onları tahtayı
tararken kuruyor. Tahtada şah yoksa kurulmuyorlar; ilk çağrıda V_ is not defined hatası fırlatıyor — ölçüldü, yalnızca piyonların bulunduğu şahsız bir
tahtada —, sonraki çağrılarda da bir önceki pozisyonun şahlarıyla çalışıyor. Oyunda tahtada hep
iki şah var; ama b'ye doğrudan yazarak pozisyon kuran bir sürücü ya da test
düzeneği bununla karşılaşabilir.
Tekrar tablosu G'nin üstünde duruyor. Sayımlar ayrı bir nesnede değil,
geometri fonksiyonunun kendi özelliklerinde (G[k]);
L1'in rok defteri de orada. G'yi bir sarmalayıcıyla sarmak —
çağrıları saymak ya da ölçmek için — ya da yeniden tanımlamak tabloyu sessizce siler: yeni
fonksiyonun özellikleri boş. G'yi sarmalayan, eski fonksiyonun sayılabilir anahtarlarını yenisine
kopyalamalı.
Motorun iki kenarı birlikte taşınmalı. Tıklamalı ve metin sürücüleri motoru
iki ayrı yerden kesiyor: tıklamalıda L çıkış karesini
sınamıyor ve O rakip taşları kendi süzgeciyle (p&8^t||) eliyor; metin
sürücülerinde L kendi taşı sınamasını taşıyor ve O'nun süzgeci düşüyor.
Bir sürücüden L'yi, ötekinden O'yu alan bir karışım rakibin hamlelerini
de sırası gelenin hamlesi sayar. Ölçüm: süzgeçsiz O ile tıklamalı L
yan yana konunca pat pozisyonunda Z() 3 yerine 0 döndürüyor — oyun sürüyor
sanılıyor. Terfide de durum aynı: tıklamalı U terfi taşını y'den
okuyor, H de her denemeden önce y=p yazıyor; metin sürücülerinin
U'su ise terfi taşını parametreden okuyor.
b[i]
b[i]<1 (bugün)
!b[i] (kısaltma)
0 — boş kare
doğru → devam
doğru → devam
5 gibi bir taş kodu
yanlış → dur
yanlış → dur
undefined — delik
yanlış → dur
doğru → devam
null — JSON'dan dönen delik
doğru → devam
doğru → devam
İlk bakışta yanlış okunan yerler
Argümansız D() teklif bitlerine dokunmaz. Teklif adımı
(o^=2-s/8)>2; s verilmezse 2-undefined/8NaN oluyor, o^=NaN ise o^0, yani o. Yani
argümansız çağrı “bitleri bozmadan yalnızca değerlendir” anlamına geliyor: bekleyen iki
teklifi, talepleri (üçlü tekrar, 50 hamle) ve Z'yi soruyor, hiçbir şeyi değiştirmiyor. Bugünkü sürücülerin hiçbiri
bu biçimi kullanmıyor; default hamleden sonraki talep için kendi
J'sini taşıyor.
A'nın tek ifadesinde sol taraf önce çalışıyor.R=G[M(i,f,p)]=-~G[k] iki ayrı k okuyor gibi görünüyor. Ama atamada
önce hedef değerlendiriliyor: M hamleyi oynuyor ve son işi olarak yeni anahtarı
kurup döndürüyor; sağ taraftaki G[k] o yüzden zaten yeni pozisyonu okuyor.
-~x ise x+1 ile aynı uzunlukta, ama undefined üzerinde
1 verdiği için pozisyon ilk kez görüldüğünde tablo girişini de aynı ifade
oluşturuyor.
Yarım hamle sayacı (50 hamle kuralının sayacı) tek ifadede sıfırlanıyor.n=!b[x]*b[i]&6&&n+1 ne bir karşılaştırma ne de bir dal içeriyor. Alma
varsa !b[x] sıfır ve çarpım da sıfır; piyon hamlesinde b[i] 1 ya da 9
ve ikisinde de &6'nın bitleri yok. Öncelik sırası *,
&, &&: ikisinden biri sıfır verirse n
sıfırlanıyor, yoksa bir artıyor.
O(_=>0) “geçerli hamle yok” demek.O(h), sırası gelen
tarafın her geçerli hamlesi için h'yi soruyor ve hepsi doğruysa doğru döndürüyor.
Her zaman yanlış döndüren bir h ile tek bir geçerli hamle bile sonucu yanlışa
çeviriyor; doğru çıkması için hiç geçerli hamle olmaması gerekiyor. Z'nin mat ve
pat dalı tam olarak bu.
U çoğu hamlede aynı kareyi iki kez sıfırlıyor.b[i]=b[e-f|P-1?i:f^16]=0 geçerken alma için yazılmış gibi duruyor ve öyle: piyon
e'ye varınca ikinci indeks alınan piyonun karesi, f^16. Ama öbür bütün
hamlelerde iki indeks de i ve atama çıkış karesini iki kez boşaltıyor. Ayrı bir
geçerken alma satırı yazılmamasının bedeli bu zararsız tekrar.
Terkte taraf parametresi — sürücülerin aldığı kısayol
Motorda çağrı F(s,r) biçiminde: s bayrağı düşen ya da terk eden
taraf, r ise sıfırsa bayrak düşmesi, birse terk. İki ayrı parametre var ve
s, t'den bağımsız.
Yayımlanan sürücülerin hepsi bu biçimi kısaltıyor. default,
input ve nonbrowser aileleri
s=g=>($=[I=t],N=2e3,H(9))?g+'M':'WB'[t/8]+g yazıyor
(nonbrowser_blindfold aynı yardımcıya D diyor) — yani ortada bir taraf
parametresi yok; onun yerine sırası gelen taraf kullanılıyor.
prompt ailesi aynı ifadeyi satır içinde kuruyor,
numerical da sayı kodlarını $=[I=t] ile üretiyor. Motorun
iki parametreli biçimini olduğu gibi taşıyan sürücü artık yok.
Bu kısayol aynı ekranda sırayla oynanan oyunda doğru. Tek ekranda oynanan bir oyunda
hareket edebilen tek kişi sırası gelendir: terk düğmesine basan da, girdi kutusuna
r yazan da odur. Bayrak düşmesinde ise kısayol zaten her durumda doğru — saat
yalnızca kendi sıranda işler, dolayısıyla bayrağı düşen taraf her zaman sırası gelen
taraftır.
Çevrimiçi bir uygulamada doğru değil. Orada iki oyuncu aynı anda masadadır ve
sırası gelmeyen taraf da her an terk edebilir. O durumda t yanlış tarafı
gösterir ve hata sessiz kalmaz, tam ters bir sonuç üretir. Ölçüm:
Durum
Motor F(s,1)
Sürücü I=t
Sıra beyazda, beyaz terk ediyor
RM
RM ✓
Sıra siyahta, siyah terk ediyor
WR
WR ✓
Sıra siyahta, beyaz terk ediyor
RM
WR✗
Sıra beyazda, siyah terk ediyor
WR
RM✗
Pozisyon: beyaz Şe1 + Vd1, siyah çıplak Şe8. Beyaz mat edebilir, siyah edemez. Son iki
satırda sürücü yalnızca yanılmıyor, hükmü tersine çeviriyor: beraberlik olması gereken
yerde galibiyet, galibiyet olması gereken yerde beraberlik veriyor.
Motorun kendisi bu hatayı yapmıyor. F(s,r) soruyu I=s ile kuruyor ve
H baştan sona I üzerinden çalışıyor; yaprak testi t^I|!T()
sırayı da hesaba katıyor. Aynı pozisyonu sıra beyazdayken ve siyahtayken ayrı ayrı ölçtüm —
motorun verdiği hüküm ikisinde de aynı.
Motoru başka bir yere taşıyacak olan için kural şu: terk kanalını sırası gelen tarafa
bağlamıyorsan, F'yi kısaltma; motordaki iki parametreli biçimi olduğu gibi
kullan.
Durum değişkenleri sürüme göre değişir
Durum tablosu motorun genel hâlini anlatıyor. Beş şey sürücüden
sürücüye değişiyor ve bir sürücüden ötekine kod taşınırken ilk bozulan yerler bunlar. Tarafın
kodu her yerde aynı (t beyaz için 8, siyah için 0; saatlerin anahtarı da bu
değer); ama değerlerin tutulduğu yer, birim ve bit düzeni aynı değil. Oyuncu katmanı olmayan
iki sürüm talep eşiklerinde de ayrılıyor: L2 oyunu yalnızca
kendiliğinden devreye giren beşli tekrarda ve 75 hamle kuralında bitiriyor,
L2_aybars_2500 ise üçlü tekrarda ve 50 hamle kuralında da bitiriyor;
gerekçesi Göstergeler bölümünde.
Ne
Motorda
Sürücülerde
Saatler
Yok — motor saat tutmuyor
default, light, input ailesi: ekrandaki öge u, saniye, u[0]=u[8]=600. prompt ailesi: G[0], G[8], saniye. nonbrowser ailesi: satır okuyan fonksiyon u, saniye. numerical: düz bir nesne, u={0:9E5,8:9E5}, milisaniye ve artış yok
Tekrar tablosu
G[k]
default, light, input ailesi: u[k], saatlerle aynı nesnede. prompt, numerical, nonbrowser: motordaki gibi G[k]
o — teklif bitleri
D: o^=2-s/8 — beyaz 1, siyah 2, ikisi birden 3
prompt ailesi motordaki gibi. default, light, input, nonbrowser, numerical: o^=t||1 ya da o^=g||1 — beyaz 8, siyah 1, ikisi birden 9; hamleden sonra tıklamalılarda o&=t?1:8, metin sürücülerinde hamlenin içinde o=0
numerical: sayı kodu. light: yalnızca bitti bayrağı; sonuç tahtadan ve saatten okunuyor. Kalanlar: iki harfli dizgi
y
Yalnızca imzada; motor okumuyor
Tıklamalı sürümler: terfi taşı — panel ve H yazıyor, U okuyor. numerical ve körleme sürümler: son hamlenin metni. Kalanlarda yalnızca imza
Teklif bitlerinin iki düzeni karışmamalı. Motorun D'si iki tarafın birden
teklif ettiğini >2 ile anlıyor; 8/1 düzenini kullanan bir sürücüye taşınırsa
tek bir beyaz teklif (8) anlaşmalı beraberlik sayılır. Tersi de geçerli: 1/2 düzeninde
>8 koşulu hiçbir zaman sağlanmıyor ve beraberlik teklifi hiç kabul
edilmiyor.
Bayrak testi de birime bağlı. Sürücüler bayrağı sırası gelenin saatinde arıyor, ama
farklı eşiklerle: numericalu[t]<1,
prompt ailesi G[t]<=0 kullanıyor; geri kalanlar
ise >0'ın tersini. Bu bir üslup meselesi değil. numerical
saatini tam sayı milisaniye olarak tutuyor, yani değer ya sıfır ya da altında, ya da en az bir;
arası yok, bu yüzden <1 hem bir bayt kısa hem güvenli. Öbürlerinin saati kesirli
saniye; orada <1 yazılsaydı 0,9 saniyesi kalan oyuncu, bayrağı
gerçekten düşmeden süreden kaybederdi. Yani <1 yalnızca tam sayılı bir saatte
doğru; birimi bir gün değiştirirsen bayrak sessizce erken düşmeye başlar.
Terfi paneli üçüncü bir giriş yolu
default'ta tahtanın iki giriş yolu olduğu sanılır: kareye tıklamak ve
düğmeye basmak. Üçüncü bir yol daha var ve gözden kaçması kolay. Terfi panelinin dört düğmesi
işaretlemede doğrudan hamle fonksiyonuna bağlı — <button onclick=E(y=3)> —
yani kare tıklamasını işleyen K'nin içinden geçmiyorlar. K'ye konan
hiçbir koruma onları durduramaz.
Bunun açtığı açık şu. Son yataya giden bir piyon hamlesi seçildiğinde panel açılıyor ve oyun
terfi kararını bekliyor. O aralıkta oyun başka bir yoldan bitebilir: bayrak düşer, terk
düğmesine basılır ya da beraberlik radyo düğmesi seçiliyken teklif kabul edilir. Sonra oyuncu
vezire tıklar. E korumasız olsaydı hamleyi tahtaya yazar ve z'ye
yeni hükmü yazardı — bitmiş oyunun sonucunun üstüne; durum satırındaki sonuç silinir,
oyun kaldığı yerden devam ederdi.
Koruma E=P=>z||…, yani 3 bayt. Terk ve beraberlik de E'den
geçtiği için aynı 3 bayt onları da tutuyor: oyun bittikten sonra düğmelere tekrar basmak sonucu
değiştirmiyor.
Panelin ikinci “hediyesi” daha da kötü. Panel açıkken başka bir taşa tıklanabilseydi seçim
oraya kayar ama panel açık kalırdı; ardından vezire basılınca E, i ile
Y'nin aynı kare olduğu bir hamle oynardı ve U'nun önce
b[f]=…, sonra b[i]=0 yazması yüzünden taş buharlaşırdı. Bunu
önleyen, K'nin başındaki p.hidden&&: panel açıkken kare
tıklaması hiç işlenmiyor.
L1 ve L2'nin tıklamalı sürümleri aynı açığı
başka bir yoldan, daha ucuza kapatıyor. p.hidden=0 yerine
p.hidden=z-- yazıyorlar: z sıfırken z-- sıfır
döndürüp z'yi -1 yapar, yani panel açılırken oyun aynı anda
“bitmiş” olarak işaretlenir ve K=u=>z||(…) kendini kilitler. Bu
bedava, çünkü orada saat de düğme de yok — z'yi hamle fonksiyonundan başka yazan
yok ve o fonksiyonun bir z|| koruması da yok; bu yüzden panelin düğmesi yine
çalışıyor. O yüzden orada bu tuzağın yalnızca ikinci yarısı görülebiliyordu.
L3'te aynı hile işlemezdi: E'nin kendi koruması paneli de
kilitler, saati süren z||F(j()) döngüsü de dururdu.
BOM ve görünmez boşluklar
ASCII dışı glif taşıyan dosyalar kodlamayı aynı yoldan bildirmiyor. Tahta çizen düz
sürümler — L1.html, L2.html, L3.html ve botlu ikisi —
<!DOCTYPE html> ile açılıp <meta charset=utf-8> taşıyor;
BOM'a ihtiyaçları yok, taşımıyorlar da. prompt ailesinin tam genişlikli
harfleri dosyada durmuyor, çalışırken fromCharCode(…+65248) ile üretiliyor;
dosyanın kendisi, input ve numerical ailelerininki
gibi, tamamen ASCII. Paketlenmiş FideLite.html ise glif taşıyor ama ne
DOCTYPE ne de meta taşıyor: kodlamayı bildiren tek şey dosyanın başındaki BOM; bu yüzden BOM
gerekli. BOM silinirse dosyanın doğru okunması tarayıcının tahminine kalır.
builds/special/ altındaki iki Toledo dosyası da aynı durumda.
light sürümleri taşları ɥ… varlıklarıyla yazıyor;
L1_light ile L2_light bu yüzden tamamen ASCII, L3_light'ta
ise yalnızca ½ ile ⚐ kalıyor ve dosya kodlamayı hiç bildirmiyor —
tarayıcının sezmesine dayanıyor; Chromium yerel bir dosyada UTF-8'i doğru seziyor.
İkinci tuzak daha ince. Render dizgisinde boş karenin karakteri normal boşluk değil,
kırılmaz boşluk: ' ♟♚♛♞♜♝''nin ilk karakteri. L1.html,
L2.html, L3.html ve paket karakterin kendisini taşıyor — kaçış
(\xA0) dört bayt, karakter iki. builds/special/'da
L1_1250elo da karakterin kendisini taşıyor; L2_aybars_2500,
L2_base ve L2_string_base hâlâ kaçışla yazıyor. Karakter
0x20'ye çevrilirse işaretlemede daraltılabilir bir boşluğa dönüşür, hücre boş
sayılır ve taşsız yataylar çöker. Ölçüldü: L3.html'de tahta 448 pikselden 232'ye
iniyor, başlangıç pozisyonunun dört boş yatayı 56 pikselden 2 piksele. Bir editörün “gereksiz
karakteri temizlemesi” bunu tek başına yapar ve artık düz sürümleri de koruyan bir kaçış
yok.
Aynı tuzak iki yerde daha var; ikisinde de aralığı CSS değil, görünmez bir boşluk karakteri
açıyor. Birincisi L3.html'de ve pakette ½ ile ⚐ arası: iki EM SPACE
(U+2003). CSS bu karakteri de daraltmıyor ve her biri yazı boyutu kadar geniş:
ikisi birlikte 44 piksel tutuyor, yerini aldıkları boşlukla margin-left:36px 41,5
piksel tutuyordu. Düz dosya bundan 12, paket 11 bayt kazanıyor. Yazının daha büyük olduğu üç
özel sürümde karışım dosyaya göre seçildi: Lichess-equivalent'te EM SPACE
ile bir çeyrek em boşluğu (U+2005, 45 piksel, birebir aynı),
Chesscom-equivalent'te EM SPACE ile bir altıda bir em boşluğu
(U+2006, 46 yerine 46,7 piksel), LUX'ta tek bir EM SPACE
(55,5 yerine 54 piksel). Kazanç 12, 12 ve 15 bayt.
İkincisi durum satırı. L1.html, L2.html, L3.html ve
L2_aybars_2500, L2_base,
L2_string_base, Lichess-equivalent,
Chesscom-equivalent'te koordinat, C!, M= ve
R= arasındaki her aralık bir EM SPACE ile bir çeyrek em boşluğu
(U+2005). Kaldırılan word-spacing:1em normal boşluğa bir em
ekliyordu; bu yazı tipinde normal boşluk çeyrek em olduğu için çift aynı genişliği veriyor —
L3.html'de 27,5 piksel. CSS'ten 17 bayt gidiyor, her aralık 5 bayt geri
getiriyor: üç aralıklı L2.html ile L3.html 2, tek aralıklı
L1.html ile iki eşdeğer 12 bayt kazanıyor; pakete 3 bayt yansıyor.
L2_aybars_2500'ün satırı bir boşlukla başlıyordu ve satır başındaki
boşluğu CSS zaten yutuyordu; EM SPACE yutulmadığı için o boşluk dizgiden çıkarıldı.
Bu karakterler normal boşluğa çevrilirse yan yanadakiler tek boşluğa iner. Ölçüldü: ⚐'nin
aralığı 44 pikselden, durum satırınınkiler 27,5 pikselden 5,5 piksele düşüyor.
İmza
İmzanın kendisi Motor sekmesinde anlatılıyor: başlıkta atanan adlar
sırayla c·un·e·y·t okunuyor. Burada işin bakım tarafı var: imzayı hangi dosyaların taşıdığı ve neye
dokunulmaması gerektiği.
b=[c=un=15,e=y=t=8,n=o=0]
// c un e y t → c·un·e·y·t
İmzayı taşıyanlar: engine.js, engine_4x.js,
engine_onlyMoveGenerator.js, L3'ün dokuz sürücüsü ve paketi,
L1 ile L2'nin harf tahtası dışındaki bütün
sürümleri, builds/special/'da da Lichess ve Chess.com eşdeğerlerinin düz sürümleri,
LUX ve L1 seviyesindeki iki bot
(L1_1250elo, ToledosOpponentbyMe). Taşımayanlar ise
harf tahtalı önceki nesil (prompt_string ailesi ve
engine_string.js; bunların bildirimleri imzayı değil
FEN alanlarının sırasını izliyor) ile builds/special/'daki
öbür dosyalar.
İki harf yalnızca imza için orada: un hiçbir yerde okunmuyor (3 bayt),
y'yi motor okumuyor (2 bayt). L1'de c de öyle:
rok defteri G'de duruyor, rok maskesi yok ve c=un=e=y=t=8'deki değer
hiçbir yerde okunmuyor. y ise sürücülerde canlı bir harf: tıklamalı sürümlerde terfi
taşı, numerical ile körleme sürümlerde son hamle. Başlangıç değeri hiçbirinde
önemli değil, çünkü okunmadan önce hep yazılıyor; bir sürücü y'yi kendi işine
alabilir, imza yine okunabilir kalır.
İmza çalışma zamanına değil, bildirimin metnine bağlı. Zincirdeki sırayı değiştiren her şey onu
sessizce bozar: bir baytlık bir kazanç ararken yazılan e=t=y=8, adları ya da
atamaların sırasını elden geçiren bir küçültücü. un= ile y='yi çıkarmak
beş bayt kazandırır ve imzayı götürür; testlerin hiçbiri bunu fark etmez.
Gölgelemeler
Aynı harfin iki ayrı şeyi adlandırdığı yerler aşağıda; bugün hiçbiri hata değil. Çoğu bir
parametre: kendi fonksiyonunun içinde kalıyor ve dışarıdaki globale dokunmuyor. Kalanlar,
motorun ve sürücünün aynı harfi farklı zamanlarda kullanmasından doğuyor. İkisi aynı anda canlı
değil — biri değerini kurduğunda öteki çoktan işini bitirmiş oluyor.
Harf
Bir tarafta
Öbür tarafta
A
Motorun hamleyi işleyen fonksiyonu; V'de önce materyal toplamı, sonra beyaz piyonların erişebildiği kareler
default ile light'ta beraberlik radyo düğmesi, id=A
u
V'de bir yatayın kaydırması, 256n
default, light ve input ailesinde tahta ögesi — saatler ve tekrar sayımları da onda; nonbrowser'da satır okuyan fonksiyon; numerical'da saat nesnesi; K'nin parametresi
x
G'de adım, T'de şahın karesi, L'de varış ve ardından şah sınamasının sonucu, M, O ve H'de varış
default'ta durum satırı ögesi; input ailesinde girdi kutusu; nonbrowser'da okuma tamponu
p
Geri çağrılarda taş kodu, U'da terfi taşı, H'de denenen terfi
default ile light'ta hamle fonksiyonunun parametresi; input_blindfold'da = eki
g
V'nin doyma döngüsü
default'ta alt saat ögesi; metin sürücülerinde hamleyi yapan taraf, g=t; s'nin parametresi
h
O'nun geri çağrısı
default ile light'ta üst saat ögesi; prompt_blindfold'da varış karesi
m
G'de tehdit modu bayrağı, V'de materyal
default'ta son çizilen tahtanın dizgisi
s
G'de işaretli adım, V'de renk ve ardından bitboard, H'de saklanan durum, D ile F'de taraf
default, input ve nonbrowser ailelerinde bayrak düşmesi ve terk sonucunu kuran yardımcı
j
Geri çağrılarda kare indeksi; V'de bir karelik bit
default, light ve input ailesinde saati işleten fonksiyon; nonbrowser_blindfold'da okuma tamponu
i
C, G, U, L, M, V'de kare parametresi — çoğunda çıkış karesi
Sürücülerde seçili ya da yazılan çıkış karesi, global
k
Pozisyon anahtarı
default'un render'ında iki saatin sırası, (E,k)=>
w
Saati işletmek için zaman damgası, w=new Date
Aynı sürücünün render'ında hücre indeksi, (p,w)=>
F
Motorun bayrak düşmesi ve terk hükmü, F(s,r)
default, light, input, nonbrowser'da render; prompt'ta bayrak düşmesi ya da terk harfi, 'T' ya da 'R'
D
Motorun beraberlik fonksiyonu, D(s)
Tahta çizen sürücülerde render'ın hesapladığı tahta karesi, D=w^7<<t/2; prompt'ta harfli D; nonbrowser_blindfold'da bayrak düşmesi ve terk sonucunu kuran yardımcı
Z
Motorun hükmü
light'ta saatleri çizen fonksiyon; prompt'ta harfli Z
E
default ile light'ta hamle fonksiyonu
Render'da saat ögesi, (E,k)=>; input ailesinde klavye olayı; prompt'ta zaman damgası; prompt_blindfold ile nonbrowser_blindfold'da = eki; numerical'da bayrak düşmesini terkten ayıran bayrak
J
default'ta talep fonksiyonu
light'ta taş glifi; metin sürücülerinde UCI ayrıştırıcısı
K
default ile light'ta kare tıklama işleyicisi
Metin sürücülerinde ham girdi metni
y
İmza harfi; motor okumuyor
Tıklamalı sürümlerde terfi taşı; numerical ile körleme sürümlerde son hamle
r
G'nin yazdığı rok kalesi karesi, global
F(s,r)'nin terk bayrağı — yalnızca F'nin gövdesinde; içinden çağrılan H ve U globali görüyor
V'nin sızdırdığı globaller: beyaz şahın ve siyah şahın bitboard'u, doyma kümelerinden biri
coHNn
Rok maskesi, teklif bitleri, 6.9 araması, düğüm bütçesi, 50 hamle sayacı
V'nin yerelleri: sarma maskesi, tarafın kendi materyali, yana yayma, şah adımı, kalan tur — Adlar'da
Tablonun büyük kısmı motor–sürücü sınırında duruyor ve çoğu tıklamalı sürümlerde doğuyor,
çünkü işaretleme taşıyanlar onlar: u, x, p,
g, h ve A birer id ve adla erişim sayesinde
global değişken oluyor. Motorun içindeki en yoğun gölgeleme ise V'de: parametre
listesinde on ad doğuyor ve çoğu, motorda başka bir işe ait bir harfin üstüne düşüyor.
Adla erişim bir tuzağı da beraberinde getiriyor. default motorun
A'sını taşımıyor, hamleyi kendi satırında işliyor; bu yüzden A adı
beraberlik radyo düğmesine kalıyor. Motorun A'sını
default'a ekleyen bir değişiklik o adı ezer: atama, pencere nesnesinde
kendi özelliğini yaratıyor ve ögeyi gölgeliyor. Ölçüldü: A=(i,f,p)=>…
atandıktan sonra A.checked=true fonksiyona yazılıyor, radyo düğmesi seçilmiyor.
Aynı şey u, x, p, g ve h için
de geçerli; motor bunları yalnızca parametre olarak kullandığı için bugün çakışmıyor.
Globallerde güvenliğin şartı zamanlama. y'yi panel de H de yazıyor,
U okuyor; X'i U yazıyor, default'un tıklama
işleyicisi ve H okuyor; r'yi G yazıyor, U
okuyor. Okuyan, yazanın hemen ardından çalıştığı sürece çakışma yok. Bu yüzden bu liste bir
muafiyet değil, bir kısıt: bir sürücüye yeni bir global eklenmeden önce bu
tabloya bakılmalı.
Yeniden adlandırma
Bir harfi değiştirmek göründüğü kadar mekanik değil; kör bir bul-değiştir üç
yerde birden bozulmaya yol açar.
Dizgi literalleri. Harf gibi görünen ama tanımlayıcı olmayan yerler:
input ve nonbrowser'daki 'a b c d e f g h'
ve 'h g f e d c b a' sütun etiketleri; default,
input, nonbrowser ve prompt'taki
'C!' göstergesi ve son üçündeki 'D?'; sonuç kodları — 'WB'[t/8]'deki W ile
B, V'nin sızdırdığı globallerle aynı harfler —; prompt,
input ve nonbrowser ailelerindeki 'T' süre bitimi kodu ve ${…}s saniye
eki.
İşaretlemedeki id=. İşaretlemeye yazılan her harf orada da değiştirilmeli;
değiştirilmezse adla erişim bağı kopar ve global undefined kalır.
Gölgelenen harfler. Aynı harf iki ayrı iş yapıyor olabilir —
gölgeleme tablosu hangi harfin nerede ne olduğunu veriyor.
Doğru yöntem, kaynağı acorn ile ayrıştırıp yalnızca tanımlayıcıları değiştirmek;
id= değişikliklerini ise ayrıca elle yapmak. Literallere ve şablon parçalarına
böylece hiç dokunulmamış olur. Ama Identifier düğümü tanımlayıcıdan daha geniş:
nesne anahtarları ve noktalı üye adları da Identifier. Metin sürücülerinin terfi
tablosundaki ({r:5,b:6,n:4}) b, tahtanın b'si değil,
UCI'deki fil harfi; b'yi bütün Identifier'larda değiştiren onu da
değiştirir ve fil isteyen terfi sessizce vezire döner. r ile n için de
durum aynı. Hesaplanmamış üye adları ile nesne anahtarları dışarıda bırakılmalı.
Değişiklikten sonra
Perft koşularının tamamı dış bir ölçüte bakar: ölçüt FIDE'nin kendisi;
motorun geçmişi ilgisiz. Kaynağa dokunulduğunda ise soru değişir: “kural doğru mu?” değil,
“bu değişiklik bir şeyi bozdu mu?” Ölçüt artık bir önceki sürüm. Bunun için beş denetim
var:
Bayt nötrlüğü. Yeniden adlandırmadan sonra dosya boyutu değişmemeli.
Yapısal eşdeğerlik. acorn ile ayrıştırılıp düğüm yapısı karşılaştırılır; tek fark
tanımlayıcı adları olmalı, hiçbir Literal ve hiçbir nesne anahtarı değişmemeli.
İç diferansiyel. Eski ve yeni sürüm ayrı vm bağlamlarında rastgele oyun
oynar; her yarım hamlede b c e t n o k R ve bütün kare çiftleri için
L(i,f) karşılaştırılır.
Render doğrulaması. jsdom'da altmış dört hücrenin metni, rengi ve
outline'ı, durum satırı ve iki saat karşılaştırılır.
Tersinirlik. Paketlenmiş çıktı açıldığında kaynağı bayt bayt geri vermeli ve açılan
kaynak, document.write satırı dışında, düz default'la aynı
olmalı (Paketten sonra ne doğrulanıyor).
Perft bu listede yok: hamle üretimine dokunulmadıysa gerekmiyor, dokunulduysa da yukarıdaki
koşuların tamamı zaten baştan çalıştırılır.
Bunun neden şart olduğunu bir örnek gösterdi: rokun kısa devresini kaldıran bir baytlık
kısaltmayı iç diferansiyel de CPW pozisyonları da kaçırdı; tricky süitindeki “Castling
Prevented” pozisyonu ise 2. derinlikte yakaladı. Rastgele oyunlar nadir pozisyonları
üretemiyor. Düzeneğin yükleyicisi de bir ders verdi. Yükleyici motoru ifade ifade çalıştırıyor
ve hata veren ifadeyi arayüz satırı sayıyordu; bu yüzden H'sinde sözdizimi hatası
olan bir motor bile sanity testini 3/3 geçiyordu. Arayüz satırları ancak çalışırken hata verir;
ayrıştırılamayan bir ifade hiçbir zaman arayüz satırı değildir. Yükleyici artık böyle bir
ifadede duruyor ve hangi tanımın bozuk olduğunu söylüyor.
Simülasyonlar
Kodlamada kullandığım simülasyonlar
Motoru yazarken her aşamada birçok program kullandım; aralarından satranç programlamaya
özellikle katkısı olacağını düşündüğüm üçünü paylaşıyorum. İlk ikisi tahtanın ve hareketin
nasıl tanımlanacağına dair: Board_Delta_Coordinate hareketi koordinat farkıyla (uzaklık ve h, v), Board_Delta_Index
ise indeks farkıyla tanımlamayı farklı tahta boyutlarında sınamaya yarıyor. Üçüncüsü, Chess960_Skeletons, motorun yapısına göre hiç gerekmeyebilir; gerektiği yerde işe
yarıyor, gerekmese de rok hakkında bazı şeyleri sezmeye katkı sağlıyor. Üçü de kendi başına
duran birer HTML dosyası; motoru kullanmıyorlar, iddianın parçası değiller. Bağlantılar yeni
sekmede açılıyor.
Hamleyi iki kare arasındaki koordinat farkıyla tanımlamanın iki yolunu yan yana koyan
simülasyon: farkı tek bir uzaklığa indirmek ya da yatay ve dikey bileşenini ayrı tutmak.
Uzaklık modunda 8×8 tahtada bir kareye tıklayınca o kareden bütün karelere olan Öklid
uzaklığı hem ondalık hem kök biçiminde yazılıyor, √5 ya da 2√2 gibi. Kale, fil, vezir, at ve
şah hazır gruplar olarak geliyor; her grup kendi uzaklık değerlerini ve karşılaştırma için
taşın gerçek hareket kuralını taşıyor, istersen yenisini kuruyorsun.
Uzaklığı tutan ve taşın gerçekten gidebildiği kareler dolu renkle, uzaklığı tuttuğu hâlde
taşın gidemediği kareler taralı çıkıyor. Kalenin beşinci karesini taklit eden 3-4-5 üçgeni
ve filin beşinci karesini taklit eden (1,7) böyle tek bakışta görünüyor. Uzaklık yaklaşımının
neden bırakıldığı Denenip elenen yollar bölümünde.
h / v modu tahtayı yatay ve dikey eksene ayırarak okuyor. Motoru uzun süre bu
yöntemle kullandım; tahtayı harflerle tutan string sürümleri bugün de onu kullanıyor. Bayt
olarak ucuz bir yöntem; baytların çoğu testlere değil, tanımları yazmaya gidiyor: sütunu ve
satırı çıkarıp yatay ve dikey göreceli koordinatı bulmak için Math.abs,
i%8 ve i>>3 sıkça kullanılıyor. Her karede iki sayı yazıyor: h yatay yöndeki fark, v dikey yöndeki fark;
ikisi de mutlak değer. Bir çizgi üstündeki karelerde bir de d = h|v çıkıyor, yani
adım sayısı. Çizgide ya biri sıfır ya da ikisi eşit olduğu için tek bir OR yetiyor.
Tanımlar kurulunca testler birkaç bayta iniyor. Gruplar uzaklık yerine h ve v üzerine bir
koşul taşıyor: at h*v==2, şah d<2, fil h==v, kale
h*v==0. Vezire ayrı bir tanım yazılmıyor; kalenin ya da filin testinden geçen her
kare onun da hamlesi. Koşulu kendin de yazabiliyorsun. Geometrik zorunluluk gereği bu
testlerin hiçbiri, tahta hangi boyutta olursa olsun, geçersiz bir kareye düşmüyor. h ve v
gerçek sütun ve satır farkı olduğu için kenardan sarma diye bir şey yok. İndeks farkında
durum farklı; bir sonraki simülasyonun konusu da bu. Uzaklık modunda taralı çıkan 3-4-5
üçgeni ve (1,7) bu yüzden burada yanmıyor.
Hamleyi indeks farkıyla tanımlamayı farklı tahta boyutlarında sınadığım simülasyon. Her kare kendi
indeksini taşıyor; bir kareye tıklayınca o kare referans oluyor, tanımlı farklar hangi
karelere düşüyorsa onlar yanıyor ve her hedefin üstüne ona giden fark yazılıyor. Dört tahta
tipi hazır geliyor: düz 8×8, 0x88 (16×8), 10×12 mailbox ve 16×12. Boyutlar literatürdeki gibi
sütun × satır yazılı. Her tip at, şah, fil, kale, vezir ve piyonun o tahtadaki farklarıyla
açılıyor ve içteki gerçek 8×8'i kendiliğinden çerçeveliyor. Tipler arasında geçince aynı taş
aynı karede kalıyor, yani dört tahta birebir karşılaştırılabiliyor.
Her hedef ayrı işaretleniyor: farkın kastettiği kareye düşenler dolu renkle, kenardan
sarıp yanlış bir gerçek kareye düşenler taralı, dolguya düşüp reddedilenler ✕ ile. Düz
8×8'de +1'in h dikeyinden bir sonraki yatayın a'sına sardığı,
+7 / +9'un ve atın farklarının kenarda nereye düştüğü böyle
doğrudan görünüyor. 0x88'de aynı farklar sağdaki yarıya düşüp i & 0x88
testine takılıyor; bekçi, indeksin kendi bitlerinde. 10×12'de ise gözcü kenarına
çarpıyor; bekçi orada bit değil, hücredeki değer. 16×12 ikisini birleştiriyor: farklar
0x88'inkiler, bekçi 10×12'deki gibi gözcü kenarı. Altındaki tablo her adımı indeksi,
karesi ve sonucuyla sıralıyor, yanlış sonuçlanan satırlar kırmızı. Serbest modda satır,
sütun ve farklar elle kuruluyor, alan seçimiyle kendi dolgunu çizebiliyorsun. Ayarlar
tarayıcıda saklanıyor. FideLite bugün bu tiplerden 0x88'i kullanıyor, ama
i & 0x88 testi olmadan, kısa bir yazımla: tahta dışındaki gözler dizide boş
bırakılıyor, kayan taş da bu deliklere çarpıp duruyor. Bu düzenin motora ne kazandırdığı
motorun kendisi sekmesinde.
Chess960'ta birinci ve sekizinci yataydaki taşların dizilişi değişiyor, ama iki kurala
uyarak: filler farklı renk karelerde olmalı, şah iki kalenin arasında olmalı. Bu iki kural 960
dizilişi mümkün kılıyor. Satranç programlama açısından ise anlamlı olan sayı 56.
Chess960'ı standart satrancın kodundan ayıran tek kural rok. Taşlar aynı hareket ediyor;
geçerlilik, mat, pat, tekrar ve sayaçlar aynı. Başlangıç dizilişi bir kural değil, veri. Rok
ise değişiyor: şah ve kale her zaman standart satrançtaki son karelerine gidiyor (şah g ya da
c dikeyine, kale f ya da d dikeyine), ama yol koşulları şahın ve kalenin başlangıç dikeylerine
bağlı hâle geliyor. Rok yalnızca bu üç taşın dikeylerine baktığı ve şah hep iki kalenin
arasında durduğu için 960 diziliş rok açısından C(8,3) = 56 iskelete iniyor.
Chess960 bugün Özel sürümler'deki LUX'ta oynanabiliyor: başlarken
dizilişin numarası (0–959) yazılıyor ya da zarla seçiliyor; kuralları Kurallar sekmesinin
10. bölümünde. Eski sürümlerde rok sabit kodlandığı için o sürümler 56
iskelette toplanıyordu: 56 iskeleti tek tek bir formülle üretip 960 dizilişin hepsini elde
etmiştim. Golfstack'in bayt sınırı olmayan, bütçesi geniş bir
algoritmada hepsini kapsayan tek bir genel sürüm yazılabilir; yine de bence onu yazan da bir
yerde 56 sayısına rastlar.
Sayfa her iskeleti bir kartta gösteriyor: sekiz karelik şerit, -R--K-R-
biçiminde yazılışı ve o iskelete düşen bütün Scharnagl numaraları. Klasik başlangıç, #518
(RNBQKBNR), işaretli.
Bir satranç bulmacası ve bir satranç varyantı. İkisi de iddianın parçası değil, motoru
kullanmıyor; her biri kendi başına duran tek bir HTML dosyası. Siteye gömülü değiller,
bağlantılar yeni sekmede açılıyor.
ParrotPuzzle — papağana karşı bir bulmaca
Tek kişilik bir satranç bulmacası. Beyazla oynuyorsun, rakibin bir papağan. İlk el serbest
bir ısınma: siyahı mat et, süre serbest. Papağanın nasıl oynadığını kimse söylemiyor, gerçi
epey belli :) Yine de onu çözmek bulmacanın ilk adımı, o yüzden burada da yazılmadı. Asıl
zorluk görevlerde başlıyor.
Isınma bitince on görev açılıyor. Zorlukları birbirinden farklı, her biri oyuna başka bir
açıdan baktırıyor; bazıları gerçekten zor. Hepsi aynı papağana karşı, değişen yalnızca amaç ve hamle
sınırı: dört hamlede mat, rok ve geçerken alma üstüne kurulu görevler, papağanı pat etmek,
hatta papağana kendini mat ettirmek. Başarısız her denemede bir ipucu daha açılıyor. Görev
çözülünce neden işe yaradığı anlatılıyor ve çözüm tahtada oynatılabiliyor; birkaçında daha
kısa bir yol olmadığı tam aramayla gösterilmiş. Arayüz Türkçe ve İngilizce, taşlar
sürükleyerek ya da dokunarak oynanıyor.
Bir satranç varyantı. Taşlar bildiğin gibi oynuyor, tek fark: kendi şahını tehdit altında
bırakmak serbest ve oyun mat ile değil, şahın yenmesiyle bitiyor. Şah çekildiğini kimse
söylemiyor, tahta da göstermiyor; görmek sana kalıyor. Pat, yetersiz materyal ve ölü pozisyon
yok, beraberlik yalnızca 50 hamle ve üçlü tekrarla geliyor.
Rakibin bir baykuş: standart satranç oynuyor ve bu varyanttan haberi yok. İlk oyun bitince
yedi görev açılıyor, ParrotPuzzle'daki gibi her başarısız denemede bir ipucu daha. İki kişilik
oyun da var; sunum ise varyantın sınır durumlarını adım adım gösteriyor. Arayüz Türkçe ve
İngilizce.
Bu bölüm FideLite'tan bağımsız. İlk ikisi bugün kullanılabilen programlar, son üçü büyük
platformlarda henüz görmediğim özellikler. Beşinin ortak noktası, ayrı bir araç ya da
eklenti olarak değil, satranç sitelerinin kendi içinde olması gerektiği.
CHA-Solver — süre bitiminde doğru hüküm
Miguel Ambrona'nın açık kaynaklı çözücüsü. Bir pozisyon ve kazanması beklenen taraf
verildiğinde ya o tarafın mat ettiği bir yardım matı dizisi buluyor ya da böyle bir dizinin
olmadığını kanıtlıyor. FIDE 6.9'un sorduğu
soru tam olarak bu: süresi biten oyuncu kaybeder, ama rakibi hiçbir geçerli hamle dizisiyle mat
edemiyorsa oyun berabere biter. Terkte (5.1.2) aynı soru soruluyor, ölü pozisyonda (5.2.2) ise
iki taraf için birden. Algoritma hakemli bir makalede, FUN 2022'de yayımlandı; pozisyon girip
sonucu görebileceğin bir web sayfası da var. Aşağıdaki görsel nasıl çalıştığını
gösteriyor.
Platformlar da satranç motorlarının neredeyse hepsi de bu kuralı algoritmalarına dahil
etmiyor; süre bitiminde belirli, sabit bir taş listesine bakmakla yetiniyorlar. Oysa bu
listenin dışında, kilitli pozisyonlarda ve zorunlu olarak beraberliğe giden pozisyonlarda
kayda değer sayıda yanlış sonuçlanmış oyun tespit edildi. Ambrona aracı bir çözüm önerisi
olarak Lichess'e sundu ↗️; entegre edilmedi, bugün hâlâ bağımsız bir araç olarak
yayında.
Bence mesele yalnızca yanlış sonuçlanan oyunlar değil. Kilitli bir pozisyonda oyun
aslında bitmiştir, ama bunu algılayacak bir algoritma olmadığı için oyuncular oynamaya devam
ediyor ve vakitleri boşuna harcanıyor. Bu tespitin her hamlede, modern platformlarda hiçbir
sürtünme yaratmayacak biçimde optimize edilebileceğinden şüphem yok. Stockfish'i en üst
seviyede çalıştırabilen platformlar için buna harcanan işlemci bütçesi insan algısıyla fark
edilmeyecek kadar küçük kalır. Her platformda olmalı; süre bitiminde yanlış verilen hüküm oyun
kapandıktan sonra geri alınmıyor. Konunun ayrıntısı bu sitede
Süre bitimi, terk ve mat imkânı bölümünde.
Google Lens bir fotoğraftaki yazıyı nasıl okuyorsa, Chessvision.ai de bir ekran
görüntüsündeki, kitap sayfasındaki ya da video karesindeki tahtayı öyle okuyor: pozisyonu
FEN'e çevirip analiz tahtasında açıyor. Tarayıcı eklentisi, telefon uygulaması ve PDF kitaplar için bir okuyucusu var; bir pozisyonu
kare kare elle dizmek yerine tek tıkla tahtaya taşımak mümkün. Aşağıdaki görsel nasıl
çalıştığını gösteriyor.
Bunun ayrı bir eklenti değil, her platformun analiz tahtasında yerleşik bir düğme olması
gerekiyor: görüntüyü yapıştır, pozisyon gelsin. Bir ayrıntı da atlanmamalı: görüntü yalnızca
taşların yerini taşıyor. Sıranın kimde olduğu, rok hakları, geçerken alma karesi ve sayaçlar
resimde yok; iyi bir araç bunları tahmin etmek yerine kullanıcıya sormalı.
Sosyal medyada ve YouTube'da satranç içeriği izleyenler için bu özellikle pratik. Bu
sayfaların neredeyse hiçbiri pozisyonu FEN olarak vermiyor; YouTube ve Instagram gibi pek çok
uygulamada yazı kopyalanamıyor bile. FEN yazılı olsa bile onu önce Google Lens'le metne
çevirmek yerine görüntüden doğrudan FEN'e geçmek, birden fazla işi tek adımda bitiriyor. Art
arda bulmaca çözmek isteyen biri için fark daha da büyük. Kesinlikle eklenmeli.
Büyük platformlarda tahta bugün yalnızca 180° çevrilebiliyor: beyazın ya da siyahın
tarafından bakıyorsun. Yan dönmüş bir görünüm, 90° ya da 270°, yok. Oysa piyon ve rok
dışında hiçbir hareket kuralı yöne bağlı değil: at her yönde aynı L'yi çiziyor, kale ve fil
dört yana da aynı biçimde gidiyor. Bu yüzden bir mat deseni tahtanın dört kenarından
herhangi birinde aynı şekille kurulabiliyor; değişen yalnızca onu hangi açıdan gördüğümüz.
Aşağıdaki görsel bunu gösteriyor.
Döndürme bir kural değişikliği değil, bir bakış seçeneği. Makineyi de kuralı da
değiştirmiyor; bu yüzden motorları hiçbir şekilde bağlamıyor. Kolaylaştırdığı şey insan
sezgisi: mat desenleri ve bazı pozisyon kalıpları çoğunlukla tek bir yönde öğreniliyor, göz
onları başka bir kenarda geç tanıyor. Anastasia matı gibi yan kenarda kurulan bir desen,
tahta 90° çevrilince bildik bir arka yatay matına benziyor. Bu yüzden faydalı buluyorum;
pek çok kişinin bu seçeneği tercih edeceğini düşünüyorum.
Taşa göre hamle işaretleri
Bir taş seçilince platformlar gidebileceği kareleri her taş için aynı noktayla gösteriyor.
Nokta taşın nereye gidebildiğini söylüyor, nasıl gittiğini söylemiyor. Oysa bu fark kuralın
kendisi: filin ve kalenin yolu kapatılabilir, atınki kapatılamaz, çünkü at sıçrıyor. Aşağıdaki
üç tahta, işaret taşın karakterini taşıyınca bu farkın nasıl göründüğünü Chess.com renkleriyle
gösteriyor.
Fil çaprazlarına mızrak, kale düz yönlerine yamuk uçlu kalın bir ok uzatıyor; yol üstündeki
her karede küçük bir yumru var. Ok kendi taşına çarpınca önündeki son boş karede bitiyor, rakip
taşa varınca da yenen kareyi saran çembere değip duruyor. At sıçrayabildiği her karede bir nal
izi bırakıyor; vezir ise kalenin ve filin oklarını birlikte taşıyor. Nokta yalnızca bir yeri
işaret ediyor; mızrak, ok ve nal izi ise hareketin kendisini gözde canlandırıyor, hayal gücüne
ve sezgiye çok daha fazla hitap ediyor. Döndürme gibi bu da bir kural değişikliği değil, bir
bakış seçeneği.
Fil — mızrak
Kale — yamuk uçlu ok
At — toynak izi
Kullanıcıya özel tema
Platformlar geniş bir seçenek sunuyor: taş takımları, tahta renkleri, üç boyutlu
görünümler, özel günlere göre temalar. Ama hepsi hazır, platformun seçip sunduğu temalar.
Kendi çizdiğin taşları ya da tahtayı kullanmak bugün ancak tarayıcı eklentileriyle mümkün;
tam da bunun için yazılmış eklentilerin çokluğu talebin gerçek olduğunu gösteriyor.
Oyuncular kendi yaratıcılığını konuşturabilmeli: kendi temasını üretip kullanabilmeli,
forumlarda başkalarıyla paylaşabilmeli. Yerleşik bir destek olsa tema hesaba bağlı kalır,
tarayıcıda da telefonda da aynı görünür.
Aşağıdaki görsel bir ekran teması değil, Yüzüklerin Efendisi konseptiyle yapılmış gerçek,
fiziksel bir satranç takımı. Ama aynı fikri anlatıyor: tahta ve taşlar yaratıcılıkla ne kadar
farklı yorumlanırsa, ortaya o kadar güzel, sanatsal ve fantastik işler çıkabiliyor. Bunların
hiçbiri satrancın kuralına ya da işleyişine dokunmuyor; oyuna yalnızca renk ve boyut
katıyor.
İşin bir erişilebilirlik tarafı da var: renk körlüğü, az görme ya da yüksek kontrast
ihtiyacı olan biri kendi rahat okuyabildiği taşları ve kare renklerini kurabilir. Tema
yalnızca sahibinin ekranında geçerli olduğu için oyunun adilliğine de dokunmuyor; rakip her
zaman kendi seçtiği görünümü görür.
Görsel: Tolkien.Creations
Geleceğin Şampiyonu
GM Yağız Kaan ERDOĞMUŞ (D. 3 Haziran 2011) – 2731 rating