Team tournaments debugging
This commit is contained in:
@@ -37,7 +37,7 @@ object PairingHandler: PairgothApiHandler {
|
|||||||
"unpairables" to unpairables
|
"unpairables" to unpairables
|
||||||
)
|
)
|
||||||
if (tournament is TeamTournament) {
|
if (tournament is TeamTournament) {
|
||||||
ret["individualGames"] = tournament.individualGames(round).map { it.toJson() }.toJsonArray()
|
ret["individualGames"] = tournament.individualGames(round).values.map { it.toJson() }.toJsonArray()
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
@@ -169,17 +169,17 @@ object PairingHandler: PairgothApiHandler {
|
|||||||
val allPlayers = payload.size == 1 && payload[0] == "all"
|
val allPlayers = payload.size == 1 && payload[0] == "all"
|
||||||
if (allPlayers) {
|
if (allPlayers) {
|
||||||
// TODO - just remove this, it is never used ; and no check is done on whether the players are playing...
|
// TODO - just remove this, it is never used ; and no check is done on whether the players are playing...
|
||||||
tournament.games(round).clear()
|
tournament.unpair(round)
|
||||||
} else {
|
} else {
|
||||||
payload.forEach {
|
payload.forEach {
|
||||||
val id = (it as Number).toInt()
|
val id = (it as Number).toID()
|
||||||
val game = tournament.games(round)[id] ?: throw Error("invalid game id")
|
val game = tournament.games(round)[id] ?: throw Error("invalid game id")
|
||||||
if (game.result != Game.Result.UNKNOWN && game.black != 0 && game.white != 0) {
|
if (game.result != Game.Result.UNKNOWN && game.black != 0 && game.white != 0) {
|
||||||
ApiHandler.logger.error("cannot unpair game id ${game.id}: it has a result")
|
ApiHandler.logger.error("cannot unpair game id ${game.id}: it has a result")
|
||||||
// we'll only skip it
|
// we'll only skip it
|
||||||
// throw Error("cannot unpair ")
|
// throw Error("cannot unpair ")
|
||||||
} else {
|
} else {
|
||||||
tournament.games(round).remove(id)
|
tournament.unpair(round, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,7 +14,7 @@ object ResultsHandler: 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() ?: badRequest("invalid round number")
|
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
|
||||||
val games = tournament.games(round).values
|
val games = tournament.individualGames(round).values
|
||||||
return games.map { it.toJson() }.toJsonArray()
|
return games.map { it.toJson() }.toJsonArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,6 +2,7 @@ package org.jeudego.pairgoth.model
|
|||||||
|
|
||||||
import com.republicate.kson.Json
|
import com.republicate.kson.Json
|
||||||
import com.republicate.kson.toJsonArray
|
import com.republicate.kson.toJsonArray
|
||||||
|
import com.republicate.kson.toJsonObject
|
||||||
// CB TODO - review
|
// CB TODO - review
|
||||||
//import kotlinx.datetime.LocalDate
|
//import kotlinx.datetime.LocalDate
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
@@ -10,6 +11,8 @@ import org.jeudego.pairgoth.api.ApiHandler.Companion.logger
|
|||||||
import org.jeudego.pairgoth.store.nextGameId
|
import org.jeudego.pairgoth.store.nextGameId
|
||||||
import org.jeudego.pairgoth.store.nextPlayerId
|
import org.jeudego.pairgoth.store.nextPlayerId
|
||||||
import org.jeudego.pairgoth.store.nextTournamentId
|
import org.jeudego.pairgoth.store.nextTournamentId
|
||||||
|
import org.jeudego.pairgoth.util.MutableBiMultiMap
|
||||||
|
import org.jeudego.pairgoth.util.mutableBiMultiMapOf
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
@@ -57,7 +60,7 @@ sealed class Tournament <P: Pairable>(
|
|||||||
var frozen: Json.Array? = null
|
var frozen: Json.Array? = null
|
||||||
|
|
||||||
// pairing
|
// pairing
|
||||||
fun pair(round: Int, pairables: List<Pairable>): List<Game> {
|
open fun pair(round: Int, pairables: List<Pairable>): List<Game> {
|
||||||
// Minimal check on round number.
|
// Minimal check on round number.
|
||||||
// CB TODO - the complete check should verify, for each player, that he was either non pairable or implied in the previous round
|
// CB TODO - the complete check should verify, for each player, that he was either non pairable or implied in the previous round
|
||||||
if (round > games.size + 1) badRequest("previous round not paired")
|
if (round > games.size + 1) badRequest("previous round not paired")
|
||||||
@@ -71,12 +74,25 @@ sealed class Tournament <P: Pairable>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open fun unpair(round: Int) {
|
||||||
|
games(round).clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun unpair(round: Int, id: ID) {
|
||||||
|
games(round).remove(id)
|
||||||
|
}
|
||||||
|
|
||||||
// games per id for each round
|
// games per id for each round
|
||||||
protected val games = mutableListOf<MutableMap<ID, Game>>()
|
protected val games = mutableListOf<MutableMap<ID, Game>>()
|
||||||
|
|
||||||
fun games(round: Int) = games.getOrNull(round - 1) ?:
|
fun games(round: Int) = games.getOrNull(round - 1) ?:
|
||||||
if (round > games.size + 1) throw Error("invalid round")
|
if (round > games.size + 1) throw Error("invalid round")
|
||||||
else mutableMapOf<ID, Game>().also { games.add(it) }
|
else mutableMapOf<ID, Game>().also { games.add(it) }
|
||||||
|
|
||||||
|
open fun individualGames(round: Int): Map<ID, Game> = games(round)
|
||||||
|
|
||||||
|
open fun postPair(round: Int, games: List<Game>) {}
|
||||||
|
|
||||||
fun lastRound() = max(1, games.size)
|
fun lastRound() = max(1, games.size)
|
||||||
|
|
||||||
fun recomputeDUDD(round: Int, gameID: ID) {
|
fun recomputeDUDD(round: Int, gameID: ID) {
|
||||||
@@ -196,7 +212,7 @@ sealed class Tournament <P: Pairable>(
|
|||||||
// standard tournament of individuals
|
// standard tournament of individuals
|
||||||
class StandardTournament(
|
class StandardTournament(
|
||||||
id: ID,
|
id: ID,
|
||||||
type: Tournament.Type,
|
type: Type,
|
||||||
name: String,
|
name: String,
|
||||||
shortName: String,
|
shortName: String,
|
||||||
startDate: LocalDate,
|
startDate: LocalDate,
|
||||||
@@ -219,7 +235,7 @@ class StandardTournament(
|
|||||||
// team tournament
|
// team tournament
|
||||||
class TeamTournament(
|
class TeamTournament(
|
||||||
id: ID,
|
id: ID,
|
||||||
type: Tournament.Type,
|
type: Type,
|
||||||
name: String,
|
name: String,
|
||||||
shortName: String,
|
shortName: String,
|
||||||
startDate: LocalDate,
|
startDate: LocalDate,
|
||||||
@@ -234,7 +250,8 @@ class TeamTournament(
|
|||||||
rules: Rules = Rules.FRENCH,
|
rules: Rules = Rules.FRENCH,
|
||||||
gobanSize: Int = 19,
|
gobanSize: Int = 19,
|
||||||
komi: Double = 7.5,
|
komi: Double = 7.5,
|
||||||
tablesExclusion: MutableList<String> = mutableListOf()
|
tablesExclusion: MutableList<String> = mutableListOf(),
|
||||||
|
val individualGames: MutableBiMultiMap<ID, Game> = mutableBiMultiMapOf<ID, Game>()
|
||||||
): Tournament<TeamTournament.Team>(id, type, name, shortName, startDate, endDate, director, country, location, online, timeSystem, rounds, pairing, rules, gobanSize, komi, tablesExclusion) {
|
): Tournament<TeamTournament.Team>(id, type, name, shortName, startDate, endDate, director, country, location, online, timeSystem, rounds, pairing, rules, gobanSize, komi, tablesExclusion) {
|
||||||
companion object {
|
companion object {
|
||||||
private val epsilon = 0.0001
|
private val epsilon = 0.0001
|
||||||
@@ -242,24 +259,43 @@ class TeamTournament(
|
|||||||
override val players = mutableMapOf<ID, Player>()
|
override val players = mutableMapOf<ID, Player>()
|
||||||
val teams: MutableMap<ID, Team> = _pairables
|
val teams: MutableMap<ID, Team> = _pairables
|
||||||
|
|
||||||
// For teams of individual players, map from a team game id to the list of individual games
|
override fun individualGames(round: Int): Map<ID, Game> {
|
||||||
// (filled on demand - it is merely a cache)
|
val teamGames = games(round)
|
||||||
private val individualGames = mutableMapOf<ID, List<Game>>()
|
return if (type.individual) {
|
||||||
|
return teamGames.values.flatMap { game ->
|
||||||
fun individualGames(round: Int): List<Game> {
|
|
||||||
val teamGames = games(round).values.toList()
|
|
||||||
return teamGames.flatMap { game ->
|
|
||||||
if (game.white == 0 || game.black == 0 ) listOf()
|
if (game.white == 0 || game.black == 0 ) listOf()
|
||||||
else {
|
else individualGames[game.id]?.toList() ?: listOf()
|
||||||
|
}.associateBy { it.id }
|
||||||
|
} else {
|
||||||
|
teamGames
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pair(round: Int, pairables: List<Pairable>) =
|
||||||
|
super.pair(round, pairables).also { games ->
|
||||||
|
if (type.individual) {
|
||||||
|
games.forEach { game ->
|
||||||
individualGames.computeIfAbsent(game.id) { id ->
|
individualGames.computeIfAbsent(game.id) { id ->
|
||||||
val whitePlayers = teams[game.white]!!.activePlayers(round)
|
val whitePlayers = teams[game.white]!!.activePlayers(round)
|
||||||
val blackPlayers = teams[game.black]!!.activePlayers(round)
|
val blackPlayers = teams[game.black]!!.activePlayers(round)
|
||||||
whitePlayers.zip(blackPlayers).map {
|
whitePlayers.zip(blackPlayers).map {
|
||||||
Game(nextGameId, game.table, it.first.id, it.second.id)
|
Game(nextGameId, game.table, it.first.id, it.second.id)
|
||||||
|
}.toMutableSet()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun unpair(round: Int) {
|
||||||
|
games(round).values.forEach { game ->
|
||||||
|
individualGames.remove(game.id)
|
||||||
|
}
|
||||||
|
super.unpair(round)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun unpair(round: Int, id: ID) {
|
||||||
|
individualGames.remove(id)
|
||||||
|
super.unpair(round, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pairedTeams() = super.pairedPlayers()
|
fun pairedTeams() = super.pairedPlayers()
|
||||||
@@ -368,7 +404,12 @@ fun Tournament.Companion.fromJson(json: Json.Object, default: Tournament<*>? = n
|
|||||||
timeSystem = json.getObject("timeSystem")?.let { TimeSystem.fromJson(it) } ?: default?.timeSystem ?: badRequest("missing timeSystem"),
|
timeSystem = json.getObject("timeSystem")?.let { TimeSystem.fromJson(it) } ?: default?.timeSystem ?: badRequest("missing timeSystem"),
|
||||||
rounds = json.getInt("rounds") ?: default?.rounds ?: badRequest("missing rounds"),
|
rounds = json.getInt("rounds") ?: default?.rounds ?: badRequest("missing rounds"),
|
||||||
pairing = json.getObject("pairing")?.let { Pairing.fromJson(it, default?.pairing) } ?: default?.pairing ?: badRequest("missing pairing"),
|
pairing = json.getObject("pairing")?.let { Pairing.fromJson(it, default?.pairing) } ?: default?.pairing ?: badRequest("missing pairing"),
|
||||||
tablesExclusion = json.getArray("tablesExclusion")?.map { item -> item as String }?.toMutableList() ?: default?.tablesExclusion ?: mutableListOf()
|
tablesExclusion = json.getArray("tablesExclusion")?.map { item -> item as String }?.toMutableList() ?: default?.tablesExclusion ?: mutableListOf(),
|
||||||
|
individualGames = json.getObject("individualGames")?.entries?.flatMap {
|
||||||
|
(key, value) -> (value as Json.Array).map { game -> Game.fromJson(game as Json.Object) }.map { Pair(key.toID(), it) }
|
||||||
|
}?.let {
|
||||||
|
mutableBiMultiMapOf<ID, Game>(*it.toTypedArray())
|
||||||
|
} ?: (default as? TeamTournament)?.individualGames ?: mutableBiMultiMapOf<ID, Game>()
|
||||||
)
|
)
|
||||||
json.getArray("players")?.forEach { obj ->
|
json.getArray("players")?.forEach { obj ->
|
||||||
val pairable = obj as Json.Object
|
val pairable = obj as Json.Object
|
||||||
@@ -428,6 +469,11 @@ 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 (this is TeamTournament && type.individual) {
|
||||||
|
json["individualGames"] = individualGames.mapValues { it ->
|
||||||
|
it.value.map { game -> game.toJson() }.toJsonArray()
|
||||||
|
}.toJsonObject()
|
||||||
|
}
|
||||||
if (tablesExclusion.isNotEmpty()) {
|
if (tablesExclusion.isNotEmpty()) {
|
||||||
json["tablesExclusion"] = tablesExclusion.toJsonArray()
|
json["tablesExclusion"] = tablesExclusion.toJsonArray()
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,86 @@
|
|||||||
|
package org.jeudego.pairgoth.util
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MultiMap is an associative structure where each key can have one or several values.
|
||||||
|
*
|
||||||
|
* BiMultiMap is an associative structure where:
|
||||||
|
* <ul>
|
||||||
|
* <li>each key can have one or several values (as in a MultiMap)</li>
|
||||||
|
* <li>each value has only a distinct key</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
|
||||||
|
// CB TODO - ways to have Set instead of MutableSet here?
|
||||||
|
interface MultiMap<K, V>: Map<K, MutableSet<V>>
|
||||||
|
|
||||||
|
interface MutableMultiMap<K, V>: MultiMap<K, V>, MutableMap<K, MutableSet<V>> {
|
||||||
|
fun put(key: K, value: V): Boolean
|
||||||
|
fun putAll(vararg pairs: Pair<K, V>)
|
||||||
|
}
|
||||||
|
|
||||||
|
open class LinkedHashMultiMap<K, V>(vararg pairs: Pair<K, V>):
|
||||||
|
MutableMultiMap<K, V>,
|
||||||
|
LinkedHashMap<K, MutableSet<V>>(pairs.groupBy {
|
||||||
|
it.first
|
||||||
|
}.mapValues {
|
||||||
|
it.value.map { it.second }.toMutableSet()
|
||||||
|
}) {
|
||||||
|
override fun put(key: K, value: V): Boolean {
|
||||||
|
val set = super<LinkedHashMap>.computeIfAbsent(key) { mutableSetOf() }
|
||||||
|
return set.add(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putAll(vararg pairs: Pair<K, V>) {
|
||||||
|
pairs.forEach { put(it.first, it.second) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BiMultiMap<K, V>: MultiMap<K, V> {
|
||||||
|
val inverse: Map<V, K>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MutableBiMultiMap<K, V>: MutableMultiMap<K, V>, BiMultiMap<K, V> {
|
||||||
|
override val inverse: MutableMap<V, K>
|
||||||
|
}
|
||||||
|
|
||||||
|
open class LinkedHashBiMultiMap<K, V>(vararg pairs: Pair<K, V>
|
||||||
|
): MutableBiMultiMap<K, V>, LinkedHashMultiMap<K, V>(*pairs) {
|
||||||
|
override val inverse: MutableMap<V, K> = mutableMapOf(*pairs.map { Pair(it.second, it.first) }.toTypedArray())
|
||||||
|
override fun put(key: K, value: V): Boolean {
|
||||||
|
inverse[value] = key
|
||||||
|
return super<LinkedHashMultiMap>.put(key, value)
|
||||||
|
}
|
||||||
|
override fun remove(key: K): MutableSet<V>? {
|
||||||
|
return super<LinkedHashMultiMap>.remove(key)?.also {
|
||||||
|
it.forEach { inverse.remove(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <K, V> multiMapOf(vararg pairs: Pair<K, V>): MultiMap<K, V> =
|
||||||
|
LinkedHashMultiMap<K, V>().apply {
|
||||||
|
pairs.forEach { (k, v) ->
|
||||||
|
put(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <K, V> mutableMultiMapOf(vararg pairs: Pair<K, V>): MutableMultiMap<K, V> =
|
||||||
|
LinkedHashMultiMap<K, V>().apply {
|
||||||
|
pairs.forEach { (k, v) ->
|
||||||
|
put(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <K, V> biMultiMapOf(vararg pairs: Pair<K, V>): BiMultiMap<K, V> =
|
||||||
|
LinkedHashBiMultiMap<K, V>().apply {
|
||||||
|
pairs.forEach { (k, v) ->
|
||||||
|
put(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <K, V> mutableBiMultiMapOf(vararg pairs: Pair<K, V>): MutableBiMultiMap<K, V> =
|
||||||
|
LinkedHashBiMultiMap<K, V>().apply {
|
||||||
|
pairs.forEach { (k, v) ->
|
||||||
|
put(k, v)
|
||||||
|
}
|
||||||
|
}
|
13
pom.xml
13
pom.xml
@@ -81,13 +81,13 @@
|
|||||||
<pairgoth.store>file</pairgoth.store>
|
<pairgoth.store>file</pairgoth.store>
|
||||||
<pairgoth.store.file.path>tournamentfiles</pairgoth.store.file.path>
|
<pairgoth.store.file.path>tournamentfiles</pairgoth.store.file.path>
|
||||||
<pairgoth.auth>none</pairgoth.auth>
|
<pairgoth.auth>none</pairgoth.auth>
|
||||||
<pairgoth.auth.sesame>this_should_be_overriden</pairgoth.auth.sesame>
|
<pairgoth.auth.sesame>this_should_be_overridden</pairgoth.auth.sesame>
|
||||||
<pairgoth.oauth.ffg.client_id>pairtogh</pairgoth.oauth.ffg.client_id>
|
<pairgoth.oauth.ffg.client_id>pairgoth</pairgoth.oauth.ffg.client_id>
|
||||||
<pairgoth.smtp.sender></pairgoth.smtp.sender>
|
<pairgoth.smtp.sender><!-- email sender address --></pairgoth.smtp.sender>
|
||||||
<pairgoth.smtp.host></pairgoth.smtp.host>
|
<pairgoth.smtp.host><!-- smtp host --></pairgoth.smtp.host>
|
||||||
<pairgoth.smtp.port>587</pairgoth.smtp.port>
|
<pairgoth.smtp.port>587</pairgoth.smtp.port>
|
||||||
<pairgoth.smtp.user></pairgoth.smtp.user>
|
<pairgoth.smtp.user><!-- smtp host login user --></pairgoth.smtp.user>
|
||||||
<pairgoth.smtp.password></pairgoth.smtp.password>
|
<pairgoth.smtp.password><!-- smtp host login password --></pairgoth.smtp.password>
|
||||||
<pairgoth.logger.level>info</pairgoth.logger.level>
|
<pairgoth.logger.level>info</pairgoth.logger.level>
|
||||||
<pairgoth.logger.format>[%level] %ip [%logger] %message</pairgoth.logger.format>
|
<pairgoth.logger.format>[%level] %ip [%logger] %message</pairgoth.logger.format>
|
||||||
</properties>
|
</properties>
|
||||||
@@ -190,6 +190,7 @@
|
|||||||
<version>${kotlin.version}</version>
|
<version>${kotlin.version}</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<jvmTarget>11</jvmTarget>
|
<jvmTarget>11</jvmTarget>
|
||||||
|
<apiVersion>2.1</apiVersion>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
Reference in New Issue
Block a user