generated from asa-holland/python-template
-
Notifications
You must be signed in to change notification settings - Fork 0
/
consoleSudoku.py
353 lines (251 loc) · 11.3 KB
/
consoleSudoku.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# filename: consoleSudokuUI.py
# author: Asa Holland
# Import random to randomly select which puzzle is chosen
import random
# import regex to match validity for user-provided input
import re
# import json to read and handle the Sudoku puzzle library (which is stored in a .json format)
import json
# perform a local import to load the main sudoku verification and solving functions
import main as main_sudoku
def welcome():
"""
Welcomes the user to Console Sudoku.
"""
print("""
Welcome to Console Sudoku!""")
def describe_sudoku():
"""
Outputs the general rules of Sudoku when the User selects the relevant option on the Console Sudoku Main Menu.
"""
print('The goal of Sudoku is to completely fill a 9×9 grid with digits. When filled with valid digits, every column, every row, and every 3×3 subgrid that compose the game board contain each digit from 1 to 9. The Sudoku player is provided a grid which is partially complete (some cells are left blank), and the Sudoku player aims to fill in the grid with valid digits so that the puzzle can be completed. If enough blanks are present when the board is first presented, it is possible for multiple valid solutions to exist for a single board, but a good instance of a Sudoku puzzle has only one single valid solution.')
return True
def generate_solution(board):
"""
Provided a Sudoku board, check to see if there is a possible solution for that board, and if there is, generate and display it for the user.
"""
# Notify the user what is being done
print('\nChecking for a solution...')
# convert the provided board into a duplicate so no changes are made to the original board
board_copy = list(board)
# Check if the board is solvable
if main_sudoku.solve_sudoku(board_to_solve=board_copy):
# If the board is solvable, let the user know that is is solvable and disply the valid solution generated by the solving algorithm
print('\nOne valid solution to this Sudoku puzzle is as follows:\n')
main_sudoku.npdisplay(board_copy)
# If the board is not solvable, let the user know.
else:
print('\nThere is no valid solution for this puzzle.')
def user_solve(puzzle_for_user_to_solve, backup):
"""
Provided two copies of a sudoku board, allow the user to make changes to one.
"""
# duplicate the provided user board to ensure no changes are made to the original
user_board = list(puzzle_for_user_to_solve)
# display the current user Sudoku board
main_sudoku.npdisplay(user_board)
# start a flag indicating the user is currently accessing the puzzle
running = True
while running:
# Before each step taken by the user, display the in-game menu options
print(f"""
You are currently solving this puzzle.
Please enter one of the following options:
1. Place a number in an empty cell of this Sudoku board.
2. Submit this Sudoku board and verify if this puzzle has been solved.
3. Reset this Sudoku board. Warning: All progress will be lost!
4. Close this Sudoku board, and return to the Puzzle Selection Menu for this puzzle.
Or enter 'Q' to quit Console Sudoku.
""")
# prompt the user for their input
user_selection = input('Enter your selection: ')
# handle user requst to quit the game
if user_selection == 'Q':
return False
# in all other cases...
else:
# First, validate the user input
validated_input = get_valid_int(provided_input=user_selection)
# If the user's input was invalid, prompt the user and return to the running loop
if validated_input is None:
print("Error, invalid input. Please enter a number selecting one of the provided options or enter 'Q' to quit Console Sudoku.")
# If the user's input was a valid integer but not a valid option, prompt the user with the mistake.
elif validated_input not in {1, 2, 3, 4}:
print("Error, invalid selection. Please enter one of the provided options or enter 'Q' to quit Console Sudoku.")
# Allow the user to set the number in a cell
elif validated_input == 1:
# prompt user with instruction
user_input = input("Please enter the Row, Column, Value you would like to place: ")
# Check if the user input is valid
is_valid_input = bool(re.match(r"^[0-8], [0-8], [1-9]$", user_input))
if not is_valid_input:
print("""Invalid entry. To enter 2 in Row 3, Column 4, enter '2, 3, 4'.""")
else:
# Parse the user provided input
[user_row, user_column, user_value] = [int(digit) for digit in user_input.split(', ')]
# Check if the user input would change an original cell
if user_board[user_row][user_column] != 0:
print("""Invalid placement. You can't change cells from the original provided puzzle.""")
# If the user input will not change the default puzzle values, place the value and output the change made to the user's board
else:
user_board[user_row][user_column] = user_value
print(f"""Valid placement. You have placed a {user_value} in Row {user_row}, Column {user_column}.""")
# Regardless of whethter the input was valid or invalid, display the updated board
main_sudoku.npdisplay(user_board)
# Allow the user to submit a solution
elif validated_input == 2:
# prompt the user that the validation is occuring
print("You have submitted the current board. Checking if the current board is a valid solution...")
# duplicate the board and run the validation algoroithm on the duplicate to confirm whether it is valid or not
copied_board = list(user_board)
submission_result = main_sudoku.validate_user_submission(user_board=copied_board)
# Output each line of the result of the validation check
for line in submission_result:
print(line)
# Output the board
main_sudoku.npdisplay(user_board)
# Allow the user to reset the board
elif validated_input == 3:
print("You have reset this Sudoku board.")
user_board = list(backup)
main_sudoku.npdisplay(user_board)
# Allow the user to quit, returning them to puzzle selection
elif validated_input == 4:
print("Returning to the Puzzle Selection Menu for this puzzle.")
return True
def load_sudoku_board(board_to_load):
"""
Loads a selected sudoku board into the Puzzle Selection Menu, allowing the user to solve the puzzle or generate a solution.
"""
# duplicate the board so changes are kept locally
copy_of_board_to_load = list(board_to_load)
second_copy = list(board_to_load)
# display the loaded board
main_sudoku.npdisplay(copy_of_board_to_load)
# start tracking running
running = True
while running:
# Output the Puzzle Selection options
print(f"""
This is the Puzzle Selection Menu.
Please enter one of the following options:
1. Attempt to solve this puzzle.
2. Generate and view a valid solution to this puzzle.
3. Return to the Main Menu.
Or enter 'Q' to quit Console Sudoku.
""")
# obtain the user input
user_selection = input('Enter your selection: ')
# allow the user to quit Console Sudoku
if user_selection == 'Q':
return False
# In all other cases
else:
# confirm that the user provided valid input
validated_input = get_valid_int(provided_input=user_selection)
# if the user did not provided valid input, let them know
if validated_input is None:
print("Error, invalid input. Please enter a number selecting one of the provided options or enter 'Q' to quit Console Sudoku.")
# If the user provided valid input but not a valid selection, let them know
elif validated_input not in {1, 2, 3}:
print("Error, invalid selection. Please enter one of the provided options or enter 'Q' to quit Console Sudoku.")
# Allow user to attempt to solve the puzzle
elif validated_input == 1:
user_solve(puzzle_for_user_to_solve=copy_of_board_to_load, backup=second_copy)
# Allow user to view a solution
elif validated_input == 2:
duplicate = list(board_to_load)
generate_solution(board=duplicate)
# Allow user to return to puzzle selection
elif validated_input == 3:
print("Returning to the Main Menu of Console Sudoku.")
return True
else:
running = valid_selections[validated_input](copy_of_board_to_load)
def load_random_sudoku():
"""
Loads a random sudoku puzzle from the puzzle libary (json file)
"""
# open the json file that stores the Sudoku puzzles
with open(f"puzzles.json") as json_file:
data = json.load(json_file)
# select a random puzzle
random_puzzle = random.choice(data)
# Identify the puzzle name and source
name = random_puzzle["puzzle_name"]
source = random_puzzle["source"]
# notify the user that a puzzle has been selected and output the name and source of the Sudoku puzzle
selection_prompt = [
f"You have selected a Sudoku puzzle called '{name}'.",
f"This source of this puzzle is {source}."
]
for line in selection_prompt:
print(line)
# create a local copy of the puzzle that the user can load
copy_of_selection = list(random_puzzle["puzzle"])
# return the loaded sudoku board
return load_sudoku_board(board_to_load=copy_of_selection)
# Modified from source: https://pynative.com/python-check-user-input-is-number-or-string
def get_valid_int(provided_input):
"""
Short function to validate the provided user input
"""
# Attempt to convert the provided input to an integer and return that integer
try:
val = int(provided_input)
return val
# If a value error occurs, the input is either a float or a string
except ValueError:
# attempt to convert the input to a float and return that
try:
val = float(provided_input)
return val
# if that doesn't work, return None (the input is not valid)
except ValueError:
return None
def present_options():
"""
Present user with options to select from on the Console Sudoku Main Menu
"""
# create a dictionary to store the options
valid_selections = {}
# Allow user to read a description about Sudoku
option_1 = 'Read a short description about the rules of Sudoku.'
valid_selections[1] = describe_sudoku
# Allow user to open a random sudoku puzzle from library
option_2 = 'Load a random Sudoku puzzle from the Puzzle Library.'
valid_selections[2] = load_random_sudoku
# start running the Comsole Sudoku Main Menu
running = True
while running:
# Present the user with their options
print(f"""
This is the Console Sudoku Main Menu.
Please enter one of the following options:
1. {option_1}
2. {option_2}
Or enter 'Q' to quit Console Sudoku.
""")
# allow the user to provide input
user_selection = input('Enter your selection: ')
# Allow the user to exit console sudoku
if user_selection == 'Q':
running = False
# in all other cases
else:
# validate the user input
validated_input = get_valid_int(provided_input=user_selection)
# if the user input is not valid, prompt the user accordingly
if validated_input is None:
print("Error, invalid input. Please enter a number selecting one of the provided options or enter 'Q' to quit Console Sudoku.")
# if the input is valid but not a legal option, let the user know.
elif validated_input not in valid_selections:
print("Error, invalid selection. Please enter one of the provided options or enter 'Q' to quit Console Sudoku.")
# otherwise, run the selected option
else:
running = valid_selections[validated_input]()
print('Thank you for using Console Sudoku.')
# when run as a script, run the console sudoku interface
if __name__ == '__main__':
welcome()
present_options()