#!/usr/bin/env python # Copyright 2006 Sam Saeed # # This program is distributed under the terms of the GNU General Public License. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # import os import copy import sys import random import wx.html import pickle SQUARE_SIZE = 3 BOARD_SIZE = SQUARE_SIZE * SQUARE_SIZE CELLS = BOARD_SIZE * BOARD_SIZE DIMENSION_RANGE = range(0,BOARD_SIZE) USE_BUFFERED_DC = 1 PYSUDOKU_VERSION_STRING = "1.0.0" #!/usr/bin/env python # -*- coding: UTF-8 -*- # Generated by wxGlade 0.4cvs on Sat May 13 01:18:56 2006 import wx class pySudokuFrame(wx.Frame): def __init__(self, *args, **kwds): ID_NEW_MENU = wx.NewId() ID_EXIT_MENU = wx.NewId() ID_UNDO_MENU = wx.NewId() ID_RESETMARKS_MENU = wx.NewId() ID_EDITBOARD_MENU = wx.NewId() ID_HINTS_MENU = wx.NewId() ID_ILLEGAL_MENU = wx.NewId() ID_ABOUT_MENU = wx.NewId() ID_OPEN_MENU = wx.NewId() ID_SAVE_MENU = wx.NewId() ID_PRINT_MENU = wx.NewId() ID_PAGE_SETUP_MENU = wx.NewId() ID_PRINT_PREVIEW_MENU = wx.NewId() # begin wxGlade: pySudokuFrame.__init__ kwds["style"] = wx.DEFAULT_FRAME_STYLE wx.Frame.__init__(self, *args, **kwds) # Menu Bar self.pySudokuFrame_menubar = wx.MenuBar() self.SetMenuBar(self.pySudokuFrame_menubar) self.GameMenu = wx.Menu() self.NewMenu = wx.MenuItem(self.GameMenu, ID_NEW_MENU, "&New\tCtrl-N", "Create a new puzzle", wx.ITEM_NORMAL) self.GameMenu.AppendItem(self.NewMenu) self.OpenMenu = wx.MenuItem(self.GameMenu, ID_OPEN_MENU, "&Open\tCtrl-O", "Open a saved game", wx.ITEM_NORMAL) self.GameMenu.AppendItem(self.OpenMenu) self.GameMenu.AppendSeparator() self.SaveMenu = wx.MenuItem(self.GameMenu, ID_SAVE_MENU, "&Save\tCtrl-S", "Save current game", wx.ITEM_NORMAL) self.GameMenu.AppendItem(self.SaveMenu) self.GameMenu.AppendSeparator() self.PageSetupMenu = wx.MenuItem(self.GameMenu, ID_PAGE_SETUP_MENU, "Page Set&up", "Page Setup", wx.ITEM_NORMAL) self.GameMenu.AppendItem(self.PageSetupMenu) self.PrintPreviewMenu = wx.MenuItem(self.GameMenu, ID_PRINT_PREVIEW_MENU, "Print Pre&view", "Print Preview", wx.ITEM_NORMAL) self.GameMenu.AppendItem(self.PrintPreviewMenu) self.PrintMenu = wx.MenuItem(self.GameMenu, ID_PRINT_MENU, "&Print\tCtrl-P", "Print current game", wx.ITEM_NORMAL) self.GameMenu.AppendItem(self.PrintMenu) self.GameMenu.AppendSeparator() self.ExitMenu = wx.MenuItem(self.GameMenu, ID_EXIT_MENU, "E&xit\tCtrl-Q", "Terminate the program", wx.ITEM_NORMAL) self.GameMenu.AppendItem(self.ExitMenu) self.pySudokuFrame_menubar.Append(self.GameMenu, "&Game") self.EditMenu = wx.Menu() self.UndoMenu = wx.MenuItem(self.EditMenu, ID_UNDO_MENU, "&Undo\tCtrl-Z", "Undo last move", wx.ITEM_NORMAL) self.EditMenu.AppendItem(self.UndoMenu) self.ResetMarksMenu = wx.MenuItem(self.EditMenu, ID_RESETMARKS_MENU, "&Reset Marks\tCtrl-R", "Reset Marks", wx.ITEM_NORMAL) self.EditMenu.AppendItem(self.ResetMarksMenu) self.EditBoardMenu = wx.MenuItem(self.EditMenu, ID_EDITBOARD_MENU, "&BOARD\tCtrl-E", "Manually Create a new puzzle.", wx.ITEM_CHECK) self.EditMenu.AppendItem(self.EditBoardMenu) self.pySudokuFrame_menubar.Append(self.EditMenu, "&Edit") self.OptionsMenu = wx.Menu() self.HintsMenu = wx.MenuItem(self.OptionsMenu, ID_HINTS_MENU, "&Hints\tCtrl-F1", "Display hints", wx.ITEM_CHECK) self.OptionsMenu.AppendItem(self.HintsMenu) self.IllegalMenu = wx.MenuItem(self.OptionsMenu, ID_ILLEGAL_MENU, "Allow &Illegal Moves\tCtrl-I", "Allow or Prevent Illegal moves from being made", wx.ITEM_CHECK) self.OptionsMenu.AppendItem(self.IllegalMenu) self.pySudokuFrame_menubar.Append(self.OptionsMenu, "&Options") self.HelpMenu = wx.Menu() self.AboutMenu = wx.MenuItem(self.HelpMenu, ID_ABOUT_MENU, "&About", "About pySudoku", wx.ITEM_NORMAL) self.HelpMenu.AppendItem(self.AboutMenu) self.pySudokuFrame_menubar.Append(self.HelpMenu, "&Help") # Menu Bar end self.pySudokuFrame_statusbar = self.CreateStatusBar(1, 0) self.__set_properties() self.__do_layout() self.Bind(wx.EVT_MENU, self.NewPuzzleClicked, self.NewMenu) self.Bind(wx.EVT_MENU, self.OpenGame, self.OpenMenu) self.Bind(wx.EVT_MENU, self.SaveGame, self.SaveMenu) self.Bind(wx.EVT_MENU, self.PageSetup, self.PageSetupMenu) self.Bind(wx.EVT_MENU, self.PrintPreview, self.PrintPreviewMenu) self.Bind(wx.EVT_MENU, self.PrintGame, self.PrintMenu) self.Bind(wx.EVT_MENU, self.OnQuit, self.ExitMenu) self.Bind(wx.EVT_MENU, self.Undo, self.UndoMenu) self.Bind(wx.EVT_MENU, self.ResetMarks, self.ResetMarksMenu) self.Bind(wx.EVT_MENU, self.EditBoard, self.EditBoardMenu) self.Bind(wx.EVT_MENU, self.ToggleHints, self.HintsMenu) self.Bind(wx.EVT_MENU, self.ToggleIllegal, self.IllegalMenu) self.Bind(wx.EVT_MENU, self.AboutSudoku, self.AboutMenu) # end wxGlade # initialize variables self.m_x = 1 self.m_y = 1 self.CurrentRow = 0 self.CurrentCol = 0 self.DisplayHints = True self.AllowIllegal = False self.OptionsMenu.Check(ID_HINTS_MENU,self.DisplayHints) # Create new Puzzle and show it self.Window = DrawWindow(self) self.NewPuzzle() self.Window.SetFocus() # setup additional event handlers wx.EVT_SIZE(self, self.OnSize) wx.EVT_CHAR(self.Window,self.KeyPress) wx.EVT_LEFT_DOWN(self.Window,self.MouseDown) wx.EVT_MOTION(self.Window,self.MouseMove) # Create Small version of screen as Icon self.IconWindow = DrawWindow(self) self.IconWindow.DrawData = self.CreateBoard() IconBundle = wx.IconBundle() Icon = wx.EmptyIcon() # self.IconWindow.SetSize( (16,16) ) # self.IconWindow.UpdateDrawing() # Icon.CopyFromBitmap(self.IconWindow._Buffer) # IconBundle.AddIcon(Icon) self.IconWindow.SetSize( (32,32) ) self.IconWindow.UpdateDrawing() Icon.CopyFromBitmap(self.IconWindow._Buffer) IconBundle.AddIcon(Icon) self.IconWindow.SetSize( (48,48) ) self.IconWindow.UpdateDrawing() Icon.CopyFromBitmap(self.IconWindow._Buffer) IconBundle.AddIcon(Icon) self.IconWindow.SetSize( (64,64) ) self.IconWindow.UpdateDrawing() Icon.CopyFromBitmap(self.IconWindow._Buffer) IconBundle.AddIcon(Icon) self.SetIcons(IconBundle) self.IconWindow.Destroy() self.PrintData = wx.PrintData() self.PrintWindow = DrawWindow(self) self.PrintWindow.Show(False) def __set_properties(self): # begin wxGlade: pySudokuFrame.__set_properties self.SetTitle("pySudoku") self.SetSize((600, 655)) self.pySudokuFrame_statusbar.SetStatusWidths([-1]) # statusbar fields pySudokuFrame_statusbar_fields = ["pySudokuFrame_statusbar"] for i in range(len(pySudokuFrame_statusbar_fields)): self.pySudokuFrame_statusbar.SetStatusText(pySudokuFrame_statusbar_fields[i], i) # end wxGlade def __do_layout(self): # begin wxGlade: pySudokuFrame.__do_layout sizer_1 = wx.BoxSizer(wx.VERTICAL) self.SetAutoLayout(True) self.SetSizer(sizer_1) self.Layout() # end wxGlade def NewPuzzleClicked(self, event): # wxGlade: pySudokuFrame. self.NewPuzzle() event.Skip() def OnQuit(self, event): # wxGlade: pySudokuFrame. self.Close(True) event.Skip() def Undo(self, event): # wxGlade: pySudokuFrame. if len(self.UndoList) > 0: Row,Col = self.UndoList.pop() self.CurrentBoard.ResetCell(Row,Col) self.Window.DrawData = self.CreateBoard() self.Window.UpdateDrawing() event.Skip() def ResetMarks(self, event): # wxGlade: pySudokuFrame. for Row in range(0,9): for Col in range(0,9): for Value in range(1,10): self.Marks[Row][Col][Value-1] = False self.Window.DrawData = self.CreateBoard() self.Window.UpdateDrawing() event.Skip() def EditBoard(self, event): # wxGlade: pySudokuFrame. Menu = self.GetMenuBar().FindItemById(event.GetId()).GetMenu() if not Menu.IsChecked(event.GetId()): NewBoard = copy.deepcopy(self.CurrentBoard) self.SetBoard(NewBoard) del NewBoard else: self.BlankPuzzle() event.Skip() def ToggleHints(self, event): # wxGlade: pySudokuFrame. Menu = self.GetMenuBar().FindItemById(event.GetId()).GetMenu() self.DisplayHints = Menu.IsChecked(event.GetId()) self.Window.DrawData = self.CreateBoard() self.Window.UpdateDrawing() event.Skip() def SetCell(self,XCoord,YCoord,Value): Row,Col = self.Window.CellNumber(XCoord,YCoord) if self.CurrentBoard.TestCell(Row,Col,Value) or (self.CurrentBoard.Board[Row][Col]==0 and self.AllowIllegal): self.UndoList.append( (Row,Col) ) self.CurrentBoard.SetCell(Row,Col,Value) self.Window.DrawData = self.CreateBoard() self.Window.UpdateDrawing() else: wx.Bell() def ToggleIllegal(self, event): # wxGlade: pySudokuFrame. Menu = self.GetMenuBar().FindItemById(event.GetId()).GetMenu() self.AllowIllegal = Menu.IsChecked(event.GetId()) self.CurrentBoard.AllowIllegal = self.AllowIllegal event.Skip() def AboutSudoku(self, event): # wxGlade: pySudokuFrame. Dlg = pySudokuAboutBox(None) Dlg.SetIcon(self.GetIcon()) Dlg.ShowModal() Dlg.Destroy() del Dlg event.Skip() def OnSize(self,event): self.Window.SetSize(self.GetClientSize()) event.Skip() def BlankPuzzle(self): self.SetBoard(SudokuBoard()) def InitializeBoard(self): self.OriginalBoard = None self.CurrentBoard = None self.Solution = None self.Marks = dim([BOARD_SIZE,BOARD_SIZE,BOARD_SIZE],False) self.UndoList = [] self.RedoList = [] def SetBoard(self,SudokuBoard): self.InitializeBoard() Temp = Sudoku() Temp.SetBoard(copy.deepcopy(SudokuBoard)) Temp.Solve() self.OriginalBoard = copy.deepcopy(Temp.Puzzle) self.CurrentBoard = copy.deepcopy(Temp.Puzzle) self.Solution = copy.deepcopy(Temp.Solution) del Temp self.Window.DrawData = self.CreateBoard() self.Window.UpdateDrawing() def NewPuzzle(self): Wait = wx.BusyCursor() x = Sudoku() NewPuzzle = x.Generate() self.SetBoard(NewPuzzle) del NewPuzzle del x del Wait def BlankPuzzle(self): self.SetBoard(SudokuBoard()) def KeyPress(self,event): if event.KeyCode() >= ord('0') and event.KeyCode() <= ord('9'): self.SetCell(self.m_x,self.m_y,event.KeyCode()-ord('0')) elif event.KeyCode() == ord('h') or event.KeyCode() == ord('H'): Row,Col = self.Window.CellNumber(self.m_x,self.m_y) self.SetCell(self.m_x,self.m_y,self.Solution.Board[Row][Col]) else: event.Skip() wx.Bell() def MouseDown(self,event): Row,Col,Value = self.Window.HintNumber(event.m_x,event.m_y) self.Marks[Row][Col][Value-1] = not self.Marks[Row][Col][Value-1] self.Window.DrawData = self.CreateBoard() self.Window.UpdateDrawing() event.Skip() def MouseMove(self,event): self.m_x = event.m_x self.m_y = event.m_y OldRow,OldCol = self.CurrentRow,self.CurrentCol self.CurrentRow,self.CurrentCol = self.Window.CellNumber(self.m_x,self.m_y) if self.CurrentRow <> OldRow or self.CurrentCol <> OldCol: self.Window.DrawData = self.CreateBoard() self.Window.UpdateDrawing() event.Skip() def CreateBoard(self): if self.OriginalBoard.Board[self.CurrentRow][self.CurrentCol] > 0: self.SetStatusText("") elif self.CurrentBoard.Board[self.CurrentRow][self.CurrentCol] > 0: self.SetStatusText("Hit Ctrl-Z to undo this Value") elif self.DisplayHints: self.SetStatusText("0-9 to set Value, H for a hint, Click on the small numbers to set/reset marks") else: self.SetStatusText("0-9 to set Value, H for a hint") DrawData = {} l = [] l.append( (self.CurrentRow,self.CurrentCol) ) DrawData["ActiveCell"] = l del l l = [] for Row in range(0,9): for Col in range(0,9): if self.CurrentBoard.Board[Row][Col] == 0: continue if self.OriginalBoard.Board[Row][Col] > 0: l.append( (Row,Col,self.CurrentBoard.Board[Row][Col],wx.FONTWEIGHT_BOLD) ) else: l.append( (Row,Col,self.CurrentBoard.Board[Row][Col],wx.FONTWEIGHT_NORMAL) ) DrawData["Numbers"] = l del l if self.DisplayHints: l = [] for Row in range(0,9): for Col in range(0,9): if self.CurrentBoard.Board[Row][Col] <> 0: continue for Value in range(1,10): if not self.Marks[Row][Col][Value-1] and self.CurrentBoard.TestCell(Row,Col,Value): l.append( (Row,Col,Value) ) DrawData["Hints"] = l l = [] return DrawData def OpenGame(self, event): # wxGlade: pySudokuFrame. Dlg = wx.FileDialog( self, message="Chose a saved game to load", defaultDir=os.getcwd(), defaultFile="", wildcard="pySudoku files (*.psu)|*.psu|" \ "All files (*.*)|*.*", style=wx.OPEN | wx.CHANGE_DIR ) if Dlg.ShowModal() == wx.ID_OK: path = Dlg.GetPath() self.InitializeBoard() self.OriginalBoard, self.CurrentBoard, self.Solution, self.Marks, self.UndoList, self.RedoList = pickle.load(open(path)) self.Window.DrawData = self.CreateBoard() self.Window.UpdateDrawing() Dlg.Destroy() event.Skip() def SaveGame(self, event): # wxGlade: pySudokuFrame. Dlg = wx.FileDialog( self, message="Specify file name to save to", defaultDir=os.getcwd(), defaultFile="pySudoku.psu", wildcard="pySudoku files (*.psu)|*.psu|" \ "All files (*.*)|*.*", style=wx.SAVE ) if Dlg.ShowModal() == wx.ID_OK: path = Dlg.GetPath() pickle.dump( (self.OriginalBoard,self.CurrentBoard,self.Solution, self.Marks, self.UndoList, self.RedoList) ,file(path,"w")) Dlg.Destroy() event.Skip() def PageSetup(self, event): # wxGlade: pySudokuFrame. Data = wx.PrintDialogData(self.PrintData) PrinterDialog = wx.PrintDialog(self, Data) PrinterDialog.GetPrintDialogData().SetSetupDialog(True) PrinterDialog.ShowModal(); self.PrintData = wx.PrintData( PrinterDialog.GetPrintDialogData().GetPrintData() ) PrinterDialog.Destroy() def PrintPreview(self, event): # wxGlade: pySudokuFrame. self.PrintWindow.DrawData = self.Window.DrawData self.PrintWindow.DrawData["ActiveCell"]=[] printout = pySudokuPrintout(self.PrintWindow) self.preview = wx.PrintPreview(printout,None,self.PrintData) if not self.preview.Ok(): return frame = wx.PreviewFrame(self.preview, self, "Print Preview") frame.Initialize() frame.SetPosition(self.GetPosition()) frame.SetSize(self.GetSize()) frame.Show(True) event.Skip() def PrintGame(self, event): # wxGlade: pySudokuFrame. self.PrintWindow.DrawData = self.Window.DrawData self.PrintWindow.DrawData["ActiveCell"]=[] printout = pySudokuPrintout(self.PrintWindow) pdd = wx.PrintDialogData(self.PrintData) printer = wx.Printer(pdd) if not printer.Print(self.Window, printout, True): wx.MessageBox("There was a problem printing.\nPerhaps your current printer is not set correctly?", "Printing", wx.OK) else: self.printData = wx.PrintData( printer.GetPrintDialogData().GetPrintData() ) printout.Destroy() event.Skip() # end of class pySudokuFrame class pySudokuAboutBox(wx.Dialog): def __init__(self, *args, **kwds): # begin wxGlade: pySudokuAboutBox.__init__ kwds["style"] = wx.DEFAULT_DIALOG_STYLE wx.Dialog.__init__(self, *args, **kwds) self.__set_properties() self.__do_layout() # end wxGlade text = '''

pySudoku %s

(%s)
Running on Python %s using wxPython %s

pySudoku is an implementation of Sudoku using Python and the wxPython GUI classes.

pySudoku is brought to you by Sam Saeed, Copyright (c) 2006.

This program is distributed under the terms of the GNU General Public License. Please see license.txt for licensing information.

''' html = wx.html.HtmlWindow(self, -1, size=(420, -1)) if "gtk2" in wx.PlatformInfo: html.SetStandardFonts() py_version = sys.version.split()[0] html.SetPage(text % (PYSUDOKU_VERSION_STRING, ", ".join(wx.PlatformInfo[1:]), py_version, wx.VERSION_STRING, )) btn = html.FindWindowById(wx.ID_OK) ir = html.GetInternalRepresentation() html.SetSize( (ir.GetWidth()+25, ir.GetHeight()+25) ) self.SetClientSize(html.GetSize()) self.CentreOnParent(wx.BOTH) def __set_properties(self): # begin wxGlade: pySudokuAboutBox.__set_properties self.SetTitle("About pySudoku") self.SetSize((400, 300)) # end wxGlade def __do_layout(self): # begin wxGlade: pySudokuAboutBox.__do_layout self.Layout() # end wxGlade # end of class pySudokuAboutBox class SudokuApp(wx.App): def OnInit(self): wx.InitAllImageHandlers() MainWindow = pySudokuFrame(None, -1, "") self.SetTopWindow(MainWindow) MainWindow.Show() return 1 # end of class SudokuApp class pySudokuPrintout(wx.Printout): def __init__(self, canvas): wx.Printout.__init__(self) self.canvas = canvas def OnBeginDocument(self, start, end): return self.base_OnBeginDocument(start, end) def OnEndDocument(self): self.base_OnEndDocument() def OnBeginPrinting(self): self.base_OnBeginPrinting() def OnEndPrinting(self): self.base_OnEndPrinting() def OnPreparePrinting(self): self.base_OnPreparePrinting() def HasPage(self, page): if page <= 1: return True else: return False def GetPageInfo(self): return (1, 1, 1, 1) def OnPrintPage(self, page): dc = self.GetDC() (w,h) = dc.GetSizeTuple() size = min(w,h)*9/10 dc.SetDeviceOrigin((w-size)/2,(h-size)/2) dc.SetUserScale(0.9, 0.9) self.canvas.Draw(dc) return True class wxBufferedWindow(wx.Window): def __init__(self, parent, id, pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.NO_FULL_REPAINT_ON_RESIZE): wx.Window.__init__(self, parent, id, pos, size, style) wx.EVT_PAINT(self, self.OnPaint) wx.EVT_SIZE(self, self.OnSize) self.OnSize(None) def Draw(self, dc): pass def OnSize(self,event): # The Buffer init is done here, to make sure the buffer is always # the same size as the Window self.Width, self.Height = self.GetClientSizeTuple() # Make new off screen bitmap: this bitmap will always have the # current drawing in it, so it can be used to save the image to # a file, or whatever. self._Buffer = wx.EmptyBitmap(self.Width, self.Height) self.UpdateDrawing() def OnPaint(self, event): # All that is needed here is to draw the buffer to screen if USE_BUFFERED_DC: dc = wx.BufferedPaintDC(self, self._Buffer) else: dc = wx.PaintDC(self) dc.DrawBitmap(self._Buffer,0,0) def GetDC(self): if USE_BUFFERED_DC: dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer) else: # update the buffer dc = wx.MemoryDC() return dc def UpdateDrawing(self): if USE_BUFFERED_DC: dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer) self.Draw(dc) else: # update the buffer dc = wx.MemoryDC() dc.SelectObject(self._Buffer) self.Draw(dc) # update the screen wx.ClientDC(self).Blit(0, 0, self.Width, self.Height, dc, 0, 0) def SaveToFile(self,FileName,FileType): ## This will save the contents of the buffer ## to the specified file. See the wxWindows docs for ## wxBitmap::SaveFile for the details self._Buffer.SaveFile(FileName,FileType) class DrawWindow(wxBufferedWindow): def __init__(self, parent, id = -1): ## Any data the Draw() function needs must be initialized before ## calling wxBufferedWindow.__init__, as it will call the Draw ## function. self.DrawData = {} wxBufferedWindow.__init__(self, parent, id) def BoxWidth(self,dc=None): if dc is None: dc = self.GetDC() MaxX,MaxY = self.GetDrawingArea(dc) return MaxX / 9 def CellNumber(self,x,y,dc=None): if dc is None: dc = self.GetDC() w = self.BoxWidth(dc) return (max(min(y/w,8),0),max(min(x/w,8),0)) def CellRange(self,Row,Col,dc=None): if dc is None: dc = self.GetDC() w = self.BoxWidth(dc) return (Col*w,Row*w,w,w) def HintNumber(self,x,y,dc=None): if dc is None: dc = self.GetDC() Row, Col = self.CellNumber(x,y,dc) x1,y1,w,h = self.CellRange(Row,Col,dc) w = w/3 h = h/3 Row1 = (y - y1)/h Col1 = (x - x1)/w return (Row,Col,Row1*3+Col1+1) def HintRange(self,Row,Col,Value,dc=None): if dc is None: dc = self.GetDC() x,y,w,h = self.CellRange(Row,Col,dc) w = w/3 h = h/3 x = x + w*((Value-1) % 3) y = y + h*((Value-1) / 3) return (x,y,w,h) def GetDrawingArea(self,dc=None): if dc is None: dc = self.GetDC() MaxX, MaxY = dc.GetSizeTuple() MaxX = min(MaxX/9,MaxY/9) MaxX = MaxX * 9 MaxY = MaxX return MaxX,MaxY def Draw(self, dc): dc.BeginDrawing() dc.SetBackground( wx.Brush("White") ) dc.Clear() # make sure you clear the bitmap! MaxX, MaxY = self.GetDrawingArea(dc) w = self.BoxWidth(dc) # Draw the Squares dc.SetBrush(wx.WHITE_BRUSH) for x in range(0,10): if x % 3 == 0: dc.SetPen(wx.Pen('VIOLET', MaxX/128)) else: if MaxX < 64: continue dc.SetPen(wx.Pen('VIOLET', MaxX / 256)) dc.DrawLine(x*w+1,1,x*w+1,w*9) dc.DrawLine(1,x*w+1,w*9,x*w+1) if MaxX < 64: dc.EndDrawing() self.Refresh() return # Here's the actual drawing code. for key,data in self.DrawData.items(): if key == "ActiveCell": # # Draw a red box around the current Square # Expected tuple (Row,Col) # dc.SetPen(wx.Pen('RED', MaxX / 128)) for r in data: Row, Col = r dc.DrawLine(Col*w+1,Row*w+1,(Col+1)*w+1,Row*w+1) dc.DrawLine((Col+1)*w+1,Row*w+1,(Col+1)*w+1,(Row+1)*w+1) dc.DrawLine((Col+1)*w+1,(Row+1)*w+1,Col*w+1,(Row+1)*w+1) dc.DrawLine(Col*w+1,(Row+1)*w+1,Col*w+1,Row*w+1) elif key == "Numbers": # # Expected tuple (Row,Col,Value,weight) # dc.SetBrush(wx.WHITE_BRUSH) f = dc.GetFont() f.SetPointSize(MaxX/20) dc.SetPen(wx.Pen('VIOLET', 4)) for r in data: Row, Col, Value, weight = r x,y,w1,h1 = self.CellRange(Row,Col,dc) f.SetWeight(weight) dc.SetFont(f) TextWidth,TextHeight = dc.GetTextExtent(str(Value)) dc.DrawText(str(Value),x+(w-TextWidth)/2,y+(w-TextHeight)/2) f.SetWeight(wx.FONTWEIGHT_NORMAL) dc.SetFont(f) del f elif key == "Hints": # # Expected tuple (Row,Col,Value) # dc.SetBrush(wx.WHITE_BRUSH) f = dc.GetFont() f.SetPointSize(MaxX/60) f.SetWeight(wx.FONTWEIGHT_NORMAL) dc.SetFont(f) del f dc.SetPen(wx.Pen('VIOLET', 4)) for r in data: Row, Col, Value = r x,y,w,h = self.HintRange(Row,Col,Value,dc) TextWidth,TextHeight = dc.GetTextExtent(str(Value)) dc.DrawText(str(Value),x+(w-TextWidth)/2,y+(h-TextHeight)/2) pass dc.EndDrawing() # # Return a random range of Values # def RandomRange(start,stop): Temp = range(start,stop) RangeLength = len(Temp) NewRange = [] for ItemsToPick in range(RangeLength-1,-1,-1): NewRange.append(Temp.pop(random.randint(0,ItemsToPick))) del Temp return NewRange # # Initialize an array # def dim(Dimensions,Value): if len(Dimensions) == 0: return Value CurrentDimension = Dimensions.pop(0) SubDimension = dim(Dimensions,Value) CurrentArray = [] for Index in range(0,CurrentDimension): CurrentArray.append(copy.deepcopy(SubDimension)) return CurrentArray class SudokuBoard: # # Initialize Board # def __init__(self): self.Board = dim([BOARD_SIZE,BOARD_SIZE],0) self.Row = dim([BOARD_SIZE,BOARD_SIZE],0) self.Col = dim([BOARD_SIZE,BOARD_SIZE],0) self.Square = dim([SQUARE_SIZE,SQUARE_SIZE,BOARD_SIZE],0) self.EmptyCells = CELLS self.AllowIllegal = False # # String representation of the class # def __str__(self): StringRepresentation = '' for Row in DIMENSION_RANGE: for Col in DIMENSION_RANGE: if self.Board[Row][Col] > 0: StringRepresentation = StringRepresentation + str(self.Board[Row][Col]) + ' ' else: StringRepresentation = StringRepresentation + 'X' + ' ' StringRepresentation = StringRepresentation + chr(10) return StringRepresentation # # Test to see if a Value can be place in a particular cell # def TestCell(self,Row,Col,Value): return (self.Board[Row][Col] + self.Row[Row][Value-1] + self.Col[Col][Value-1] + self.Square[Row/SQUARE_SIZE][Col/SQUARE_SIZE][Value-1] == 0) # # Count the number of allowable moves in a particular cell # def AllowableMoves(self,Row,Col): ReturnValue = 0 for x in range(1,BOARD_SIZE+1): ReturnValue = ReturnValue + (self.Board[Row][Col] + self.Row[Row][x-1] + self.Col[Col][x-1] + self.Square[Row/SQUARE_SIZE][Col/SQUARE_SIZE][x-1] == 0) return ReturnValue # # Assign a Value to a particular cell # def SetCell(self,Row,Col,Value): if self.TestCell(Row,Col,Value) or (self.Board[Row][Col] == 0 and self.AllowIllegal): self.Board[Row][Col] = Value self.Row[Row][Value-1]+=1 self.Col[Col][Value-1]+=1 self.Square[Row/SQUARE_SIZE][Col/SQUARE_SIZE][Value-1]+=1 self.EmptyCells-=1 return 1 return 0 # # Reset the Value in a cell # def ResetCell(self,Row,Col): if self.Board[Row][Col] > 0: self.Row[Row][self.Board[Row][Col]-1]-=1 self.Col[Col][self.Board[Row][Col]-1]-=1 self.Square[Row/SQUARE_SIZE][Col/SQUARE_SIZE][self.Board[Row][Col]-1]-=1 self.Board[Row][Col] = 0 self.EmptyCells+=1 return True # # Reset the Board back to its initial state # def ResetBoard(self): for x in DIMENSION_RANGE: for y in DIMENSION_RANGE: self.ResetCell(x,y) return True # # Assigns Values to a particular Row # def SetRow(self,RowNumber,Values): ReturnValue = 1 for ColumnNumber in DIMENSION_RANGE: if Values[ColumnNumber] > 0: ReturnValue = ReturnValue * self.SetCell(RowNumber,ColumnNumber,Values[ColumnNumber]) else: ReturnValue = ReturnValue * self.ResetCell(RowNumber,ColumnNumber) return ReturnValue # # Test to see if we found a Solution # def Test(self): return (self.EmptyCells == 0) class Sudoku: def __init__(self): self.Solution = None self.Puzzle = None # # Generate a Solution # def Generate(self): # # Initialize the Board # self.__init__() self.Puzzle = SudokuBoard() # # Define the three diagonal blocks randomly # RandomCells1 = RandomRange(1,BOARD_SIZE+1) RandomCells2 = RandomRange(1,BOARD_SIZE+1) RandomCells3 = RandomRange(1,BOARD_SIZE+1) for x in range(0,3): for y in range(0,3): self.Puzzle.SetCell(0+x,0+y,RandomCells1.pop()) self.Puzzle.SetCell(3+x,3+y,RandomCells2.pop()) self.Puzzle.SetCell(6+x,6+y,RandomCells3.pop()) self.Solve() self.EliminateCells() return self.Puzzle def SetBoard(self,Puzzle): self.__init__() self.Puzzle = copy.deepcopy(Puzzle) def EliminateCells(self): OldSolution = copy.deepcopy(self.Solution) self.Puzzle = copy.deepcopy(self.Solution) # # Get rid of the three diagonal Squares # for x in range(0,3): for y in range(0,3): self.Puzzle.ResetCell(0+x,0+y) self.Puzzle.ResetCell(3+x,3+y) self.Puzzle.ResetCell(6+x,6+y) # # Create list of cells to try # CellsToTry = [] for x in DIMENSION_RANGE: for y in DIMENSION_RANGE: if self.Puzzle.Board[x][y] == 0: continue CellsToTry.append([x,y]) while len(CellsToTry) > 0: # # Try a random cell # CellToTry = CellsToTry.pop(random.randint(0,len(CellsToTry)-1)) SolutionsFound = 0 CurrentValue = self.Puzzle.Board[CellToTry[0]][CellToTry[1]] for Attempt in DIMENSION_RANGE: self.Puzzle.ResetCell(CellToTry[0],CellToTry[1]) if Attempt+1 == CurrentValue: SolutionsFound += 1 continue elif self.Puzzle.TestCell(CellToTry[0],CellToTry[1],Attempt+1): self.Puzzle.SetCell(CellToTry[0],CellToTry[1],Attempt+1) else: continue Puzzle_Solution = self.Solve() if Puzzle_Solution <> None: if Puzzle_Solution.Test(): SolutionsFound += 1 del Puzzle_Solution self.Puzzle.ResetCell(CellToTry[0],CellToTry[1]) if SolutionsFound > 1: self.Puzzle.SetCell(CellToTry[0],CellToTry[1],CurrentValue) self.Solution = OldSolution return self.Puzzle # # Solve (or create) a Puzzle # def Solve(self): self.Solution = copy.deepcopy(self.Puzzle) # # select a random cell to try # CellsToTry = [] FoundMove = False for Row in RandomRange(0,BOARD_SIZE): for Col in RandomRange(0,BOARD_SIZE): if self.Solution.Board[Row][Col] > 0: continue CellsToTry.append( (Row,Col,0) ) for Value in RandomRange(1,BOARD_SIZE+1): if self.Solution.TestCell(Row,Col,Value): CellsToTry.append( (Row,Col,Value) ) FoundMove = True break if FoundMove: break while(len(CellsToTry)>0): Row,Col,Value = CellsToTry.pop() # # If exhausted all possible moves, reset this cell and backtrack # if Value == 0: self.Solution.ResetCell(Row,Col) continue # # Try the top cell on the stack # self.Solution.ResetCell(Row,Col) if not self.Solution.SetCell(Row,Col,Value): print "Inconceivable!!!" return # # Find the cells with the least possible number of allowed # numbers. # ContinueLooping = True while ContinueLooping: Impossible = False MinAllowable = BOARD_SIZE+1 Row = 0 Col = 0 ContinueLooping = False for CurrentRow in DIMENSION_RANGE: for CurrentCol in DIMENSION_RANGE: if self.Solution.Board[CurrentRow][CurrentCol]>0: continue AllowableMoves = self.Solution.AllowableMoves(CurrentRow,CurrentCol) # # If a cell has no allowable moves, mark Solutions as # Impossible # if AllowableMoves == 0: Impossible = True break # # if a cell has only one possible move, mark that cell # as identified # elif AllowableMoves == 1: CellsToTry.append( (CurrentRow,CurrentCol,0) ) for Value in range(1,BOARD_SIZE+1): if self.Solution.TestCell(CurrentRow,CurrentCol,Value): self.Solution.SetCell(CurrentRow,CurrentCol,Value) break ContinueLooping = True break # # If this cell has a lower number of allowable moves than # our current best, mark it as the best # elif AllowableMoves < MinAllowable: MinAllowable = AllowableMoves Row = CurrentRow Col = CurrentCol # # If no Solution is possible, stop trying to look at this # Board. If we made a change to this Board, start checking it again # from the beginning. # if Impossible or ContinueLooping: break if Impossible: break # # If there are no possible moves, try the next Puzzle # if Impossible: continue # # If a Solution is found, return current Board. # if self.Solution.Test(): return self.Solution # # If there are no possible moves, try the next Puzzle # if self.Solution.Board[Row][Col] > 0: continue CellsToTry.append( (Row,Col,0) ) for Value in RandomRange(1,BOARD_SIZE+1): if self.Solution.TestCell(Row,Col,Value): CellsToTry.append( (Row,Col,Value) ) # # If no Solution is found, return an empty Board. # del CellsToTry self.Solution = None return self.Solution if __name__ == "__main__": app = SudokuApp(0) app.MainLoop()