566 lines
17 KiB
C++
566 lines
17 KiB
C++
/************************************************************************
|
|
MeOS - Orienteering Software
|
|
Copyright (C) 2009-2020 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::getNextFinal(int instance, int orderPlace, int numSharedPlaceNext) const {
|
|
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;
|
|
return ans;
|
|
}
|
|
}
|
|
|
|
|
|
auto ans = res->second;
|
|
ans.second += iter;
|
|
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].timeQualifications.empty();
|
|
}
|
|
|
|
|
|
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::import(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;
|
|
int iLevel = 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())
|
|
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);
|
|
if (rules.empty())
|
|
numBaseLevels = 1; // Instance zero is not used as qualification,
|
|
|
|
idToIndex[classId] = classDefinition.size() + numBaseLevels;
|
|
classDefinition.push_back(Class());
|
|
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 {
|
|
string time;
|
|
qf.getObjectString("place", time);
|
|
if (time == "time") {
|
|
string ids;
|
|
qf.getObjectString("id", ids);
|
|
vector<string> vid;
|
|
split(ids, ",;", vid);
|
|
vector<int> ivid;
|
|
for (auto &s : vid) {
|
|
int i = atoi(s.c_str());
|
|
if (!idToIndex.count(i))
|
|
throw meosException("Unknown class with id " + itos(i));
|
|
ivid.push_back(idToIndex[i]);
|
|
}
|
|
|
|
if (ivid.empty())
|
|
throw meosException(L"Empty time qualification for " + name);
|
|
classDefinition.back().timeQualifications.push_back(ivid);
|
|
}
|
|
else throw meosException("Unknown classification rule " + time);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/*classDefinition.resize(3);
|
|
classDefinition[0].name = L"Semi A";
|
|
classDefinition[1].name = L"Semi B";
|
|
classDefinition[2].name = L"Final";
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
classDefinition[0].qualificationMap.push_back(make_pair(0, i * 2 + 1));
|
|
classDefinition[1].qualificationMap.push_back(make_pair(0, i * 2 + 2));
|
|
classDefinition[2].qualificationMap.push_back(make_pair(i%2+1, i/2 + 1));
|
|
}
|
|
*/
|
|
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++) {
|
|
bool rankLevel = false;
|
|
if (races[k].size() > 0 && races[k][0] == 'R') {
|
|
rankLevel = true;
|
|
races[k] = races[k].substr(1);
|
|
}
|
|
split(races[k], L"T", rtdef);
|
|
classDefinition[k].qualificationMap.clear();
|
|
classDefinition[k].timeQualifications.clear();
|
|
classDefinition[k].rankLevel = rankLevel;
|
|
|
|
if (first && rtdef.empty())
|
|
continue;
|
|
|
|
valid = rtdef.size() > 0;
|
|
if (!valid)
|
|
break;
|
|
first = false;
|
|
|
|
split(rtdef[0], L";", rdef);
|
|
valid = rdef.size() > 0 && rdef.size()%2 == 0;
|
|
if (!valid)
|
|
break;
|
|
|
|
for (size_t j = 0; j < rdef.size(); j+=2) {
|
|
size_t src = _wtoi(rdef[j].c_str());
|
|
if (src > k) {
|
|
valid = false;
|
|
break;
|
|
}
|
|
const wstring &rd = rdef[j + 1];
|
|
size_t d1 = _wtoi(rd.c_str());
|
|
size_t d2 = d1;
|
|
size_t range = rd.find_first_of('-', 0);
|
|
if (range < rd.size())
|
|
d2 = _wtoi(rd.c_str()+range+1);
|
|
|
|
if (d1 > d2) {
|
|
valid = false;
|
|
break;
|
|
}
|
|
|
|
while (d1 <= d2) {
|
|
classDefinition[k].qualificationMap.push_back(make_pair(int(src), int(d1)));
|
|
d1++;
|
|
}
|
|
}
|
|
|
|
for (size_t i = 1; valid && i < rtdef.size(); i++) {
|
|
split(rtdef[i], L";", rdef);
|
|
classDefinition[k].timeQualifications.push_back(vector<int>());
|
|
for (size_t j = 0; valid && j < rdef.size(); j += 2) {
|
|
size_t src = _wtoi(rdef[j].c_str());
|
|
if (src > k) {
|
|
valid = false;
|
|
break;
|
|
}
|
|
classDefinition[k].timeQualifications.back().push_back(src);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!valid)
|
|
classDefinition.clear();
|
|
|
|
initgmap(false);
|
|
}
|
|
|
|
void QualificationFinal::encode(wstring &output) const {
|
|
output.clear();
|
|
|
|
for (size_t k = 0; k < classDefinition.size(); k++) {
|
|
if (k > 0)
|
|
output.append(L"|");
|
|
|
|
if (classDefinition[k].rankLevel)
|
|
output.append(L"R");
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
auto &tqm = classDefinition[k].timeQualifications;
|
|
for (auto &source : tqm) {
|
|
output.append(L"T");
|
|
for (size_t i = 0; i < source.size(); i++) {
|
|
if (i > 0)
|
|
output.append(L";");
|
|
output.append(itow(source[i]));
|
|
}
|
|
}
|
|
}
|
|
|
|
serializedFrom = output;
|
|
}
|
|
|
|
int QualificationFinal::getNumStages(int stage) const {
|
|
if (stage == 0 || classDefinition[stage-1].qualificationMap.empty())
|
|
return 1;
|
|
|
|
set<int> races;
|
|
for (auto &qm : classDefinition[stage - 1].qualificationMap) {
|
|
if (qm.first == stage)
|
|
throw meosException("Invalid qualification scheme");
|
|
races.insert(qm.first);
|
|
}
|
|
|
|
int def = 0;
|
|
for (int r : races)
|
|
def = max(def, 1 + getNumStages(r));
|
|
|
|
return def;
|
|
}
|
|
|
|
void QualificationFinal::initgmap(bool check) {
|
|
sourcePlaceToFinalOrder.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 && sourcePlaceToFinalOrder.count(make_pair(instance, 1)) == 0;
|
|
}
|
|
|
|
int QualificationFinal::getNumLevels() const {
|
|
return getLevel(classDefinition.size() - 1) + 1;
|
|
}
|
|
|
|
int QualificationFinal::getMinInstance(int level) const {
|
|
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.
|
|
if (getLevel(i) == level)
|
|
minInst = i;
|
|
}
|
|
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();
|
|
}
|
|
|
|
/** Retuns the final class and the order within that class. */
|
|
void QualificationFinal::setupNextFinal(pRunner r, int instance, int orderPlace, int numSharedPlaceNext) {
|
|
storedInfo.resize(getNumClasses());
|
|
|
|
pair<int, int> res = getNextFinal(instance, orderPlace, numSharedPlaceNext);
|
|
int level = getLevel(instance);
|
|
storedInfo[level].emplace_back(r, res.first, res.second);
|
|
}
|
|
|
|
/** Do internal calculations. */
|
|
void QualificationFinal::computeFinals() {
|
|
if (storedInfo.empty())
|
|
return;
|
|
int nl = getNumLevels();
|
|
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());
|
|
/*
|
|
vector<int> work;
|
|
|
|
int ix = placeQueue.size() - 1;
|
|
int iteration = 0;
|
|
bool versionA = false;
|
|
while (ix>=0) {
|
|
work.clear();
|
|
for (int i = 0; i < numClass && ix >= 0; i++) {
|
|
work.push_back(ix--);
|
|
}
|
|
if (versionA) {
|
|
if (work.size() == numClass)
|
|
rotate(work.begin(), work.begin() + iteration, work.end());
|
|
iteration = rotmap[iteration];
|
|
}
|
|
else {
|
|
if (iteration % 2 == 1)
|
|
reverse(work.begin(), work.end());
|
|
iteration++;
|
|
}
|
|
|
|
for (size_t i = 1; i <= work.size(); i++) {
|
|
placementOrder[ix + i] = work[work.size() - i];
|
|
}
|
|
}*/
|
|
|
|
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(res->second->instance, res->second->order);
|
|
}
|
|
|
|
void QualificationFinal::printScheme(oClass * cls, gdioutput &gdi) const {
|
|
vector<wstring> cname;
|
|
// vector<map<int, int> > targetCls(classDefinition.size());
|
|
|
|
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"));
|
|
// for (auto d : classDefinition[i].qualificationMap) {
|
|
// targetCls[i].push_back(d.first);
|
|
// }
|
|
}
|
|
|
|
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]);
|
|
bool rankingBased = false;
|
|
vector<wstring> dst;
|
|
|
|
for (int place = 1; place < 20; place++) {
|
|
auto res = sourcePlaceToFinalOrder.find(make_pair(i + 1, place));
|
|
|
|
if (res != sourcePlaceToFinalOrder.end()) {
|
|
if (classDefinition[res->second.first - 1].rankLevel)
|
|
rankingBased = true;
|
|
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(dst.size()));
|
|
}
|
|
else {
|
|
for (auto &c : dst) {
|
|
gdi.addStringUT(0, c);
|
|
}
|
|
}
|
|
|
|
if (gdi.getCY() > ylimit) {
|
|
gdi.newColumn();
|
|
gdi.popY();
|
|
gdi.pushX();
|
|
|
|
}
|
|
}
|
|
|
|
gdi.refresh();
|
|
}
|