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 random
|
||||
import sudoku_generator
|
||||
|
||||
class Sudoku:
|
||||
def __init__(self):
|
||||
|
@ -6,10 +8,12 @@ class Sudoku:
|
|||
self.t.title("Sudoku")
|
||||
self.t.resizable(0,0)
|
||||
|
||||
self.color_enterbox = "#cfc"
|
||||
self.color_entertext = "#111"
|
||||
self.color_enterbox = "#555"
|
||||
self.color_entertext = "#fff"
|
||||
self.color_background = "#222"
|
||||
self.color_helptext = "#ccc"
|
||||
self.color_incorrecttext = "#f00"
|
||||
self.color_giventext = "#7f7"
|
||||
self.checkerboard_step = 2
|
||||
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(bg=self.color_background)
|
||||
self.entities_entry = []
|
||||
self.entities_stringvar = []
|
||||
|
||||
self.permanents = []
|
||||
self.create_boxes()
|
||||
|
||||
self.t.bind("<KeyPress>", self.keypress)
|
||||
|
@ -41,12 +45,28 @@ class Sudoku:
|
|||
"w":[0, -1],
|
||||
"s":[0, 1],
|
||||
"a":[-1, 0],
|
||||
"d":[1, 0]
|
||||
}
|
||||
"d":[1, 0]}
|
||||
|
||||
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.select_entry_by_pos(self.cursor_position)
|
||||
self.game_win = False
|
||||
self.t.mainloop()
|
||||
|
||||
def keypress(self, event):
|
||||
|
@ -60,6 +80,8 @@ class Sudoku:
|
|||
y = self.cursor_position[1]
|
||||
index = (9 * y) + x
|
||||
self.entities_entry[index].delete(0, "end")
|
||||
elif event.char in self.key_grade:
|
||||
self.grade()
|
||||
|
||||
def create_helptext(self, helptext):
|
||||
helplabel = tkinter.Label(self.t, text=helptext)
|
||||
|
@ -95,12 +117,15 @@ class Sudoku:
|
|||
enter.stringvar = 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
|
||||
relief = self.relief_enterbox
|
||||
if self.docheckerboard:
|
||||
docheckerboard = (str(x+y)[-1] in "13579")
|
||||
if docheckerboard:
|
||||
bg = self.color_checkerboard
|
||||
|
||||
enter.configure(justify="c",
|
||||
textvariable=enter.stringvar,
|
||||
font=self.font_enterbox,
|
||||
|
@ -111,6 +136,7 @@ class Sudoku:
|
|||
enter.coordinates = [x, y]
|
||||
|
||||
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)
|
||||
|
||||
def checkerboard(self, hexivalue):
|
||||
|
@ -141,6 +167,10 @@ class Sudoku:
|
|||
|
||||
def checkinput(self, *bullish):
|
||||
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()
|
||||
try:
|
||||
test_for_integer= int(stringvalue)
|
||||
|
@ -189,7 +219,58 @@ class Sudoku:
|
|||
|
||||
self.cursor_position = [xposition, yposition]
|
||||
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()
|
||||
|
|
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