New Demo - Sudoku
This commit is contained in:
parent
0f92dc6252
commit
3fc153229b
|
@ -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)
|
||||
|
Loading…
Reference in New Issue