import random import wx import dabo.ui dabo.ui.loadUI("wx") from dabo.dLocalize import _ # Dabo MineSweeper # This is a demo of Dabo's UI - no bizobj or database layer. # # Begun 10/22/2004 while I was waiting for a program to compile. # I wanted to see how useful dEditor was, so I developed minesweeper # completely with dEditor. It isn't finished, but is playable. The # code needs to be cleaned/refactored in places. But it does use the # Dabo UI exclusively. # # My idea is to make a network-playable "battle minesweeper" where 2 or # more users can play the same puzzle and see who can finish first. Each # user's form would show how many mines their opponent has cleared. # # Another idea is to make a network "team minesweeper" where 2 or more # players can collaborate on clearing the same minefield. Each user's # minefield would get updated as other players flag mines and clear # squares. # # Perhaps the high-scores can be recorded using a database/bizobj just # to stay relevant with the Dabo project. # # Anyway, this is also a good test for Dabo's performance, as each square # is a dButton: make a 20x20 grid and all of a sudden you are # instantiating 200 dButtons, each responding to mouse clicks. # # (if someone really wants to speed it up, I'd suggest using a DC to draw # directly on the panel, and divining the current square based on the # position of the mouse click). # # pkm _defaultBoardSize = (8,8) _defaultMineCount = 10 _mineMarkChar = "@" class StateChanged(dabo.dEvents.Event): pass class Square(dabo.ui.dButton): def initProperties(self): Square.doDefault() self.Size = (35,35) self.Caption = "" def cycleState(self): if self.State == "UnMarked": self.State = "MarkedMine" elif self.State == "MarkedMine": self.State = "QuestionMarked" elif self.State == "QuestionMarked": self.State = "UnMarked" else: self.State = "Cleared" def _getState(self): """Returns one of: "UnMarked", "MarkedMine", "QuestionMarked", "Cleared" """ try: state = self._state except AttributeError: state = self._state = "UnMarked" return state def _setState(self, val): """Sets State to one of: + "UnMarked" + "MarkedMine" + "QuestionMarked" + "Cleared" Side Effects: + Caption set to one of "", , "?" + Raises StateChanged event """ self._state = val self.Caption = {"UnMarked": "", "MarkedMine": _mineMarkChar, "QuestionMarked": "?", "Cleared": ""}[val] self.raiseEvent(StateChanged) State = property(_getState, _setState, None, "") class Board(dabo.ui.dPanel): def afterInit(self): Board.doDefault() self.Sizer = dabo.ui.dSizer("vertical") def initEvents(self): Board.doDefault() self.bindEvent(dabo.dEvents.Create, self.onCreate) def newGame(self): import time beg = time.time() self._makeBoardDict() self._fillBoard() self.layout() end = time.time() print "Board created in %f second(s)." % (end-beg,) def onHit(self, evt): o = evt.EventObject if o.Caption not in ("@", "?"): # if the user has marked the square, don't detonate: it could have # been a mistake. self.showSquare(o.square) self.checkWin() def checkWin(self): """See if the player has won the game. """ check = True if self.allCleared(): for sq in self._boardDict.keys(): square = self._boardDict[sq] if square["mine"]: if square["obj"].Caption == "@": pass else: check = False break if check: dabo.ui.dMessageBox.info("You win!") dabo.ui.callAfter(self.Form.newGame) def allCleared(self): """Return True if all non-mine squares have been cleared. """ bd = self._boardDict for sq in bd.keys(): square = bd[sq] if not square["mine"]: if ((square["adjacent"] == 0 and square["obj"].Visible == False) or square["adjacent"] > 0 and square["obj"].Enabled == False): pass else: return False return True def onRightClick(self, evt): o = evt.EventObject o.cycleState() def showSquare(self, square): i = self._boardDict[square] o = i["obj"] if i["mine"]: o.Caption = "M" self.showAllSquares() dabo.ui.dMessageBox.stop("You lose!") dabo.ui.callAfter(self.Form.newGame) else: a = i["adjacent"] #- print "showSquare, adjacent:", a if a == 0: # recursively clear all adjacent 0 squares self.clearZeros(o.square) else: o.Caption = str(a) o.Enabled = False o.unBindEvent(dabo.dEvents.Hit) def showAllSquares(self): bd = self._boardDict for sq in bd.keys(): o = bd[sq]["obj"] if bd[sq]["mine"]: o.Caption = "M" o.Enabled = False elif bd[sq]["adjacent"] == 0: o.Visible = False else: o.Caption = bd[sq]["adjacent"] o.Enabled = False def clearZeros(self, square): bd = self._boardDict if bd[square]["adjacent"] == 0: bd[square]["obj"].Visible = False #- print "Square: %s | Adj.: %s" % (square, self.getAdjacentSquares(square)) for sq in self.getAdjacentSquares(square): if bd[sq]["adjacent"] == 0 \ and bd[sq]["mine"] == False \ and bd[sq]["obj"].Visible: bd[sq]["obj"].Visible = False self.clearZeros(sq) else: if bd[sq]["obj"].Visible and bd[sq]["adjacent"] > 0: bd[sq]["obj"].Caption = str(bd[sq]["adjacent"]) bd[sq]["obj"].Enabled = False def _makeBoardDict(self): self._boardDict = {} width = self.BoardSize[0] height = self.BoardSize[1] for h in range(height): for w in range(width): self._boardDict[(w,h)] = {"mine": False, "flag": False, "adjacent": 0} self._fillMines() self._fillAdjacentCounts() def onCreate(self, evt): self.BoardSize = _defaultBoardSize def _fillMines(self): r = random.Random() r.seed() bc = self.MineCount squares = self._boardDict.keys() if bc > len(squares): bc = self.MineCount = len(squares) mines = random.sample(squares, bc) for mine in mines: self._boardDict[mine]["mine"] = True def _fillAdjacentCounts(self): for key in self._boardDict.keys(): as = self.getAdjacentSquares(key) c = 0 for s in as: if self._boardDict[s]["mine"]: c += 1 self._boardDict[key]["adjacent"] = c def _fillBoard(self): cols = self.BoardSize[0] rows = self.BoardSize[1] rowSizer = dabo.ui.dSizer("vertical") for row in range(rows): colSizer = dabo.ui.dSizer("horizontal") for col in range(cols): o = self.addObject(Square, "square_%s_%s" % (col, row)) o.square = (col, row) self._boardDict[(col, row)]["obj"] = o o.bindEvent(dabo.dEvents.Hit, self.onHit) o.bindEvent(dabo.dEvents.MouseRightClick, self.onRightClick) o.bindEvent(StateChanged, self.onStateChanged) colSizer.append(o, alignment="center", border=3) rowSizer.append(colSizer) self.Sizer = rowSizer def onStateChanged(self, evt): self.checkWin() def getAdjacentSquares(self, square): row, col = square[1], square[0] adj = [] for i in (-1,0,1): for j in (-1,0,1): r = j+row c = i+col if r >= 0 and r < self.BoardSize[1] \ and c >= 0 and c < self.BoardSize[0] \ and (c,r) != square: adj.append((c,r)) #- print square, adj return tuple(adj) def _getBoardSize(self): try: bs = self._boardSize except AttributeError: bs = self._boardSize = _defaultBoardSize return bs def _setBoardSize(self, size): assert type(size) in (list, tuple) assert len(size) == 2 assert (type(size[0]), type(size[1])) == (int, int) self._boardSize = size def _getMineCount(self): try: bc = self._mineCount except AttributeError: bc = self._mineCount = _defaultMineCount return bc def _setMineCount(self, count): assert type(count) == int self._mineCount = count BoardSize = property(_getBoardSize, _setBoardSize, None, "Sets the dimension of the board. (w,h)") MineCount = property(_getMineCount, _setMineCount, None, "Sets the number of mines on the board.") class Form(dabo.ui.dForm): def afterInit(self): Form.doDefault() self.Caption = "Dabo MineSweeper" self.Sizer = dabo.ui.dSizer("vertical") self.addObject(Board, "board") self.Sizer.append(self.board, "expand", 1) self.fillMenu() def initEvents(self): Form.doDefault() self.bindEvent(dabo.dEvents.Create, self.onNewGame) # self.bindEvent(NewGame, self.onNewGame) def onChangeSettings(self, evt): dlg = NewGameDlg(self) dlg.spnWidth.Value = self.board.BoardSize[0] dlg.spnHeight.Value = self.board.BoardSize[1] dlg.spnMines.Value = self.board.MineCount dlg.show() if dlg.accepted: #- self.board.release() #- self.addObject(Board, "board") #- self.GetSizer().append(self.board, "expand", 1) self.board.BoardSize = (dlg.spnWidth.Value, dlg.spnHeight.Value) self.board.MineCount = dlg.spnMines.Value #- self.board.newGame() #- self.GetSizer().layout() dlg.release() def onNewGame(self, evt): self.newGame() def newGame(self): bs = self.board.BoardSize mc = self.board.MineCount self.board.release() self.addObject(Board, "board") self.Sizer.append(self.board, "expand", 1) self.board.BoardSize = bs self.board.MineCount = mc self.board.newGame() self.layout() def fillMenu(self): mb = self.MenuBar fileMenu = mb.getMenu(_("File")) editMenu = mb.getMenu(_("Edit")) viewMenu = mb.getMenu(_("View")) dIcons = dabo.ui.dIcons fileMenu.prependSeparator() fileMenu.prepend(_("&Settings"), help=_("Change settings"), bindfunc=self.onChangeSettings, bmp="preferences") fileMenu.prependSeparator() fileMenu.prepend(_("&New Game\tCtrl+N"), help=_("Start a new game"), bindfunc=self.onNewGame, bmp="new") class NewGameDlg(dabo.ui.dDialog): def initStyleProperties(self): self.ShowCloseButton = True self.ShowCaption = True self.BorderResizable = True def afterInit(self): NewGameDlg.doDefault() self.accepted = False self.Modal = True def addControls(self): class lbl(dabo.ui.dLabel): def initStyleProperties(self): self.Alignment = "Right" self.AutoResize = False def initProperties(self): self.Width = 100 b = 5 vs = dabo.ui.dSizer("vertical") for name in ("Width", "Height", "Mines"): hs = dabo.ui.dSizer("horizontal") l = lbl(self, name="lbl%s" % name) l.Caption = "%s:" % name s = self.addObject(dabo.ui.dSpinner, "spn%s" % name) hs.append(l, "fixed", alignment="right", border=b) hs.append(s, border=b) vs.append(hs) hs = dabo.ui.dSizer("horizontal") for name in ("Accept", "Cancel"): c = self.addObject(dabo.ui.dButton, "cmd%s" % name) c.Caption = name hs.append(c, border=b) vs.append(hs, "expand", 1) self.Sizer = vs self.cmdAccept.bindEvent(dabo.dEvents.Hit, self.onAccept) self.cmdCancel.bindEvent(dabo.dEvents.Hit, self.onCancel) def onAccept(self, evt): self.accepted = True self.hide() def onCancel(self, evt): self.hide() app = dabo.dApp() app.MainFormClass = Form app.setup() app.start()