833 lines
26 KiB
C++
833 lines
26 KiB
C++
/************************************************************************
|
|
MeOS - Orienteering Software
|
|
Copyright (C) 2009-2024 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 "qualification_final.h"
|
|
|
|
#include "meos_util.h"
|
|
#include "meosexception.h"
|
|
#include <set>
|
|
#include <algorithm>
|
|
#include "xmlparser.h"
|
|
#include "oRunner.h"
|
|
#include "localizer.h"
|
|
|
|
pair<int, int> QualificationFinal::getPrelFinalFromPlace(int instance, int orderPlace, int numSharedPlaceNext) {
|
|
pair<int, int> key(instance, orderPlace);
|
|
int iter = 0;
|
|
while (numSharedPlaceNext >= 0) {
|
|
auto res = sourcePlaceToFinalOrder.find(key);
|
|
if (res != sourcePlaceToFinalOrder.end()) {
|
|
if (iter >= 2) { // For three in a shared last place
|
|
pair<int, int> key2 = key;
|
|
int extraSub = ((iter + 1) % 2);
|
|
key2.second -= extraSub;
|
|
auto res2 = sourcePlaceToFinalOrder.find(key2);
|
|
if (res2 != sourcePlaceToFinalOrder.end()) {
|
|
auto ans = res2->second;
|
|
ans.second += iter + extraSub;
|
|
++numExtraAssigned[ans.first]; // More than specified
|
|
return ans;
|
|
}
|
|
}
|
|
|
|
auto ans = res->second;
|
|
ans.second += iter;
|
|
if (iter > 0)
|
|
++numExtraAssigned[ans.first]; // More than specified
|
|
|
|
return ans;
|
|
}
|
|
--key.second;
|
|
--numSharedPlaceNext;
|
|
++iter;
|
|
}
|
|
|
|
return make_pair(0, -1);
|
|
}
|
|
|
|
bool QualificationFinal::noQualification(int instance) const {
|
|
if (size_t(instance) >= classDefinition.size())
|
|
return false;
|
|
|
|
return classDefinition[instance].qualificationMap.empty() &&
|
|
classDefinition[instance].numTimeQualifications == 0 &&
|
|
classDefinition[instance].extraQualification == QFClass::ExtraQualType::None;
|
|
}
|
|
|
|
void QualificationFinal::getBaseClassInstances(set<int> &base) const {
|
|
for (size_t k = 0; k < classDefinition.size(); k++) {
|
|
if (noQualification(k))
|
|
base.insert(k+1);
|
|
else break;
|
|
}
|
|
|
|
}
|
|
|
|
void QualificationFinal::exportXML(const wstring& file) const {
|
|
xmlparser xml;
|
|
xml.openOutputT(file.c_str(), false, "QualificationRules");
|
|
int cLevel = -1;
|
|
|
|
for (int j = 0; j < classDefinition.size(); j++) {
|
|
int level = getLevel(j+1);
|
|
if (level != cLevel) {
|
|
if (cLevel >= 0)
|
|
xml.endTag();
|
|
|
|
cLevel = level;
|
|
if (classDefinition[j].rankLevel)
|
|
xml.startTag("Level", "distribution", "Ranking");
|
|
else
|
|
xml.startTag("Level");
|
|
}
|
|
|
|
vector<wstring> pv;
|
|
pv.emplace_back(L"name");
|
|
pv.emplace_back(classDefinition[j].name.empty() ? lang.tl("Kval") + itow(j + 1) : classDefinition[j].name);
|
|
pv.emplace_back(L"id");
|
|
pv.emplace_back(itow(j + 1));
|
|
xml.startTag("Class", pv);
|
|
|
|
vector<pair<string, wstring>> psv(2);;
|
|
psv[0].first = "id";
|
|
psv[1].first = "place";
|
|
|
|
for (auto& qmap : classDefinition[j].qualificationMap) {
|
|
psv[0].second = itow(qmap.first);
|
|
psv[1].second = itow(qmap.second);
|
|
xml.write("Qualification", psv, L"");
|
|
}
|
|
|
|
if (classDefinition[j].numTimeQualifications > 0) {
|
|
psv[0].first = "time";
|
|
psv[0].second = itow(classDefinition[j].numTimeQualifications);
|
|
psv.resize(1);
|
|
xml.write("Qualification", psv, L"");
|
|
}
|
|
|
|
if (classDefinition[j].extraQualification != QFClass::ExtraQualType::None) {
|
|
psv[0].first = "type";
|
|
switch (classDefinition[j].extraQualification) {
|
|
case QFClass::ExtraQualType::All:
|
|
psv.resize(1);
|
|
psv[0].second = L"All";
|
|
break;
|
|
case QFClass::ExtraQualType::NBest:
|
|
psv.resize(2);
|
|
psv[0].second = L"Best";
|
|
psv[1].first = "number";
|
|
psv[1].second = itow(classDefinition[j].extraQualData);
|
|
break;
|
|
case QFClass::ExtraQualType::TimeLimit:
|
|
psv.resize(2);
|
|
psv[0].second = L"Time";
|
|
psv[1].first = "limit";
|
|
psv[1].second = formatTimeMS(classDefinition[j].extraQualData, false);
|
|
break;
|
|
}
|
|
xml.write("Remaining", psv, L"");
|
|
}
|
|
|
|
xml.endTag();
|
|
}
|
|
xml.closeOut();
|
|
}
|
|
|
|
void QualificationFinal::importXML(const wstring &file) {
|
|
xmlparser xml;
|
|
xml.read(file);
|
|
|
|
auto qr = xml.getObject("QualificationRules");
|
|
xmlList levels;
|
|
qr.getObjects("Level", levels);
|
|
map<int, int> idToIndex;
|
|
map<int, set<int> > qualificationRelations;
|
|
int numBaseLevels = 0;
|
|
for (size_t iLevel = 0; iLevel < levels.size(); iLevel++) {
|
|
auto &level = levels[iLevel];
|
|
wstring rankS;
|
|
level.getObjectString("distribution", rankS);
|
|
bool rankSort = false;
|
|
if (rankS == L"Ranking")
|
|
rankSort = true;
|
|
else if (!rankS.empty())
|
|
throw meosException(L"Unknown distribution: " + rankS);
|
|
|
|
xmlList classes;
|
|
level.getObjects("Class", classes);
|
|
for (auto &cls : classes) {
|
|
wstring name;
|
|
|
|
cls.getObjectString("name", name);
|
|
if (name.empty())
|
|
cls.getObjectString("Name", name);
|
|
|
|
if (name.empty())
|
|
throw meosException("Klassen måste ha ett namn.");
|
|
int classId = cls.getObjectInt("id");
|
|
if (!(classId>0))
|
|
throw meosException("Id must be a positive integer.");
|
|
if (idToIndex.count(classId))
|
|
throw meosException("Duplicate class with id " + itos(classId));
|
|
|
|
xmlList rules;
|
|
cls.getObjects("Qualification", rules);
|
|
|
|
|
|
xmlobject remaining = cls.getObject("Remaining");
|
|
|
|
if (rules.empty() && !remaining)
|
|
numBaseLevels = 1; // Instance zero is not used as qualification,
|
|
|
|
idToIndex[classId] = classDefinition.size() + numBaseLevels;
|
|
classDefinition.emplace_back();
|
|
classDefinition.back().level = iLevel;
|
|
if (remaining) {
|
|
wstring rtype;
|
|
remaining.getObjectString("type", rtype);
|
|
if (rtype == L"All") {
|
|
classDefinition.back().extraQualification = QFClass::ExtraQualType::All;
|
|
}
|
|
else if (rtype == L"Best") {
|
|
classDefinition.back().extraQualification = QFClass::ExtraQualType::NBest;
|
|
classDefinition.back().extraQualData = remaining.getObjectInt("number");
|
|
if (classDefinition.back().extraQualData > 10000 || classDefinition.back().extraQualData < 0)
|
|
classDefinition.back().extraQualData = 0;
|
|
}
|
|
else if (rtype == L"Time") {
|
|
classDefinition.back().extraQualification = QFClass::ExtraQualType::TimeLimit;
|
|
wstring wt;
|
|
remaining.getObjectString("limit", wt);
|
|
classDefinition.back().extraQualData = convertAbsoluteTimeMS(wt);
|
|
if (classDefinition.back().extraQualData == NOTIME || classDefinition.back().extraQualData < 0)
|
|
classDefinition.back().extraQualData = 0;
|
|
|
|
}
|
|
}
|
|
|
|
classDefinition.back().name = name;
|
|
classDefinition.back().rankLevel = rankSort;
|
|
for (auto &qf : rules) {
|
|
int place = qf.getObjectInt("place");
|
|
if (place > 0) {
|
|
int id = qf.getObjectInt("id");
|
|
if (id == 0 && iLevel == 0)
|
|
classDefinition.back().qualificationMap.push_back(make_pair(0, place));
|
|
else if (idToIndex.count(id)) {
|
|
classDefinition.back().qualificationMap.push_back(make_pair(idToIndex[id], place));
|
|
qualificationRelations[classId].insert(id);
|
|
}
|
|
else
|
|
throw meosException("Unknown class with id " + itos(id));
|
|
}
|
|
else {
|
|
int numTime = qf.getObjectInt("time");
|
|
if (numTime > 0)
|
|
classDefinition.back().numTimeQualifications += numTime;
|
|
else if (qf.got("time")) {
|
|
throw meosException(L"Empty time qualification for " + name);
|
|
}
|
|
else throw meosException(L"Unknown classification rule for " + name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
initgmap(true);
|
|
}
|
|
|
|
void QualificationFinal::init(const wstring &def) {
|
|
serializedFrom = def;
|
|
vector<wstring> races, rtdef, rdef;
|
|
split(def, L"|", races);
|
|
classDefinition.resize(races.size());
|
|
bool valid = true;
|
|
bool first = true;
|
|
for (size_t k = 0; k < races.size(); k++) {
|
|
if (races[k].size() > 0 && races[k][0] == '@') {
|
|
// Explicit level, since 4.0
|
|
classDefinition[k].level = _wtoi(races[k].c_str() + 1);
|
|
int mx = 1;
|
|
while (mx < races[k].size() && races[k][mx] != '@')
|
|
mx++;
|
|
int mx2 = mx + 1;
|
|
while (mx2 < races[k].size() && races[k][mx2] != '@')
|
|
mx2++;
|
|
|
|
if (mx2 >= races[k].size() || classDefinition[k].level > k) {
|
|
valid = false;
|
|
break;
|
|
}
|
|
races[k][mx2] = 0;
|
|
classDefinition[k].name = races[k].c_str() + mx + 1;
|
|
races[k] = races[k].substr(mx2 + 1);
|
|
}
|
|
bool rankLevel = false;
|
|
if (races[k].size() > 0 && races[k][0] == 'R') {
|
|
rankLevel = true;
|
|
races[k] = races[k].substr(1);
|
|
}
|
|
|
|
if (races[k].size() > 0 && QFClass::deserialType(races[k][0]) != QFClass::ExtraQualType::None) {
|
|
first = false;
|
|
classDefinition[k].extraQualification = QFClass::deserialType(races[k][0]);
|
|
if (classDefinition[k].extraQualification == QFClass::ExtraQualType::NBest ||
|
|
classDefinition[k].extraQualification == QFClass::ExtraQualType::TimeLimit) {
|
|
classDefinition[k].extraQualData = _wtoi(races[k].c_str() + 1);
|
|
}
|
|
int end = 1;
|
|
while (end < races[k].size() && races[k][end-1] != ';')
|
|
end++;
|
|
races[k] = races[k].substr(end);
|
|
}
|
|
split(races[k], L"T", rtdef);
|
|
classDefinition[k].qualificationMap.clear();
|
|
classDefinition[k].numTimeQualifications = 0;
|
|
classDefinition[k].rankLevel = rankLevel;
|
|
|
|
if (rtdef.empty())
|
|
continue; // Remaining qualified
|
|
|
|
first = false;
|
|
|
|
split(rtdef[0], L";", rdef);
|
|
bool thisValid = rdef.size()%2 == 0 &&
|
|
(rdef.size() > 0 || (rtdef.size() == 2 && !rtdef[1].empty()));
|
|
|
|
if (!thisValid)
|
|
continue;
|
|
|
|
for (size_t j = 0; j < rdef.size(); j+=2) {
|
|
int src = _wtoi(rdef[j].c_str());
|
|
if (src > k || src <= 0) {
|
|
thisValid = false;
|
|
break;
|
|
}
|
|
const wstring &rd = rdef[j + 1];
|
|
int d1 = _wtoi(rd.c_str());
|
|
if (d1 < 1 || d1>1000) {
|
|
thisValid = false;
|
|
break;
|
|
}
|
|
int d2 = d1;
|
|
size_t range = rd.find_first_of('-', 0);
|
|
if (range < rd.size())
|
|
d2 = _wtoi(rd.c_str()+range+1);
|
|
|
|
if (d1 > d2) {
|
|
thisValid = false;
|
|
break;
|
|
}
|
|
|
|
while (d1 <= d2) {
|
|
classDefinition[k].qualificationMap.emplace_back(src, d1);
|
|
d1++;
|
|
}
|
|
}
|
|
|
|
if (!thisValid)
|
|
classDefinition[k].qualificationMap.clear();
|
|
|
|
if (rtdef.size() > 1) {
|
|
int numTime = _wtoi(rtdef[1].c_str());
|
|
classDefinition[k].numTimeQualifications = numTime;
|
|
}
|
|
}
|
|
|
|
if (!valid)
|
|
classDefinition.clear();
|
|
|
|
initgmap(false);
|
|
}
|
|
|
|
|
|
void QualificationFinal::setClasses(const vector<QFClass>& def) {
|
|
classDefinition = def;
|
|
initgmap(true);
|
|
wstring tmp;
|
|
encode(tmp);
|
|
}
|
|
|
|
wstring QualificationFinal::validName(const wstring& name) {
|
|
wstring out;
|
|
out.reserve(name.size());
|
|
for (int j = 0; j < name.length(); j++)
|
|
if (isValidNameChar(name[j]))
|
|
out.push_back(name[j]);
|
|
|
|
return out;
|
|
}
|
|
|
|
bool QualificationFinal::isValidNameChar(wchar_t c) {
|
|
if (c == 0 || c == '|' || c == '@')
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void QualificationFinal::encode(wstring &output) const {
|
|
output.clear();
|
|
|
|
for (size_t k = 0; k < classDefinition.size(); k++) {
|
|
if (k > 0)
|
|
output.append(L"|");
|
|
|
|
output.append(L"@"+ itow(getLevel(k+1)) + L"@" + validName(classDefinition[k].name) + L"@");
|
|
|
|
if (classDefinition[k].rankLevel)
|
|
output.append(L"R");
|
|
|
|
output.append(classDefinition[k].serialExtra());
|
|
|
|
auto &qm = classDefinition[k].qualificationMap;
|
|
|
|
for (size_t j = 0; j < qm.size(); j++) {
|
|
if (j > 0)
|
|
output.append(L";");
|
|
|
|
size_t i = j;
|
|
while ((i + 1) < qm.size() && qm[i + 1].first == qm[i].first
|
|
&& qm[i + 1].second == qm[i].second+1) {
|
|
i++;
|
|
}
|
|
output.append(itow(qm[j].first) + L";");
|
|
if (i <= j + 1) {
|
|
output.append(itow(qm[j].second));
|
|
}
|
|
else {
|
|
output.append(itow(qm[j].second) + L"-" + itow(qm[i].second));
|
|
j = i;
|
|
}
|
|
}
|
|
|
|
if (classDefinition[k].numTimeQualifications > 0) {
|
|
output.append(L"T");
|
|
output.append(itow(classDefinition[k].numTimeQualifications));
|
|
}
|
|
}
|
|
|
|
serializedFrom = output;
|
|
}
|
|
|
|
wstring QFClass::serialExtra() const {
|
|
if (extraQualification == ExtraQualType::All)
|
|
return L"A;";
|
|
else if (extraQualification == ExtraQualType::TimeLimit)
|
|
return L"L" + itow(extraQualData) + L";";
|
|
else if (extraQualification == ExtraQualType::NBest)
|
|
return L"B" + itow(extraQualData) + L";";
|
|
return L"";
|
|
}
|
|
|
|
int QFClass::getMinQualInst() const {
|
|
if (qualificationMap.empty())
|
|
return -1;
|
|
int ret = 1024;
|
|
for (auto& cp : qualificationMap)
|
|
ret = min(ret, cp.first);
|
|
return ret;
|
|
}
|
|
|
|
void QualificationFinal::initgmap(bool check) {
|
|
level2DefningInstance.clear();
|
|
sourcePlaceToFinalOrder.clear();
|
|
levels.clear();
|
|
levels.resize(getNumLevels(), LevelInfo::Normal);
|
|
|
|
for (int ix = 0; ix < (int)classDefinition.size(); ix++) {
|
|
auto &c = classDefinition[ix];
|
|
if (c.rankLevel)
|
|
levels[getLevel(ix+1)] = LevelInfo::RankSort;
|
|
|
|
for (int k = 0; k < (int)c.qualificationMap.size(); k++) {
|
|
const pair<int, int> &sd = c.qualificationMap[k];
|
|
if (check && sourcePlaceToFinalOrder.count(sd))
|
|
throw meosException(L"Inconsistent qualification rule, X#" + c.name + + L"/" + itow(sd.first));
|
|
|
|
sourcePlaceToFinalOrder[sd] = make_pair(ix+1, k);
|
|
}
|
|
}
|
|
}
|
|
|
|
int QualificationFinal::getLevel(int instance) const {
|
|
if (instance == 0)
|
|
return 0;
|
|
instance--;
|
|
if (classDefinition[instance].level >= 0)
|
|
return classDefinition[instance].level;
|
|
int level = 0;
|
|
int src = instance;
|
|
while (!classDefinition[instance].qualificationMap.empty()) {
|
|
instance = classDefinition[instance].qualificationMap.front().first - 1;
|
|
if (instance < 0)
|
|
break;
|
|
level++;
|
|
if (size_t(level) > classDefinition.size())
|
|
throw meosException("Internal error");
|
|
}
|
|
classDefinition[src].level = level;
|
|
return level;
|
|
}
|
|
|
|
bool QualificationFinal::isFinalClass(int instance) const {
|
|
return instance > 0 && getLevel(instance) + 1 == getNumLevels();
|
|
}
|
|
|
|
int QualificationFinal::getNumLevels() const {
|
|
return getLevel(classDefinition.size()) + 1;
|
|
}
|
|
|
|
int QualificationFinal::getMinInstance(const int levelIn) const {
|
|
int level = levelIn;
|
|
auto res = level2DefningInstance.find(levelIn);
|
|
if (res != level2DefningInstance.end())
|
|
return res->second;
|
|
|
|
int minInst = classDefinition.size() - 1;
|
|
for (int i = classDefinition.size() - 1; i >= 0; i--) {
|
|
if (i == 0 && minInst == 1)
|
|
break; // No need to include base instance.
|
|
int thisLevel = getLevel(i+1);
|
|
if (thisLevel > level) {
|
|
int minQualInst = classDefinition[i].getMinQualInst();
|
|
if (minQualInst >= 0) {
|
|
// If there are direct qualification to this level from an
|
|
// earlier level, we must start from that level.
|
|
int qualLevel = getLevel(minQualInst+1);
|
|
if (qualLevel < level) {
|
|
level = qualLevel;
|
|
i = classDefinition.size(); // Restart
|
|
}
|
|
}
|
|
}
|
|
else if (thisLevel == level)
|
|
minInst = i;
|
|
}
|
|
|
|
level2DefningInstance[levelIn] = minInst;
|
|
return minInst;
|
|
}
|
|
|
|
|
|
int QualificationFinal::getHeatFromClass(int finalClassId, int baseClassId) const {
|
|
if (baseClassId == baseId) {
|
|
int fci = (finalClassId - baseId) / maxClassId;
|
|
|
|
if (fci * maxClassId + baseClassId == finalClassId)
|
|
return fci;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** Clear previous staus data*/
|
|
void QualificationFinal::prepareCalculations() {
|
|
storedInfoLookup.clear();
|
|
storedInfo.clear();
|
|
numExtraAssigned.clear();
|
|
}
|
|
|
|
/** Retuns the final class and the order within that class. */
|
|
void QualificationFinal::provideQualificationResult(pRunner r, int instance, int orderPlace, int numSharedPlace) {
|
|
storedInfo.resize(getNumClasses());
|
|
|
|
pair<int, int> res = getPrelFinalFromPlace(instance, orderPlace, numSharedPlace);
|
|
int level = getLevel(instance);
|
|
storedInfo[level].emplace_back(r, res.first, res.second);
|
|
}
|
|
|
|
void QualificationFinal::provideUnqualified(int level, pRunner r) {
|
|
storedInfo.resize(getNumClasses());
|
|
storedInfo[level].emplace_back(r, -1, storedInfo[level].size());
|
|
}
|
|
|
|
/** Do internal calculations. */
|
|
void QualificationFinal::computeFinals() {
|
|
if (storedInfo.empty())
|
|
return;
|
|
int nl = getNumLevels();
|
|
|
|
// Handle time qualifications
|
|
vector<pair<int, ResultInfo*>> timeToIx;
|
|
set<pair<int, ResultInfo*>> remainingTimeQualified;
|
|
int lastLevel = -1;
|
|
vector<int> levelClasses;
|
|
|
|
auto distributeRemaning = [&]() {
|
|
if (levelClasses.empty())
|
|
return;
|
|
int cIx = 0;
|
|
for (auto& tq : remainingTimeQualified) {
|
|
tq.second->instance = levelClasses[cIx];
|
|
tq.second->order = 100 + cIx;
|
|
cIx++;
|
|
if (cIx >= levelClasses.size())
|
|
cIx = 0;
|
|
}
|
|
};
|
|
|
|
for (int j = 1; j < classDefinition.size(); j++) {
|
|
int level = getLevel(j + 1) - 1;
|
|
if (level != lastLevel) {
|
|
distributeRemaning();
|
|
levelClasses.clear();
|
|
remainingTimeQualified.clear();
|
|
lastLevel = level;
|
|
}
|
|
levelClasses.push_back(j + 1);
|
|
int NT = classDefinition[j].numTimeQualifications;
|
|
auto res = numExtraAssigned.find(j + 1);
|
|
if (res != numExtraAssigned.end()) {
|
|
NT -= res->second; // Already assigned from (duplicate) places
|
|
}
|
|
for (int i = 0; i < NT; i++) {
|
|
timeToIx.clear();
|
|
int order = 0;
|
|
// Select the best time from unqualified competitors
|
|
if (level < storedInfo.size()) {
|
|
for (auto& res : storedInfo[level]) {
|
|
if (res.instance == 0) {
|
|
int t = res.r->getRunningTime(false);
|
|
if (timeToIx.empty() || t <= timeToIx.front().first) {
|
|
// Select the current best time
|
|
|
|
if (!timeToIx.empty() && t < timeToIx.front().first)
|
|
timeToIx.clear(); // Found better time
|
|
|
|
timeToIx.emplace_back(t, &res);
|
|
}
|
|
}
|
|
else {
|
|
order = std::max(res.order, order);
|
|
}
|
|
}
|
|
}
|
|
if (!timeToIx.empty()) {
|
|
timeToIx[0].second->instance = j + 1;
|
|
timeToIx[0].second->order = ++order;
|
|
for (int j = 1; j < timeToIx.size(); j++) {
|
|
// Qualified by time, but not yet in a race (tie position)
|
|
remainingTimeQualified.insert(timeToIx[j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Allow both to advance on tie
|
|
distributeRemaning();
|
|
|
|
for (int j = 1; j < classDefinition.size(); j++) {
|
|
int level = getLevel(j + 1) - 1;
|
|
|
|
if (classDefinition[j].extraQualification == QFClass::ExtraQualType::All) {
|
|
// All remaining
|
|
for (auto& si : storedInfo[level]) {
|
|
if (si.instance <= 0)
|
|
si.instance = j + 1;
|
|
}
|
|
}
|
|
else if (classDefinition[j].extraQualification == QFClass::ExtraQualType::NBest) {
|
|
// N best competitors
|
|
int count = 0;
|
|
for (auto& si : storedInfo[level]) {
|
|
if (si.instance <= 0) {
|
|
si.instance = j + 1;
|
|
if (++count >= classDefinition[j].extraQualData)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (classDefinition[j].extraQualification == QFClass::ExtraQualType::TimeLimit) {
|
|
// Fixed time limit
|
|
for (auto& si : storedInfo[level]) {
|
|
if (si.instance == 0 &&
|
|
si.r->getStatus() == StatusOK &&
|
|
si.r->getRunningTime(false) < classDefinition[j].extraQualData){
|
|
si.instance = j + 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
for (int level = 1; level < nl; level++) {
|
|
vector<int> placeCount(classDefinition.size(), 0);
|
|
if (levels[level] == LevelInfo::RankSort) {
|
|
vector<pair<int, int>> rankIx;
|
|
vector<pair<int, int>> placeQueue;
|
|
|
|
set<int> numClassCount;
|
|
auto &si = storedInfo[level-1];
|
|
if (si.empty())
|
|
continue;
|
|
rankIx.reserve(si.size());
|
|
placeQueue.reserve(si.size());
|
|
|
|
for (int i = 0; size_t(i) < si.size(); i++) {
|
|
if (si[i].instance > 0) {
|
|
int rank = si[i].r->getRanking();
|
|
rankIx.emplace_back(rank, i);
|
|
placeQueue.emplace_back(si[i].order, si[i].instance);
|
|
numClassCount.insert(si[i].instance);
|
|
}
|
|
}
|
|
int numClass = numClassCount.size();
|
|
vector<int> rotmap;
|
|
if (numClass == 2) {
|
|
rotmap = { 1,0 };
|
|
}
|
|
else if (numClass == 3) {
|
|
rotmap = { 2,0,1 };
|
|
}
|
|
else if (numClass == 4) {
|
|
rotmap = { 3, 2, 0, 1 };
|
|
}
|
|
else if (numClass == 5) {
|
|
rotmap = { 4, 3, 0, 2, 1 };
|
|
}
|
|
else if (numClass == 6) {
|
|
rotmap = { 5, 4, 3, 0, 2, 1 };
|
|
}
|
|
else {
|
|
rotmap.resize(numClass);
|
|
for (int k = 0; k < numClass; k++)
|
|
rotmap[k] = (k + 1) % numClass;
|
|
}
|
|
|
|
sort(rankIx.begin(), rankIx.end());
|
|
sort(placeQueue.begin(), placeQueue.end());
|
|
vector<int> placementOrder;
|
|
for (size_t ix = 0; ix < placeQueue.size(); ix++) {
|
|
placementOrder.push_back(ix);
|
|
}
|
|
for (size_t i = numClass; i + numClass <= placementOrder.size(); i += numClass * 2) {
|
|
reverse(placementOrder.begin() + i, placementOrder.begin() + i + numClass);
|
|
}
|
|
reverse(placementOrder.begin(), placementOrder.end());
|
|
|
|
for (auto &rankRes : rankIx) {
|
|
si[rankRes.second].instance = placeQueue[placementOrder.back()].second;
|
|
si[rankRes.second].order = placeQueue[placementOrder.back()].first;
|
|
placementOrder.pop_back();
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto & si : storedInfo) {
|
|
for (auto &res : si) {
|
|
storedInfoLookup[res.r->getId()] = &res;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Retuns the final class and the order within that class. */
|
|
pair<int, int> QualificationFinal::getNextFinal(int runnerId) const {
|
|
auto res = storedInfoLookup.find(runnerId);
|
|
if (res == storedInfoLookup.end()) {
|
|
return make_pair(0, -1);
|
|
}
|
|
|
|
return make_pair(max(res->second->instance, 0), res->second->order);
|
|
}
|
|
|
|
void QualificationFinal::printScheme(const oClass& cls, gdioutput &gdi) const {
|
|
vector<wstring> cname;
|
|
|
|
for (size_t i = 0; i < classDefinition.size(); i++) {
|
|
pClass inst = cls.getVirtualClass(i + 1);
|
|
cname.push_back(inst ? inst->getName() : lang.tl("Saknad klass"));
|
|
}
|
|
|
|
int ylimit = gdi.getHeight();
|
|
gdi.pushY();
|
|
for (size_t i = 0; i < classDefinition.size(); i++) {
|
|
gdi.dropLine();
|
|
gdi.addStringUT(1, itow(i+1) + L" " + cname[i]);
|
|
|
|
if (getLevel(i + 1) == 0) {
|
|
//gdi.addStringUT(italicText, "Kval").setColor(GDICOLOR::colorDarkGrey);
|
|
}
|
|
else {
|
|
gdi.addString("", italicText, classDefinition[i].getQualInfo()).setColor(GDICOLOR::colorDarkGreen);
|
|
}
|
|
gdi.dropLine(0.2);
|
|
int rankingBased = 0;
|
|
vector<wstring> dst;
|
|
|
|
for (int place = 1; place < 100; place++) {
|
|
auto res = sourcePlaceToFinalOrder.find(make_pair(i + 1, place));
|
|
|
|
if (res != sourcePlaceToFinalOrder.end()) {
|
|
int level = getLevel(res->second.first);
|
|
if (levels[level] == LevelInfo::RankSort)
|
|
++rankingBased;
|
|
else
|
|
dst.push_back(itow(place) + L". ➞ " + cname[res->second.first - 1]);
|
|
}
|
|
else break;
|
|
}
|
|
|
|
if (rankingBased) {
|
|
gdi.addString("", 0, L" X går vidare, klass enligt ranking#" + itow(rankingBased));
|
|
}
|
|
for (auto &c : dst) {
|
|
gdi.addStringUT(0, L" " + c);
|
|
}
|
|
|
|
if (gdi.getCY() > ylimit) {
|
|
gdi.newColumn();
|
|
gdi.popY();
|
|
gdi.pushX();
|
|
}
|
|
}
|
|
|
|
gdi.refresh();
|
|
}
|
|
|
|
wstring QFClass::getQualInfo() const {
|
|
if (extraQualification == ExtraQualType::All)
|
|
return L"Alla övriga";
|
|
else if (extraQualification == ExtraQualType::NBest)
|
|
return L"X bästa#" + itow(extraQualData);
|
|
else if (extraQualification == ExtraQualType::TimeLimit)
|
|
return L"Tidsgräns X#" + formatTimeMS(extraQualData, false);
|
|
|
|
int nq = qualificationMap.size() + numTimeQualifications;
|
|
if (nq > 0 && numTimeQualifications == 0)
|
|
return L"X kvalificerade#" + itow(nq);
|
|
else if (nq > 0)
|
|
return L"X kvalificerade#" + itow(qualificationMap.size()) + L" + " + itow(numTimeQualifications);
|
|
|
|
return L"Ingen";
|
|
}
|
|
|
|
|
|
bool QualificationFinal::hasRemainingClass() const {
|
|
for (auto& c : classDefinition) {
|
|
if (c.extraQualification != QFClass::ExtraQualType::None)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|