Sei sulla pagina 1di 22

IntellidAma

Rossinelli Emanuele

10 Aprile 2010

1
CONTENTS 2

Contents
1 Introduzione 3

2 Dama Italiana 3
2.1 Regole del gioco . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.2 Regole rilassate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

3 Intelligenza Artificiale 5
3.1 Costruzione dell’albero di ricerca . . . . . . . . . . . . . . . . . . . . . . . 5
3.2 Valutazione dei nodi foglia . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3.3 Verifica di situazioni non quiescenti . . . . . . . . . . . . . . . . . . . . . . 6
3.4 Algoritmo MIN-MAX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.4.1 Potatura alfa-beta . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.5 Scelta della mossa migliore . . . . . . . . . . . . . . . . . . . . . . . . . . 10

4 Implementazione 10
4.1 Struttura del programma . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
4.2 Implementazione della scacchiera . . . . . . . . . . . . . . . . . . . . . . . 10
4.3 Struttura dati Mossa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
4.4 Mosse possibili . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
4.5 Struttura dell’albero di ricerca . . . . . . . . . . . . . . . . . . . . . . . . 13
4.6 Classe IDamaEngine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
4.6.1 Costruzione dell’albero di ricerca . . . . . . . . . . . . . . . . . . . 14
4.6.2 Funzioni di valutazione . . . . . . . . . . . . . . . . . . . . . . . . 14
4.6.3 Verifica di quiescenza . . . . . . . . . . . . . . . . . . . . . . . . . 17
4.6.4 Minimax e potatura alfa-beta . . . . . . . . . . . . . . . . . . . . . 18
4.6.5 Effettuare la mossa . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
4.7 Grafica e gestione degli eventi . . . . . . . . . . . . . . . . . . . . . . . . . 18

5 Conclusioni 20
5.1 Stato dell’arte sui programmi risolutori della dama . . . . . . . . . . . . . 21
1 INTRODUZIONE 3

1 Introduzione

L’obiettivo del progetto svolto è stato quello di costruire un software che permettesse a
un umano (in seguito chiamato PLAYER) di giocare a dama italiana contro la macchina
(in seguito chiamata COMPUTER).
Per il raggiungimento dell’obiettivo si è reso necessario un preliminare studio delle regole
del gioco, per permettere la scelta di un adatto algoritmo di Intelligenza Artificiale.
Il programma è stato scritto in C#, con l’ausilio dell’ambiente di sviluppo integrato
(IDE) Microsoft Visual Studio 2008.

Figure 1: Videata del programma in esecuzione

2 Dama Italiana

2.1 Regole del gioco

Le regole della dama italiana sono le seguenti:


1. La damiera si compone di 64 caselle alternate per colore, bianche e scure, e va
posizionata con l’ultima casella in basso a destra di colore nero.
2. Ciascun giocatore dispone all’inizio di 12 pedine, di colore diverso da quelle dell’avversario
(bianche o nere), collocate sulle prime tre righe di caselle scure poste sul proprio
lato della damiera. Il nero occupa le caselle dal n. 1 al n. 12, il bianco quelle dal
n. 21 al n. 32. La scelta del colore fra i giocatori si effettua per sorteggio. Inizia
a giocare sempre il bianco.
2 DAMA ITALIANA 4

3. La pedina si muove sempre in diagonale sulle caselle scure di una casella alla volta e
soltanto in avanti. Quando una pedina raggiunge una delle caselle dell’ultima riga
viene promossa, diventa dama e deve essere contraddistinta con la sovrapposizione
di un’altra pedina prelevata tra quelle non in gioco.
4. Ogni pedina può mangiare quelle avversarie che si trovano in avanti, sulla casella
diagonale accanto alla propria e che abbiano la casella successiva libera. Dopo la
presa, se incontrano in diagonale altre pedine con la successiva casella libera, si
deve continuare a mangiare senza togliere la mano dalla pedina stessa. In tal caso
la presa si chiama multipla. Le pedine prese vanno tolte dalla damiera.
5. La dama si muove anch’essa di una casella alla volta, sempre in diagonale, in tutte
le direzioni possibili, mangiando sia le pedine che le dame avversarie.
6. In caso di presa è obbligatorio mangiare i pezzi. L’antica regola del ”soffio”, ossia
quella di catturare il pezzo avversario che pur avendone diritto, per distrazione o
per scelta non aveva mangiato, è stata abolita dalla Federazione Dama nel 1934.
7. Avendo più possibilità di presa si debbono rispettare obbligatoriamente nell’ordine
le seguenti priorità:
(a) è obbligatorio mangiare dove ci sono più pezzi;
(b) a parità di pezzi di presa tra pedina e dama, quest’ultima è obbligata a
mangiare;
(c) la dama sceglie la presa dove si mangiano più dame;
(d) a parità di condizioni si mangia dove s’incontra prima la dama avversaria.
8. ”Pezzo toccato = Pezzo mosso”: il giocatore che, nel proprio turno di gioco, tocca
un proprio pezzo sulla damiera è obbligato a muoverlo. Se si vuole aggiustare un
pezzo messo male sulla damiera bisogna prima avvertire l’avversario dichiarando
”accomodo” o ”acconcio” e attendere l’assenso dell’avversario.
9. Si vince per abbandono dell’avversario, che si trova in palese difficoltà, o quando
si catturano o si bloccano tutti i pezzi avversari.
10. Si pareggia in una situazione di evidente equilibrio finale per accordo dei giocatori
o per decisione dell’arbitro a seguito del conteggio di 40 mosse richiesto da uno dei
due giocatori. Il conteggio delle mosse si azzera e riparte da capo tutte le volte che
uno dei due giocatori muove una pedina o effettua una presa.

2.2 Regole rilassate

Il programma è stato scritto considerando un numero ridotto di regole, precisamente


eliminando la regola n. 8 e la n. 10 (il pareggio non è considerato nel nostro gioco).
Inoltre, relativamente alla regola n. 7, sono stati rilassati i vincoli b, c, d.
3 INTELLIGENZA ARTIFICIALE 5

3 Intelligenza Artificiale

Prima di addentrarci nei dettagli implementativi del software, descriviamo come è stato
costruito il modulo di Intelligenza Artificiale, ovvero quali strategie il software usa per
scegliere la mossa migliore da effettuare.
Ogniqualvolta sia il computer a dover muovere, vengono eseguite le seguenti azioni in
sequenza:
1. Costruzione dell’albero di ricerca
2. Valutazione dei nodi foglia dell’albero creato
3. Verifica di situazioni non quiescenti
4. Applicazione dell’algoritmo MIN-MAX per la valutazione di tutti i nodi dell’albero
5. Scelta della mossa migliore da eseguire
Vediamole brevemente.

3.1 Costruzione dell’albero di ricerca

La prima cosa da fare è costruire l’albero di ricerca, che ha le seguenti proprietà:


• il nodo radice dell’albero rappresenta la situazione attuale (il computer deve muo-
vere), tale nodo è a profondità 0 nell’albero
• ogni altro nodo dell’albero rappresenta possibili situazioni future
• i nodi a profondità dispari sono possibili situazioni di arrivo generate da una mossa
del computer
• i nodi a profondità pari sono possibili situazioni di arrivo generate da una mossa
del player
• le foglie dell’albero sono sempre a profondità pari
L’unico nodo noto è proprio il nodo radice. L’albero poi viene costruito simulando tutte
le mosse possibili, nel rispetto delle regole del gioco. La profondità massima dell’albero
(ovvero la profondità delle foglie, che chiameremo depth) è definita a priori; più profonda
sarà la ricerca, migliori risulteranno le mosse del computer, e peggiori saranno i tempi di
risposta. La scelta di terminare la costruzione dell’albero a una certa profondità depth,
piuttosto che costruirlo fino ai nodi terminali (vincita o perdita) è necessaria per rendere
interattivo il programma. Tale decisione si chiama ”taglio della ricerca”.

La nozione di ”situazione” riassume lo stato attuale del gioco, ovvero rappresenta la


scacchiera e la mossa effettuata per arrivare a tale situazione a partire dalla precedente
(il padre del nodo in questione).
3 INTELLIGENZA ARTIFICIALE 6

3.2 Valutazione dei nodi foglia

Adesso che l’albero è stato creato, dobbiamo per prima cosa valutare le situazioni di
arrivo, ovvero associare una valutazione ai nodi foglia.
Tale valutazione, associata quindi alla scacchiera in un preciso istante, è un numero
intero, che deve essere tanto più grande quanto più la scacchiera è favorevole dal punto
di vista del computer. Per ottenere questo, si deve utilizzare una funzione di valutazione,
chiamata anche euristica, che sintetizzi in un numero la possibilità di vincere da parte
del giocatore computer.
La scelta di tale funzione è molto importante, in quanto rappresenta la strategia di gioco
utilizzata dal computer: possiamo costruire euristiche in modo tale che il computer sia
difensivo, e cerchi di proteggere le zone più sensibili della scacchiera, oppure posiamo
fare in modo che il computer giochi aggressivo, cercando di andare velocemente a dama
ed eliminare una ad una le pedine dell’avversario.
Creare euristiche valide non è assolutamente semplice; la prima che ci può venire in
mente è la seguente:

V = 100(|pedinecomputer|−|pedineplayer|)+200(|damecomputer|−|dameplayer|) (1)

E’ ovvio che non si tratta di una buona funzione di valutazione: alcune configurazioni
della scacchiera potrebbero risultare migliori di altre solo perchè presentano una pedina
del giocatore avversario in meno, non considerando il fatto che le posizioni in cui si
trovano le pedine sono fondamentali per l’esito del gioco. Chiameremo questa funzione
”peso pedine”.
Come ultima osservazione, ricordiamo che il calcolo della funzione di valutazione non
deve impiegare troppo tempo per non degradare le prestazioni del software; si preferiscono
quindi funzioni di valutazioni lineari piuttosto che metodi più complessi.

3.3 Verifica di situazioni non quiescenti

Per prima cosa, cerchiamo di dare una definizione (o almeno un’idea) di situazione
quiescente.
Una situazione (nel nostro caso, una configurazione della scacchiera) si dice quiescente
se è improbabile che nell’immediato futuro manifesti notevoli oscillazioni.
Per esempio, la configurazione rappresentata in fig. 2, è evidentemente non quiescente
se il turno è del giocatore verde: la sua prossima mossa sarà una cattura doppia con una
conseguente certezza di andare a dama.
Chiaramente si tratta di una verifica strettamente legata alla funzione di valutazione
scelta: se abbiamo scelto una funzione che prende in considerazione solo il numero di
pedine, possiamo decidere di etichettare una situazione come non quiescente se alla
prossima mossa il numero di pedine rimarrà invariato. D’altra parte, se avessimo scelto
3 INTELLIGENZA ARTIFICIALE 7

Figure 2: Situazione non quiescente

una funzione di valutazione che dà importanza anche alle posizioni delle pedine nella
scacchiera, allora anche lo spostamento di una pedina verso una posizione strategica-
mente buona (per esempio, vicino a dama) renderebbe la situazione non quiescente.

Abbiamo già parlato del taglio della ricerca. Proprio a causa di tale taglio entra in gioco
la ricerca di quiescenza: tagliando la ricerca a una certa profondità depth, andremo a
valutare anche nodi che non sono quiescenti. La situazione rappresentata in fig. 2, per
esempio, secondo la funzione di valutazione ”peso pedine” è in leggero vantaggio per il
giocatore rosso, visto che presenta una pedina in più. In effetti, però, scegliendo questa
mossa, al passo successivo il giocatore verde farà una cattura multipla, passando quindi
immediatamente in vantaggio. Quindi la valutazione che avevamo fatto sul nostro nodo
non è affatto affidabile.
Per questo motivo è necessario applicare una valutazione solo alle situazioni quiescenti,
mentre per quelle situazioni che non lo sono, si preferisce costruire un albero più pro-
fondo, esplorando mosse successive, fino a quando non si arriva a una situazione quies-
cente da poter valutare. Scendere in profondità fino a quando non si arriva ad avere solo
situazioni quiescenti può essere molto oneroso: per questo motivo si sceglie di tagliare
anche la ricerca di quiescenza.

3.4 Algoritmo MIN-MAX

L’algoritmo min-max (o minimax) è il vero e proprio modulo di intelligenza artificiale.


Una volta che abbiamo le valutazioni dei nodi foglia (che supponiamo essere quiescenti),
3 INTELLIGENZA ARTIFICIALE 8

dobbiamo risalire l’albero e valutare tutti i nodi, fino ad arrivare alla radice. Solo in
questo modo possiamo scegliere la mossa migliore da effettuare a partire dalla radice.
L’algoritmo sfrutta le seguenti assunzioni:
• Si considerano due giocatori, MIN e MAX
• Nel nostro caso, il giocatore MAX è il computer: sceglie ogni volta la mossa che
gli consente di massimizzare la valutazione della situazione di arrivo
• Dualmente, il giocatore MIN è l’avversario, e cerca quindi di minimizzare tale
valutazione (ovvero cerca di rendere minime le nostre probabilità di vittoria)
Il turno di gioco passa alternativamente da MIN a MAX e viceversa, e l’algoritmo
etichetta i nodi dell’albero in modo da garantire per il computer la mossa migliore,
assumendo che l’avversario giocherà al meglio, minimizzando la valutazione.

Figure 3: Albero di ricerca con le foglie valutate, e applicazione dell’algoritmo minimax

In fig. 3 si vede l’applicazione dell’algoritmo: partendo dalle foglie, sceglie per il padre
la valutazione minore (in quanto il turno è dell’avversario, e minimizzerà le nostre possi-
bilità di vincita). Applicandolo ricorsivamente, alla prossima iterazione il turno sarà di
max, quindi sceglie la valutazione maggiore (perchè è il computer che sceglie la mossa).
Si vede che viene scelta come mossa migliore la seconda, che mi assicura una valutazione
futura di 8, ma potrebbe diventare 10 a causa di un errore dell’avversario. Viene invece
scartata la mossa n. 3, anche se a causa di un errore dell’avversario mi avrebbe portato
una valutazione maggiore, pari a 15. Ma il nostro algoritmo assume che l’avversario sia
preparato, e giochi bene.
3 INTELLIGENZA ARTIFICIALE 9

Per contro, la complessità dell’algoritmo minimax è molto elevata: in termini temporali,


è esponenziale e pari a

O(bm ) (2)

dove b è il branching factor medio dei nodi (ovvero il numero di figli medio per ogni
nodo), mentre m è la profondità dell’albero (depth).
Per quanto riguarda la complessità spaziale, essa equivale alla complessità di una ricerca
in profondità, quindi lineare.

3.4.1 Potatura alfa-beta

Si può ridurre la complessità dell’algoritmo minimax usando la potatura alfa-beta:


sostanzialmente si potano parti dell’albero che non è necessario analizzare, in quanto
la soluzione finale sarà la stessa indifferentemente da tale porzione.

Figure 4: Porzioni dell’albero che è possibile potare

Dalla fig. 4 si può intuire perchè i nodi cancellati sono ininfluenti ai fini del calcolo
dell’algoritmo minimax, e possono quindi essere potati. Quando si arriva a valutare
il nodo foglia con valore 3, abbiamo già etichettato un nodo al livello superiore con il
valore 8. Sappiamo che al turno successivo, ovvero per la valutazione del nodo radice,
si sceglierà il massimo, quindi nodi con valore minore di 8 non verranno considerati.
D’altra parte, valutando il nodo foglia di valore 3, sappiamo che la valutazione del nodo
padre sarà il minimo dei suoi figli, quindi nel nostro caso sarà un valore minore o uguale
a 3. Ma siccome 3 è già un valore più piccolo di 8, sappiamo subito che questo sottoal-
bero non verrà certo scelto come mossa migliore. Possiamo quindi tagliarlo ed evitare
di analizzare gli altri figli.
La miglioria portata dalle potature all’algoritmo minimax dipende ovviamente dall’ordine
in cui analizziamo i nodi. Se avessimo analizzato per primo il nodo foglia di valore 8,
avremo tagliato anche il sottoalbero con le foglie 2 e 5, senza bisogno di analizzarlo.
Non è immediato poter ordinare i nodi senza introdurre ulteriore complessità, e anche il
comportamento dell’algoritmo minimax con l’aiuto di alfa-beta risulta scoraggiante: la
complessità temporale diventa
4 IMPLEMENTAZIONE 10

 m 
b
O . (3)
log(b)

3.5 Scelta della mossa migliore

Il lavoro è ormai finito: la scelta della mossa migliore è immediata. Basta infatti anal-
izzare i figli del nodo radice, e considerare il figlio con valutazione massima. A questo
punto andiamo a vedere quale mossa è stata applicata per generare quel figlio (tale in-
formazione deve essere contenuta nel nodo figlio), e semplicemente la applichiamo.

4 Implementazione

Adesso che sono chiare le modalità di funzionamento degli algoritmi usati per l’intelligenza
artificiale, vediamo come sono stati implementati nel linguaggio C#.

4.1 Struttura del programma

Il programma segue le regole della programmazione orientata agli oggetti (OOP). La


figura 5 rappresenta l’interconnessione delle varie classi definite nel software.

4.2 Implementazione della scacchiera

La soluzione più ovvia per l’implementazione della scacchiera è usare una matrice. Più
precisamente, una matrice 8x8 di tipo Casella, quindi ogni elemento della matrice è
un’istanza della classe Casella. La scacchiera viene costruita settando opportunamente
gli attributi delle istanze di Casella, a seconda della posizione dell’elemento. Avremo
quindi alcune caselle di tipo NERA, alcune di tipo FREE, alcune COMPUTER e altre
PLAYER. Questi tipi sono definiti dalla struttura enumerativa casella tipo. Allo stesso
modo, la struttura casella peso indica se la casella (in questo caso un pezzo di un gioca-
tore) è una PEDINA oppure una DAMA.
Questa matrice rappresentante la scacchiera è un attributo della classe Scacchiera; si è
ritenuto necessario definire una classe che contenesse la scacchiera in quanto durante la
costruzione dell’albero, ogni nodo avrà come attributo una diversa istanza della classe
Scacchiera, ciascuna rappresentante la particolare configurazione di pezzi nella damiera.
Alcuni dei metodi più rilevanti della classe Scacchiera sono:
• posizione futura: data una istanza di Casella e una direzione (mossa direction)
ritorna la futura posizione della casella, in termini di row e col;
4 IMPLEMENTAZIONE 11

Figure 5: Ossatura del programma


4 IMPLEMENTAZIONE 12

• mossa possibile multiple: data una istanza di Casella e una direzione, ritorna una
lista di tipo Mossa che rappresenta tutte le mosse possibili a partire da quella
direzione. Sarà analizzata in dettaglio più avanti;
• muovi multiple: data una istanza di Casella e una Mossa, effettua la mossa sulla
scacchiera;
• n caselle player: ritorna il numero di pezzi del giocatore sulla scacchiera, per capire
se il computer ha vinto.

4.3 Struttura dati Mossa

La struttura dati Mossa ha due attributi:


• direzioni: una lista concatenata di tipo mossa direction che rappresenta la lista di
mosse semplici consecutive che compongono la mossa completa;
• priorita: un intero che rappresenta il numero di pezzi catturati dalla mossa, usato
per decidere se la mossa può essere effettuata oppure se esistono altre mosse con
priorita maggiore, quindi obbligatorie.
Per mossa semplice si intende uno spostamento di un pezzo (senza catture), o una
cattura di un singolo pezzo. Per realizzare le catture multiple (consecutive), è stata
quindi implementata una lista di mosse semplici (direzioni, di fatto) consecutive da
eseguire. La struttura mossa direction è una struttura enumerativa con i valori NE, NO,
SE, SO.

4.4 Mosse possibili

Analizziamo in dettaglio la funzione più interessante che abbiamo descritto fino ad ora:
il metodo mossa possibile multiple della classe Scacchiera. La funzione di costruzione
dell’albero, che simula tutte le mosse possibili per tutte le caselle, chiama il presente
metodo per tutte le direzioni: NE, NO, SE, SO. Questo metodo ritorna un valore
booleano, true o false, a seconda che la mossa sia possibile o meno.
La prima fase è controllare se con la mossa proposta si esce o meno dalla scacchiera. In
caso positivo, si va a vedere se la mossa è legale: per esempio, le pedine del computer
non possono muovere verso Nord, cosı̀ come quelle del player non possono muovere verso
Sud, finchè non diventano dame. Se la mossa risulta legale, si prova a vedere se la casella
di arrivo è libera. In tal caso, abbiamo trovato una mossa semplice (priorità = 0) e si
esce.
Se invece la mossa semplice non è possibile, perchè la casella di arrivo è occupata, può
presentarsi possibilità di mangiare un pezzo: se la pedina nella posizione di arrivo ap-
partiene all’avversario, e la casella ancora successiva è libera. In questo caso, dobbiamo
controllare se sono possibili catture consecutive, richiamando ricorsivamente il metodo
4 IMPLEMENTAZIONE 13

in questione per tutte le direzioni possibili.

Questa funzione ritorna, insieme al valore booleano, una lista di mosse perchè nel caso
di catture multiple la pedina potrebbe poter scegliere tra due o più pedine da mangiare
al secondo step, dopo la prima cattura. Queste sono mosse con la stessa priorità e quindi
tutte legali.

4.5 Struttura dell’albero di ricerca

L’albero di ricerca è implementato come un albero generico. La classe Tree ha sola-


mente la funzione di rappresentare il nodo radice dell’albero. Ogni nodo dell’albero è un
elemento di tipo TreeNode, una classe che ha come attributi
• n figli: un intero che rappresenta il numero di figli del nodo;
• profondita: un intero che rappresenta la profondità del nodo;
• padre: link al nodo padre;
• fratello dx, fratello sx: link ai nodi fratelli;
• primofiglio: link al primo figlio dell’albero (il primo figlio dell’albero è l’unico figlio
a non avere fratello sinistro);
• valutazione: un intero (anche nullo) che rappresenta la valutazione associata al
nodo (configurazione della scacchiera);
• value: un’istanza della classe Scacchiera, ovvero la configurazione della scacchiera
legata al nodo;
• casella: un’istanza della classe Casella, che rappresenta il pezzo della damiera sul
quale si è effettuata la mossa;
• mossa: un elemento di tipo Mossa, rappresenta la mossa effettuata sulla casella di
cui sopra, a partire dalla configurazione della scacchiera del nodo padre;
Le classi Tree e TreeNode contengono tutte le funzioni, richiamate dalla classe IDamaEngine,
necessarie per la costruzione dell’albero, la valutazione dell’albero, la potatura alfa-beta,
la scelta della mossa migliore, e la ricerca di quiescenza. Si tratta semplicemente di
metodi in grado di lavorare ricorsivamente sulla struttura ad albero creata. In partico-
lare, per la costruzione dell’albero è stata creata la funzione AddFiglio, per la potatura la
funzione DeleteNode, mentre la ricerca di quiescenza ha richiesto la scrittura del metodo
AddSottoAlbero. Sono metodi intuitivi, di cui viene omessa la descrizione dettagliata.
Sono inoltre presenti funzioni per il calcolo della profondità dell’albero (TreeNodeDepth),
per la valutazione dei nodi a profondità depth (NodeValuta) e la funzione che realizza
l’algoritmo minimax, NodeValutaMinMax, l’unica che merita una descrizione dettagliata,
lasciata al capitolo ”Minimax e potatura alfa-beta”.
4 IMPLEMENTAZIONE 14

4.6 Classe IDamaEngine

IDamaEngine è la classe che si occupa della strategia di gioco del computer. Di fatto è
la classe che comunica con la parte grafica del programma, come spiegato in dettaglio
più avanti. Essa è definita come ”partial class”, ovvero come classe parziale, dove l’altra
parte della classe è chiamata Euristiche.
Gli attributi sono, ovviamente, una istanza della classe Scacchiera (master), e altre carat-
teristiche definite dall’utente: la profondità massima dell’albero che verrà creato (dMax),
la funzione euristica scelta (funzione euristica), e due variabili booleane (quiescenza e
quiescenzaricorsiva).

Quando è il turno del computer, la parte grafica del programma invoca la funzione
alfabetaPruning che si occupa di costruire l’albero, valutarlo, applicare la verifica di
quiescenza, applicare l’algoritmo minimax ed effettuare la mossa. Vediamo questi passi
in dettaglio.

4.6.1 Costruzione dell’albero di ricerca

Per la costruzione dell’albero, viene chiamata la funzione costruisci albero con il parametro
master, ovvero la scacchiera al momento attuale. Tale funzione inizializza l’albero,
costruendo il nodo radice proprio con l’istanza master. Poi chiama la funzione ricor-
siva costruisci albero.
Inizialmente si cerca la mossa possibile con la priorità maggiore; successivamente, si
aggiunge un figlio per ogni mossa legale (quindi si aggiunge un figlio per ogni mossa
possibile di priorità non minore alla priorità massima calcolata). Per ogni figlio creato,
si chiama ricorsivamente la stessa funzione, fino a quando non si raggiunge la profondità
massima stabilita.

L’albero viene poi valutato, applicando alle foglie una funzione di valutazione, tra quelle
descritte nel capitolo successivo.

4.6.2 Funzioni di valutazione

Abbiamo già parlato di funzioni di valutazione in quanto euristiche, e della loro impor-
tanza ai fini della strategia di gioco della macchina. Nel software sono state implementate
quattro diverse funzioni di valutazione:
1. Peso Pedine
2. Peso + Posizione
3. Spettacolo
4 IMPLEMENTAZIONE 15

4. Fully Guarded
La prima funzione di valutazione, Peso Pedine, valuta una configurazione della scacchiera
ignorando le posizioni delle pedine, ma contandone solo il numero. Alle dame è attribuito
un peso doppio rispetto a quelle delle pedine; i pezzi appartenenti al computer vengono
sommati, mentre quelli appartenenti al player vengono sottratti dalla valutazione. Gio-
cando con questa strategia, ci si accorge che è un po’ troppo semplicistica; per esempio,
il programma ci permetterà facilmente di avvicinarci a dama se gli permetteremo di farci
catturare una pedina.

La funzione Peso + Posizione include nel computo anche un valore legato alla posizione
delle pedine. Il peso dei pezzi viene moltiplicato per il quadrato dell’indice relativo alla
riga in cui si trova il pezzo. In questo modo si preferiscono configurazioni in cui le i
pezzi del computer vanno verso dama, e allo stesso modo si preferisce che le pedine del
computer non si avvicinino troppo alla nostra base. Inoltre viene attribuita una penalità
alle dame che stanno sui bordi, per incitarle a porsi in mezzo alla damiera, per rendere
la strategia più aggressiva. Anche questa funzione di valutazione non ci assicura buone
giocate: tende a scoprire velocemente le sue posizioni di base, per andare verso il centro
della damiera, rendendo cosı̀ il player più facilitato a fare dama.

Per realizzare la terza funzione di valutazione, Spettacolo, sono state utilizzate al-
cune considerazioni che sono state documentate in rete. L’idea sfrutta la geometria
della damiera, che presenta delle regioni più facilmente proteggibili contro gli attacchi
dell’avversario ed altre più vulnerabili. La damiera può essere vista come divisa in quat-
tro aree: l’angolo in alto a destra e quello diametralmente opposto in basso a sinistra
vengono definiti cantoni, mentre i due rimanenti sono definiti biscacchi.

Figure 6: Biscacchi e cantoni.


4 IMPLEMENTAZIONE 16

Un giocatore riesce facilmente a difendere il proprio cantone, infatti l’avversario non ha


modo di sfondare la base, diversamente la parte del biscacco è più facilmente sfondabile
per un avversario sacrificando una propria pedina e permettendo all’altra di andare a
fare dama. In base alle osservazioni precedentemente fatte, si può decidere di utilizzare
varie strategie di gioco. In attacco di solito si preferisce dirigere la propria azione verso
il biscacco avversario, che presenta più possilità di sfondare la linea base avversaria e
di andare a dama. Per quanto riguarda il gioco in difesa, dato che anche il proprio
biscacco può essere facilemente attaccato dall’avversario, l’idea è di non far avanzare i
pezzi da questa parte e di muovere quelli del cantone. Per implementare tale strategia, si
assegna ad ogni area un peso in modo che il secondo ed il terzo quadrante abbiano peso
maggiore mentre il primo ed il quarto peso minore. In questo modo, quando si esegue
la valutazione, si obbligano le pedine che sono all’interno di tale area di rimanervici e
quelle che sono in aree con peso minore di dirigersi verso le altre. Per permettere alle
pedine di muoversi all’interno delle aree, si è deciso di dare alle caselle dei quadranti dei
pesi crescenti in base all’aumetare della distanza.
Provando ad implementare la strategia come è stata sopra descritta, ci si accorge che
è troppo difensiva e che pertanto, arriva difficilemente a fare dama. Per questo motivo
si è pensato di modificare la funzione di valutazione e di suddividerla in due fasi: nella
fase iniziale si è implementata la strategia precedentemente descritta, mentre nella fase
finale si promuovono le pedine che vanno a dama. Il test per disinguere la fase iniziale
da quella finale controlla se è presente una dama oppure se sono rimaste poche pedine.
Inoltre si privilegiano ulteriormente le configurazioni in cui il giocatore ha la mossa, ossia
se il numero di coppie di pedine che distano l’una dall’altra di un numero pari di caselle
è dispari allora il giocatore che ha il turno è avvantaggiato.
Queste considerazioni, che sembrano assolutamente vincenti, nella pratica si sono ver-
ificate molto banali e facilmente battibili. Un motivo del perchè di questa situazione si
può ritrovare nella difficoltà di riuscire ad associare i giusti pesi ai parametri delle fun-
zioni di valutazione. Un peso sbagliato rischia o di dar troppo peso ad alcuni parametri
penalizzandone altri o viceversa, e si può verificare che il risultato ottenuto non è quello
desiderato.

Per la realizzazione della quarta funzione di valutazione si è utilizzata la documentazione


di un’altra strategia di gioco che permette di giocare in attacco, mantenendo comunque
una buona difesa della propria linea di partenza. Questa idea è stata suggerita da una
prima considerazione banale e strategica che spesso viene utilizzata in una partita. Se si
tiene la propria linea di base piena, che in termini tecnici si dice avere una fully guarded
back rank, non si permette all’avversario di andare facilmente a dama, a meno che non
sacrifichi qualche pezzo.
Mantenendo la base coperta e spostando tutte le pedine verso la dama, si rischia però
di creare una fascia di pedine che alle spalle presentano delle caselle vuote, permettono
facilmente l’avversario di attaccare e mangiarle. Per questo motivo si è realizzata una
strategia più sicura, basata su tale concetto, Federazione Italiana Dama. In questa si
afferma che i pezzi devono muoversi in formazione compatta, solidamente piantata alla
4 IMPLEMENTAZIONE 17

base e con punte avanzate ben appoggiate. Questa frase riassume il punto centrale della
strategia che è stata realizzata: si è provato a muovere le pedine, facendole avanzare verso
dama in formazione offensiva, ma cercando nel contempo che tale formazione fosse ben
salda alla base, cosı̀ da poter sfondare le linee avversarie, mantenendo le proprie spalle
coperte. Per realizzare la strategia, si è costruito una funzione per il calcolo del supporto,
ossia delle pedine che si trovano alle spalle di altre. La funzione scansiona la damiera
cercando se nelle posizioni dietro la pedina selezionata lungo le diagonali, compaiono
pedine dello stesso colore e se questo accade continua a controllare all’indietro ricorsi-
vamente fino al raggiungimento della base. Ogni volta che trova una tale situazione per
una propria pedina aumenta il valore della funzione di valutazione, privilegiando i casi in
cui si presenta una posizione delle proprie pedine che ricorda una forma a piramide con
la base ben saldata sulla linea di base. In tal modo si promuovono quelle configurazioni
ben saldate alla base, penalizzando le altre. Inoltre per ogni pedina si è aggiunto un
valore in base alla posizione occupata all’interno della damiera, privilegiando quelle più
vicine alla dama, e si sono penalizzate le configurazioni che prevedevano le dame vicino
ai bordi laterali della scacchiera, preferendo che ricoprissero ruoli centrali più offensivi.

In tutte le funzioni si aggiunge una piccola quantità aleatoria, per evitare che il pro-
gramma esegua sempre la stessa mossa in caso di mosse con identica valutazione.

4.6.3 Verifica di quiescenza

Una volta valutate le foglie dell’albero, per ogni foglia si fa la verifica di stato quiescente.
Se ne occupa la funzione is quiescent, che di fatto, data la foglia da verificare, costruisce
un albero di profondità 2 a partire da quella foglia, usando proprio la stessa funzione
costruisci albero. Per prima cosa, si valutano le foglie di questo albero. A questo punto,
viene usato l’algoritmo minimax con potatura alfa-beta (lo stesso di cui parleremo tra
poco) per valutare tutti i nodi dell’albero. Se la valutazione del nodo foglia cosı̀ calcolata,
si discosta molto (negativamente) dalla precedente valutazione associata al nodo foglia
di cui stiamo verificando la quiescenza, si dice che la foglia non è uno stato quiescente.
In questo caso, possiamo decidere di richiamare (ricorsivamente) la stessa funzione, per
esplorare ancora più in profondità l’albero creato, nella speranza di arrivare a configu-
razioni stabili (quiescenti).
Fatto ciò, se non abbiamo trovato configurazioni quiescenti, siamo costretti ad ”appen-
dere” l’albero creato all’albero di ricerca creato inizialmente (con la funzione AddSot-
toAlbero della classe Tree), aumentandone quindi la profondità totale.
La decisione della quiescenza o meno di una configurazione dipende strettamente dalla
funzione di valutazione utilizzata. Sono state definite quindi tante funzioni per prendere
questa decisione quante sono le euristiche implementate.

La verifica di quiescenza, e l’ulteriore seconda verifica sono opzionali, a seconda della


scelta del giocatore.
4 IMPLEMENTAZIONE 18

4.6.4 Minimax e potatura alfa-beta

Una volta noto l’albero di ricerca, compresa la verifica


La funzione che valuta tutti i nodi dell’albero, a partire dalle foglie fino alla radice, sec-
ondo l’algoritmo minimax fa parte della classe Tree: NodeValutaMinMax. Tale funzione
viene invocata con un parametro, esattamente la profondità dei nodi che dobbiamo val-
utare. All’interno del metodo alfabetaPruning della classe IDamaEngine, quindi, viene
chiamata questa funzione per ogni valore di depth, partendo dalla profondità delle foglie
fino al valore uno, in modo da valutare tutto l’albero.
NodeValutaMinMax segue alla lettera la teoria dell’algoritmo minimax e della potatura
alfa beta, già illustrata all’inizio della relazione.

4.6.5 Effettuare la mossa

Adesso che l’albero è totalmente etichettato, basta analizzare tutti i figli del nodo radice
(ovvero tutti i nodi a profondità uno). Consideriamo quindi il nodo che ha la valutazione
maggiore; da tale nodo estraiamo la mossa associata ed il gioco è fatto. Basta richia-
mare la funzione muovi multiple sulla scacchiera, passando come parametro la mossa in
questione.

Tale funzione si occuperà di seguire in dettaglio le direzioni proposte dalla mossa, elim-
inando i pezzi catturati e riconfigurando opportunamente le caselle della scacchiera. In
seguito viene richiamata la procedura che si occupa di visualizzare la mossa effettuata,
quindi di aggiornare la scacchiera sul video.

4.7 Grafica e gestione degli eventi

La finestra dell’applicazione permette l’impostazione di alcuni parametri del software


in qualsiasi momento del gioco. E’ possibile scegliere la strategia (di fatto la funzione
di valutazione euristica), la profondità dell’albero di ricerca (ovvero il numero di mosse
in avanti da prevedere), l’attivazione della verifica di quiescenza e l’attivazione della
seconda iterazione della stessa verifica.
La prima mossa è del giocatore. E’ possibile trascinare solo le pedine che hanno mosse
legali, e tale pedina può essere lasciata su una casella libera (bianca) solo se la mossa cosı̀
effettuata è permessa. Questa operazione si chiama drag&drop. E’ possibile effettuare
catture multiple, semplicemente eseguendo le catture una dopo l’altra.
Quando la pedina viene rilasciata sulla casella di arrivo, l’evento associato chiama una
funzione che si occupa di notificare la mossa alla classe IDamaEngine, che provvede a
ripercuotere le modifiche sulla matrice che rappresenta la scacchiera. Allo stesso modo,
quando il computer effettua una mossa vengono visualizzati degli effetti per rendere
4 IMPLEMENTAZIONE 19

Figure 7: Impostazioni del programma.

Figure 8: La casella di arrivo viene evidenziata, se la mossa è legale.


5 CONCLUSIONI 20

Figure 9: Presa multipla effettuata dal computer.

chiara la mossa scelta dal computer.


Il software notifica la fine della partita con un semplice messaggio all’utente, tramite un
oggetto detto MessageBox.

5 Conclusioni

Per ottenere giocate sensate da parte del computer, è necessario attivare la verifica di
quiescenza a due livelli, con una profondità dell’albero di ricerca almeno pari a 3 (di
fatto, l’albero sarà in questo caso profondo 6). La strategia che sembra essere la migliore
è l’ultima tra quelle analizzate, Fully Guarded. Con questi parametri, la risposta del
programma sarà molto lenta, non solo a causa delle dimensioni dell’albero da valutare,
ma anche a causa dell’analisi delle mosse possibili. C’è da dire che, analizzando le mosse
possibili, in molti casi l’albero di ricerca generato è significativamente ridotto, il che
porta la valutazione ad essere estremamente veloce.

Ricordando la regola che in caso di più mosse possibili si debba scegliere quella che
garantisce il numero massimo di pezzi catturati, si potrebbe pensare che in realtà non è
necessario analizzare la legalità delle mosse prima della costruzione dell’albero, ma sem-
plicemente la ricerca dell’ottimo porterebbe a scegliere la mossa che garantisce il numero
massimo di pezzi catturati. Questo è estremamente falso, in quanto:
1. a causa della profondità dell’albero di ricerca, potrebbe essere conveniente non
eseguire una cattura per migliorare la funzione obiettivo nelle mosse successive;
2. potrebbe essere conveniente non eseguire una cattura per mantenere la pedina in
5 CONCLUSIONI 21

posizione favorevole, nel caso in cui la funzione di valutazione consideri anche la


posizione dei pezzi.

5.1 Stato dell’arte sui programmi risolutori della dama

La prima grande impresa relativa all’intelligenza artificiale associata al gioco della dama
risale al 1952, quando Arthur Samuel dell’IBM sviluppò un programma per la dama che
apprese la sua funzione di valutazione giocando da solo per migliaia di volte.
Nel 1989, all’Università di Alberta, sotto la coordinazione del prof. Jonathan Schaeffer,
un gruppo di studiosi comincia a concentrarsi sul problema della dama. Nasce quindi
Chinook, che vince gli U.S. Open nel 1992. Il programma usa, tra l’altro, una ricerca
alfa-beta combinata con un database di soluzioni perfette per tutte le posizioni con sei
pezzi. Nel 2004 vince il campionato del mondo, e nel 2006 il programma è stato costretto
al ritiro per manifesta superiorità.
Nel 2007, finalmente, l’equipe di studiosi ha affermato di aver risolto il gioco della dama,
con il loro programma: Chinook non può perdere, quindi qualsiasi umano provi a sfidarlo,
può al massimo pareggiare.
REFERENCES 22

References

[1] Università degli Studi di Padova, “http://www.math.unipd.it/ mgelain/dama/,” .


[2] Federazione Italiana Dama, “http://www.federdama.it,” .
[3] Russel e Norvig, Intelligenza Artificiale: un approccio moderno, Prentice Hall Inter-
national, 1995.