- added tris GUI
- added more tris tests
- added new methods for helping with tris winner
This commit is contained in:
2023-12-19 02:13:12 +01:00
parent 988328cd49
commit 918496ed2b
8 changed files with 321 additions and 57 deletions

View File

@@ -2,8 +2,10 @@ package net.berack.upo.ai.problem2;
import static net.berack.upo.ai.problem2.Tris.Symbol.EMPTY;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
/**
* Classe che rappresenta il classico gioco del tris, dove per vincere bisogna
@@ -93,7 +95,7 @@ public class Tris implements Iterable<Tris.Symbol> {
* @throws UnsupportedOperationException nel caso in cui si ha già avuto un vincitore
*/
public void play(int x, int y) {
if(this.haveWinner() != Symbol.EMPTY) throw new UnsupportedOperationException("The game has already finished!");
if(this.getWinner() != Symbol.EMPTY) throw new UnsupportedOperationException("The game has already finished!");
if(!isPlayAvailable(x, y)) throw new IllegalArgumentException("The state to modify must be Empty!");
this.tris[this.index(x, y)] = this.currentTurn;
@@ -126,7 +128,7 @@ public class Tris implements Iterable<Tris.Symbol> {
* @return un array di coordinate disponibili per giocare.
*/
public Coordinate[] availablePlays() {
if(this.haveWinner() != EMPTY) return new Coordinate[0];
if(this.getWinner() != EMPTY) return new Coordinate[0];
var count = 0;
for(var i = 0; i < this.tris.length; i++)
@@ -148,10 +150,10 @@ public class Tris implements Iterable<Tris.Symbol> {
* Indica se il gioco è finito.
* Il gioco finisce se si ha un vincitore o se non ci sono più caselle vuote.
*
* @return vero se iol gioco è finito
* @return vero se il gioco è finito
*/
public boolean isFinished() {
if(haveWinner() != EMPTY) return true;
if(this.checkTris() != null) return true;
for(var symbol : this.tris)
if(symbol == EMPTY)
@@ -159,35 +161,66 @@ public class Tris implements Iterable<Tris.Symbol> {
return true;
}
/**
* Se si ha un vincitore restiruisce le coordinate delle celle in cui si ha tris.
* @return le coordinate delle celle del tris o null se non si ha ancora un vincitore.
*/
public List<int[]> getWinnerTris() {
var list = this.checkTris();
if(list == null) return null;
var coord = new ArrayList<int[]>();
for(var index : list) {
coord.add(new int[] {
index % Tris.LENGTH,
index / Tris.LENGTH
});
}
return coord;
}
/**
* Indica se si ha un vincitore e restituisce chi ha vinto.
* @return EMPTY se non c'è ancora un vincitore, altrimenti restituisci il vincitore
*/
public Symbol haveWinner() {
// top left corner -> horizontal and vertical
var state = this.tris[0];
if(state != Symbol.EMPTY) {
if(this.tris[1] == state && this.tris[2] == state) return state;
if(this.tris[3] == state && this.tris[6] == state) return state;
public Symbol getWinner() {
var check = this.checkTris();
if(check == null) return EMPTY;
return tris[check[0]];
}
/**
* Funzione privata per il controllo del tris.
* Nel caso ci sia un tris questo metodo restituisce il valore
* degli indici di dove si trova.
*
* @return un array degli indici del tris, altrimenti null;
*/
private int[] checkTris() {
var possibleTris = new int[][] {
// top left corner -> horizontal and vertical
{0, 1, 2},
{0, 3, 6},
// bottom right corner -> horizontal and vertical
{8, 7, 6},
{8, 5, 2},
// central -> diagonals, horizontal and vertical
{4, 0, 8},
{4, 6, 2},
{4, 3, 5},
{4, 1, 7}
};
for(var check : possibleTris) {
var symbol = this.tris[check[0]];
if(symbol == EMPTY) continue;
if(symbol != this.tris[check[1]]) continue;
if(symbol != this.tris[check[2]]) continue;
return check;
}
// bottom right corner -> horizontal and vertical
state = this.tris[8];
if(state != Symbol.EMPTY) {
if(this.tris[7] == state && this.tris[6] == state) return state;
if(this.tris[5] == state && this.tris[2] == state) return state;
}
// central -> diagonals, horizontal and vertical
state = this.tris[4];
if(state != Symbol.EMPTY) {
if(this.tris[0] == state && this.tris[8] == state) return state;
if(this.tris[6] == state && this.tris[2] == state) return state;
if(this.tris[3] == state && this.tris[5] == state) return state;
if(this.tris[1] == state && this.tris[7] == state) return state;
}
return Symbol.EMPTY;
return null;
}
/**

View File

@@ -0,0 +1,172 @@
package net.berack.upo.ai.problem2;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import java.awt.Color;
import java.awt.GridLayout;
/**
* Classe che permette di visualizzare graficamente il gioco Tris
* In questa classe si trova un main che crea una istanza di questa finestra
*
* @author Berack96
*/
public class TrisGUI extends JFrame {
public static void main(String[] args) {
new TrisGUI();
}
/**
*/
private static final ImageIcon IMAGE_X;
private static final ImageIcon IMAGE_O;
private static final ImageIcon IMAGE_X_RED;
private static final ImageIcon IMAGE_O_RED;
private static final ImageIcon IMAGE_EMPTY;
static {
var loader = TrisGUI.class.getClassLoader();
IMAGE_X = new ImageIcon(loader.getResource("tris/value_x.png"));
IMAGE_O = new ImageIcon(loader.getResource("tris/value_o.png"));
IMAGE_X_RED = new ImageIcon(loader.getResource("tris/value_x_red.png"));
IMAGE_O_RED = new ImageIcon(loader.getResource("tris/value_o_red.png"));
IMAGE_EMPTY = new ImageIcon(loader.getResource("tris/value_empty.png"));
}
private final MyComponent[][] buttons = new MyComponent[Tris.LENGTH][Tris.LENGTH];
private Tris tris = new Tris();
private TrisAi ai = new TrisAi(this.tris);
private JCheckBoxMenuItem aiFirst;
/**
*/
private TrisGUI() {
super("Tris");
var grid = new GridLayout(Tris.LENGTH, Tris.LENGTH);
var panel = new JPanel(grid);
for(var i = 0; i < Tris.LENGTH; i++) {
for(var j = 0; j < Tris.LENGTH; j++) {
var comp = new MyComponent(Tris.LENGTH, j, i);
panel.add(comp);
this.buttons[j][i] = comp;
}
}
this.add(panel);
this.attachMenu();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.pack();
this.setResizable(false);
this.setLocationRelativeTo(null);
this.setVisible(true);
}
/**
* Metodo utile per non mettere tutto nel costruttore.
* Qui viene creata la menubar
*/
private void attachMenu() {
var menuBar = new JMenuBar();
var menu1 = new JMenu("Game");
var item1 = new JMenuItem("Reset");
item1.addActionListener(action -> this.reset());
menu1.add(item1);
var separator = new JSeparator();
menu1.add(separator);
var item2 = new JCheckBoxMenuItem("AI Enabled");
item2.setSelected(this.ai != null);
item2.addChangeListener(action -> this.ai = item2.getState()? new TrisAi(this.tris):null);
menu1.add(item2);
this.aiFirst = new JCheckBoxMenuItem("AI First");
this.aiFirst.setSelected(false);
menu1.add(this.aiFirst);
menuBar.add(menu1);
this.setJMenuBar(menuBar);
}
/**
*/
public void reset() {
this.tris = new Tris();
this.ai = this.ai == null? null:new TrisAi(this.tris);
if(this.ai != null && aiFirst.getState()) this.ai.playNext();
redraw();
}
/**
* Dopo questo metodo la finestra verrà ridisegnata (sempre se ci sono stati dei cambiamenti)
* Nel caso in cui sia stata precedentemente calcolata la soluzione,
* allora verrà evidenziata una tessera con il colore rosso per indicare
* la mossa migliore da fare per la risoluzione.
*/
public void redraw() {
for(var arr: this.buttons) {
for(var button: arr) {
var value = tris.get(button.x, button.y);
var newIcon = switch(value) {
case VALUE_X -> IMAGE_X;
case VALUE_O -> IMAGE_O;
case EMPTY -> IMAGE_EMPTY;
};
if(!newIcon.equals(button.getIcon()))
button.setIcon(newIcon);
}
}
var icon = switch(this.tris.getWinner()) {
case VALUE_X -> IMAGE_X_RED;
case VALUE_O -> IMAGE_O_RED;
default -> null;
};
if(icon != null)
for(var coord : this.tris.getWinnerTris())
this.buttons[coord[0]][coord[1]].setIcon(icon);
}
/**
* Classe privata usata come appoggio per la gestione dei pulsanti.
* Qui vengono disabilitate alcune impostazioni base dei bottoni e
* viene creato un listener per quando viene premuto su di esso.
*/
private class MyComponent extends JButton {
final int x;
final int y;
private MyComponent(int n, int x, int y) {
this.x = x;
this.y = y;
this.setBorder(BorderFactory.createLineBorder(Color.BLACK));
this.setBackground(Color.WHITE);
this.setContentAreaFilled(false);
this.setFocusable(false);
this.setIcon(IMAGE_EMPTY);
this.addActionListener(action -> {
if(tris.isPlayAvailable(x, y) && !tris.isFinished()) {
tris.play(x, y);
if(ai != null) ai.playNext();
redraw();
}
});
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -3,11 +3,12 @@ package net.berack.upo.ai.problem2;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import static net.berack.upo.ai.problem2.Tris.Symbol.*;
import org.junit.jupiter.api.Test;
@@ -128,98 +129,156 @@ public class TestTris {
// horizontal 1 line X
var tris = new Tris();
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(1,0);
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(1,1);
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(0,0);
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(1,2);
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(2,0);
assertEquals(VALUE_X, tris.haveWinner());
assertEquals(VALUE_X, tris.getWinner());
assertTrue(tris.isFinished());
var trisPos = new ArrayList<int[]>();
trisPos.add(new int[] {0, 0});
trisPos.add(new int[] {1, 0});
trisPos.add(new int[] {2, 0});
var trisCoord = tris.getWinnerTris();
trisCoord.sort((a, b) -> Arrays.compare(a, b));
assertEquals(trisPos.size(), trisCoord.size());
for(var i = 0; i < trisPos.size(); i++)
assertArrayEquals(trisPos.get(i), trisCoord.get(i));
// diagonal \ O
tris = new Tris();
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(2,1);
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(1,1);
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(2,0);
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(2,2);
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(1,2);
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(0,0);
assertEquals(VALUE_O, tris.haveWinner());
assertEquals(VALUE_O, tris.getWinner());
assertTrue(tris.isFinished());
trisPos = new ArrayList<int[]>();
trisPos.add(new int[] {0, 0});
trisPos.add(new int[] {1, 1});
trisPos.add(new int[] {2, 2});
trisCoord = tris.getWinnerTris();
trisCoord.sort((a, b) -> Arrays.compare(a, b));
assertEquals(trisPos.size(), trisCoord.size());
for(var i = 0; i < trisPos.size(); i++)
assertArrayEquals(trisPos.get(i), trisCoord.get(i));
// vertical 2 column X
tris = new Tris();
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(1,0);
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(0,2);
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(1,1);
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(0,1);
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(1,2);
assertEquals(VALUE_X, tris.haveWinner());
assertEquals(VALUE_X, tris.getWinner());
assertTrue(tris.isFinished());
trisPos = new ArrayList<int[]>();
trisPos.add(new int[] {1, 0});
trisPos.add(new int[] {1, 1});
trisPos.add(new int[] {1, 2});
trisCoord = tris.getWinnerTris();
trisCoord.sort((a, b) -> Arrays.compare(a, b));
assertEquals(trisPos.size(), trisCoord.size());
for(var i = 0; i < trisPos.size(); i++)
assertArrayEquals(trisPos.get(i), trisCoord.get(i));
// No winner
tris = new Tris();
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(0,0);
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(1,0);
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(2,0);
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(1,1);
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(0,1);
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(0,2);
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(2,1);
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(2,2);
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertFalse(tris.isFinished());
tris.play(1,2);
assertEquals(EMPTY, tris.haveWinner());
assertEquals(EMPTY, tris.getWinner());
assertTrue(tris.isFinished());
assertNull(tris.getWinnerTris());
}
@Test
public void testWinner2() {
var tris = new Tris();
tris.play(1,1);
assertFalse(tris.isFinished());
tris.play(0,0);
assertFalse(tris.isFinished());
tris.play(0,1);
assertFalse(tris.isFinished());
tris.play(2,1);
assertFalse(tris.isFinished());
tris.play(0,2);
assertFalse(tris.isFinished());
tris.play(2,0);
assertFalse(tris.isFinished());
tris.play(1,0);
assertFalse(tris.isFinished());
tris.play(2,2);
assertTrue(tris.isFinished());
}
}