From f6528e0009505fa0a1f2ae8f2f148437a20a2653 Mon Sep 17 00:00:00 2001 From: Quentin Rendu Date: Wed, 11 Oct 2023 13:53:34 +0200 Subject: [PATCH] Add weight to decide color of pairables --- .../org/jeudego/pairgoth/model/Pairable.kt | 6 +-- .../org/jeudego/pairgoth/pairing/Solver.kt | 46 ++++++++++++++----- api-webapp/src/test/kotlin/BasicTests.kt | 6 +-- api-webapp/src/test/kotlin/UnitaryTests.kt | 43 +++++++++++++++++ 4 files changed, 84 insertions(+), 17 deletions(-) create mode 100644 api-webapp/src/test/kotlin/UnitaryTests.kt diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Pairable.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Pairable.kt index a543cb3..2db36f3 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Pairable.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Pairable.kt @@ -13,7 +13,7 @@ sealed class Pairable(val id: ID, val name: String, open val rating: Int, open v abstract fun toJson(): Json.Object abstract val club: String? abstract val country: String? - open fun nameSeed(): String { + open fun nameSeed(separator: String =" "): String { return name } val skip = mutableSetOf() // skipped rounds @@ -69,8 +69,8 @@ class Player( ).also { if (skip.isNotEmpty()) it["skip"] = Json.Array(skip) } - override fun nameSeed(): String { - return name + " " + firstname + override fun nameSeed(separator: String): String { + return name + separator + firstname } } diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/Solver.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/Solver.kt index 194754e..0d2c4e3 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/Solver.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/Solver.kt @@ -16,10 +16,10 @@ import kotlin.math.abs import kotlin.math.max import kotlin.math.min -private fun detRandom(max: Double, p1: Pairable, p2: Pairable): Double { +fun detRandom(max: Double, p1: Pairable, p2: Pairable): Double { var inverse = false - var name1 = p1.nameSeed() - var name2 = p2.nameSeed() + var name1 = p1.nameSeed("") + var name2 = p2.nameSeed("") if (name1 > name2) { name1 = name2.also { name2 = name1 } inverse = true @@ -109,12 +109,16 @@ sealed class Solver( return p.rating - q.rating } - open fun weight(p1: Pairable, p2: Pairable) = + open fun openGothaWeight(p1: Pairable, p2: Pairable) = 1.0 + // 1 is minimum value because 0 means "no matching allowed" - pairing.base.apply(p1, p2) + - pairing.main.apply(p1, p2) + - pairing.secondary.apply(p1, p2) + - pairing.geo.apply(p1, p2) + pairing.base.apply(p1, p2) + + pairing.main.apply(p1, p2) + + pairing.secondary.apply(p1, p2) + + pairing.geo.apply(p1, p2) + + open fun weight(p1: Pairable, p2: Pairable) = + openGothaWeight(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 @@ -133,11 +137,11 @@ sealed class Solver( if (round==1) File(WEIGHTS_FILE).writeText("Round 1\n") else File(WEIGHTS_FILE).appendText("Round "+round.toString()+"\n") File(WEIGHTS_FILE).appendText("Costs\n") - println("placement criteria" + placement.criteria.toString()) + // println("placement criteria" + placement.criteria.toString()) } for (i in nameSortedPairables.indices) { - println(nameSortedPairables[i].nameSeed() + " id="+nameSortedPairables[i].id.toString()+" clasmt="+nameSortedPairables[i].placeInGroup.toString()) + // println(nameSortedPairables[i].nameSeed() + " id="+nameSortedPairables[i].id.toString()+" clasmt="+nameSortedPairables[i].placeInGroup.toString()) for (j in i + 1 until pairables.size) { val p = nameSortedPairables[i] val q = nameSortedPairables[j] @@ -156,7 +160,7 @@ sealed class Solver( File(WEIGHTS_FILE).appendText("mainSeedCost="+dec.format(pairing.main.applySeeding(p, q))+"\n") 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(weight(p,q))+"\n") + File(WEIGHTS_FILE).appendText("totalCost="+dec.format(openGothaWeight(p,q))+"\n") logWeights("total", p, q, weight(p,q)) } @@ -172,6 +176,7 @@ sealed class Solver( listOf(graph.getEdgeSource(it), graph.getEdgeTarget(it)) }.sortedBy { gamesSort(it[0],it[1])} + var result = sorted.flatMap { games(white = it[0], black = it[1]) } return result @@ -424,6 +429,25 @@ sealed class Solver( return hd } + open fun HandicapParams.color(p1: Pairable, p2: Pairable): Double { + var score = 0.0 + val hd = pairing.handicap.handicap(p1,p2) + if(hd==0){ + if (p1.colorBalance > p2.colorBalance) { + score = 1.0 + } else if (p1.colorBalance < p2.colorBalance) { + score = -1.0 + } else { // choose color from a det random + if (detRandom(1.0, p1, p2) === 0.0) { + score = 1.0 + } else { + score = -1.0 + } + } + } + return score + } + open fun games(black: Pairable, white: Pairable): List { // CB TODO team of individuals pairing return listOf(Game(id = Store.nextGameId, black = black.id, white = white.id, handicap = pairing.handicap.handicap(black, white))) diff --git a/api-webapp/src/test/kotlin/BasicTests.kt b/api-webapp/src/test/kotlin/BasicTests.kt index 788ce0a..6cce070 100644 --- a/api-webapp/src/test/kotlin/BasicTests.kt +++ b/api-webapp/src/test/kotlin/BasicTests.kt @@ -315,7 +315,6 @@ class BasicTests: TestBase() { assertNotNull(id_np) val tournament_np = TestAPI.get("/api/tour/$id_np").asObject() logger.info(tournament_np.toString().slice(0..50) + "...") - logger.info(tournament_np.toString()) val players_np = TestAPI.get("/api/tour/$id_np/part").asArray() logger.info(players_np.toString().slice(0..50) + "...") var games_np = TestAPI.post("/api/tour/$id_np/pair/1", Json.Array("all")).asArray() @@ -332,10 +331,11 @@ class BasicTests: TestBase() { val pairings_R4 = """[{"id":891,"w":506,"b":528,"h":0,"r":"?","dd":0},{"id":892,"w":517,"b":530,"h":0,"r":"?","dd":0},{"id":893,"w":518,"b":512,"h":0,"r":"?","dd":0},{"id":894,"w":511,"b":519,"h":0,"r":"?","dd":0},{"id":895,"w":508,"b":504,"h":0,"r":"?","dd":0},{"id":896,"w":533,"b":514,"h":0,"r":"?","dd":0},{"id":897,"w":529,"b":502,"h":0,"r":"?","dd":0},{"id":898,"w":520,"b":509,"h":0,"r":"?","dd":0},{"id":899,"w":531,"b":516,"h":0,"r":"?","dd":0},{"id":900,"w":507,"b":503,"h":0,"r":"?","dd":0},{"id":901,"w":510,"b":505,"h":0,"r":"?","dd":0},{"id":902,"w":523,"b":524,"h":0,"r":"?","dd":0},{"id":903,"w":532,"b":526,"h":0,"r":"?","dd":0},{"id":904,"w":515,"b":525,"h":0,"r":"?","dd":0},{"id":905,"w":522,"b":527,"h":0,"r":"?","dd":0},{"id":906,"w":513,"b":521,"h":0,"r":"?","dd":0}]""" val pairings_R5 = """[{"id":907,"w":528,"b":530,"h":0,"r":"?","dd":0},{"id":908,"w":512,"b":517,"h":0,"r":"?","dd":0},{"id":909,"w":504,"b":519,"h":0,"r":"?","dd":0},{"id":910,"w":514,"b":509,"h":0,"r":"?","dd":0},{"id":911,"w":506,"b":502,"h":0,"r":"?","dd":0},{"id":912,"w":516,"b":518,"h":0,"r":"?","dd":0},{"id":913,"w":511,"b":505,"h":0,"r":"?","dd":0},{"id":914,"w":520,"b":526,"h":0,"r":"?","dd":0},{"id":915,"w":525,"b":533,"h":0,"r":"?","dd":0},{"id":916,"w":524,"b":508,"h":0,"r":"?","dd":0},{"id":917,"w":503,"b":529,"h":0,"r":"?","dd":0},{"id":918,"w":531,"b":532,"h":0,"r":"?","dd":0},{"id":919,"w":527,"b":510,"h":0,"r":"?","dd":0},{"id":920,"w":523,"b":515,"h":0,"r":"?","dd":0},{"id":921,"w":507,"b":521,"h":0,"r":"?","dd":0},{"id":922,"w":513,"b":522,"h":0,"r":"?","dd":0}]""" - logger.info(compare_string(pairings_R1, games_np.toString())) + //logger.info(compare_string(pairings_R1, games_np.toString())) // val games = TestAPI.get("/api/tour/$id/res/1").asArray() + //logger.info("Compare pairings for round 1") assertEquals(pairings_R1, games_np.toString(), "pairings for round 1 differ") - + logger.info("Pairings for round 1 match OpenGotha") } diff --git a/api-webapp/src/test/kotlin/UnitaryTests.kt b/api-webapp/src/test/kotlin/UnitaryTests.kt new file mode 100644 index 0000000..1df706e --- /dev/null +++ b/api-webapp/src/test/kotlin/UnitaryTests.kt @@ -0,0 +1,43 @@ +package org.jeudego.pairgoth.test + +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + + +class UnitaryTests: TestBase() { + + @Test + fun `001 test detRandom`() { + + fun detRandomCopy(p1:String, p2:String):Double{ + var name1 = p1 + var name2 = p2 + if (name1 > name2) { + name1 = name2.also { name2 = name1 } + } + val s = "$name1$name2" + var nR = s.mapIndexed { i, c -> + c.code.toDouble() * (i + 1) + }.sum() +/* logger.info("nR = "+nR.toString()) + var i = 0 + nR = 0.0 + for (i in 0..s.length-1) { + nR += s[i].code.toDouble()*(i+1) + logger.info(i.toString()+" "+s[i]+" "+nR) + } + logger.info("nR for string "+"$name1$name2"+" "+nR.toString())*/ + return nR + } + + var name1 = "MizessynFrançois" + var name2 = "BonisMichel" + + assertEquals(42923.0, detRandomCopy(name1, name2)) + +/* nR = nR * 1234567 % (max + 1) + if (inverse) nR = max - nR + assertEquals(1.0, nR)*/ + } + +}