Appendice B - Othello come applet Il gioco dell'othello, detto anche Reversi, consiste in una scacchiera di 64 caselle, 8 x 8, su cui all'inizio si trovano quattro pedine, due nere e due bianche, vedi figura B.1. Ognuno dei due avversari è legato a un colore diverso e può mettere sulla scacchiera pedine del proprio colore. Una pedina può essere posta sulla scacchiera solo se racchiude una o più pedine del colore avversario fra se stessa e un altra pedina del proprio colore. Tutte le pedine racchiuse in questo modo diventano del colore della pedina posta sulla scacchiera. Lo scopo del gioco è di riempire la scacchiera con il maggior numero di pedine del proprio colore. Figura B.1 Posizioni di partenza del gioco Othello Viene qui presentato un applet Java che permette di giocare a Othello contro il computer. In questa versione il computer gioca con le pedine nere mentre il giocatore ha le pedine bianche. Il giocatore pone le sue pedine effettuando un click con il mouse sulla casella prescelta. Alla prima mossa, il giocatore può passare il gioco al computer effettuando un click in una posizione non valida. La stessa cosa deve essere fatta nel caso in cui il giocatore non abbia mosse valide. Effettuando un click dopo la fine del gioco, si fa iniziare una nuova partita. Sono disponibili più livelli che possono essere impostati dal documento HTML per mezzo del parametro livello: il livello 1 è per principianti e man mano che si sale di livello, aumenta la bravura del computer e i suoi tempi di elaborazione per generare una mossa. Il livello di default è il 4. I sorgenti contengono in totale circa 500 linee di codice, esclusi i commenti, e le classi compilate occupano circa 10K, per cui possono essere eseguite da un browser Internet anche disponendo solo di una connessione via modem a bassa velocità. Il codice è stato scritto per essere più leggibile che efficiente, ma comunque, con le potenze elaborative attuali, non c è nessun problema fino al livello 4 compreso. I sorgenti del gioco possono essere inseriti in un file di nome Othello.java e sono riportati di seguito. Gioco dell'othello di Marco Bertacca
import java.awt.; import java.awt.event.; import java.applet.applet; Questa classe rappresenta la scacchiera del gioco dell'othello. Essa si occupa di accettare le mosse, verificarle e memorizzare la situazione derivante da esse. @version 1.00 97/08/11 @author Marco Bertacca class Scacchiera { Larghezza della scacchiera espressa in numero di celle static final int LARGHEZZA = 8; Altezza della scacchiera espressa in numero di celle static final int ALTEZZA = 8; Intero indicante una casella vuota static final int VUOTO = -1; Intero indicante una casella occupata da una pedina bianca static final int BIANCO = 0; Intero indicante una casella occupata da una pedina bianca static final int NERO = 1; Indice dell'elemento dell'array che riporta il punteggio posizionale static final int POSIZIONALE = 0; Indice dell'elemento dell'array che riporta il numero di pedine girate static final int PEDINE = 1; Matrice del punteggio di ogni casella racchiusa @see #aggiorna private static final int puntiinterni[][] ={ { 50, 50, 50, 50, 50, 50, 50, 50, { 50, 0, 0, 0, 0, 0, 0, 50, { 50, 0, 20, 10, 10, 20, 0, 50, { 50, 0, 10, 20, 20, 10, 0, 50, { 50, 0, 10, 20, 20, 10, 0, 50, { 50, 0, 20, 10, 10, 20, 0, 50, { 50, 0, 0, 0, 0, 0, 0, 50, { 50, 50, 50, 50, 50, 50, 50, 50 ; Matrice del punteggio di ogni casella racchiudente @see #aggiorna
private static final int puntiesterni[][] ={ { 800, -20, 20, 20, 20, 20,-20, 800, { -20,-400,-40,-20,-20,-40,-400,-20, { 20, -40, 20, 0, 0, 20, -40, 20, { 20, -20, 0, 0, 0, 0, -20, 20, { 20, -20, 0, 0, 0, 0, -20, 20, { 20, -40, 20, 0, 0, 20, -40, 20, { -20,-400,-40,-20,-20,-40,-400,-20, { 800, -40, 20, 20, 20, 20, -20,800 ; Matrice per individuare velocemente le posizioni intorno a una casella data private static final int intorno[][] = { { -1, -1, { -1, 0, { -1, 1, { 0, -1, { 0, 1, { 1, -1, { 1, 0, { 1, 1 ; Array contenente le pedine della scacchiera private int tavola[][]; Costruisce una scacchiera con le pedine per iniziare una nuova partita. @see #reset Scacchiera() { reset(); Costruisce una scacchiera copiando lo stato da quella passata come argomento. Scacchiera(Scacchiera s) { int y; tavola = (int[][]) s.tavola.clone(); for (y = 0; y < ALTEZZA; y++) tavola[y] = (int[]) s.tavola[y].clone(); Inizializza la scacchiera alle condizioni iniziali void reset () { int y, x; tavola = new int[altezza][larghezza]; for (y = 0; y < ALTEZZA; y++) for (x = 0; x < LARGHEZZA; x++) tavola[y][x] = VUOTO; tavola[altezza / 2][LARGHEZZA / 2] = BIANCO; tavola[altezza / 2-1][LARGHEZZA / 2-1] = BIANCO; tavola[altezza / 2][LARGHEZZA / 2-1] = NERO; tavola[altezza / 2-1][LARGHEZZA / 2] = NERO; Restituisce la pedina a riga y e colonna x int pedinaa(int y, int x) { if (y >= 0 && y < ALTEZZA && x >= 0 && x < LARGHEZZA)
return tavola[y][x]; else return VUOTO; Aggiorna la scacchiera in conseguenza dell'inserimento di una pedina nella posizione indicata da y e x e col colore indicato. @param colore colore della pedina da porre @param y riga su cui porre la pedina @param x colonna su cui porre la pedina @param punteggio pone in punteggio[posizionale] una valutuzione posizionale della mossa e in punteggio[pedine] il numero di pedine girate void aggiorna(int colore, int y,int x, int punteggio[]) { int ny, nx; int i; boolean gira; int altrocolore = colore == BIANCO? NERO : BIANCO; punteggio[posizionale] = puntiesterni[y][x]; punteggio[pedine] = 0; tavola[y][x] = colore; for (i = 0; i < 8; i++) { ny = y + intorno[i][0]; nx = x + intorno[i][1]; if (ny >= 0 && ny < ALTEZZA && nx >= 0 && nx < LARGHEZZA && tavola[ny][nx] == altrocolore) { gira = false; ny += intorno[i][0]; nx += intorno[i][1]; for ( ; ny >= 0 && ny < ALTEZZA && nx >= 0 && nx < LARGHEZZA; ny += intorno[i][0], nx += intorno[i][1]) { if (tavola[ny][nx] == colore) { gira = true; else if (tavola[ny][nx] == VUOTO) if (gira) { ny = y + intorno[i][0]; nx = x + intorno[i][1]; for ( ; ny >= 0 && ny < ALTEZZA && nx >= 0 && nx < LARGHEZZA; ny += intorno[i][0], nx += intorno[i][1]) { if (tavola[ny][nx] == altrocolore) { tavola[ny][nx] = colore; punteggio[posizionale] += puntiinterni[ny][nx]; punteggio[pedine]++; else Restituisce true se è ammesso porre una pedina nella posizione specificata, false altrimenti. @param colore colore della pedina da porre @param y riga su cui porre la pedina
@param x colonna su cui porre la pedina boolean èvalida(int colore, int y,int x) { boolean Return = false; int ny, nx; int i; int altrocolore = colore == BIANCO? NERO : BIANCO; if (y >= 0 && y < ALTEZZA && x >= 0 && x < LARGHEZZA && tavola[y][x] == VUOTO && (colore == BIANCO colore == NERO)) for (i = 0; i < 8 &&!Return; i++) { ny = y + intorno[i][0]; nx = x + intorno[i][1]; if (ny >= 0 && ny < ALTEZZA && nx >= 0 && nx < LARGHEZZA) { if (tavola[ny][nx] == altrocolore) { ny += intorno[i][0]; nx += intorno[i][1]; for ( ; ny >= 0 && ny < ALTEZZA && nx >= 0 && nx < LARGHEZZA; ny += intorno[i][0], nx += intorno[i][1]) { if (tavola[ny][nx] == colore) { Return = true; else if (tavola[ny][nx] == VUOTO) return Return; Se è ammesso porre una pedina nella posizione specificata, la pedina viene posta e viene restituito true, altrimenti viene restituito false. @param colore colore della pedina da porre @param y riga su cui porre la pedina @param x colonna su cui porre la pedina boolean mettipedina(int colore, int y, int x) { boolean Return; int punteggio[] = { 0, 0 ; if (Return = èvalida (colore, y, x)) aggiorna (colore, y, x, punteggio); return Return; Restituisce il numero di pedine vuote sulla scacchiera. L'array di due elementi fornito come argomento viene riempito con il numero di pedine bianche e nere. @param colore array di due elementi il cui elemento colore[bianco] viene riempito col numero di pedine bianche mentre l'elemento colore[nero] viene riempito col numero di pedine nere @return il numero di caselle vuote int punti (int colore[]) { int y, x; int Return = colore[bianco] = colore[nero] = 0; for (y = 0; y < ALTEZZA; y++) for (x = 0; x < LARGHEZZA; x++)
if (tavola[y][x] == BIANCO) colore[bianco]++; else if (tavola[y][x] == NERO) colore[nero]++; else Return++; return Return; Questa classe rappresenta un giocatore di othello. @version 1.00 97/08/11 @author Marco Bertacca class Computer { L'oggetto scacchiera su cui giocare private Scacchiera scacchiera; Il colore associato al giocatore private int colore; Il livello di gioco private int livello; Variabile usata per eseguire il numero di ricorsioni pari al livello di gioco. private int livellotemp; Flag per stabilire quando il gioco è nella fase finale private boolean finale; Contiene il numero di mosse da giocare nella fase finale private int finaleda; Costruisce un'istanza @param sc scacchiera su cui giocare @param co colore assegnato all'istanza @param li livello di gioco. Computer (Scacchiera sc, int co, int li) { scacchiera = sc; colore = co; livello = li; if (livello < 4) finaleda = 2 livello + 2; else finaleda = 10; Sollecita una mossa. @return true se la mossa viene eseguita, false se non ci sono mosse possibili
boolean muovi () { int x, y; int mx = -1, my = -1; int punteggio[] = { 0, 0 ; int valore, maxvalore = Integer.MIN_VALUE; if (scacchiera.punti(punteggio) < finaleda) finale = true; else finale = false; for (y = 0; y < 8; y++) for (x = 0; x < 8; x++) if (scacchiera.èvalida (colore, y, x)) { livellotemp = 0; valore = valutamossa (colore, y, x, scacchiera); if (valore > maxvalore) { maxvalore = valore; mx = x; my = y; if (mx >= 0) { scacchiera.mettipedina (colore, my, mx); return true; else return false; Esegue una valutazione della mossa. Questo metodo richiama se stesso per un numero di volte pari al numero del livello meno 1 @param colore colore della pedina da porre @param y riga su cui porre la pedina @param x colonna su cui porre la pedina @param sc scacchiera su cui valutare la mossa @return un intero corrispondente alla bontà della mossa private int valutamossa (int colore, int y, int x, Scacchiera sc) { int Return; int nx, ny; int punteggio[] = { 0, 0 ; int punti; int altropuntitmp, altropuntimax; int altrocolore = colore == Scacchiera.BIANCO? Scacchiera.NERO : Scacchiera.BIANCO; Scacchiera sclocale = new Scacchiera(sc); sclocale.aggiorna (colore, y, x, punteggio); if (finale) punti = punteggio[scacchiera.pedine]; else punti = punteggio[scacchiera.pedine] + punteggio[scacchiera.posizionale]; if ((y == 0 y == 7 x == 0 x == 7)) punti += valutabordi(y, x, colore, sclocale); if (++livellotemp < livello finale) { altropuntimax=-10000; for (ny=0; ny < 8; ny++) for (nx = 0; nx < 8; nx++) if (sclocale.èvalida (altrocolore, ny, nx )) { altropuntitmp = valutamossa(altrocolore, ny, nx, sclocale); if ( altropuntitmp > altropuntimax ) altropuntimax = altropuntitmp; punti -= altropuntimax;
--livellotemp; return punti; Valuta la bontà di una mossa sul bordo della scacchiera. @param y riga su cui porre la pedina @param x colonna su cui porre la pedina @param colore colore della pedina da porre @param sc scacchiera su cui valutare la mossa @return un intero corrispondente alla bontà della mossa private int valutabordi (int y, int x, int colore, Scacchiera sc) { int Return = 0; int altrocolore = colore == Scacchiera.BIANCO? Scacchiera.NERO : Scacchiera.BIANCO; int pedinasx = Scacchiera.VUOTO; int pedinadx = Scacchiera.VUOTO; if (y == 0 y == 7) { if (x == 0 x == 7) Return = 100; else { pedinasx = sc.pedinaa (y, x - 1); pedinadx = sc.pedinaa (y, x + 1); if (pedinasx == altrocolore && pedinadx == altrocolore) Return = 100; else if (pedinasx==scacchiera.vuoto && pedinadx==scacchiera.vuoto && (x - 2 >= 0 && sc.pedinaa (y, x - 2) == colore x + 2 < 8 && sc.pedinaa (y, x + 2) == colore)) Return = -10; if (pedinasx == Scacchiera.VUOTO) { if (pedinadx == Scacchiera.VUOTO) if (pedinadx == altrocolore) { x++; while (x < 8); if (pedinasx == altrocolore) { if (pedinadx == altrocolore) if (pedinadx == Scacchiera.VUOTO) { x++; while (x < 8); if (pedinadx == Scacchiera.VUOTO) { if (pedinasx == Scacchiera.VUOTO) if (pedinasx == altrocolore) { x--; while (x >= 0);
if (pedinadx == altrocolore) { if (pedinasx == altrocolore) if (pedinasx == Scacchiera.VUOTO) { x--; while (x >= 0); else if (x == 0 x == 7) { pedinasx = sc.pedinaa (y - 1, x); pedinadx = sc.pedinaa (y + 1, x); if (pedinasx == altrocolore && pedinadx == altrocolore) Return = 100; else if (pedinasx==scacchiera.vuoto && pedinadx==scacchiera.vuoto && (y - 2 >= 0 && sc.pedinaa (y - 2, x) == colore y + 2 < 8 && sc.pedinaa (y + 2, x) == colore)) Return = -10; if (pedinasx == Scacchiera.VUOTO) { if (pedinadx == Scacchiera.VUOTO) if (pedinadx == altrocolore) { y++; while (y < 8); if (pedinasx == altrocolore) { if (pedinadx == altrocolore) if (pedinadx == Scacchiera.VUOTO) { y++; while (y < 8); if (pedinadx == Scacchiera.VUOTO) { if (pedinasx == Scacchiera.VUOTO) if (pedinasx == altrocolore) { y--; while (y >= 0); if (pedinadx == altrocolore) { if (pedinasx == altrocolore) if (pedinasx == Scacchiera.VUOTO) { y--; while (y >= 0);
return Return; Questa classe rappresenta l'interfaccia grafica per giocare a othello. Essa accetta le mosse dall'utente e sollecita le mosse del computer. @see java.applet.applet @version 1.00 97/08/11 @author Marco Bertacca public class Othello extends Applet implements Runnable, MouseListener { Larghezza di una cella della scacchiera in pixel private int larghcella; Altezza di una cella della scacchiera in pixel private int altcella; Larghezza totale private int larghezza; Altezza totale private int altezza; Colore assegnato al giocatore umano private int colore; Colore assegnato al computer private int altrocolore; Flag che indica se il giocatore umano deve passare private boolean passa = false; Flag che indica quando il computer sta elaborando una mossa private boolean muovecomputer = false; Scacchiera di gioco private Scacchiera scac; Il giocatore computer private Computer comp; Livello di gioco private int livello = 4; Stringa per fornire informazioni al giocatore umano private String lineastato;
Font usato per visualizzare le informazioni private Font font = new Font("Courier", Font.PLAIN, 12); Altezza del font usato per visualizzare le informazioni private int altezzafont; Effettua l'inizializzazione dell'applet public void init() { int livn; String livs = getparameter ("livello"); if (livs!= null) { livn = new Integer(livs).intValue(); if (livn > 0 && livn < 9) livello = livn; altezzafont = getfontmetrics(font).getheight(); Dimension d = getsize(); larghcella = d.width / 8; altcella = (d.height - altezzafont) / 8; larghezza = larghcella 8; altezza = altcella 8; lineastato = "Othello livello: " + livello; scac = new Scacchiera(); comp = new Computer (scac, Scacchiera.NERO, livello); colore = Scacchiera.BIANCO; altrocolore = Scacchiera.NERO; addmouselistener (this); Disegna la scacchiera e le informazioni per l'utente public void paint (Graphics g) { int i, j; Color oldcolor = g.getcolor(); g.setcolor (new Color (0, 128, 0)); g.fillrect (0, 0, larghezza, altezza); g.setcolor (Color.black); for (i = 0; i < 9; i++) g.drawline (0, i altcella, larghezza, i altcella); for (i = 0; i < 9; i++) g.drawline (i larghcella, 0, i larghcella, altezza); for (i = 0; i < 8; i++) for (j = 0; j < 8; j++) { switch(scac.pedinaa(i, j)) { case Scacchiera.BIANCO: g.setcolor (Color.white); g.filloval (j larghcella + 1, i altcella + 1, larghcella - 1, altcella - 1); case Scacchiera.NERO: g.setcolor (Color.black); g.filloval (j larghcella + 1, i altcella + 1, larghcella - 1, altcella - 1); g.setfont(font); g.setcolor (Color.black); g.drawstring (lineastato, 0, altezza + altezzafont);
Valuta se esiste una mossa possibile per una pedina del colore specificato private boolean esistemossa(int col) { int y, x; for (y = 0; y < Scacchiera.ALTEZZA; y++) for (x = 0; x < Scacchiera.LARGHEZZA; x++) if (scac.èvalida (col, y, x)) return true; return false; public void mouseclicked(mouseevent e) { public void mousepressed(mouseevent e) { public void mouseentered(mouseevent e) { public void mouseexited(mouseevent e) { In base alla posizione del mouse, ricava la mossa del giocatore umano. Se il gioco è già finito, fa iniziare una nuova partita. Se siamo alla prima mossa e la mossa del giocatore umano non è valida, passa la mossa al computer. Se la mossa è valida, aggiorna la scacchiera e se a quel punto il gioco è finito, visualizza i risultati, altrimenti attiva un thread per l'elaborazione della mossa del computer. public void mousereleased(mouseevent e) { int x = e.getx(); int y = e.gety(); int punteggio[] = { 0, 0 ; if (!muovecomputer) { if (!esistemossa(colore) &&!esistemossa(altrocolore)) { scac.reset(); lineastato = "Othello livello: " + livello; repaint(); else if (passa scac.mettipedina(colore,y/altcella,x/larghcella)) { if (!esistemossa(colore) &&!esistemossa(altrocolore)) stampapunteggio(); else { muovecomputer = true; Thread t = new Thread(this); t.start (); lineastato = "Sto pensando..."; repaint(); else if (scac.punti (punteggio) == 60) { muovecomputer = true; Thread t = new Thread(this); t.start (); lineastato = "Sto pensando..."; repaint(); Sollecita una mossa del computer. public void run() { comp.muovi(); passa = false; if (!esistemossa(colore)) if (!esistemossa(altrocolore)) stampapunteggio();
else { lineastato = "Devi passare: clicka"; passa = true; else lineastato = "Tocca a te"; repaint(); muovecomputer = false; Visualizza il punteggio finale di una partita. void stampapunteggio () { int punteggio[] = { 0, 0 ; scac.punti (punteggio); if (punteggio[scacchiera.bianco] > punteggio[scacchiera.nero]) lineastato = "Hai vinto " + punteggio[scacchiera.bianco] + " a " + punteggio[scacchiera.nero]; else if (punteggio[scacchiera.bianco] < punteggio[scacchiera.nero]) lineastato = "Hai perso " + punteggio[scacchiera.bianco] + " a " + punteggio[scacchiera.nero]; else lineastato = "Hai pareggiato " + punteggio[scacchiera.bianco] + " a " + punteggio[scacchiera.nero]; passa = false; repaint(); Per eseguire l applet si può ricorrere a un testo in formato HTML come il seguente <title>othello</title> <hr> <applet code=othello.class width=161 height=190> <param name=livello value=4> </applet> <hr> Il livello di gioco viene impostato tramite il parametro livello e per default è 4. Il programma è formato da tre sole classi: Othello, che deriva da Applet e si occupa dell interazione con l utente, Computer, che elabora le mosse del computer e infine Scacchiera, che rappresenta appunto la scacchiera del gioco. Si può notare come la valutazione della mossa del computer sia eseguita da un thread indipendente in modo tale da permettere il ridisegno della scacchiera senza dover aspettare il termine dell elaborazione della mossa. Il metodo repaint infatti non causa l immediato ridisegno della finestra, ma segnala solo una necessità, a cui il sistema risponde appena possibile. I tempi di valutazione della mossa sono quasi impercettibili se il livello è basso ma crescono vertiginosamente al crescere del livello. Per capirne il perché, vediamo come fa il computer a generare una mossa. La classe Scacchiera comprende il metodo èvalida che permette di valutare se una mossa è valida o meno, e il metodo aggiorna che, oltre ad accettare una mossa e a modificare la scacchiera di conseguenza, restituisce anche una valutazione sulla sua validità. Questa valutazione è espressa da due numeri, uno dei quali rappresenta semplicemente il numero di pedine convertite mentre l altro corrisponde a una valutazione strategica basata su due matrici, puntiinterni e puntiesterni. Ogni volta che il computer deve muovere, viene invocato il metodo muovi nel quale vengono riconosciute le mosse valide tramite il metodo èvalida e per ognuna di esse viene invocato il metodo valutamossa passandogli come argomento anche l oggetto della classe
Scacchiera che rappresenta la scacchiera corrente, vedi figura B.2. Quest ultimo metodo si crea una scacchiera temporanea identica a quella passata come argomento e su di essa applica la mossa in valutazione, ricavando in tal modo un punteggio che ne indica la validità. Se stiamo giocando al livello 1, questo valore viene direttamente restituito al metodo muovi che alla fine premierà la mossa col punteggio maggiore. Giocando al livello 2 invece vengono valutate tutte le mosse che possono derivare dalla mossa in esame e il punteggio più alto viene sottratto a quello della mossa in esame. Giocando a livello n vengono valutati quindi n livelli di mosse e contro mosse, dando luogo a un esplosione combinatoria di valutazioni che rapidamente satura la potenza d elaborazione di un computer. Il procedimento può apparire complicato ma invece diventa di semplice implementazione utilizzando la ricorsione. Figura B.2 E in corso la valutazione delle possibili mosse valide Per migliorare la capacità di gioco del calcolatore, è stato aggiunto il metodo valutabordi per compensare la valutazione delle mosse effettuate sul bordo. Oltre a questo, quando si arriva al finale del gioco, il computer valuta in modo esaustivo tutte le mosse che rimangono basando a questo punto il proprio giudizio solo sul numero di pedine convertite.