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 eb3ea0f..49119fc 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/model/Pairing.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/model/Pairing.kt @@ -12,9 +12,7 @@ import java.util.Random // TODO - this is only an early draft sealed class Pairing(val type: PairingType) { - companion object { - val rand = Random(/* seed from properties - TODO */) - } + companion object {} enum class PairingType { SWISS, MACMAHON, ROUNDROBIN } abstract fun pair(tournament: Tournament, round: Int, pairables: List): List @@ -30,7 +28,7 @@ class Swiss( val history = if (tournament.games.isEmpty()) emptyList() else tournament.games.slice(0 until round).flatMap { it.values } - return SwissSolver(history, actualMethod).pair(pairables) + return SwissSolver(history, pairables, 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 382cc98..4afb921 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/Solver.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/Solver.kt @@ -9,19 +9,23 @@ import org.jgrapht.alg.matching.blossom.v5.ObjectiveSense import org.jgrapht.graph.DefaultWeightedEdge import org.jgrapht.graph.SimpleWeightedGraph import org.jgrapht.graph.builder.GraphBuilder +import java.util.* -sealed class Solver(private val history: List) { +sealed class Solver(protected val history: List, protected val pairables: List) { + + companion object { + val rand = Random(/* seed from properties - TODO */) + } open fun sort(p: Pairable, q: Pairable): Int = 0 // no sort by default abstract fun weight(p: Pairable, q: Pairable): Double - fun pair(pairables: List): List { + 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 sorted = pairables.sortedWith(::sort) val builder = GraphBuilder(SimpleWeightedGraph(DefaultWeightedEdge::class.java)) - for (i in sorted.indices) { - for (j in i + 1 until sorted.size) { + 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)) @@ -38,6 +42,35 @@ sealed class Solver(private val history: List) { return result } + // Calculation parameters + + val n = pairables.size + + // pairables sorted using overloadable sort function + private val sortedPairables by lazy { + pairables.sortedWith(::sort) + } + + // place (among sorted pairables) + val Pairable.place: Int get() = _place[id]!! + private val _place by lazy { + sortedPairables.mapIndexed { index, pairable -> + Pair(pairable.id, index) + }.toMap() + } + + // placeInGroup (of same score) : Pair(place, groupSize) + val Pairable.placeInGroup: Pair get() = _placeInGroup[id]!! + private val _placeInGroup by lazy { + sortedPairables.groupBy { + it.score + }.values.flatMap { group -> + group.mapIndexed { index, pairable -> + Pair(pairable.id, Pair(index, group.size)) + } + }.toMap() + } + // already paired players map fun Pairable.played(other: Pairable) = _paired.contains(Pair(id, other.id)) private val _paired: Set> 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 9af0f89..17294c7 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/SwissSolver.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/SwissSolver.kt @@ -3,9 +3,14 @@ package org.jeudego.pairgoth.pairing import org.jeudego.pairgoth.model.Game import org.jeudego.pairgoth.model.Pairable import org.jeudego.pairgoth.model.Swiss +import org.jeudego.pairgoth.model.Swiss.Method.* import kotlin.math.abs -class SwissSolver(history: List, method: Swiss.Method): Solver(history) { +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 override fun sort(p: Pairable, q: Pairable): Int = when (p.score) { @@ -14,8 +19,20 @@ class SwissSolver(history: List, method: Swiss.Method): Solver(history) { } override fun weight(p: Pairable, q: Pairable) = when { - p.played(q) -> 100_000.0 - p.score != q.score -> abs(p.score - q.score) * 10_000.0 - else -> abs(p.rating - q.rating) * 10.0 + p.played(q) -> PLAYED_WEIGHT + 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 + } + 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.toDouble()) * PLACE_WEIGHT + SPLIT_AND_SLIP -> abs(abs(p.placeInGroup.first - q.placeInGroup.first) - p.placeInGroup.second) * PLACE_WEIGHT + else -> throw Error("unhandled case") + } } }