Beta version of explain page
This commit is contained in:
@@ -18,11 +18,6 @@ import kotlin.math.min
|
||||
|
||||
fun Tournament<*>.getSortedPairables(round: Int, includePreliminary: Boolean = false): List<Json.Object> {
|
||||
|
||||
fun Pairable.mmBase(): Double {
|
||||
if (pairing !is MacMahon) throw Error("invalid call: tournament is not Mac Mahon")
|
||||
return min(max(rank, pairing.mmFloor), pairing.mmBar) + MacMahonSolver.mmsZero + mmsCorrection
|
||||
}
|
||||
|
||||
if (frozen != null) {
|
||||
return ArrayList(frozen!!.map { it -> it as Json.Object })
|
||||
}
|
||||
|
@@ -0,0 +1,68 @@
|
||||
package org.jeudego.pairgoth.api
|
||||
|
||||
import com.republicate.kson.Json
|
||||
import com.republicate.kson.toJsonArray
|
||||
import com.republicate.kson.toJsonObject
|
||||
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
||||
import org.jeudego.pairgoth.model.toJson
|
||||
import org.jeudego.pairgoth.pairing.solver.CollectingListener
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
|
||||
object ExplainHandler: PairgothApiHandler {
|
||||
|
||||
override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
||||
val tournament = getTournament(request)
|
||||
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
|
||||
if (round > tournament.lastRound() + 1) badRequest("invalid round: previous round has not been played")
|
||||
val paired = tournament.games(round).values.flatMap {
|
||||
listOf(it.black, it.white)
|
||||
}.filter {
|
||||
it != 0
|
||||
}.map {
|
||||
tournament.pairables[it] ?: throw Error("Unknown pairable ID: $it")
|
||||
}
|
||||
val games = tournament.games(round).map { it.value }.toList()
|
||||
// build the scores map by redoing the whole pairing
|
||||
tournament.unpair(round)
|
||||
val history = tournament.historyHelper(round)
|
||||
val weightsCollector = CollectingListener()
|
||||
tournament.pair(round, paired, false, weightsCollector)
|
||||
val weights = weightsCollector.out
|
||||
// Since weights are generally in two groups towards the min and the max,
|
||||
// compute the max of the low group ("low") and the min of the high group ("high")
|
||||
// to improve coloring.
|
||||
// Total weights axis:
|
||||
// ----[min]xxxx[low]----[middle]----[high]xxxx[max]---->
|
||||
val min = weights.values.minOfOrNull { it.values.sum() } ?: 0.0
|
||||
val max = weights.values.maxOfOrNull { it.values.sum() } ?: 0.0
|
||||
val middle = (max - min) / 2.0
|
||||
val low = weights.values.map { it.values.sum() }.filter { it < middle }.maxOrNull() ?: middle
|
||||
val high = weights.values.map { it.values.sum() }.filter { it > middle }.minOrNull() ?: middle
|
||||
val ret = Json.Object(
|
||||
"paired" to paired.sortedByDescending { 1000 * (history.scores[it.id] ?: 0.0) + (history.sos[it.id] ?: 0.0) }.map {
|
||||
it.toMutableJson().apply {
|
||||
put("score", history.scores[it.id])
|
||||
put("wins", history.wins[it.id])
|
||||
put("sos", history.sos[it.id])
|
||||
put("dudd", history.drawnUpDown[it.id])
|
||||
}
|
||||
}.toJsonArray(),
|
||||
// "games" to games.map { it.toJson() }.toJsonArray(),
|
||||
"games" to games.associateBy { "${it.white}-${it.black}" }.mapValues { it.value.toJson() }.toJsonObject(),
|
||||
"weights" to weights.entries.map { (key, value) ->
|
||||
Pair(
|
||||
"${key.first}-${key.second}",
|
||||
value.also {
|
||||
it.put("total", it.values.sum())
|
||||
}
|
||||
)
|
||||
}.toJsonObject(),
|
||||
"min" to min,
|
||||
"low" to low,
|
||||
"high" to high,
|
||||
"max" to max
|
||||
)
|
||||
return ret
|
||||
}
|
||||
}
|
@@ -133,8 +133,8 @@ sealed class Pairing(
|
||||
val pairingParams: PairingParams,
|
||||
val placementParams: PlacementParams) {
|
||||
companion object {}
|
||||
abstract fun solver(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): Solver
|
||||
fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>, legacyMode: Boolean = false, listener: PairingListener? = null): List<Game> {
|
||||
internal abstract fun solver(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): Solver
|
||||
internal fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>, legacyMode: Boolean = false, listener: PairingListener? = null): List<Game> {
|
||||
return solver(tournament, round, pairables)
|
||||
.also { solver ->
|
||||
solver.legacyMode = legacyMode
|
||||
|
@@ -100,14 +100,17 @@ sealed class Tournament <P: Pairable>(
|
||||
|
||||
fun lastRound() = max(1, games.size)
|
||||
|
||||
/**
|
||||
* Recompute DUDD for a specific game
|
||||
*/
|
||||
fun recomputeDUDD(round: Int, gameID: ID) {
|
||||
// Instantiate solver with game history
|
||||
val solver = pairing.solver(this, round, pairables.values.toList())
|
||||
val solver = pairing.solver(this, round, emptyList())
|
||||
|
||||
// Recomputes DUDD and hd
|
||||
val game = games(round)[gameID]!!
|
||||
val white = solver.pairables.find { p-> p.id == game.white }!!
|
||||
val black = solver.pairables.find { p-> p.id == game.black }!!
|
||||
val white = pairables[game.white]!!
|
||||
val black = pairables[game.black]!!
|
||||
game.drawnUpDown = solver.dudd(black, white)
|
||||
game.handicap = solver.hd(white = white, black = black)
|
||||
}
|
||||
@@ -119,17 +122,16 @@ sealed class Tournament <P: Pairable>(
|
||||
fun recomputeDUDD(round: Int) {
|
||||
if (pairables.isEmpty() || games(1).isEmpty()) return;
|
||||
// Instantiate solver with game history
|
||||
val solver = pairing.solver(this, round, pairables.values.toList())
|
||||
val solver = pairing.solver(this, round, emptyList())
|
||||
for (game in games(round).values) {
|
||||
if (game.black != 0 && game.white != 0) {
|
||||
val white = solver.pairables.find { p-> p.id == game.white }!!
|
||||
val black = solver.pairables.find { p-> p.id == game.black }!!
|
||||
val white = pairables[game.white]!!
|
||||
val black = pairables[game.black]!!
|
||||
game.drawnUpDown = solver.dudd(black, white)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Recompute DUDD for all rounds
|
||||
*/
|
||||
|
@@ -78,17 +78,17 @@ abstract class BasePairingHelper(
|
||||
protected fun Pairable.played(other: Pairable) = history.playedTogether(this, other)
|
||||
|
||||
// color balance (nw - nb)
|
||||
protected val Pairable.colorBalance: Int get() = history.colorBalance(this) ?: 0
|
||||
protected val Pairable.colorBalance: Int get() = history.colorBalance[id] ?: 0
|
||||
|
||||
protected val Pairable.group: Int get() = _groups[id]!!
|
||||
|
||||
protected val Pairable.drawnUpDown: Pair<Int, Int> get() = history.drawnUpDown(this) ?: Pair(0, 0)
|
||||
protected val Pairable.drawnUpDown: Pair<Int, Int> get() = history.drawnUpDown[id] ?: Pair(0, 0)
|
||||
|
||||
protected val Pairable.nbBye: Int get() = history.nbPlayedWithBye(this) ?: 0
|
||||
|
||||
val Pairable.score: Double get() = history.scores[id] ?: 0.0
|
||||
val Pairable.scoreX: Double get() = history.scoresX[id] ?: 0.0
|
||||
val Pairable.nbW: Double get() = history.nbW(this) ?: 0.0
|
||||
val Pairable.nbW: Double get() = history.wins[id] ?: 0.0
|
||||
val Pairable.sos: Double get() = history.sos[id] ?: 0.0
|
||||
val Pairable.sosm1: Double get() = history.sosm1[id] ?: 0.0
|
||||
val Pairable.sosm2: Double get() = history.sosm2[id] ?: 0.0
|
||||
|
@@ -33,9 +33,6 @@ open class HistoryHelper(
|
||||
open fun playedTogether(p1: Pairable, p2: Pairable) = paired.contains(Pair(p1.id, p2.id))
|
||||
open fun colorBalance(p: Pairable) = colorBalance[p.id]
|
||||
open fun nbPlayedWithBye(p: Pairable) = nbPlayedWithBye[p.id]
|
||||
open fun nbW(p: Pairable) = wins[p.id]
|
||||
|
||||
fun drawnUpDown(p: Pairable) = drawnUpDown[p.id]
|
||||
|
||||
protected val paired: Set<Pair<ID, ID>> by lazy {
|
||||
(history.flatten().map { game ->
|
||||
@@ -47,7 +44,7 @@ open class HistoryHelper(
|
||||
|
||||
// Returns the number of games played as white minus the number of games played as black
|
||||
// Only count games without handicap
|
||||
private val colorBalance: Map<ID, Int> by lazy {
|
||||
val colorBalance: Map<ID, Int> by lazy {
|
||||
history.flatten().filter { game ->
|
||||
game.handicap == 0
|
||||
}.filter { game ->
|
||||
|
@@ -5,6 +5,7 @@ import com.github.benmanes.caffeine.cache.Caffeine
|
||||
import com.republicate.kson.Json
|
||||
import org.apache.commons.io.input.BOMInputStream
|
||||
import org.jeudego.pairgoth.api.ApiHandler
|
||||
import org.jeudego.pairgoth.api.ExplainHandler
|
||||
import org.jeudego.pairgoth.api.PairingHandler
|
||||
import org.jeudego.pairgoth.api.PlayerHandler
|
||||
import org.jeudego.pairgoth.api.ResultsHandler
|
||||
@@ -99,6 +100,7 @@ class ApiServlet: HttpServlet() {
|
||||
if ("token" == selector) TokenHandler
|
||||
else when (subEntity) {
|
||||
null -> TournamentHandler
|
||||
"explain" -> ExplainHandler
|
||||
"part" -> PlayerHandler
|
||||
"pair" -> PairingHandler
|
||||
"res" -> ResultsHandler
|
||||
|
Reference in New Issue
Block a user