diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/ApiTools.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/ApiTools.kt index a0f0dbe..8d74eff 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/ApiTools.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/ApiTools.kt @@ -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, round: Int = rounds) { - val sortedMap = sortedPairables.associateBy { +fun Tournament<*>.populateStandings(sortedEntries: List, 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, 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, round: I } } } + +fun TeamTournament.getSortedTeamMembers(round: Int, includePreliminary: Boolean = false): List { + + 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 +} diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/StandingsHandler.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/StandingsHandler.kt index e978fd1..fa3e400 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/StandingsHandler.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/StandingsHandler.kt @@ -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 } diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Tournament.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Tournament.kt index 4037102..2d8efaf 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Tournament.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Tournament.kt @@ -263,7 +263,7 @@ class TeamTournament( override fun individualGames(round: Int): Map { 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 } diff --git a/view-webapp/src/main/sass/main.scss b/view-webapp/src/main/sass/main.scss index dee6fae..fa9baea 100644 --- a/view-webapp/src/main/sass/main.scss +++ b/view-webapp/src/main/sass/main.scss @@ -56,6 +56,10 @@ font-style: italic; } + .strong { + font-weight: bold; + } + /* header, center, footer */ #header { diff --git a/view-webapp/src/main/webapp/WEB-INF/translations/fr b/view-webapp/src/main/webapp/WEB-INF/translations/fr index 4a2aa5a..6285150 100644 --- a/view-webapp/src/main/webapp/WEB-INF/translations/fr +++ b/view-webapp/src/main/webapp/WEB-INF/translations/fr @@ -184,7 +184,7 @@ unpairable, non disponibles, supports the implémente le système d’appariement white blanc White Blanc -white vs. black blanc vs. Noir +white vs. black Blanc vs. Noir confirmed. confirmé(s). Note that login to this instance is reserved to French federation actors plus several external people at our discretion. Send us La connexion à cette instance est réservée aux acteurs de la FFG et à quelques personnes extérieures, à notre discrétion. Envoyez-nous yyyymmdd-city aaaammjj-ville diff --git a/view-webapp/src/main/webapp/tour-standings.inc.html b/view-webapp/src/main/webapp/tour-standings.inc.html index 8aebd31..b52fc18 100644 --- a/view-webapp/src/main/webapp/tour-standings.inc.html +++ b/view-webapp/src/main/webapp/tour-standings.inc.html @@ -30,6 +30,9 @@ +#if($tour.type.startsWith('TEAM')) +
Team Standings
+#end
#set($standings = $api.get("tour/${params.id}/standings/$round")) #if($standings.isObject() && ($standings.error || $standings.message)) @@ -105,6 +108,78 @@
+#if($tour.type.startsWith('TEAM')) +
Individual Standings
+
+#set($indvstandings = $api.get("tour/${params.id}/standings/$round?individual_standings=true")) +#if($indvstandings.isObject() && ($indvstandings.error || $indvstandings.message)) + #if($indvstandings.error) + #set($error = $indvstandings.error) + #else + #set($error = $indvstandings.message) + #end + + #set($indvstandings = []) +#end +#set($indvsmap = {}) +#foreach($part in $indvstandings) + #set($indvsmap[$part.num] = $part) +#end + + + + + + + + +#foreach($r in [1..$round]) + +#end +#set($indvcriteres = ['NBW']) +#foreach($crit in $indvcriteres) + +#end + + +#foreach($part in $indvstandings) + + + + + + + + #set($mx = $round - 1) + #foreach($r in [0..$mx]) + #set($rst = $part.results[$r]) + #set($opp_num = $math.toLong($rst)) + #if($opp_num) + #set($opponent = $!indvsmap[$opp_num]) + #else + #set($opponent = false) + #end + #if($rst.contains('+')) + #set($rst = "$rst") + #elseif($rst.contains('-')) + #set($rst = "$rst") + #end + + #end + #foreach($crit in $indvcriteres) + #set($value = "$number.format('0.#', $part[$crit])") + + #end + +#end + +
NumPlcNameRankCtrNbwR$r$crit
$part.num$part.place$esc.html($part.name)#if($part.firstname) $esc.html($part.firstname)#end#rank($part.rank)#if($part.country)$part.country#end$number.format('0.#', $part.NBW)$rst$value.replace('.5', '½')
+
+#end
#if(!$tour.frozen && $round == $tour.rounds)