Università degli Studi di Trieste
             Facoltà di Ingegneria



        Tesi di Laurea Specialistica
             ...
2
Indice
Introduzione                                                  5
  Motivazione                                      ...
Videocamere Virtuali                            81
      Gestione dei modelli                            90
      Logiche ...
Introduzione

Motivazione
Il gioco degli scacchi è considerato un problema molto interessante dal
punto di vista della sua...
Si sono scelti i Raumschach fra i tre perché essi sono stati indubbiamente i
più giocati e i più sperimentati; inoltre non...
Analisi

Descrizione del gioco preso in esame
La versione scelta è, fra le versioni tridimensionali esistenti del gioco de...
neri risulta speculare a quella dei pezzi bianchi, quindi con il re posto sul
                                            ...
Pezzi
I pezzi sono, come negli scacchi classici, Re, Regina, Torri, Alfieri, Cavalli e
                             scacch...
Torre:

può muovere in linea retta di quante caselle si vuole in un solo turno. Può
muoversi in sei versi; è utile immagin...
Alfiere: anch’esso muove in linea retta per quante caselle si vuole in un solo
                                           ...
Unicorno: anch’esso in linea retta per quante caselle si vuole in un solo
            nch’esso
turno, ma uscendo dai verti...
Regina: essa può muovere sia come una torre, sia come un alfiere, sia come
un unicorno.
Lettera identificativa: Q (dall’in...
Re: esso si sposta come la regina però di un solo passo per turno. Ovvero, la
                           regina
casella di...
Cavallo: E’ l’unico pezzo in grado di saltare oltre gli altri pezzi. Si deve
muovere di un solo passo di torre più un solo...
Scopo del gioco e relativo regolamento
Come negli scacchi classici, lo scopo del gioco consiste nel dare "scacco
matto" (d...
Requisiti funzionali

Di seguito sono riportati i requisiti funzionali raggruppati in
macrofunzionalità. Ciascuna macrofun...
Requisiti giocabilità              giocatore       umano         vs    giocatore
computerizzato

     •   Si vuole che vi ...
Progettazione

Considerazioni preliminari
Dallo studio dei requisiti emerge come prima peculiarità il fatto che si tratti
...
Architettura del sistema

Nella primissima fase della definizione architetturale è opportuno
mantenersi ad alti livelli di...
L’architettura così descritta richiama il pattern archietturale Model View
Controller. Il comportamento del sistema viene ...
A questo punto, basandosi sullo stato così descritto, il Motore deve essere in
grado di:

     •   Esprimere una valutazio...
Rappresentazione della scacchiera e generazione mosse

Nei motori per il gioco degli scacchi classici vengono utilizzati d...
Servono ancora 1 bit per identificare chi ha il tratto e 7 bit per la
        regola delle 50 mosse.

        Questa strat...
c. Arrocco
      Se non si può più arroccare si segnerà “-“. Altrimenti il
      vocabolario è il seguente: “K” (il bianco...
Come nell’originale le righe saranno separate da “/”. I piani, invece,
        saranno separati da “-“.

        Esempio d...
Addresser sarà invece composto come segue:

     26     27      28      29     30     31     32      33
     38     39    ...
Si vede subito che tentando di far muovere l’alfiere in questione fuori
     dalla scacchiera si ottiene −1.
     Infatti ...
•   “mailbox” l’array da 144 caselle.
   •   “addresser” l’array da 64 caselle.
   •    “chessboard” la scacchiera Array B...
//è possibile occuparla (catturando il pezzo)

                           break;
                     }
                  ...
Soluzione adottata

La soluzione più adatta al caso nostro è sicuramente “Mailbox Array”,
unendo essa la praticità della s...
Tecniche di ricerca

La ricerca di una mossa in un gioco a turni si risolve, in generale, sotto
forma di ricerca in un alb...
Senza entrare ne merito della teoria dei giochi, il cui
              nel
approfondimento va al di là dello scopo di quest...
•   Alfa-Beta pruning

         È un algortimo di ricerca il cui obiettivo è ridurre il numero di nodi
         da valutar...
Fig. 5 - Una illustrazione di alfa-beta pruning. Esplorare i sottoalberi in grigio non
                                   ...
•   Killer Heuristic
         Si tratta di una tecnica di ordinamento di alberi in supporto
         all’algoritmo di alfa...
La variazione principale (principal variation) è quella particolare
variazione che è la più vantaggiosa per il giocatore c...
Soluzione adottata

Per la sua comprovata capacità di migliorare l’efficienza rispetto al
Minimax, l’algoritmo che si util...
Con il sistema così progettato si può ottenere grande profondità di calcolo
nell’esplorazione della posizione nel caso il ...
Fig. 7 -Algoritmo semplificato di ricerca della mossa migliore




40
Valutazione di una posizione

Questo, dei tre problemi è sicuramente il più arduo: come valutare la bontà
di una posizione...
Vediamo in una tabella le “potenzialità” di ciascun pezzo:

                   Direzioni          Caselle
     Pezzo      ...
Fig. 8 - I 4 “tipi” di unicorno diversi e il dettaglio delle loro mobilità.


La cosa più difficile nella valutazione del ...
Indipendentemente da tutto ciò, analizzando la nostra variante 3D, ci
         si accorge che i valori non vi si adattano:...
Omettiamo quindi di descrivere le varie strategie utilizzate per il
    calcolo del valore della struttura dei pedoni negl...
Soluzione adottata

           Bilanciamento degli schieramenti
           Anzitutto è opportuno fissare dei valori di bas...
Si osservi ora nella tabella seguente l’aumento di mobilità in
          prossimità dei vertici e in posizione centrale di...
I valori così ottenuti saranno utilizzati come punteggio base dei
     pezzi. Mancano ancora tuttavia Unicorno e Pedone.

...
Il re necessita anche di due array di interi grandi come la scacchiera:
      uno per l’inizio della partita, per fare in ...
Progettazione dell’Interfaccia


Scacchiera Tridimensionale
Essa è la componente principale dell’interfaccia utente.
Vale ...
modello poligonale. Questo presenta "faccette" piuttosto che curve, ma
sono state sviluppate tecniche d rendering per ovvi...
Per una spiegazione intuitiva è sufficiente immaginare una videocamera che
si muove all’interno di uno spazio tridimension...
•    NURBS – Le superfici NURBS sono definite da curve spline, cioè
        parametrizzate da punti di controllo pesati. L...
Interazione con l’utente


Mouse – Movimentazione dei pezzi

Per muovere i pezzi si deve poter, tramite mouse, selezionare...
Il problema fondamentale però è che l’utente fa click su un punto di una
superficie piana; a partire dalle coordinate pian...
Un’altra invece vincolata alle righe e alle colonne della scacchiera: ovvero,
essendovi, 5 righe e 5 colonne per ciascuno ...
Realizzazione

Tecnologie utilizzate
Microsoft XNA (Xbox New Architecture)
Si tratta di un inseme di tool inseriti in un a...
Blender
E’ un’applicazione per grafica 3D rilasciata sotto forma di software libero
sotto GNU General Public License. Può ...
Su MSDN si raccomanda di scegliere seguendo il flow-chart riportato in
figura 13.




Fig. 13 - Flow chart per la scelta d...
Realizzazione del Motore

Il motore è stato realizzato in ANSI C, non essendovi necessità o particolari
benefici nel ricor...
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Tesi Specialistica - Bruno Tagliapietra
Upcoming SlideShare
Loading in …5
×

Tesi Specialistica - Bruno Tagliapietra

1,198 views
956 views

Published on

Published in: Business
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
1,198
On SlideShare
0
From Embeds
0
Number of Embeds
4
Actions
Shares
0
Downloads
19
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Tesi Specialistica - Bruno Tagliapietra

  1. 1. Università degli Studi di Trieste Facoltà di Ingegneria Tesi di Laurea Specialistica in Ingegneria Informatica PROGETTAZIONE E REALIZZAZIONE DI UNA APPLICAZIONE PER IL GIOCO DEGLI "SCACCHI 3D" LAUREANDO: RELATORE: Chiar.mo Prof. Bruno TAGLIAPIETRA Maurizio FERMEGLIA Anno Accademico 2008/09 200
  2. 2. 2
  3. 3. Indice Introduzione 5 Motivazione 5 Analisi 7 Descrizione del gioco preso in esame 7 Raumschach 7 Pezzi 9 Scopo del gioco e relativo regolamento 16 Notazione delle mosse 16 Requisiti funzionali 17 Requisiti non funzionali 18 Progettazione 19 Considerazioni preliminari 19 Architettura del sistema 20 Interfaccia – Motore VS Model – View - Controller 20 Progettazione del Motore 21 Rappresentazione della scacchiera e generazione mosse 23 Tecniche di ricerca 32 Valutazione di una posizione 41 Progettazione dell’Interfaccia 50 Scacchiera Tridimensionale 50 Comunicazione Interfaccia-Motore 56 Realizzazione 57 Tecnologie utilizzate 57 Realizzazione del Motore 60 Header files 60 C Files 62 Realizzazione dei modelli 3D 75 Realizzazione dell’Interfaccia 77 Premessa: XNA Application Model 77 Librerie di terze parti 79 Gameplay 81 3
  4. 4. Videocamere Virtuali 81 Gestione dei modelli 90 Logiche di gioco 92 Conclusioni 93 Raggiungimento degli obiettivi 93 Sviluppi futuri 93 Bibliografia 94 Letteratura 94 Web 94 Ringraziamenti 96 Appendice 97 Fast, Minimum Storage Ray/Triangle Intersection 97 4
  5. 5. Introduzione Motivazione Il gioco degli scacchi è considerato un problema molto interessante dal punto di vista della sua risoluzione e gioco da parte di un elaboratore: nel corso degli anni, fin dagli albori dell’informatica, gli appassionati e i professionisti di scacchi e di computer hanno perfezionato algoritmi sempre più sofisticati, fino a riuscire recentemente a far vincere il computer contro i più grandi maestri. Vale la pena ricordare che gli scacchi, a differenza ad esempio della dama, sono ancora un problema aperto: cioè, mentre nella dama ormai si conosce la mossa “migliore” in ogni situazione, dall’inizio della partita fino alla fine, Vi sono competizioni mondiali che consistono in un gran numero di partite fra i cosiddetti “motori” per il gioco degli scacchi, ovvero software molto perfezionati e specifici il cui unico obiettivo è vincere una partita di scacchi. Alcuni fra i motori migliori sono stati poi inseriti nei programmi di scacchi commerciali, il più famoso di tutti è sicuramente “Chessmaster 10”: presenta innumerevoli funzionalità quali interfaccia grafica user friendly, possibilità di selezionare le abilità dell’avversario computerizzato (fino ad arrivare ad un’abilità stimata con punteggio FIDE 2966), un breve corso di scacchi integrato... Sarebbe stato quindi presuntuoso e difficilmente fattibile per un singolo scrivere qualcosa che sia competitivo sia dal punto di vista del motore, cioè dell’intelligenza del programma, sia dal punto di vista delle funzionalità offerte all’utente, rimanendo nel campo degli scacchi classici. Un’alternativa sarebbe stata inventare un gioco del tutto nuovo. Tuttavia spesso i giochi complessi difficilmente risultano equilibrati, interessanti e umanamente giocabili alla prima versione. Per questi motivi si è deciso di “riesumare” e computerizzare una fra le versioni più interessanti di questo gioco, che sono indubbiamente quelle che si svolgono su una scacchiera tridimensionale. Le tre principali alternative erano: Raumschach (in tedesco “scacchi nello spazio”), inventata nei primi del ‘900, Asimov Chess (versione ideata da Isaac Asimov in uno dei suoi racconti, ma mai sufficientemente giocata), Star Trek 3D Chess (versione giocata nell’omonima saga). 5
  6. 6. Si sono scelti i Raumschach fra i tre perché essi sono stati indubbiamente i più giocati e i più sperimentati; inoltre non esiste alcuna implementazione sotto forma di videogame che sia fedele all’originale di questo gioco, ideato e giocato in alcuni club tedeschi nel periodo tra la prima e la seconda guerra mondiale. 6
  7. 7. Analisi Descrizione del gioco preso in esame La versione scelta è, fra le versioni tridimensionali esistenti del gioco degli scacchi, la più documentata e la più giocata in assoluto. Le versioni tridimensionali del gioco degli scacchi, abbreviabili in 3D chess, fanno parte delle innumerevoli varianti di questo famosissimo gioco. Le più antiche risalgono alla fine del 1800. Una delle più famose versioni tridimensionali degli scacchi è appunto quella analizzata in questo documento. Essa è detta Raumschach, ovvero “scacchi spaziali” in lingua tedesca. Inventata nel 1907 da Ferdinand Maack, è stata giocata prevalentemente in Germania. Nel 1919 Maack fondò anche un club di Raumschach ad Amburgo, rimasto attivo fino all’inizio del secondo conflitto mondiale. Inizialmente giocata in un cubo 8x8x8, la versione del 1907 vide la luce su una scacchiera cubica con cinque caselle per ognuna delle tre dimensioni. Raumschach Come negli scacchi classici l’obiettivo è catturare il re avversario. Questo può avvenire solo tramite lo “scacco matto”, ossia una posizione in cui non vi è mossa in grado di impedire la cattura del re nella mossa immediatamente successiva. I contendenti muovono uno dei propri pezzi alla volta, a turno, alternandosi. I movimenti, descritti più avanti in dettaglio, sono simili a quelli degli scacchi classici. Ognuna delle 125 caselle è identificata da tre coordinate: “livello” di appartenenza, “riga” e “colonna”, in quest’ordine. • Livelli: identificati dalle lettere Fig. 1 Schema della scacchiera maiuscole A,B,C,D,E • Righe: identificate dalle cifre 1,2,3,4,5 • Colonne: identificate dalle lettere minuscole a,b,c,d,e Seguendo la disposizione di fig.2 si inizia, per convenzione, la numerazione delle caselle da quella posta sul livello più basso, sulla colonna più a sinistra e sulla riga più vicina al giocatore con i pezzi di colore bianco. Perciò, nella casella Aa 1 vi sarà all’inizio partita una torre “bianca”, e in Ba1 nel medesimo istante un alfiere “bianco”, con il re posto nel piano più basso. Sempre da fig.2 è facile ed importante notare che la disposizione dei pezzi 7
  8. 8. neri risulta speculare a quella dei pezzi bianchi, quindi con il re posto sul posto livello più alto. Quindi in Ee5 vi sarà una torre “nera” e in Ed5 un unicorno “nero”. Fig. 2 - La disposizione iniziale della scacchiera, dal piano più alto (E) al piano più basso (A) 8
  9. 9. Pezzi I pezzi sono, come negli scacchi classici, Re, Regina, Torri, Alfieri, Cavalli e scacchi Pedoni, con l’aggiunta degli Unicorni. Ogni giocatore ha a disposizione 10 pedoni, 2 torri, 2 cavalli, 2 alfieri, 2 unicorni, 1 regina e naturalmente 1 re. Come negli scacchi classici su ciascuna casella può stazionare un solo pezzo può e l’unico in grado di “saltare”, cioè di non subire l’ostruzione degli altri pezzi presenti sulla scacchiera, è il cavallo. Qualsiasi pezzo di un certo colore può occupare una casella contenente un pezzo avversario, catturando in questoi modo quest’ultimo, il quale verrà rimosso dalla scacchiera. Pedone: come negli scacchi classici il suoi movimenti cambiano a seconda che esso catturi o meno un pezzo. Senza cattura esso deve muovere di un passo di torre verso il campo avversario; se “bianco”, quindi, può muovere verso l’alto o verso avanti rispetto al giocatore che lo controlla. In caso di cattura a questo movimento dovrà essere aggiunto un cambio di colonna verso destra o verso sinistra, risultante in un passo d’alfiere sullo ste stesso piano o sul piano immediatamente successivo nel viaggio verso il campo avversario. A differenza degli scacchi classici non c’è differenza di possibilità di movimento fra la prima mossa e le successive. Non c’è mossa en-passant. Lettera identificativa: P 9
  10. 10. Torre: può muovere in linea retta di quante caselle si vuole in un solo turno. Può muoversi in sei versi; è utile immaginare che essa per muoversi esca da una delle sei facce del cubo di partenza. Es: una torre posta al centro di una scacchiera vuota, quindi in Cc3, si può : vuota, muovere in o Cc1, Cc2,Cc4, Cc5 (Varia la riga) o Ca3, Cb3, Cd3, Ce3 (Varia la colonna) o Ac3, Bc3, Dc3, Ec3 (Varia il livello) Lettera identificativa: R (dall’inglese “rook”) 10
  11. 11. Alfiere: anch’esso muove in linea retta per quante caselle si vuole in un solo caselle turno. A differenza della torre però esso esce dagli spigoli del cubo, perciò può muoversi in otto versi. Es: Un alfiere posizionato nella casella Dc4 in una scacchiera vuota può : muoversi in • Da2, Db3, Dd5, De2, Dd3, Db5 (nello ste stesso livello, come negli scacchi classici) • Eb4, Ed4, Cb4, cd4, Ba4, Be4 (nel piano posto frontalmente al giocatore) • Ec3, Ec5, Cc3, Cc5, Bc2, Ac1 (nel piano posto lateralmente al giocatore) Lettera identificativa: B (dall’ingelse “bishop”) 11
  12. 12. Unicorno: anch’esso in linea retta per quante caselle si vuole in un solo nch’esso turno, ma uscendo dai vertici del cubo. Perciò si muove in quattro versi. Es: un unicorno posizionato al centro di una scacchiera vuota (Cc3) può : essere posto in: Dd4, Ee5, Bb2, Aa1, Db2, Ea1, Bd4, Ae5, Ee1, Dd2, Bb4, Aa5, Ea5, Db4, Bd2, , Ae1 Lettera identificativa: U 12
  13. 13. Regina: essa può muovere sia come una torre, sia come un alfiere, sia come un unicorno. Lettera identificativa: Q (dall’inglese “queen”) 13
  14. 14. Re: esso si sposta come la regina però di un solo passo per turno. Ovvero, la regina casella di arrivo deve essere adiacente alla casella di partenza cioè avere in comune con essa una faccia, uno spigolo o alla peggio un solo vertice. In altre parole un re può muovere in ciascuna delle otto caselle più vicine alla caselle casella di partenza. A differenza degli scacchi classici non vi sono manovre di arrocco. Lettera identificativa: K (dall’inglese “king”) 14
  15. 15. Cavallo: E’ l’unico pezzo in grado di saltare oltre gli altri pezzi. Si deve muovere di un solo passo di torre più un solo passo di alfiere. In altre olo parole, date le coordinate della casella di partenza, una di esse deve restare invariata, una deve variare di 1 e la terza deve variare di 2. Es: in questi esempi indichiamo il variare delle coordinate racchiudendo tra : coordinate parentesi l’entità della variazione di livello, riga e colonna, in quest’ordine. Perciò ad esempio la tripla (0,1,2) indicherà un insieme di caselle poste contemporaneamente nello stesso livello di quella di partenza, su righe distanti 1 e colonne distanti 2. i Un cavallo posto al centro di una scacchiera, nella casella Cc3, può essere posto in ciascuna delle 24 caselle sottostanti, purché libere da pezzi dello stesso colore: Cb1, Cb5, Cd1, Cd5 (0,1,2) --------- Ca2, Ca4, Ce2, Ce4 (0,2,1) Bc1, Bc5, Dc1, Dc5 (1,0,2) --------- Ba3, Be3, Da3, De3 (1,2,0) Ab3, Eb3, Ad3, Ed3 (2,1,0) --------- Ac2, Ec4, Ac4, Ac2 (2,0,1) Lettera identificativa: N (dall’inglese “knight”) 15
  16. 16. Scopo del gioco e relativo regolamento Come negli scacchi classici, lo scopo del gioco consiste nel dare "scacco matto" (dal persiano Shah Màt = il re è morto) al re avversario; si ha "scacco matto" quando il Re, trovandosi sotto la minaccia diretta dei pezzi avversari, non ha la possibilità di sottrarsi ad essa (cioè sarebbe sicuramente catturato alla mossa successiva, se non si trattasse del Re). Lo "scacco" invece è l'attacco (evitabile) che un pezzo avversario porta al Re. L'avversario non può eseguire alcuna mossa che metta o lasci il proprio re sotto "scacco". La partita può terminare anche per abbandono da parte di un contendente, ovviamente con la vittoria dell'altro. Il gioco termina obbligatoriamente in parità (patta) nei seguenti casi: 1. se restano sulla scacchiera soltanto i due re; 2. se il giocatore che ha il tratto non può muovere alcun pezzo, ma il suo re non è sotto scacco (stallo). 3. se per cinquanta mosse consecutive (cinquanta mosse per ciascun giocatore) non viene catturato alcun pezzo e non viene mosso alcun pedone. Notazione delle mosse Durante il corso della storia sono state ideate innumerevoli notazioni per gli scacchi classici. Senza dubbio la più utilizzata, per le sue caratteristiche di intuitività e brevità, è la cosiddetta “notazione algebrica”, descritta nel regolamento internazionale degli scacchi. Risulterebbe però problematico, e con tutta probabilità controproducente: infatti in essa si preferisce, per identificare una mossa, annotare la lettera iniziale del nome del pezzo che la esegue seguita dalle coordinate della casella di arrivo. Es: Cf3 (cavallo mosso in f3, non si sa da quale punto di partenza). E’ evidente che tale notazione necessita di complesse regole per evitare equivoci nel caso in cui, ad esempio, i cavalli che possono muovere in f3 siano più di uno. Per questo motivo, la notazione adottata nel nostro caso consisterà in: casella di partenza – casella di arrivo – iniziale di eventuale pezzo “di promozione” es: Aa1Ca1 (il pezzo presente in Aa1 va in Ca1), Ec4Ec5Q (il pezzo, pedone, presente in Ec4 va Ec5 e viene promosso in regina); 16
  17. 17. Requisiti funzionali Di seguito sono riportati i requisiti funzionali raggruppati in macrofunzionalità. Ciascuna macrofunzionalità è raggiunta nel momento in cui vengono soddisfatti tutti i requisiti in esso contenuti. Requisiti giocabilità 2 giocatori su stessa postazione • Il software deve presentare una scacchiera cubica 5x5x5, conforme al regolamento indicato precedentemente. Detta scacchiera deve essere inizialmente configurata come da Fig.2 e contenere tutti e soli i pezzi indicati in Fig.2. • Si vuole inoltre che i movimenti consentiti sui pezzi siano conformi al regolamento precedentemente indicato. • Essendo la visualizzazione di una scacchiera 3D ostica all’essere umano, si vuole che vi siano più modalità di visualizzazione della stessa (lontano-vicino). • Per selezionare il pezzo da muovere e conseguentemente la casella in cui muoverlo si vuole poter usare il mouse: un primo click sul pezzo lo deve evidenziare, un secondo click fuori dal pezzo deve: o Spostare il pezzo se il click è stato effettuato su una casella dove il pezzo stesso possa muovere. o Selezionare un pezzo diverso se il click è stato effettuato su un altro pezzo selezionabile. o Deselezionare il pezzo selezionato altrimenti. 17
  18. 18. Requisiti giocabilità giocatore umano vs giocatore computerizzato • Si vuole che vi sia la possibilità di giocare contro un avversario computerizzato. • Si vuole che l’abilità di detto avversario sia regolabile. Per la precisione: o Deve essere possibile regolare il tempo massimo concesso per eseguire una mossa. o Deve essere possibile fissare la profondità di analisi massima del software. Requisiti giocabilità 2 giocatori umani su postazioni diverse • Si vuole che vi sia possibilità di giocare via internet con un avversario umano. Requisiti non funzionali • Portabilità: si vuole che il gioco sia cross platform il più possibile. 18
  19. 19. Progettazione Considerazioni preliminari Dallo studio dei requisiti emerge come prima peculiarità il fatto che si tratti di arrivare alla realizzazione di un videogame. Già questa semplice particolarità presenta non poche implicazioni progettuali: Restringendo il campo e riassumendo, dal punto di vista informatico si tratta di un gioco da tavolo (board videogame) dotato di “intelligenza artificiale” (AI), che deve quindi essere in grado di tener testa ad un giocatore umano. Si delina quindi una prima suddivisione in “motore”, ossia la parte pensante del gioco, e “interfaccia utente”, cioè tutto ciò che è percepito sensorialmente dal giocatore umano. Dovendo essere intelligente, il motore deve avere come caratteristiche peculiari rapidità di calcolo, efficienza, ed un certo grado di “regolabilità della difficoltà”. L’interfaccia, per contro, dovrà soddisfare i requisiti richiesti; inoltre, non avendo particolari requisiti di efficienza, sarà facile progettarla in modo che sia il più manutenibile possibile. Altro punto fondamentale, si tratta di un software che fa uso di grafica 3D. Sarà quindi necessaria la scelta di tecnologie adatte a coprire le necessità emerse da queste prime considerazioni iniziali. In particolare si avrà bisogno di: • Un game engine con supporto 3D (OpenGL, Direct3D o entrambi). E’ un software che serve a sviluppare video game. Le funzionalità principali fornite da un game engine comprendono un renderer per grafica 2D e/o 3D, animazioni, networking, gestione della memoria, threading. In più quello da noi scelto dovrà facilitare la portabilità, come da requisito. • Un software di 3D modeling per la realizzazione dei modelli (i pezzi, la scacchiera). 19
  20. 20. Architettura del sistema Nella primissima fase della definizione architetturale è opportuno mantenersi ad alti livelli di astrazione, scendendo nei dettagli il meno possibile. Via via che l’architettura prende forma, la definizione delle singole parti sarà sempre più dettagliata. Interfaccia – Motore VS Model – View - Controller In accordo alle considerazioni preliminari, in prima approssimazione viene molto naturale suddividere il progetto in due macroparti, come segue: • Interfaccia Utente Si occupa di raccogliere input dall’utente (le mosse che egli intende dall’utente fare) e di visualizzare a video la scacchiera. • Motore In esso sono implementate le regole del gioco, lo stato della partita e gioco, la logica di gioco computerizzato. In questo modo, ad ogni input dell’utente l’Interfaccia Utent dovrà: Utente 1. Compiere un’operazione di traduzione dell’input. 2. Passare al Motore la suddetta traduzione. 3. Attendere risposta del Motore. 4. Aggiornare la visualizzazione basandosi sullo stato del Motore. Ad ogni input ricevuto, il Motore dovrà: 1. Porre a confronto l’input ricevuto con le regole. 2. Se l’input ricevuto corrisponde ad una mossa permessa, modificare lo stato e generare un messaggio di conferma. Altrimenti, generare un messaggio di errore. 3. Rispondere con il messaggio generato all’Intefaccia Utente 20 Fig. 3 Model - View - Controller
  21. 21. L’architettura così descritta richiama il pattern archietturale Model View Controller. Il comportamento del sistema viene normalmente così illustrato: 1. L’utente interagisce in qualche modo con l’interfaccia (View) 2. Il Controller reagisce all’input, ad esempio tramite una callback. 3. Il Controller propaga sul Modello il risultato dell’azione dell’utente; solitamente modifica lo stato del Modello. 4. Una View utilizza il modello indirettamente per generare un’interfaccia utente appropriata, o per modificare quella già esistente. Il Controller e il Modello non “sanno” che la View esiste. Nel nostro caso Model e Controller farebbero entrambi parte del Motore. Questa è infatti l’architettura più conveniente; tutto lo stato è contenuto nel Motore, che viene interrogato dall’Interfaccia ogniqualvolta ve ne sia bisogno. Progettazione del Motore Per essere in grado di assolvere alla sua funzione in base ai requisiti descritti, il Motore di 3D Chess deve innanzitutto possedere un sistema di strutture dati atte a descrivere lo stato della partita in corso. Lo stato sarà formato da: • Stato della scacchiera, ovvero stato di ciascuna delle 125 caselle. Ciascuna di esse può essere vuota oppure contenere uno dei 7 tipi di pezzi disponibili, che possono essere a loro volta “bianchi” o “neri. Perciò ogni casella ha 7 ∗ 2 + 1 = 15 stati possibili. • Numero di mosse consecutive dall’ultima cattura di pezzo o mossa di pedone (essenziale per soddisfare la regola di partita patta in caso di 50 mosse consecutive senza catture o mosse di pedoni). • Giocatore che ha il tratto (cioè: tocca muovere al bianco o al nero?) • Inoltre, volendo dare la possibilità di “takeback”, ovvero di poter annullare un certo numero di mosse, sarebbe opportuno mantenere uno stack delle mosse fatte fino a quel momento. In alternativa, per semplificare il calcolo, si potrebbero memorizzare direttamente le posizioni della scacchiera. 21
  22. 22. A questo punto, basandosi sullo stato così descritto, il Motore deve essere in grado di: • Esprimere una valutazione quantitativa sulla posizione • Per un certo numero di “livelli”, si esso n: o Generare tutte le mosse possibili o Valutare tutte le posizioni o Per ciascuna posizione, generare tutte le mosse possibili o Fermarsi quando ha raggiunto il tempo limite In questo modo si sarà in grado di fissare il “livello” di difficoltà sia basandosi sulla massima profondità di esplorazione dell’albero delle mosse, sia fissando un tempo limite massimo oltre il quale il Motore dovrà selezionare la mossa ritenuta fino a quel momento la migliore. Naturalmente, tutto questo deve essere fatto nel modo più efficiente possibile, in quanto questo è il requisito fondamentale che deve avere il nostro Motore. Da queste considerazioni iniziali si può facilmente concludere che tre sono i problemi fondamentali da risolvere: • Rappresentazione della scacchiera: quali strutture dati utilizzare per rappresentare una posizione? • Tecniche di ricerca: come identificare le mosse possibili e selezionare quelle che sembrano essere più “promettenti” in modo che vengano esplorate per prime? • Valutazione di una posizione: come valutare quantitativamente una posizione? Descriviamo ora i punti presi in considerazione nella progettazione della soluzione a questi tre principali problemi. 22
  23. 23. Rappresentazione della scacchiera e generazione mosse Nei motori per il gioco degli scacchi classici vengono utilizzati da molti anni delle strutture dati ormai considerate standard per la rappresentazione di una scacchiera. E’ nostro intento adottare la più consona ai nostri scopi, fornendo dapprima una descrizione delle possibili alternative. Alternative progettuali 1. Lista di pezzi Era utilizzata dai primi programmi di scacchi, i quali avevano a disposizione pochissima memoria. Perciò anziché riferirsi prima alla cella e da essa ottenere il pezzo, mantenevano una lista dei pezzi in gioco e relative coordinate. E’ utilizzata ancora oggi in congiunzione ad altre tecniche nel caso serva scoprire rapidamente la posizione di un certo pezzo. 2. Array based Una fra le alternative più intuitive, ovvero creare un array di tante caselle quante sono quelle della scacchiera che trattiamo. Ciascun elemento dell’array identificherà il contenuto della casella: ad esempio assegnando un valore convenzionale a ciascuna delle 15 possibili combinazioni (vuota, uno dei 7 tipi di pezzi bianchi o uno dei 7 tipi di pezzi neri). Diventa lento durante la generazione delle mosse in quanto per ogni mossa generata bisogna controllare se essa è dentro o fuori dalla scacchiera, rallentando in questo modo il processo di generazione. 3. Codifica Huffman Ispirato alla nota codifica di Huffman, le posizioni vengono descritte con configurazioni di bit di lunghezza inversamente proporzionale alla probabilità che esse compaiono. Ecco un esempio che farebbe al caso nostro: Casella vuota = 0 Pedone = 10c Unicorno = 1100c Alfiere = 1101c Cavallo = 1110c Torre = 1111c Regina = 11110c Re = 11111c dove ‘c’ rappresenta il colore (ad esempio 1 per bianco e 0 per nero). 23
  24. 24. Servono ancora 1 bit per identificare chi ha il tratto e 7 bit per la regola delle 50 mosse. Questa strategia risulta molto onerosa dal punto di vista della complessità di calcolo, mentre è conveniente per le ridotte dimensioni in memoria. 4. Metodo 0x88: Questo metodo funziona negli scacchi classici perché le 64 caselle sono in numero una potenza di 2, come pure le due dimensioni 8x8. Non può essere utilizzato nel nostro caso (la scacchiera è composta da 125 caselle, con tre dimensioni 5x5x5, e non vi sono potenze di 2 fra questi numeri), ma lo citiamo, senza spiegarlo, per completezza. 5. Bitboard: Anche questo metodo non funziona per noi. Consiste nello sfruttare la parallelizzabilità del calcolo in caso di processori a 64 bit quando le caselle sono 64, come negli scacchi normali. Anche questo è citato solo per completezza 6. Notazione Forsyth–Edwards (FEN) Basata su un sistema ideato dal giornalista scozzese David Forsyth, è stata estesa da Steven J. Edwards per utilizzi informatici. Un “record” FEN definisce una particolare posizione di gioco; si tratta di una stringa di caratteri ASCII tutti sulla stessa riga, contenente sei campi, separati da uno spazio. I campi sono: (NB: la sguente descrizione si riferisce al regolamento degli scacchi classici. E’ qui riportata per confronto con la variante FEN elaborata per il caso nostro e descritta in seguito) a. Posizionamento dei pezzi (dalla prospettiva del bianco) Ciascuna delle 8 righe è descritta come segue, ed è separata dalle altre utilizzando il carattere “/”. Seguendo la notazione algebrica standard (stessa iniziale dei pezzi descritta in questo documento nella parte di analisi), i pezzi bianchi sono designati usando lettere maiuscole (“PNBRQK”) mentre quelli neri con lettere minuscole (“pnbrqk”). Le caselle vuote consecutive vengono raggruppate e indicate con cifre da 1 a 8. b. Colore di chi ha il tratto “w” per bianco (white), “b” per nero (black). 24
  25. 25. c. Arrocco Se non si può più arroccare si segnerà “-“. Altrimenti il vocabolario è il seguente: “K” (il bianco può arroccare dal lato di re), “Q” (il bianco può arroccare dal lato di regina), “k” (il nero può arroccare dal lato di re), “q” (il nero può arroccare dal lato di regina). d. En Passant Se un pedone può essere mangiato “en passant” si segneranno le coordinate della casella sulla quale finirà un eventuale pedone avversario che effettuerà la mangiata. Altrimenti si segna “-“. e. Halfmove clock Così si definisce il numero di mezze mosse passate da quando è stato mosso un pedone o mangiato un pezzo. Se raggiunge 100 la partita è pari. f. Fullmove number: Il numero di mosse complete eseguite fino al momento della scrittura della posizione. Comincia da 1 e viene incrementato ogniqualvolta muove il nero. Esempio di record FEN per scacchi classici (appena descritto) nella posizione iniziale: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 Ispirandoci a questa notazione possiamo derivarne con poco sforzo una rappresentazione appropriata ed intuitiva per il caso preso in esame da questa tesi. Notazione Forsyth–Edwards-Raumschach (FENR) Non contemplando il regolamento del gioco da noi preso in esame la presa “en passant” né tantomeno alcun tipo di arrocco, i campi che compongono un record FENR saranno soltanto 4, separati da spazi. Gli ultimi 3, ovvero “Colore di chi ha il tratto”, “Halfmove clock” e “Fullmove number”, rimarranno fedeli all’originale. Il primo, riguardante la descrizione del posizionamento dei pezzi, sarà annotato come segue. Come nell’originale la scacchiera verrà rappresentata dal punto di vista del bianco. I piani saranno scritti dal più alto al più basso. La rappresentazione delle caselle vuote e le lettere rappresentanti i pezzi seguiranno le regole dell’originale, con l’aggiunta della “u” e “U” per gli unicorni rispettivamente neri e bianchi. 25
  26. 26. Come nell’originale le righe saranno separate da “/”. I piani, invece, saranno separati da “-“. Esempio di record FENR per 3D Chess nella posizione iniziale: rnknr/ppppp/5/5/5-buqbu/ppppp/5/5/5-5/5/5/5/5- 5/5/5/PPPPP/BUQBU-5/5/5/PPPPP/RNKNR w 0 0 (cfr. con fig. 2). 7. Mailbox Array E’ una variante della struttura dati Array Based (descritta al punto 2), mantiene i suoi vantaggi ed abbatte la difficoltà di determinare, durante la generazione delle mosse, se la mossa generata finisca dentro o fuori dalla scacchiera. Descriviamo dapprima il metodo utilizzato negli scacchi classici, più semplice in quanto riferito ad una scacchiera bidimensionale. Per rappresentare la scacchiera si utilizzano 2 array monodimensionali di interi. Uno formato da 8 ∗ 8 = 64 caselle, denominato “addresser”. L’altro formato da 12 ∗ 12 = 144 caselle, denominato “mailbox”. Mailbox sarà così composto: -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 0 1 2 3 4 5 6 7 -1 -1 -1 -1 8 9 10 11 12 13 14 15 -1 -1 -1 -1 16 17 18 19 20 21 22 23 -1 -1 -1 -1 24 25 26 27 28 29 30 31 -1 -1 -1 -1 32 33 34 35 36 37 38 39 -1 -1 -1 -1 40 41 42 43 44 45 46 47 -1 -1 -1 -1 48 49 50 51 52 53 54 55 -1 -1 -1 -1 56 57 58 59 60 61 62 63 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 26
  27. 27. Addresser sarà invece composto come segue: 26 27 28 29 30 31 32 33 38 39 40 41 42 43 44 45 50 51 52 53 54 55 56 57 62 63 64 65 66 67 68 69 74 75 76 77 78 79 80 81 86 87 88 89 90 91 92 93 98 99 100 101 102 103 104 105 110 111 112 113 114 115 116 117 Ovvero addresser conterrà tutti e soli, nell’ordine, gli indici delle caselle di Mailbox che non contengono −1. A questi due array si aggiunge la scacchiera Array Based, sia esso ℎ , costituita da un array monodimensionale di interi composto da 64 caselle. I pezzi vengono rappresentati in esso con valori convenzionali. Esempio: 0: casella vuota 1: pedone bianco , -1 pedone nero 2: unicorno bianco , -2 pedone nero 3: cavallo bianco , -3 cavallo nero 4: alfiere bianco , -4 alfiere nero 5: torre bianca , -5 torre nera 6: regina bianca , -6 regina nera 7: re bianco , -7 re nero Ora, per ogni verso in cui ciascun pezzo può muoversi si definisce un offset. Aggiungendo questo offset all’indice della casella in cui esso si trova si otterrà la successiva casella nella direzione di movimento di quell’offset. Ad esempio, per l’alfiere gli offset saranno 4, uno per ogni direzione, e saranno −13, −11, 11, 13. Infatti, un ipotetico alfiere in posizione 115, ovvero la posizione di partenza dell’alfiere di re bianco, potrebbe muoversi ad esempio nelle caselle 115 − 13 = 102 e 115 − 11 = 104 (prendendo come riferimento l’array Addresser). E infatti: mailbox[102] contiene 52 e mailbox[104] contiene 54 Infatti 52 e 54 sono le nuove coordinate dell’alfiere nella scacchiera Array Based. 27
  28. 28. Si vede subito che tentando di far muovere l’alfiere in questione fuori dalla scacchiera si ottiene −1. Infatti 115 + 11 = 126 e 115 + 13 = 128. E mailbox[126] contiene -1 e mailbox[128] contiene -1 Vediamo l’algoritmo in linguaggio naturale: con questo sistema, per ogni casella 0 ≤ ≤ 63 sarà necessario e sufficiente: • Se la casella non è vuota, fare riferimento agli offset del pezzo in essa contenuto. • Per ogni offset: 1. Sommare il primo offset ad [ ]. Sia l’intero così ottenuto. 2. Se [ ] contiene −1 fermarsi e passare all’offset successivo. Se sono finiti gli offset, passare alla casella successiva. 3. Altrimenti significa che il pezzo può muoversi in ℎ [ [ ]] , a meno che questa casella non sia occupata da un altro pezzo dello stesso colore o che a seguito di questa mossa il re non venga messo sotto scacco. 4. Se il pezzo può muoversi di più caselle in una direzione (alfiere, torre o regina) ripetere il punto 1. Altrimenti passare all’offset successivo. Se sono finiti gli offset, passare alla casella successiva. NOTA: Le colonne e le righe contenenti −1 sono due per ogni lato dell’array. Ne basterebbero soltanto una per lato se non fosse per il cavallo, il quale nel caso in cui si trovasse sul bordo di uno dei lati potrebbe essere spinto appunto di due colonne, o righe, fuori dalla scacchiera da uno dei suoi offset. A questo punto si può immaginare un’implementazione di questo algoritmo in C. Avendo già descritto l’algoritmo in linguaggio naturale sembra superfluo utilizzare lo pseudocodice, e più appropriato il C, anche per sottolineare la semplicità di calcolo ottenuta con la struttura dati “Mailbox Array” Siano: 28
  29. 29. • “mailbox” l’array da 144 caselle. • “addresser” l’array da 64 caselle. • “chessboard” la scacchiera Array Based (supponiamo che il valore convenzionale per “casella vuota” sia zero), quindi da 64 caselle. • “color” un array da 64 caselle contenente −1, 1 o 0 a seconda che una casella sia occupata da un pezzo nero, bianco o vuota, rispettivamente. • “offset” un array bidimensionale contenente in ogni colonna tutti gli offset per muovere ciascun pezzo (zero per i pezzi che hanno meno direzioni). • “slide” un array di boolean che contiene “vero” nelle celle destinate ad alfiere, torre e regina, “falso” nelle altre. L’algoritmo necessario a determinare tutte le caselle in cui il pezzo contenuto nella casella “i” (da zero a 64) può essere mosso è il seguente: // *** ALGORITMO DI GENERAZIONE MOSSE BOOL is_moveable = FALSE; int n; for (j = 0; j < offsets[piece[i]]; ++j) //il tipo di pezzo contenuto in piece[i] //ha offsets[piece[i]] offset diversi for (n = i;;) { //inizializzazione e ciclo infinito //”n” è la casella da esaminare n = mailbox[addresser[n] + offset[piece[i]][j]]; //utilizzando le schematizzazioni di mailbox e //addresser sopra indicate è facile capire che //con questa operazione “n” diventa -1 nel caso //in cui il movimento usando l’offset //individuato nel modo descritto porti fuori //dalla scacchiera if (n == -1) break; //se siamo usciti dalla scacchiera ci fermiamo //e passiamo all’offset successivo if (color[n] != 0) { //se la casella non è vuota... if (color[n] == not_my_side) is_moveable = TRUE; //se è occupata da un pezzo avversario allora 29
  30. 30. //è possibile occuparla (catturando il pezzo) break; } is_moveable = TRUE; if (!slide[piece[i]]) break; //se si tratta di alfiere, torre o regina //esaminiamo un ulteriore spostamento nella //direzione dell’offset preso in esame } E’ chiaro che questo algoritmo risolve il problema della generazione delle mosse Passando tutte le 64 caselle in questo modo si ha rapidamente la situazione della scacchiera in una determinata posizione. A questo punto si tratta di portare questo metodo alla nostra situazione di scacchiera cubica. A tal proposito, sarà sufficiente: 1. Modificare l’array “Mailbox” in un array con 9x9x9 = 729 caselle, in modo da avere in ogni direzione sufficiente spazio per eventuali pezzi “caduti” fuori dalla scacchiera. Per ogni dimensione, infatti, avremo le 5 caselle utili più 2 caselle per lato (necessarie a mantenere i movimenti dei cavalli) 2. Modificare l’array “addresser” trasformandolo in un array di 125 caselle. Anch’esse, come nell’esempio degli scacchi classici, dovranno contenere gli indirizzi delle caselle di Mailbox che non contengono “-1”. 3. Costruire opportunamente l’array degli offset basandosi sulle dimensioni di “mailbox”. 4. Modificare gli array “chessboard” e “color” in array da 125 caselle. 5. Modificare l’array “slide” aggiungendo ad esso l’unicorno. L’algoritmo resterà invariato e il metodo sarà applicabile al caso nostro. 30
  31. 31. Soluzione adottata La soluzione più adatta al caso nostro è sicuramente “Mailbox Array”, unendo essa la praticità della scacchiera “Array Based” senza però gli handicap di inefficienza di cui quest’ultima soffre. Anche la facilità di adattabilità della metodologia al nostro tipo di problema la rende la miglior candidata fra le proposte elencate. Esso però è difficilmente applicabile al caso del pedone: il movimento del pedone è condizionato in modo particolare dai pezzi avversari che lo circondano; esso infatti può muovere in diagonale soltanto se vi sono presenti pezzi avversari nelle caselle di destinazione (per un’accurata descrizione del movimento del pedone v. Analisi). Basterà utilizzare l’algoritmo di generazione precedentemente individuato solo per i pezzi che non siano pedoni, anteponendo ad esso un controllo condizionale. Per i pedoni sarà sufficiente controllare lo stato delle 6 caselle interessate dal movimento di ciascuno di loro, a seconda che sia nero o sia bianco, in modo da massimizzare l’efficienza pur scrivendo codice ridondante. In pseudocodice: foreach (casella c della scacchiera) { if (color[c] == colore di turno) { if (piece[c] == pedone) { if(color[c] == bianco) { //controllo dello stato delle 6 caselle //corrispondenti al movimento pedone bianco } else { //controllo dello stato delle 6 caselle //corrispondenti al movimento pedone nero } } else { //esegui algoritmo di generazione mosse } } } I dettagli sulla effettiva implementazione sono riportati nella sezione dedicata alla realizzazione. 31
  32. 32. Tecniche di ricerca La ricerca di una mossa in un gioco a turni si risolve, in generale, sotto forma di ricerca in un albero. La prima idea che viene in mente è impostare una massima profondità, generare tutto l’albero fino a quella profondità e valutare le “foglie”, ovvero le posizioni definite terminali. E’ ovvio che in ogni caso la “visibilità” che l’algoritmo consente è limitata dalla profondità impostata. Questo fenomeno è chiamato “horizon effect”: il numero dei possibili stati, o posizioni, è molto grande e I computer possono cercare soltanto fra una piccola parte di essi. Perciò potrebbe capitare ad esempio che la mossa ritenuta migliore guardando soltanto 5 mosse più avanti risulti disastrosa, al punto di essere determinante per la perdita della partita (pensiamo ai famosi “sacrifici”: ne è un esempio lampante la famosa partita a scacchi detta “L’immortale”, nella quale il bianco vince pur essendo in schiacciante inferiorità numerica). Per questo motivo negli anni si è tentato di ottimizzare la ricerca della mossa migliore facendo in modo di selezionare i “rami” nei quali cercare con profondità maggiore o nei quali cercare prima. Perciò il problema della ricerca viene a sua volta scomposto in tre problemi: 1. Algoritmo di ricerca da utilizzare 2. Come ottenere una differenza efficace di profondità nella ricerca 3. In quale ordine effettuare la ricerca nei vari livelli dell’albero Algoritmo di ricerca Di seguito si elencano brevemente alcune fra le procedure di ricerca più utilizzate: • Minimax Così cita il teorema del minimax (dalla teoria dei giochi): per ogni gioco a somma zero con strategie finite che interessa due giocatori, esiste un valore V ed una strategia mista per ciascun giocatore tale che: a. Data la strategia del giocatore 2, il miglior risultato per il giocatore 1 è V. b. Data la strategia del giocatore 1, il miglior risultato per il giocatore 2 è –V. 32
  33. 33. Senza entrare ne merito della teoria dei giochi, il cui nel approfondimento va al di là dello scopo di questa tesi, possiamo applicare questo teorema agli scacchi, ed anche ai nostri scacchi 3D. Infatti, dall’applicazione del teorema del minimax scaturisce l’applicazione l’algoritmo minimax. Esso è un algoritmo ricorsivo che serve minimax. generalmente a cercare la miglior mossa successiva in un gioco di n giocatori. Viene associate un valore a ciascuna posizione o stato del sta gioco; questo valore indica quanto buono è per un giocatore raggiungere q quella posizione. Il giocatore poi farà la mossa che massimizza il minimo valore della posizione risultante dalle possibili mosse successive dell’avversario. Min Fig. 4 Esempio di albero minimax con valori calcolati Il valore dei nodi viene valutato con una funzione euristica discussa e descritta più avanti. a Di seguito un’implementazione in pseudocodice dell’algortimo maximin. function minimax(node, depth, color) if ((node è un nodo terminale) or (depth == 0)) node terminale depth 0 return color * (valore euristico del nodo valore nodo) else α := - -∞ foreach child of node α := max(α, max(α,-minimax(child, depth-1), -color) color) return α Alla prima chiamata si setta il nodo di partenza, il colore che deve setta muovere e la profondità massima da raggiungere. 33
  34. 34. • Alfa-Beta pruning È un algortimo di ricerca il cui obiettivo è ridurre il numero di nodi da valutare rispetto all’algoritmo minimax: esso infatti smette di valutare una mossa quando è stata trovata almeno una possibilità in grado di provare che la mossa in questione è peggiore di una precedentemente esaminata. Questo farà sì che tale mossa non necessiti di ulteriore valutazione. E’ un’ottimizzazione che non cambia il risultato dell’algoritmo, quindi sicuramente da adottare. I benefici dell’alfa-beta pruning consistono nel fatto che utilizzandolo si riescono ad eliminare rami dell’albero di ricerca: in questo modo il tempo di ricerca può essere limitato ai rami “più promettenti”, permettendo così una più profonda ricerca. L’ottimizzazione riduce la profondità effettiva a poco più della metà rispetto al minimax se i nodi sono valutati in ordine ottimale o quasi ottimale. La dimostrazione di tale affermazione esula dagli obiettivi di questa tesi. L’algoritmo funziona mantenendo due valori, alfa e beta, che rappresentano il minimo punteggio ottenibile dal giocatore massimizzante e il massimo punteggio ottenibile dal giocatore minimizzante, rispettivamente. Alfa è inizialmente infinitamente negativo, beta infinitamente positivo. La loro distanza decresce al progredire della ricorsione. Nel momento in cui beta diventa minore di alfa significa che la posizione corrente non può essere il risultato del gioco ottimale di entrambi i giocatori, perciò diventa inutile esplorarla ulteriormente: è infatti inutile esplorare eventualità di gioco non ottimali nella speranza che esse si verifichino; questo, negli scacchi ma non solo, è uno dei più gravi e comuni errori dei giocatori principianti: sperare in un grossolano errore dell’avversario per assicurarsi la vittoria. 34
  35. 35. Fig. 5 - Una illustrazione di alfa-beta pruning. Esplorare i sottoalberi in grigio non beta migliora la soluzione (nell'esplorazione da sinistra a destra). "min" e "max" rappresentano i turni del giocatore e dell'avversario rispettivamente. Pseudocodice dell’algori dell’algoritmo: function alfa alfabeta(node, depth, α, β)) //β rappresenta la miglior scelta della mossa preecedente (e quindi dell’altro giocatore) if ((node è un nodo terminale) or (depth = 0)) node depth == return (valore euristico del nodo) foreach child of node α := ma max(α, -alfabeta(child, depth-1, 1,-β,-α)) // uso della simmetria, -β diventa α if β≤α break // Beta cut-off return α //chiamata iniziale alfabeta(origin, depth, -infinity, +infinity) beta(origin, infinity, NOTA: Dalla fig. 5 si nota che se i sottorami di terzo livello, figli del nodo di i secondo livello marcato con valore 3, fossero scambiati di posto, uno di loro non verrebbe nemmeno analizzato perché verrebbe tagliato fuori dal pruning. Infatti la tecnica di pruning può venire ulteriormente migliorata p utilizzando metodi di ordinamento euristici per individuare parti di alberi che probabilmente potrebbero causare tagli se analizzate per prime. Idealmente, sarebbe necessario che fossero esaminate per prime le mos “migliori”. mosse Una delle tecniche più diffuse in questo senso, per la sua efficacia e basso costo di calcolo, è la cosiddetta killer heuristic. 35
  36. 36. • Killer Heuristic Si tratta di una tecnica di ordinamento di alberi in supporto all’algoritmo di alfa-beta pruning. Si tenta di produrre un cutoff sperando che una mossa che ha prodotto un cutoff in un altro ramo dell’albero alla stessa profondità produca un cutoff nella posizione in cui ci si trova al momento. Particolarmente efficace negli scacchi, dove spesso una mossa buona si rivela tale anche in posizioni di poco diverse. Perciò, esplorando la mossa killer prima di altre mosse spesso si riesce a creare un taglio, il cui beneficio sarà tanto maggiore quanto minore sarà la profondità in cui lo si esegue. History Heuristic Ulteriore evoluzione, di comprovata efficacia negli scacchi classici, è la cosiddetta history heuristic. Si costruisce una tabella di mosse, indicizzandole in qualche modo, ad esempio tramite il pezzo che muove e la casella di destinazione. Al verificarsi di un taglio l’elemento appropriato della tabella viene incrementato, ad esempio aggiungendo ad esso la profondità rimanente (quindi mosse meno profonde risulteranno avere punteggio più alto). In questo modo si tenderà a privilegiare, fra le mosse che hanno causato tagli, quelle avvenute in rami meno profondi dell’albero, quindi in grado di produrre tagli più cospicui. Qualunque sia l’algoritmo di ordinamento utilizzato, questo dovrà avvenire prima della ricerca sull’albero. • Quiescenza Questo metodo di ricerca si è provato funzionale sperimentalmente, e deriva da un’imitazione di comportamento umano: un giocatore infatti tende istintivamente ad analizzare più in profondità situazioni “agitate”, ovvero mosse attraverso le quali la posizione viene profondamente alterata; tipicamente, mosse di cattura. Il metodo consiste semplicemente nel dedicare maggiore profondità all’analisi di mosse di cattura. • Principal Variation + Iterative Deepening Il termine variazione si riferisce ad una specifica sequenza di mosse in un gioco a turni generico; spesso è utilizzato per identificare un ipotetico stato futuro del gioco. 36
  37. 37. La variazione principale (principal variation) è quella particolare variazione che è la più vantaggiosa per il giocatore corrente, assumendo che il giocatore avversario risponda con le mosse per lui di volta in volta migliori. In altre parole essa è la “migliore” o “corretta” linea di gioco. Nel contesto qui preso in esame si indica con variazione principale la sequenza che il giocatore computerizzato crede essere la migliore; questo assunto non è garantito a causa dell’euristicità dell’algoritmo di valutazione e dell’horizon effect. Fig. 6 In blu la variazione principale di un ipotetico albero minimax Estendendo ancora il significato del termine, si può pensare di avere una variazione principale per ciascun grado di profondità dell’albero: ciascuna di esse sarebbe lunga quanto la profondità esaminata. Utilizzare un array bidimensionale quadrato (avente come “lato” la massima profondità che si decide di analizzare), unito ad uno schema iniziale di iterative deepening, ovvero un ciclo che richiama l’algoritmo di alfa-beta pruning con limite di profondità inizialmente pari a 1 e ad ogni iterazione incrementato fino ad un certo massimo, permette di mantenere la “storia” delle soluzioni trovate e l’evoluzione della soluzione migliore trovata verso l’ottimalità attraverso i vari passaggi all’interno dell’albero. 37
  38. 38. Soluzione adottata Per la sua comprovata capacità di migliorare l’efficienza rispetto al Minimax, l’algoritmo che si utilizzerà sarà l’alfa-beta pruning, corredato da ricerca di quiescenza a profondità maggiore. Inoltre, si utilizzerà una tabella per l’implementazione dell’algoritmo “History Heuristics”. In questo modo si potrà efficientemente compiere ricerche in profondità nell’albero, potendo così supplire alle carenze (inevitabili) della funzione di valutazione della posizione. Si è desciso di utilizzare una ricerca a tre livelli. Riferendosi alla figura 7: una prima funzione, think, richiama l’algoritmo alfa-beta pruning, chiamato search, dando ad esso profondità limite inizialmente pari ad 1 e ad ogni iterazione incrementata di 1. Questo è necessario per soddisfare il requisito della limitazione temporale nella nostra ricerca: alfa-beta pruning è un algoritmo di tipo depth-first; questo significa che non si ha soluzione fino a che non è stato esplorato tutto l’albero alla massima profondità desiderata. Invece, impostando la profondità massima di volta in volta maggiore, ed utilizzando un array per contenere tutte le variazioni principali ottenute nelle varie iterazioni, l’algoritmo di ricerca potrà essere interrotto anche dopo brevissimo tempo: in caso di interruzione verrà utilizzata la miglior soluzione trovata fino a quel momento, indipendentemente da quanto in profondità si sia riusciti ad andare. Ad ogni iterazione viene chiamata la search, che implementa alfa-beta pruning. Essa genera tutte le mosse possibili nella posizione corrente e, iterando sull’elenco che le contiene, ne esegue la i-esima, richiama sé stessa (invertendo alfa e beta perché il punteggio del giocatore nero è opposto a quello del giocatore bianco) riducendo la profondità rimanente (depth), annulla la mossa i-esima. Poi controlla il valore restituito dalla chiamata ricorsiva, e, se è avvenuto un cut-off, aggiorna la tabella per history heuristics, aggiorna l’array delle variazioni principali, e aggiorna il valore di alfa. Proseguendo con la ricorsione, la profondità rimanente (depth) descresce fino ad arrivare a zero. A quel punto viene richiamata un’altra funzione che implementa l’alfa-beta pruning, chiamata quiesce. Essa, a differenza di search, genera soltanto mosse di cattura; così facendo si è implementata la ricerca estesa per situazioni dove il punteggio della posizione può variare di molto (per l’appunto nel caso di catture di pezzi). In essa avviene anche la chiamata alla funzione di valutazione della posizione. 38
  39. 39. Con il sistema così progettato si può ottenere grande profondità di calcolo nell’esplorazione della posizione nel caso il tempo concesso al computer sia elevato e contemporaneamente una risposta in breve tempo nel caso si volesse dare al software soltanto pochi secondi per decidere. 39
  40. 40. Fig. 7 -Algoritmo semplificato di ricerca della mossa migliore 40
  41. 41. Valutazione di una posizione Questo, dei tre problemi è sicuramente il più arduo: come valutare la bontà di una posizione? Molte sono le strategie di calcolo utilizzate negli scacchi normali. In ogni caso, la valutazione avviene con un algoritmo molto specifico, che funziona soltanto per lo speciale gioco degli scacchi classici. Tuttavia, alcuni fattori di cui si tiene conto negli scacchi possono essere utilizzati anche nel nostro caso. Sicuramente saranno sufficienti a giungere ad una soluzione iniziale soddisfacente, in modo che l’utente percepisca un certo grado di intelligenza nella scelta delle mosse da parte del computer, in quanto sono le più influenti anche nelle scacchi classici. In ogni caso si tratta sempre di algoritmi euristici, per i quali non riusciamo nemmeno a stimare il grado di ottimalità della soluzione trovata. Vediamo gli accorgimenti applicabili. Alternative progettuali • Bilanciamento degli schieramenti Il modo più intuitivo per assegnare un punteggio ad una posizione è contare quanto “materiale”, nel senso di pezzi, è ancora disponibile al bianco e quanto al nero. Inizialmente, entrambi dispongono di dieci pedoni, due unicorni, due cavalli, due alfieri, due torri, una regina, e naturalmente un re. La “potenza” di un pezzo è determinata unicamente dal suo grado di mobilità. La mobilità dipende sia da come esso può intrinsecamente muoversi (per regolamento) sia dalla sua posizione sulla scacchiera. 41
  42. 42. Vediamo in una tabella le “potenzialità” di ciascun pezzo: Direzioni Caselle Pezzo Al Sui Al Sui Slide Note centro vertici centro vertici Può muoversi in un ristretto numero di caselle Unicorno 8 1 8 4 Sì rispetto le 125 totali (vedi fig.8) La sua mobilità è ostacolata soltanto da pezzi Cavallo 24 8 24 8 No presenti sulle caselle di destinazione e non da quelli sul tragitto (“salta” oltre i pezzi). Può muoversi su metà delle caselle totali Alfiere 12 3 32 12 Sì (quelle del colore della sua casa di partenza) Può muoversi sempre al massimo in 12 caselle, Torre 6 3 12 12 Sì a meno che non vi siano ostacoli sul tragitto. Ad ogni mossa possiede le mobilità addizionate Regina 26 7 52 28 Sì di un alfiere, di una torre e di un unicorno. Come tutti i pezzi è molto mobile al centro della scacchiera. Tuttavia è opportuno tenerlo Re 26 7 26 7 No “in salvo” in quanto la sua perdita comporta la sconfitta. Il suo valore sarà quindi infinito. Il pedone non è qui trattato, essendo un pezzo “speciale” sotto molti punti di vista. Alcuni chiarimenti sulla tabella: con “slide” stiamo ad indicare se un pezzo può “scivolare” di più caselle in una direzione, nel modo in cui lo fanno appunto l’alfiere, la torre, la regina e l’unicorno. Nella tabella si è tentato di quantificare intuitivamente in prima approssimazione la mobilità dei pezzi, confrontando il numero di direzioni e il numero di caselle in cui ciascun pezzo può muoversi nel caso esso si trovi su una scacchiera vuota in un vertice oppure al centro. Risulta immediatamente chiaro che alcuni pezzi sono sempre più mobili di altri, e tutti traggono beneficio quando sono spostati verso il centro della scacchiera. Ad esempio, è chiaro che l’unicorno è sicuramente il pezzo di minor valore fra quelli segnati in tabella: non solo non è molto mobile, ma non può nemmeno viaggiare su tutta la scacchiera, bensì solo su una minima parte di essa (circa ¼ delle caselle). Per contro, la regina è sicuramente il pezzo che vale di più (ricordiamo che il re ha valore idealmente infinito, quindi non conta), in quanto essa è il pezzo più mobile di tutti. 42
  43. 43. Fig. 8 - I 4 “tipi” di unicorno diversi e il dettaglio delle loro mobilità. La cosa più difficile nella valutazione del materiale a disposizione è quantificare il valore dei singoli pezzi; negli scacchi classici si sono fissati, nei secoli di esperienza, alcuni valori di riferimento: o Pedone: 1 o Cavallo: 3 o Alfiere: 3 o Torre: 5 o Regina: 9 In realtà vi sono opinioni discordanti, spesso all’alfiere viene attribuito un valore maggiore che al cavallo. Poi verso il finale, ovvero quando ad un certo punto i pezzi in gioco cominciano ad essere “pochi”, i valori cambiano. Inoltre, il valore dei pedoni negli scacchi classici varia fortemente a seconda della posizione del pedone: se infatti esso può facilmente essere portato in zona di promozione varrà la pena difenderlo anche a costo di perdere ad esempio una torre ed un alfiere, per guadagnare una regina. 43
  44. 44. Indipendentemente da tutto ciò, analizzando la nostra variante 3D, ci si accorge che i valori non vi si adattano: la torre, ad esempio, non è così “potente” come negli scacchi, perde infatti nella terza dimensione la sua funzione di “muro”; inoltre non è più così efficace nel sostegno dei pedoni. Lungi dagli obiettivi di questa tesi eseguire un approfondito studio sul punteggio base da assegnare ai pezzi, si è immaginata una sorta di “traduzione” dagli scacchi classici agli scacchi 3D, successivamente spiegata. • Sviluppo Il valore di un pezzo è tanto maggiore quanto maggiore è la sua influenza sulla scacchiera. Ad esempio una torre nella sua posizione originale non “controlla” nessuna casella, ovvero non può muoversi perché bloccata da altri pezzi del suo stesso schieramento. I pezzi hanno massima influenza quando sono mossi verso il centro della scacchiera. Per questi motivi, negli scacchi classici si costruiscono array di interi di dimensioni della scacchiera, i cui valori corrispondono a piccoli incrementi o decrementi di punteggio da applicare ai pezzi a seconda della loro posizione. In questo modo una scacchiera con pezzi sviluppati varrà di più rispetto ad una con i pezzi bloccati nelle posizioni originali. Nel caso nostro sarà semplice adattare una simile tecnica, non essendo obiettivo di questa tesi individuare i numeri ottimali per ottenere la valutazione più precisa: sarà sufficiente un array con somma zero e valori tanto cospicui da fare la differenza rispetto ad un metodo di valutazione privo di questo accorgimento. • Struttura dei pedoni Negli scacchi classici, in due dimensioni, risulta abbastanza semplice e intuitivo modificare il valore dei pedoni in funzione delle loro posizioni reciproche. Ad esempio, se in una colonna vi sono due o più pedoni dello stesso colore è naturale pensare che essi si ostacoleranno. Al comparire della terza dimensione tuttavia questo fenomeno tende praticamente a scomparire, muovendosi essi non solo lungo le colonne ma anche attraverso i piani. 44
  45. 45. Omettiamo quindi di descrivere le varie strategie utilizzate per il calcolo del valore della struttura dei pedoni negli scacchi classici, perché esse non sarebbero adottabili per nostri scacchi 3D. • Posizione del re Negli scacchi classici, durante l’apertura (inizio della partita) e il mediogioco (parte del gioco difficilmente definibile, in essa i pezzi sono sviluppati e sono ancora “tanti”), il re è bene che stia dove si trova all’inizio, o ancora meglio “arroccato” ad uno dei due lati. Negli scacchi 3D non vi è manovra di arrocco, quindi è consigliabile che all’inizio esso rimanga nei pressi della casella iniziale, anche perché sarebbe inutile sprecare preziose mosse di sviluppo per spostare il lento re e portarlo alla mercé dell’esercito avversario. Nel finale della partita, quando i pezzi sono “pochi”, è essenziale, negli scacchi classici, che il re partecipi attivamente al combattimento. Infatti, in tutti i finali fondamentali (re e regina contro re, re e torre contro re, re e pedone contro re, ...) esso gioca un ruolo fondamentale. Di uguale importanza, se non maggiore, sarà negli scacchi 3D, nei quali è ancora più arduo, a causa della tridimensionalità, dare lo scacco matto. E’ quindi utile individuare un momento della partita oltre il quale il re “vale” di più se portato verso il centro dela scacchiera. La metodologia più semplice da utilizzare è valutare il materiale a disposizione dell’avversario: nel momento in cui i pezzi sono troppo pochi per nuocere seriamente, allora il re potrà essere portato allo scoperto. 45
  46. 46. Soluzione adottata Bilanciamento degli schieramenti Anzitutto è opportuno fissare dei valori di base a ciascun tipo di pezzo. Ciò può essere fatto nel seguente modo: si possono definire i suddetti valori rapportando i gradi di mobilità dei pezzi negli scacchi classici con quelli degli stessi nel 3D, e variare il loro valore proporzionalmente. Nella tabella seguente si compara la mobilità di cavallo, alfiere, torre e regina negli scacchi 3D e negli scacchi classici. 3D Chess Scacchi Classici Pezzo Direzioni Caselle visitabili Direzioni Caselle visitabili Al centro Sui vertici Al centro Sui vertici Al centro Sui vertici Al centro Sui vertici Cavallo 24 8 24 8 8 2 8 2 Alfiere 12 3 32 12 4 1 13 7 Torre 6 3 12 12 4 2 14 14 Regina 26 7 52 28 8 3 27 21 Nella prossima tabella si riportano soltanto le caselle visitabili, rapportate però al numero di caselle totali della scacchiera (125 nel 3D, 64 negli scacchi classici). Caselle visitabili / Caselle totali Pezzo 3D Chess Scacchi Classici Al centro Sui vertici Al centro Sui vertici Cavallo 0.192 0.064 0.125 0.031 Alfiere 0.256 0.096 0.203 0.109 Torre 0.096 0.096 0.219 0.219 Regina 0.416 0.224 0.422 0.328 Si nota subito che il cavallo 3D risulta molto più mobile di quello classico. Al contrario, la torre è sensibilmente meno mobile, come ci si aspettava. 46
  47. 47. Si osservi ora nella tabella seguente l’aumento di mobilità in prossimità dei vertici e in posizione centrale di ciascun pezzo dagli scacchi classici agli scacchi 3D: Fattore di incremento di mobilità nel 3D Pezzo Direzioni Caselle visitabili Caselle visitabili/Caselle totali Al centro Sui vertici Al centro Sui vertici Al centro Sui vertici Cavallo 3 4 3 4 1.536 2.064 Alfiere 3 3 2.286 1.714 1.261 0.881 Torre 1.5 1.5 0.857 0.857 0.438 0.438 Regina 3.25 2.333 1.857 1.333 0.986 0.683 A questo punto, applicando a questi fattori di incremento e ai valori negli scacchi classici un’opportuna funzione, sarà possibile ottenere dei valori da sperimentare nel motore per questi pezzi. Sicuramente il fattore di peso maggiore sarà il rapporto tra caselle visitabili e caselle totali, esso esprime la differenza di mobilità meglio rispetto agli altri fattori, essendo relativo. Un buon esempio potrebbe essere dare 10% di peso agli incrementi di direzioni, 14% di peso agli incrementi assoluti di caselle visitabili e 26% a quelli relativi. Perciò, definiti con e gli incrementi assoluti di direzione rispettivamente al centro e ai vertici e gli incrementi assoluti di caselle rispettivamente al centro e ai vertici e gli incrementi relativi di caselle rispettivamente al centro e ai vertici il fattore selezionato è: = [10 ∗ ( + ) + 14 ∗ ( + ) + 28 ∗ ( + ) ]/100 Esso andrà poi moltiplicato per il valore convenzionale del pezzo negli scacchi classici. Sostituendo i valori numerici si ottiene: o Cavallo: = 2.616 segue punteggio: 2.616 ∗ 3 = 7.848 o Alfiere: = 1.717 segue punteggio: 1.717 ∗ 3 = 5.151 o Torre: = 0.768 segue punteggio: 0.768 ∗ 5 = 3.839 o Regina: = 1.439 segue punteggio: 1.439 ∗ 9 = 12.95 47
  48. 48. I valori così ottenuti saranno utilizzati come punteggio base dei pezzi. Mancano ancora tuttavia Unicorno e Pedone. Per quanto riguarda l’Unicorno, esso ha una mobilità che oscilla tra circa (sui vertici) e circa (al centro) rispetto a quello della regina (in base alle tabelle stilate). Inoltre, può muoversi al massimo su circa ¼ della scacchiera; questo fa di esso un pezzo estremamente debole e poco prezioso. Fissiamo il valore dell’unicorno a rispetto a quello della regina, cioè 2.16. Per i pedoni non si hanno parametri di valutazione da cui partire, se non il fatto che negli scacchi classici essi valgono 1 punto. Fissiamo il loro valore a 1 anche negli scacchi 3D. Pezzo valore Si riassume nella tabella a fianco i valori di base che Pedone 1 si è deciso di adottare, arrotondati ad una cifra Unicorno 2.2 Cavallo 7.8 decimale: sarebbe inutile essere molto precisi in Alfiere 5.1 quanto si tratta di valori praticamente arbitrari. Torre 3.8 Regina 12.9 Sviluppo I pezzi aumentano di valore quando vengono sviluppati, cioè sostanzialmente portati verso il centro della scacchiera. Questo vale quasi per tutti, a parte per i pedoni. Essi infatti valgono tanto quanto più si avvicinano alla zona di promozione. Il modo più semplice ed efficiente per ottenere questo effetto è utilizzare un array di interi grande quanto la scacchiera, inserendo un numero negativo nelle caselle “cattive” (vertici e bordi) e un numero positivo nelle caselle “buone” (centrali). Per ogni pezzo mosso si sommerà al suo valore il numero corrispondente alla casella in cui lo si vuole muovere. Questi accorgimenti si rivelano utili specialmente nella fase iniziale del gioco. Posizione del re Al re assegneremo in fase di realizzazione un valore incommensurabilmente alto rispetto a quello degli altri pezzi, naturalmente, perché perderlo equivale a perdere la partita. 48
  49. 49. Il re necessita anche di due array di interi grandi come la scacchiera: uno per l’inizio della partita, per fare in modo che esso tenda a rimanere nella posizione iniziale, e uno per il finale. Si entrerà nel “finale” quando il valore dei pezzi avversari sarà uguale o inferiore a 1300, re escluso. Riepilogo funzionamento globale Fig. 9 - comportamento macroscopico del motore In figura 9 è illustrato il comportamento del motore dal punto di vista macroscopico: la UI comunica al motore la posizione della scacchiera, intesa come stato della partita. Se valida, si generano le mosse possibili. Se ne esistono, si ricerca la mossa migliore, e la si comunica alla UI. 49
  50. 50. Progettazione dell’Interfaccia Scacchiera Tridimensionale Essa è la componente principale dell’interfaccia utente. Vale la pena stendere una breve introduzione sull’argomento grafica tridimensionale. La grafica tridimensionale La computer grafica 3D è un ramo della computer grafica che si basa sull'elaborazione di modelli virtuali in 3D da parte di un computer. Essa viene utilizzata insieme alla computer animation nella realizzazione di immagini visuali per cinema o televisione, videogiochi, ingegneria, usi commerciali o scientifici. Il termine può anche essere riferito ad immagini prodotte con lo stesso metodo. La grafica computerizzata tridimensionale è basilarmente la scienza, lo studio e il metodo di proiezione della rappresentazione matematica di oggetti tridimensionali tramite un'immagine bidimensionale attraverso l'uso di tecniche come la prospettiva e l'ombreggiatura (shading) per simulare la percezione di questi oggetti da parte dell'occhio umano. Ogni sistema 3D deve fornire due elementi: un metodo di descrizione del sistema 3D stesso ("scena"), composto di rappresentazioni matematiche di oggetti tridimensionali, detti "modelli", e un meccanismo di produzione di un'immagine 2D dalla scena, detto "renderer". Modelli 3D Oggetti tridimensionali semplici possono essere rappresentati con equazioni operanti su un sistema di riferimento cartesiano tridimensionale: per esempio, l'equazione x²+y²+z²=r² è perfetta per una sfera di raggio r. Anche se equazioni così semplici possono sembrare limitative, l'insieme degli oggetti realizzabili viene ampliato con una tecnica chiamata geometria solida costruttiva (CSG, constructive solid geometry), la quale combina oggetti solidi (come cubi, sfere, cilindri, ecc.) per formare oggetti più complessi attraverso le operazioni booleane (unione, sottrazione e intersezione): un tubo può ad esempio essere rappresentato come la differenza tra due cilindri aventi diametro differente. L'impiego di equazioni matematiche pure come queste richiede l'utilizzo di una gran quantità di potenza di calcolo, e non sono quindi pratiche per le applicazioni in tempo reale come videogiochi e simulazioni. Una tecnica più efficiente, ma che permette un minore livello di dettaglio, per modellare oggetti consiste nel rilevare solo alcuni punti dell'oggetto, senza informazioni sulla curva compresa tra di essi. Il risultato è chiamato 50
  51. 51. modello poligonale. Questo presenta "faccette" piuttosto che curve, ma sono state sviluppate tecniche d rendering per ovviare a questa perdita di di dati. Delle superfici poligonali di un modello senza informazioni sulla curvatura possono essere comunque raffinate per via algoritmica in superfici perfettamente curve: questa tecnica è chiamata "superfici di su suddivisione" (subsurfing), perché la superficie viene suddivisa con un processo iterativo , in più superfici, sempre più piccole, fedeli alla curva interpolata e che vanno a comporre un'unica superficie sempre più liscia. Creazione della scena Una scena si compone di "primitive" (modelli tridimensionali che non ompone possono essere ulteriormente scomposti). Le primitive sono generalmente descritte all'interno del proprio sistema di riferimento locale, e vengono posizionate sulla scena attraverso opportune trasformazioni. Le trasformazioni affini più impiegate, come omotetia, zioni. rotazione e traslazione, possono essere descritte in uno spazio proiettivo con una matrice 4x4: esse si applicano moltiplicando la matrice per il vettore a quattro componenti che rappresenta ogni punto di controllo delle ogni curva. La quarta dimensione è denominata coordinata omogenea. Rendering Il rendering è il processo di produzione dell'immagine finale a partire dal modello matematico del soggetto (scena). Esistono molti algoritmi di rendering, ma tutti implicano la proiezione dei modelli 3D su una superficie 2D. Gli oggetti tridimensionali devono essere proiettati idealmente sulla superficie bidimensionale del monitor. Il tipo di proiezione p usato è la prospettica, anche se ad esempio nei più , sistemi CAD e CAM si utilizza la proiezione ortogonale. stemi La differenza fra le due è che nella prospettica gli oggetti più lontani sono visualizzati di dimensioni minori rispetto a quelli più vicini all’ “occhio”. I minori software rendono la prospettiva moltiplicando una costante di dilatazione k moltiplicando elevata all’opposto della distanza dall’osservatore. Se k = 1 non vi è prospettiva. All’aumentare di k aumenta la distorsione prospettica. Fig. 10 Un punto di fuga - Due punti di fuga - Tre punti di fuga Una trattazione dettagliata della matematica riguardande la proiezione prospettica esula dagli scopi di questo testo. 51
  52. 52. Per una spiegazione intuitiva è sufficiente immaginare una videocamera che si muove all’interno di uno spazio tridimensionale. In questo spazio sono contenuti i modelli tridimensionali, nel nostro caso si tratta della scacchiera e dei pezzi. L’utente sarà in grado di controllare il “movimento” di questa videocamera ideale all’interno dello spazio 3D in modo tale da poter visualizzare comodamente. Fig. 11 - Field of view (campo visivo) della videocamera In figura 11 è descritto il campo visivo che viene sottoposto all’utente: è di forma tronco-conica (frustum), tutto ciò che non è contenuto in esso non viene proiettato (sulla base minore, che rappresenta la superficie dello schermo) e quindi non viene visualizzato. Progettazione dei modelli Vi sono principalmente quattro modi per rappresentare un modello 3D: • Modelli poligonali – Un insieme di punti nello spazio tridimensionale, detti vertici, vengono connessi da segmenti in modo da formare una maglia (mesh). La maggior parte dei modelli 3D sono costruiti in questo modo per la loro flessibilità e velocità di rendering. Tuttavia, i poligoni sono piani, quindi con essi le curvature possono soltanto essere approssimate utilizzando un gran numero di poligoni. 52
  53. 53. • NURBS – Le superfici NURBS sono definite da curve spline, cioè parametrizzate da punti di controllo pesati. La curva segue (senza necessariamente interpolarli) i punti. Aumentando il peso di un punto si farà in modo che la curva passi più vicino ad esso. In questo modo si riescono ad ottenere superfici estremamente lisce. • Splines & Patches – Come le NURBS, splines and patches dipendono da line curve per definire la superficie visibile. Sono una via di mezzo fra NURBS e poligoni in termini di flessibilità e facilità di utilizzo. • Primitives modeling – Consiste nell’utilizzare primitive geometriche come sfere, cilindri, coni o cubi, come “mattoni” per modelli più complessi. Si ottengono così costruzioni veloci, e le forme sono matematicamente precise. Richiede però di definire geometricamente il modello da costruire; ciò non è sempre possible o facile da ottenere. Nel nostro caso, trattandosi di pezzi degli scacchi, quindi di forme geometriche non troppo complesse, i metodi migliori sarebbero modelli poligonali o primitives modeling. Quest’ultimo sembrerebbe il più indicato, tuttavia, sebbene sia controintuitivo, i modelli ottenuti sarebbero certamente più grezzi: modellare tutto tramite primitive, infatti, implicherebbe poter utilizzare soltanto curve e forme definibili geometricamente. Invece, tramite poligoni, si avrà più rapidità al momento della costruzione dei modelli (si potranno comporre facilmente tramite una serie di punti); l’arrotondamento delle superfici si potrà ottenere per suddivisioni successive, mantenendo inoltre la massima compatibilità (i formati poligonali sono i più comuni al momento). Pezzi Consisteranno in sette modelli poligonali, ciascuno per un tipo di pezzo. Il colore e il posizionamento sulla scacchiera verranno dati loro dal programma di gioco. Scacchiera Per costruirla si utilizzerà un mattoncino fondamentale (un parallelepipedo molto esteso in larghezza rispetto alla sua altezza, una sorta di “mattonella”), ripetuto 125 volte a colorazione alterna. Si visualizzeranno cioè cinque scacchiere piane sovrapposte, come in figura 1. 53
  54. 54. Interazione con l’utente Mouse – Movimentazione dei pezzi Per muovere i pezzi si deve poter, tramite mouse, selezionare il pezzo da muovere e successivamente selezionare la casella in cui muoverlo. In figura 12 è illustrato l’algoritmo ad alto livello per la selezione del pezzo. L’utente fa click sulla schermata dell’interfaccia 3D No Ha fatto click su un Sì Attesa di pezzo? interazione utente No No Ha fatto click su una Il pezzo è suo? casella? No Sì Sì Al momento è Il pezzo può evidenziato un muovere? pezzo? Sì Sì Togliere l’evidenziazione Evidenziare il pezzo e le al pezzo precedentemente La casella è caselle in cui esso può selezionato ed alle caselle No evidenziata? muovere in cui esso può muovere (Selezione Pezzo) (Deselezione Pezzo) Sì Muovere il pezzo nella casella (Spostamento Pezzo) Fig. 12 - Flow chart raffigurante il loop di input da mouse per l'utente 54
  55. 55. Il problema fondamentale però è che l’utente fa click su un punto di una superficie piana; a partire dalle coordinate piane del click bisogna risalire a quale punto dello spazio esso corrisponde. I modelli che posizioniamo nello spazio sono costituiti da triangoli. Si tratterà quindi di determinare, ciclando su tutti i modelli contenuti nello spazio, quale di essi è il più vicino alla camera fra quelli intersecati dalla semiretta avente origine nel punto in cui l’utente ha fatto click. I modelli sono composti da triangoli. Bisognerà quindi procedere in questo modo: 1. Per ogni modello controllare se la sfera di raggio minimo che lo contiene tutto interseca la semiretta in questione. 2. Se la sfera è intersecata bisognerà testare, triangolo per triangolo, se vi sono triangoli componenti il modello che intersecano la semiretta. 3. Se ve ne sono, salvare quello di distanza minima dalla camera. Se minore di quelli precedentemente trovati, memorizzare il modello a cui appartiene. In questo modo si troverà il modello più vicino alla camera intersecato dalla semiretta. Il problema più complesso è individuare quali triangoli di un modello, ammesso che ve ne siano, siano intersecati dalla semiretta. Un buon sistema per ottenere questo risultato è utilizzare un algoritmo il cui uso è abbastanza diffuso per casi come questo, ideato da Tomas Moller e Ben Trumbore nel 2003 chiamato “Fast, Minimum Storage Ray-Triangle Intersection". Questo algoritmo permette, senza perdita di velocità rispetto agli algoritmi precedenti, di risparmiare memoria nel suo utilizzo. In appendice si allega il documento originale che sarà utilizzato per implementare l’algoritmo nella parte di realizzazione. Tastiera – Camera Per far in modo che il gioco sia il più controllabile possibile, si progettano due modalità di visualizzazione (camera). Una il prima persona, che permette cioè di spostare liberamente la camera per mezzo della tastiera nello spazio contenente la scacchiera. 55
  56. 56. Un’altra invece vincolata alle righe e alle colonne della scacchiera: ovvero, essendovi, 5 righe e 5 colonne per ciascuno dei 5 piani, con (5 + 5) ∗ 5 = 50 posizioni; ad ogni spostamento di posizione la si farà puntare parallelamente alla direzione della riga o colonna, ma sarà possibile ruotarla sul posto per facilitare la visualizzazione. L’utente potrà in qualsiasi momento cambiare camera passando da totale libertà di movimento a controllo più vincolato. Comunicazione Interfaccia-Motore Differenza sostanziale tra le realizzazioni di Interfaccia e Motore sarà la tecnologia d’implementazione; per la prima è necessaria una piattaforma in grado di supportare la grafica 3D e che renda il più possibile agevole lo sviluppo di videogame, mentre per il secondo è importante l’efficienza di calcolo. Perciò, per l’Interfaccia sarà certamente opportuno utilizzare una API per il game-programming. Ve ne sono tante disponibili, ma non molte soddisfano il requisito della portabilità che è stato richiesto (vedi Analisi, pag. 17). Per quanto riguarda il Motore sarà più utile una realizzazione che non giri su macchina virtuale; in questo modo si avrà il massimo dell’efficienza. Quindi un linguaggio compilato. Per soddisfare la portabilità basterà usare un linguaggio molto diffuso, in modo che esistano compilatori per esso almeno per le piattaforme più utilizzate; certo, ci sarà bisogno di ricompilare per passare da un ambiente ad un altro, ma il requisito di efficienza è certamente più importante per ottenere un Motore “intelligente” in grado di tener testa al giocatore umano. La comunicazione fra i due è sufficiente che sia in una sola direzione, cioè dall’Interfaccia al Motore, o vice versa; non ponendo vincoli in questo frangente si lascia maggior libertà in fase di realizzazione. 56
  57. 57. Realizzazione Tecnologie utilizzate Microsoft XNA (Xbox New Architecture) Si tratta di un inseme di tool inseriti in un ambiente di runtime “managed” il cui scopo è facilitare lo sviluppo di videogame sollevando lo sviluppatore dall’onere di dover scrivere codice ripetitivo. Comprende: • XNA Framework Si basa sull’implementazione del .NET Compact Framework 2.0 per Xbox 360 e su .NET Framework 2.0 per Windows. E’ disponibile per Windows XP, Windows Vista e Xbox 360; permette di scrivere i videogame per il runtime di XNA: questo fa sì che essi possano girare su qualsiasi piattaforma a patto che questa supporti il runtime. L’utilizzo di questa tecnologia quindi ci permetterebbe di portare senza troppe difficoltà il gioco su piattaforme diverse, e ci solleva inoltre dall’onere di reinventare ciò che nel gergo del game development si definisce “game loop” (e tutto ciò che esso implica). • XNA Build E’ un insieme di tool per la gestione degli “asset” di gioco (icone, file audio, modelli 3D, ...). Aiuta a definire la cosiddetta game asset pipeline. Quest’ultima descrive il processo attraverso il quale i contenuti vengono trasformati in forme utilizzabili dall’engine del gioco. XNA Build permette di personalizzare a vari livelli ogni stadio della suddetta pipeline. • Mono.XNA E’ una implementazione cross platform di XNA gaming framework. Prende il nome da Mono, ormai nota implementazione cross platform di .NET framework Gira su Windows, MacOS e Linux e usa OpenGL per la grafica 3D. Al momento è ancora in fase di realizzazione. Questo aspetto di XNA ci assicura la portabilità richiesta nei requisiti non funzionali. XNA sarà quindi utilizzato per la realizzazione dell’Interfaccia. 57
  58. 58. Blender E’ un’applicazione per grafica 3D rilasciata sotto forma di software libero sotto GNU General Public License. Può essere usato per modeling e molto altro ancora, ed è disponibile per Linux, Mac Os X e Windows. Questo lo rende più che sufficiente per lo scopo di questa tesi. Con Blender saranno costruiti i modelli 3D di pezzi e scacchiera. ANSI C E’ lo standard emesso dall’American National Standards Institute (ANSI) per il linguaggio di programmazione C. ANSI C è supportato praticamente da tutti i compilatori più utilizzati; perciò, ogni programma scritto in C standard garantisce la compilazione su qualsiasi piattaforma che abbia una conforme implementazione del C. Il motore, essendo nulla più che una collezione di algoritmi e strutture dati, potrà quindi essere costruito in ANSI C per massimizzarne la portabilità pur mantenendo alta l’efficienza, trattandosi di un linguaggio compilato. Managed/Unmanaged Interoperability Realizzando l’Interfaccia in XNA, si utilizzerà C#, un linguaggio Microsoft facente parte del CLR di .NET. Si tratterà quindi di codice che gira in macchina virtuale, ovvero managed. Per contro, il Motore sarà scritto in ANSI C. Per la realizzazione in Microsoft Windows verrà compilato come dll C++ ed esporrà delle funzioni che potranno essere richiamate dall’Interfaccia tramite il sistema di interoperabilità tra codice managed e unmanaged. Si tratterà quindi di invocare codice unmanaged dal codice managed. Vi sono alcune alternative per compiere questa operazione: • Platform Invoke (detta anche P/Invoke) permette di richiamare funzioni scritte in qualsiasi linguaggio unmanaged a patto che la signature sia ridichiarata in codice managed. • COM interop permette di richiamare component COM da qualsiasi linguaggio managed. • C++ interop (anche detta It Just Works, IJW) è una caratteristica specifica del C++ che permette di utilizzare direttamente API COM e non solo. E’ più potente di COM interop ma richiede più cura. 58
  59. 59. Su MSDN si raccomanda di scegliere seguendo il flow-chart riportato in figura 13. Fig. 13 - Flow chart per la scelta della tecnologia per interoperabilità managed/unmanaged La dll del motore non sarà certo una COM API, essendo scritta in C. Con complex API si intende un’API che ha le signature dei metodi scritte in modo difficile da dichiarare in codice managed. Essendo l’API ancora da scrivere, faremo in modo che ciò non avvenga. L’API una volta scritta rimarrà tale; o comunque il numero di funzioni sarà sufficientemente piccolo (dell’ordine della decina) che anche se varierà non sarà difficile propagare la variazione anche al codice managed. Quindi la tecnologia per la comunicazione tra Interfaccia e Motore sarà Platform Invoke. 59
  60. 60. Realizzazione del Motore Il motore è stato realizzato in ANSI C, non essendovi necessità o particolari benefici nel ricorrere all’OOP in un caso come questo. Esso consiste di 5 file header “.h” e 8 file “.c”. Header files defs.h defs.h, contenente la definizione delle costanti, dei tipi non primitivi utilizzati e delle variabili globali. Qui sotto si riporta uno spezzone di codice contente le strutture dati utilizzate per la memorizzazione delle mosse generate. moveBytes contiene l’indice della casella di arrivo e partenza di una mossa (from e to), il pezzo a cui viene promosso nel caso di mossa di promozione (promote), una serie di bit indicante il tipo di mossa (bits: i primi 3 bit di questo char vengono usati per indicare se si tratta di una mossa di cattura, e/o di un pedone, e/o di promozione). typedef struct { int from; int to; int promote; char bits; } moveBytes; In union con un int in modo che si riesca a compararle rapidamente. typedef union { moveBytes b; int u; } moveUni; gener_t associa la mossa ad un punteggio (score) utilizzato per il sorting nell’implementazione dell’history heuristics. typedef struct { moveUni m; int score; } gener_t; history_t è invece un elemento dello stack usato per mantenere la storia della partita, in modo da poter ritornare indietro di una mossa. 60

×