diff --git a/.idea/.gitignore b/.idea/.gitignore index 13566b8..98f4b2f 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -1,8 +1 @@ -# Default ignored files -/shelf/ /workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml deleted file mode 100644 index 2b63946..0000000 --- a/.idea/uiDesigner.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..bf64647 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,12 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "name": "Labyrinthe", + "request": "launch", + "mainClass": "Main", + "projectName": "SAE21_2022_c8f95a52" + } + ] +} \ No newline at end of file diff --git a/Makefile b/Makefile index e48f2d5..31e9758 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,11 @@ +# If the first argument is "run"... +ifeq (run,$(firstword $(MAKECMDGOALS))) + # use the rest as arguments for "run" + RUN_ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)) + # ...and turn them into do-nothing targets + $(eval $(RUN_ARGS):;@:) +endif + ### VARIABLES ### JC = javac @@ -21,7 +29,7 @@ $(OUTDIR)/Main.class : $(OFILES) ### REGLES OPTIONNELLES ### run : $(OUTDIR)/Main.class - ${JVM} ${JVMFLAGS} -cp $(OUTDIR) Main + ${JVM} ${JVMFLAGS} -cp $(OUTDIR) Main $(RUN_ARGS) clean : -rm -rf $(OUTDIR) diff --git a/SAE21_2022.iml b/SAE21_2022.iml index c90834f..22eec9e 100644 --- a/SAE21_2022.iml +++ b/SAE21_2022.iml @@ -1,6 +1,7 @@ - + + diff --git a/src/Algo.java b/src/Algo.java index 8258902..a7cb282 100644 --- a/src/Algo.java +++ b/src/Algo.java @@ -10,8 +10,4 @@ public interface Algo { */ void nextMove(); - /** - * Resets the algorithm - */ - void reset(); } diff --git a/src/AlgoType.java b/src/AlgoType.java new file mode 100644 index 0000000..cf57464 --- /dev/null +++ b/src/AlgoType.java @@ -0,0 +1,17 @@ +/** + * Enum for the different types of algorithms + * @version 1.0 + * @author Amir Daouadi + * @author Lyanis Souidi + */ +public enum AlgoType { + /** + * The random algorithm + */ + RANDOM, + + /** + * The deterministic algorithm + */ + DETERMINISTIC, +} diff --git a/src/AutoSimulation.java b/src/AutoSimulation.java new file mode 100644 index 0000000..b77b2f1 --- /dev/null +++ b/src/AutoSimulation.java @@ -0,0 +1,98 @@ +/** + * This class is used to store the data of the auto simulations + * @version 1.0 + * @author Amir Daouadi + * @author Lyanis Souidi + */ +public class AutoSimulation { + /** + * The grid to use for the simulations + */ + private final Grid grid; + + /** + * The algorithm type to use for the simulations + */ + private final AlgoType algoType; + + /** + * The simulations + */ + private final Simulation[] simulations = new Simulation[100]; + + /** + * Constructor + * @param grid The grid to use for the simulations + * @param algoType The algorithm type to use for the simulations + */ + public AutoSimulation(Grid grid, AlgoType algoType) { + this.grid = grid; + this.algoType = algoType; + + for (int i = 0; i < this.simulations.length; i++) { + simulations[i] = new Simulation(); + } + } + + /** + * Get the grid used for the simulations + * @return The grid + */ + public Grid getGrid() { + return this.grid; + } + + /** + * Get the algorithm type used for the simulations + * @return The algorithm type + */ + public AlgoType getAlgoType() { + return this.algoType; + } + + /** + * Get the simulations + * @return The simulations + */ + public Simulation[] getSimulations() { + return this.simulations; + } + + /** + * Get the average number of moves of the simulations + * @return The average number of moves + */ + public float getAverageMoves() { + int endedSimulations = 0; + float averageMoves = 0; + + for (Simulation simulation : simulations) { + if (simulation.isEnded()) { + endedSimulations++; + averageMoves += simulation.getMoves(); + } + } + + if (endedSimulations == 0) return 0; + + averageMoves = averageMoves / endedSimulations; + + return averageMoves; + } + + /** + * Get the number of ended simulations + * @return The number of ended simulations + */ + public int getNumberOfEndedSimulations() { + int endedSimulations = 0; + + for (Simulation simulation : simulations) { + if (simulation.isEnded()) { + endedSimulations++; + } + } + + return endedSimulations; + } +} diff --git a/src/AutoSimulationController.java b/src/AutoSimulationController.java new file mode 100644 index 0000000..20b0fe6 --- /dev/null +++ b/src/AutoSimulationController.java @@ -0,0 +1,51 @@ +/** + * Controller for the automatic simulation + * @version 1.0 + * @author Amir Daouadi + * @author Lyanis Souidi + */ +public class AutoSimulationController { + /** + * The automatic simulation view + */ + private final AutoSimulationView view; + + /** + * The automatic simulation model + */ + private final AutoSimulation model; + + /** + * Constructor + * @param view The automatic simulation view + * @param model The automatic simulation model + */ + public AutoSimulationController(AutoSimulationView view, AutoSimulation model) { + this.view = view; + this.model = model; + + new Thread(this::run).start(); + } + + /** + * Run the simulations + */ + public void run() { + for (Simulation simulation : this.model.getSimulations()) { + if (!simulation.isEnded()) { + Algo algo; + if (this.model.getAlgoType() == AlgoType.RANDOM) { + algo = new RandomAlgo(this.model.getGrid(), simulation); + } else { + algo = new DeterministicAlgo(this.model.getGrid(), simulation); + } + while (!simulation.isEnded()) { + algo.nextMove(); + this.view.repaint(); + } + + this.model.getGrid().reset(); + } + } + } +} diff --git a/src/AutoSimulationView.java b/src/AutoSimulationView.java index 32d9b44..3802572 100644 --- a/src/AutoSimulationView.java +++ b/src/AutoSimulationView.java @@ -2,64 +2,51 @@ import javax.swing.*; import java.awt.*; /** - * The view for the auto simulation + * The view for the automatic simulation * Display directly the success rate and the average number of moves of the simulations - * @version 1.0 + * @version 1.1 * @author Amir Daouadi * @author Lyanis Souidi */ public class AutoSimulationView extends JPanel { - /** - * The simulations to display - */ - private Simulation[] simulations; - /** - * The success rate of the simulations - */ - private float success = 0; - - /** - * The average number of moves of the simulations - */ - private float moves = 0; + public final AutoSimulation model; /** * Constructor - * @param simulations The simulations to display + * @param model The automatic simulation model */ - public AutoSimulationView(Simulation[] simulations) { + public AutoSimulationView(AutoSimulation model) { super(); - this.simulations = simulations; + this.model = model; this.setOpaque(false); this.setPreferredSize(new Dimension(700, 500)); } - /** - * Calculate the success rate and the average number of moves - */ - private void calculate() { - for (Simulation simulation : simulations) { - this.success += simulation.isSuccess() ? 1 : 0; - this.moves += simulation.getMoves(); - } - - this.success = (this.success / simulations.length) * 100; - - this.moves = this.moves / simulations.length; - } - @Override public void paintComponent(Graphics g) { super.paintComponent(g); - calculate(); - g.setColor(Color.BLACK); + + if (this.model.getSimulations()[0].getMoves() == 0) return; + g.setFont(new Font("Arial", Font.PLAIN, 20)); FontMetrics metrics = g.getFontMetrics(g.getFont()); - String successStr = "Taux de réussite : " + this.success + "%"; - g.drawString(successStr, (getWidth() - metrics.stringWidth(successStr)) / 2, ((getHeight() - metrics.getHeight()) / 2 + metrics.getAscent()) - 50); + int y = 0; - String movesStr = "Nombre de mouvements moyen : " + this.moves; - g.drawString(movesStr, (getWidth() - metrics.stringWidth(movesStr)) / 2, ((getHeight() - metrics.getHeight()) / 2 + metrics.getAscent()) + 50); + int endedSimulations = this.model.getNumberOfEndedSimulations(); + int totalSimulations = this.model.getSimulations().length; + int index = endedSimulations >= totalSimulations ? totalSimulations - 1 : endedSimulations; + Simulation lastSimulation = this.model.getSimulations()[index]; + + if (!lastSimulation.isEnded()) { + y = 50; + g.setColor(Color.RED); + String simulationStr = "Simulation " + endedSimulations + "/" + totalSimulations + " : " + lastSimulation.getMoves() + " mouvements"; + g.drawString(simulationStr, (getWidth() - metrics.stringWidth(simulationStr)) / 2, ((getHeight() - metrics.getHeight()) / 2 + metrics.getAscent()) - 50); + } + + g.setColor(Color.BLACK); + String movesStr = "Nombre de mouvements moyen : " + this.model.getAverageMoves(); + g.drawString(movesStr, (getWidth() - metrics.stringWidth(movesStr)) / 2, ((getHeight() - metrics.getHeight()) / 2 + metrics.getAscent()) + y); } } diff --git a/src/DeterministicAlgo.java b/src/DeterministicAlgo.java new file mode 100644 index 0000000..db6f8ac --- /dev/null +++ b/src/DeterministicAlgo.java @@ -0,0 +1,74 @@ +import java.util.Stack; + +/** + * Deterministic algorithm + * @version 0.1 + * @author Amir Daouadi + * @author Lyanis Souidi + */ +public class DeterministicAlgo implements Algo { + /** + * The grid model + */ + private final Grid grid; + + /** + * The simulation model + */ + private final Simulation simulation; + + /** + * The Thésée model + */ + private final Thesee thesee; + + /** + * The Thésée controller + */ + private final TheseeController theseeController; + + /** + * The directions stack used to go back + */ + private final Stack directions = new Stack<>(); + + /** + * Constructor + * @param grid The grid model + * @param simulation The simulation model + */ + public DeterministicAlgo(Grid grid, Simulation simulation) { + this.grid = grid; + this.simulation = simulation; + this.thesee = this.grid.getThesee(); + this.theseeController = new TheseeController(this.thesee); + this.thesee.getSquare().setVisited(true); + } + + /** + * Makes the next move of the algorithm + */ + @Override + public void nextMove() { + if (this.simulation.isEnded()) return; + Direction[] availableDirection = this.theseeController.getAvailableDirections(); + + Direction nextUnvisitedDirection = null; + for (Direction direction : availableDirection) { + try { + if (!this.thesee.getSquare(direction).isVisited()) { + nextUnvisitedDirection = direction; + break; + } + } catch (Exception ignored) {} + } + if (nextUnvisitedDirection != null) { + if (this.theseeController.move(nextUnvisitedDirection, this.simulation)) this.directions.push(nextUnvisitedDirection); + } else this.theseeController.move(this.directions.pop().opposite(), this.simulation); + + if (!this.simulation.isEnded() && this.grid.isEnded()) { + this.simulation.setEnded(); + } + } + +} diff --git a/src/Direction.java b/src/Direction.java new file mode 100644 index 0000000..4cb441b --- /dev/null +++ b/src/Direction.java @@ -0,0 +1,64 @@ +/** + * Enum for the direction of Thésée's movement. + * @version 1.0 + * @author Amir Daouadi + * @author Lyanis Souidi + */ +public enum Direction { + /** + * Up direction + */ + UP, + + /** + * Down direction + */ + DOWN, + + /** + * Left direction + */ + LEFT, + + /** + * Right direction + */ + RIGHT; + + /** + * Get the index to add to the row of the current square to get the row of the next square. + * @return The index to add to the row + */ + public int row() { + return switch (this) { + case UP -> -1; + case DOWN -> 1; + default -> 0; + }; + } + + /** + * Get the index to add to the column of the current square to get the column of the next square. + * @return The index to add to the column + */ + public int column() { + return switch (this) { + case LEFT -> -1; + case RIGHT -> 1; + default -> 0; + }; + } + + /** + * Get the opposite direction + * @return The opposite direction + */ + public Direction opposite() { + return switch (this) { + case UP -> DOWN; + case DOWN -> UP; + case LEFT -> RIGHT; + case RIGHT -> LEFT; + }; + } +} diff --git a/src/Editor.java b/src/Editor.java index 3af53ea..57c3620 100644 --- a/src/Editor.java +++ b/src/Editor.java @@ -1,5 +1,5 @@ public class Editor { - private Grid gridModel; + private final Grid gridModel; public Editor(Grid gridModel) { this.gridModel = gridModel; diff --git a/src/EditorController.java b/src/EditorController.java index 358525a..4a92a0f 100644 --- a/src/EditorController.java +++ b/src/EditorController.java @@ -1,76 +1,141 @@ import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.io.File; import java.util.Random; public class EditorController extends GridController { - private Editor model; - private EditorView view; + private final Editor model; + private final EditorView view; private enum Mode { DISABLED, WALL, THESEE, EXIT } private Mode editMode = Mode.DISABLED; - private Button editTheseeButton = new Button("Placer Joueur"); - private Button editExitButton = new Button("Placer Sortie"); - private Button editWallButton = new Button("Enlever/Ajouter Murs"); + private boolean edited = false; + private final Button editTheseeButton = new Button("Modifier Thésée"); + private final Button editExitButton = new Button("Modifier Sortie"); + private final Button editWallButton = new Button("Modifier Murs"); + private final Button exportButton = new Button("Exporter"); + private final Button startButton = new Button("Démarrer"); public EditorController(Editor model, EditorView view) { super(model.getGrid(), view); this.model = model; this.view = view; + this.view.window.setPageTitle(this.model.getGrid().getFile().getName()); + this.view.addMouseListener(new MouseAdapter() { - public void mouseClicked(MouseEvent e) { + public void mousePressed(MouseEvent e) { edit(view.click(e)); } }); JPanel buttons = new JPanel(); - - editWallButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - if (editMode == Mode.DISABLED) { - editWallButton.setText("Mode Dessin"); - setEditMode(Mode.WALL); - } else { - editWallButton.setText("Enlever/Ajouter Murs"); - setEditMode(Mode.DISABLED); - } - } - }); - buttons.add(editWallButton); - - editTheseeButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - if (editMode == Mode.DISABLED) { - editTheseeButton.setText("Mode Dessin"); - setEditMode(Mode.THESEE); - } else { - editTheseeButton.setText("Placer Joueur"); - setEditMode(Mode.DISABLED); - } - } - }); - buttons.add(editTheseeButton); - editExitButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - if (editMode == Mode.DISABLED) { - editExitButton.setText("Mode Dessin"); - setEditMode(Mode.EXIT); - } else { - editExitButton.setText("Placer Sortie"); - setEditMode(Mode.DISABLED); + Button randomizeButton = new Button("Aléatoire"); + randomizeButton.addActionListener(e -> { + random(); + if (!edited) { + edited = true; + view.window.setPageTitle("*" + view.window.getPageTitle()); + } + view.repaint(); + }); + buttons.add(randomizeButton); + + this.editWallButton.addActionListener(e -> setEditMode(Mode.WALL)); + buttons.add(editWallButton); + + this.editTheseeButton.addActionListener(e -> setEditMode(Mode.THESEE)); + buttons.add(this.editTheseeButton); + + this.editExitButton.addActionListener(e -> setEditMode(Mode.EXIT)); + buttons.add(this.editExitButton); + + this.exportButton.addActionListener(e -> { + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setDialogTitle("Sélectionnez le fichier dans lequel vous souhaitez enregistrer votre grille"); + fileChooser.setSelectedFile(model.getGrid().getFile()); + fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + fileChooser.setMultiSelectionEnabled(false); + fileChooser.setAcceptAllFileFilterUsed(false); + fileChooser.setFileFilter(new FileNameExtensionFilter("Fichier labyrinthe (*.lab)", "lab")); + int choix = fileChooser.showSaveDialog(view); + if (choix == JFileChooser.APPROVE_OPTION) { + File file = fileChooser.getSelectedFile(); + if (!file.toString().toLowerCase().endsWith(".lab")) { + file = new File(file + ".lab"); + } + view.window.setPageTitle(file.getName()); + model.getGrid().setFile(file); + try { + FileManager.exportGrid(model.getGrid()); + } catch (Exception ex) { + JOptionPane.showMessageDialog(view, ex.getMessage(), "Erreur", JOptionPane.ERROR_MESSAGE); } } }); - buttons.add(editExitButton); + + buttons.add(this.exportButton); + + this.startButton.addActionListener(e -> { + if (!this.model.getGrid().validate()) { + JOptionPane.showMessageDialog(view, "La grille n'est pas valide.\nAssurez-vous que la grille contienne Thésée ainsi qu'une sortie, et que Thésée peut aller jusqu'à la sortie.", "Erreur", JOptionPane.ERROR_MESSAGE); + return; + } + String[] algoOptions = {"Aléatoire", "Déterministe"}; + String[] viewOptions = {"Automatique", "Manuel"}; + int algoChoice = JOptionPane.showOptionDialog(view, "Choisissez l'algorithme à utiliser :", "Choix de l'algorithme", JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, algoOptions, algoOptions[0]); + + if (algoChoice == -1) { + JOptionPane.showMessageDialog(view, "Aucun choix n'a été fait.", "Attention", JOptionPane.WARNING_MESSAGE); + return; + } + + int viewChoice = JOptionPane.showOptionDialog(view, "Choisissez l'affichage à utiliser :", "Choix de l'affichage", JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, viewOptions, viewOptions[0]); + + if (viewChoice == -1) { + JOptionPane.showMessageDialog(view, "Aucun choix n'a été fait.", "Attention", JOptionPane.WARNING_MESSAGE); + return; + } + + AlgoType algoType = algoChoice == 0 ? AlgoType.RANDOM : AlgoType.DETERMINISTIC; + + if (viewChoice == 0) { + AutoSimulation autoSimulation = new AutoSimulation(model.getGrid(), algoType); + AutoSimulationView autoSimulationView = new AutoSimulationView(autoSimulation); + new AutoSimulationController(autoSimulationView, autoSimulation); + view.window.setContentPane(autoSimulationView); + } else { + ManualSimulation manualSimulation = new ManualSimulation(model.getGrid(), algoType); + ManualSimulationView manualSimulationView = new ManualSimulationView(view.window, manualSimulation); + new ManualSimulationController(manualSimulationView, manualSimulation); + view.window.setContentPane(manualSimulationView); + } + view.window.validate(); + }); + + buttons.add(this.startButton); + + boolean validGrid = this.model.getGrid().validate(); + this.exportButton.setEnabled(validGrid); + this.startButton.setEnabled(validGrid); + this.view.add(buttons, BorderLayout.NORTH); } + + /** + * Process the click on a square + * @param square The square clicked + */ private void edit(Square square) { if (square != null) { + if (!this.edited) { + this.edited = true; + this.view.window.setPageTitle("*" + this.view.window.getPageTitle()); + } if (this.editMode == Mode.WALL) { if (square.isWall()) { square.setEmpty(); @@ -81,22 +146,24 @@ public class EditorController extends GridController { JOptionPane.showMessageDialog(this.view, ex.getMessage(), "Erreur", JOptionPane.ERROR_MESSAGE); } } - this.view.repaint(); } else if (this.editMode == Mode.THESEE) { try { - this.model.getGrid().getThesee().setSquare(square); + this.model.getGrid().getThesee().setSquare(square, true); } catch (Exception ex) { JOptionPane.showMessageDialog(this.view, ex.getMessage(), "Erreur", JOptionPane.ERROR_MESSAGE); } - this.view.repaint(); } else if (this.editMode == Mode.EXIT) { try { square.setExit(); } catch (Exception ex) { JOptionPane.showMessageDialog(this.view, ex.getMessage(), "Erreur", JOptionPane.ERROR_MESSAGE); } - this.view.repaint(); } + + boolean validGrid = this.model.getGrid().validate(); + this.exportButton.setEnabled(validGrid); + this.startButton.setEnabled(validGrid); + this.view.repaint(); } } @@ -106,15 +173,10 @@ public class EditorController extends GridController { */ private void setEditMode(Mode mode) { this.editMode = mode; - if (mode != Mode.DISABLED) { - this.editTheseeButton.setEnabled(mode == Mode.THESEE); - this.editExitButton.setEnabled(mode == Mode.EXIT); - this.editWallButton.setEnabled(mode == Mode.WALL); - } else { - this.editTheseeButton.setEnabled(true); - this.editExitButton.setEnabled(true); - this.editWallButton.setEnabled(true); - } + + this.editTheseeButton.setEnabled(mode != Mode.THESEE); + this.editExitButton.setEnabled(mode != Mode.EXIT); + this.editWallButton.setEnabled(mode != Mode.WALL); } /** @@ -125,16 +187,25 @@ public class EditorController extends GridController { Random rand = new Random(); Grid gridModel = this.model.getGrid(); + gridModel.empty(); + gridModel.getSquare(rand.nextInt(gridModel.getSize()), rand.nextInt(gridModel.getSize())).setExit(); - gridModel.getThesee().setSquare(gridModel.getSquare(rand.nextInt(gridModel.getSize()), rand.nextInt(gridModel.getSize()))); + try { + gridModel.getThesee().setSquare(gridModel.getSquare(rand.nextInt(gridModel.getSize()), rand.nextInt(gridModel.getSize()))); + } catch (Exception ignored) {} for (int i = 0; i < gridModel.getSize(); i++) { for (int j = 0; j < gridModel.getSize(); j++) { if (!gridModel.getSquare(i, j).isExit() && !gridModel.getSquare(i, j).isThesee() && rand.nextInt(3) == 0) gridModel.getSquare(i, j).setWall(); } } + + // If the grid is invalid, try again. + if (!gridModel.validate()) random(); + this.exportButton.setEnabled(true); + this.startButton.setEnabled(true); } catch (Exception e) { - System.out.println(e.getMessage()); + System.err.println(e.getMessage()); } } } diff --git a/src/FileManager.java b/src/FileManager.java index 604832a..79786fe 100644 --- a/src/FileManager.java +++ b/src/FileManager.java @@ -1,7 +1,7 @@ import java.io.*; /** * Class to manage file import/export - * @version 1.1 + * @version 1.0 * @author Amir Daouadi * @author Lyanis Souidi */ @@ -18,7 +18,7 @@ public class FileManager { FileInputStream fs = new FileInputStream(file); DataInputStream ds = new DataInputStream(fs); try { - grid = new Grid(ds.read()); + grid = new Grid(ds.read(), file); grid.getThesee().setSquare(grid.getSquare(ds.read(), ds.read())); grid.getSquare(ds.read(), ds.read()).setExit(); @@ -47,10 +47,11 @@ public class FileManager { /** * Export a grid to a file * @param grid The grid to export - * @param file The file to export to * @throws Exception If an error occurs during the export */ - public static void exportGrid(Grid grid, File file) throws Exception { + public static void exportGrid(Grid grid) throws Exception { + if (!grid.validate()) throw new Exception("La grille n'est pas valide. Assurez-vous que la grille contient Thésée et la sortie ainsi que la sortie puisse être accessible depuis la position de Thésée."); + File file = grid.getFile(); try { FileOutputStream fs = new FileOutputStream(file); DataOutputStream ds = new DataOutputStream(fs); @@ -64,16 +65,9 @@ public class FileManager { ds.writeByte(theseeSquare.getColumn()); // Écriture de la position de la sortie - for (int i = 0; i < grid.getSize(); i++) { - for (int j = 0; j < grid.getSize(); j++) { - Square square = grid.getSquare(i, j); - if (square.isExit()) { - ds.writeByte(square.getRow()); - ds.writeByte(square.getColumn()); - break; - } - } - } + Square exitSquare = grid.getExit(); + ds.writeByte(exitSquare.getRow()); + ds.writeByte(exitSquare.getColumn()); // Écriture des murs int bit = 0; @@ -95,6 +89,7 @@ public class FileManager { if (bit != 0) { ds.writeByte(value); } + ds.close(); } catch (Exception e) { throw new Exception("Une erreur est survenue lors de l'écriture du fichier."); } diff --git a/src/Grid.java b/src/Grid.java index f85faf0..d298928 100644 --- a/src/Grid.java +++ b/src/Grid.java @@ -1,8 +1,52 @@ +import java.io.File; +import java.util.Stack; + +/** + * This class is used to store the grid's data + * @version 1.0 + * @author Amir Daouadi + * @author Lyanis Souidi + */ public class Grid { + /** + * The grid's squares + */ private Square[][] squares; + + /** + * Thésée + */ private Thesee thesee = new Thesee(); + /** + * The grid's name + */ + private File file; + + /** + * Constructor + * @param size The size of the grid (number of squares on a row/column) + */ public Grid(int size) { + createGrid(size, new File("Sans titre")); + } + + /** + * Constructor + * @param size The size of the grid (number of squares on a row/column) + * @param file The name of the grid + */ + public Grid(int size, File file) { + createGrid(size, file); + } + + /** + * Create the grid + * @param size The size of the grid (number of squares on a row/column) + * @param file The name of the grid + */ + private void createGrid(int size, File file) { + this.file = file; this.squares = new Square[size][size]; for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { @@ -11,6 +55,37 @@ public class Grid { } } + + /** + * Empty the grid + * Removes the exit, Thésée and walls + * @see Square#setEmpty() + */ + public void empty() { + this.thesee = new Thesee(); + for (Square[] row : this.squares) { + for (Square square : row) { + square.setEmpty(); + } + } + } + + /** + * Get the grid's name + * @return The grid's name + */ + public File getFile() { + return this.file; + } + + /** + * Set the grid's name + * @param file The grid's name + */ + public void setFile(File file) { + this.file = file; + } + /** * Get the grid's size (number of squares on a row/column) * @return The grid's size @@ -34,7 +109,102 @@ public class Grid { } } + /** + * Get Thésée + * @return Thésée + */ public Thesee getThesee() { return this.thesee; } + + /** + * Checks if all the accessible squares have been visited + * @return true if all the accessible squares have been visited, false otherwise + */ + public boolean isEnded() { + for (Square[] row : this.squares) { + for (Square square : row) { + if (square.isAccessible() && !square.isVisited()) return false; + } + } + return true; + } + + /** + * Resets Thésée's position and all the squares' visited and accessible status + */ + public void reset() { + this.thesee.reset(); + for (Square[] row : this.squares) { + for (Square square : row) { + square.setVisited(false); + } + } + } + + /** + * Get the exit square + * @return The exit square + */ + public Square getExit() { + for (Square[] row : this.squares) { + for (Square square : row) { + if (square.isExit()) return square; + } + } + return null; + } + + /** + * Validates the grid + * @return true if the grid can be solved, false otherwise + */ + public boolean validate() { + reset(); + Square theseeSquare = this.thesee.getSquare(); + if (theseeSquare == null) return false; + + Square exitSquare = getExit(); + if (exitSquare == null) return false; + + if (theseeSquare == exitSquare) return false; + + discover(theseeSquare); + + return exitSquare.isAccessible(); + } + + /** + * Discovers all the accessible squares from a given square + * @param square The square to start from + */ + private void discover(Square square) { + Stack discoveredSquares = new Stack<>(); + discoveredSquares.push(square); + while (!discoveredSquares.isEmpty()) { + Square discoveredSquare = discoveredSquares.pop(); + + discoveredSquare.setAccessible(true); + try { + Square upSquare = getSquare(discoveredSquare.getRow() - 1, discoveredSquare.getColumn()); + if (!upSquare.isWall() && !upSquare.isAccessible()) discoveredSquares.push(upSquare); + } catch (Exception ignored) {} + + try { + Square downSquare = getSquare(discoveredSquare.getRow() + 1, discoveredSquare.getColumn()); + if (!downSquare.isWall() && !downSquare.isAccessible()) discoveredSquares.push(downSquare); + } catch (Exception ignored) {} + + try { + Square leftSquare = getSquare(discoveredSquare.getRow(), discoveredSquare.getColumn() - 1); + if (!leftSquare.isWall() && !leftSquare.isAccessible()) discoveredSquares.push(leftSquare); + } catch (Exception ignored) {} + + try { + Square rightSquare = getSquare(discoveredSquare.getRow(), discoveredSquare.getColumn() + 1); + if (!rightSquare.isWall() && !rightSquare.isAccessible()) discoveredSquares.push(rightSquare); + + } catch (Exception ignored) {} + } + } } \ No newline at end of file diff --git a/src/GridView.java b/src/GridView.java index 4ab9adc..3930555 100644 --- a/src/GridView.java +++ b/src/GridView.java @@ -2,15 +2,13 @@ import javax.swing.*; import java.awt.*; public class GridView extends JPanel { - public Window window; + public final Window window; protected Grid model; protected int gridSize; protected int gridStartX; protected int gridStartY; protected int squareSize; private Font font; - private final String exit = "∩"; - private final String thesee = "Θ"; /** * Manages the display of the grid @@ -44,7 +42,11 @@ public class GridView extends JPanel { for (int i = 0; i < this.model.getSize(); i++) { for (int j = 0; j < this.model.getSize(); j++) { try { - if (this.model.getSquare(i, j).isWall()) g.setColor(new Color(122, 68, 25)); + Square square = this.model.getSquare(i, j); + + if (square.isWall() && square.isVisited()) g.setColor(new Color(82, 79, 47)); + else if (square.isWall()) g.setColor(new Color(122, 68, 25)); + else if (square.isVisited()) g.setColor(new Color(41, 90, 69)); else g.setColor(new Color(77, 170, 87)); g.fillRect(this.gridStartX + (this.squareSize * j), this.gridStartY + (this.squareSize * i), this.squareSize, this.squareSize); @@ -52,13 +54,15 @@ public class GridView extends JPanel { FontMetrics metrics = g.getFontMetrics(this.font); g.setFont(this.font); // Draw exit - if (this.model.getSquare(i, j).isExit()) { - g.drawString(this.exit, (this.gridStartX + (this.squareSize * j)) + ((this.squareSize - metrics.stringWidth(this.exit)) / 2), (this.gridStartY + (this.squareSize * i)) + (((this.squareSize - metrics.getHeight()) / 2) + metrics.getAscent())); + if (square.isExit()) { + String exit = "∩"; + g.drawString(exit, (this.gridStartX + (this.squareSize * j)) + ((this.squareSize - metrics.stringWidth(exit)) / 2), (this.gridStartY + (this.squareSize * i)) + (((this.squareSize - metrics.getHeight()) / 2) + metrics.getAscent())); } // Draw Thésée - if (this.model.getThesee().getSquare() == this.model.getSquare(i, j)) { - g.drawString(this.thesee, (this.gridStartX + (this.squareSize * j)) + ((this.squareSize - metrics.stringWidth(this.thesee)) / 2), (this.gridStartY + (this.squareSize * i)) + (((this.squareSize - metrics.getHeight()) / 2) + metrics.getAscent())); + if (this.model.getThesee().getSquare() == square) { + String thesee = "Θ"; + g.drawString(thesee, (this.gridStartX + (this.squareSize * j)) + ((this.squareSize - metrics.stringWidth(thesee)) / 2), (this.gridStartY + (this.squareSize * i)) + (((this.squareSize - metrics.getHeight()) / 2) + metrics.getAscent())); } } catch (Exception e) { System.out.println(e.getMessage()); diff --git a/src/HomeView.java b/src/HomeView.java index f89a1f9..8cc635f 100644 --- a/src/HomeView.java +++ b/src/HomeView.java @@ -1,9 +1,10 @@ import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; import java.awt.*; import java.io.File; public class HomeView extends JPanel { - private Window window; + public final Window window; public HomeView(Window window) { this.window = window; @@ -40,52 +41,50 @@ public class HomeView extends JPanel { } private Button choisirGrille() { - JPanel panel = new JPanel(); Button choisirGrille = new Button("Générer une grille", new Dimension(250, 50)); choisirGrille.addActionListener(e -> { - String strTaille = JOptionPane.showInputDialog(panel, "Entrez la taille de la grille :", "Taille de la grille", JOptionPane.PLAIN_MESSAGE); + String strTaille = JOptionPane.showInputDialog(this, "Entrez la taille de la grille :", "Taille de la grille", JOptionPane.PLAIN_MESSAGE); if (strTaille != null && !strTaille.isEmpty()) { if (!Character.isDigit(strTaille.charAt(0))) { - JOptionPane.showMessageDialog(panel, "Le premier caractère doit être un chiffre ou nombre.", "Erreur", JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(this, "Le premier caractère doit être un chiffre ou nombre.", "Erreur", JOptionPane.ERROR_MESSAGE); return; } try { int taille = Integer.parseInt(strTaille); - if (taille > 3 && taille < 21) { + if (taille >= 2 && taille <= 255) { + if (!sizeWarning(this, taille)) return; + String[] options = {"Remplir aléatoirement", "Partir d'une grille vide"}; - int choix = JOptionPane.showOptionDialog(panel, "Choisissez comment remplir la grille :", "Remplissage de la grille", JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options[0]); + int choix = JOptionPane.showOptionDialog(this, "Choisissez comment remplir la grille :", "Remplissage de la grille", JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options[0]); EditorView editorView = new EditorView(window); EditorController editorController = new EditorController(new Editor(new Grid(taille)), editorView); switch (choix) { - case 0: + case 0 -> { // afficher la grille aléatoirement editorController.random(); window.setContentPane(editorView); window.validate(); - break; - case 1: + } + case 1 -> { window.setContentPane(editorView); window.validate(); - break; - default: + } + default -> // gérer le cas où aucun choix n'a été fait - JOptionPane.showMessageDialog(panel, "Aucun choix n'a été fait.", "Attention", JOptionPane.WARNING_MESSAGE); - return; + JOptionPane.showMessageDialog(this, "Aucun choix n'a été fait.", "Attention", JOptionPane.WARNING_MESSAGE); } } else { - String errorMessage = "La taille doit être au moins de 4."; - if (taille >= 21) { - errorMessage = "La taille ne doit pas dépasser 20."; + String errorMessage = "La taille doit être supérieur ou égale à 2."; + if (taille > 255) { + errorMessage = "La taille doit être inférieur ou égale à 255."; } - JOptionPane.showMessageDialog(panel, errorMessage, "Erreur", JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(this, errorMessage, "Erreur", JOptionPane.ERROR_MESSAGE); } } catch (NumberFormatException ex) { - JOptionPane.showMessageDialog(panel, "Tapez " + strTaille.charAt(0) + " pour une grille " + strTaille.charAt(0) +"x"+ strTaille.charAt(0) +".", "Erreur", JOptionPane.ERROR_MESSAGE); - - + JOptionPane.showMessageDialog(this, "Tapez " + strTaille.charAt(0) + " pour une grille " + strTaille.charAt(0) +"x"+ strTaille.charAt(0) +".", "Erreur", JOptionPane.ERROR_MESSAGE); } } }); @@ -94,22 +93,27 @@ public class HomeView extends JPanel { } private Button importerGrille() { - JPanel panel = new JPanel(); Button importerGrille = new Button("Importer une grille", new Dimension(250, 50)); importerGrille.addActionListener(e -> { JFileChooser fileChooser = new JFileChooser(); - fileChooser.setDialogTitle("Selectionnez le fichier ou se trouve votre grille"); - int choix = fileChooser.showOpenDialog(panel); + fileChooser.setDialogTitle("Sélectionnez le fichier où se trouve votre grille"); + fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + fileChooser.setMultiSelectionEnabled(false); + fileChooser.setAcceptAllFileFilterUsed(false); + fileChooser.setFileFilter(new FileNameExtensionFilter("Fichier labyrinthe (*.lab)", "lab")); + int choix = fileChooser.showOpenDialog(this); if (choix == JFileChooser.APPROVE_OPTION) { File fichier = fileChooser.getSelectedFile(); try { + Grid grid = FileManager.importGrid(fichier); + if (!sizeWarning(this, grid.getSize())) return; EditorView editorView = new EditorView(window); - new EditorController(new Editor(FileManager.importGrid(fichier)), editorView); + new EditorController(new Editor(grid), editorView); window.setContentPane(editorView); window.validate(); } catch (Exception ex) { - JOptionPane.showMessageDialog(panel, ex.getMessage(), "Erreur", JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(this, ex.getMessage(), "Erreur", JOptionPane.ERROR_MESSAGE); } } }); @@ -117,6 +121,18 @@ public class HomeView extends JPanel { return importerGrille; } + /** + * Shows a warning message if the grid size is too big + * @param size the size of the grid + * @return true if the user wants to continue, false otherwise + */ + public static boolean sizeWarning(JComponent parentComponent, int size) { + if (size <= 25) return true; + String[] options = {"Abandonner", "Continuer avec cette grille"}; + int choice = JOptionPane.showOptionDialog(parentComponent, "Vous essayez d'ouvrir une grille de taille " + size + "x" + size + ".\nEn continuant avec cette grille, vous pourrez rencontrer des difficultées lors de l'edition et la durée des simulations de résolutions pourra être impactée.\nVoulez-vous continuer quand même ?", "Attention", JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]); + return choice != 0; + } + @Override public void paintComponent(Graphics g) { super.paintComponent(g); diff --git a/src/Main.java b/src/Main.java index 4f44671..4e2e87f 100644 --- a/src/Main.java +++ b/src/Main.java @@ -1,8 +1,30 @@ +import javax.swing.*; +import java.io.File; public class Main { public static void main(String[] args) { Window window = new Window(); - HomeView home = new HomeView(window); - window.setContentPane(home); + + // If the first argument is a .lab file, try to open it + if (args.length > 0 && args[0].toLowerCase().endsWith(".lab")) { + Grid grid = null; + try { + File file = new File(args[0]); + grid = FileManager.importGrid(new File(file.getAbsolutePath())); + } catch (Exception ignored) {} + + if (grid != null && HomeView.sizeWarning(new JPanel(), grid.getSize())) { + window.setPageTitle(args[0]); + EditorView editorView = new EditorView(window); + new EditorController(new Editor(grid), editorView); + window.setContentPane(editorView); + window.setVisible(true); + return; + } + } + + // Else, open the home page + HomeView homeView = new HomeView(window); + window.setContentPane(homeView); window.setVisible(true); } } \ No newline at end of file diff --git a/src/ManualSimulation.java b/src/ManualSimulation.java new file mode 100644 index 0000000..83c4263 --- /dev/null +++ b/src/ManualSimulation.java @@ -0,0 +1,57 @@ +/** + * This class is used to store the data of a manual simulation + * @version 1.0 + * @author Amir Daouadi + * @author Lyanis Souidi + */ +public class ManualSimulation { + /** + * The grid to use for the simulations + */ + private final Grid grid; + + /** + * The algorithm type to use for the simulations + */ + private final AlgoType algoType; + + /** + * The simulation + */ + private final Simulation simulation = new Simulation(); + + /** + * Constructor + * @param grid The grid to use for the simulation + * @param algoType The algorithm type to use for the simulation + */ + public ManualSimulation(Grid grid, AlgoType algoType) { + this.grid = grid; + this.algoType = algoType; + } + + /** + * Get the grid used for the simulation + * @return The grid + */ + public Grid getGrid() { + return this.grid; + } + + /** + * Get the algorithm type used for the simulation + * @return The algorithm type + */ + public AlgoType getAlgoType() { + return this.algoType; + } + + /** + * Get the simulation + * @return The simulation + */ + public Simulation getSimulation() { + return this.simulation; + } + +} diff --git a/src/ManualSimulationController.java b/src/ManualSimulationController.java index 0944b69..ebc4b0f 100644 --- a/src/ManualSimulationController.java +++ b/src/ManualSimulationController.java @@ -1,7 +1,6 @@ import javax.swing.*; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; +import java.awt.event.*; /** * Controller for the manual simulation view. @@ -13,58 +12,68 @@ public class ManualSimulationController { /** * The simulation model */ - private Simulation model; + private final ManualSimulation model; + /** * The manual simulation view */ - private ManualSimulationView view; - /** - * The grid model - */ - private Grid grid; + private final ManualSimulationView view; + /** * The algorithm used for the simulation */ private Algo algo; - /** - * The restart button - */ - private Button restartButton = new Button("Recommencer"); + /** * The next button */ - private Button nextButton = new Button("Coup suivant"); + private final Button nextButton = new Button("Coup suivant"); /** * Constructor - * @param model The simulation model + * @param model The manual simulation model * @param view The manual simulation view - * @param algo The algorithm used for the simulation */ - public ManualSimulationController(Simulation model, ManualSimulationView view, Algo algo) { + public ManualSimulationController(ManualSimulationView view, ManualSimulation model) { this.model = model; this.view = view; - this.algo = algo; JPanel buttons = new JPanel(); - restartButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - algo.reset(); - } - }); - buttons.add(restartButton); - - nextButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - algo.nextMove(); - if (model.isSuccess()) { - nextButton.setEnabled(false); - } - } - }); + nextButton.addActionListener(e -> move()); buttons.add(nextButton); this.view.add(buttons, BorderLayout.NORTH); + + this.view.addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent e) { + move(); + } + }); + this.view.setFocusable(true); + this.view.requestFocusInWindow(); + + run(); + } + + /** + * Run the simulation + */ + public void run() { + if (this.model.getAlgoType() == AlgoType.RANDOM) { + this.algo = new RandomAlgo(this.model.getGrid(), this.model.getSimulation()); + } else { + this.algo = new DeterministicAlgo(this.model.getGrid(), this.model.getSimulation()); + } + } + + private void move() { + if (model.getSimulation().isEnded()) return; + this.algo.nextMove(); + this.view.repaint(); + if (model.getSimulation().isEnded()) { + nextButton.setEnabled(false); + JOptionPane.showMessageDialog(view, "Partie terminée en " + model.getSimulation().getMoves() + " coups !", "Fin de partie", JOptionPane.INFORMATION_MESSAGE); + } } } diff --git a/src/ManualSimulationView.java b/src/ManualSimulationView.java index f4e25f0..26ff082 100644 --- a/src/ManualSimulationView.java +++ b/src/ManualSimulationView.java @@ -10,15 +10,17 @@ public class ManualSimulationView extends GridView { /** * The simulation model */ - private Simulation model; + private final ManualSimulation model; /** * Constructor * @param window The window * @param model The simulation model */ - public ManualSimulationView(Window window, Simulation model) { + public ManualSimulationView(Window window, ManualSimulation model) { super(window); + this.model = model; + super.setGrid(this.model.getGrid()); } @Override @@ -27,9 +29,8 @@ public class ManualSimulationView extends GridView { g.setColor(Color.BLACK); g.setFont(new Font("Arial", Font.PLAIN, 20)); - FontMetrics metrics = g.getFontMetrics(g.getFont()); - String movesStr = "Coups : " + this.model.getMoves(); - g.drawString(movesStr, 5, 5); + String movesStr = "Coups : " + this.model.getSimulation().getMoves(); + g.drawString(movesStr, 10, 20); } } diff --git a/src/RandomAlgo.java b/src/RandomAlgo.java new file mode 100644 index 0000000..c27f2de --- /dev/null +++ b/src/RandomAlgo.java @@ -0,0 +1,59 @@ +import java.util.Random; + +/** + * Random algorithm + * @version 0.1 + * @author Amir Daouadi + * @author Lyanis Souidi + */ +public class RandomAlgo implements Algo { + /** + * The grid model + */ + private final Grid grid; + + /** + * The simulation model + */ + private final Simulation simulation; + + /** + * The Thésée controller + */ + private final TheseeController theseeController; + + /** + * Random number generator + */ + private final Random random = new Random(); + + /** + * Constructor + * @param grid The grid model + * @param simulation The simulation model + */ + public RandomAlgo(Grid grid, Simulation simulation) { + this.grid = grid; + this.simulation = simulation; + this.theseeController = new TheseeController(this.grid.getThesee()); + this.grid.getThesee().getSquare().setVisited(true); + } + + /** + * Makes the next move of the algorithm + */ + public void nextMove() { + if (this.simulation.isEnded()) return; + Direction[] availableDirection = this.theseeController.getAvailableDirections(); + + Direction randomDirection = availableDirection[this.random.nextInt(availableDirection.length)]; + + this.theseeController.move(randomDirection, this.simulation); + + this.theseeController.getAvailableDirections(); + if (!this.simulation.isEnded() && this.grid.isEnded()) { + this.simulation.setEnded(); + } + } + +} diff --git a/src/RandomDirection.java b/src/RandomDirection.java deleted file mode 100644 index 455fe99..0000000 --- a/src/RandomDirection.java +++ /dev/null @@ -1,15 +0,0 @@ -import java.util.Random; - -public class RandomDirection { - public static final int UP = 0; - public static final int DOWN = 1; - public static final int LEFT = 2; - public static final int RIGHT = 3; - - public static int getRandomDirection() { - Random rand = new Random(); - int direction = rand.nextInt(4); - - return direction; - } -} \ No newline at end of file diff --git a/src/Simulation.java b/src/Simulation.java index 67e6bb6..e16996e 100644 --- a/src/Simulation.java +++ b/src/Simulation.java @@ -11,9 +11,9 @@ public class Simulation { private int moves = 0; /** - * If the simulation has been successful or not + * If the simulation has ended or not */ - private boolean success = false; + private boolean end = false; /** * Get the number of moves of the simulation @@ -24,11 +24,18 @@ public class Simulation { } /** - * Get if the simulation has been successful or not - * @return If the simulation has been successful or not + * Check if the simulation has ended or not + * @return If the simulation has ended or not */ - public boolean isSuccess() { - return this.success; + public boolean isEnded() { + return this.end; + } + + /** + * Set the simulation as ended + */ + public void setEnded() { + this.end = true; } /** @@ -38,11 +45,4 @@ public class Simulation { this.moves++; } - /** - * Set if the simulation has been successful or not - * @param success If the simulation has been successful or not - */ - public void setSuccess(boolean success) { - this.success = success; - } } diff --git a/src/Square.java b/src/Square.java index 6fdf6ff..15df910 100644 --- a/src/Square.java +++ b/src/Square.java @@ -1,8 +1,10 @@ public class Square { - public final int row; - public final int column; - public int type = 0; - private Grid gridModel; + private final int row; + private final int column; + private int type = 0; + private boolean isVisited = false; + private boolean isAccessible = false; + private final Grid gridModel; public Square(Grid gridModel, int row, int column) { this.gridModel = gridModel; @@ -26,14 +28,6 @@ public class Square { return this.type == 2; } - /** - * Checks if the current square is empty (not a wall and not an exit) - * @return true if the current square is empty, false otherwise - */ - public boolean isEmpty() { - return this.type == 0; - } - public boolean isThesee() { return this.gridModel.getThesee().getSquare() == this; } @@ -52,16 +46,11 @@ public class Square { */ public void setExit() throws Exception { if (this.gridModel.getThesee().getSquare() == this) throw new Exception("Vous ne pouvez pas placer la sortie sur la même case que Thésée. Déplacez d'abord Thésée puis réessayez."); - for (int i = 0; i < this.gridModel.getSize(); i++) { - for (int j = 0; j < this.gridModel.getSize(); j++) { - try { - if (this.gridModel.getSquare(i, j).isExit()) { - this.gridModel.getSquare(i, j).setEmpty(); - } - } catch (Exception e) { - System.err.println(e.getMessage()); - } - } + + Square oldExit = this.gridModel.getExit(); + + if (oldExit != null) { + oldExit.setEmpty(); } this.type = 2; @@ -85,4 +74,20 @@ public class Square { public Grid getGrid() { return this.gridModel; } + + public boolean isVisited() { + return this.isVisited; + } + + public void setVisited(boolean isVisited) { + this.isVisited = isVisited; + } + + public boolean isAccessible() { + return this.isAccessible; + } + + public void setAccessible(boolean isAccessible) { + this.isAccessible = isAccessible; + } } diff --git a/src/Thesee.java b/src/Thesee.java index 3b382ea..5cf346b 100644 --- a/src/Thesee.java +++ b/src/Thesee.java @@ -1,18 +1,57 @@ +/** + * Represents Thésée in the labyrinth. + */ public class Thesee { private Square square; private Square intialSquare; + /** + * Moves Thésée to the given square. + * @param square The square to move Thesee to + * @throws Exception If the given square is a wall + */ public void setSquare(Square square) throws Exception { - if (square.isExit()) throw new Exception("Vous ne pouvez pas placer Thesee sur la même case que la sortie. Déplacez d'abord la sortie puis réessayez."); - square.setEmpty(); - this.square = square; - if (this.intialSquare == null) this.intialSquare = this.square; + setSquare(square, false); } + /** + * Moves Thésée to the given square. + * @param square The square to move Thesee to + * @param initialPosition Whether the square is the initial position + * @throws Exception If the given square is a wall + */ + public void setSquare(Square square, boolean initialPosition) throws Exception { + if (square.isExit()) throw new Exception("Vous ne pouvez pas placer Thésée sur la même case que la sortie. Déplacez d'abord la sortie puis réessayez."); + square.setEmpty(); + this.square = square; + if (initialPosition) { + this.intialSquare = this.square; + } else { + if (this.intialSquare == null) this.intialSquare = this.square; + } + } + + /** + * Get the square where Thésée is. + * @return The square where Thésée is + */ public Square getSquare() { return this.square; } + /** + * Get a square next to Thésée. + * @param direction The direction to get the square from + * @return The requested square + * @throws Exception If the position is out of grid's bounds + */ + public Square getSquare(Direction direction) throws Exception { + return this.square.getGrid().getSquare(this.square.getRow() + direction.row(), this.square.getColumn() + direction.column()); + } + + /** + * Resets Thésée's position to the initial square/ + */ public void reset() { this.square = this.intialSquare; } diff --git a/src/TheseeController.java b/src/TheseeController.java index cf89680..e882a33 100644 --- a/src/TheseeController.java +++ b/src/TheseeController.java @@ -1,9 +1,5 @@ -import javax.swing.*; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; - -public class TheseeController implements KeyListener { - private Thesee model; +public class TheseeController { + private final Thesee model; private GridView gridView; public TheseeController(Thesee model, GridView gridView) { @@ -11,78 +7,71 @@ public class TheseeController implements KeyListener { this.gridView = gridView; } - public boolean moveUp() { + public TheseeController(Thesee model) { + this.model = model; + } + + /** + * Move Thésée in the given direction + * @param direction The direction to move Thésée to + * @param simulation The simulation model + * @return true if the move was successful, false otherwise. + */ + public boolean move(Direction direction, Simulation simulation) { + simulation.addMove(); try { - Square currentSquare = this.model.getSquare(); - Square newSquare = currentSquare.getGrid().getSquare(currentSquare.getRow(), currentSquare.getColumn() - 1); - if (newSquare.isWall()) return false; - this.model.setSquare(newSquare); - this.gridView.repaint(); + Square newSquare = this.model.getSquare(direction); + newSquare.setVisited(true); + // If the new square is a wall, add a move to simulate the rollback of the move + if (newSquare.isWall()) { + simulation.addMove(); + if (this.gridView != null) this.gridView.repaint(); + return false; + } + if (newSquare.isExit()) simulation.setEnded(); + else this.model.setSquare(newSquare); + if (this.gridView != null) this.gridView.repaint(); return true; } catch (Exception e) { return false; } } - public boolean moveDown() { + /** + * Get the available directions for the next move. + * It helps prevent Thésée from going out of the grid. + * @return The available directions + */ + public Direction[] getAvailableDirections() { + Direction[] availableDirections = new Direction[4]; + int availableDirectionsCount = 0; + try { - Square currentSquare = this.model.getSquare(); - Square newSquare = currentSquare.getGrid().getSquare(currentSquare.getRow(), currentSquare.getColumn() + 1); - if (newSquare.isWall()) return false; - this.model.setSquare(newSquare); - this.gridView.repaint(); - return true; - } catch (Exception e) { - return false; - } - } + this.model.getSquare(Direction.UP); + availableDirections[availableDirectionsCount] = Direction.UP; + availableDirectionsCount++; + } catch (Exception ignored) {} - public boolean moveLeft() { try { - Square currentSquare = this.model.getSquare(); - Square newSquare = currentSquare.getGrid().getSquare(currentSquare.getRow() - 1, currentSquare.getColumn()); - if (newSquare.isWall()) return false; - this.model.setSquare(newSquare); - this.gridView.repaint(); - return true; - } catch (Exception e) { - return false; - } - } + this.model.getSquare(Direction.DOWN); + availableDirections[availableDirectionsCount] = Direction.DOWN; + availableDirectionsCount++; + } catch (Exception ignored) {} - public boolean moveRight() { try { - Square currentSquare = this.model.getSquare(); - Square newSquare = currentSquare.getGrid().getSquare(currentSquare.getRow() + 1, currentSquare.getColumn()); - if (newSquare.isWall()) return false; - this.model.setSquare(newSquare); - this.gridView.repaint(); - return true; - } catch (Exception e) { - return false; - } - } + this.model.getSquare(Direction.LEFT); + availableDirections[availableDirectionsCount] = Direction.LEFT; + availableDirectionsCount++; + } catch (Exception ignored) {} - @Override - public void keyPressed(KeyEvent e) { - int keyCode = e.getKeyCode(); - boolean moved; - if (keyCode == KeyEvent.VK_UP) { - moved = moveUp(); - } else if (keyCode == KeyEvent.VK_DOWN) { - moved = moveDown(); - } else if (keyCode == KeyEvent.VK_LEFT) { - moved = moveLeft(); - } else if (keyCode == KeyEvent.VK_RIGHT) { - moved = moveRight(); - } - } + try { + this.model.getSquare(Direction.RIGHT); + availableDirections[availableDirectionsCount] = Direction.RIGHT; + availableDirectionsCount++; + } catch (Exception ignored) {} - @Override - public void keyReleased(KeyEvent e) { - } - - @Override - public void keyTyped(KeyEvent e) { + Direction[] availableDirectionsTrimmed = new Direction[availableDirectionsCount]; + System.arraycopy(availableDirections, 0, availableDirectionsTrimmed, 0, availableDirectionsCount); + return availableDirectionsTrimmed; } } diff --git a/src/Window.java b/src/Window.java index 0761e77..d927e70 100644 --- a/src/Window.java +++ b/src/Window.java @@ -3,13 +3,24 @@ import java.awt.Dimension; import java.awt.Toolkit; public class Window extends JFrame { + private static final String programTitle = "Labyrinthe"; + private String pageTitle = ""; public Window() { - super("Labyrinthe"); + super(programTitle); Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Dimension d = new Dimension(screenSize.width-150, screenSize.height-150); this.setSize(d); this.setLocationRelativeTo(null); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - this.setMinimumSize(new Dimension(750, 750)); + this.setMinimumSize(new Dimension(800, 850)); + } + + public String getPageTitle() { + return this.pageTitle; + } + + public void setPageTitle(String title) { + this.pageTitle = title; + this.setTitle(this.pageTitle + " - " + Window.programTitle); } } \ No newline at end of file