package sudoku.io; import gui.RenderableMultidoku; import gui.constants.Symbols; import sudoku.constraint.*; import sudoku.solver.*; import sudoku.structure.*; import java.util.ArrayList; import java.util.List; import java.util.Scanner; public class ConsoleInterface { private final Scanner reader = new Scanner(System.in); /** * Début de la séquence console, affiche un message de bienvenue et les crédits * du projet * puis donne à l'utilisateur le choix de récupérer un doku sauvegardé * ou d'en créer un nouveau. */ public void welcome() { System.out.println("Welcome to our Sudoku Solver!"); System.out.println("This is the project of Melvyn Bauvent, Lilas Grenier and Simon Pribylski."); System.out.println("Do you have a save sudoku you would like to continue? (y/n, default n)"); if (reader.next().equalsIgnoreCase("y")) { useSavedDoku(); } else { createDoku(); } } /** * Demande à l'utilisateur un fichier de sauvegarde et le laisse jouer au * MultiDoku. * qui y est sauvegardé */ private void useSavedDoku() { System.out.println("What save should we use? Please enter the save number."); MultiDoku md = getSavedDoku(); int blockWidth = md.getSubGrid(0).getBlockWidth(); int blockHeight = md.getSubGrid(0).getBlocks().getFirst().getCells().size() / blockWidth; List listSymbols = pickSymbols(blockWidth * blockHeight); System.out.println("This is the saved sudoku:"); showMultidoku(md, listSymbols, blockWidth, blockHeight); do { turn(md, listSymbols, blockWidth, blockHeight); } while (!md.isSolved()); congrats(); } /** * Demande à l'utilisateur les paramètres du doku à générer. */ private void createDoku() { System.out.println("First of all, you need to tell me the size of the sudoku you want to generate."); int width = getBlockWidth(); int height = getBlockHeight(); System.out.println("Your sudoku will have blocks of a " + width + " x " + height + " format."); int numberOfSymbols = width * height; List listSymbols = pickSymbols(numberOfSymbols); List listConstraints = getListConstraints(); System.out.println("Now that we have the size of our sudoku, " + "would you rather have a single grid ('one', default), " + "or a a multidoku composed of 5 subgrids ('multi') ?"); MultiDoku doku; if (reader.next().equalsIgnoreCase("multi")) { doku = SudokuFactory.createBasicXShapedMultidoku(width, height, listConstraints); } else { doku = SudokuFactory.createBasicEmptyRectangleDoku(width, height, listConstraints); } System.out.println("Your sudoku will look like this:"); showMultidoku(doku, listSymbols, width, height); System.out.println( "You can now manually fill this sudoku ('fill'), or generate a playable one from this template ('generate', default):"); if (reader.next().equalsIgnoreCase("fill")) { findSolution(doku, listSymbols, width, height); } else { playableDoku(doku, listSymbols, width, height); } } /** * Remplit un doku vide en fonction de la difficulté que l'utilisateur * renseigne, * et le laisse jouer. * * @param doku MultiDoku, MultiDoku vide à remplir * @param listSymbols List, liste des symboles à utiliser * @param width int, largeur d'un bloc du sudoku * @param height int, hauteur d'un bloc du sudoku */ private void playableDoku(MultiDoku doku, List listSymbols, int width, int height) { System.out.println("We will now fill this sudoku."); System.out.println("What level of difficulty would you like?" + " ('very easy', 'easy', 'medium' (default), 'hard', 'full' (sudoku fully completed))"); String difficulty = reader.next().toLowerCase(); if (difficulty.equals("full")) { generateFullDoku(doku); System.out.println("Here's your sudoku !"); showMultidoku(doku, listSymbols, width, height); exit(); } else { generatePartialDoku(doku, difficulty); System.out.println("Here's your sudoku !"); showMultidoku(doku, listSymbols, width, height); do { turn(doku, listSymbols, width, height); } while (!doku.isSolved()); congrats(); } } /** * Permet à l'utilisateur de remplir manuellement un sudoku vide, et de le * remplir * quand souhaité. * * @param doku MultiDoku, MultiDoku vide à remplir * @param listSymbols List, liste des symboles à utiliser * @param width int, largeur d'un bloc du sudoku * @param height int, hauteur d'un bloc du sudoku */ private void findSolution(MultiDoku doku, List listSymbols, int width, int height) { do { turn(doku, listSymbols, width, height); } while (!doku.isSolved()); System.out.println("This doku can be solved like this :"); showMultidoku(doku, listSymbols, width, height); exit(); } /** * Message de félicitation quand l'utilisateur a rempli son doku. */ private void congrats() { System.out.println("Congrats! You've solved this sudoku! We hope this was fun! Let's play together again!"); System.exit(0); } /** * Renvoie un MultiDoku préenregistré, dont le numéro de sauvegarde est * renseigné * par l'utilisateur. * * @return Mutidoku, multidoku enregistré à la sauveagrde de numéro donné. */ private MultiDoku getSavedDoku() { int nbSave; MultiDoku md = null; do { nbSave = reader.nextInt(); if (nbSave == -1) { System.exit(0); } try { md = SudokuSerializer.getSavedMultiDoku(nbSave); } catch (Exception e) { System.out.println( "There seems to be a problem with this save, please try again or write '-1' to abort."); } } while (md == null); return md; } /** * Demande à l'utilisateur la largeur d'un bloc du sudoku à générer. * * @return int, largeur d'un bloc du sudoku */ private int getBlockWidth() { System.out.println("Width of a block: "); int widthBlock = reader.nextInt(); checkValidSize(widthBlock); while (!checkValidSize(widthBlock)) { System.out.println("That is not a valid width for a block. Try again:"); widthBlock = reader.nextInt(); } System.out.println("You have chose a width of " + widthBlock + "."); return widthBlock; } /** * Demande à l'utilisateur la hauteur d'un bloc du sudoku à générer. * * @return int, hauteur d'un bloc du sudoku */ private int getBlockHeight() { System.out.println("Height of a block: "); int heightBlock = reader.nextInt(); checkValidSize(heightBlock); while (!checkValidSize(heightBlock)) { System.out.println("That is not a valid height for a block. Try again:"); heightBlock = reader.nextInt(); } System.out.println("You have chose a height of " + heightBlock + "."); return heightBlock; } /** * Vérifie si la taille passée en paramètres est une taille valide. * * @param size int, longueur à vérifier * @return true si size>0, false sinon. */ private Boolean checkValidSize(int size) { return (size > 0); } /** * Permet à l'utilisateur de choisir les symboles qu'il souhaite utiliser pour * l'affichage. * * @param numberOfSymbols int, nombre de symboles à choisir * @return LIst, liste des symboles à utiliser */ private List pickSymbols(int numberOfSymbols) { System.out.println("Would you like to pick the " + numberOfSymbols + " symbols from the sudoku? (y/n, default 'no')"); if (reader.next().equalsIgnoreCase("y")) { List listSymbols = new ArrayList<>(); System.out.println("You have chosen to pick your own symbols."); for (int i = 0; i < numberOfSymbols; i++) { System.out.println("Choose for the symbol number " + i + ": "); String newSymbol = reader.next(); while (listSymbols.contains(newSymbol)) { System.out.println("This symbol has already been given. Try again:"); newSymbol = reader.next(); } listSymbols.add(newSymbol); } System.out.println("You chose the symbols: " + listSymbols.toString()); return listSymbols; } else { System.out.println( "What existing sets of symbols do you want to use? Numbers ('n', default), letters ('l'), or emojis ('e', may not work on all consoles)?"); return switch (reader.next().toLowerCase()) { case "l" -> Symbols.Letters.getSymbols(); case "e" -> Symbols.Emojis.getSymbols(); default -> Symbols.Numbers.getSymbols(); }; } } /** * Permet à l'utilisateur de choisir les contraintes qu'il souhaite utiliser * pour son sudoku. * * @return List, liste des contraintes à utiliser */ private List getListConstraints() { List listConstraints = SudokuFactory.DEFAULT_CONSTRAINTS; System.out.println( "The sudoku have constraints of blocks, lines and columns. Would you like to add the diagonal constraints ? (y/n, default 'no')"); if (reader.next().equalsIgnoreCase("y")) { listConstraints.add(Constraint.Diagonal.getConstraint()); } return listConstraints; } /** * Remplit un sudoku selon la difficulté passée en paramètre. * * @param doku MultiDoku, doku vide à remplir selon la difficulté. * @param difficultyName String, difficulté de résolution du doku à remplir. */ private void generatePartialDoku(MultiDoku doku, String difficultyName) { Difficulty difficulty; switch (difficultyName) { case "very easy": difficulty = Difficulty.VeryEasy; case "easy": difficulty = Difficulty.Easy; case "hard": difficulty = Difficulty.Hard; default: difficulty = Difficulty.Medium; } try { SudokuFactory.fillDoku(doku, difficulty); } catch (Exception e) { System.out.println("There seems to be a problem with those settings. Let's start again."); } } /** * Remplit entièrement le doku passé en paramètre. * * @param doku MultiDoku, doku à remplir */ private void generateFullDoku(MultiDoku doku) { new RandomSolver().solve(doku); } /** * Affiche le doku passé en paramètre. * * @param doku MultiDoku, MultiDoku à afficher * @param listSymbols List, liste des symboles à utiliser * @param width int, largeur d'un bloc du sudoku * @param height int, hauteur d'un bloc du sudoku */ private void showMultidoku(MultiDoku doku, List listSymbols, int width, int height) { showMultiDoku(RenderableMultidoku.fromMultidoku(doku), listSymbols, width, height); } /** * Affiche le doku passé en paramètre. * * @param doku RenderableMultiDoku, MultiDoku à afficher * @param listSymbols List, liste des symboles à utiliser * @param width int, largeur d'un bloc du sudoku * @param height int, hauteur d'un bloc du sudoku */ private void showMultiDoku(RenderableMultidoku doku, List listSymbols, int width, int height) { SudokuPrinter.printMultiDokuWithIndex(doku, listSymbols, width, height); } /** * Permet à l'utilisateur de sauvegarder l'état de son doku, soit dans un * nouveau fichier * de sauvegarde, soit en écrasant une sauvegarde précédente. * * @param doku MultiDoku, MultiDoku à sauvegarder */ private void saveMultiDoku(MultiDoku doku) { System.out.println("Number of the file to overwrite ('-1' or unused save file number to create a new save) :"); int n = reader.nextInt(); String path = SudokuSerializer.saveMultiDoku(doku, n); System.out.println("The path to your save is: " + path); } /** * Tour de jeu de l'utilisateur, présenté avec les choix de remplir une case du * doku, * de sauvegarder son état actuel dans un fichier de sauvegarde, * de le résoudre tel qu'il est, * ou de quitter l'application. * * @param doku MultiDoku, MultiDoku actuel * @param listSymbols List, liste des symboles à utiliser * @param width int, largeur d'un bloc du sudoku * @param height int, hauteur d'un bloc du sudoku */ private void turn(MultiDoku doku, List listSymbols, int width, int height) { System.out.println( "You can now put a number in a cell ('play', default), show a solution ('solution'), save the grid ('save') or exit the program ('exit')."); switch (reader.next()) { case "save": saveMultiDoku(doku); break; case "solution": solve(doku, listSymbols, width, height); break; case "exit": exit(); break; default: play(doku, listSymbols, width, height); break; } } /** * Applique l'étape passée en paramètre. * * @param step SolverStep, étape à appliquer */ private void applyStep(SolverStep step) { step.getCell().setSymbolIndex(step.getNewValue()); } /** * Permet d'afficher une étape de résolution du doku complété. * * @param doku MultiDoku, MultiDoku actuel * @param listSymbols List, liste des symboles à utiliser * @param width int, largeur d'un bloc du sudoku * @param height int, hauteur d'un bloc du sudoku * @param step SolverStep, étape de résolution à afficher * @return boolean, valant true si l'utilisateur veut afficher l'étape, false * sinon */ private boolean showStep(MultiDoku doku, List listSymbols, int width, int height, SolverStep step) { System.out.println("Here is the step : \n"); showMultidoku(doku, listSymbols, width, height); applyStep(step); System.out.println("\nTurns into :\n"); showMultidoku(doku, listSymbols, width, height); System.out.println("Do you want to see the next step ? (y/n, default n)"); return reader.next().equals("y"); } /** * Permet d'afficher les étapes de résolution du doku complété si l'utilisateur * le souhaite. * * @param doku MultiDoku, MultiDoku actuel * @param listSymbols List, liste des symboles à utiliser * @param width int, largeur d'un bloc du sudoku * @param height int, hauteur d'un bloc du sudoku * @param steps List, liste des étapes de résolution */ private void showSolveSteps(MultiDoku doku, List listSymbols, int width, int height, List steps) { System.out.println("Would you like to see the steps of the solver ? (y/n, default n)"); doku.getStateManager().popState(); if (reader.next().equalsIgnoreCase("y")) { int stepCount = 0; while (stepCount < steps.size() && showStep(doku, listSymbols, width, height, steps.get(stepCount))) { stepCount++; } } } /** * Résout le doku en fonction du solver que choisit l'utilisateur. * * @param doku MultiDoku, MultiDoku actuel * @param listSymbols List, liste des symboles à utiliser * @param width int, largeur d'un bloc du sudoku * @param height int, hauteur d'un bloc du sudoku */ private void solve(MultiDoku doku, List listSymbols, int width, int height) { System.out.println( "Pick a solver to use : random ('random', default), human ('human') or mixed solver ('mixed')."); List steps = new ArrayList<>(); doku.getStateManager().pushState(); switch (reader.next()) { case "human": new HumanSolver().solve(doku, steps); break; case "mixed": new MixedSolver().solve(doku, steps); break; default: new RandomSolver().solve(doku, steps); break; } showMultidoku(doku, listSymbols, width, height); showSolveSteps(doku, listSymbols, width, height, steps); } /** * Remplissage d'une Cell du doku en fonction des coordonnées et du symboles que * l'utilisateur choisit. * * @param doku MultiDoku, MultiDoku actuel * @param listSymbols List, liste des symboles à utiliser * @param width int, largeur d'un bloc du sudoku * @param height int, hauteur d'un bloc du sudoku */ private void play(MultiDoku doku, List listSymbols, int width, int height) { int x, y; RenderableMultidoku rdoku = RenderableMultidoku.fromMultidoku(doku); do { System.out.println("Line of the cell to fill:"); y = reader.nextInt(); System.out.println("Column of the cell to fill:"); x = reader.nextInt(); } while (!isValidCoordinates(rdoku, width, height, x - 1, y - 1)); Cell cell = rdoku.getCell(x - 1, y - 1); System.out.println("Character to put in the (" + x + ", " + y + ") cell:"); String character = reader.next(); while (!isValidSymbol(character, listSymbols, width * height)) { System.out.println("This is not a valid symbol; try again:"); character = reader.next(); } cell.setSymbolIndex(indexOfSymbol(character, listSymbols, width * height)); showMultiDoku(rdoku, listSymbols, width, height); } /** * Vérifie que la Cell identifiée par les coordonées x et y dans le * RenderableMultiDOku fourni * existe et est modifiable. * * @param doku RenderableMultiDoku, MultiDoku actuel * @param width int, largeur d'un bloc du sudoku * @param height int, hauteur d'un bloc du sudoku * @param x int, abscisse de la Cell à vérifier * @param y int, ordonnée de la Cell à vérifier * @return Boolean true si la Cell aux coordonéees données peut être modifiée, * false sinon */ private boolean isValidCoordinates(RenderableMultidoku doku, int width, int height, int x, int y) { Cell cell = doku.getCell(x, y); return ((cell != null) && cell.isMutable()); } /** * Renvoie l'index du symbole passé en paramètre. * * @param symbol String, symbole dont on veut l'index * @param listSymbols List, liste des symboles possibles * @param nbSymbols int, nombre de symboles possibles * @return int, index du symbole si celui-ci est valide, Cell.NOSYMBOL sinon. */ private int indexOfSymbol(String symbol, List listSymbols, int nbSymbols) { for (int i = 0; i < nbSymbols; i++) { if (listSymbols.get(i).equals(symbol)) { return i; } } return Cell.NOSYMBOL; } /** * Vérifie que le symbol passé en paramètre est valide. * * @param symbol String, symbole dont on vérifie la validité * @param listSymbols List, liste des symboles possibles * @param size int, nombre de symboles possibles * @return boolean, valant true si le symbole est valide, false sinon. */ private boolean isValidSymbol(String symbol, List listSymbols, int size) { for (int i = 0; i < size; i++) { if (listSymbols.get(i).equals(symbol)) { return true; } } return false; } /** * Affiche un message d'aurevoir et ferme l'application. */ private void exit() { System.out.println("Thank you for playing!"); System.exit(0); } }