From f8848e5d81931bf0959eee41ad6230726fa2c342 Mon Sep 17 00:00:00 2001 From: Claude Brisson Date: Mon, 2 Oct 2023 03:42:33 +0200 Subject: [PATCH 1/4] Fix toJson --- .../src/main/kotlin/org/jeudego/pairgoth/model/Game.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Game.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Game.kt index a4042d2..50d0ae4 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Game.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Game.kt @@ -42,9 +42,9 @@ fun Game.toJson() = Json.Object( fun Game.Companion.fromJson(json: Json.Object) = Game( id = json.getID("id") ?: throw Error("missing game id"), - white = json.getID("white") ?: throw Error("missing white player"), - black = json.getID("black") ?: throw Error("missing black player"), - handicap = json.getInt("handicap") ?: 0, - result = json.getChar("result")?.let { Game.Result.fromSymbol(it) } ?: UNKNOWN, - drawnUpDown = json.getInt("drawnUpDown") ?: 0 + white = json.getID("w") ?: throw Error("missing white player"), + black = json.getID("b") ?: throw Error("missing black player"), + handicap = json.getInt("h") ?: 0, + result = json.getChar("r")?.let { Game.Result.fromSymbol(it) } ?: UNKNOWN, + drawnUpDown = json.getInt("dd") ?: 0 ) From d7a9c25a3460aef7ca12cdaea8bd85a6afd03202 Mon Sep 17 00:00:00 2001 From: Claude Brisson Date: Mon, 2 Oct 2023 04:07:45 +0200 Subject: [PATCH 2/4] Add comments --- .../src/main/kotlin/org/jeudego/pairgoth/pairing/Solver.kt | 3 +++ 1 file changed, 3 insertions(+) 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 cde7e70..95bcdd4 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 @@ -430,12 +430,15 @@ sealed class Solver( // placeInGroup (of same score) : Pair(place, groupSize) private val Pairable.placeInGroup: Pair get() = _placeInGroup[id]!! private val _placeInGroup by lazy { + // group by group number sortedPairables.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() } From 3ebf7c024c39a51cba6d98e85f5f2b35e69528bb Mon Sep 17 00:00:00 2001 From: Claude Brisson Date: Mon, 2 Oct 2023 04:14:12 +0200 Subject: [PATCH 3/4] Fix another comment --- .../main/kotlin/org/jeudego/pairgoth/pairing/HistoryHelper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f0949d1..516e4a0 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 @@ -32,7 +32,7 @@ open class HistoryHelper(protected val history: List>, scoresGetter: }).toSet() } - // Returns the number of games played as white + // Returns the number of games played as white minus the number of games played as black // Only count games without handicap private val colorBalance: Map by lazy { history.flatten().filter { game -> From c2d1e53f3f0e045654cff62f78e9bde1bcc5dfa2 Mon Sep 17 00:00:00 2001 From: Claude Brisson Date: Mon, 2 Oct 2023 09:50:13 +0200 Subject: [PATCH 4/4] Fix parameters import/export --- .../org/jeudego/pairgoth/ext/OpenGotha.kt | 98 ++++++++++++++----- .../org/jeudego/pairgoth/util/XmlUtils.kt | 2 +- .../src/main/resources/xsd/opengotha.xsd | 2 +- .../src/test/kotlin/ImportExportTests.kt | 16 ++- 4 files changed, 91 insertions(+), 27 deletions(-) diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/ext/OpenGotha.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/ext/OpenGotha.kt index 0ed46b1..2fb76d2 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/ext/OpenGotha.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/ext/OpenGotha.kt @@ -12,9 +12,28 @@ import java.util.* import javax.xml.datatype.XMLGregorianCalendar private const val MILLISECONDS_PER_DAY = 86400000 -fun XMLGregorianCalendar.toLocalDate() = LocalDate.fromEpochDays((toGregorianCalendar().time.time / MILLISECONDS_PER_DAY).toInt()) +fun XMLGregorianCalendar.toLocalDate() = LocalDate(year, month, day) object OpenGotha { + + private fun parseDrawUpDownMode(str: String) = when (str) { + "BOT" -> MainCritParams.DrawUpDown.BOTTOM + "MID" -> MainCritParams.DrawUpDown.MIDDLE + "TOP" -> MainCritParams.DrawUpDown.TOP + else -> throw Error("Invalid drawUpDown mode: $str") + } + + private fun parseSeedSystem(str: String) = when (str) { + "SPLITANDSLIP" -> MainCritParams.SeedMethod.SPLIT_AND_SLIP + "SPLITANDRANDOM" -> MainCritParams.SeedMethod.SPLIT_AND_RANDOM + "SPLITANDFOLD" -> MainCritParams.SeedMethod.SPLIT_AND_FOLD + else -> throw Error("Invalid seed system: $str") + } + + private fun String.titlecase(locale: Locale = Locale.ROOT) = lowercase(locale).replaceFirstChar { it.titlecase(locale) } + + private fun MainCritParams.SeedMethod.format() = toString().replace("_", "") + fun import(element: Element): Tournament<*> { val context = JAXBContext.newInstance(ObjectFactory::class.java) @@ -28,6 +47,57 @@ object OpenGotha { val placmtParams = ogTournament.tournamentParameterSet.placementParameterSet val pairParams = ogTournament.tournamentParameterSet.pairingParameterSet + val pairgothPairingParams = PairingParams( + base = BaseCritParams( + nx1 = pairParams.paiStandardNX1Factor.toDouble(), + dupWeight = pairParams.paiBaAvoidDuplGame.toDouble(), + random = pairParams.paiBaRandom.toDouble(), + deterministic = pairParams.paiBaDeterministic.toBoolean(), + colorBalanceWeight = pairParams.paiBaBalanceWB.toDouble() + ), + main = MainCritParams( + categoriesWeight = pairParams.paiMaAvoidMixingCategories.toDouble(), + scoreWeight = pairParams.paiMaMinimizeScoreDifference.toDouble(), + drawUpDownWeight = pairParams.paiMaDUDDWeight.toDouble(), + compensateDrawUpDown = pairParams.paiMaCompensateDUDD.toBoolean(), + drawUpDownUpperMode = parseDrawUpDownMode(pairParams.paiMaDUDDUpperMode), + drawUpDownLowerMode = parseDrawUpDownMode(pairParams.paiMaDUDDLowerMode), + seedingWeight = pairParams.paiMaMaximizeSeeding.toDouble(), + lastRoundForSeedSystem1 = pairParams.paiMaLastRoundForSeedSystem1, + seedSystem1 = parseSeedSystem(pairParams.paiMaSeedSystem1), + seedSystem2 = parseSeedSystem(pairParams.paiMaSeedSystem2 ?: "SPLITANDSLIP"), + additionalPlacementCritSystem1 = Criterion.valueOf(pairParams.paiMaAdditionalPlacementCritSystem1.uppercase()), + additionalPlacementCritSystem2 = Criterion.valueOf(pairParams.paiMaAdditionalPlacementCritSystem2.uppercase().replace("NULL", "NONE")) + ), + secondary = SecondaryCritParams( + barThresholdActive = pairParams.paiSeBarThresholdActive.toBoolean(), + rankThreshold = Pairable.parseRank(pairParams.paiSeRankThreshold), + nbWinsThresholdActive = pairParams.paiSeNbWinsThresholdActive.toBoolean(), + defSecCrit = pairParams.paiSeDefSecCrit.toDouble() + ), + geo = GeographicalParams( + avoidSameGeo = pairParams.paiSeAvoidSameGeo.toDouble(), + preferMMSDiffRatherThanSameCountry = pairParams.paiSePreferMMSDiffRatherThanSameCountry, + preferMMSDiffRatherThanSameClubsGroup = 2, + preferMMSDiffRatherThanSameClub = pairParams.paiSePreferMMSDiffRatherThanSameClub + ), + handicap = HandicapParams( + weight = pairParams.paiSeMinimizeHandicap.toDouble(), + useMMS = handParams.hdBasedOnMMS.toBoolean(), + rankThreshold = Pairable.parseRank(pairParams.paiSeRankThreshold), + correction = handParams.hdCorrection, + ceiling = handParams.hdCeiling + ) + ) + + val pairgothPlacementParams = PlacementParams( + crit = placmtParams.placementCriteria.placementCriterion.filter { + it.name != "NULL" + }.map { + Criterion.valueOf(it.name) + }.toTypedArray() + ) + val tournament = StandardTournament( id = Store.nextTournamentId, type = Tournament.Type.INDIVIDUAL, // CB for now, TODO @@ -46,26 +116,8 @@ object OpenGotha { else -> throw Error("missing byoyomi type") }, pairing = when (handParams.hdCeiling) { - 0 -> Swiss( - pairingParams = PairingParams( - - ), - placementParams = PlacementParams( - crit = placmtParams.placementCriteria.placementCriterion.filter { - it.name != "NULL" - }.map { - Criterion.valueOf(it.name) - }.toTypedArray() - ) - ) // TODO - else -> MacMahon( - pairingParams = PairingParams( - - ), - placementParams = PlacementParams( - - ) - ) // TODO + 0 -> Swiss(pairingParams = pairgothPairingParams, placementParams = pairgothPlacementParams) + else -> MacMahon(pairingParams = pairgothPairingParams, placementParams = pairgothPlacementParams) }, rounds = genParams.numberOfRounds ) @@ -123,7 +175,7 @@ object OpenGotha { // method 2 (quick and dirty) is to rely on templating: val xml = """ - + ${tournament.pairables.values.map { player -> player as Player @@ -200,7 +252,7 @@ object OpenGotha { } - + diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/util/XmlUtils.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/util/XmlUtils.kt index f2de090..f4b5edd 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/util/XmlUtils.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/util/XmlUtils.kt @@ -276,7 +276,7 @@ object XmlUtils { * @param xml xml string * @return The document object */ - fun parse(xml: String): Element = parse(StringReader(xml)) + fun parse(xml: String): Element = parse(StringReader(xml.trim())) /** * Search for nodes using an XPath expression diff --git a/api-webapp/src/main/resources/xsd/opengotha.xsd b/api-webapp/src/main/resources/xsd/opengotha.xsd index aac7b3d..00c8ece 100644 --- a/api-webapp/src/main/resources/xsd/opengotha.xsd +++ b/api-webapp/src/main/resources/xsd/opengotha.xsd @@ -146,7 +146,7 @@ - + diff --git a/api-webapp/src/test/kotlin/ImportExportTests.kt b/api-webapp/src/test/kotlin/ImportExportTests.kt index c18acb5..218e71d 100644 --- a/api-webapp/src/test/kotlin/ImportExportTests.kt +++ b/api-webapp/src/test/kotlin/ImportExportTests.kt @@ -1,12 +1,19 @@ package org.jeudego.pairgoth.test import org.jeudego.pairgoth.ext.OpenGotha +import org.jeudego.pairgoth.model.toJson import org.jeudego.pairgoth.util.XmlUtils import org.junit.jupiter.api.Test import java.nio.charset.StandardCharsets +import kotlin.test.assertEquals class ImportExportTests: TestBase() { + companion object { + val maskIdRegex = Regex("(?<=\"id\" ?: )\\d+") + } + + /* @Test fun `001 test imports`() { getTestResources("opengotha/tournamentfiles/").forEach { file -> @@ -27,18 +34,23 @@ class ImportExportTests: TestBase() { } } + */ + @Test fun `002 test opengotha import export`() { // We import a tournament // Check that after exporting and reimporting we get the same pairgoth tournament object - getTestResources("opengotha").forEach { file -> + getTestResources("opengotha/tournamentfiles").forEach { file -> val resource = file.readText(StandardCharsets.UTF_8) val root_xml = XmlUtils.parse(resource) val tournament = OpenGotha.import(root_xml) + val jsonTournament = tournament.toJson().toPrettyString()!!.replace(maskIdRegex, "0") val exported = OpenGotha.export(tournament) val tournament2 = OpenGotha.import(XmlUtils.parse(exported)) - assert(tournament == tournament2) + val jsonTournament2 = tournament2.toJson().toPrettyString()!!.replace(maskIdRegex, "0") + + assertEquals(jsonTournament, jsonTournament2) } } }