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)