More sudoku
This commit is contained in:
Voussoir 2015-01-29 02:37:12 -08:00
parent eef44ae127
commit 46b49f7113
3 changed files with 342 additions and 6 deletions

4
Sudoku/README.md Normal file
View 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.

View file

@ -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
View 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())