Add files via upload
This commit is contained in:
commit
499517a2bf
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 Rokas Puzonas
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
48
README.md
Normal file
48
README.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
Sudoku solver
|
||||||
|
=============
|
||||||
|
|
||||||
|
This is a python application made using [pyglet](https://pypi.org/project/pyglet/) for solving sudoku boards using the [backtracking algorithm](https://en.wikipedia.org/wiki/Backtracking).
|
||||||
|
|
||||||
|
Installation
|
||||||
|
============
|
||||||
|
|
||||||
|
First of all download the repository and place it anywhere on your machine. As I metioned before the application uses Python so it will need to be installed from [here](https://www.python.org/). It also requires that you have the [pyglet](https://pypi.org/project/pyglet/) package installed on your machine, you can do so with [pip](https://pypi.org/) using the command:
|
||||||
|
```bash
|
||||||
|
pip install pyglet
|
||||||
|
```
|
||||||
|
Or if you already downloaded the repository, then you can run this command while you are in the root directory.
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage
|
||||||
|
============
|
||||||
|
|
||||||
|
The application can by launched with the following command:
|
||||||
|
```bash
|
||||||
|
python main.py
|
||||||
|
```
|
||||||
|
You can change the starting board by editing `board.txt` in the root directory. Each line must have 9 whitespace seperated values ranging from 0-9, 0 meaning that the cell is empty. There must be 9 lines for in total of 81 values. If the number of values wasen't 81 the board will be blank. Here is an example of how it should look like:
|
||||||
|
```
|
||||||
|
0 0 5 0 0 1 2 7 4
|
||||||
|
2 0 0 0 0 5 0 0 0
|
||||||
|
4 0 0 0 0 9 0 6 1
|
||||||
|
0 0 2 1 0 0 0 0 0
|
||||||
|
0 7 0 0 0 0 0 8 0
|
||||||
|
0 0 0 0 0 2 3 0 0
|
||||||
|
7 2 0 9 0 0 0 0 8
|
||||||
|
0 0 0 3 0 0 0 0 6
|
||||||
|
6 8 9 4 0 0 1 0 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Screenshots
|
||||||
|
============
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
License
|
||||||
|
============
|
||||||
|
[MIT](LICENSE)
|
9
board.txt
Normal file
9
board.txt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
0 0 5 0 0 1 2 7 4
|
||||||
|
2 0 0 0 0 5 0 0 0
|
||||||
|
4 0 0 0 0 9 0 6 1
|
||||||
|
0 0 2 1 0 0 0 0 0
|
||||||
|
0 7 0 0 0 0 0 8 0
|
||||||
|
0 0 0 0 0 2 3 0 0
|
||||||
|
7 2 0 9 0 0 0 0 8
|
||||||
|
0 0 0 3 0 0 0 0 6
|
||||||
|
6 8 9 4 0 0 1 0 0
|
546
main.py
Normal file
546
main.py
Normal file
@ -0,0 +1,546 @@
|
|||||||
|
# Was made using Python 3.6.2
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
try:
|
||||||
|
import pyglet
|
||||||
|
from pyglet.gl import *
|
||||||
|
from pyglet.window import key
|
||||||
|
from pyglet.window import mouse
|
||||||
|
except ImportError as e:
|
||||||
|
print("ERROR: Make sure the packages in requirements.txt are installed.")
|
||||||
|
print("You can do so with \"pip install -r requirements.txt\"")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
MOVE = {
|
||||||
|
(0, -1): [(key.UP , 0), (key.W, 0), (key.NUM_8, 0)],
|
||||||
|
(0, 1): [(key.DOWN , 0), (key.S, 0), (key.NUM_2, 0)],
|
||||||
|
(-1, 0): [(key.LEFT , 0), (key.A, 0), (key.NUM_4, 0)],
|
||||||
|
(1 , 0): [(key.RIGHT, 0), (key.D, 0), (key.NUM_6, 0)],
|
||||||
|
}
|
||||||
|
|
||||||
|
class Graphics:
|
||||||
|
line_width = 1
|
||||||
|
|
||||||
|
def vertex2(x, y):
|
||||||
|
glVertex2i(int(x), int(y))
|
||||||
|
|
||||||
|
def rectangle(rect_type, x, y, w, h):
|
||||||
|
if w < 0:
|
||||||
|
x+=w
|
||||||
|
w*=-1
|
||||||
|
if h < 0:
|
||||||
|
y+=h
|
||||||
|
h*=-1
|
||||||
|
if rect_type == "fill":
|
||||||
|
glBegin(GL_QUADS)
|
||||||
|
Graphics.vertex2(x, y)
|
||||||
|
Graphics.vertex2(x, y+h)
|
||||||
|
Graphics.vertex2(x+w, y+h)
|
||||||
|
Graphics.vertex2(x+w, y)
|
||||||
|
glEnd()
|
||||||
|
elif rect_type == "line":
|
||||||
|
# Could be optimized by having intermediary variables, for common calculations
|
||||||
|
glBegin(GL_POLYGON)
|
||||||
|
Graphics.vertex2(x, y)
|
||||||
|
Graphics.vertex2(x, y+h)
|
||||||
|
Graphics.vertex2(x+Graphics.line_width, y+h-Graphics.line_width)
|
||||||
|
Graphics.vertex2(x+Graphics.line_width, y+Graphics.line_width)
|
||||||
|
Graphics.vertex2(x+w-Graphics.line_width, y+Graphics.line_width)
|
||||||
|
Graphics.vertex2(x+w, y)
|
||||||
|
glEnd()
|
||||||
|
glBegin(GL_POLYGON)
|
||||||
|
Graphics.vertex2(x+w, y+h)
|
||||||
|
Graphics.vertex2(x+w, y)
|
||||||
|
Graphics.vertex2(x+w-Graphics.line_width, y+Graphics.line_width)
|
||||||
|
Graphics.vertex2(x+w-Graphics.line_width, y+h-Graphics.line_width)
|
||||||
|
Graphics.vertex2(x+Graphics.line_width, y+h-Graphics.line_width)
|
||||||
|
Graphics.vertex2(x, y+h)
|
||||||
|
glEnd()
|
||||||
|
|
||||||
|
def line(x1, y1, x2, y2):
|
||||||
|
glBegin(GL_LINES)
|
||||||
|
glVertex2i(int(x1), int(y1))
|
||||||
|
glVertex2i(int(x2), int(y2))
|
||||||
|
glEnd()
|
||||||
|
|
||||||
|
def setLineWidth(width):
|
||||||
|
glLineWidth(width)
|
||||||
|
Graphics.line_width = width
|
||||||
|
|
||||||
|
def getLineWidth():
|
||||||
|
return Graphics.line_width
|
||||||
|
|
||||||
|
def setColor(color):
|
||||||
|
if len(color) == 4:
|
||||||
|
glEnable( GL_BLEND );
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
glColor4ub(int(color[0]), int(color[1]), int(color[2]), int(color[3]))
|
||||||
|
else:
|
||||||
|
glColor4ub(int(color[0]), int(color[1]), int(color[2]), 255)
|
||||||
|
|
||||||
|
def setClearColor(color):
|
||||||
|
glClearColor(color[0]/255, color[1]/255, color[2]/255 ,1)
|
||||||
|
|
||||||
|
class Button:
|
||||||
|
base_color = (19, 20, 23)
|
||||||
|
hover_color = (29, 30, 23)
|
||||||
|
pressed_color = (9, 10, 13)
|
||||||
|
text_color = (248, 248, 242, 255)
|
||||||
|
font_name = "Arial"
|
||||||
|
|
||||||
|
def __init__(self, x, y, width, height, text=""):
|
||||||
|
self.hover = False
|
||||||
|
self.pressed = False
|
||||||
|
self.onClick = None
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self.label = pyglet.text.Label(
|
||||||
|
text,
|
||||||
|
font_name=Button.font_name, font_size=self.height*0.5,
|
||||||
|
x = self.x + self.width/2,
|
||||||
|
y = self.y - self.height/2,
|
||||||
|
anchor_x='center', anchor_y='center',
|
||||||
|
color=Button.text_color
|
||||||
|
)
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
if self.pressed:
|
||||||
|
Graphics.setColor(self.pressed_color)
|
||||||
|
elif self.hover:
|
||||||
|
Graphics.setColor(self.hover_color)
|
||||||
|
else:
|
||||||
|
Graphics.setColor(self.base_color)
|
||||||
|
Graphics.rectangle("fill", self.x, self.y, self.width, -self.height)
|
||||||
|
self.label.draw()
|
||||||
|
|
||||||
|
def onMouseMotion(self, x, y, dx, dy):
|
||||||
|
self.hover = self.x <= x < self.x+self.width and self.y-self.height<= y < self.y
|
||||||
|
|
||||||
|
def onMousePress(self, x, y, button, modifiers):
|
||||||
|
self.pressed = self.hover and button & mouse.LEFT
|
||||||
|
|
||||||
|
def onMouseRelease(self, x, y, button, modifiers):
|
||||||
|
if self.pressed:
|
||||||
|
if self.onClick:
|
||||||
|
self.onClick()
|
||||||
|
self.pressed = False
|
||||||
|
|
||||||
|
class AboutPopup:
|
||||||
|
shade_color = (19, 20, 23, 128)
|
||||||
|
background_color = (39, 40, 34)
|
||||||
|
border_color = (19, 20, 23)
|
||||||
|
border_width = 8
|
||||||
|
font_name = "Arial"
|
||||||
|
font_color = (248, 248, 242, 255)
|
||||||
|
font_size = 10
|
||||||
|
|
||||||
|
def addLabel(self, text, x, y):
|
||||||
|
self.elements.append(pyglet.text.Label(text, x=x, y=y, font_name=self.font_name,font_size=self.font_size,color=self.font_color))
|
||||||
|
|
||||||
|
def __init__(self, parent_width, parent_height):
|
||||||
|
self.elements = []
|
||||||
|
self.visible = False
|
||||||
|
self.parent_width = parent_width
|
||||||
|
self.parent_height = parent_height
|
||||||
|
self.width = 200
|
||||||
|
self.height = 150
|
||||||
|
self.x = (parent_width - self.width)/2
|
||||||
|
self.y = (parent_height + self.height)/2
|
||||||
|
|
||||||
|
back_button_size = (50, 25)
|
||||||
|
padding = 5
|
||||||
|
self.back_button = Button(
|
||||||
|
self.x+self.width-back_button_size[0]-padding-self.border_width,
|
||||||
|
self.y-self.height+back_button_size[1]+padding+self.border_width,
|
||||||
|
back_button_size[0], back_button_size[1], "Back"
|
||||||
|
)
|
||||||
|
self.back_button.onClick = lambda: self.toggle()
|
||||||
|
|
||||||
|
self.addLabel("Made by Rokas Puzonas ©", self.x+self.border_width+padding, self.y-self.font_size-self.border_width-padding)
|
||||||
|
self.addLabel("MIT License", self.x+self.border_width+padding, self.y-self.border_width-2*(padding+self.font_size))
|
||||||
|
|
||||||
|
def toggle(self):
|
||||||
|
self.visible = not self.visible
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
if not self.visible: return
|
||||||
|
Graphics.setColor(self.shade_color)
|
||||||
|
Graphics.rectangle("fill", 0, 0, self.parent_width, self.parent_height)
|
||||||
|
|
||||||
|
Graphics.setColor(self.background_color)
|
||||||
|
Graphics.rectangle("fill", self.x, self.y, self.width, -self.height)
|
||||||
|
Graphics.setColor(self.border_color)
|
||||||
|
Graphics.setLineWidth(self.border_width)
|
||||||
|
Graphics.rectangle("line", self.x, self.y, self.width, -self.height)
|
||||||
|
|
||||||
|
self.back_button.draw()
|
||||||
|
|
||||||
|
for element in self.elements:
|
||||||
|
element.draw()
|
||||||
|
|
||||||
|
def onKeyPress(self, symbol, modifiers):
|
||||||
|
if not self.visible: return
|
||||||
|
if symbol == key.ESCAPE:
|
||||||
|
self.visible = False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def onMouseMotion(self, x, y, dx, dy):
|
||||||
|
if not self.visible: return
|
||||||
|
self.back_button.onMouseMotion(x, y, dx, dy)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def onMousePress(self, x, y, button, modifiers):
|
||||||
|
if not self.visible: return
|
||||||
|
self.back_button.onMousePress(x, y, button, modifiers)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def onMouseRelease(self, x, y, button, modifiers):
|
||||||
|
if not self.visible: return
|
||||||
|
self.back_button.onMouseRelease(x, y, button, modifiers)
|
||||||
|
return True
|
||||||
|
|
||||||
|
class SudokuSolver:
|
||||||
|
@staticmethod
|
||||||
|
def findEmpty(board):
|
||||||
|
for i in range(81):
|
||||||
|
if board[i] == 0:
|
||||||
|
return i
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def solve(board):
|
||||||
|
if len(board) != 81: return False
|
||||||
|
empty_pos = SudokuSolver.findEmpty(board)
|
||||||
|
if empty_pos == None: return True
|
||||||
|
|
||||||
|
for i in range(1, 10):
|
||||||
|
if SudokuSolver.isValid(board,empty_pos, i):
|
||||||
|
board[empty_pos] = i
|
||||||
|
if SudokuSolver.solve(board): return True
|
||||||
|
board[empty_pos] = 0
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def isValid(board, pos, value=None):
|
||||||
|
# Get value that is already is board
|
||||||
|
if value == None:
|
||||||
|
value = board[pos]
|
||||||
|
|
||||||
|
# Zero is always not valid
|
||||||
|
if value == 0: return False
|
||||||
|
|
||||||
|
# Check row
|
||||||
|
row_start = pos//9*9
|
||||||
|
for i in range(row_start, row_start+9):
|
||||||
|
if i != pos and board[i] == value:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check column
|
||||||
|
for i in range(pos%9, 81, 9):
|
||||||
|
if i != pos and board[i] == value:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check square
|
||||||
|
square_start = pos//3%3*3 + pos//27*27
|
||||||
|
for x in range(3):
|
||||||
|
for y in range(square_start, square_start+27, 9):
|
||||||
|
i = y+x
|
||||||
|
if i != pos and board[i] == value:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def printBoard(board):
|
||||||
|
for i in range(0, 81, 9):
|
||||||
|
if i % 27 == 0 and i != 0:
|
||||||
|
print("- - - - - - - - - - - -")
|
||||||
|
print(f" {board[i]} {board[i+1]} {board[i+2]} | {board[i+3]} {board[i+4]} {board[i+5]} | {board[i+6]} {board[i+7]} {board[i+8]}")
|
||||||
|
|
||||||
|
class SudokuBoard:
|
||||||
|
grid_color = (19, 20, 23)
|
||||||
|
border_color = (9, 10, 13)
|
||||||
|
selected_color = (230, 219, 116)
|
||||||
|
cell_size = 50
|
||||||
|
grid_width = 6
|
||||||
|
font_name = "Arial"
|
||||||
|
default_color = (248, 248, 242, 255)
|
||||||
|
user_color = (117, 113, 94, 255)
|
||||||
|
mistake_color = (249, 38, 114, 255)
|
||||||
|
stepped_solve_speed = 60
|
||||||
|
|
||||||
|
def __init__(self, x=0, y=0, board=None):
|
||||||
|
self.stepped_cells = None
|
||||||
|
self.current_cell = None
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.selected_cell = (-1, -1)
|
||||||
|
self.labels = []
|
||||||
|
self.resetBoard(board)
|
||||||
|
|
||||||
|
def resetBoard(self, board=[]):
|
||||||
|
if len(board) != 81:
|
||||||
|
board = list(0 for _ in range(81))
|
||||||
|
self.start_board = board
|
||||||
|
self.solution_board = self.start_board.copy()
|
||||||
|
SudokuSolver.solve(self.solution_board)
|
||||||
|
self.clear()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
board_size = self.size()
|
||||||
|
|
||||||
|
Graphics.setLineWidth(self.grid_width)
|
||||||
|
Graphics.setColor(self.grid_color)
|
||||||
|
for i in (1, 2, 4, 5, 7, 8):
|
||||||
|
offset = i*self.cell_size+Graphics.line_width*(i+0.5)
|
||||||
|
Graphics.line(self.x+offset , self.y+Graphics.line_width, self.x+offset , self.y+board_size)
|
||||||
|
Graphics.line(self.x+Graphics.line_width, self.y+offset , self.x+board_size, self.y+offset )
|
||||||
|
|
||||||
|
Graphics.setColor(self.border_color)
|
||||||
|
for i in (3, 6):
|
||||||
|
offset = i*self.cell_size+Graphics.line_width*(i+0.5)
|
||||||
|
Graphics.line(self.x+offset , self.y+Graphics.line_width, self.x+offset , self.y+board_size)
|
||||||
|
Graphics.line(self.x+Graphics.line_width, self.y+offset , self.x+board_size, self.y+offset )
|
||||||
|
|
||||||
|
Graphics.setColor(self.border_color)
|
||||||
|
Graphics.rectangle("line", self.x, self.y, board_size, board_size)
|
||||||
|
|
||||||
|
for label in self.labels:
|
||||||
|
if label.text != "0":
|
||||||
|
label.draw()
|
||||||
|
|
||||||
|
if not self.isBusy():
|
||||||
|
Graphics.setColor(self.selected_color)
|
||||||
|
self.highlightCell(self.selected_cell)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def size():
|
||||||
|
return int(SudokuBoard.cell_size*9 + SudokuBoard.grid_width*10)
|
||||||
|
|
||||||
|
def getCellAt(self, x, y):
|
||||||
|
board_size = self.size()
|
||||||
|
left_side = self.x+self.grid_width
|
||||||
|
bottom_side = self.y+self.grid_width
|
||||||
|
|
||||||
|
if not (left_side <= x < self.x + board_size-self.grid_width and bottom_side <= y < self.y + board_size-self.grid_width):
|
||||||
|
return (-1, -1)
|
||||||
|
|
||||||
|
cell_span = (self.cell_size+self.grid_width)
|
||||||
|
return (int((x-left_side+self.grid_width/2)/cell_span), int(8-(y-bottom_side+self.grid_width/2)//cell_span))
|
||||||
|
|
||||||
|
def highlightCell(self, cell):
|
||||||
|
if cell[0] < 0 or cell[1] < 0: return
|
||||||
|
highlight_size = self.cell_size+self.grid_width
|
||||||
|
x = self.x+self.grid_width/2 + cell[0]*highlight_size
|
||||||
|
y = self.y+self.grid_width/2 + (8-cell[1])*highlight_size
|
||||||
|
Graphics.setLineWidth(self.grid_width/2)
|
||||||
|
Graphics.rectangle("line", x, y, highlight_size, highlight_size)
|
||||||
|
|
||||||
|
def onMousePress(self, x, y, button, modifiers):
|
||||||
|
if self.isBusy(): return
|
||||||
|
cell = self.getCellAt(x, y)
|
||||||
|
if cell == self.selected_cell:
|
||||||
|
self.selected_cell = (-1, -1)
|
||||||
|
else:
|
||||||
|
self.selected_cell = cell
|
||||||
|
|
||||||
|
def onKeyPress(self, symbol, modifiers):
|
||||||
|
if self.isBusy(): return
|
||||||
|
if key._0 <= symbol <= key._9:
|
||||||
|
self.setCell(self.selected_cell[0], self.selected_cell[1], symbol-key._0)
|
||||||
|
return
|
||||||
|
elif modifiers & key.MOD_NUMLOCK and (key.NUM_0 <= symbol <= key.NUM_9):
|
||||||
|
self.setCell(self.selected_cell[0], self.selected_cell[1], symbol-key.NUM_0)
|
||||||
|
return
|
||||||
|
elif symbol == key.BACKSPACE:
|
||||||
|
self.setCell(self.selected_cell[0], self.selected_cell[1], 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
for axis in MOVE:
|
||||||
|
keybinds = MOVE[axis]
|
||||||
|
for keybind in keybinds:
|
||||||
|
if symbol == keybind[0] and modifiers & keybind[1] == keybind[1]:
|
||||||
|
x, y = self.selected_cell
|
||||||
|
if self.selected_cell == (-1, -1):
|
||||||
|
x, y = 4, 4
|
||||||
|
x, y = x+axis[0], y+axis[1]
|
||||||
|
if 0 <= x < 9 and 0 <= y < 9:
|
||||||
|
self.selected_cell = (x, y)
|
||||||
|
return
|
||||||
|
|
||||||
|
def updateLabels(self):
|
||||||
|
for i in range(81):
|
||||||
|
self.updateLabel(i)
|
||||||
|
|
||||||
|
def getLabel(self, index):
|
||||||
|
if index >= len(self.labels):
|
||||||
|
step = self.grid_width + self.cell_size
|
||||||
|
self.labels.append(pyglet.text.Label(
|
||||||
|
str(self.start_board[index]),
|
||||||
|
font_name=self.font_name, font_size=self.cell_size*0.6,
|
||||||
|
x = self.x + (index%9+0.5) * step,
|
||||||
|
y = self.y + (8-index//9+0.5) * step,
|
||||||
|
anchor_x='center', anchor_y='center',
|
||||||
|
color=self.default_color
|
||||||
|
))
|
||||||
|
return self.labels[index]
|
||||||
|
|
||||||
|
def updateLabel(self, index):
|
||||||
|
label = self.getLabel(index)
|
||||||
|
|
||||||
|
if self.start_board[index] != 0:
|
||||||
|
label.text = str(self.start_board[index])
|
||||||
|
label.color = SudokuBoard.default_color
|
||||||
|
else:
|
||||||
|
label.text = str(self.user_board[index])
|
||||||
|
label.color = SudokuBoard.user_color
|
||||||
|
label.italic = False
|
||||||
|
|
||||||
|
def setCell(self, x, y, value):
|
||||||
|
if self.isBusy(): return
|
||||||
|
if not (0 <= x < 9 and 0 <= y < 9): return False
|
||||||
|
index = y*9+x
|
||||||
|
if self.start_board[index] != 0: return False
|
||||||
|
self.user_board[index] = value
|
||||||
|
self.updateLabel(index)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def checkMistakes(self):
|
||||||
|
if self.isBusy(): return
|
||||||
|
for i in range(81):
|
||||||
|
if self.user_board[i] == 0 or self.user_board[i] == self.solution_board[i]: continue
|
||||||
|
label = self.getLabel(i)
|
||||||
|
label.color = SudokuBoard.mistake_color
|
||||||
|
label.italic = True
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
if self.isBusy(): return
|
||||||
|
self.user_board = [0 for _ in range(81)]
|
||||||
|
self.updateLabels()
|
||||||
|
|
||||||
|
def solve(self):
|
||||||
|
if self.isBusy(): return
|
||||||
|
self.user_board = self.solution_board.copy()
|
||||||
|
self.updateLabels()
|
||||||
|
|
||||||
|
def steppedSolve(self):
|
||||||
|
if self.isBusy():
|
||||||
|
pyglet.clock.unschedule(self.step)
|
||||||
|
self.stepped_cells = None
|
||||||
|
self.current_cell = None
|
||||||
|
else:
|
||||||
|
pyglet.clock.schedule_interval(self.step, 1/SudokuBoard.stepped_solve_speed)
|
||||||
|
self.stepped_cells = []
|
||||||
|
self.user_board = self.start_board.copy()
|
||||||
|
self.updateLabels()
|
||||||
|
|
||||||
|
def step(self, dt):
|
||||||
|
if self.current_cell == None:
|
||||||
|
self.current_cell = SudokuSolver.findEmpty(self.user_board)
|
||||||
|
|
||||||
|
if self.current_cell == None:
|
||||||
|
self.steppedSolve()
|
||||||
|
return
|
||||||
|
|
||||||
|
while True:
|
||||||
|
self.user_board[self.current_cell] = (self.user_board[self.current_cell]+1) % 10
|
||||||
|
if SudokuSolver.isValid(self.user_board, self.current_cell):
|
||||||
|
self.updateLabel(self.current_cell)
|
||||||
|
self.stepped_cells.append(self.current_cell)
|
||||||
|
self.current_cell = SudokuSolver.findEmpty(self.user_board)
|
||||||
|
break
|
||||||
|
|
||||||
|
if self.user_board[self.current_cell] == 0:
|
||||||
|
self.updateLabel(self.current_cell)
|
||||||
|
self.current_cell = self.stepped_cells[-1]
|
||||||
|
self.stepped_cells.pop()
|
||||||
|
break
|
||||||
|
|
||||||
|
def isBusy(self):
|
||||||
|
return self.stepped_cells != None
|
||||||
|
|
||||||
|
class SudokuApp(pyglet.window.Window):
|
||||||
|
background_color = (39, 40, 34)
|
||||||
|
|
||||||
|
def __init__(self, board=[]):
|
||||||
|
button_height = 16
|
||||||
|
padding = 16
|
||||||
|
|
||||||
|
self.elements = []
|
||||||
|
self.board = SudokuBoard(padding, padding, board)
|
||||||
|
board_size = SudokuBoard.size()
|
||||||
|
super().__init__(board_size+padding*2, board_size+padding*3+button_height, "Sudoku Solver")
|
||||||
|
self.about_popup = AboutPopup(self.width, self.height)
|
||||||
|
buttons = (
|
||||||
|
("Check mistakes", lambda: self.board.checkMistakes() ),
|
||||||
|
("Solve" , lambda: self.board.solve() ),
|
||||||
|
("Stepped solve" , lambda: self.board.steppedSolve() ),
|
||||||
|
("Clear" , lambda: self.board.clear() ),
|
||||||
|
("About" , lambda: self.about_popup.toggle() ),
|
||||||
|
)
|
||||||
|
|
||||||
|
button_width = (SudokuBoard.size()-padding*(len(buttons)-1))/len(buttons)
|
||||||
|
button_y = self.height-padding
|
||||||
|
|
||||||
|
for i in range(len(buttons)):
|
||||||
|
button_x = padding*(i+1)+button_width*i
|
||||||
|
button = self.addButton(button_x, button_y, button_width, button_height, buttons[i][0])
|
||||||
|
button.onClick = buttons[i][1]
|
||||||
|
|
||||||
|
def addButton(self, *args, **kwargs):
|
||||||
|
button = Button(*args, **kwargs)
|
||||||
|
self.elements.append(button)
|
||||||
|
return button
|
||||||
|
|
||||||
|
def on_draw(self):
|
||||||
|
Graphics.setClearColor(self.background_color)
|
||||||
|
self.clear()
|
||||||
|
self.board.draw()
|
||||||
|
self.emitToElements("draw")
|
||||||
|
self.about_popup.draw()
|
||||||
|
|
||||||
|
def on_key_press(self, symbol, modifiers):
|
||||||
|
if self.about_popup.onKeyPress(symbol, modifiers): return
|
||||||
|
self.board.onKeyPress(symbol, modifiers)
|
||||||
|
self.emitToElements("onKeyPress", symbol, modifiers)
|
||||||
|
|
||||||
|
def on_mouse_press(self, x, y, button, modifiers):
|
||||||
|
if self.about_popup.onMousePress(x, y, button, modifiers): return
|
||||||
|
self.board.onMousePress(x, y, button, modifiers)
|
||||||
|
self.emitToElements("onMousePress", x, y, button, modifiers)
|
||||||
|
|
||||||
|
def on_mouse_release(self, x, y, button, modifiers):
|
||||||
|
if self.about_popup.onMouseRelease(x, y, button, modifiers): return
|
||||||
|
self.emitToElements("onMouseRelease", x, y, button, modifiers)
|
||||||
|
|
||||||
|
def on_mouse_motion(self, x, y, dx, dy):
|
||||||
|
if self.about_popup.onMouseMotion(x, y, dx, dy): return
|
||||||
|
self.emitToElements("onMouseMotion", x, y, dx, dy)
|
||||||
|
|
||||||
|
def emitToElements(self, event_name, *args, **kwargs):
|
||||||
|
for element in self.elements:
|
||||||
|
event_method = getattr(element, event_name, None)
|
||||||
|
if callable(event_method):
|
||||||
|
event_method(*args, **kwargs)
|
||||||
|
|
||||||
|
def boardFromFile(filename):
|
||||||
|
if not os.path.isfile(filename): return None
|
||||||
|
board = []
|
||||||
|
file = open(filename, "r")
|
||||||
|
for line in file:
|
||||||
|
for value in line.split():
|
||||||
|
try:
|
||||||
|
board.append(int(value))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
file.close()
|
||||||
|
return board
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
SudokuApp(boardFromFile("board.txt"))
|
||||||
|
pyglet.app.run()
|
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
pyglet==1.3.2
|
Loading…
Reference in New Issue
Block a user