Merge branch 'pairing2' of gitlab.jeudego.org:tournois/pairgoth into pairing2
This commit is contained in:
@@ -12,9 +12,28 @@ import java.util.*
|
|||||||
import javax.xml.datatype.XMLGregorianCalendar
|
import javax.xml.datatype.XMLGregorianCalendar
|
||||||
|
|
||||||
private const val MILLISECONDS_PER_DAY = 86400000
|
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 {
|
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<*> {
|
fun import(element: Element): Tournament<*> {
|
||||||
|
|
||||||
val context = JAXBContext.newInstance(ObjectFactory::class.java)
|
val context = JAXBContext.newInstance(ObjectFactory::class.java)
|
||||||
@@ -28,6 +47,57 @@ object OpenGotha {
|
|||||||
val placmtParams = ogTournament.tournamentParameterSet.placementParameterSet
|
val placmtParams = ogTournament.tournamentParameterSet.placementParameterSet
|
||||||
val pairParams = ogTournament.tournamentParameterSet.pairingParameterSet
|
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(
|
val tournament = StandardTournament(
|
||||||
id = Store.nextTournamentId,
|
id = Store.nextTournamentId,
|
||||||
type = Tournament.Type.INDIVIDUAL, // CB for now, TODO
|
type = Tournament.Type.INDIVIDUAL, // CB for now, TODO
|
||||||
@@ -46,26 +116,8 @@ object OpenGotha {
|
|||||||
else -> throw Error("missing byoyomi type")
|
else -> throw Error("missing byoyomi type")
|
||||||
},
|
},
|
||||||
pairing = when (handParams.hdCeiling) {
|
pairing = when (handParams.hdCeiling) {
|
||||||
0 -> Swiss(
|
0 -> Swiss(pairingParams = pairgothPairingParams, placementParams = pairgothPlacementParams)
|
||||||
pairingParams = PairingParams(
|
else -> MacMahon(pairingParams = pairgothPairingParams, placementParams = pairgothPlacementParams)
|
||||||
|
|
||||||
),
|
|
||||||
placementParams = PlacementParams(
|
|
||||||
crit = placmtParams.placementCriteria.placementCriterion.filter {
|
|
||||||
it.name != "NULL"
|
|
||||||
}.map {
|
|
||||||
Criterion.valueOf(it.name)
|
|
||||||
}.toTypedArray()
|
|
||||||
)
|
|
||||||
) // TODO
|
|
||||||
else -> MacMahon(
|
|
||||||
pairingParams = PairingParams(
|
|
||||||
|
|
||||||
),
|
|
||||||
placementParams = PlacementParams(
|
|
||||||
|
|
||||||
)
|
|
||||||
) // TODO
|
|
||||||
},
|
},
|
||||||
rounds = genParams.numberOfRounds
|
rounds = genParams.numberOfRounds
|
||||||
)
|
)
|
||||||
@@ -123,7 +175,7 @@ object OpenGotha {
|
|||||||
// method 2 (quick and dirty) is to rely on templating:
|
// method 2 (quick and dirty) is to rely on templating:
|
||||||
val xml = """
|
val xml = """
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<Tournament dataVersion="201" externalIPAddress="88.122.144.219" fullVersionNumber="3.51" runningMode="SAL" saveDT="20210111180800">
|
<Tournament dataVersion="201" externalIPAddress="127.0.0.1" fullVersionNumber="3.51" runningMode="SAL" saveDT="20210111180800">
|
||||||
<Players>
|
<Players>
|
||||||
${tournament.pairables.values.map { player ->
|
${tournament.pairables.values.map { player ->
|
||||||
player as Player
|
player as Player
|
||||||
@@ -200,7 +252,7 @@ object OpenGotha {
|
|||||||
}
|
}
|
||||||
</PlacementCriteria>
|
</PlacementCriteria>
|
||||||
</PlacementParameterSet>
|
</PlacementParameterSet>
|
||||||
<PairingParameterSet paiBaAvoidDuplGame="${tournament.pairing.pairingParams.base.dupWeight.toInt()}" paiBaBalanceWB="${tournament.pairing.pairingParams.base.colorBalanceWeight.toInt()}" paiBaDeterministic="${tournament.pairing.pairingParams.base.deterministic}" paiBaRandom="${tournament.pairing.pairingParams.base.random.toInt()}" paiMaAdditionalPlacementCritSystem1="Rating" paiMaAdditionalPlacementCritSystem2="Rating" paiMaAvoidMixingCategories="0" paiMaCompensateDUDD="true" paiMaDUDDLowerMode="MID" paiMaDUDDUpperMode="MID" paiMaDUDDWeight="100000000" paiMaLastRoundForSeedSystem1="2" paiMaMaximizeSeeding="5000000" paiMaMinimizeScoreDifference="100000000000" paiMaSeedSystem1="SPLITANDSLIP" paiMaSeedSystem2="SPLITANDSLIP" paiSeAvoidSameGeo="0" paiSeBarThresholdActive="true" paiSeDefSecCrit="20000000000000" paiSeMinimizeHandicap="0" paiSeNbWinsThresholdActive="true" paiSePreferMMSDiffRatherThanSameClub="0" paiSePreferMMSDiffRatherThanSameCountry="0" paiSeRankThreshold="30K" paiStandardNX1Factor="0.5"/>
|
<PairingParameterSet paiBaAvoidDuplGame="${tournament.pairing.pairingParams.base.dupWeight.toLong()}" paiBaBalanceWB="${tournament.pairing.pairingParams.base.colorBalanceWeight.toLong()}" paiBaDeterministic="${tournament.pairing.pairingParams.base.deterministic}" paiBaRandom="${tournament.pairing.pairingParams.base.random.toLong()}" paiMaAdditionalPlacementCritSystem1="${tournament.pairing.pairingParams.main.additionalPlacementCritSystem1.toString().titlecase()}" paiMaAdditionalPlacementCritSystem2="${tournament.pairing.pairingParams.main.additionalPlacementCritSystem2.toString().titlecase()}" paiMaAvoidMixingCategories="${tournament.pairing.pairingParams.main.categoriesWeight.toLong()}" paiMaCompensateDUDD="${tournament.pairing.pairingParams.main.compensateDrawUpDown}" paiMaDUDDLowerMode="${tournament.pairing.pairingParams.main.drawUpDownLowerMode.toString().substring(0, 3)}" paiMaDUDDUpperMode="${tournament.pairing.pairingParams.main.drawUpDownUpperMode.toString().substring(0, 3)}" paiMaDUDDWeight="${tournament.pairing.pairingParams.main.drawUpDownWeight.toLong()}" paiMaLastRoundForSeedSystem1="${tournament.pairing.pairingParams.main.lastRoundForSeedSystem1}" paiMaMaximizeSeeding="${tournament.pairing.pairingParams.main.seedingWeight.toLong()}" paiMaMinimizeScoreDifference="${tournament.pairing.pairingParams.main.scoreWeight.toLong()}" paiMaSeedSystem1="${tournament.pairing.pairingParams.main.seedSystem1.format()}" paiMaSeedSystem2="${tournament.pairing.pairingParams.main.seedSystem2.format()}" paiSeAvoidSameGeo="${tournament.pairing.pairingParams.geo.avoidSameGeo.toLong()}" paiSeBarThresholdActive="${tournament.pairing.pairingParams.secondary.barThresholdActive}" paiSeDefSecCrit="${tournament.pairing.pairingParams.secondary.defSecCrit.toLong()}" paiSeMinimizeHandicap="${tournament.pairing.pairingParams.handicap.weight.toLong()}" paiSeNbWinsThresholdActive="${tournament.pairing.pairingParams.secondary.nbWinsThresholdActive}" paiSePreferMMSDiffRatherThanSameClub="${tournament.pairing.pairingParams.geo.preferMMSDiffRatherThanSameClub}" paiSePreferMMSDiffRatherThanSameCountry="${tournament.pairing.pairingParams.geo.preferMMSDiffRatherThanSameCountry}" paiSeRankThreshold="${displayRank(tournament.pairing.pairingParams.secondary.rankThreshold).uppercase()}" paiStandardNX1Factor="${tournament.pairing.pairingParams.base.nx1}"/>
|
||||||
<DPParameterSet displayClCol="true" displayCoCol="true" displayIndGamesInMatches="true" displayNPPlayers="false" displayNumCol="true" displayPlCol="true" gameFormat="short" playerSortType="name" showByePlayer="true" showNotFinallyRegisteredPlayers="true" showNotPairedPlayers="true" showNotParticipatingPlayers="false" showPlayerClub="true" showPlayerCountry="false" showPlayerGrade="true"/>
|
<DPParameterSet displayClCol="true" displayCoCol="true" displayIndGamesInMatches="true" displayNPPlayers="false" displayNumCol="true" displayPlCol="true" gameFormat="short" playerSortType="name" showByePlayer="true" showNotFinallyRegisteredPlayers="true" showNotPairedPlayers="true" showNotParticipatingPlayers="false" showPlayerClub="true" showPlayerCountry="false" showPlayerGrade="true"/>
|
||||||
<PublishParameterSet exportToLocalFile="true" htmlAutoScroll="false" print="false"/>
|
<PublishParameterSet exportToLocalFile="true" htmlAutoScroll="false" print="false"/>
|
||||||
</TournamentParameterSet>
|
</TournamentParameterSet>
|
||||||
|
@@ -42,9 +42,9 @@ fun Game.toJson() = Json.Object(
|
|||||||
|
|
||||||
fun Game.Companion.fromJson(json: Json.Object) = Game(
|
fun Game.Companion.fromJson(json: Json.Object) = Game(
|
||||||
id = json.getID("id") ?: throw Error("missing game id"),
|
id = json.getID("id") ?: throw Error("missing game id"),
|
||||||
white = json.getID("white") ?: throw Error("missing white player"),
|
white = json.getID("w") ?: throw Error("missing white player"),
|
||||||
black = json.getID("black") ?: throw Error("missing black player"),
|
black = json.getID("b") ?: throw Error("missing black player"),
|
||||||
handicap = json.getInt("handicap") ?: 0,
|
handicap = json.getInt("h") ?: 0,
|
||||||
result = json.getChar("result")?.let { Game.Result.fromSymbol(it) } ?: UNKNOWN,
|
result = json.getChar("r")?.let { Game.Result.fromSymbol(it) } ?: UNKNOWN,
|
||||||
drawnUpDown = json.getInt("drawnUpDown") ?: 0
|
drawnUpDown = json.getInt("dd") ?: 0
|
||||||
)
|
)
|
||||||
|
@@ -32,7 +32,7 @@ open class HistoryHelper(protected val history: List<List<Game>>, scoresGetter:
|
|||||||
}).toSet()
|
}).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
|
// Only count games without handicap
|
||||||
private val colorBalance: Map<ID, Int> by lazy {
|
private val colorBalance: Map<ID, Int> by lazy {
|
||||||
history.flatten().filter { game ->
|
history.flatten().filter { game ->
|
||||||
|
@@ -430,12 +430,15 @@ sealed class Solver(
|
|||||||
// placeInGroup (of same score) : Pair(place, groupSize)
|
// placeInGroup (of same score) : Pair(place, groupSize)
|
||||||
private val Pairable.placeInGroup: Pair<Int, Int> get() = _placeInGroup[id]!!
|
private val Pairable.placeInGroup: Pair<Int, Int> get() = _placeInGroup[id]!!
|
||||||
private val _placeInGroup by lazy {
|
private val _placeInGroup by lazy {
|
||||||
|
// group by group number
|
||||||
sortedPairables.groupBy {
|
sortedPairables.groupBy {
|
||||||
it.group
|
it.group
|
||||||
|
// get a list { id { placeInGroup, groupSize } }
|
||||||
}.values.flatMap { group ->
|
}.values.flatMap { group ->
|
||||||
group.mapIndexed { index, pairable ->
|
group.mapIndexed { index, pairable ->
|
||||||
Pair(pairable.id, Pair(index, group.size))
|
Pair(pairable.id, Pair(index, group.size))
|
||||||
}
|
}
|
||||||
|
// get a map id -> { placeInGroup, groupSize }
|
||||||
}.toMap()
|
}.toMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -276,7 +276,7 @@ object XmlUtils {
|
|||||||
* @param xml xml string
|
* @param xml xml string
|
||||||
* @return The document object
|
* @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
|
* Search for nodes using an XPath expression
|
||||||
|
@@ -146,7 +146,7 @@
|
|||||||
<xs:attribute type="xs:int" name="paiSePreferMMSDiffRatherThanSameClub"/>
|
<xs:attribute type="xs:int" name="paiSePreferMMSDiffRatherThanSameClub"/>
|
||||||
<xs:attribute type="xs:int" name="paiSePreferMMSDiffRatherThanSameCountry"/>
|
<xs:attribute type="xs:int" name="paiSePreferMMSDiffRatherThanSameCountry"/>
|
||||||
<xs:attribute type="xs:string" name="paiSeRankThreshold"/>
|
<xs:attribute type="xs:string" name="paiSeRankThreshold"/>
|
||||||
<xs:attribute type="xs:float" name="paiStandardNX1Factor"/>
|
<xs:attribute type="xs:double" name="paiStandardNX1Factor"/>
|
||||||
</xs:extension>
|
</xs:extension>
|
||||||
</xs:simpleContent>
|
</xs:simpleContent>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
@@ -1,12 +1,19 @@
|
|||||||
package org.jeudego.pairgoth.test
|
package org.jeudego.pairgoth.test
|
||||||
|
|
||||||
import org.jeudego.pairgoth.ext.OpenGotha
|
import org.jeudego.pairgoth.ext.OpenGotha
|
||||||
|
import org.jeudego.pairgoth.model.toJson
|
||||||
import org.jeudego.pairgoth.util.XmlUtils
|
import org.jeudego.pairgoth.util.XmlUtils
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class ImportExportTests: TestBase() {
|
class ImportExportTests: TestBase() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val maskIdRegex = Regex("(?<=\"id\" ?: )\\d+")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
@Test
|
@Test
|
||||||
fun `001 test imports`() {
|
fun `001 test imports`() {
|
||||||
getTestResources("opengotha/tournamentfiles/").forEach { file ->
|
getTestResources("opengotha/tournamentfiles/").forEach { file ->
|
||||||
@@ -27,18 +34,23 @@ class ImportExportTests: TestBase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `002 test opengotha import export`() {
|
fun `002 test opengotha import export`() {
|
||||||
// We import a tournament
|
// We import a tournament
|
||||||
// Check that after exporting and reimporting we get the same pairgoth tournament object
|
// 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 resource = file.readText(StandardCharsets.UTF_8)
|
||||||
val root_xml = XmlUtils.parse(resource)
|
val root_xml = XmlUtils.parse(resource)
|
||||||
val tournament = OpenGotha.import(root_xml)
|
val tournament = OpenGotha.import(root_xml)
|
||||||
|
val jsonTournament = tournament.toJson().toPrettyString()!!.replace(maskIdRegex, "0")
|
||||||
|
|
||||||
val exported = OpenGotha.export(tournament)
|
val exported = OpenGotha.export(tournament)
|
||||||
val tournament2 = OpenGotha.import(XmlUtils.parse(exported))
|
val tournament2 = OpenGotha.import(XmlUtils.parse(exported))
|
||||||
assert(tournament == tournament2)
|
val jsonTournament2 = tournament2.toJson().toPrettyString()!!.replace(maskIdRegex, "0")
|
||||||
|
|
||||||
|
assertEquals(jsonTournament, jsonTournament2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user