package gui.menu; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CancellationException; import gui.widget.SudokuRenderer; import imgui.ImGui; import imgui.ImGuiStyle; import sudoku.io.SudokuSerializer; import sudoku.solver.BacktrackingSolver; import sudoku.solver.HintHelper; import sudoku.solver.HumanSolver; import sudoku.solver.MixedSolver; import sudoku.solver.Solver; import sudoku.solver.SolverStep; import sudoku.solver.HintHelper.Hint; import sudoku.structure.MultiDoku; public class SudokuView extends BaseView { private final SudokuRenderer sudokuRenderer; private Thread resolveThread; private final MultiDoku doku; private String lastSavePath = null; private boolean resolved = false; // if the solver can't solve private volatile boolean unresolved = false; public SudokuView(StateMachine stateMachine, MultiDoku doku) { super(stateMachine); this.doku = doku; this.sudokuRenderer = new SudokuRenderer(doku); this.sudokuRenderer.onResolve.connect(this::onResolve); } private void onResolve() { this.resolved = true; } private void stopResolve() { if (resolveThread != null && resolveThread.isAlive()) { resolveThread.interrupt(); resolveThread = null; } } boolean centeredButton(String label) { ImGuiStyle style = ImGui.getStyle(); float size = ImGui.calcTextSizeX(label) + style.getFramePaddingX() * 2.0f; float avail = ImGui.getContentRegionAvailX(); float off = (avail - size) * 0.5f; if (off > 0.0f) ImGui.setCursorPosX(ImGui.getCursorPosX() + off); return ImGui.button(label); } private void renderCancelButton() { boolean wantsToStop = false; if (resolveThread != null && resolveThread.isAlive()) { // ImGui.endDisabled(); if (centeredButton("Annuler")) { // we can't stop the Thread right now wantsToStop = true; } } if (resolveThread != null && !resolveThread.isAlive()) { resolveThread.start(); } if (wantsToStop) stopResolve(); } private void renderHintButton() { if (!this.resolved && centeredButton("Indice")) { Hint hint = HintHelper.getHint(this.doku, new BacktrackingSolver()); assert (hint != null); hint.cell().setSymbolIndex(hint.newValue()); if (this.doku.isSolved()) this.sudokuRenderer.onResolve.emit(); } } private void renderUnsolvableText() { if (this.unresolved) ImGui.text("Impossible de résoudre avec l'algorithme actuel !"); } private void startSolve(Solver solver) { resolveThread = new Thread(() -> { List steps = new ArrayList<>(); try { unresolved = !solver.solve(this.doku, steps); } catch (CancellationException e) { System.out.println("The user is bored !"); } stopResolve(); }); } private void renderResolvedText() { if (this.resolved) ImGui.text("Bravo !"); } 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")) { ImGui.openPopup("solve"); } if (resolveThread != null) ImGui.endDisabled(); renderResolvedText(); renderUnsolvableText(); renderSolvePopup(); } private void renderSaveButton() { if (ImGui.button("Sauvegarder l'état de la grille")) { lastSavePath = SudokuSerializer.saveMultiDoku(doku); ImGui.openPopup("saveDone"); } if (ImGui.beginPopup("saveDone")) { ImGui.text("Sudoku sauvegardé dans "); ImGui.text(lastSavePath); ImGui.endPopup(); } } private void renderClearButton() { if (centeredButton("Effacer")) { this.doku.clearMutableCells(); this.resolved = false; this.unresolved = false; } } @Override public void render() { sudokuRenderer.render(); renderHintButton(); renderClearButton(); renderSolveButton(); renderSaveButton(); renderCancelButton(); renderReturnButton(); } @Override public void closeMenu() { super.closeMenu(); stopResolve(); } }