9 Commits

Author SHA1 Message Date
Janet-Doe
0795f9256d big merge je vais dcd
All checks were successful
Linux arm64 / Build (push) Successful in 11m6s
2025-01-30 18:56:46 +01:00
a74bf42e59 refactor solvers
All checks were successful
Linux arm64 / Build (push) Successful in 42s
2025-01-30 18:05:18 +01:00
1f92c49f3c feat: select solve algorithm
All checks were successful
Linux arm64 / Build (push) Successful in 25m12s
2025-01-30 17:28:43 +01:00
c262007ca8 fix multithreading issue 2025-01-30 17:28:22 +01:00
73f572ee18 fix merge
All checks were successful
Linux arm64 / Build (push) Successful in 42s
2025-01-30 16:37:10 +01:00
ec07d88577 Merge branch 'symbols' 2025-01-30 16:35:05 +01:00
6caf5d5c54 feat: add cyrilic
All checks were successful
Linux arm64 / Build (push) Successful in 29s
2025-01-30 16:32:09 +01:00
7789209651 feat: change symbols
All checks were successful
Linux arm64 / Build (push) Successful in 27s
2025-01-30 15:23:54 +01:00
9b776d4b0f custom symbols
All checks were successful
Linux arm64 / Build (push) Successful in 26s
2025-01-29 19:47:46 +01:00
19 changed files with 580 additions and 288 deletions

View File

@@ -1,6 +1,8 @@
package gui; package gui;
import imgui.ImFont; import imgui.ImFont;
import imgui.ImFontConfig;
import imgui.ImFontGlyphRangesBuilder;
import imgui.ImGui; import imgui.ImGui;
public class Fonts { public class Fonts {
@@ -10,13 +12,21 @@ public class Fonts {
public static ImFont CHERI; public static ImFont CHERI;
public static ImFont COMIC; public static ImFont COMIC;
public static ImFont INFECTED; public static ImFont INFECTED;
public static ImFont EMOJIS;
private static final String baseDir = ""; private static final String baseDir = "";
public static void createFonts() { public static void createFonts() {
ImFontGlyphRangesBuilder builder = new ImFontGlyphRangesBuilder();
builder.addRanges(ImGui.getIO().getFonts().getGlyphRangesDefault());
builder.addRanges(ImGui.getIO().getFonts().getGlyphRangesCyrillic());
// builder.addRanges(ImGui.getIO().getFonts().getGlyphRangesChineseFull());
ImFontConfig cfg = new ImFontConfig();
cfg.setGlyphRanges(builder.buildRanges());
COMIC = ImGui.getIO().getFonts().addFontFromFileTTF(baseDir + "comic.ttf", 50.0f); COMIC = ImGui.getIO().getFonts().addFontFromFileTTF(baseDir + "comic.ttf", 50.0f);
ARIAL_BOLD = ImGui.getIO().getFonts().addFontFromFileTTF(baseDir + "arial_bold.ttf", 50.0f); ARIAL_BOLD = ImGui.getIO().getFonts().addFontFromFileTTF(baseDir + "arial_bold.ttf", 50.0f);
ARIAL = ImGui.getIO().getFonts().addFontFromFileTTF(baseDir + "arial.ttf", 50.0f); ARIAL = ImGui.getIO().getFonts().addFontFromFileTTF(baseDir + "arial.ttf", 50.0f, cfg);
CHERI = ImGui.getIO().getFonts().addFontFromFileTTF(baseDir + "cheri.ttf", 50.0f); CHERI = ImGui.getIO().getFonts().addFontFromFileTTF(baseDir + "cheri.ttf", 50.0f);
INFECTED = ImGui.getIO().getFonts().addFontFromFileTTF(baseDir + "INFECTED.ttf", 50.0f); INFECTED = ImGui.getIO().getFonts().addFontFromFileTTF(baseDir + "INFECTED.ttf", 50.0f);
} }

View File

@@ -0,0 +1,7 @@
package gui;
public class Options {
public static Symbols Symboles = Symbols.Numbers;
}

View File

@@ -47,8 +47,6 @@ public class RenderableMultidoku {
return cells.get(index); return cells.get(index);
} }
private static record PositionConstraint(Sudoku sudoku1, Sudoku sudoku2, Coordinate offset) { private static record PositionConstraint(Sudoku sudoku1, Sudoku sudoku2, Coordinate offset) {
} }

View File

@@ -75,7 +75,7 @@ public class SudokuRenderer {
ImGui.closeCurrentPopup(); ImGui.closeCurrentPopup();
} }
} else { } else {
if (ImGui.button(Integer.toString(i + 1), cellSize)) { if (ImGui.button(Options.Symboles.getSymbols().get(i), cellSize)) {
currentCell.trySetValue(i); currentCell.trySetValue(i);
if (this.doku.getDoku().isSolved()) if (this.doku.getDoku().isSolved())
this.onResolve.emit(); this.onResolve.emit();
@@ -88,6 +88,9 @@ public class SudokuRenderer {
} }
public void render() { public void render() {
if (Options.Symboles == Symbols.Russian) {
ImGui.pushFont(Fonts.ARIAL);
}
final float sudokuViewWidth = cellSize.x * doku.getWidth(); final float sudokuViewWidth = cellSize.x * doku.getWidth();
final float displayWidth = ImGui.getIO().getDisplaySizeX(); final float displayWidth = ImGui.getIO().getDisplaySizeX();
float offsetX = displayWidth / 2.0f - sudokuViewWidth / 2.0f; float offsetX = displayWidth / 2.0f - sudokuViewWidth / 2.0f;
@@ -123,7 +126,7 @@ public class SudokuRenderer {
ImGui.pushStyleColor(ImGuiCol.Button, new ImVec4(blockColor.r, blockColor.g, blockColor.b, 1.0f)); ImGui.pushStyleColor(ImGuiCol.Button, new ImVec4(blockColor.r, blockColor.g, blockColor.b, 1.0f));
String cellText = ""; String cellText = "";
if (symbol != -1) if (symbol != -1)
cellText += Integer.toString(symbol + 1); cellText += Options.Symboles.getSymbols().get(symbol);
if (ImGui.button(cellText + "##" + index, cellSize) && cell.isMutable()) { if (ImGui.button(cellText + "##" + index, cellSize) && cell.isMutable()) {
ImGui.openPopup("editPopup"); ImGui.openPopup("editPopup");
currentCell = cell; currentCell = cell;
@@ -135,6 +138,9 @@ public class SudokuRenderer {
ImGui.popStyleVar(2); ImGui.popStyleVar(2);
renderPopup(); renderPopup();
ImGui.endChild(); ImGui.endChild();
if (Options.Symboles == Symbols.Russian) {
ImGui.popFont();
}
} }
} }

View File

@@ -0,0 +1,83 @@
package gui;
import java.util.ArrayList;
import java.util.List;
public enum Symbols {
Numbers("Nombres", getNumbers()),
Letters("Lettres", getLetters()),
Russian("Cyrilique", getRussian()),
Emojis("Emojis (Console uniquement)", getEmojis());
String displayName;
List<String> symbols;
private Symbols(String displayName, List<String> symbols) {
this.symbols = symbols;
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
public List<String> getSymbols() {
return symbols;
}
private static List<String> getNumbers() {
List<String> sym = new ArrayList<>();
for (int i = 1; i <= 100; i++) {
sym.add(Integer.toString(i));
}
return sym;
}
private static List<String> getLetters() {
List<String> sym = new ArrayList<>();
for (int i = 0; i < 100; i++) {
int currentOffset = i;
String letter = "";
while (currentOffset >= 26) {
letter += Character.toString((char) ('A' + currentOffset % 26));
currentOffset /= 26;
currentOffset--;
}
letter += Character.toString((char) ('A' + currentOffset % 26));
sym.add(new StringBuilder(letter).reverse().toString());
}
return sym;
}
private static List<String> getRussian() {
List<String> sym = new ArrayList<>();
for (int i = 0; i < 100; i++) {
sym.add(new String(Character.toChars(0X0400 + i)));
}
return sym;
}
private static List<String> getEmojis() {
List<String> sym = new ArrayList<>();
for (int i = 0; i < 100; i++) {
sym.add(new String(Character.toChars(0X1F600 + i)));
}
return sym;
}
private static final String[] symbolNames;
static {
Symbols[] symbols = Symbols.values();
symbolNames = new String[symbols.length];
for (int i = 0; i < symbols.length; i++) {
symbolNames[i] = symbols[i].getDisplayName();
}
}
public static String[] getSymbolsNames() {
return symbolNames;
}
}

View File

@@ -1,17 +1,24 @@
package gui.menu; package gui.menu;
import gui.Options;
import gui.Symbols;
import imgui.ImGui; import imgui.ImGui;
import imgui.type.ImInt;
public class OptionsMenu extends BaseView { public class OptionsMenu extends BaseView {
private ImInt currentValue = new ImInt();
public OptionsMenu(StateMachine stateMachine) { public OptionsMenu(StateMachine stateMachine) {
super(stateMachine); super(stateMachine);
} }
@Override @Override
public void render() { public void render() {
// TODO Auto-generated method stub
ImGui.text("Options"); ImGui.text("Options");
if(ImGui.combo("Jeu de symboles", currentValue, Symbols.getSymbolsNames())){
Options.Symboles = Symbols.values()[currentValue.get()];
}
renderReturnButton(); renderReturnButton();
} }

View File

@@ -1,12 +1,14 @@
package gui.menu; package gui.menu;
import java.util.Random;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
import gui.SudokuRenderer; import gui.SudokuRenderer;
import imgui.ImGui; import imgui.ImGui;
import imgui.ImGuiStyle; import imgui.ImGuiStyle;
import sudoku.io.SudokuSerializer; import sudoku.io.SudokuSerializer;
import sudoku.solver.BacktrackingSolver;
import sudoku.solver.HumanSolver;
import sudoku.solver.MixedSolver;
import sudoku.solver.Solver; import sudoku.solver.Solver;
import sudoku.structure.MultiDoku; import sudoku.structure.MultiDoku;
@@ -66,34 +68,52 @@ public class SudokuView extends BaseView {
stopResolve(); stopResolve();
} }
private void renderSolveButton() { private void startSolve(Solver solver) {
if (resolveThread != null)
ImGui.beginDisabled();
boolean beginSolve = false;
if (!resolved && centeredButton("Résoudre")) {
beginSolve = true;
}
if (resolveThread != null)
ImGui.endDisabled();
if (beginSolve) {
resolveThread = new Thread(() -> { resolveThread = new Thread(() -> {
try { try {
Random rand = new Random(); solver.solve(this.doku);
Solver.randomSolve(doku, rand); } catch (CancellationException e) {
Thread.sleep(200);
} catch (CancellationException | InterruptedException e) {
System.out.println("The user is bored !"); System.out.println("The user is bored !");
} }
stopResolve(); stopResolve();
}); });
} }
if (resolved) { private void renderSolvePopup() {
if (ImGui.beginPopup("solve")) {
if (ImGui.button("Résoudre avec backtrace")) {
startSolve(new BacktrackingSolver());
ImGui.closeCurrentPopup();
}
if (ImGui.button("Résoudre avec déduction")) {
startSolve(new HumanSolver());
ImGui.closeCurrentPopup();
}
if (ImGui.button("Résoudre avec déduction et backtrace")) {
startSolve(new MixedSolver());
ImGui.closeCurrentPopup();
}
ImGui.endPopup();
}
}
private void renderSolveButton() {
if (resolveThread != null)
ImGui.beginDisabled();
if (!this.resolved && centeredButton("Résoudre")) {
// beginSolve = true;
ImGui.openPopup("solve");
}
if (resolveThread != null)
ImGui.endDisabled();
if (this.resolved) {
ImGui.text("Bravo !"); ImGui.text("Bravo !");
} }
renderSolvePopup();
} }
private void renderSaveButton() { private void renderSaveButton() {

View File

@@ -3,15 +3,40 @@
*/ */
package sudoku; package sudoku;
import gui.RenderableMultidoku;
import gui.Symbols;
import sudoku.io.ConsoleInterface; import sudoku.io.ConsoleInterface;
import sudoku.io.SudokuPrinter;
import sudoku.solver.RandomSolver;
import sudoku.solver.Solver;
import sudoku.structure.MultiDoku;
import sudoku.structure.SudokuFactory;
import java.util.Random;
public class Main { public class Main {
public String getGreeting() { public String getGreeting() {
return "Hello World!"; return "Hello World!";
} }
public static void voidTest(){
MultiDoku md = SudokuFactory.createBasicXShapedMultidoku(3, SudokuFactory.DEFAULT_CONSTRAINTS);
SudokuPrinter.printMultiDoku(RenderableMultidoku.fromMultidoku(md), Symbols.Numbers, 3, 3);
}
public static void filledTest(){
MultiDoku md = SudokuFactory.createBasicXShapedMultidoku(3, SudokuFactory.DEFAULT_CONSTRAINTS);
new RandomSolver().solve(md);
SudokuPrinter.printMultiDoku(RenderableMultidoku.fromMultidoku(md), Symbols.Numbers, 3, 3);
}
public static void main(String[] args) { public static void main(String[] args) {
ConsoleInterface console = new ConsoleInterface(); ConsoleInterface console = new ConsoleInterface();
console.start(); /*
voidTest();
filledTest();
filledTest();
*/
console.welcome();
} }
} }

View File

@@ -1,22 +1,27 @@
package sudoku.io; package sudoku.io;
import gui.RenderableMultidoku;
import gui.Symbols;
import sudoku.constraint.*; import sudoku.constraint.*;
import sudoku.solver.Solver; import sudoku.solver.RandomSolver;
import sudoku.structure.Difficulty; import sudoku.structure.Difficulty;
import sudoku.structure.MultiDoku; import sudoku.structure.MultiDoku;
import sudoku.structure.Sudoku;
import sudoku.structure.SudokuFactory; import sudoku.structure.SudokuFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Random;
import java.util.Scanner; import java.util.Scanner;
public class ConsoleInterface { public class ConsoleInterface {
public Scanner reader = new Scanner(System.in); public Scanner reader = new Scanner(System.in);
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 Priblyski.");
start();
}
public void start(){ public void start(){
welcome();
System.out.println("First of all, you need to tell me the size of the sudoku you want to generate."); System.out.println("First of all, you need to tell me the size of the sudoku you want to generate.");
int width = getBlockWidth(); int width = getBlockWidth();
int height = getBlockHeight(); int height = getBlockHeight();
@@ -28,26 +33,21 @@ public class ConsoleInterface {
pickSymbols(listSymbols, numberOfSymbols); pickSymbols(listSymbols, numberOfSymbols);
} }
else { else {
// TODO listSymbols = Symbols.Numbers.getSymbols();
System.out.println("Simon doit finir sa partie.");
assert false;
} }
List<Constraint> listConstraints = getListConstraints(); List<Constraint> listConstraints = getListConstraints();
System.out.println("Now that we have the size of our sudoku, would you rather have a single grid ('one', default), " + 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') ?"); "or a a multidoku composed of 5 subgrids ('multi') ?");
List<Sudoku> subGrids = new ArrayList<>();
MultiDoku doku; MultiDoku doku;
if (reader.next().equalsIgnoreCase("multi")) { if (reader.next().equalsIgnoreCase("multi")) {
doku = SudokuFactory.createBasicEmptyRectangleDoku(width, height, listConstraints);
}
else {
doku = SudokuFactory.createBasicXShapedMultidoku(width, height, listConstraints); doku = SudokuFactory.createBasicXShapedMultidoku(width, height, listConstraints);
} }
else {
doku = SudokuFactory.createBasicEmptyRectangleDoku(width, height, listConstraints);
}
RenderableMultidoku rm = RenderableMultidoku.fromMultidoku(doku);
System.out.println("Your sudoku will look like this:"); System.out.println("Your sudoku will look like this:");
// TODO printMultiDoku method not yet implemented SudokuPrinter.printMultiDoku(rm, listSymbols, width, height);
SudokuPrinter.printMultiDoku(doku, width, height);
System.out.println("We now will fill this sudoku."); System.out.println("We now will fill this sudoku.");
System.out.println("What level of difficulty would you like? ('very easy', 'easy', 'medium' (default), 'hard', 'full' (sudoku fully completed))"); 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(); String difficulty = reader.next().toLowerCase();
@@ -58,12 +58,7 @@ public class ConsoleInterface {
generatePartialDoku(doku, difficulty); generatePartialDoku(doku, difficulty);
} }
System.out.println("Here's your sudoku !"); System.out.println("Here's your sudoku !");
SudokuPrinter.printMultiDoku(doku, width, height); SudokuPrinter.printMultiDoku(rm, listSymbols, width, height);
}
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 Priblyski.");
} }
public int getBlockWidth() { public int getBlockWidth() {
@@ -133,7 +128,7 @@ public class ConsoleInterface {
} }
private void generateFullDoku(MultiDoku doku) { private void generateFullDoku(MultiDoku doku) {
Solver.randomSolve(doku, new Random()); new RandomSolver().solve(doku);
} }
} }

View File

@@ -1,18 +1,40 @@
package sudoku.io; package sudoku.io;
import gui.RenderableMultidoku;
import gui.Symbols;
import sudoku.structure.Cell;
import sudoku.structure.MultiDoku; import sudoku.structure.MultiDoku;
import sudoku.structure.Sudoku; import sudoku.structure.Sudoku;
public class SudokuPrinter { import java.util.List;
public static void printRectangleSudoku(final Sudoku s, int blockWidth, int blockHeight) { public class SudokuPrinter {
public static final String ANSI_RESET = "\u001B[0m";
public static final String ANSI_RED = "\u001B[31m";
public static final String ANSI_GREEN = "\u001B[32m";
public static final String ANSI_YELLOW = "\u001B[33m";
public static final String ANSI_BLUE = "\u001B[34m";
public static final String ANSI_PURPLE = "\u001B[35m";
public static final String ANSI_CYAN = "\u001B[36m";
public static void printRectangleSudoku(final Sudoku s, int blockWidth, int blockHeight, Symbols symbols) {
printRectangleSudoku(s, blockWidth, blockHeight, symbols.getSymbols());
}
public static void printRectangleSudoku(final Sudoku s, int blockWidth, int blockHeight, List<String> listSymbols){
for (int y = 0; y < s.getSize(); y++) { for (int y = 0; y < s.getSize(); y++) {
if (y % blockHeight == 0 && y > 0) { if (y % blockHeight == 0 && y > 0) {
System.out.println(); System.out.println();
} }
StringBuilder line = new StringBuilder("[ "); StringBuilder line = new StringBuilder("[ ");
for (int x = 0; x < s.getSize(); x++) { for (int x = 0; x < s.getSize(); x++) {
line.append((s.getCell(x, y).getSymbolIndex() + 1)).append(" "); Cell c = s.getCell(x, y);
if (c.getSymbolIndex() == Cell.NOSYMBOL) {
line.append(" ");
}
else {
line.append(listSymbols.get(c.getSymbolIndex())).append(" ");
}
if (x % blockWidth == blockWidth - 1 && x != blockWidth * blockHeight - 1) { if (x % blockWidth == blockWidth - 1 && x != blockWidth * blockHeight - 1) {
line.append("| "); line.append("| ");
} }
@@ -20,9 +42,48 @@ public class SudokuPrinter {
line.append("]"); line.append("]");
System.out.println(line); System.out.println(line);
} }
}
public static void printMultiDoku(final RenderableMultidoku rm, Symbols symbols, int blockWidth, int blockHeight) {
printMultiDoku(rm, symbols.getSymbols(), blockWidth, blockHeight);
} }
public static String toStringRectangleSudoku(final Sudoku s, int blockWidth, int blockHeight) { public static void printMultiDoku(final RenderableMultidoku rm, List<String> listSymbols, int blockWidth, int blockHeight) {
StringBuilder line = new StringBuilder("\n");
int nBlockInWidth = rm.getWidth() / blockWidth;
for (int y = 0; y < rm.getHeight(); y++) {
if (y % blockHeight == 0) {
line.append("__".repeat(Math.max(0, rm.getWidth()+nBlockInWidth))).append("_\n");
}
line.append("[ ");
for (int x = 0; x < rm.getWidth(); x++) {
if (x % blockWidth == 0 && x > 0) {
line.append("| ");
}
Cell cell = rm.getCell(x, y);
if (cell != null) {
if (cell.getSymbolIndex() == Cell.NOSYMBOL) {
line.append("- ");
}
else {
line.append(listSymbols.get(cell.getSymbolIndex())).append(" ");
}
}
else {
line.append(" ");
}
}
line.append("]\n");
}
line.append("__".repeat(Math.max(0, rm.getWidth()+nBlockInWidth))).append("_\n");
System.out.println(line);
}
public static String toStringRectangleSudoku(final Sudoku s, int blockWidth, int blockHeight, Symbols symbols){
return toStringRectangleSudoku(s, blockWidth, blockHeight, symbols.getSymbols());
}
public static String toStringRectangleSudoku(final Sudoku s, int blockWidth, int blockHeight, List<String> listSymbols) {
StringBuilder result = new StringBuilder(); StringBuilder result = new StringBuilder();
for (int y = 0; y < s.getSize(); y++) { for (int y = 0; y < s.getSize(); y++) {
// Ajouter une ligne vide entre les blocs horizontaux // Ajouter une ligne vide entre les blocs horizontaux
@@ -32,7 +93,13 @@ public class SudokuPrinter {
StringBuilder line = new StringBuilder("[ "); StringBuilder line = new StringBuilder("[ ");
for (int x = 0; x < s.getSize(); x++) { for (int x = 0; x < s.getSize(); x++) {
// Ajouter la valeur de la cellule // Ajouter la valeur de la cellule
line.append((s.getCell(x, y).getSymbolIndex() + 1)).append(" "); Cell cell = s.getCell(x, y);
if (cell.getSymbolIndex() == Cell.NOSYMBOL) {
line.append(" ");
}
else {
line.append(listSymbols.get(cell.getSymbolIndex())).append(" ");
}
// Ajouter un séparateur vertical entre les blocs // Ajouter un séparateur vertical entre les blocs
if (x % blockWidth == blockWidth - 1 && x != s.getSize() - 1) { if (x % blockWidth == blockWidth - 1 && x != s.getSize() - 1) {
@@ -45,7 +112,12 @@ public class SudokuPrinter {
return result.toString(); return result.toString();
} }
public static void printMultiDoku(final MultiDoku doku, int blockWidth, int blockHeight){ public static void printMultiDoku(final MultiDoku doku, int blockWidth, int blockHeight, Symbols symbols){
// TODO if (doku.getNbSubGrids()==1) {
printRectangleSudoku(doku.getSubGrid(0), blockWidth, blockHeight, symbols);
}
else {
printMultiDoku(RenderableMultidoku.fromMultidoku(doku), symbols, blockWidth, blockHeight);
}
} }
} }

View File

@@ -0,0 +1,49 @@
package sudoku.solver;
import java.util.List;
import java.util.concurrent.CancellationException;
import sudoku.structure.Cell;
import sudoku.structure.MultiDoku;
public class BacktrackingSolver implements Solver {
/**
* Résout le MultiDoku passé en paramètre, avec backtracking.
*
* @param doku MultiDoku, MultiDoku à résoudre.
* @return boolean, valant true si le MultiDoku est résolu, false sinon.
*/
@Override
public boolean solve(MultiDoku doku) {
if (Thread.interrupted())
throw new CancellationException("User wants to stop the solver");
if (doku.isSolved()) {
return true;
}
Cell cellToFill = doku.getFirstEmptyCell();
if (cellToFill == null) {
return false;
}
List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
if (possibleSymbols.isEmpty()) {
return false;
}
for (int symbol : possibleSymbols) {
cellToFill.setSymbolIndex(symbol);
if (this.solve(doku)) {
return true;
} else {
cellToFill.setSymbolIndex(Cell.NOSYMBOL);
}
}
return false;
}
}

View File

@@ -0,0 +1,57 @@
package sudoku.solver;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.logging.Level;
import gui.Symbols;
import sudoku.io.SudokuPrinter;
import sudoku.structure.Cell;
import sudoku.structure.MultiDoku;
import sudoku.structure.Sudoku;
public class HumanSolver implements Solver {
/**
* Résout le MultiDoku passé en paramètre, avec règles de déduction.
*
* @param doku MultiDoku, MultiDoku à résoudre.
* @return boolean, valant true si le MultiDoku est résolu, false sinon.
*/
@Override
public boolean solve(MultiDoku doku) {
if (Thread.interrupted())
throw new CancellationException("User wants to stop the solver");
Sudoku sudoku = doku.getSubGrid(0);
logger.log(Level.FINE,
'\n' + SudokuPrinter.toStringRectangleSudoku(sudoku,
sudoku.getBlockWidth() == 0 ? sudoku.getSize() : sudoku.getBlockWidth(),
sudoku.getBlockWidth() == 0 ? sudoku.getSize() : sudoku.getSize() / sudoku.getBlockWidth(),
Symbols.Numbers));
if (doku.isSolved()) {
return true;
}
List<Cell> cellsToFill = doku.getEmptyCells();
if (cellsToFill.isEmpty()) {
return false;
}
for (Cell cellToFill : cellsToFill) {
List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
if (possibleSymbols.size() != 1) {
continue;
}
cellToFill.setSymbolIndex(possibleSymbols.getFirst());
return this.solve(doku);
}
return doku.isSolved();
}
}

View File

@@ -0,0 +1,82 @@
package sudoku.solver;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CancellationException;
import java.util.logging.Level;
import gui.Symbols;
import sudoku.io.SudokuPrinter;
import sudoku.structure.Cell;
import sudoku.structure.MultiDoku;
import sudoku.structure.Sudoku;
public class MixedSolver implements Solver{
/**
* Résout le MultiDoku passé en paramètre, avec règles de déduction et
* backtracking.
*
* @param doku MultiDoku, MultiDoku à résoudre.
* @return boolean, valant true si le MultiDoku est résolu, false sinon.
*/
@Override
public boolean solve(MultiDoku doku) {
Random rand = new Random();
if (Thread.interrupted()) {
throw new CancellationException("User wants to stop the solver");
}
Sudoku sudoku = doku.getSubGrid(0);
logger.log(Level.FINE,
'\n' + SudokuPrinter.toStringRectangleSudoku(
sudoku,
sudoku.getBlockWidth() == 0 ? sudoku.getSize() : sudoku.getBlockWidth(),
sudoku.getBlockWidth() == 0 ? sudoku.getSize() : sudoku.getSize() / sudoku.getBlockWidth(),
Symbols.Numbers));
if (doku.isSolved()) {
return true;
}
List<Cell> cellsToFill = doku.getEmptyCells();
if (cellsToFill.isEmpty()) {
return false;
}
// Règles de déduction
for (Cell cellToFill : cellsToFill) {
List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
if (possibleSymbols.size() != 1) {
continue;
}
cellToFill.setSymbolIndex(possibleSymbols.getFirst());
return this.solve(doku);
}
// Si ça ne marche pas
// On fait du backtracking
Cell cellToFill = doku.getRandomEmptyCell(rand);
List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
while (!possibleSymbols.isEmpty()) {
int nextPossibleSymbolIndex = rand.nextInt(possibleSymbols.size());
int nextSymbol = possibleSymbols.get(nextPossibleSymbolIndex);
cellToFill.setSymbolIndex(nextSymbol);
if (this.solve(doku)) {
return true;
}
cellToFill.setSymbolIndex(Cell.NOSYMBOL);
possibleSymbols.remove(nextPossibleSymbolIndex);
}
return false;
}
}

View File

@@ -0,0 +1,65 @@
package sudoku.solver;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CancellationException;
import java.util.logging.Level;
import gui.Symbols;
import sudoku.io.SudokuPrinter;
import sudoku.structure.Cell;
import sudoku.structure.MultiDoku;
import sudoku.structure.Sudoku;
public class RandomSolver implements Solver {
/**
* Résout, si possible, le multidoku passé en paramètre
* en testant toutes les possibilités, de manière aléatoire, avec un algorithme
* de backtracking.
*
* @param doku Multidoku, à résoudre
* @param rand Random, pour tester aléatoirement les symboles
* @return boolean, true s'il est résolu ou false s'il ne l'est pas.
*/
@Override
public boolean solve(MultiDoku doku) {
Random rand = new Random();
if (Thread.interrupted())
throw new CancellationException("User wants to stop the solver");
Sudoku sudoku = doku.getSubGrid(0);
logger.log(Level.FINE,
'\n' + SudokuPrinter.toStringRectangleSudoku(sudoku,
sudoku.getBlockWidth() == 0 ? sudoku.getSize() : sudoku.getBlockWidth(),
sudoku.getBlockWidth() == 0 ? sudoku.getSize() : sudoku.getSize() / sudoku.getBlockWidth(),
Symbols.Numbers));
if (doku.isSolved()) {
return true;
}
Cell cellToFill = doku.getFirstEmptyCell();
if (cellToFill == null) {
return false;
}
List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
while (!possibleSymbols.isEmpty()) {
int nextPossibleSymbolIndex = rand.nextInt(possibleSymbols.size());
int nextSymbol = possibleSymbols.get(nextPossibleSymbolIndex);
cellToFill.setSymbolIndex(nextSymbol);
if (this.solve(doku)) {
return true;
}
cellToFill.setSymbolIndex(Cell.NOSYMBOL);
possibleSymbols.remove(nextPossibleSymbolIndex);
}
return false;
}
}

View File

@@ -1,67 +1,19 @@
package sudoku.solver; package sudoku.solver;
import java.util.List; import java.util.List;
import java.util.Random;
import java.util.concurrent.CancellationException;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import sudoku.io.SudokuPrinter;
import sudoku.structure.Cell; import sudoku.structure.Cell;
import sudoku.structure.MultiDoku; import sudoku.structure.MultiDoku;
import sudoku.structure.Sudoku;
public class Solver { public interface Solver {
/** /**
* Log du Solver, qui garde trace des actions réalisées. * Log du Solver, qui garde trace des actions réalisées.
*/ */
private static final Logger logger = Logger.getLogger("SolverLogger"); public static final Logger logger = Logger.getLogger("SolverLogger");
/** boolean solve(MultiDoku doku);
* Résout, si possible, le multidoku passé en paramètre
* en testant toutes les possibilités, de manière aléatoire, avec un algorithme
* de backtracking.
*
* @param doku Multidoku, à résoudre
* @param rand Random, pour tester aléatoirement les symboles
* @return boolean, true s'il est résolu ou false s'il ne l'est pas.
*/
public static boolean randomSolve(MultiDoku doku, Random rand) {
if (Thread.interrupted())
throw new CancellationException("User wants to stop the solver");
Sudoku sudoku = doku.getSubGrid(0);
logger.log(Level.FINE,
'\n' + SudokuPrinter.toStringRectangleSudoku(sudoku,
sudoku.getBlockWidth() == 0 ? sudoku.getSize() : sudoku.getBlockWidth(),
sudoku.getBlockWidth() == 0 ? sudoku.getSize() : sudoku.getSize() / sudoku.getBlockWidth()));
if (doku.isSolved()) {
return true;
}
Cell cellToFill = doku.getFirstEmptyCell();
if (cellToFill == null) {
return false;
}
List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
while (!possibleSymbols.isEmpty()) {
int nextPossibleSymbolIndex = rand.nextInt(possibleSymbols.size());
int nextSymbol = possibleSymbols.get(nextPossibleSymbolIndex);
cellToFill.setSymbolIndex(nextSymbol);
if (Solver.randomSolve(doku, rand)) {
return true;
}
cellToFill.setSymbolIndex(Cell.NOSYMBOL);
possibleSymbols.remove(nextPossibleSymbolIndex);
}
return false;
}
/** /**
* Compte le nombre de solutions possibles au MultiDoku passé en paramètres. * Compte le nombre de solutions possibles au MultiDoku passé en paramètres.
@@ -69,168 +21,27 @@ public class Solver {
* @param doku MultiDoku, MultiDoku dont on veut le nombre de solutions. * @param doku MultiDoku, MultiDoku dont on veut le nombre de solutions.
* @return int, nombre de solutions possibles. * @return int, nombre de solutions possibles.
*/ */
public static int countSolution(MultiDoku doku) { default int countSolution(MultiDoku doku) {
int result = 0; int result = 0;
if (doku.isSolved()) { if (doku.isSolved()) {
return 1; return 1;
} }
Cell cellToFill = doku.getFirstEmptyCell(); Cell cellToFill = doku.getFirstEmptyCell();
assert(cellToFill != null); assert (cellToFill != null);
List<Integer> possibleSymbols = cellToFill.getPossibleSymbols(); List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
for (int symbol : possibleSymbols) { for (int symbol : possibleSymbols) {
doku.getStateManager().pushState(); doku.getStateManager().pushState();
cellToFill.setSymbolIndex(symbol); cellToFill.setSymbolIndex(symbol);
if (Solver.solve(doku)) { if (solve(doku)) {
result++; result++;
} }
doku.getStateManager().popState(); doku.getStateManager().popState();
} }
return result; return result;
} }
/**
* Résout le MultiDoku passé en paramètre, avec backtracking.
*
* @param doku MultiDoku, MultiDoku à résoudre.
* @return boolean, valant true si le MultiDoku est résolu, false sinon.
*/
public static boolean solve(MultiDoku doku) {
if (Thread.interrupted())
throw new CancellationException("User wants to stop the solver");
if (doku.isSolved()) {
return true;
}
Cell cellToFill = doku.getFirstEmptyCell();
if (cellToFill == null) {
return false;
}
List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
if (possibleSymbols.isEmpty()) {
return false;
}
for (int symbol : possibleSymbols) {
cellToFill.setSymbolIndex(symbol);
if (Solver.solve(doku)) {
return true;
} else {
cellToFill.setSymbolIndex(Cell.NOSYMBOL);
}
}
return false;
}
/**
* Résout le MultiDoku passé en paramètre, avec règles de déduction.
*
* @param doku MultiDoku, MultiDoku à résoudre.
* @return boolean, valant true si le MultiDoku est résolu, false sinon.
*/
public static boolean humanSolve(MultiDoku doku) {
if (Thread.interrupted())
throw new CancellationException("User wants to stop the solver");
Sudoku sudoku = doku.getSubGrid(0);
logger.log(Level.FINE,
'\n' + SudokuPrinter.toStringRectangleSudoku(sudoku,
sudoku.getBlockWidth() == 0 ? sudoku.getSize() : sudoku.getBlockWidth(),
sudoku.getBlockWidth() == 0 ? sudoku.getSize() : sudoku.getSize() / sudoku.getBlockWidth()));
if (doku.isSolved()) {
return true;
}
List<Cell> cellsToFill = doku.getEmptyCells();
if (cellsToFill.isEmpty()) {
return false;
}
for (Cell cellToFill : cellsToFill) {
List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
if (possibleSymbols.size() != 1) {
continue;
}
cellToFill.setSymbolIndex(possibleSymbols.getFirst());
return Solver.humanSolve(doku);
}
return doku.isSolved();
}
/**
* Résout le MultiDoku passé en paramètre, avec règles de déduction et backtracking.
*
* @param doku MultiDoku, MultiDoku à résoudre.
* @param rand Random, pour tester aléatoirement les symboles, lors du backtracking.
* @return boolean, valant true si le MultiDoku est résolu, false sinon.
*/
public static boolean mixedSolve(MultiDoku doku, Random rand) {
if (Thread.interrupted()) {
throw new CancellationException("User wants to stop the solver");
}
Sudoku sudoku = doku.getSubGrid(0);
logger.log(Level.FINE,
'\n' + SudokuPrinter.toStringRectangleSudoku(
sudoku,
sudoku.getBlockWidth() == 0 ? sudoku.getSize() : sudoku.getBlockWidth(),
sudoku.getBlockWidth() == 0 ? sudoku.getSize() : sudoku.getSize() / sudoku.getBlockWidth())
);
if (doku.isSolved()) {
return true;
}
List<Cell> cellsToFill = doku.getEmptyCells();
if (cellsToFill.isEmpty()) {
return false;
}
//Règles de déduction
for (Cell cellToFill : cellsToFill) {
List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
if (possibleSymbols.size() != 1) {
continue;
}
cellToFill.setSymbolIndex(possibleSymbols.getFirst());
return Solver.mixedSolve(doku, rand);
}
//Si ça ne marche pas
//On fait du backtracking
Cell cellToFill = doku.getRandomEmptyCell(rand);
List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
while (!possibleSymbols.isEmpty()) {
int nextPossibleSymbolIndex = rand.nextInt(possibleSymbols.size());
int nextSymbol = possibleSymbols.get(nextPossibleSymbolIndex);
cellToFill.setSymbolIndex(nextSymbol);
if (Solver.mixedSolve(doku, rand)) {
return true;
}
cellToFill.setSymbolIndex(Cell.NOSYMBOL);
possibleSymbols.remove(nextPossibleSymbolIndex);
}
return false;
}
} }

View File

@@ -2,7 +2,6 @@ package sudoku.solver;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
import sudoku.structure.Coordinate;
import sudoku.structure.MultiDoku; import sudoku.structure.MultiDoku;
import sudoku.structure.Sudoku; import sudoku.structure.Sudoku;

View File

@@ -9,9 +9,9 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import sudoku.io.SudokuSerializer;
import sudoku.constraint.Constraint; import sudoku.constraint.Constraint;
import sudoku.io.SudokuSerializer;
import sudoku.solver.RandomSolver;
import sudoku.solver.Solver; import sudoku.solver.Solver;
public class SudokuFactory { public class SudokuFactory {
@@ -116,7 +116,7 @@ public class SudokuFactory {
* @return boolean, valant true si un MultiDoku de difficulté donnée peut être créée, false sinon. * @return boolean, valant true si un MultiDoku de difficulté donnée peut être créée, false sinon.
* @throws Exception si la difficulté n'est pas compatible avec la taille du MultiDoku. * @throws Exception si la difficulté n'est pas compatible avec la taille du MultiDoku.
*/ */
public static boolean newDokuFromFilledOne(MultiDoku doku, int nbCellsToEmpty) throws Exception { public static boolean newDokuFromFilledOne(MultiDoku doku, int nbCellsToEmpty, Solver solver) throws Exception {
if (nbCellsToEmpty >= doku.getCells().size()) { if (nbCellsToEmpty >= doku.getCells().size()) {
throw new Exception(); throw new Exception();
@@ -134,9 +134,9 @@ public class SudokuFactory {
int oldSymbol = cellToEmpty.empty(); int oldSymbol = cellToEmpty.empty();
int nbDokuSultions = Solver.countSolution(doku); int nbDokuSultions = solver.countSolution(doku);
if (nbDokuSultions == 1) { if (nbDokuSultions == 1) {
if (newDokuFromFilledOne(doku, --nbCellsToEmpty)) { if (newDokuFromFilledOne(doku, --nbCellsToEmpty, solver)) {
return true; return true;
} }
} }
@@ -145,7 +145,7 @@ public class SudokuFactory {
cellsThatCanBeEmptied.remove(cellToEmpty); cellsThatCanBeEmptied.remove(cellToEmpty);
} }
return newDokuFromFilledOne(doku, --nbCellsToEmpty); return newDokuFromFilledOne(doku, --nbCellsToEmpty, solver);
} }
/** /**
@@ -272,9 +272,10 @@ public class SudokuFactory {
} }
public static void fillDoku(MultiDoku doku, Difficulty difficulty) throws Exception { public static void fillDoku(MultiDoku doku, Difficulty difficulty) throws Exception {
Solver.randomSolve(doku, random); Solver solver = new RandomSolver();
solver.solve(doku);
int nbCellsToEmpty = (int) (difficulty.getFactor() * doku.getNbCells()); int nbCellsToEmpty = (int) (difficulty.getFactor() * doku.getNbCells());
boolean successfull = newDokuFromFilledOne(doku, nbCellsToEmpty); boolean successfull = newDokuFromFilledOne(doku, nbCellsToEmpty, solver);
if (!successfull) { if (!successfull) {
throw new Exception("Canno't create this doku with this difficulty"); throw new Exception("Canno't create this doku with this difficulty");
} }

View File

@@ -10,7 +10,7 @@ import org.json.JSONObject;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import sudoku.io.SudokuSerializer; import sudoku.io.SudokuSerializer;
import sudoku.solver.Solver; import sudoku.solver.RandomSolver;
import sudoku.structure.MultiDoku; import sudoku.structure.MultiDoku;
import sudoku.structure.SudokuFactory; import sudoku.structure.SudokuFactory;
@@ -19,7 +19,7 @@ public class SudokuSerializerTest {
void testSerializeWithSize(int blockWidth, int blockHeight) { void testSerializeWithSize(int blockWidth, int blockHeight) {
var sudoku = SudokuFactory.createBasicEmptyRectangleDoku(blockWidth, blockHeight, var sudoku = SudokuFactory.createBasicEmptyRectangleDoku(blockWidth, blockHeight,
SudokuFactory.DEFAULT_CONSTRAINTS); SudokuFactory.DEFAULT_CONSTRAINTS);
Solver.randomSolve(sudoku, new Random()); new RandomSolver().solve(sudoku);
JSONObject data = SudokuSerializer.serializeSudoku(sudoku); JSONObject data = SudokuSerializer.serializeSudoku(sudoku);
MultiDoku multiDoku = SudokuSerializer.deserializeSudoku(data); MultiDoku multiDoku = SudokuSerializer.deserializeSudoku(data);
assertTrue(data.toString().equals(SudokuSerializer.serializeSudoku(multiDoku).toString())); assertTrue(data.toString().equals(SudokuSerializer.serializeSudoku(multiDoku).toString()));
@@ -28,14 +28,15 @@ public class SudokuSerializerTest {
void testSaveWithSize(int blockWidth, int blockHeight) { void testSaveWithSize(int blockWidth, int blockHeight) {
MultiDoku doku = SudokuFactory.createBasicEmptyRectangleDoku(blockWidth, blockHeight, MultiDoku doku = SudokuFactory.createBasicEmptyRectangleDoku(blockWidth, blockHeight,
SudokuFactory.DEFAULT_CONSTRAINTS); SudokuFactory.DEFAULT_CONSTRAINTS);
Solver.randomSolve(doku, new Random()); new RandomSolver().solve(doku);
String savePath = SudokuSerializer.saveMultiDoku(doku); String savePath = SudokuSerializer.saveMultiDoku(doku);
MultiDoku otherDoku = null; MultiDoku otherDoku = null;
try { try {
otherDoku = SudokuFactory.fromfile(savePath); otherDoku = SudokuFactory.fromfile(savePath);
assert (otherDoku != null); assert (otherDoku != null);
assertEquals(SudokuSerializer.serializeSudoku(doku).toString(), SudokuSerializer.serializeSudoku(otherDoku).toString()); assertEquals(SudokuSerializer.serializeSudoku(doku).toString(),
SudokuSerializer.serializeSudoku(otherDoku).toString());
// clean file after test // clean file after test
File fileToDelete = new File(savePath); File fileToDelete = new File(savePath);
fileToDelete.delete(); fileToDelete.delete();
@@ -47,7 +48,7 @@ public class SudokuSerializerTest {
void testSerializeX(int size) { void testSerializeX(int size) {
var sudoku = SudokuFactory.createBasicXShapedMultidoku(size, SudokuFactory.DEFAULT_CONSTRAINTS); var sudoku = SudokuFactory.createBasicXShapedMultidoku(size, SudokuFactory.DEFAULT_CONSTRAINTS);
Solver.randomSolve(sudoku, new Random()); new RandomSolver().solve(sudoku);
JSONObject data = SudokuSerializer.serializeSudoku(sudoku); JSONObject data = SudokuSerializer.serializeSudoku(sudoku);
MultiDoku multiDoku = SudokuSerializer.deserializeSudoku(data); MultiDoku multiDoku = SudokuSerializer.deserializeSudoku(data);

View File

@@ -1,5 +1,6 @@
package sudoku.solver; package sudoku.solver;
import gui.Symbols;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import sudoku.io.SudokuPrinter; import sudoku.io.SudokuPrinter;
import sudoku.io.SudokuSerializer; import sudoku.io.SudokuSerializer;
@@ -38,7 +39,8 @@ class SolverTest {
assert (sudokuToTest.setImmutableCellsSymbol(immutableCells)); assert (sudokuToTest.setImmutableCellsSymbol(immutableCells));
SudokuPrinter.printRectangleSudoku(dokuToTest.getSubGrid(0), 3, 3); //SudokuPrinter.printRectangleSudoku(dokuToTest.getSubGrid(0), 3, 3);
SudokuPrinter.printMultiDoku(dokuToTest, 3, 3, Symbols.Numbers);
List<Integer> correctCells = List.of(7, 6, 0, 3, 4, 2, 8, 5, 1, List<Integer> correctCells = List.of(7, 6, 0, 3, 4, 2, 8, 5, 1,
2, 3, 8, 1, 5, 6, 7, 0, 4, 2, 3, 8, 1, 5, 6, 7, 0, 4,
@@ -53,14 +55,15 @@ class SolverTest {
sudokuResult.setCellsSymbol(correctCells); sudokuResult.setCellsSymbol(correctCells);
System.out.println("\n****************************Doku Control\n"); System.out.println("\n****************************Doku Control\n");
SudokuPrinter.printRectangleSudoku(sudokuResult, 3, 3); SudokuPrinter.printRectangleSudoku(sudokuResult, 3, 3, Symbols.Russian);
assert (dokuResult.isSolved()); assert (dokuResult.isSolved());
Solver.randomSolve(dokuToTest, rand); new RandomSolver().solve(dokuToTest);
System.out.println("\n****************************\nDoku solved"); System.out.println("\n****************************\nDoku solved");
SudokuPrinter.printRectangleSudoku(dokuToTest.getSubGrid(0), 3, 3); //SudokuPrinter.printRectangleSudoku(dokuToTest.getSubGrid(0), 3, 3);
SudokuPrinter.printMultiDoku(dokuToTest, 3, 3, Symbols.Emojis);
assert (dokuToTest.isSolved()); assert (dokuToTest.isSolved());
@@ -89,14 +92,15 @@ class SolverTest {
5, ns, ns, ns, 3, 1, 0, ns, ns); 5, ns, ns, ns, 3, 1, 0, ns, ns);
sudokuToTest2.setImmutableCellsSymbol(immutableCells2); sudokuToTest2.setImmutableCellsSymbol(immutableCells2);
boolean isSolved = Solver.randomSolve(dokuToTest2, rand); boolean isSolved = new RandomSolver().solve(dokuToTest2);
assert (!isSolved); assert (!isSolved);
MultiDoku dokuToTest3 = SudokuFactory.createBasicEmptySquareDoku(3, SudokuFactory.DEFAULT_CONSTRAINTS); MultiDoku dokuToTest3 = SudokuFactory.createBasicEmptySquareDoku(3, SudokuFactory.DEFAULT_CONSTRAINTS);
Solver.randomSolve(dokuToTest3, rand); new RandomSolver().solve(dokuToTest3);
SudokuPrinter.printRectangleSudoku(dokuToTest3.getSubGrid(0), 3, 3); //SudokuPrinter.printRectangleSudoku(dokuToTest3.getSubGrid(0), 3, 3);
SudokuPrinter.printMultiDoku(dokuToTest3, 3, 3, Symbols.Letters);
} }
} }