...
 
Commits (14)
/__pycache__/
*/__pycache__/
/.spyproject/
*/*.log
*/*_ui.py
*/*_rc.py
......@@ -103,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
* [ ] Add the possibility to stop the replay mid-video by pushing any button
......@@ -7,9 +7,11 @@ 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/authquick.ui -o ui/authquick_ui.py
pyuic5 --import-from=ui ui/authleague.ui -o ui/authleague_ui.py
pyuic5 --import-from=ui ui/leaderboard.ui -o ui/leaderboard_ui.py
# Custom widgets
pyuic5 --import-from=ui ui/playerlist.ui -o ui/playerlist_ui.py
pyuic5 --import-from=ui ui/playerlist.ui -o ui/playerlist_ui.py
pyuic5 --import-from=ui ui/delete_dialog.ui -o ui/delete_dialog_ui.py
pyrcc5 -root /ui ui/assets.qrc -o ui/assets_rc.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@author: Antoine Lima, Leo Reynaert, Domitille Jehenne
"""
import sqlite3
class DatabaseError(Exception):
pass
class Database():
__db = None
def __init__(self):
if not Database.__db:
from main import getContent
db_path = getContent('babyfut.sqlite')
self._connection = sqlite3.connect(db_path)
@staticmethod
def instance():
'''
Singleton
'''
if not Database.__db:
Database.__db = Database()
return Database.__db
@property
def _cursor(self):
return self._connection.cursor()
def select_one(self, query, *args):
res = self._cursor.execute(query, args).fetchone()
if not res:
raise DatabaseError('Query \"{}\" returned nothing with args {}'.format(query, args))
return res
def select_guest_team(self):
return self.select_one('SELECT id FROM players WHERE fname LIKE "guest"')[0]
def insert_team(self, players, goals):
if len(players)<2:
players.append(None)
self._cursor.execute('INSERT INTO Teams (nGoals, player1, player2) VALUES (?, ?, ?)', (goals, players[0], players[1],))
self._connection.commit()
return self._cursor.execute('SELECT seq FROM sqlite_sequence WHERE name="Teams"').fetchone()[0]
def insert_match(self, start_time, duration, team1, team2):
self._cursor.execute('INSERT INTO Matchs (timestamp, duration, winningTeam, losingTeam) VALUES (?, ?, ?, ?)', (start_time, duration, team1, team2,))
self._connection.commit()
def select_all_rfid(self, debug=False):
from settings import Settings
if Settings['app.mode']=='prod':
return self._cursor.execute('SELECT rfid FROM Players WHERE rfid>0').fetchall()
else:
return self._cursor.execute('SELECT rfid FROM Players WHERE rfid<-1').fetchall()
def delete_player(self, playerID):
self._cursor.execute('DELETE FROM Players WHERE id==?', (playerID,))
self._connection.commit()
def close(self):
self._connection.close()
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@author: Antoine Lima, Leo Reynaert, Domitille Jehenne
"""
import logging
import pyautogui # PyPi library
from threading import Thread
from main import OnRasp
if OnRasp:
import RPi.GPIO as GPIO
from player import Side
class GPIOThread(Thread):
_keyButtonBindings = {
26: 'up',
22: 'left',
27: 'right',
23: 'down',
17: 'return',
18: 'escape'
}
def __init__(self, dispatcher):
Thread.__init__(self)
self.dispatcher = dispatcher
self.continueRunning = True
if OnRasp:
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
for pin in GPIOThread._keyButtonBindings.keys():
print(pin)
GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.add_event_detect(pin, GPIO.RISING, callback=self.handleButtonPress)
def run(self):
if OnRasp:
try:
while self.continueRunning:
pass
finally:
GPIOThread.clean()
def handleButtonPress(self, button_pin):
if button_pin not in GPIOThread._keyButtonBindings.keys():
logging.warn('Unknown button pin: {}'.format(button_pin))
else:
key = GPIOThread._keyButtonBindings[button_pin]
logging.debug('Sending {} as {}'.format(button_pin, key))
pyautogui.press(key)
def stop(self):
self.continueRunning = False
@staticmethod
def clean():
if OnRasp:
GPIO.cleanup()
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Apr 18 18:34:40 2018
@author: Antoine Lima, Leo Reynaert, Domitille Jehenne
"""
import os
OnRasp = os.uname()[1] == 'raspberrypi'
import sys
import logging
from os.path import dirname, abspath, join
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QGraphicsBlurEffect, QApplication
from PyQt5.QtCore import QTime, Qt
from ui.main_ui import Ui_MainWindow
from modules import *
class MainWin(QtWidgets.QMainWindow):
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
def getContent(path):
contentFolder = join(dirname(dirname(abspath(__file__))), 'content')
return join(contentFolder, path)
if __name__=='__main__':
from ui.mainwin import MainWin
from modules import GameModule
from player import Side
from input import GPIOThread
from database import Database
from replay import Replay as ReplayThread
try:
#logging.basicConfig(filename='babyfoot.log', level=logging.DEBUG)
logging.basicConfig(level=logging.DEBUG)
#Background blur
bgBlur = QGraphicsBlurEffect()
bgBlur.setBlurHints(QGraphicsBlurEffect.QualityHint)
#bgBlur.setBlurRadius(5)
#self.ui.panels.setGraphicsEffect(bgBlur)
app = QtWidgets.QApplication(sys.argv)
myapp = MainWin()
# Module loading
self.modules = [
MenuModule(self),
AuthQuickModule(self),
AuthLeagueModule(self),
GameModule(self),
EndGameModule(self),
LeaderboardModule(self),
OptionsModule(self)
]
if ReplayThread.isCamAvailable():
threadReplay = ReplayThread(Side.Left)
threadReplay.start()
myapp.dispatchMessage({'replayThread': threadReplay}, toType=GameModule)
for mod in self.modules:
self.ui.panels.addWidget(mod)
threadGPIO = GPIOThread(myapp)
threadGPIO.start()
self.ui.panels.setCurrentIndex(0)
self.ui.panels.currentWidget().setFocus()
self.ui.panels.currentWidget().grabKeyboard()
self.ui.panels.currentWidget().load()
self.displaySystemTime()
self.startTimer(1000)
#def eventFilter(target, event):
# return event.type()==QEvent.KeyPress and event.key() not in acceptedKeys
def timerEvent(self, e):
self.displaySystemTime()
def displaySystemTime(self):
self.ui.lcdTime.display(QTime.currentTime().toString("hh:mm:ss"))
@staticmethod
def getContent(path):
contentFolder = join(dirname(dirname(abspath(__file__))), 'content')
return join(contentFolder, path)
def _refreshAfterSettings(self):
from settings import Settings
myapp.show()
app.exec_()
if Settings.ui['fullscreen']:
self.showFullScreen()
QApplication.setOverrideCursor(Qt.BlankCursor);
else:
self.showNormal()
QApplication.setOverrideCursor(Qt.ArrowCursor);
if __name__=='__main__':
app = QtWidgets.QApplication(sys.argv)
#logging.basicConfig(filename='babyfoot.log', level=logging.DEBUG)
logging.basicConfig(level=logging.DEBUG)
myapp = MainWin()
myapp.show()
app.exec_()
threadGPIO.stop()
if ReplayThread.isCamAvailable():
threadReplay.stop()
threadReplay.join()
threadGPIO.join()
finally:
GPIOThread.clean()
Database.instance().close()
#!/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.QtCore import QTime, QTimer, Qt
from PyQt5.QtWidgets import QTableWidgetItem, QComboBox, QApplication
from modules import *
from PyQt5.QtWidgets import QApplication, QWidget
class Module(QtWidgets.QWidget):
def __init__(self, parent=None, widget=None):
class Module(QWidget):
def __init__(self, parent, widget):
# UI Setup
QtWidgets.QWidget.__init__(self, parent)
QWidget.__init__(self, 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):
curmod_idx = self.find(type(self))
newmod_idx = self.find(new_type)
curmod_idx = self.mainwin.findMod(type(self))
newmod_idx = self.mainwin.findMod(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:
# Unfocus the current module
self.mainwin.ui.panels.currentWidget().releaseKeyboard()
# Unfocus the current module
if QApplication.focusWidget() != None:
QApplication.focusWidget().clearFocus()
# Swap modules by unloading, changing the ui then loading
# 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.mainwin.ui.panels.setFocusProxy(self.mainwin.modules[newmod_idx])
self.mainwin.modules[newmod_idx].setFocus()
# Select first element of the Module
self.mainwin.modules[newmod_idx].focusNextChild()
self.mainwin.modules[newmod_idx].focusPreviousChild()
self.mainwin.modules[newmod_idx].grabKeyboard()
self.mainwin.modules[newmod_idx].load()
def send(self, to, **kwargs):
mod_idx = self.find(to)
mod_idx = self.mainwin.findMod(to)
if mod_idx<0:
logging.error('Unknown panel {}'.format(to))
......
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Apr 18 18:34:40 2018
@author: Antoine Lima, Leo Reynaert, Domitille Jehenne
"""
......@@ -12,23 +10,26 @@ from PyQt5.QtCore import Qt
import modules
from module import Module
from player import Side, Player, PlayerGuest
from player import Side, Player
class AuthModuleBase(Module):
def __init__(self, parent, widget):
super().__init__(parent, widget)
self.players = {Side.Left: list(), Side.Right: list()}
self.createPlayerList()
self.numPlayers = 0
def load(self):
pass
def unload(self):
self.players = {Side.Left: list(), Side.Right: list()}
self.createPlayerList()
self.numPlayers = 0
def other(self, **kwargs):
for key, val in kwargs.items():
if key=='rfid' and 'source' in kwargs:
side = kwargs['source']
self.numPlayers += 1
self.addPlayer(side, Player.fromRFID(val))
def keyPressEvent(self, e):
......@@ -40,10 +41,10 @@ class AuthModuleBase(Module):
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)
rfid = -(2 + self.numPlayers%5)
self.send(type(self), rfid=rfid, source=side)
def addPlayer(self, side, player):
def createPlayerList(self):
logging.warning('Base function meant to be reimplemented')
def handleCancel(self):
......
#!/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
from PyQt5.QtWidgets import QAbstractItemView
from modules.auth import AuthModuleBase
from ui.authleague_ui import Ui_Form as AuthLeagueWidget
from player import Side, PlayerEmpty
class AuthLeagueModule(Module):
class AuthLeagueModule(AuthModuleBase):
def __init__(self, parent):
super().__init__(parent, AuthQuickWidget())
super().__init__(parent, AuthLeagueWidget())
def load(self):
logging.debug('Loading AuthLeagueModule')
super().load()
self.addPlayer(Side.Left, PlayerEmpty)
def unload(self):
logging.debug('Loading AuthLeagueModule')
super().load()
super().unload()
self.ui.playersList.clear()
def createPlayerList(self):
'''
Duplicates the player list to be the same on both sides.
That way, adding a player on the left or on the right have the exact same effect,
and thus the AuthModuleBase code can remain generic.
'''
l = list()
self.players = {Side.Left: l, Side.Right: l}
def addPlayer(self, side, player):
# Add the player if not already in the list
if all([p.id!=player.id for p in self.players[side]]):
if player!=PlayerEmpty:
self.players[side].append(player)
# Update the left side description
player.displayImg(self.ui.img)
self.ui.lblName.setText(player.name)
self.ui.lblStat1.setText('{} Victories'.format(player.stats['victories']))
self.ui.lblStat2.setText('{} Games Played'.format(player.stats['games_played']))
self.ui.lblStat3.setText('{} Goals Scored'.format(player.stats['goals_scored']))
if player!=PlayerEmpty:
# Update the right side list, making sure that the added player is showed
self.ui.playersList.addItem('{}. {}'.format(len(self.players[side]), player.name))
widgetItem = self.ui.playersList.item(self.ui.playersList.count()-1)
self.ui.playersList.scrollToItem(widgetItem, QAbstractItemView.PositionAtBottom)
def handleDone(self):
super().handleDone()
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Apr 18 18:34:40 2018
@author: Antoine Lima, Leo Reynaert, Domitille Jehenne
"""
......@@ -10,10 +8,8 @@ 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 player import Side, PlayerGuest
from ui.authquick_ui import Ui_Form as AuthQuickWidget
class AuthQuickModule(AuthModuleBase):
......@@ -40,7 +36,11 @@ class AuthQuickModule(AuthModuleBase):
super().unload()
#self.updateSides()
def createPlayerList(self):
self.players = {Side.Left: list(), Side.Right: list()}
def addPlayer(self, side, player):
# If there is a placeholder Guest, clear it from the list, we don't need it anymore
if len(self.players[side])>0 and self.players[side][0]==PlayerGuest:
self.players[side].clear()
......
#!/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 PyQt5.QtCore import QTimer, Qt
from player import Side
from database import Database
from player import Side, PlayerGuest
from module import Module
import modules
from ui.endgame_ui import Ui_Form as EndGameWidget
......@@ -31,13 +29,16 @@ class EndGameModule(Module):
self.setActiveP2(len(self.players[self.winSide])>1)
self.displayPlayers()
db = Database.instance()
idTeams = {}
for side in [Side.Left, Side.Right]:
for player in self.players[side]:
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()
if PlayerGuest in self.players[side]:
idTeams[side] = db.select_guest_team()
else:
idTeams[side] = db.insert_team([player.id for player in self.players[side]], self.scores[side])
db.insert_match(int(self.start_time), int(self.duration), idTeams[self.winSide], idTeams[self.winSide.opposite])
# Quit the screen after 5 seconds if the user doesn't do it before
#self.screenTimeout.start(5000)
......@@ -45,8 +46,13 @@ class EndGameModule(Module):
def unload(self):
logging.debug('Unloading EndGameModule')
self.screenTimeout.stop()
del self.players
del self.gameType
del self.winSide
del self.scores
del self.start_time
del self.duration
def other(self, **kwargs):
logging.debug('Other EndGameModule')
......@@ -54,14 +60,16 @@ class EndGameModule(Module):
for key, val in kwargs.items():
if key=='players':
self.players = val
elif key=='gameType':
self.gameType = val
elif key=='winSide':
self.winSide = val
elif key=='scores':
self.scores = val
elif key=='time':
self.time = val
#else:
# raise ValueError('Unknown message identifier {}'.format(kwargs)
elif key=='start_time':
self.start_time = val
elif key=='duration':
self.duration = val
def keyPressEvent(self, e):
if e.key() == Qt.Key_Escape or e.key() == Qt.Key_Return:
......
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Apr 18 18:34:40 2018
@author: Antoine Lima, Leo Reynaert, Domitille Jehenne
"""
......@@ -10,14 +8,16 @@ import os
import logging
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtGui import QRegion
from PyQt5.QtCore import QTime, QTimer, QRect, Qt, QUrl
from PyQt5.QtCore import QDateTime, QDate, QTime, QTimer, QRect, Qt, QUrl
from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer
from PyQt5.QtMultimediaWidgets import QVideoWidget
from player import Side, PlayerGuest
from replay import Replay
from module import Module
from settings import Settings
import modules
from ui.game_ui import Ui_Form as GameWidget
......@@ -25,25 +25,53 @@ 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
Returns the winning side or Side.Undef otherwise
Takes the game time is seconds and a list containing the two scores
'''
# Gets the index of the highest scoring player
# Gets the index of the highest scoring player
bestPlayer = max(scores, key=scores.get)
if self.conditionType=='score' and scores[bestPlayer]>=self.limit:
return bestPlayer
elif self.conditionType=='time' and time>self.limit:
elif self.conditionType=='time' and time>=self.limit:
return bestPlayer
else:
return Side.Undef
class ReplayHolder(QVideoWidget):
def __init__(self, mediaPlayer, parent):
super().__init__(parent)
self.mediaPlayer = mediaPlayer
def keyPressEvent(self, e):
self.mediaPlayer.stop_replay(QMediaPlayer.StoppedState)
class ReplayPlayer(QMediaPlayer):
def __init__(self, parent):
super().__init__(parent, QMediaPlayer.VideoSurface)
self.stateChanged.connect(self.stop_replay)
self.setMuted(True)
def start_replay(self, video_file):
self.setMedia(QMediaContent(QUrl.fromLocalFile(video_file)))
self._playerWidget = ReplayHolder(self, self.parent())
self.setVideoOutput(self._playerWidget)
self.play()
self._playerWidget.setFullScreen(True)
def stop_replay(self, status):
if status==QMediaPlayer.StoppedState:
self._playerWidget.setFullScreen(False);
self._playerWidget.setVisible(False);
self.parent().endOfReplay()
class GameModule(Module):
def __init__(self, parent=None):
super().__init__(parent, GameWidget())
......@@ -56,18 +84,28 @@ class GameModule(Module):
self.ui.btnScore1.clicked.connect(lambda: self.goal(Side.Left))
self.ui.btnScore2.clicked.connect(lambda: self.goal(Side.Right))
self.replayer = Replay()
self.camera = None
self.video_player = None
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.showingReplay = False
self.gameoverChecker = GameOverChecker('score', 10)
self.video_player = None
if self.camera:
self.camera.start_recording()
gameover_type = Settings['gameover.type']
gameover_value = Settings['gameover.value']
if gameover_type=='time':
gameover_value *= 60
self.gameoverChecker = GameOverChecker(gameover_type, gameover_value)
if all([len(val)==0 for val in self.players.values()]):
self.players[Side.Left ].append(PlayerGuest)
self.players[Side.Right].append(PlayerGuest)
......@@ -77,18 +115,25 @@ class GameModule(Module):
def unload(self):
logging.debug('Unloading GameModule')
del self.gameStartTime
self.timerUpdateChrono.stop()
self.gameStartTime = None
if self.camera:
self.camera.stop_recording()
def other(self, **kwargs):
logging.debug('Other GameModule')
for key, val in kwargs.items():
if key=='goal' and 'source' in kwargs:
self.goal(kwargs['source'])
elif key=='players':
self.players = val
elif key=='replayThread':
self.replayer = val
def resizeEvent(self, event):
# 40% of the window width to have (5% margin)-(40% circle)-(10% middle)-(40% circle)-(5% margin)
......@@ -98,68 +143,76 @@ class GameModule(Module):
self.ui.btnScore2.setMinimumSize(btnDiameter, btnDiameter)
self.ui.btnScore1.setMask(region)
self.ui.btnScore2.setMask(region)
QtWidgets.QWidget.resizeEvent(self, event)
def keyPressEvent(self, e):
if e.key() == Qt.Key_Escape:
self.handleCancel()
ret = QMessageBox.question(self, 'Stop the match?', 'Do you really want to stop this match? It wont be saved.')
if ret == QMessageBox.Yes:
self.handleCancel()
elif e.key() == Qt.Key_Left:
self.goal(Side.Left)
elif e.key() == Qt.Key_Right:
self.goal(Side.Right)
def updateChrono(self):
# Updated each second
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:
# Don't check scores while showing a replay to avoid closing the engame screen too soon
if not self.video_player:
self.checkEndGame()
def getGameTime(self):
return self.gameStartTime.secsTo(QTime.currentTime())
def updateScores(self):
self.ui.btnScore1.setText(str(self.scores[Side.Left]))
self.ui.btnScore2.setText(str(self.scores[Side.Right]))
self.checkEndGame()
def goal(self, side):
if side not in Side:
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
replayFile = self.mainwin.getContent('Replay {}.mp4'.format(side.name))
if os.path.exists(replayFile):
if True: # Debug Mode
self.updateScores()
else:
self.showingReplay = True
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
if self.camera:
replayFile = self.camera.stop_recording()
elif Settings['replay.debug']:
replayFile = Replay.Dummy()
else:
replayFile = ''
if replayFile and os.path.exists(replayFile):
self.video_player = ReplayPlayer(self)
self.video_player.start_replay(replayFile)
else:
self.updateScores()
def endOfReplay(self):
self.video_player = None
if self.gameStartTime:
self.updateScores()
if self.camera:
self.camera.start_recording()
def handleCancel(self):
self.switchModule(modules.MenuModule)
def checkEndGame(self):
winSide = self.gameoverChecker.check(self.getGameTime(), self.scores)
if winSide!=Side.Undef:
self.send(modules.EndGameModule, players=self.players, winSide=winSide, scores=self.scores, time=self.getGameTime())
start_timestamp = int(QDateTime(QDate.currentDate(), self.gameStartTime).toMSecsSinceEpoch()/1000)
self.send(modules.EndGameModule, players=self.players, winSide=winSide, scores=self.scores)
self.send(modules.EndGameModule, start_time=start_timestamp, duration=self.getGameTime(), gameType=self)
self.switchModule(modules.EndGameModule)
#!/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 operator import attrgetter
from PyQt5 import QtWidgets
from PyQt5.QtCore import QTime, Qt, QSize, QItemSelectionModel
from PyQt5.QtWidgets import QWidget, QDialog, QListWidgetItem
from PyQt5.QtCore import Qt, QItemSelectionModel
from module import Module
import modules
from player import PlayerGuest, Player
from ui.leaderboard_ui import Ui_Form as LeaderboardWidget
from ui.playerlist_ui import Ui_Form as PlayerListWidget
from module import Module
from player import Player, Side
from database import Database
class LeaderboardItemWidget(QtWidgets.QWidget):
from ui.leaderboard_ui import Ui_Form as LeaderboardWidget
from ui.playerlist_ui import Ui_Form as PlayerListWidget
from ui.delete_dialog_ui import Ui_Dialog as PlayerDeleteDialog
class LeaderboardItemWidget(QWidget):
def __init__(self, parent, player):
QtWidgets.QWidget.__init__(self, parent)
QWidget.__init__(self, parent)
self.ui = PlayerListWidget()
self.ui.setupUi(self)
......@@ -28,13 +29,30 @@ class LeaderboardItemWidget(QtWidgets.QWidget):
self.ui.lblFName.setText(player.fname)
self.ui.lblLName.setText(player.lname)
self.ui.lblVictories.setText (self.ui.lblVictories.text().replace('####', str(player.stats.victories)))
self.ui.lblGamesPlayed.setText (self.ui.lblGamesPlayed.text().replace('####', str(player.stats.games_played)))
self.ui.lblGoalsScored.setText (self.ui.lblGoalsScored.text().replace('####', str(player.stats.goals_scored)))
self.ui.lblMinutesPlayed.setText(self.ui.lblMinutesPlayed.text().replace('####', str(player.stats.time_played)))
self.ui.lblVictories.setText (self.ui.lblVictories.text().replace('####', str(player.stats['victories'])))
self.ui.lblGamesPlayed.setText (self.ui.lblGamesPlayed.text().replace('####', str(player.stats['games_played'])))
self.ui.lblGoalsScored.setText (self.ui.lblGoalsScored.text().replace('####', str(player.stats['goals_scored'])))
self.ui.lblMinutesPlayed.setText(self.ui.lblMinutesPlayed.text().replace('####', str(player.stats['time_played'])))
self.ui.pushButton.clicked.connect(lambda: logging.debug('clicked'))
class DeleteDialog(QDialog):
def __init__(self, parent, player):
print('DeleteDialog {}'.format(player.name))
QDialog.__init__(self, parent)
self.ui = PlayerDeleteDialog()
self.ui.setupUi(self)
self.player = player
self.ui.lblTitle.setText(self.ui.lblTitle.text().format(player.name))
def check(self, rfid):
return rfid == -self.player.id
# Debug
def keyPressEvent(self, e):
if e.key() == Qt.Key_Return:
self.parent().send(modules.LeaderboardModule, rfid=self.player.rfid, source=Side.Right)
class LeaderboardModule(Module):
def __init__(self, parent):
super().__init__(parent, LeaderboardWidget())
......@@ -48,13 +66,15 @@ class LeaderboardModule(Module):
self.selectedSort = 0
self.sortMethodRB = [self.ui.rbName, self.ui.rbVictories, self.ui.rbScore, self.ui.rbGamesPlayed, self.ui.rbTimePlayed]
self.sortMethodAttr = ['lname', 'stats.victories', 'stats.goals_scored', 'stats.games_played', 'stats.time_played']
self.sortMethodAttr = ['lname', 'stats_property.victories', 'stats_property.goals_scored', 'stats_property.games_played', 'stats_property.time_played']
self.sortMethodRB[self.selectedSort].setChecked(True)
self.deleteDialog = None
def load(self):
logging.debug('Loading LeaderboardModule')
self.loadList()
self.setFocus()
def unload(self):
logging.debug('Unloading LeaderboardModule')
......@@ -62,6 +82,18 @@ class LeaderboardModule(Module):
def other(self, **kwargs):
logging.debug('Other LeaderboardModule')
for key, val in kwargs.items():
if key=='rfid' and self.deleteDialog and self.deleteDialog.check(val):
Database.instance().delete_player(self.deleteDialog.player.id)
# Reset the dialog and the player list
self.deleteDialog.close()
del self.deleteDialog
self.deleteDialog = None
self.players = []
self.ui.listWidget.clear()
self.loadList()
def changeSort(self, rbSort):
self.selectedSort = self.sortMethodRB.index(rbSort)
......@@ -71,12 +103,12 @@ class LeaderboardModule(Module):
if self.players:
self.ui.listWidget.clear()
else:
self.players = [Player.fromRFID(id) for id in range(-2 , -7, -1)]
self.players = Player.allPlayers()
self.players.sort(key=attrgetter(self.sortMethodAttr[self.selectedSort]), reverse=True)
for player in self.players:
item = QtWidgets.QListWidgetItem()
item = QListWidgetItem()
playerWidget = LeaderboardItemWidget(self.ui.listWidget, player)
item.setSizeHint(playerWidget.size())
self.ui.listWidget.addItem(item)
......@@ -106,6 +138,10 @@ class LeaderboardModule(Module):
elif e.key() == Qt.Key_Right:
newSort = curSort+1 if curSort!=len(self.sortMethodRB)-1 else 0
self.sortMethodRB[newSort].animateClick()
elif e.key() == Qt.Key_Delete:
self.deleteDialog = DeleteDialog(self, self.players[curRow])
self.deleteDialog.open()
def handleExit(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
"""
......@@ -10,10 +8,10 @@ import logging
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont
from module import Module
import modules
from module import Module
from settings import Settings
from ui.menu_ui import Ui_Form as MenuWidget
from player import Side
......@@ -42,7 +40,7 @@ class MenuModule(Module):
self.ui.btnStartQuick.animateClick()
def keyPressEvent(self, e):
if e.key() == Qt.Key_Escape:
if e.key() == Qt.Key_Escape and Settings['app.mode']=='dev':
self.handleExit()
elif e.key() == Qt.Key_Up:
......@@ -55,7 +53,7 @@ class MenuModule(Module):
self.send(modules.MenuModule, rfid=-2, source=Side.Left)
elif e.key() == Qt.Key_Right:
self.send(modules.MenuModule, rfid=-4, source=Side.Right)
self.send(modules.MenuModule, rfid=-3, source=Side.Right)
elif e.key() == Qt.Key_Return:
if QApplication.focusWidget()==None:
......
#!/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.QtCore import Qt
from PyQt5.QtWidgets import QTableWidgetItem, QComboBox, QApplication
from PyQt5.QtCore import Qt, QEvent
from PyQt5.QtWidgets import QRadioButton, QSlider
from settings import Settings
from module import Module
......@@ -19,39 +17,85 @@ from ui.options_ui import Ui_Form as OptionsWidget
class OptionsModule(Module):
def __init__(self, parent):
super().__init__(parent, OptionsWidget())
# Button connections
self.ui.btnSave.clicked.connect(self.handleSave)
self.ui.btnBack.clicked.connect(self.handleBack)
self.ui.sliderGameOverValue.valueChanged.connect(self.updateGameOverLabel)
self.ui.rbGameOver_Score.clicked.connect(self.updateGameOverLabel)
self.ui.rbGameOver_Time.clicked.connect(self.updateGameOverLabel)
self.ui.sliderGameOverValue.installEventFilter(self)
def load(self):
logging.debug('Loading OptionsModule')
cbb = QComboBox()
cbb.addItem('true')
cbb.addItem('false')
self.ui.options.insertRow(self.ui.options.rowCount())
self.ui.options.setItem(self.ui.options.rowCount()-1, 0, QTableWidgetItem('FullScreen'))
self.ui.options.setCellWidget(self.ui.options.rowCount()-1, 1, cbb)
# Set Gameover condition from settings
self.ui.rbGameOver_Score.setChecked(Settings['gameover.type']=='score')
self.ui.rbGameOver_Time.setChecked(Settings['gameover.type']=='time')
self.ui.sliderGameOverValue.setValue(Settings['gameover.value'])
# Set League players from settings
self.ui.rbNumPlayerLeague_1.setChecked(Settings['league.playerPerTeam']==1)
self.ui.rbNumPlayerLeague_2.setChecked(Settings['league.playerPerTeam']==2)
# Set Language from settings
self.ui.rbLanguage_English.setChecked(Settings['ui.language']=='en')
self.ui.rbLanguage_French.setChecked(Settings['ui.language']=='fr')
self.selectIndex = 0
self.updateSelection()
self.updateGameOverLabel(0)
def unload(self):
logging.debug('Unloading OptionsModule')
# Delete the table's content
self.ui.options.setRowCount(0)
def other(self, **kwargs):
logging.debug('Other OptionsModule')
def eventFilter(self, obj, event):
if obj==self.ui.sliderGameOverValue and event.type()==QEvent.KeyPress and (event.key()==Qt.Key_Up or event.key()==Qt.Key_Down):
self.keyPressEvent(event)
return True
return False
def keyPressEvent(self, e):
if e.key() == Qt.Key_Escape:
self.handleBack()
elif e.key() == Qt.Key_Return:
self.handleSave()
elif e.key() == Qt.Key_Up:
#self.parent().focusPreviousChild()
self.selectIndex = self.selectIndex-1 if self.selectIndex!=0 else len(self.selectables)-1
self.updateSelection()
elif e.key() == Qt.Key_Down:
#self.parent().focusNextChild()
self.selectIndex = self.selectIndex+1 if self.selectIndex!=len(self.selectables)-1 else 0
self.updateSelection()
def updateSelection(self):
self.selectables = []
isSelectable = lambda widget: (isinstance(widget, QRadioButton) and widget.isChecked()) or isinstance(widget, QSlider)
for gb in [self.ui.gbGameOver, self.ui.gbLeaguePlayers, self.ui.gbLanguage]:
self.selectables.extend([child for child in gb.children() if isSelectable(child)])
self.selectables[self.selectIndex].setFocus()
def updateGameOverLabel(self, val):
sliderVal = self.ui.sliderGameOverValue.value()
strPoints = '{} point{}'.format(sliderVal, 's' if sliderVal>1 else '')
strTime = '{} minute{}'.format(sliderVal, 's' if sliderVal>1 else '')
self.ui.lblGameOverValue.setText(strPoints if self.ui.rbGameOver_Score.isChecked() else strTime)
def handleSave(self):
Settings['ui.fullscreen'] = self.ui.options.cellWidget(0, 1).currentText().lower() == 'true'
self.mainwin._refreshAfterSettings()
Settings['ui.language'] = 'en' if self.ui.rbLanguage_English.isChecked() else 'fr'
Settings['gameover.type'] = 'score' if self.ui.rbGameOver_Score.isChecked() else 'time'
Settings['gameover.value'] = self.ui.sliderGameOverValue.value()
Settings['league.playerPerTeam'] = 1 if self.ui.rbNumPlayerLeague_1.isChecked() else 2
Settings.saveSettingsToJSON()
self.mainwin._loadSettings()
self.switchModule(modules.MenuModule)
def handleBack(self):
# ToDo: Maybe add a warning
self.switchModule(modules.MenuModule)
......@@ -7,6 +7,7 @@ Created on Wed Apr 18 18:34:40 2018
"""
import logging
from enum import Enum
class Side(Enum):
......@@ -16,45 +17,50 @@ class Side(Enum):
Undef = -1
Left = 0
Right = 1
@property
def opposite(self):
return Side.Right if self==Side.Left else Side.Left
from database import Database, DatabaseError
class Player():
def __init__(self, id, fname='', lname='', pic_path=':ui/img/placeholder_head.jpg'):
__query_infos = 'SELECT id, fname, lname, pic FROM Players WHERE rfid==?'
__query_time_goals_games = 'SELECT SUM(Matchs.duration) AS timePlayed, SUM(Teams.nGoals) AS goalsScored, COUNT(*) AS gamesPlayed FROM Teams INNER JOIN Matchs ON (Teams.id==Matchs.winningTeam OR Teams.id==Matchs.losingTeam) WHERE (Teams.player1==? OR player2==?)'
__query_victories = 'SELECT COUNT(*) AS victories FROM Players INNER JOIN Teams ON (Players.id==Teams.player1 OR Players.id==Teams.player2) INNER JOIN Matchs ON (Teams.id==Matchs.winningTeam) WHERE Players.id==?'
_placeholder_pic_path = ':ui/img/placeholder_head.jpg'
def __init__(self, id, rfid, fname, lname, pic_path, stats):
self.id = id
self.rfid = rfid
self.fname = fname
self.lname = lname
self.pic_path = pic_path
self.stats = Stat(id)
self.pic_path = pic_path if pic_path else Player._placeholder_pic_path # Default pic if None
self.stats = stats
@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
def fromRFID(rfid):
db = Database.instance()
try:
# Retrieve generic informations
id, fname, lname, pic = db.select_one(Player.__query_infos, rfid)
elif id==-5:
player = Player(id, 'Dorian', 'Boulet')
player.stats.games_played = 1
# Retrieve stats
stats = {}
stats['time_played'], stats['goals_scored'], stats['games_played'] = db.select_one(Player.__query_time_goals_games, id, id)
stats['victories'], = db.select_one(Player.__query_victories, id)
elif id==-6:
player = Player(id, 'Enzo', 'Arobaz')
player.stats.goals_scored = 1
for key, val in stats.items():
if val==None:
stats[key] = 0
else:
player = Player(id, fname, lname, pic_url)
return Player(id, rfid, fname, lname, pic, stats)
return player
except DatabaseError as e:
logging.warn('DB Error: {}'.format(e))
return PlayerGuest
def displayImg(self, containerWidget):
containerWidget.setStyleSheet('border-image: url({});'.format(self.pic_path))
......@@ -63,28 +69,30 @@ class Player():
'''
Update or create the player in database
'''
# TODO
pass
@property
def name(self):
return '{} {}'.format(self.fname, self.lname.upper())
@property
def pic(self):
return QPixmap(self.pic_path)
class Stat():
def __init__(self, player_id):
self.victories = 0
self.time_played = 0
self.games_played = 0
self.goals_scored = 0
def stats_property(self):
'''
Compatibility property allowing to access stats as a object member and not dict
ex: player.stats['victories'] can be accessed with player.stats_property.victories'
This is mostly used for sorting players in leaderboard.py
'''
class Stat:
def __init__(self, stats):
self.victories = stats['victories']
self.time_played = stats['time_played']
self.goals_scored = stats['goals_scored']
self.games_played = stats['games_played']
if player_id >= 0:
self.victories = 0
self.time_played = 0
self.games_played = 0
self.goals_scored = 0
return Stat(self.stats)
@staticmethod
def allPlayers():
return [Player.fromRFID(rfid) for rfid, in Database.instance().select_all_rfid()]
PlayerGuest = Player.fromRFID(-1)
PlayerEmpty = Player(-1, -42, '', '', Player._placeholder_pic_path, {'time_played':'', 'goals_scored':'', 'games_played':'', 'victories': ''})
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Apr 18 18:34:40 2018
@author: Antoine Lima, Leo Reynaert, Domitille Jehenne
"""
import os
from settings import Settings
from threading import Thread, Event
onRasp = os.uname()[1] == 'raspberrypi'
from main import getContent, OnRasp
from settings import Settings
if onRasp:
if OnRasp:
import picamera
class Replay(Thread):
def __init__(self, side):
Thread.__init__(self)
self.replayPath = getContent('Replay {}.mp4'.format(side.name))
self.shutdown = False
self.start_flag = Event()
self.stop_flag = Event()
self.stopped_flag = Event()
class Replay():
def __init__(self):
if onRasp:
if OnRasp:
self.cam = picamera.PiCamera()
self.cam.resolution = Settings['picam.resolution']
self.cam.framerate = Settings['picam.fps']
self.cam.hflip = Settings['picam.hflip']
self.cam.vflip = Settings['picam.vflip']
self.format = Settings['picam.format']
self.continue_recording = False
self.stream = picamera.PiCameraCircularIO(self.cam, seconds=Settings['replay.duration'])
def capture(self, fileToSave):
if onRasp:
self.cam.start_recording(self.stream, self.format)
self.continue_recording = True
def start_recording(self):
if OnRasp:
self.start_flag.set()
def stop_recording(self):
if OnRasp:
self.stop_flag.set()
self.stopped_flag.wait()
try:
while self.continue_recording:
self.cam.wait_recording(1)
finally:
self.cam.stop_recording()
# Clear all control flags
self.stop_flag.clear()
self.start_flag.clear()
self.stopped_flag.clear()
return self.replayPath
def stop(self):
self.start_flag.set()
self.shutdown = True
def run(self):
while not self.shutdown:
self.start_flag.wait()
if not self.shutdown:
self.cam.start_recording(self.stream, Settings['picam.format'])
try:
while not self.stop_flag.is_set():
self.cam.wait_recording(1)
self.stream.copy_to(fileToSave)
self.cam.close()
self.stream.close()
finally :
self.cam.stop_recording()
self.stream.copy_to(self.replayPath)
self.stream.clear()
def stop():
self.continue_recording = False
# Set this flag to tell the calling thread that replay is saved
self.stopped_flag.set()
self.cam.close()
self.stream.close()
@classmethod
def Dummy(cls):
return getContent('Replay Left.mp4')
@staticmethod
def isCamAvailable():
return OnRasp # and other checks (ToDo)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Apr 18 18:34:40 2018
@author: Antoine Lima, Leo Reynaert, Domitille Jehenne
"""
import json
from main import MainWin
class MyEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Setting):
return obj.__dict__
elif isinstance(obj, String) and obj=='settingsPath':
return None
else:
return json.JSONEncoder.default(self, obj)
class Setting(object):
TypeName = ''
def __init__(self, value):
self.type = type(self).TypeName
self.value = value
class SettingBoolean(Setting):
......@@ -36,10 +43,9 @@ class SettingRange(Setting):
def __init__(self, value, limits):
Setting.__init__(self, value)
self.lower_limit = min(limits)
self.upper_limit = max(limits)
self.range = [min(limits), max(limits)]
if self.value<self.lower_limit or self.value>self.upper_limit:
if self.value<self.range[0] or self.value>self.range[1]:
raise ValueError('Setting value {} not in range {}'.format(self.value, (self.lower_limit, self.upper_limit)))
class SettingsHolder(object):
......@@ -73,13 +79,8 @@ class SettingsHolder(object):
@staticmethod
def _parseKey(key):
return [k for k in key.split('.') if k]
def _init_ui(self):
self.ui.append(Setting())
def loadSettingsFromJSON(self):
settings = list()
with open(self.settingsPath, 'r') as f:
content = json.load(f)
......@@ -106,4 +107,15 @@ class SettingsHolder(object):
getattr(self, cat)[name] = setting
Settings = SettingsHolder(MainWin.getContent('settings.json'))
def saveSettingsToJSON(self):
# Deletes the settings path member to prevent it from being saved in the JSON
settingsPath = self.settingsPath
del self.settingsPath
with open(settingsPath, 'w') as f:
json.dump(self.__dict__, f, cls=MyEncoder, indent=4)
self.settingsPath = settingsPath
from main import getContent
Settings = SettingsHolder(getContent('settings.json'))