Search code examples
javauser-interfacejavafxbacktracking

Gui to visualize recursive backtracking sudoku


I wrote a class a while ago to solve a sudoku game using recursive backtracking - that's working as expected.

Now I want to visualize this algorithm step by step. I basically want to see where the algorithm put which number and when it is backtracking.

I wrote all the necessary function and implemented a basic gui that already calls and displays my sudokuSolver inside my gui (after you start it and press a random key once you want to begin the solving).

Now my problem is that I want to see each step of the algorithm - see more in the SudokuSolver.solve() function.

At the moment I somehow only update my gui once the whole backtracking is finished, although my SudokuSlver.setGui() function is called during the backtracking.

Which is also the reason why my approach to "slow down" the algorithm with "Thread.sleep()" and "TimeUnit" failed.

Github

Code Gui.java

import java.util.ArrayList;
import java.util.concurrent.*;

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.input.KeyEvent;
import javafx.scene.shape.*;
import javafx.scene.text.Text;
import javafx.scene.text.TextBoundsType;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.scene.paint.*;

public class Gui extends Application{
    

    static ArrayList<Rectangle> rects = new ArrayList<>();
    static ArrayList<Text> texts = new ArrayList<>();

    @Override
    public void start(Stage stage) {

        Pane pane = new Pane();
        Scene scene = new Scene(pane, 297, 297);
        

        createBoard();

        
        for (int box = 0; box < rects.size(); box++) {
            pane.getChildren().addAll(rects.get(box), texts.get(box));
        }


        scene.setOnKeyPressed(new EventHandler<KeyEvent>(){
            
            public void handle(final KeyEvent keyEvent){
                System.out.println("event triggered");
                handleEvent(keyEvent);
            }
            
        });


        stage.setTitle("SudokuSolver");
        stage.setScene(scene);
        stage.show();
        
    }

    public void handleEvent(KeyEvent key){
        System.out.println("event triggered");
        callSudokuSolver();
    }

    public void createBoard(){
        for (int x = 0; x < 288; x+=32) {
            for (int y = 0; y < 288; y+=32) {

                Rectangle r = new Rectangle(x, y, 32, 32);
                Text   text   = createText("0");
                text.setX(x);
                text.setY(y+32);
                r.setFill(Color.WHITE);
                r.setStroke(Color.BLACK);
                r.setOpacity(0.5);
                

                rects.add(r);
                texts.add(text);
            }
        }
    }
    
    public static void setTextOfRect(String s, int pos){
        texts.get(pos).setText(s);
    }

    public static void setColorOfRect(Color col, int pos){
        rects.get(pos).setFill(col);
    }

    private Text createText(String string) {
        Text text = new Text(string);
        text.setBoundsType(TextBoundsType.VISUAL);
        text.setStyle(
                "-fx-font-family: \"Times New Roman\";" +
                "-fx-font-size: 16px;"
        );

        return text;
    }

    private void callSudokuSolver(){
        //test
        int[][] board =
        {{2,0,5,0,0,0,0,0,0},
        {3,0,8,6,0,0,9,0,0},
        {0,0,0,1,0,0,4,0,0},
        {0,0,0,0,5,0,0,1,0},
        {0,0,0,0,9,0,0,2,0},
        {8,7,0,0,2,0,0,0,0},
        {0,0,0,0,8,9,0,0,3},
        {0,0,6,0,0,3,0,0,5},
        {5,0,4,0,0,0,0,0,1}};
        
        
        SudokuSolver sudokuSolver = new SudokuSolver();
        
        if(!sudokuSolver.startSolving(board)){
            System.out.println("No solution");
        }else{
            System.out.println("Solved");
        }
    }


    public static void main(String[] args) throws Exception {
        launch();
    }

    
}

Code SudokuSolver.java

import javafx.scene.paint.*;

public class SudokuSolver {

    private boolean solve(int[][] board, int counter){


        int col = counter / board.length;
        int row = counter % board.length;

        if (col >= board.length){
            return true;
        }

        if (board[row][col] == 0) {

        for (int n = 1; n <= board.length; n++) {

            if (isValid(n,row,col, board)){
                board[row][col] = n;
                setGui(false, counter, n);

                if (solve(board,counter+1)){
                    return true;
                }

            }
            board[row][col] = 0;
            setGui(true, counter, n);

        }
        }else{
            setGui(false, counter, board[row][col]);
            return solve(board, counter + 1);
        }

        return false;
    }

    public boolean startSolving(int[][] board){

        if(!solve(board, 0)){
            return false;
        }else{
            return true;
        }
    }

    private boolean isValid(int n, int row, int col, int[][] board){

        int i;

        for (i = 0; i < board.length; i++) {
            if(board[row][i] == n){
                return false;
            }
        }

        for (i = 0; i < board.length; i++) {
            if(board[i][col] == n){
                return false;
            }
        }

        //check if block is valid

        final int blockRow = 3 * (row / 3);
        final int blockCol = 3 * (col / 3);
        return isBlockValid(n, board, blockRow, blockRow + 2, blockCol, blockCol + 2);
    }

    private boolean isBlockValid(int n, int[][] board, int starti, int stopi, int startj, int stopj){

        for (int i = starti; i <= stopi; i++) {

            for (int j = startj; j <= stopj; j++) {

                if (board[i][j] == n) {
                    return false;
                }
            }
        }

        return true;
    }

    private void printBoard(int[][] board){

        System.out.println();

        for (int[] row : board){
            System.out.print("|");
            for (int col : row){
                System.out.print(col);
                System.out.print("|");
            }
            System.out.println();
        }
        System.out.println();
    }

    private void setGui(boolean wrong, int pos, int number){
        String s = Integer.toString(number);
        Color color = Color.GREEN;
        if(wrong){
            color = Color.RED;
        }
        Gui.setColorOfRect(color, pos);
        Gui.setTextOfRect(s, pos);
    }


}

Solution

  • https://stackoverflow.com/a/9167420/11162097 solved my problem.

    It was necessary to open my sodokusolver in another thread and then run the gui commands with Platform.runLater() (@tevemadar).

    new Thread() {
            public void run() {
                SudokuSolver sudokuSolver = new SudokuSolver();
                sudokuSolver.startSolving(board);
            }
        }.start();
    

    After that I used this to "slow down" my algorithm.

    try {
            Thread.sleep(10);
        } catch (Exception e) {
            //TODO: handle exception
        }