/************************************************************************ 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 . Melin Software HB - software@melin.nu - www.melin.nu Eksoppsvägen 16, SE-75646 UPPSALA, Sweden ************************************************************************/ // oCourse.cpp: implementation of the oCourse class. // ////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "oCourse.h" #include "oEvent.h" #include "SportIdent.h" #include #include "Localizer.h" #include "meos_util.h" #include "meosexception.h" #include #include "gdioutput.h" #include "Table.h" #include ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// #include "intkeymapimpl.hpp" oCourse::oCourse(oEvent *poe) : oBase(poe) { getDI().initData(); nControls=0; Length=0; clearCache(); tMapsUsed = -1; tMapsUsedNoVacant = -1; Id=oe->getFreeCourseId(); } oCourse::oCourse(oEvent *poe, int id) : oBase(poe) { getDI().initData(); nControls=0; Length=0; clearCache(); if (id == 0) id = oe->getFreeCourseId(); Id=id; tMapsUsed = -1; tMapsUsedNoVacant = -1; oe->qFreeCourseId = max(id, oe->qFreeCourseId); } oCourse::~oCourse() { } wstring oCourse::getInfo() const { return L"Bana " + Name; } bool oCourse::Write(xmlparser &xml) { if (Removed) return true; xml.startTag("Course"); xml.write("Id", Id); xml.write("Updated", getStamp()); xml.write("Name", Name); xml.write("Length", Length); xml.write("Controls", getControls()); xml.write("Legs", getLegLengths()); getDI().write(xml); xml.endTag(); return true; } void oCourse::Set(const xmlobject *xo) { xmlList xl; xo->getObjects(xl); xmlList::const_iterator it; for(it=xl.begin(); it != xl.end(); ++it){ if (it->is("Id")){ Id=it->getInt(); } else if (it->is("Length")){ Length=it->getInt(); } else if (it->is("Name")){ Name=it->getWStr(); } else if (it->is("Controls")){ importControls(it->getRawStr(), false, false); } else if (it->is("Legs")) { importLegLengths(it->getRawStr(), false); } else if (it->is("oData")){ getDI().set(*it); } else if (it->is("Updated")){ Modified.setStamp(it->getRawStr()); } } } string oCourse::getLegLengths() const { string str; for(size_t m=0; m0) str += ";"; str+=itos(legLengths[m]); } return str; } string oCourse::getControls() const { string str=""; char bf[16]; for(int m=0;mId); str+=bf; } return str; } wstring oCourse::getControlsUI() const { wstring str; wchar_t bf[16]; int m; for(m=0;mId); str += bf; } if (mId); str += bf; } return str; } vector oCourse::getCourseReadable(int limit) const { vector res; wstring str; if (!useFirstAsStart()) str = lang.tl("Start").substr(0, 1); int m; vector rg; bool needFinish = false; bool rogaining = hasRogaining(); for (m=0; misRogaining(rogaining)) rg.push_back(Controls[m]); else { if (!str.empty()) str += L"-"; str += Controls[m]->getLongString(); needFinish = true; } if (str.length() >= size_t(limit)) { res.push_back(str); str.clear(); } } if (needFinish && !useLastAsFinish()) { if (!str.empty()) str += L"-"; str += lang.tl("Mål").substr(0,1); } if (!str.empty()) { if (str.length()<5 && !res.empty()) res.back().append(str); else res.push_back(str); str.clear(); } if (!rg.empty()) { str = lang.tl("Rogaining: "); for (size_t k = 0; k0) str += L", "; if (str.length() >= size_t(limit)) { res.push_back(str); str.clear(); } str += rg[k]->getLongString(); } if (!str.empty()) { res.push_back(str); } } return res; } pControl oCourse::addControl(int Id) { pControl pc = doAddControl(Id); updateChanged(); return pc; } pControl oCourse::doAddControl(int Id) { if (nControlsgetControl(Id, true, false); if (c==0) throw meosException("Felaktig kontroll"); Controls[nControls++]=c; return c; } else throw meosException("För många kontroller."); } void oCourse::splitControls(const string &ctrls, vector &nr) { const char *str=ctrls.c_str(); nr.clear(); while (*str) { int cid=atoi(str); while(*str && (*str!=';' && *str!=',' && *str!=' ')) str++; while(*str && (*str==';' || *str==',' || *str==' ')) str++; if (cid>0) nr.push_back(cid); } } bool oCourse::importControls(const string &ctrls, bool setChanged, bool updateLegLengths) { int oldNC = nControls; vector oldC; for (int k = 0; kgetId() : 0); nControls = 0; vector newC; splitControls(ctrls, newC); for (size_t k = 0; k< newC.size(); k++) doAddControl(newC[k]); bool changed = nControls != oldNC; if (changed && updateLegLengths && legLengths.size() > 0) { int oldIndex = 0; int newIndex = 0; vector newLen(nControls + 1); bool lastOK = true; while (newIndex < nControls) { if (oldIndex < int(oldC.size())) { if (oldC[oldIndex] == newC[newIndex]) { if (lastOK && oldIndex < int(legLengths.size())) { newLen[newIndex] = legLengths[oldIndex]; } lastOK = true; oldIndex++; } else { lastOK = false; int forward = oldIndex + 1; while(forward < int(oldC.size())) { if (oldC[forward] == newC[newIndex]) { oldIndex = forward + 1; lastOK = true; break; } forward++; } } } else { lastOK = false; } newIndex++; } if (lastOK) { newLen.back() = legLengths.back(); } swap(newLen, legLengths); } for (int k = 0; !changed && kgetId(); if (changed) { if (setChanged) updateChanged(); oe->punchIndex.clear(); } return changed; } void oCourse::importLegLengths(const string &legs, bool setChanged) { vector splits; split(legs, ";", splits); bool changed = false; if (legLengths.size() != splits.size()) { legLengths.resize(splits.size()); changed = true; } for (size_t k = 0; k=0 && indexgetString(); if (c.length() > 32) c= c.substr(0, 32) + L"..."; if (k == startIx) c += L" (" + lang.tl("Start") + L")"; else if (k == finishIx) c += L" (" + lang.tl("Mål") + L")"; int multi = Controls[k]->getNumMulti(); int submulti = 0; wchar_t bf[256]; if (Controls[k]->isRogaining(rogaining)) { if (Controls[k]->getStatus() == oControl::ControlStatus::StatusRogainingRequired) swprintf_s(bf, 64, L"R!\t%s", c.c_str()); else swprintf_s(bf, 64, L"R\t%s", c.c_str()); offset--; } else if (multi == 1) { swprintf_s(bf, 64, L"%d\t%s", k+offset, c.c_str()); } else swprintf_s(bf, 64, L"%d%c\t%s", k+offset, 'A', c.c_str()); gdi.addItem(name, bf, k); while (multi>1) { submulti++; swprintf_s(bf, 64, L"%d%c\t-:-", k+offset, 'A'+submulti); gdi.addItem(name, bf, -1); multi--; } offset += submulti; } if (finishIx == -1) gdi.addItem(name, lang.tl("Mål"), -1); return true; } void oCourse::getControls(vector &pc) const { pc.clear(); pc.reserve(nControls); for(int k=0;k oCourse::getControlNumbers() const { vector ret; for (int k = 0; kgetFirstNumber()); } return ret; } int oCourse::distance(const SICard& card) const { if (card.nPunch >= 192) return -100; int punches[192]; for (int i = 0; i < card.nPunch; i++) { punches[i] = card.Punch[i].Code; } return distance(punches, card.nPunch); } int oCourse::distance(const oCard& card) const { int np = card.getNumPunches(); if (np >= 192) return -100; vector p; card.getPunches(p); int punches[192]; int numRealPunch = 0; for (int i = 0; i < np; i++) { int c = p[i]->getControlNumber(); if (c > 0) punches[numRealPunch++] = c; } return distance(punches, numRealPunch); } int oCourse::distance(int *punches, int numPunches) const { int matches=0; set rogaining; vector< map > allowedControls; allowedControls.reserve(nControls); set commonCode; if (hasRogaining()) { for (int k=0;kisRogaining(true)) { for (int j = 0; j < Controls[k]->nNumbers; j++) rogaining.insert(Controls[k]->Numbers[j]); } } } int toMatch = 0; size_t orderIndex = 0; for (int k=0;kisRogaining(hasRogaining()) || Controls[k]->getStatus() == oControl::ControlStatus::StatusBad || Controls[k]->getStatus() == oControl::ControlStatus::StatusOptional || Controls[k]->getStatus() == oControl::ControlStatus::StatusBadNoTiming) continue; if (Controls[k]->getStatus() == oControl::ControlStatus::StatusMultiple) { for (int j = 0; j < Controls[k]->nNumbers; j++) { if (allowedControls.size() <= orderIndex) allowedControls.resize(orderIndex+1); for (int i = 0; i < Controls[k]->nNumbers; i++) { ++allowedControls[orderIndex][Controls[k]->Numbers[i]]; } orderIndex++; toMatch++; } } else { if (allowedControls.size() <= orderIndex) allowedControls.resize(orderIndex+1); for (int j = 0; j < Controls[k]->nNumbers; j++) { ++allowedControls[orderIndex][Controls[k]->Numbers[j]]; } orderIndex++; toMatch++; } if (getCommonControl() == Controls[k]->getId()) { orderIndex = 0; commonCode.insert(Controls[k]->Numbers, Controls[k]->Numbers+Controls[k]->nNumbers); } } size_t matchIndex = 0; for (unsigned k = 0; k < numPunches && matches < toMatch; k++) { for (unsigned j = k; j < numPunches; j++) { if (matchIndex < allowedControls.size() && allowedControls[matchIndex].count(punches[j]) && allowedControls[matchIndex][punches[j]] > 0) { --allowedControls[matchIndex][punches[j]]; k = j; matches++; break; } } matchIndex++; if (commonCode.count(punches[k])) matchIndex = 0; } if (matches==toMatch) { //This course is OK. Extra controls? return numPunches-toMatch; //Positive return } else { return matches-toMatch; //Negative return; } return 0; } wstring oCourse::getLengthS() const { return itow(getLength()); } void oCourse::setName(const wstring &n) { if (Name!=n){ Name=n; updateChanged(); } } void oCourse::setLength(int le) { if (le<0 || le > 1000000) le = 0; if (Length!=le){ Length=le; updateChanged(); } } oDataContainer &oCourse::getDataBuffers(pvoid &data, pvoid &olddata, pvectorstr &strData) const { data = (pvoid)oData; olddata = (pvoid)oDataOld; strData = 0; return *oe->oCourseData; } pCourse oEvent::getCourseCreate(int Id) { oCourseList::iterator it; for (it=Courses.begin(); it != Courses.end(); ++it) { if (it->Id==Id) return &*it; } if (Id>0) { oCourse c(this, Id); c.setName(getAutoCourseName()); return addCourse(c); } else { return addCourse(getAutoCourseName()); } } pCourse oEvent::getCourse(int Id) const { if (Id==0) return 0; pCourse value; if (courseIdIndex.lookup(Id,value)) return value; return 0; } pCourse oEvent::getCourse(const wstring &n) const { oCourseList::const_iterator it; for (it=Courses.begin(); it != Courses.end(); ++it) { if (it->Name==n) return pCourse(&*it); } return 0; } void oEvent::fillCourses(gdioutput &gdi, const string &id, bool simple) { vector< pair > d; oe->getCourses(d, L"", simple, true); gdi.addItem(id, d); } const vector< pair > &oEvent::getCourses(vector>& out, const wstring& filter, bool simple, bool synchronize) { out.clear(); oCourseList::iterator it; if (synchronize) { synchronizeList(oListId::oLCourseId); Courses.sort(); } vector> > ac; ac.reserve(Courses.size()); map id2ix; for (it=Courses.begin(); it != Courses.end(); ++it) { if (!it->Removed) { if (!simple) id2ix[it->getId()] = ac.size(); ac.emplace_back(pCourse(&*it), make_pair(pCourse(0), false)); } } if (!simple) { for (size_t k = 0; k < ac.size(); k++) { pCourse sh = ac[k].first->getShorterVersion().second; if (sh != 0) { int ix = id2ix[sh->getId()]; if (!ac[ix].second.first) ac[ix].second.first = ac[k].first; else ac[ix].second.second = true; } } } vector filt_lc(filter.length() + 1); wcscpy_s(filt_lc.data(), filt_lc.size(), filter.c_str()); CharLowerBuff(filt_lc.data(), filter.length()); int score; wstring b; for (size_t k = 0; k < ac.size(); k++) { pCourse it = ac[k].first; if (!filter.empty()) { if (!filterMatchString(it->Name, filt_lc.data(), score)) continue; } if (simple) out.emplace_back(it->Name, it->Id); else { b = it->Name; if (ac[k].second.first) { b += L" < " + ac[k].second.first->Name; if (ac[k].second.second) b += L", ..."; } b += L"\t(" + itow(it->nControls) + L")"; if (!it->getCourseProblems().empty()) b = L"[!] " + b; out.emplace_back(b, it->Id); } } return out; } void oCourse::setNumberMaps(int block) { getDI().setInt("NumberMaps", block); } int oCourse::getNumberMaps() const { return getDCI().getInt("NumberMaps"); } int oCourse::getNumUsedMaps(bool noVacant) const { if (tMapsUsed == -1) oe->calculateNumRemainingMaps(false); if (noVacant) return tMapsUsedNoVacant; else return tMapsUsed; } void oCourse::setStart(const wstring &start, bool sync) { if (getDI().setString("StartName", start)) { if (sync) synchronize(); oClassList::iterator it; for (it=oe->Classes.begin();it!=oe->Classes.end();++it) { if (it->getCourse()==this) { it->setStart(start); if (sync) it->synchronize(); } } } } wstring oCourse::getStart() const { return getDCI().getString("StartName"); } void oEvent::calculateNumRemainingMaps(bool forceRecalculate) { if (!forceRecalculate) { if (dataRevision == tCalcNumMapsDataRevision) return; } synchronizeList({ oListId::oLCourseId, oListId::oLClassId, oListId::oLTeamId, oListId::oLRunnerId }); for (auto &cit : Courses) { if (cit.isRemoved()) continue; int numMaps = cit.getNumberMaps(); if (numMaps == 0) cit.tMapsRemaining = numeric_limits::min(); else cit.tMapsRemaining = numMaps; cit.tMapsUsed = 0; cit.tMapsUsedNoVacant = 0; } for (auto &cit : Classes) { if (cit.isRemoved()) continue; int numMaps = cit.getNumberMaps(true); if (numMaps == 0) cit.tMapsRemaining = numeric_limits::min(); else cit.tMapsRemaining = numMaps; cit.tMapsUsed = 0; cit.tMapsUsedNoVacant = 0; } for (oRunnerList::const_iterator it=Runners.begin(); it != Runners.end(); ++it) { if (!it->isRemoved() && it->getStatus() != StatusDNS && it->getStatus() != StatusCANCEL) { pCourse pc = it->getCourse(false); if (pc) { if (pc->tMapsRemaining != numeric_limits::min()) pc->tMapsRemaining--; pc->tMapsUsed++; if (!it->isVacant()) pc->tMapsUsedNoVacant++; } pClass cls = it->getClassRef(true); if (cls) { if (cls->tMapsRemaining != numeric_limits::min()) cls->tMapsRemaining--; cls->tMapsUsed++; if (!it->isVacant()) cls->tMapsUsedNoVacant++; } } } // Count maps used for vacant team positions for (oTeamList::const_iterator it=Teams.begin(); it != Teams.end(); ++it) { if (!it->isRemoved()) { for (size_t j = 0; j < it->Runners.size(); j++) { pRunner r = it->Runners[j]; if (r) continue; // Already included if (it->Class) { it->Class->tMapsUsed++; if (it->Class->tMapsRemaining != numeric_limits::min()) it->Class->tMapsRemaining--; const vector &courses = it->Class->MultiCourse[j]; if (courses.size()>0) { int index = it->StartNo; if (index > 0) index = (index-1) % courses.size(); pCourse tCrs = courses[index]; if (tCrs) { tCrs->tMapsUsed++; if (tCrs->tMapsRemaining != numeric_limits::min()) tCrs->tMapsRemaining--; } } } } } } tCalcNumMapsDataRevision = dataRevision; } int oCourse::getIdSum(int nC) { int id = 0; for (int k = 0; kgetId() : 0); if (id == 0) return getId(); return id; } void oCourse::setLegLengths(const vector &legs) { if (legs.size() == nControls +1 || legs.empty()) { bool diff = legs.size() != legLengths.size(); if (!diff) { for (size_t k = 0; k= legLengths.size() || unsigned(end) >= legLengths.size() || Length==0) return 0.0; int dist = 0; for (int k = start; kdataRevision != cacheDataRevision) clearCache(); if (size_t(controlIndex) < cachedControlOrdinal.size() && !cachedControlOrdinal[controlIndex].empty()) return cachedControlOrdinal[controlIndex]; if (controlIndex > nControls) throw meosException("Invalid index"); cachedControlOrdinal.resize(nControls); int o = useFirstAsStart() ? 0 : 1; bool rogaining = hasRogaining(); for (int k = 0; kisRogaining(rogaining)) o++; } cachedControlOrdinal[controlIndex] = itow(o); return cachedControlOrdinal[controlIndex]; } void oCourse::setRogainingPointsPerMinute(int p) { getDI().setInt("RReduction", p); } int oCourse::getRogainingPointsPerMinute() const { return getDCI().getInt("RReduction"); } int oCourse::calculateReduction(int overTime) const { int reduction = 0; if (overTime > 0) { int method = getDCI().getInt("RReductionMethod"); if (method == 0) // Linear model reduction = (59 * timeConstSecond + overTime * getRogainingPointsPerMinute()) / timeConstMinute; else // Time (minute) discrete model reduction = ((59 * timeConstSecond + overTime) / timeConstMinute) * getRogainingPointsPerMinute(); } return reduction; } void oCourse::setMinimumRogainingPoints(int p) { cachedHasRogaining = 0; getDI().setInt("RPointLimit", p); } int oCourse::getMinimumRogainingPoints() const { return getDCI().getInt("RPointLimit"); } void oCourse::setMaximumRogainingTime(int p) { cachedHasRogaining = 0; if (p == NOTIME) p = 0; getDI().setInt("RTimeLimit", p); } int oCourse::getMaximumRogainingTime() const { return getDCI().getInt("RTimeLimit"); } bool oCourse::hasRogaining() const { if (oe->dataRevision != cacheDataRevision) clearCache(); if (cachedHasRogaining>0) return cachedHasRogaining == 2; bool r = getMaximumRogainingTime() > 0 || getMinimumRogainingPoints() > 0; cachedHasRogaining = r ? 2 : 1; return r; } void oCourse::clearCache() const { cachedHasRogaining = 0; cachedControlOrdinal.clear(); cacheDataRevision = oe->dataRevision; oe->tCalcNumMapsDataRevision = -1; tMapsUsed = -1; tMapsUsedNoVacant = -1; } wstring oCourse::getCourseProblems() const { int max_time = getMaximumRogainingTime(); int min_point = getMinimumRogainingPoints(); if (max_time > 0) { for (int k = 0; kisRogaining(true)) return L""; } return L"Banan saknar rogainingkontroller."; } else if (min_point > 0) { int max_p = 0; for (int k = 0; kisRogaining(true)) max_p += Controls[k]->getRogainingPoints(); } if (max_p < min_point) { return L"Banans kontroller ger för få poäng för att täcka poängkravet."; } } return L""; } void oCourse::remove() { if (oe) oe->removeCourse(Id); } bool oCourse::canRemove() const { return !oe->isCourseUsed(Id); } void oCourse::changeId(int newId) { pCourse old = oe->courseIdIndex[Id]; if (old == this) oe->courseIdIndex.remove(Id); oBase::changeId(newId); oe->courseIdIndex[newId] = this; } bool oCourse::useFirstAsStart() const { return getDCI().getInt("FirstAsStart") != 0; } bool oCourse::useLastAsFinish() const { return getDCI().getInt("LastAsFinish") != 0; } void oCourse::firstAsStart(bool f) { getDI().setInt("FirstAsStart", f ? 1:0); } void oCourse::lastAsFinish(bool f) { getDI().setInt("LastAsFinish", f ? 1:0); } int oCourse::getFinishPunchType() const { if (useLastAsFinish() && nControls > 0) return Controls[nControls - 1]->Numbers[0]; else return oPunch::PunchFinish; } int oCourse::getStartPunchType() const { if (useFirstAsStart() && nControls > 0) return Controls[0]->Numbers[0]; else return oPunch::PunchStart; } void oEvent::getCourses(vector &crs) const{ crs.clear(); for (oCourseList::const_iterator it = Courses.begin(); it != Courses.end(); ++it) { if (it->isRemoved()) continue; crs.push_back(pCourse(&*it)); } } int oCourse::getCommonControl() const { return getDCI().getInt("CControl"); } int oCourse::getNumLoops() const { int cc = getCommonControl(); if (cc == 0) return 0; bool wasCC = true; int loopCount = 0; for (int i = 0; i < nControls; i++) { if (Controls[i]->getId() == cc) wasCC = true; else if (wasCC) { loopCount++; wasCC = false; } } return loopCount; } void oCourse::setCommonControl(int ctrlId) { if (ctrlId != 0) { int found = 0; for (int k = 0; k < nControls; k++) { if (Controls[k]->getId() == ctrlId) found++; } if (found == 0) throw meosException("Kontroll X finns inte på banan#" + itos(ctrlId)); } getDI().setInt("CControl", ctrlId); } pCourse oCourse::getAdapetedCourse(const oCard &card, oCourse &tmpCourse, int &numShorten) const { /*adaptedToOriginalCardOrder.resize(nControls + 1); for (int k = 0; k < nControls + 1; k++) adaptedToOriginalCardOrder[k] = k;*/ int cc = getCommonControl(); if (cc == 0) return pCourse(this); vector ccIndex; vector> loopKeys; if (!constructLoopKeys(cc, loopKeys, ccIndex)) return pCourse(this); bool firstAsStart = ccIndex[0] == 0; vector> punchSequence; vector punches; card.getPunches(punches); punchSequence.push_back(vector()); for (size_t k = 0; k < punches.size(); k++) { int code = punches[k]->getTypeCode(); if (code < 10) continue; // Start, Finish etc. if (code == cc && !punchSequence.back().empty()) punchSequence.push_back(vector()); else { if (code != cc) punchSequence.back().push_back(code); } } map > > preferences; for (int k = 0; k < punchSequence.size(); k++) { for (int j = 0; j < loopKeys.size(); j++) { int v = matchLoopKey(punchSequence[k], loopKeys[j]); if (v < 1000) preferences[v].push_back(make_pair(k, j)); } } vector assignedKeys(loopKeys.size(), -1); vector usedPunches(punchSequence.size()); int assigned = 0; for (map > >::iterator it = preferences.begin(); it != preferences.end(); ++it) { vector< pair > &bestMatches = it->second; map > sortedBestMatches; vector< pair > sortKey(loopKeys.size()); for (size_t j = 0; j < bestMatches.size(); j++) { int loopIndex = bestMatches[j].second; sortKey[loopIndex].second = loopIndex; ++sortKey[loopIndex].first; sortedBestMatches[loopIndex].push_back(bestMatches[j].first); } sort(sortKey.begin(), sortKey.end()); for (size_t j = 0; j < sortKey.size(); j++) { if (sortKey[j].first == 0) continue; int loopIndex = sortKey[j].second; if (assignedKeys[loopIndex] != -1) continue; vector &bm = sortedBestMatches[loopIndex]; for (size_t k = 0; k < bm.size(); k++) { if (usedPunches[bm[k]] == 0) { usedPunches[bm[k]] = 1; assignedKeys[loopIndex] = bm[k]; assigned++; break; } } if (assigned == assignedKeys.size()) break; } if (assigned == assignedKeys.size()) break; } vector loopOrder; map keyToIndex; assert(ccIndex.size() == assignedKeys.size()); for (size_t k = 0; k < assignedKeys.size(); k++) { if (assignedKeys[k] != -1) { keyToIndex[assignedKeys[k]] = k; } } for (map::iterator it = keyToIndex.begin(); it != keyToIndex.end(); ++it) { loopOrder.push_back(it->second); } int checksum = (ccIndex.size() * (ccIndex.size()-1))/2; // Add remaining, unmatched, loops in defined order for (size_t k = 0; k < ccIndex.size(); k++) { if (assignedKeys[k] == -1) loopOrder.push_back(k); checksum-=loopOrder[k]; } assert(checksum == 0 && loopOrder.size() == ccIndex.size()); tmpCourse.setLocalObject(); tmpCourse.cacheDataRevision = cacheDataRevision; tmpCourse.cachedControlOrdinal.clear(); tmpCourse.cachedHasRogaining = cachedHasRogaining; memcpy(tmpCourse.oData, oData, sizeof(oData)); tmpCourse.nControls = 0; tmpCourse.Length = Length; tmpCourse.Name = Name; tmpCourse.sqlUpdated = "TMP"; // Mark as tmp to prevent accidental write to DB tmpCourse.tMapToOriginalOrder.clear(); tmpCourse.tMapToOriginalOrder.reserve(nControls+1); if (firstAsStart) { tmpCourse.tMapToOriginalOrder.push_back(0); tmpCourse.Controls[tmpCourse.nControls++] = Controls[0]; if (0 < legLengths.size()) tmpCourse.legLengths.push_back(legLengths[0]); } bool allowShorten = getShorterVersion().first; numShorten = 0; bool lastAsFinish = useLastAsFinish() || Controls[nControls-1]->getId() == cc; int endIx = lastAsFinish ? nControls - 1 : nControls; for (size_t k = 0; k< loopOrder.size(); k++) { if (allowShorten && assignedKeys[loopOrder[k]] == -1) { numShorten++; continue; } int start = ccIndex[loopOrder[k]]; int end = size_t(loopOrder[k] + 1) < ccIndex.size() ? ccIndex[loopOrder[k] + 1] : endIx; for (int i = start + 1; i < end; i++) { tmpCourse.tMapToOriginalOrder.push_back(i); tmpCourse.Controls[tmpCourse.nControls++] = Controls[i]; if (size_t(i) < legLengths.size()) tmpCourse.legLengths.push_back(legLengths[i]); } tmpCourse.tMapToOriginalOrder.push_back(end); if (k + 1 < loopOrder.size()) { int currentCC = ccIndex[k+1]; tmpCourse.Controls[tmpCourse.nControls++] = Controls[currentCC]; if (size_t(end) < legLengths.size()) tmpCourse.legLengths.push_back(legLengths[end]); } } if (numShorten > 0) { //Shortened course. Do not duplicate last. if (lastAsFinish && !useLastAsFinish()) { lastAsFinish = false; } else { if (tmpCourse.nControls > 0) { tmpCourse.tMapToOriginalOrder.pop_back(); tmpCourse.nControls--; if (!legLengths.empty()) tmpCourse.legLengths.pop_back(); } } } if (lastAsFinish) { tmpCourse.tMapToOriginalOrder.push_back(nControls); tmpCourse.Controls[tmpCourse.nControls++] = Controls[nControls - 1]; if (size_t(nControls-1) < legLengths.size()) tmpCourse.legLengths.push_back(legLengths[nControls-1]); } if (!allowShorten) { assert(tmpCourse.nControls == nControls); assert(tmpCourse.tMapToOriginalOrder.size() == nControls + 1); } if (!legLengths.empty()) { tmpCourse.legLengths.push_back(legLengths.back()); if (!allowShorten) { assert(tmpCourse.legLengths.size() == legLengths.size()); } } tmpCourse.Id = Id; return &tmpCourse; } bool oCourse::isAdapted() const { return tMapToOriginalOrder.size() > 0; } int oCourse::getAdaptionId() const { int key = 0; for (size_t j = 0; j < tMapToOriginalOrder.size(); j++) key = key * 97 + tMapToOriginalOrder[j]; return key; } int oCourse::matchLoopKey(const vector &punches, const vector &key) { if (key.empty()) return 999; size_t ix = -1; for (size_t k = 0; k < key.size(); k++) { int code = key[k]->getFirstNumber(); while (++ix < punches.size()) { if (punches[ix] == code) { code = -1; break; } } if (code != -1) return 1000; } return ix; } bool oCourse::constructLoopKeys(int cc, vector< vector > &loopKeys, vector &ccIndex) const { bool firstAsStart = useFirstAsStart(); if (firstAsStart) { // Only if it is a unique control for (int k = 1; k < nControls; k++) { if (Controls[k] == Controls[0]) { firstAsStart = false; break; } } } if (Controls[0]->getId() == cc) firstAsStart = true; // Handle a course that starts with the loop control bool lastAsFinish = useLastAsFinish(); if (lastAsFinish) { // Only if it is a unique control for (int k = 0; k < nControls - 1; k++) { if (Controls[k] == Controls[nControls-1]) { lastAsFinish = false; break; } } } int startIx = firstAsStart ? 1 : 0; int endIx = lastAsFinish ? nControls : nControls-1; ccIndex.push_back(startIx-1); for (int k = startIx; k < endIx; k++) { if (Controls[k]->getId() == cc) ccIndex.push_back(k); } if (ccIndex.size() <= 1) return false; loopKeys.clear(); loopKeys.resize(ccIndex.size()); int keyIndex = 1; bool changed = true; bool enough = false; while(changed && !enough) { changed = false; for (size_t k = 0; k < ccIndex.size(); k++) { int keyIx = ccIndex[k] + keyIndex; int nextIx = (k + 1) < ccIndex.size() ? ccIndex[k+1] : nControls; if (keyIx < nextIx && Controls[keyIx]->isSingleStatusOK() && Controls[keyIx]->nNumbers == 1) { loopKeys[k].push_back(Controls[keyIx]); changed = true; } } keyIndex++; if (changed) { enough = false; set<__int64> hashes; for (size_t k = 0; k < loopKeys.size(); k++) { __int64 h = loopKeys[k].size(); for (size_t j = 0; j < loopKeys[k].size(); j++) { h = h * 997 + loopKeys[k][j]->Numbers[0]; } hashes.insert(h); } enough = hashes.size() == loopKeys.size(); } } return enough; } void oCourse::changedObject() { if (oe) oe->globalModification = true; oe->sqlCourses.changed = true; } int oCourse::getCourseControlId(int controlIx) const { if (controlIx >= nControls) { assert(false); return -1; } int id = Controls[controlIx] ? Controls[controlIx]->getId() : 0; if (id == 0) return 0; int count = 0; for (int j = 0; j < controlIx; j++) { if (Controls[j] && Controls[j]->Id == id) count++; } return oControl::getCourseControlIdFromIdIndex(id, count); } wstring oCourse::getRadioName(int courseControlId) const { pair idix = oControl::getIdIndexFromCourseControlId(courseControlId); pControl pc = 0; int numRadio = 0; int clsix = 1; for (int k = 0; k < nControls; k++) { if (Controls[k]) { if (Controls[k]->isValidRadio()) numRadio++; if (Controls[k]->Id == idix.first) { if (idix.second == 0) { pc = Controls[k]; break; } else { clsix++; idix.second--; } } } } if (pc == 0) return L"?"; wstring name; if (pc->hasName()) { name = pc->getName(); if (pc->getNumberDuplicates() > 1) name += makeDash(L"-" + itow(clsix)); } else { name = lang.tl("radio X#" + itos(numRadio)); capitalize(name); } return name; } // Returns the next shorter course, if any, null otherwise pair oCourse::getShorterVersion() const { int ix = getDCI().getInt("Shorten"); if (ix == -1) return make_pair(true, nullptr); auto c = oe->getCourse(ix); return make_pair(c != 0, c); } // Returns the next longer course, if any, null otherwise. Note that this method is slow. pCourse oCourse::getLongerVersion() const { oCourseList::const_iterator it; for (it = oe->Courses.begin(); it != oe->Courses.end(); ++it) { int ix = it->getDCI().getInt("Shorten"); if (ix == Id) return pCourse(&*it); } return 0; } void oCourse::setShorterVersion(bool activeShortening, pCourse shorten) { if (activeShortening) getDI().setInt("Shorten", shorten != 0 ? shorten->getId() : -1); else getDI().setInt("Shorten", 0); } bool oCourse::hasControl(const oControl *ctrl) const { for (int i = 0; i < nControls; i++) { if (Controls[i] == ctrl) return true; } return false; } bool oCourse::hasControlCode(int code) const { for (int i = 0; i < nControls; i++) { if (Controls[i]->hasNumber(code)) return true; } return false; } void oCourse::getClasses(vector &usageClass) const { vector cls; oe->getClasses(cls, false); for (size_t k = 0; k < cls.size(); k++) { if (cls[k]->usesCourse(*this)) usageClass.push_back(cls[k]); } } const shared_ptr &oCourse::getTable(oEvent *oe) { oe->synchronizeList(oListId::oLCourseId); oClassList::iterator it; int numCtrl = 20; vector crs; oe->getCourses(crs); for (pCourse pc : crs) numCtrl = max(numCtrl, pc->getNumControls() + 5); static int generatedNumCtrl = 0; if (generatedNumCtrl != numCtrl || !oe->hasTable("course")) { auto table = make_shared
(oe, 20, L"Banor", "courses"); table->addColumn("Id", 70, true, true); table->addColumn("Ändrad", 70, false); table->addColumn("Namn", 200, false); table->addColumn("Startande", 70, true); table->addColumn("Klasser", 200, false); for (int i = 0; i < numCtrl; i++) table->addColumn("#C" + itos(i + 1), 40, true, true); generatedNumCtrl = numCtrl; oe->oCourseData->buildTableCol(table.get()); oe->setTable("course", table); } return oe->getTable("course"); } void oCourse::generateTableData(oEvent *oe, Table &table, oCourse *add) { if (add) { add->addTableRow(table); return; } oe->synchronizeList(oListId::oLCourseId); oClassList::iterator it; vector crs; oe->getCourses(crs); for (pCourse pc : crs) pc->addTableRow(table); } void oCourse::addTableRow(Table &table) const { pCourse it = pCourse(this); table.addRow(getId(), it); int row = 0; table.set(row++, *it, TID_ID, itow(getId()), false); table.set(row++, *it, TID_MODIFIED, getTimeStamp(), false); table.set(row++, *it, TID_NAME, getName(), true); table.set(row++, *it, TID_NUM, itow(getNumUsedMaps(true)), false); vector cls; getClasses(cls); sort(cls.begin(), cls.end(), [](const pClass &a, const pClass &b) {return *a < *b; }); wstring clsStr; for (pClass c : cls) { if (!clsStr.empty()) clsStr += L", "; clsStr += c->getName(); } table.set(row++, *it, TID_CLASSNAME, clsStr, false); for (int i = 0; i < getNumControls(); i++) { table.set(row++, *it, 100+i, Controls[i] ? itow(Controls[i]->getId()) : L"", true); } oe->oCourseData->fillTableCol(*this, table, true); } pair oCourse::inputData(int id, const wstring &input, int inputId, wstring &output, bool noUpdate) { synchronize(false); if (id>1000) { return oe->oCourseData->inputData(this, id, input, inputId, output, noUpdate); } switch (id) { case TID_NAME: setName(input); synchronize(); output = getName(); break; } if (id >= 100) { int cix = id - 100; int v = _wtoi(input.c_str()); pControl ctrl = oe->getControl(v); if (!ctrl && v > 32 && v < 300) ctrl = oe->addControl(v, v, L""); if (ctrl && (cix == 0 || cix <= getNumControls())) { if (cix == getNumControls()) addControl(ctrl->getId()); else { Controls[cix] = ctrl; updateChanged(); } oe->reEvaluateCourse(getId(), false); synchronize(true); } } return make_pair(0, false); } void oCourse::fillInput(int id, vector< pair > &out, size_t &selected) { if (id>1000) { oe->oCourseData->fillInput(this, id, 0, out, selected); return; } }