From cb17d59cd13776ddd32059e4f57f52a1ad30fae6 Mon Sep 17 00:00:00 2001 From: Theo Barollet Date: Sun, 31 Dec 2023 15:16:16 +0100 Subject: [PATCH] MMS up to round 4 out of 5 (bug with BWbalance) --- .../jeudego/pairgoth/api/PairingHandler.kt | 5 +-- .../org/jeudego/pairgoth/model/Tournament.kt | 11 ++++--- .../jeudego/pairgoth/pairing/HistoryHelper.kt | 2 +- .../pairgoth/pairing/solver/BaseSolver.kt | 19 +++++++++-- api-webapp/src/test/kotlin/PairingTests.kt | 32 ++++++++++++++----- 5 files changed, 50 insertions(+), 19 deletions(-) diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairingHandler.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairingHandler.kt index ef15003..b668cc4 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairingHandler.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairingHandler.kt @@ -73,9 +73,10 @@ object PairingHandler: PairgothApiHandler { }.toSet() game.black = payload.getID("b") ?: badRequest("missing black player id") game.white = payload.getID("w") ?: badRequest("missing white player id") - tournament.recomputeDUDD(round, game.id) + + tournament.recomputeHdAndDUDD(round, game.id) // temporary - payload.getInt("dudd")?.let { game.drawnUpDown = it } + //payload.getInt("dudd")?.let { game.drawnUpDown = it } val black = tournament.pairables[game.black] ?: badRequest("invalid black player id") val white = tournament.pairables[game.black] ?: badRequest("invalid white player id") if (black.skip.contains(round)) badRequest("black is not playing this round") diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Tournament.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Tournament.kt index d4e5810..adc3627 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Tournament.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Tournament.kt @@ -71,7 +71,7 @@ sealed class Tournament ( else mutableMapOf().also { games.add(it) } fun lastRound() = games.size - fun recomputeDUDD(round: Int, gameID: ID) { + fun recomputeHdAndDUDD(round: Int, gameID: ID) { // Instantiate solver with game history // TODO cleaner solver instantiation val history = historyBefore(round) @@ -81,11 +81,12 @@ sealed class Tournament ( MacMahonSolver(round, history, pairables.values.toList(), pairing.pairingParams, pairing.placementParams, pairing.mmFloor, pairing.mmBar) } else throw Exception("Invalid tournament type") - // Recomputes DUDD + // Recomputes DUDD and hd val game = games(round)[gameID]!! - val whiteplayer = solver.pairables.find { p-> p.id == game.white }!! - val blackplayer = solver.pairables.find { p-> p.id == game.black }!! - game.drawnUpDown = solver.dudd(blackplayer, whiteplayer) + val white = solver.pairables.find { p-> p.id == game.white }!! + val black = solver.pairables.find { p-> p.id == game.black }!! + game.drawnUpDown = solver.dudd(black, white) + game.handicap = solver.hd(black, white) } } 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 cd6ca8f..2fd5127 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 @@ -41,7 +41,7 @@ open class HistoryHelper(protected val history: List>, scoresGetter: history.flatten().filter { game -> game.handicap == 0 }.filter { game -> - game.white != 0 // Remove games against byePlayer + game.white != ByePlayer.id // Remove games against byePlayer }.flatMap { game -> listOf(Pair(game.white, +1), Pair(game.black, -1)) }.groupingBy { 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 797298c..e7d6288 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 @@ -184,6 +184,9 @@ sealed class BaseSolver( // This cost is never applied if potential Handicap != 0 // It is fully applied if wbBalance(sP1) and wbBalance(sP2) are strictly of different signs // It is half applied if one of wbBalance is 0 and the other is >=2 + if (p1.name == "Marechal" && p2.name == "Goloubkov") { + println("coucou ${p1.colorBalance} ${p2.colorBalance}") + } val hd1 = pairing.handicap.handicap(p1, p2) val hd2 = pairing.handicap.handicap(p2, p1) val potentialHd: Int = max(hd1, hd2) @@ -331,7 +334,7 @@ sealed class BaseSolver( score += 4 * duddWeight } - /*if(true) { + /*if(p1.name == "Durand" && p2.name == "Aim") { println("Names DU DD "+p1.nameSeed()+" "+p1_DU+" "+p1_DD+" "+p2.nameSeed()+" "+p2_DU+" "+p2_DD) println("Names "+upperSP.nameSeed()+" "+upperSP.group+" "+lowerSP.nameSeed()+" "+lowerSP.group) println("DUDD scenario, GroupDiff = "+scenario.toString()+" "+(upperSP.group-lowerSP.group).toString()) @@ -526,13 +529,23 @@ sealed class BaseSolver( } fun dudd(black: Pairable, white: Pairable): Int { - return white.group - black.group + return if (white.main > black.main) { + 1 + } else if (white.main < black.main) { + -1 + } else { + 0 + } + //return white.group - black.group + } + fun hd(black: Pairable, white: Pairable): Int { + return pairing.handicap.handicap(white, black) } open fun games(black: Pairable, white: Pairable): List { // CB TODO team of individuals pairing val usedTables = tables.getOrNull(round - 1) ?: BitSet().also { tables.add(it) } val table = if (black.id == 0 || white.id == 0) 0 else usedTables.nextClearBit(1) usedTables.set(table) - return listOf(Game(id = Store.nextGameId, table = table, black = black.id, white = white.id, handicap = pairing.handicap.handicap(white, black), drawnUpDown = dudd(black, white))) + return listOf(Game(id = Store.nextGameId, table = table, black = black.id, white = white.id, handicap = hd(white, black), drawnUpDown = dudd(black, white))) } } diff --git a/api-webapp/src/test/kotlin/PairingTests.kt b/api-webapp/src/test/kotlin/PairingTests.kt index e154d75..2624cbe 100644 --- a/api-webapp/src/test/kotlin/PairingTests.kt +++ b/api-webapp/src/test/kotlin/PairingTests.kt @@ -11,6 +11,8 @@ import java.io.FileWriter import java.io.PrintWriter import java.nio.charset.StandardCharsets import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -93,7 +95,7 @@ class PairingTests: TestBase() { return identical } - fun compare_games(games:Json.Array, opengotha:Json.Array): Boolean{ + fun compare_games(games:Json.Array, opengotha:Json.Array, skipColor: Boolean = false): Boolean{ if (games.size != opengotha.size) { val tmp = Game.fromJson(games.getJson(games.size-1)!!.asObject()) if ((tmp.white != 0) and (tmp.black != 0)) {return false} @@ -104,11 +106,16 @@ class PairingTests: TestBase() { val tmp = Game.fromJson(games.getJson(i)!!.asObject().let { Json.MutableObject(it).set("t", 0) // hack to fill the table to make fromJson() happy }) - gamesPair.add(Pair(tmp.white, tmp.black)) val tmpOG = Game.fromJson(opengotha.getJson(i)!!.asObject().let { Json.MutableObject(it).set("t", 0) // hack to fill the table to make fromJson() happy }) - openGothaPair.add(Pair(tmpOG.white, tmpOG.black)) + if (skipColor) { + gamesPair.add(Pair(min(tmp.white, tmp.black), max(tmp.white, tmp.black))) + openGothaPair.add(Pair(min(tmpOG.white, tmpOG.black), max(tmpOG.white, tmpOG.black))) + } else { + gamesPair.add(Pair(tmp.white, tmp.black)) + openGothaPair.add(Pair(tmpOG.white, tmpOG.black)) + } } return gamesPair==openGothaPair } @@ -300,17 +307,20 @@ class PairingTests: TestBase() { val respOG = TestAPI.post("/api/tour", resourceOG) val idOG = respOG.asObject().getInt("id") val tournamentOG = TestAPI.get("/api/tour/$idOG").asObject() - logger.info(tournamentOG.toString().slice(0..50) + "...") + //logger.info(tournamentOG.toString().slice(0..50) + "...") val playersOG = TestAPI.get("/api/tour/$idOG/part").asArray() //logger.info(players.toString().slice(0..50) + "...") - //logger.info(playersOG.toString()) + logger.info(playersOG.toString()) - val pairingsOG = mutableListOf() + val pairingsOG = mutableListOf() for (round in 1..tournamentOG.getInt("rounds")!!) { val games = TestAPI.get("/api/tour/$idOG/res/$round").asArray() logger.info("games for round $round: {}", games.toString()) - pairingsOG.add(games.toString()) - }*/ + pairingsOG.add(games) + } + */ + + //assert(false) val pairingsR1 = """[{"id":1,"w":3,"b":5,"h":0,"r":"w","dd":0},{"id":2,"w":12,"b":10,"h":0,"r":"b","dd":0},{"id":3,"w":9,"b":14,"h":0,"r":"b","dd":0},{"id":4,"w":11,"b":6,"h":0,"r":"b","dd":0},{"id":5,"w":13,"b":15,"h":0,"r":"b","dd":0},{"id":6,"w":2,"b":16,"h":1,"r":"w","dd":0},{"id":7,"w":8,"b":4,"h":5,"r":"b","dd":0},{"id":8,"w":7,"b":1,"h":2,"r":"w","dd":0}]""" @@ -346,16 +356,22 @@ class PairingTests: TestBase() { BaseSolver.weightsLogger = PrintWriter(FileWriter(getOutputFile("weights.txt"))) // games must be created and then modified by PUT games = TestAPI.post("/api/tour/$id/pair/$round", Json.Array("all")).asArray() + logger.info(games.toString()) val skipSeeding = round <= 2 assertTrue(compare_weights(getOutputFile("weights.txt"), getTestFile("opengotha/simplemm/simplemm_weights_R$round.txt"), skipSeeding), "Not matching opengotha weights for round $round") logger.info("Weights for round $round match OpenGotha") + assertTrue(compare_games(games, Json.parse(pairings[round - 1])!!.asArray(), skipColor=true),"pairings for round $round differ") + logger.info("Pairing for round $round match OpenGotha") forcedGames = Json.parse(pairings[round-1])!!.asArray() + //forcedGames = pairingsOG[round-1] + for (j in 0..forcedGames.size-1) { game = forcedGames.getJson(j)!!.asObject() TestAPI.put("/api/tour/$id/pair/$round", game) } + // Enter results firstGameID = (games.getJson(0)!!.asObject()["id"] as Long?)!!.toInt() // Extract results