Implement standings freezing
This commit is contained in:
@@ -25,7 +25,7 @@ interface ApiHandler {
|
|||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun put(request: HttpServletRequest, response: HttpServletResponse): Json {
|
fun put(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,6 +3,7 @@ package org.jeudego.pairgoth.api
|
|||||||
import com.republicate.kson.Json
|
import com.republicate.kson.Json
|
||||||
import org.jeudego.pairgoth.model.Criterion
|
import org.jeudego.pairgoth.model.Criterion
|
||||||
import org.jeudego.pairgoth.model.DatabaseId
|
import org.jeudego.pairgoth.model.DatabaseId
|
||||||
|
import org.jeudego.pairgoth.model.Game
|
||||||
import org.jeudego.pairgoth.model.MacMahon
|
import org.jeudego.pairgoth.model.MacMahon
|
||||||
import org.jeudego.pairgoth.model.Pairable
|
import org.jeudego.pairgoth.model.Pairable
|
||||||
import org.jeudego.pairgoth.model.Pairable.Companion.MIN_RANK
|
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)
|
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
|
// CB TODO - factorize history helper creation between here and solver classes
|
||||||
val historyHelper = HistoryHelper(historyBefore(round + 1)) {
|
val historyHelper = HistoryHelper(historyBefore(round + 1)) {
|
||||||
if (pairing.type == PairingType.SWISS) wins.mapValues { Pair(0.0, it.value) }
|
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 }
|
it.value.forEach { p -> p["place"] = place }
|
||||||
place += it.value.size
|
place += it.value.size
|
||||||
}
|
}
|
||||||
|
|
||||||
return sortedPairables
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -3,7 +3,6 @@ package org.jeudego.pairgoth.api
|
|||||||
import com.republicate.kson.Json
|
import com.republicate.kson.Json
|
||||||
import com.republicate.kson.toJsonArray
|
import com.republicate.kson.toJsonArray
|
||||||
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
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.Game
|
||||||
import org.jeudego.pairgoth.model.Tournament
|
import org.jeudego.pairgoth.model.Tournament
|
||||||
import org.jeudego.pairgoth.model.getID
|
import org.jeudego.pairgoth.model.getID
|
||||||
@@ -66,7 +65,7 @@ object PairingHandler: PairgothApiHandler {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun put(request: HttpServletRequest, response: HttpServletResponse): Json {
|
override fun put(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
||||||
val tournament = getTournament(request)
|
val tournament = getTournament(request)
|
||||||
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
|
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...)
|
// only allow last round (if players have not been paired in the last round, it *may* be possible to be more laxist...)
|
||||||
|
@@ -31,7 +31,7 @@ object PlayerHandler: PairgothApiHandler {
|
|||||||
return Json.Object("success" to true, "id" to player.id)
|
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 tournament = getTournament(request)
|
||||||
val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid player selector")
|
val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid player selector")
|
||||||
val player = tournament.players[id] ?: badRequest("invalid player id")
|
val player = tournament.players[id] ?: badRequest("invalid player id")
|
||||||
|
@@ -18,7 +18,7 @@ object ResultsHandler: PairgothApiHandler {
|
|||||||
return games.map { it.toJson() }.toJsonArray()
|
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 tournament = getTournament(request)
|
||||||
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
|
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
|
||||||
val payload = getObjectPayload(request)
|
val payload = getObjectPayload(request)
|
||||||
|
@@ -2,27 +2,23 @@ package org.jeudego.pairgoth.api
|
|||||||
|
|
||||||
import com.republicate.kson.Json
|
import com.republicate.kson.Json
|
||||||
import com.republicate.kson.toJsonArray
|
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.Criterion.*
|
import org.jeudego.pairgoth.model.Criterion.*
|
||||||
import org.jeudego.pairgoth.model.Game.Result.*
|
import org.jeudego.pairgoth.model.Game.Result.*
|
||||||
import org.jeudego.pairgoth.model.ID
|
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.PairingType
|
||||||
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
|
||||||
import org.jeudego.pairgoth.model.getID
|
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.io.PrintWriter
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
import javax.servlet.http.HttpServletResponse
|
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.TimeSystem.TimeSystemType.*
|
||||||
|
import org.jeudego.pairgoth.model.toJson
|
||||||
|
import org.jeudego.pairgoth.server.Event
|
||||||
import org.jeudego.pairgoth.server.WebappManager
|
import org.jeudego.pairgoth.server.WebappManager
|
||||||
import java.io.OutputStreamWriter
|
import java.io.OutputStreamWriter
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
@@ -35,46 +31,8 @@ object StandingsHandler: PairgothApiHandler {
|
|||||||
val includePreliminary = request.getParameter("include_preliminary")?.let { it.toBoolean() } ?: false
|
val includePreliminary = request.getParameter("include_preliminary")?.let { it.toBoolean() } ?: false
|
||||||
|
|
||||||
val sortedPairables = tournament.getSortedPairables(round, includePreliminary)
|
val sortedPairables = tournament.getSortedPairables(round, includePreliminary)
|
||||||
val sortedMap = sortedPairables.associateBy {
|
tournament.populateResultsArray(sortedPairables, round)
|
||||||
it.getID()!!
|
|
||||||
}
|
|
||||||
|
|
||||||
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 acceptHeader = request.getHeader("Accept") as String?
|
||||||
val accept = acceptHeader?.substringBefore(";")
|
val accept = acceptHeader?.substringBefore(";")
|
||||||
val acceptEncoding = acceptHeader?.substringAfter(";charset=", "utf-8") ?: "utf-8"
|
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 numFormat = DecimalFormat("###0.#")
|
||||||
private val frDate: DateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy")
|
private val frDate: DateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy")
|
||||||
}
|
}
|
||||||
|
@@ -29,7 +29,7 @@ object TeamHandler: PairgothApiHandler {
|
|||||||
return Json.Object("success" to true, "id" to team.id)
|
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)
|
val tournament = getTournament(request)
|
||||||
if (tournament !is TeamTournament) badRequest("tournament is not a team tournament")
|
if (tournament !is TeamTournament) badRequest("tournament is not a team tournament")
|
||||||
val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid player selector")
|
val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid player selector")
|
||||||
|
@@ -34,6 +34,7 @@ object TournamentHandler: PairgothApiHandler {
|
|||||||
// additional attributes for the webapp
|
// additional attributes for the webapp
|
||||||
json["stats"] = tour.stats()
|
json["stats"] = tour.stats()
|
||||||
json["teamSize"] = tour.type.playersNumber
|
json["teamSize"] = tour.type.playersNumber
|
||||||
|
json["frozen"] = tour.frozen != null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} ?: badRequest("no tournament with id #${id}")
|
} ?: badRequest("no tournament with id #${id}")
|
||||||
@@ -61,7 +62,7 @@ object TournamentHandler: PairgothApiHandler {
|
|||||||
return Json.Object("success" to true, "id" to tournament.id)
|
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)
|
// 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 tournament = getTournament(request)
|
||||||
val payload = getObjectPayload(request)
|
val payload = getObjectPayload(request)
|
||||||
|
@@ -52,6 +52,9 @@ sealed class Tournament <P: Pairable>(
|
|||||||
protected val _pairables = mutableMapOf<ID, P>()
|
protected val _pairables = mutableMapOf<ID, P>()
|
||||||
val pairables: Map<ID, Pairable> get() = _pairables
|
val pairables: Map<ID, Pairable> get() = _pairables
|
||||||
|
|
||||||
|
// frozen standings
|
||||||
|
var frozen: Json.Array? = null
|
||||||
|
|
||||||
// pairing
|
// pairing
|
||||||
fun pair(round: Int, pairables: List<Pairable>): List<Game> {
|
fun pair(round: Int, pairables: List<Pairable>): List<Game> {
|
||||||
// Minimal check on round number.
|
// 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)
|
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 round = i + 1
|
||||||
val tournamentGames = tournament.games(round)
|
val tournamentGames = tournament.games(round)
|
||||||
val games = arr as Json.Array
|
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)
|
tournamentGames[game.getID("id")!!] = Game.fromJson(game)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
json.getArray("frozen")?.also {
|
||||||
|
tournament.frozen = it
|
||||||
|
}
|
||||||
return tournament
|
return tournament
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,5 +380,8 @@ fun Tournament<*>.toFullJson(): Json.Object {
|
|||||||
json["teams"] = Json.Array(teams.values.map { it.toJson() })
|
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() } });
|
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
|
return json
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,16 @@ function publishHtml() {
|
|||||||
close_modal();
|
close_modal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function freeze() {
|
||||||
|
api.put(`tour/${tour_id}/standings/${activeRound}`, {}
|
||||||
|
).then(resp => {
|
||||||
|
if (resp.ok) {
|
||||||
|
document.location.reload();
|
||||||
|
}
|
||||||
|
else throw "freeze error"
|
||||||
|
}).catch(err => showError(err));
|
||||||
|
}
|
||||||
|
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
new Tablesort($('#standings-table')[0]);
|
new Tablesort($('#standings-table')[0]);
|
||||||
$('.criterium').on('click', e => {
|
$('.criterium').on('click', e => {
|
||||||
@@ -86,4 +96,7 @@ onLoad(() => {
|
|||||||
$('.publish-html').on('click', e => {
|
$('.publish-html').on('click', e => {
|
||||||
publishHtml();
|
publishHtml();
|
||||||
});
|
});
|
||||||
|
$('#freeze').on('click', e => {
|
||||||
|
freeze()
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -105,6 +105,12 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="right form-actions">
|
<div class="right form-actions">
|
||||||
|
#if(!$tour.frozen && $round == $tour.rounds)
|
||||||
|
<button id="freeze" class="ui orange floating right labeled icon button">
|
||||||
|
<i class="snowflake plane outline icon"></i>
|
||||||
|
Freeze
|
||||||
|
</button>
|
||||||
|
#end
|
||||||
<button id="publish" class="ui yellow floating right labeled icon button">
|
<button id="publish" class="ui yellow floating right labeled icon button">
|
||||||
<i class="paper plane outline icon"></i>
|
<i class="paper plane outline icon"></i>
|
||||||
Publish
|
Publish
|
||||||
|
Reference in New Issue
Block a user