This repository has been archived on 2024-06-03. You can view files and clone it, but cannot push or open issues or pull requests.
shared-libraries/ScStwLibraries/sources/scstwrace.cpp

410 lines
12 KiB
C++
Raw Normal View History

2020-06-07 14:43:47 +02:00
/****************************************************************************
** ScStw Libraries
** Copyright (C) 2020 Itsblue development
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
****************************************************************************/
#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->nextStartAction = None;
connect(this->nextActionTimer, &QTimer::timeout, this->nextActionLoop, &QEventLoop::quit);
connect(this, &ScStwRace::nextStartActionChanged, this, &ScStwRace::nextStartActionDetailsChanged);
// 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 ---
// --------------------------
2020-04-19 13:09:24 +02:00
int ScStwRace::start(bool asyncronous) {
if(this->state != IDLE) {
return ScStw::CurrentStateNotVaildForOperationError;
}
qDebug() << "+ [INFO] starting race";
this->setState(STARTING);
2020-04-19 13:09:24 +02:00
if(asyncronous) {
QTimer::singleShot(1, [=](){this->playSoundsAndStartTimers(None);});
}
else
this->playSoundsAndStartTimers(None);
2020-04-19 22:39:33 +02:00
return ScStw::Success;
}
int ScStwRace::stop() {
if(this->state != RUNNING && this->state != STARTING) {
return ScStw::CurrentStateNotVaildForOperationError;
}
qDebug() << "+ [INFO] stopping race";
double timeOfStop = QDateTime::currentMSecsSinceEpoch();
2020-04-19 22:39:33 +02:00
int returnCode = ScStw::Success;
foreach(ScStwTimer *speedTimer, this->timers){
if(!speedTimer->stop(timeOfStop) && speedTimer->getState() != ScStwTimer::DISABLED){
2020-04-19 22:39:33 +02:00
returnCode = ScStw::InternalErrorTimerOperationFailed;
}
}
2020-04-19 22:39:33 +02:00
if(returnCode == ScStw::Success) {
this->setState(STOPPED);
}
return returnCode;
}
void ScStwRace::handleTimerStop() {
if(this->state == RUNNING) {
// find out which timer has won
double lowestStoppedTime = -1;
QList<ScStwTimer *> timersWhichHaveWonIds;
// iterate through all timers and find the lowest time taht was stopped
foreach(ScStwTimer * timer, this->timers) {
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
foreach(ScStwTimer * timer, this->timers) {
if(timer->getCurrentTime() > 0
&& (timer->getCurrentTime() <= lowestStoppedTime || lowestStoppedTime < 0)
&& timer->getState() != ScStwTimer::RUNNING
) {
// this is the timer with the lowest stopped time
timersWhichHaveWonIds.append(timer);
}
}
// update the states of all timers
foreach(ScStwTimer * timer, this->timers) {
if(timer->getState() == ScStwTimer::RUNNING)
continue;
if(timersWhichHaveWonIds.contains(timer)) {
timer->setResult(ScStwTimer::WON);
}
else {
timer->setResult(ScStwTimer::LOST);
}
}
}
}
int ScStwRace::reset() {
if(this->state != STOPPED) {
return ScStw::CurrentStateNotVaildForOperationError;
}
qDebug() << "+ [INFO] resetting race";
2020-04-19 22:39:33 +02:00
int returnCode = ScStw::Success;
foreach(ScStwTimer *speedTimer, this->timers){
if(!speedTimer->reset() && speedTimer->getState() != ScStwTimer::DISABLED) {
2020-04-19 22:39:33 +02:00
returnCode = ScStw::InternalErrorTimerOperationFailed;
}
}
2020-04-19 22:39:33 +02:00
if(returnCode == ScStw::Success){
this->setState(IDLE);
}
return returnCode;
}
2020-04-19 22:39:33 +02:00
int ScStwRace::cancel() {
if(this->state != STARTING && this->state != RUNNING)
return ScStw::CurrentStateNotVaildForOperationError;
2020-04-19 22:39:33 +02:00
qDebug() << "[INFO][RACE] cancelling race";
2020-04-19 22:39:33 +02:00
this->soundPlayer->cancel(this->soundVolume);
this->nextActionTimer->stop();
this->nextActionLoop->quit();
this->nextStartAction = None;
int returnCode = ScStw::Success;
2020-04-19 22:39:33 +02:00
foreach(ScStwTimer *timer, this->timers){
if(!timer->cancel() && timer->getState() != ScStwTimer::DISABLED)
returnCode = ScStw::InternalErrorTimerOperationFailed;
}
2020-04-19 22:39:33 +02:00
return returnCode;
}
int ScStwRace::handleFalseStart() {
if(this->getState() != STARTING && this->getState() != RUNNING)
return ScStw::CurrentStateNotVaildForOperationError;
2020-04-19 22:39:33 +02:00
int returnCode = ScStw::Success;
// cancel all running timers
foreach(ScStwTimer *timer, this->timers) {
if(!timer->cancel() && timer->getState() != ScStwTimer::DISABLED && timer->getState() != ScStwTimer::FAILED)
returnCode = ScStw::InternalErrorTimerOperationFailed;
}
2020-04-19 22:39:33 +02:00
this->setState(STOPPED);
this->soundPlayer->cancel(this->soundVolume);
return returnCode;
}
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;
if(this->state != STARTING)
return true;
}
if(thisAction < Start) {
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();
}
// 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;
}
void ScStwRace::setState(RaceState newState) {
if(newState != this->state) {
2020-04-19 13:09:24 +02:00
qDebug() << "[INFO][RACE] 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();
}
}
}
void ScStwRace::refreshTimerStates() {
qDebug() << "[INFO][MAIN] refreshing timer states";
// check if the race is over
bool raceIsOver = true;
foreach(ScStwTimer * timer, this->timers){
if(timer->getState() < ScStwTimer::WON && timer->getState() != ScStwTimer::WAITING){
// if the timer is not in stoped state
raceIsOver = false;
break;
}
else if(timer->getState() == ScStwTimer::WAITING) {
this->handleTimerStop();
}
2020-04-19 22:39:33 +02:00
else if (timer->getState() == ScStwTimer::FAILED) {
this->handleFalseStart();
}
}
2020-04-19 22:39:33 +02:00
if(raceIsOver)
this->setState(STOPPED);
}
// ------------------------
// --- 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);
}
}
QVariantList ScStwRace::getNextStartActionDetails() {
int nextActionDelay = 0;
double nextActionDelayProg = -1;
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 {
this->nextStartAction,
2020-04-19 22:39:33 +02:00
nextActionDelay,
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::stateChanged, this, &ScStwRace::refreshTimerStates);
connect(timer, &ScStwTimer::requestEnableChange, this, &ScStwRace::handleTimerEnable);
connect(timer, &ScStwTimer::stateChanged, this, &ScStwRace::timersChanged);
connect(timer, &ScStwTimer::reactionTimeChanged, this, &ScStwRace::timersChanged);
return true;
}
ScStwRace::RaceState ScStwRace::getState() {
return this->state;
}
ScStwRace::StartAction ScStwRace::getNextStartAction()
{
return this->nextStartAction;
}
QList<ScStwTimer*> ScStwRace::getTimers() {
return this->timers;
}
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());
tmpTimer.insert("text", timer->getText());
2020-06-13 12:38:24 +02:00
tmpTimer.insert("letter", timer->getLetter());
tmpTimers.append(tmpTimer);
}
return tmpTimers;
}