/************************************************************************
MeOS - Orienteering Software
Copyright (C) 2009-2017 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 fro 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
************************************************************************/
#include "stdafx.h"
#include
#include
#include
#include
#include "oEvent.h"
#include "gdioutput.h"
#include "oDataContainer.h"
#include "random.h"
#include "meos.h"
#include "meos_util.h"
#include "localizer.h"
#include "gdifonts.h"
#include "oEventDraw.h"
#include "meosexception.h"
int ClassInfo::sSortOrder=0;
DrawInfo::DrawInfo() {
vacancyFactor = 0.05;
extraFactor = 0.1;
minVacancy = 1;
maxVacancy = 10;
baseInterval = 60;
minClassInterval = 120;
maxClassInterval = 180;
nFields = 10;
firstStart = 3600;
maxCommonControl = 3;
allowNeighbourSameCourse = true;
coursesTogether = false;
// Statistics output from optimize start order
numDistinctInit = -1;
numRunnerSameInitMax = -1;
minimalStartDepth = -1;
}
bool ClassInfo::operator <(ClassInfo &ci)
{
if (sSortOrder==0) {
return sortFactor > ci.sortFactor;
}
else if (sSortOrder == 2) {
return pc->getSortIndex() < ci.pc->getSortIndex();
}
else if (sSortOrder == 3) {
if (unique != ci.unique) {
if (ci.nRunnersGroup != nRunnersGroup)
return nRunnersGroup > ci.nRunnersGroup;
else
return unique < ci.unique;
}
else
return firstStart > > &StartField, int nFields,
int FirstPos, int PosInterval, ClassInfo &cInfo)
{
int Type = cInfo.unique;
int courseId = cInfo.courseId;
int nEntries = cInfo.nRunners;
bool disallowNeighbors = !di.allowNeighbourSameCourse;
// Adjust first pos to make room for extra (before first start)
if (cInfo.nExtra>0) {
int newFirstPos = FirstPos - cInfo.nExtra * PosInterval;
while (newFirstPos<0)
newFirstPos += PosInterval;
int extra = (FirstPos - newFirstPos) / PosInterval;
nEntries += extra;
FirstPos = newFirstPos;
}
//Check if free at all...
for(int k = 0; k < nEntries; k++){
bool hasFree=false;
for(int f=0;f 1 && ix+1 < StartField[f].size())
nextT = StartField[f][ix + 1].second;
if (PosInterval > 1 && ix>0)
prevT = StartField[f][ix - 1].second;
if ((nextT > 0 && nextT == courseId) || (prevT > 0 && prevT == courseId))
return false;
}
if (t == 0)
hasFree=true;
else if (t == Type)
return false;//Type of course occupied. Cannot put it here;
}
if (!hasFree) return false;//No free start position.
}
return true;
}
bool insertStart(vector< vector< pair > > &StartField, int nFields, ClassInfo &cInfo)
{
int Type = cInfo.unique;
int courseId = cInfo.courseId;
int nEntries = cInfo.nRunners;
int FirstPos = cInfo.firstStart;
int PosInterval = cInfo.interval;
// Adjust first pos to make room for extra (before first start)
if (cInfo.nExtra>0) {
int newFirstPos = FirstPos - cInfo.nExtra * PosInterval;
while (newFirstPos<0)
newFirstPos += PosInterval;
int extra = (FirstPos - newFirstPos) / PosInterval;
nEntries += extra;
FirstPos = newFirstPos;
}
for (int k=0; k &cInfo)
{
if (Classes.size()==0)
return;
struct StartParam {
int nControls;
int alternator;
double badness;
int last;
StartParam() : nControls(1), alternator(1), badness(1000), last(90000000) {}
};
StartParam opt;
bool found = false;
int nCtrl = 1;//max(1, di.maxCommonControl-2);
const int maxControlDiff = di.maxCommonControl < 1000 ? di.maxCommonControl : 10;
bool checkOnlyClass = di.maxCommonControl == 1000;
while (!found) {
StartParam optInner;
for (int alt = 0; alt <= 20 && !found; alt++) {
vector< vector > > startField(di.nFields);
optimizeStartOrder(startField, di, cInfo, nCtrl, alt);
int overShoot = 0;
int overSum = 0;
int numOver = 0;
for (size_t k=0;k0) {
numOver++;
overShoot = max (overShoot, ci.overShoot);
overSum += ci.overShoot;
}
//laststart=max(laststart, ci.firstStart+ci.nRunners*ci.interval);
}
double avgShoot = double(overSum)/cInfo.size();
double badness = overShoot==0 ? 0 : overShoot / avgShoot;
if (badnessmaxControlDiff) //We need some limit
found = true;
}
vector< vector > > startField(di.nFields);
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));
gdi.addString("", 0, "Antal löpare på vanligaste banan X.#" + itos(di.numRunnerSameCourseMax));
gdi.addString("", 0, "Kortast teoretiska startdjup utan krockar är X minuter.#" + itos(di.minimalStartDepth/60));
gdi.dropLine();
//Find last starter
int last = opt.last;
int laststart=0;
for (size_t k=0;kgetAbsTime(laststart*di.baseInterval+di.firstStart));
gdi.dropLine();
int nr;
int T=0;
int sum=0;
gdi.addString("", 1, "Antal startande per intervall (inklusive redan lottade):");
string str="";
int empty=4;
while (T <= last) {
nr=0;
for(size_t k=0;k > &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;
drawInfo.maxClassInterval = 1;
drawInfo.minVacancy = 10;
drawInfo.maxVacancy = 1;
set reducedStart;
for (set::const_iterator it = classes.begin(); it != classes.end(); ++it) {
pClass pc = oe->getClass(*it);
if (pc) {
int fs = pc->getDrawFirstStart();
int iv = pc->getDrawInterval();
if (iv > 0 && fs > 0) {
drawInfo.firstStart = min(drawInfo.firstStart, fs);
drawInfo.minClassInterval = min(drawInfo.minClassInterval, iv);
drawInfo.maxClassInterval = max(drawInfo.maxClassInterval, iv);
drawInfo.minVacancy = min(drawInfo.minVacancy, pc->getDrawVacant());
drawInfo.maxVacancy = max(drawInfo.maxVacancy, pc->getDrawVacant());
reducedStart.insert(fs%iv);
}
}
}
drawInfo.baseInterval = drawInfo.minClassInterval;
int lastStart = -1;
for (set::iterator it = reducedStart.begin(); it != reducedStart.end(); ++it) {
if (lastStart == -1)
lastStart = *it;
else {
drawInfo.baseInterval = min(drawInfo.baseInterval, *it-lastStart);
lastStart = *it;
}
}
map runnerPerGroup;
map runnerPerCourse;
cInfo.clear();
cInfo.resize(classes.size());
int i = 0;
for (set::const_iterator it = classes.begin(); it != classes.end(); ++it) {
pClass pc = oe->getClass(*it);
if (pc) {
int fs = pc->getDrawFirstStart();
int iv = pc->getDrawInterval();
if (iv <= 0)
iv = drawInfo.minClassInterval;
if (fs <= 0)
fs = drawInfo.firstStart; //Fallback
cInfo[i].pc = pc;
cInfo[i].classId = *it;
cInfo[i].courseId = pc->getCourseId();
cInfo[i].firstStart = fs;
cInfo[i].unique = pc->getCourseId();
if (cInfo[i].unique == 0)
cInfo[i].unique = pc->getId() * 10000;
cInfo[i].firstStart = (fs - drawInfo.firstStart) / drawInfo.baseInterval;
cInfo[i].interval = iv / drawInfo.baseInterval;
cInfo[i].nVacant = pc->getDrawVacant();
cInfo[i].nExtra = pc->getDrawNumReserved();
cInfo[i].nRunners = pc->getNumRunners(true, true, true) + cInfo[i].nVacant;
if (cInfo[i].nRunners>0) {
runnerPerGroup[cInfo[i].unique] += cInfo[i].nRunners;
runnerPerCourse[cInfo[i].courseId] += cInfo[i].nRunners;
}
drawInfo.classes[*it] = cInfo[i];
i++;
}
}
for (size_t k = 0; k > > &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) {
// 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;
}
if (!ci.nExtraSpecified) {
// Auto initialize
ci.nExtra = max(int(nr * di.extraFactor + 0.5), 1);
if (di.extraFactor == 0)
ci.nExtra = 0;
}
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())==0)
continue;
if (!di.startName.empty() && it->Class && it->Class->getStart()!=di.startName)
continue;
ClassInfo &ci = otherClasses[it->getClassId()];
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(bool useSOFTMethod, bool placeAfter)
{
DrawType drawType = placeAfter ? remainingAfter : remainingBefore;
for (oClassList::iterator it = Classes.begin(); it != Classes.end(); ++it) {
vector spec;
spec.push_back(ClassDrawSpecification(it->getId(), 0, 0, 0, 0));
drawList(spec, useSOFTMethod, 1, drawType);
}
}
void oEvent::drawList(const vector &spec,
bool useSOFTMethod, int pairSize, DrawType drawType) {
autoSynchronizeLists(false);
assert(pairSize > 0);
oRunnerList::iterator it;
int VacantClubId=getVacantClub();
map clsId2Ix;
set clsIdClearVac;
const bool multiDay = hasPrevStage();
for (size_t k = 0; k < spec.size(); k++) {
pClass pc = getClass(spec[k].classID);
if (!pc)
throw std::exception("Klass saknas");
if (spec[k].vacances>0 && pc->getClassType()==oClassRelay)
throw std::exception("Vakanser stöds ej i stafett.");
if (spec[k].vacances>0 && spec[k].leg>0)
throw std::exception("Det går endast att sätta in vakanser på sträcka 1.");
if (size_t(spec[k].leg) < pc->legInfo.size()) {
pc->legInfo[spec[k].leg].startMethod = STDrawn; //Automatically change start method
}
clsId2Ix[spec[k].classID] = k;
if (!multiDay && spec[k].leg == 0)
clsIdClearVac.insert(spec[k].classID);
}
vector runners;
runners.reserve(Runners.size());
if (drawType == drawAll) {
if (!clsIdClearVac.empty()) {
//Only remove vacances on leg 0.
vector toRemove;
//Remove old vacances
for (it=Runners.begin(); it != Runners.end(); ++it) {
if (clsIdClearVac.count(it->getClassId())) {
if (it->isRemoved())
continue;
if (it->tInTeam)
continue; // Cannot remove team runners
if (it->getClubId()==VacantClubId) {
toRemove.push_back(it->getId());
}
}
}
removeRunner(toRemove);
toRemove.clear();
//loop over specs, check clsIdClearVac...
for (size_t k = 0; k < spec.size(); k++) {
if (!clsIdClearVac.count(spec[k].classID))
continue;
for (int i = 0; i < spec[k].vacances; i++) {
oe->addRunnerVacant(spec[k].classID);
}
}
}
for (it=Runners.begin(); it != Runners.end(); ++it) {
if (!it->isRemoved() && clsId2Ix.count(it->getClassId())) {
if (it->getStatus() == StatusNotCompetiting)
continue;
int ix = clsId2Ix[it->getClassId()];
if (it->legToRun() == spec[ix].leg ) {
runners.push_back(&*it);
spec[ix].ntimes++;
}
}
}
}
else {
// Find first/last start in class and interval:
vector first(spec.size(), 7*24*3600);
vector last(spec.size(), 0);
set cinterval;
int baseInterval = 10*60;
for (it=Runners.begin(); it != Runners.end(); ++it) {
if (!it->isRemoved() && clsId2Ix.count(it->getClassId())) {
if (it->getStatus() == StatusNotCompetiting)
continue;
int st = it->getStartTime();
int ix = clsId2Ix[it->getClassId()];
if (st>0) {
first[ix] = min(first[ix], st);
last[ix] = max(last[ix], st);
cinterval.insert(st);
}
else {
spec[ix].ntimes++;
runners.push_back(&*it);
}
}
}
// Find start interval
int t=0;
for (set::iterator sit = cinterval.begin(); sit!=cinterval.end();++sit) {
if ( (*sit-t) > 0)
baseInterval = min(baseInterval, (*sit-t));
t = *sit;
}
for (size_t k = 0; k < spec.size(); k++) {
if (drawType == remainingBefore)
spec[k].firstStart = first[k] - runners.size()*baseInterval;
else
spec[k].firstStart = last[k] + baseInterval;
spec[k].interval = baseInterval;
if (last[k] == 0 || spec[k].firstStart<=0 || baseInterval == 10*60) {
// Fallback if incorrect specification.
spec[k].firstStart = 3600;
spec[k].interval = 2*60;
}
}
}
if (runners.empty())
return;
vector stimes(runners.size());
int nr = 0;
for (size_t k = 0; k < spec.size(); k++) {
for (int i = 0; i < spec[k].ntimes; i++) {
int kx = i/pairSize;
stimes[nr++] = spec[k].firstStart + spec[k].interval * kx;
}
}
if (spec.size() > 1)
sort(stimes.begin(), stimes.end());
if (gdibase.isTest())
InitRanom(0,0);
if (useSOFTMethod)
drawSOFTMethod(runners);
else
permute(stimes);
int minStartNo = Runners.size();
for(unsigned k=0;ksetStartTime(stimes[k], true, false);
minStartNo = min(minStartNo, runners[k]->getStartNo());
}
CurrentSortOrder = SortByStartTime;
sort(runners.begin(), runners.end());
if (minStartNo == 0)
minStartNo = nextFreeStartNo + 1;
for(size_t k=0; ksetStartNo(k+minStartNo, false);
runners[k]->synchronize();
}
nextFreeStartNo = max(nextFreeStartNo, minStartNo + stimes.size());
}
void getLargestClub(map > &clubRunner, vector &largest)
{
size_t maxClub=0;
for (map >::iterator it =
clubRunner.begin(); it!=clubRunner.end(); ++it) {
maxClub = max(maxClub, it->second.size());
}
for (map >::iterator it =
clubRunner.begin(); it!=clubRunner.end(); ++it) {
if (it->second.size()==maxClub) {
swap(largest, it->second);
clubRunner.erase(it);
return;
}
}
}
void getRange(int size, vector &p)
{
p.resize(size);
for (size_t k=0;k &runners, bool handleBlanks)
{
if (runners.empty())
return;
//Group runners per club
map > clubRunner;
for (size_t k=0;kgetClubId() : -1;
clubRunner[clubId].push_back(runners[k]);
}
vector< vector > runnerGroups(1);
// Find largest club
getLargestClub(clubRunner, runnerGroups[0]);
int largeSize = runnerGroups[0].size();
int ngroups = (runners.size()+largeSize-1) / largeSize;
runnerGroups.resize(ngroups);
while (!clubRunner.empty()) {
// Find the smallest available group
unsigned small = runners.size()+1;
int cgroup = -1;
for (size_t k=1;k largest;
getLargestClub(clubRunner, largest);
runnerGroups[cgroup].insert(runnerGroups[cgroup].end(), largest.begin(), largest.end());
}
unsigned maxGroup=runnerGroups[0].size();
//Permute the first group
vector pg(maxGroup);
getRange(pg.size(), pg);
permute(pg);
vector pr(maxGroup);
for (unsigned k=0;k p(runnerGroups.size());
getRange(p.size(), p);
permute(p);
// Write back result
int index = 0;
for (unsigned level = 0; level0 && pc->getClassType()!=oClassIndividual)
throw std::exception("Lottningsmetoden stöds ej i den här klassen.");
oRunnerList::iterator it;
int nRunners=0;
autoSynchronizeLists(false);
while (Vacances>0) {
addRunnerVacant(ClassID);
Vacances--;
}
for (it=Runners.begin(); it != Runners.end(); ++it)
if (it->Class && it->Class->Id==ClassID) nRunners++;
if (nRunners==0) return;
int *stimes=new int[nRunners];
//Number of start groups
//int ngroups=(nRunners/5)
int ginterval;
if (nRunners>=Interval)
ginterval=10;
else if (Interval/nRunners>60){
ginterval=40;
}
else if (Interval/nRunners>30){
ginterval=20;
}
else if (Interval/nRunners>20){
ginterval=15;
}
else if (Interval/nRunners>10){
ginterval=1;
}
else ginterval=10;
int nGroups=Interval/ginterval+1; //15 s. per interval.
int k;
if (nGroups>0){
int MaxRunnersGroup=max((2*nRunners)/nGroups, 4)+GetRandomNumber(2);
int *sgroups=new int[nGroups];
for(k=0;k5){
//Remove second group...
sgroups[1]=sgroups[nGroups-1];
nGroups--;
}
if (nGroups>9 && ginterval<60 && (GetRandomBit() || GetRandomBit() || GetRandomBit())){
//Remove third group...
sgroups[2]=sgroups[nGroups-1];
nGroups--;
if (nGroups>13 && ginterval<30 && (GetRandomBit() || GetRandomBit() || GetRandomBit())){
//Remove third group...
sgroups[3]=sgroups[nGroups-1];
nGroups--;
int ng=4; //Max two minutes pause
while(nGroups>10 && (nRunners/nGroups)5){
permute(sgroups+2, nGroups-2);
//Remove some random groups (except first and last).
for(k=2;k5){
sgroups[k]=sgroups[nGroups-1];
nGroups--;
}
}
}
//Premute all groups;
permute(sgroups, nGroups);
int *counters=new int[nGroups];
memset(counters, 0, sizeof(int)*nGroups);
stimes[0]=FirstStart;
stimes[1]=FirstStart+Interval;
for(k=2;kMaxRunnersGroup){
g=(g+3)%nGroups;
}
if (sgroups[g]==FirstStart){
//Avoid first start
if (GetRandomBit() || GetRandomBit())
g=(g+1)%nGroups;
}
if (counters[g]>MaxRunnersGroup){
g=(g+2)%nGroups;
}
if (counters[g]>MaxRunnersGroup){
g=(g+2)%nGroups;
}
stimes[k]=sgroups[g];
counters[g]++;
}
delete[] sgroups;
delete[] counters;
}
else{
for(k=0;kClass && it->Class->Id==ClassID){
it->setStartTime(stimes[k++], true, false);
it->StartNo=k;
it->synchronize();
}
delete[] stimes;
}
void oEvent::automaticDrawAll(gdioutput &gdi, const string &firstStart,
const string &minIntervall, const string &vacances,
bool lateBefore, bool softMethod, int pairSize)
{
gdi.refresh();
const int leg = 0;
const double extraFactor = 0.0;
int drawn = 0;
int baseInterval = convertAbsoluteTimeMS(minIntervall)/2;
if (baseInterval == 0) {
gdi.fillDown();
int iFirstStart = getRelativeTime(firstStart);
if (iFirstStart>0)
gdi.addString("", 1, "Gemensam start");
else {
gdi.addString("", 1, "Nollställer starttider");
iFirstStart = 0;
}
gdi.refreshFast();
gdi.dropLine();
for (oClassList::iterator it = Classes.begin(); it!=Classes.end(); ++it) {
if (it->isRemoved())
continue;
vector spec;
spec.push_back(ClassDrawSpecification(it->getId(), 0, iFirstStart, 0, 0));
oe->drawList(spec, false, 1, drawAll);
}
return;
}
if (baseInterval<1 || baseInterval>60*60)
throw std::exception("Felaktigt tidsformat för intervall");
int iFirstStart = getRelativeTime(firstStart);
if (iFirstStart<=0)
throw std::exception("Felaktigt tidsformat för första start");
double vacancy = atof(vacances.c_str())/100;
gdi.fillDown();
gdi.addString("", 1, "Automatisk lottning").setColor(colorGreen);
gdi.addString("", 0, "Inspekterar klasser...");
gdi.refreshFast();
set notDrawn;
getNotDrawnClasses(notDrawn, false);
set needsCompletion;
getNotDrawnClasses(needsCompletion, true);
for(set::iterator it = notDrawn.begin(); it!=notDrawn.end(); ++it)
needsCompletion.erase(*it);
//Start with not drawn classes
map starts;
map runnersPerClass;
// Count number of runners per start
for (oRunnerList::iterator it = Runners.begin(); it!=Runners.end(); ++it) {
if (it->skip())
continue;
if (it->tLeg != leg)
continue;
if (it->isVacant() && notDrawn.count(it->getClassId())==1)
continue;
pClass pc = it->Class;
if (pc && pc->hasFreeStart())
continue;
if (pc)
++starts[pc->getStart()];
++runnersPerClass[pc];
}
while ( !starts.empty() ) {
// Select smallest start
int runnersStart = Runners.size()+1;
string start;
for ( map::iterator it = starts.begin(); it != starts.end(); ++it) {
if (runnersStart > it->second) {
start = it->first;
runnersStart = it->second;
}
}
starts.erase(start);
// Estimate parameters for start
DrawInfo di;
int maxRunners = 0;
// Find largest class in start;
for (oClassList::iterator it = Classes.begin(); it!=Classes.end(); ++it) {
if (it->getStart() != start)
continue;
if (it->hasFreeStart())
continue;
maxRunners = max(maxRunners, runnersPerClass[&*it]);
}
if (maxRunners==0)
continue;
int maxParallell = 15;
if (runnersStart < 100)
maxParallell = 4;
else if (runnersStart < 300)
maxParallell = 6;
else if (runnersStart < 700)
maxParallell = 10;
else if (runnersStart < 1000)
maxParallell = 12;
else
maxParallell = 15;
int optimalParallel = runnersStart / (maxRunners*2); // Min is every second interval
di.nFields = max(3, min (optimalParallel + 2, 15));
di.baseInterval = baseInterval;
di.extraFactor = extraFactor;
di.firstStart = iFirstStart;
di.minClassInterval = baseInterval * 2;
di.maxClassInterval = di.minClassInterval;
di.minVacancy = 1;
di.maxVacancy = 100;
di.vacancyFactor = vacancy;
di.startName = start;
for (oClassList::iterator it = Classes.begin(); it!=Classes.end(); ++it) {
if (it->getStart() != start)
continue;
if (notDrawn.count(it->getId())==0)
continue; // Only not drawn classes
if (it->hasFreeStart())
continue;
di.classes[it->getId()] = ClassInfo(&*it);
}
if (di.classes.size()==0)
continue;
gdi.dropLine();
gdi.addStringUT(1, lang.tl("Optimerar startfördelning") + " " + start);
gdi.refreshFast();
gdi.dropLine();
vector cInfo;
optimizeStartOrder(gdi, di, cInfo);
int laststart=0;
for (size_t k=0;kgetClassType() == oClassRelay) {
gdi.addString("", 0, "Hoppar över stafettklass: X#" +
getClass(ci.classId)->getName()).setColor(colorRed);
continue;
}
gdi.addString("", 0, "Lottar: X#" + getClass(ci.classId)->getName());
vector spec;
spec.push_back(ClassDrawSpecification(ci.classId, leg,
di.firstStart + di.baseInterval * ci.firstStart,
di.baseInterval * ci.interval, ci.nVacant));
drawList(spec, softMethod, pairSize, oEvent::drawAll);
gdi.scrollToBottom();
gdi.refreshFast();
drawn++;
}
}
// Classes that need completion
for (oClassList::iterator it = Classes.begin(); it!=Classes.end(); ++it) {
if (needsCompletion.count(it->getId())==0)
continue;
if (it->hasFreeStart())
continue;
gdi.addStringUT(0, lang.tl("Lottar efteranmälda") + ": " + it->getName());
vector spec;
spec.push_back(ClassDrawSpecification(it->getId(), leg, 0, 0, 0));
drawList(spec, softMethod, 1, lateBefore ? remainingBefore : remainingAfter);
gdi.scrollToBottom();
gdi.refreshFast();
drawn++;
}
gdi.dropLine();
if (drawn==0)
gdi.addString("", 1, "Klart: inga klasser behövde lottas.").setColor(colorGreen);
else
gdi.addString("", 1, "Klart: alla klasser lottade.").setColor(colorGreen);
// Relay classes?
gdi.dropLine();
gdi.refreshFast();
}
void oEvent::drawPersuitList(int classId, int firstTime, int restartTime,
int maxTime, int interval,
int pairSize, bool reverse, double scale) {
if (classId<=0)
return;
pClass pc=getClass(classId);
if (!pc)
throw std::exception("Klass saknas");
const int leg = 0;
if (size_t(leg) < pc->legInfo.size()) {
pc->legInfo[leg].startMethod = STDrawn; //Automatically change start method
}
vector trunner;
getRunners(classId, 0, trunner);
vector runner;
runner.reserve(trunner.size());
for (size_t k = 0; k< trunner.size(); k++) // Only treat specified leg
if (trunner[k]->tLeg == leg)
runner.push_back(trunner[k]);
if (runner.empty())
return;
// Make sure patrol members use the same time
vector adjustedTimes(runner.size());
for (size_t k = 0; kinputStatus == StatusOK && runner[k]->inputTime>0) {
int it = runner[k]->inputTime;
if (runner[k]->tInTeam) {
for (size_t j = 0; j < runner[k]->tInTeam->Runners.size(); j++) {
int it2 = runner[k]->tInTeam->Runners[j]->inputTime;
if (it2 > 0)
it = max(it, it2);
}
}
adjustedTimes[k] = it;
}
}
vector< pair > times(runner.size());
for (size_t k = 0; kinputStatus == StatusOK && adjustedTimes[k]>0) {
if (scale != 1.0)
times[k].first = int(floor(double(adjustedTimes[k]) * scale + 0.5));
else
times[k].first = adjustedTimes[k];
}
else {
times[k].first = 3600 * 24 * 7 + runner[k]->inputStatus;
if (runner[k]->isVacant())
times[k].first += 10; // Vacansies last
}
}
// Sorted by name in input
stable_sort(times.begin(), times.end());
int delta = times[0].first;
if (delta >= 3600*24*7)
delta = 0;
int reverseDelta = 0;
if (reverse) {
for (size_t k = 0; ksetStartTime(firstTime + times[k].first - delta, true, false);
else
r->setStartTime(firstTime - times[k].first + reverseDelta, true, false);
}
else if (!reverse) {
if (breakIndex == -1)
breakIndex = k;
r->setStartTime(restartTime + ((k - breakIndex)/pairSize) * interval, true, false);
}
else {
if (breakIndex == -1) {
breakIndex = times.size() - 1;
odd = times.size() % 2;
}
r->setStartTime(restartTime + ((breakIndex - k + odd)/pairSize) * interval, true, false);
}
r->synchronize(true);
}
}