From 7a99f0a4cd9baf995476b43dd58dd82f35e97f62 Mon Sep 17 00:00:00 2001 From: Erik Melin <31467290+erikmelin@users.noreply.github.com> Date: Tue, 8 Oct 2019 21:58:19 +0200 Subject: [PATCH] MeOS version 3.6.1109 --- code/TabClass.cpp | 47 +++- code/TabClass.h | 3 + code/TabCompetition.cpp | 4 +- code/Table.cpp | 2 +- code/english.lng | 2 +- code/gdioutput.cpp | 3 +- code/meosversion.cpp | 15 +- code/oCourse.h | 7 +- code/oDataContainer.cpp | 10 +- code/oEvent.cpp | 8 +- code/oEvent.h | 6 +- code/oEventDraw.cpp | 603 ++++++++++++++++++++++------------------ code/oRunner.cpp | 7 +- code/pdfwriter.cpp | 24 +- code/swedish.lng | 2 +- 15 files changed, 425 insertions(+), 318 deletions(-) diff --git a/code/TabClass.cpp b/code/TabClass.cpp index 80e6176..0243516 100644 --- a/code/TabClass.cpp +++ b/code/TabClass.cpp @@ -900,6 +900,7 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) wstring firstStart = gdi.getText("FirstStart"); wstring minInterval = gdi.getText("MinInterval"); wstring vacances = gdi.getText("Vacances"); + setDefaultVacant(vacances); clearPage(gdi, false); gdi.addString("", boldLarge, "Lotta flera klasser"); @@ -1044,13 +1045,14 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) gdi.addInput("FirstStart", oe->getAbsTime(3600), 10, 0, L"Första (ordinarie) start:"); gdi.addInput("MinInterval", L"2:00", 10, 0, L"Minsta startintervall:"); gdi.fillDown(); - gdi.addInput("Vacances", L"5%", 10, 0, L"Andel vakanser:"); + gdi.addInput("Vacances", getDefaultVacant(), 10, 0, L"Andel vakanser:"); gdi.popX(); createDrawMethod(gdi); gdi.fillDown(); gdi.addCheckbox("LateBefore", "Efteranmälda före ordinarie"); + gdi.addCheckbox("AllowNeighbours", "Tillåt samma bana inom basintervall", 0, oe->getPropertyInt("DrawInterlace", 1) != 0); gdi.dropLine(); gdi.popX(); @@ -1180,8 +1182,11 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) return 0; wstring minInterval = gdi.getText("MinInterval"); wstring vacances = gdi.getText("Vacances"); - bool lateBefore = false; - //bool pairwise = gdi.isChecked("Pairwise"); + setDefaultVacant(vacances); + bool lateBefore = gdi.isChecked("LateBefore"); + bool allowNeighbourSameCourse = gdi.isChecked("AllowNeighbours"); + oe->setProperty("DrawInterlace", allowNeighbourSameCourse ? 1 : 0); + int pairSize = 1; if (gdi.hasField("PairSize")) { pairSize = gdi.getSelectedItem("PairSize").first; @@ -1201,7 +1206,7 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) clearPage(gdi, true); oe->automaticDrawAll(gdi, firstStart, minInterval, vacances, - lateBefore, method, pairSize); + lateBefore, allowNeighbourSameCourse, method, pairSize); oe->addAutoBib(); gdi.scrollToBottom(); gdi.addButton("Cancel", "Återgå", ClassesCB); @@ -1263,10 +1268,19 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) int origin = bi.getExtraInt(); wstring firstStart = oe->getAbsTime(3600); wstring minInterval = L"2:00"; - wstring vacances = L"5%"; + wstring vacances = getDefaultVacant(); + if (gdi.hasField("Vacances")) { + vacances = gdi.getText("Vacances"); + setDefaultVacant(vacances); + } + int maxNumControl = 1; int pairSize = 1; + if (gdi.hasField("AllowNeighbours")) { + bool allowNeighbourSameCourse = gdi.isChecked("AllowNeighbours"); + oe->setProperty("DrawInterlace", allowNeighbourSameCourse ? 1 : 0); + } //bool pairwise = false; int by = 0; int bx = gdi.getCX(); @@ -4387,6 +4401,7 @@ void TabClass::readDrawInfo(gdioutput &gdi, DrawInfo &drawInfoOut) { int maxVacancy = gdi.getTextNo("VacancesMax"); int minVacancy = gdi.getTextNo("VacancesMin"); + setDefaultVacant(gdi.getText("Vacances")); double vacancyFactor = 0.01*_wtof(gdi.getText("Vacances").c_str()); double extraFactor = 0.01*_wtof(gdi.getText("Extra").c_str()); @@ -4661,7 +4676,7 @@ void TabClass::loadBasicDrawSetup(gdioutput &gdi, int &bx, int &by, const wstrin gdi.popX(); gdi.dropLine(4); gdi.fillDown(); - gdi.addCheckbox("AllowNeighbours", "Tillåt samma bana inom basintervall", 0, oe->getPropertyInt("DrawInterlace", 1) == 0); + gdi.addCheckbox("AllowNeighbours", "Tillåt samma bana inom basintervall", 0, oe->getPropertyInt("DrawInterlace", 1) != 0); gdi.addCheckbox("CoursesTogether", "Lotta klasser med samma bana gemensamt", 0, false); gdi.dropLine(0.5); @@ -4680,8 +4695,10 @@ void TabClass::loadBasicDrawSetup(gdioutput &gdi, int &bx, int &by, const wstrin gdi.fillRight(); gdi.popX(); gdi.addInput("Vacances", vacances, 6, 0, L"Andel vakanser:"); - gdi.addInput("VacancesMin", L"1", 6, 0, L"Min. vakanser (per klass):"); - gdi.addInput("VacancesMax", L"10", 6, 0, L"Max. vakanser (per klass):"); + bool zeroVac = _wtoi(vacances.c_str()) == 0; + + gdi.addInput("VacancesMin", zeroVac ? L"0" : L"1", 6, 0, L"Min. vakanser (per klass):"); + gdi.addInput("VacancesMax", zeroVac ? L"0" : L"10", 6, 0, L"Max. vakanser (per klass):"); gdi.addInput("Extra", L"0%", 6, 0, L"Förväntad andel efteranmälda:"); gdi.dropLine(4); @@ -4749,3 +4766,17 @@ void TabClass::loadReadyToDistribute(gdioutput &gdi, int &bx, int &by) { gdi.refresh(); } + +wstring TabClass::getDefaultVacant() { + int dvac = oe->getPropertyInt("VacantPercent", -1); + if (dvac >= 0 && dvac <= 100) + return itow(dvac) + L" %"; + else + return L"5 %"; +} + +void TabClass::setDefaultVacant(const wstring &v) { + int val = _wtoi(v.c_str()); + if (val >= 0 && val <= 100) + oe->setProperty("VacantPercent", val); +} diff --git a/code/TabClass.h b/code/TabClass.h index 02b9dd3..d568516 100644 --- a/code/TabClass.h +++ b/code/TabClass.h @@ -156,6 +156,9 @@ class TabClass : int maxNumControl, const wstring& minInterval, const wstring& vacances, const set &clsId); void loadReadyToDistribute(gdioutput &gdi, int &bx, int &by); + + wstring getDefaultVacant(); + void setDefaultVacant(const wstring &val); public: void clearCompetitionData(); diff --git a/code/TabCompetition.cpp b/code/TabCompetition.cpp index 2c9fd32..8f650ba 100644 --- a/code/TabCompetition.cpp +++ b/code/TabCompetition.cpp @@ -1545,7 +1545,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) switch (startType) { case SMCommon: - oe->automaticDrawAll(gdi, formatTimeHMS(firstStart), L"0", L"0", false, oEvent::DrawMethod::Random, 1); + oe->automaticDrawAll(gdi, formatTimeHMS(firstStart), L"0", L"0", false, false, oEvent::DrawMethod::Random, 1); drawn = true; break; @@ -1570,7 +1570,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) } } if (!skip) - oe->automaticDrawAll(gdi, formatTimeHMS(firstStart), L"2:00", L"2", true, oEvent::DrawMethod::MeOS, 1); + oe->automaticDrawAll(gdi, formatTimeHMS(firstStart), L"2:00", L"2", true, false, oEvent::DrawMethod::MeOS, 1); drawn = true; break; } diff --git a/code/Table.cpp b/code/Table.cpp index 59293a5..364638a 100644 --- a/code/Table.cpp +++ b/code/Table.cpp @@ -1686,7 +1686,7 @@ bool Table::inputChange(gdioutput &gdi, HWND hdt) int Table::getColumn(int x, bool limit) const { - if (columns.empty()) + if (columns.empty() || xpos.empty()) return -1; if (x &supp, vector &developSupp) supp.emplace_back(L"JWOC 2019"); developSupp.emplace_back(L"OK Nackhe"); supp.emplace_back(L"OK Rodhen"); - + supp.emplace_back(L"HEYRIES"); + developSupp.emplace_back(L"SongTao Wang / Henan Zhixing Exploration Sports Culture Co., Ltd."); + developSupp.emplace_back(L"Australian and Oceania Orienteering Championships 2019"); + supp.emplace_back(L"Järfälla OK"); + supp.emplace_back(L"TJ Slávia Farmaceut Bratislava"); + supp.emplace_back(L"OK Tyr, Karlstad"); + supp.emplace_back(L"Magnus Thornell, Surahammars SOK"); + reverse(supp.begin(), supp.end()); } diff --git a/code/oCourse.h b/code/oCourse.h index 6f4066e..7e69d28 100644 --- a/code/oCourse.h +++ b/code/oCourse.h @@ -71,9 +71,7 @@ protected: mutable int tMapsUsed = -1; mutable int tMapsUsedNoVacant = -1; - // Get an identity sum based on controls - int getIdSum(int nControls); - + /// Add an control without update pControl doAddControl(int Id); @@ -95,6 +93,9 @@ protected: public: + // Get an identity sum based on controls + int getIdSum(int nControls); + void getClasses(vector &usageClass) const; void remove(); diff --git a/code/oDataContainer.cpp b/code/oDataContainer.cpp index aed7778..c272f01 100644 --- a/code/oDataContainer.cpp +++ b/code/oDataContainer.cpp @@ -799,10 +799,12 @@ bool oDataContainer::isModified(const oDataInfo &di, return memcmp(vd, vdOld, 8) != 0; } } - else if (di.Type == oDTString){ - char * vd=(char *)(data)+di.Index; - char * vdOld=(char *)(oldData)+di.Index; - return strcmp(vd, vdOld) != 0; + else if (di.Type == oDTString) { + LPBYTE vdB = LPBYTE(data) + di.Index; + LPBYTE vdOldB = LPBYTE(oldData) + di.Index; + wchar_t * vd=(wchar_t *)vdB; + wchar_t * vdOld=(wchar_t *)vdOldB; + return wcscmp(vd, vdOld) != 0; } else if (di.Type == oDTStringDynamic){ const wstring &newS = (*strptr)[0][di.Index]; diff --git a/code/oEvent.cpp b/code/oEvent.cpp index aa21140..cf03603 100644 --- a/code/oEvent.cpp +++ b/code/oEvent.cpp @@ -2604,7 +2604,6 @@ int oEvent::getRelativeTime(const string &date, const string &absoluteTime, cons else return -1; } - int oEvent::getRelativeTime(const wstring &m) const { int dayIndex = 0; for (size_t k = 0; k + 1 < m.length(); k++) { @@ -2947,12 +2946,13 @@ void oEvent::generateVacancyList(gdioutput &gdi, GUICALLBACK cb) oRunnerList::iterator it; // BIB, START, NAME, CLUB, SI - int dx[5]={0, 0, 70, 150}; + int dx[5]={0, 0, gdi.scaleLength(70), gdi.scaleLength(150)}; bool withbib=hasBib(true, false); int i; - if (withbib) for (i=1;i<4;i++) dx[i]+=40; + const int bibLen = gdi.scaleLength(40); + if (withbib) for (i = 1; i < 4; i++) dx[i] += bibLen; int y=gdi.getCY(); int x=gdi.getCX(); @@ -2984,7 +2984,7 @@ void oEvent::generateVacancyList(gdioutput &gdi, GUICALLBACK cb) if (nRunner>=RunnersPerCol) { y = yStart; - x += dx[3]+5; + x += dx[3]+gdi.scaleLength(5); nRunner = 0; } diff --git a/code/oEvent.h b/code/oEvent.h index 7834adf..fe18fbf 100644 --- a/code/oEvent.h +++ b/code/oEvent.h @@ -588,7 +588,7 @@ public: // Automatic draw of all classes void automaticDrawAll(gdioutput &gdi, const wstring &firstStart, const wstring &minIntervall, const wstring &vacances, - bool lateBefore, DrawMethod method, int pairSize); + bool lateBefore, bool allowNeighbourSameCourse, DrawMethod method, int pairSize); // Restore a backup by renamning the file to .meos void restoreBackup(); @@ -773,10 +773,6 @@ public: /** Use the current computer time to convert the specified time to a long time, if long times are used. */ int convertToFullTime(int inTime); - /** Internal version of start order optimizer */ - void optimizeStartOrder(vector< vector > > &StartField, DrawInfo &drawInfo, - vector &cInfo, int useNControls, int alteration); - struct ResultEvent { ResultEvent() {} ResultEvent(pRunner r, int time, int control, RunnerStatus status): diff --git a/code/oEventDraw.cpp b/code/oEventDraw.cpp index 1808040..94fdb72 100644 --- a/code/oEventDraw.cpp +++ b/code/oEventDraw.cpp @@ -421,6 +421,328 @@ namespace { } } + +class DrawOptimAlgo { +private: + oEvent * oe; + vector Classes; + vector Runners; + + int maxNRunner = 0; + int maxGroup = 0; + int maxCourse = 0; + int bestEndPos = 0; + + map otherClasses; + + int bestEndPosGlobal = 0; + + static int optimalLayout(int interval, vector< pair > &classes) { + sort(classes.begin(), classes.end()); + + vector chaining(interval, 0); + + for (int k = int(classes.size()) - 1; k >= 0; k--) { + int ix = 0; + // Find free position + for (int i = 1; i 0) + nr += classes[k].second; + + chaining[ix] += 1 + interval * (nr - 1); + } + + int last = chaining[0]; + for (int i = 1; i &cInfo, int useNControls, int alteration) { + //OutputDebugString((L"Use NC:" + itow(useNControls)).c_str()); + if (di.firstStart <= 0) + di.firstStart = 0; + + otherClasses.clear(); + cInfo.clear(); + map runnerPerGroup; + map runnerPerCourse; + int nRunnersTot = 0; + for (auto c_it : Classes) { + bool drawClass = di.classes.count(c_it->getId()) > 0; + ClassInfo *cPtr = 0; + + if (!drawClass) { + otherClasses[c_it->getId()] = ClassInfo(&*c_it); + cPtr = &otherClasses[c_it->getId()]; + } + else + cPtr = &di.classes[c_it->getId()]; + + ClassInfo &ci = *cPtr; + pCourse pc = c_it->getCourse(); + + if (pc && useNControls < 1000) { + if (useNControls > 0 && pc->getNumControls() > 0) + ci.unique = 1000000 + pc->getIdSum(useNControls); + else + ci.unique = 10000 + pc->getId(); + + ci.courseId = pc->getId(); + } + else + ci.unique = ci.classId; + + if (!drawClass) + continue; + + int nr = c_it->getNumRunners(true, true, true); + if (ci.nVacant == -1 || !ci.nVacantSpecified || di.changedVacancyInfo) { + // Auto initialize + int nVacancies = int(nr * di.vacancyFactor + 0.5); + nVacancies = max(nVacancies, di.minVacancy); + nVacancies = min(nVacancies, di.maxVacancy); + nVacancies = max(nVacancies, 0); + + if (di.vacancyFactor == 0) + nVacancies = 0; + + ci.nVacant = nVacancies; + ci.nVacantSpecified = false; + } + + if (!ci.nExtraSpecified || di.changedExtraInfo) { + // Auto initialize + ci.nExtra = max(int(nr * di.extraFactor + 0.5), 1); + + if (di.extraFactor == 0) + ci.nExtra = 0; + ci.nExtraSpecified = false; + } + + ci.nRunners = nr + ci.nVacant; + + if (ci.nRunners > 0) { + nRunnersTot += ci.nRunners + ci.nExtra; + cInfo.push_back(ci); + runnerPerGroup[ci.unique] += ci.nRunners + ci.nExtra; + runnerPerCourse[ci.courseId] += ci.nRunners + ci.nExtra; + } + } + + maxGroup = 0; + maxCourse = 0; + maxNRunner = 0; + int a = 1 + (alteration % 7); + int b = (alteration % 3); + int c = alteration % 5; + + for (size_t k = 0; k < cInfo.size(); k++) { + maxNRunner = max(maxNRunner, cInfo[k].nRunners); + cInfo[k].nRunnersGroup = runnerPerGroup[cInfo[k].unique]; + cInfo[k].nRunnersCourse = runnerPerCourse[cInfo[k].courseId]; + maxGroup = max(maxGroup, cInfo[k].nRunnersGroup); + maxCourse = max(maxCourse, cInfo[k].nRunnersCourse); + cInfo[k].sortFactor = cInfo[k].nRunners * a + cInfo[k].nRunnersGroup * b + cInfo[k].nRunnersCourse * c; + } + + /*for (size_t k = 0; k < cInfo.size(); k++) { + auto pc = oe->getClass(cInfo[k].classId); + auto c = pc->getCourse(); + wstring cc; + for (int i = 0; i < 3; i++) + cc += itow(c->getControl(i)->getId()) + L","; + wstring w = pc->getName() + L"; " + cc + L"; " + itow(cInfo[k].unique) + L"; " + itow(cInfo[k].nRunners) + L"\n"; + OutputDebugString(w.c_str()); + }*/ + + di.numDistinctInit = runnerPerGroup.size(); + di.numRunnerSameInitMax = maxGroup; + di.numRunnerSameCourseMax = maxCourse; + // Calculate the theoretical best end position to use. + bestEndPos = 0; + + for (map::iterator it = runnerPerGroup.begin(); it != runnerPerGroup.end(); ++it) { + vector< pair > classes; + for (size_t k = 0; k < cInfo.size(); k++) { + if (cInfo[k].unique == it->first) + classes.push_back(make_pair(cInfo[k].nRunners, cInfo[k].nExtra)); + } + int optTime = optimalLayout(di.minClassInterval / di.baseInterval, classes); + bestEndPos = max(optTime, bestEndPos); + } + + if (nRunnersTot > 0) + bestEndPos = max(bestEndPos, nRunnersTot / di.nFields); + + if (!di.allowNeighbourSameCourse) + bestEndPos = max(bestEndPos, maxCourse * 2 - 1); + else + bestEndPos = max(bestEndPos, maxCourse); + } + +public: + + DrawOptimAlgo(oEvent *oe) : oe(oe) { + oe->getClasses(Classes, false); + oe->getRunners(-1, -1, Runners); + } + + void optimizeStartOrder(vector< vector > > &StartField, DrawInfo &di, + vector &cInfo, int useNControls, int alteration) { + + if (bestEndPosGlobal == 0) { + bestEndPosGlobal = 100000; + for (int i = 1; i <= di.maxCommonControl && i<=10; i++) { + int ncc = i; + if (i >= 10) + ncc = 0; + computeBestStartDepth(di, cInfo, ncc, 0); + bestEndPosGlobal = min(bestEndPos, bestEndPosGlobal); + } + + di.minimalStartDepth = bestEndPosGlobal * di.baseInterval; + } + + computeBestStartDepth(di, cInfo, useNControls, alteration); + + ClassInfo::sSortOrder = 0; + sort(cInfo.begin(), cInfo.end()); + + int maxSize = di.minClassInterval * maxNRunner; + + // Special case for constant time start + if (di.baseInterval == 0) { + di.baseInterval = 1; + di.minClassInterval = 0; + } + + // Calculate an estimated maximal class intervall + for (size_t k = 0; k < cInfo.size(); k++) { + int quotient = maxSize / (cInfo[k].nRunners*di.baseInterval); + + if (quotient*di.baseInterval > di.maxClassInterval) + quotient = di.maxClassInterval / di.baseInterval; + + if (cInfo[k].nRunnersGroup >= maxGroup) + quotient = di.minClassInterval / di.baseInterval; + + if (!cInfo[k].hasFixedTime) + cInfo[k].interval = quotient; + } + + for (int m = 0; m < di.nFields; m++) + StartField[m].resize(3000); + + int alternator = 0; + + // Fill up with non-drawn classes + for (auto &it : Runners) { + int st = it->getStartTime(); + int relSt = st - di.firstStart; + int relPos = relSt / di.baseInterval; + + if (st > 0 && relSt >= 0 && relPos < 3000 && (relSt%di.baseInterval) == 0) { + if (otherClasses.count(it->getClassId(false)) == 0) + continue; + pClass cls = it->getClassRef(true); + if (cls) { + if (!di.startName.empty() && cls->getStart() != di.startName) + continue; + + if (cls->hasFreeStart()) + continue; + } + + ClassInfo &ci = otherClasses[it->getClassId(false)]; + int k = 0; + while (true) { + if (k == StartField.size()) { + StartField.push_back(vector< pair >()); + StartField.back().resize(3000); + } + if (StartField[k][relPos].first == 0) { + StartField[k][relPos].first = ci.unique; + StartField[k][relPos].second = ci.courseId; + break; + } + k++; + } + } + } + + // Fill up classes with fixed starttime + for (size_t k = 0; k < cInfo.size(); k++) { + if (cInfo[k].hasFixedTime) { + insertStart(StartField, di.nFields, cInfo[k]); + } + } + + if (di.minClassInterval == 0) { + // Set fixed start time + for (size_t k = 0; k < cInfo.size(); k++) { + if (cInfo[k].hasFixedTime) + continue; + cInfo[k].firstStart = di.firstStart; + cInfo[k].interval = 0; + } + } + else { + // Do the distribution + for (size_t k = 0; k < cInfo.size(); k++) { + if (cInfo[k].hasFixedTime) + continue; + + int minPos = 1000000; + int minEndPos = 1000000; + int minInterval = cInfo[k].interval; + + for (int i = di.minClassInterval / di.baseInterval; i <= cInfo[k].interval; i++) { + + int startpos = alternator % max(1, (bestEndPos - cInfo[k].nRunners * i) / 3); + startpos = 0; + int ipos = startpos; + int t = 0; + + while (!isFree(di, StartField, di.nFields, ipos, i, cInfo[k])) { + t++; + + // Algorithm to randomize start position + // First startpos -> bestEndTime, then 0 -> startpos, then remaining + if (t < (bestEndPos - startpos)) + ipos = startpos + t; + else { + ipos = t - (bestEndPos - startpos); + if (ipos >= startpos) + ipos = t; + } + } + + int endPos = ipos + i * cInfo[k].nRunners; + if (endPos < minEndPos || endPos < bestEndPos) { + minEndPos = endPos; + minPos = ipos; + minInterval = i; + } + } + + cInfo[k].firstStart = minPos; + cInfo[k].interval = minInterval; + cInfo[k].overShoot = max(minEndPos - bestEndPosGlobal, 0); + insertStart(StartField, di.nFields, cInfo[k]); + + alternator += alteration; + } + } + } +}; + void oEvent::optimizeStartOrder(gdioutput &gdi, DrawInfo &di, vector &cInfo) { if (Classes.size()==0) @@ -439,12 +761,15 @@ void oEvent::optimizeStartOrder(gdioutput &gdi, DrawInfo &di, vector int nCtrl = 1;//max(1, di.maxCommonControl-2); const int maxControlDiff = di.maxCommonControl < 1000 ? di.maxCommonControl : 10; bool checkOnlyClass = di.maxCommonControl == 1000; + + DrawOptimAlgo drawOptim(this); + while (!found) { StartParam optInner; for (int alt = 0; alt <= 20 && !found; alt++) { vector< vector > > startField(di.nFields); - optimizeStartOrder(startField, di, cInfo, nCtrl, alt); + drawOptim.optimizeStartOrder(startField, di, cInfo, nCtrl, alt); int overShoot = 0; int overSum = 0; @@ -495,7 +820,7 @@ void oEvent::optimizeStartOrder(gdioutput &gdi, DrawInfo &di, vector } vector< vector > > startField(di.nFields); - optimizeStartOrder(startField, di, cInfo, opt.nControls, opt.alternator); + drawOptim.optimizeStartOrder(startField, di, cInfo, opt.nControls, opt.alternator); gdi.addString("", 0, "Identifierar X unika inledningar på banorna.#" + itos(di.numDistinctInit)); gdi.addString("", 0, "Största gruppen med samma inledning har X platser.#" + itos(di.numRunnerSameInitMax)); @@ -545,33 +870,6 @@ void oEvent::optimizeStartOrder(gdioutput &gdi, DrawInfo &di, vector gdi.dropLine(); } -int optimalLayout(int interval, vector< pair > &classes) { - sort(classes.begin(), classes.end()); - - vector chaining(interval, 0); - - for (int k = int(classes.size())-1 ; k >= 0; k--) { - int ix = 0; - // Find free position - for (int i = 1; i 0) - nr += classes[k].second; - - chaining[ix] += 1 + interval*(nr-1); - } - - int last = chaining[0]; - for (int i = 1; i &classes, DrawInfo &drawInfo, vector &cInfo) const { drawInfo.firstStart = 3600 * 22; drawInfo.minClassInterval = 3600; @@ -656,250 +954,6 @@ void oEvent::loadDrawSettings(const set &classes, DrawInfo &drawInfo, vecto cInfo[k].nRunnersCourse = runnerPerCourse[cInfo[k].courseId]; } } - -void oEvent::optimizeStartOrder(vector< vector > > &StartField, DrawInfo &di, - vector &cInfo, int useNControls, int alteration) -{ - - if (di.firstStart<=0) - di.firstStart = 0; - - if (di.minClassInterval < di.baseInterval) { - throw meosException("Startintervallet får inte vara kortare än basintervallet."); - } - - map otherClasses; - cInfo.clear(); - oClassList::iterator c_it; - map runnerPerGroup; - map runnerPerCourse; - int nRunnersTot = 0; - for (c_it=Classes.begin(); c_it != Classes.end(); ++c_it) { - bool drawClass = di.classes.count(c_it->getId())>0; - ClassInfo *cPtr = 0; - - if (!drawClass) { - otherClasses[c_it->getId()] = ClassInfo(&*c_it); - cPtr = &otherClasses[c_it->getId()]; - } - else - cPtr = &di.classes[c_it->getId()]; - - ClassInfo &ci = *cPtr; - pCourse pc = c_it->getCourse(); - - if (pc && useNControls < 1000) { - if (useNControls>0 && pc->nControls>0) - ci.unique = 1000000 + pc->getIdSum(useNControls); - else - ci.unique = 10000 + pc->getId(); - - ci.courseId = pc->getId(); - } - else - ci.unique = ci.classId; - - if (!drawClass) - continue; - - int nr = c_it->getNumRunners(true, true, true); - if (ci.nVacant == -1 || !ci.nVacantSpecified || di.changedVacancyInfo) { - // Auto initialize - int nVacancies = int(nr * di.vacancyFactor + 0.5); - nVacancies = max(nVacancies, di.minVacancy); - nVacancies = min(nVacancies, di.maxVacancy); - nVacancies = max(nVacancies, 0); - - if (di.vacancyFactor == 0) - nVacancies = 0; - - ci.nVacant = nVacancies; - ci.nVacantSpecified = false; - } - - if (!ci.nExtraSpecified || di.changedExtraInfo) { - // Auto initialize - ci.nExtra = max(int(nr * di.extraFactor + 0.5), 1); - - if (di.extraFactor == 0) - ci.nExtra = 0; - ci.nExtraSpecified = false; - } - - ci.nRunners = nr + ci.nVacant; - - if (ci.nRunners>0) { - nRunnersTot += ci.nRunners + ci.nExtra; - cInfo.push_back(ci); - runnerPerGroup[ci.unique] += ci.nRunners + ci.nExtra; - runnerPerCourse[ci.courseId] += ci.nRunners + ci.nExtra; - } - } - - int maxGroup = 0; - int maxCourse = 0; - int maxNRunner = 0; - int a = 1 + (alteration % 7); - int b = (alteration % 3); - int c = alteration % 5; - - for (size_t k = 0; k::iterator it = runnerPerGroup.begin(); it != runnerPerGroup.end(); ++it) { - vector< pair > classes; - for (size_t k = 0; kfirst) - classes.push_back(make_pair(cInfo[k].nRunners, cInfo[k].nExtra)); - } - int optTime = optimalLayout(di.minClassInterval/di.baseInterval, classes); - bestEndPos = max(optTime, bestEndPos); - } - - if (nRunnersTot > 0) - bestEndPos = max(bestEndPos, nRunnersTot / di.nFields); - - bestEndPos = max(bestEndPos, maxCourse * 2); - - di.minimalStartDepth = bestEndPos * di.baseInterval; - - ClassInfo::sSortOrder = 0; - sort(cInfo.begin(), cInfo.end()); - - int maxSize = di.minClassInterval * maxNRunner; - - // Special case for constant time start - if (di.baseInterval==0) { - di.baseInterval = 1; - di.minClassInterval = 0; - } - - // Calculate an estimated maximal class intervall - for (size_t k = 0; k < cInfo.size(); k++) { - int quotient = maxSize/(cInfo[k].nRunners*di.baseInterval); - - if (quotient*di.baseInterval > di.maxClassInterval) - quotient=di.maxClassInterval/di.baseInterval; - - if (cInfo[k].nRunnersGroup >= maxGroup) - quotient = di.minClassInterval / di.baseInterval; - - if (!cInfo[k].hasFixedTime) - cInfo[k].interval = quotient; - } - - for(int m=0;m < di.nFields;m++) - StartField[m].resize(3000); - - int alternator = 0; - - // Fill up with non-drawn classes - for (oRunnerList::iterator it = Runners.begin(); it!=Runners.end(); ++it) { - int st = it->getStartTime(); - int relSt = st-di.firstStart; - int relPos = relSt / di.baseInterval; - - if (st>0 && relSt>=0 && relPos<3000 && (relSt%di.baseInterval) == 0) { - if (otherClasses.count(it->getClassId(false))==0) - continue; - - if (!di.startName.empty() && it->Class && it->Class->getStart()!=di.startName) - continue; - - ClassInfo &ci = otherClasses[it->getClassId(false)]; - int k = 0; - while(true) { - if (k==StartField.size()) { - StartField.push_back(vector< pair >()); - StartField.back().resize(3000); - } - if (StartField[k][relPos].first==0) { - StartField[k][relPos].first = ci.unique; - StartField[k][relPos].second = ci.courseId; - break; - } - k++; - } - } - } - - // Fill up classes with fixed starttime - for (size_t k = 0; k < cInfo.size(); k++) { - if (cInfo[k].hasFixedTime) { - insertStart(StartField, di.nFields, cInfo[k]); - } - } - - if (di.minClassInterval == 0) { - // Set fixed start time - for (size_t k = 0; k < cInfo.size(); k++) { - if (cInfo[k].hasFixedTime) - continue; - cInfo[k].firstStart = di.firstStart; - cInfo[k].interval = 0; - } - } - else { - // Do the distribution - for (size_t k = 0; k < cInfo.size(); k++) { - if (cInfo[k].hasFixedTime) - continue; - - int minPos = 1000000; - int minEndPos = 1000000; - int minInterval=cInfo[k].interval; - - for (int i = di.minClassInterval/di.baseInterval; i<=cInfo[k].interval; i++) { - - int startpos = alternator % max(1, (bestEndPos - cInfo[k].nRunners * i)/3); - startpos = 0; - int ipos = startpos; - int t = 0; - - while( !isFree(di, StartField, di.nFields, ipos, i, cInfo[k]) ) { - t++; - - // Algorithm to randomize start position - // First startpos -> bestEndTime, then 0 -> startpos, then remaining - if (t<(bestEndPos-startpos)) - ipos = startpos + t; - else { - ipos = t - (bestEndPos-startpos); - if (ipos>=startpos) - ipos = t; - } - } - - int endPos = ipos + i*cInfo[k].nRunners; - if (endPos < minEndPos || endPos < bestEndPos) { - minEndPos = endPos; - minPos = ipos; - minInterval = i; - } - } - - cInfo[k].firstStart = minPos; - cInfo[k].interval = minInterval; - cInfo[k].overShoot = max(minEndPos - bestEndPos, 0); - insertStart(StartField, di.nFields, cInfo[k]); - - alternator += alteration; - } - } -} - void oEvent::drawRemaining(DrawMethod method, bool placeAfter) { DrawType drawType = placeAfter ? DrawType::RemainingAfter : DrawType::RemainingBefore; @@ -1271,7 +1325,7 @@ void oEvent::drawListClumped(int ClassID, int FirstStart, int Interval, int Vaca void oEvent::automaticDrawAll(gdioutput &gdi, const wstring &firstStart, const wstring &minIntervall, const wstring &vacances, - bool lateBefore, DrawMethod method, int pairSize) + bool lateBefore, bool allowNeighbourSameCourse, DrawMethod method, int pairSize) { gdi.refresh(); const int leg = 0; @@ -1403,6 +1457,7 @@ void oEvent::automaticDrawAll(gdioutput &gdi, const wstring &firstStart, di.minVacancy = 1; di.maxVacancy = 100; di.vacancyFactor = vacancy; + di.allowNeighbourSameCourse = allowNeighbourSameCourse; di.startName = start; diff --git a/code/oRunner.cpp b/code/oRunner.cpp index 43a5484..f0e8c82 100644 --- a/code/oRunner.cpp +++ b/code/oRunner.cpp @@ -3136,8 +3136,13 @@ bool oRunner::apply(bool sync, pRunner src, bool setTmpOnly) { tLeg = -1; tLegEquClass = 0; tUseStartPunch=true; - if (tInTeam) + if (tInTeam) { tInTeam->apply(sync, this, setTmpOnly); + if (Class && Class->isQualificationFinalBaseClass()) { + if (tLeg > 0 && Class == getClassRef(true)) + tNeedNoCard = true; // Not qualified + } + } else { if (Class && Class->hasMultiCourse()) { pClass pc=Class; diff --git a/code/pdfwriter.cpp b/code/pdfwriter.cpp index 49c0fe1..0c656ab 100644 --- a/code/pdfwriter.cpp +++ b/code/pdfwriter.cpp @@ -142,6 +142,12 @@ void pdfwriter::selectFont(HPDF_Page page, const PDFFontSet &fs, int format, flo } } +const float fontFromGdiScale(double gdiScale) { + double f = max(1.0, min(gdiScale, 3.0))-1.0; + double s = 1.05 + 0.75*f; + return float(s); +} + void pdfwriter::generatePDF(const gdioutput &gdi, const wstring &file, const wstring &pageTitleW, @@ -206,7 +212,9 @@ void pdfwriter::generatePDF(const gdioutput &gdi, float w = HPDF_Page_GetWidth(page); float h = HPDF_Page_GetHeight(page); float scale = (w / maxX) * 0.95f; - float fontScale = 1.5f; + + double gdiScale = gdi.getScale(); + const float fontScale = fontFromGdiScale(gdiScale); vector pages; PageInfo pageInfo; @@ -243,33 +251,33 @@ void pdfwriter::generatePDF(const gdioutput &gdi, if (fonts.count(info[k].ti.font) == 0) { FontInfo fi; gdi.getFontInfo(info[k].ti, fi); - float fontScale; + float fontScaleLoc; wstring tmpFile; fonts[info[k].ti.font] = fs; //Default fallback PDFFontSet &f = fonts[info[k].ti.font]; - HPDF_Font font = getPDFFont(fi.normal, float(gdi.getScale()), tmpFile, fontScale); + HPDF_Font font = getPDFFont(fi.normal, float(gdi.getScale()), tmpFile, fontScaleLoc); if (!tmpFile.empty()) tmpFiles.push_back(tmpFile); if (font) { f.font = font; - f.fontScale = fontScale; + f.fontScale = fontScaleLoc; } - font = getPDFFont(fi.italic, float(gdi.getScale()), tmpFile, fontScale); + font = getPDFFont(fi.italic, float(gdi.getScale()), tmpFile, fontScaleLoc); if (!tmpFile.empty()) tmpFiles.push_back(tmpFile); if (font) { f.fontItalic = font; - f.fontScaleItalic = fontScale; + f.fontScaleItalic = fontScaleLoc; } - font = getPDFFont(fi.bold, (float)gdi.getScale(), tmpFile, fontScale); + font = getPDFFont(fi.bold, (float)gdi.getScale(), tmpFile, fontScaleLoc); if (!tmpFile.empty()) tmpFiles.push_back(tmpFile); if (font) { f.fontBold = font; - f.fontScaleBold = fontScale; + f.fontScaleBold = fontScaleLoc; } } diff --git a/code/swedish.lng b/code/swedish.lng index b6b8590..6542ed7 100644 --- a/code/swedish.lng +++ b/code/swedish.lng @@ -835,7 +835,7 @@ SI X är redan inläst. Ska den läsas in igen? = SI X är redan inläst. Ska de SI på = SI på SI-dubbletter: %d = SI-dubbletter: %d SOFT-avgift = SOFT-avgift -SOFT-lottning = SOFT-lottning +SOFT-lottning = Äldre SOFT-lottning Saknad starttid = Saknad starttid Sammanställning = Sammanställning Sammanställning, ekonomi = Sammanställning, ekonomi