From 121e1a22b7b3b2cda0aeb50139dddb8d51e83639 Mon Sep 17 00:00:00 2001 From: Claude Brisson Date: Mon, 5 Jun 2023 13:28:17 +0200 Subject: [PATCH] Partial handling of teams of individuals --- run.sh | 3 +- .../org/jeudego/pairgoth/model/Tournament.kt | 9 +- .../jeudego/pairgoth/pairing/HistoryHelper.kt | 95 +++++++++++++++++++ .../org/jeudego/pairgoth/pairing/Solver.kt | 84 ++++------------ 4 files changed, 121 insertions(+), 70 deletions(-) create mode 100644 webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/HistoryHelper.kt diff --git a/run.sh b/run.sh index bc07847..fa204ca 100755 --- a/run.sh +++ b/run.sh @@ -1,4 +1,3 @@ #!/bin/sh -mvn package -java -jar application/target/pairgoth-engine.war +mvn package && java -jar application/target/pairgoth-engine.war diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/model/Tournament.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/model/Tournament.kt index 586a09d..b175afd 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/model/Tournament.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/model/Tournament.kt @@ -25,11 +25,11 @@ sealed class Tournament ( val komi: Double = 7.5 ) { companion object {} - enum class Type(val playersNumber: Int) { + enum class Type(val playersNumber: Int, val individual: Boolean = true) { INDIVIDUAL(1), - PAIRGO(2), - RENGO2(2), - RENGO3(3), + PAIRGO(2, false), + RENGO2(2, false), + RENGO3(3, false), TEAM2(2), TEAM3(3), TEAM4(4), @@ -131,6 +131,7 @@ class TeamTournament( "name" to name, "players" to playerIds.toList().toJsonArray() ) + val teamOfIndividuals: Boolean get() = type.individual } fun teamFromJson(json: Json.Object, default: TeamTournament.Team? = null) = Team( diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/HistoryHelper.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/HistoryHelper.kt new file mode 100644 index 0000000..daccd5b --- /dev/null +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/HistoryHelper.kt @@ -0,0 +1,95 @@ +package org.jeudego.pairgoth.pairing + +import org.jeudego.pairgoth.model.Game +import org.jeudego.pairgoth.model.Pairable +import org.jeudego.pairgoth.model.TeamTournament + +open class HistoryHelper(protected val history: List) { + + open fun playedTogether(p1: Pairable, p2: Pairable) = paired.contains(Pair(p1.id, p2.id)) + open fun colorBalance(p: Pairable) = colorBalance[p.id] + open fun score(p: Pairable) = score[p.id] + open fun sos(p: Pairable) = sos[p.id] + open fun sosos(p: Pairable) = sosos[p.id] + open fun sodos(p: Pairable) = sodos[p.id] + + protected val paired: Set> by lazy { + (history.map { game -> + Pair(game.black, game.white) + } + history.map { game -> + Pair(game.white, game.black) + }).toSet() + } + + private val colorBalance: Map by lazy { + history.flatMap { game -> + listOf(Pair(game.white, +1), Pair(game.black, -1)) + }.groupingBy { it.first }.fold(0) { acc, next -> + acc + next.second + } + } + + private val score: Map by lazy { + mutableMapOf().apply { + history.forEach { game -> + when (game.result) { + Game.Result.BLACK -> put(game.black, getOrDefault(game.black, 0.0) + 1.0) + Game.Result.WHITE -> put(game.white, getOrDefault(game.white, 0.0) + 1.0) + Game.Result.BOTHWIN -> { + put(game.black, getOrDefault(game.black, 0.0) + 0.5) + put(game.white, getOrDefault(game.white, 0.0) + 0.5) + } + else -> {} + } + } + } + } + + private val sos by lazy { + (history.map { game -> + Pair(game.black, score[game.white] ?: 0.0) + } + history.map { game -> + Pair(game.white, score[game.black] ?: 0.0) + }).groupingBy { it.first }.fold(0.0) { acc, next -> + acc + next.second + } + } + + private val sosos by lazy { + (history.map { game -> + Pair(game.black, sos[game.white] ?: 0.0) + } + history.map { game -> + Pair(game.white, sos[game.black] ?: 0.0) + }).groupingBy { it.first }.fold(0.0) { acc, next -> + acc + next.second + } + } + + private val sodos by lazy { + (history.map { game -> + Pair(game.black, if (game.result == Game.Result.BLACK) score[game.white] ?: 0.0 else 0.0) + } + history.map { game -> + Pair(game.white, if (game.result == Game.Result.WHITE) score[game.black] ?: 0.0 else 0.0) + }).groupingBy { it.first }.fold(0.0) { acc, next -> + acc + next.second + } + } + + +} + +// CB TODO - a big problem with the current naive implementation is that the team score is -for now- the sum of team members individual scores + +class TeamOfIndividualsHistoryHelper(history: List): HistoryHelper(history) { + + private fun Pairable.asTeam() = this as TeamTournament.Team + + override fun playedTogether(p1: Pairable, p2: Pairable) = paired.intersect(p1.asTeam().playerIds.first().let { id -> + (p2.asTeam()).playerIds.map {Pair(it, id) } + }.toSet()).isNotEmpty() + + override fun score(p: Pairable) = p.asTeam().teamPlayers.map { super.score(it) ?: throw Error("unknown player id: #${it.id}") }.sum() + override fun sos(p:Pairable) = p.asTeam().teamPlayers.map { super.sos(it) ?: throw Error("unknown player id: #${it.id}") }.sum() + override fun sosos(p:Pairable) = p.asTeam().teamPlayers.map { super.sosos(it) ?: throw Error("unknown player id: #${it.id}") }.sum() + override fun sodos(p:Pairable) = p.asTeam().teamPlayers.map { super.sodos(it) ?: throw Error("unknown player id: #${it.id}") }.sum() +} diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/Solver.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/Solver.kt index 14c83c7..ec2dc36 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/Solver.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/Solver.kt @@ -4,6 +4,7 @@ import org.jeudego.pairgoth.model.Game import org.jeudego.pairgoth.model.Game.Result.* import org.jeudego.pairgoth.model.Pairable import org.jeudego.pairgoth.model.Pairing +import org.jeudego.pairgoth.model.TeamTournament import org.jeudego.pairgoth.store.Store import org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedPerfectMatching import org.jgrapht.alg.matching.blossom.v5.ObjectiveSense @@ -13,7 +14,15 @@ import org.jgrapht.graph.SimpleWeightedGraph import org.jgrapht.graph.builder.GraphBuilder import java.util.* -sealed class Solver(val history: List, val pairables: List, val weights: Pairing.Weights) { +interface HistoryDigester { + val colorBalance: Map + val score: Map + val sos: Map + val sosos: Map + val sodos: Map +} + +sealed class Solver(history: List, val pairables: List, val weights: Pairing.Weights) { companion object { val rand = Random(/* seed from properties - TODO */) @@ -53,6 +62,10 @@ sealed class Solver(val history: List, val pairables: List, val val n = pairables.size + private val historyHelper = + if (pairables.first().let { it is TeamTournament.Team && it.teamOfIndividuals }) TeamOfIndividualsHistoryHelper(history) + else HistoryHelper(history) + // pairables sorted using overloadable sort function private val sortedPairables by lazy { pairables.sortedWith(::sort) @@ -79,77 +92,20 @@ sealed class Solver(val history: List, val pairables: List, val } // already paired players map - fun Pairable.played(other: Pairable) = _paired.contains(Pair(id, other.id)) - private val _paired: Set> by lazy { - (history.map { game -> - Pair(game.black, game.white) - } + history.map { game -> - Pair(game.white, game.black) - }).toSet() - } + fun Pairable.played(other: Pairable) = historyHelper.playedTogether(this, other) // color balance (nw - nb) - val Pairable.colorBalance: Int get() = _colorBalance[id] ?: 0 - private val _colorBalance: Map by lazy { - history.flatMap { game -> - listOf(Pair(game.white, +1), Pair(game.black, -1)) - }.groupingBy { it.first }.fold(0) { acc, next -> - acc + next.second - } - } + val Pairable.colorBalance: Int get() = historyHelper.colorBalance(this) ?: 0 // score (number of wins) - val Pairable.score: Double get() = _score[id] ?: 0.0 - private val _score: Map by lazy { - mutableMapOf().apply { - history.forEach { game -> - when (game.result) { - BLACK -> put(game.black, getOrDefault(game.black, 0.0) + 1.0) - WHITE -> put(game.white, getOrDefault(game.white, 0.0) + 1.0) - BOTHWIN -> { - put(game.black, getOrDefault(game.black, 0.0) + 0.5) - put(game.white, getOrDefault(game.white, 0.0) + 0.5) - } - else -> {} - } - } - } - } + val Pairable.score: Double get() = historyHelper.score(this) ?: 0.0 // sos - val Pairable.sos: Double get() = _sos[id] ?: 0.0 - private val _sos by lazy { - (history.map { game -> - Pair(game.black, _score[game.white] ?: 0.0) - } + history.map { game -> - Pair(game.white, _score[game.black] ?: 0.0) - }).groupingBy { it.first }.fold(0.0) { acc, next -> - acc + next.second - } - } + val Pairable.sos: Double get() = historyHelper.sos(this) ?: 0.0 // sosos - val Pairable.sosos: Double get() = _sosos[id] ?: 0.0 - private val _sosos by lazy { - (history.map { game -> - Pair(game.black, _sos[game.white] ?: 0.0) - } + history.map { game -> - Pair(game.white, _sos[game.black] ?: 0.0) - }).groupingBy { it.first }.fold(0.0) { acc, next -> - acc + next.second - } - } + val Pairable.sosos: Double get() = historyHelper.sosos(this) ?: 0.0 // sodos - val Pairable.sodos: Double get() = _sodos[id] ?: 0.0 - private val _sodos by lazy { - (history.map { game -> - Pair(game.black, if (game.result == BLACK) _score[game.white] ?: 0.0 else 0.0) - } + history.map { game -> - Pair(game.white, if (game.result == WHITE) _score[game.black] ?: 0.0 else 0.0) - }).groupingBy { it.first }.fold(0.0) { acc, next -> - acc + next.second - } - } - + val Pairable.sodos: Double get() = historyHelper.sodos(this) ?: 0.0 }