Ne propunem să realizăm o aplicaţie Web pentru şah prin corespondenţă, folosind javascript şi biblioteca prototype.js (desigur şi perl pe partea de server). Deocamdată prezentăm aici doar un experiment incipient, conducând la o mică aplicaţie (v. buildboard.zip) care modelează modest "tabla de şah" şi permite efectuarea de "mutări" (precum şi vizualizarea codului generat pentru tablă).

Motivaţii

Există destule servere de Chess ("play chess online"), destule programe de "jucat şah" şi chiar lucrări de doctorat referitoare la "computer chess". Marea majoritate a acestor programe au scopuri ambiţioase şi în privinţa realizării conexiunilor "online" între parteneri, şi în privinţa oferirii a diverse facilităţi puternice (cea mai importantă fiind posibilitatea de a analiza poziţii).

Abia în ultimii doi-trei ani, s-au făcut câteva programe de şah folosind javascript (ceea ce pare o alegere mai rea, faţă de C sau perl) şi anume tot de tip "play chess online" (a căuta cu Google "chess+javascript").

Eu am abandonat acum câţiva ani "şahul prin corespondenţă", dar mai joc câte o partidă amicală prin e-Mail; mi-e greu să tot citesc mutările făcute şi să reconstitui mereu poziţiile pe tablă (pe tabla reală); mi-ar fi mult mai comod (dar chiar şi partenerului) dacă aş avea un site pe care să se păstreze lista mutărilor efectuate (pentru toate partidele în curs) şi care să ofere posibilitatea vizualizării poziţiilor şi să înregistreze mutarea pe care o decide jucătorul (acesta va accesa site-ul când doreşte să răspundă, putând să vizualizeze poziţia, mutările sau variantele proprii de continuare, să decidă o continuare şi să o înregistreze, etc.; sau, desigur... să-şi aleagă alt partener dintre cei înscrişi).

Cu alte cuvinte… iată o idee "nouă" - şah prin corespondenţă în browser (e suficient să se folosească javascript, fiindcă nu se pune problema analizei poziţiei; nu se pune nici problema delicată a susţinerii în timp a conexiunilor simultane). Iar aceasta este chiar o idee valoroasă, fiindcă ar uşura din toate punctele de vedere organizarea turneelor oficiale — şi tocmai de aceea, deja nu mai este o idee nouă: căutând "correspondence + chess + javascript" am dat imediat peste o aplicaţie superbă de "turnee de şah prin corespondenţă în browser", la adresa //www.schemingmind.com/. Încât, cam ca de obicei - trecem să ne ocupăm de "reinventarea roţii", cu dorinţa de a înţelege problemele, cu propriile instrumente de lucru şi cu propriile speranţe.

^TOP^
Crearea unei table de şah într-un document HTML

Să facem întâi o tablă de şah (cu toate piesele) şi o funcţie care să permită mutarea unei piese (fără să avem în vedere acum şi legalitatea mutării). Vom avea mereu de ales între două posibilităţi de lucru, una mai rea şi una mai bună; deocamdată vom prefera pe cea "mai rea", fiindcă de obicei este cea mai simplă dintre cele două.

Piesele vor fi desigur nişte IMG-uri (care pot fi preluate de pe Internet); putem angaja 12 IMG-uri (6 piese albe şi 6 negre), sau am putea crea un singur IMG cu toate piesele (urmând să folosim o specificaţie CSS corespunzătoare fiecărei piese şi proprietatea CSS background-position pentru a selecta piesa dorită din IMG-ul pieselor). Este clar că a doua variantă este mai bună (browserul trebuie să încarce un singur IMG, nu 12), dar pentru simplitate, o vom folosi pe prima.

Realizăm tabla de şah folosind <table> (alternativa este CSS: proprietatea float ar permite alăturarea pătrăţelelor pe linie). Aspectul esenţial (el va permite mutarea piesei) este acela de a asocia fiecărui câmp al tablei un atribut ID şi alegem ca valori ID chiar notaţia obişnuită a câmpului ([a..h][1..8]); funcţia "mută_piesa" va primi ca parametri ID-ul câmpului de plecare (de exemplu, "e2") şi ID-ul destinaţiei ("e4").

Contrar aparenţelor, avem totuşi două variante: construcţia tablei corespunzătoare poziţiei iniţiale a pieselor (ceea ce este varianta obişnuită şi aparent, singura); sau, construcţia tablei pe baza unui parametru care să precizeze o poziţie legală oarecare (şi e clar cu ce începem...).

Funcţiile javascript (cu "simplificări" din prototype.js) care asigură injectarea în document a unei table de şah sunt redate pe următoarea "imagine" highlight-ificată a fişierului HTML respectiv:

    1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
    2 	"http://www.w3.org/TR/html4/strict.dtd">
    3 <head>
    4 <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
    5 <meta http-equiv="Content-Script-Type" content="text/javascript">
    6 <script type="text/javascript" src="static/prototype.js"></script>
    7 
    8 <style>
    9 td {width:40px; height:40px; text-align:center;}
   10 .white {background: #FFFFFF;} /* clasa CSS a câmpurilor "albe" */
   11 .black {background: #CCCCCC;} /* clasa CSS a câmpurilor "negre" */
   12 </style>
   13 
   14 <script>
   15 var img_path = "static/"; // calea pentru PNG-urile pieselor
   16 var bgc = ['black','white']; // background-ul alternativ al câmpurilor
   17 var a_h = $A($R('a','h')); // tabloul ['a','b','c','d','e','f','g','h']
   18 
   19 function set_board(divb) { divb = $(divb);
   20    var docwr = "<table border='0' cellspacing='0' cellpadding='0'>";
   21    for (row=0;row<8;row++) {
   22       var q = row % 2; // liniile pare (impare) încep cu câmp "negru" ("alb")
   23       docwr += "<tr>";
   24       for (col=0; col<8; col++) {
   25          docwr += "<td class='" + ( q? bgc[col % 2] : bgc[(col+1)%2] ) + "'>";
   26          docwr += "<img id='" + a_h[col] + "" + (8-row) + "'/>";
   27          docwr += "</td>";
   28       }
   29       docwr += "</tr>";
   30    }
   31    docwr += "</table>"; // pentru verificare: alert(docwr);
   32    divb.innerHTML = docwr;
   33    set_pos_initial(); // mai general: set_pos(şir_FEN) înscrie o poziţie reprezentată în FEN
   34 }
   35 
   36 function set_pos_initial() {
   37    var w_p = $w('wrook.png wknight.png wbishop.png wqueen.png wking.png');
   38    var b_p = $w('brook.png bknight.png bbishop.png bqueen.png bking.png');
   39    a_h.each(function(col){
   40       $(col+'2').src = img_path + 'wpawn.png';
   41       $(col+'7').src = img_path + 'bpawn.png';
   42    });
   43    var ics = [0,1,2,3,4,2,1,0]; var i = 0, j = 0;
   44    a_h.each(function(col){
   45       $(col+'1').src = img_path + w_p[ics[j++]];
   46       $(col+'8').src = img_path + b_p[ics[i++]];
   47    });
   48 }
   49 
   50 </script>
   51 </head>
   52 
   53 <div id="divb"></div>
   54 
   55 <script>
   56    Event.observe(window, 'load', function() { set_board('divb');} );
   57 </script>

Biblioteca prototype.js (vezi linia 6) se poate descărca de la prototypejs.org; sunt documentate acolo şi metodele utilitare $() (linia 19), $A() şi $R() (linia 17), $w() (liniile 37, 38) precum şi iteratorul each() (care aplică o anumită funcţie pe toate elementele unei colecţii; vezi liniile 39, 44).

Câmpurile tablei de şah alternează în privinţa culorii de "background". Definind clasele CSS ".white" şi ".black" (vezi liniile 10, 11) am creat posibilitatea modificării celor două culori prin simpla modificare a definiţiilor acestor clase CSS (în loc de a umbla la atributele de culoare ale tuturor celor 64 de elemente <td>). În linia 25 se creează elementele <td> care reprezintă pătrăţelele tablei şi se înscrie fiecăruia câte un atribut "class" de valoare fie "white" (browserul îi va aplica definiţia CSS existentă pentru ".white"), fie "black". Anume, valoarea atributului "class" este determinată prin expresia
            ( q? bgc[col % 2] : bgc[(col+1)%2] )
unde "q" este definit în linia 22, iar "bgc" în linia 16; această expresie condiţională (obişnuită în multe limbaje, inclusiv în C) modelează simplu următoarea regulă simplă: trebuie pusă valoarea "black" pentru câmpurile 0..7 de rang par aflate pe liniile 0..7 de rang par şi de asemenea, pentru câmpurile 0..7 de rang impar aflate pe liniile 0..7 de rang impar.

Fiecare element <td> conţine (vezi linia 26) un element <img>, căruia i se înscrie atributul "id" cu valoarea dată de notaţia obişnuită a câmpurilor ("a1", "a2",..., "h8"). Atributele "id" sunt folosite în funcţia set_pos_initial() pentru identificarea elementelor <img> pe care trebuie "aşezată" o piesă sau alta; de exemplu, în secvenţa de linii 39—42 pentru fiecare col = 'a'..'h' (din tabloul "a_h" definit în linia 17) se constituie prin concatenare identificatorul "a2" (apoi "b2", ..., "h2"), se determină elementul din document corespunzător (cu $('a2'), adică "getElementById") şi se înscrie în elementul <img> respectiv atributul "src" cu numele fişierului PNG (vezi liniile 37, 38) corespunzător piesei care trebuie să stea pe câmpul respectiv.

^TOP^
Verificarea conţinutului generat de funcţii

De multe ori, cea mai bună modalitate de a înţelege programul sau de a verifica rezultatul, constă în vizualizarea conţinutului generat de funcţii; de exemplu, dacă în linia 31 se adaugă apelul alert(docwr), atunci la încărcarea fişierului HTML în browser se va "afişa" conţinutul generat de set_board() (înainte de apelarea celeilalte funcţii). Putem proceda şi altfel (nu de alta, dar conţinutul ferestrei generate de "alert()" nu poate fi copiat în manierele obişnuite): adăugăm în fişier (de exemplu la linia 52) un element <div id="view-content"></div> şi înlocuim "alert"-ul menţionat mai sus cu
          $('view-content').innerHTML = docwr.toString().escapeHTML();
(ceea ce se afişa în fereastra de "alert", acum se va înscrie drept conţinut în elementul "view-content", putând astfel să fie salvat, copiat, etc.). Redăm parţial (şi cu reformatarea firească) conţinutul obţinut astfel:

     <table border='0' cellspacing='0' cellpadding='0'>
     <tr>
         <td class='white'><img id='a8'/></td>
         <td class='black'><img id='b8'/></td>
         <td class='white'><img id='c8'/></td>
         <td class='black'><img id='d8'/></td>
         <td class='white'><img id='e8'/></td>
         <td class='black'><img id='f8'/></td>
         <td class='white'><img id='g8'/></td>
         <td class='black'><img id='h8'/></td>
     </tr>

Dar bineînţeles că avem şi alte procedee de verificare, în funcţie de browserul folosit; Firefix oferă în Web Developer Toolbar, meniul Information în care există opţiunea Display Id & Class Datails care ne permite să obţinem:

N-am observat la timp, dar acum - vizualizând întreaga construcţie - este lesne de văzut un defect de principiu: folosind ca ID-uri notaţiile obişnuite pentru câmpuri (#a1..#h8), ne anulăm posibilitatea firească de a instanţia o a doua tablă de şah, într-un acelaşi document (s-ar găsi două elemente cu acelaşi ID...); acest defect este uşor de corectat (dacă este remarcat la timp) şi va trebui să-l avem în vedere când vom ajunge eventual, să modelăm tabla de şah în termenii claselor de obiecte.

^TOP^
Funcţia pentru mutarea unei piese

La încărcarea unui document HTML, browserul face o analiză a conţinutului şi construieşte o structură arborescentă de obiecte de memorie (numită DOM, Document Object Model) care conţine date şi funcţii, atât ale documentului analizat cât şi ale browserului. "Rădăcina" arborelui construit este obiectul window (cu aproape 100 de "membri"); document este un "child" al obiectului "window" (şi are aproape 150 de membri, de exemplu: document.write()), iar body este un obiect DOM ("child" al "document"-ului) care corespunde elementului HTML <body> şi reprezintă (în memorie, arborescent) conţinutul vizibil al documentului încărcat.

În general, obiectele DOM au asociate diverse "handlere", adică apeluri de funcţii (ale browserului, sau existente în documentul analizat) executate în momentul sesizării unor anumite evenimente - produse fie ca urmare a interacţiunii externe (a utilizatorului) asupra elementelor vizibile (evenimente ca "onClick", "onMouseOver", "onSubmit", etc.), fie setate automat de către browser pentru a semnala o schimbare produsă în starea documentului (de exemplu, "onLoad" este declanşat de browser în momentul încheierii încărcării documentului - adică de fapt, după ce browserul a creat DOM-ul corespunzător).

Linia 56 (din fişierul HTML redat mai sus) adaugă între handler-ele asociate obiectului "window", apelul set_board('divb'), ca răspuns la evenimentul "onLoad"; astfel că, după ce DOM a fost creat (deci există 'divb') se va executa funcţia indicată (efectul fiind inserarea în DOM a elementelor <table> etc. necesare vizualizării tablei de şah).

Bun... am creat tabla de şah, dar ea nu are nici o funcţionalitate asociată (este o poză, pentru care era suficient de prevăzut un element <img> şi-atât). Să vedem acum, cum anume putem permite utilizatorului să mute o piesă pe tablă.

Ca idee, avem iarăşi mai multe posibilităţi (le dispunem în ordinea crescătoare a dificultăţii de realizare):
   — să adăugăm un <input> în care utilizatorul să indice mutarea (sub forma "e2-e4") şi un "handler" corespunzător (care să identifice câmpul de plecare, să "şteargă" piesa existentă, să identifice câmpul de sosire şi să "pasteze" pe el piesa respectivă);
   — analog, să adăugăm o listă a mutărilor, în care fiecare element are asociat "handler"-ul de efectuare a mutării;
   — să captăm "click"-ul făcut pe câmpul de plecare şi pe cel făcut apoi, pe câmpul de sosire - urmând să se activeze "handler"-ul corespunzător pentru efectuarea mutării;
   — să captăm "onMouseDown"-ul făcut pe piesa care trebuie mutată, urmând să se activeze un "handler" care să urmărească mişcarea de "dragare" a mouse-ului până când apare evenimentul "onMouseUp".

Să realizăm deocamdată, prima idee. Maniera standard (iarăşi avem mai multe!) constă în prevederea în document a unui element <input> pentru notarea mutării şi a unui element <button> pentru declanşarea funcţiei necesare (efectuarea mutării indicate); de obicei, aceste două elemente sunt "ambalate" într-un element <form> - dar aceasta chiar nu este necesar aici, mai ales pentru faptul că nu avem de transmis date la server (şi nici de accesat vreo funcţie de pe server).

 <div class = "subboard">
    <input id = "move" type = "text" value = "e2-e4" size = "5"/>   
    <button onclick = "var move = $('move').value;
                          var arr_m = move.split('-');
                          take_move(arr_m[0], arr_m[1]);"
    >Mută</button>
 </div>

Cele două elemente de interacţiune au fost incluse într-un container <div>, cu scopul de a permite o stilare CSS convenabilă (prin clasa ".subboard"). Funcţia take_move():

  function take_move(from, to) {
     var fr = $(from) || 0, to = $(to) || 0;
     if(fr & to) {
        to.src = fr.src;
        fr.src = ""; // img_path + "spacer.png";
     } else { alert("notaţia mutării este greşită"); }
  }

determină elementele DOM corespunzătoare ID-urilor primite (<img>-urile câmpurilor sursă şi destinaţie; dacă se inserează un "alert(fr)" se va constata că "fr" este un "object HTMLImageElement"); dacă acestea nu există (adică "mutarea" a fost indicată greşit, de exemplu ca "e2-w5") atunci se anulează valorile "from" şi "to" şi se alertează un mesaj de eroare. Apoi, se înscrie atributul "src" al destinaţiei cu valoarea din "src"-ul câmpului de plecare şi se "şterge" imaginea din câmpul de plecare.

Deocamdată, take_move() nu face şi verificarea legalităţii mutării (din punctul de vedere al regulilor jocului de şah), încât este posibilă în acest stadiu, mutarea piesei de pe oricare câmp pe oricare altul (inclusiv, "ştergerea" unei piese). Dar problema legalităţii mutării se poate şi abandona, dacă lăsăm utilizatorului numai posibilitatea de a parcurge o listă de mutări (verificate în prealabil), de exemplu fişa unei partide (apelul funcţiei take_move() fiind declanşat de click pe mutarea curentă din listă).

^TOP^
Bibliografie

CHESS ALGEBRAIC NOTATIONForsyth-Edwards Notation (FEN)Chess program board representationsAlgebraic chess notationProgramming a Computer for Playing Chess (Claude Shannon, 1950)