gioco del Tris contro il computer

Analisi del problema

Strategia del gioco (ipotesi 1)

Ordine di valutazione delle scelte per una strategia ottimale del gioco del tris:

  1. Vincere: Il giocatore che trova una casella libera allineata con due pedine sue, deve giocare in quella casella

  2. Bloccare: Se l'avversario ha una casella libera allineata con due caselle occupate dalle sue pedine, giocare in quella casella per impedirgli di vincere.

  3. Doppia opportunità: Scegliere la casella libera dove si possono creare due opportunità di vincere.

  4. Impedire all'avversario di giocare in una casella dove crea due opportunità di realizzare un allineamento:

    1. Allineare due pedine, per obbligare l'avversario a difendersi, a meno che non si crea un vantaggio per l'vversario. Ad esempio, se "X" ha un angolo, "O" ha il centro, ed "X" ha l'angolo opposto, "O" non deve occupare un angolo per vincere. (Giocando in un angolo, si consente a X di creare du opportunità di vincere.)

    2. Se esiste una possibilità che l'avversario si crei due possibilità di allineare le pedine, bloccare.

  5. Centro: mettere la pedina nella casella centrale.

  6. Angoli opposti: Se l'avversario si trova in un angolo, mettere una pedina nell'angolo opposto.

  7. Angolo vuoto: mettere la pedina in un angolo vuoto.

  8. Lato vuoto: mettere la pedina in una casella laterale vuota.

Strategia del gioco (ipotesi 2 - adottata nel programma presentato)

Un tipico algoritmo per il gioco del tris usa i seguenti dati:

Griglia: un vettore di 9 elementi. Un elemento del vettore contiene

Turno: un intero che specifica quale dei due giocatori deve segnare una scelta. La prima mossa è indicata con 1, l'ultima mossa con 9.

L'Algoritmo principale usa tre funzioni:

ottieniDoppia (possibilità di allineare le pedine): restituisce 5 se la casella centrale della griglia è vuota, cioè se Griglia[5]=2. Altrimenti questa funzione restituisce una qualsiasi casella laterale (2,4,6 o 8), cioè esclude gli angoli.

vince(g): restituisce 0 se il giocatore g non può vincere alla prossima mossa, altrimenti restituisce il numero della casella che costituise una mossa vincente per lui. Questa funzione permette al programma sia di vincere, sia di bloccare la mossa vincente dell'avversario. Questa funzione controlla le righe, le colonne e le diagonali. Moltiplicando i valori delle celle allineate, si possono valutare le possibili vittorie. Se il prodotto è 18 (3 x 3 x 2), allora X può vincere. Se il prodotto è 50 (5 x 5 x 2), allora O può vincere. Se si trova che g può creare un allineamento, la funzione deve restituire il numero della cella vuota.

giocaIn(n): Scrive nella cella n il numero 3 (Griglia[n] = 3), se il Turno è dispari, scrive 5 se il turno è pari. Incrementa il turno di 1.

In questo algoritmo le mosse dispari sono effettuate da X, le mosse pari sono effettuate da O.

Turno = 1 giocaIn(0) (angolo superiore sinistro).

Turno = 2 Se griglia[4] è vuota, giocaIn(4), altrimenti giocaIn(0).

Turno = 3 Se griglia[8] è vuota, giocaIn(8), altrimenti giocaIn(2).

Turno = 4 Se vince(X) ritorna ? 0, allora giocaIn(vince(X)) cioè blocca la vittoria dell'avversario, altrimenti giocaIn(ottieniDoppia).

Turno = 5 Se vince(X) ritorna ? 0 allora giocaIn(vince(X)), cioè X vince, altrimenti se vince(O) ritorna ? 0 allora giocaIn(vince(O)) [cioè blocca la vittoria dell'avversario, altrimenti se griglia[6] è libera, allora giocaIn(6), altrimenti giocaIn(2).

Turno = 6 Se vince(O) ritorna ? 0 allora giocaIn(vince(O)), altrimenti se vince(X) ritorna ? 0, allora giocaIn(vince(X)), altrimenti giocaIn(ottieniDoppia()).

Turno = 7 Se vince(X) ritorna ? 0 allora giocaIn(vince(X)), altrimenti se vince(O) ritorna ? 0, allora giocaIn(vince(O)) altrimenti gioca in una qualsiasi casella vuota.

Turno = 8 Se vince(O) ritorna ? 0 allora giocaIn(vince(O)), altrimenti se vince(X) ritorna ? 0, allora giocaIn(vince(X)), altrimenti gioca in una qualsiasi casella vuota.

Turno = 9 identico a Turno = 7.

Resta da definire altre variabili:

Strutture dati impiegate

La griglia del gioco viene riprodotta anche in memoria. Le celle della griglia di gioco siano numerate a partire da 0, nel seguente ordine:

 0  1  2 
 3  4  5 
 6  7  8 

Sviluppo del programma

  1. Creare un nuovo progetto: nome=TrisControC.

  2. Clic sull'icona Create New Class swing: nome classe=Tris. Modificare le proprietà title e iconImage del frame.

  3. Creare un nuovo package all'interno della cartella src. Denominarla img.

  4. Creare le immagini (dimensioni 70x70) che rappresentano la casella vuota, la casella marcata da X e la casella marcata da O.

     
    2.png1.png0.png
  5. Memorizzare le immagini nella cartella src/img.

  6. Clic destro nella casella "Package Explorer" e, dal menu a comparsa, scegliere Refresh.

  7. Collocare un componente JButton sul Frame. Modificare la proprietà Variable in C1. Clic sul pulsante con i puntini a destra della proprietà icon. Nella finestra che si apre scegliere la prima opzione (Classpath resource). Nella casella Parameters, aprire la cartella src/img e selezionare l'immagine della casella vuota (0.png). Ok.

  8. Ripetere l'operazione precedente per collocare gli altri 8 JButton sul frame. Cambiare la proprietà variable assegnando i nomi C2, C3, ai pulsanti da sinistra a destra della prima riga, i nomi C4, C5, C6, ai pulsanti da sinistra a destra della seconda riga e C7, C8, C9 ai pulsanti della terza riga.

  9. Creare una nuova classe: FileNewClass (name = Gioco)Finish.

  10. Aggiungere le proprietà: turno e griglia[] di tipo intero.

    public class Gioco {
      int griglia[];
      int turno;
  11. Aggiungere il costruttore.

    public Gioco() {
      griglia = new int[9];
      turno = 1;
      svuota();
    }
  12. Aggiungere il metodo svuota():

    private void svuota() {
      for (int i=0; i<9; i++) {
        griglia[i] = 2;
      }
    }
  13. Aggiungere il metodo di accesso alla proprietà:

    public int getTurno() {
      return turno;
    }
  14. Aggiungere il metodo per individuare se esiste una casella vuota che crea una doppia possibilità di vincere:

    public int ottieniDoppia() {
      if (griglia[4]==2) {
        return 4; // la casella centrale è vuota
      }
      for (int i=1; i<8; i+=2) { // cerca una casella laterale vuota
        if (griglia[i]==2) { // restituisce 1, 3, 5 o 7
          return i;
        }
      }
    return -1; // nessuna casella libera
    }
  15. Il metodo coppia() valuta per ogni riga, colonna e diagonale se c'è una casella vuota allineata con due caselle occupate dal giocatore XoO. Riceve i parametri a, b e c che rappresentano le coordinate delle celle di una riga, di una colonna o di una diagonale, e il parametro XoO che può assumere il valore 18 corrispondente alle pedine di X o il valore 50 corrispondetente alle pedine di O.

    private int coppia(int a, int b, int c, int XoO) {
      int per;
      per = griglia[a]*griglia[b]*griglia[c];
      if (per==XoO) { // se XoO=18 può vincere X, Se XoO=50 può vincere O
        if (griglia[a]==2) return a; // restituisce la cella vuota
        if (griglia[b]==2) return b; // restituisce la cella vuota
        if (griglia[c]==2) return c; // restituisce la cella vuota
      }
      return -1; // nessuna coppia adiacente a una casella vuota
    }
  16. Il metodo vince(g) restituisce la cella in cui il giocatore g può vincere, se non può vincere restituisce -1:

    public int vince(int g) {
      int vinceG;
      vinceG=coppia(0, 1, 2, g); // prima riga
      if (vinceG != -1) return vinceG;
      vinceG=coppia(3, 4, 5, g); // seconda riga
      if (vinceG != -1) return vinceG;
      vinceG=coppia(6, 7, 8, g); // terza riga
      if (vinceG != -1) return vinceG;
      vinceG=coppia(0, 3, 6, g); // prima colonna
      if (vinceG != -1) return vinceG;
      vinceG=coppia(1, 4, 7, g); // seconda colonna
      if (vinceG != -1) return vinceG;
      vinceG=coppia(2, 5, 8, g); // terza colonna
      if (vinceG != -1) return vinceG;
      vinceG=coppia(0, 4, 8, g); // prima diagonale
      if (vinceG != -1) return vinceG;
      vinceG=coppia(2, 4, 6, g); // seconda diagonale
      if (vinceG != -1) return vinceG;
      return -1; // nessuno vince
    }
  17. Il metodo giocaIn(casella) scrive 3 per posizionare la pedina X in una cella oppure scrive 5 per mettere la pedina O. Incrementa il turno.

    public void giocaIn(int casella) {
      griglia[casella] = (turno%2==1) ? 3 : 5; // scrive 3 se il turno è dispari
      turno++;
    }
  18. Applicazione dell'algoritmo:

    public int partita() {
      int g;
      switch (turno) {
        case 1: giocaIn(0);
          return 0;
        case 2:
          if (griglia[4]==2) {
            giocaIn(4);
            return 4;
          } else {
            giocaIn(0);
            return 0;
          }
        case 3:
          if (griglia[8]==2) {
            giocaIn(8);
            return 8;
          } else {
            giocaIn(2);
           return 2;
          }
        case 4: g = vince(18);
          if (g!=-1){
            giocaIn(g);
            return g;
          } else {
            giocaIn(g= ottieniDoppia());
            return g;
          }
        case 5: g = vince(18);
          if (g!=-1) {
            giocaIn(g);
            return g;
          } else {
            g=vince(50);
          if (g!=-1) {
            giocaIn(g);
            return g;
          } else {
          if (griglia[6]==2){
            giocaIn(6);
            return 6;
          } else {
            giocaIn(2);
            return 2;
          }
        }
      }
        case 6: g = vince(50);
          if (g!=-1) {
            giocaIn(g);
              return g;
          } else {
            g=vince(18);
            if (g!=-1) {
              giocaIn(g);
              return g;
            } else {
              giocaIn(g=ottieniDoppia());
              return g;
            }
          }
        case 7:
        case 9:
          g = vince(18);
          if (g!=-1) {
            giocaIn(g);
            return g;
          } else {
            g = vince(50);
            if (g!=-1) {
              giocaIn(g);
              return g;
            }
          }
          for (g=0; g<9; g++) // cerca vuota
            if (griglia[g]==2) return g;
        case 8: g=vince(50);
          if (g!=-1) {
            giocaIn(g);
            return g;
          } else {
            g=vince(18);
            if (g!=-1) {
              giocaIn(g);
              return g;
            }
          }
          for (g=0; g<9; g++) { // cerca vuota
            if (griglia[g]==2) return g;
          }
        default:
        }
        return -1;
    } // fine metodo partita
    } // fine classe
  19. Passare alla scheda design dell'applicazione Tris (JFrame).

  20. Aggiungere la proprietà alla classe:

      private Gioco gioco = new Gioco();

  21. Associare il gestore di evento actionPerformed al pulsante C1. Automaticamente si apre la scheda Source, con il cursore posizionato sull'intestazione del metodo. Completarlo con l'istruzione:

    gestore(C1, 0);

    il primo parametro è il nome della variabile del pulsante su cui si fa clic. Il secondo parametro è l'indice della cella in cui si deve marcare il codice della pedina giocata

  22. Associare i gestori a tutti i pulsanti e completarli come il gestore del pulsante C1.

    gestore(C2, 1); // gestore evento actionPerformed del pulsante C2
    gestore(C3, 2); // gestore evento actionPerformed del pulsante C3

    gestore(C9, 8); // gestore evento actionPerformed del pulsante C9
  23. Nella sezione metodi della classe scrivere il metodo gestore:

    private void gestore(JButton C, int casella) {
      if (gioco.getTurno()%2==1)
         C.setIcon(new ImageIcon(Tris.class.getResource("/img/1.png")));
      else
        C.setIcon(new ImageIcon(Tris.class.getResource("/img/2.png")));
      gioco.giocaIn(casella);
      int cella=gioco.partita();
      marcaCasella (cella);
    }
  24. Nella sezione metodi della classe aggiungere il metodo:

    private void marcaCasella(int cella) {
       JButton C=null;
      switch (cella) {
        case 0:
          C = C1;
          break;
        case 1:
          C = C2;
          break;
        case 2:
          C = C3;
          break;
        case 3:
          C = C4;
          break;
        case 4:
          C = C5;
          break;
        case 5:
          C = C6;
          break;
        case 6:
          C = C7;
          break;
        case 7:
          C = C8;
          break;
        case 8:
          C = C9;
          break;
        }
        if (gioco.getTurno()%2==1)
          C.setIcon(new ImageIcon(Tris.class.getResource("/img/2.png")));
        else
          C.setIcon(new ImageIcon(Tris.class.getResource("/img/1.png")));
      }
  25. Rendere proprietà della classe i pulsanti dichiarati nel costruttore.

  26. Provare il funzionamento del programma.

  27. Problemi:

    • Riconoscere la fine di una partita e fermare il gioco.

    • Comunicare l'esito di una partita.

    • Consentire di iniziare una nuova partita

    • Contare le vittorie del computer, del giocatore e le partite pareggiate.