Commit b27bc7ac authored by Antoine Lima's avatar Antoine Lima
Browse files

2 to 4 player authentication/win UI + guests

parent f39dcfb9
...@@ -3,6 +3,12 @@ ...@@ -3,6 +3,12 @@
Source code for the foosball embedded Raspberry PI. It runs the UI and manages games. Source code for the foosball embedded Raspberry PI. It runs the UI and manages games.
The UI is done with PyQt5. The UI is done with PyQt5.
## Dependencies
* PyQt5
* pyserial
* autopy
* qtmultimedia5-examples
## Launch ## Launch
* Make sure to have Python>3.5 installed (`sudo apt-get install python3` otherwise) * Make sure to have Python>3.5 installed (`sudo apt-get install python3` otherwise)
* Make sure to have PyQt5 installed (`sudo pip install pyqt5` otherwise) * Make sure to have PyQt5 installed (`sudo pip install pyqt5` otherwise)
...@@ -97,4 +103,4 @@ For the time being, those are provided by the PI camera module. ...@@ -97,4 +103,4 @@ For the time being, those are provided by the PI camera module.
## Todo ## Todo
* [ ] ... * [ ] Filter serial ports to get arduinos or specific serial id
...@@ -6,7 +6,7 @@ pyuic5 --import-from=ui ui/menu.ui -o ui/menu_ui.py ...@@ -6,7 +6,7 @@ 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/game.ui -o ui/game_ui.py
pyuic5 --import-from=ui ui/endgame.ui -o ui/endgame_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/options.ui -o ui/options_ui.py
pyuic5 --import-from=ui ui/auth2p.ui -o ui/auth2p_ui.py pyuic5 --import-from=ui ui/authquick.ui -o ui/authquick_ui.py
pyuic5 --import-from=ui ui/leaderboard.ui -o ui/leaderboard_ui.py pyuic5 --import-from=ui ui/leaderboard.ui -o ui/leaderboard_ui.py
# Custom widgets # Custom widgets
......
...@@ -32,7 +32,8 @@ class MainWin(QtWidgets.QMainWindow): ...@@ -32,7 +32,8 @@ class MainWin(QtWidgets.QMainWindow):
# Module loading # Module loading
self.modules = [ self.modules = [
MenuModule(self), MenuModule(self),
AuthModule(self), AuthQuickModule(self),
AuthLeagueModule(self),
GameModule(self), GameModule(self),
EndGameModule(self), EndGameModule(self),
LeaderboardModule(self), LeaderboardModule(self),
......
from modules.auth import AuthModule from modules.authquick import AuthQuickModule
from modules.authleague import AuthLeagueModule
from modules.game import GameModule from modules.game import GameModule
from modules.endgame import EndGameModule from modules.endgame import EndGameModule
from modules.menu import MenuModule from modules.menu import MenuModule
......
...@@ -8,39 +8,44 @@ Created on Wed Apr 18 18:34:40 2018 ...@@ -8,39 +8,44 @@ Created on Wed Apr 18 18:34:40 2018
import logging import logging
from PyQt5.QtCore import QTime, Qt from PyQt5.QtCore import Qt
from module import Module
from player import Side
import modules import modules
from ui.auth2p_ui import Ui_Form as Auth2pWidget from module import Module
from player import Side, Player, PlayerGuest
class AuthModule(Module):
def __init__(self, parent):
super().__init__(parent, Auth2pWidget())
def load(self): class AuthModuleBase(Module):
logging.debug('Loading AuthModule') def __init__(self, parent, widget):
super().__init__(parent, widget)
self.players = {Side.Left: list(), Side.Right: list()} self.players = {Side.Left: list(), Side.Right: list()}
def load(self):
pass
def unload(self): def unload(self):
logging.debug('Unloading AuthModule') self.players = {Side.Left: list(), Side.Right: list()}
del self.players
def other(self, **kwargs): def other(self, **kwargs):
logging.debug('Other AuthModule')
for key, val in kwargs.items(): for key, val in kwargs.items():
if key=='ardl_rfid' or key=='ardr_rfid': if key=='rfid' and 'source' in kwargs:
side = Side.Left if key.startswith('ardl') else Side.Right side = kwargs['source']
self.players.append(Player(val)) self.addPlayer(side, Player.fromRFID(val))
def keyPressEvent(self, e): def keyPressEvent(self, e):
if e.key() == Qt.Key_Escape: if e.key() == Qt.Key_Escape:
self.handleCancel() self.handleCancel()
elif e.key() == Qt.Key_Return: elif e.key() == Qt.Key_Return:
self.handleDone() self.handleDone()
elif e.key() == Qt.Key_Left or e.key() == Qt.Key_Right:
side = Side.Left if e.key() == Qt.Key_Left else Side.Right
rfid = -2*(side.value+1) - (self.players[side][0]!=PlayerGuest)
self.send(type(self), rfid=rfid, source=side)
def addPlayer(self, side, player):
logging.warning('Base function meant to be reimplemented')
def handleCancel(self): def handleCancel(self):
self.switchModule(modules.MenuModule) self.switchModule(modules.MenuModule)
......
#!/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 module import Module
from ui.authquick_ui import Ui_Form as AuthQuickWidget
class AuthLeagueModule(Module):
def __init__(self, parent):
super().__init__(parent, AuthQuickWidget())
def load(self):
logging.debug('Loading AuthLeagueModule')
super().load()
def unload(self):
logging.debug('Loading AuthLeagueModule')
super().load()
#!/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.QtWidgets import QSizePolicy
import modules
from module import Module
from modules.auth import AuthModuleBase
from player import Side, Player, PlayerGuest
from ui.authquick_ui import Ui_Form as AuthQuickWidget
class AuthQuickModule(AuthModuleBase):
def __init__(self, parent):
super().__init__(parent, AuthQuickWidget())
self.smallPicSize = 200
self.bigPicSize = 300
self.leftSpacerWidth = self.ui.widgetLayoutP1.layout().itemAt(0).spacerItem().geometry().width()
self.rightSpacerWidth = self.ui.widgetLayoutP3.layout().itemAt(0).spacerItem().geometry().width()
def load(self):
logging.debug('Loading AuthQuickModule')
super().load()
for side in [Side.Left, Side.Right]:
if len(self.players[side])==0:
self.addPlayer(side, PlayerGuest)
self.updateSides()
def unload(self):
logging.debug('Unloading AuthQuickModule')
super().unload()
#self.updateSides()
def addPlayer(self, side, player):
if len(self.players[side])>0 and self.players[side][0]==PlayerGuest:
self.players[side].clear()
if len(self.players[side])<2:
self.players[side].append(player)
if len(self.players[Side.Left])>1 and len(self.players[Side.Right])>1:
self.handleDone()
else:
self.updateSides()
def updateSides(self):
'''
There might be issues with this function in the future:
Unfortunatly, Qt does not allow naming spacers, we can only access them indirectly,
via the layout's itemAt function. The problem with this approach is that if a spacer
is for a reason or another placed out of order by pyuic5, this won't work. Take a look to
ui/auth4p_ui.py to adapt if this ever occurs
'''
spacerLeft = self.ui.widgetLayoutP1.layout().itemAt(0).spacerItem()
spacerRight = self.ui.widgetLayoutP3.layout().itemAt(0).spacerItem()
# Update Left P1
if len(self.players[Side.Left])>0:
self.players[Side.Left][0].displayImg(self.ui.imgP1)
self.ui.lblP1.setText(self.players[Side.Left][0].name)
else:
PlayerGuest.displayImg(self.ui.imgP1)
self.ui.lblP1.setText('')
# Update Left P2
if len(self.players[Side.Left])>1:
self.ui.imgP1.setMaximumSize(self.smallPicSize, self.smallPicSize)
self.players[Side.Left][1].displayImg(self.ui.imgP2)
self.ui.lblP2.setText(self.players[Side.Left][1].name)
self.ui.widgetLayoutP2.setVisible(True)
spacerLeft.changeSize(self.leftSpacerWidth, spacerLeft.geometry().height(), QSizePolicy.Expanding, QSizePolicy.Expanding)
else:
self.ui.imgP1.setMaximumSize(self.bigPicSize, self.bigPicSize)
PlayerGuest.displayImg(self.ui.imgP2)
self.ui.lblP2.setText('')
self.ui.widgetLayoutP2.setVisible(False)
spacerLeft.changeSize(0, spacerLeft.geometry().height(), QSizePolicy.Ignored, QSizePolicy.Expanding)
# Update Right P1
if len(self.players[Side.Right])>0:
self.players[Side.Right][0].displayImg(self.ui.imgP3)
self.ui.lblP3.setText(self.players[Side.Right][0].name)
else:
PlayerGuest.displayImg(self.ui.imgP3)
self.ui.lblP3.setText('')
# Update Right P2
if len(self.players[Side.Right])>1:
self.ui.imgP3.setMaximumSize(self.smallPicSize, self.smallPicSize)
self.players[Side.Right][1].displayImg(self.ui.imgP4)
self.ui.lblP4.setText(self.players[Side.Right][1].name)
self.ui.widgetLayoutP4.setVisible(True)
spacerRight.changeSize(self.rightSpacerWidth, spacerRight.geometry().height(), QSizePolicy.Expanding, QSizePolicy.Expanding)
else:
self.ui.imgP3.setMaximumSize(self.bigPicSize, self.bigPicSize)
PlayerGuest.displayImg(self.ui.imgP4)
self.ui.lblP4.setText('')
self.ui.widgetLayoutP4.setVisible(False)
spacerRight.changeSize(0, spacerRight.geometry().height(), QSizePolicy.Ignored, QSizePolicy.Expanding)
...@@ -15,27 +15,28 @@ from PyQt5.QtCore import QTime, QTimer, QRect, Qt ...@@ -15,27 +15,28 @@ from PyQt5.QtCore import QTime, QTimer, QRect, Qt
from player import Side from player import Side
from module import Module from module import Module
import modules import modules
from ui.endgame_ui import Ui_Form as GameWidget from ui.endgame_ui import Ui_Form as EndGameWidget
class EndGameModule(Module): class EndGameModule(Module):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent, GameWidget()) super().__init__(parent, EndGameWidget())
self.screenTimeout = QTimer() self.screenTimeout = QTimer()
self.screenTimeout.timeout.connect(self.handleQuit) self.screenTimeout.timeout.connect(self.handleQuit)
self.screenTimeout.setSingleShot(True) self.screenTimeout.setSingleShot(True)
self.middleSpacerWidth = self.ui.horizontalLayout.itemAt(2).spacerItem().geometry().width()
def load(self): def load(self):
logging.debug('Loading EndGameModule') logging.debug('Loading EndGameModule')
if len(self.players[self.winSide])>1: self.setActiveP2(len(self.players[self.winSide])>1)
self.ui.lblP2_2.setText('{} Side'.format(self.winSide.name)) self.displayPlayers()
for side in [Side.Left, Side.Right]: for side in [Side.Left, Side.Right]:
for player in self.players[side]: for player in self.players[side]:
player.victories += 1 if side==self.winSide else 0 player.stats.victories += 1 if side==self.winSide else 0
player.goals_scored += self.scores[side] player.stats.goals_scored += self.scores[side]
player.time_played += self.time player.stats.time_played += self.time
player.games_played += 1 player.stats.games_played += 1
player.save() player.save()
# Quit the screen after 5 seconds if the user doesn't do it before # Quit the screen after 5 seconds if the user doesn't do it before
...@@ -61,10 +62,29 @@ class EndGameModule(Module): ...@@ -61,10 +62,29 @@ class EndGameModule(Module):
self.time = val self.time = val
#else: #else:
# raise ValueError('Unknown message identifier {}'.format(kwargs) # raise ValueError('Unknown message identifier {}'.format(kwargs)
def keyPressEvent(self, e): def keyPressEvent(self, e):
if e.key() == Qt.Key_Escape or e.key() == Qt.Key_Return: if e.key() == Qt.Key_Escape or e.key() == Qt.Key_Return:
self.handleQuit() self.handleQuit()
def setActiveP2(self, active):
self.ui.widgetLayoutP2.setVisible(active)
spacer = self.ui.horizontalLayout.itemAt(2).spacerItem()
if active:
spacer.changeSize(self.middleSpacerWidth, spacer.geometry().height(), QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
else:
spacer.changeSize(0, spacer.geometry().height(), QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding)
def displayPlayers(self):
players = self.players[self.winSide]
players[0].displayImg(self.ui.imgP1)
self.ui.lblP1.setText(players[0].name)
if len(self.players[self.winSide])>1:
players[1].displayImg(self.ui.imgP2)
self.ui.lblP2.setText(players[1].name)
def handleQuit(self): def handleQuit(self):
self.switchModule(modules.MenuModule) self.switchModule(modules.MenuModule)
...@@ -15,7 +15,7 @@ from PyQt5.QtCore import QTime, QTimer, QRect, Qt, QUrl ...@@ -15,7 +15,7 @@ from PyQt5.QtCore import QTime, QTimer, QRect, Qt, QUrl
from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer
from PyQt5.QtMultimediaWidgets import QVideoWidget from PyQt5.QtMultimediaWidgets import QVideoWidget
from player import Side from player import Side, PlayerGuest
from replay import Replay from replay import Replay
from module import Module from module import Module
import modules import modules
...@@ -67,6 +67,10 @@ class GameModule(Module): ...@@ -67,6 +67,10 @@ class GameModule(Module):
self.showingReplay = False self.showingReplay = False
self.gameoverChecker = GameOverChecker('score', 10) self.gameoverChecker = GameOverChecker('score', 10)
if all([len(val)==0 for val in self.players.values()]):
self.players[Side.Left ].append(PlayerGuest)
self.players[Side.Right].append(PlayerGuest)
self.scores = {Side.Left: 0, Side.Right: 0} self.scores = {Side.Left: 0, Side.Right: 0}
self.updateScores() self.updateScores()
...@@ -79,8 +83,12 @@ class GameModule(Module): ...@@ -79,8 +83,12 @@ class GameModule(Module):
def other(self, **kwargs): def other(self, **kwargs):
logging.debug('Other GameModule') logging.debug('Other GameModule')
if 'players' in kwargs: for key, val in kwargs.items():
self.players = kwargs['players'] if key=='goal' and 'source' in kwargs:
self.goal(kwargs['source'])
elif key=='players':
self.players = val
def resizeEvent(self, event): def resizeEvent(self, event):
# 40% of the window width to have (5% margin)-(40% circle)-(10% middle)-(40% circle)-(5% margin) # 40% of the window width to have (5% margin)-(40% circle)-(10% middle)-(40% circle)-(5% margin)
...@@ -146,7 +154,6 @@ class GameModule(Module): ...@@ -146,7 +154,6 @@ class GameModule(Module):
self.showingReplay = False self.showingReplay = False
self.updateScores() self.updateScores()
def handleCancel(self): def handleCancel(self):
self.switchModule(modules.MenuModule) self.switchModule(modules.MenuModule)
......
...@@ -24,7 +24,7 @@ class LeaderboardItemWidget(QtWidgets.QWidget): ...@@ -24,7 +24,7 @@ class LeaderboardItemWidget(QtWidgets.QWidget):
self.ui = PlayerListWidget() self.ui = PlayerListWidget()
self.ui.setupUi(self) self.ui.setupUi(self)
self.ui.picHolder.setStyleSheet('border-image: url({});'.format(player.pic_path)) player.displayImg(self.ui.picHolder)
self.ui.lblFName.setText(player.fname) self.ui.lblFName.setText(player.fname)
self.ui.lblLName.setText(player.lname) self.ui.lblLName.setText(player.lname)
...@@ -71,19 +71,7 @@ class LeaderboardModule(Module): ...@@ -71,19 +71,7 @@ class LeaderboardModule(Module):
if self.players: if self.players:
self.ui.listWidget.clear() self.ui.listWidget.clear()
else: else:
# Load from DB self.players = [Player.fromRFID(id) for id in range(-2 , -7, -1)]
dummy1 = Player(0, 'A', 'E', ':ui/img/placeholder_head.jpg')
dummy2 = Player(1, 'B', 'D', ':ui/img/placeholder_head.jpg')
dummy3 = Player(2, 'C', 'C', ':ui/img/placeholder_head.jpg')
dummy4 = Player(3, 'D', 'B', ':ui/img/placeholder_head.jpg')
dummy5 = Player(4, 'E', 'A', ':ui/img/placeholder_head.jpg')
dummy2.stats.victories = 1
dummy3.stats.time_played = 1
dummy4.stats.games_played = 1
dummy5.stats.goals_scored = 1
self.players = [PlayerGuest, dummy1, dummy2, dummy3, dummy4, dummy5]
self.players.sort(key=attrgetter(self.sortMethodAttr[self.selectedSort]), reverse=True) self.players.sort(key=attrgetter(self.sortMethodAttr[self.selectedSort]), reverse=True)
......
...@@ -15,41 +15,54 @@ from PyQt5.QtGui import QFont ...@@ -15,41 +15,54 @@ from PyQt5.QtGui import QFont
from module import Module from module import Module
import modules import modules
from ui.menu_ui import Ui_Form as MenuWidget from ui.menu_ui import Ui_Form as MenuWidget
from player import Side
class MenuModule(Module): class MenuModule(Module):
def __init__(self, parent): def __init__(self, parent):
super().__init__(parent, MenuWidget()) super().__init__(parent, MenuWidget())
# Button connections # Button connections
self.ui.btnStart2p.clicked.connect (lambda: self.switchModule(modules.AuthModule)) self.ui.btnStartQuick.clicked.connect (lambda: self.switchModule(modules.AuthQuickModule))
self.ui.btnStartParty.clicked.connect (lambda: self.switchModule(modules.GameModule)) self.ui.btnStartLeague.clicked.connect(lambda: self.switchModule(modules.AuthLeagueModule))
self.ui.btnStartLeague.clicked.connect(lambda: self.switchModule(modules.GameModule))
self.ui.btnLeaderboard.clicked.connect(lambda: self.switchModule(modules.LeaderboardModule)) self.ui.btnLeaderboard.clicked.connect(lambda: self.switchModule(modules.LeaderboardModule))
self.ui.btnOptions.clicked.connect (lambda: self.switchModule(modules.OptionsModule)) self.ui.btnOptions.clicked.connect (lambda: self.switchModule(modules.OptionsModule))
def load(self): def load(self):
logging.debug('Loading MenuModule') logging.debug('Loading MenuModule')
self.ui.btnStart2p.setFocus() self.ui.btnStartQuick.setFocus()
def unload(self): def unload(self):
logging.debug('Unloading MenuModule') logging.debug('Unloading MenuModule')
def other(self, **kwargs): def other(self, **kwargs):
logging.debug('Other MenuModule') logging.debug('Other MenuModule')
if 'rfid' in kwargs and 'source' in kwargs:
self.send(modules.AuthQuickModule, **kwargs)
self.ui.btnStartQuick.animateClick()
def keyPressEvent(self, e): def keyPressEvent(self, e):
if e.key() == Qt.Key_Escape: if e.key() == Qt.Key_Escape:
self.handleExit() self.handleExit()
elif e.key() == Qt.Key_Up: elif e.key() == Qt.Key_Up:
self.parent().focusPreviousChild() self.parent().focusPreviousChild()
elif e.key() == Qt.Key_Down: elif e.key() == Qt.Key_Down:
self.parent().focusNextChild() self.parent().focusNextChild()
elif e.key() == Qt.Key_Left:
self.send(modules.MenuModule, rfid=-2, source=Side.Left)
elif e.key() == Qt.Key_Right:
self.send(modules.MenuModule, rfid=-4, source=Side.Right)
elif e.key() == Qt.Key_Return: elif e.key() == Qt.Key_Return:
if QApplication.focusWidget()==None: if QApplication.focusWidget()==None:
logging.error('No focused widget to activate') logging.error('No focused widget to activate')
else: else:
QApplication.focusWidget().animateClick() QApplication.focusWidget().animateClick()
def handleExit(self): def handleExit(self):
logging.info('Closing..') logging.info('Closing..')
self.mainwin.close() self.mainwin.close()
...@@ -10,21 +10,54 @@ import logging ...@@ -10,21 +10,54 @@ import logging
from enum import Enum from enum import Enum
class Side(Enum): class Side(Enum):
Undef = 0 '''
Left = 1 Values of the enum are used throughout the code for indexing purposes, not to be changed
Right = 2 '''
Undef = -1
Left = 0
Right = 1
class Player(): class Player():
def __init__(self, id): def __init__(self, id, fname='', lname='', pic_path=':ui/img/placeholder_head.jpg'):
fname, lname, pic_url = '','','' # Replace with DB calls
self.__init__(id, fname, lname, pic_url)
def __init__(self, id, fname, lname, pic_path):
self.id = id self.id = id
self.fname = fname self.fname = fname
self.lname = lname self.lname = lname
self.pic_path = pic_path self.pic_path = pic_path
self.stats = Stat(id) self.stats = Stat(id)
@staticmethod
def fromRFID(id):
fname, lname, pic_url = '','','' # Replace with DB calls
if id==-1:
player = Player(id, 'Guest')
elif id==-2:
player = Player(id, 'Alfr