else
More sudoku
This commit is contained in:
parent
eef44ae127
commit
46b49f7113
3 changed files with 342 additions and 6 deletions
4
Sudoku/README.md
Normal file
4
Sudoku/README.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
Sudoku
|
||||||
|
=======
|
||||||
|
|
||||||
|
sudoku_generator.py comes from [David Bau of davidbau.com](http://davidbau.com/archives/2006/09/04/sudoku_generator.html), I only wrote this game ui.
|
|
@ -1,4 +1,6 @@
|
||||||
import tkinter
|
import tkinter
|
||||||
|
import random
|
||||||
|
import sudoku_generator
|
||||||
|
|
||||||
class Sudoku:
|
class Sudoku:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -6,10 +8,12 @@ class Sudoku:
|
||||||
self.t.title("Sudoku")
|
self.t.title("Sudoku")
|
||||||
self.t.resizable(0,0)
|
self.t.resizable(0,0)
|
||||||
|
|
||||||
self.color_enterbox = "#cfc"
|
self.color_enterbox = "#555"
|
||||||
self.color_entertext = "#111"
|
self.color_entertext = "#fff"
|
||||||
self.color_background = "#222"
|
self.color_background = "#222"
|
||||||
self.color_helptext = "#ccc"
|
self.color_helptext = "#ccc"
|
||||||
|
self.color_incorrecttext = "#f00"
|
||||||
|
self.color_giventext = "#7f7"
|
||||||
self.checkerboard_step = 2
|
self.checkerboard_step = 2
|
||||||
self.color_checkerboard = self.checkerboard(self.color_enterbox)
|
self.color_checkerboard = self.checkerboard(self.color_enterbox)
|
||||||
|
|
||||||
|
@ -32,8 +36,8 @@ class Sudoku:
|
||||||
self.t.configure(width=self.window_square, height=self.window_square+self.misc_height)
|
self.t.configure(width=self.window_square, height=self.window_square+self.misc_height)
|
||||||
self.t.configure(bg=self.color_background)
|
self.t.configure(bg=self.color_background)
|
||||||
self.entities_entry = []
|
self.entities_entry = []
|
||||||
|
self.entities_stringvar = []
|
||||||
|
|
||||||
self.permanents = []
|
|
||||||
self.create_boxes()
|
self.create_boxes()
|
||||||
|
|
||||||
self.t.bind("<KeyPress>", self.keypress)
|
self.t.bind("<KeyPress>", self.keypress)
|
||||||
|
@ -41,12 +45,28 @@ class Sudoku:
|
||||||
"w":[0, -1],
|
"w":[0, -1],
|
||||||
"s":[0, 1],
|
"s":[0, 1],
|
||||||
"a":[-1, 0],
|
"a":[-1, 0],
|
||||||
"d":[1, 0]
|
"d":[1, 0]}
|
||||||
}
|
|
||||||
self.key_clearcurrent = ["e"]
|
self.key_clearcurrent = ["e"]
|
||||||
|
self.key_grade = ["\r"]
|
||||||
|
|
||||||
|
print('Creating puzzle')
|
||||||
|
self.entries_solution = sudoku_generator.cgimain()[0]
|
||||||
|
for pos in range(len(self.entries_solution)):
|
||||||
|
self.entries_solution[pos] += 1
|
||||||
|
self.entries_given = self.entries_solution[:]
|
||||||
|
for pos in range(len(self.entries_given)):
|
||||||
|
if random.randint(0, 100) <= 50:
|
||||||
|
self.entries_given[pos] = None
|
||||||
|
#self.entries_given = self.entries_solution[1]
|
||||||
|
#self.entries_solution = self.entries_solution[0]
|
||||||
|
self.entries_current = []
|
||||||
|
|
||||||
|
self.apply_given()
|
||||||
|
|
||||||
self.cursor_position = [0,0]
|
self.cursor_position = [0,0]
|
||||||
self.select_entry_by_pos(self.cursor_position)
|
self.select_entry_by_pos(self.cursor_position)
|
||||||
|
self.game_win = False
|
||||||
self.t.mainloop()
|
self.t.mainloop()
|
||||||
|
|
||||||
def keypress(self, event):
|
def keypress(self, event):
|
||||||
|
@ -60,6 +80,8 @@ class Sudoku:
|
||||||
y = self.cursor_position[1]
|
y = self.cursor_position[1]
|
||||||
index = (9 * y) + x
|
index = (9 * y) + x
|
||||||
self.entities_entry[index].delete(0, "end")
|
self.entities_entry[index].delete(0, "end")
|
||||||
|
elif event.char in self.key_grade:
|
||||||
|
self.grade()
|
||||||
|
|
||||||
def create_helptext(self, helptext):
|
def create_helptext(self, helptext):
|
||||||
helplabel = tkinter.Label(self.t, text=helptext)
|
helplabel = tkinter.Label(self.t, text=helptext)
|
||||||
|
@ -95,12 +117,15 @@ class Sudoku:
|
||||||
enter.stringvar = stringvar
|
enter.stringvar = stringvar
|
||||||
enter.stringvar.trace("w", lambda name,index,mode, stringvar=stringvar: self.checkinput(stringvar))
|
enter.stringvar.trace("w", lambda name,index,mode, stringvar=stringvar: self.checkinput(stringvar))
|
||||||
|
|
||||||
|
enter.is_permanent = False
|
||||||
|
enter.stringvar.is_permanent = False
|
||||||
bg = self.color_enterbox
|
bg = self.color_enterbox
|
||||||
relief = self.relief_enterbox
|
relief = self.relief_enterbox
|
||||||
if self.docheckerboard:
|
if self.docheckerboard:
|
||||||
docheckerboard = (str(x+y)[-1] in "13579")
|
docheckerboard = (str(x+y)[-1] in "13579")
|
||||||
if docheckerboard:
|
if docheckerboard:
|
||||||
bg = self.color_checkerboard
|
bg = self.color_checkerboard
|
||||||
|
|
||||||
enter.configure(justify="c",
|
enter.configure(justify="c",
|
||||||
textvariable=enter.stringvar,
|
textvariable=enter.stringvar,
|
||||||
font=self.font_enterbox,
|
font=self.font_enterbox,
|
||||||
|
@ -111,6 +136,7 @@ class Sudoku:
|
||||||
enter.coordinates = [x, y]
|
enter.coordinates = [x, y]
|
||||||
|
|
||||||
self.entities_entry.append(enter)
|
self.entities_entry.append(enter)
|
||||||
|
self.entities_stringvar.append(enter.stringvar)
|
||||||
enter.place(x=xpos, y=ypos, width=self.entry_square, height=self.entry_square)
|
enter.place(x=xpos, y=ypos, width=self.entry_square, height=self.entry_square)
|
||||||
|
|
||||||
def checkerboard(self, hexivalue):
|
def checkerboard(self, hexivalue):
|
||||||
|
@ -141,6 +167,10 @@ class Sudoku:
|
||||||
|
|
||||||
def checkinput(self, *bullish):
|
def checkinput(self, *bullish):
|
||||||
stringvar = bullish[0]
|
stringvar = bullish[0]
|
||||||
|
if stringvar.is_permanent:
|
||||||
|
index = self.entities_stringvar.index(stringvar)
|
||||||
|
stringvar.set(self.entries_solution[index])
|
||||||
|
self.entities_entry[index].configure(fg=self.color_giventext)
|
||||||
stringvalue = stringvar.get()
|
stringvalue = stringvar.get()
|
||||||
try:
|
try:
|
||||||
test_for_integer= int(stringvalue)
|
test_for_integer= int(stringvalue)
|
||||||
|
@ -189,7 +219,58 @@ class Sudoku:
|
||||||
|
|
||||||
self.cursor_position = [xposition, yposition]
|
self.cursor_position = [xposition, yposition]
|
||||||
self.select_entry_by_pos(self.cursor_position)
|
self.select_entry_by_pos(self.cursor_position)
|
||||||
print(self.cursor_position)
|
#print(self.cursor_position)
|
||||||
|
|
||||||
|
def generate_puzzle(self):
|
||||||
|
return [random.randint(1, 8) for x in range(81)]
|
||||||
|
|
||||||
|
def reset_colors(self):
|
||||||
|
for enterbox in self.entities_entry:
|
||||||
|
if not enterbox.is_permanent:
|
||||||
|
enterbox.configure(fg=self.color_entertext)
|
||||||
|
|
||||||
|
def apply_given(self):
|
||||||
|
for givenpos in range(len(self.entries_given)):
|
||||||
|
given = self.entries_given[givenpos]
|
||||||
|
if given:
|
||||||
|
self.entities_entry[givenpos].is_permanent = True
|
||||||
|
self.entities_stringvar[givenpos].is_permanent = True
|
||||||
|
self.checkinput(self.entities_stringvar[givenpos])
|
||||||
|
self.entities_entry[givenpos].configure(fg=self.color_giventext)
|
||||||
|
|
||||||
|
def grade(self):
|
||||||
|
self.entries_current = []
|
||||||
|
self.reset_colors()
|
||||||
|
|
||||||
|
self.game_win = True
|
||||||
|
self.has_errors = False
|
||||||
|
|
||||||
|
for enterbox_index in range(len(self.entities_entry)):
|
||||||
|
enterbox = self.entities_entry[enterbox_index]
|
||||||
|
cell = enterbox.get()
|
||||||
|
try:
|
||||||
|
cell = int(cell)
|
||||||
|
if cell != self.entries_solution[enterbox_index]:
|
||||||
|
self.game_win = False
|
||||||
|
self.has_errors = True
|
||||||
|
enterbox.configure(fg=self.color_incorrecttext)
|
||||||
|
except:
|
||||||
|
self.game_win = False
|
||||||
|
pass
|
||||||
|
if cell == '':
|
||||||
|
cell = 0
|
||||||
|
self.entries_current.append(cell)
|
||||||
|
|
||||||
|
if self.game_win:
|
||||||
|
print("WOOOOO")
|
||||||
|
|
||||||
|
elif not self.has_errors:
|
||||||
|
print('Doing well')
|
||||||
|
else:
|
||||||
|
print('Some mistakes')
|
||||||
|
|
||||||
|
print(self.entries_solution)
|
||||||
|
print(self.entries_current)
|
||||||
|
|
||||||
|
|
||||||
soduku = Sudoku()
|
soduku = Sudoku()
|
||||||
|
|
251
Sudoku/sudoku_generator.py
Normal file
251
Sudoku/sudoku_generator.py
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Sudoku Generator and Solver in 250 lines of python
|
||||||
|
# Copyright (c) 2006 David Bau. All rights reserved.
|
||||||
|
#
|
||||||
|
# Can be used as either a command-line tool or as a cgi script.
|
||||||
|
#
|
||||||
|
# As a cgi-script, generates puzzles and estimates their level of
|
||||||
|
# difficulty. Uses files sudoku-template.pdf/.ps/.txt/.html
|
||||||
|
# in which it can fill in 81 underscores with digits for a puzzle.
|
||||||
|
# The suffix of the request URL determines which template is used.
|
||||||
|
#
|
||||||
|
# On a command line without any arguments, prints text for a
|
||||||
|
# random sudoku puzzle, with an estimate of its difficulty.
|
||||||
|
# On a command line with a filename, solves the given puzzles
|
||||||
|
# (files should look like the text generated by the generator).
|
||||||
|
|
||||||
|
import sys, os, random, getopt, re
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = sys.argv[1:]
|
||||||
|
if len(args) > 0:
|
||||||
|
puzzles = [loadboard(filename) for filename in args]
|
||||||
|
else:
|
||||||
|
puzzles = [makepuzzle(solution([None] * 81))]
|
||||||
|
for puzzle in puzzles:
|
||||||
|
print("PUZZLE:")
|
||||||
|
print(printboard(puzzle))
|
||||||
|
print("RATING:", ratepuzzle(puzzle, 4))
|
||||||
|
if len(args) > 0:
|
||||||
|
print()
|
||||||
|
print("SOLUTION:")
|
||||||
|
answer = solution(puzzle)
|
||||||
|
if answer is None: print("NO SOLUTION")
|
||||||
|
else: print(printboard(answer))
|
||||||
|
|
||||||
|
def cgimain(ext='txt'):
|
||||||
|
query = os.getenv('QUERY_STRING', '')
|
||||||
|
pathinfo = os.getenv('REQUEST_URI', '').split('?', 1)[0] or ''
|
||||||
|
template = ((("_"*9)+'\n')*9)
|
||||||
|
texttype = {'pdf':'application/pdf', 'ps':'application/postscript',
|
||||||
|
'txt':'text/plain', 'html':'text/html'}[ext]
|
||||||
|
#print("Content-Type: %s" % texttype)
|
||||||
|
#print("Content-Length: %d" % len(template))
|
||||||
|
#print("Cache-Control: no-cache")
|
||||||
|
#print("Expires: Sat, 01 Jan 2000 00:00:00 GMT")
|
||||||
|
#print()
|
||||||
|
segments = template.split('_', 81)
|
||||||
|
sys.stdout.write(segments[0])
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
answers = solution([None] * 81)
|
||||||
|
puzzle = makepuzzle(answers)
|
||||||
|
|
||||||
|
return [answers, puzzle]
|
||||||
|
|
||||||
|
#for pos in range(81):
|
||||||
|
# if puzzle[pos] is None: sys.stdout.write(' ')
|
||||||
|
# else: sys.stdout.write(str(puzzle[pos] + 1))
|
||||||
|
# sys.stdout.write(segments[pos + 1].replace('####', ratingstr))
|
||||||
|
|
||||||
|
def makepuzzle(board):
|
||||||
|
puzzle = []; deduced = [None] * 81
|
||||||
|
order = random.sample(range(81), 81)
|
||||||
|
for pos in order:
|
||||||
|
if deduced[pos] is None:
|
||||||
|
puzzle.append((pos, board[pos]))
|
||||||
|
deduced[pos] = board[pos]
|
||||||
|
deduce(deduced)
|
||||||
|
random.shuffle(puzzle)
|
||||||
|
for i in range(len(puzzle) - 1, -1, -1):
|
||||||
|
e = puzzle[i]; del puzzle[i]
|
||||||
|
rating = checkpuzzle(boardforentries(puzzle), board)
|
||||||
|
if rating == -1: puzzle.append(e)
|
||||||
|
return boardforentries(puzzle)
|
||||||
|
|
||||||
|
def ratepuzzle(puzzle, samples):
|
||||||
|
total = 0
|
||||||
|
for i in range(samples):
|
||||||
|
state, answer = solveboard(puzzle)
|
||||||
|
if answer is None: return -1
|
||||||
|
total += len(state)
|
||||||
|
return float(total) / samples
|
||||||
|
|
||||||
|
def checkpuzzle(puzzle, board = None):
|
||||||
|
state, answer = solveboard(puzzle)
|
||||||
|
if answer is None: return -1
|
||||||
|
if board is not None and not boardmatches(board, answer): return -1
|
||||||
|
difficulty = len(state)
|
||||||
|
state, second = solvenext(state)
|
||||||
|
if second is not None: return -1
|
||||||
|
return difficulty
|
||||||
|
|
||||||
|
def solution(board):
|
||||||
|
return solveboard(board)[1]
|
||||||
|
|
||||||
|
def solveboard(original):
|
||||||
|
board = list(original)
|
||||||
|
guesses = deduce(board)
|
||||||
|
if guesses is None: return ([], board)
|
||||||
|
track = [(guesses, 0, board)]
|
||||||
|
return solvenext(track)
|
||||||
|
|
||||||
|
def solvenext(remembered):
|
||||||
|
while len(remembered) > 0:
|
||||||
|
guesses, c, board = remembered.pop()
|
||||||
|
if c >= len(guesses): continue
|
||||||
|
remembered.append((guesses, c + 1, board))
|
||||||
|
workspace = list(board)
|
||||||
|
pos, n = guesses[c]
|
||||||
|
workspace[pos] = n
|
||||||
|
guesses = deduce(workspace)
|
||||||
|
if guesses is None: return (remembered, workspace)
|
||||||
|
remembered.append((guesses, 0, workspace))
|
||||||
|
return ([], None)
|
||||||
|
|
||||||
|
def deduce(board):
|
||||||
|
while True:
|
||||||
|
stuck, guess, count = True, None, 0
|
||||||
|
# fill in any spots determined by direct conflicts
|
||||||
|
allowed, needed = figurebits(board)
|
||||||
|
for pos in range(81):
|
||||||
|
if None == board[pos]:
|
||||||
|
numbers = listbits(allowed[pos])
|
||||||
|
if len(numbers) == 0: return []
|
||||||
|
elif len(numbers) == 1: board[pos] = numbers[0]; stuck = False
|
||||||
|
elif stuck:
|
||||||
|
guess, count = pickbetter(guess, count, [(pos, n) for n in numbers])
|
||||||
|
if not stuck: allowed, needed = figurebits(board)
|
||||||
|
# fill in any spots determined by elimination of other locations
|
||||||
|
for axis in range(3):
|
||||||
|
for x in range(9):
|
||||||
|
numbers = listbits(needed[axis * 9 + x])
|
||||||
|
for n in numbers:
|
||||||
|
bit = 1 << n
|
||||||
|
spots = []
|
||||||
|
for y in range(9):
|
||||||
|
pos = posfor(x, y, axis)
|
||||||
|
if allowed[pos] & bit: spots.append(pos)
|
||||||
|
if len(spots) == 0: return []
|
||||||
|
elif len(spots) == 1: board[spots[0]] = n; stuck = False
|
||||||
|
elif stuck:
|
||||||
|
guess, count = pickbetter(guess, count, [(pos, n) for pos in spots])
|
||||||
|
if stuck:
|
||||||
|
if guess is not None: random.shuffle(guess)
|
||||||
|
return guess
|
||||||
|
|
||||||
|
def figurebits(board):
|
||||||
|
allowed, needed = [e is None and 511 or 0 for e in board], []
|
||||||
|
for axis in range(3):
|
||||||
|
for x in range(9):
|
||||||
|
bits = axismissing(board, x, axis)
|
||||||
|
needed.append(bits)
|
||||||
|
for y in range(9):
|
||||||
|
allowed[posfor(x, y, axis)] &= bits
|
||||||
|
return allowed, needed
|
||||||
|
|
||||||
|
def posfor(x, y, axis = 0):
|
||||||
|
if axis == 0: return x * 9 + y
|
||||||
|
elif axis == 1: return y * 9 + x
|
||||||
|
else: return ((0,3,6,27,30,33,54,57,60)[x] + (0,1,2,9,10,11,18,19,20)[y])
|
||||||
|
|
||||||
|
def axisfor(pos, axis):
|
||||||
|
if axis == 0: return pos / 9
|
||||||
|
elif axis == 1: return pos % 9
|
||||||
|
else: return (pos / 27) * 3 + (pos / 3) % 3
|
||||||
|
|
||||||
|
def axismissing(board, x, axis):
|
||||||
|
bits = 0
|
||||||
|
for y in range(9):
|
||||||
|
e = board[posfor(x, y, axis)]
|
||||||
|
if e is not None: bits |= 1 << e
|
||||||
|
return 511 ^ bits
|
||||||
|
|
||||||
|
def listbits(bits):
|
||||||
|
return [y for y in range(9) if 0 != bits & 1 << y]
|
||||||
|
|
||||||
|
def allowed(board, pos):
|
||||||
|
bits = 511
|
||||||
|
for axis in range(3):
|
||||||
|
x = axisfor(pos, axis)
|
||||||
|
bits &= axismissing(board, x, axis)
|
||||||
|
return bits
|
||||||
|
|
||||||
|
def pickbetter(b, c, t):
|
||||||
|
if b is None or len(t) < len(b): return (t, 1)
|
||||||
|
if len(t) > len(b): return (b, c)
|
||||||
|
if random.randint(0, c) == 0: return (t, c + 1)
|
||||||
|
else: return (b, c + 1)
|
||||||
|
|
||||||
|
def entriesforboard(board):
|
||||||
|
return [(pos, board[pos]) for pos in range(81) if board[pos] is not None]
|
||||||
|
|
||||||
|
def boardforentries(entries):
|
||||||
|
board = [None] * 81
|
||||||
|
for pos, n in entries: board[pos] = n
|
||||||
|
return board
|
||||||
|
|
||||||
|
def boardmatches(b1, b2):
|
||||||
|
for i in range(81):
|
||||||
|
if b1[i] != b2[i]: return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def printcode(n):
|
||||||
|
if n is None: return '_'
|
||||||
|
return str(n + 1)
|
||||||
|
|
||||||
|
def printboard(board):
|
||||||
|
out = ""
|
||||||
|
for row in range(9):
|
||||||
|
for col in range(9):
|
||||||
|
out += (""," "," "," "," "," "," "," "," ")[col]
|
||||||
|
out += printcode(board[posfor(row, col)])
|
||||||
|
out += ('\n','\n','\n\n','\n','\n','\n\n','\n','\n','\n')[row]
|
||||||
|
return out
|
||||||
|
|
||||||
|
def parseboard(str):
|
||||||
|
result = []
|
||||||
|
for w in str.split():
|
||||||
|
for x in w:
|
||||||
|
if x in '|-=+': continue
|
||||||
|
if x in '123456789': result.append(int(x) - 1)
|
||||||
|
else: result.append(None)
|
||||||
|
if len(result) == 81: return result
|
||||||
|
|
||||||
|
def loadboard(filename):
|
||||||
|
f = file(filename, 'r')
|
||||||
|
result = parseboard(f.read())
|
||||||
|
f.close()
|
||||||
|
return result
|
||||||
|
|
||||||
|
def basedir():
|
||||||
|
if hasattr(sys.modules[__name__], '__file__'):
|
||||||
|
return os.path.split(__file__)[0]
|
||||||
|
elif __name__ == '__main__':
|
||||||
|
if len(sys.argv) > 0 and sys.argv[0] != '':
|
||||||
|
return os.path.split(sys.argv[0])[0]
|
||||||
|
else:
|
||||||
|
return os.curdir
|
||||||
|
|
||||||
|
def loadsudokutemplate(ext):
|
||||||
|
f = open(os.path.join(basedir(), 'sudoku-template.%s' % ext), 'r')
|
||||||
|
result = f.read()
|
||||||
|
f.close()
|
||||||
|
return result
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if os.getenv('GATEWAY_INTERFACE') is not None:
|
||||||
|
sys.exit(cgimain())
|
||||||
|
sys.exit(main())
|
||||||
|
|
Loading…
Reference in a new issue