diff --git a/Sudoku/README.md b/Sudoku/README.md new file mode 100644 index 0000000..7ba6318 --- /dev/null +++ b/Sudoku/README.md @@ -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. \ No newline at end of file diff --git a/Sudoku/sudoku.py b/Sudoku/sudoku.py index bb0b38b..0876515 100644 --- a/Sudoku/sudoku.py +++ b/Sudoku/sudoku.py @@ -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("", 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() diff --git a/Sudoku/sudoku_generator.py b/Sudoku/sudoku_generator.py new file mode 100644 index 0000000..71e1c07 --- /dev/null +++ b/Sudoku/sudoku_generator.py @@ -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()) +