Commit b27bc7ac authored by Antoine Lima's avatar Antoine Lima

2 to 4 player authentication/win UI + guests

parent f39dcfb9
......@@ -3,6 +3,12 @@
Source code for the foosball embedded Raspberry PI. It runs the UI and manages games.
The UI is done with PyQt5.
## Dependencies
* PyQt5
* pyserial
* autopy
* qtmultimedia5-examples
## Launch
* 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)
......@@ -97,4 +103,4 @@ For the time being, those are provided by the PI camera module.
## 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
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/authquick.ui -o ui/authquick_ui.py
pyuic5 --import-from=ui ui/leaderboard.ui -o ui/leaderboard_ui.py
# Custom widgets
......
......@@ -32,7 +32,8 @@ class MainWin(QtWidgets.QMainWindow):
# Module loading
self.modules = [
MenuModule(self),
AuthModule(self),
AuthQuickModule(self),
AuthLeagueModule(self),
GameModule(self),
EndGameModule(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.endgame import EndGameModule
from modules.menu import MenuModule
......
......@@ -8,39 +8,44 @@ Created on Wed Apr 18 18:34:40 2018
import logging
from PyQt5.QtCore import QTime, Qt
from PyQt5.QtCore import Qt
from module import Module
from player import Side
import modules
from ui.auth2p_ui import Ui_Form as Auth2pWidget
class AuthModule(Module):
def __init__(self, parent):
super().__init__(parent, Auth2pWidget())
from module import Module
from player import Side, Player, PlayerGuest
def load(self):
logging.debug('Loading AuthModule')
class AuthModuleBase(Module):
def __init__(self, parent, widget):
super().__init__(parent, widget)
self.players = {Side.Left: list(), Side.Right: list()}
def load(self):
pass
def unload(self):
logging.debug('Unloading AuthModule')
del self.players
self.players = {Side.Left: list(), Side.Right: list()}
def other(self, **kwargs):
logging.debug('Other AuthModule')
def other(self, **kwargs):
for key, val in kwargs.items():
if key=='ardl_rfid' or key=='ardr_rfid':
side = Side.Left if key.startswith('ardl') else Side.Right
self.players.append(Player(val))
if key=='rfid' and 'source' in kwargs:
side = kwargs['source']
self.addPlayer(side, Player.fromRFID(val))
def keyPressEvent(self, e):
if e.key() == Qt.Key_Escape:
self.handleCancel()
elif e.key() == Qt.Key_Return:
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):
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
from player import Side
from module import Module
import modules
from ui.endgame_ui import Ui_Form as GameWidget
from ui.endgame_ui import Ui_Form as EndGameWidget
class EndGameModule(Module):
def __init__(self, parent=None):
super().__init__(parent, GameWidget())
super().__init__(parent, EndGameWidget())
self.screenTimeout = QTimer()
self.screenTimeout.timeout.connect(self.handleQuit)
self.screenTimeout.setSingleShot(True)
self.middleSpacerWidth = self.ui.horizontalLayout.itemAt(2).spacerItem().geometry().width()
def load(self):
logging.debug('Loading EndGameModule')
if len(self.players[self.winSide])>1:
self.ui.lblP2_2.setText('{} Side'.format(self.winSide.name))
self.setActiveP2(len(self.players[self.winSide])>1)
self.displayPlayers()
for side in [Side.Left, Side.Right]:
for player in self.players[side]:
player.victories += 1 if side==self.winSide else 0
player.goals_scored += self.scores[side]
player.time_played += self.time
player.games_played += 1
player.stats.victories += 1 if side==self.winSide else 0
player.stats.goals_scored += self.scores[side]
player.stats.time_played += self.time
player.stats.games_played += 1
player.save()
# Quit the screen after 5 seconds if the user doesn't do it before
......@@ -61,10 +62,29 @@ class EndGameModule(Module):
self.time = val
#else:
# raise ValueError('Unknown message identifier {}'.format(kwargs)
def keyPressEvent(self, e):
if e.key() == Qt.Key_Escape or e.key() == Qt.Key_Return:
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):
self.switchModule(modules.MenuModule)
......@@ -15,7 +15,7 @@ from PyQt5.QtCore import QTime, QTimer, QRect, Qt, QUrl
from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer
from PyQt5.QtMultimediaWidgets import QVideoWidget
from player import Side
from player import Side, PlayerGuest
from replay import Replay
from module import Module
import modules
......@@ -67,6 +67,10 @@ class GameModule(Module):
self.showingReplay = False
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.updateScores()
......@@ -79,8 +83,12 @@ class GameModule(Module):
def other(self, **kwargs):
logging.debug('Other GameModule')
if 'players' in kwargs:
self.players = kwargs['players']
for key, val in kwargs.items():
if key=='goal' and 'source' in kwargs:
self.goal(kwargs['source'])
elif key=='players':
self.players = val
def resizeEvent(self, event):
# 40% of the window width to have (5% margin)-(40% circle)-(10% middle)-(40% circle)-(5% margin)
......@@ -146,7 +154,6 @@ class GameModule(Module):
self.showingReplay = False
self.updateScores()
def handleCancel(self):
self.switchModule(modules.MenuModule)
......
......@@ -24,7 +24,7 @@ class LeaderboardItemWidget(QtWidgets.QWidget):
self.ui = PlayerListWidget()
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.lblLName.setText(player.lname)
......@@ -71,19 +71,7 @@ class LeaderboardModule(Module):
if self.players:
self.ui.listWidget.clear()
else:
# Load from DB
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 = [Player.fromRFID(id) for id in range(-2 , -7, -1)]
self.players.sort(key=attrgetter(self.sortMethodAttr[self.selectedSort]), reverse=True)
......
......@@ -15,41 +15,54 @@ from PyQt5.QtGui import QFont
from module import Module
import modules
from ui.menu_ui import Ui_Form as MenuWidget
from player import Side
class MenuModule(Module):
def __init__(self, parent):
super().__init__(parent, MenuWidget())
# Button connections
self.ui.btnStart2p.clicked.connect (lambda: self.switchModule(modules.AuthModule))
self.ui.btnStartParty.clicked.connect (lambda: self.switchModule(modules.GameModule))
self.ui.btnStartLeague.clicked.connect(lambda: self.switchModule(modules.GameModule))
self.ui.btnStartQuick.clicked.connect (lambda: self.switchModule(modules.AuthQuickModule))
self.ui.btnStartLeague.clicked.connect(lambda: self.switchModule(modules.AuthLeagueModule))
self.ui.btnLeaderboard.clicked.connect(lambda: self.switchModule(modules.LeaderboardModule))
self.ui.btnOptions.clicked.connect (lambda: self.switchModule(modules.OptionsModule))
def load(self):
logging.debug('Loading MenuModule')
self.ui.btnStart2p.setFocus()
self.ui.btnStartQuick.setFocus()
def unload(self):
logging.debug('Unloading MenuModule')
def other(self, **kwargs):
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):
if e.key() == Qt.Key_Escape:
self.handleExit()
elif e.key() == Qt.Key_Up:
self.parent().focusPreviousChild()
elif e.key() == Qt.Key_Down:
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:
if QApplication.focusWidget()==None:
logging.error('No focused widget to activate')
else:
QApplication.focusWidget().animateClick()
def handleExit(self):
logging.info('Closing..')
self.mainwin.close()
......@@ -10,21 +10,54 @@ import logging
from enum import Enum
class Side(Enum):
Undef = 0
Left = 1
Right = 2
'''
Values of the enum are used throughout the code for indexing purposes, not to be changed
'''
Undef = -1
Left = 0
Right = 1
class Player():
def __init__(self, id):
fname, lname, pic_url = '','','' # Replace with DB calls
self.__init__(id, fname, lname, pic_url)
def __init__(self, id, fname, lname, pic_path):
def __init__(self, id, fname='', lname='', pic_path=':ui/img/placeholder_head.jpg'):
self.id = id
self.fname = fname
self.lname = lname
self.pic_path = pic_path
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, 'Alfredo', 'Enrique')
elif id==-3:
player = Player(id, 'Bastien', 'Dali')
player.stats.victories = 1
elif id==-4:
player = Player(id, 'Carim', 'Cuebache')
player.stats.time_played = 1
elif id==-5:
player = Player(id, 'Dorian', 'Boulet')
player.stats.games_played = 1
elif id==-6:
player = Player(id, 'Enzo', 'Arobaz')
player.stats.goals_scored = 1
else:
player = Player(id, fname, lname, pic_url)
return player
def displayImg(self, containerWidget):
containerWidget.setStyleSheet('border-image: url({});'.format(self.pic_path))
def save(self):
'''
......@@ -35,7 +68,7 @@ class Player():
@property
def name(self):
return self.lname.upper() + self.fname
return '{} {}'.format(self.fname, self.lname.upper())
@property
def pic(self):
......@@ -54,4 +87,4 @@ class Stat():
self.games_played = 0
self.goals_scored = 0
PlayerGuest = Player(-1, 'Guest', '', ':ui/img/placeholder_head.jpg')
PlayerGuest = Player.fromRFID(-1)
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1280</width>
<height>720</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_5">
<property name="minimumSize">
<size>
<width>0</width>
<height>32</height>
</size>
</property>
<property name="font">
<font>
<pointsize>40</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Identify yourselves! </string>
</property>
<property name="alignment">
<set>Qt::AlignBottom|Qt::AlignHCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_6">
<property name="font">
<font>
<pointsize>20</pointsize>
<italic>true</italic>
</font>
</property>
<property name="text">
<string>Use the table's RFID reader with your card.</string>
</property>
<property name="alignment">
<set>Qt::AlignHCenter|Qt::AlignTop</set>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="imgP1">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>400</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>300</width>
<height>300</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="assets.qrc">:/ui/img/placeholder_head.jpg</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="lblP1">
<property name="minimumSize">
<size>
<width>0</width>
<height>32</height>
</size>
</property>
<property name="font">
<font>
<pointsize>25</pointsize>
</font>
</property>
<property name="text">
<string>Player 1</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer_8">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_7">
<property name="font">
<font>
<pointsize>26</pointsize>
</font>
</property>
<property name="text">
<string>vs.</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name=