Display individual standings below team standings
This commit is contained in:
@@ -7,6 +7,7 @@ import org.jeudego.pairgoth.model.MacMahon
|
|||||||
import org.jeudego.pairgoth.model.Pairable
|
import org.jeudego.pairgoth.model.Pairable
|
||||||
import org.jeudego.pairgoth.model.PairingType
|
import org.jeudego.pairgoth.model.PairingType
|
||||||
import org.jeudego.pairgoth.model.Player
|
import org.jeudego.pairgoth.model.Player
|
||||||
|
import org.jeudego.pairgoth.model.TeamTournament
|
||||||
import org.jeudego.pairgoth.model.Tournament
|
import org.jeudego.pairgoth.model.Tournament
|
||||||
import org.jeudego.pairgoth.model.getID
|
import org.jeudego.pairgoth.model.getID
|
||||||
import org.jeudego.pairgoth.model.historyBefore
|
import org.jeudego.pairgoth.model.historyBefore
|
||||||
@@ -50,9 +51,9 @@ fun Tournament<*>.getSortedPairables(round: Int, includePreliminary: Boolean = f
|
|||||||
val mmBase = pairable.mmBase()
|
val mmBase = pairable.mmBase()
|
||||||
val score = roundScore(mmBase +
|
val score = roundScore(mmBase +
|
||||||
(nbW(pairable) ?: 0.0) +
|
(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
|
if (playersPerRound.getOrNull(round - 1)?.contains(pairable.id) == true) 0.0 else 1.0
|
||||||
}.sum() * pairing.pairingParams.main.mmsValueAbsent)
|
} * pairing.pairingParams.main.mmsValueAbsent)
|
||||||
Pair(
|
Pair(
|
||||||
if (pairing.pairingParams.main.sosValueAbsentUseBase) mmBase
|
if (pairing.pairingParams.main.sosValueAbsentUseBase) mmBase
|
||||||
else roundScore(mmBase + round/2),
|
else roundScore(mmBase + round/2),
|
||||||
@@ -100,14 +101,14 @@ fun Tournament<*>.getSortedPairables(round: Int, includePreliminary: Boolean = f
|
|||||||
Criterion.DC -> StandingsHandler.nullMap
|
Criterion.DC -> StandingsHandler.nullMap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val pairables = pairables.values.filter { includePreliminary || it.final }.map { it.toDetailedJson() }
|
val jsonPairables = pairables.values.filter { includePreliminary || it.final }.map { it.toDetailedJson() }
|
||||||
pairables.forEach { player ->
|
jsonPairables.forEach { player ->
|
||||||
for (crit in criteria) {
|
for (crit in criteria) {
|
||||||
player[crit.first] = crit.second[player.getID()] ?: 0.0
|
player[crit.first] = crit.second[player.getID()] ?: 0.0
|
||||||
}
|
}
|
||||||
player["results"] = Json.MutableArray(List(round) { "0=" })
|
player["results"] = Json.MutableArray(List(round) { "0=" })
|
||||||
}
|
}
|
||||||
val sortedPairables = pairables.sortedWith { left, right ->
|
val sortedPairables = jsonPairables.sortedWith { left, right ->
|
||||||
for (crit in criteria) {
|
for (crit in criteria) {
|
||||||
val lval = left.getDouble(crit.first) ?: 0.0
|
val lval = left.getDouble(crit.first) ?: 0.0
|
||||||
val rval = right.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
|
return sortedPairables
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Tournament<*>.populateStandings(sortedPairables: List<Json.Object>, round: Int = rounds) {
|
fun Tournament<*>.populateStandings(sortedEntries: List<Json.Object>, round: Int = rounds, individualStandings: Boolean) {
|
||||||
val sortedMap = sortedPairables.associateBy {
|
val sortedMap = sortedEntries.associateBy {
|
||||||
it.getID()!!
|
it.getID()!!
|
||||||
}
|
}
|
||||||
|
|
||||||
// refresh name, firstname, club and level
|
// refresh name, firstname, club and level
|
||||||
|
val refMap = if (individualStandings) players else pairables
|
||||||
sortedMap.forEach { (id, pairable) ->
|
sortedMap.forEach { (id, pairable) ->
|
||||||
val mutable = pairable as Json.MutableObject
|
val mutable = pairable as Json.MutableObject
|
||||||
pairables[id]?.let {
|
refMap[id]?.let {
|
||||||
mutable["name"] = it.name
|
mutable["name"] = it.name
|
||||||
if (it is Player) {
|
if (it is Player) {
|
||||||
mutable["firstname"] = it.firstname
|
mutable["firstname"] = it.firstname
|
||||||
@@ -150,7 +152,8 @@ fun Tournament<*>.populateStandings(sortedPairables: List<Json.Object>, round: I
|
|||||||
|
|
||||||
// fill result
|
// fill result
|
||||||
for (r in 1..round) {
|
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 white = if (game.white != 0) sortedMap[game.white] else null
|
||||||
val black = if (game.black != 0) sortedMap[game.black] else null
|
val black = if (game.black != 0) sortedMap[game.black] else null
|
||||||
val whiteNum = white?.getInt("num") ?: 0
|
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
|
||||||
|
}
|
||||||
|
@@ -6,6 +6,7 @@ import org.jeudego.pairgoth.model.Criterion
|
|||||||
import org.jeudego.pairgoth.model.Criterion.*
|
import org.jeudego.pairgoth.model.Criterion.*
|
||||||
import org.jeudego.pairgoth.model.ID
|
import org.jeudego.pairgoth.model.ID
|
||||||
import org.jeudego.pairgoth.model.PairingType
|
import org.jeudego.pairgoth.model.PairingType
|
||||||
|
import org.jeudego.pairgoth.model.TeamTournament
|
||||||
import org.jeudego.pairgoth.model.Tournament
|
import org.jeudego.pairgoth.model.Tournament
|
||||||
import org.jeudego.pairgoth.model.adjustedTime
|
import org.jeudego.pairgoth.model.adjustedTime
|
||||||
import org.jeudego.pairgoth.model.displayRank
|
import org.jeudego.pairgoth.model.displayRank
|
||||||
@@ -27,10 +28,18 @@ object StandingsHandler: PairgothApiHandler {
|
|||||||
override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
||||||
val tournament = getTournament(request)
|
val tournament = getTournament(request)
|
||||||
val round = getSubSelector(request)?.toIntOrNull() ?: tournament.rounds
|
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)
|
val individualStandings = tournament is TeamTournament &&
|
||||||
tournament.populateStandings(sortedPairables, round)
|
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 acceptHeader = request.getHeader("Accept") as String?
|
||||||
val accept = acceptHeader?.substringBefore(";")
|
val accept = acceptHeader?.substringBefore(";")
|
||||||
@@ -44,7 +53,7 @@ object StandingsHandler: PairgothApiHandler {
|
|||||||
PrintWriter(OutputStreamWriter(response.outputStream, encoding))
|
PrintWriter(OutputStreamWriter(response.outputStream, encoding))
|
||||||
}
|
}
|
||||||
return when (accept) {
|
return when (accept) {
|
||||||
"application/json" -> sortedPairables.toJsonArray()
|
"application/json" -> sortedEntries.toJsonArray()
|
||||||
"application/egf" -> {
|
"application/egf" -> {
|
||||||
response.contentType = "text/plain;charset=${encoding}"
|
response.contentType = "text/plain;charset=${encoding}"
|
||||||
val neededCriteria = ArrayList(tournament.pairing.placementParams.criteria)
|
val neededCriteria = ArrayList(tournament.pairing.placementParams.criteria)
|
||||||
@@ -52,19 +61,19 @@ object StandingsHandler: PairgothApiHandler {
|
|||||||
if (neededCriteria.first() == SCOREX) {
|
if (neededCriteria.first() == SCOREX) {
|
||||||
neededCriteria.add(1, MMS)
|
neededCriteria.add(1, MMS)
|
||||||
}
|
}
|
||||||
exportToEGFFormat(tournament, sortedPairables, neededCriteria, writer)
|
exportToEGFFormat(tournament, sortedEntries, neededCriteria, writer)
|
||||||
writer.flush()
|
writer.flush()
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
"application/ffg" -> {
|
"application/ffg" -> {
|
||||||
response.contentType = "text/plain;charset=${encoding}"
|
response.contentType = "text/plain;charset=${encoding}"
|
||||||
exportToFFGFormat(tournament, sortedPairables, writer)
|
exportToFFGFormat(tournament, sortedEntries, writer)
|
||||||
writer.flush()
|
writer.flush()
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
"text/csv" -> {
|
"text/csv" -> {
|
||||||
response.contentType = "text/csv;charset=${encoding}"
|
response.contentType = "text/csv;charset=${encoding}"
|
||||||
exportToCSVFormat(tournament, sortedPairables, writer)
|
exportToCSVFormat(tournament, sortedEntries, writer)
|
||||||
writer.flush()
|
writer.flush()
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@@ -263,7 +263,7 @@ class TeamTournament(
|
|||||||
override fun individualGames(round: Int): Map<ID, Game> {
|
override fun individualGames(round: Int): Map<ID, Game> {
|
||||||
val teamGames = games(round)
|
val teamGames = games(round)
|
||||||
return if (type.individual) {
|
return if (type.individual) {
|
||||||
return teamGames.values.flatMap { game ->
|
teamGames.values.flatMap { game ->
|
||||||
if (game.white == 0 || game.black == 0 ) listOf()
|
if (game.white == 0 || game.black == 0 ) listOf()
|
||||||
else individualGames[game.id]?.toList() ?: listOf()
|
else individualGames[game.id]?.toList() ?: listOf()
|
||||||
}.associateBy { it.id }
|
}.associateBy { it.id }
|
||||||
|
@@ -56,6 +56,10 @@
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
/* header, center, footer */
|
/* header, center, footer */
|
||||||
|
|
||||||
#header {
|
#header {
|
||||||
|
@@ -184,7 +184,7 @@ unpairable, non disponibles,
|
|||||||
supports the implémente le système d’appariement
|
supports the implémente le système d’appariement
|
||||||
white blanc
|
white blanc
|
||||||
White Blanc
|
White Blanc
|
||||||
white vs. black blanc vs. Noir
|
white vs. black Blanc vs. Noir
|
||||||
confirmed. confirmé(s).
|
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
|
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
|
yyyymmdd-city aaaammjj-ville
|
||||||
|
@@ -30,6 +30,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
#if($tour.type.startsWith('TEAM'))
|
||||||
|
<div class="strong">Team Standings</div>
|
||||||
|
#end
|
||||||
<div id="standings-container" class="roundbox">
|
<div id="standings-container" class="roundbox">
|
||||||
#set($standings = $api.get("tour/${params.id}/standings/$round"))
|
#set($standings = $api.get("tour/${params.id}/standings/$round"))
|
||||||
#if($standings.isObject() && ($standings.error || $standings.message))
|
#if($standings.isObject() && ($standings.error || $standings.message))
|
||||||
@@ -105,6 +108,78 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
#if($tour.type.startsWith('TEAM'))
|
||||||
|
<div class="strong">Individual Standings</div>
|
||||||
|
<div id="individual-standings-container" class="roundbox">
|
||||||
|
#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
|
||||||
|
<script type="text/javascript">
|
||||||
|
onLoad(() => {
|
||||||
|
showError("$error")
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
#set($indvstandings = [])
|
||||||
|
#end
|
||||||
|
#set($indvsmap = {})
|
||||||
|
#foreach($part in $indvstandings)
|
||||||
|
#set($indvsmap[$part.num] = $part)
|
||||||
|
#end
|
||||||
|
<table id="individual-standings-table" class="ui striped table">
|
||||||
|
<thead>
|
||||||
|
<th>Num</th>
|
||||||
|
<th>Plc</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Rank</th>
|
||||||
|
<th>Ctr</th>
|
||||||
|
<th>Nbw</th>
|
||||||
|
#foreach($r in [1..$round])
|
||||||
|
<th>R$r</th>
|
||||||
|
#end
|
||||||
|
#set($indvcriteres = ['NBW'])
|
||||||
|
#foreach($crit in $indvcriteres)
|
||||||
|
<th>$crit</th>
|
||||||
|
#end
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
#foreach($part in $indvstandings)
|
||||||
|
<tr data-id="$part.id">
|
||||||
|
<td>$part.num</td>
|
||||||
|
<td>$part.place</td>
|
||||||
|
<td>$esc.html($part.name)#if($part.firstname) $esc.html($part.firstname)#end</td>
|
||||||
|
<td data-sort="$part.rank">#rank($part.rank)</td>
|
||||||
|
<td>#if($part.country)$part.country#end</td>
|
||||||
|
<td>$number.format('0.#', $part.NBW)</td>
|
||||||
|
#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 = "<b>$rst</b>")
|
||||||
|
#elseif($rst.contains('-'))
|
||||||
|
#set($rst = "<i>$rst</i>")
|
||||||
|
#end
|
||||||
|
<td class="nobreak game-result" #if($opponent)title="$esc.html($opponent.name)#if($opponent.firstname) $esc.html($opponent.firstname)#end #rank($opponent.rank)#if($opponent.country) $opponent.country#end"#end>$rst</td>
|
||||||
|
#end
|
||||||
|
#foreach($crit in $indvcriteres)
|
||||||
|
#set($value = "$number.format('0.#', $part[$crit])")
|
||||||
|
<td data-sort="$value">$value.replace('.5', '½')</td>
|
||||||
|
#end
|
||||||
|
</tr>
|
||||||
|
#end
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
#end
|
||||||
<div class="right form-actions">
|
<div class="right form-actions">
|
||||||
#if(!$tour.frozen && $round == $tour.rounds)
|
#if(!$tour.frozen && $round == $tour.rounds)
|
||||||
<button id="freeze" class="ui orange floating right labeled icon button">
|
<button id="freeze" class="ui orange floating right labeled icon button">
|
||||||
|
Reference in New Issue
Block a user