Standings page in progress
This commit is contained in:
@@ -1,4 +1,92 @@
|
|||||||
package org.jeudego.pairgoth.api
|
package org.jeudego.pairgoth.api
|
||||||
|
|
||||||
|
import com.republicate.kson.Json
|
||||||
|
import com.republicate.kson.toJsonArray
|
||||||
|
import org.jeudego.pairgoth.model.Criterion
|
||||||
|
import org.jeudego.pairgoth.model.MacMahon
|
||||||
|
import org.jeudego.pairgoth.model.Pairable
|
||||||
|
import org.jeudego.pairgoth.model.PairingType
|
||||||
|
import org.jeudego.pairgoth.model.historyBefore
|
||||||
|
import org.jeudego.pairgoth.pairing.HistoryHelper
|
||||||
|
import org.jeudego.pairgoth.pairing.solver.MacMahonSolver
|
||||||
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
import javax.servlet.http.HttpServletResponse
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
import org.jeudego.pairgoth.model.Criterion.*
|
||||||
|
import org.jeudego.pairgoth.model.ID
|
||||||
|
import org.jeudego.pairgoth.model.getID
|
||||||
|
|
||||||
object StandingsHandler: PairgothApiHandler {
|
object StandingsHandler: PairgothApiHandler {
|
||||||
|
override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
||||||
|
val tournament = getTournament(request)
|
||||||
|
val round = getSubSelector(request)?.toIntOrNull() ?: ApiHandler.badRequest("invalid round number")
|
||||||
|
|
||||||
|
fun mmBase(pairable: Pairable): Double {
|
||||||
|
if (tournament.pairing !is MacMahon) throw Error("invalid call: tournament is not Mac Mahon")
|
||||||
|
return min(max(pairable.rank, tournament.pairing.mmFloor), tournament.pairing.mmBar) + MacMahonSolver.mmsZero
|
||||||
|
}
|
||||||
|
|
||||||
|
val historyHelper = HistoryHelper(tournament.historyBefore(round)) {
|
||||||
|
if (tournament.pairing.type == PairingType.SWISS) wins
|
||||||
|
else tournament.pairables.mapValues {
|
||||||
|
it.value.let {
|
||||||
|
pairable -> mmBase(pairable) + ( nbW(pairable) ?: 0.0) // TODO take tournament parameter into account
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val criteria = tournament.pairing.placementParams.criteria.map { crit ->
|
||||||
|
crit.name to when (crit) {
|
||||||
|
NONE -> nullMap
|
||||||
|
CATEGORY -> nullMap
|
||||||
|
RANK -> tournament.pairables.mapValues { it.value.rank }
|
||||||
|
RATING -> tournament.pairables.mapValues { it.value.rating }
|
||||||
|
NBW -> historyHelper.wins
|
||||||
|
MMS -> historyHelper.scores
|
||||||
|
STS -> nullMap
|
||||||
|
CPS -> nullMap
|
||||||
|
|
||||||
|
SOSW -> historyHelper.sos
|
||||||
|
SOSWM1 -> historyHelper.sosm1
|
||||||
|
SOSWM2 -> historyHelper.sosm2
|
||||||
|
SODOSW -> historyHelper.sodos
|
||||||
|
SOSOSW -> historyHelper.sosos
|
||||||
|
CUSSW -> historyHelper.cumScore
|
||||||
|
SOSM -> historyHelper.sos
|
||||||
|
SOSMM1 -> historyHelper.sosm1
|
||||||
|
SOSMM2 -> historyHelper.sosm2
|
||||||
|
SODOSM -> historyHelper.sodos
|
||||||
|
SOSOSM -> historyHelper.sosos
|
||||||
|
CUSSM -> historyHelper.cumScore
|
||||||
|
|
||||||
|
SOSTS -> nullMap
|
||||||
|
|
||||||
|
EXT -> nullMap
|
||||||
|
EXR -> nullMap
|
||||||
|
|
||||||
|
SDC -> nullMap
|
||||||
|
DC -> nullMap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val pairables = tournament.pairables.values.map { it.toMutableJson() }
|
||||||
|
pairables.forEach { it ->
|
||||||
|
val player = it as Json.MutableObject
|
||||||
|
for (crit in criteria) {
|
||||||
|
player[crit.first] = crit.second[player.getID()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pairables.sortedWith { left, right ->
|
||||||
|
for (crit in criteria) {
|
||||||
|
val lval = left.getDouble(crit.first) ?: 0.0
|
||||||
|
val rval = right.getDouble(crit.first) ?: 0.0
|
||||||
|
val cmp = lval.compareTo(rval)
|
||||||
|
if (cmp != 0) return@sortedWith -cmp
|
||||||
|
}
|
||||||
|
return@sortedWith 0
|
||||||
|
|
||||||
|
}.toJsonArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
val nullMap = mapOf<ID, Double>()
|
||||||
}
|
}
|
||||||
|
@@ -7,5 +7,5 @@ typealias ID = Int
|
|||||||
fun String.toID() = toInt()
|
fun String.toID() = toInt()
|
||||||
fun String.toIDOrNull() = toIntOrNull()
|
fun String.toIDOrNull() = toIntOrNull()
|
||||||
fun Number.toID() = toInt()
|
fun Number.toID() = toInt()
|
||||||
fun Json.Object.getID(key: String) = getInt(key)
|
fun Json.Object.getID(key: String = "id") = getInt(key)
|
||||||
fun Json.Array.getID(index: Int) = getInt(index)
|
fun Json.Array.getID(index: Int) = getInt(index)
|
@@ -13,6 +13,7 @@ sealed class Pairable(val id: ID, val name: String, open val rating: Int, open v
|
|||||||
val MAX_RANK: Int = 8 // 9D
|
val MAX_RANK: Int = 8 // 9D
|
||||||
}
|
}
|
||||||
abstract fun toJson(): Json.Object
|
abstract fun toJson(): Json.Object
|
||||||
|
abstract fun toMutableJson(): Json.MutableObject
|
||||||
abstract val club: String?
|
abstract val club: String?
|
||||||
abstract val country: String?
|
abstract val country: String?
|
||||||
open fun nameSeed(separator: String =" "): String {
|
open fun nameSeed(separator: String =" "): String {
|
||||||
@@ -29,6 +30,9 @@ object ByePlayer: Pairable(0, "bye", 0, Int.MIN_VALUE) {
|
|||||||
override fun toJson(): Json.Object {
|
override fun toJson(): Json.Object {
|
||||||
throw Error("bye player should never be serialized")
|
throw Error("bye player should never be serialized")
|
||||||
}
|
}
|
||||||
|
override fun toMutableJson(): Json.MutableObject {
|
||||||
|
throw Error("bye player should never be serialized")
|
||||||
|
}
|
||||||
|
|
||||||
override val club = "none"
|
override val club = "none"
|
||||||
override val country = "none"
|
override val country = "none"
|
||||||
@@ -71,7 +75,7 @@ class Player(
|
|||||||
companion object
|
companion object
|
||||||
// used to store external IDs ("FFG" => FFG ID, "EGF" => EGF PIN, "AGA" => AGA ID ...)
|
// used to store external IDs ("FFG" => FFG ID, "EGF" => EGF PIN, "AGA" => AGA ID ...)
|
||||||
val externalIds = mutableMapOf<DatabaseId, String>()
|
val externalIds = mutableMapOf<DatabaseId, String>()
|
||||||
override fun toJson(): Json.Object = Json.MutableObject(
|
override fun toMutableJson() = Json.MutableObject(
|
||||||
"id" to id,
|
"id" to id,
|
||||||
"name" to name,
|
"name" to name,
|
||||||
"firstname" to firstname,
|
"firstname" to firstname,
|
||||||
@@ -85,6 +89,8 @@ class Player(
|
|||||||
json[dbid.key] = id
|
json[dbid.key] = id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toJson(): Json.Object = toMutableJson()
|
||||||
override fun nameSeed(separator: String): String {
|
override fun nameSeed(separator: String): String {
|
||||||
return name + separator + firstname
|
return name + separator + firstname
|
||||||
}
|
}
|
||||||
|
@@ -129,7 +129,7 @@ sealed class Pairing(
|
|||||||
abstract fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): List<Game>
|
abstract fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): List<Game>
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Tournament<*>.historyBefore(round: Int) =
|
internal fun Tournament<*>.historyBefore(round: Int) =
|
||||||
if (lastRound() == 1) emptyList()
|
if (lastRound() == 1) emptyList()
|
||||||
else (1 until round).map { games(it).values.toList() }
|
else (1 until round).map { games(it).values.toList() }
|
||||||
|
|
||||||
@@ -212,12 +212,12 @@ class RoundRobin(
|
|||||||
|
|
||||||
// Serialization
|
// Serialization
|
||||||
|
|
||||||
fun BaseCritParams.Companion.fromJson(json: Json.Object) = BaseCritParams(
|
fun BaseCritParams.Companion.fromJson(json: Json.Object, localDefault: BaseCritParams? = null) = BaseCritParams(
|
||||||
nx1 = json.getDouble("nx1") ?: default.nx1,
|
nx1 = json.getDouble("nx1") ?: localDefault?.nx1 ?: default.nx1,
|
||||||
dupWeight = json.getDouble("dupWeight") ?: default.dupWeight,
|
dupWeight = json.getDouble("dupWeight") ?: localDefault?.dupWeight ?: default.dupWeight,
|
||||||
random = json.getDouble("random") ?: default.random,
|
random = json.getDouble("random") ?: localDefault?.random ?: default.random,
|
||||||
deterministic = json.getBoolean("deterministic") ?: default.deterministic,
|
deterministic = json.getBoolean("deterministic") ?: localDefault?.deterministic ?: default.deterministic,
|
||||||
colorBalanceWeight = json.getDouble("colorBalanceWeight") ?: default.colorBalanceWeight
|
colorBalanceWeight = json.getDouble("colorBalanceWeight") ?: localDefault?.colorBalanceWeight ?: default.colorBalanceWeight
|
||||||
)
|
)
|
||||||
|
|
||||||
fun BaseCritParams.toJson() = Json.Object(
|
fun BaseCritParams.toJson() = Json.Object(
|
||||||
@@ -227,19 +227,19 @@ fun BaseCritParams.toJson() = Json.Object(
|
|||||||
"colorBalanceWeight" to colorBalanceWeight
|
"colorBalanceWeight" to colorBalanceWeight
|
||||||
)
|
)
|
||||||
|
|
||||||
fun MainCritParams.Companion.fromJson(json: Json.Object) = MainCritParams(
|
fun MainCritParams.Companion.fromJson(json: Json.Object, localDefault: MainCritParams? = null) = MainCritParams(
|
||||||
categoriesWeight = json.getDouble("catWeight") ?: default.categoriesWeight,
|
categoriesWeight = json.getDouble("catWeight") ?: localDefault?.categoriesWeight ?: default.categoriesWeight,
|
||||||
scoreWeight = json.getDouble("scoreWeight") ?: default.scoreWeight,
|
scoreWeight = json.getDouble("scoreWeight") ?: localDefault?.scoreWeight ?: default.scoreWeight,
|
||||||
drawUpDownWeight = json.getDouble("upDownWeight") ?: default.drawUpDownWeight,
|
drawUpDownWeight = json.getDouble("upDownWeight") ?: localDefault?.drawUpDownWeight ?: default.drawUpDownWeight,
|
||||||
compensateDrawUpDown = json.getBoolean("upDownCompensate") ?: default.compensateDrawUpDown,
|
compensateDrawUpDown = json.getBoolean("upDownCompensate") ?: localDefault?.compensateDrawUpDown ?: default.compensateDrawUpDown,
|
||||||
drawUpDownLowerMode = json.getString("upDownLowerMode")?.let { MainCritParams.DrawUpDown.valueOf(it) } ?: default.drawUpDownLowerMode,
|
drawUpDownLowerMode = json.getString("upDownLowerMode")?.let { MainCritParams.DrawUpDown.valueOf(it) } ?: localDefault?.drawUpDownLowerMode ?: default.drawUpDownLowerMode,
|
||||||
drawUpDownUpperMode = json.getString("upDownUpperMode")?.let { MainCritParams.DrawUpDown.valueOf(it) } ?: default.drawUpDownUpperMode,
|
drawUpDownUpperMode = json.getString("upDownUpperMode")?.let { MainCritParams.DrawUpDown.valueOf(it) } ?: localDefault?.drawUpDownUpperMode ?: default.drawUpDownUpperMode,
|
||||||
seedingWeight = json.getDouble("maximizeSeeding") ?: default.seedingWeight,
|
seedingWeight = json.getDouble("maximizeSeeding") ?: localDefault?.seedingWeight ?: default.seedingWeight,
|
||||||
lastRoundForSeedSystem1 = json.getInt("firstSeedLastRound") ?: default.lastRoundForSeedSystem1,
|
lastRoundForSeedSystem1 = json.getInt("firstSeedLastRound") ?: localDefault?.lastRoundForSeedSystem1 ?: default.lastRoundForSeedSystem1,
|
||||||
seedSystem1 = json.getString("firstSeed")?.let { MainCritParams.SeedMethod.valueOf(it) } ?: default.seedSystem1,
|
seedSystem1 = json.getString("firstSeed")?.let { MainCritParams.SeedMethod.valueOf(it) } ?: localDefault?.seedSystem1 ?: default.seedSystem1,
|
||||||
seedSystem2 = json.getString("secondSeed")?.let { MainCritParams.SeedMethod.valueOf(it) } ?: default.seedSystem2,
|
seedSystem2 = json.getString("secondSeed")?.let { MainCritParams.SeedMethod.valueOf(it) } ?: localDefault?.seedSystem2 ?: default.seedSystem2,
|
||||||
additionalPlacementCritSystem1 = json.getString("firstSeedAddCrit")?.let { Criterion.valueOf(it) } ?: default.additionalPlacementCritSystem1,
|
additionalPlacementCritSystem1 = json.getString("firstSeedAddCrit")?.let { Criterion.valueOf(it) } ?: localDefault?.additionalPlacementCritSystem1 ?: default.additionalPlacementCritSystem1,
|
||||||
additionalPlacementCritSystem2 = json.getString("secondSeedAddCrit")?.let { Criterion.valueOf(it) } ?: default.additionalPlacementCritSystem2
|
additionalPlacementCritSystem2 = json.getString("secondSeedAddCrit")?.let { Criterion.valueOf(it) } ?: localDefault?.additionalPlacementCritSystem2 ?: default.additionalPlacementCritSystem2
|
||||||
)
|
)
|
||||||
|
|
||||||
fun MainCritParams.toJson() = Json.Object(
|
fun MainCritParams.toJson() = Json.Object(
|
||||||
@@ -257,11 +257,11 @@ fun MainCritParams.toJson() = Json.Object(
|
|||||||
"secondSeedAddCrit" to additionalPlacementCritSystem2
|
"secondSeedAddCrit" to additionalPlacementCritSystem2
|
||||||
)
|
)
|
||||||
|
|
||||||
fun SecondaryCritParams.Companion.fromJson(json: Json.Object) = SecondaryCritParams(
|
fun SecondaryCritParams.Companion.fromJson(json: Json.Object, localDefault: SecondaryCritParams? = null) = SecondaryCritParams(
|
||||||
barThresholdActive = json.getBoolean("barThreshold") ?: default.barThresholdActive,
|
barThresholdActive = json.getBoolean("barThreshold") ?: localDefault?.barThresholdActive ?: default.barThresholdActive,
|
||||||
rankThreshold = json.getInt("rankThreshold") ?: default.rankThreshold,
|
rankThreshold = json.getInt("rankThreshold") ?: localDefault?.rankThreshold ?: default.rankThreshold,
|
||||||
nbWinsThresholdActive = json.getBoolean("winsThreshold") ?: default.nbWinsThresholdActive,
|
nbWinsThresholdActive = json.getBoolean("winsThreshold") ?: localDefault?.nbWinsThresholdActive ?: default.nbWinsThresholdActive,
|
||||||
defSecCrit = json.getDouble("secWeight") ?: default.defSecCrit
|
defSecCrit = json.getDouble("secWeight") ?: localDefault?.defSecCrit ?: default.defSecCrit
|
||||||
)
|
)
|
||||||
|
|
||||||
fun SecondaryCritParams.toJson() = Json.Object(
|
fun SecondaryCritParams.toJson() = Json.Object(
|
||||||
@@ -271,11 +271,11 @@ fun SecondaryCritParams.toJson() = Json.Object(
|
|||||||
"secWeight" to defSecCrit
|
"secWeight" to defSecCrit
|
||||||
)
|
)
|
||||||
|
|
||||||
fun GeographicalParams.Companion.fromJson(json: Json.Object) = GeographicalParams(
|
fun GeographicalParams.Companion.fromJson(json: Json.Object, localDefault: GeographicalParams? = null) = GeographicalParams(
|
||||||
avoidSameGeo = json.getDouble("weight") ?: disabled.avoidSameGeo,
|
avoidSameGeo = json.getDouble("weight") ?: localDefault?.avoidSameGeo ?: disabled.avoidSameGeo,
|
||||||
preferMMSDiffRatherThanSameCountry = json.getInt("mmsDiffCountry") ?: disabled.preferMMSDiffRatherThanSameCountry,
|
preferMMSDiffRatherThanSameCountry = json.getInt("mmsDiffCountry") ?: localDefault?.preferMMSDiffRatherThanSameCountry ?: disabled.preferMMSDiffRatherThanSameCountry,
|
||||||
preferMMSDiffRatherThanSameClubsGroup = json.getInt("mmsDiffClubGroup") ?: disabled.preferMMSDiffRatherThanSameClubsGroup,
|
preferMMSDiffRatherThanSameClubsGroup = json.getInt("mmsDiffClubGroup") ?: localDefault?.preferMMSDiffRatherThanSameClub ?: disabled.preferMMSDiffRatherThanSameClubsGroup,
|
||||||
preferMMSDiffRatherThanSameClub = json.getInt("mmsDiffClub") ?: disabled.preferMMSDiffRatherThanSameClub
|
preferMMSDiffRatherThanSameClub = json.getInt("mmsDiffClub") ?: localDefault?.preferMMSDiffRatherThanSameClub ?: disabled.preferMMSDiffRatherThanSameClub
|
||||||
)
|
)
|
||||||
|
|
||||||
fun GeographicalParams.toJson() = Json.Object(
|
fun GeographicalParams.toJson() = Json.Object(
|
||||||
@@ -285,12 +285,12 @@ fun GeographicalParams.toJson() = Json.Object(
|
|||||||
"mmsDiffClub" to preferMMSDiffRatherThanSameClub
|
"mmsDiffClub" to preferMMSDiffRatherThanSameClub
|
||||||
)
|
)
|
||||||
|
|
||||||
fun HandicapParams.Companion.fromJson(json: Json.Object, type: PairingType) = HandicapParams(
|
fun HandicapParams.Companion.fromJson(json: Json.Object, type: PairingType, localDefault: HandicapParams? = null) = HandicapParams(
|
||||||
weight = json.getDouble("weight") ?: default(type).weight,
|
weight = json.getDouble("weight") ?: localDefault?.weight ?: default(type).weight,
|
||||||
useMMS = json.getBoolean("useMMS") ?: default(type).useMMS,
|
useMMS = json.getBoolean("useMMS") ?: localDefault?.useMMS ?: default(type).useMMS,
|
||||||
rankThreshold = json.getInt("threshold") ?: default(type).rankThreshold,
|
rankThreshold = json.getInt("threshold") ?: localDefault?.rankThreshold ?: default(type).rankThreshold,
|
||||||
correction = json.getInt("correction") ?: default(type).correction,
|
correction = json.getInt("correction") ?: localDefault?.correction ?: default(type).correction,
|
||||||
ceiling = json.getInt("ceiling") ?: default(type).ceiling
|
ceiling = json.getInt("ceiling") ?: localDefault?.ceiling ?: default(type).ceiling
|
||||||
)
|
)
|
||||||
|
|
||||||
fun HandicapParams.toJson() = Json.Object(
|
fun HandicapParams.toJson() = Json.Object(
|
||||||
@@ -301,21 +301,21 @@ fun HandicapParams.toJson() = Json.Object(
|
|||||||
"ceiling" to ceiling
|
"ceiling" to ceiling
|
||||||
)
|
)
|
||||||
|
|
||||||
fun Pairing.Companion.fromJson(json: Json.Object): Pairing {
|
fun Pairing.Companion.fromJson(json: Json.Object, default: Pairing?): Pairing {
|
||||||
// get default values for each type
|
// get default values for each type
|
||||||
val type = json.getString("type")?.let { PairingType.valueOf(it) } ?: badRequest("missing pairing type")
|
val type = json.getString("type")?.let { PairingType.valueOf(it) } ?: default?.type ?: badRequest("missing pairing type")
|
||||||
val defaultParams = when (type) {
|
val defaultParams = when (type) {
|
||||||
SWISS -> Swiss()
|
SWISS -> Swiss()
|
||||||
MAC_MAHON -> MacMahon()
|
MAC_MAHON -> MacMahon()
|
||||||
ROUND_ROBIN -> RoundRobin()
|
ROUND_ROBIN -> RoundRobin()
|
||||||
}
|
}
|
||||||
val base = json.getObject("base")?.let { BaseCritParams.fromJson(it) } ?: defaultParams.pairingParams.base
|
val base = json.getObject("base")?.let { BaseCritParams.fromJson(it, default?.pairingParams?.base) } ?: default?.pairingParams?.base ?: defaultParams.pairingParams.base
|
||||||
val main = json.getObject("main")?.let { MainCritParams.fromJson(it) } ?: defaultParams.pairingParams.main
|
val main = json.getObject("main")?.let { MainCritParams.fromJson(it, default?.pairingParams?.main) } ?: default?.pairingParams?.main ?: defaultParams.pairingParams.main
|
||||||
val secondary = json.getObject("secondary")?.let { SecondaryCritParams.fromJson(it) } ?: defaultParams.pairingParams.secondary
|
val secondary = json.getObject("secondary")?.let { SecondaryCritParams.fromJson(it, default?.pairingParams?.secondary) } ?: default?.pairingParams?.secondary ?: defaultParams.pairingParams.secondary
|
||||||
val geo = json.getObject("geo")?.let { GeographicalParams.fromJson(it) } ?: defaultParams.pairingParams.geo
|
val geo = json.getObject("geo")?.let { GeographicalParams.fromJson(it, default?.pairingParams?.geo) } ?: default?.pairingParams?.geo ?: defaultParams.pairingParams.geo
|
||||||
val hd = json.getObject("handicap")?.let { HandicapParams.fromJson(it, type) } ?: defaultParams.pairingParams.handicap
|
val hd = json.getObject("handicap")?.let { HandicapParams.fromJson(it, type, default?.pairingParams?.handicap) } ?: default?.pairingParams?.handicap ?: defaultParams.pairingParams.handicap
|
||||||
val pairingParams = PairingParams(base, main, secondary, geo, hd)
|
val pairingParams = PairingParams(base, main, secondary, geo, hd)
|
||||||
val placementParams = json.getArray("placement")?.let { PlacementParams.fromJson(it) } ?: defaultParams.placementParams
|
val placementParams = json.getArray("placement")?.let { PlacementParams.fromJson(it) } ?: default?.placementParams ?: defaultParams.placementParams
|
||||||
return when (type) {
|
return when (type) {
|
||||||
SWISS -> Swiss(pairingParams, placementParams)
|
SWISS -> Swiss(pairingParams, placementParams)
|
||||||
MAC_MAHON -> MacMahon(pairingParams, placementParams).also { mm ->
|
MAC_MAHON -> MacMahon(pairingParams, placementParams).also { mm ->
|
||||||
|
@@ -117,11 +117,12 @@ class TeamTournament(
|
|||||||
override val rank: Int get() = if (teamPlayers.isEmpty()) super.rank else (teamPlayers.sumOf { player -> player.rank.toDouble() } / players.size).roundToInt()
|
override val rank: Int get() = if (teamPlayers.isEmpty()) super.rank else (teamPlayers.sumOf { player -> player.rank.toDouble() } / players.size).roundToInt()
|
||||||
override val club: String? get() = teamPlayers.map { club }.distinct().let { if (it.size == 1) it[0] else null }
|
override val club: String? get() = teamPlayers.map { club }.distinct().let { if (it.size == 1) it[0] else null }
|
||||||
override val country: String? get() = teamPlayers.map { country }.distinct().let { if (it.size == 1) it[0] else null }
|
override val country: String? get() = teamPlayers.map { country }.distinct().let { if (it.size == 1) it[0] else null }
|
||||||
override fun toJson() = Json.Object(
|
override fun toMutableJson() = Json.MutableObject(
|
||||||
"id" to id,
|
"id" to id,
|
||||||
"name" to name,
|
"name" to name,
|
||||||
"players" to playerIds.toList().toJsonArray()
|
"players" to playerIds.toList().toJsonArray()
|
||||||
)
|
)
|
||||||
|
override fun toJson(): Json.Object = toMutableJson()
|
||||||
val teamOfIndividuals: Boolean get() = type.individual
|
val teamOfIndividuals: Boolean get() = type.individual
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +161,7 @@ fun Tournament.Companion.fromJson(json: Json.Object, default: Tournament<*>? = n
|
|||||||
gobanSize = json.getInt("gobanSize") ?: default?.gobanSize ?: 19,
|
gobanSize = json.getInt("gobanSize") ?: default?.gobanSize ?: 19,
|
||||||
timeSystem = json.getObject("timeSystem")?.let { TimeSystem.fromJson(it) } ?: default?.timeSystem ?: badRequest("missing timeSystem"),
|
timeSystem = json.getObject("timeSystem")?.let { TimeSystem.fromJson(it) } ?: default?.timeSystem ?: badRequest("missing timeSystem"),
|
||||||
rounds = json.getInt("rounds") ?: default?.rounds ?: badRequest("missing rounds"),
|
rounds = json.getInt("rounds") ?: default?.rounds ?: badRequest("missing rounds"),
|
||||||
pairing = json.getObject("pairing")?.let { Pairing.fromJson(it) } ?: default?.pairing ?: badRequest("missing pairing")
|
pairing = json.getObject("pairing")?.let { Pairing.fromJson(it, default?.pairing) } ?: default?.pairing ?: badRequest("missing pairing")
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
TeamTournament(
|
TeamTournament(
|
||||||
@@ -178,7 +179,7 @@ fun Tournament.Companion.fromJson(json: Json.Object, default: Tournament<*>? = n
|
|||||||
gobanSize = json.getInt("gobanSize") ?: default?.gobanSize ?: 19,
|
gobanSize = json.getInt("gobanSize") ?: default?.gobanSize ?: 19,
|
||||||
timeSystem = json.getObject("timeSystem")?.let { TimeSystem.fromJson(it) } ?: default?.timeSystem ?: badRequest("missing timeSystem"),
|
timeSystem = json.getObject("timeSystem")?.let { TimeSystem.fromJson(it) } ?: default?.timeSystem ?: badRequest("missing timeSystem"),
|
||||||
rounds = json.getInt("rounds") ?: default?.rounds ?: badRequest("missing rounds"),
|
rounds = json.getInt("rounds") ?: default?.rounds ?: badRequest("missing rounds"),
|
||||||
pairing = json.getObject("pairing")?.let { Pairing.fromJson(it) } ?: default?.pairing ?: badRequest("missing pairing")
|
pairing = json.getObject("pairing")?.let { Pairing.fromJson(it, default?.pairing) } ?: default?.pairing ?: badRequest("missing pairing")
|
||||||
)
|
)
|
||||||
json["pairables"]?.let { pairables ->
|
json["pairables"]?.let { pairables ->
|
||||||
|
|
||||||
|
@@ -3,7 +3,7 @@ package org.jeudego.pairgoth.pairing
|
|||||||
import org.jeudego.pairgoth.model.*
|
import org.jeudego.pairgoth.model.*
|
||||||
import org.jeudego.pairgoth.model.Game.Result.*
|
import org.jeudego.pairgoth.model.Game.Result.*
|
||||||
|
|
||||||
open class HistoryHelper(protected val history: List<List<Game>>, scoresGetter: ()-> Map<ID, Double>) {
|
open class HistoryHelper(protected val history: List<List<Game>>, scoresGetter: HistoryHelper.()-> Map<ID, Double>) {
|
||||||
|
|
||||||
private val Game.blackScore get() = when (result) {
|
private val Game.blackScore get() = when (result) {
|
||||||
BLACK, BOTHWIN -> 1.0
|
BLACK, BOTHWIN -> 1.0
|
||||||
@@ -15,7 +15,9 @@ open class HistoryHelper(protected val history: List<List<Game>>, scoresGetter:
|
|||||||
else -> 0.0
|
else -> 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
private val scores by lazy(scoresGetter)
|
val scores by lazy {
|
||||||
|
scoresGetter()
|
||||||
|
}
|
||||||
|
|
||||||
// Generic helper functions
|
// Generic helper functions
|
||||||
open fun playedTogether(p1: Pairable, p2: Pairable) = paired.contains(Pair(p1.id, p2.id))
|
open fun playedTogether(p1: Pairable, p2: Pairable) = paired.contains(Pair(p1.id, p2.id))
|
||||||
@@ -172,7 +174,7 @@ open class HistoryHelper(protected val history: List<List<Game>>, scoresGetter:
|
|||||||
// CB TODO - a big problem with the current naive implementation is that the team score is -for now- the sum of team members individual scores
|
// CB TODO - a big problem with the current naive implementation is that the team score is -for now- the sum of team members individual scores
|
||||||
|
|
||||||
class TeamOfIndividualsHistoryHelper(history: List<List<Game>>, scoresGetter: () -> Map<ID, Double>):
|
class TeamOfIndividualsHistoryHelper(history: List<List<Game>>, scoresGetter: () -> Map<ID, Double>):
|
||||||
HistoryHelper(history, scoresGetter) {
|
HistoryHelper(history, { scoresGetter() }) {
|
||||||
|
|
||||||
private fun Pairable.asTeam() = this as TeamTournament.Team
|
private fun Pairable.asTeam() = this as TeamTournament.Team
|
||||||
|
|
||||||
|
@@ -13,9 +13,11 @@ class MacMahonSolver(round: Int,
|
|||||||
BaseSolver(round, history, pairables, pairingParams, placementParams) {
|
BaseSolver(round, history, pairables, pairingParams, placementParams) {
|
||||||
|
|
||||||
override val scores: Map<ID, Double> by lazy {
|
override val scores: Map<ID, Double> by lazy {
|
||||||
pairablesMap.mapValues { it.value.let {
|
pairablesMap.mapValues {
|
||||||
pairable -> pairable.mmBase + pairable.nbW // TODO take tournament parameter into account
|
it.value.let {
|
||||||
} }
|
pairable -> pairable.mmBase + pairable.nbW // TODO take tournament parameter into account
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun debug(p: Pairable) {
|
override fun debug(p: Pairable) {
|
||||||
|
@@ -5,6 +5,7 @@ import org.jeudego.pairgoth.api.ApiHandler
|
|||||||
import org.jeudego.pairgoth.api.PairingHandler
|
import org.jeudego.pairgoth.api.PairingHandler
|
||||||
import org.jeudego.pairgoth.api.PlayerHandler
|
import org.jeudego.pairgoth.api.PlayerHandler
|
||||||
import org.jeudego.pairgoth.api.ResultsHandler
|
import org.jeudego.pairgoth.api.ResultsHandler
|
||||||
|
import org.jeudego.pairgoth.api.StandingsHandler
|
||||||
import org.jeudego.pairgoth.api.TeamHandler
|
import org.jeudego.pairgoth.api.TeamHandler
|
||||||
import org.jeudego.pairgoth.api.TournamentHandler
|
import org.jeudego.pairgoth.api.TournamentHandler
|
||||||
import org.jeudego.pairgoth.util.Colorizer.blue
|
import org.jeudego.pairgoth.util.Colorizer.blue
|
||||||
@@ -87,6 +88,7 @@ class ApiServlet: HttpServlet() {
|
|||||||
"part" -> PlayerHandler
|
"part" -> PlayerHandler
|
||||||
"pair" -> PairingHandler
|
"pair" -> PairingHandler
|
||||||
"res" -> ResultsHandler
|
"res" -> ResultsHandler
|
||||||
|
"standings" -> StandingsHandler
|
||||||
"team" -> TeamHandler
|
"team" -> TeamHandler
|
||||||
else -> ApiHandler.badRequest("unknown sub-entity: $subEntity")
|
else -> ApiHandler.badRequest("unknown sub-entity: $subEntity")
|
||||||
}
|
}
|
||||||
|
@@ -7,5 +7,40 @@ import com.republicate.kson.Json
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
class PairgothTool {
|
class PairgothTool {
|
||||||
public fun toMap(array: Json.Array) = array.map { ser -> ser as Json.Object }.associateBy { it.getLong("id")!! }
|
fun toMap(array: Json.Array) = array.map { ser -> ser as Json.Object }.associateBy { it.getLong("id")!! }
|
||||||
|
|
||||||
|
fun getCriteria() = mapOf(
|
||||||
|
"NONE" to "No tie break", // No ranking / tie-break
|
||||||
|
|
||||||
|
"CATEGORY" to "Category",
|
||||||
|
"RANK" to "Rank",
|
||||||
|
"RATING" to "Rating",
|
||||||
|
"NBW" to "Number of wins", // Number win
|
||||||
|
"MMS" to "Mac Mahon score", // Macmahon score
|
||||||
|
"STS" to "Strasbourg score", // Strasbourg score
|
||||||
|
"CPS" to "Cup score", // Cup score
|
||||||
|
|
||||||
|
"SOSW" to "Sum of opponents wins", // Sum of opponents NBW
|
||||||
|
"SOSWM1" to "Sum of opponents wins minus 1", //-1
|
||||||
|
"SOSWM2" to "Sum of opponents wins minus 2", //-2
|
||||||
|
"SODOSW" to "Sum of defeated opponents wins", // Sum of defeated opponents NBW
|
||||||
|
"SOSOSW" to "Sum of opponents SOSW", // Sum of opponent SOS
|
||||||
|
"CUSSW" to "Cumulative sum of opponents wins", // Cumulative sum of scores (NBW)
|
||||||
|
|
||||||
|
"SOSM" to "Sum of opponents Mac Mahon score", // Sum of opponents McMahon score
|
||||||
|
"SOSMM1" to "Sum of opponents Mac Mahon score minus 1", // Same as previous group but with McMahon score
|
||||||
|
"SOSMM2" to "Sum of opponents Mac Mahon score minus 2",
|
||||||
|
"SODOSM" to "Sum of defeated opponents Mac Mahon score",
|
||||||
|
"SOSOSM" to "Sum of opponents SOSM",
|
||||||
|
"CUSSM" to "Cumulative sum of opponents Mac Mahon score",
|
||||||
|
|
||||||
|
"SOSTS" to "Sum of opponents Strasbourg score", // Sum of opponnents Strasbourg score
|
||||||
|
|
||||||
|
"EXT" to "Attempted achievements", // Exploits tentes
|
||||||
|
"EXR" to "Succeeded achievements", // Exploits reussis
|
||||||
|
|
||||||
|
// For the two criteria below see the user documentation
|
||||||
|
"SDC" to "Simplified direct confrontation", // Simplified direct confrontation
|
||||||
|
"DC" to "Direct confrontation", // Direct confrontation
|
||||||
|
)
|
||||||
}
|
}
|
@@ -11,6 +11,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.active-round-box {
|
||||||
|
padding: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
/* information section */
|
/* information section */
|
||||||
|
|
||||||
form {
|
form {
|
||||||
@@ -232,4 +237,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* standings section */
|
||||||
|
|
||||||
|
#standings-params {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #eeeeee;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
|
||||||
|
.criterium {
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
select {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
z-index: 2;
|
||||||
|
&.active {
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
select.active {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#params-submit {
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
42
view-webapp/src/main/webapp/js/tour-standings.inc.js
Normal file
42
view-webapp/src/main/webapp/js/tour-standings.inc.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
onLoad(() => {
|
||||||
|
$('.criterium').on('click', e => {
|
||||||
|
let alreadyOpen = e.target.closest('select');
|
||||||
|
if (alreadyOpen) return;
|
||||||
|
let select = e.target.closest('.criterium').find('select');
|
||||||
|
$('.criterium select').removeClass('active');
|
||||||
|
select.toggleClass('active');
|
||||||
|
});
|
||||||
|
document.on('click', e => {
|
||||||
|
let crit = e.target.closest('.criterium');
|
||||||
|
if (!crit) $('.criterium select').removeClass('active');
|
||||||
|
});
|
||||||
|
$('.criterium select').on('input', e => {
|
||||||
|
let select = e.target.closest('select');
|
||||||
|
let info = select.previousElementSibling;
|
||||||
|
info.textContent = select.selectedOptions[0].value;
|
||||||
|
$('.criterium select').removeClass('active');
|
||||||
|
$('#params-submit').removeClass('hidden');
|
||||||
|
});
|
||||||
|
$('#params-form .cancel.button').on('click', e => {
|
||||||
|
$('.criterium select').removeClass('active').forEach(elem => {
|
||||||
|
elem.value = elem.data('initial');
|
||||||
|
elem.previousElementSibling.textContent = elem.value;
|
||||||
|
});
|
||||||
|
$('#params-submit').addClass('hidden');
|
||||||
|
});
|
||||||
|
$('#params-form').on('submit', e => {
|
||||||
|
if (!$('#params-submit').hasClass('hidden')) {
|
||||||
|
api.putJson(`tour/${tour_id}`, {
|
||||||
|
pairing: {
|
||||||
|
placement: $('.criterium select').map(elem => elem.value)
|
||||||
|
}
|
||||||
|
}).then(rst => {
|
||||||
|
if (rst !== 'error') {
|
||||||
|
document.location.reload();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
});
|
@@ -15,7 +15,7 @@
|
|||||||
#end
|
#end
|
||||||
<div class="tab-content" id="pairing">
|
<div class="tab-content" id="pairing">
|
||||||
<div id="pairing-content">
|
<div id="pairing-content">
|
||||||
<div id="pairing-round">
|
<div id="pairing-round" class="active-round"-box>
|
||||||
Pairings for round
|
Pairings for round
|
||||||
<button class="ui floating choose-round prev-round button">«</button>
|
<button class="ui floating choose-round prev-round button">«</button>
|
||||||
<span class="active-round">$round</span>
|
<span class="active-round">$round</span>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<div class="tab-content" id="results">
|
<div class="tab-content" id="results">
|
||||||
<div id="results-round">
|
<div id="results-round" class="active-round-box">
|
||||||
Results for round
|
Results for round
|
||||||
<button class="ui floating choose-round prev-round button">«</button>
|
<button class="ui floating choose-round prev-round button">«</button>
|
||||||
<span class="active-round">$round</span>
|
<span class="active-round">$round</span>
|
||||||
|
@@ -1,3 +1,52 @@
|
|||||||
|
#macro(placement $i, $p)
|
||||||
|
<span class="info"></span>
|
||||||
|
<select name="crit-$i" data-initial="$p" class="short-value">
|
||||||
|
#foreach($crit in $$utils.criteria.entrySet())
|
||||||
|
<option value="$crit.key" data-tooltip="$crit.value" #if($p == $crit.key)selected#end>$crit.key - $crit.value</option>
|
||||||
|
#end
|
||||||
|
</select>
|
||||||
|
#end
|
||||||
<div class="tab-content" id="standings">
|
<div class="tab-content" id="standings">
|
||||||
Standings...
|
<div id="standings-round" class="active-round-box">
|
||||||
|
Standings after round
|
||||||
|
<button class="ui floating choose-round prev-round button">«</button>
|
||||||
|
<span class="active-round">$round</span>
|
||||||
|
<button class="ui floating choose-round next-round button">»</button>
|
||||||
|
</div>
|
||||||
|
<div id="standings-params" class="roundbox">
|
||||||
|
<form id="params-form" class="ui form">
|
||||||
|
<div class="inline fields">
|
||||||
|
#foreach($placement in $tour.pairing.placement)
|
||||||
|
<div class="criterium field">
|
||||||
|
#set($num = $foreach.index + 1)
|
||||||
|
<label>Criterium #$num</label>
|
||||||
|
#placement($num $placement)
|
||||||
|
</div>
|
||||||
|
#end
|
||||||
|
</div>
|
||||||
|
<div id="params-submit" class="hidden centered inline fields">
|
||||||
|
<button type="button" class="ui gray floating cancel button">Cancel</button>
|
||||||
|
<button type="submit" class="ui blue floating button">Change</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="standings-container" class="roundbox">
|
||||||
|
#set($standings = $api.get("tour/${params.id}/standings/$round"))
|
||||||
|
#if($standings.isObject() && ($standings.error || $standings.message))
|
||||||
|
#if($standings.error)
|
||||||
|
#set($error = $standings.error)
|
||||||
|
#else
|
||||||
|
#set($error = $standings.message)
|
||||||
|
#end
|
||||||
|
<script type="text/javascript">
|
||||||
|
onLoad(() => {
|
||||||
|
showError("$error")
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
#set($standings = [])
|
||||||
|
#end
|
||||||
|
#foreach($line in $standings)
|
||||||
|
<div class="standings-line">$line</div>
|
||||||
|
#end
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -132,7 +132,11 @@
|
|||||||
if (input.tagName === 'SELECT') {
|
if (input.tagName === 'SELECT') {
|
||||||
let sel = input.selectedOptions;
|
let sel = input.selectedOptions;
|
||||||
if (sel && sel.length === 1) {
|
if (sel && sel.length === 1) {
|
||||||
info.textContent = sel[0].textContent;
|
let txt = sel[0].textContent
|
||||||
|
if (input.hasClass('short-value')) {
|
||||||
|
txt = txt.replace(/ - .*$/, '');
|
||||||
|
}
|
||||||
|
info.textContent = txt;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (input.attr('name') === 'location' && $('input[name="online"]')[0].checked) {
|
if (input.attr('name') === 'location' && $('input[name="online"]')[0].checked) {
|
||||||
@@ -169,6 +173,7 @@
|
|||||||
#include('/js/tour-registration.inc.js')
|
#include('/js/tour-registration.inc.js')
|
||||||
#include('/js/tour-pairing.inc.js')
|
#include('/js/tour-pairing.inc.js')
|
||||||
#include('/js/tour-results.inc.js')
|
#include('/js/tour-results.inc.js')
|
||||||
|
#include('/js/tour-standings.inc.js')
|
||||||
</script>
|
</script>
|
||||||
<div id="invalid_character" class="hidden">Invalid character</div>
|
<div id="invalid_character" class="hidden">Invalid character</div>
|
||||||
<script type="text/javascript" src="/lib/datepicker-1.3.4/datepicker-full.min.js"></script>
|
<script type="text/javascript" src="/lib/datepicker-1.3.4/datepicker-full.min.js"></script>
|
||||||
|
Reference in New Issue
Block a user