781 lines
24 KiB
C++
781 lines
24 KiB
C++
/************************************************************************
|
|
MeOS - Orienteering Software
|
|
Copyright (C) 2009-2023 Melin Software HB
|
|
|
|
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/>.
|
|
|
|
Melin Software HB - software@melin.nu - www.melin.nu
|
|
Eksoppsvägen 16, SE-75646 UPPSALA, Sweden
|
|
|
|
************************************************************************/
|
|
|
|
#include "stdafx.h"
|
|
#include "oEvent.h"
|
|
#include "gdioutput.h"
|
|
#include "meos_util.h"
|
|
#include <cassert>
|
|
#include "Localizer.h"
|
|
#include <algorithm>
|
|
#include "gdifonts.h"
|
|
#include "meosexception.h"
|
|
#include "speakermonitor.h"
|
|
#include "oListInfo.h"
|
|
|
|
extern gdioutput *gdi_main;
|
|
|
|
SpeakerMonitor::SpeakerMonitor(oEvent &oe) : oe(oe) {
|
|
placeLimit = 0;
|
|
numLimit = 0;
|
|
|
|
maxClassNameWidth = 0;
|
|
totalResults = false;
|
|
classWidth = 0;
|
|
timeWidth = 0;
|
|
totWidth = 0;
|
|
extraWidth = 0;
|
|
}
|
|
|
|
SpeakerMonitor::~SpeakerMonitor() {
|
|
}
|
|
|
|
void SpeakerMonitor::setClassFilter(const set<int> &filter, const set<int> &cfilter) {
|
|
classFilter = filter;
|
|
controlIdFilter = cfilter;
|
|
oListInfo li;
|
|
maxClassNameWidth = oe.gdiBase().scaleLength(li.getMaxCharWidth(&oe, classFilter, EPostType::lClassName, L"", gdiFonts::normalText));
|
|
}
|
|
|
|
void SpeakerMonitor::setLimits(int place, int num) {
|
|
placeLimit = place;
|
|
numLimit = num;
|
|
}
|
|
|
|
bool orderResultsInTime(const oEvent::ResultEvent &a,
|
|
const oEvent::ResultEvent &b) {
|
|
if (a.time != b.time)
|
|
return a.time > b.time;
|
|
|
|
if (a.time > 0) {
|
|
const wstring &na = a.r->getName();
|
|
const wstring &nb = b.r->getName();
|
|
|
|
return CompareString(LOCALE_USER_DEFAULT, 0,
|
|
na.c_str(), na.length(),
|
|
nb.c_str(), nb.length()) == CSTR_LESS_THAN;
|
|
}
|
|
return a.r->getId() < b.r->getId();
|
|
}
|
|
|
|
void SpeakerMonitor::show(gdioutput &gdi) {
|
|
classWidth = gdi.scaleLength(int(1.5*maxClassNameWidth));
|
|
timeWidth = gdi.scaleLength(60);
|
|
totWidth = gdi.scaleLength(600);
|
|
extraWidth = gdi.scaleLength(200);
|
|
dash = makeDash(L"- ");
|
|
|
|
oe.getResultEvents(classFilter, controlIdFilter, results);
|
|
calculateResults();
|
|
|
|
orderedResults.resize(results.size());
|
|
for (size_t k = 0; k < results.size(); k++) {
|
|
// Initialize forward map
|
|
results[k].localIndex = k;
|
|
orderedResults[k].r = results[k].r;
|
|
orderedResults[k].totalTime = results[k].runTime;
|
|
}
|
|
|
|
// Sort
|
|
sort(results.begin(), results.end(), orderResultsInTime);
|
|
|
|
// Initialize map back
|
|
for (size_t k = 0; k < results.size(); k++) {
|
|
orderedResults[results[k].localIndex].eventIx = k;
|
|
}
|
|
|
|
for (size_t k = 0; k < results.size(); k++) {
|
|
oEvent::ResultEvent &re(results[results.size()-(1+k)]);
|
|
if (re.status == StatusOK) {
|
|
map<int,int> &dynLead = dynamicTotalLeaderTimes[ResultKey(re.classId(), re.control, re.leg())];
|
|
int totTime = re.runTime;
|
|
if (dynLead.empty() || totTime < dynLead.rbegin()->second)
|
|
dynLead[re.time] = totTime;
|
|
}
|
|
}
|
|
|
|
int order = 0;
|
|
for (size_t k = 0; k < results.size() && (order < numLimit || numLimit == 0); k++) {
|
|
if (results[k].time > 0) {
|
|
oEvent::ResultEvent *ahead = 0, *behind = 0;
|
|
|
|
if (results[k].status == StatusOK)
|
|
ahead = k > 0 && sameResultPoint(results[k], results[k-1]) ? &results[k-1] : 0;
|
|
if (results[k].status == StatusOK)
|
|
behind = (k + 1) < results.size() && sameResultPoint(results[k], results[k+1]) ? &results[k+1] : 0;
|
|
|
|
renderResult(gdi, ahead, results[k], behind, order, true);
|
|
}
|
|
}
|
|
|
|
for (size_t k = 0; k < results.size() && (order < numLimit || numLimit == 0); k++) {
|
|
if (results[k].time == 0)
|
|
renderResult(gdi, 0, results[k], 0, order, false);
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool SpeakerMonitor::sameResultPoint(const oEvent::ResultEvent &a,
|
|
const oEvent::ResultEvent &b) {
|
|
return a.control == b.control && a.classId() == b.classId() && a.leg() == b.leg();
|
|
}
|
|
|
|
void SpeakerMonitor::renderResult(gdioutput &gdi,
|
|
const oEvent::ResultEvent *ahead,
|
|
const oEvent::ResultEvent &res,
|
|
const oEvent::ResultEvent *behind,
|
|
int &order,
|
|
bool firstResults) {
|
|
|
|
|
|
/*string s = itos(res.localIndex) + ": " + res.r->getName() + " control: " +
|
|
itos(res.control) + " tid: " + formatTime(res.runTime);
|
|
gdi.addStringUT(0, s);
|
|
*/
|
|
int finishLargePlaceLimit = 5;
|
|
int radioLargePlaceLimit = 3;
|
|
const bool showClass = classFilter.size() > 1;
|
|
|
|
if (res.status == StatusOK) {
|
|
if (res.place > placeLimit && res.r->getSpeakerPriority() == 0 ) {
|
|
return;
|
|
}
|
|
}
|
|
else if (order < max(2, numLimit/20)) {
|
|
return;
|
|
}
|
|
else if (res.status == StatusNotCompetiting) {
|
|
return; // DQ on ealier leg, for example
|
|
}
|
|
|
|
order++;
|
|
const int extra = 5;
|
|
|
|
int t = res.time;
|
|
wstring msg = t>0 ? oe.getAbsTime(t) : makeDash(L"--:--:--");
|
|
pRunner r = res.r;
|
|
|
|
RECT rc;
|
|
rc.top = gdi.getCY();
|
|
rc.left = gdi.getCX();
|
|
|
|
int xp = rc.left + extra;
|
|
int yp = rc.top + extra;
|
|
|
|
bool largeFormat = res.status == StatusOK &&
|
|
((res.control == oPunch::PunchFinish && res.place <= finishLargePlaceLimit) ||
|
|
res.place <= radioLargePlaceLimit);
|
|
wstring message;
|
|
deque<wstring> details;
|
|
getMessage(res,message, details);
|
|
|
|
if (largeFormat) {
|
|
gdi.addStringUT(yp, xp, 0, msg);
|
|
|
|
int dx = 0;
|
|
if (showClass) {
|
|
pClass pc = res.r->getClassRef(true);
|
|
if (pc) {
|
|
gdi.addStringUT(yp, xp + timeWidth, fontMediumPlus, pc->getName());
|
|
dx = classWidth;
|
|
}
|
|
}
|
|
|
|
wstring bib = r->getBib();
|
|
if (!bib.empty())
|
|
msg = bib + L", ";
|
|
else
|
|
msg = L"";
|
|
|
|
msg += r->getCompleteIdentification(false);
|
|
int xlimit = totWidth + extraWidth - (timeWidth + dx);
|
|
gdi.addStringUT(yp, xp + timeWidth + dx, fontMediumPlus, msg, xlimit);
|
|
|
|
yp += int(gdi.getLineHeight() * 1.5);
|
|
|
|
msg = dash + lang.tl(message);
|
|
gdi.addStringUT(yp, xp + 10, breakLines, msg, totWidth);
|
|
|
|
for (size_t k = 0; k < details.size(); k++) {
|
|
gdi.addStringUT(gdi.getCY(), xp + 20, 0, dash + lang.tl(details[k])).setColor(colorDarkGrey);
|
|
}
|
|
|
|
if (res.control == oPunch::PunchFinish && r->getStatus() == StatusOK) {
|
|
splitAnalysis(gdi, xp + 20, gdi.getCY(), r);
|
|
}
|
|
|
|
GDICOLOR color = colorLightRed;
|
|
|
|
if (res.control == oPunch::PunchFinish) {
|
|
if (r && r->statusOK(true, true))
|
|
color = colorLightGreen;
|
|
else
|
|
color = colorLightRed;
|
|
}
|
|
else {
|
|
color = colorLightCyan;
|
|
}
|
|
|
|
rc.bottom = gdi.getCY() + extra;
|
|
rc.right = rc.left + totWidth + extraWidth + 2 * extra;
|
|
gdi.addRectangle(rc, color, true);
|
|
gdi.dropLine(0.5);
|
|
}
|
|
else {
|
|
if (showClass) {
|
|
pClass pc = res.r->getClassRef(true);
|
|
if (pc)
|
|
msg += L" (" + pc->getName() + L") ";
|
|
else
|
|
msg += L" ";
|
|
}
|
|
else
|
|
msg += L" ";
|
|
|
|
msg += r->getCompleteIdentification(false) + L" ";
|
|
|
|
msg += lang.tl(message);
|
|
gdi.addStringUT(gdi.getCY(), gdi.getCX(), breakLines, msg, totWidth);
|
|
|
|
for (size_t k = 0; k < details.size(); k++) {
|
|
gdi.addString("", gdi.getCY(), gdi.getCX()+50, 0, details[k]).setColor(colorDarkGrey);
|
|
}
|
|
|
|
if (res.control == oPunch::PunchFinish && r->getStatus() == StatusOK) {
|
|
splitAnalysis(gdi, xp, gdi.getCY(), r);
|
|
}
|
|
gdi.dropLine(0.5);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
bool compareResult(const oEvent::ResultEvent &a,
|
|
const oEvent::ResultEvent &b) {
|
|
|
|
if (a.classId() != b.classId())
|
|
return a.classId() < b.classId();
|
|
if (a.control != b.control)
|
|
return a.control < b.control;
|
|
int lega = a.leg();
|
|
int legb = b.leg();
|
|
if (lega != legb)
|
|
return lega<legb;
|
|
if (a.resultScore != b.resultScore)
|
|
return a.resultScore < b.resultScore;
|
|
|
|
return a.r->getId() < b.r->getId();
|
|
}
|
|
|
|
void SpeakerMonitor::calculateResults() {
|
|
// TODO Result modules
|
|
|
|
for (size_t k = 0; k < results.size(); k++) {
|
|
results[k].runTime = results[k].r->getTotalRunningTime(results[k].time, true, totalResults);
|
|
if (results[k].status == StatusOK && totalResults)
|
|
results[k].status = results[k].r->getTotalStatus();
|
|
|
|
if (results[k].status == StatusOK)
|
|
results[k].resultScore = results[k].runTime;
|
|
else
|
|
results[k].resultScore = RunnerStatusOrderMap[results[k].status] + timeConstHour*24*7;
|
|
}
|
|
|
|
totalLeaderTimes.clear();
|
|
firstTimes.clear();
|
|
dynamicTotalLeaderTimes.clear();
|
|
runnerToTimeKey.clear();
|
|
timeToResultIx.clear();
|
|
sort(results.begin(), results.end(), compareResult);
|
|
|
|
int clsId = -1;
|
|
int ctrlId = -1;
|
|
int legId = -1;
|
|
int lastScore = 0;
|
|
int place = 0;
|
|
|
|
for (size_t k = 0; k < results.size(); k++) {
|
|
if (results[k].partialCount > 0)
|
|
continue; // Skip in result calculation
|
|
int totTime = results[k].r->getTotalRunningTime(results[k].time, true, totalResults);
|
|
assert(totTime == results[k].runTime);
|
|
int leg = results[k].leg();
|
|
|
|
if (clsId != results[k].classId() || ctrlId != results[k].control || legId != leg) {
|
|
clsId = results[k].classId();
|
|
ctrlId = results[k].control;
|
|
legId = leg;
|
|
place = 1;
|
|
ResultKey key(clsId, ctrlId, leg);
|
|
|
|
totalLeaderTimes[key] = totTime;
|
|
}
|
|
else if (lastScore != results[k].resultScore)
|
|
place++;
|
|
|
|
ResultKey key(clsId, ctrlId, leg);
|
|
lastScore = results[k].resultScore;
|
|
|
|
if (results[k].status == StatusOK) {
|
|
results[k].place = place;
|
|
runnerToTimeKey[results[k].r->getId()].push_back(ResultInfo(key, results[k].time, totTime));
|
|
|
|
if (results[k].time > 0) {
|
|
int val = firstTimes[key];
|
|
firstTimes[key] = (val > 0 && val < results[k].time) ? val : results[k].time;
|
|
}
|
|
}
|
|
else
|
|
results[k].place = 0;
|
|
}
|
|
|
|
// Sort times for each runner
|
|
for (map<int, vector<ResultInfo> >::iterator it = runnerToTimeKey.begin();
|
|
it != runnerToTimeKey.end(); ++it) {
|
|
sort(it->second.begin(), it->second.end(), timeSort);
|
|
}
|
|
}
|
|
|
|
void SpeakerMonitor::handle(gdioutput &gdi, BaseInfo &info, GuiEventType type) {
|
|
|
|
}
|
|
|
|
void SpeakerMonitor::splitAnalysis(gdioutput &gdi, int xp, int yp, pRunner r) {
|
|
vector<int> delta;
|
|
r->getSplitAnalysis(delta);
|
|
wstring timeloss = lang.tl("Bommade kontroller: ");
|
|
pCourse pc = 0;
|
|
bool first = true;
|
|
const int charlimit = 90;
|
|
for (size_t j = 0; j<delta.size(); ++j) {
|
|
if (delta[j] > 0) {
|
|
if (pc == 0) {
|
|
pc = r->getCourse(true);
|
|
if (pc == 0)
|
|
break;
|
|
}
|
|
|
|
if (!first)
|
|
timeloss += L" | ";
|
|
else
|
|
first = false;
|
|
|
|
timeloss += pc->getControlOrdinal(j) + L". " + formatTime(delta[j]);
|
|
}
|
|
if (timeloss.length() > charlimit || (!timeloss.empty() && !first && j+1 == delta.size())) {
|
|
gdi.addStringUT(yp, xp, 0, timeloss).setColor(colorDarkRed);
|
|
yp += gdi.getLineHeight();
|
|
timeloss = L"";
|
|
}
|
|
}
|
|
if (first) {
|
|
gdi.addString("", yp, xp, 0, "Inga bommar registrerade").setColor(colorDarkGreen);
|
|
}
|
|
}
|
|
|
|
wstring getOrder(int k);
|
|
wstring getNumber(int k);
|
|
void getTimeAfterDetail(wstring &detail, int timeAfter, int deltaTime, bool wasAfter);
|
|
|
|
wstring getTimeDesc(int t1, int t2) {
|
|
int tb = abs(t1 - t2);
|
|
wstring stime;
|
|
if (tb == 1)
|
|
stime = L"1" + lang.tl(L" sekund");
|
|
else if (tb <= 60)
|
|
stime = itow(tb) + lang.tl(L" sekunder");
|
|
else
|
|
stime = formatTime(tb);
|
|
|
|
return stime;
|
|
}
|
|
|
|
void SpeakerMonitor::getMessage(const oEvent::ResultEvent &res,
|
|
wstring &message, deque<wstring> &details) {
|
|
pClass cls = res.r->getClassRef(true);
|
|
if (!cls)
|
|
return;
|
|
|
|
wstring &msg = message;
|
|
|
|
int totTime = res.runTime;
|
|
int leaderTime = getLeaderTime(res);
|
|
int timeAfter = totTime - leaderTime;
|
|
|
|
ResultKey key(res.classId(), res.control, res.leg());
|
|
|
|
vector<ResultInfo> &rResults = runnerToTimeKey[res.r->getId()];
|
|
ResultInfo *preRes = 0;
|
|
pRunner preRunner = 0;
|
|
|
|
for (size_t k = 0; k < rResults.size(); k++) {
|
|
if (rResults[k] == key) {
|
|
if (k > 0) {
|
|
preRes = &rResults[k-1];
|
|
preRunner = res.r;
|
|
}
|
|
else {
|
|
int leg = res.r->getLegNumber();
|
|
if (leg > 0 && res.r->getTeam()) {
|
|
while (leg > 0) {
|
|
pRunner pr = res.r->getTeam()->getRunner(--leg);
|
|
// TODO: Pursuit
|
|
if (pr && pr->prelStatusOK(false, false, true) && pr->getFinishTime() == res.r->getStartTime()) {
|
|
vector<ResultInfo> &rResultsP = runnerToTimeKey[pr->getId()];
|
|
if (!rResultsP.empty()) {
|
|
preRes = &rResultsP.back();
|
|
preRunner = pr;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if (res.status != StatusOK) {
|
|
if (res.status != StatusDQ)
|
|
msg = L"är inte godkänd.";
|
|
else
|
|
msg = L"är diskvalificerad.";
|
|
|
|
return;
|
|
}
|
|
|
|
bool hasPrevRes = false;
|
|
int deltaTime = 0;
|
|
|
|
const oEvent::ResultEvent *ahead = getAdjacentResult(res, -1);
|
|
const oEvent::ResultEvent *behind = getAdjacentResult(res, +1);
|
|
|
|
if (preRes != 0) {
|
|
int prevLeader = getDynamicLeaderTime(*preRes, preRes->time);
|
|
int prevAfter = preRes->totTime - prevLeader; // May be negative (for leader)
|
|
int after = totTime - getDynamicLeaderTime(res, res.time);
|
|
|
|
hasPrevRes = totTime > 0 && preRes->totTime > 0;
|
|
|
|
if (after == 0) { // Takes (and holds) the lead in a relay (for example)
|
|
if (!behind) {
|
|
hasPrevRes = false;
|
|
}
|
|
else {
|
|
after = totTime - behind->runTime; // A negative number. Time ahead.
|
|
}
|
|
}
|
|
|
|
if (prevAfter == 0) { // Took (and holds) the lead
|
|
int rix = getResultIx(*preRes);
|
|
const oEvent::ResultEvent *prevBehind = 0;
|
|
if (rix != -1)
|
|
prevBehind = getAdjacentResult(results[rix], +1);
|
|
|
|
if (!prevBehind) {
|
|
hasPrevRes = false;
|
|
}
|
|
else {
|
|
prevAfter = preRes->totTime - prevBehind->runTime; // A negative number. Time ahead
|
|
}
|
|
|
|
}
|
|
|
|
deltaTime = after - prevAfter; // Positive -> more after.
|
|
}
|
|
|
|
wstring timeS = formatTime(res.runTime);
|
|
|
|
wstring detail;
|
|
const wstring *cname = 0;
|
|
if (timeAfter > 0 && ahead)
|
|
cname = &ahead->r->getName();
|
|
else if (timeAfter < 0 && behind)
|
|
cname = &behind->r->getName();
|
|
|
|
int ta = timeAfter;
|
|
if (res.place == 1 && behind && behind->status == StatusOK)
|
|
ta = res.runTime - behind->runTime;
|
|
|
|
getTimeAfterDetail(detail, ta, deltaTime, hasPrevRes);
|
|
if (!detail.empty())
|
|
details.push_back(detail);
|
|
|
|
pCourse crs = res.r->getCourse(false);
|
|
|
|
wstring location, locverb, thelocation;
|
|
|
|
bool finish = res.control == oPunch::PunchFinish;
|
|
bool changeover = false;
|
|
int leg = res.r->getLegNumber();
|
|
int numstage = cls->getNumStages();
|
|
if (finish && res.r->getTeam() != 0 && (leg + 1) < numstage) {
|
|
for (int k = leg + 1; k < numstage; k++)
|
|
if (!cls->isParallel(k) && !cls->isOptional(k)) {
|
|
finish = false;
|
|
changeover = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (finish) {
|
|
location = L"i mål";
|
|
locverb = L"går i mål";
|
|
thelocation = lang.tl(L"målet") + L"#";
|
|
}
|
|
else if (changeover) {
|
|
location = L"vid växeln";
|
|
locverb = L"växlar";
|
|
thelocation = lang.tl(L"växeln") + L"#";
|
|
}
|
|
else {
|
|
thelocation = crs ? (crs->getRadioName(res.control) + L"#") : L"#";
|
|
}
|
|
|
|
|
|
bool sharedPlace = (ahead && ahead->place == res.place) || (behind && behind->place == res.place);
|
|
|
|
if (finish || changeover) {
|
|
if (res.place == 1) {
|
|
if ((ahead == 0 && behind == 0) || firstTimes[key] == res.time)
|
|
msg = L"är först " + location + L" med tiden X.#" + timeS;
|
|
else if (!sharedPlace) {
|
|
msg = L"tar ledningen med tiden X.#" + timeS;
|
|
if (behind && res.place>2) {
|
|
wstring stime(getTimeDesc(behind->runTime, totTime));
|
|
details.push_back(L"är X före Y#" + stime + L"#" + behind->r->getCompleteIdentification(false));
|
|
}
|
|
}
|
|
else
|
|
msg = L"går upp i delad ledning med tiden X.#" + timeS;
|
|
}
|
|
else {
|
|
if (firstTimes[key] == res.time) {
|
|
msg = L"var först " + location + L" med tiden X.#" + timeS;
|
|
|
|
if (!sharedPlace) {
|
|
details.push_front(L"är nu på X plats med tiden Y.#" +
|
|
getOrder(res.place) + L"#" + timeS);
|
|
if (ahead && res.place != 2) {
|
|
wstring stime(getTimeDesc(ahead->runTime, totTime));
|
|
details.push_back(L"är X efter Y#" + stime + L"#" + ahead->r->getCompleteIdentification(false));
|
|
}
|
|
}
|
|
else {
|
|
details.push_back(L"är nu på delad X plats med tiden Y.#" + getOrder(res.place) +
|
|
L"#" + timeS);
|
|
wstring share;
|
|
getSharedResult(res, share);
|
|
details.push_back(share);
|
|
}
|
|
}
|
|
else if (!sharedPlace) {
|
|
msg = locverb + L" på X plats med tiden Y.#" +
|
|
getOrder(res.place) + L"#" + timeS;
|
|
if (ahead && res.place != 2) {
|
|
wstring stime(getTimeDesc(ahead->runTime, totTime));
|
|
details.push_back(L"är X efter Y#" + stime + L"#" + ahead->r->getCompleteIdentification(false));
|
|
}
|
|
}
|
|
else {
|
|
msg = locverb + L" på delad X plats med tiden Y.#" + getOrder(res.place) +
|
|
L"#" + timeS;
|
|
wstring share;
|
|
getSharedResult(res, share);
|
|
details.push_back(share);
|
|
}
|
|
}
|
|
if (changeover && res.r->getTeam() && res.r->getTeam()->getClassRef(true) != 0) {
|
|
wstring vxl = L"skickar ut X.#";
|
|
pTeam t = res.r->getTeam();
|
|
pClass cls = t->getClassRef(true);
|
|
bool second = false;
|
|
int nextLeg = cls->getNextBaseLeg(res.r->getLegNumber());
|
|
if (nextLeg > 0) {
|
|
for (int k = nextLeg; k < t->getNumRunners(); k++) {
|
|
pRunner r = t->getRunner(k);
|
|
if (!r && !vxl.empty())
|
|
continue;
|
|
if (second)
|
|
vxl += L", ";
|
|
second = true;
|
|
vxl += r ? r->getName() : L"?";
|
|
if (!(cls->isParallel(k) || cls->isOptional(k)))
|
|
break;
|
|
}
|
|
}
|
|
details.push_front(vxl);
|
|
}
|
|
}
|
|
else {
|
|
if (res.place == 1) {
|
|
if ((ahead == 0 && behind == 0) || res.time == firstTimes[key])
|
|
msg = L"är först vid X med tiden Y.#" + thelocation + timeS;
|
|
else if (!sharedPlace) {
|
|
msg = L"tar ledningen vid X med tiden Y.#" + thelocation + timeS;
|
|
if (behind) {
|
|
wstring stime(getTimeDesc(behind->runTime, totTime));
|
|
details.push_back(L"är X före Y#" + stime + L"#" + behind->r->getCompleteIdentification(false));
|
|
}
|
|
}
|
|
else
|
|
msg = L"går upp i delad ledning vid X med tiden Y.#" + thelocation + timeS;
|
|
}
|
|
else {
|
|
if (firstTimes[key] == res.time) {
|
|
msg = L"var först vid X med tiden Y.#" + thelocation + timeS;
|
|
|
|
if (!sharedPlace) {
|
|
details.push_front(L"är nu på X plats med tiden Y.#" +
|
|
getOrder(res.place) + L"#" + timeS);
|
|
if (ahead) {
|
|
wstring stime(getTimeDesc(ahead->runTime, totTime));
|
|
details.push_back(L"är X efter Y#" + stime + L"#" + ahead->r->getCompleteIdentification(false));
|
|
}
|
|
}
|
|
else {
|
|
details.push_back(L"är nu på delad X plats med tiden Y.#" + getOrder(res.place) +
|
|
L"#" + timeS);
|
|
wstring share;
|
|
getSharedResult(res, share);
|
|
details.push_back(share);
|
|
}
|
|
}
|
|
else if (!sharedPlace) {
|
|
msg = L"stämplar vid X som Y, på tiden Z.#" +
|
|
thelocation + getNumber(res.place) +
|
|
L"#" + timeS;
|
|
if (ahead && res.place>2) {
|
|
wstring stime(getTimeDesc(ahead->runTime, totTime));
|
|
details.push_back(L"är X efter Y#" + stime + L"#" + ahead->r->getCompleteIdentification(false));
|
|
}
|
|
}
|
|
else {
|
|
msg = L"stämplar vid X som delad Y med tiden Z.#" + thelocation + getNumber(res.place) +
|
|
L"#" + timeS;
|
|
wstring share;
|
|
getSharedResult(res, share);
|
|
details.push_back(share);
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
void SpeakerMonitor::getSharedResult(const oEvent::ResultEvent &res, wstring &detail) const {
|
|
vector<pRunner> shared;
|
|
getSharedResult(res, shared);
|
|
if (!shared.empty())
|
|
detail = L"delar placering med X.#";
|
|
else
|
|
detail = L"";
|
|
|
|
for (size_t k = 0; k < shared.size(); k++) {
|
|
if (k > 0)
|
|
detail += L", ";
|
|
|
|
detail += shared[k]->getCompleteIdentification(false);
|
|
}
|
|
}
|
|
|
|
void SpeakerMonitor::getSharedResult(const oEvent::ResultEvent &res, vector<pRunner> &shared) const {
|
|
shared.clear();
|
|
if (res.status != StatusOK)
|
|
return;
|
|
int p = 1;
|
|
const oEvent::ResultEvent * ar;;
|
|
while ( (ar = getAdjacentResult(res, p++)) != 0 && ar->place == res.place) {
|
|
shared.push_back(ar->r);
|
|
}
|
|
p = 1;
|
|
while ( (ar = getAdjacentResult(res, -(p++))) != 0 && ar->place == res.place) {
|
|
shared.push_back(ar->r);
|
|
}
|
|
}
|
|
|
|
const oEvent::ResultEvent *SpeakerMonitor::getAdjacentResult(const oEvent::ResultEvent &res, int delta) const {
|
|
while (true) {
|
|
int orderIx = res.localIndex + delta;
|
|
if (orderIx < 0 || orderIx >= int(orderedResults.size()) )
|
|
return 0;
|
|
|
|
int aIx = orderedResults[orderIx].eventIx;
|
|
|
|
if (!sameResultPoint(res, results[aIx]))
|
|
return 0;
|
|
|
|
if (results[aIx].partialCount == 0)
|
|
return &results[aIx];
|
|
|
|
if (delta < 0)
|
|
delta--;
|
|
else
|
|
delta++;
|
|
}
|
|
}
|
|
|
|
int SpeakerMonitor::getLeaderTime(const oEvent::ResultEvent &res) {
|
|
return totalLeaderTimes[ResultKey(res.classId(), res.control, res.leg())];
|
|
}
|
|
|
|
int SpeakerMonitor::getDynamicLeaderTime(const oEvent::ResultEvent &res, int time) {
|
|
return getDynamicLeaderTime(ResultKey(res.classId(), res.control, res.leg()), time);
|
|
}
|
|
|
|
int SpeakerMonitor::getDynamicLeaderTime(const ResultKey &res, int time) {
|
|
map<int, int> &leaderMap = dynamicTotalLeaderTimes[res];
|
|
map<int, int>::iterator g = leaderMap.lower_bound(time);
|
|
if (leaderMap.empty())
|
|
return 0;
|
|
|
|
if (g != leaderMap.end()) {
|
|
if (g != leaderMap.begin())
|
|
--g;
|
|
return g->second;
|
|
}
|
|
else
|
|
return leaderMap.rbegin()->second;
|
|
}
|
|
|
|
|
|
int SpeakerMonitor::getResultIx(const ResultInfo &rinfo) {
|
|
if (timeToResultIx.empty()) {
|
|
for (int k = 0; k < results.size(); k++) {
|
|
timeToResultIx.insert(make_pair(results[k].time, k));
|
|
}
|
|
}
|
|
|
|
pair<multimap<int,int>::iterator, multimap<int,int>::iterator> range = timeToResultIx.equal_range(rinfo.time);
|
|
while (range.first != range.second) {
|
|
int ix = range.first->second;
|
|
if (results[ix].classId() == rinfo.classId &&
|
|
results[ix].leg() == rinfo.leg &&
|
|
results[ix].control == rinfo.radio) {
|
|
return ix;
|
|
}
|
|
++range.first;
|
|
}
|
|
|
|
return -1;
|
|
}
|