import pygame
import sys
import time
from solver import Cell, Sudoku
[Link]()
# Set size of game and other constants
cell_size = 50
minor_grid_size = 1
major_grid_size = 3
buffer = 5
button_height = 50
button_width = 125
button_border = 2
width = cell_size*9 + minor_grid_size*6 + major_grid_size*4 + buffer*2
height = cell_size*9 + minor_grid_size*6 + \
major_grid_size*4 + button_height + buffer*3 + button_border*2
size = width, height
white = 255, 255, 255
black = 0, 0, 0
gray = 200, 200, 200
green = 0, 175, 0
red = 200, 0, 0
inactive_btn = 51, 255, 255
active_btn = 51, 153, 255
screen = [Link].set_mode(size)
[Link].set_caption('Sudoku')
class RectCell([Link]):
'''
A class built upon the pygame Rect class used to represent individual cells in
the game.
This class has a few extra attributes not contained within the base Rect class.
'''
def __init__(self, left, top, row, col):
super().__init__(left, top, cell_size, cell_size)
[Link] = row
[Link] = col
def create_cells():
'''Creates all 81 cells with RectCell class.'''
cells = [[] for _ in range(9)]
# Set attributes for for first RectCell
row = 0
col = 0
left = buffer + major_grid_size
top = buffer + major_grid_size
while row < 9:
while col < 9:
cells[row].append(RectCell(left, top, row, col))
# Update attributes for next RectCell
left += cell_size + minor_grid_size
if col != 0 and (col + 1) % 3 == 0:
left = left + major_grid_size - minor_grid_size
col += 1
# Update attributes for next RectCell
top += cell_size + minor_grid_size
if row != 0 and (row + 1) % 3 == 0:
top = top + major_grid_size - minor_grid_size
left = buffer + major_grid_size
col = 0
row += 1
return cells
def draw_grid():
'''Draws the major and minor grid lines for Sudoku.'''
# Draw minor grid lines
lines_drawn = 0
pos = buffer + major_grid_size + cell_size
while lines_drawn < 6:
[Link](screen, black, (pos, buffer),
(pos, width-buffer-1), minor_grid_size)
[Link](screen, black, (buffer, pos),
(width-buffer-1, pos), minor_grid_size)
# Update number of lines drawn
lines_drawn += 1
# Update pos for next lines
pos += cell_size + minor_grid_size
if lines_drawn % 2 == 0:
pos += cell_size + major_grid_size
# Draw major grid lines
for pos in range(buffer+major_grid_size//2, width, cell_size*3 +
minor_grid_size*2 + major_grid_size):
[Link](screen, black, (pos, buffer),
(pos, width-buffer-1), major_grid_size)
[Link](screen, black, (buffer, pos),
(width-buffer-1, pos), major_grid_size)
def fill_cells(cells, board):
'''Fills in all the numbers for the game.'''
font = [Link](None, 36)
# Fill in all cells with correct value
for row in range(9):
for col in range(9):
if [Link][row][col].value is None:
continue
# Fill in given values
if not [Link][row][col].editable:
[Link] = True
text = [Link](f'{[Link][row][col].value}', 1, black)
# Fill in values entered by user
else:
[Link] = False
if board.check_move([Link][row][col], [Link][row]
[col].value):
text = [Link](
f'{[Link][row][col].value}', 1, green)
else:
text = [Link](
f'{[Link][row][col].value}', 1, red)
# Center text in cell
xpos, ypos = cells[row][col].center
textbox = text.get_rect(center=(xpos, ypos))
[Link](text, textbox)
def draw_button(left, top, width, height, border, color, border_color, text):
'''Creates a button with a border.'''
# Draw the border as outer rect
[Link](
screen,
border_color,
(left, top, width+border*2, height+border*2),
)
# Draw the inner button
button = [Link](
left+border,
top+border,
width,
height
)
[Link](screen, color, button)
# Set the text
font = [Link](None, 26)
text = [Link](text, 1, black)
xpos, ypos = [Link]
textbox = text.get_rect(center=(xpos, ypos))
[Link](text, textbox)
return button
def draw_board(active_cell, cells, game):
'''Draws all elements making up the board.'''
# Draw grid and cells
draw_grid()
if active_cell is not None:
[Link](screen, gray, active_cell)
# Fill in cell values
fill_cells(cells, game)
def visual_solve(game, cells):
'''Solves the game while giving a visual representation of what is being
done.'''
# Get first empty cell
cell = game.get_empty_cell()
# Solve is complete if cell is False
if not cell:
return True
# Check each possible move
for val in range(1, 10):
# Allow game to quit when being solved
for event in [Link]():
if [Link] == [Link]:
[Link]()
# Place value in board
[Link] = val
# Outline cell being changed in red
[Link](white)
draw_board(None, cells, game)
cell_rect = cells[[Link]][[Link]]
[Link](screen, red, cell_rect, 5)
[Link]([cell_rect])
[Link](0.05)
# Check if the value is a valid move
if not game.check_move(cell, val):
[Link] = None
continue
# If all recursive calls return True then board is solved
[Link](white)
[Link](screen, green, cell_rect, 5)
draw_board(None, cells, game)
[Link]([cell_rect])
if visual_solve(game, cells):
return True
# Undo move is solve was unsuccessful
[Link] = None
# No moves were successful
[Link](white)
[Link](screen, white, cell_rect, 5)
draw_board(None, cells, game)
[Link]([cell_rect])
return False
def check_sudoku(sudoku):
'''
Takes a complete instance of Soduku and
returns whether or not the solution is valid.
'''
# Ensure all cells are filled
if sudoku.get_empty_cell():
raise ValueError('Game is not complete')
# Will hold values for each row, column, and box
row_sets = [set() for _ in range(9)]
col_sets = [set() for _ in range(9)]
box_sets = [set() for _ in range(9)]
# Check all rows, columns, and boxes contain no duplicates
for row in range(9):
for col in range(9):
box = (row // 3) * 3 + col // 3
value = [Link][row][col].value
# Check if number already encountered in row, column, or box
if value in row_sets[row] or value in col_sets[col] or value in
box_sets[box]:
return False
# Add value to corresponding set
row_sets[row].add(value)
col_sets[col].add(value)
box_sets[box].add(value)
# All rows, columns, and boxes are valid
return True
def play():
'''Contains all the functionality for playing a game of Sudoku.'''
easy = [
[0, 0, 0, 9, 0, 0, 0, 3, 0],
[3, 0, 6, 0, 2, 0, 0, 4, 0],
[2, 0, 4, 0, 0, 3, 1, 0, 6],
[0, 7, 0, 0, 5, 1, 0, 8, 0],
[0, 3, 1, 0, 6, 0, 0, 5, 7],
[5, 0, 9, 0, 0, 0, 6, 0, 0],
[4, 1, 0, 0, 0, 2, 0, 7, 8],
[7, 6, 3, 0, 0, 5, 4, 0, 0],
[9, 2, 8, 0, 0, 4, 0, 0, 1]
]
game = Sudoku(easy)
cells = create_cells()
active_cell = None
solve_rect = [Link](
buffer,
height-button_height - button_border*2 - buffer,
button_width + button_border*2,
button_height + button_border*2
)
while True:
for event in [Link]():
if [Link] == [Link]:
[Link]()
# Handle mouse click
if [Link] == [Link]:
mouse_pos = [Link].get_pos()
# Reset button is pressed
if reset_btn.collidepoint(mouse_pos):
[Link]()
# Solve button is pressed
if solve_btn.collidepoint(mouse_pos):
[Link](white)
active_cell = None
draw_board(active_cell, cells, game)
reset_btn = draw_button(
width - buffer - button_border*2 - button_width,
height - button_height - button_border*2 - buffer,
button_width,
button_height,
button_border,
inactive_btn,
black,
'Reset'
)
solve_btn = draw_button(
width - buffer*2 - button_border*4 - button_width*2,
height - button_height - button_border*2 - buffer,
button_width,
button_height,
button_border,
inactive_btn,
black,
'Visual Solve'
)
[Link]()
visual_solve(game, cells)
# Test if point in any cell
active_cell = None
for row in cells:
for cell in row:
if [Link](mouse_pos):
active_cell = cell
# Test if active cell is empty
if active_cell and not [Link][active_cell.row]
[active_cell.col].editable:
active_cell = None
# Handle key press
if [Link] == [Link]:
if active_cell is not None:
# Input number based on key press
if [Link] == pygame.K_0 or [Link] == pygame.K_KP0:
[Link][active_cell.row][active_cell.col].value = 0
if [Link] == pygame.K_1 or [Link] == pygame.K_KP1:
[Link][active_cell.row][active_cell.col].value = 1
if [Link] == pygame.K_2 or [Link] == pygame.K_KP2:
[Link][active_cell.row][active_cell.col].value = 2
if [Link] == pygame.K_3 or [Link] == pygame.K_KP3:
[Link][active_cell.row][active_cell.col].value = 3
if [Link] == pygame.K_4 or [Link] == pygame.K_KP4:
[Link][active_cell.row][active_cell.col].value = 4
if [Link] == pygame.K_5 or [Link] == pygame.K_KP5:
[Link][active_cell.row][active_cell.col].value = 5
if [Link] == pygame.K_6 or [Link] == pygame.K_KP6:
[Link][active_cell.row][active_cell.col].value = 6
if [Link] == pygame.K_7 or [Link] == pygame.K_KP7:
[Link][active_cell.row][active_cell.col].value = 7
if [Link] == pygame.K_8 or [Link] == pygame.K_KP8:
[Link][active_cell.row][active_cell.col].value = 8
if [Link] == pygame.K_9 or [Link] == pygame.K_KP9:
[Link][active_cell.row][active_cell.col].value = 9
if [Link] == pygame.K_BACKSPACE or [Link] ==
pygame.K_DELETE:
[Link][active_cell.row][active_cell.col].value = None
[Link](white)
# Draw board
draw_board(active_cell, cells, game)
# Create buttons
reset_btn = draw_button(
width - buffer - button_border*2 - button_width,
height - button_height - button_border*2 - buffer,
button_width,
button_height,
button_border,
inactive_btn,
black,
'Reset'
)
solve_btn = draw_button(
width - buffer*2 - button_border*4 - button_width*2,
height - button_height - button_border*2 - buffer,
button_width,
button_height,
button_border,
inactive_btn,
black,
'Visual Solve'
)
# Check if mouse over either button
if reset_btn.collidepoint([Link].get_pos()):
reset_btn = draw_button(
width - buffer - button_border*2 - button_width,
height - button_height - button_border*2 - buffer,
button_width,
button_height,
button_border,
active_btn,
black,
'Reset'
)
if solve_btn.collidepoint([Link].get_pos()):
solve_btn = draw_button(
width - buffer*2 - button_border*4 - button_width*2,
height - button_height - button_border*2 - buffer,
button_width,
button_height,
button_border,
active_btn,
black,
'Visual Solve'
)
# Check if game is complete
if not game.get_empty_cell():
if check_sudoku(game):
# Set the text
font = [Link](None, 36)
text = [Link]('Solved!', 1, green)
textbox = text.get_rect(center=(solve_rect.center))
[Link](text, textbox)
# Update screen
[Link]()
if __name__ == '__main__':
play()