From 471b316dce49050131ad82d0e086baa76726261c Mon Sep 17 00:00:00 2001 From: Claude Brisson Date: Sat, 27 Jul 2024 16:34:35 +0200 Subject: [PATCH] Table numbers exclusion is functional --- .../jeudego/pairgoth/api/PairingHandler.kt | 14 +++++++++++++ .../jeudego/pairgoth/api/TournamentHandler.kt | 2 +- .../org/jeudego/pairgoth/model/Tournament.kt | 21 +++++++++++++++++++ .../src/main/webapp/js/tour-pairing.inc.js | 9 +++++++- 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairingHandler.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairingHandler.kt index ee534a9..1e9eef1 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairingHandler.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairingHandler.kt @@ -3,6 +3,7 @@ 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.getID import org.jeudego.pairgoth.model.toID @@ -38,6 +39,7 @@ object PairingHandler: PairgothApiHandler { if (round > tournament.lastRound() + 1) badRequest("invalid round: previous round has not been played") val payload = getArrayPayload(request) if (payload.isEmpty()) badRequest("nobody to pair") + // CB TODO - change convention to empty array for all players val allPlayers = payload.size == 1 && payload[0] == "all" //if (!allPlayers && tournament.pairing.type == PairingType.SWISS) badRequest("Swiss pairing requires all pairable players") val playing = (tournament.games(round).values).flatMap { @@ -57,6 +59,10 @@ object PairingHandler: PairgothApiHandler { } ?: badRequest("invalid pairable id: #$id") } val games = tournament.pair(round, pairables) + + // always renumber table to take table exclusion into account + tournament.renumberTables(round) + val ret = games.map { it.toJson() }.toJsonArray() tournament.dispatchEvent(GamesAdded, request, Json.Object("round" to round, "games" to ret)) return ret @@ -122,6 +128,14 @@ object PairingHandler: PairgothApiHandler { return Json.Object("success" to true) } else { // without id, it's a table renumbering + if (payload.containsKey("excludeTables")) { + val tablesExclusion = payload.getString("excludeTables") ?: badRequest("missing 'excludeTables'") + TournamentHandler.validateTablesExclusion(tablesExclusion) + while (tournament.tablesExclusion.size < round) tournament.tablesExclusion.add("") + tournament.tablesExclusion[round - 1] = tablesExclusion + tournament.dispatchEvent(TournamentUpdated, request, tournament.toJson()) + } + val sortedPairables = tournament.getSortedPairables(round) val sortedMap = sortedPairables.associateBy { it.getID()!! diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/TournamentHandler.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/TournamentHandler.kt index d276ddc..25b0e55 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/TournamentHandler.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/TournamentHandler.kt @@ -91,7 +91,7 @@ object TournamentHandler: PairgothApiHandler { return Json.Object("success" to true) } - private fun validateTablesExclusion(exclusion: String) { + internal fun validateTablesExclusion(exclusion: String) { if (!tablesExclusionValidator.matches(exclusion)) badRequest("invalid tables exclusion pattern") } 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 d3304fa..a95dc15 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 @@ -6,10 +6,12 @@ import com.republicate.kson.toJsonArray //import kotlinx.datetime.LocalDate import java.time.LocalDate import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest +import org.jeudego.pairgoth.api.ApiHandler.Companion.logger import org.jeudego.pairgoth.store.nextPlayerId import org.jeudego.pairgoth.store.nextTournamentId import kotlin.math.max import java.util.* +import java.util.regex.Pattern import kotlin.math.roundToInt sealed class Tournament ( @@ -127,9 +129,12 @@ sealed class Tournament ( fun renumberTables(round: Int, pivot: Game? = null, orderBY: (Game) -> Int = ::defaultGameOrderBy): Boolean { var changed = false var nextTable = 1 + val excluded = excludedTables(round) games(round).values.filter{ game -> pivot?.let { pivot.id != game.id } ?: true }.sortedBy(orderBY).forEach { game -> + while (excluded.contains(nextTable)) ++nextTable if (pivot != null && nextTable == pivot.table) { ++nextTable + while (excluded.contains(nextTable)) ++nextTable } if (game.table != 0) { changed = changed || game.table != nextTable @@ -150,6 +155,22 @@ sealed class Tournament ( "ready" to (games.getOrNull(index)?.values?.count { it.result != Game.Result.UNKNOWN } ?: 0) ) }.toJsonArray() + + fun excludedTables(round: Int): Set { + if (round > tablesExclusion.size) return emptySet() + val excluded = mutableSetOf() + val parser = Regex("(\\d+)(?:-(\\d+))?") + parser.findAll(tablesExclusion[round - 1]).forEach { match -> + val left = match.groupValues[1].toInt() + val right = match.groupValues[2].let { if (it.isEmpty()) left else it.toInt() } + var t = left + do { + excluded.add(t) + ++t + } while (t <= right) + } + return excluded + } } // standard tournament of individuals diff --git a/view-webapp/src/main/webapp/js/tour-pairing.inc.js b/view-webapp/src/main/webapp/js/tour-pairing.inc.js index 4df7bcf..9ca05ed 100644 --- a/view-webapp/src/main/webapp/js/tour-pairing.inc.js +++ b/view-webapp/src/main/webapp/js/tour-pairing.inc.js @@ -38,7 +38,14 @@ function unpair(games) { } function renumberTables() { - api.putJson(`tour/${tour_id}/pair/${activeRound}`, {}) + let payload = {} + let tablesExclusionControl = $('#exclude-tables'); + let value = tablesExclusionControl[0].value; + let origValue = tablesExclusionControl.data('orig'); + if (value !== origValue) { + payload['excludeTables'] = value; + } + api.putJson(`tour/${tour_id}/pair/${activeRound}`, payload) .then(rst => { if (rst !== 'error') { document.location.reload();