Files
upo-ai/src/main/java/net/berack/upo/ai/problem1/Puzzle8GUI.java
Berack96 e5f953526e HELP
- added help menu to guide the user
2024-01-12 08:44:56 +01:00

192 lines
6.3 KiB
Java

package net.berack.upo.ai.problem1;
import java.awt.Color;
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JSeparator;
import net.berack.upo.ai.gui.MyPanel;
import net.berack.upo.ai.problem1.Puzzle8.Move;
/**
* 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 MyPanel {
/**
* 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<Move> 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'evidenziazione della migliore mossa.
*
* Come default viene creato il puzzle "GOAL" ovvero non mescolato.
*/
public Puzzle8GUI() {
super();
var grid = new GridLayout(Puzzle8.LENGTH, Puzzle8.LENGTH);
grid.setHgap(6);
grid.setVgap(grid.getHgap());
this.setLayout(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);
this.add(comp);
this.buttons[j][i] = comp;
}
}
}
/**
* 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());
this.solution.clear();
this.updateAll();
}
/**
* 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.updateAll();
}
/**
* 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.
*/
@Override
public void updateAll() {
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);
}
}
this.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();
updateAll();
});
}
}
@Override
public JMenu getMenu() {
var menu = new JMenu("Game");
var item = new JMenuItem("Shuffle");
item.addActionListener(action -> this.shuffleGame());
menu.add(item);
item = new JMenuItem("Show solution");
item.addActionListener(action -> this.solveGame());
menu.add(item);
menu.add(new JSeparator());
item = new JMenuItem("Help");
item.addActionListener(a -> JOptionPane.showMessageDialog(this,
"This is a recreation of the game of the 8 tiles\n"
+ "To win the game you must sort the tiles in ascending order\n"
+ "with the empty tile at the end.\n"
+ "The empty space is used to move all the other tiles."
));
menu.add(item);
return menu;
}
}