From: Alex T. <ale...@us...> - 2005-09-29 22:05:30
|
Update of /cvsroot/pythoncard/PythonCard/samples/sudoku In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30655 Added Files: about.txt readme.txt sudoku.py sudoku.rsrc.py Log Message: Add new sample for Suodku Solver. --- NEW FILE: about.txt --- Sudoku Solver 0.1 (c) 2005 Alex Tweedly. --- NEW FILE: sudoku.py --- #!/usr/bin/python """ __version__ = "$Revision: 1.1 $" __date__ = "$Date: 2005/09/29 22:05:23 $" """ import os, sys import wx from wx.html import HtmlEasyPrinting from PythonCard import configuration, dialog, model import copy COLOR = { 'a': (255,200,200), 'f': (255,255,255), 'p': (255,255,255), 'u': (255,255,255), 'z': (255,0,0), 'i': (200,255,200), 'o': (200,200,255), } # copied from helpful.py # This function will, I hope, at some point become part of PythonCard import types VERSION = "0.1" class PopUpMenu: def __init__(self, aBg, items, pos): # Yet another alternate way to do IDs. Some prefer them up top to # avoid clutter, some prefer them close to the object of interest # for clarity. self.popup = {} self.reverse = {} self.selected = None # make a menu self.menu = wx.Menu() # add the items for it in items: if type(it) == types.StringType: Id = wx.NewId() self.popup[it] = Id self.reverse[Id] = it aBg.Bind(wx.EVT_MENU, self.OnPopup, id=self.popup[it]) self.menu.Append(self.popup[it], it) else: # make a menu submenu = wx.Menu() Id = wx.NewId() aBg.Bind(wx.EVT_MENU, self.OnPopup, id=Id) for that in it: if type(that) == types.StringType: Id = wx.NewId() self.popup[that] = Id self.reverse[Id] = that aBg.Bind(wx.EVT_MENU, self.OnPopup, id=self.popup[that]) submenu.Append(self.popup[that], that) self.menu.AppendMenu(Id, "Test Submenu", submenu) # Popup the menu. If an item is selected then its handler # will be called before PopupMenu returns. aBg.PopupMenu(self.menu, pos) self.menu.Destroy() def OnPopup(self, event): self.selected = self.reverse[event.GetId()] def popUpMenu(aBg, items, pos): menu = PopUpMenu(aBg, items, pos) return menu.selected # end of section from helpful.py def textToHtml(txt): # the wxHTML classes don't require valid HTML # so this is enough html = txt.replace('\n\n', '<P>') html = html.replace('\n', '<BR>') return html class square: def __init__(self, x,y, comp): self.x = x self.y = y self.comp = comp self.state = None self.values = [1,2,3,4,5,6,7,8,9] self.color = (255,255,255) class undosquare: def __init__(self, val, state): self.state = state self.values = val class MyBackground(model.Background): def on_initialize(self, event): # if you have any initialization # including sizer setup, do it here self.readme = None self.hideUndecided = False self.hideChoices = False self.squares = {} for i in range(9): for j in range(9): self.squares[i,j] = square(i,j, self.components["B%d%d" % (i,j)]) self.peer = {} for i in range(3): for j in range(3): self.peer[3*i+j] = [] for x in range(3): for y in range(3): self.peer[3*i+j].append( (3*i+x, 3*j+y) ) self.sizerLayout() self.startTitle = self.title self.newFile() self.components.solveIt.SetFocus() def sizerLayout(self): # Need this nonsense because we are using list components in an unusual way # We want them to be entirely visible - and Mac OSX doesn't record an adequate # minimum size (GetBestSize()) for list controls if wx.Platform == '__WXMSW__': minListSize = (45,44) elif wx.Platform <> '__WXMAC__': minListSize = (66,66) else: minListSize = (66,66) for i in range(9): for j in range(9): self.components["B%d%d" % (i,j)].SetMinSize( minListSize ) sizer1 = wx.BoxSizer(wx.VERTICAL) sizer2 = wx.BoxSizer(wx.HORIZONTAL) ## There is no sizer 3 !! sizer4 = wx.GridSizer(9, 9, 2, 2) sizer5 = wx.BoxSizer(wx.HORIZONTAL) stcSizerAttrs = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL fldSizerAttrs = wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL vertFlags = wx.LEFT | wx.TOP | wx.ALIGN_LEFT chkSizerAttrs = wx.RIGHT | wx.ALIGN_CENTER_VERTICAL C = self.components sizer2.Add(C.solveIt, flag=fldSizerAttrs) sizer2.Add((5, 5), 0) sizer2.Add(C.Singles, flag=fldSizerAttrs) sizer2.Add((5, 5), 0) sizer2.Add(C.Soles, flag=fldSizerAttrs) sizer2.Add((45, 5), 0) sizer2.Add(C.Blank, flag=fldSizerAttrs) sizer2.Add((25, 5), 0) sizer2.Add(C.Undo, flag=fldSizerAttrs) sizer2.Add((5, 5), 0) sizer2.Add(C.Redo, flag=fldSizerAttrs) for i in range(9): for j in range(9): sizer4.Add(C["B"+str(i)+str(j)]) sizer5.Add(C.hideUndecided, flag=fldSizerAttrs) sizer5.Add((5, 5), 0) sizer5.Add(C.hideChoices, flag=fldSizerAttrs) sizer1.Add(sizer2, 0, vertFlags) sizer1.Add((5, 15), 0) # spacer sizer1.Add(sizer4, 0, vertFlags) sizer1.Add((5, 15), 0) # spacer sizer1.Add(sizer5, 0, vertFlags) sizer1.Fit(self) sizer1.SetSizeHints(self) self.panel.SetSizer(sizer1) self.panel.SetAutoLayout(1) self.panel.Layout() self.visible = True def reset(self): for i in range(9): for j in range(9): self.squares[i,j].values = [1,2,3,4,5,6,7,8,9] self.squares[i,j].color = (255,255,255) self.squares[i,j].state = 'u' self.undo = [] self.redo = [] def loadConfig(self): pass def saveConfig(self): pass def saveChanges(self): # save configuration info in the app directory #filename = os.path.basename(self.documentPath) if self.documentPath is None: filename = "Untitled" else: filename = self.documentPath msg = "The text in the %s file has changed.\n\nDo you want to save the changes?" % filename result = dialog.messageDialog(self, msg, 'textEditor', wx.ICON_EXCLAMATION | wx.YES_NO | wx.CANCEL) return result.returnedString def doExit(self): return 1 def on_close(self, event): if self.doExit(): # self.saveConfig() self.fileHistory = None self.printer = None event.skip() def on_menuFileSave_select(self, event): if self.documentPath is None: # this a "new" document and needs to go through Save As... self.on_menuFileSaveAs_select(None) else: self.saveFile(self.documentPath) def on_menuFileSaveAs_select(self, event): wildcard = "Text files (*.txt)|*.TXT;*.txt|All files (*.*)|*.*" if self.documentPath is None: dir = '' filename = '*.txt' else: dir = os.path.dirname(self.documentPath) filename = os.path.basename(self.documentPath) result = dialog.saveFileDialog(None, "Save As", dir, filename, wildcard) if result.accepted: path = result.paths[0] self.saveFile(path) return True else: return False def newFile(self): self.documentPath = None self.documentChanged = 0 self.title = 'Untitled - ' + self.startTitle self.statusBar.text = 'Untitled' self.reset() for i in range(9): for j in range(9): self.updateList(self.squares[i,j]) def setUpPuzzle(self, definition): self.reset() y = 0 for line in definition: if line.strip() == "": continue if y == 9: # must be the optional title self.title = line self.statusBar.text = line break x = 0 for c in line.strip(): sqr = self.squares[x,y] if c == " ": continue if c == "x": sqr.state = 'u' else: sqr.state = 'p' sqr.values = [int(c),] self.updateList(sqr) x += 1 y += 1 def on_builtInPuzzle_command(self, event): self.documentPath = None self.documentChanged = 0 self.title = 'Untitled - ' + self.startTitle self.statusBar.text = 'Untitled' tname = event.target.name.replace("menuFilePuzzle", "P") self.setUpPuzzle(self.components[tname].userdata.split("\n")) def openFile(self, path): # change the code below for # opening an existing document # the commented lines are from the textEditor tool # may be overridden if file includesthe optinoal title on last line self.title = os.path.split(path)[-1] + ' - ' + self.startTitle f = open(path) self.setUpPuzzle(f) f.close() self.documentPath = path self.documentChanged = 0 self.statusBar.text = path def saveFile(self, path): # change the code below for # saving an existing document f = open(path, 'w') for y in range(9): s = [] for x in range(9): tList = self.squares[x,y].values if len(tList) == 1: s.append(str(tList[0])) else: s.append('x') if x == 2 or x == 5: s.append(' ') s.append("\n") f.write(''.join(s)) if y == 2 or y == 5: f.write("\n") f.close() self.documentPath = path self.documentChanged = False self.title = os.path.split(path)[-1] + ' - ' + self.startTitle self.statusBar.text = path def on_menuFileNew_select(self, event): if self.documentChanged: save = self.saveChanges() if save == "Cancel": # don't do anything, just go back to editing pass elif save == "No": # any changes will be lost self.newFile() else: if self.documentPath is None: if self.on_menuFileSaveAs_select(None): self.newFile() else: self.saveFile(self.documentPath) self.newFile() else: # don't need to save self.newFile() def on_menuFileOpen_select(self, event): # split this method into several pieces to make it more flexible wildcard = "Text files (*.txt)|*.txt;*.TXT|All files (*.*)|*.*" result = dialog.openFileDialog(wildcard=wildcard) if result.accepted: path = result.paths[0] # an error will probably occur here if the text is too large # to fit in the wxTextCtrl (TextArea) or the file is actually # binary. Not sure what happens with CR/LF versus CR versus LF # line endings either self.openFile(path) def on_menuFilePrint_select(self, event): # put your code here for print # the commented code below is from the textEditor tool # and is simply an example #source = textToHtml(self.components.fldDocument.text) #self.printer.PrintText(source) pass def on_menuFilePrintPreview_select(self, event): # put your code here for print preview # the commented code below is from the textEditor tool # and is simply an example #source = textToHtml(self.components.fldDocument.text) #self.printer.PreviewText(source) pass def on_menuFilePageSetup_select(self, event): self.printer.PageSetup() def saveUndoPosition(self): st = {} for i in range(9): for j in range(9): sqr = self.squares[i,j] st[i,j] = undosquare(copy.copy(sqr.values), sqr.state) self.undo.append(st) self.redo = [] def on_Blank_mouseClick(self, event): self.on_menuFileNew_select(event) def on_Undo_command(self, event): # if we are at the end - i.e. no redo info yet, then we should take a snapshot # right now so that we can get back to this point if len(self.redo) == 0: self.saveUndoPosition() st = self.undo[-1] self.redo.append(st) del self.undo[-1] ## print "undo", len(self.undo), len(self.redo) if len(self.undo) == 0: result = dialog.alertDialog(self, 'No more to Undo', 'Undo Alert') return st = self.undo[-1] ## for k,v in self.undo[-1].iteritems(): ## print k, v.state, v.values for i in range(9): for j in range(9): sqr = self.squares[i,j] sqr.values = copy.copy(st[i,j].values) sqr.state = st[i,j].state self.updateList(sqr) self.redo.append(st) del self.undo[-1] t = self.count() self.statusBar.text = "Possible choices left %d" % t ## print "undo", len(self.undo), len(self.redo) def on_Redo_command(self, event): ## print "redo", len(self.undo), len(self.redo) if len(self.redo) == 0: result = dialog.alertDialog(self, 'No more to Redo', 'Redo Alert') return st = self.redo[-1] for i in range(9): for j in range(9): sqr = self.squares[i,j] sqr.values = copy.copy(st[i,j].values) sqr.state = st[i,j].state self.updateList(sqr) self.undo.append(st) del self.redo[-1] t = self.count() self.statusBar.text = "Possible choices left %d" % t ## print "redo", len(self.undo), len(self.redo) def on_hideUndecided_mouseClick(self, event): self.hideUndecided = self.components.hideUndecided.checked for i in range(9): for j in range(9): self.updateList(self.squares[i,j]) def on_hideChoices_mouseClick(self, event): self.hideChoices = self.components.hideChoices.checked def on_doHelpAbout_command(self, event): dialog.messageDialog(self, "Sudoku Solver" + "\n\n" + \ "Version %s" % VERSION + "\n\n" + \ "(c) 2005 Alex Tweedly", "About Sudoku Solver", wx.ICON_INFORMATION | wx.OK) pass def on_doHelpHelp_command(self, event): if not self.readme: self.readme = open('readme.txt').read() dialog.scrolledMessageDialog(self, self.readme, 'Help') pass def singles(self): self.saveUndoPosition() for i in range(9): for j in range(9): sqr = self.squares[i,j] if len(sqr.values) == 1 and (sqr.state == 'p' or sqr.state == 'a' or sqr.state == 'f'): sqr.state = 'i' self.fillin(sqr, i,j) wx.SafeYield(self) def soles(self): self.saveUndoPosition() for i in range(9): # check the col used = {1:0,2:0,3:0,4:0,5:0,6:0,7:0,8:0,9:0} last = {1:0,2:0,3:0,4:0,5:0,6:0,7:0,8:0,9:0} for j in range(9): sqr = self.squares[i,j] for c in sqr.values: used[c] += 1 last[c] = j for c,val in used.iteritems(): if val == 1: ss = self.squares[i, last[c]] if len(ss.values) <> 1: ss.values = [c,] ss.state = 'o' #rint "col", i, last[c], c self.fillin(ss,i,last[c]) # check the row used = {1:0,2:0,3:0,4:0,5:0,6:0,7:0,8:0,9:0} last = {1:0,2:0,3:0,4:0,5:0,6:0,7:0,8:0,9:0} for j in range(9): sqr = self.squares[j,i] for c in sqr.values: used[c] += 1 last[c] = j for c,val in used.iteritems(): if val == 1: ss = self.squares[last[c], i] if len(ss.values) <> 1: ss.values = [c,] ss.state = 'o' #rint "row", last[c], i, c self.fillin(ss,last[c],i) # check the square used = {1:0,2:0,3:0,4:0,5:0,6:0,7:0,8:0,9:0} last = {1:0,2:0,3:0,4:0,5:0,6:0,7:0,8:0,9:0} for j in range(9): sqr = self.squares[self.peer[i][j]] #rint i,j,self.peer[i][j] for c in sqr.values: used[c] += 1 last[c] = self.peer[i][j] for c,val in used.iteritems(): if val == 1: x,y = last[c] ss = self.squares[x,y] #rint i,c,val,ss.values if len(ss.values) <> 1: ss.values = [c,] ss.state = 'o' #rint "3x3", x, y, c self.fillin(ss,x,y) def fillin(self, sqr, i, j): if len(sqr.values) <> 1: print "help - bad values list", i,j, ss value = sqr.values[0] for d in range(9): if d == j: continue ss = self.squares[i,d] if value in ss.values: self.delValue(ss, value) ss.state = 'f' self.updateList(ss) #if len(ss.values) == 1: self.fillin(ss,i,d) for d in range(9): if d == i: continue ss = self.squares[d,j] if value in ss.values: self.delValue(ss, value) #if len(ss.values) == 1: self.fillin(ss,d,j) for d,e in self.peer[3*(i/3) + j/3]: if d == i and e == j: continue ss = self.squares[d,e] if value in ss.values: self.delValue(ss, value) ss.state = 'f' #if len(ss.values) == 1: self.fillin(ss,d,e) self.updateList(sqr) t = self.count() self.statusBar.text = "Possible choices left %d" % t def count(self): t = 1 for i in range(9): for j in range(9): t *= len(self.squares[i,j].values) if t == 0: return 0 return t def on_Singles_mouseClick(self, event): self.singles() def on_Soles_mouseClick(self, event): self.soles() def on_solveIt_mouseClick(self, event): last = -1 t = self.count() while last <> t and t > 1: last = t self.singles() t = self.count() if t == last: self.soles() t = self.count() def updateList(self, sqr): if len(sqr.values) == 0: items = [" ", " ", " "] sqr.state = 'z' elif len(sqr.values) == 1: items = [" ", " "+str(sqr.values[0])+" ", " "] else: s = [] for c in xrange(1,10): if c in sqr.values: s.append(str(c)) else: s.append(" ") all = " ".join(s) items = [all[:5], all[6:11], all[12:]] if self.hideUndecided and (sqr.state == 'u' or sqr.state == 'f'): items = [' ', ' ', ' '] sqr.comp.items = items sqr.comp.backgroundColor = COLOR[sqr.state] def delValue(self, sqr, value): if value in sqr.values: del sqr.values[sqr.values.index(value)] self.updateList(sqr) def on_popup_command(self, event): comp = event.target selected = 0 x = int(comp.name[1]) y = int(comp.name[2]) sqr = self.squares[x,y] if len(sqr.values) == 1: if self.hideChoices and (sqr.state == 'u' or sqr.state == 'f'): items = ['1','2','3','4','5','6','7','8','9'] else: selected = sqr.values[0] else: # make a menu self.menu = wx.Menu() # add the items items = [] if self.hideChoices: items = ['1','2','3','4','5','6','7','8','9'] else: for c in sqr.values: items.append(str(c)) if selected == 0: # Popup the menu. selected = popUpMenu(self, items, comp.position) ###selected = helpful.popUpMenu(self, items, comp.position) if selected: selected = int(selected) else: selected = 0 #rint "comp, val", comp.name, selected, type(selected) if selected > 0: self.saveUndoPosition() sqr.values = [selected,] sqr.state = 'a' self.updateList(sqr) comp.backgroundColor = (200,100,100) self.fillin(sqr, int(comp.name[1]), int(comp.name[2])) if __name__ == '__main__': app = model.Application(MyBackground) app.MainLoop() --- NEW FILE: sudoku.rsrc.py --- {'application':{'type':'Application', 'name':'Template', 'backgrounds': [ {'type':'Background', 'name':'Sudoku', 'title':'Standard Template with full menus', 'size':(565, 647), 'statusBar':1, 'style':['resizeable'], 'menubar': {'type':'MenuBar', 'menus': [ {'type':'Menu', 'name':'menuFile', 'label':'&File', 'items': [ {'type':'MenuItem', 'name':'menuFileNew', 'label':'&New\tCtrl+N', }, {'type':'MenuItem', 'name':'menuFileOpen', 'label':'&Open\tCtrl+O', }, {'type':'MenuItem', 'name':'menuFileSave', 'label':'&Save\tCtrl+S', 'enabled':0, }, {'type':'MenuItem', 'name':'menuFileSaveAs', 'label':'Save &As...', 'command':'saveas', }, {'type':'MenuItem', 'name':'fileSep1', 'label':'-', }, {'type':'MenuItem', 'name':'menuFilePuzzle1', 'label':'Puzzle1\tCtrl+1', 'command':'builtInPuzzle', }, {'type':'MenuItem', 'name':'menuFilePuzzle2', 'label':'Puzzle2\tCtrl+2', 'command':'builtInPuzzle', }, {'type':'MenuItem', 'name':'menuFilePuzzle3', 'label':'Puzzle3\tCtrl+3', 'command':'builtInPuzzle', }, {'type':'MenuItem', 'name':'menuFilePuzzle4', 'label':'Puzzle4\tCtrl+4', 'command':'builtInPuzzle', }, {'type':'MenuItem', 'name':'fileSep2', 'label':'-', }, {'type':'MenuItem', 'name':'menuFilePageSetup', 'label':'Page Set&up...', }, {'type':'MenuItem', 'name':'menuFilePrint', 'label':'&Print...\tCtrl+P', }, {'type':'MenuItem', 'name':'menuFilePrintPreview', 'label':'Print Pre&view', }, {'type':'MenuItem', 'name':'fileSep2', 'label':'-', }, {'type':'MenuItem', 'name':'menuFileExit', 'label':'E&xit\tAlt+X', 'command':'exit', }, ] }, {'type':'Menu', 'name':'Edit', 'label':'&Edit', 'items': [ {'type':'MenuItem', 'name':'menuEditUndo', 'label':'&Undo\tCtrl+Z', 'command':'Undo', }, {'type':'MenuItem', 'name':'menuEditRedo', 'label':'&Redo\tCtrl+Y', 'command':'Redo', }, {'type':'MenuItem', 'name':'editSep1', 'label':'-', }, {'type':'MenuItem', 'name':'menuEditMode', 'label':'&Mode and Preferences\tCtrl+M', 'command':'doMode', }, ] }, {'type':'Menu', 'name':'menuHelp', 'label':'&Help', 'items': [ {'type':'MenuItem', 'name':'menuHelpHelp', 'label':'&Help', 'command':'doHelpHelp', }, {'type':'MenuItem', 'name':'menuHelpAbout', 'label':'&About ...', 'command':'doHelpAbout', }, ] }, ] }, 'components': [ {'type':'ToggleButton', 'name':'hideChoices', 'position':(347, 540), 'size':(85, -1), 'label':'Hide Choices', }, {'type':'ToggleButton', 'name':'hideUndecided', 'position':(49, 540), 'label':'Hide Undecided', }, {'type':'Button', 'name':'Redo', 'position':(223, 65), 'size':(60, -1), 'command':'Redo', 'label':'Redo', }, {'type':'Button', 'name':'Undo', 'position':(135, 65), 'size':(60, -1), 'command':'Undo', 'label':'Undo', }, {'type':'Button', 'name':'Blank', 'position':(48, 65), 'size':(60, -1), 'label':'Blank', }, {'type':'Button', 'name':'P4', 'position':(526, 99), 'size':(20, -1), 'enabled':False, 'label':'P4', 'userdata':'42x xxx x1x\nxxx 54x x3x\nxx6 xx7 xxx\n\nxxx xxx 279\nx1x xxx x6x\n342 xxx xxx\n\nxxx 9xx 3xx\nx6x x38 xxx\nx8x xxx x57\nNine-by-Nine Puzzle 3\n', 'visible':False, }, {'type':'Button', 'name':'P3', 'position':(523, 65), 'size':(20, -1), 'enabled':False, 'label':'P3', 'userdata':'9x4 xxx 3x1\nx1x xxx xx9\nxx2 xxx 68x\n\nx5x xxx xxx\nx93 1x5 4x6\nxx7 x24 8xx\n\n34x 79x xx8\n8xx 3x1 94x\n6xx xx8 21x\n\nNine-by-Nine Puzzle 3\n', 'visible':False, }, {'type':'Button', 'name':'P2', 'position':(524, 30), 'size':(20, -1), 'enabled':False, 'label':'P2', 'userdata':'52x xxx xx4\nxxx xxx 5xx\nxx7 6xx xxx\n\nx15 x6x xxx\n4xx 8x3 x6x\nxxx xx2 9x5\n\nxx1 xxx 8xx\nxxx xx1 x42\n674 9xx xx3\n\nNine-by-Nine Puzzle 2', 'visible':False, }, {'type':'Button', 'name':'P1', 'position':(524, 2), 'size':(20, -1), 'enabled':False, 'label':'P1', 'userdata':'98x 354 xxx\n41x xx2 8xx\nx32 x1x 7xx\n\n29x 1x5 xxx\nxxx x6x xxx\n643 xx7 21x\n\nxxx 8xx x2x\nx5x x2x 6x7\nxxx x3x x9x\nNine-by-Nine Puzzle 1\n ', 'visible':False, }, {'type':'Button', 'name':'Soles', 'position':(235, 20), 'size':(53, -1), 'label':'Soles', }, {'type':'Button', 'name':'Singles', 'position':(128, 20), 'size':(53, -1), 'label':'Singles', }, {'type':'Button', 'name':'solveIt', 'position':(26, 20), 'size':(53, -1), 'label':'Solve It !', }, {'type':'List', 'name':'B00', 'position':(50, 100), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'stringSelection':'3', 'userdata':'123456789', }, {'type':'List', 'name':'B01', 'position':(50, 145), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B02', 'position':(50, 190), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B03', 'position':(50, 241), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B04', 'position':(50, 286), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B05', 'position':(50, 331), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B06', 'position':(50, 382), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B07', 'position':(50, 427), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B08', 'position':(50, 472), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B10', 'position':(95, 100), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'stringSelection':'2', 'userdata':'123456789', }, {'type':'List', 'name':'B11', 'position':(95, 145), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B12', 'position':(95, 190), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B13', 'position':(95, 241), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B14', 'position':(95, 286), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B15', 'position':(95, 331), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B16', 'position':(95, 382), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B17', 'position':(95, 427), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B18', 'position':(95, 472), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B20', 'position':(140, 100), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B21', 'position':(140, 145), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B22', 'position':(140, 190), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B23', 'position':(140, 241), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B24', 'position':(140, 286), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B25', 'position':(140, 331), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B26', 'position':(140, 382), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B27', 'position':(140, 427), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B28', 'position':(140, 472), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B30', 'position':(191, 100), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B31', 'position':(191, 145), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B32', 'position':(191, 190), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B33', 'position':(191, 241), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B34', 'position':(191, 286), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B35', 'position':(191, 331), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B36', 'position':(191, 382), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B37', 'position':(191, 427), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B38', 'position':(191, 472), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B40', 'position':(236, 100), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B41', 'position':(236, 145), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B42', 'position':(236, 190), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B43', 'position':(236, 241), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B44', 'position':(236, 286), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B45', 'position':(236, 331), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B46', 'position':(236, 382), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B47', 'position':(236, 427), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B48', 'position':(236, 472), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B50', 'position':(281, 100), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B51', 'position':(281, 145), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B52', 'position':(281, 190), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B53', 'position':(281, 241), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B54', 'position':(281, 286), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B55', 'position':(281, 331), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B56', 'position':(281, 382), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B57', 'position':(281, 427), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B58', 'position':(281, 472), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B60', 'position':(332, 100), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B61', 'position':(332, 145), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B62', 'position':(332, 190), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B63', 'position':(332, 241), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B64', 'position':(332, 286), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B65', 'position':(332, 331), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B66', 'position':(332, 382), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B67', 'position':(332, 427), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B68', 'position':(332, 472), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B70', 'position':(377, 100), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B71', 'position':(377, 145), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B72', 'position':(377, 190), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B73', 'position':(377, 241), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B74', 'position':(377, 286), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B75', 'position':(377, 331), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B76', 'position':(377, 382), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B77', 'position':(377, 427), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B78', 'position':(377, 472), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B80', 'position':(422, 100), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B81', 'position':(422, 145), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B82', 'position':(422, 190), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B83', 'position':(422, 241), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B84', 'position':(422, 286), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B85', 'position':(422, 331), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B86', 'position':(422, 382), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B87', 'position':(422, 427), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, {'type':'List', 'name':'B88', 'position':(422, 472), 'size':(66, 66), 'command':'popup', 'items':['1', '2', '3'], 'userdata':'123456789', }, ] # end components } # end background ] # end backgrounds } } --- NEW FILE: readme.txt --- Sudoku Solver can solve, or help you solve, Sudoku puzzles. The rules of Sudoku are very simple: - each digit 1-9 must appear exactly once in each row, column or 3x3 square Solving a puzzle. ================= The basic step to solve a puzzle is to select a value for a square. If you click in a square which has only one possible value available, that square will be coloured in, and that value is removed from all the other squares in the same row, column and 3x3 square (because it has now been assigned to the square you clicked in, it cannot be also put in the other squares in the same row, column and 3x3 square). If there are still multiple values possible for the square you click in, you will be presented with a pop-up menu of the possible values. Selecting any one of these will assign that value to the square (as above). Of course, you can also choose to not select on of the values, but releasing the mouse button while outside the pop-up menu. When you assign a value like this, the "mechanical" work of removing that value from the row, column and 3x3 square is done for you. Note that the status bar at the bottom of the window will show the number of possible combinations left: this will decrease as the solution progresses, and should eventually reach 1. There are three buttons which further automate the solving of a puzzle. Two of these (Singles and Soles) implement the two basic solution strategies, while the third (Solve it) simply applies the two strategies in turn until it succeeds (or goes as far as it can). Singles. -------- Any square for which there is only a single remaining value allowed is called a "single". Obviously, the initial values assigned as part of the puzzle definition are one example of this. The "single" value can be assigned to that square, and then that digit can be eliminate from the other squares in the same row, the same column and the same 3x3 square. This elimination of values may generate additional "singles" - they are not immediately taken, but clicking the button again will do them. Soles. ------ There may be only one square in a row which can still contain one of the digits, and this will be called the "sole" place that digit can go. Note this square may not be a "single" - it may have any number of still-valid possibilities, but if a particular digit can only be in the one place in that row, then it can be assigned as the "sole" place for it to go. Obviously, this can be applied to columns, and to 3x3 squares as well. Solve It -------- As we said above, this simply applies each of the two methods (Singles and Soles) above in turn, until neither produces any progress. There are three further buttons for use in solving puzzles. Blank ----- This produces a new, blank puzzle (i.e. equivalent to selecting File / New from the menu). Undo and Redo ---- ---- These are fairly self-explanatory - each click will undo (or redo) one step of the solution so far. Simple, Moderate and Complex Puzzles. ==================================== The four built-in puzzles are in order of simple to more difficult. You will find that many puzzles, like the first three here, can be solved simply by applying the strategies described above (either by clicking the buttons, or more satisfyingly by working through the puzzle one square at a time). However, some puzzles, such as the fourth built-in example, will not be fully resolved by using these techniques. While there are other analytical methods possible, the usual method to tackle these is simply to "postulate" (i.e. guess !!) one of the remaining values and see whether that leads to a solution or not. Well constructed Sudoku puzzles should have only a single solution, so this method will work. The easiest way to use this Sudoku Solver to do this is to simply choose the top, left square which still has multiple values possible, and do a trial assignment of the first value to it. Then click on "Solve it", and see what happens. If this value-assignment is incorrect, it will usually lead to some square(s) having no possible values left, and it (they) will be coloured bright red, so will be easy to spot. You can then click "Undo" multiple times, until you reach the point at which you made the guess. Then try the next possible value, and again click "Solve it". If the assignment is correct, then clicking "Solve it" will usually lead to a complete solution. Occasionally, the first guessed value will lead to neither a complete solution nor a complete conflict. In this case, you may need to move on to the next square which is not yet decided, and make a guess there. But do make sure that you remember to click on "Solve it" (or that you do the equivalent effort yourself) or you risk the possibility of not detecting the complete conflict or solution. (I have not actually found a puzzle that requires this multi-step postulation, but it's theoretically possible - if you do find one, please send it to me.) Defining or selecting a puzzle. =============================== A puzzle definition sets the initial values for some of the squares assignments. There are four puzzles built-in, and you can add as many new puzzles as you like. The quickest way to create a new puzzle is to edit a text definition file, and the solve that puzzle by selecting File / Open from the menu. The format for these files is simple; each row of the puzzle is a line in the file, each character is a single square, containing either a digit or 'x'. Extra spaces or blank lines are ignored. So the file to define the first built-in puzzle could look like: 98x 354 xxx 41x xx2 8xx x32 x1x 7xx 29x 1x5 xxx xxx x6x xxx 643 xx7 21x xxx 8xx x2x x5x x2x 6x7 xxx x3x x9x You can also create a puzzle by starting with a blank puzzle (i.e. immediately after starting the program, or by clicking the "Blank" button), then specifying values for your initial squares (by clicking in a square, then selecting the value from the pop-up menu), and finally selecting menu File / Save as ... and storing the puzzle definition in a file. You could also add more 'built-in' puzzles using the resource editor, by adding invisible buttons (similar to P1, P2, P3 P4) and adding menu entries (similar to File / Puzzle1 (the actual puzzle definition goes in the "userdata" of the invisible buttons). You can save a puzzle at any time, using the File / Save As .... menu selection. This will save the puzzle in the format described above, i.e. the only info saved is which squares have a single possible value assigned. All other squares will be simply marked as 'unknown' so if you later re-open this puzzle, you will be in an equivalent position, but will not see exactly the possibilities as you had seen before saving the puzzle state. The File / Save menu item is currently disabled, because it rarely, if ever, makes sense to save the current state so as to overwrite the state in the file. Further options =============== The two buttons at the bottom of the window give you further options on how much help you are given. Toggling both of these will let you solve the puzzle with no help at all - equivalent to solving it on paper, but without any easy way to mark-up your partial solutions or ideas. Hide Undecided -------------- Normally, any square for which there are multiple values still possible will show all of those choices (and only those choices). [Remember that until you have assigned a value to a square (and the square background has been coloured in), the assignment has not happened, so even though there is only a single possible value showing, that value will continue to be available in the other squares in the row, column and 3x3 square).] Toggling this button down will hide the multiple values in such undecided squares (but if you click in the square, the pop-up menu will present only the valid choices). Hide choices ------------ This controls which values are presented in the pop-up menu; when it is toggled, all values (1-9) are in the menu - whereas in the other case, only the values known to be valid are shown. |