Implement standings freezing

This commit is contained in:
Claude Brisson
2024-08-20 11:48:29 +02:00
parent 15257028c0
commit bae30c7465
11 changed files with 98 additions and 54 deletions

View File

@@ -25,7 +25,7 @@ interface ApiHandler {
notImplemented()
}
fun put(request: HttpServletRequest, response: HttpServletResponse): Json {
fun put(request: HttpServletRequest, response: HttpServletResponse): Json? {
notImplemented()
}

View File

@@ -3,6 +3,7 @@ package org.jeudego.pairgoth.api
import com.republicate.kson.Json
import org.jeudego.pairgoth.model.Criterion
import org.jeudego.pairgoth.model.DatabaseId
import org.jeudego.pairgoth.model.Game
import org.jeudego.pairgoth.model.MacMahon
import org.jeudego.pairgoth.model.Pairable
import org.jeudego.pairgoth.model.Pairable.Companion.MIN_RANK
@@ -34,6 +35,10 @@ fun Tournament<*>.getSortedPairables(round: Int, includePreliminary: Boolean = f
else ceil(score - epsilon)
}
if (frozen != null) {
return ArrayList(frozen!!.map { it -> it as Json.Object })
}
// CB TODO - factorize history helper creation between here and solver classes
val historyHelper = HistoryHelper(historyBefore(round + 1)) {
if (pairing.type == PairingType.SWISS) wins.mapValues { Pair(0.0, it.value) }
@@ -116,5 +121,50 @@ fun Tournament<*>.getSortedPairables(round: Int, includePreliminary: Boolean = f
it.value.forEach { p -> p["place"] = place }
place += it.value.size
}
return sortedPairables
}
fun Tournament<*>.populateResultsArray(sortedPairables: List<Json.Object>, round: Int = rounds) {
// fill result
val sortedMap = sortedPairables.associateBy {
it.getID()!!
}
for (r in 1..round) {
games(r).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
val blackNum = black?.getInt("num") ?: 0
val whiteColor = if (black == null) "" else "w"
val blackColor = if (white == null) "" else "b"
val handicap = if (game.handicap == 0) "" else "${game.handicap}"
assert(white != null || black != null)
if (white != null) {
val mark = when (game.result) {
Game.Result.UNKNOWN -> "?"
Game.Result.BLACK, Game.Result.BOTHLOOSE -> "-"
Game.Result.WHITE, Game.Result.BOTHWIN -> "+"
Game.Result.JIGO, Game.Result.CANCELLED -> "="
}
val results = white.getArray("results") as Json.MutableArray
results[r - 1] =
if (blackNum == 0) "0$mark"
else "$blackNum$mark/$whiteColor$handicap"
}
if (black != null) {
val mark = when (game.result) {
Game.Result.UNKNOWN -> "?"
Game.Result.BLACK, Game.Result.BOTHWIN -> "+"
Game.Result.WHITE, Game.Result.BOTHLOOSE -> "-"
Game.Result.JIGO, Game.Result.CANCELLED -> "="
}
val results = black.getArray("results") as Json.MutableArray
results[r - 1] =
if (whiteNum == 0) "0$mark"
else "$whiteNum$mark/$blackColor$handicap"
}
}
}
}

View File

@@ -3,7 +3,6 @@ package org.jeudego.pairgoth.api
import com.republicate.kson.Json
import com.republicate.kson.toJsonArray
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
import org.jeudego.pairgoth.api.TournamentHandler.dispatchEvent
import org.jeudego.pairgoth.model.Game
import org.jeudego.pairgoth.model.Tournament
import org.jeudego.pairgoth.model.getID
@@ -66,7 +65,7 @@ object PairingHandler: PairgothApiHandler {
return ret
}
override fun put(request: HttpServletRequest, response: HttpServletResponse): Json {
override fun put(request: HttpServletRequest, response: HttpServletResponse): Json? {
val tournament = getTournament(request)
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
// only allow last round (if players have not been paired in the last round, it *may* be possible to be more laxist...)

View File

@@ -31,7 +31,7 @@ object PlayerHandler: PairgothApiHandler {
return Json.Object("success" to true, "id" to player.id)
}
override fun put(request: HttpServletRequest, response: HttpServletResponse): Json {
override fun put(request: HttpServletRequest, response: HttpServletResponse): Json? {
val tournament = getTournament(request)
val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid player selector")
val player = tournament.players[id] ?: badRequest("invalid player id")

View File

@@ -18,7 +18,7 @@ object ResultsHandler: PairgothApiHandler {
return games.map { it.toJson() }.toJsonArray()
}
override fun put(request: HttpServletRequest, response: HttpServletResponse): Json {
override fun put(request: HttpServletRequest, response: HttpServletResponse): Json? {
val tournament = getTournament(request)
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
val payload = getObjectPayload(request)

View File

@@ -2,27 +2,23 @@ package org.jeudego.pairgoth.api
import com.republicate.kson.Json
import com.republicate.kson.toJsonArray
import org.jeudego.pairgoth.api.PairingHandler.dispatchEvent
import org.jeudego.pairgoth.model.Criterion
import org.jeudego.pairgoth.model.Criterion.*
import org.jeudego.pairgoth.model.Game.Result.*
import org.jeudego.pairgoth.model.ID
import org.jeudego.pairgoth.model.MacMahon
import org.jeudego.pairgoth.model.Pairable
import org.jeudego.pairgoth.model.PairingType
import org.jeudego.pairgoth.model.Tournament
import org.jeudego.pairgoth.model.adjustedTime
import org.jeudego.pairgoth.model.displayRank
import org.jeudego.pairgoth.model.getID
import org.jeudego.pairgoth.model.historyBefore
import org.jeudego.pairgoth.pairing.HistoryHelper
import org.jeudego.pairgoth.pairing.solver.MacMahonSolver
import java.io.PrintWriter
import java.time.format.DateTimeFormatter
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import kotlin.math.max
import kotlin.math.min
import org.jeudego.pairgoth.model.TimeSystem.TimeSystemType.*
import org.jeudego.pairgoth.model.toJson
import org.jeudego.pairgoth.server.Event
import org.jeudego.pairgoth.server.WebappManager
import java.io.OutputStreamWriter
import java.nio.charset.StandardCharsets
@@ -35,46 +31,8 @@ object StandingsHandler: PairgothApiHandler {
val includePreliminary = request.getParameter("include_preliminary")?.let { it.toBoolean() } ?: false
val sortedPairables = tournament.getSortedPairables(round, includePreliminary)
val sortedMap = sortedPairables.associateBy {
it.getID()!!
}
tournament.populateResultsArray(sortedPairables, round)
for (r in 1..round) {
tournament.games(r).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
val blackNum = black?.getInt("num") ?: 0
val whiteColor = if (black == null) "" else "w"
val blackColor = if (white == null) "" else "b"
val handicap = if (game.handicap == 0) "" else "${game.handicap}"
assert(white != null || black != null)
if (white != null) {
val mark = when (game.result) {
UNKNOWN -> "?"
BLACK, BOTHLOOSE -> "-"
WHITE, BOTHWIN -> "+"
JIGO, CANCELLED -> "="
}
val results = white.getArray("results") as Json.MutableArray
results[r - 1] =
if (blackNum == 0) "0$mark"
else "$blackNum$mark/$whiteColor$handicap"
}
if (black != null) {
val mark = when (game.result) {
UNKNOWN -> "?"
BLACK, BOTHWIN -> "+"
WHITE, BOTHLOOSE -> "-"
JIGO, CANCELLED -> "="
}
val results = black.getArray("results") as Json.MutableArray
results[r - 1] =
if (whiteNum == 0) "0$mark"
else "$whiteNum$mark/$blackColor$handicap"
}
}
}
val acceptHeader = request.getHeader("Accept") as String?
val accept = acceptHeader?.substringBefore(";")
val acceptEncoding = acceptHeader?.substringAfter(";charset=", "utf-8") ?: "utf-8"
@@ -268,6 +226,14 @@ ${
}
}
override fun put(request: HttpServletRequest, response: HttpServletResponse): Json? {
val tournament = getTournament(request)
val sortedPairables = tournament.getSortedPairables(tournament.rounds)
tournament.frozen = sortedPairables.toJsonArray()
tournament.dispatchEvent(Event.TournamentUpdated, request, tournament.toJson())
return Json.Object("status" to "ok")
}
private val numFormat = DecimalFormat("###0.#")
private val frDate: DateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy")
}

View File

@@ -29,7 +29,7 @@ object TeamHandler: PairgothApiHandler {
return Json.Object("success" to true, "id" to team.id)
}
override fun put(request: HttpServletRequest, response: HttpServletResponse): Json {
override fun put(request: HttpServletRequest, response: HttpServletResponse): Json? {
val tournament = getTournament(request)
if (tournament !is TeamTournament) badRequest("tournament is not a team tournament")
val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid player selector")

View File

@@ -34,6 +34,7 @@ object TournamentHandler: PairgothApiHandler {
// additional attributes for the webapp
json["stats"] = tour.stats()
json["teamSize"] = tour.type.playersNumber
json["frozen"] = tour.frozen != null
}
}
} ?: badRequest("no tournament with id #${id}")
@@ -61,7 +62,7 @@ object TournamentHandler: PairgothApiHandler {
return Json.Object("success" to true, "id" to tournament.id)
}
override fun put(request: HttpServletRequest, response: HttpServletResponse): Json {
override fun put(request: HttpServletRequest, response: HttpServletResponse): Json? {
// CB TODO - some checks are needed here (cannot lower rounds number if games have been played in removed rounds, for instance)
val tournament = getTournament(request)
val payload = getObjectPayload(request)

View File

@@ -52,6 +52,9 @@ sealed class Tournament <P: Pairable>(
protected val _pairables = mutableMapOf<ID, P>()
val pairables: Map<ID, Pairable> get() = _pairables
// frozen standings
var frozen: Json.Array? = null
// pairing
fun pair(round: Int, pairables: List<Pairable>): List<Game> {
// Minimal check on round number.
@@ -335,7 +338,7 @@ fun Tournament.Companion.fromJson(json: Json.Object, default: Tournament<*>? = n
tournament.teams[team.getID("id")!!] = tournament.teamFromJson(team)
}
}
(json["games"] as Json.Array?)?.forEachIndexed { i, arr ->
json.getArray("games")?.forEachIndexed { i, arr ->
val round = i + 1
val tournamentGames = tournament.games(round)
val games = arr as Json.Array
@@ -344,6 +347,9 @@ fun Tournament.Companion.fromJson(json: Json.Object, default: Tournament<*>? = n
tournamentGames[game.getID("id")!!] = Game.fromJson(game)
}
}
json.getArray("frozen")?.also {
tournament.frozen = it
}
return tournament
}
@@ -374,5 +380,8 @@ fun Tournament<*>.toFullJson(): Json.Object {
json["teams"] = Json.Array(teams.values.map { it.toJson() })
}
json["games"] = Json.Array((1..lastRound()).mapTo(Json.MutableArray()) { round -> games(round).values.mapTo(Json.MutableArray()) { it.toJson() } });
if (frozen != null) {
json["frozen"] = frozen
}
return json
}