diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/ApiTools.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/ApiTools.kt index cc995f0..9cfea98 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/ApiTools.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/ApiTools.kt @@ -4,33 +4,48 @@ import com.republicate.kson.Json import org.jeudego.pairgoth.model.Criterion import org.jeudego.pairgoth.model.MacMahon import org.jeudego.pairgoth.model.Pairable +import org.jeudego.pairgoth.model.Pairable.Companion.MIN_RANK import org.jeudego.pairgoth.model.PairingType import org.jeudego.pairgoth.model.Tournament import org.jeudego.pairgoth.model.getID import org.jeudego.pairgoth.model.historyBefore import org.jeudego.pairgoth.pairing.HistoryHelper import org.jeudego.pairgoth.pairing.solver.MacMahonSolver +import kotlin.math.ceil +import kotlin.math.floor import kotlin.math.max import kotlin.math.min +import kotlin.math.roundToInt // TODO CB avoid code redundancy with solvers -fun Tournament<*>.mmBase(pairable: Pairable): Double { - if (pairing !is MacMahon) throw Error("invalid call: tournament is not Mac Mahon") - return min(max(pairable.rank, pairing.mmFloor), pairing.mmBar) + MacMahonSolver.mmsZero + pairable.mmsCorrection -} - fun Tournament<*>.getSortedPairables(round: Int): List { + + fun Pairable.mmBase(): Double { + if (pairing !is MacMahon) throw Error("invalid call: tournament is not Mac Mahon") + return min(max(rank, pairing.mmFloor), pairing.mmBar) + MacMahonSolver.mmsZero + mmsCorrection + } + + fun roundScore(score: Double): Double { + val epsilon = 0.00001 + // Note: this works for now because we only have .0 and .5 fractional parts + return if (pairing.pairingParams.main.roundDownScore) floor(score + epsilon) + else ceil(score - epsilon) + } + val historyHelper = HistoryHelper(historyBefore(round + 1)) { - if (pairing.type == PairingType.SWISS) wins + if (pairing.type == PairingType.SWISS) wins.mapValues { Pair(0.0, it.value) } else pairables.mapValues { - it.value.let { - pairable -> - mmBase(pairable) + - (nbW(pairable) ?: 0.0) + // TODO take tournament parameter into account - (1..round).map { round -> - if (playersPerRound.getOrNull(round - 1)?.contains(pairable.id) == true) 0 else 1 - }.sum() * pairing.pairingParams.main.mmsValueAbsent + it.value.let { pairable -> + val mmBase = pairable.mmBase() + Pair( + mmBase, + roundScore(mmBase + + (nbW(pairable) ?: 0.0) + // TODO take tournament parameter into account + (1..round).map { round -> + if (playersPerRound.getOrNull(round - 1)?.contains(pairable.id) == true) 0 else 1 + }.sum() * pairing.pairingParams.main.mmsValueAbsent) + ) } } } diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/BasePairingHelper.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/BasePairingHelper.kt index c4d968d..8d4c944 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/BasePairingHelper.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/BasePairingHelper.kt @@ -11,7 +11,7 @@ abstract class BasePairingHelper( val placement: PlacementParams, ) { - abstract val scores: Map + abstract val scores: Map> val historyHelper = if (pairables.first().let { it is TeamTournament.Team && it.teamOfIndividuals }) TeamOfIndividualsHistoryHelper(history) { scores } else HistoryHelper(history) { scores } @@ -19,7 +19,7 @@ abstract class BasePairingHelper( // The main criterion that will be used to define the groups should be defined by subclasses // SOS and variants will be computed based on this score - val Pairable.main: Double get() = scores[id] ?: 0.0 + val Pairable.main: Double get() = scores[id]?.second ?: 0.0 abstract val mainLimits: Pair // pairables sorted using overloadable sort function diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/HistoryHelper.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/HistoryHelper.kt index 04e5608..b861587 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/HistoryHelper.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/HistoryHelper.kt @@ -3,7 +3,7 @@ package org.jeudego.pairgoth.pairing import org.jeudego.pairgoth.model.* import org.jeudego.pairgoth.model.Game.Result.* -open class HistoryHelper(protected val history: List>, scoresGetter: HistoryHelper.()-> Map) { +open class HistoryHelper(protected val history: List>, scoresGetter: HistoryHelper.()-> Map>) { // List of all the pairables ID present in the history val allPairables = history.flatten() @@ -95,25 +95,31 @@ open class HistoryHelper(protected val history: List>, scoresGetter: } // define mms to be a synonym of scores - val mms by lazy { scores } + val mms by lazy { scores.mapValues { it -> it.value.second } } // SOS related functions given a score function val sos by lazy { (history.flatten().map { game -> - Pair(game.black, scores[game.white] ?: 0.0) + Pair(game.black, scores[game.white]?.second ?: 0.0) } + history.flatten().map { game -> - Pair(game.white, scores[game.black] ?: 0.0) + Pair(game.white, scores[game.black]?.second ?: 0.0) }).groupingBy { it.first }.fold(0.0) { acc, next -> acc + next.second + }.mapValues { (id, score) -> + // "If the player does not participate in a round, the opponent's score is replaced by the starting score of the player himself." + score + playersPerRound.map { players -> + if (players.contains(id)) 0.0 + else scores[id]?.first ?: 0.0 + }.sum() } } // sos-1 val sosm1 by lazy { (history.flatten().map { game -> - Pair(game.black, scores[game.white] ?: 0.0) + Pair(game.black, scores[game.white]?.second ?: 0.0) } + history.flatten().map { game -> - Pair(game.white, scores[game.black] ?: 0.0) + Pair(game.white, scores[game.black]?.second ?: 0.0) }).groupBy { it.first }.mapValues { @@ -125,9 +131,9 @@ open class HistoryHelper(protected val history: List>, scoresGetter: // sos-2 val sosm2 by lazy { (history.flatten().map { game -> - Pair(game.black, scores[game.white] ?: 0.0) + Pair(game.black, scores[game.white]?.second ?: 0.0) } + history.flatten().map { game -> - Pair(game.white, scores[game.black] ?: 0.0) + Pair(game.white, scores[game.black]?.second ?: 0.0) }).groupBy { it.first }.mapValues { @@ -141,11 +147,11 @@ open class HistoryHelper(protected val history: List>, scoresGetter: (history.flatten().filter { game -> game.white != 0 // Remove games against byePlayer }.map { game -> - Pair(game.black, if (game.result == Game.Result.BLACK) scores[game.white] ?: 0.0 else 0.0) + Pair(game.black, if (game.result == Game.Result.BLACK) scores[game.white]?.second ?: 0.0 else 0.0) } + history.flatten().filter { game -> game.white != 0 // Remove games against byePlayer }.map { game -> - Pair(game.white, if (game.result == Game.Result.WHITE) scores[game.black] ?: 0.0 else 0.0) + Pair(game.white, if (game.result == Game.Result.WHITE) scores[game.black]?.second ?: 0.0 else 0.0) }).groupingBy { it.first }.fold(0.0) { acc, next -> acc + next.second } @@ -202,7 +208,7 @@ open class HistoryHelper(protected val history: List>, scoresGetter: // 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>, scoresGetter: () -> Map): +class TeamOfIndividualsHistoryHelper(history: List>, scoresGetter: () -> Map>): HistoryHelper(history, { scoresGetter() }) { private fun Pairable.asTeam() = this as TeamTournament.Team diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/BaseSolver.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/BaseSolver.kt index ae6a4f7..7d8913b 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/BaseSolver.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/BaseSolver.kt @@ -496,11 +496,11 @@ sealed class BaseSolver( return pairable.rank } - fun roundScore(score: Double): Int { + fun roundScore(score: Double): Double { val epsilon = 0.00001 // Note: this works for now because we only have .0 and .5 fractional parts - return if (pairing.main.roundDownScore) floor(score + epsilon).roundToInt() - else ceil(score - epsilon).roundToInt() + return if (pairing.main.roundDownScore) floor(score + epsilon) + else ceil(score - epsilon) } open fun HandicapParams.clamp(input: Int): Int { diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/MacMahonSolver.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/MacMahonSolver.kt index 9f37120..c613031 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/MacMahonSolver.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/MacMahonSolver.kt @@ -4,6 +4,7 @@ import org.jeudego.pairgoth.model.* import java.util.* import kotlin.math.max import kotlin.math.min +import kotlin.math.roundToInt class MacMahonSolver(round: Int, history: List>, @@ -11,31 +12,33 @@ class MacMahonSolver(round: Int, pairingParams: PairingParams, placementParams: PlacementParams, usedTables: BitSet, - private val mmFloor: Int, private val mmBar: Int): + private val mmFloor: Int, private val mmBar: Int) : BaseSolver(round, history, pairables, pairingParams, placementParams, usedTables) { - override val scores: Map by lazy { + override val scores: Map> by lazy { require (mmBar > mmFloor) { "MMFloor is higher than MMBar" } val pairing = pairables.map { it.id }.toSet() pairablesMap.mapValues { it.value.let { pairable -> - pairable.mmBase + + Pair( + pairable.mmBase, + roundScore(pairable.mmBase + pairable.nbW + // TODO take tournament parameter into account - pairable.missedRounds(round, pairing) * pairingParams.main.mmsValueAbsent + pairable.missedRounds(round, pairing) * pairingParams.main.mmsValueAbsent)) } } } override fun HandicapParams.pseudoRank(pairable: Pairable): Int { if (useMMS) { - return roundScore(pairable.mms + Pairable.MIN_RANK) + return pairable.mms.roundToInt() } else { return pairable.rank } } val Pairable.mmBase: Double get() = min(max(rank, mmFloor), mmBar) + mmsZero + mmsCorrection - val Pairable.mms: Double get() = scores[id] ?: 0.0 + val Pairable.mms: Double get() = scores[id]?.second ?: 0.0 // CB TODO - configurable criteria val mainScoreMin = mmFloor + PLA_SMMS_SCORE_MIN - Pairable.MIN_RANK diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/SwissSolver.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/SwissSolver.kt index 21b127b..e7b63a6 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/SwissSolver.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/SwissSolver.kt @@ -15,7 +15,8 @@ class SwissSolver(round: Int, // In a Swiss tournament the main criterion is the number of wins and already computed override val scores by lazy { - historyHelper.wins + historyHelper.wins.mapValues { + Pair(0.0, it.value) } } // // get() by lazy { historyHelper.wins }