def optionsHandler(self, event): """ Displays options to the user, including the type of draw, the scoring, and whether the game is timed. """ self.optionsDialog = OptionsDialog(self.player, self.options) self.optionsDialog.set_OKhandler(self.options_OK) self.optionsDialog.set_cancelHandler(self.options_cancel) self.optionsDialog.CenterOnParent() self.optionsDialog.ShowModal()
class Control(object): """ The Control class sends commands to the model to change the state of the Solitaire game, and to the view to update the visual aspect of the game. """ def __init__(self): """ Initializes a new Control for the Solitaire game. Sets all event handlers for the GUI. """ app = wx.App(False) # Seconds elapsed since the epoch until when the application # is launched. For use in the Status Bar. self.current = time.time() self.preferences = DEFAULT_PREFERENCES # Create Model and View instances. self.dealer = Dealer() self.player = self.dealer.player self.options = self.player.options self.gui = Frame() # Start the game by dealing the cards. self.dealer.deal() self.resetSrcMove() self.resetDestMove() self.drag_image = None # Set all event handlers for the Frame, the main Panel, # the PilePanels, the MenuBar, and the StatusBar. self.setHandlers() # Show the Frame and run the app. self.gui.Show() app.MainLoop() def setHandlers(self): """ Set all event handlers for the Frame, the main Panel, the PilePanels, the MenuBar, and the StatusBar. """ self.setFrameHandlers() self.setPilePanelHandlers() self.setMenuBarHandlers() self.setStatusBarHandlers() def setFrameHandlers(self): """ Sets all event handlers for the Frame. """ self.gui.setOnPaintHandler(self.frameOnPaint) self.gui.setOnSizeHandler(self.frameOnRepaint) self.gui.setPanelOnPaint(self.panelOnPaint) self.gui.setOnCharHandler(self.charPressedHandler) self.gui.setOnLeftUpHandler(self.frameOnLeftUp) self.gui.setPanelOnLeftUp(self.frameOnLeftUp) self.gui.setPanelOnRightDown(self.frameOnRightDown) self.gui.setOnMotionHandler(self.frameOnMotion) def setPilePanelHandlers(self): """ Sets the event handlers for the PilePanels. """ # Set all event handlers for the Stock panel. self.gui.stock_panel.setOnPaintHandler(self.pilePanelOnPaint) self.gui.stock_panel.setOnLeftDownHandler(self.stockOnLeftDown) # Set all event handlers for the Waste panel. self.gui.waste_panel.setOnPaintHandler(self.pilePanelOnPaint) self.gui.waste_panel.setOnLeftDownHandler(self.pilePanelOnLeftDown) self.gui.waste_panel.setOnLeftUpHandler(self.pilePanelOnLeftUp) self.gui.waste_panel.setOnLeftDoubleHandler(self.pilePanelOnRightDown) self.gui.waste_panel.setOnRightDownHandler(self.pilePanelOnRightDown) # Set all event handlers for the Foundation panels. for suit in Suit.SUITS: fpanel = self.gui.foundation_panels[suit] fpanel.setOnPaintHandler(self.pilePanelOnPaint) fpanel.setOnLeftDownHandler(self.pilePanelOnLeftDown) fpanel.setOnLeftUpHandler(self.pilePanelOnLeftUp) # Set all event handlers for the Tableau panels. for tab in self.gui.tableau_panels: tab.setOnPaintHandler(self.pilePanelOnPaint) tab.setOnLeftDownHandler(self.pilePanelOnLeftDown) tab.setOnLeftUpHandler(self.pilePanelOnLeftUp) tab.setOnLeftDoubleHandler(self.pilePanelOnRightDown) tab.setOnRightDownHandler(self.pilePanelOnRightDown) def setMenuBarHandlers(self): """ Sets all event handlers for the MenuBar. """ # Set all event handlers for the Game menu items. self.gui.setNewGameHandler(self.newGameHandler) self.gui.setUndoHandler(self.undoMenuHandler) self.gui.setHintMenuHandler(self.hint) self.gui.setStatisticsHandler(self.statisticsHandler) self.gui.setOptionsHandler(self.optionsHandler) self.gui.setPreferencesHandler(self.preferencesHandler) self.gui.setChangeUserHandler(self.changeUserHandler) self.gui.setExitHandler(self.closewindow) # Set all event handlers for the Help menu items. self.gui.setHowToPlayHandler(self.howtoplaywindow) self.gui.setAboutHandler(self.aboutwindow) def setStatusBarHandlers(self): """ Initializes the StatusBar by setting the event handler for the timer and redirecting standard output to the fourth field of the StatusBar. """ # Initialize the timer in the status bar, and # set its handler. self.gui.setTimerHandler(self.start_timer) # Redirect stdout to the 4th StatusBar Field. redirection = RedirectText(self.gui.GetStatusBar()) sys.stdout = redirection def frameOnPaint(self, event): """ Paints the background image of the Frame, and calls Update() on each of the panels to update the cards they contain. """ # Create a new Device Context for drawing # graphics; AutoBuffered to prevent flickering. dc = wx.AutoBufferedPaintDC(self.gui) # Scale the background image to fit the size of the Frame. # Draw the new background image, and update all pile Panels. self.gui.paintBackground(dc) del dc self.gui.updateChildren() def frameOnRepaint(self, event): """ Repaints the background image of the Frame on a resize event. """ self.gui.Layout() self.gui.Refresh(eraseBackground=False) def frameOnLeftUp(self, event): """ The event handler for when the left mouse button is released on the Frame. """ (x, y) = event.GetPosition() pLeft, pTop = self.gui.panel.GetPosition() for panel in self.gui.panels: l, t, w, h = panel.GetRect() (left, top) = (l + pLeft, t + pTop) (right, bottom) = (left + w, top + h) if x >= left and x <= right and y <= bottom and y >= top: self.pileFinishMove(panel) break if (self.clickedPile is not None and self.srcPile is not None and self.destPile is None): self.dealer.push_pile(self.clickedPile, self.srcPile, self.destPile) self.srcPanel.Refresh() self.resetSrcMove() self.resetDestMove() self.endDrag() def frameOnRightDown(self, event): """ The event handler for when the right mouse button is pressed on the Frame. """ if self.dealer.can_autocomplete(): self.autocomplete() def frameOnMotion(self, event): """ The event handler for when the mouse is moving on the Frame. """ if self.drag_image is not None and event.LeftIsDown(): self.drag_image.Show() (x, y) = event.GetPosition() self.drag_image.Move((x, y)) return def panelOnPaint(self, event): """ Paints the background image of the Panel. """ # Create a new Device Context for drawing # graphics; AutoBuffered to prevent flickering. dc = wx.AutoBufferedPaintDC(self.gui.panel) newbg = self.gui.scaleBackground() dc.DrawBitmap(newbg.GetSubBitmap(self.gui.panel.GetRect()), 0, 0) def pilePanelOnPaint(self, event): """ Paints the Cards in the Pile associated with a PilePanel. """ panel = event.GetEventObject() pile = self.getPile(panel) # Create a new Device Context for drawing # graphics; AutoBuffered to prevent flickering. dc = wx.AutoBufferedPaintDC(panel) panel.paintPile(dc, self.gui.scaleBackground(), pile) def pilePanelOnLeftDown(self, event): """ The event handler for when the left mouse button is pressed on a PilePanel. """ (x, y), panel, pile = self.startMove(event) if panel.inClickableRange(x, y): card_idx = panel.findCardAt(x, y) if card_idx is not None: coords = panel.get_card_coord(x, y) self.dragPos = (coords[0], coords[1]) self.setSrcMove(pile[card_idx], panel, pile) self.srcPanel.Refresh() self.beginDrag(event) def stockOnLeftDown(self, event): """ The event handler for when the left mouse button is pressed on the Stock Panel. """ (x, y), spanel, spile = self.startMove(event) self.setSrcMove(None, spanel, spile) wpanel = self.gui.waste_panel wpile = self.getPile(wpanel) self.setDestMove(wpanel, wpile) if spanel.inClickableRange(x, y): # Draw cards from the Stock, and tell the Waste Panel # how many cards to fan. self.dealer.draw() wpanel.squared = max(len(wpile) - 3, wpanel.squared, 0) self.endMove() def pilePanelOnLeftUp(self, event): """ The event handler for when the left mouse button is released on a PilePanel. """ # Find out which pile the mouse was released on. _, panel, pile = self.startMove(event) if self.srcPanel is None: return # Tell the dealer to make the move. self.setDestMove(panel, pile) self.dealer.push_pile(self.clickedPile, self.srcPile, self.destPile) self.endMove() self.endDrag() def pileFinishMove(self, panel): """ """ pile = self.getPile(panel) if self.srcPanel is None: return # Tell the dealer to make the move. self.setDestMove(panel, pile) self.dealer.push_pile(self.clickedPile, self.srcPile, self.destPile) self.endMove() self.endDrag() def pilePanelOnRightDown(self, event): """ Pushes the card on top of a pile to its respective Foundation pile, if it is suitable. """ (x, y), panel, pile = self.startMove(event) if (pile.is_empty() or not panel.inClickableRange(x, y)): event.Skip() return # Grab the card that was clicked. card_idx = panel.findCardAt(x, y) self.setSrcMove(pile[card_idx], panel, pile) # Find the appropriate foundation pile. card_suit = self.clickedCard.suit fpanel = self.gui.foundation_panels[card_suit] fpile = self.getPile(fpanel) self.setDestMove(fpanel, fpile) # Make the move. self.dealer.push_pile(self.clickedPile, self.srcPile, self.destPile) self.endMove() def pilePanelOnMotion(self, event): """ The event handler for when the mouse is moving on a Pile Panel. """ if self.drag_image is not None and event.LeftIsDown(): (x, y) = event.GetPosition() self.drag_image.Move((x, y)) return event.Skip() def startMove(self, event): """ Sets the variables for the beginning of a move. """ (x, y) = event.GetPosition() panel = event.GetEventObject() pile = self.getPile(panel) self.gui.SetStatusText('', 4) return (x, y), panel, pile def doMove(self): """ """ pass def endMove(self): """ Refreshes the Panels where cards were moved, updates moves and score, and resets the source/destination variables. Checks if the player won. """ # Mark the source and destination Panels as 'dirty' so # that they will be repainted on the next paint event. self.srcPanel.Refresh() self.destPanel.Refresh() # Update the Status Bar fields. self.set_moves_text() self.set_score_text() # Reset the pile variables and check if the Player won. self.resetSrcMove() self.resetDestMove() self.checkEndGame() def beginDrag(self, event): """ Begins dragging the image representing the pile the player popped. """ (x, y) = event.GetPosition() if self.clickedPile is not None: pileBMP = self.gui.pileBMP(self.clickedPile) self.drag_image = wx.DragImage(pileBMP) xPos, yPos = self.dragPos hotspot = (x - xPos, y - yPos) self.drag_image.BeginDrag(hotspot, self.gui) self.drag_image.Hide() self.gui.Update() def endDrag(self): """ Ends the drag. """ if self.drag_image is not None: self.drag_image.Hide() self.drag_image.EndDrag() self.gui.Update() self.drag_image = None def getPile(self, panel): """ Gets the Pile associated with a Pile Panel. """ name = panel.GetName() if name == 'Waste': return self.dealer.board.waste if name == 'Stock': return self.dealer.board.stock if name in Suit.SUITS: return self.dealer.board.foundations[name] if int(name) in range(0,7): return self.dealer.board.tableaux[int(name)] def getPanel(self, pile): """ Gets the Panel associated with a Pile. """ name = pile.name if name == 'Waste': return self.gui.waste_panel if name == 'Stock': return self.gui.stock_panel if name in Suit.SUITS: return self.gui.foundation_panels[name] if name.find('Tableau') >= 0: return self.gui.tableau_panels[pile.id_] def setSrcMove(self, clickedCard, srcPanel, srcPile): """ Sets the source variables. """ self.clickedCard = clickedCard self.srcPanel = srcPanel self.srcPile = srcPile self.clickedPile = self.dealer.pop_pile(self.clickedCard, self.srcPile) def setDestMove(self, destPanel, destPile): """ Sets the destination variables. """ self.destPanel = destPanel self.destPile = destPile def resetSrcMove(self): """ Sets the source Panel, source Pile, and clicked card to None. """ self.srcPanel = None self.srcPile = None self.clickedCard = None self.clickedPile = None def resetDestMove(self): """ Sets the destination Panel and destination Pile to None. """ self.destPanel = None self.destPile = None def start_timer(self, event): """ Sets the text in the GUI's StatusBar Time Field to the number of seconds elapsed since the application was launched. """ t = "Time:\t" + str(int(time.time() - self.current)) self.gui.SetStatusText(t, 1) def stop_timer(self): """ Stops the timer and updates the current time so that a new game can begin. """ self.gui.timer.Stop() def set_score_text(self): """ Sets the text in the GUI's status bar Score field to the score of the current game. """ self.gui.setScoreText(self.player.current_score) def set_moves_text(self): """ Sets the text in the Moves StatusBar Field to be the player's current number of moves. """ self.gui.setMovesText(self.player.current_moves) def set_player_data(self): """ Sets fields in the StatusBar relevant to the Player's data. """ self.player.current_time = self.gui.getTimeText() self.player.current_moves = self.gui.getMovesText() self.player.current_score = self.gui.getScoreText() def autocomplete(self): """ Finishes the game by moving all cards to their Foundation piles. """ piles = [] piles.append(self.dealer.board.waste) piles.extend(self.dealer.board.tableaux) all_empty = self.dealer.all_empty() while not all_empty: for pile in piles: if pile.is_empty(): continue # Grab the card at the top of the pile. card = pile.peek() panel = self.getPanel(pile) self.setSrcMove(card, panel, pile) self.gui.SetStatusText('', 4) self.srcPanel.Refresh() # Send it to its respective Foundation pile. fpanel = self.gui.foundation_panels[card.suit] fpile = self.dealer.board.foundations[card.suit] self.setDestMove(fpanel, fpile) self.dealer.push_pile(self.clickedPile, self.srcPile, self.destPile) self.endMove() # Update the panels. panel.Update() fpanel.Update() all_empty = self.dealer.all_empty() def checkEndGame(self): """ If the player has completed all four foundation panels, end the game. """ if self.dealer.player_won(): self.gui.updatePilePanels() winDialog = wx.MessageDialog(self.gui, 'You win!', 'Congratulations!', wx.OK) winDialog.CenterOnParent() winDialog.Show() self.gui.paintBouncingCards() self.endGame() def endGame(self): """ Stops the timer, sends the current game information to the player to be uploaded to the database. """ self.stop_timer() self.set_player_data() self.gui.refreshChildren() if self.dealer.player_won(): self.player.end_game(win=1) else: self.player.end_game(win=0) def newGameHandler(self, event): """ Opens a dialog message asking if the user wants to start a new game. """ idx = wx.MessageBox(message="Start a new game?", caption='New Game', style=wx.YES_NO|wx.NO_DEFAULT|wx.ICON_QUESTION|wx.CENTER_FRAME, parent=self.gui) if idx == wx.YES: self.endGame() self.startNewGame() def startNewGame(self): """ Initializes the model and view for a new game. """ self.dealer.new_game() self.gui.resetStatusText() self.current = time.time() self.gui.timer.Start() dc = wx.ClientDC(self.gui) self.gui.paintBackground(dc) for panel in self.gui.panels: panel.clearCardCoords() panel.Refresh() def undoMove(self): """ Undoes the last move. """ if self.dealer.undo() == False: return self.set_score_text() self.set_moves_text() self.gui.refreshPilePanels(False) def undoMenuHandler(self, event): """ Undoes the last move when 'Undo' is selected from the MenuBar. """ self.undoMove() def hint(self, event): """ Prints out the next possible move in the StatusBar. """ self.dealer.next_move() def charPressedHandler(self, event): """ The handler for when either of H or Ctrl+Z are pressed. """ keyCode = event.GetKeyCode() controlDown = event.CmdDown() # Handle when H is pressed - Display a Hint. if keyCode == ord('H') and not controlDown: return self.hint(event) # Handle when Ctrl+Z is pressed - Undo the last move. elif controlDown and keyCode == ord('Z'): return self.undoMove() event.Skip() def statisticsHandler(self, event): """ Displays a dialog with statistics for a user. """ self.statsDialog = StatisticsDialog(self.player) self.statsDialog.set_OKhandler(self.statistics_OK) self.statsDialog.set_ResetHandler(self.statistics_Reset) self.statsDialog.CenterOnParent() self.statsDialog.ShowModal() def optionsHandler(self, event): """ Displays options to the user, including the type of draw, the scoring, and whether the game is timed. """ self.optionsDialog = OptionsDialog(self.player, self.options) self.optionsDialog.set_OKhandler(self.options_OK) self.optionsDialog.set_cancelHandler(self.options_cancel) self.optionsDialog.CenterOnParent() self.optionsDialog.ShowModal() def preferencesHandler(self, event): """ Displays a dialog with preferences for changing card backs and the background image. """ card_backs = self.dealer.board.getCardBacks() background_img = self.gui.backgroundFilename preferences = {'Card Back': card_backs, 'Background': background_img} self.prefsDialog = PreferencesDialog(prefs=preferences) self.prefsDialog.setUploadCardBackBtnHandler(self.prefsDialog.uploadCardBackHandler) self.prefsDialog.setUploadBackgroundBtnHandler(self.prefsDialog.uploadBackgroundHandler) self.prefsDialog.setOKButtonHandler(self.preferences_OK) self.prefsDialog.setCancelButtonHandler(self.preferences_CANCEL) self.prefsDialog.CenterOnParent() self.prefsDialog.ShowModal() def preferences_OK(self, event): """ Changes the card backs and background image if the user changed the preferences. """ newCardBack = self.prefsDialog.newCardBack newBackground = self.prefsDialog.newBackground self.changeCardBacks(newCardBack) self.changeBackgroundImage(newBackground) self.prefsDialog.Destroy() def preferences_CANCEL(self, event): """ Closes the PreferencesDialog. """ self.prefsDialog.Destroy() def changeUserHandler(self, event): """ Displays a dialog to the user to change the current Player based on the entered username. """ usrDialog = wx.TextEntryDialog(self.gui, 'What is your username?', 'Change Username', self.player.username) usrDialog.ShowModal() new_username = usrDialog.GetValue() self.dealer.change_player(new_username) self.player = self.dealer.player self.startNewGame() def changeCardBacks(self, filename): """ Updates the card back images and refreshes the PilePanels. """ self.dealer.board.setCardBacks(filename) self.gui.refreshPilePanels() def changeBackgroundImage(self, filename): """ Updates the background image and refreshes the Frame and all Panels. """ self.gui.changeBackgroundImage(filename) def closewindow(self, event): """ Stops the timer and closes the window. """ self.endGame() self.player.db.close_connection() if self.optionsDialog is not None: self.optionsDialog.Destroy() if self.statisticsHandler is not None: self.statisticsHandler.Destroy() self.gui.Destroy() self.gui.Close() def options_onDrawSelection(self, event): """ Update the options for this game. """ self.optionsDialog.selectedOptions['draw'] = self.optionsDialog.drawRB.GetSelection() def options_OK(self, event): """ When the OK button is pressed in the Options Dialog, the user is prompted to begin a new game if any options were changed. """ updatedOptions = self.optionsDialog.selectedOptions updatedDraw = updatedOptions['draw'] updatedScoring = updatedOptions['scoring'] currDraw = self.player.options['draw'] currScoring = self.player.options['scoring'] if updatedDraw != currDraw: idx = wx.MessageBox(message="Changing the type of draw requires starting a new game.\n\ The current game will be counted as a loss.\n\ Would you like to start a new game?", caption='New Game', style=wx.YES_NO|wx.NO_DEFAULT|wx.ICON_QUESTION, parent=None) if idx == wx.NO: self.optionsDialog.drawRB.SetSelection(self.options['draw']) else: self.player.update_options(updatedOptions) self.options = self.player.options self.endGame() self.startNewGame() if updatedScoring != currScoring: idx = wx.MessageBox(message="Changing the scoring requires starting a new game.\n\ The current game will be counted as a loss.\n\ Would you like to start a new game?", caption='New Game', style=wx.YES_NO|wx.NO_DEFAULT|wx.ICON_QUESTION, parent=None) if idx == wx.NO: self.optionsDialog.scoringRB.SetSelection(self.options['scoring']) else: self.optionsDialog.Destroy() def options_cancel(self, event): """ When the Cancel button is pressed in the Options Dialog, the Dialog window is destroyed, and the changed options are not saved. """ self.optionsDialog.Destroy() def statistics_OK(self, event): """ Pressing the OK button on the statisticsHandler dialog simply closes the window. """ self.statsDialog.Destroy() def statistics_Reset(self, event): """ Pressing the Reset button on the Statistics dialog resets the statisticsHandler for the user and closes the window. """ self.player.reset_stats() self.statsDialog.Destroy() def aboutwindow(self, event): """ Displays information about this version of solitaire, and Copyright/license information. """ description = """Solitaire is a virtual card game for the Windows operating system. Features include comprehensive statisticsHandler, a beautiful graphical user interface, and changing appearance. """ licence = """Solitaire is free software; you may\nredistribute and/or modify it.""" info = wx.AboutDialogInfo() info.SetIcon(wx.Icon(ABOUT_ICON_PATH, wx.BITMAP_TYPE_PNG)) info.SetName('Solitaire') info.SetVersion('1.0') info.SetCopyright('(C) 2013 Elizabeth Jakubowski') info.SetLicence(licence) info.AddDeveloper('Elizabeth Jakubowski') info.AddDocWriter('Elizabeth Jakubowski') info.SetDescription(description) wx.AboutBox(info) def howtoplaywindow(self, event): """ A Dialog for how to play Solitaire and the rules. """ self.howToPlay = HowToPlayDialog() self.howToPlay.CenterOnParent() self.howToPlay.Show()