Merge from webview2

This commit is contained in:
Claude Brisson
2023-10-29 14:11:38 +01:00
23 changed files with 37465 additions and 389 deletions

View File

@@ -3,8 +3,6 @@ package org.jeudego.pairgoth.api
import com.republicate.kson.Json
import com.republicate.kson.toJsonArray
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
import org.jeudego.pairgoth.model.Pairing
import org.jeudego.pairgoth.model.PairingType
import org.jeudego.pairgoth.model.getID
import org.jeudego.pairgoth.model.toID
import org.jeudego.pairgoth.model.toJson
@@ -29,7 +27,7 @@ object PairingHandler: PairgothApiHandler {
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
val payload = getArrayPayload(request)
val allPlayers = payload.size == 1 && payload[0] == "all"
if (!allPlayers && tournament.pairing.type == PairingType.SWISS) badRequest("Swiss pairing requires all pairable players")
//if (!allPlayers && tournament.pairing.type == PairingType.SWISS) badRequest("Swiss pairing requires all pairable players")
val playing = (tournament.games(round).values).flatMap {
listOf(it.black, it.white)
}.toSet()

View File

@@ -9,6 +9,7 @@ import org.jeudego.pairgoth.store.Store
sealed class Pairable(val id: ID, val name: String, open val rating: Int, open val rank: Int) {
companion object {
val MIN_RANK: Int = -30 // 30k
val MAX_RANK: Int = 20
}
abstract fun toJson(): Json.Object
abstract val club: String?
@@ -17,6 +18,10 @@ sealed class Pairable(val id: ID, val name: String, open val rating: Int, open v
return name
}
val skip = mutableSetOf<Int>() // skipped rounds
fun equals(other: Pairable): Boolean {
return id == other.id
}
}
object ByePlayer: Pairable(0, "bye", 0, Int.MIN_VALUE) {

View File

@@ -4,8 +4,8 @@ import com.republicate.kson.Json
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
import org.jeudego.pairgoth.model.MainCritParams.SeedMethod.SPLIT_AND_SLIP
import org.jeudego.pairgoth.model.PairingType.*
import org.jeudego.pairgoth.pairing.MacMahonSolver
import org.jeudego.pairgoth.pairing.SwissSolver
import org.jeudego.pairgoth.pairing.solver.MacMahonSolver
import org.jeudego.pairgoth.pairing.solver.SwissSolver
// base pairing parameters
data class BaseCritParams(
@@ -14,7 +14,8 @@ data class BaseCritParams(
val dupWeight: Double = MAX_AVOIDDUPGAME,
val random: Double = 0.0,
val deterministic: Boolean = true,
val colorBalanceWeight: Double = MAX_COLOR_BALANCE
val colorBalanceWeight: Double = MAX_COLOR_BALANCE,
val byeWeight: Double = MAX_BYE_WEIGHT // This weight is not in opengotha
) {
init {
if (nx1 < 0.0 || nx1 > 1.0) throw Error("invalid standardNX1Factor")
@@ -25,6 +26,7 @@ data class BaseCritParams(
companion object {
const val MAX_AVOIDDUPGAME = 500000000000000.0 // 5e14
const val MAX_BYE_WEIGHT = 100000000000.0 // 1e11
const val MAX_RANDOM = 1000000000.0 // 1e9
const val MAX_COLOR_BALANCE = 1000000.0 // 1e6
val default = BaseCritParams()

View File

@@ -0,0 +1,134 @@
package org.jeudego.pairgoth.pairing
import org.jeudego.pairgoth.model.*
abstract class BasePairingHelper(
history: List<List<Game>>, // History of all games played for each round
var pairables: List<Pairable>, // All pairables for this round, it may include the bye player
val pairing: PairingParams,
val placement: PlacementParams,
) {
abstract val scores: Map<ID, Double>
val historyHelper = if (pairables.first().let { it is TeamTournament.Team && it.teamOfIndividuals }) TeamOfIndividualsHistoryHelper(history) { scores }
else HistoryHelper(history) { scores }
// 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
abstract val mainLimits: Pair<Double, Double>
// pairables sorted using overloadable sort function
protected val sortedPairables by lazy {
pairables.sortedWith(::sort)
}
// pairables sorted for pairing purposes
protected val pairingSortedPairables by lazy {
pairables.sortedWith(::pairingSort)
}
// pairables sorted for pairing purposes
protected val nameSortedPairables by lazy {
pairables.sortedWith(::nameSort)
}
protected val pairablesMap by lazy {
pairables.associateBy { it.id }
}
// Generic parameters calculation
//private val standingScore by lazy { computeStandingScore() }
// Decide each pairable group based on the main criterion
protected val groupsCount get() = 1 + (mainLimits.second - mainLimits.first).toInt()
private val _groups by lazy {
pairables.associate { pairable -> Pair(pairable.id, pairable.main.toInt()) }
}
// place (among sorted pairables)
val Pairable.place: Int get() = _place[id]!!
private val _place by lazy {
pairingSortedPairables.mapIndexed { index, pairable ->
Pair(pairable.id, index)
}.toMap()
}
// placeInGroup (of same score) : Pair(place, groupSize)
protected val Pairable.placeInGroup: Pair<Int, Int> get() = _placeInGroup[id]!!
private val _placeInGroup by lazy {
// group by group number
pairingSortedPairables.groupBy {
it.group
// get a list { id { placeInGroup, groupSize } }
}.values.flatMap { group ->
group.mapIndexed { index, pairable ->
Pair(pairable.id, Pair(index, group.size))
}
// get a map id -> { placeInGroup, groupSize }
}.toMap()
}
// already paired players map
protected fun Pairable.played(other: Pairable) = historyHelper.playedTogether(this, other)
// color balance (nw - nb)
protected val Pairable.colorBalance: Int get() = historyHelper.colorBalance(this) ?: 0
protected val Pairable.group: Int get() = _groups[id]!!
protected val Pairable.drawnUpDown: Pair<Int,Int> get() = historyHelper.drawnUpDown(this) ?: Pair(0,0)
protected val Pairable.nbBye: Int get() = historyHelper.nbPlayedWithBye(this) ?: 0
// score (number of wins)
val Pairable.nbW: Double get() = historyHelper.nbW(this) ?: 0.0
val Pairable.sos: Double get() = historyHelper.sos[id] ?: 0.0
val Pairable.sosm1: Double get() = historyHelper.sosm1[id] ?: 0.0
val Pairable.sosm2: Double get() = historyHelper.sosm2[id] ?: 0.0
val Pairable.sosos: Double get() = historyHelper.sosos[id] ?: 0.0
val Pairable.sodos: Double get() = historyHelper.sodos[id] ?: 0.0
val Pairable.cums: Double get() = historyHelper.cumScore[id] ?: 0.0
fun Pairable.eval(criterion: Criterion) = evalCriterion(this, criterion)
open fun evalCriterion(pairable: Pairable, criterion: Criterion) = when (criterion) {
Criterion.NONE -> 0.0
Criterion.CATEGORY -> TODO()
Criterion.RANK -> pairable.rank.toDouble()
Criterion.RATING -> pairable.rating.toDouble()
Criterion.NBW -> pairable.nbW
Criterion.SOSW -> pairable.sos
Criterion.SOSWM1 -> pairable.sosm1
Criterion.SOSWM2 -> pairable.sosm2
Criterion.SOSOSW -> pairable.sosos
Criterion.SODOSW -> pairable.sodos
Criterion.CUSSW -> pairable.cums
else -> throw Error("criterion cannot be evaluated: ${criterion.name}")
}
open fun sort(p: Pairable, q: Pairable): Int {
for (criterion in placement.criteria) {
val criterionP = p.eval(criterion)
val criterionQ = q.eval(criterion)
if (criterionP != criterionQ) {
return (criterionQ * 100 - criterionP * 100).toInt()
}
}
return 0
}
open fun pairingSort(p: Pairable, q: Pairable): Int {
for (criterion in placement.criteria) {
val criterionP = p.eval(criterion)
val criterionQ = q.eval(criterion)
if (criterionP != criterionQ) {
return (criterionQ * 1e6 - criterionP * 1e6).toInt()
}
}
if (p.rating == q.rating) {
return if (p.name > q.name) 1 else -1
}
return q.rating - p.rating
}
open fun nameSort(p: Pairable, q: Pairable): Int {
return if (p.name > q.name) 1 else -1
}
}

View File

@@ -20,6 +20,7 @@ open class HistoryHelper(protected val history: List<List<Game>>, scoresGetter:
// Generic helper functions
open fun playedTogether(p1: Pairable, p2: Pairable) = paired.contains(Pair(p1.id, p2.id))
open fun colorBalance(p: Pairable) = colorBalance[p.id]
open fun nbPlayedWithBye(p: Pairable) = nbPlayedWithBye[p.id]
open fun nbW(p: Pairable) = wins[p.id]
fun drawnUpDown(p: Pairable) = drawnUpDown[p.id]
@@ -46,6 +47,17 @@ open class HistoryHelper(protected val history: List<List<Game>>, scoresGetter:
}
}
private val nbPlayedWithBye: Map<ID, Int> by lazy {
history.flatten().flatMap { game ->
// Duplicates (white, black) into (white, black) and (black, white)
listOf(Pair(game.white, game.black), Pair(game.black, game.white))
}.groupingBy {
it.first
}.fold(0) { acc, next ->
acc + if (next.second == ByePlayer.id) 1 else 0
}
}
val wins: Map<ID, Double> by lazy {
mutableMapOf<ID, Double>().apply {
history.flatten().forEach { game ->

View File

@@ -1,3 +1,8 @@
# File hierarchy
- `HistoryHelper.kt` computes all the criterion for pairings and standings like number of wins, colors, sos, etc
- `BasePairingHelper.kt` extends the `Pairable` objects with attributes corresponding to these criteria. This class plays the role of the `ScoredPlayer` in OpenGotha.
- `solver` folder contains the actual solver base class and the concrete solvers for different tournaments type (Swiss, MacMahon, etc).
# Weights internal name
## Base criteria
- avoiddup

View File

@@ -0,0 +1,22 @@
package org.jeudego.pairgoth.pairing
import org.jeudego.pairgoth.model.Pairable
fun detRandom(max: Double, p1: Pairable, p2: Pairable): Double {
var inverse = false
var name1 = p1.nameSeed("")
var name2 = p2.nameSeed("")
if (name1 > name2) {
name1 = name2.also { name2 = name1 }
inverse = true
}
var nR = "$name1$name2".mapIndexed { i, c ->
c.code.toDouble() * (i + 1)
}.sum() * 1234567 % (max + 1)
if (inverse) nR = max - nR
return nR
}
fun nonDetRandom(max: Double) =
if (max == 0.0) 0.0
else Math.random() * (max + 1.0)

View File

@@ -1,8 +1,10 @@
package org.jeudego.pairgoth.pairing
package org.jeudego.pairgoth.pairing.solver
import org.jeudego.pairgoth.model.*
import org.jeudego.pairgoth.model.Criterion.*
import org.jeudego.pairgoth.model.MainCritParams.SeedMethod.*
import org.jeudego.pairgoth.pairing.BasePairingHelper
import org.jeudego.pairgoth.pairing.detRandom
import org.jeudego.pairgoth.pairing.nonDetRandom
import org.jeudego.pairgoth.store.Store
import org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedPerfectMatching
import org.jgrapht.alg.matching.blossom.v5.ObjectiveSense
@@ -16,99 +18,19 @@ import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
fun detRandom(max: Double, p1: Pairable, p2: Pairable): Double {
var inverse = false
var name1 = p1.nameSeed("")
var name2 = p2.nameSeed("")
if (name1 > name2) {
name1 = name2.also { name2 = name1 }
inverse = true
}
var nR = "$name1$name2".mapIndexed { i, c ->
c.code.toDouble() * (i + 1)
}.sum() * 1234567 % (max + 1)
if (inverse) nR = max - nR
return nR
}
private fun nonDetRandom(max: Double) =
if (max == 0.0) 0.0
else Math.random() * (max + 1.0)
sealed class Solver(
val round: Int,
history: List<List<Game>>,
val pairables: List<Pairable>,
val pairing: PairingParams,
val placement: PlacementParams
) {
sealed class BaseSolver(
val round: Int, // Round number
history: List<List<Game>>, // History of all games played for each round
pairables: List<Pairable>, // All pairables for this round, it may include the bye player
pairing: PairingParams,
placement: PlacementParams,
) : BasePairingHelper(history, pairables, pairing, placement) {
companion object {
val rand = Random(/* seed from properties - TODO */)
val DEBUG_EXPORT_WEIGHT = true
}
abstract val scores: Map<ID, Double>
val historyHelper = if (pairables.first().let { it is TeamTournament.Team && it.teamOfIndividuals }) TeamOfIndividualsHistoryHelper(history) { scores }
else HistoryHelper(history) { scores }
// pairables sorted using overloadable sort function
private val sortedPairables by lazy {
pairables.sortedWith(::sort)
}
// pairables sorted for pairing purposes
private val pairingSortedPairables by lazy {
pairables.sortedWith(::pairingSort)
}
// pairables sorted for pairing purposes
private val nameSortedPairables by lazy {
pairables.sortedWith(::nameSort)
}
// Sorting for logging purpose
private val logSortedPairablesMap by lazy {
val logSortedPairables = pairables.sortedWith(::logSort)
logSortedPairables.associateWith { logSortedPairables.indexOf(it) }
}
protected val pairablesMap by lazy {
pairables.associateBy { it.id }
}
open fun sort(p: Pairable, q: Pairable): Int {
for (criterion in placement.criteria) {
val criterionP = p.eval(criterion)
val criterionQ = q.eval(criterion)
if (criterionP != criterionQ) {
return (criterionP * 100 - criterionQ * 100).toInt()
}
}
return 0
}
open fun pairingSort(p: Pairable, q: Pairable): Int {
for (criterion in placement.criteria) {
val criterionP = p.eval(criterion)
val criterionQ = q.eval(criterion)
if (criterionP != criterionQ) {
return (criterionP * 100 - criterionQ * 100).toInt()
}
}
if (p.rating == q.rating) {
return if (p.name > q.name) 1 else -1
}
return q.rating - p.rating
}
open fun nameSort(p: Pairable, q: Pairable): Int {
return if (p.name > q.name) 1 else -1
}
// Sorting function to order the weight matrix for debugging
open fun logSort(p: Pairable, q: Pairable): Int {
if (p.rating == q.rating) {
return if (p.name > q.name) 1 else -1
}
return p.rating - q.rating
}
open fun openGothaWeight(p1: Pairable, p2: Pairable) =
1.0 + // 1 is minimum value because 0 means "no matching allowed"
pairing.base.apply(p1, p2) +
@@ -118,15 +40,12 @@ sealed class Solver(
open fun weight(p1: Pairable, p2: Pairable) =
openGothaWeight(p1, p2) +
//pairing.base.applyByeWeight(p1, p2) +
pairing.handicap.color(p1, p2)
// The main criterion that will be used to define the groups should be defined by subclasses
val Pairable.main: Double get() = scores[id] ?: 0.0
abstract val mainLimits: Pair<Double, Double>
// SOS and variants will be computed based on this score
fun pair(): List<Game> {
weightLogs.clear()
// check that at this stage, we have an even number of pairables
// The BYE player should have been added beforehand to make a number of pairables even.
if (pairables.size % 2 != 0) throw Error("expecting an even number of pairables")
val builder = GraphBuilder(SimpleDirectedWeightedGraph<Pairable, DefaultWeightedEdge>(DefaultWeightedEdge::class.java))
@@ -162,7 +81,6 @@ sealed class Solver(
File(WEIGHTS_FILE).appendText("secGeoCost="+dec.format(pairing.geo.apply(p, q))+"\n")
File(WEIGHTS_FILE).appendText("totalCost="+dec.format(openGothaWeight(p,q))+"\n")
logWeights("total", p, q, weight(p,q))
}
}
}
@@ -170,34 +88,37 @@ sealed class Solver(
val matching = KolmogorovWeightedPerfectMatching(graph, ObjectiveSense.MAXIMIZE)
val solution = matching.matching
fun gamesSort(p1:Pairable, p2:Pairable) = 0.5*(p1.place + p2.place)
var sorted = solution.map{
val sorted = solution.map{
listOf(graph.getEdgeSource(it), graph.getEdgeTarget(it))
}.sortedBy { gamesSort(it[0],it[1])}
}.sortedWith(compareBy({ min(it[0].place, it[1].place) }))
val result = sorted.flatMap { games(white = it[0], black = it[1]) }
var result = sorted.flatMap { games(white = it[0], black = it[1]) }
if (DEBUG_EXPORT_WEIGHT) {
var sumOfWeights = 0.0
for (it in sorted) {
println(it[0].nameSeed() + " " + it[0].place.toString()
+ " " + it[0].id.toString()
+ " " + it[0].colorBalance.toString()
+ " " + it[0].group.toString()
+ " " + it[0].drawnUpDown.toString()
+ " vs " + it[1].nameSeed()
+ " " + it[1].place.toString()
+ " " + it[1].id.toString()
+ " " + it[1].colorBalance.toString()
+ " " + it[1].group.toString()
+ " " + it[1].drawnUpDown.toString()
)
sumOfWeights += weight(it[0], it[1])
}
val dec = DecimalFormat("#.#")
println("sumOfWeights = " + dec.format(sumOfWeights))
}
return result
}
var weightLogs: MutableMap<String, Array<DoubleArray>> = mutableMapOf()
fun logWeights(weightName: String, p1: Pairable, p2: Pairable, weight: Double) {
if (DEBUG_EXPORT_WEIGHT) {
if (!weightLogs.contains(weightName)) {
weightLogs[weightName] = Array(pairables.size) { DoubleArray(pairables.size) }
}
val pos1: Int = logSortedPairablesMap[p1]!!
val pos2: Int = logSortedPairablesMap[p2]!!
weightLogs[weightName]!![pos1][pos2] = weight
}
}
// base criteria
// Base criteria
open fun BaseCritParams.apply(p1: Pairable, p2: Pairable): Double {
var score = 0.0
// Base Criterion 1 : Avoid Duplicating Game
@@ -215,14 +136,12 @@ sealed class Solver(
open fun BaseCritParams.avoidDuplicatingGames(p1: Pairable, p2: Pairable): Double {
val score = if (p1.played(p2)) 0.0 // We get no score if pairables already played together
else dupWeight
logWeights("avoiddup", p1, p2, score)
return score
}
open fun BaseCritParams.applyRandom(p1: Pairable, p2: Pairable): Double {
val score = if (deterministic) detRandom(random, p1, p2)
else nonDetRandom(random)
logWeights("random", p1, p2, score)
return score
}
@@ -237,10 +156,22 @@ sealed class Solver(
if (wb1 * wb2 < 0) colorBalanceWeight
else if (wb1 == 0 && abs(wb2) >= 2 || wb2 == 0 && abs(wb1) >= 2) colorBalanceWeight / 2 else 0.0
} else 0.0
logWeights("color", p1, p2, score)
return score
}
open fun BaseCritParams.applyByeWeight(p1: Pairable, p2: Pairable): Double {
// The weight is applied if one of p1 or p2 is the BYE player
return if (p1.id == ByePlayer.id || p2.id == ByePlayer.id) {
val actualPlayer = if (p1.id == ByePlayer.id) p2 else p1
// TODO maybe use a different formula than opengotha
val x = (actualPlayer.rank - Pairable.MIN_RANK + actualPlayer.main) / (Pairable.MAX_RANK - Pairable.MIN_RANK + mainLimits.second)
concavityFunction(x, BaseCritParams.MAX_BYE_WEIGHT)
BaseCritParams.MAX_BYE_WEIGHT - (actualPlayer.rank + 2*actualPlayer.main)
} else {
0.0
}
}
// Main criteria
open fun MainCritParams.apply(p1: Pairable, p2: Pairable): Double {
var score = 0.0
@@ -274,11 +205,9 @@ sealed class Solver(
// TODO check category equality if category are used in SwissCat
if (scoreRange!=0){
val x = abs(p1.group - p2.group).toDouble() / scoreRange.toDouble()
val k: Double = pairing.base.nx1
score = scoreWeight * (1.0 - x) * (1.0 + k * x)
score = concavityFunction(x, scoreWeight)
}
logWeights("score", p1, p2, score)
return score
}
@@ -286,6 +215,84 @@ sealed class Solver(
var score = 0.0
// TODO apply Drawn-Up/Drawn-Down if needed
// Main Criterion 3 : If different groups, make a directed Draw-up/Draw-down
// Modifs V3.44.05 (ideas from Tuomo Salo)
if (Math.abs(p1.group - p2.group) < 4 && (p1.group != p2.group)) {
// 5 scenarii
// scenario = 0 : Both players have already been drawn in the same sense
// scenario = 1 : One of the players has already been drawn in the same sense
// scenario = 2 : Normal conditions (does not correct anything and no previous drawn in the same sense)
// This case also occurs if one DU/DD is increased, while one is compensated
// scenario = 3 : It corrects a previous DU/DD //
// scenario = 4 : it corrects a previous DU/DD for both
var scenario = 2
val p1_DU = p1.drawnUpDown.first
val p1_DD = p1.drawnUpDown.second
val p2_DU = p2.drawnUpDown.first
val p2_DD = p2.drawnUpDown.second
if (p1_DU > 0 && p1.group > p2.group) {
scenario--
}
if (p1_DD > 0 && p1.group < p2.group) {
scenario--
}
if (p2_DU > 0 && p2.group > p1.group) {
scenario--
}
if (p2_DD > 0 && p2.group < p1.group) {
scenario--
}
if (scenario != 0 && p1_DU > 0 && p1_DD < p1_DU && p1.group < p2.group) {
scenario++
}
if (scenario != 0 && p1_DD > 0 && p1_DU < p1_DD && p1.group > p2.group) {
scenario++
}
if (scenario != 0 && p2_DU > 0 && p2_DD < p2_DU && p2.group < p1.group) {
scenario++
}
if (scenario != 0 && p2_DD > 0 && p2_DU < p2_DD && p2.group > p1.group) {
scenario++
}
val duddWeight: Double = pairing.main.drawUpDownWeight/5.0
val upperSP = if (p1.group < p2.group) p1 else p2
val lowerSP = if (p1.group < p2.group) p2 else p1
val uSPgroupSize = upperSP.placeInGroup.second
val lSPgroupSize = lowerSP.placeInGroup.second
if (pairing.main.drawUpDownUpperMode === MainCritParams.DrawUpDown.TOP) {
score += duddWeight / 2 * (uSPgroupSize - 1 - upperSP.placeInGroup.first) / uSPgroupSize
} else if (pairing.main.drawUpDownUpperMode === MainCritParams.DrawUpDown.MIDDLE) {
score += duddWeight / 2 * (uSPgroupSize - 1 - Math.abs(2 * upperSP.placeInGroup.first - uSPgroupSize + 1)) / uSPgroupSize
} else if (pairing.main.drawUpDownUpperMode === MainCritParams.DrawUpDown.BOTTOM) {
score += duddWeight / 2 * upperSP.placeInGroup.first / uSPgroupSize
}
if (pairing.main.drawUpDownLowerMode === MainCritParams.DrawUpDown.TOP) {
score += duddWeight / 2 * (lSPgroupSize - 1 - lowerSP.placeInGroup.first) / lSPgroupSize
} else if (pairing.main.drawUpDownLowerMode === MainCritParams.DrawUpDown.MIDDLE) {
score += duddWeight / 2 * (lSPgroupSize - 1 - Math.abs(2 * lowerSP.placeInGroup.first - lSPgroupSize + 1)) / lSPgroupSize
} else if (pairing.main.drawUpDownLowerMode === MainCritParams.DrawUpDown.BOTTOM) {
score += duddWeight / 2 * lowerSP.placeInGroup.first / lSPgroupSize
}
if (scenario == 0) {
// Do nothing
} else if (scenario == 1) {
score += 1 * duddWeight
} else if (scenario == 2 || (scenario > 2 && !pairing.main.compensateDrawUpDown)) {
score += 2 * duddWeight
} else if (scenario == 3) {
score += 3 * duddWeight
} else if (scenario == 4) {
score += 4 * duddWeight
}
}
// TODO adapt to Swiss with categories
/*// But, if players come from different categories, decrease score(added in 3.11)
val catGap: Int = Math.abs(p1.category(gps) - p2.category(gps))
score = score / (catGap + 1) / (catGap + 1) / (catGap + 1) / (catGap + 1)*/
return score
}
@@ -324,7 +331,6 @@ sealed class Solver(
}
}
}
logWeights("seed", p1, p2, score)
return Math.round(score).toDouble()
}
@@ -434,9 +440,9 @@ sealed class Solver(
val hd = pairing.handicap.handicap(p1,p2)
if(hd==0){
if (p1.colorBalance > p2.colorBalance) {
score = 1.0
score = - 1.0
} else if (p1.colorBalance < p2.colorBalance) {
score = -1.0
score = 1.0
} else { // choose color from a det random
if (detRandom(1.0, p1, p2) === 0.0) {
score = 1.0
@@ -448,74 +454,14 @@ sealed class Solver(
return score
}
fun concavityFunction(x: Double, scale: Double) : Double {
val k = pairing.base.nx1
return scale * (1.0 - x) * (1.0 + k * x)
}
open fun games(black: Pairable, white: Pairable): List<Game> {
// CB TODO team of individuals pairing
return listOf(Game(id = Store.nextGameId, black = black.id, white = white.id, handicap = pairing.handicap.handicap(black, white)))
}
// Generic parameters calculation
//private val standingScore by lazy { computeStandingScore() }
// Decide each pairable group based on the main criterion
private val groupsCount get() = 1 + (mainLimits.second - mainLimits.first).toInt()
private val _groups by lazy {
pairables.associate { pairable -> Pair(pairable.id, pairable.main.toInt()) }
}
// place (among sorted pairables)
val Pairable.place: Int get() = _place[id]!!
private val _place by lazy {
pairingSortedPairables.mapIndexed { index, pairable ->
Pair(pairable.id, index)
}.toMap()
}
// placeInGroup (of same score) : Pair(place, groupSize)
private val Pairable.placeInGroup: Pair<Int, Int> get() = _placeInGroup[id]!!
private val _placeInGroup by lazy {
// group by group number
pairingSortedPairables.groupBy {
it.group
// get a list { id { placeInGroup, groupSize } }
}.values.flatMap { group ->
group.mapIndexed { index, pairable ->
Pair(pairable.id, Pair(index, group.size))
}
// get a map id -> { placeInGroup, groupSize }
}.toMap()
}
// already paired players map
private fun Pairable.played(other: Pairable) = historyHelper.playedTogether(this, other)
// color balance (nw - nb)
private val Pairable.colorBalance: Int get() = historyHelper.colorBalance(this) ?: 0
private val Pairable.group: Int get() = _groups[id]!!
// score (number of wins)
val Pairable.nbW: Double get() = historyHelper.nbW(this) ?: 0.0
val Pairable.sos: Double get() = historyHelper.sos[id] ?: 0.0
val Pairable.sosm1: Double get() = historyHelper.sosm1[id] ?: 0.0
val Pairable.sosm2: Double get() = historyHelper.sosm2[id] ?: 0.0
val Pairable.sosos: Double get() = historyHelper.sosos[id] ?: 0.0
val Pairable.sodos: Double get() = historyHelper.sodos[id] ?: 0.0
val Pairable.cums: Double get() = historyHelper.cumScore[id] ?: 0.0
fun Pairable.eval(criterion: Criterion) = evalCriterion(this, criterion)
open fun evalCriterion(pairable: Pairable, criterion: Criterion) = when (criterion) {
NONE -> 0.0
CATEGORY -> TODO()
RANK -> pairable.rank.toDouble()
RATING -> pairable.rating.toDouble()
NBW -> pairable.nbW
SOSW -> pairable.sos
SOSWM1 -> pairable.sosm1
SOSWM2 -> pairable.sosm2
SOSOSW -> pairable.sosos
SODOSW -> pairable.sodos
CUSSW -> pairable.cums
else -> throw Error("criterion cannot be evaluated: ${criterion.name}")
return listOf(Game(id = Store.nextGameId, black = black.id, white = white.id, handicap = pairing.handicap.handicap(black, white), drawnUpDown = white.group-black.group))
}
}

View File

@@ -1,4 +1,4 @@
package org.jeudego.pairgoth.pairing
package org.jeudego.pairgoth.pairing.solver
import org.jeudego.pairgoth.model.*
@@ -7,7 +7,7 @@ class MacMahonSolver(round: Int,
pairables: List<Pairable>,
pairingParams: PairingParams,
placementParams: PlacementParams):
Solver(round, history, pairables, pairingParams, placementParams) {
BaseSolver(round, history, pairables, pairingParams, placementParams) {
override val scores: Map<ID, Double> by lazy {
historyHelper.wins.mapValues {

View File

@@ -1,14 +1,13 @@
package org.jeudego.pairgoth.pairing
package org.jeudego.pairgoth.pairing.solver
import org.jeudego.pairgoth.model.*
import kotlin.properties.Delegates
class SwissSolver(round: Int,
history: List<List<Game>>,
pairables: List<Pairable>,
pairingParams: PairingParams,
placementParams: PlacementParams):
Solver(round, history, pairables, pairingParams, placementParams) {
BaseSolver(round, history, pairables, pairingParams, placementParams) {
// In a Swiss tournament the main criterion is the number of wins and already computed