diff --git a/src/main/java/net/berack/upo/ai/problem1/Puzzle8GUI.java b/src/main/java/net/berack/upo/ai/problem1/Puzzle8GUI.java new file mode 100644 index 0000000..ab7ad14 --- /dev/null +++ b/src/main/java/net/berack/upo/ai/problem1/Puzzle8GUI.java @@ -0,0 +1,197 @@ +package net.berack.upo.ai.problem1; + +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JPanel; + +import net.berack.upo.ai.problem1.Puzzle8.Move; + +import java.awt.Color; +import java.awt.GridLayout; +import java.util.ArrayList; +import java.util.List; + +/** + * Classe che permette di visualizzare graficamente il gioco Puzzle8 + * In questa classe si trova un main che crea una istanza di questa finestra + * + * @author Berack96 + */ +public class Puzzle8GUI extends JFrame { + + public static void main(String[] args) { + new Puzzle8GUI(); + } + + /** + * Carica staticamente le immagini necessarie per giocare al gioco + */ + private static final ImageIcon[] PUZZLE_ICON = new ImageIcon[Puzzle8.LENGTH * Puzzle8.LENGTH]; + static { + var loader = Puzzle8GUI.class.getClassLoader(); + for(var i = 0; i < PUZZLE_ICON.length; i++) + PUZZLE_ICON[i] = new ImageIcon(loader.getResource("puzzle8/Puzzle_" + i + ".png")); + } + + private final Puzzle8 puzzle = new Puzzle8(Puzzle8.DEFAULT_GOAL); + private final MyComponent[][] buttons = new MyComponent[Puzzle8.LENGTH][Puzzle8.LENGTH]; + private List solution = new ArrayList<>(); + + /** + * Crea una nuova istanza del gioco sottoforma di finestra. + * In essa si potrà giocare muovendo le tessere al posto della tessera vuota. + * Inoltre ci saranno sarà una menubar con delle opzioni tra le quali + * il mescolare le tessere e l'eveidenziazione della migliore mossa. + * + * Come default viene creato il puzzle "GOAL" ovvero non mescolato. + */ + private Puzzle8GUI() { + super("Puzzle 8 game"); + + var grid = new GridLayout(Puzzle8.LENGTH, Puzzle8.LENGTH); + grid.setHgap(6); + grid.setVgap(grid.getHgap()); + + var panel = new JPanel(grid); + for(var i = 0; i < Puzzle8.LENGTH; i++) { + for(var j = 0; j < Puzzle8.LENGTH; j++) { + var comp = new MyComponent(Puzzle8.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("Shuffle"); + item1.addActionListener(action -> this.shuffleGame()); + menu1.add(item1); + + var item2 = new JMenuItem("Show solution"); + item2.addActionListener(action -> this.solveGame()); + menu1.add(item2); + + menuBar.add(menu1); + this.setJMenuBar(menuBar); + } + + /** + * Permette di mescolare le tessere in modo casuale, ma sempre + * in modo tale che il puzzle possa essere risolto. + * Dopo questo metodo la finestra verrà aggiornata automaticamente. + */ + public void shuffleGame() { + do { this.puzzle.shuffle(); } while(!this.puzzle.isSolvable()); + redraw(); + } + + /** + * Risolve il gioco in modo da poter aiutare l'utente nella risoluzione. + * Dopo questo metodo la finestra verrà aggiornata automaticamente + * e la mossa migliore possibile verrà evidenziata in rosso. + */ + public void solveGame() { + this.solution = this.puzzle.solve(); + this.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() { + MyComponent zero = null; + + for(var arr: this.buttons) { + for(var button: arr) { + var value = puzzle.get(button.x, button.y); + var newIcon = PUZZLE_ICON[value]; + + button.setContentAreaFilled(false); + if(value == 0) zero = button; + if(!button.getIcon().equals(newIcon)) + button.setIcon(newIcon); + } + } + + higlightNextMove(zero); + } + + /** + * Evidenzia la mossa migliore se disponibile. + * @param zero il componente con il valore zero + */ + private void higlightNextMove(MyComponent zero) { + if(this.solution.size() == 0) return; + + zero = switch(this.solution.get(0)) { + case UP -> this.buttons[zero.x][zero.y-1]; + case DOWN -> this.buttons[zero.x][zero.y+1]; + case LEFT -> this.buttons[zero.x-1][zero.y]; + case RIGHT -> this.buttons[zero.x+1][zero.y]; + }; + + zero.setContentAreaFilled(true); + zero.setBackground(Color.RED); + } + + /** + * 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(null); + this.setBackground(Color.WHITE); + this.setContentAreaFilled(false); + this.setFocusable(false); + + this.setIcon(PUZZLE_ICON[puzzle.get(x, y)]); + this.addActionListener(action -> { + Move move = null; + + if(puzzle.get(x, y) == 0) return; + else if(x-1 >= 0 && puzzle.get(x-1, y) == 0) move = Move.RIGHT; + else if(x+1 < n && puzzle.get(x+1, y) == 0) move = Move.LEFT; + else if(y-1 >= 0 && puzzle.get(x, y-1) == 0) move = Move.DOWN; + else if(y+1 < n && puzzle.get(x, y+1) == 0) move = Move.UP; + else return; + + puzzle.move(move); + if(solution.size() > 0 && solution.remove(0) != move) + solution.clear(); + + redraw(); + }); + } + } +} diff --git a/src/main/resources/puzzle8/Puzzle_0.png b/src/main/resources/puzzle8/Puzzle_0.png new file mode 100644 index 0000000..0c51611 Binary files /dev/null and b/src/main/resources/puzzle8/Puzzle_0.png differ diff --git a/src/main/resources/puzzle8/Puzzle_1.png b/src/main/resources/puzzle8/Puzzle_1.png new file mode 100644 index 0000000..264eda5 Binary files /dev/null and b/src/main/resources/puzzle8/Puzzle_1.png differ diff --git a/src/main/resources/puzzle8/Puzzle_2.png b/src/main/resources/puzzle8/Puzzle_2.png new file mode 100644 index 0000000..54d75b5 Binary files /dev/null and b/src/main/resources/puzzle8/Puzzle_2.png differ diff --git a/src/main/resources/puzzle8/Puzzle_3.png b/src/main/resources/puzzle8/Puzzle_3.png new file mode 100644 index 0000000..2b40b9b Binary files /dev/null and b/src/main/resources/puzzle8/Puzzle_3.png differ diff --git a/src/main/resources/puzzle8/Puzzle_4.png b/src/main/resources/puzzle8/Puzzle_4.png new file mode 100644 index 0000000..63d06cf Binary files /dev/null and b/src/main/resources/puzzle8/Puzzle_4.png differ diff --git a/src/main/resources/puzzle8/Puzzle_5.png b/src/main/resources/puzzle8/Puzzle_5.png new file mode 100644 index 0000000..76e3637 Binary files /dev/null and b/src/main/resources/puzzle8/Puzzle_5.png differ diff --git a/src/main/resources/puzzle8/Puzzle_6.png b/src/main/resources/puzzle8/Puzzle_6.png new file mode 100644 index 0000000..3581ad3 Binary files /dev/null and b/src/main/resources/puzzle8/Puzzle_6.png differ diff --git a/src/main/resources/puzzle8/Puzzle_7.png b/src/main/resources/puzzle8/Puzzle_7.png new file mode 100644 index 0000000..9e96695 Binary files /dev/null and b/src/main/resources/puzzle8/Puzzle_7.png differ diff --git a/src/main/resources/puzzle8/Puzzle_8.png b/src/main/resources/puzzle8/Puzzle_8.png new file mode 100644 index 0000000..2aa3a5d Binary files /dev/null and b/src/main/resources/puzzle8/Puzzle_8.png differ