SudokuGame/graphic_components/board.py

219 lines
7.7 KiB
Python

from PyQt5.QtGui import QPen
from PyQt5.QtWidgets import QSizePolicy, QGraphicsWidget
from PyQt5.QtCore import (QAbstractAnimation, Qt, QLineF, QPropertyAnimation, pyqtProperty,
pyqtSignal, QSizeF, QRectF)
from . import sudoku_graphics as sdk_grap
from . import menu_graphics as menu_grap
class BoxBoard(QGraphicsWidget):
"""
A generic board that draws an animated rectangular border
"""
def __init__(self, width, height, parent=None):
super().__init__(parent)
self.width = width
self.height = height
self.half_circumference = width+height
self.freeze = False
self.setMinimumSize(QSizeF(width, height))
#self.setMaximumSize(QSizeF(width, height))
self.size_policy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
self.size_policy.setHeightForWidth(True)
self.setSizePolicy(self.size_policy)
# Set up a default pen for drawing
self.default_pen = QPen()
self.default_pen.setColor(Qt.white)
self.default_pen.setWidth(5)
# The 4 lines to construct the box
self.left = QLineF(0, 0, 0, self.height)
self.down = QLineF(0, self.height, self.width, self.height)
self.right = QLineF(self.width, self.height, self.width, 0)
self.up = QLineF(self.width, 0, 0, 0)
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')
self.anim.setDuration(800) # Animation speed
self.anim.setStartValue(0)
for t in range(1, 10):
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):
return self._length
# Determine the length of the four lines to be drawn
@length.setter
def length(self, value):
self._length = value
remaining_length = value
if remaining_length >= self.height:
length_to_draw = remaining_length - self.height
remaining_length -= length_to_draw
else:
length_to_draw = 0
self.down.setLine(0, self.height, length_to_draw, self.height)
self.up.setLine(self.width, 0, self.width - length_to_draw, 0)
self.left.setLine(0, 0, 0, remaining_length)
self.right.setLine(self.width, self.height, self.width, self.height - remaining_length)
self.update()
class GameBoard(BoxBoard):
"""
The Board in which the main game takes place.
It is intended to swap the interface depending on whether the game is ongoing
"""
boxClicked = pyqtSignal(bool)
newGameSelected = pyqtSignal(str)
gridDrawn = pyqtSignal()
sudokuDone = pyqtSignal()
def __init__(self, width, height, parent=None):
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.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.toggle_anim(True)
def show_number_ring(self, x=0, y=0, scribbling=False):
if not self.numring.isVisible():
self.numring.setPos(x, y)
self.numring.setVisible(True)
self.numring.setFocus()
self.numring.toggle_anim(True)
self.numring.scribbling = scribbling
self.numring.set_buttons_transparent(False)
def select_ring_number(self, val, scribbling):
if val == 'X':
val = 0
if scribbling:
self.gamegrid.change_cell_scribbles(val)
else:
self.gamegrid.replace_cell_number(int(val))
def game_refocus(self):
self.gamegrid.set_disabled(False)
self.gamegrid.setFocus()
self.gamegrid.scribbling = self.numring.scribbling
def show_grid(self, state):
if state ^ self.gamegrid.isVisible():
self.gamegrid.setVisible(state)
if state:
self.gamegrid.toggle_anim(True)
def show_playmenu(self, state):
self.playmenu.setVisible(state)
def new_game(self, string):
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):
super().paint(painter, style, widget)
painter.drawText(QRectF(0, self.height+15, self.width, 15), Qt.AlignCenter,
"Hold M to scribble down numbers in a cell")
class MenuBoard(BoxBoard):
"""
The Board that contains menu options. Also contains the timer.
"""
def __init__(self, width, height, parent=None):
super().__init__(width, height, parent)
self.margin = 10
self.spacing = 20
w_spacing = (self.width - 2*self.margin) /3
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)
self.timer_display = menu_grap.TimerDisplayer(parent=self)
self.timer_display.setParent(self)
self.timer_display.setX(self.margin + w_spacing + self.spacing)
self.timer_display.setY(self.geometry().height()/2-self.timer_display.height/2)
self.score_display = menu_grap.HighScoreDisplayer(parent=self)
self.score_display.setX(self.width - self.margin)
self.score_display.setY(self.height - self.margin)
self.score_display.scoreboard_widget.highScoreSet.connect(self.return_to_normal)
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):
self.timer_display.setVisible(state)
self.diff_display.setVisible(state)
self.timer_display.reset_time()
def set_difficulty_text(self, string):
self.diff_display.set_text(string)
self.timer_display.reset_time()
def finish_the_game(self):
self.timer_display.timer.stop()
diff = self.diff_display.text
time = self.timer_display.get_time()
if self.score_display.scoreboard_widget.check_ranking(diff, time):
self.diff_display.set_disabled(True)
self.score_display.set_disabled(True)
self.score_display.show_board(True)
def return_to_normal(self):
self.diff_display.set_disabled(False)
self.score_display.set_disabled(False)