389 lines
11 KiB
C++
389 lines
11 KiB
C++
#include "../headers/scstwrace.h"
|
|
|
|
ScStwRace::ScStwRace(QObject *parent) : QObject(parent)
|
|
{
|
|
this->state = IDLE;
|
|
|
|
// configure the loop that waits for the sound effect to finish
|
|
this->soundPlayer = new ScStwSoundPlayer();
|
|
|
|
// configure timer that handles the delay between the start commands
|
|
this->nextActionTimer = new QTimer(this);
|
|
nextActionTimer->setSingleShot(true);
|
|
this->nextActionLoop = new QEventLoop(this);
|
|
|
|
connect(this->nextActionTimer, &QTimer::timeout, this->nextActionLoop, &QEventLoop::quit);
|
|
|
|
// write default settings
|
|
this->startActionSettings.insert(Start, {{"Enabled", true}, {"Delay", 1}});
|
|
this->writeStartActionSetting(AtYourMarks, false, 0);
|
|
this->writeStartActionSetting(Ready, false, 0);
|
|
this->setSoundVolume(1.0);
|
|
}
|
|
|
|
// --------------------------
|
|
// --- Main Functionality ---
|
|
// --------------------------
|
|
|
|
/**
|
|
* Function to start the race
|
|
*
|
|
* @brief ScStwRace::startRace
|
|
* @return {int} 200: OK; 904: MAIN state not matching
|
|
*/
|
|
int ScStwRace::start() {
|
|
if(this->state != IDLE) {
|
|
return 904;
|
|
}
|
|
|
|
qDebug() << "+ [INFO] starting race";
|
|
|
|
this->setState(STARTING);
|
|
|
|
this->playSoundsAndStartTimers(None);
|
|
|
|
return 200;
|
|
}
|
|
|
|
/**
|
|
* Function to stop the currently running race
|
|
*
|
|
* @brief ScStwRace::stopRace
|
|
* @param {int} type 0: stop; 1: cancel; 2: false start
|
|
* @return {int} 200: OK; 904: MAIN state not matching
|
|
*/
|
|
int ScStwRace::stop() {
|
|
if(this->state != RUNNING && this->state != STARTING) {
|
|
return 904;
|
|
}
|
|
|
|
qDebug() << "+ [INFO] stopping race";
|
|
|
|
int returnCode = 900;
|
|
|
|
bool stopOk = true;
|
|
|
|
foreach(ScStwTimer *speedTimer, this->timers){
|
|
if(!speedTimer->stop() && speedTimer->getState() != ScStwTimer::DISABLED){
|
|
stopOk = false;
|
|
}
|
|
}
|
|
|
|
returnCode = stopOk ? 200:904;
|
|
|
|
if(returnCode == 200) {
|
|
this->setState(STOPPED);
|
|
}
|
|
|
|
return returnCode;
|
|
}
|
|
|
|
/**
|
|
* @brief ScStwRace::handleTimerStop Function to declare the winner and looser timers after a timer has been stopped
|
|
*/
|
|
void ScStwRace::handleTimerStop() {
|
|
if(this->state == RUNNING) {
|
|
// find out which timer has won
|
|
double lowestStoppedTime = -1;
|
|
QList<int> timersWhichHaveWonIds;
|
|
|
|
// iterate through all timers and find the lowest time taht was stopped
|
|
for(int i = 0; i < this->timers.length(); i++) {
|
|
ScStwTimer * timer = this->timers[i];
|
|
if(timer->getCurrentTime() > 0 && (timer->getCurrentTime() <= lowestStoppedTime || lowestStoppedTime < 0)) {
|
|
// this is the timer with the lowest stopped time
|
|
lowestStoppedTime = timer->getCurrentTime();
|
|
}
|
|
}
|
|
|
|
// append the timer(s) with the lowest stopped time to the winner list
|
|
for(int i = 0; i < this->timers.length(); i++) {
|
|
ScStwTimer * timer = this->timers[i];
|
|
if(timer->getCurrentTime() > 0 && (timer->getCurrentTime() <= lowestStoppedTime || lowestStoppedTime < 0)) {
|
|
// this is the timer with the lowest stopped time
|
|
timersWhichHaveWonIds.append(i);
|
|
}
|
|
}
|
|
|
|
// update the states of all timers
|
|
for(int i = 0; i < this->timers.length(); i++) {
|
|
ScStwTimer * timer = this->timers[i];
|
|
|
|
if(!timersWhichHaveWonIds.contains(i) && timer->getCurrentTime() > 0) {
|
|
timer->stop(ScStwTimer::LOST);
|
|
}
|
|
else if (timersWhichHaveWonIds.contains(i)) {
|
|
qDebug() << "timer " << i << " has won";
|
|
timer->stop(ScStwTimer::WON);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief ScStwRace::resetRace
|
|
* @return {int} 200: OK; 904: MAIN state not matching
|
|
*/
|
|
int ScStwRace::reset() {
|
|
if(this->state != STOPPED) {
|
|
return 904;
|
|
}
|
|
|
|
qDebug() << "+ [INFO] resetting race";
|
|
|
|
int returnCode = 200;
|
|
|
|
foreach(ScStwTimer *speedTimer, this->timers){
|
|
if(!speedTimer->reset() && speedTimer->getState() != ScStwTimer::DISABLED) {
|
|
returnCode = 904;
|
|
}
|
|
}
|
|
|
|
if(returnCode == 200){
|
|
this->setState(IDLE);
|
|
}
|
|
|
|
return returnCode;
|
|
}
|
|
|
|
void ScStwRace::cancelStart(bool falseStart){
|
|
// TODO: FIXME
|
|
qDebug() << "+ [INFO] cancelling race: " << falseStart;
|
|
|
|
if(falseStart){
|
|
// cancel all running timers
|
|
foreach(ScStwTimer *timer, this->timers) {
|
|
timer->cancel();
|
|
}
|
|
|
|
this->setState(STOPPED);
|
|
|
|
this->soundPlayer->cancel(this->soundVolume);
|
|
}
|
|
else {
|
|
this->soundPlayer->cancel(this->soundVolume);
|
|
this->nextActionTimer->stop();
|
|
this->nextActionLoop->quit();
|
|
this->nextStartAction = None;
|
|
|
|
foreach(ScStwTimer *timer, this->timers){
|
|
timer->cancel();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ScStwRace::playSoundsAndStartTimers(StartAction thisAction) {
|
|
if(this->state != STARTING)
|
|
return true;
|
|
|
|
double timeOfSoundPlaybackStart;
|
|
if(this->startActionSettings.contains(thisAction) && this->startActionSettings[thisAction]["Enabled"].toBool()) {
|
|
if(!this->soundPlayer->play(thisAction, this->soundVolume, &timeOfSoundPlaybackStart))
|
|
return false;
|
|
}
|
|
|
|
this->nextStartAction = StartAction(thisAction + 1);
|
|
qDebug() << "next action is: " << nextStartAction;
|
|
|
|
int nextActionDelay = -1;
|
|
if(this->startActionSettings.contains(this->nextStartAction) && this->startActionSettings[this->nextStartAction]["Enabled"].toBool()) {
|
|
nextActionDelay = this->startActionSettings[this->nextStartAction]["Delay"].toInt();
|
|
}
|
|
|
|
if(this->nextStartAction <= Start) {
|
|
// perform next action
|
|
this->nextActionTimer->start(nextActionDelay > 0 ? nextActionDelay:1);
|
|
if(nextActionDelay > 0)
|
|
emit this->nextStartActionChanged();
|
|
this->nextActionLoop->exec();
|
|
return this->playSoundsAndStartTimers(this->nextStartAction);
|
|
}
|
|
|
|
// perform start
|
|
|
|
// start all timers
|
|
bool startOk = true;
|
|
foreach(ScStwTimer *timer, this->timers){
|
|
if(!timer->start(timeOfSoundPlaybackStart + 3100) && timer->getState() != ScStwTimer::DISABLED){
|
|
startOk = false;
|
|
}
|
|
}
|
|
if(!startOk) {
|
|
qDebug() << "[ERROR][START] error staring all timers";
|
|
return false;
|
|
}
|
|
|
|
if(!this->soundPlayer->waitForSoundFinish()) {
|
|
qDebug() << "[ERROR][START] start sound wait error";
|
|
return false;
|
|
}
|
|
|
|
// check if a false start occured
|
|
if(this->state != STARTING)
|
|
return true;
|
|
|
|
nextStartAction = None;
|
|
emit this->nextStartActionChanged();
|
|
|
|
this->setState(RUNNING);
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
/**
|
|
* @brief ScStwRace::setState
|
|
* @param {QString} newState
|
|
*/
|
|
void ScStwRace::setState(RaceState newState) {
|
|
if(newState != this->state) {
|
|
qDebug() << "+ [INFO][MAIN] state changed: " << newState;
|
|
this->state = newState;
|
|
emit this->stateChanged(newState);
|
|
|
|
if(this->state == IDLE) {
|
|
// if we changed to IDLE -> enable timers
|
|
foreach(ScStwTimer* timer, this->timerEnableQueque) {
|
|
this->handleTimerEnable(timer);
|
|
}
|
|
|
|
this->timerEnableQueque.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief ScStwRace::refreshTimerStates
|
|
*/
|
|
void ScStwRace::refreshTimerStates() {
|
|
|
|
qDebug() << "[INFO][MAIN] refreshing timer states";
|
|
|
|
// check if the race is over
|
|
int stoppedState = 1;
|
|
|
|
foreach(ScStwTimer * timer, this->timers){
|
|
if(timer->getState() < ScStwTimer::WON){
|
|
// if the timer is not in stoped state
|
|
stoppedState = 0;
|
|
break;
|
|
}
|
|
else if(this->state != RUNNING) {
|
|
// TODO athleteProfiles->addResult(timer->getCurrTime(), timer->getReactionTime(), this->speedTimers.indexOf(timer));
|
|
}
|
|
}
|
|
|
|
if(stoppedState == 1){
|
|
this->setState(STOPPED);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// ------------------------
|
|
// --- helper functions ---
|
|
// ------------------------
|
|
|
|
/**
|
|
* @brief ScStwRace::handleTimerEnable function to enable timers at the right moment to prevent them from bricking the state machine
|
|
* @param {ScStwExtensionControlledTimer*} timer timer to be enabled
|
|
*/
|
|
void ScStwRace::handleTimerEnable(ScStwTimer* timer) {
|
|
if(this->state == IDLE) {
|
|
if(timer->getState() == ScStwTimer::DISABLED) {
|
|
timer->setDisabled(false);
|
|
}
|
|
else {
|
|
timer->setDisabled(true);
|
|
}
|
|
}
|
|
else {
|
|
this->timerEnableQueque.append(timer);
|
|
}
|
|
}
|
|
|
|
QVariantMap ScStwRace::getNextStartActionDetails() {
|
|
int nextActionDelay = 0;
|
|
double nextActionDelayProg = 0;
|
|
|
|
if(this->nextStartAction == AtYourMarks || this->nextStartAction == Ready) {
|
|
// get the total delay and the delay progress of the next action timer
|
|
double remaining = this->nextActionTimer->remainingTime();
|
|
nextActionDelay = this->startActionSettings[this->nextStartAction]["Delay"].toInt();
|
|
if(remaining < 0) {
|
|
remaining = nextActionDelay;
|
|
}
|
|
nextActionDelayProg = 1 - (remaining / nextActionDelay);
|
|
}
|
|
|
|
return {{"nextAction", this->nextStartAction}, {"nextActionDelay", nextActionDelay}, {"nextActionDelayProg", nextActionDelayProg}};
|
|
}
|
|
|
|
bool ScStwRace::writeStartActionSetting(StartAction action, bool enabled, int delay) {
|
|
if(action != AtYourMarks && action != Ready)
|
|
return false;
|
|
|
|
QVariantMap setting = {{"Enabled", enabled}, {"Delay", delay}};
|
|
|
|
if(!this->startActionSettings.contains(action))
|
|
this->startActionSettings.insert(action, setting);
|
|
else
|
|
this->startActionSettings[action] = setting;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ScStwRace::setSoundVolume(double volume) {
|
|
if(volume >= 0 && volume <= 1) {
|
|
this->soundVolume = volume;
|
|
return true;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool ScStwRace::addTimer(ScStwTimer *timer) {
|
|
if(this->state != IDLE)
|
|
return false;
|
|
|
|
foreach(ScStwTimer *existingTimer, this->timers) {
|
|
if(existingTimer == timer)
|
|
return true;
|
|
}
|
|
|
|
this->timers.append(timer);
|
|
|
|
connect(timer, &ScStwTimer::startCanceled, this, &ScStwRace::cancelStart);
|
|
connect(timer, &ScStwTimer::stateChanged, this, &ScStwRace::refreshTimerStates);
|
|
connect(timer, &ScStwTimer::stopRequested, this, &ScStwRace::handleTimerStop);
|
|
connect(timer, &ScStwTimer::requestEnableChange, this, &ScStwRace::handleTimerEnable);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
ScStwRace::RaceState ScStwRace::getState() {
|
|
return this->state;
|
|
}
|
|
|
|
ScStwRace::StartAction ScStwRace::getNextStartAction()
|
|
{
|
|
return this->nextStartAction;
|
|
}
|
|
|
|
QVariantList ScStwRace::getTimerDetailList() {
|
|
QVariantList tmpTimers;
|
|
|
|
foreach(ScStwTimer * timer, this->timers){
|
|
QVariantMap tmpTimer;
|
|
tmpTimer.insert("id", this->timers.indexOf(timer));
|
|
tmpTimer.insert("state", timer->getState());
|
|
tmpTimer.insert("currentTime", timer->getCurrentTime());
|
|
tmpTimer.insert("reactionTime", timer->getReactionTime());
|
|
tmpTimers.append(tmpTimer);
|
|
}
|
|
|
|
return tmpTimers;
|
|
}
|