diff --git a/data/xsl/index.xsl b/data/xsl/index.xsl
index 8f077f58b..e817426df 100644
--- a/data/xsl/index.xsl
+++ b/data/xsl/index.xsl
@@ -234,7 +234,14 @@
-
+
+
+ HM
+
+
+
+
+
@@ -342,7 +349,14 @@
-
+
+
+ HM
+
+
+
+
+
|
diff --git a/src/edu/csus/ecs/pc2/core/model/FinalizeData.java b/src/edu/csus/ecs/pc2/core/model/FinalizeData.java
index af58b213d..a7f2a108c 100644
--- a/src/edu/csus/ecs/pc2/core/model/FinalizeData.java
+++ b/src/edu/csus/ecs/pc2/core/model/FinalizeData.java
@@ -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;
@@ -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);
+ return(finalizeData);
+ }
}
diff --git a/src/edu/csus/ecs/pc2/core/scoring/DefaultScoringAlgorithm.java b/src/edu/csus/ecs/pc2/core/scoring/DefaultScoringAlgorithm.java
index 8b8c65286..e3b4c34af 100644
--- a/src/edu/csus/ecs/pc2/core/scoring/DefaultScoringAlgorithm.java
+++ b/src/edu/csus/ecs/pc2/core/scoring/DefaultScoringAlgorithm.java
@@ -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;
@@ -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 {
@@ -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;
}
@@ -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) {
@@ -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 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
@@ -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 treeMap = new TreeMap(src);
+ TreeMap treeMap = new TreeMap(dsrc);
Collection 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();
+ } 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(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
@@ -655,9 +687,12 @@ private boolean dumpGroupList(Group[] groups, IMemento memento, List want
*/
private void createStandingXML (TreeMap treeMap, XMLMemento mementoRoot,
AccountList accountList, Problem[] problems, Hashtable problemsIndexHash, Group[] groups,
- IInternalContest theContest, IMemento summaryMememento, boolean excludedGroups) {
+ IInternalContest theContest, IMemento summaryMememento, boolean excludedGroups, boolean isWFStandings, int medianSolved) {
ContestInformation contestInformation = theContest.getContestInformation();
+ // This may be needed if using WF Standings
+ int lastMedalRank = 0;
+
// easy access
Hashtable groupHash = new Hashtable();
Hashtable groupIndexHash = new Hashtable();
@@ -695,7 +730,16 @@ private void createStandingXML (TreeMap 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 coll = treeMap.values();
Iterator iterator = coll.iterator();
@@ -754,16 +798,19 @@ private void createStandingXML (TreeMap 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;
@@ -901,13 +948,14 @@ private void createStandingXML (TreeMap 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();
@@ -938,6 +986,81 @@ private void createStandingXML (TreeMap 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?
*
@@ -1034,7 +1157,7 @@ private void generateSummaryTotalsForProblem(Problem[] problems, Hashtable |