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

Modelarea tablei şi jocului de şah (XVIII)

javaScript | reprezentare 0x88 | widget
2012 jul

Actualizarea poziţiei, în urma unei mutări legale

Legalitatea rocadei a fost stabilită complet chiar în momentul generării mutării, în _gen_moves(). Pentru celelalte mutări posibile este necesară finalizarea verificării legalităţii: dacă prin efectuarea mutării regele propriu ar ajunge în şah, sau dacă regele advers nu mai există (fiind capturat prin mutarea tocmai efectuată) - atunci mutarea este ilegală.

Iar pentru cazul când mutarea este legală, rămân de făcut actualizările necesare în x88Board[] şi în variabilele interne asociate poziţiei ("flagurile" poziţiei) în care se efectuează mutarea.

Metoda _makeMove() primeşte codul binar complet al mutării (vezi (XIV)); întâi se verifică dacă mutarea indicată capturează regele advers - caz în care se încheie (mutarea este ilegală):

/* Primeşte codul binar complet al mutării (4 octeţi, bit-31 = 0):
       move = | 0xxx SPECIAL | FROM | TO | PIECE CAPTURED |
   simulează mutarea pe x88Board[]:
       mută PIECE FROM-TO capturând CAPTERED, ţinând cont de SPECIAL
   dacă regele nu rămâne în şah (mutarea este legală):
       actualizează x88Board[], .castle, .en_pass, .fifty, .to_move */
_makeMove: function( move ) { 
    var BOARD = this.x88Board, // scurtează accesul la .x88Board[]
        to_move = this.to_move,
        castle = this.castle;
    var spec = (move >> 24) & 0x0F, // câmpul SPECIAL (4 biţi)
        from = (move >> 16) & 0xFF, // câmpurile FROM şi TO
        to = (move >> 8) & 0xFF;  
    var p = BOARD[from], // if( (p != PIECE) || (p1 != CAPTURED)) return 0;
        p1 = BOARD[to];
    if ((p1 == 6) || (p1 == 7)) return 0; // s-ar captura chiar regele advers (ilegal)

N-ar fi greu să revenim la _gen_moves() şi să excludem de la generare mutările care capturează chiar regele advers; dar ar trebui adăugat câte un test (p1 == rege) în mai multe locuri, iar astfel de mutări sunt totuşi rare, într-o poziţie dată.

Câmpurile PIECE şi CAPTURED nu sunt necesare aici! Le-am păstrat (în _gen_moves() şi în _makeMove()) fiindcă utilitatea lor apare totuşi, în contextul mai general al unui program care joacă şah.

Mai departe, dacă mutarea nu este "rocadă" - se simulează efectuarea mutării indicate (făcând-o efectiv pe x88Board[] - dar fără a actualiza şi flagurile poziţiei - şi retrăgând-o la sfârşitul verificării) şi dacă regele propriu rămâne în şah - atunci nu se întreprinde nici o modificare:

    // simulează mutarea verificând că regele nu rămâne în şah, dar
    // exceptează rocadele (au fost simulate şi verificate în cursul gen_moves())
    if ((spec != 0x01) && (spec != 0x02)) { // exclude rocadele
        BOARD[to] = p;   // mută piesa p de pe FROM pe TO
        BOARD[from] = 0; // (ignoră piesa în care ar promova pionul, dacă este cazul)
        if (spec == 0x0F) // elimină pionul luat "en=passant", dacă este cazul
            BOARD[to + (to_move ? 16 : -16)] = 0; 
        // câmpul pe care se află regele (chiar TO, dacă p este regele)
        var sqk = (p == (6 | to_move)) ? to: this.sq_king[to_move];
        if (this._isAttacked(sqk, to_move ^ 1)) { 
            BOARD[from] = p; // dacă regele rămâne în şah după mutare,
            BOARD[to] = p1;  // atunci reconstituie poziţia anterioară mutării
            if (spec == 0x0F) { // (pune înapoi pionul luat "en-passant")
                if (to_move) BOARD[to + 16] = 2;
                else BOARD[to - 16] = 3
            }
            return 0; // mutarea este ilegală - încheie complet procesul
        }
        // regele nu rămâne în şah; încheie simularea (retrăgând mutarea simulată)
        BOARD[from] = p; 
        BOARD[to] = p1;
    }

Ajungând aici, mutarea este legală; în acest caz - se actualizează x88Board[] şi flagurile interne asociate (.castle, .to_move, etc.), corespunzător efectuării propriu-zise a mutării.

În cadrul unui program care joacă, înainte de a actualiza poziţia, trebuie salvate valorile specifice poziţiei curente - pentru ca după ce se obţine o evaluare a şansele părţilor în noua poziţie, să se poată reconstitui poziţia precedentă (în care se va încerca o altă mutare dintre cele posibile, evaluând din nou poziţia rezultată, revenind la poziţia precedentă şi repetând - până ce se obţin "suficiente" informaţii pentru a decide care este cel mai "bun" răspuns).

Mai întâi, resetăm .en_pass (altfel, când se va apela _gen_moves() pentru următoarea poziţie, este posibil să se genereze şi o mutare "en-passant" - deşi mutarea din acest moment nu este neapărat o avansare cu două linii a unui pion):

    if (this.en_pass > 0) this.en_pass = -1; // resetează "en_pass" pentru mutarea curentă

Urmează actualizările necesare, în funcţie de valoarea câmpului SPECIAL din codul mutării. Dacă aceasta este una dintre cele pe care le-am convenit pentru mutările de rocadă:

    switch (spec) { /* actualizări în funcţie de valoarea câmpului SPECIAL */
        case 0x01: // O-O
            if (!to_move) { // pentru alb
                BOARD[5] = 10; // turnul (cod=10) din 'h1' (index=7) vine pe 'f1' 
                BOARD[6] = 6;  // regele (cod=6) vine din 'e1' (index=4) pe 'g1'
                BOARD[7] = BOARD[4] = 0 // şterge piesele din vechile poziţii
            } else { // pentru negru
                BOARD[0x75] = 11; // turnul (cod=11) din 'h8' (index=0x77) vine pe 'f8'
                BOARD[0x76] = 7;  // regele (cod=7) vine din 'e8' (index=0x74) pe 'g8'
                BOARD[0x77] = BOARD[0x74] = 0
            }
            this.fifty++; // încă o mutare fără captură şi care nu angajează pioni
            break;
        case 0x02: // O-O-O, 
            if (!to_move) { // la alb
                BOARD[3] = 10;
                BOARD[2] = 6;
                BOARD[0] = BOARD[4] = 0
            } else { // la negru
                BOARD[0x73] = 11;
                BOARD[0x72] = 7;
                BOARD[0x70] = BOARD[0x74] = 0
            }
            this.fifty++;
            break;

Pentru cazul mutărilor reversibile (mutările de piese - nu de pioni - fără capturi) trebuie incrementat şi contorul pentru "regula celor 50 de mutări":

        case 0x03: // mutare fără captură, fără pion (şi fără rocadă)
            BOARD[to] = p;  // pune piesa pe TO, o şterge de pe FROM 
            BOARD[from] = 0;
            this.fifty++;   // actualizează contorul celor 50 de mutări
            break;

Pentru mutările de piesă cu efect de captură şi pentru mutările normale de pion (şi în general, pentru mutarile ireversibile) contorul pentru "regula celor 50 de mutări" trebuie resetat:

        case 0x07: // captură cu o piesă (nu pion)
        case 0x06: // mutare normală de pion (NU-enPassant, NU-cu 2 linii, NU-promovare)
            BOARD[to] = p;
            BOARD[from] = 0;
            this.fifty = 0; // resetează contorul celor 50 de mutări
            break;

Pentru toate mutările de transformare de pion:

        case 0x0C: // transformare de pion alb în Q (damă)
        case 0x0A: 
        case 0x08: 
        case 0x04:
        case 0x0D: // transformare de pion negru în Q (damă)
        case 0x0B:
        case 0x09:
        case 0x05:
            BOARD[to] = spec; // pune pe TO piesa în care s-a transformat pionul
            BOARD[from] = 0;
            this.fifty = 0; // resetează contorul celor 50 de mutări
            break;

Dacă mutarea anterioară (ultima făcută de adversar) a fost o avansare cu două linii a unui pion, atunci pentru acea mutare s-a înscris indexul câmpului de "en-passant" în this.en_pass; ca urmare, pentru mutarea curentă _gen_moves() a găsit this.en_pass > 0 şi atunci a setat în codul mutării câmpul de biţi SPECIAL = 0x0F.

Pentru mutările "en-passant"şi pentru mutările de avansare cu două linii a unui pion:

        case 0x0F: // captură "en-passant"
            if (!to_move) BOARD[to - 16] = 0; // şterge pionul advers luat "en-passant"
            else BOARD[to + 16] = 0;
            BOARD[to] = p;   // pune pionul pe TO,
            BOARD[from] = 0; // ştergându-l de FROM
            this.fifty = 0; // resetează contorul celor 50 de mutări
            break;
        case 0x0E: // avansare cu două linii a unui pion nemişcat anterior
            BOARD[to] = p;
            BOARD[from] = 0;
            this.en_pass = to_move ? // setează .en_pass =
                to + 16 : to - 16;   // indexul câmpului sărit de pion
            this.fifty = 0; // resetează contorul celor 50 de mutări
            break
    } /* încheie actualizările impuse imediat de valoarea SPECIAL */

Afară de actualizările directe operate mai sus în funcţie de valoarea câmpului SPECIAL, mai avem de operat actualizarea drepturilor de rocadă - şi avem două cazuri; partea care mută regele sau turnul (inclusiv, printr-o rocadă) pierde (total sau numai parţial) drepturile de rocadă (dacă mai există):

    /* când mută regele sau un turn, pierde din "drepturile de rocadă rămase" */
    switch (p) { // p este piesa mutată
        case 6: // regele alb
            if (castle & 3) castle &= 0x0C; // resetează biţii rocadei albe (bit_0, _1)
            this.sq_king[0] = to; // păstrează noua poziţie a regelui
            break;
        case 7: // regele negru
            if (castle & 0x0C) castle &= 3; // resetează biţii rocadei negre (bit_2, _3)
            this.sq_king[1] = to;
            break;
        case 10: // turn alb
            if ((castle & 1) && (from == 7)) 
                castle ^= 1; // anulează bitul de rocadă mică albă
            else {
                if ((castle & 2) && (from == 0)) 
                    castle ^= 2 // anulează bitul de rocadă mare albă
            }
            break;
        case 11: // turn negru
            if ((castle & 4) && (from == 0x77)) 
                castle ^= 4; // anulează bitul de rocadă mică neagră
            else {
                if ((castle & 8) && (from == 0x70)) 
                    castle ^= 8 // anulează bitul de rocadă mare neagră
            }
            break
    }

Iar dacă este cazul unei capturi de turn, atunci adversarul pierde "drepturile de rocadă rămase" pentru rocada corespunzătoare turnului capturat:

    /* capturarea unui turn afectează drepturile de rocadă ale adversarului */
    switch (p1) { // p1 este piesa capturată
        case 10: // negrul capturează turnul alb din 'h1' sau din 'a1'
            if ((castle & 1) && (to == 7)) 
                castle ^= 1; // anulează bitul de rocadă mică albă
            else {
                if ((castle & 2) && (to == 0)) 
                    castle ^= 2 // anulează bitul de rocadă mare albă
            }
            break;
        case 11: // albul capturează turnul negru din 'h8' sau din 'a8'
            if ((castle & 4) && (to == 0x77)) 
                castle ^= 4; // anulează bitul de rocadă mică neagră
            else {
                if ((castle & 8) && (to == 0x70)) 
                    castle ^= 8 // anulează bitul de rocadă mare neagră
            }
            break
    }

În final, trebuie incrementat contorul perechilor de mutări în cazul când mutarea curentă este efectuată de către negru şi trebui comutat .to_move, indicând că urmează la mutare cealaltă parte:

    if (to_move) this.nr_move++; // când mută negrul, actualizează contorul de mutări
    to_move ^= 1;   // urmează să mute cealaltă parte
    this.to_move = to_move; // păstrează intern flagurile constituite 
    this.castle = castle;
    return 1; // mutarea este legală (şi x88Board[] a fost actualizat conform acesteia)
}, /* încheie _makeMove() */

Desigur că pentru construirea unei funcţii precum cea redată mai sus, sunt necesare pe parcurs diverse testări; adevărul este însă că _gen_moves() şi _makeMove() au fost preluate aici (în PGN-browser) dintr-un program care joacă şah - anume din chessJSengine - iar testarea lucrurilor n-a mai fost necesară şi acum (fiind testate anterior, în cursul constituirii programului care joacă).

Ne propunem totuşi să imaginăm o manieră de testare - spre a ne convinge că lucrurile funcţionează corect - şi pentru cazul de faţă (când generarea mutărilor se face numai pentru poziţia curentă, nu şi pentru răspunsurile posibile ale partenerului - ca în cazul unui program care joacă).

vezi Cărţile mele (de programare)

docerpro | Prev | Next