momente şi schiţe de informatică şi matematică
anti point—and—click

Modelarea tablei şi jocului de şah (VII)

Bash | CSS | Firefox | background-position | jQuery
2012 jun

În (VI) am reuşit să constituim în fişierul "brw-sah.css" proprietăţi CSS de poziţionare procentuală a liniilor şi coloanelor tablei şi respectiv a pieselor dintr-un sprite - cu meritul că aceste poziţionări sunt independente de setul curent de piese şi de orientarea tablei.

Dependenţa faţă de setul de piese

Dimensiunea tablei de şah (indicată anterior în proprietatea ".container"), dimensiunea câmpurilor ei (indicată în ".field") şi dimensiunea pieselor (în ".Piece") depind evident de setul de piese curent. Sunt posibile mai multe variante de lucru, pentru a ţine cont de setul curent NxN.

S-ar putea defini direct '.Piece-N', '.field-N', '.container-N' pentru fiecare N (amintim că am optat pentru N = 20..32 pixeli). Este adevărat că proprietăţile "directe" sunt mai uşor de aplicat (browserul nu are de căutat ascendenţi ai nodului pe care trebuie să aplice proprietatea); în schimb, va trebui mereu să adăugăm sufixul "-N", în cadrul funcţiilor care constituie câmpurile sau plasează piesele (setChessTable() şi setPosition()). Pe de altă parte… există cu siguranţă, metode mai elegante.

O idee tentantă ar fi specificarea "inline" a proprietăţilor respective, direct în metoda _create(). De exemplu, dacă variabila container este obiectul jQuery care corespunde tablei, atunci container .find('div') .css({'width': N +'px', 'height': N +'px', 'position': 'absolute'}); va aplica proprietăţile indicate tuturor celor 64 de elemente <div> corespunzătoare câmpurilor tablei. Dar am amesteca prea mult CSS-inline în funcţii javaScript, ar fi cel puţin incomod de făcut eventuale modificări, iar "eficienţa" poate lăsa de dorit.

Dusă la extrem, această idee "tentantă" ar însemna construirea unui widget pentru a cărui utilizare să fie suficient ca browserul să încarce fişierul javaScript care defineşte widget-ul; nu mai este necesară încărcarea vreunui fişier CSS specific, iar încărcarea altor biblioteci javascript (în cazul nostru jQuery) se poate lăsa în seama metodei _create() (se testează dacă jQuery există în cache; dacă nu, atunci se adaugă un element <script> pentru a forţa browserul să download-eze de undeva, biblioteca respectivă).

Dar soluţia cea mai firească este bazată pe imbricarea anumitor selectori. De exemplu, specificaţia .container-N .Piece va fi aplicată de către browser elementelor cu proprietatea ".Piece" pentru care există un ascendent cu proprietatea ".container-N"; efortul de a căuta în lanţul ascendenţilor înainte de a aplica proprietatea ".Piece" este totuşi, neglijabil.

Alegând "soluţia cea mai firească", va trebui să fim atenţi când vom scrie funcţiile necesare. De exemplu, dacă widget-ul este instanţiat pe mai multe elemente, o selecţie ca $('.Piece') va cuprinde "piesele" din toate aceste instanţe; corect va fi ca fiecare instanţă să păstreze o referinţă internă la propriul .container, urmând să-şi repereze "piesele" relativ la această referinţă.

Deasemenea, trebuie să prevedem că vor apărea şi dependenţe indirecte faţă de N; de exemplu, vom vrea să adăugăm o "bară de navigare" cu diverse butoane pentru parcurgerea partidei - ori dimensiunea acestei bare trebuie corelată cu dimensiunea tablei, fiind astfel dependentă de N.

Prin urmare, ar fi de dorit să specificăm "-N" nu în .container, ci mai "deasupra" - încât nu numai "tabla" şi elementele subordonate ei să fie sub incidenţa lui "-N", dar şi elemente nesubordonate direct tablei, dar ale căror proprietăţi CSS depind de N.

Infrastructura CSS a widget-ului şi testarea ei

Având în vedere cele de mai sus, constituim schema ierarhică următoare:

Setând lăţimea diviziunii .chessmen-N în funcţie de N (să zicem, 9*N), ne asigurăm că lăţimea implicită a unor elemente subordonate (precum .gameInfo) nu va depăşi această valoare.

Setând "font-size" tot aici (în funcţie de N), fontul utilizat în notaţia liniilor şi coloanelor va putea fi proporţionat faţă de această valoare (şi în mod implicit, faţă de N).

Diviziunea .boardFields corespunde tablei de şah şi în raport cu ea vor fi poziţionate şi cele două bare pentru notaţia liniilor şi respectiv a coloanelor.

.boardFields va trebui să aibă dimensiunile de 8*N (plus eventual, 16*lăţimea borderului unui câmp .Field) şi desigur, .Field are dimensiunea NxN.

Având de calculat şi de setat aceste proprietăţi pentru N=20..32, vom imagina un program Bash pentru a obţine fragmentul de fişier CSS necesar; bineînţeles că întâi se cuvine să facem o probă, stabilind exact şi ceea ce va trebui să obţinem prin program pentru fiecare N.

Amânăm setarea "font-size" (vizată în schema de mai sus) până ce vom aborda şi chestiunea notaţiei liniilor şi coloanelor.

/* "brw-sah.css" */
.Col-1, .revBoard .Col-8 {left: 0%}     .Row-8, .revBoard .Row-1 {top: 0%}
.Col-2, .revBoard .Col-7 {left: 12.5%}  .Row-7, .revBoard .Row-2 {top: 12.5%}
.Col-3, .revBoard .Col-6 {left: 25%}    .Row-6, .revBoard .Row-3 {top: 25%}
.Col-4, .revBoard .Col-5 {left: 37.5%}  .Row-5, .revBoard .Row-4 {top: 37.5%}
.Col-5, .revBoard .Col-4 {left: 50%}    .Row-4, .revBoard .Row-5 {top: 50%}
.Col-6, .revBoard .Col-3 {left: 62.5%}  .Row-3, .revBoard .Row-6 {top: 62.5%}
.Col-7, .revBoard .Col-2 {left: 75%}    .Row-2, .revBoard .Row-7 {top: 75%}
.Col-8, .revBoard .Col-1 {left: 87.5%}  .Row-1, .revBoard .Row-8 {top: 87.5%}

.Wh-pawn   {background-position:0%;}    .Bl-pawn   {background-position:100%;}      
.Wh-king   {background-position:-100%;} .Bl-king   {background-position:-1000%;}  
.Wh-queen  {background-position:-200%;} .Bl-queen  {background-position:-900%;}  
.Wh-rook   {background-position:-300%;} .Bl-rook   {background-position:-800%;}  
.Wh-bishop {background-position:-400%;} .Bl-bishop {background-position:-700%;}   
.Wh-knight {background-position:-500%;} .Bl-knight {background-position:-600%;}

.WhField { background: white; }
.BlField { background: lightgrey; }

/* proprietăţi comune (pentru toate valorile -N) */
.boardFields {position: relative; border: 2px solid black;}
.Field {position: absolute; padding: 1px; /* 2px; pentru N > 23 ?*/}

/* proprietăţi specifice setului; exemplu pentru N = 24 */
.chessmen-24 {width: 248px;} /* 8*24 + 32(border) + 24 */
.chessmen-24 .boardFields {width: 224px; height: 224px;} /* 8*24 + 32(border) */
.chessmen-24 .Field {width: 24px; height: 24px; padding: 2px;}
.chessmen-24 .Piece {
    width: 24px; height: 24px; 
    background: url('../images/men24.png'); /* GREŞIT! Revenim mai jos... */
}

În vechiul fişier "brw-sah.css" am modificat cum se vede mai sus unele denumiri (scurtându-le) şi am introdus proprietăţile din schema de mai sus, pentru N = 24. Pentru .Piese am folosit ca şi mai înainte, forma "scurtă" background: url() - ceea ce se dovedeşte în final, greşit…

Din fişierul "brw-sah.js" redăm numai porţiunile modificate faţă de conţinutul prezentat anterior:

/* "brw-sah.js" - modificările faţă de conţinutul prezentat anterior */
    /* ... vezi (IV), (V) ... */
    _create: function() {
        /* ...  ... */    
        this.Tabla = $('<div class="chessmen-24"></div>')
            .insertAfter( inputFEN.next() )
            .html( '<div class="boardFields">' + this._setChessTable() + "</div>" )
            ;//.addClass('revBoard');
        this.boardFields = this.Tabla.find('div:first');
    },
    /* ...  ... */
    _setPosition: function(fen) {
        /* ...  ... */
        this.boardFields.children().each(function(index) {
            var piece = fen64.charAt(index);
            if (piece && piece != " ") {
                var piece_low = piece.toLowerCase(),
                    side = piece == piece_low ? "Bl-" : "Wh-";  
                    pieceClass = side + PIECE_CLASS_NAMES[piece_low];
                $(this).html("<div class='Piece " + pieceClass + "'></div>");
            }
            else $(this).html(""); // şterge piesa veche, dacă există 
        });
    }
    /* ...  ... */

Amintim în sfârşit, fişierul "brw-sah.html"; încărcând în browser, vedem că în principiu lucrurile funcţionează (doar că piesele nu sunt găsite, cu excepţia pionului alb):

va rezulta (totuşi, greşit):

<!-- "brw-sah.html" (vezi (III)) -->

<body>
    <textarea id="getFEN">

    <script>
        $(function() { 
            $('#getFEN').fenBrowser();
        });
    </script>
</body>

Dar de ce au fost reduse toate piesele la background-position: 0; (pionul alb)?

Explicaţia resetării background-position:0, în cazul selectorilor imbricaţi

setPosition() a inserat în DOM aşa ceva: <div class="Piece black-rook"></div>. Browserul aplică proprietăţile de la dreapta la stânga, adică întâi "black-rook" şi apoi "Piece"; deci întâi stabileşte background-position: -800% corespunzător cu proprietatea .black-rook şi după aceeea, stabileşte din .Piece sursa imaginii background: url('...'). Ori pentru background-position avem precizarea:

If the value of this property is not set in a background shorthand property that is applied to the element after the background-position CSS property, the value of this property is then reset to its initial value by the shorthand property.

Aceasta explică de ce background-position a fost resetat la zero, în cazul nostru. În orice caz, nu ne convine să înscriem "-position" împreună cu "url", într-un "background" cu format scurt (definiţiile .black-rook, etc. ar deveni dependente de N - ori tocmai aceasta am vrut să evităm).

Pentru corectare, este suficient să evităm "forma scurtă" background: url() - rescriind .Piece astfel:

.chessmen-24 .Piece { width: 24px; height: 24px; 
                      background-image: url('../images/men24.png');
}

deci folosind direct background-image, pentru a indica fişierul care conţine imaginea.

Obs. Poate că explicaţia pe care am găsit-o (plecând de la ordinea implicită "de la dreapta spre stânga") nu este cea "adevărată" (probabil am putea proba, schimbând ordinea…) - dar ea ne-a condus totuşi, la o soluţie bună şi simplă (ba chiar şi "logică").

Obţinerea fragmentului CSS care este dependent de setul de piese

Pentru fiecare set de piese NxN, avem de înscris într-un fişier cele patru proprietăţi CSS pe care le-am testat mai sus pe N = 24. Este uşor să scriem un program Bash pentru aceasta (dar desigur, putem folosi orice alt limbaj):

#!/bin/bash

css="20-32.css"; # CSS specific fiecărui set de piese
touch $css       # creem fişierul, în directorul curent

padd=''      # .Field are deja padding=1px
add_oriz=16  # (padding-left + padding-right)*8 = 16 pixeli

for set in $(seq 20 32)
do
    if (($set > 23))    # pentru unele seturi, alege padding=2
    then 
        padd='padding: 2px;'
        let add_oriz=32  # (2 + 2)*8 = 32px de adăugat lăţimii
    fi

    let w1=$set*8+$add_oriz; # lăţime pentru .chessmen
    let w2=$w1+$set;         # lăţimea pentru .boardFields

    # înscrie (adaugă) în fişier cele patru proprietăţi CSS
    spec=".chessmen-$set"  # .chessmen-24  etc.
    echo "$spec {width: ${w2}px;}" >> $css
    echo "$spec .boardFields {width: ${w1}px; height: ${w1}px;}" >> $css
    echo "$spec .Field {width: ${set}px; height: ${set}px; $padd}" >> $css
    echo -e "$spec .Piece {width: ${set}px; height: ${set}px;" \
            "background-image: url('../images/men$set.png')}\n" >> $css
done

Desigur, puteam adăuga direct în fişierul nostru "brw-sah.css"; am folosit totuşi un fişier separat ca să putem verifica lucrurile şi nu rămâne decât să copiem conţinutul rezultat în "20-32.css" şi să-l adăugăm la sfârşitul fişierului "brw-sah.css".

Opţiunea instanţierii pe un anumit set de piese

Astfel constituit, "brw-sah.css" este complet, în sensul că permite instanţierea fenBrowser() cu oricare set 20..32 de piese de şah. Dar mai sus n-am făcut decât o probă, indicând direct setul de piese prin this.Tabla = $('<div class="chessmen-24"></div>'), în metoda _create().

Pentru a evita modificarea de la un set la altul, ar trebui creată o variabilă T care să indice setul dorit, folosind în _create() $('<div class="chessmen-" + T + '"></div>)'; T trebuie să fie externă metodei _create() şi ar trebui să poată fi setată dinafara widget-ului.

Pentru astfel de necesităţi, jQuery.widget() prevede obiectul options, conţinând perechi "cheie: valoare"; în metoda _create() avem de folosit aceste opţiuni, iar setarea valorilor acestora se poate face şi prin linia de instanţiere a widget-ului.

În "brw-sah.js" adăugăm options şi avem de modificat o singură linie în _create:

    $.widget("brw.fenBrowser", {
        options: {
            field_size: 20  // setul de piese implicit ("men20.png")
        },
        
        _create: function() {
            /* ... ... */
            this.Tabla = 
                $('<div class="chessmen-' + this.options.field_size + '"></div>')
                /* ... ... */
        },

Amintim fişierul "brw-sah.html", dar acum prevedem două elemente <textarea>: pe primul instanţiem widget-ul cu setul implicit, iar pe celălalt - cu {field_size: 22}:

<!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/brw-sah.css' rel='stylesheet' />
    <script src='js/brw-sah.js'></script>
</head>
<body>
    <textarea id='getFEN'></textarea>
    <textarea id='getFEN1'></textarea>
    <script>
        $(function() { 
            $('#getFEN').fenBrowser();
            $('#getFEN1').fenBrowser({field_size: 22});
        });
    </script>
</body>

Pe parcurs vom adăuga widget-ului şi alte opţiuni, în măsura în care ar apărea ca fiind utile.

vezi Cărţile mele (de programare)

docerpro | Prev | Next