Merge branch 'pairing2' into webview2

This commit is contained in:
Claude Brisson
2023-12-21 13:09:10 +01:00
16 changed files with 9744 additions and 1319 deletions

View File

@@ -116,8 +116,16 @@ object OpenGotha {
else -> throw Error("missing byoyomi type")
},
pairing = when (handParams.hdCeiling) {
0 -> Swiss(pairingParams = pairgothPairingParams, placementParams = pairgothPlacementParams)
else -> MacMahon(pairingParams = pairgothPairingParams, placementParams = pairgothPlacementParams)
0 -> Swiss(
pairingParams = pairgothPairingParams,
placementParams = pairgothPlacementParams
)
else -> MacMahon(
pairingParams = pairgothPairingParams,
placementParams = pairgothPlacementParams,
mmFloor = Pairable.parseRank(genParams.genMMFloor),
mmBar = Pairable.parseRank(genParams.genMMBar)
)
},
rounds = genParams.numberOfRounds
)
@@ -241,7 +249,15 @@ object OpenGotha {
TimeSystem.TimeSystemType.STANDARD -> "STDBYOYOMI"
TimeSystem.TimeSystemType.CANADIAN -> "CANBYOYOMI"
TimeSystem.TimeSystemType.FISCHER -> "FISCHER"
} }" director="" endDate="${tournament.endDate}" fischerTime="${tournament.timeSystem.increment}" genCountNotPlayedGamesAsHalfPoint="false" genMMBar="9D" genMMFloor="30K" genMMS2ValueAbsent="1" genMMS2ValueBye="2" genMMZero="30K" genNBW2ValueAbsent="0" genNBW2ValueBye="2" genRoundDownNBWMMS="true" komi="${tournament.komi}" location="${tournament.location}" name="${tournament.name}" nbMovesCanTime="${tournament.timeSystem.stones}" numberOfCategories="1" numberOfRounds="${tournament.rounds}" shortName="${tournament.shortName}" size="${tournament.gobanSize}" stdByoYomiTime="${tournament.timeSystem.byoyomi}"/>
} }" director="" endDate="${tournament.endDate}" fischerTime="${tournament.timeSystem.increment}" genCountNotPlayedGamesAsHalfPoint="false" genMMBar="${
displayRank(
if (tournament.pairing is MacMahon) tournament.pairing.mmBar else 8
).uppercase(Locale.ROOT)
}" genMMFloor="${
displayRank(
if (tournament.pairing is MacMahon) tournament.pairing.mmFloor else -30
).uppercase(Locale.ROOT)
}" genMMS2ValueAbsent="1" genMMS2ValueBye="2" genMMZero="30K" genNBW2ValueAbsent="0" genNBW2ValueBye="2" genRoundDownNBWMMS="true" komi="${tournament.komi}" location="${tournament.location}" name="${tournament.name}" nbMovesCanTime="${tournament.timeSystem.stones}" numberOfCategories="1" numberOfRounds="${tournament.rounds}" shortName="${tournament.shortName}" size="${tournament.gobanSize}" stdByoYomiTime="${tournament.timeSystem.byoyomi}"/>
<HandicapParameterSet hdBasedOnMMS="${tournament.pairing.pairingParams.handicap.useMMS}" hdCeiling="${tournament.pairing.pairingParams.handicap.ceiling}" hdCorrection="${tournament.pairing.pairingParams.handicap.correction}" hdNoHdRankThreshold="${displayRank(tournament.pairing.pairingParams.handicap.rankThreshold)}"/>
<PlacementParameterSet>
<PlacementCriteria>

View File

@@ -10,7 +10,7 @@ import java.util.*
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
val MAX_RANK: Int = 8 // 9D
}
abstract fun toJson(): Json.Object
abstract val club: String?

View File

@@ -180,11 +180,13 @@ class MacMahon(
),
placementParams: PlacementParams = PlacementParams(
Criterion.NBW, Criterion.SOSW, Criterion.SOSOSW
)
),
var mmFloor: Int = -20, // 20k
var mmBar: Int = 0 // 1D
): Pairing(MAC_MAHON, pairingParams, placementParams) {
companion object {}
override fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): List<Game> {
return MacMahonSolver(round, tournament.historyBefore(round), pairables, pairingParams, placementParams).pair()
return MacMahonSolver(round, tournament.historyBefore(round), pairables, pairingParams, placementParams, mmFloor, mmBar).pair()
}
}
@@ -305,12 +307,15 @@ fun Pairing.Companion.fromJson(json: Json.Object): Pairing {
val placementParams = json.getArray("placement")?.let { PlacementParams.fromJson(it) } ?: defaultParams.placementParams
return when (type) {
SWISS -> Swiss(pairingParams, placementParams)
MAC_MAHON -> MacMahon(pairingParams, placementParams)
MAC_MAHON -> MacMahon(pairingParams, placementParams).also { mm ->
mm.mmFloor = json.getInt("mmFloor") ?: -20
mm.mmBar = json.getInt("mmBar") ?: 0
}
ROUND_ROBIN -> RoundRobin(pairingParams, placementParams)
}
}
fun Pairing.toJson() = Json.Object(
fun Pairing.toJson(): Json.Object = Json.MutableObject(
"type" to type.name,
"base" to pairingParams.base.toJson(),
"main" to pairingParams.main.toJson(),
@@ -318,4 +323,9 @@ fun Pairing.toJson() = Json.Object(
"geo" to pairingParams.geo.toJson(),
"handicap" to pairingParams.handicap.toJson(),
"placement" to placementParams.toJson()
)
).also { ret ->
if (this is MacMahon) {
ret["mmFloor"] = mmFloor
ret["mmBar"] = mmBar
}
}

View File

@@ -28,7 +28,7 @@ abstract class BasePairingHelper(
}
// pairables sorted for pairing purposes
protected val nameSortedPairables by lazy {
pairables.sortedWith(::nameSort)
pairables.sortedWith(::nameSort).toMutableList()
}
protected val pairablesMap by lazy {

View File

@@ -29,6 +29,7 @@ sealed class BaseSolver(
companion object {
val rand = Random(/* seed from properties - TODO */)
val DEBUG_EXPORT_WEIGHT = true
var byePlayers: MutableList<Pairable> = mutableListOf()
}
open fun openGothaWeight(p1: Pairable, p2: Pairable) =
@@ -40,7 +41,7 @@ sealed class BaseSolver(
open fun weight(p1: Pairable, p2: Pairable) =
openGothaWeight(p1, p2) +
//pairing.base.applyByeWeight(p1, p2) +
// pairing.base.applyByeWeight(p1, p2) +
pairing.handicap.color(p1, p2)
fun pair(): List<Game> {
@@ -59,13 +60,34 @@ sealed class BaseSolver(
// println("placement criteria" + placement.criteria.toString())
}
var chosenByePlayer: Pairable = ByePlayer
// Choose bye player and remove from pairables
if (ByePlayer in nameSortedPairables){
nameSortedPairables.remove(ByePlayer)
var minWeight = 1000.0*round + (Pairable.MAX_RANK - Pairable.MIN_RANK) + 1;
var weightForBye : Double
var byePlayerIndex = 0
for (p in nameSortedPairables){
weightForBye = p.rank + 2*p.main
if (p in byePlayers) weightForBye += 1000
if (weightForBye <= minWeight){
minWeight = weightForBye
chosenByePlayer = p
}
println("choose Bye: " + p.nameSeed() + " " + weightForBye)
}
println("Bye player : " + chosenByePlayer.nameSeed())
byePlayers.add(chosenByePlayer)
nameSortedPairables.remove(chosenByePlayer)
}
for (i in nameSortedPairables.indices) {
// println(nameSortedPairables[i].nameSeed() + " id="+nameSortedPairables[i].id.toString()+" clasmt="+nameSortedPairables[i].placeInGroup.toString())
for (j in i + 1 until pairables.size) {
for (j in i + 1 until nameSortedPairables.size) {
val p = nameSortedPairables[i]
val q = nameSortedPairables[j]
weight(p, q).let { if (it != Double.NaN) builder.addEdge(p, q, it) }
weight(q, p).let { if (it != Double.NaN) builder.addEdge(q, p, it) }
weight(p, q).let { if (it != Double.NaN) builder.addEdge(p, q, it/1e6) }
weight(q, p).let { if (it != Double.NaN) builder.addEdge(q, p, it/1e6) }
if (DEBUG_EXPORT_WEIGHT)
{
File(WEIGHTS_FILE).appendText("Player1Name="+p.nameSeed()+"\n")
@@ -80,6 +102,7 @@ sealed class BaseSolver(
File(WEIGHTS_FILE).appendText("secHandiCost="+dec.format(pairing.handicap.handicap(p, q))+"\n")
File(WEIGHTS_FILE).appendText("secGeoCost="+dec.format(pairing.geo.apply(p, q))+"\n")
File(WEIGHTS_FILE).appendText("totalCost="+dec.format(openGothaWeight(p,q))+"\n")
//File(WEIGHTS_FILE).appendText("ByeCost="+dec.format(pairing.base.applyByeWeight(p,q))+"\n")
}
}
@@ -92,10 +115,20 @@ sealed class BaseSolver(
listOf(graph.getEdgeSource(it), graph.getEdgeTarget(it))
}.sortedWith(compareBy({ min(it[0].place, it[1].place) }))
val result = sorted.flatMap { games(white = it[0], black = it[1]) }
/* println(sorted.size)
if (chosenByePlayer != ByePlayer) sorted.add(listOf(chosenByePlayer, ByePlayer))
println(sorted.size)*/
var result = sorted.flatMap { games(white = it[0], black = it[1]) }
// add game for ByePlayer
if (chosenByePlayer != ByePlayer) result += Game(id = Store.nextGameId, white = ByePlayer.id, black = chosenByePlayer.id, result = Game.Result.fromSymbol('b'))
if (DEBUG_EXPORT_WEIGHT) {
println("DUDD debug")
println(nameSortedPairables[2].nameSeed() + " " + nameSortedPairables[6].nameSeed())
pairing.main.applyDUDD(nameSortedPairables[2],nameSortedPairables[6], debug=true)
var sumOfWeights = 0.0
println("name place ID colorBal group DUDD vs name place ID colorBal group DUDD")
for (it in sorted) {
println(it[0].nameSeed() + " " + it[0].place.toString()
+ " " + it[0].id.toString()
@@ -165,8 +198,9 @@ sealed class BaseSolver(
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)
//concavityFunction(x, BaseCritParams.MAX_BYE_WEIGHT)
//BaseCritParams.MAX_BYE_WEIGHT - (actualPlayer.rank + 2*actualPlayer.main)
BaseCritParams.MAX_BYE_WEIGHT*(1 - x)
} else {
0.0
}
@@ -202,8 +236,7 @@ sealed class BaseSolver(
open fun MainCritParams.minimizeScoreDifference(p1: Pairable, p2: Pairable): Double {
var score = 0.0
val scoreRange: Int = groupsCount
// TODO check category equality if category are used in SwissCat
if (scoreRange!=0){
if (scoreRange != 0){
val x = abs(p1.group - p2.group).toDouble() / scoreRange.toDouble()
score = concavityFunction(x, scoreWeight)
}
@@ -211,7 +244,7 @@ sealed class BaseSolver(
return score
}
open fun MainCritParams.applyDUDD(p1: Pairable, p2: Pairable): Double {
open fun MainCritParams.applyDUDD(p1: Pairable, p2: Pairable, debug: Boolean =false): Double {
var score = 0.0
// TODO apply Drawn-Up/Drawn-Down if needed
@@ -256,12 +289,16 @@ sealed class BaseSolver(
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 upperSP = if (p1.group < p2.group) p2 else p1
val lowerSP = if (p1.group < p2.group) p1 else p2
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) {
@@ -287,8 +324,20 @@ sealed class BaseSolver(
} else if (scenario == 4) {
score += 4 * duddWeight
}
if(debug){
println("Names "+upperSP.nameSeed()+" "+lowerSP.nameSeed())
println("DUDD scenario, GroupDiff = "+scenario.toString()+" "+(upperSP.group-lowerSP.group).toString())
println("DUDD Upper/Lower modes = "+pairing.main.drawUpDownUpperMode.toString()+" "+pairing.main.drawUpDownLowerMode.toString())
println("u/lSPgroupsize = "+uSPgroupSize.toString()+" "+lSPgroupSize.toString())
println("u/lSPplaceingroup = "+upperSP.placeInGroup.first.toString()+" "+lowerSP.placeInGroup.first.toString())
println("score = " + score.toString())
}
}
// 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))

View File

@@ -1,29 +1,43 @@
package org.jeudego.pairgoth.pairing.solver
import org.jeudego.pairgoth.model.*
import kotlin.math.max
import kotlin.math.min
class MacMahonSolver(round: Int,
history: List<List<Game>>,
pairables: List<Pairable>,
pairingParams: PairingParams,
placementParams: PlacementParams):
placementParams: PlacementParams,
private val mmFloor: Int, private val mmBar: Int):
BaseSolver(round, history, pairables, pairingParams, placementParams) {
override val scores: Map<ID, Double> by lazy {
historyHelper.wins.mapValues {
pairablesMap[it.key]!!.let { pairable ->
pairable.mmBase + pairable.nbW
}
}
pairablesMap.mapValues { it.value.let {
pairable -> pairable.mmBase + pairable.nbW // TODO take tournament parameter into account
} }
}
val Pairable.mmBase: Double get() = rank + 30.0 // TODO use params
val Pairable.mmBase: Double get() = min(max(rank, mmFloor), mmBar) + mmsZero
val Pairable.mms: Double get() = scores[id] ?: 0.0
// CB TODO - configurable criteria
override val mainLimits get() = Pair(0.0, 100.0) // TODO
val mainScoreMin = mmFloor + PLA_SMMS_SCORE_MIN - Pairable.MIN_RANK
val mainScoreMax = mmBar + PLA_SMMS_SCORE_MAX + (round-1) - Pairable.MIN_RANK // round number starts at 1
override val mainLimits get() = Pair(mainScoreMin.toDouble(), mainScoreMax.toDouble())
override fun evalCriterion(pairable: Pairable, criterion: Criterion) = when (criterion) {
Criterion.MMS -> pairable.mms
Criterion.SOSM -> pairable.sos
Criterion.SOSOSM -> pairable.sosos
Criterion.SOSMM1 -> pairable.sosm1
Criterion.SOSMM2 -> pairable.sosm2
else -> super.evalCriterion(pairable, criterion)
}
companion object {
const val mmsZero = 30.0
const val PLA_SMMS_SCORE_MAX = 2 // TODO move this into placement criteria
const val PLA_SMMS_SCORE_MIN = -1
}
}