0% found this document useful (0 votes)
17 views

Sudoku

This document contains code for a Sudoku game implemented in Python using the Pygame library. It defines functions for initializing the game, drawing the grid and cells, filling in values, handling user input, and solving puzzles either manually or through a visual step-by-step process. Key aspects include representing each cell as a Rect object, drawing the grid lines, checking that inputs are valid moves, and using recursive calls to solve puzzles one cell at a time.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
17 views

Sudoku

This document contains code for a Sudoku game implemented in Python using the Pygame library. It defines functions for initializing the game, drawing the grid and cells, filling in values, handling user input, and solving puzzles either manually or through a visual step-by-step process. Key aspects include representing each cell as a Rect object, drawing the grid lines, checking that inputs are valid moves, and using recursive calls to solve puzzles one cell at a time.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 8

import pygame

import sys
import time
from solver import Cell, Sudoku

pygame.init()

# 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 = pygame.display.set_mode(size)
pygame.display.set_caption('Sudoku')

class RectCell(pygame.Rect):
'''
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)
self.row = row
self.col = 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:
pygame.draw.line(screen, black, (pos, buffer),
(pos, width-buffer-1), minor_grid_size)
pygame.draw.line(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):
pygame.draw.line(screen, black, (pos, buffer),
(pos, width-buffer-1), major_grid_size)
pygame.draw.line(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 = pygame.font.Font(None, 36)

# Fill in all cells with correct value


for row in range(9):
for col in range(9):
if board.board[row][col].value is None:
continue

# Fill in given values


if not board.board[row][col].editable:
font.bold = True
text = font.render(f'{board.board[row][col].value}', 1, black)
# Fill in values entered by user
else:
font.bold = False
if board.check_move(board.board[row][col], board.board[row]
[col].value):
text = font.render(
f'{board.board[row][col].value}', 1, green)
else:
text = font.render(
f'{board.board[row][col].value}', 1, red)

# Center text in cell


xpos, ypos = cells[row][col].center
textbox = text.get_rect(center=(xpos, ypos))
screen.blit(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
pygame.draw.rect(
screen,
border_color,
(left, top, width+border*2, height+border*2),
)

# Draw the inner button


button = pygame.Rect(
left+border,
top+border,
width,
height
)
pygame.draw.rect(screen, color, button)

# Set the text


font = pygame.font.Font(None, 26)
text = font.render(text, 1, black)
xpos, ypos = button.center
textbox = text.get_rect(center=(xpos, ypos))
screen.blit(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:
pygame.draw.rect(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 pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()

# Place value in board


cell.value = val

# Outline cell being changed in red


screen.fill(white)
draw_board(None, cells, game)
cell_rect = cells[cell.row][cell.col]
pygame.draw.rect(screen, red, cell_rect, 5)
pygame.display.update([cell_rect])
time.sleep(0.05)

# Check if the value is a valid move


if not game.check_move(cell, val):
cell.value = None
continue

# If all recursive calls return True then board is solved


screen.fill(white)
pygame.draw.rect(screen, green, cell_rect, 5)
draw_board(None, cells, game)
pygame.display.update([cell_rect])
if visual_solve(game, cells):
return True

# Undo move is solve was unsuccessful


cell.value = None

# No moves were successful


screen.fill(white)
pygame.draw.rect(screen, white, cell_rect, 5)
draw_board(None, cells, game)
pygame.display.update([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 = sudoku.board[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 = pygame.Rect(
buffer,
height-button_height - button_border*2 - buffer,
button_width + button_border*2,
button_height + button_border*2
)

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()

# Handle mouse click


if event.type == pygame.MOUSEBUTTONUP:
mouse_pos = pygame.mouse.get_pos()

# Reset button is pressed


if reset_btn.collidepoint(mouse_pos):
game.reset()

# Solve button is pressed


if solve_btn.collidepoint(mouse_pos):
screen.fill(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'
)
pygame.display.flip()
visual_solve(game, cells)

# Test if point in any cell


active_cell = None
for row in cells:
for cell in row:
if cell.collidepoint(mouse_pos):
active_cell = cell

# Test if active cell is empty


if active_cell and not game.board[active_cell.row]
[active_cell.col].editable:
active_cell = None

# Handle key press


if event.type == pygame.KEYUP:
if active_cell is not None:

# Input number based on key press


if event.key == pygame.K_0 or event.key == pygame.K_KP0:
game.board[active_cell.row][active_cell.col].value = 0
if event.key == pygame.K_1 or event.key == pygame.K_KP1:
game.board[active_cell.row][active_cell.col].value = 1
if event.key == pygame.K_2 or event.key == pygame.K_KP2:
game.board[active_cell.row][active_cell.col].value = 2
if event.key == pygame.K_3 or event.key == pygame.K_KP3:
game.board[active_cell.row][active_cell.col].value = 3
if event.key == pygame.K_4 or event.key == pygame.K_KP4:
game.board[active_cell.row][active_cell.col].value = 4
if event.key == pygame.K_5 or event.key == pygame.K_KP5:
game.board[active_cell.row][active_cell.col].value = 5
if event.key == pygame.K_6 or event.key == pygame.K_KP6:
game.board[active_cell.row][active_cell.col].value = 6
if event.key == pygame.K_7 or event.key == pygame.K_KP7:
game.board[active_cell.row][active_cell.col].value = 7
if event.key == pygame.K_8 or event.key == pygame.K_KP8:
game.board[active_cell.row][active_cell.col].value = 8
if event.key == pygame.K_9 or event.key == pygame.K_KP9:
game.board[active_cell.row][active_cell.col].value = 9
if event.key == pygame.K_BACKSPACE or event.key ==
pygame.K_DELETE:
game.board[active_cell.row][active_cell.col].value = None

screen.fill(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(pygame.mouse.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(pygame.mouse.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 = pygame.font.Font(None, 36)
text = font.render('Solved!', 1, green)
textbox = text.get_rect(center=(solve_rect.center))
screen.blit(text, textbox)

# Update screen
pygame.display.flip()

if __name__ == '__main__':
play()

You might also like