From 19dd30b9b534699f4ab67cfef97003bd85cb5eec Mon Sep 17 00:00:00 2001 From: Antoine Lima Date: Sun, 7 Oct 2018 17:33:33 +0200 Subject: [PATCH] End screen and video replay --- compile_resources.sh | 1 + main.py | 21 +++++- module.py | 39 +++++++--- modules/__init__.py | 1 + modules/endgame.py | 50 +++++++++++++ modules/game.py | 107 +++++++++++++++++++------- modules/menu.py | 7 +- modules/options.py | 7 +- ui/endgame.ui | 175 +++++++++++++++++++++++++++++++++++++++++++ ui/game.ui | 24 ++++++ ui/main.ui | 4 +- 11 files changed, 385 insertions(+), 51 deletions(-) create mode 100644 modules/endgame.py create mode 100644 ui/endgame.ui diff --git a/compile_resources.sh b/compile_resources.sh index 2d37604..4e164fd 100755 --- a/compile_resources.sh +++ b/compile_resources.sh @@ -3,6 +3,7 @@ pyuic5 --import-from=ui ui/main.ui -o ui/main_ui.py pyuic5 --import-from=ui ui/menu.ui -o ui/menu_ui.py pyuic5 --import-from=ui ui/game.ui -o ui/game_ui.py +pyuic5 --import-from=ui ui/endgame.ui -o ui/endgame_ui.py pyuic5 --import-from=ui ui/options.ui -o ui/options_ui.py pyuic5 --import-from=ui ui/auth2p.ui -o ui/auth2p_ui.py pyuic5 --import-from=ui ui/leaderboard.ui -o ui/leaderboard_ui.py diff --git a/main.py b/main.py index 9dcb035..0efbb1e 100644 --- a/main.py +++ b/main.py @@ -8,6 +8,7 @@ Created on Wed Apr 18 18:34:40 2018 import sys import logging +from os.path import dirname, abspath, join from PyQt5 import QtWidgets from PyQt5.QtWidgets import QGraphicsBlurEffect @@ -16,8 +17,6 @@ from PyQt5.QtCore import QTime, Qt from ui.main_ui import Ui_MainWindow from modules import * -#acceptedKeys = [Qt.Key_Escape, Qt.Key_Enter, Qt.Key_Return, Qt.UpArrow, Qt.DownArrow, Qt.LeftArrow, Qt.RightArrow] - class MainWin(QtWidgets.QMainWindow): def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) @@ -30,11 +29,22 @@ class MainWin(QtWidgets.QMainWindow): #bgBlur.setBlurRadius(5) #self.ui.panels.setGraphicsEffect(bgBlur) + # Set the content folder's path + self._contentFolder = join(dirname(dirname(abspath(__file__))), 'content') + print(self._contentFolder) + # Module loading - self.modules = [MenuModule, GameModule, OptionsModule, AuthModule, LeaderboardModule] + self.modules = [ + MenuModule(self), + AuthModule(self), + GameModule(self), + EndGameModule(self), + LeaderboardModule(self), + OptionsModule(self) + ] for mod in self.modules: - self.ui.panels.addWidget(mod(self)) + self.ui.panels.addWidget(mod) self.ui.panels.setCurrentIndex(0) self.ui.panels.currentWidget().setFocus() @@ -51,6 +61,9 @@ class MainWin(QtWidgets.QMainWindow): def displaySystemTime(self): self.ui.lcdTime.display(QTime.currentTime().toString("hh:mm:ss")) + + def getContent(self, path): + return join(self._contentFolder, path) if __name__=='__main__': app = QtWidgets.QApplication(sys.argv) diff --git a/module.py b/module.py index dc49828..1a12f8b 100644 --- a/module.py +++ b/module.py @@ -18,29 +18,46 @@ class Module(QtWidgets.QWidget): def __init__(self, parent=None, widget=None): # UI Setup QtWidgets.QWidget.__init__(self, parent) - self.parent_win = parent + self.mainwin = parent self.ui = widget self.ui.setupUi(self) + def find(self, type): + mod_idx = [i for i, x in enumerate(self.mainwin.modules) if isinstance(x, type)] + return -1 if len(mod_idx)==0 else mod_idx[0] + def switchModule(self, new_type): - panel_idx = self.parent_win.modules.index(new_type) + curmod_idx = self.find(type(self)) + newmod_idx = self.find(new_type) - if panel_idx<0: - logging.error('Error: unknown panel {}'.format(new_type)) + if curmod_idx<0: + logging.error('Unknown panel {}'.format(type(self))) + elif newmod_idx<0: + logging.error('Unknown panel {}'.format(new_type)) else: - self.parent_win.ui.panels.currentWidget().releaseKeyboard() + # Unfocus the current module + self.mainwin.ui.panels.currentWidget().releaseKeyboard() if QApplication.focusWidget() != None: QApplication.focusWidget().clearFocus() - self.parent_win.ui.panels.currentWidget().unload() - self.parent_win.ui.panels.setCurrentIndex(panel_idx) + # Swap modules by unloading, changing the ui then loading + self.mainwin.modules[curmod_idx].unload() + self.mainwin.ui.panels.setCurrentIndex(newmod_idx) + self.mainwin.modules[newmod_idx].load() - self.parent_win.ui.panels.currentWidget().load() # Select first element of the Module - self.parent_win.ui.panels.currentWidget().focusNextChild() - self.parent_win.ui.panels.currentWidget().focusPreviousChild() - self.parent_win.ui.panels.currentWidget().grabKeyboard() + self.mainwin.modules[newmod_idx].focusNextChild() + self.mainwin.modules[newmod_idx].focusPreviousChild() + self.mainwin.modules[newmod_idx].grabKeyboard() + def send(self, to, **kwargs): + mod_idx = self.find(to) + + if mod_idx<0: + logging.error('Unknown panel {}'.format(to)) + else: + self.mainwin.modules[mod_idx].other(**kwargs) + def load(self): logging.warning('Unimplemented method "load" for {}'.format(self.__class__)) diff --git a/modules/__init__.py b/modules/__init__.py index 2ce2052..5beba43 100644 --- a/modules/__init__.py +++ b/modules/__init__.py @@ -1,5 +1,6 @@ from modules.auth import AuthModule from modules.game import GameModule +from modules.endgame import EndGameModule from modules.menu import MenuModule from modules.options import OptionsModule from modules.leaderboard import LeaderboardModule diff --git a/modules/endgame.py b/modules/endgame.py new file mode 100644 index 0000000..0f3965e --- /dev/null +++ b/modules/endgame.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Wed Apr 18 18:34:40 2018 + +@author: Antoine Lima, Leo Reynaert, Domitille Jehenne +""" + +import logging + +from PyQt5 import QtWidgets +from PyQt5.QtGui import QRegion +from PyQt5.QtCore import QTime, QTimer, QRect, Qt + +from module import Module +import modules +from ui.endgame_ui import Ui_Form as GameWidget + +class EndGameModule(Module): + def __init__(self, parent=None): + super().__init__(parent, GameWidget()) + self.screenTimeout = QTimer() + self.screenTimeout.timeout.connect(self.ui_handleClick_btnQuit) + self.screenTimeout.setSingleShot(True) + + def load(self): + logging.debug('Loading EndGameModule') + + self.ui.lblP2_2.setText('Player {}'.format(self.winner+1)) + + # Quit the screen after 5 seconds if the user doesn't do it before + self.screenTimeout.start(5000) + + def unload(self): + logging.debug('Unloading EndGameModule') + self.screenTimeout.stop() + self.winner = -1 + + def other(self, **kwargs): + logging.debug('Other EndGameModule') + + if 'winner' in kwargs: + self.winner = kwargs['winner'] + + def keyPressEvent(self, e): + if e.key() == Qt.Key_Escape or e.key() == Qt.Key_Return: + self.ui_handleClick_btnQuit() + + def ui_handleClick_btnQuit(self): + self.switchModule(modules.MenuModule) diff --git a/modules/game.py b/modules/game.py index c074628..2c7b17a 100644 --- a/modules/game.py +++ b/modules/game.py @@ -10,40 +10,59 @@ import logging from PyQt5 import QtWidgets from PyQt5.QtGui import QRegion -from PyQt5.QtCore import QTime, QTimer, QRect, Qt +from PyQt5.QtCore import QTime, QTimer, QRect, Qt, QUrl +from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer +from PyQt5.QtMultimediaWidgets import QVideoWidget from module import Module import modules from ui.game_ui import Ui_Form as GameWidget +class GameOverChecker(): + def __init__(self, conditionType, limit): + self.conditionType = conditionType + self.limit = limit + + def check(self, time, scores): + ''' + Checks if a game is over and return the winner if that's the case + Either returns -1 if the game is not finished or the highest score's index (0 or 1) + + Takes the game time is seconds and a list containing the two scores + ''' + + # Gets the index of the highest scoring player (either 0 or 1) + bestPlayer = scores.index(max(scores)) + + if self.conditionType=='score' and scores[bestPlayer]>=self.limit: + return bestPlayer + elif self.conditionType=='time' and time>self.limit: + return bestPlayer + else: + return -1 + class GameModule(Module): def __init__(self, parent=None): super().__init__(parent, GameWidget()) - # Button setup - #print(str(self.ui.btnScore1.rect())) - #print(str(self.ui.btnScore2.rect())) - #region = QRegion(self.ui.btnScore1.rect(), QRegion.Ellipse); - #self.ui.btnScore1.setMask(region); - #self.ui.btnScore2.setMask(region); - # Timer managment self.timerUpdateChrono = QTimer(self) self.timerUpdateChrono.timeout.connect(self.updateChrono) # Button connections - self.ui.btnScore1.clicked.connect(self.ui_handleClick_btnScore1) - self.ui.btnScore2.clicked.connect(self.ui_handleClick_btnScore2) - + self.ui.btnScore1.clicked.connect(lambda: self.goal(0)) + self.ui.btnScore2.clicked.connect(lambda: self.goal(1)) + def load(self): logging.debug('Loading GameModule') self.gameStartTime = QTime.currentTime() self.timerUpdateChrono.start(1000) self.ui.lcdChrono.display(QTime(0,0).toString("hh:mm:ss")) + + self.gameoverChecker = GameOverChecker('score', 10) - self.score1 = 0 - self.score2 = 0 + self.scores = [0, 0] self.updateScores() def unload(self): @@ -56,7 +75,7 @@ class GameModule(Module): def resizeEvent(self, event): # 40% of the window width to have (5% margin)-(40% circle)-(10% middle)-(40% circle)-(5% margin) - btnDiameter = self.parent_win.width()*0.4 + btnDiameter = self.mainwin.width()*0.4 region = QRegion(QRect(0, 0, btnDiameter, btnDiameter), QRegion.Ellipse) self.ui.btnScore1.setMinimumSize(btnDiameter, btnDiameter) self.ui.btnScore2.setMinimumSize(btnDiameter, btnDiameter) @@ -69,26 +88,58 @@ class GameModule(Module): if e.key() == Qt.Key_Escape: self.ui_handleClick_btnCancel() elif e.key() == Qt.Key_Left: - self.ui_handleClick_btnScore1() + self.goal(0) elif e.key() == Qt.Key_Right: - self.ui_handleClick_btnScore2() + self.goal(1) def updateChrono(self): # Updated each second - elapsedSec = self.gameStartTime.secsTo(QTime.currentTime()) - self.ui.lcdChrono.display(QTime(0,0).addSecs(elapsedSec).toString("hh:mm:ss")) + self.ui.lcdChrono.display(QTime(0,0).addSecs(self.getGameTime()).toString("hh:mm:ss")) + + # Don't check scores while showing a replay to avoid closing the engame screen too soon + if not self.showingReplay: + self.checkEndGame() + def getGameTime(self): + return self.gameStartTime.secsTo(QTime.currentTime()) + def updateScores(self): - self.ui.btnScore1.setText(str(self.score1)) - self.ui.btnScore2.setText(str(self.score2)) - - def ui_handleClick_btnScore1(self): - self.score1 += 1 - self.updateScores() - - def ui_handleClick_btnScore2(self): - self.score2 += 1 - self.updateScores() + self.ui.btnScore1.setText(str(self.scores[0])) + self.ui.btnScore2.setText(str(self.scores[1])) + self.checkEndGame() + + def goal(self, side): + if side!=0 and side!=1: + logging.error('Wrong goal side: {}'.format(side)) + else: + self.scores[side] += 1 + + # Show replay + # May require `sudo apt-get install qtmultimedia5-examples` in order to install the right libraries + self.showingReplay = True + replayFile = self.mainwin.getContent("replay{}.mp4".format(side)) + + self.player = QMediaPlayer(None, QMediaPlayer.VideoSurface) + self.player.stateChanged.connect(self.endOfReplay) + self.player.setMuted(True) + self.player.setVideoOutput(self.ui.videoWidget) + self.player.setMedia(QMediaContent(QUrl.fromLocalFile(replayFile))) + self.player.play() + self.ui.videoWidget.setFullScreen(True) + + def endOfReplay(self, status): + if status!=QMediaPlayer.PlayingState: + self.ui.videoWidget.setFullScreen(False); + self.showingReplay = False + self.updateScores() + def ui_handleClick_btnCancel(self): self.switchModule(modules.MenuModule) + + def checkEndGame(self): + win = self.gameoverChecker.check(self.getGameTime(), self.scores) + + if win>=0: + self.send(modules.EndGameModule, winner=win) + self.switchModule(modules.EndGameModule) diff --git a/modules/menu.py b/modules/menu.py index 610172d..56f8f4e 100644 --- a/modules/menu.py +++ b/modules/menu.py @@ -45,8 +45,11 @@ class MenuModule(Module): elif e.key() == Qt.Key_Down: self.parent().focusNextChild() elif e.key() == Qt.Key_Return: - QApplication.focusWidget().animateClick() + if QApplication.focusWidget()==None: + logging.error('No focused widget to activate') + else: + QApplication.focusWidget().animateClick() def ui_handleClick_btnExit(self): logging.info('Closing..') - self.parent_win.close() + self.mainwin.close() diff --git a/modules/options.py b/modules/options.py index 6ac094b..4b0b188 100644 --- a/modules/options.py +++ b/modules/options.py @@ -48,12 +48,11 @@ class OptionsModule(Module): def ui_handleClick_btnSave(self): if self.ui.options.cellWidget(0, 1).currentText().lower() == 'true': - self.parent_win.showFullScreen() - #QWSServer::setCursorVisible(False); + self.mainwin.showFullScreen() QApplication.setOverrideCursor(Qt.BlankCursor); else: - self.parent_win.showNormal() - #QWSServer.setCursorVisible(True); + self.mainwin.showNormal() + QApplication.setOverrideCursor(Qt.ArrowCursor); self.switchModule(modules.MenuModule) diff --git a/ui/endgame.ui b/ui/endgame.ui new file mode 100644 index 0000000..c03905c --- /dev/null +++ b/ui/endgame.ui @@ -0,0 +1,175 @@ + + + Form + + + + 0 + 0 + 1280 + 720 + + + + Form + + + + + + + 0 + 32 + + + + + 40 + 75 + true + + + + The Winner is.. + + + Qt::AlignBottom|Qt::AlignHCenter + + + + + + + Qt::Vertical + + + + 20 + 21 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 0 + + + + + 400 + 400 + + + + + 300 + 300 + + + + + + + :/ui/img/placeholder_head.jpg + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 100 + + + + + + + + + 0 + 32 + + + + + 25 + + + + Player 2 + + + Qt::AlignCenter + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 37 + + + + + + + + + + + + diff --git a/ui/game.ui b/ui/game.ui index e3a9d49..3882691 100644 --- a/ui/game.ui +++ b/ui/game.ui @@ -64,6 +64,9 @@ 70 + + Qt::NoFocus + 0 @@ -114,6 +117,9 @@ 70 + + Qt::NoFocus + 0 @@ -188,8 +194,26 @@ + + + + + 0 + 0 + + + + + + + QVideoWidget + QWidget +
PyQt5.QtMultimediaWidgets
+ 1 +
+
diff --git a/ui/main.ui b/ui/main.ui index bf267c3..5c446ae 100644 --- a/ui/main.ui +++ b/ui/main.ui @@ -6,8 +6,8 @@ 0 0 - 640 - 360 + 1280 + 720 -- GitLab