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)