momente şi schiţe de informatică şi matematică
To attain knowledge, write. To attain wisdom, rewrite.

Modelarea tablei şi jocului de şah (XI)

FEN | PGN | javaScript | parser | regexp
2012 jul

Notaţia liniilor prin 1..8 (începând dinspre partea albului), a coloanelor prin a..h (începând din stânga albului) şi notaţia algebrică derivată pentru mutările pieselor - SAN, adoptată de FIDE şi implicată în reprezentarea PGN - a fost introdusă de către Philipp Stamma, pe la 1750.

Un exemplu edificator de reprezentare PGN

PGN poate avea două formate; "PGN Export format" este mai riguros - fiind folosit pentru arhivarea datelor PGN şi uşurând interschimbul acestora între programe - şi poate arăta astfel:

În acest format anumite taguri sunt obligatorii şi au o ordine prestabilită; nu este permisă scrierea mai multor taguri pe o aceeaşi linie; după "[" şi înainte de "]" nu trebuie lăsat spaţiu, iar între cheia şi valoarea tagului trebuie să existe un singur spaţiu. Antetul PGN trebuie separat de secţiunea mutărilor printr-o linie liberă. În plus, orice linie trebuie să conţină cel mult 80 de caractere.

Disecăm mai jos acest exemplu, evidenţiind elementele generale care trebuie luate în considerare de către o funcţie de analiză a textului PGN; dar în acest scop folosim formatul mai liber "PGN Import format", care elimină toate restricţiile menţionate mai sus - ideea fiind aceea de a putea folosi PGN-browserul (pe care-l dezvoltăm aici de ceva vreme) şi pentru PGN-uri (întâlnite şi acestea) care au anumite abateri de la standard.

Secţiunea informativă (antetul PGN, cu informaţii despre partidă)

[Event "Philidor"] [Site "L'analyse des echecs 1749"] [Date "1726.??.??"]
[White "Philidor, Francois"] [Black "?"] [Result "1-0"]
[FEN "3k4/4r3/3K4/3B4/8/8/8/5R2 w - - 0 1"]

Am păstrat numai unele taguri, relevante pentru conţinutul partidei: este vorba de un studiu pe tema "Turn contra Nebun", dintr-o carte celebră - Analyse du jeu des Échecs a lui Philidor.

Tagul FEN indică poziţia iniţială pentru lista de mutări (care urmează în PGN după antet) - vezi diagrama alăturată. Linia 8 corespunde primului câmp FEN 3k4/ (3 câmpuri libere, regele negru, 4 câmpuri libere); linia 7 are un turn negru, cu 4 câmpuri libere în stânga lui şi 3 în dreapta - deci se reprezintă prin /4r3/ în al doilea câmp FEN; ş.a.m.d.

Liniile 4, 3 şi 2 sunt libere, deci corespund câmpurilor FEN /8/8/8/. Prima linie are doar un turn alb pe câmpul f1, deci "ultimul" câmp din FEN este /5R2.

Câmpurile din FEN vizate mai sus reprezintă poziţia din diagramă, linie după linie. Dar FEN-ul mai conţine încă cinci "câmpuri" w - - 0 1 (separate prin spaţiu) - fiecare având o anumită semnificaţie pentru poziţia reprezentată în prima parte a FEN-ului; de exemplu, w marchează faptul că în poziţia respectivă albul ("white") este la mutare.

Comentariu introductiv (opţional; apare înainte de lista mutărilor)

{ Endgame theory started with Polerio, Greco and Stamma. Philidor also made valuable contributions. He formulated the basics for the endgame of rook and bishop versus rook. }

Comentariile sunt texte cuprinse între acolade (şi nu pot fi imbricate). Un comentariu introductiv se referă eventual la întreaga partidă (nu la o anumită mutare din cursul partidei) şi trebuie să preceadă imediat, lista mutărilor; el va trebui reţinut separat, faţă de tabloul mutărilor.

Secţiunea mutărilor (a consulta eventual Rules of chess (Regulile şahului))

1. Rf8+ Re8 2. Rf7 Re2! (2... Rh8 3. Ra7 Rh6+ 4. Be6 {leads to mate.}) 

3. Rg7 $1 ; The black rook is forced to a less favourable square.
3... Re1 ({Weak is} 3... Re3 4. Rb7) 4. Rb7 Rc1 ({Another interesting variation is} 4... Kc8 5. Ra7 $1 Rb1 6. Rh7 $1 Kb8 (6... Rb6+ 7. Bc6) 7. Rh8+ Ka7 8. Ra8+ Kb6 9. Rb8+) 5. Bb3 $3 {This move would make no sense if the black rook should be on c2.} 5... Rc3 $1 {The rook moves to a bad rank.} ({White also wins in} 5... Kc8 6. Rb4 Kd8 7. Rf4 Re1 (7... Kc8 8. Bd5 Kb8 9. Ra4) 8. Ba4 Kc8 9. Bc6 Rd1+ 10. Bd5 Kb8 11. Ra4 ) 6. Be6 Rd3+ 7. Bd5 Rc3 (7... Kc8 8. Ra7 {loses at once.}) 8. Rd7+ $1 Kc8 ({Or } 8... Ke8 9. Rg7) 9. Rf7 Kb8 10. Rb7+ Kc8 11. Rb4 $1 Kd8 ({ The first point of the last white move is} 11... Rd3 12. Ra4)
12. Bc4 $3 ; The second pointe.
12... Kc8 13. Be6+ Kd8 14. Rb8+ Rc8 15. Rxc8# 1-0

Prima mutare 1. Rf8+ constituie reprezentarea SAN a mutării care s-ar descrie prin "ia turnul alb de pe câmpul f1 şi pune-l pe câmpul f8, apoi zi şah" (a vedea diagrama de mai sus).

SAN este minimală, fiindcă ţine seama de context; prefixul 1. (numărul de ordine al mutării) arată că albul va executa mutarea; R arată că piesa care trebuie mutată este un turn; câmpul de start nu este indicat, fiindcă pe câmpul destinaţie precizat f8 nu poate fi mutat (în contextul curent) decât un singur turn (cel de pe f1). Sufixul + indică un efect al mutării: negrul "este în şah" (iar sufixul # la mutarea 15 indică faptul că negrul este mat).

Unele mutări sunt adnotate cu un simbol care exprimă o apreciere de calitate a mutării: mutarea 2 a negrului Re2! este o mutare bună; sau sunt adnotate cu un index prefixat cu $, indicând o anumită intrare într-un tabel predefinit (denumit NAG) care conţine exprimări standard de apreciere a mutării sau a poziţiei: pentru 12. Bc4 $3 găsim pe locul NAG[3] aprecierea 'very good or brilliant move (traditional "!!")'.

Pentru unele mutări sunt indicate variante alternative de joc (posibil, cu sub-variante): o listă de mutări încadrată între paranteze rotunde. De exemplu, aici avem indicată o variantă de joc la mutarea a 2-a a negrului şi o variantă cu o sub-variantă la mutarea a 4-a a negrului.

La unele mutări (inclusiv, în cadrul variantelor) sunt oferite comentarii. De obicei, acestea sunt texte încadrate între acolade; dar este prevăzută şi forma "linie": se pune ; după mutarea respectivă şi atunci tot textul care urmează până la sfârşitul liniei este considerat comentariu - cum avem mai sus pe linia mutării 3 şi apoi, pe linia mutării 12.

La sfârşitul secţiunii mutărilor trebuie înscris rezultatul partidei - în cazul nostru 1-0, indicând că albul a câştigat; dar acesta nu va face parte din tabloul mutărilor.

Am văzut în (X) cum putem folosi expresii regulate pentru a extrage partea de antet a PGN-ului, constituind tabloul intern this.tags cu tagurile respective. Vom proceda analog asupra secţiunii mutărilor din PGN, constituind un obiect javaScript care să permită apoi PGN-browserului să acceseze uniform fiecare mutare SAN, împreună cu adnotările, variantele şi comentariile asociate acesteia (urmând să verifice legalitatea mutării şi să înscrie cele cuvenite în diviziunea mutărilor şi în cea a comentariilor).

Constituirea listei mutărilor, pe baza secţiunii de mutări din PGN

Definim în afara widget-ului un obiect javaScript care să reprezinte o mutare din PGN:

function Move() {
    this.SAN = "";  // Standard Algebraic Notation
    this.FEN = "";  // the resulting FEN
    this.mark = ""; // + (check), # (checkmate), ! (good move), etc.
    this.NAG = 0;   // Numeric Annotation Glyphs index
    this.variant = "";  // variants for this move (but... as String?)
    this.comment = ""   // annotations
};

Poate că mai târziu vom reveni, punând this.variant = []; - având adică în vedere că o variantă este ea însăşi o "listă de mutări" (deci un Array() cu obiecte Move()).

Aşa cum pentru a păstra tagurile extrase din PGN, am instituit obiectul this.tags - adăugăm acum, la începutul metodei _init(), un tablou în care vom înregistra câte un obiect Move() pentru fiecare mutare din PGN şi deasemenea, un şir care să păstreze eventualul comentariu iniţial şi unul care să păstreze anumite mesaje generate în situaţia când parcurgând textul PGN, am întâlni o eroare de sintaxă (paranteze desperecheate, sau o mutare notată greşit, etc.):

_init: function() {
    this.tags = {};    // from the 'tag pairs' section of the PGN=this.element.val()
    this.moves = [];   // Move() objects, for each move from the 'movetext' PGN-section
    this.in_comm = ""; // the initial comment (that precede 'movetext')
    this.errors = "";  // concatenate various error messages (when PGN is parsed)
    // ... //
},

După eliminarea secţiunii informative şi a comentariului iniţial (de care ne-am ocupat anterior), PGN-ul s-a redus la secţiunea mutărilor. În primul rând să scăpăm de câmpul de "rezultat final" de la sfârşit şi să eliminăm eventualele spaţii de la început:

//strip the final result field, but assure that exist a Result tag
var result; // 1-0 | 0-1 | 1/2-1/2 | *
if ((result = PGN.match(/1\/2\s*-\s*1\/2\s*$/)) || 
    (result = PGN.match(/0\s*-\s*1\s*$/)) || 
    (result = PGN.match(/1\s*-\s*0\s*$/)) || 
    (result = PGN.match(/\*\s*$/))) {
        PGN = PGN.replace(result, "");
        if (!this.tags['Result']) this.tags['Result'] = result;
}
else alert('a Result is needed at the end of PGN');
            
PGN = PGN.replace(/^\s+/, ""); // strip initial spaces

Ne-am asigurat astfel că PGN-ul rămas conţine doar mutări SAN numerotate (şi eventual marcate cu simboluri de apreciere), comentarii şi variante; parcurgând acest şir putem stabili începutul fiecăruia dintre aceste elemente şi folosind expresii regulate corespunzătoare vom putea extrage fiecare element (repetând apoi, asupra şirului rămas după "extragerea" elementului curent identificat). Algoritmul poate fi descris succint astfel:

Algoritm de analiză sintactică şi lexicală a şirului mutărilor PGN - pseudocod
PGN-syntax-error = FALSE
WHILE PGN este nevid {
    first_car = primul caracter din PGN
    IF first_car este o cifră:  deci urmează "numărul mutării"
        DELETE(număr de mutare)  va putea fi dedus din indexul lui MOVE() în this.moves[]
    IF first_car este caracter iniţial de mutare SAN:
        IF se respectă sintaxa SAN:
            EXTRACT(mutare SAN) în câmpul m.SAN al unui nou obiect m = MOVE()
            adaugă obiectul m în tabloul this.moves[]
        ELSE PGN-syntax-error = TRUE
    IF first_car este marcaj de apreciere sau este index NAG:
        EXTRACT(apreciere) în câmpul m.mark respectiv în m.NAG
    IF first_car este '{':  început de comentariu
        IF acoladele se împerechează corect şi nu sunt imbricate
            EXTRACT(comentariu) în m.comment
        ELSE PGN-syntax-error = TRUE
    IF first_car este ';':  început de comentariu-linie
        EXTRACT(restul liniei curente) în m.comment
    IF first_car este '(':  variantă/sub-variantă
        IF parantezele se împerechează corect
            EXTRACT(variantă) adăugând-o în m.variant
        ELSE PGN-syntax-error = TRUE
    IF PGN-syntax-error este TRUE
        BREAK  afişează mesaj de eroare şi încheie
    EXTRACT(spaţii iniţiale) actualizează first_car şi reia analiza
}

Prezentăm mai întâi aspecte specifice mutărilor de şah, de care trebuie să ţină seama operaţia EXTRACT(mutare SAN); acestea vor fi vizate deasemenea, când vom aborda problema stabilirii legalităţii unei mutări. La sfârşit, vom schiţa şi o implementare a algoritmului de mai sus în cadrul widget-ului nostru (dacă n-am spus - întregul cod-sursă este postat pe github).

Construcţia unui şablon pentru mutările SAN

Sunt de văzut vreo trei-patru tipuri de mutări; le discutăm pe rând şi construim expresii regulate pentru fiecare tip, încercând să reunim (cât se poate) aceste şabloane parţiale într-o singură expresie regulată (permiţând extragerea unei game cât mai largi de mutări SAN).

Mutarea unei piese. "Piesă" înseamnă rege, damă, turn, nebun, sau cal (nu şi pionul!) - notate în mod standard prin K, Q, R, B, N (indiferent de culoare, în cadrul SAN).

În acest caz, primul caracter din SAN-ul mutării este unul din gama [KQRBN].

Urmează - cel mai frecvent - specificarea câmpului pe care se mută piesa, ceea ce revine la concatenarea notaţiei de coloană cu cea de linie: [a-h][1-8]; de exemplu, Rc8 înseamnă mutarea turnului pe câmpul c8.

Dar specificarea numai a destinaţiei ar fi insuficientă atunci când partea care trebuie să mute are mai multe piese de tipul indicat de primul caracter SAN care să poată muta legal pe câmpul destinaţie considerat. Astfel, în figura alăturată avem două turnuri care pot muta pe câmpul c8 şi (presupunând că ambele mutări sunt legale) notaţia "Rc8" nu poate de data aceasta, să indice unic o mutare. Pentru asemenea cazuri de excepţie, SAN implică şi o informaţie (dar minimală!) privind câmpul iniţial al piesei; pentru cazul redat în figură este suficient să se precizeze coloana câmpului iniţial: Rcc8. Pentru situaţia în care disocierea s-ar putea face şi prin precizarea liniei câmpului iniţial (adică aici, prin R6c8) - SAN prevede o regulă de prioritate: se foloseşte linia iniţială numai dacă disocierea nu se poate face prin coloană.

Vom vedea mai târziu că SAN prevede şi disocierea prin faptul că numai una dintre mutările posibile este şi legală. De exemplu, dacă ne imaginăm că în contextul figurii de mai sus, turnul din c6 este legat (adică nu poate muta din cauză că ar lăsa propriul rege în şah) - atunci este suficientă notaţia Rc8, fiindcă numai unul dintre turnuri (cel din a8) poate muta legal pe c8.

În situaţia (mai rară) în care totuşi, nu se poate disocia între două mutări posibile nici prin indicarea coloanei şi nici prin indicarea liniei de start - nu rămâne altă soluţie decât precizarea completă a câmpului iniţial al piesei. Astfel, în figura alăturată toţi cei patru cai (câte doi pe o aceeaşi linie şi respectiv, coloană) pot muta pe câmpul c2 şi nu avem cum disocia mutările decât precizând complet câmpul iniţial: Na1c2.

Dacă mai ţinem seama de faptul că pe câmpul destinaţie poate să se afle o piesă adversă, astfel că efectul mutării este capturarea acesteia - rezultă că şablonul unei mutări de piesă poate fi constituit prin concatenarea următoarelor elemente:
     /^[KQRBN] obligatoriu, primul caracter din şir este o "piesă"
     [a-h]? coloana câmpului iniţial, dar numai în caz de ambiguitate
     [1-8]? linia câmpului iniţial, dar numai dacă ambiguitatea persistă
     x? efectul de "captură", dar numai dacă este cazul
     [a-h][1-8]/ (obligatoriu) câmpul pe care se mută piesa
unde: /^ înseamnă "caută o potrivire cu şablonul, începând de la primul caracter" al şirului PGN; ? înseamnă zero apariţii sau, o singură apariţie a caracterului din stânga; iar litera x este consacrată în SAN pentru efectul de "captură".

Mutări de pion. Şablonul constituit mai sus va corespunde şi mutărilor obişnuite de pion, imediat ce ignorăm primul câmp (cel care indică piesa mutată) - iar pentru aceasta este suficient să-l sufixăm cu ? (zero apariţii [KQRBN] - deci pion; sau, una apariţie - deci piesă).

Astfel, e4 reprezintă mutarea unui pion pe câmpul e4; câmpul de start este lăsat spre deducere din contextul curent al jocului (dacă negrul a făcut mutarea, atunci câmpul iniţial nu poate fi decât e5; dacă albul a făcut mutarea, atunci câmpul iniţial poate fi e2 (e2-e4), sau poate fi e3 - ceea ce iarăşi se deduce din context).

Dar de data aceasta, câmpul al doilea din şablonul de mai sus (coloana câmpului iniţial) nu mai este opţional, în cazul unei capturi: în figura alăturată, pionul e2 poate să captureze calul f3 - dar SAN nu acceptă notaţia xf3 (care începe cu simbolul pentru "captură") şi impune exf3 (specificând şi coloana iniţială, ca şi când ar fi vorba de o ambiguitate - precum în cazul când ar exista şi pe g2 un pion alb).

Pentru cazul specific pionilor, al mutărilor de transformare, şablonul de mai sus trebuie completat. În figura alăturată, pionul alb poate fi transformat în [QRBN] fie înaintând c8=Q, fie capturând în stânga: cxb8=Q. În SAN = indică o mutare de transformare şi trebuie urmat de precizarea piesei în care promovează pionul: =[QRBN] - dar acest şablon (cu două caractere) trebuie adăugat ca opţional în şablonul (deja destul de general) constituit mai înainte.

Pentru a-l face "opţional", trebuie să grupăm cele două caractere şi să sufixăm acest grup cu ? (zero sau una apariţie, a grupului): (=[QRBN])?. Trebuie să mai ţinem seama de faptul că pentru grupul creat astfel se creează automat şi o referinţă numerică suplimentară, care păstrează secvenţa respectivă (RegExp.$1, ceea ce am folosit anterior); pentru a evita aceasta, putem folosi construcţia (?:secvenţă) care grupează fără să şi capteze, secvenţa respectivă: (?:\=?[QRBN])? (am folosit totuşi "\=", fiindcă "=" are un rol prestabilit, în special în legătură cu "?" - iar aici avem nevoie de caracter şi nu de "rol"). În plus, am prevăzut "=" ca opţional - permiţând astfel şi reprezentări "directe" ca c8Q (întâlnită în diverse fişiere PGN!), în loc de (cum se pretinde în mod standard) c8=Q.

Prin urmare, din cele de mai sus am avea deja această expresie regulată unitară:
     /^[KQRBN]?[a-h]?[1-8]?x?[a-h][1-8](?:\=?[QRBN])?/
care permite recunoaşterea mutărilor de piese şi de pioni (inclusiv a mutărilor de transformare).

"Am avea" - pentru că există totuşi un defect, rezultat din faptul al doilea câmp (cel de "coloana iniţială") a rămas opţional (ori el este obligatoriu în cazul "mutare de pion cu captură", cum am văzut mai sus); ca urmare, 3e4 va fi recunoscută ca mutare de pion valabilă (deşi începe cu "linie iniţială", ceea ce este incorect pentru SAN). Dar de ce să nu lăsăm rezolvarea unui asemenea defect chiar în seama funcţiei de parcurgere a PGN-ului? (în loc de a strica generalitatea şablonului construit mai sus, tratând separat cazul particular "mutare de pion cu efect de captură").

Un singur tip de mutare, nu se poate încadra în acest şablon: mutarea de tip rocadă. Dar expresia regulată pentru rocadă este foarte simplă: /^O-O-O|^0-0-0|^O-O|^0-0/ - ţinând seama că rocada mică, respectiv cea mare se notează prin O-O şi O-O-O (în SAN, cu litera majusculă "O"), sau 0-0 şi 0-0-0 (notaţie nestandard, dar întâlnită - folosind cifra "0").

De remarcat că am precizat întâi "O-O-O" şi abia apoi, "O-O"; dacă am folosi /^O-O|^O-O-O/ atunci pentru "O-O-O" funcţia match() ar da ca rezultat un tablou de două potriviri: [O-O, O-O-O].

Schiţa implementării algoritmului de analiză PGN

Folosind aceste două şabloane de mutare putem formula acum operaţia EXTRACT(mutare SAN) din algoritmul de analiză PGN prezentat mai sus; dar desigur, prezentăm formularea acestei operaţii în contextul unei schiţe de implementare completă, a algoritmului menţionat:

_extract_pgn: function() {
    var PGN = this.element.val();
    
    /* ... */    // extrage tagurile PGN, în this.tags{}
    /* ... */   // extrage "comentariu iniţial" în this.in_comm
    /* ... */    // elimină câmpul "rezultat" din finalul PGN

    // acum PGN are numai mutări (numerotate, marcate NAG), comentarii şi variante
    
    // EXTRACT() mutări, comentarii, variante - verificând sintaxa PGN 
    var thm = 0,    // indexul curent  al obiectului Move() în tabloul this.moves[]
        san = "",   // mutarea tocmai extrasă din PGN 
        /*...*/  // alte variabile (pentru NAG, variantă, comentariu)   
        ERR = false; // sintaxă greşită (ERR = true), în cursul traversării PGN
    
    // şablonul mutărilor SAN şi şablonul pentru rocadă (stabilite mai sus)
    var rg_move = /^[KQRBN]?[a-h]?[1-8]?x?[a-h][1-8](?:\=?[QRBN])?/,
        rg_castle = /^O-O-O|^0-0-0|^O-O|^0-0/;
    
    var mtch, tmp, len_crt = PGN.length; // lungimea curentă a şirului PGN

    while ((len_crt > 0) && !ERR) {
        PGN = PGN.replace(/^[ |\t]+/, '');  // elimină spaţiile iniţiale
        // PGN-curent  începe cu "număr de mutare"? (EXTRACT număr-mutare)
        if (mtch = PGN.match(/^\d+\.{1,3}\s*/)) { 
            PGN = PGN.replace(mtch, ""); // elimină "număr-mutare" (afectând PGN-curent)
            if (PGN.length == 0) {
                /* eroare: PGN nu se poate termina cu "număr-mutare" */
            }
        }
        // PGN-curent se potriveşte cu şablonul principal de mutare? (EXTRACT move)
        if (mtch = PGN.match(rg_move)) { 
            // rezolvă defectul de şablon menţionat pentru "mutare de pion şi captură"
            if (/^\d/.test(mtch)) { // nu-i corect "3e4" şi nici "3xf4"
                this.errors = " SAN notation error for move " + mtch + ". ";
                ERR = true; break;
            }
            tmp = new Move();  // înscrie SAN într-un obiect Move() 
            tmp.SAN = mtch;    // şi adaugă acest obiect în tabloul this.moves[]
            this.moves[thm++] = tmp; 
            PGN = PGN.replace(mtch, ""); // avansează în PGN, peste mutarea curent extrasă
            PGN = PGN.replace(/^\s+/, ""); // elimină spaţiile rămase la începutul PGN
        }
        // PGN-curent se potriveşte la început cu şablonul de rocadă?
        if (mtch = PGN.match(rg_castle)) { 
            tmp = new Move();
            tmp.SAN = mtch;
            this.moves[thm++] = tmp;
            PGN = PGN.replace(mtch, "").replace(/^\s+/, "");
        }

        /* IF-uri pentru celelalte potriviri posibile (NAG, comentariu, variantă)  */

        /* Dacă nu s-a reuşit să se avanseze în şirul PGN (ajungând aici, lungimea PGN-curent
           a rămas aceeaşi cu cea iniţială) - înseamnă că pe locul curent analizat avem un 
           caracter care nu poate fi recunoscut nici ca mutare, nici ca variantă, etc. */
        if (len_crt == PGN.length) {
            /* caracter invalid PGN */                 
            ERR = true; break;
        }
        len_crt = PGN.length; // lungimea şirului PGN rămas
    }

    if (ERR) alert(this.errors);
},

Apelând this._extract_pgn(); în cursul metodei _init(), obţinem tablourile interne pentru tagurile PGN şi pentru mutările SAN din partida transmisă - putând atunci constitui conţinuturile diverselor diviziuni create în momentul instanţierii widget-ului.

Este drept că încă nu am menţionat o diviziune destinată să păstreze comentariile şi variantele; ea poate fi implicată adăugând în _create() '<div class="AnnoList"></div>' (imediat după linia pentru diviziunea .MoveList) şi setând cam aceleaşi proprietăţi CSS ca şi pentru diviziunea mutărilor (în principal, float: left - încât să alăturăm div.AnnoList la dreapta div.MoveList) - vezi (IX).

În principiu, aproape am terminat: mai trebuie făcută o legătură între mutările din .MoveList (înscrise aici din this.moves[], constituit mai sus de către _extract_pgn()) şi handlerele de navigare introduse anterior - "legătură" care să asigure că la click pe o mutare din listă (sau pe un buton de navigare) să fie înscrisă pe tablă poziţia rezultată în urma mutării respective.

Dar această "poziţia rezultată" înseamnă un şir FEN; ca să obţii acest FEN, trebuie sau să prelucrezi FEN-ul precedent, sau (mult mai simplu) să faci mutarea pe o tablă auxiliară (actualizată cu fiecare mutare); mai trebuie ca FEN-ul obţinut să fie unul corect, deci trebuie să te asiguri că mutarea este legală în poziţia respectivă - iar _extract_pgn() a verificat doar corectitudinea sintactică a mutărilor SAN (mutarea Ra1a3 poate fi corectă sintactic, dar poate fi ilegală dacă pe a2 există un pion alb).

Ajungem astfel la concluzia că este obligatoriu să implicăm şi un mecanism de verificare a legalităţii unei mutări (deci… chiar nu am terminat).

vezi Cărţile mele (de programare)

docerpro | Prev | Next