Display individual standings below team standings

This commit is contained in:
Claude Brisson
2025-06-11 11:03:58 +02:00
parent be18f159be
commit 17bb013feb
6 changed files with 162 additions and 18 deletions

View File

@@ -7,6 +7,7 @@ import org.jeudego.pairgoth.model.MacMahon
import org.jeudego.pairgoth.model.Pairable
import org.jeudego.pairgoth.model.PairingType
import org.jeudego.pairgoth.model.Player
import org.jeudego.pairgoth.model.TeamTournament
import org.jeudego.pairgoth.model.Tournament
import org.jeudego.pairgoth.model.getID
import org.jeudego.pairgoth.model.historyBefore
@@ -50,9 +51,9 @@ fun Tournament<*>.getSortedPairables(round: Int, includePreliminary: Boolean = f
val mmBase = pairable.mmBase()
val score = roundScore(mmBase +
(nbW(pairable) ?: 0.0) +
(1..round).map { round ->
(1..round).sumOf { round ->
if (playersPerRound.getOrNull(round - 1)?.contains(pairable.id) == true) 0.0 else 1.0
}.sum() * pairing.pairingParams.main.mmsValueAbsent)
} * pairing.pairingParams.main.mmsValueAbsent)
Pair(
if (pairing.pairingParams.main.sosValueAbsentUseBase) mmBase
else roundScore(mmBase + round/2),
@@ -100,14 +101,14 @@ fun Tournament<*>.getSortedPairables(round: Int, includePreliminary: Boolean = f
Criterion.DC -> StandingsHandler.nullMap
}
}
val pairables = pairables.values.filter { includePreliminary || it.final }.map { it.toDetailedJson() }
pairables.forEach { player ->
val jsonPairables = pairables.values.filter { includePreliminary || it.final }.map { it.toDetailedJson() }
jsonPairables.forEach { player ->
for (crit in criteria) {
player[crit.first] = crit.second[player.getID()] ?: 0.0
}
player["results"] = Json.MutableArray(List(round) { "0=" })
}
val sortedPairables = pairables.sortedWith { left, right ->
val sortedPairables = jsonPairables.sortedWith { left, right ->
for (crit in criteria) {
val lval = left.getDouble(crit.first) ?: 0.0
val rval = right.getDouble(crit.first) ?: 0.0
@@ -129,15 +130,16 @@ fun Tournament<*>.getSortedPairables(round: Int, includePreliminary: Boolean = f
return sortedPairables
}
fun Tournament<*>.populateStandings(sortedPairables: List<Json.Object>, round: Int = rounds) {
val sortedMap = sortedPairables.associateBy {
fun Tournament<*>.populateStandings(sortedEntries: List<Json.Object>, round: Int = rounds, individualStandings: Boolean) {
val sortedMap = sortedEntries.associateBy {
it.getID()!!
}
// refresh name, firstname, club and level
val refMap = if (individualStandings) players else pairables
sortedMap.forEach { (id, pairable) ->
val mutable = pairable as Json.MutableObject
pairables[id]?.let {
refMap[id]?.let {
mutable["name"] = it.name
if (it is Player) {
mutable["firstname"] = it.firstname
@@ -150,7 +152,8 @@ fun Tournament<*>.populateStandings(sortedPairables: List<Json.Object>, round: I
// fill result
for (r in 1..round) {
games(r).values.forEach { game ->
val roundGames = if (individualStandings) individualGames(r) else games(r)
roundGames.values.forEach { game ->
val white = if (game.white != 0) sortedMap[game.white] else null
val black = if (game.black != 0) sortedMap[game.black] else null
val whiteNum = white?.getInt("num") ?: 0
@@ -186,3 +189,56 @@ fun Tournament<*>.populateStandings(sortedPairables: List<Json.Object>, round: I
}
}
}
fun TeamTournament.getSortedTeamMembers(round: Int, includePreliminary: Boolean = false): List<Json.Object> {
val teamGames = historyBefore(round + 1)
val individualHistory = teamGames.map { roundTeamGames ->
roundTeamGames.flatMap { game -> individualGames[game.id]?.toList() ?: listOf() }
}
val historyHelper = HistoryHelper(individualHistory) {
pairables.mapValues {
Pair(0.0, wins[it.key] ?: 0.0)
}
}
val neededCriteria = mutableListOf(Criterion.NBW, Criterion.RATING)
val criteria = neededCriteria.map { crit ->
crit.name to when (crit) {
Criterion.NBW -> historyHelper.wins
Criterion.RANK -> pairables.mapValues { it.value.rank }
Criterion.RATING -> pairables.mapValues { it.value.rating }
else -> null
}
}
val jsonPlayers = players.values.filter { includePreliminary || it.final }.map { it.toDetailedJson() }
jsonPlayers.forEach { player ->
for (crit in criteria) {
player[crit.first] = crit.second?.get(player.getID()) ?: 0.0
}
player["results"] = Json.MutableArray(List(round) { "0=" })
}
val sortedPlayers = jsonPlayers.sortedWith { left, right ->
for (crit in criteria) {
val lval = left.getDouble(crit.first) ?: 0.0
val rval = right.getDouble(crit.first) ?: 0.0
val cmp = lval.compareTo(rval)
if (cmp != 0) return@sortedWith -cmp
}
return@sortedWith 0
}.mapIndexed() { i, obj ->
obj.set("num", i+1)
}
var place = 1
sortedPlayers.groupBy { p ->
Triple(
criteria.getOrNull(0)?.first?.let { crit -> p.getDouble(crit) ?: 0.0 } ?: 0.0,
criteria.getOrNull(1)?.first?.let { crit -> p.getDouble(crit) ?: 0.0 } ?: 0.0,
criteria.getOrNull(2)?.first?.let { crit -> p.getDouble(crit) ?: 0.0 } ?: 0.0
)
}.forEach {
it.value.forEach { p -> p["place"] = place }
place += it.value.size
}
return sortedPlayers
}

View File

@@ -6,6 +6,7 @@ import org.jeudego.pairgoth.model.Criterion
import org.jeudego.pairgoth.model.Criterion.*
import org.jeudego.pairgoth.model.ID
import org.jeudego.pairgoth.model.PairingType
import org.jeudego.pairgoth.model.TeamTournament
import org.jeudego.pairgoth.model.Tournament
import org.jeudego.pairgoth.model.adjustedTime
import org.jeudego.pairgoth.model.displayRank
@@ -27,10 +28,18 @@ object StandingsHandler: PairgothApiHandler {
override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
val tournament = getTournament(request)
val round = getSubSelector(request)?.toIntOrNull() ?: tournament.rounds
val includePreliminary = request.getParameter("include_preliminary")?.let { it.toBoolean() } ?: false
val includePreliminary = request.getParameter("include_preliminary")?.toBoolean() ?: false
val sortedPairables = tournament.getSortedPairables(round, includePreliminary)
tournament.populateStandings(sortedPairables, round)
val individualStandings = tournament is TeamTournament &&
tournament.type.individual &&
request.getParameter("individual_standings")?.toBoolean() == true
val sortedEntries = if (individualStandings) {
tournament.getSortedTeamMembers(round)
} else {
tournament.getSortedPairables(round, includePreliminary)
}
tournament.populateStandings(sortedEntries, round, individualStandings)
val acceptHeader = request.getHeader("Accept") as String?
val accept = acceptHeader?.substringBefore(";")
@@ -44,7 +53,7 @@ object StandingsHandler: PairgothApiHandler {
PrintWriter(OutputStreamWriter(response.outputStream, encoding))
}
return when (accept) {
"application/json" -> sortedPairables.toJsonArray()
"application/json" -> sortedEntries.toJsonArray()
"application/egf" -> {
response.contentType = "text/plain;charset=${encoding}"
val neededCriteria = ArrayList(tournament.pairing.placementParams.criteria)
@@ -52,19 +61,19 @@ object StandingsHandler: PairgothApiHandler {
if (neededCriteria.first() == SCOREX) {
neededCriteria.add(1, MMS)
}
exportToEGFFormat(tournament, sortedPairables, neededCriteria, writer)
exportToEGFFormat(tournament, sortedEntries, neededCriteria, writer)
writer.flush()
return null
}
"application/ffg" -> {
response.contentType = "text/plain;charset=${encoding}"
exportToFFGFormat(tournament, sortedPairables, writer)
exportToFFGFormat(tournament, sortedEntries, writer)
writer.flush()
return null
}
"text/csv" -> {
response.contentType = "text/csv;charset=${encoding}"
exportToCSVFormat(tournament, sortedPairables, writer)
exportToCSVFormat(tournament, sortedEntries, writer)
writer.flush()
return null
}

View File

@@ -263,7 +263,7 @@ class TeamTournament(
override fun individualGames(round: Int): Map<ID, Game> {
val teamGames = games(round)
return if (type.individual) {
return teamGames.values.flatMap { game ->
teamGames.values.flatMap { game ->
if (game.white == 0 || game.black == 0 ) listOf()
else individualGames[game.id]?.toList() ?: listOf()
}.associateBy { it.id }