diff --git a/src/main/java/net/berack/upo/ai/problem2/Tris.java b/src/main/java/net/berack/upo/ai/problem2/Tris.java index d1922ae..7c40557 100644 --- a/src/main/java/net/berack/upo/ai/problem2/Tris.java +++ b/src/main/java/net/berack/upo/ai/problem2/Tris.java @@ -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 { * @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 { * @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 { * 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 { 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 getWinnerTris() { + var list = this.checkTris(); + if(list == null) return null; + + var coord = new ArrayList(); + 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; } /** diff --git a/src/main/java/net/berack/upo/ai/problem2/TrisGUI.java b/src/main/java/net/berack/upo/ai/problem2/TrisGUI.java new file mode 100644 index 0000000..2254868 --- /dev/null +++ b/src/main/java/net/berack/upo/ai/problem2/TrisGUI.java @@ -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(); + } + }); + } + } +} diff --git a/src/main/resources/tris/value_empty.png b/src/main/resources/tris/value_empty.png new file mode 100644 index 0000000..e1bc56c Binary files /dev/null and b/src/main/resources/tris/value_empty.png differ diff --git a/src/main/resources/tris/value_o.png b/src/main/resources/tris/value_o.png new file mode 100644 index 0000000..b53913b Binary files /dev/null and b/src/main/resources/tris/value_o.png differ diff --git a/src/main/resources/tris/value_o_red.png b/src/main/resources/tris/value_o_red.png new file mode 100644 index 0000000..6fb5e0c Binary files /dev/null and b/src/main/resources/tris/value_o_red.png differ diff --git a/src/main/resources/tris/value_x.png b/src/main/resources/tris/value_x.png new file mode 100644 index 0000000..96ad8c4 Binary files /dev/null and b/src/main/resources/tris/value_x.png differ diff --git a/src/main/resources/tris/value_x_red.png b/src/main/resources/tris/value_x_red.png new file mode 100644 index 0000000..f1d6549 Binary files /dev/null and b/src/main/resources/tris/value_x_red.png differ diff --git a/src/test/java/net/berack/upo/ai/problem2/TestTris.java b/src/test/java/net/berack/upo/ai/problem2/TestTris.java index c4c7c33..c0b56e1 100644 --- a/src/test/java/net/berack/upo/ai/problem2/TestTris.java +++ b/src/test/java/net/berack/upo/ai/problem2/TestTris.java @@ -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(); + 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(); + 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(); + 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()); + } }