diff --git a/graphic_components/board.py b/graphic_components/board.py index 33cd850..c8cebde 100644 --- a/graphic_components/board.py +++ b/graphic_components/board.py @@ -1,3 +1,6 @@ +"""This module contains the two boards shown in the program. A base BoxBoard class provides the drawing and animation +of the boards.""" + from PyQt5.QtGui import QPen from PyQt5.QtWidgets import QSizePolicy, QGraphicsWidget from PyQt5.QtCore import (QAbstractAnimation, Qt, QLineF, QPropertyAnimation, pyqtProperty, @@ -8,11 +11,21 @@ from . import menu_graphics as menu_grap class BoxBoard(QGraphicsWidget): - """ - A generic board that draws an animated rectangular border + """A generic board that draws an animated rectangular border """ def __init__(self, width, height, parent=None): + """Prepare the lines to be drawn and set up the animation + + Parameters + ---------- + width: float + Width of the box + height: float + Height of the box + parent: object + Pass into QGraphicsWidget init method + """ super().__init__(parent) self.width = width self.height = height @@ -39,7 +52,6 @@ class BoxBoard(QGraphicsWidget): self.line_order = [self.up, self.right, self.down, self.left] - # Length of the box to be drawn self.length = 0 # Set up the length to be animated self.anim = QPropertyAnimation(self, b'length') @@ -49,25 +61,14 @@ class BoxBoard(QGraphicsWidget): self.anim.setKeyValueAt(t / 10, self.half_circumference * t/10) self.anim.setEndValue(self.half_circumference) - # Toggle the animation to be play forward or backward - def toggle_anim(self, toggling): - if toggling: - self.anim.setDirection(QAbstractAnimation.Forward) - else: - self.anim.setDirection(QAbstractAnimation.Backward) - - self.anim.start() - - # Reimplemented paint - def paint(self, painter, style, widget=None): - painter.setPen(self.default_pen) - for line in self.line_order: - if line.length() > 1: - painter.drawLine(line) - # Defining the length to be drawn as a pyqtProperty @pyqtProperty(float) def length(self): + """float: The length of the box to be drawn + + When the value is set, the length of the lines making up the box + are calculated and updated. + """ return self._length # Determine the length of the four lines to be drawn @@ -87,40 +88,94 @@ class BoxBoard(QGraphicsWidget): self.right.setLine(self.width, self.height, self.width, self.height - remaining_length) self.update() + def toggle_anim(self, toggling): + """Toggle the animation forward and backwards + + Parameters + ---------- + toggling: bool + True for forward, False for backwards + """ + if toggling: + self.anim.setDirection(QAbstractAnimation.Forward) + else: + self.anim.setDirection(QAbstractAnimation.Backward) + + self.anim.start() + + def paint(self, painter, style, widget=None): + """Reimplemented from QGraphicsWdiget paint function. Draws the lines making up the box. + """ + painter.setPen(self.default_pen) + for line in self.line_order: + if line.length() > 1: + painter.drawLine(line) + + + class GameBoard(BoxBoard): - """ - The Board in which the main game takes place. + """The Board in which the main game takes place. It is intended to swap the interface depending on whether the game is ongoing + + Attributes + ---------- + newGameSelected: pyqtSignal(str) + Emitted when the difficulty is selected from here. Emits the difficulty string + gridDrawn: pyqtSignal + Emitted when the Sudoku grid has been drawn + sudokuDone: pyqtSignal + Emitted when the Sudoku puzzle is finished """ - boxClicked = pyqtSignal(bool) newGameSelected = pyqtSignal(str) gridDrawn = pyqtSignal() sudokuDone = pyqtSignal() def __init__(self, width, height, parent=None): + """Create the game area consisting of a Sudoku Grid and a Number Ring, + and a difficulty selector at startup + + Parameters + ---------- + width: float + Passed into BoxBoard init method + height: float + Passed into BoxBoard init method + parent: object + Passed into BoxBoard init method + """ super().__init__(width, height, parent) self.gamegrid = sdk_grap.SudokuGrid(self.width, self.height, parent=self) self.numring = sdk_grap.NumberRing(parent=self) self.playmenu = sdk_grap.PlayMenu(parent=self) + self.gamegrid.setFocus(Qt.MouseFocusReason) self.show_grid(False) self.show_playmenu(False) self.gamegrid.buttonClicked.connect(self.show_number_ring) - self.numring.keyPressed.connect(self.select_ring_number) - - self.gamegrid.setFocus(Qt.MouseFocusReason) - - self.anim.finished.connect(lambda: self.show_playmenu(True)) - self.playmenu.buttonClicked.connect(self.new_game) self.gamegrid.finishDrawing.connect(self.gridDrawn.emit) self.gamegrid.puzzleFinished.connect(self.sudokuDone.emit) self.numring.loseFocus.connect(self.game_refocus) + self.numring.keyPressed.connect(self.select_ring_number) + self.playmenu.buttonClicked.connect(self.new_game) + + self.anim.finished.connect(lambda: self.show_playmenu(True)) self.toggle_anim(True) def show_number_ring(self, x=0, y=0, scribbling=False): + """Display the Number Ring if it is not visible, while setting the focus to it + + Parameters + ---------- + x: float + x coordinate of where to position the Ring + y: float + y coordinate of where to position the Ring + scribbling: + True to set Scribble mode, False otherwise + """ if not self.numring.isVisible(): self.numring.setPos(x, y) self.numring.setVisible(True) @@ -130,6 +185,15 @@ class GameBoard(BoxBoard): self.numring.set_buttons_transparent(False) def select_ring_number(self, val, scribbling): + """Get the selected number from the Ring and pass into the grid + + Parameters + ---------- + val: str + The number string received + scribbling: bool + True to indicate Scribble mode, False otherwise + """ if val == 'X': val = 0 if scribbling: @@ -138,25 +202,50 @@ class GameBoard(BoxBoard): self.gamegrid.replace_cell_number(int(val)) def game_refocus(self): + """Enable the grid and give it grid focus + """ self.gamegrid.set_disabled(False) self.gamegrid.setFocus() - self.gamegrid.scribbling = self.numring.scribbling + self.gamegrid.scribbling = self.numring.scribbling # To update the grid scribbling mode def show_grid(self, state): + """Show the grid, if it is not; Hide the grid, if it is. + Note: Animation only plays when showing the grid. + + Parameters + ---------- + state: bool + True to show the grid, False otherwise + """ if state ^ self.gamegrid.isVisible(): self.gamegrid.setVisible(state) if state: self.gamegrid.toggle_anim(True) def show_playmenu(self, state): + """Show the startup play menu + + Parameters + ---------- + state: bool + True to show the startup play menu, False otherwise + """ self.playmenu.setVisible(state) def new_game(self, string): + """Generate a new Sudoku Board, given the difficulty + + Parameters + ---------- + string: str + The difficulty e.g. Easy + """ self.gamegrid.generate_new_grid(menu_grap.DIFFICULTIES.index(string)) self.show_grid(True) self.newGameSelected.emit(string) def paint(self, painter, style, widget=None): + """Reimplemented from BoxBoard paint method. Draw the instruction to toggle scribble mode """ super().paint(painter, style, widget) painter.drawText(QRectF(0, self.height+15, self.width, 15), Qt.AlignCenter, @@ -164,8 +253,7 @@ class GameBoard(BoxBoard): class MenuBoard(BoxBoard): - """ - The Board that contains menu options. Also contains the timer. + """The Board that contains difficulty options, timer, and high scores. """ def __init__(self, width, height, parent=None): @@ -173,8 +261,10 @@ class MenuBoard(BoxBoard): self.margin = 10 self.spacing = 20 - w_spacing = (self.width - 2*self.margin) /3 + w_spacing = (self.width - 2*self.margin) / 3 + # Create the components and manually position them + # Not bothered to use the layout item self.diff_display = menu_grap.DifficultyDisplayer(parent=self) self.diff_display.setX(self.margin) self.diff_display.setY(self.geometry().height()/2-self.diff_display.height/2) @@ -191,20 +281,27 @@ class MenuBoard(BoxBoard): self.show_children(False) self.toggle_anim(True) - def show_difficulty(self, state): - self.diff_display.selected = state - self.diff_display.update() - def show_children(self, state): + """Show the timer and the difficulty button + + Parameters + ---------- + state: bool + True to show them, False otherwise + """ self.timer_display.setVisible(state) self.diff_display.setVisible(state) self.timer_display.reset_time() def set_difficulty_text(self, string): + """Change the difficulty to be display and reset the timer + """ self.diff_display.set_text(string) self.timer_display.reset_time() def finish_the_game(self): + """Stop the timer and prepare the high scores if necessary. Should only happen when the puzzle is finished + """ self.timer_display.timer.stop() diff = self.diff_display.text time = self.timer_display.get_time() @@ -214,5 +311,6 @@ class MenuBoard(BoxBoard): self.score_display.show_board(True) def return_to_normal(self): + """Reenable the difficulty and high score buttons. Used after setting the high scores""" self.diff_display.set_disabled(False) self.score_display.set_disabled(False) diff --git a/main.py b/main.py index 71c968a..169e189 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,4 @@ -""" -This is the main module to be run. Contains the program itself. +"""This is the main module to be run. Contains the program itself. """ from PyQt5.QtGui import QPainter, QBrush @@ -12,8 +11,7 @@ from graphic_components import board class SudokuWindow(QGraphicsView): - """ - The main window that shows the Sudoku Board and the Menu Board. + """The main window that shows the Sudoku Board and the Menu Board. """ def __init__(self): @@ -50,8 +48,7 @@ class SudokuWindow(QGraphicsView): self.menuboard.diff_display.difficultySelected.connect(self.gameboard.new_game) def resizeEvent(self, event): - """ - Reimplemented from QGraphicsView. Resize and maintain the board aspect ratio. + """Reimplemented from QGraphicsView. Resize and maintain the board aspect ratio. """ self.fitInView(self.scene.sceneRect(), Qt.KeepAspectRatio) super().resizeEvent(event)