Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions data/xsl/index.xsl
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,14 @@
</xsl:if>
</xsl:if>
</xsl:if>
<xsl:value-of select="@rank"/>
<xsl:choose>
<xsl:when test="@isHonorable = 'true' and /contestStandings/standingsHeader/@scoreboardMessage = 'Final Scoreboard'">
HM
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@rank"/>
</xsl:otherwise>
</xsl:choose>
</td>
<td>
<xsl:if test="@isGold = 'true'">
Expand Down Expand Up @@ -342,7 +349,14 @@
</xsl:if>
</xsl:if>
</xsl:if>
<xsl:value-of select="@rank"/>
<xsl:choose>
<xsl:when test="@isHonorable = 'true' and /contestStandings/standingsHeader/@scoreboardMessage = 'Final Scoreboard'">
HM
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@rank"/>
</xsl:otherwise>
</xsl:choose>
</td>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed the file data/xsl/index.xsl. I do not know very much about XSL; it "looks right" to me but that's all I can say.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does work... so, that's something.

<td>
<xsl:if test="@isGold = 'true'">
Expand Down
19 changes: 18 additions & 1 deletion src/edu/csus/ecs/pc2/core/model/FinalizeData.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 1989-2024 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau.
// Copyright (C) 1989-2026 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau.
package edu.csus.ecs.pc2.core.model;

import java.io.Serializable;
Expand Down Expand Up @@ -199,4 +199,21 @@ public int getHighHonorSolvedCount() {
public int getHonorSolvedCount() {
return honorSolvedCount;
}


/**
* Generate some default finalize data
* @return FinalizeData object
*/
public static FinalizeData getDefaultFinalizeData()
{
FinalizeData finalizeData = new FinalizeData();
finalizeData.setGoldRank(4);
finalizeData.setSilverRank(8);
finalizeData.setBronzeRank(12);
finalizeData.setCertified(false);
finalizeData.setComment("Preliminary Results - Contest not Finalized");
finalizeData.setUseWFGroupRanking(true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really the case that we want the default for useWFGroupRanking to be true? It seems like we ONLY use that value for WF contests... in which case false might be a more logical default?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but I think that is out of scope for this issue and PR. I will open a new issue for changing the default ranking to non-WF Group Ranking. Issue #1219 created.

return(finalizeData);
}
}
175 changes: 149 additions & 26 deletions src/edu/csus/ecs/pc2/core/scoring/DefaultScoringAlgorithm.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau.
// Copyright (C) 1989-2026 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau.
package edu.csus.ecs.pc2.core.scoring;

import java.io.IOException;
Expand Down Expand Up @@ -354,6 +354,8 @@ public String getStandings(IInternalContest theContest, Run[] runs, Integer divi
this.log = inputLog;
long freezeSeconds = -1;
boolean isThawn = false;
FinalizeData finalizeData = null;

if (obeyFreeze) {
String freezeTime = theContest.getContestInformation().getFreezeTime();
try {
Expand All @@ -366,7 +368,7 @@ public String getStandings(IInternalContest theContest, Run[] runs, Integer divi
log.warning("Could not convert '"+freezeTime+"' to seconds");
throw new InvalidParameterException("Invalid freezeTime "+freezeTime);
}
FinalizeData finalizeData = theContest.getFinalizeData(); // sometimes, eg junit this is null
finalizeData = theContest.getFinalizeData(); // sometimes, eg junit this is null
if (finalizeData != null && finalizeData.isCertified() && theContest.getContestInformation().isUnfrozen()) {
isThawn = true;
}
Expand Down Expand Up @@ -410,10 +412,10 @@ public String getStandings(IInternalContest theContest, Run[] runs, Integer divi
}

summaryMemento.putLong("problemCount", problems.length);

Site[] sites = theContest.getSites();
summaryMemento.putInteger("siteCount", sites.length);

Group[] groups = theContest.getGroups();
boolean bGroupsExcluded = false;
if (groups != null) {
Expand All @@ -425,7 +427,7 @@ public String getStandings(IInternalContest theContest, Run[] runs, Integer divi
//Note also that generateSummaryTotalsForProblem() already existed and was inserting MOST of that information in the output already;
// the "colorList" processing code which used to be here was simply added to that method instead (adding internalId, letter, and
// url to the <problem> elements), avoiding duplication of problem description data in the output XML.

if (runs == null) {
// Note: we do not deal with divisionNumber here since
// 1) it is being deprecated
Expand Down Expand Up @@ -512,16 +514,46 @@ public String getStandings(IInternalContest theContest, Run[] runs, Integer divi

applyScoringAdjustments(standingsRecordHash, accountList);

boolean wfStandings = false;
int medianSolved = 0;

DefaultStandingsRecordComparator dsrc = new DefaultStandingsRecordComparator();
dsrc.setCachedAccountList(accountList);

// use TreeMap to sort
DefaultStandingsRecordComparator src = new DefaultStandingsRecordComparator();
src.setCachedAccountList(accountList);
TreeMap<StandingsRecord, StandingsRecord> treeMap = new TreeMap<StandingsRecord, StandingsRecord>(src);
TreeMap<StandingsRecord, StandingsRecord> treeMap = new TreeMap<StandingsRecord, StandingsRecord>(dsrc);
Collection<StandingsRecord> enumeration = standingsRecordHash.values();
for (StandingsRecord record : enumeration) {
treeMap.put(record, record);
}

createStandingXML(treeMap, mementoRoot, accountList, problems, problemsIndexHash, groups, theContest, summaryMemento, bGroupsExcluded);
// Determine how we want to sort the teams. We may have to re-sort using WF rankings for the public final scoreboard
// For the public scoreboard, isThawn is true, however, we only want to use WF rankings if it is specified
// to do so, and, it's for the entire contest, not just a group(s)
// If you're wondering why we sort twice (once above, and once in this "if" block:
// We need to get the median solved, so, we have to sort normally,
// just to get the median so the FinalsStandingsRecordComparator can use it.
if(isThawn && finalizeData.isUseWFGroupRanking() && (wantedGroups == null || wantedGroups.isEmpty())) {
FinalsStandingsRecordComparator fsrc = new FinalsStandingsRecordComparator();
fsrc.setCachedAccountList(accountList);
fsrc.setLastRank(finalizeData.getBronzeRank());
if (finalizeData.getHonorSolvedCount() != 0) {
medianSolved = finalizeData.getHonorSolvedCount();
Comment on lines +540 to +541

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new static method getDefaultFinalizeData() (in src/edu/csus/ecs/pc2/core/model/FinalizeData.java) doesn't set a default value for honorSolvedCount. (I realize that it's an int, and that it's initialized to zero in its declaration... but still, it seems like that method should explicitly set it -- just like it's already explicitly setting certified to false and useWFGroupRanking to true even though those are the values

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed. Also as a CI, created static final int's for gold, silver and bronze default last ranks.
Next push.

} else {
medianSolved = getMedian(treeMap.keySet().toArray(new StandingsRecord[0]));
}
fsrc.setMedian(medianSolved);
wfStandings = true;

// Now we have to re-sort the treeMap using WF sorting
treeMap = new TreeMap<StandingsRecord, StandingsRecord>(fsrc);
enumeration = standingsRecordHash.values();
for (StandingsRecord record : enumeration) {
treeMap.put(record, record);
}
}

createStandingXML(treeMap, mementoRoot, accountList, problems, problemsIndexHash, groups, theContest, summaryMemento, bGroupsExcluded, wfStandings, medianSolved);

} // mutex

Expand Down Expand Up @@ -655,9 +687,12 @@ private boolean dumpGroupList(Group[] groups, IMemento memento, List<Group> want
*/
private void createStandingXML (TreeMap<StandingsRecord, StandingsRecord> treeMap, XMLMemento mementoRoot,
AccountList accountList, Problem[] problems, Hashtable<ElementId, Integer> problemsIndexHash, Group[] groups,
IInternalContest theContest, IMemento summaryMememento, boolean excludedGroups) {
IInternalContest theContest, IMemento summaryMememento, boolean excludedGroups, boolean isWFStandings, int medianSolved) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trivial, but it would be nice if the misspelling summaryMememento was fixed (to be summaryMemento).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed.
Next push.


ContestInformation contestInformation = theContest.getContestInformation();
// This may be needed if using WF Standings
int lastMedalRank = 0;

// easy access
Hashtable<ElementId, Group> groupHash = new Hashtable<ElementId, Group>();
Hashtable<Group, Integer> groupIndexHash = new Hashtable<Group, Integer>();
Expand Down Expand Up @@ -695,7 +730,16 @@ private void createStandingXML (TreeMap<StandingsRecord, StandingsRecord> treeMa
divisionCount = highestFound;
String teamVarDisplayString = contestInformation.getTeamScoreboardDisplayFormat();

StandingsRecord[] srArray = new StandingsRecord[treeMap.size()];
// get the sorted treeMap as an array for createCitationRankInformation and later for medal citations
StandingsRecord[] srArray = treeMap.values().toArray(new StandingsRecord[0]);

// If doing public WF style standings, we assign the block ranks now
if(isWFStandings) {
if(Utilities.isDebugMode()) {
System.err.println("createStandingsXML: Generating Final board for public (obeyFreeze)");
}
assignRanksBlock(srArray, theContest, medianSolved);
}

Collection<StandingsRecord> coll = treeMap.values();
Iterator<StandingsRecord> iterator = coll.iterator();
Expand Down Expand Up @@ -754,16 +798,19 @@ private void createStandingXML (TreeMap<StandingsRecord, StandingsRecord> treeMa
Object o = iterator.next();

StandingsRecord standingsRecord = (StandingsRecord)o;
indexRank++;
if (!isTeamTied(standingsRecord, numSolved, score, lastSolved)) {
numSolved = standingsRecord.getNumberSolved();
score = standingsRecord.getPenaltyPoints();
lastSolved = standingsRecord.getLastSolved();
rank = indexRank;
standingsRecord.setRankNumber(rank);
} else {
// current user tied with last user, so same rank
standingsRecord.setRankNumber(rank);
// Only assign rank if not doing WF final rankings on public scoreboard because we did it above with assignRankBlocks()
if(!isWFStandings) {
indexRank++;
if (!isTeamTied(standingsRecord, numSolved, score, lastSolved)) {
numSolved = standingsRecord.getNumberSolved();
score = standingsRecord.getPenaltyPoints();
lastSolved = standingsRecord.getLastSolved();
rank = indexRank;
standingsRecord.setRankNumber(rank);
} else {
// current user tied with last user, so same rank
standingsRecord.setRankNumber(rank);
}
}
// mementoRoot.putMemento(standingsRecord.toMemento());
long totalAttempts = 0;
Expand Down Expand Up @@ -901,13 +948,14 @@ private void createStandingXML (TreeMap<StandingsRecord, StandingsRecord> treeMa
// entire contest, not one particular group (or groups). So, if any groups were excluded
// from the scoreboard, we do not fill in citations.
if(!excludedGroups) {
// Now go back and fill in any award citations for the teams
ResultsFile resultsInfo = new ResultsFile();
// Note that createCitationRankInformation() may change the order of srArray
CitationRankInformation ri = resultsInfo.createCitationRankInformation(theContest, srArray);
if(index == teamStandingsMementos.size()) {
int teamRank, sIndex;
IMemento standingsRecordMemento;
// Now go back and fill in any award citations for the teams
ResultsFile resultsInfo = new ResultsFile();
// Note that createCitationRankInformation() may change the order of srArray
CitationRankInformation ri = resultsInfo.createCitationRankInformation(theContest, srArray);

for(sIndex = 0; sIndex < index; ) {
standingsRecordMemento = teamStandingsMementos.get(sIndex);
teamRank = srArray[sIndex].getRankNumber();
Expand Down Expand Up @@ -938,6 +986,81 @@ private void createStandingXML (TreeMap<StandingsRecord, StandingsRecord> treeMa
}
}

/**
* Assigns WF style "honors" ranking values.
* This is only used to generate the public (obeyFreeze), unfrozen, finalized scoreboard.
* This logic similar to ResultsFile.createFileLines().
*
* @param srArray - sorted array of standings records
* @param theContest
* @param median - number of problems solved by median team
*/
private void assignRanksBlock(StandingsRecord [] srArray, IInternalContest theContest, int median) {

int rank = 1;
StandingsRecord sr, prev = null;
int idx = 0;
int numRecs = srArray.length;
FinalizeData finalizeData = theContest.getFinalizeData();
boolean didFirstHonorableMention = false;

// Tammy: just in case it IS null; purportedly it may be null for Junit's, but that is unconfirmed.
if(finalizeData == null) {
finalizeData = FinalizeData.getDefaultFinalizeData();
}

// First one, handle it special to make the for loop below easier. Basically, we're priming the pump here (prev).
if (numRecs > 0) {
sr = srArray[idx];
sr.setRankNumber(rank);
prev = sr;
idx++;
}

int numInBlock = 0;

// We always rank teams uniquely that get medals, and, the first team AFTER the medals is assigned
// its own rank as well.
int alwaysRanked = finalizeData.getBronzeRank();

if(finalizeData.getHonorSolvedCount() > 0) {
median = finalizeData.getHonorSolvedCount();
}

if(Utilities.isDebugMode()) {
System.err.printf("assignRankBlock: alwaysRanked=%d median=%d%n", alwaysRanked, median);
}
for(; idx < numRecs; idx++) {
sr = srArray[idx];
if(Utilities.isDebugMode()) {
System.err.printf(" rank=%d sr.getNumberSolved=%d prev.getNumberSolver=%d numInBlock=%d%n",
rank, sr.getNumberSolved(), prev.getNumberSolved(), numInBlock);
}
// For medals, each team is ranked. Or, if after medals, teams are grouped by # solved until honorable mention
if (rank <= alwaysRanked || (!didFirstHonorableMention && sr.getNumberSolved() != prev.getNumberSolved())) {
rank++;
rank += numInBlock;
numInBlock = 0;
if(sr.getNumberSolved() < median) {
// From now on, all teams are the same rank since they're all honorable mention
if(Utilities.isDebugMode()) {
System.err.printf(" FOUND first honorable mention at idx=%d rank=%d%n", idx, rank);
}
didFirstHonorableMention = true;
}
} else {
numInBlock++;
}

sr.setRankNumber(rank);
prev = sr;
if(Utilities.isDebugMode()) {
System.err.printf(" Setting team at index %d(%s) to rank %d%n", idx, sr.getClientId().getName(), rank);
}
}
}


/**
* Input is a sorted ranking list. What is the number of problems solved by the median team?
*
Expand Down Expand Up @@ -1034,7 +1157,7 @@ private void generateSummaryTotalsForProblem(Problem[] problems, Hashtable<Eleme
problemsIndexHash.put(problems[i].getElementId(), new Integer(id));
IMemento problemMemento = summaryMemento.createChild("problem");
problemMemento.putInteger("id", id); //ordinal starting at 1

//the following was (probably) added when BalloonSettings were removed; BalloonSettings was creating
// a variable named "id" and this was probably an attempt at renaming that variable.
// It's likely that no other code is actually using "internalId", although it MIGHT be used
Expand Down
17 changes: 10 additions & 7 deletions src/edu/csus/ecs/pc2/core/scoring/NewScoringAlgorithm.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau.
// Copyright (C) 1989-2026 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau.
package edu.csus.ecs.pc2.core.scoring;

import java.io.IOException;
Expand Down Expand Up @@ -736,12 +736,6 @@ public ProblemScoreRecord createProblemScoreRecord(Run[] runs, Problem problem,

ignoreSub = false;

if (run.isJudged()) {
numberJudged++;
} else {
numberPending++;
}

if (run.isSolved() && solutionTime == 0) {
// set to solved, set solution time
solved = true;
Expand Down Expand Up @@ -771,6 +765,15 @@ public ProblemScoreRecord createProblemScoreRecord(Run[] runs, Problem problem,
}
}

if (run.isJudged()) {
// Only count as judged run if not ignoring it.
if(!ignoreSub) {
numberJudged++;
}
} else {
numberPending++;
}

if(!ignoreSub) {
numberSubmissions++;
}
Expand Down
4 changes: 2 additions & 2 deletions src/edu/csus/ecs/pc2/exports/ccs/ResultsFile.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau.
// Copyright (C) 1989-2026 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau.
package edu.csus.ecs.pc2.exports.ccs;

import java.util.ArrayList;
Expand Down Expand Up @@ -170,7 +170,7 @@ public String[] createFileLines(IInternalContest contest, Group group, String re
int highHonorSolvedCount = 0;

if (finalizeData == null) {
finalizeData = GenDefaultFinalizeData();
finalizeData = FinalizeData.getDefaultFinalizeData();
}
// Only use Bill honors WF ranking rules if not for a specific group and we're doing WF ranks
// Calculating the Bill honors rules doesn't make sense for sub-groups
Expand Down