Ne-a rămas de formulat metoda vizată deja în (IX) prin _setMoveList() şi pe care o redenumim acum _setListDivs(); ea trebuie să completeze câmpurile FEN în obiectele Move() din tabloul this.moves[] (apelând _san_legal(SAN), cu SAN = this.moves[i].SAN) şi în acelaşi timp, să insereze în diviziunile documentului rezervate listei mutărilor şi comentariilor, link-uri şi handlere corespunzătoare - încât "click" pe mutare să determine actualizarea diagramei afişate.
_setListDivs() leagă între ele cele două infrastructuri pe care le-am creat în cadrul widget-ului: DOM şi CSS pe de o parte (care creează şi "afişează" în document diagrama de şah, butoanele şi handlerele de navigare, diviziunile pentru lista mutărilor, etc.) şi infrastructura pe care am denumit-o generic BOARD (servind pentru verificarea legalităţii mutărilor şi pentru constituirea şirurilor FEN pe care se bazează mecanismul nostru de navigare).
DOM-ul este constituit în cursul metodei _create() (ţinând cont de specificaţiile "pgnbrw.css"), în momentul instanţierii widget-ului pgnbrw() pe elementul <textarea> indicat. În cursul metodei _init() (apelată automat imediat după _create(), sau ulterior prin handler-ul butonului "LOAD") se analizează textul PGN din <textarea> - folosind _extract_pgn() - obţinând tabloul moves[] cu obiecte Move() reprezentând mutările extrase din PGN; apoi, se constituie o infrastructură BOARD - folosind _setBOARD() - şi în final, se apelează _setListDivs() care validează mutările din moves[] (completând şi câmpurile FEN) şi le inserează ca link-uri în diviziunea div.MoveList:
_init: function() { this.tags = {}; // păstrează tagurile extrase din PGN (tags['White'], etc.) this.moves = []; // obiecte Move() pentru mutările partidei this.in_comm = ""; // comentariul iniţial (precede lista mutărilor în PGN) this.errors = ""; // mesaje cumulate pe parcursul analizei textului PGN this.x88Board = new Array(128); // reprezentarea 0x88 a tablei this.sq_king = [0, 0]; // păstrează indecşii câmpurilor ocupate de regi this._extract_pgn(); // analizează PGN, extrăgând this.tags, this.moves, etc. // alert( JSON.stringify(this.moves, null, 2) ); this.FEN_initial = this.tags['FEN'] || FEN_STD; this._setBOARD(this.FEN_initial); // setează x88Board[] şi "flagurile" poziţiei this._firstMove(); // "afişează" poziţia iniţială (apelând _setDiagram(fen)) this._setListDivs(); // validează mutările din moves[], completând moves[i].FEN // şi înscrie link-uri identificatoare în div.MoveList if (this.k_Info && this.tags['White']) { // completează div.GameInfo }; this.brwtimer = null; // utilizat de _auto_nextMove() },
Linia alert(JSON.stringify(this.moves)) (marcată "comentariu", în codul de mai sus) ne aminteşte că înregistrările din .moves[] sunt obiecte cu proprietăţile "SAN", "FEN", etc. (conform definiţiei Move(), redată în (XI)); selectăm una dintre înregistrările alertate:
{ "SAN": ["Rc3"], "FEN": "", "mark": "!", "NAG": 0, "variant": " White also wins in 5... Kc8 6. Rb4 Kd8 7. Rf4 Re1 ... ", "comment": " The rook moves to a bad rank." },
A rămas de completat câmpul "FEN", cu şirul FEN corespunzător poziţiei rezultate după ce mutarea indicată în "SAN" ar fi efectuată în poziţia curentă. Pentru aceasta, _setListDivs() va invoca _san_legal(SAN), unde SAN este valoarea câmpului "SAN" din mutarea curentă.
Dar să observăm că valoarea câmpului "SAN" nu este un şir, ci este un tablou [] care conţine şirul dorit; aceasta explică (abia acum!) de ce apăruse obligaţia de a converti parametrul SAN în obiect String(), în debutul metodei _san_legal(SAN) - vezi (XVII).
Dar de ce este "SAN" un tablou, când ar fi trebuit să fie un simplu şir? Explicaţia o găsim în cuprinsul metodei _extract_pgn() - cea care a creat înregistrările Move() din tabloul .moves[]:
/* din (XI), _extract_pgn() */ // PGN-curent se potriveşte la început cu şablonul de rocadă? if (mtch = PGN.match(rg_castle)) { tmp = new Move(); tmp.SAN = mtch; // este tablou, NU şir tmp.SAN = mtch[0] this.moves[thm++] = tmp; PGN = PGN.replace(mtch, "").replace(/^\s+/, ""); }
Metoda .match(şablon) returnează un tablou, conţinând acele subşiruri găsite în obiectul String() din care este apelată care se potrivesc cu şablonul indicat ca parametru; prin urmare, tmp.SAN = mtch conduce la înregistrări precum cea redată mai sus ("SAN": ["Rc3"]).
Punând însă tmp.SAN = mtch[0], vom obţine "SAN" ca şir ("SAN": "Rc3"); avem de făcut această mică îndreptare în trei locuri din cuprinsul metodei _extract_pgn() (de două ori pentru "SAN" şi o dată pentru .mark = mtch[0];) - după care putem elimina conversia supărătoare (devenită acum inutilă) SAN = String(SAN) de la începutul metodei _san_legal().
<div class="MoveList"> (la care ne referim prin div.MoveList) va trebui să conţină link-uri pentru mutări, iar div.AnnoList va conţine "adnotările" din textul PGN (variantele şi comentariile):
_setListDivs: function() { var movs = this.moves, // tabloul obiectelor Move() errors = false, html = [], // va cumula ceea ce trebuie înscris în div.MoveList in_st = [], // va cumula ceea ce trebuie înscris în diviziunea adnotărilor mdiv = this.k_moves; // elementul DIV.MoveList prevăzut pentru lista mutărilor if (this.in_comm) // comentariul iniţial, dacă există (de înscris în DIV.AnnoList) in_st.push('<div class="ann-init">', this.in_comm, '</div>'); if (this.to_move) // numerotează prima mutare cu "N...", dacă mută negrul html.push("<span class='numberOfMove'>", this.nr_move, "...</span>");
În pgnbrw.css avem de prevăzut eventual, anumite definiţii pentru aspectul diviziunilor create (.ann-init, .numberOfMove, apoi .oneMove, etc.); acestea nu conţin nimic special - doar specificaţii de font, de margini, de culoare. Am ţinut cont (în al doilea "if") de faptul că PGN poate conţine un tag [FEN "fen"] definind poziţia iniţială cu negrul la mutare şi cu this.nr_move = N mutări anterioare - caz în care numerotăm prima mutare sub forma "N...".
Mai departe, se parcurg obiectele din this.moves[]; se verifică - prin _san_legal() - dacă mutarea este legală şi în acest caz se determină - prin _getFEN() - şirul FEN pentru poziţia BOARD rezultată:
// parcurge obiectele Move() din this.moves[] (în ordinea mutărilor) for(var i = 0, n = movs.length; i < n; i++) { var move = movs[i].SAN; if(this._san_legal(move)) { // dacă mutarea este legală, determină var fen = this._getFEN(), // şirul FEN al poziţiei rezultate w_to_move = (/ w /.test(fen)); var mark = movs[i].mark, // scurtează accesul la proprietăţile din Move() nag = movs[i].NAG, variant = movs[i].variant, comm = movs[i].comment; movs[i].FEN = fen; // completează câmpul FEN din obiectul Move() curent
Pentru mutarea curentă - fie pentru exemplu i=5, cu SAN="Nge2" - creează un link de forma
<a href="" chessMoveId="5">Nge2</a>, în care atributul chessMoveId are ca valoare indexul obiectului Move() din this.moves[]:
// creează un link cu atributul "chessMoveId" = indexul mutării if (!w_to_move) // precede mutările albului cu "numărul mutării" html.push("<span class='numberOfMove'>", this.nr_move, ".</span>"); html.push("<a href='' class='oneMove' chessMoveId='", i, "'>", move, "<span class='moveMark'>", mark, "</span></a>"); html.push((w_to_move ? "<br>": " "));
Dacă mutarea are "adnotări", acestea sunt înscrise într-un anumit format în div.AnnoList. După încheierea parcurgerii obiectelor Move(), sau după întâlnirea eventuală a unei mutări pentru care _san_legal(move) returnează "false" - se inserează în document elementele HTML create:
if (nag || variant || comm) { // există adnotări pentru mutarea curentă var nrm = w_to_move ? (this.nr_move - 1 + '...') : this.nr_move + '.'; in_st.push('<div class="ann-crt"><p>', nrm, "<a href='' chessMoveId='", i, "'>", move, mark, "</a></p>"); if (nag) in_st.push('<div class="ann-nag">', NAG[nag], '</div>'); if (variant) in_st.push('<div class="ann-variant">', variant, '</div>'); if (comm) in_st.push('<div class="ann-comment">', comm, '</div>'); in_st.push('</div>'); } } else { errors = true; break; } } // încheie parcurgerea obiectelor din this.moves[] mdiv.html(html.join('')); // inserează link-urile create în div.MoveList this.k_annot.html(in_st.join('')); // inserează adnotările
În final, montăm un handler de click pentru div.MoveList şi unul pentru div.AnnoList, care identifică elementul <a> de unde s-a propagat evenimentul "click" şi îl transmite metodei _go_to_pos() - aceasta va identifica obiectul Move() corespunzător pe baza valorii atributului chessMoveId din link-ul primit şi va folosi câmpul .FEN din acest obiect pentru a actualiza afişarea diagramei:
// handlere de click pentru lista mutărilor şi lista adnotărilor var self = this; // salvează o referinţă la instanţa curentă (pentru a putea // invoca metodele proprii din cadrul handlerelor) this.k_moves.click(function(event) { // click în div.MoveList var tgt = $(event.target); if (tgt.is('a')) self._go_to_pos(tgt); // poziţia afişată va fi actualizată corespunzător return false; // mutării din div.MoveList de unde provine click-ul }); this.k_annot.click(function(event) { // click în div.AnnoList var tgt = $(event.target); if (tgt.is('a')) { self.k_moves.scrollTop(0); // scroll-ează div.MoveList, la prima mutare self._go_to_pos(tgt, true); // div.MoveList va fi derulată la mutarea curentă } return false; }); if (errors) alert("Notaţie greşită la o mutare din PGN (vezi " + this.nr_move + ")"); }, // încheie metoda _setListDivs()
Dacă se întâlneşte Move() pentru care _san_legal() returnează "false", atunci parcurgerea tabloului .moves[] se opreşte şi se alertează un mesaj de eroare, indicând numărul mutării respective.
Metoda _go_to_pos() (folosită de handlerele de navigare) a fost redată în (IX), dar în forma cea mai simplă; ea trebuie rescrisă pentru a implica şi derularea listei mutărilor şi a listei adnotărilor - încât mutarea curentă să fie vizibilă în toate situaţiile de provenienţă a "click"-ului (de pe butoanele de navigare, din lista mutărilor, sau a adnotărilor) care a determinat actualizarea afişării poziţiei.
vezi Cărţile mele (de programare)