diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/model/Pairing.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/model/Pairing.kt index 49119fc..afad9c5 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/model/Pairing.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/model/Pairing.kt @@ -11,9 +11,15 @@ import java.util.Random // TODO - this is only an early draft -sealed class Pairing(val type: PairingType) { +sealed class Pairing(val type: PairingType, val weights: Weights = Weights()) { companion object {} enum class PairingType { SWISS, MACMAHON, ROUNDROBIN } + data class Weights( + val played: Double = 1_000_000.0, // weight if players already met + val score: Double = 10_000.0, // per difference of score or MMS + val place: Double = 1_000.0, // per difference of expected position for Swiss + val color: Double = 100.0 // per color unbalancing + ) abstract fun pair(tournament: Tournament, round: Int, pairables: List): List } @@ -28,7 +34,7 @@ class Swiss( val history = if (tournament.games.isEmpty()) emptyList() else tournament.games.slice(0 until round).flatMap { it.values } - return SwissSolver(history, pairables, actualMethod).pair() + return SwissSolver(history, pairables, weights, actualMethod).pair() } } 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 4afb921..e18abf2 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/Solver.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/Solver.kt @@ -3,15 +3,17 @@ package org.jeudego.pairgoth.pairing 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.store.Store import org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedPerfectMatching import org.jgrapht.alg.matching.blossom.v5.ObjectiveSense import org.jgrapht.graph.DefaultWeightedEdge +import org.jgrapht.graph.SimpleDirectedWeightedGraph import org.jgrapht.graph.SimpleWeightedGraph import org.jgrapht.graph.builder.GraphBuilder import java.util.* -sealed class Solver(protected val history: List, protected val pairables: List) { +sealed class Solver(val history: List, val pairables: List, val weights: Pairing.Weights) { companion object { val rand = Random(/* seed from properties - TODO */) @@ -23,12 +25,13 @@ sealed class Solver(protected val history: List, protected val pairables: fun pair(): List { // check that at this stage, we have an even number of pairables if (pairables.size % 2 != 0) throw Error("expecting an even number of pairables") - val builder = GraphBuilder(SimpleWeightedGraph(DefaultWeightedEdge::class.java)) + val builder = GraphBuilder(SimpleDirectedWeightedGraph(DefaultWeightedEdge::class.java)) for (i in sortedPairables.indices) { for (j in i + 1 until n) { val p = pairables[i] val q = pairables[j] builder.addEdge(p, q, weight(p, q)) + builder.addEdge(q, p, weight(q, p)) } } val graph = builder.build() @@ -36,7 +39,6 @@ sealed class Solver(protected val history: List, protected val pairables: val solution = matching.matching val result = solution.map { - // CB TODO - choice of colors should be here Game(Store.nextGameId, graph.getEdgeSource(it).id , graph.getEdgeTarget(it).id) } return result @@ -81,6 +83,16 @@ sealed class Solver(protected val history: List, protected val pairables: }).toSet() } + // 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 + } + } + // score (number of wins) val Pairable.score: Int get() = _score[id] ?: 0 private val _score: Map by lazy { diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/SwissSolver.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/SwissSolver.kt index 2edd5a3..adacc44 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/SwissSolver.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/SwissSolver.kt @@ -2,15 +2,12 @@ package org.jeudego.pairgoth.pairing import org.jeudego.pairgoth.model.Game import org.jeudego.pairgoth.model.Pairable +import org.jeudego.pairgoth.model.Pairing import org.jeudego.pairgoth.model.Swiss import org.jeudego.pairgoth.model.Swiss.Method.* import kotlin.math.abs -class SwissSolver(history: List, pairables: List, val method: Swiss.Method): Solver(history, pairables) { - - val PLAYED_WEIGHT = 1_000_000.0 // weight if players already met - val SCORE_WEIGHT = 10_000.0 // weight per difference of score - val PLACE_WEIGHT = 1_000.0 // weight per difference of place +class SwissSolver(history: List, pairables: List, weights: Pairing.Weights, val method: Swiss.Method): Solver(history, pairables, weights) { override fun sort(p: Pairable, q: Pairable): Int = when (p.score) { @@ -19,20 +16,20 @@ class SwissSolver(history: List, pairables: List, val method: Sw } override fun weight(p: Pairable, q: Pairable) = when { - p.played(q) -> PLAYED_WEIGHT + p.played(q) -> weights.played p.score != q.score -> { val placeWeight = - if (p.score > q.score) (p.placeInGroup.second + q.placeInGroup.first) * PLACE_WEIGHT - else (q.placeInGroup.second + p.placeInGroup.first) * PLACE_WEIGHT - abs(p.score - q.score) * SCORE_WEIGHT + placeWeight + if (p.score > q.score) (p.placeInGroup.second + q.placeInGroup.first) * weights.place + else (q.placeInGroup.second + p.placeInGroup.first) * weights.place + abs(p.score - q.score) * weights.score + placeWeight } else -> when (method) { SPLIT_AND_FOLD -> - if (p.placeInGroup.first > q.placeInGroup.first) abs(p.placeInGroup.first - (q.placeInGroup.second - q.placeInGroup.first)) * PLACE_WEIGHT - else abs(q.placeInGroup.first - (p.placeInGroup.second - p.placeInGroup.first)) * PLACE_WEIGHT - SPLIT_AND_RANDOM -> rand.nextDouble() * p.placeInGroup.second * PLACE_WEIGHT - SPLIT_AND_SLIP -> abs(abs(p.placeInGroup.first - q.placeInGroup.first) - p.placeInGroup.second) * PLACE_WEIGHT + if (p.placeInGroup.first > q.placeInGroup.first) abs(p.placeInGroup.first - (q.placeInGroup.second - q.placeInGroup.first)) * weights.place + else abs(q.placeInGroup.first - (p.placeInGroup.second - p.placeInGroup.first)) * weights.place + SPLIT_AND_RANDOM -> rand.nextDouble() * p.placeInGroup.second * weights.place + SPLIT_AND_SLIP -> abs(abs(p.placeInGroup.first - q.placeInGroup.first) - p.placeInGroup.second) * weights.place else -> throw Error("unhandled case") } - } + } + (abs(p.colorBalance + 1) + abs(q.colorBalance - 1)) * weights.color }