Standings page in progress

This commit is contained in:
Claude Brisson
2023-12-24 15:45:14 +01:00
parent 864ba82b57
commit 31411eb859
15 changed files with 325 additions and 60 deletions

View File

@@ -1,4 +1,92 @@
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 {
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>()
}

View File

@@ -7,5 +7,5 @@ typealias ID = Int
fun String.toID() = toInt()
fun String.toIDOrNull() = toIntOrNull()
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)

View File

@@ -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
}
abstract fun toJson(): Json.Object
abstract fun toMutableJson(): Json.MutableObject
abstract val club: String?
abstract val country: 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 {
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 country = "none"
@@ -71,7 +75,7 @@ class Player(
companion object
// used to store external IDs ("FFG" => FFG ID, "EGF" => EGF PIN, "AGA" => AGA ID ...)
val externalIds = mutableMapOf<DatabaseId, String>()
override fun toJson(): Json.Object = Json.MutableObject(
override fun toMutableJson() = Json.MutableObject(
"id" to id,
"name" to name,
"firstname" to firstname,
@@ -85,6 +89,8 @@ class Player(
json[dbid.key] = id
}
}
override fun toJson(): Json.Object = toMutableJson()
override fun nameSeed(separator: String): String {
return name + separator + firstname
}

View File

@@ -129,7 +129,7 @@ sealed class Pairing(
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()
else (1 until round).map { games(it).values.toList() }
@@ -212,12 +212,12 @@ class RoundRobin(
// Serialization
fun BaseCritParams.Companion.fromJson(json: Json.Object) = BaseCritParams(
nx1 = json.getDouble("nx1") ?: default.nx1,
dupWeight = json.getDouble("dupWeight") ?: default.dupWeight,
random = json.getDouble("random") ?: default.random,
deterministic = json.getBoolean("deterministic") ?: default.deterministic,
colorBalanceWeight = json.getDouble("colorBalanceWeight") ?: default.colorBalanceWeight
fun BaseCritParams.Companion.fromJson(json: Json.Object, localDefault: BaseCritParams? = null) = BaseCritParams(
nx1 = json.getDouble("nx1") ?: localDefault?.nx1 ?: default.nx1,
dupWeight = json.getDouble("dupWeight") ?: localDefault?.dupWeight ?: default.dupWeight,
random = json.getDouble("random") ?: localDefault?.random ?: default.random,
deterministic = json.getBoolean("deterministic") ?: localDefault?.deterministic ?: default.deterministic,
colorBalanceWeight = json.getDouble("colorBalanceWeight") ?: localDefault?.colorBalanceWeight ?: default.colorBalanceWeight
)
fun BaseCritParams.toJson() = Json.Object(
@@ -227,19 +227,19 @@ fun BaseCritParams.toJson() = Json.Object(
"colorBalanceWeight" to colorBalanceWeight
)
fun MainCritParams.Companion.fromJson(json: Json.Object) = MainCritParams(
categoriesWeight = json.getDouble("catWeight") ?: default.categoriesWeight,
scoreWeight = json.getDouble("scoreWeight") ?: default.scoreWeight,
drawUpDownWeight = json.getDouble("upDownWeight") ?: default.drawUpDownWeight,
compensateDrawUpDown = json.getBoolean("upDownCompensate") ?: default.compensateDrawUpDown,
drawUpDownLowerMode = json.getString("upDownLowerMode")?.let { MainCritParams.DrawUpDown.valueOf(it) } ?: default.drawUpDownLowerMode,
drawUpDownUpperMode = json.getString("upDownUpperMode")?.let { MainCritParams.DrawUpDown.valueOf(it) } ?: default.drawUpDownUpperMode,
seedingWeight = json.getDouble("maximizeSeeding") ?: default.seedingWeight,
lastRoundForSeedSystem1 = json.getInt("firstSeedLastRound") ?: default.lastRoundForSeedSystem1,
seedSystem1 = json.getString("firstSeed")?.let { MainCritParams.SeedMethod.valueOf(it) } ?: default.seedSystem1,
seedSystem2 = json.getString("secondSeed")?.let { MainCritParams.SeedMethod.valueOf(it) } ?: default.seedSystem2,
additionalPlacementCritSystem1 = json.getString("firstSeedAddCrit")?.let { Criterion.valueOf(it) } ?: default.additionalPlacementCritSystem1,
additionalPlacementCritSystem2 = json.getString("secondSeedAddCrit")?.let { Criterion.valueOf(it) } ?: default.additionalPlacementCritSystem2
fun MainCritParams.Companion.fromJson(json: Json.Object, localDefault: MainCritParams? = null) = MainCritParams(
categoriesWeight = json.getDouble("catWeight") ?: localDefault?.categoriesWeight ?: default.categoriesWeight,
scoreWeight = json.getDouble("scoreWeight") ?: localDefault?.scoreWeight ?: default.scoreWeight,
drawUpDownWeight = json.getDouble("upDownWeight") ?: localDefault?.drawUpDownWeight ?: default.drawUpDownWeight,
compensateDrawUpDown = json.getBoolean("upDownCompensate") ?: localDefault?.compensateDrawUpDown ?: default.compensateDrawUpDown,
drawUpDownLowerMode = json.getString("upDownLowerMode")?.let { MainCritParams.DrawUpDown.valueOf(it) } ?: localDefault?.drawUpDownLowerMode ?: default.drawUpDownLowerMode,
drawUpDownUpperMode = json.getString("upDownUpperMode")?.let { MainCritParams.DrawUpDown.valueOf(it) } ?: localDefault?.drawUpDownUpperMode ?: default.drawUpDownUpperMode,
seedingWeight = json.getDouble("maximizeSeeding") ?: localDefault?.seedingWeight ?: default.seedingWeight,
lastRoundForSeedSystem1 = json.getInt("firstSeedLastRound") ?: localDefault?.lastRoundForSeedSystem1 ?: default.lastRoundForSeedSystem1,
seedSystem1 = json.getString("firstSeed")?.let { MainCritParams.SeedMethod.valueOf(it) } ?: localDefault?.seedSystem1 ?: default.seedSystem1,
seedSystem2 = json.getString("secondSeed")?.let { MainCritParams.SeedMethod.valueOf(it) } ?: localDefault?.seedSystem2 ?: default.seedSystem2,
additionalPlacementCritSystem1 = json.getString("firstSeedAddCrit")?.let { Criterion.valueOf(it) } ?: localDefault?.additionalPlacementCritSystem1 ?: default.additionalPlacementCritSystem1,
additionalPlacementCritSystem2 = json.getString("secondSeedAddCrit")?.let { Criterion.valueOf(it) } ?: localDefault?.additionalPlacementCritSystem2 ?: default.additionalPlacementCritSystem2
)
fun MainCritParams.toJson() = Json.Object(
@@ -257,11 +257,11 @@ fun MainCritParams.toJson() = Json.Object(
"secondSeedAddCrit" to additionalPlacementCritSystem2
)
fun SecondaryCritParams.Companion.fromJson(json: Json.Object) = SecondaryCritParams(
barThresholdActive = json.getBoolean("barThreshold") ?: default.barThresholdActive,
rankThreshold = json.getInt("rankThreshold") ?: default.rankThreshold,
nbWinsThresholdActive = json.getBoolean("winsThreshold") ?: default.nbWinsThresholdActive,
defSecCrit = json.getDouble("secWeight") ?: default.defSecCrit
fun SecondaryCritParams.Companion.fromJson(json: Json.Object, localDefault: SecondaryCritParams? = null) = SecondaryCritParams(
barThresholdActive = json.getBoolean("barThreshold") ?: localDefault?.barThresholdActive ?: default.barThresholdActive,
rankThreshold = json.getInt("rankThreshold") ?: localDefault?.rankThreshold ?: default.rankThreshold,
nbWinsThresholdActive = json.getBoolean("winsThreshold") ?: localDefault?.nbWinsThresholdActive ?: default.nbWinsThresholdActive,
defSecCrit = json.getDouble("secWeight") ?: localDefault?.defSecCrit ?: default.defSecCrit
)
fun SecondaryCritParams.toJson() = Json.Object(
@@ -271,11 +271,11 @@ fun SecondaryCritParams.toJson() = Json.Object(
"secWeight" to defSecCrit
)
fun GeographicalParams.Companion.fromJson(json: Json.Object) = GeographicalParams(
avoidSameGeo = json.getDouble("weight") ?: disabled.avoidSameGeo,
preferMMSDiffRatherThanSameCountry = json.getInt("mmsDiffCountry") ?: disabled.preferMMSDiffRatherThanSameCountry,
preferMMSDiffRatherThanSameClubsGroup = json.getInt("mmsDiffClubGroup") ?: disabled.preferMMSDiffRatherThanSameClubsGroup,
preferMMSDiffRatherThanSameClub = json.getInt("mmsDiffClub") ?: disabled.preferMMSDiffRatherThanSameClub
fun GeographicalParams.Companion.fromJson(json: Json.Object, localDefault: GeographicalParams? = null) = GeographicalParams(
avoidSameGeo = json.getDouble("weight") ?: localDefault?.avoidSameGeo ?: disabled.avoidSameGeo,
preferMMSDiffRatherThanSameCountry = json.getInt("mmsDiffCountry") ?: localDefault?.preferMMSDiffRatherThanSameCountry ?: disabled.preferMMSDiffRatherThanSameCountry,
preferMMSDiffRatherThanSameClubsGroup = json.getInt("mmsDiffClubGroup") ?: localDefault?.preferMMSDiffRatherThanSameClub ?: disabled.preferMMSDiffRatherThanSameClubsGroup,
preferMMSDiffRatherThanSameClub = json.getInt("mmsDiffClub") ?: localDefault?.preferMMSDiffRatherThanSameClub ?: disabled.preferMMSDiffRatherThanSameClub
)
fun GeographicalParams.toJson() = Json.Object(
@@ -285,12 +285,12 @@ fun GeographicalParams.toJson() = Json.Object(
"mmsDiffClub" to preferMMSDiffRatherThanSameClub
)
fun HandicapParams.Companion.fromJson(json: Json.Object, type: PairingType) = HandicapParams(
weight = json.getDouble("weight") ?: default(type).weight,
useMMS = json.getBoolean("useMMS") ?: default(type).useMMS,
rankThreshold = json.getInt("threshold") ?: default(type).rankThreshold,
correction = json.getInt("correction") ?: default(type).correction,
ceiling = json.getInt("ceiling") ?: default(type).ceiling
fun HandicapParams.Companion.fromJson(json: Json.Object, type: PairingType, localDefault: HandicapParams? = null) = HandicapParams(
weight = json.getDouble("weight") ?: localDefault?.weight ?: default(type).weight,
useMMS = json.getBoolean("useMMS") ?: localDefault?.useMMS ?: default(type).useMMS,
rankThreshold = json.getInt("threshold") ?: localDefault?.rankThreshold ?: default(type).rankThreshold,
correction = json.getInt("correction") ?: localDefault?.correction ?: default(type).correction,
ceiling = json.getInt("ceiling") ?: localDefault?.ceiling ?: default(type).ceiling
)
fun HandicapParams.toJson() = Json.Object(
@@ -301,21 +301,21 @@ fun HandicapParams.toJson() = Json.Object(
"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
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) {
SWISS -> Swiss()
MAC_MAHON -> MacMahon()
ROUND_ROBIN -> RoundRobin()
}
val base = json.getObject("base")?.let { BaseCritParams.fromJson(it) } ?: defaultParams.pairingParams.base
val main = json.getObject("main")?.let { MainCritParams.fromJson(it) } ?: defaultParams.pairingParams.main
val secondary = json.getObject("secondary")?.let { SecondaryCritParams.fromJson(it) } ?: defaultParams.pairingParams.secondary
val geo = json.getObject("geo")?.let { GeographicalParams.fromJson(it) } ?: defaultParams.pairingParams.geo
val hd = json.getObject("handicap")?.let { HandicapParams.fromJson(it, type) } ?: defaultParams.pairingParams.handicap
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, default?.pairingParams?.main) } ?: default?.pairingParams?.main ?: defaultParams.pairingParams.main
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, default?.pairingParams?.geo) } ?: default?.pairingParams?.geo ?: defaultParams.pairingParams.geo
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 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) {
SWISS -> Swiss(pairingParams, placementParams)
MAC_MAHON -> MacMahon(pairingParams, placementParams).also { mm ->

View File

@@ -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 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 fun toJson() = Json.Object(
override fun toMutableJson() = Json.MutableObject(
"id" to id,
"name" to name,
"players" to playerIds.toList().toJsonArray()
)
override fun toJson(): Json.Object = toMutableJson()
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,
timeSystem = json.getObject("timeSystem")?.let { TimeSystem.fromJson(it) } ?: default?.timeSystem ?: badRequest("missing timeSystem"),
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
TeamTournament(
@@ -178,7 +179,7 @@ fun Tournament.Companion.fromJson(json: Json.Object, default: Tournament<*>? = n
gobanSize = json.getInt("gobanSize") ?: default?.gobanSize ?: 19,
timeSystem = json.getObject("timeSystem")?.let { TimeSystem.fromJson(it) } ?: default?.timeSystem ?: badRequest("missing timeSystem"),
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 ->

View File

@@ -3,7 +3,7 @@ package org.jeudego.pairgoth.pairing
import org.jeudego.pairgoth.model.*
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) {
BLACK, BOTHWIN -> 1.0
@@ -15,7 +15,9 @@ open class HistoryHelper(protected val history: List<List<Game>>, scoresGetter:
else -> 0.0
}
private val scores by lazy(scoresGetter)
val scores by lazy {
scoresGetter()
}
// Generic helper functions
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
class TeamOfIndividualsHistoryHelper(history: List<List<Game>>, scoresGetter: () -> Map<ID, Double>):
HistoryHelper(history, scoresGetter) {
HistoryHelper(history, { scoresGetter() }) {
private fun Pairable.asTeam() = this as TeamTournament.Team

View File

@@ -13,9 +13,11 @@ class MacMahonSolver(round: Int,
BaseSolver(round, history, pairables, pairingParams, placementParams) {
override val scores: Map<ID, Double> by lazy {
pairablesMap.mapValues { it.value.let {
pairablesMap.mapValues {
it.value.let {
pairable -> pairable.mmBase + pairable.nbW // TODO take tournament parameter into account
} }
}
}
}
override fun debug(p: Pairable) {

View File

@@ -5,6 +5,7 @@ import org.jeudego.pairgoth.api.ApiHandler
import org.jeudego.pairgoth.api.PairingHandler
import org.jeudego.pairgoth.api.PlayerHandler
import org.jeudego.pairgoth.api.ResultsHandler
import org.jeudego.pairgoth.api.StandingsHandler
import org.jeudego.pairgoth.api.TeamHandler
import org.jeudego.pairgoth.api.TournamentHandler
import org.jeudego.pairgoth.util.Colorizer.blue
@@ -87,6 +88,7 @@ class ApiServlet: HttpServlet() {
"part" -> PlayerHandler
"pair" -> PairingHandler
"res" -> ResultsHandler
"standings" -> StandingsHandler
"team" -> TeamHandler
else -> ApiHandler.badRequest("unknown sub-entity: $subEntity")
}

View File

@@ -7,5 +7,40 @@ import com.republicate.kson.Json
*/
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
)
}

View File

@@ -11,6 +11,11 @@
}
}
.active-round-box {
padding: 1em;
font-weight: bold;
}
/* information section */
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;
}
}
}

View 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;
});
});

View File

@@ -15,7 +15,7 @@
#end
<div class="tab-content" id="pairing">
<div id="pairing-content">
<div id="pairing-round">
<div id="pairing-round" class="active-round"-box>
Pairings for round
<button class="ui floating choose-round prev-round button">&laquo;</button>
<span class="active-round">$round</span>

View File

@@ -1,5 +1,5 @@
<div class="tab-content" id="results">
<div id="results-round">
<div id="results-round" class="active-round-box">
Results for round
<button class="ui floating choose-round prev-round button">&laquo;</button>
<span class="active-round">$round</span>

View File

@@ -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">
Standings...
<div id="standings-round" class="active-round-box">
Standings after round
<button class="ui floating choose-round prev-round button">&laquo;</button>
<span class="active-round">$round</span>
<button class="ui floating choose-round next-round button">&raquo;</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>

View File

@@ -132,7 +132,11 @@
if (input.tagName === 'SELECT') {
let sel = input.selectedOptions;
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 {
if (input.attr('name') === 'location' && $('input[name="online"]')[0].checked) {
@@ -169,6 +173,7 @@
#include('/js/tour-registration.inc.js')
#include('/js/tour-pairing.inc.js')
#include('/js/tour-results.inc.js')
#include('/js/tour-standings.inc.js')
</script>
<div id="invalid_character" class="hidden">Invalid character</div>
<script type="text/javascript" src="/lib/datepicker-1.3.4/datepicker-full.min.js"></script>