1390 lines
35 KiB
C++
1390 lines
35 KiB
C++
/************************************************************************
|
|
MeOS - Orienteering Software
|
|
Copyright (C) 2009-2018 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
|
|
|
|
************************************************************************/
|
|
|
|
// oCourse.cpp: implementation of the oCourse class.
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
#include "stdafx.h"
|
|
#include "oCourse.h"
|
|
#include "oEvent.h"
|
|
#include "SportIdent.h"
|
|
#include <limits>
|
|
#include "Localizer.h"
|
|
#include "meos_util.h"
|
|
#include "meosexception.h"
|
|
#include <cassert>
|
|
#include "gdioutput.h"
|
|
#include <algorithm>
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// 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", Modified.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->getw();
|
|
}
|
|
else if (it->is("Controls")){
|
|
importControls(it->getRaw(), false);
|
|
}
|
|
else if (it->is("Legs")) {
|
|
importLegLengths(it->getRaw(), false);
|
|
}
|
|
else if (it->is("oData")){
|
|
getDI().set(*it);
|
|
}
|
|
else if (it->is("Updated")){
|
|
Modified.setStamp(it->getRaw());
|
|
}
|
|
}
|
|
}
|
|
|
|
string oCourse::getLegLengths() const
|
|
{
|
|
string str;
|
|
for(size_t m=0; m<legLengths.size(); m++){
|
|
if (m>0)
|
|
str += ";";
|
|
str+=itos(legLengths[m]);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
string oCourse::getControls() const
|
|
{
|
|
string str="";
|
|
char bf[16];
|
|
for(int m=0;m<nControls;m++){
|
|
sprintf_s(bf, 16, "%d;", Controls[m]->Id);
|
|
str+=bf;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
wstring oCourse::getControlsUI() const
|
|
{
|
|
wstring str;
|
|
wchar_t bf[16];
|
|
int m;
|
|
|
|
for(m=0;m<nControls-1;m++){
|
|
swprintf_s(bf, 16, L"%d, ", Controls[m]->Id);
|
|
str += bf;
|
|
}
|
|
|
|
if (m<nControls){
|
|
swprintf_s(bf, 16, L"%d", Controls[m]->Id);
|
|
str += bf;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
vector<wstring> oCourse::getCourseReadable(int limit) const
|
|
{
|
|
vector<wstring> res;
|
|
|
|
wstring str;
|
|
if (!useFirstAsStart())
|
|
str = lang.tl("Start").substr(0, 1);
|
|
int m;
|
|
|
|
vector<pControl> rg;
|
|
bool needFinish = false;
|
|
bool rogaining = hasRogaining();
|
|
for (m=0; m<nControls; m++) {
|
|
if (Controls[m]->isRogaining(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; k<rg.size(); k++) {
|
|
if (k>0)
|
|
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 (nControls<NControlsMax) {
|
|
pControl c=oe->getControl(Id, true);
|
|
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<int> &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 updateLegLengths) {
|
|
int oldNC = nControls;
|
|
vector<int> oldC;
|
|
for (int k = 0; k<nControls; k++)
|
|
oldC.push_back(Controls[k] ? Controls[k]->getId() : 0);
|
|
|
|
nControls = 0;
|
|
|
|
vector<int> 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<int> 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 && k<nControls; k++)
|
|
changed |= oldC[k] != Controls[k]->getId();
|
|
|
|
if (changed) {
|
|
updateChanged();
|
|
|
|
oe->punchIndex.clear();
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
void oCourse::importLegLengths(const string &legs, bool setChanged)
|
|
{
|
|
vector<string> splits;
|
|
split(legs, ";", splits);
|
|
|
|
bool changed = false;
|
|
|
|
if (legLengths.size() != splits.size()) {
|
|
legLengths.resize(splits.size());
|
|
changed = true;
|
|
}
|
|
for (size_t k = 0; k<legLengths.size(); k++) {
|
|
int val = atoi(splits[k].c_str());
|
|
if (legLengths[k] != val)
|
|
changed = true;
|
|
legLengths[k] = val;
|
|
}
|
|
|
|
if (changed && setChanged)
|
|
updateChanged();
|
|
}
|
|
|
|
oControl *oCourse::getControl(int index) const
|
|
{
|
|
if (index>=0 && index<nControls)
|
|
return Controls[index];
|
|
else return 0;
|
|
}
|
|
|
|
int oCourse::getLegLength(int index) const {
|
|
if (size_t(index) < legLengths.size()) {
|
|
return legLengths[index];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool oCourse::fillCourse(gdioutput &gdi, const string &name)
|
|
{
|
|
int finishIx = useLastAsFinish() ? nControls - 1 : -1;
|
|
int startIx = useFirstAsStart() ? 0 : -1;
|
|
|
|
oPunchList::iterator it;
|
|
bool rogaining = hasRogaining();
|
|
gdi.clearList(name);
|
|
int offset = 1;
|
|
if (startIx == -1)
|
|
gdi.addItem(name, lang.tl("Start"), -1);
|
|
for (int k=0;k<nControls;k++) {
|
|
wstring c = Controls[k]->getString();
|
|
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)) {
|
|
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<pControl> &pc)
|
|
{
|
|
pc.clear();
|
|
pc.reserve(nControls);
|
|
for(int k=0;k<nControls;k++){
|
|
pc.push_back(Controls[k]);
|
|
}
|
|
}
|
|
|
|
int oCourse::distance(const SICard &card)
|
|
{
|
|
int matches=0;
|
|
|
|
set<int> rogaining;
|
|
vector< map<int, int> > allowedControls;
|
|
allowedControls.reserve(nControls);
|
|
set<int> commonCode;
|
|
if (hasRogaining()) {
|
|
for (int k=0;k<nControls;k++) {
|
|
if (Controls[k]->isRogaining(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;k<nControls;k++) {
|
|
if (Controls[k]->isRogaining(hasRogaining()) ||
|
|
Controls[k]->getStatus() == oControl::StatusBad ||
|
|
Controls[k]->getStatus() == oControl::StatusOptional)
|
|
continue;
|
|
|
|
if (Controls[k]->getStatus() == oControl::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<card.nPunch && matches < toMatch; k++) {
|
|
for (unsigned j = k; j < card.nPunch; j++) {
|
|
if (matchIndex < allowedControls.size() &&
|
|
allowedControls[matchIndex].count(card.Punch[j].Code) &&
|
|
allowedControls[matchIndex][card.Punch[j].Code] > 0) {
|
|
--allowedControls[matchIndex][card.Punch[j].Code];
|
|
k = j;
|
|
matches++;
|
|
break;
|
|
}
|
|
}
|
|
matchIndex++;
|
|
if (commonCode.count(card.Punch[k].Code))
|
|
matchIndex = 0;
|
|
}
|
|
|
|
if (matches==toMatch) {
|
|
//This course is OK. Extra controls?
|
|
return card.nPunch-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<wstring, size_t> > d;
|
|
oe->fillCourses(d, simple);
|
|
gdi.addItem(id, d);
|
|
}
|
|
|
|
const vector< pair<wstring, size_t> > &oEvent::fillCourses(vector< pair<wstring, size_t> > &out, bool simple)
|
|
{
|
|
out.clear();
|
|
oCourseList::iterator it;
|
|
synchronizeList(oLCourseId);
|
|
|
|
Courses.sort();
|
|
|
|
vector< pair<pCourse, pair<pCourse, bool>> > ac;
|
|
ac.reserve(Courses.size());
|
|
map<int,int> id2ix;
|
|
for (it=Courses.begin(); it != Courses.end(); ++it) {
|
|
if (!it->Removed){
|
|
id2ix[it->getId()] = ac.size();
|
|
ac.push_back(make_pair(pCourse(&*it), make_pair(pCourse(0), false)));
|
|
}
|
|
}
|
|
|
|
for (size_t k = 0; k < ac.size(); k++) {
|
|
pCourse sh = ac[k].first->getShorterVersion();
|
|
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;
|
|
}
|
|
}
|
|
|
|
wstring b;
|
|
for (size_t k = 0; k < ac.size(); k++) {
|
|
pCourse it = ac[k].first;
|
|
|
|
if (simple) //gdi.addItem(name, it->Name, it->Id);
|
|
out.push_back(make_pair(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.push_back(make_pair(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();
|
|
|
|
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()
|
|
{
|
|
synchronizeList(oLCourseId, true, false);
|
|
synchronizeList(oLTeamId, false, false);
|
|
synchronizeList(oLRunnerId, false, true);
|
|
|
|
for (oCourseList::iterator cit=Courses.begin();
|
|
cit!=Courses.end();++cit) {
|
|
int numMaps = cit->getNumberMaps();
|
|
if (numMaps == 0)
|
|
cit->tMapsRemaining = numeric_limits<int>::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<int>::min())
|
|
pc->tMapsRemaining--;
|
|
|
|
pc->tMapsUsed++;
|
|
if (!it->isVacant())
|
|
pc->tMapsUsedNoVacant++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Count maps used for vacant team positions
|
|
|
|
for (oTeamList::const_iterator it=Teams.begin(); it != Teams.end(); ++it) {
|
|
if (!it->isRemoved()/* && it->getStatus() != StatusDNS*/) {
|
|
for (size_t j = 0; j < it->Runners.size(); j++) {
|
|
pRunner r = it->Runners[j];
|
|
if (r)
|
|
continue; // Already included
|
|
|
|
if (it->Class) {
|
|
const vector<pCourse> &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<int>::min())
|
|
tCrs->tMapsRemaining--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int oCourse::getIdSum(int nC) {
|
|
|
|
int id = 0;
|
|
for (int k = 0; k<min(nC, nControls); k++)
|
|
id = 31 * id + (Controls[k] ? Controls[k]->getId() : 0);
|
|
|
|
if (id == 0)
|
|
return getId();
|
|
|
|
return id;
|
|
}
|
|
|
|
void oCourse::setLegLengths(const vector<int> &legs) {
|
|
if (legs.size() == nControls +1 || legs.empty()) {
|
|
bool diff = legs.size() != legLengths.size();
|
|
if (!diff) {
|
|
for (size_t k = 0; k<legs.size(); k++)
|
|
if (legs[k] != legLengths[k])
|
|
diff = true;
|
|
}
|
|
if (diff) {
|
|
updateChanged();
|
|
legLengths = legs;
|
|
}
|
|
}
|
|
else
|
|
throw std::exception("Invalid parameter value");
|
|
}
|
|
|
|
double oCourse::getPartOfCourse(int start, int end) const
|
|
{
|
|
if (end == 0)
|
|
end = nControls;
|
|
|
|
if (legLengths.size() != nControls +1 || start <= end ||
|
|
unsigned(start) >= legLengths.size() ||
|
|
unsigned(end) >= legLengths.size() || Length==0)
|
|
return 0.0;
|
|
|
|
int dist = 0;
|
|
for (int k = start; k<end; k++)
|
|
dist += legLengths[k];
|
|
|
|
return max(1.0, double(dist) / double(Length));
|
|
}
|
|
|
|
|
|
const wstring &oCourse::getControlOrdinal(int controlIndex) const
|
|
{
|
|
if ( (controlIndex + 1 == nControls && useLastAsFinish()) || controlIndex == nControls)
|
|
return lang.tl("Mål");
|
|
|
|
if (oe->dataRevision != 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; k<controlIndex && k<nControls; k++) {
|
|
if (Controls[k] && !Controls[k]->isRogaining(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 + overTime * getRogainingPointsPerMinute()) / 60;
|
|
else // Time (minute) discrete model
|
|
reduction = ((59 + overTime) / 60) * 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;
|
|
tMapsUsed = -1;
|
|
tMapsUsedNoVacant = -1;
|
|
}
|
|
|
|
wstring oCourse::getCourseProblems() const
|
|
{
|
|
int max_time = getMaximumRogainingTime();
|
|
int min_point = getMinimumRogainingPoints();
|
|
|
|
if (max_time > 0) {
|
|
for (int k = 0; k<nControls; k++) {
|
|
if (Controls[k]->isRogaining(true))
|
|
return L"";
|
|
}
|
|
return L"Banan saknar rogainingkontroller.";
|
|
}
|
|
else if (min_point > 0) {
|
|
int max_p = 0;
|
|
for (int k = 0; k<nControls; k++) {
|
|
if (Controls[k]->isRogaining(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<pCourse> &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");
|
|
}
|
|
|
|
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) 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<int> ccIndex;
|
|
vector< vector<pControl> > loopKeys;
|
|
if (!constructLoopKeys(cc, loopKeys, ccIndex))
|
|
return pCourse(this);
|
|
|
|
bool firstAsStart = ccIndex[0] == 0;
|
|
|
|
vector< vector<int> > punchSequence;
|
|
|
|
vector<pPunch> punches;
|
|
card.getPunches(punches);
|
|
|
|
punchSequence.push_back(vector<int>());
|
|
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<int>());
|
|
else {
|
|
if (code != cc)
|
|
punchSequence.back().push_back(code);
|
|
}
|
|
}
|
|
|
|
|
|
map<int, vector< pair<int,int> > > preferences;
|
|
for (size_t k = 0; k < punchSequence.size(); k++) {
|
|
for (size_t 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<int> assignedKeys(loopKeys.size(), -1);
|
|
vector<int> usedPunches(punchSequence.size());
|
|
int assigned = 0;
|
|
for (map<int, vector< pair<int,int> > >::iterator it = preferences.begin(); it != preferences.end(); ++it) {
|
|
vector< pair<int,int> > &bestMatches = it->second;
|
|
map<int, vector<int> > sortedBestMatches;
|
|
vector< pair<int, int> > 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<int> &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;
|
|
}
|
|
|
|
|
|
/*for (size_t k = 0; k < punchSequence.size(); k++) {
|
|
bool done = false;
|
|
for (size_t j = 0; j < loopKeys.size(); j++) {
|
|
if (assignedKeys[j] == -1 && matchLoopKey(punchSequence[k], loopKeys[j])) {
|
|
assignedKeys[j] = k;
|
|
done = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!done) {
|
|
// Check if an already assigned key matches the current loop
|
|
int undone = -1;
|
|
size_t start = loopKeys.size();
|
|
for (size_t j = 0; j < loopKeys.size(); j++) {
|
|
if (matchLoopKey(punchSequence[k], loopKeys[j])) {
|
|
// Push that key away and try it somewhere else
|
|
undone = assignedKeys[j];
|
|
start = j + 1;
|
|
assignedKeys[j] = k;
|
|
done = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Try an already assigned key later
|
|
for (size_t j = start; undone != -1 && j < loopKeys.size(); j++) {
|
|
if (matchLoopKey(punchSequence[undone], loopKeys[j])) {
|
|
swap(assignedKeys[j], undone);
|
|
}
|
|
}
|
|
}
|
|
}*/
|
|
|
|
//set<int> loops;
|
|
//for (size_t k = 0; k < ccIndex.size(); k++)
|
|
// loops.insert(k);
|
|
vector<int> loopOrder;
|
|
map<int, int> 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<int, int>::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.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 lastAsFinish = useLastAsFinish() || Controls[nControls-1]->getId() == cc;
|
|
|
|
int endIx = lastAsFinish ? nControls - 1 : nControls;
|
|
|
|
for (size_t k = 0; k< loopOrder.size(); k++) {
|
|
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.tMapToOriginalOrder.push_back(currentCC);
|
|
tmpCourse.Controls[tmpCourse.nControls++] = Controls[currentCC];
|
|
if (size_t(end) < legLengths.size())
|
|
tmpCourse.legLengths.push_back(legLengths[end]);
|
|
}
|
|
}
|
|
|
|
if (lastAsFinish) {
|
|
//tmpCourse.tMapToOriginalOrder.push_back(nControls - 1);
|
|
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]);
|
|
}
|
|
|
|
//tmpCourse.tMapToOriginalOrder.push_back(nControls);
|
|
|
|
assert(tmpCourse.nControls == nControls);
|
|
assert(tmpCourse.tMapToOriginalOrder.size() == nControls + 1);
|
|
|
|
if (!legLengths.empty()) {
|
|
tmpCourse.legLengths.push_back(legLengths.back());
|
|
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<int> &punches, const vector<pControl> &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<pControl> > &loopKeys, vector<int> &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;
|
|
}
|
|
|
|
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<int,int> 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
|
|
pCourse oCourse::getShorterVersion() const {
|
|
int ix = getDCI().getInt("Shorten");
|
|
return oe->getCourse(ix);
|
|
}
|
|
|
|
// 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(pCourse shorten) {
|
|
if (shorten != 0)
|
|
getDI().setInt("Shorten", shorten->getId());
|
|
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;
|
|
}
|
|
|
|
void oCourse::getClasses(vector<pClass> &usageClass) const {
|
|
vector<pClass> cls;
|
|
oe->getClasses(cls, false);
|
|
|
|
for (size_t k = 0; k < cls.size(); k++) {
|
|
if (cls[k]->usesCourse(*this))
|
|
usageClass.push_back(cls[k]);
|
|
}
|
|
}
|
|
|