diff --git a/DemoPrograms/Demo_Sudoku.py b/DemoPrograms/Demo_Sudoku.py new file mode 100644 index 00000000..eef6c939 --- /dev/null +++ b/DemoPrograms/Demo_Sudoku.py @@ -0,0 +1,148 @@ +import PySimpleGUI as sg, random +import numpy as np +from typing import List, Any, Union, Tuple, Dict + + +""" + Sudoku Puzzle Demo + + How to easily generate a GUI for a Sudoku puzzle. + The Window definition and creation is a single line of code. + + Code to generate a playable puzzle was supplied from: + https://github.com/MorvanZhou/sudoku + + Copyright 2020 PySimpleGUI.com + +""" + + +def generate_sudoku(mask_rate): + """ + Create a Sukoku board + + :param mask_rate: % of squares to hide + :type mask_rate: float + :rtype: List[numpy.ndarry, numpy.ndarry] + """ + while True: + n = 9 + solution = np.zeros((n, n), np.int) + rg = np.arange(1, n + 1) + solution[0, :] = np.random.choice(rg, n, replace=False) + try: + for r in range(1, n): + for c in range(n): + col_rest = np.setdiff1d(rg, solution[:r, c]) + row_rest = np.setdiff1d(rg, solution[r, :c]) + avb1 = np.intersect1d(col_rest, row_rest) + sub_r, sub_c = r//3, c//3 + avb2 = np.setdiff1d(np.arange(0, n+1), solution[sub_r*3:(sub_r+1)*3, sub_c*3:(sub_c+1)*3].ravel()) + avb = np.intersect1d(avb1, avb2) + solution[r, c] = np.random.choice(avb, size=1) + break + except ValueError: + pass + puzzle = solution.copy() + puzzle[np.random.choice([True, False], size=solution.shape, p=[mask_rate, 1 - mask_rate])] = 0 + return puzzle, solution + + + +def check_progress(window, solution): + """ + Gives you a visual hint on your progress. + Red - You've got an incorrect number at the location + Yellow - You're missing an anwer for that location + + :param window: The GUI's Window + :type window: sg.Window + :param solution: A 2D array containing the solution + :type solution: numpy.ndarray + :return: True if the puzzle has been solved correctly + :rtype: bool + """ + solved = True + for r, row in enumerate(solution): + for c, col in enumerate(row): + value = window[r,c].get() + if value: + try: + value = int(value) + except: + value = 0 + if value != solution[r][c]: + window[r,c].update(background_color='red') + solved = False + else: + window[r,c].update(background_color=sg.theme_input_background_color()) + else: + solved = False + window[r, c].update(background_color='yellow') + return solved + + +def main(mask_rate=0.7): + """" + The Main GUI - It does it all. + + The "Board" is a grid that's 9 x 9. Even though the layout is a grid of 9 Frames, the + addressing of the individual squares is via a key that's a tuple (0,0) to (8,8) + """ + + def create_and_show_puzzle(): + # create and display a puzzle by updating the Input elements + rate = mask_rate + if window['-RATE-'].get(): + try: + rate = float(window['-RATE-'].get()) + except: + pass + puzzle, solution = generate_sudoku(mask_rate=rate) + for r, row in enumerate(puzzle): + for c, col in enumerate(row): + window[r, c].update(puzzle[r][c] if puzzle[r][c] else '', background_color=sg.theme_input_background_color()) + return puzzle, solution + + + # It's 1 line of code to make a Sudoku board. If you don't like it, then replace it. + # Dude (Dudette), it's 1-line of code. If you don't like the board, write a line of code. + # The keys for the inputs are tuples (0-8, 0-8) that reference each Input Element. + # Get an input element for a position using: window[row, col] + # To get a better understanding, take it apart. Spread it out. You'll learn in the process. + window = sg.Window('Sudoku', + [[sg.Frame('', [[sg.I(random.randint(1,9), justification='r', size=(3,1), key=(fr*3+r,fc*3+c)) for c in range(3)] for r in range(3)]) for fc in range(3)] for fr in range(3)] + + [[sg.B('Solve'), sg.B('Check'), sg.B('Hint'), sg.B('New Game'), sg.T('Mask rate (0-1)'), sg.In(str(mask_rate), size=(3,1),key='-RATE-')],], finalize=True) + + # create and display a puzzle by updating the Input elements + + puzzle, solution = create_and_show_puzzle() + + while True: # The Event Loop + event, values = window.read() + if event is None: + break + + if event == 'Solve': + for r, row in enumerate(solution): + for c, col in enumerate(row): + window[r, c].update(solution[r][c], background_color=sg.theme_input_background_color()) + elif event == 'Check': + solved = check_progress(window, solution) + if solved: + sg.popup('Solved! You have solved the puzzle correctly.') + elif event == 'Hint': + elem = window.find_element_with_focus() + try: + elem.update(solution[elem.Key[0]][elem.Key[1]], background_color=sg.theme_input_background_color()) + except: + pass # Likely because an input element didn't have focus + elif event == 'New Game': + puzzle, solution = create_and_show_puzzle() + + window.close() + +if __name__ == "__main__": + mask_rate = 0.7 # % Of cells to hide + main(mask_rate) +