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

Modelarea tablei şi jocului de şah (XVI)

JSON | jQuery | javaScript | reprezentare 0x88
2012 jul

Conversia între notaţia obişnuită a câmpurilor şi indecşii corespunzători acestora în .x88Board[] poate fi uşurată, dacă am folosi un tabel TO_x88{} în care (de la bun început) vom fi înregistrat asocierile respective. Va fi util şi un tabel ALL_MOVES{} în care, pentru fiecare piesă şi pentru fiecare câmp [a-h][1-8] - să avem (tot în notaţie obişnuită) lista câmpurilor pe care ar putea muta acea piesă, plecând de pe câmpul indicat.

Un exemplu de utilizare a unor tabele precalculate

Să zicem că tocmai am preluat din textul PGN al partidei, mutarea albului Ne2. Ştim câmpul final 'e2' şi piesa care trebuie mutată - un cal alb. Nu ştim câmpul iniţial al acestui cal - în schimb (vezi sintaxa SAN în (XI) şi (XII)) mai ştim că numai unul singur dintre caii albi, poate muta legal pe e2; prin urmare, putem proceda ca în acest pseudocod:

/* Reducerea posibilităţilor FROM-TO la aceea care este legală */
piece = 2; // codul calului alb
TO = 'e2'; // câmpul final, indicat de SAN (Ne2)
table_cal = ALL_MOVES['N'][TO]; // unde poate muta calul de pe TO = 'e2'
for (i = 0; i < table_cal.length; i++) { 
    FROM = table_cal[i]; // de exemplu: FROM = 'g1', sau FROM = 'c3'  
    if (BOARD[TO_x88[FROM]] == piece) { // există Cal pe câmpul FROM?
        dacă mutarea FROM-TO este legală: // Nc3-e2 lasă regele în şah?
             actualizează x88Board[], conform mutării
             BREAK // şi treci la următoarea mutare din textul PGN
    }
}

În loc de a căuta pe toată tabla, care cai albi pot muta pe 'e2' - am căutat numai pe cele maximum 8 câmpuri preînregistrate în ALL_MOVES['N']['e2'] (câmpurile "TO" pe care poate sări calul din 'e2', sau totuna - câmpurile "FROM" de unde ar putea veni pe 'e2'). Şi am folosit TO_x88{} pentru a transforma FROM din notaţia obişnuită, în index de acces pe BOARD[].

Desigur, am profitat de faptul că mutările calului (şi la fel pentru celelalte piese) sunt reversibile (după mutarea de pe FROM pe TO este posibil şi invers: de pe TO pe FROM); pentru mutările pionului (care sunt ireversibile) va trebui să procedăm altfel decât în pseudocodul de mai sus.

Tabel de conversie la index 0x88 a notaţiei obişnuite

TO_x88{} se poate obţine direct (fără widget-ul pe care îl tot dezvoltăm de ceva vreme aici), încărcând în browser fişierul următor:

<!DOCTYPE html>
<head>
    <script type="text/javascript" src="js/jquery-1.6.2.min.js"></script>
    <script type="text/javascript" src="js/json2.js"></script>
    <title>Tabel de conversie: notaţia obişnuită -> index BOARD[]</title>
</head>
<body>
    <div id="to_x88"></div>
    <script>
function ah18_x88() {
    var a_h = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
    var ah_88 = {}; // câmp [a-h][1-8] ==> index în BOARD[]
    for(var c = 0; c < 8; c++) 
        for(var r = 1; r <= 8; r++) {
            field = a_h[c] + r; // notaţia obişnuită [a-h][1-8]
            // asociază indexul în BOARD[] al câmpului
            ah_88[field] = (field.charCodeAt(0) - 97) + 
                           (field.charCodeAt(1) - 49) * 16;
        }
    return ah_88;
}
$(function() {
    ah_88 = ah18_x88();
    $('#to_x88').html("var TO_x88 = " + JSON.stringify(ah_88));     
});
    </script>
</body>

Vizualizăm sursa generată de browser prin execuţia <script>-ului (în Firefox avem un meniu "View Generated Source") şi copiem conţinutul diviziunii "to_x88" (după o mică reformatare) în fişierul JS al widget-ului nostru (îl numisem mai demult, "brw-sah.js"):

var TO_x88 = { 
    'a1': 0, 'a2': 16, 'a3': 32, 'a4': 48, 'a5': 64, 'a6': 80, 'a7': 96, 'a8': 112,
    'b1': 1, 'b2': 17, 'b3': 33, 'b4': 49, 'b5': 65, 'b6': 81, 'b7': 97, 'b8': 113,
    'c1': 2, 'c2': 18, 'c3': 34, 'c4': 50, 'c5': 66, 'c6': 82, 'c7': 98, 'c8': 114,
    'd1': 3, 'd2': 19, 'd3': 35, 'd4': 51, 'd5': 67, 'd6': 83, 'd7': 99, 'd8': 115,
    'e1': 4, 'e2': 20, 'e3': 36, 'e4': 52, 'e5': 68, 'e6': 84, 'e7': 100, 'e8': 116,
    'f1': 5, 'f2': 21, 'f3': 37, 'f4': 53, 'f5': 69, 'f6': 85, 'f7': 101, 'f8': 117,
    'g1': 6, 'g2': 22, 'g3': 38, 'g4': 54, 'g5': 70, 'g6': 86, 'g7': 102, 'g8': 118,
    'h1': 7, 'h2': 23, 'h3': 39, 'h4': 55, 'h5': 71, 'h6': 87, 'h7': 103, 'h8': 119
};

În fişierul redat mai sus am folosit JSON, pentru a obţine imediat (scutindu-ne de a scrie codul prin care se adaugă apostrofurile, două-puncte, virgulele, etc.) reprezentarea textuală a obiectului ah_88 constituit prin funcţia ah18_x88() - invocând metoda .stringify() a obiectului JSON. De fapt, JSON.stringify() este deja disponibil în unele browsere moderne (şi în acest caz se poate elimina linia din <head> prin care se încarcă "json2.js" în documentul redat mai sus).

Tabelul tuturor mutărilor pieselor

Pentru calculul acestui tabel folosim metoda _gen_moves(), făcând astfel şi o primă testare a generatorului de mutări (vezi (XV)).

Vom proceda astfel: adăugăm (temporar!) widget-ului nostru o metodă publică .allMovesTable(), care constituie obiectul JS conţinând toate mutările pieselor şi îl înscrie (după serializarea lui, cu JSON.stringify()) în diviziunea de document al cărei identificator i-a fost transmis la apelare.

Construcţia unui obiect JS al tuturor mutărilor pieselor

În .allMovesTable() prevedem întâi, şablonul var allMoves = {'N': {}, ...} pentru obiectul JS care trebuie completat; de exemplu allMoves['N']['e2'] va trebui să fie tabloul ['c1', 'c3', 'd4', 'f4', 'g3', 'g1'], conţinând câmpurile pe care poate sări calul aflat pe 'e2' (sau, la fel de valabil: al câmpurilor de unde calul poate veni pe 'e2').

Apoi, se şterg de pe x88Board[] toate piesele care există eventual în momentul apelării metodei; pe parcurs, se va aşeza pe tablă o singură piesă şi se vor genera mutările posibile ale ei - după care piesa va fi ştearsă, reluând pentru altă poziţionare a ei, sau pentru altă piesă.

Culoarea piesei nu contează - încât se alege albul, pentru piese şi pentru partea aflată la mutare; deasemenea, rocadele nu au sens în acest context - încât se resetează this.castle.

Să observăm însă că anularea drepturilor de rocadă este chiar obligatorie: dacă în momentul apelării valoarea .castle ar fi de exemplu 2 (adică albul poate face încă, mutările O-O şi O-O-O), atunci în toate listele de mutări ale piesei se vor adăuga şi câmpurile "TO" ale rocadelor 'g1', 'c1' - dat fiind că _gen_moves() generează toate mutările posibile pentru piesele de pe tablă ale părţii aflate la mutare şi pe de altă parte, rocadele sunt generate fără testarea prealabilă a prezenţei regilor pe câmpurile cuvenite (aceasta rezultând implicit din valoarea curentă .castle).

Mai departe, pentru fiecare piesă (dintre cele cinci existente) şi pentru fiecare câmp al tablei - se generează lista mutărilor (în codificarea binară pe care am introdus-o anterior) şi apoi se reţine din fiecare mutare numai câmpul "TO", constituind cu aceste valori (convertite la notaţia obişnuită) tabloul care se adaugă corespunzător în obiectul allMoves:

allMovesTable: function(id_dest) {
    var allMoves = {
        'N': {}, 'K': {}, 'B': {}, 'R': {}, 'Q': {}
    }; // allMoves['N']['e2'] = ['c1', 'c3', 'd4', 'f4', 'g3', 'g1']

    var BOARD = this.x88Board; // vom avea o singură piesă (albă)
    for (var i = 0; i < 128; i++) BOARD[i] = 0; 

    this.to_move = 0; // fixăm Albul la mutare
    this.castle = 0;  // excludem rocadele (important!) 

    for(var p = 4; p <= 12; p += 2) { // N, K, B, R, Q (piesele albe)
        var piece = PIECE_CHAR[p];
        for(var fld in TO_x88) { // 'a1', 'a2', ..., 'h8'
            BOARD[TO_x88[fld]] = p; // pune piesa p pe câmpul fld
            var moves = []; // codurile mutărilor posibile ale piesei
            this._gen_moves(moves);  
            var to_arr = []; // câmpurile TO din codurile mutărilor
            for(var i = 0, n = moves.length; i < n; i++) {
                var TO = (moves[i] >> 8) & 0xFF; // extrage indexul TO
                to_arr.push(// transformă TO în notaţie obişnuită
                            String.fromCharCode(97 + (TO & 7)) + ((TO >> 4) + 1));
            }
            allMoves[piece][fld] = to_arr;
            BOARD[TO_x88[fld]] = 0; // şterge piesa curentă de pe fld
        }
    }
    $('#' + id_dest).html( // inserează în DOM, la #id_dest
                JSON.stringify(allMoves, null, 2)); 
},

Am adăugat .allMovesTable() în cadrul widget-ului nostru numai în scopul de a obţine tabelul ALL_MOVES{}, de care vom avea nevoie mai încolo (şi eventual, în scopul testării generatorului de mutări); după ce vom fi obţinut acest tabel - putem şterge metoda redată mai sus.

Obţinerea efectivă a tabelului tuturor mutărilor pieselor

Rămâne să invocăm această metodă dintr-un fişier HTML care instanţiază widget-ul nostru - de exemplu (rezumând la strictul necesar):

<!DOCTYPE html>
<head>
    <script src="js/jquery-1.7.2.min.js"></script>
    <script src="js/jquery.ui.core.min.js"></script>
    <script src="js/jquery.ui.widget.min.js"></script>
    <link href="css/pgnbrw.css" rel="stylesheet" type="text/css" />
    <script src="js/pgnbrw.js"></script>
    <title>Tabelul tuturor mutărilor pieselor</title>
</head>
<body>
<textarea id="txtPGN"></textarea> <!-- pentru instanţierea widget-ului -->
<div id="allMoves"></div> <!-- aici vom obţine tabelul tuturor mutărilor -->
<script>
    $(function() {
        $('#txtPGN').pgnbrw({field_size: 20, show_PGN: false, with_FEN: false})
                    .pgnbrw('allMovesTable', 'allMoves');
    });
</script>
</body>

Cum se vede mai sus - am făcut unele modificări de denumiri faţă de (VII) (de când n-am mai redat un HTML pentru instanţierea widget-ului): am renunţat la denumirile "brw-sah.js", "brw-sah.css" (de-acum vor fi "pgnbrw.*") şi am redenumit widget-ul prin .pgnbrw(), în loc de ".fenBrowser()".

Nu derularea unei partide de şah ne interesează, încât am instanţiat widget-ul folosind opţiuni de anulare a unor anumite zone; după instanţiere, .pgnbrw('allMovesTable', 'allMoves') invocă metoda publică definită mai sus, indicându-i (ca parametru) diviziunea "allMoves".

Încărcând în browser, obţinem ceea ce redăm parţial alături. Dedesubtul tablei de şah avem textul tabelului care ne interesează; îl copiem din fereastra browserului într-un editor de text - în gedit. Dacă dorim (mai ales, pentru a verifica uşor mutările generate) putem formata textul respectiv folosind meniul "Search/Replace" (căutăm "}, " şi înlocuim cu "},\n\n", etc.).

În final - înscriem tabelul în "pgnbrw.js" (în zona variabilelor exterioare widget-ului):

var ALL_MOVES = {
   'N': {
       'a1': ['b3','c2'],
       'b1': ['a3','c3','d2'],
       'c1': ['a2','b3','d3','e2'],
       // etc.
       'g8': ['f6','e7','h6'],
       'h8': ['g6','f7']
   },
   'K': {
       'a1': ['a2','b2','b1'], 
       'b1': ['a2','b2','c2','a1','c1'],
       //etc.
       'h8': ['g8', 'g7', 'h7']
   },
   'B': {
       'a1': ['b2','c3','d4','e5','f6','g7','h8'],
       'b1': ['c2','d3','e4','f5','g6','h7','a2'],
       // etc.
       'h8': ['a1','b2','c3','d4','e5','f6','g7']
   },
   'R': {
       'a1': ['b1','c1','d1','e1','f1','g1','h1','a2','a3','a4','a5','a6','a7','a8'],
       //etc.
   },
   'Q': {
       'a1': ['b1','c1','d1','e1','f1','g1','h1','a2','a3','a4','a5','a6','a7','a8',
              'b2','c3','d4','e5','f6','g7','h8'],
       //etc.
   }
};

Avem de folosit acest tabel (în maniera pseudocodului formulat la început) în cursul metodei de validare a mutării curente din textul PGN al partidei: mutarea SAN respectivă ne dă câmpul TO, iar tabelul de mai sus ne furnizează câmpurile FROM posibile pentru acest TO; aceste posibilităţi "FROM-TO" sunt apoi reduse folosind _gen_moves() şi _makeMove(), la aceea care este legală.

vezi Cărţile mele (de programare)

docerpro | Prev | Next