Merge from master
This commit is contained in:
@@ -67,6 +67,7 @@
|
|||||||
</httpConnector>
|
</httpConnector>
|
||||||
<systemProperties>
|
<systemProperties>
|
||||||
<pairgoth.env>${pairgoth.env}</pairgoth.env>
|
<pairgoth.env>${pairgoth.env}</pairgoth.env>
|
||||||
|
<pairgoth.version>${project.version}</pairgoth.version>
|
||||||
<pairgoth.api.external.url>${pairgoth.api.external.url}</pairgoth.api.external.url>
|
<pairgoth.api.external.url>${pairgoth.api.external.url}</pairgoth.api.external.url>
|
||||||
<pairgoth.webapp.external.url>${pairgoth.webapp.external.url}</pairgoth.webapp.external.url>
|
<pairgoth.webapp.external.url>${pairgoth.webapp.external.url}</pairgoth.webapp.external.url>
|
||||||
<pairgoth.store>${pairgoth.store}</pairgoth.store>
|
<pairgoth.store>${pairgoth.store}</pairgoth.store>
|
||||||
|
@@ -16,7 +16,7 @@ interface PairgothApiHandler: ApiHandler {
|
|||||||
fun Tournament<*>.dispatchEvent(event: Event, data: Json? = null) {
|
fun Tournament<*>.dispatchEvent(event: Event, data: Json? = null) {
|
||||||
Event.dispatch(event, Json.Object("tournament" to id, "data" to data))
|
Event.dispatch(event, Json.Object("tournament" to id, "data" to data))
|
||||||
// when storage is not in memory, the tournament has to be persisted
|
// when storage is not in memory, the tournament has to be persisted
|
||||||
if (event != Event.tournamentAdded && event != Event.tournamentDeleted && event != Event.gameUpdated)
|
if (event != Event.TournamentAdded && event != Event.TournamentDeleted)
|
||||||
Store.replaceTournament(this)
|
Store.replaceTournament(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,7 +7,6 @@ import org.jeudego.pairgoth.model.Game
|
|||||||
import org.jeudego.pairgoth.model.getID
|
import org.jeudego.pairgoth.model.getID
|
||||||
import org.jeudego.pairgoth.model.toID
|
import org.jeudego.pairgoth.model.toID
|
||||||
import org.jeudego.pairgoth.model.toJson
|
import org.jeudego.pairgoth.model.toJson
|
||||||
import org.jeudego.pairgoth.server.Event
|
|
||||||
import org.jeudego.pairgoth.server.Event.*
|
import org.jeudego.pairgoth.server.Event.*
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
import javax.servlet.http.HttpServletResponse
|
import javax.servlet.http.HttpServletResponse
|
||||||
@@ -21,9 +20,11 @@ object PairingHandler: PairgothApiHandler {
|
|||||||
val playing = tournament.games(round).values.flatMap {
|
val playing = tournament.games(round).values.flatMap {
|
||||||
listOf(it.black, it.white)
|
listOf(it.black, it.white)
|
||||||
}.toSet()
|
}.toSet()
|
||||||
val unpairables = tournament.pairables.values.filter { it.skip.contains(round) }.sortedByDescending { it.rating }.map { it.id }.toJsonArray()
|
val unpairables = tournament.pairables.values.filter { !it.final || it.skip.contains(round) }.sortedByDescending { it.rating }.map { it.id }.toJsonArray()
|
||||||
val pairables = tournament.pairables.values.filter { !it.skip.contains(round) && !playing.contains(it.id) }.sortedByDescending { it.rating }.map { it.id }.toJsonArray()
|
val pairables = tournament.pairables.values.filter { it.final && !it.skip.contains(round) && !playing.contains(it.id) }.sortedByDescending { it.rating }.map { it.id }.toJsonArray()
|
||||||
val games = tournament.games(round).values
|
val games = tournament.games(round).values.sortedBy {
|
||||||
|
if (it.table == 0) Int.MAX_VALUE else it.table
|
||||||
|
}
|
||||||
return Json.Object(
|
return Json.Object(
|
||||||
"games" to games.map { it.toJson() }.toCollection(Json.MutableArray()),
|
"games" to games.map { it.toJson() }.toCollection(Json.MutableArray()),
|
||||||
"pairables" to pairables,
|
"pairables" to pairables,
|
||||||
@@ -44,19 +45,20 @@ object PairingHandler: PairgothApiHandler {
|
|||||||
}.toSet()
|
}.toSet()
|
||||||
val pairables =
|
val pairables =
|
||||||
if (allPlayers)
|
if (allPlayers)
|
||||||
tournament.pairables.values.filter { !it.skip.contains(round) && !playing.contains(it.id) }
|
tournament.pairables.values.filter { it.final && !it.skip.contains(round) && !playing.contains(it.id) }
|
||||||
else payload.map {
|
else payload.map {
|
||||||
// CB - because of the '["all"]' map, conversion to int lands here... Better API syntax for 'all players'?
|
// CB - because of the '["all"]' map, conversion to int lands here... Better API syntax for 'all players'?
|
||||||
if (it is Number) it.toID() else badRequest("invalid pairable id: #$it")
|
if (it is Number) it.toID() else badRequest("invalid pairable id: #$it")
|
||||||
}.map { id ->
|
}.map { id ->
|
||||||
tournament.pairables[id]?.also {
|
tournament.pairables[id]?.also {
|
||||||
|
if (!it.final) badRequest("pairable #$id registration status is not final")
|
||||||
if (it.skip.contains(round)) badRequest("pairable #$id does not play round $round")
|
if (it.skip.contains(round)) badRequest("pairable #$id does not play round $round")
|
||||||
if (playing.contains(it.id)) badRequest("pairable #$id already plays round $round")
|
if (playing.contains(it.id)) badRequest("pairable #$id already plays round $round")
|
||||||
} ?: badRequest("invalid pairable id: #$id")
|
} ?: badRequest("invalid pairable id: #$id")
|
||||||
}
|
}
|
||||||
val games = tournament.pair(round, pairables)
|
val games = tournament.pair(round, pairables)
|
||||||
val ret = games.map { it.toJson() }.toJsonArray()
|
val ret = games.map { it.toJson() }.toJsonArray()
|
||||||
tournament.dispatchEvent(gamesAdded, Json.Object("round" to round, "games" to ret))
|
tournament.dispatchEvent(GamesAdded, Json.Object("round" to round, "games" to ret))
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,20 +73,37 @@ object PairingHandler: PairgothApiHandler {
|
|||||||
val playing = (tournament.games(round).values).filter { it.id != gameId }.flatMap {
|
val playing = (tournament.games(round).values).filter { it.id != gameId }.flatMap {
|
||||||
listOf(it.black, it.white)
|
listOf(it.black, it.white)
|
||||||
}.toSet()
|
}.toSet()
|
||||||
|
if (game.result != Game.Result.UNKNOWN && (
|
||||||
|
game.black != payload.getInt("b") ||
|
||||||
|
game.white != payload.getInt("w") ||
|
||||||
|
game.handicap != payload.getInt("h")
|
||||||
|
)) badRequest("Game already has a result")
|
||||||
game.black = payload.getID("b") ?: badRequest("missing black player id")
|
game.black = payload.getID("b") ?: badRequest("missing black player id")
|
||||||
game.white = payload.getID("w") ?: badRequest("missing white player id")
|
game.white = payload.getID("w") ?: badRequest("missing white player id")
|
||||||
|
|
||||||
tournament.recomputeHdAndDUDD(round, game.id)
|
tournament.recomputeHdAndDUDD(round, game.id)
|
||||||
|
val previousTable = game.table;
|
||||||
// temporary
|
// temporary
|
||||||
//payload.getInt("dudd")?.let { game.drawnUpDown = it }
|
//payload.getInt("dudd")?.let { game.drawnUpDown = it }
|
||||||
val black = tournament.pairables[game.black] ?: badRequest("invalid black player id")
|
val black = tournament.pairables[game.black] ?: badRequest("invalid black player id")
|
||||||
val white = tournament.pairables[game.black] ?: badRequest("invalid white player id")
|
val white = tournament.pairables[game.black] ?: badRequest("invalid white player id")
|
||||||
|
if (!black.final) badRequest("black registration status is not final")
|
||||||
|
if (!white.final) badRequest("white registration status is not final")
|
||||||
if (black.skip.contains(round)) badRequest("black is not playing this round")
|
if (black.skip.contains(round)) badRequest("black is not playing this round")
|
||||||
if (white.skip.contains(round)) badRequest("white is not playing this round")
|
if (white.skip.contains(round)) badRequest("white is not playing this round")
|
||||||
if (playing.contains(black.id)) badRequest("black is already in another game")
|
if (playing.contains(black.id)) badRequest("black is already in another game")
|
||||||
if (playing.contains(white.id)) badRequest("white is already in another game")
|
if (playing.contains(white.id)) badRequest("white is already in another game")
|
||||||
if (payload.containsKey("h")) game.handicap = payload.getString("h")?.toIntOrNull() ?: badRequest("invalid handicap")
|
if (payload.containsKey("h")) game.handicap = payload.getString("h")?.toIntOrNull() ?: badRequest("invalid handicap")
|
||||||
tournament.dispatchEvent(gameUpdated, Json.Object("round" to round, "game" to game.toJson()))
|
if (payload.containsKey("t")) {
|
||||||
|
game.table = payload.getString("t")?.toIntOrNull() ?: badRequest("invalid table number")
|
||||||
|
}
|
||||||
|
tournament.dispatchEvent(GameUpdated, Json.Object("round" to round, "game" to game.toJson()))
|
||||||
|
if (game.table != previousTable && tournament.renumberTables(round, game)) {
|
||||||
|
val games = tournament.games(round).values.sortedBy {
|
||||||
|
if (it.table == 0) Int.MAX_VALUE else it.table
|
||||||
|
}
|
||||||
|
tournament.dispatchEvent(TablesRenumbered, Json.Object("round" to round, "games" to games.map { it.toJson() }.toCollection(Json.MutableArray())))
|
||||||
|
}
|
||||||
return Json.Object("success" to true)
|
return Json.Object("success" to true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +121,7 @@ object PairingHandler: PairgothApiHandler {
|
|||||||
payload.forEach {
|
payload.forEach {
|
||||||
val id = (it as Number).toInt()
|
val id = (it as Number).toInt()
|
||||||
val game = tournament.games(round)[id] ?: throw Error("invalid game id")
|
val game = tournament.games(round)[id] ?: throw Error("invalid game id")
|
||||||
if (game.result != Game.Result.UNKNOWN) {
|
if (game.result != Game.Result.UNKNOWN && game.black != 0 && game.white != 0) {
|
||||||
ApiHandler.logger.error("cannot unpair game id ${game.id}: it has a result")
|
ApiHandler.logger.error("cannot unpair game id ${game.id}: it has a result")
|
||||||
// we'll only skip it
|
// we'll only skip it
|
||||||
// throw Error("cannot unpair ")
|
// throw Error("cannot unpair ")
|
||||||
@@ -111,7 +130,7 @@ object PairingHandler: PairgothApiHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tournament.dispatchEvent(gamesDeleted, Json.Object("round" to round, "games" to payload))
|
tournament.dispatchEvent(GamesDeleted, Json.Object("round" to round, "games" to payload))
|
||||||
return Json.Object("success" to true)
|
return Json.Object("success" to true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,6 @@ import com.republicate.kson.toJsonArray
|
|||||||
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
||||||
import org.jeudego.pairgoth.model.Player
|
import org.jeudego.pairgoth.model.Player
|
||||||
import org.jeudego.pairgoth.model.fromJson
|
import org.jeudego.pairgoth.model.fromJson
|
||||||
import org.jeudego.pairgoth.server.Event
|
|
||||||
import org.jeudego.pairgoth.server.Event.*
|
import org.jeudego.pairgoth.server.Event.*
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
import javax.servlet.http.HttpServletResponse
|
import javax.servlet.http.HttpServletResponse
|
||||||
@@ -25,7 +24,7 @@ object PlayerHandler: PairgothApiHandler {
|
|||||||
val payload = getObjectPayload(request)
|
val payload = getObjectPayload(request)
|
||||||
val player = Player.fromJson(payload)
|
val player = Player.fromJson(payload)
|
||||||
tournament.players[player.id] = player
|
tournament.players[player.id] = player
|
||||||
tournament.dispatchEvent(playerAdded, player.toJson())
|
tournament.dispatchEvent(PlayerAdded, player.toJson())
|
||||||
return Json.Object("success" to true, "id" to player.id)
|
return Json.Object("success" to true, "id" to player.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +35,7 @@ object PlayerHandler: PairgothApiHandler {
|
|||||||
val payload = getObjectPayload(request)
|
val payload = getObjectPayload(request)
|
||||||
val updated = Player.fromJson(payload, player)
|
val updated = Player.fromJson(payload, player)
|
||||||
tournament.players[updated.id] = updated
|
tournament.players[updated.id] = updated
|
||||||
tournament.dispatchEvent(playerUpdated, player.toJson())
|
tournament.dispatchEvent(PlayerUpdated, player.toJson())
|
||||||
return Json.Object("success" to true)
|
return Json.Object("success" to true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +43,7 @@ object PlayerHandler: PairgothApiHandler {
|
|||||||
val tournament = getTournament(request)
|
val tournament = getTournament(request)
|
||||||
val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid player selector")
|
val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid player selector")
|
||||||
tournament.players.remove(id) ?: badRequest("invalid player id")
|
tournament.players.remove(id) ?: badRequest("invalid player id")
|
||||||
tournament.dispatchEvent(playerDeleted, Json.Object("id" to id))
|
tournament.dispatchEvent(PlayerDeleted, Json.Object("id" to id))
|
||||||
return Json.Object("success" to true)
|
return Json.Object("success" to true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,7 @@ object ResultsHandler: PairgothApiHandler {
|
|||||||
val payload = getObjectPayload(request)
|
val payload = getObjectPayload(request)
|
||||||
val game = tournament.games(round)[payload.getInt("id")] ?: badRequest("invalid game id")
|
val game = tournament.games(round)[payload.getInt("id")] ?: badRequest("invalid game id")
|
||||||
game.result = Game.Result.fromSymbol(payload.getChar("result") ?: badRequest("missing result"))
|
game.result = Game.Result.fromSymbol(payload.getChar("result") ?: badRequest("missing result"))
|
||||||
tournament.dispatchEvent(Event.resultUpdated, Json.Object("round" to round, "data" to game))
|
tournament.dispatchEvent(Event.ResultUpdated, Json.Object("round" to round, "data" to game))
|
||||||
return Json.Object("success" to true)
|
return Json.Object("success" to true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,21 +3,27 @@ package org.jeudego.pairgoth.api
|
|||||||
import com.republicate.kson.Json
|
import com.republicate.kson.Json
|
||||||
import com.republicate.kson.toJsonArray
|
import com.republicate.kson.toJsonArray
|
||||||
import org.jeudego.pairgoth.model.Criterion
|
import org.jeudego.pairgoth.model.Criterion
|
||||||
|
import org.jeudego.pairgoth.model.Criterion.*
|
||||||
|
import org.jeudego.pairgoth.model.Game.Result.*
|
||||||
|
import org.jeudego.pairgoth.model.ID
|
||||||
import org.jeudego.pairgoth.model.MacMahon
|
import org.jeudego.pairgoth.model.MacMahon
|
||||||
import org.jeudego.pairgoth.model.Pairable
|
import org.jeudego.pairgoth.model.Pairable
|
||||||
import org.jeudego.pairgoth.model.PairingType
|
import org.jeudego.pairgoth.model.PairingType
|
||||||
|
import org.jeudego.pairgoth.model.Tournament
|
||||||
|
import org.jeudego.pairgoth.model.adjustedTime
|
||||||
|
import org.jeudego.pairgoth.model.displayRank
|
||||||
|
import org.jeudego.pairgoth.model.getID
|
||||||
import org.jeudego.pairgoth.model.historyBefore
|
import org.jeudego.pairgoth.model.historyBefore
|
||||||
import org.jeudego.pairgoth.pairing.HistoryHelper
|
import org.jeudego.pairgoth.pairing.HistoryHelper
|
||||||
import org.jeudego.pairgoth.pairing.solver.MacMahonSolver
|
import org.jeudego.pairgoth.pairing.solver.MacMahonSolver
|
||||||
|
import java.io.PrintWriter
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
import javax.servlet.http.HttpServletResponse
|
import javax.servlet.http.HttpServletResponse
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
import org.jeudego.pairgoth.model.TimeSystem.TimeSystemType.*
|
||||||
import org.jeudego.pairgoth.model.Criterion.*
|
import java.text.DecimalFormat
|
||||||
import org.jeudego.pairgoth.model.Game.Result.*
|
|
||||||
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? {
|
override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
||||||
@@ -78,12 +84,12 @@ object StandingsHandler: PairgothApiHandler {
|
|||||||
DC -> nullMap
|
DC -> nullMap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val pairables = tournament.pairables.values.map { it.toMutableJson() }
|
val pairables = tournament.pairables.values.filter { it.final }.map { it.toMutableJson() }
|
||||||
pairables.forEach { player ->
|
pairables.forEach { player ->
|
||||||
for (crit in criteria) {
|
for (crit in criteria) {
|
||||||
player[crit.first] = crit.second[player.getID()] ?: 0.0
|
player[crit.first] = crit.second[player.getID()] ?: 0.0
|
||||||
}
|
}
|
||||||
player["results"] = Json.MutableArray(List(round) { "=0" })
|
player["results"] = Json.MutableArray(List(round) { "0=" })
|
||||||
}
|
}
|
||||||
val sortedPairables = pairables.sortedWith { left, right ->
|
val sortedPairables = pairables.sortedWith { left, right ->
|
||||||
for (crit in criteria) {
|
for (crit in criteria) {
|
||||||
@@ -114,38 +120,156 @@ object StandingsHandler: PairgothApiHandler {
|
|||||||
val blackNum = black?.getInt("num") ?: 0
|
val blackNum = black?.getInt("num") ?: 0
|
||||||
val whiteColor = if (black == null) "" else "w"
|
val whiteColor = if (black == null) "" else "w"
|
||||||
val blackColor = if (white == null) "" else "b"
|
val blackColor = if (white == null) "" else "b"
|
||||||
val handicap = if (game.handicap == 0) "" else "/h${game.handicap}"
|
val handicap = if (game.handicap == 0) "" else "${game.handicap}"
|
||||||
assert(white != null || black != null)
|
assert(white != null || black != null)
|
||||||
if (white != null) {
|
if (white != null) {
|
||||||
val mark = when (game.result) {
|
val mark = when (game.result) {
|
||||||
UNKNOWN -> "?"
|
UNKNOWN -> "?"
|
||||||
BLACK -> "-"
|
BLACK, BOTHLOOSE -> "-"
|
||||||
WHITE -> "+"
|
WHITE, BOTHWIN -> "+"
|
||||||
JIGO -> "="
|
JIGO, CANCELLED -> "="
|
||||||
CANCELLED -> "X"
|
|
||||||
BOTHWIN -> "++"
|
|
||||||
BOTHLOOSE -> "--"
|
|
||||||
}
|
}
|
||||||
val results = white.getArray("results") as Json.MutableArray
|
val results = white.getArray("results") as Json.MutableArray
|
||||||
results[r - 1] = "$whiteColor$mark$blackNum$handicap"
|
results[r - 1] =
|
||||||
|
if (blackNum == 0) "0$mark"
|
||||||
|
else "$blackNum$mark/$whiteColor$handicap"
|
||||||
}
|
}
|
||||||
if (black != null) {
|
if (black != null) {
|
||||||
val mark = when (game.result) {
|
val mark = when (game.result) {
|
||||||
UNKNOWN -> "?"
|
UNKNOWN -> "?"
|
||||||
BLACK -> "+"
|
BLACK, BOTHWIN -> "+"
|
||||||
WHITE -> "-"
|
WHITE, BOTHLOOSE -> "-"
|
||||||
JIGO -> "="
|
JIGO, CANCELLED -> "="
|
||||||
CANCELLED -> "X"
|
|
||||||
BOTHWIN -> "++"
|
|
||||||
BOTHLOOSE -> "--"
|
|
||||||
}
|
}
|
||||||
val results = black.getArray("results") as Json.MutableArray
|
val results = black.getArray("results") as Json.MutableArray
|
||||||
results[r - 1] = "$blackColor$mark$whiteNum$handicap"
|
results[r - 1] =
|
||||||
|
if (whiteNum == 0) "0$mark"
|
||||||
|
else "$whiteNum$mark/$blackColor$handicap"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sortedPairables.toJsonArray()
|
val accept = request.getHeader("Accept")?.substringBefore(";")
|
||||||
|
return when(accept) {
|
||||||
|
"application/json" -> sortedPairables.toJsonArray()
|
||||||
|
"application/egf" -> {
|
||||||
|
exportToEGFFormat(tournament, sortedPairables, neededCriteria, response.writer)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
"application/ffg" -> {
|
||||||
|
exportToFFGFormat(tournament, sortedPairables, response.writer)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
else -> ApiHandler.badRequest("invalid Accept header: $accept")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val nullMap = mapOf<ID, Double>()
|
val nullMap = mapOf<ID, Double>()
|
||||||
|
|
||||||
|
private fun exportToEGFFormat(tournament: Tournament<*>, lines: List<Json.Object>, criteria: List<Criterion>, writer: PrintWriter) {
|
||||||
|
val mainTime = tournament.timeSystem.mainTime
|
||||||
|
val adjustedTime = tournament.timeSystem.adjustedTime()
|
||||||
|
val egfClass =
|
||||||
|
if (tournament.online) {
|
||||||
|
when (tournament.timeSystem.type) {
|
||||||
|
FISCHER ->
|
||||||
|
if (mainTime >= 1800 && adjustedTime >= 3000) "D"
|
||||||
|
else "X"
|
||||||
|
else ->
|
||||||
|
if (mainTime >= 2400 && adjustedTime >= 3000) "D"
|
||||||
|
else "X"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
when (tournament.timeSystem.type) {
|
||||||
|
FISCHER ->
|
||||||
|
if (mainTime >= 2700 && adjustedTime >= 4500) "A"
|
||||||
|
else if (mainTime >= 1800 && adjustedTime >= 3000) "B"
|
||||||
|
else if (mainTime >= 1200 && adjustedTime >= 1800) "C"
|
||||||
|
else "X"
|
||||||
|
else ->
|
||||||
|
if (mainTime >= 3600 && adjustedTime >= 4500) "A"
|
||||||
|
else if (mainTime >= 2400 && adjustedTime >= 3000) "B"
|
||||||
|
else if (mainTime >= 1500 && adjustedTime >= 1800) "C"
|
||||||
|
else "X"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val ret =
|
||||||
|
"""
|
||||||
|
; CL[${egfClass}]
|
||||||
|
; EV[${tournament.name}]
|
||||||
|
; PC[${tournament.country.lowercase()},${tournament.location}]
|
||||||
|
; DT[${tournament.startDate},${tournament.endDate}]
|
||||||
|
; HA[${
|
||||||
|
if (tournament.pairing.type == PairingType.MAC_MAHON) "h${tournament.pairing.pairingParams.handicap.correction}"
|
||||||
|
else "h9"
|
||||||
|
|
||||||
|
}]
|
||||||
|
; KM[${tournament.komi}]
|
||||||
|
; TM[${tournament.timeSystem.adjustedTime() / 60}]
|
||||||
|
; CM[Generated by Pairgoth v0.1]
|
||||||
|
;
|
||||||
|
; Pl Name Rk Co Club ${ criteria.map { it.name.replace(Regex("(S?)O?(SOS|DOS)[MW]?"), "$1$2").padStart(7, ' ') }.joinToString(" ") }
|
||||||
|
${
|
||||||
|
lines.joinToString("\n") { player ->
|
||||||
|
"${
|
||||||
|
player.getString("num")!!.padStart(4, ' ')
|
||||||
|
} ${
|
||||||
|
"${player.getString("name")} ${player.getString("firstname")}".padEnd(30, ' ').take(30)
|
||||||
|
} ${
|
||||||
|
displayRank(player.getInt("rank")!!).uppercase().padStart(3, ' ')
|
||||||
|
} ${
|
||||||
|
player.getString("country")!!.uppercase()
|
||||||
|
} ${
|
||||||
|
(player.getString("club") ?: "").padStart(4).take(4)
|
||||||
|
} ${
|
||||||
|
criteria.joinToString(" ") { numFormat.format(player.getDouble(it.name)!!).let { if (it.contains('.')) it else "$it " }.padStart(7, ' ') }
|
||||||
|
} ${
|
||||||
|
player.getArray("results")!!.map {
|
||||||
|
(it as String).padStart(8, ' ')
|
||||||
|
}.joinToString(" ")
|
||||||
|
}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
writer.println(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun exportToFFGFormat(tournament: Tournament<*>, lines: List<Json.Object>, writer: PrintWriter) {
|
||||||
|
// let's try in UTF-8
|
||||||
|
val ret =
|
||||||
|
""";name=${tournament.shortName}
|
||||||
|
;date=${frDate.format(tournament.startDate)}
|
||||||
|
;vill=${tournament.location}${if (tournament.online) "(online)" else ""}
|
||||||
|
;comm=${tournament.name}
|
||||||
|
;prog=Pairgoth v0.1
|
||||||
|
;time=${tournament.timeSystem.mainTime / 60}
|
||||||
|
;ta=${tournament.timeSystem.adjustedTime() / 60}
|
||||||
|
;size=${tournament.gobanSize}
|
||||||
|
;komi=${tournament.komi}
|
||||||
|
;
|
||||||
|
;Num Nom Prenom Niv Licence Club
|
||||||
|
${
|
||||||
|
lines.joinToString("\n") { player ->
|
||||||
|
"${
|
||||||
|
player.getString("num")!!.padStart(4, ' ')
|
||||||
|
} ${
|
||||||
|
"${player.getString("name")} ${player.getString("firstname")}".padEnd(24, ' ').take(24)
|
||||||
|
} ${
|
||||||
|
displayRank(player.getInt("rank")!!).uppercase().padStart(3, ' ')
|
||||||
|
} ${
|
||||||
|
player.getString("ffg") ?: " "
|
||||||
|
} ${
|
||||||
|
(player.getString("club") ?: "").padStart(6).take(6)
|
||||||
|
} ${
|
||||||
|
player.getArray("results")!!.joinToString(" ") {
|
||||||
|
(it as String).replace("/", "").replace(Regex("(?<=[bw])$"), "0").padStart(7, ' ')
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
writer.println(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val numFormat = DecimalFormat("###0.#")
|
||||||
|
private val frDate: DateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy")
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,6 @@ import com.republicate.kson.Json
|
|||||||
import com.republicate.kson.toJsonArray
|
import com.republicate.kson.toJsonArray
|
||||||
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
||||||
import org.jeudego.pairgoth.model.TeamTournament
|
import org.jeudego.pairgoth.model.TeamTournament
|
||||||
import org.jeudego.pairgoth.server.Event
|
|
||||||
import org.jeudego.pairgoth.server.Event.*
|
import org.jeudego.pairgoth.server.Event.*
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
import javax.servlet.http.HttpServletResponse
|
import javax.servlet.http.HttpServletResponse
|
||||||
@@ -26,7 +25,7 @@ object TeamHandler: PairgothApiHandler {
|
|||||||
val payload = getObjectPayload(request)
|
val payload = getObjectPayload(request)
|
||||||
val team = tournament.teamFromJson(payload)
|
val team = tournament.teamFromJson(payload)
|
||||||
tournament.teams[team.id] = team
|
tournament.teams[team.id] = team
|
||||||
tournament.dispatchEvent(teamAdded, team.toJson())
|
tournament.dispatchEvent(TeamAdded, team.toJson())
|
||||||
return Json.Object("success" to true, "id" to team.id)
|
return Json.Object("success" to true, "id" to team.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +37,7 @@ object TeamHandler: PairgothApiHandler {
|
|||||||
val payload = getObjectPayload(request)
|
val payload = getObjectPayload(request)
|
||||||
val updated = tournament.teamFromJson(payload, team)
|
val updated = tournament.teamFromJson(payload, team)
|
||||||
tournament.teams[updated.id] = updated
|
tournament.teams[updated.id] = updated
|
||||||
tournament.dispatchEvent(teamUpdated, team.toJson())
|
tournament.dispatchEvent(TeamUpdated, team.toJson())
|
||||||
return Json.Object("success" to true)
|
return Json.Object("success" to true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +46,7 @@ object TeamHandler: PairgothApiHandler {
|
|||||||
if (tournament !is TeamTournament) badRequest("tournament is not a team tournament")
|
if (tournament !is TeamTournament) badRequest("tournament is not a team tournament")
|
||||||
val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid team selector")
|
val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid team selector")
|
||||||
tournament.teams.remove(id) ?: badRequest("invalid team id")
|
tournament.teams.remove(id) ?: badRequest("invalid team id")
|
||||||
tournament.dispatchEvent(teamDeleted, Json.Object("id" to id))
|
tournament.dispatchEvent(TeamDeleted, Json.Object("id" to id))
|
||||||
return Json.Object("success" to true)
|
return Json.Object("success" to true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,6 @@ import org.jeudego.pairgoth.model.fromJson
|
|||||||
import org.jeudego.pairgoth.model.toJson
|
import org.jeudego.pairgoth.model.toJson
|
||||||
import org.jeudego.pairgoth.store.Store
|
import org.jeudego.pairgoth.store.Store
|
||||||
import org.jeudego.pairgoth.server.ApiServlet
|
import org.jeudego.pairgoth.server.ApiServlet
|
||||||
import org.jeudego.pairgoth.server.Event
|
|
||||||
import org.jeudego.pairgoth.server.Event.*
|
import org.jeudego.pairgoth.server.Event.*
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
@@ -44,7 +43,7 @@ object TournamentHandler: PairgothApiHandler {
|
|||||||
else -> badRequest("missing or invalid payload")
|
else -> badRequest("missing or invalid payload")
|
||||||
}
|
}
|
||||||
Store.addTournament(tournament)
|
Store.addTournament(tournament)
|
||||||
tournament.dispatchEvent(tournamentAdded, tournament.toJson())
|
tournament.dispatchEvent(TournamentAdded, tournament.toJson())
|
||||||
return Json.Object("success" to true, "id" to tournament.id)
|
return Json.Object("success" to true, "id" to tournament.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,14 +63,14 @@ object TournamentHandler: PairgothApiHandler {
|
|||||||
clear()
|
clear()
|
||||||
putAll(tournament.games(round))
|
putAll(tournament.games(round))
|
||||||
}
|
}
|
||||||
updated.dispatchEvent(tournamentUpdated, updated.toJson())
|
updated.dispatchEvent(TournamentUpdated, updated.toJson())
|
||||||
return Json.Object("success" to true)
|
return Json.Object("success" to true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun delete(request: HttpServletRequest): Json {
|
override fun delete(request: HttpServletRequest): Json {
|
||||||
val tournament = getTournament(request)
|
val tournament = getTournament(request)
|
||||||
Store.deleteTournament(tournament)
|
Store.deleteTournament(tournament)
|
||||||
tournament.dispatchEvent(tournamentDeleted, Json.Object("id" to tournament.id))
|
tournament.dispatchEvent(TournamentDeleted, Json.Object("id" to tournament.id))
|
||||||
return Json.Object("success" to true)
|
return Json.Object("success" to true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@ package org.jeudego.pairgoth.ext
|
|||||||
|
|
||||||
import jakarta.xml.bind.JAXBContext
|
import jakarta.xml.bind.JAXBContext
|
||||||
import jakarta.xml.bind.JAXBElement
|
import jakarta.xml.bind.JAXBElement
|
||||||
import kotlinx.datetime.LocalDate
|
import java.time.LocalDate
|
||||||
import org.jeudego.pairgoth.model.*
|
import org.jeudego.pairgoth.model.*
|
||||||
import org.jeudego.pairgoth.opengotha.TournamentType
|
import org.jeudego.pairgoth.opengotha.TournamentType
|
||||||
import org.jeudego.pairgoth.opengotha.ObjectFactory
|
import org.jeudego.pairgoth.opengotha.ObjectFactory
|
||||||
@@ -13,7 +13,7 @@ import javax.xml.datatype.XMLGregorianCalendar
|
|||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
private const val MILLISECONDS_PER_DAY = 86400000
|
private const val MILLISECONDS_PER_DAY = 86400000
|
||||||
fun XMLGregorianCalendar.toLocalDate() = LocalDate(year, month, day)
|
fun XMLGregorianCalendar.toLocalDate() = LocalDate.of(year, month, day)
|
||||||
|
|
||||||
object OpenGotha {
|
object OpenGotha {
|
||||||
|
|
||||||
@@ -114,10 +114,10 @@ object OpenGotha {
|
|||||||
location = genParams.location,
|
location = genParams.location,
|
||||||
online = genParams.isBInternet ?: false,
|
online = genParams.isBInternet ?: false,
|
||||||
timeSystem = when (genParams.complementaryTimeSystem) {
|
timeSystem = when (genParams.complementaryTimeSystem) {
|
||||||
"SUDDENDEATH" -> SuddenDeath(genParams.basicTime)
|
"SUDDENDEATH" -> SuddenDeath(genParams.basicTime * 60)
|
||||||
"STDBYOYOMI" -> StandardByoyomi(genParams.basicTime, genParams.stdByoYomiTime, 1) // no periods?
|
"STDBYOYOMI" -> StandardByoyomi(genParams.basicTime * 60, genParams.stdByoYomiTime, 1) // no periods?
|
||||||
"CANBYOYOMI" -> CanadianByoyomi(genParams.basicTime, genParams.canByoYomiTime, genParams.nbMovesCanTime)
|
"CANBYOYOMI" -> CanadianByoyomi(genParams.basicTime * 60, genParams.canByoYomiTime, genParams.nbMovesCanTime)
|
||||||
"FISCHER" -> FischerTime(genParams.basicTime, genParams.fischerTime)
|
"FISCHER" -> FischerTime(genParams.basicTime * 60, genParams.fischerTime)
|
||||||
else -> throw Error("missing byoyomi type")
|
else -> throw Error("missing byoyomi type")
|
||||||
},
|
},
|
||||||
pairing = when (handParams.hdCeiling) {
|
pairing = when (handParams.hdCeiling) {
|
||||||
@@ -145,7 +145,8 @@ object OpenGotha {
|
|||||||
rating = player.rating,
|
rating = player.rating,
|
||||||
rank = Pairable.parseRank(player.rank),
|
rank = Pairable.parseRank(player.rank),
|
||||||
country = player.country,
|
country = player.country,
|
||||||
club = player.club
|
club = player.club,
|
||||||
|
final = "FIN" == player.registeringStatus
|
||||||
).also {
|
).also {
|
||||||
player.participating.toString().forEachIndexed { i,c ->
|
player.participating.toString().forEachIndexed { i,c ->
|
||||||
if (c == '0') it.skip.add(i + 1)
|
if (c == '0') it.skip.add(i + 1)
|
||||||
@@ -215,7 +216,9 @@ object OpenGotha {
|
|||||||
player.displayRank()
|
player.displayRank()
|
||||||
}" rating="${
|
}" rating="${
|
||||||
player.rating
|
player.rating
|
||||||
}" ratingOrigin="" registeringStatus="FIN" smmsCorrection="0"/>"""
|
}" ratingOrigin="" registeringStatus="${
|
||||||
|
if (player.final) "FIN" else "PRE"
|
||||||
|
}" smmsCorrection="0"/>"""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</Players>
|
</Players>
|
||||||
@@ -269,9 +272,9 @@ object OpenGotha {
|
|||||||
}
|
}
|
||||||
</ByePlayer>
|
</ByePlayer>
|
||||||
<TournamentParameterSet>
|
<TournamentParameterSet>
|
||||||
<GeneralParameterSet bInternet="${tournament.online}" basicTime="${tournament.timeSystem.mainTime}" beginDate="${tournament.startDate}" canByoYomiTime="${tournament.timeSystem.byoyomi}" complementaryTimeSystem="${when(tournament.timeSystem.type) {
|
<GeneralParameterSet bInternet="${tournament.online}" basicTime="${tournament.timeSystem.mainTime / 60}" beginDate="${tournament.startDate}" canByoYomiTime="${tournament.timeSystem.byoyomi}" complementaryTimeSystem="${when(tournament.timeSystem.type) {
|
||||||
TimeSystem.TimeSystemType.SUDDEN_DEATH -> "SUDDENDEATH"
|
TimeSystem.TimeSystemType.SUDDEN_DEATH -> "SUDDENDEATH"
|
||||||
TimeSystem.TimeSystemType.STANDARD -> "STDBYOYOMI"
|
TimeSystem.TimeSystemType.JAPANESE -> "STDBYOYOMI"
|
||||||
TimeSystem.TimeSystemType.CANADIAN -> "CANBYOYOMI"
|
TimeSystem.TimeSystemType.CANADIAN -> "CANBYOYOMI"
|
||||||
TimeSystem.TimeSystemType.FISCHER -> "FISCHER"
|
TimeSystem.TimeSystemType.FISCHER -> "FISCHER"
|
||||||
} }" director="" endDate="${tournament.endDate}" fischerTime="${tournament.timeSystem.increment}" genCountNotPlayedGamesAsHalfPoint="false" genMMBar="${
|
} }" director="" endDate="${tournament.endDate}" fischerTime="${tournament.timeSystem.increment}" genCountNotPlayedGamesAsHalfPoint="false" genMMBar="${
|
||||||
|
@@ -7,10 +7,10 @@ import java.util.*
|
|||||||
|
|
||||||
// Pairable
|
// Pairable
|
||||||
|
|
||||||
sealed class Pairable(val id: ID, val name: String, open val rating: Int, open val rank: Int) {
|
sealed class Pairable(val id: ID, val name: String, open val rating: Int, open val rank: Int, val final: Boolean, val mmsCorrection: Int = 0) {
|
||||||
companion object {
|
companion object {
|
||||||
val MIN_RANK: Int = -30 // 30k
|
const val MIN_RANK: Int = -30 // 30k
|
||||||
val MAX_RANK: Int = 8 // 9D
|
const val MAX_RANK: Int = 8 // 9D
|
||||||
}
|
}
|
||||||
abstract fun toJson(): Json.Object
|
abstract fun toJson(): Json.Object
|
||||||
abstract fun toMutableJson(): Json.MutableObject
|
abstract fun toMutableJson(): Json.MutableObject
|
||||||
@@ -26,7 +26,7 @@ sealed class Pairable(val id: ID, val name: String, open val rating: Int, open v
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object ByePlayer: Pairable(0, "bye", 0, Int.MIN_VALUE) {
|
object ByePlayer: Pairable(0, "bye", 0, Int.MIN_VALUE, true) {
|
||||||
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")
|
||||||
}
|
}
|
||||||
@@ -70,8 +70,10 @@ class Player(
|
|||||||
rating: Int,
|
rating: Int,
|
||||||
rank: Int,
|
rank: Int,
|
||||||
override var country: String,
|
override var country: String,
|
||||||
override var club: String
|
override var club: String,
|
||||||
): Pairable(id, name, rating, rank) {
|
final: Boolean,
|
||||||
|
mmsCorrection: Int = 0
|
||||||
|
): Pairable(id, name, rating, rank, final, mmsCorrection) {
|
||||||
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>()
|
||||||
@@ -82,9 +84,11 @@ class Player(
|
|||||||
"rating" to rating,
|
"rating" to rating,
|
||||||
"rank" to rank,
|
"rank" to rank,
|
||||||
"country" to country,
|
"country" to country,
|
||||||
"club" to club
|
"club" to club,
|
||||||
|
"final" to final
|
||||||
).also { json ->
|
).also { json ->
|
||||||
if (skip.isNotEmpty()) json["skip"] = Json.Array(skip)
|
if (skip.isNotEmpty()) json["skip"] = Json.Array(skip)
|
||||||
|
if (mmsCorrection != 0) json["mmsCorrection"] = mmsCorrection
|
||||||
externalIds.forEach { (dbid, id) ->
|
externalIds.forEach { (dbid, id) ->
|
||||||
json[dbid.key] = id
|
json[dbid.key] = id
|
||||||
}
|
}
|
||||||
@@ -103,7 +107,9 @@ fun Player.Companion.fromJson(json: Json.Object, default: Player? = null) = Play
|
|||||||
rating = json.getInt("rating") ?: default?.rating ?: badRequest("missing rating"),
|
rating = json.getInt("rating") ?: default?.rating ?: badRequest("missing rating"),
|
||||||
rank = json.getInt("rank") ?: default?.rank ?: badRequest("missing rank"),
|
rank = json.getInt("rank") ?: default?.rank ?: badRequest("missing rank"),
|
||||||
country = json.getString("country") ?: default?.country ?: badRequest("missing country"),
|
country = json.getString("country") ?: default?.country ?: badRequest("missing country"),
|
||||||
club = json.getString("club") ?: default?.club ?: badRequest("missing club")
|
club = json.getString("club") ?: default?.club ?: badRequest("missing club"),
|
||||||
|
final = json.getBoolean("final") ?: default?.final ?: true,
|
||||||
|
mmsCorrection = json.getInt("mmsCorrection") ?: default?.mmsCorrection ?: 0
|
||||||
).also { player ->
|
).also { player ->
|
||||||
player.skip.clear()
|
player.skip.clear()
|
||||||
json.getArray("skip")?.let {
|
json.getArray("skip")?.let {
|
||||||
|
@@ -6,6 +6,7 @@ import org.jeudego.pairgoth.model.MainCritParams.SeedMethod.SPLIT_AND_SLIP
|
|||||||
import org.jeudego.pairgoth.model.PairingType.*
|
import org.jeudego.pairgoth.model.PairingType.*
|
||||||
import org.jeudego.pairgoth.pairing.solver.MacMahonSolver
|
import org.jeudego.pairgoth.pairing.solver.MacMahonSolver
|
||||||
import org.jeudego.pairgoth.pairing.solver.SwissSolver
|
import org.jeudego.pairgoth.pairing.solver.SwissSolver
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
// base pairing parameters
|
// base pairing parameters
|
||||||
data class BaseCritParams(
|
data class BaseCritParams(
|
||||||
@@ -172,7 +173,7 @@ class Swiss(
|
|||||||
): Pairing(SWISS, pairingParams, placementParams) {
|
): Pairing(SWISS, pairingParams, placementParams) {
|
||||||
companion object {}
|
companion object {}
|
||||||
override fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): List<Game> {
|
override fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): List<Game> {
|
||||||
return SwissSolver(round, tournament.historyBefore(round), pairables, pairingParams, placementParams).pair()
|
return SwissSolver(round, tournament.historyBefore(round), pairables, pairingParams, placementParams, tournament.usedTables(round)).pair()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +202,7 @@ class MacMahon(
|
|||||||
): Pairing(MAC_MAHON, pairingParams, placementParams) {
|
): Pairing(MAC_MAHON, pairingParams, placementParams) {
|
||||||
companion object {}
|
companion object {}
|
||||||
override fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): List<Game> {
|
override fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): List<Game> {
|
||||||
return MacMahonSolver(round, tournament.historyBefore(round), pairables, pairingParams, placementParams, mmFloor, mmBar).pair()
|
return MacMahonSolver(round, tournament.historyBefore(round), pairables, pairingParams, placementParams, tournament.usedTables(round), mmFloor, mmBar).pair()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
package org.jeudego.pairgoth.model
|
package org.jeudego.pairgoth.model
|
||||||
|
|
||||||
import com.republicate.kson.Json
|
import com.republicate.kson.Json
|
||||||
import org.jeudego.pairgoth.api.ApiHandler
|
|
||||||
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
||||||
import org.jeudego.pairgoth.model.TimeSystem.TimeSystemType.*
|
import org.jeudego.pairgoth.model.TimeSystem.TimeSystemType.*
|
||||||
|
|
||||||
@@ -15,7 +14,7 @@ data class TimeSystem(
|
|||||||
val stones: Int
|
val stones: Int
|
||||||
) {
|
) {
|
||||||
companion object {}
|
companion object {}
|
||||||
enum class TimeSystemType { CANADIAN, STANDARD, FISCHER, SUDDEN_DEATH }
|
enum class TimeSystemType { CANADIAN, JAPANESE, FISCHER, SUDDEN_DEATH }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun CanadianByoyomi(mainTime: Int, byoyomi: Int, stones: Int) =
|
fun CanadianByoyomi(mainTime: Int, byoyomi: Int, stones: Int) =
|
||||||
@@ -30,7 +29,7 @@ fun CanadianByoyomi(mainTime: Int, byoyomi: Int, stones: Int) =
|
|||||||
|
|
||||||
fun StandardByoyomi(mainTime: Int, byoyomi: Int, periods: Int) =
|
fun StandardByoyomi(mainTime: Int, byoyomi: Int, periods: Int) =
|
||||||
TimeSystem(
|
TimeSystem(
|
||||||
type = STANDARD,
|
type = JAPANESE,
|
||||||
mainTime = mainTime,
|
mainTime = mainTime,
|
||||||
increment = 0,
|
increment = 0,
|
||||||
byoyomi = byoyomi,
|
byoyomi = byoyomi,
|
||||||
@@ -86,9 +85,16 @@ fun TimeSystem.Companion.fromJson(json: Json.Object) =
|
|||||||
|
|
||||||
fun TimeSystem.toJson() = when (type) {
|
fun TimeSystem.toJson() = when (type) {
|
||||||
TimeSystem.TimeSystemType.CANADIAN -> Json.Object("type" to type.name, "mainTime" to mainTime, "byoyomi" to byoyomi, "stones" to stones)
|
TimeSystem.TimeSystemType.CANADIAN -> Json.Object("type" to type.name, "mainTime" to mainTime, "byoyomi" to byoyomi, "stones" to stones)
|
||||||
TimeSystem.TimeSystemType.STANDARD -> Json.Object("type" to type.name, "mainTime" to mainTime, "byoyomi" to byoyomi, "periods" to periods)
|
TimeSystem.TimeSystemType.JAPANESE -> Json.Object("type" to type.name, "mainTime" to mainTime, "byoyomi" to byoyomi, "periods" to periods)
|
||||||
TimeSystem.TimeSystemType.FISCHER ->
|
TimeSystem.TimeSystemType.FISCHER ->
|
||||||
if (maxTime == Int.MAX_VALUE) Json.Object("type" to type.name, "mainTime" to mainTime, "increment" to increment)
|
if (maxTime == Int.MAX_VALUE) Json.Object("type" to type.name, "mainTime" to mainTime, "increment" to increment)
|
||||||
else Json.Object("type" to type.name, "mainTime" to mainTime, "increment" to increment, "maxTime" to maxTime)
|
else Json.Object("type" to type.name, "mainTime" to mainTime, "increment" to increment, "maxTime" to maxTime)
|
||||||
TimeSystem.TimeSystemType.SUDDEN_DEATH -> Json.Object("type" to type.name, "mainTime" to mainTime)
|
TimeSystem.TimeSystemType.SUDDEN_DEATH -> Json.Object("type" to type.name, "mainTime" to mainTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun TimeSystem.adjustedTime() = when (type) {
|
||||||
|
TimeSystem.TimeSystemType.CANADIAN -> mainTime + 60 * byoyomi / stones
|
||||||
|
TimeSystem.TimeSystemType.JAPANESE -> mainTime + 45 * byoyomi
|
||||||
|
TimeSystem.TimeSystemType.FISCHER -> mainTime + 120 * increment
|
||||||
|
TimeSystem.TimeSystemType.SUDDEN_DEATH -> mainTime
|
||||||
|
}
|
||||||
|
@@ -2,7 +2,9 @@ package org.jeudego.pairgoth.model
|
|||||||
|
|
||||||
import com.republicate.kson.Json
|
import com.republicate.kson.Json
|
||||||
import com.republicate.kson.toJsonArray
|
import com.republicate.kson.toJsonArray
|
||||||
import kotlinx.datetime.LocalDate
|
// CB TODO - review
|
||||||
|
//import kotlinx.datetime.LocalDate
|
||||||
|
import java.time.LocalDate
|
||||||
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
||||||
import org.jeudego.pairgoth.pairing.HistoryHelper
|
import org.jeudego.pairgoth.pairing.HistoryHelper
|
||||||
import org.jeudego.pairgoth.pairing.solver.MacMahonSolver
|
import org.jeudego.pairgoth.pairing.solver.MacMahonSolver
|
||||||
@@ -10,6 +12,7 @@ import org.jeudego.pairgoth.pairing.solver.SwissSolver
|
|||||||
import org.jeudego.pairgoth.store.Store
|
import org.jeudego.pairgoth.store.Store
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
import java.util.*
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
sealed class Tournament <P: Pairable>(
|
sealed class Tournament <P: Pairable>(
|
||||||
@@ -76,9 +79,9 @@ sealed class Tournament <P: Pairable>(
|
|||||||
// TODO cleaner solver instantiation
|
// TODO cleaner solver instantiation
|
||||||
val history = historyBefore(round)
|
val history = historyBefore(round)
|
||||||
val solver = if (pairing is Swiss) {
|
val solver = if (pairing is Swiss) {
|
||||||
SwissSolver(round, history, pairables.values.toList(), pairing.pairingParams, pairing.placementParams)
|
SwissSolver(round, history, pairables.values.toList(), pairing.pairingParams, pairing.placementParams, usedTables(round))
|
||||||
} else if (pairing is MacMahon) {
|
} else if (pairing is MacMahon) {
|
||||||
MacMahonSolver(round, history, pairables.values.toList(), pairing.pairingParams, pairing.placementParams, pairing.mmFloor, pairing.mmBar)
|
MacMahonSolver(round, history, pairables.values.toList(), pairing.pairingParams, pairing.placementParams, usedTables(round), pairing.mmFloor, pairing.mmBar)
|
||||||
} else throw Exception("Invalid tournament type")
|
} else throw Exception("Invalid tournament type")
|
||||||
|
|
||||||
// Recomputes DUDD and hd
|
// Recomputes DUDD and hd
|
||||||
@@ -88,6 +91,29 @@ sealed class Tournament <P: Pairable>(
|
|||||||
game.drawnUpDown = solver.dudd(black, white)
|
game.drawnUpDown = solver.dudd(black, white)
|
||||||
game.handicap = solver.hd(black, white)
|
game.handicap = solver.hd(black, white)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun usedTables(round: Int): BitSet =
|
||||||
|
games(round).values.map { it.table }.fold(BitSet()) { acc, table ->
|
||||||
|
acc.set(table)
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
|
||||||
|
fun renumberTables(round: Int, pivot: Game? = null): Boolean {
|
||||||
|
var changed = false
|
||||||
|
var nextTable = 1
|
||||||
|
games(round).values.filter{ game -> pivot?.let { pivot.id != game.id } ?: true }.sortedBy { game ->
|
||||||
|
val whiteRank = pairables[game.white]?.rating ?: Int.MIN_VALUE
|
||||||
|
val blackRank = pairables[game.black]?.rating ?: Int.MIN_VALUE
|
||||||
|
-(2 * whiteRank + 2 * blackRank) / 2
|
||||||
|
}.forEach { game ->
|
||||||
|
if (pivot != null && nextTable == pivot.table) {
|
||||||
|
++nextTable
|
||||||
|
}
|
||||||
|
changed = changed || game.table != nextTable
|
||||||
|
game.table = nextTable++
|
||||||
|
}
|
||||||
|
return changed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// standard tournament of individuals
|
// standard tournament of individuals
|
||||||
@@ -133,7 +159,7 @@ class TeamTournament(
|
|||||||
override val players = mutableMapOf<ID, Player>()
|
override val players = mutableMapOf<ID, Player>()
|
||||||
val teams: MutableMap<ID, Team> = _pairables
|
val teams: MutableMap<ID, Team> = _pairables
|
||||||
|
|
||||||
inner class Team(id: ID, name: String): Pairable(id, name, 0, 0) {
|
inner class Team(id: ID, name: String, final: Boolean): Pairable(id, name, 0, 0, final) {
|
||||||
val playerIds = mutableSetOf<ID>()
|
val playerIds = mutableSetOf<ID>()
|
||||||
val teamPlayers: Set<Player> get() = playerIds.mapNotNull { players[id] }.toSet()
|
val teamPlayers: Set<Player> get() = playerIds.mapNotNull { players[id] }.toSet()
|
||||||
override val rating: Int get() = if (teamPlayers.isEmpty()) super.rating else (teamPlayers.sumOf { player -> player.rating.toDouble() } / players.size).roundToInt()
|
override val rating: Int get() = if (teamPlayers.isEmpty()) super.rating else (teamPlayers.sumOf { player -> player.rating.toDouble() } / players.size).roundToInt()
|
||||||
@@ -151,7 +177,8 @@ class TeamTournament(
|
|||||||
|
|
||||||
fun teamFromJson(json: Json.Object, default: TeamTournament.Team? = null) = Team(
|
fun teamFromJson(json: Json.Object, default: TeamTournament.Team? = null) = Team(
|
||||||
id = json.getInt("id") ?: default?.id ?: Store.nextPlayerId,
|
id = json.getInt("id") ?: default?.id ?: Store.nextPlayerId,
|
||||||
name = json.getString("name") ?: default?.name ?: badRequest("missing name")
|
name = json.getString("name") ?: default?.name ?: badRequest("missing name"),
|
||||||
|
final = json.getBoolean("final") ?: default?.final ?: badRequest("missing final")
|
||||||
).apply {
|
).apply {
|
||||||
json.getArray("players")?.let { arr ->
|
json.getArray("players")?.let { arr ->
|
||||||
arr.mapTo(playerIds) {
|
arr.mapTo(playerIds) {
|
||||||
@@ -174,8 +201,8 @@ fun Tournament.Companion.fromJson(json: Json.Object, default: Tournament<*>? = n
|
|||||||
type = type,
|
type = type,
|
||||||
name = json.getString("name") ?: default?.name ?: badRequest("missing name"),
|
name = json.getString("name") ?: default?.name ?: badRequest("missing name"),
|
||||||
shortName = json.getString("shortName") ?: default?.shortName ?: badRequest("missing shortName"),
|
shortName = json.getString("shortName") ?: default?.shortName ?: badRequest("missing shortName"),
|
||||||
startDate = json.getLocalDate("startDate") ?: default?.startDate ?: badRequest("missing startDate"),
|
startDate = json.getString("startDate")?.let { LocalDate.parse(it) } ?: default?.startDate ?: badRequest("missing startDate"),
|
||||||
endDate = json.getLocalDate("endDate") ?: default?.endDate ?: badRequest("missing endDate"),
|
endDate = json.getString("endDate")?.let { LocalDate.parse(it) } ?: default?.endDate ?: badRequest("missing endDate"),
|
||||||
country = json.getString("country") ?: default?.country ?: badRequest("missing country"),
|
country = json.getString("country") ?: default?.country ?: badRequest("missing country"),
|
||||||
location = json.getString("location") ?: default?.location ?: badRequest("missing location"),
|
location = json.getString("location") ?: default?.location ?: badRequest("missing location"),
|
||||||
online = json.getBoolean("online") ?: default?.online ?: false,
|
online = json.getBoolean("online") ?: default?.online ?: false,
|
||||||
@@ -192,8 +219,8 @@ fun Tournament.Companion.fromJson(json: Json.Object, default: Tournament<*>? = n
|
|||||||
type = type,
|
type = type,
|
||||||
name = json.getString("name") ?: default?.name ?: badRequest("missing name"),
|
name = json.getString("name") ?: default?.name ?: badRequest("missing name"),
|
||||||
shortName = json.getString("shortName") ?: default?.shortName ?: badRequest("missing shortName"),
|
shortName = json.getString("shortName") ?: default?.shortName ?: badRequest("missing shortName"),
|
||||||
startDate = json.getLocalDate("startDate") ?: default?.startDate ?: badRequest("missing startDate"),
|
startDate = json.getString("startDate")?.let { LocalDate.parse(it) } ?: default?.startDate ?: badRequest("missing startDate"),
|
||||||
endDate = json.getLocalDate("endDate") ?: default?.endDate ?: badRequest("missing endDate"),
|
endDate = json.getString("endDate")?.let { LocalDate.parse(it) } ?: default?.endDate ?: badRequest("missing endDate"),
|
||||||
country = json.getString("country") ?: default?.country ?: badRequest("missing country"),
|
country = json.getString("country") ?: default?.country ?: badRequest("missing country"),
|
||||||
location = json.getString("location") ?: default?.location ?: badRequest("missing location"),
|
location = json.getString("location") ?: default?.location ?: badRequest("missing location"),
|
||||||
online = json.getBoolean("online") ?: default?.online ?: false,
|
online = json.getBoolean("online") ?: default?.online ?: false,
|
||||||
|
@@ -136,11 +136,4 @@ abstract class BasePairingHelper(
|
|||||||
open fun nameSort(p: Pairable, q: Pairable): Int {
|
open fun nameSort(p: Pairable, q: Pairable): Int {
|
||||||
return if (p.name > q.name) 1 else -1
|
return if (p.name > q.name) 1 else -1
|
||||||
}
|
}
|
||||||
|
|
||||||
val tables = history.mapTo(mutableListOf()) { games ->
|
|
||||||
games.map { it.table }.fold(BitSet()) { acc, table ->
|
|
||||||
acc.set(table)
|
|
||||||
acc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -24,6 +24,7 @@ sealed class BaseSolver(
|
|||||||
pairables: List<Pairable>, // All pairables for this round, it may include the bye player
|
pairables: List<Pairable>, // All pairables for this round, it may include the bye player
|
||||||
pairing: PairingParams,
|
pairing: PairingParams,
|
||||||
placement: PlacementParams,
|
placement: PlacementParams,
|
||||||
|
val usedTables: BitSet
|
||||||
) : BasePairingHelper(history, pairables, pairing, placement) {
|
) : BasePairingHelper(history, pairables, pairing, placement) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -540,7 +541,6 @@ sealed class BaseSolver(
|
|||||||
}
|
}
|
||||||
open fun games(black: Pairable, white: Pairable): List<Game> {
|
open fun games(black: Pairable, white: Pairable): List<Game> {
|
||||||
// CB TODO team of individuals pairing
|
// CB TODO team of individuals pairing
|
||||||
val usedTables = tables.getOrNull(round - 1) ?: BitSet().also { tables.add(it) }
|
|
||||||
val table = if (black.id == 0 || white.id == 0) 0 else usedTables.nextClearBit(1)
|
val table = if (black.id == 0 || white.id == 0) 0 else usedTables.nextClearBit(1)
|
||||||
usedTables.set(table)
|
usedTables.set(table)
|
||||||
return listOf(Game(id = Store.nextGameId, table = table, black = black.id, white = white.id, handicap = hd(white, black), drawnUpDown = dudd(black, white)))
|
return listOf(Game(id = Store.nextGameId, table = table, black = black.id, white = white.id, handicap = hd(white, black), drawnUpDown = dudd(black, white)))
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package org.jeudego.pairgoth.pairing.solver
|
package org.jeudego.pairgoth.pairing.solver
|
||||||
|
|
||||||
import org.jeudego.pairgoth.model.*
|
import org.jeudego.pairgoth.model.*
|
||||||
|
import java.util.*
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
@@ -9,9 +10,9 @@ class MacMahonSolver(round: Int,
|
|||||||
pairables: List<Pairable>,
|
pairables: List<Pairable>,
|
||||||
pairingParams: PairingParams,
|
pairingParams: PairingParams,
|
||||||
placementParams: PlacementParams,
|
placementParams: PlacementParams,
|
||||||
|
usedTables: BitSet,
|
||||||
private val mmFloor: Int, private val mmBar: Int):
|
private val mmFloor: Int, private val mmBar: Int):
|
||||||
BaseSolver(round, history, pairables, pairingParams, placementParams) {
|
BaseSolver(round, history, pairables, pairingParams, placementParams, usedTables) {
|
||||||
|
|
||||||
|
|
||||||
override val scores: Map<ID, Double> by lazy {
|
override val scores: Map<ID, Double> by lazy {
|
||||||
require (mmBar > mmFloor) { "MMFloor is higher than MMBar" }
|
require (mmBar > mmFloor) { "MMFloor is higher than MMBar" }
|
||||||
@@ -32,7 +33,7 @@ class MacMahonSolver(round: Int,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val Pairable.mmBase: Double get() = min(max(rank, mmFloor), mmBar) + mmsZero
|
val Pairable.mmBase: Double get() = min(max(rank, mmFloor), mmBar) + mmsZero + mmsCorrection
|
||||||
val Pairable.mms: Double get() = scores[id] ?: 0.0
|
val Pairable.mms: Double get() = scores[id] ?: 0.0
|
||||||
|
|
||||||
// CB TODO - configurable criteria
|
// CB TODO - configurable criteria
|
||||||
|
@@ -1,13 +1,16 @@
|
|||||||
package org.jeudego.pairgoth.pairing.solver
|
package org.jeudego.pairgoth.pairing.solver
|
||||||
|
|
||||||
import org.jeudego.pairgoth.model.*
|
import org.jeudego.pairgoth.model.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class SwissSolver(round: Int,
|
class SwissSolver(round: Int,
|
||||||
history: List<List<Game>>,
|
history: List<List<Game>>,
|
||||||
pairables: List<Pairable>,
|
pairables: List<Pairable>,
|
||||||
pairingParams: PairingParams,
|
pairingParams: PairingParams,
|
||||||
placementParams: PlacementParams):
|
placementParams: PlacementParams,
|
||||||
BaseSolver(round, history, pairables, pairingParams, placementParams) {
|
usedTables: BitSet
|
||||||
|
):
|
||||||
|
BaseSolver(round, history, pairables, pairingParams, placementParams, usedTables) {
|
||||||
|
|
||||||
// In a Swiss tournament the main criterion is the number of wins and already computed
|
// In a Swiss tournament the main criterion is the number of wins and already computed
|
||||||
|
|
||||||
|
@@ -218,8 +218,12 @@ class ApiServlet: HttpServlet() {
|
|||||||
"Missing 'Accept' header"
|
"Missing 'Accept' header"
|
||||||
)
|
)
|
||||||
// CB TODO 1) a reference to a specific API call at this point is a code smell.
|
// CB TODO 1) a reference to a specific API call at this point is a code smell.
|
||||||
// 2) there will e other content types: .tou, .h9, .html
|
// 2) there will be other content types: .tou, .h9, .html
|
||||||
if (!isJson(accept) && (!isXml(accept) || !request.requestURI.matches(Regex("/api/tour/\\d+")))) throw ApiException(
|
if (!isJson(accept) &&
|
||||||
|
(!isXml(accept) || !request.requestURI.matches(Regex("/api/tour/\\d+"))) &&
|
||||||
|
(accept != "application/ffg" && accept != "application/egf" || !request.requestURI.matches(Regex("/api/tour/\\d+/standings/\\d+")))
|
||||||
|
|
||||||
|
) throw ApiException(
|
||||||
HttpServletResponse.SC_BAD_REQUEST,
|
HttpServletResponse.SC_BAD_REQUEST,
|
||||||
"Invalid 'Accept' header"
|
"Invalid 'Accept' header"
|
||||||
)
|
)
|
||||||
|
@@ -4,19 +4,20 @@ import info.macias.sse.events.MessageEvent
|
|||||||
import java.util.concurrent.atomic.AtomicLong
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
|
|
||||||
enum class Event {
|
enum class Event {
|
||||||
tournamentAdded,
|
TournamentAdded,
|
||||||
tournamentUpdated,
|
TournamentUpdated,
|
||||||
tournamentDeleted,
|
TournamentDeleted,
|
||||||
playerAdded,
|
PlayerAdded,
|
||||||
playerUpdated,
|
PlayerUpdated,
|
||||||
playerDeleted,
|
PlayerDeleted,
|
||||||
teamAdded,
|
TeamAdded,
|
||||||
teamUpdated,
|
TeamUpdated,
|
||||||
teamDeleted,
|
TeamDeleted,
|
||||||
gamesAdded,
|
GamesAdded,
|
||||||
gamesDeleted,
|
GamesDeleted,
|
||||||
gameUpdated,
|
GameUpdated,
|
||||||
resultUpdated,
|
ResultUpdated,
|
||||||
|
TablesRenumbered
|
||||||
;
|
;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
3
version.sh
Executable file
3
version.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
mvn versions:set -DnewVersion=$1
|
@@ -153,11 +153,6 @@
|
|||||||
<artifactId>kotlin-reflect</artifactId>
|
<artifactId>kotlin-reflect</artifactId>
|
||||||
<version>${kotlin.version}</version>
|
<version>${kotlin.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.jetbrains.kotlinx</groupId>
|
|
||||||
<artifactId>kotlinx-datetime-jvm</artifactId>
|
|
||||||
<version>0.4.0</version>
|
|
||||||
</dependency>
|
|
||||||
<!-- servlets and mail APIs -->
|
<!-- servlets and mail APIs -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>jakarta.servlet</groupId>
|
<groupId>jakarta.servlet</groupId>
|
||||||
|
@@ -25,6 +25,8 @@ object FFGRatingsHandler: RatingsHandler(RatingsManager.Ratings.FFG) {
|
|||||||
val rating = it["rating"]?.toString()?.toIntOrNull()
|
val rating = it["rating"]?.toString()?.toIntOrNull()
|
||||||
if (rating != null) {
|
if (rating != null) {
|
||||||
it["rank"] = (rating/100).let { if (it < 0) "${-it}k" else "${it+1}d" }
|
it["rank"] = (rating/100).let { if (it < 0) "${-it}k" else "${it+1}d" }
|
||||||
|
// then adjust to match EGF ratings
|
||||||
|
it["rating"] = rating + 2050
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,8 @@ import com.republicate.kson.Json
|
|||||||
class PairgothTool {
|
class PairgothTool {
|
||||||
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 countFinals(array: Json.Array) = array.map { ser -> ser as Json.Object }.count { it.getBoolean("final") ?: false }
|
||||||
|
|
||||||
fun getCriteria() = mapOf(
|
fun getCriteria() = mapOf(
|
||||||
"NONE" to "No tie break", // No ranking / tie-break
|
"NONE" to "No tie break", // No ranking / tie-break
|
||||||
|
|
||||||
@@ -43,4 +45,17 @@ class PairgothTool {
|
|||||||
"SDC" to "Simplified direct confrontation", // Simplified direct confrontation
|
"SDC" to "Simplified direct confrontation", // Simplified direct confrontation
|
||||||
"DC" to "Direct confrontation", // Direct confrontation
|
"DC" to "Direct confrontation", // Direct confrontation
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun getResultsStats(games: Collection<Json.Object>): Json.Object {
|
||||||
|
var total = 0
|
||||||
|
var known = 0
|
||||||
|
games
|
||||||
|
.filter{ it.getInt("b")!! != 0 && it.getInt("w")!! != 0 }
|
||||||
|
.map { it -> it.getString("r") }
|
||||||
|
.forEach {
|
||||||
|
++total
|
||||||
|
if ("?" != it) ++known
|
||||||
|
}
|
||||||
|
return Json.Object("total" to total, "known" to known)
|
||||||
|
}
|
||||||
}
|
}
|
@@ -210,6 +210,24 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ui.form .field :invalid {
|
||||||
|
color: #9f3a38;
|
||||||
|
background: #fff6f6;
|
||||||
|
background-image: initial;
|
||||||
|
background-position-x: initial;
|
||||||
|
background-position-y: initial;
|
||||||
|
background-size: initial;
|
||||||
|
background-repeat-x: initial;
|
||||||
|
background-repeat-y: initial;
|
||||||
|
background-attachment: initial;
|
||||||
|
background-origin: initial;
|
||||||
|
background-clip: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.striped.table>tbody>tr:nth-child(2n),.ui.striped.table>tr:nth-child(2n) {
|
||||||
|
background-color: rgba(0,0,50,.1)
|
||||||
|
}
|
||||||
|
|
||||||
.form-actions {
|
.form-actions {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
bottom: 1em;
|
bottom: 1em;
|
||||||
@@ -359,7 +377,7 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
thead {
|
thead th {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
@@ -371,4 +389,46 @@
|
|||||||
a.disabled {
|
a.disabled {
|
||||||
color: darkgray;
|
color: darkgray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
|
||||||
|
body {
|
||||||
|
width: unset;
|
||||||
|
height: unset;
|
||||||
|
font-size: 0.65em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.roundbox {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#title {
|
||||||
|
font-size: 1rem !important;
|
||||||
|
margin-top: 0.1em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logo, #lang, .steps, #filter-box, #footer, #pairing-left, #pairing-buttons, button, #standings-params, #logout {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circular.label {
|
||||||
|
transform: scale(0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pairing-right {
|
||||||
|
max-width: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#paired {
|
||||||
|
max-height: unset !important;
|
||||||
|
max-width: unset !important;
|
||||||
|
font-size: 1rem !important;
|
||||||
|
line-height: 1.1rem !important;
|
||||||
|
min-width: 60vw;
|
||||||
|
&::before {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -54,6 +54,18 @@
|
|||||||
|
|
||||||
/* registration section */
|
/* registration section */
|
||||||
|
|
||||||
|
#list-header {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#players-list {
|
||||||
|
max-width: 95vw;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
#player {
|
#player {
|
||||||
&.create {
|
&.create {
|
||||||
.edition {
|
.edition {
|
||||||
@@ -138,6 +150,35 @@
|
|||||||
|
|
||||||
#player.popup {
|
#player.popup {
|
||||||
min-width: 65vw;
|
min-width: 65vw;
|
||||||
|
#final-reg {
|
||||||
|
.final {
|
||||||
|
color: green;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
&.final {
|
||||||
|
.preliminary {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.final {
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
td.reg-status {
|
||||||
|
.final {
|
||||||
|
color: green;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
&.final {
|
||||||
|
.final {
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
|
.preliminary {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* pairing section */
|
/* pairing section */
|
||||||
@@ -195,8 +236,30 @@
|
|||||||
background-color: rgba(100,200,255,200);
|
background-color: rgba(100,200,255,200);
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
}
|
}
|
||||||
|
&:not(.selected):nth-child(2n) {
|
||||||
|
background-color: rgba(0,0,50,.1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
#pairables {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
#paired {
|
||||||
|
.listitem {
|
||||||
|
position: relative;
|
||||||
|
gap: 0;
|
||||||
|
.table, .handicap {
|
||||||
|
width: 3em;
|
||||||
|
}
|
||||||
|
.black, .white {
|
||||||
|
width: 45%;
|
||||||
|
}
|
||||||
|
.levels {
|
||||||
|
width: 6em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
#pairing-buttons {
|
#pairing-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column nowrap;
|
flex-flow: column nowrap;
|
||||||
@@ -280,4 +343,8 @@
|
|||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#standings-container {
|
||||||
|
max-width: 95vw;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -145,4 +145,6 @@ the configuration guide le guide de configuration
|
|||||||
to à
|
to à
|
||||||
unpairable players joueurs non disponibles
|
unpairable players joueurs non disponibles
|
||||||
version 0.1 supports the version 0.1 supporte le système d’appariement
|
version 0.1 supports the version 0.1 supporte le système d’appariement
|
||||||
|
white blanc
|
||||||
white vs. black blanc vs. Noir
|
white vs. black blanc vs. Noir
|
||||||
|
confirmed. confirmé(s).
|
@@ -9,13 +9,16 @@ const apiVersion = '1.0';
|
|||||||
// .catch(err => { ... });
|
// .catch(err => { ... });
|
||||||
|
|
||||||
const base = '/api/';
|
const base = '/api/';
|
||||||
let headers = function() {
|
let headers = function(withJson) {
|
||||||
let ret = {
|
let ret = {
|
||||||
"Content-Type": "application/json; charset=utf-8",
|
'Accept-Version': apiVersion,
|
||||||
"Accept-Version": apiVersion,
|
'Accept': 'application/json',
|
||||||
"Accept": "application/json",
|
'X-Browser-Key': store('browserKey')
|
||||||
"X-Browser-Key": store('browserKey')
|
|
||||||
};
|
};
|
||||||
|
if (typeof(withJson) === 'undefined') withJson = true;
|
||||||
|
if (withJson) {
|
||||||
|
ret['Content-Type'] = 'application/json';
|
||||||
|
}
|
||||||
let accessToken = store('accessToken');
|
let accessToken = store('accessToken');
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
ret['Authorization'] = `Bearer ${accessToken}`;
|
ret['Authorization'] = `Bearer ${accessToken}`;
|
||||||
|
@@ -120,8 +120,14 @@ Element.prototype.hide = function() {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
NodeList.prototype.text = function(txt) {
|
NodeList.prototype.text = function(txt) {
|
||||||
this.item(0).text(txt);
|
if (typeof(txt) === 'undefined') {
|
||||||
|
return this.item(0).text();
|
||||||
|
} else {
|
||||||
|
this.forEach(elem => {
|
||||||
|
elem.text(txt);
|
||||||
|
});
|
||||||
return this;
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Element.prototype.text = function(txt) {
|
Element.prototype.text = function(txt) {
|
||||||
if (typeof(txt) === 'undefined') {
|
if (typeof(txt) === 'undefined') {
|
||||||
|
@@ -161,6 +161,7 @@ function modal(id) {
|
|||||||
function close_modal() {
|
function close_modal() {
|
||||||
$('body').removeClass('dimmed');
|
$('body').removeClass('dimmed');
|
||||||
$(`.popup`).removeClass('shown');
|
$(`.popup`).removeClass('shown');
|
||||||
|
store('addingPlayers', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadFile(blob, filename) {
|
function downloadFile(blob, filename) {
|
||||||
@@ -189,17 +190,23 @@ onLoad(() => {
|
|||||||
|
|
||||||
// keyboard handling
|
// keyboard handling
|
||||||
document.on('keyup', e => {
|
document.on('keyup', e => {
|
||||||
|
let tab = document.location.hash;
|
||||||
switch (e.key) {
|
switch (e.key) {
|
||||||
case 'Escape': {
|
case 'Escape': {
|
||||||
|
if (tab === '#registration') {
|
||||||
if ($('#player').hasClass('shown') && $('#needle')[0].value) {
|
if ($('#player').hasClass('shown') && $('#needle')[0].value) {
|
||||||
$('#needle')[0].value = '';
|
$('#needle')[0].value = '';
|
||||||
initSearch();
|
initSearch();
|
||||||
} else {
|
} else {
|
||||||
close_modal();
|
close_modal();
|
||||||
}
|
}
|
||||||
|
} else if (tab === '#pairing') {
|
||||||
|
$('#pairing-lists .selected.listitem').removeClass('selected');
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'ArrowDown': {
|
case 'ArrowDown': {
|
||||||
|
if (tab === '#registration') {
|
||||||
if (typeof(searchResultShown) === 'function' && searchResultShown()) {
|
if (typeof(searchResultShown) === 'function' && searchResultShown()) {
|
||||||
let lines = $('.result-line');
|
let lines = $('.result-line');
|
||||||
if (typeof (searchHighlight) === 'undefined') searchHighlight = 0;
|
if (typeof (searchHighlight) === 'undefined') searchHighlight = 0;
|
||||||
@@ -208,9 +215,11 @@ onLoad(() => {
|
|||||||
lines.removeClass('highlighted');
|
lines.removeClass('highlighted');
|
||||||
lines[searchHighlight].addClass('highlighted');
|
lines[searchHighlight].addClass('highlighted');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'ArrowUp': {
|
case 'ArrowUp': {
|
||||||
|
if (tab === '#registration') {
|
||||||
if (typeof(searchResultShown) === 'function' && searchResultShown()) {
|
if (typeof(searchResultShown) === 'function' && searchResultShown()) {
|
||||||
let lines = $('.result-line');
|
let lines = $('.result-line');
|
||||||
if (typeof (searchHighlight) === 'undefined') searchHighlight = 0;
|
if (typeof (searchHighlight) === 'undefined') searchHighlight = 0;
|
||||||
@@ -219,9 +228,11 @@ onLoad(() => {
|
|||||||
lines.removeClass('highlighted');
|
lines.removeClass('highlighted');
|
||||||
lines[searchHighlight].addClass('highlighted');
|
lines[searchHighlight].addClass('highlighted');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'Enter': {
|
case 'Enter': {
|
||||||
|
if (tab === '#registration') {
|
||||||
if (typeof(searchResultShown) === 'function') {
|
if (typeof(searchResultShown) === 'function') {
|
||||||
if (searchResultShown()) {
|
if (searchResultShown()) {
|
||||||
fillPlayer(searchResult[searchHighlight]);
|
fillPlayer(searchResult[searchHighlight]);
|
||||||
@@ -229,6 +240,7 @@ onLoad(() => {
|
|||||||
$('#register')[0].click();
|
$('#register')[0].click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,5 +253,18 @@ onLoad(() => {
|
|||||||
}, 1);
|
}, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// persistent scroll
|
||||||
|
$('#center').on('scroll', e => {
|
||||||
|
let scroll = $('#center')[0].scrollTop;
|
||||||
|
store('scroll', scroll);
|
||||||
|
});
|
||||||
|
let persistentScroll = store('scroll');
|
||||||
|
if (persistentScroll) {
|
||||||
|
setTimeout(() => {
|
||||||
|
$('#center')[0].scrollTop = persistentScroll;
|
||||||
|
let scroll = $('#center')[0].scrollTop;
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -118,7 +118,6 @@ onLoad(() => {
|
|||||||
$('#tournament-infos').on('submit', e => {
|
$('#tournament-infos').on('submit', e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let form = e.target;
|
let form = e.target;
|
||||||
console.log(form.val('country'));
|
|
||||||
let tour = {
|
let tour = {
|
||||||
name: form.val('name'),
|
name: form.val('name'),
|
||||||
shortName: form.val('shortName'),
|
shortName: form.val('shortName'),
|
||||||
|
@@ -18,6 +18,26 @@ function unpair(games) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function editGame(game) {
|
||||||
|
let t = game.find('.table');
|
||||||
|
let w = game.find('.white');
|
||||||
|
let b = game.find('.black');
|
||||||
|
let h = game.find('.handicap');
|
||||||
|
|
||||||
|
let form = $('#pairing-form')[0];
|
||||||
|
form.val('id', game.data('id'));
|
||||||
|
form.val('t', t.data('value'));
|
||||||
|
form.val('w', w.data('id'));
|
||||||
|
$('#edit-pairing-white').text(w.text());
|
||||||
|
form.val('b', b.data('id'));
|
||||||
|
$('#edit-pairing-black').text(b.text());
|
||||||
|
form.val('h', h.data('value'));
|
||||||
|
|
||||||
|
$('#update-pairing').addClass('disabled');
|
||||||
|
|
||||||
|
modal('edit-pairing');
|
||||||
|
}
|
||||||
|
|
||||||
onLoad(()=>{
|
onLoad(()=>{
|
||||||
$('.listitem').on('click', e => {
|
$('.listitem').on('click', e => {
|
||||||
if (e.shiftKey && typeof(focused) !== 'undefined') {
|
if (e.shiftKey && typeof(focused) !== 'undefined') {
|
||||||
@@ -31,13 +51,17 @@ onLoad(()=>{
|
|||||||
let parent = e.target.closest('.multi-select');
|
let parent = e.target.closest('.multi-select');
|
||||||
let children = parent.childNodes.filter('.listitem');
|
let children = parent.childNodes.filter('.listitem');
|
||||||
for (let j = from; j <= to; ++j) { new Tablesort($('#players')[0]);
|
for (let j = from; j <= to; ++j) { new Tablesort($('#players')[0]);
|
||||||
|
|
||||||
children.item(j).addClass('selected');
|
children.item(j).addClass('selected');
|
||||||
children.item(j).attr('draggable', true);
|
children.item(j).attr('draggable', true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let target = e.target.closest('.listitem');
|
let target = e.target.closest('.listitem');
|
||||||
|
if (e.detail === 1) {
|
||||||
focused = target.toggleClass('selected').attr('draggable', target.hasClass('selected'));
|
focused = target.toggleClass('selected').attr('draggable', target.hasClass('selected'));
|
||||||
|
} else {
|
||||||
|
focused = target.attr('draggable', target.hasClass('selected'));
|
||||||
|
editGame(focused);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$('#pair').on('click', e => {
|
$('#pair').on('click', e => {
|
||||||
@@ -56,4 +80,39 @@ onLoad(()=>{
|
|||||||
}
|
}
|
||||||
unpair(games);
|
unpair(games);
|
||||||
});
|
});
|
||||||
|
$('#pairing-form [name]').on('input', e => {
|
||||||
|
$('#update-pairing').removeClass('disabled');
|
||||||
|
});
|
||||||
|
$('#pairing-exchange').on('click', e => {
|
||||||
|
let form = $('#pairing-form')[0];
|
||||||
|
let w = form.val('w');
|
||||||
|
let b = form.val('b');
|
||||||
|
form.val('w', b);
|
||||||
|
form.val('b', w);
|
||||||
|
let wName = $('#edit-pairing-white').text();
|
||||||
|
let bName = $('#edit-pairing-black').text();
|
||||||
|
$('#edit-pairing-white').text(bName);
|
||||||
|
$('#edit-pairing-black').text(wName);
|
||||||
|
$('#update-pairing').removeClass('disabled');
|
||||||
|
});
|
||||||
|
$('#pairing-form').on('submit', e => {
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
$('#update-pairing').on('click', e => {
|
||||||
|
let form = $('#pairing-form')[0];
|
||||||
|
let game = {
|
||||||
|
id: form.val('id'),
|
||||||
|
t: form.val('t'),
|
||||||
|
w: form.val('w'),
|
||||||
|
b: form.val('b'),
|
||||||
|
h: form.val('h')
|
||||||
|
}
|
||||||
|
api.putJson(`tour/${tour_id}/pair/${activeRound}`, game)
|
||||||
|
.then(game => {
|
||||||
|
if (game !== 'error') {
|
||||||
|
document.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -67,18 +67,45 @@ function parseRank(rank) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function fillPlayer(player) {
|
function fillPlayer(player) {
|
||||||
|
// hack UK / GB
|
||||||
|
let country = player.country.toLowerCase();
|
||||||
|
if ('uk' === country) country = 'gb';
|
||||||
let form = $('#player-form')[0];
|
let form = $('#player-form')[0];
|
||||||
form.val('name', player.name);
|
form.val('name', player.name);
|
||||||
form.val('firstname', player.firstname);
|
form.val('firstname', player.firstname);
|
||||||
form.val('country', player.country.toLowerCase());
|
console.log(country);
|
||||||
|
form.val('country', country);
|
||||||
form.val('club', player.club);
|
form.val('club', player.club);
|
||||||
form.val('rank', parseRank(player.rank));
|
form.val('rank', parseRank(player.rank));
|
||||||
form.val('rating', player.rating);
|
form.val('rating', player.rating);
|
||||||
|
form.val('final', false);
|
||||||
$('#needle')[0].value = '';
|
$('#needle')[0].value = '';
|
||||||
initSearch();
|
initSearch();
|
||||||
$('#register').focus();
|
$('#register').focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addPlayers() {
|
||||||
|
let form = $('#player-form')[0];
|
||||||
|
form.addClass('add');
|
||||||
|
// keep preliminary/final status
|
||||||
|
let status = form.val('final') || false;
|
||||||
|
form.reset();
|
||||||
|
// initial search checkboxes position
|
||||||
|
['countryFilter', 'aga', 'egf', 'ffg'].forEach(id => {
|
||||||
|
let value = store(id);
|
||||||
|
if (value !== null && typeof(value) !== 'undefined') {
|
||||||
|
$(`#${id}`)[0].checked = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
form.val('final', status);
|
||||||
|
$('#player').removeClass('edit').addClass('create');
|
||||||
|
modal('player');
|
||||||
|
$('#needle').focus();
|
||||||
|
store('addingPlayers', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let tableSort;
|
||||||
|
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
$('input.numeric').imask({
|
$('input.numeric').imask({
|
||||||
mask: Number,
|
mask: Number,
|
||||||
@@ -86,15 +113,39 @@ onLoad(() => {
|
|||||||
min: 0,
|
min: 0,
|
||||||
max: 4000
|
max: 4000
|
||||||
});
|
});
|
||||||
new Tablesort($('#players')[0]);
|
|
||||||
|
let prevSort = store('registrationSort');
|
||||||
|
if (prevSort) {
|
||||||
|
let columns = $('#players thead th');
|
||||||
|
columns.forEach(th => {
|
||||||
|
th.removeAttribute('data-sort-default');
|
||||||
|
th.removeAttribute('aria-sort');
|
||||||
|
})
|
||||||
|
prevSort.forEach(i => {
|
||||||
|
let col = columns[Math.abs(i)];
|
||||||
|
col.setAttribute('data-sort-default', '1');
|
||||||
|
if (i < 0) {
|
||||||
|
// take into account TableSort initiailization bug
|
||||||
|
col.setAttribute('aria-sort', 'ascending');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tableSort = new Tablesort($('#players')[0]);
|
||||||
|
$('#players').on('afterSort', e => {
|
||||||
|
let sort = [];
|
||||||
|
$('#players thead th').forEach((th, i) => {
|
||||||
|
let attr = th.attr('aria-sort');
|
||||||
|
if (attr) {
|
||||||
|
let dir = i;
|
||||||
|
if (attr === 'descending') dir = -dir;
|
||||||
|
sort.push(dir);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
store('registrationSort', sort);
|
||||||
|
});
|
||||||
|
|
||||||
$('#add').on('click', e => {
|
$('#add').on('click', e => {
|
||||||
let form = $('#player-form')[0];
|
addPlayers();
|
||||||
form.addClass('add');
|
|
||||||
// $('#player-form input.participation').forEach(chk => chk.checked = true);
|
|
||||||
form.reset();
|
|
||||||
$('#player').removeClass('edit').addClass('create');
|
|
||||||
modal('player');
|
|
||||||
$('#needle').focus();
|
|
||||||
});
|
});
|
||||||
$('#cancel-register').on('click', e => {
|
$('#cancel-register').on('click', e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -122,7 +173,6 @@ onLoad(() => {
|
|||||||
$('#player-form')[0].dispatchEvent(new CustomEvent('submit', {cancelable: true}));
|
$('#player-form')[0].dispatchEvent(new CustomEvent('submit', {cancelable: true}));
|
||||||
});
|
});
|
||||||
$('#player-form').on('submit', e => {
|
$('#player-form').on('submit', e => {
|
||||||
("submitting!!")
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let form = $('#player-form')[0];
|
let form = $('#player-form')[0];
|
||||||
let player = {
|
let player = {
|
||||||
@@ -132,11 +182,13 @@ onLoad(() => {
|
|||||||
rank: form.val('rank'),
|
rank: form.val('rank'),
|
||||||
country: form.val('country'),
|
country: form.val('country'),
|
||||||
club: form.val('club'),
|
club: form.val('club'),
|
||||||
skip: form.find('input.participation').map((input,i) => [i+1, input.checked]).filter(arr => !arr[1]).map(arr => arr[0])
|
skip: form.find('input.participation').map((input,i) => [i+1, input.checked]).filter(arr => !arr[1]).map(arr => arr[0]),
|
||||||
|
final: form.val('final')
|
||||||
}
|
}
|
||||||
if (form.hasClass('add')) {
|
if (form.hasClass('add')) {
|
||||||
api.postJson(`tour/${tour_id}/part`, player)
|
api.postJson(`tour/${tour_id}/part`, player)
|
||||||
.then(player => {
|
.then(player => {
|
||||||
|
console.log(player)
|
||||||
if (player !== 'error') {
|
if (player !== 'error') {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
@@ -146,6 +198,7 @@ onLoad(() => {
|
|||||||
player['id'] = id;
|
player['id'] = id;
|
||||||
api.putJson(`tour/${tour_id}/part/${id}`, player)
|
api.putJson(`tour/${tour_id}/part/${id}`, player)
|
||||||
.then(player => {
|
.then(player => {
|
||||||
|
console.log(player)
|
||||||
if (player !== 'error') {
|
if (player !== 'error') {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
@@ -153,6 +206,8 @@ onLoad(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
$('#players > tbody > tr').on('click', e => {
|
$('#players > tbody > tr').on('click', e => {
|
||||||
|
let regStatus = e.target.closest('td.reg-status');
|
||||||
|
if (regStatus) return;
|
||||||
let id = e.target.closest('tr').attr('data-id');
|
let id = e.target.closest('tr').attr('data-id');
|
||||||
api.getJson(`tour/${tour_id}/part/${id}`)
|
api.getJson(`tour/${tour_id}/part/${id}`)
|
||||||
.then(player => {
|
.then(player => {
|
||||||
@@ -163,8 +218,11 @@ onLoad(() => {
|
|||||||
form.val('firstname', player.firstname);
|
form.val('firstname', player.firstname);
|
||||||
form.val('rating', player.rating);
|
form.val('rating', player.rating);
|
||||||
form.val('rank', player.rank);
|
form.val('rank', player.rank);
|
||||||
form.val('country', player.country);
|
form.val('country', player.country.toLowerCase());
|
||||||
form.val('club', player.club);
|
form.val('club', player.club);
|
||||||
|
form.val('final', player.final);
|
||||||
|
if (player.final) $('#final-reg').addClass('final');
|
||||||
|
else $('#final-reg').removeClass('final');
|
||||||
for (r = 1; r <= tour_rounds; ++r) {
|
for (r = 1; r <= tour_rounds; ++r) {
|
||||||
form.val(`r${r}`, !(player.skip && player.skip.includes(r)));
|
form.val(`r${r}`, !(player.skip && player.skip.includes(r)));
|
||||||
}
|
}
|
||||||
@@ -192,6 +250,9 @@ onLoad(() => {
|
|||||||
let chk = e.target.closest('.toggle');
|
let chk = e.target.closest('.toggle');
|
||||||
let checkbox = chk.find('input')[0];
|
let checkbox = chk.find('input')[0];
|
||||||
checkbox.checked = !checkbox.checked;
|
checkbox.checked = !checkbox.checked;
|
||||||
|
let id = checkbox.getAttribute('id');
|
||||||
|
let value = checkbox.checked;
|
||||||
|
store(id, value);
|
||||||
initSearch();
|
initSearch();
|
||||||
});
|
});
|
||||||
document.on('click', e => {
|
document.on('click', e => {
|
||||||
@@ -211,4 +272,49 @@ onLoad(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
$('#reg-status').on('click', e => {
|
||||||
|
let current = $('#final-reg').hasClass('final');
|
||||||
|
if (current) {
|
||||||
|
$('input[name="final"]')[0].value = false;
|
||||||
|
$('#final-reg').removeClass('final');
|
||||||
|
} else {
|
||||||
|
$('input[name="final"]')[0].value = true;
|
||||||
|
$('#final-reg').addClass('final');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$('.reg-status').on('click', e => {
|
||||||
|
let cell = e.target.closest('td');
|
||||||
|
let tr = e.target.closest('tr');
|
||||||
|
let id = tr.data('id');
|
||||||
|
let newStatus = !cell.hasClass('final');
|
||||||
|
api.putJson(`tour/${tour_id}/part/${id}`, {
|
||||||
|
id: id,
|
||||||
|
final: newStatus
|
||||||
|
}).then(player => {
|
||||||
|
if (player !== 'error') {
|
||||||
|
cell.toggleClass('final');
|
||||||
|
standingsUpToDate = false;
|
||||||
|
pairablesUpToDate = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
$('#filter').on('input', (e) => {
|
||||||
|
let input = e.target;
|
||||||
|
let value = input.value.toUpperCase();
|
||||||
|
if (value === '') $('tbody > tr').removeClass('hidden');
|
||||||
|
else $('tbody > tr').forEach(tr => {
|
||||||
|
let txt = tr.data('text');
|
||||||
|
if (txt && txt.indexOf(value) === -1) tr.addClass('hidden');
|
||||||
|
else tr.removeClass('hidden');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$('#filter-box i').on('click', e => {
|
||||||
|
$('#filter')[0].value = '';
|
||||||
|
$('tbody > tr').removeClass('hidden');
|
||||||
|
});
|
||||||
|
if (store('addingPlayers')) {
|
||||||
|
addPlayers();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
function setResult(id, result) {
|
function setResult(id, result, previous) {
|
||||||
api.putJson(`tour/${tour_id}/res/${activeRound}`, { id: id, result: result })
|
api.putJson(`tour/${tour_id}/res/${activeRound}`, { id: id, result: result })
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res !== 'error') {
|
if (res !== 'error') {
|
||||||
@@ -9,15 +9,26 @@ function setResult(id, result) {
|
|||||||
let dispResult = result;
|
let dispResult = result;
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case '?': break;
|
case '?': break;
|
||||||
case 'w': white.addClass('winner'); black.addClass('looser'); dispResult = 'w+'; break;
|
case 'w': white.addClass('winner'); black.addClass('looser'); dispResult = '1-0'; break;
|
||||||
case 'b': black.addClass('winner'); white.addClass('looser'); dispResult = 'b+'; break;
|
case 'b': black.addClass('winner'); white.addClass('looser'); dispResult = '0-1'; break;
|
||||||
case '=': break;
|
case '=': dispResult = '½-½'; break;
|
||||||
case 'X': break;
|
case 'X': break;
|
||||||
case '#': white.addClass('winner'); black.addClass('winner'); dispResult = '1-1'; break;
|
case '#': white.addClass('winner'); black.addClass('winner'); dispResult = '1-1'; break;
|
||||||
case '0': white.addClass('looser'); black.addClass('looser'); dispResult = '0-0'; break;
|
case '0': white.addClass('looser'); black.addClass('looser'); dispResult = '0-0'; break;
|
||||||
}
|
}
|
||||||
let resultCell = row.find('td.result');
|
let resultCell = row.find('td.result');
|
||||||
resultCell.text(dispResult).data('result', result);
|
resultCell.text(dispResult).data('result', result);
|
||||||
|
standingsUpToDate = false;
|
||||||
|
|
||||||
|
if (previous === '?') {
|
||||||
|
let indicator = $('#known')[0];
|
||||||
|
let known = parseInt(indicator.innerText);
|
||||||
|
indicator.innerText = ++known;
|
||||||
|
} else if (result === '?') {
|
||||||
|
let indicator = $('#known')[0];
|
||||||
|
let known = parseInt(indicator.innerText);
|
||||||
|
indicator.innerText = --known;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -35,11 +46,9 @@ onLoad(()=>{
|
|||||||
$('#results-table .result').on('click', e => {
|
$('#results-table .result').on('click', e => {
|
||||||
let cell = e.target.closest('.result');
|
let cell = e.target.closest('.result');
|
||||||
let gameId = e.target.closest('tr').data('id');
|
let gameId = e.target.closest('tr').data('id');
|
||||||
let result = cell.data('result');
|
let oldResult = cell.data('result');
|
||||||
let index = results.indexOf(result);
|
let index = results.indexOf(oldResult);
|
||||||
console.log(index)
|
let newResult = results[(index + 1)%results.length];
|
||||||
result = results[(index + 1)%results.length];
|
setResult(gameId, newResult, oldResult);
|
||||||
console.log(result)
|
|
||||||
setResult(gameId, result);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,3 +1,20 @@
|
|||||||
|
function publish(format, extension) {
|
||||||
|
let form = $('#tournament-infos')[0];
|
||||||
|
let shortName = form.val('shortName');
|
||||||
|
let hdrs = headers();
|
||||||
|
hdrs['Accept'] = `application/${format}`
|
||||||
|
fetch(`api/tour/${tour_id}/standings/${activeRound}`, {
|
||||||
|
headers: hdrs
|
||||||
|
}).then(resp => {
|
||||||
|
if (resp.ok) return resp.text()
|
||||||
|
else throw "publish error"
|
||||||
|
}).then(txt => {
|
||||||
|
let blob = new Blob(['\uFEFF', txt.trim()], {type: 'plain/text;charset=utf-8'});
|
||||||
|
downloadFile(blob, `${shortName}.${extension}`);
|
||||||
|
close_modal();
|
||||||
|
}).catch(err => showError(err));
|
||||||
|
}
|
||||||
|
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
$('.criterium').on('click', e => {
|
$('.criterium').on('click', e => {
|
||||||
let alreadyOpen = e.target.closest('select');
|
let alreadyOpen = e.target.closest('select');
|
||||||
@@ -45,4 +62,10 @@ onLoad(() => {
|
|||||||
$('#publish-modal').on('click', e => {
|
$('#publish-modal').on('click', e => {
|
||||||
close_modal();
|
close_modal();
|
||||||
});
|
});
|
||||||
|
$('.publish-ffg').on('click', e => {
|
||||||
|
publish('ffg', 'tou');
|
||||||
|
});
|
||||||
|
$('.publish-egf').on('click', e => {
|
||||||
|
publish('egf', 'h9');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,6 +1 @@
|
|||||||
/*!
|
(function(){function e(t,n){if(!(this instanceof e))return new e(t,n);if(!t||t.tagName!=="TABLE")throw new Error("Element must be a table");this.init(t,n||{})}var t=[],n=function(e){var t;return!window.CustomEvent||typeof window.CustomEvent!="function"?(t=document.createEvent("CustomEvent"),t.initCustomEvent(e,!1,!1,void 0)):t=new CustomEvent(e),t},s=function(e,t){return e.getAttribute(t.sortAttribute||"data-sort")||e.textContent||e.innerText||""},a=function(e,t){return e=e.trim().toLowerCase(),t=t.trim().toLowerCase(),e===t?0:e<t?1:-1},o=function(e,t){return[].slice.call(e).find(function(e){return e.getAttribute("data-sort-column-key")===t})},i=function(e,t){return function(n,s){var o=e(n.td,s.td);return o===0?t?s.index-n.index:n.index-s.index:o}};e.extend=function(e,n,s){if(typeof n!="function"||typeof s!="function")throw new Error("Pattern and sort must be a function");t.push({name:e,pattern:n,sort:s})},e.prototype={init:function(e,t){var s,o,i,a,r,n=this;if(n.table=e,n.thead=!1,n.options=t,e.rows&&e.rows.length>0)if(e.tHead&&e.tHead.rows.length>0){for(s=0;s<e.tHead.rows.length;s++)if(e.tHead.rows[s].getAttribute("data-sort-method")==="thead"){o=e.tHead.rows[s];break}o||(o=e.tHead.rows[e.tHead.rows.length-1]),n.thead=!0}else o=e.rows[0];if(!o)return;r=function(){n.current&&n.current!==this&&n.current.removeAttribute("aria-sort"),n.current=this,n.sortTable(this)};for(s=0;s<o.cells.length;s++)i=o.cells[s],i.setAttribute("role","columnheader"),i.getAttribute("data-sort-method")!=="none"&&(i.tabindex=0,i.addEventListener("click",r,!1),i.getAttribute("data-sort-default")!==null&&(a=i));a&&(n.current=a,n.sortTable(a))},sortTable:function(e,r){var h,l=this,v=e.getAttribute("data-sort-column-key"),_=e.cellIndex,g=a,c="",p=[],d=l.thead?0:1,j=e.getAttribute("data-sort-method"),m=e.getAttribute("aria-sort");if(l.table.dispatchEvent(n("beforeSort")),r||(m==="ascending"?m="descending":m==="descending"?m="ascending":m=l.options.descending?"descending":"ascending",e.setAttribute("aria-sort",m)),l.table.rows.length<2)return;if(!j){for(;p.length<3&&d<l.table.tBodies[0].rows.length;)v?h=o(l.table.tBodies[0].rows[d].cells,v):h=l.table.tBodies[0].rows[d].cells[_],c=h?s(h,l.options):"",c=c.trim(),c.length>0&&p.push(c),d++;if(!p)return}for(d=0;d<t.length;d++)if(c=t[d],j){if(c.name===j){g=c.sort;break}}else if(p.every(c.pattern)){g=c.sort;break}l.col=_;for(d=0;d<l.table.tBodies.length;d++){var u,f=[],y={},b=0,w=0;if(l.table.tBodies[d].rows.length<2)continue;for(u=0;u<l.table.tBodies[d].rows.length;u++)c=l.table.tBodies[d].rows[u],c.getAttribute("data-sort-method")==="none"?y[b]=c:(v?h=o(c.cells,v):h=c.cells[l.col],f.push({tr:c,td:h?s(h,l.options):"",index:b})),b++;m==="descending"?f.sort(i(g,!0)):(f.sort(i(g,!1)),f.reverse());for(u=0;u<b;u++)y[u]?(c=y[u],w++):c=f[u-w].tr,l.table.tBodies[d].appendChild(c)}l.table.dispatchEvent(n("afterSort"))},refresh:function(){this.current!==void 0&&this.sortTable(this.current,!0)}},typeof module!="undefined"&&module.exports?module.exports=e:window.Tablesort=e})()
|
||||||
* tablesort v5.4.0 (2023-05-04)
|
|
||||||
* http://tristen.ca/tablesort/demo/
|
|
||||||
* Copyright (c) 2023 ; Licensed MIT
|
|
||||||
*/
|
|
||||||
!function(){function r(t,e){if(!(this instanceof r))return new r(t,e);if(!t||"TABLE"!==t.tagName)throw new Error("Element must be a table");this.init(t,e||{})}function m(t){var e;return window.CustomEvent&&"function"==typeof window.CustomEvent?e=new CustomEvent(t):(e=document.createEvent("CustomEvent")).initCustomEvent(t,!1,!1,void 0),e}function p(t,e){return t.getAttribute(e.sortAttribute||"data-sort")||t.textContent||t.innerText||""}function v(t,e){return(t=t.trim().toLowerCase())===(e=e.trim().toLowerCase())?0:t<e?1:-1}function A(t,e){return[].slice.call(t).find(function(t){return t.getAttribute("data-sort-column-key")===e})}function E(n,o){return function(t,e){var r=n(t.td,e.td);return 0===r?o?e.index-t.index:t.index-e.index:r}}var x=[];r.extend=function(t,e,r){if("function"!=typeof e||"function"!=typeof r)throw new Error("Pattern and sort must be a function");x.push({name:t,pattern:e,sort:r})},r.prototype={init:function(t,e){var r,n,o,i=this;if(i.table=t,i.thead=!1,i.options=e,t.rows&&0<t.rows.length)if(t.tHead&&0<t.tHead.rows.length){for(a=0;a<t.tHead.rows.length;a++)if("thead"===t.tHead.rows[a].getAttribute("data-sort-method")){r=t.tHead.rows[a];break}r=r||t.tHead.rows[t.tHead.rows.length-1],i.thead=!0}else r=t.rows[0];if(r){function s(){i.current&&i.current!==this&&i.current.removeAttribute("aria-sort"),i.current=this,i.sortTable(this)}for(var a=0;a<r.cells.length;a++)(o=r.cells[a]).setAttribute("role","columnheader"),"none"!==o.getAttribute("data-sort-method")&&(o.tabindex=0,o.addEventListener("click",s,!1),null!==o.getAttribute("data-sort-default")&&(n=o));n&&(i.current=n,i.sortTable(n))}},sortTable:function(t,e){var r=this,n=t.getAttribute("data-sort-column-key"),o=t.cellIndex,i=v,s="",a=[],d=r.thead?0:1,u=t.getAttribute("data-sort-method"),l=t.getAttribute("aria-sort");if(r.table.dispatchEvent(m("beforeSort")),e||(l="ascending"===l||"descending"!==l&&r.options.descending?"descending":"ascending",t.setAttribute("aria-sort",l)),!(r.table.rows.length<2)){if(!u){for(;a.length<3&&d<r.table.tBodies[0].rows.length;)0<(s=(s=(f=n?A(r.table.tBodies[0].rows[d].cells,n):r.table.tBodies[0].rows[d].cells[o])?p(f,r.options):"").trim()).length&&a.push(s),d++;if(!a)return}for(d=0;d<x.length;d++)if(s=x[d],u){if(s.name===u){i=s.sort;break}}else if(a.every(s.pattern)){i=s.sort;break}for(r.col=o,d=0;d<r.table.tBodies.length;d++){var c,f,h=[],b={},w=0,g=0;if(!(r.table.tBodies[d].rows.length<2)){for(c=0;c<r.table.tBodies[d].rows.length;c++)"none"===(s=r.table.tBodies[d].rows[c]).getAttribute("data-sort-method")?b[w]=s:(f=n?A(s.cells,n):s.cells[r.col],h.push({tr:s,td:f?p(f,r.options):"",index:w})),w++;for("descending"===l?h.sort(E(i,!0)):(h.sort(E(i,!1)),h.reverse()),c=0;c<w;c++)b[c]?(s=b[c],g++):s=h[c-g].tr,r.table.tBodies[d].appendChild(s)}}r.table.dispatchEvent(m("afterSort"))}},refresh:function(){void 0!==this.current&&this.sortTable(this.current,!0)}},"undefined"!=typeof module&&module.exports?module.exports=r:window.Tablesort=r}();
|
|
@@ -48,13 +48,68 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="paired" class="multi-select" title="white vs. black">
|
<div id="paired" class="multi-select" title="white vs. black">
|
||||||
#foreach($game in $games)
|
#foreach($game in $games)
|
||||||
#set($white = $pmap[$game.w])
|
#set($white = $pmap[$game.w])
|
||||||
#set($black = $pmap[$game.b])
|
#set($black = $pmap[$game.b])
|
||||||
<div class="listitem game" data-id="$game.id"><span class="table">#$game.t</span><span class="white">#if($white)$white.name $white.firstname #rank($white.rank)#{else}BIP#end</span><span> </span><span class="black">#if($black)$black.name $black.firstname #rank($black.rank)#{else}BIP#end</span>#if($game.h)<span class="handicap">h$game.h</span>#end</div>
|
<div class="listitem game" data-id="$game.id">
|
||||||
#end
|
<div class="table" data-value="$game.t">#$game.t</div>
|
||||||
|
<div class="white" data-id="$game.w">#if($white)$white.name $white.firstname#{else}BIP#end</div>
|
||||||
|
<div class="levels">#if($white)#rank($white.rank)#end / #if($black)#rank($black.rank)#end</div>
|
||||||
|
<div class="black" data-id="$game.b">#if($black)$black.name $black.firstname#{else}BIP#end</div>
|
||||||
|
<div class="handicap" data-value="$game.h">#if($game.h)h$game.h#{else} #end</div>
|
||||||
|
</div>
|
||||||
|
#end
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="edit-pairing" class="popup">
|
||||||
|
<div class="popup-body">
|
||||||
|
<form id="pairing-form" class="ui form edit">
|
||||||
|
<input type="hidden" name="id"/>
|
||||||
|
<div class="popup-content">
|
||||||
|
<div class="inline fields">
|
||||||
|
<div class="field">
|
||||||
|
<label>Tbl</label>
|
||||||
|
<span class="nobreak">#<input name="t" type="number"/></span>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>White</label>
|
||||||
|
<input type="hidden" name="w"/>
|
||||||
|
<div id="edit-pairing-white"></div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<button id="pairing-exchange" type="button" class="ui icon button">
|
||||||
|
<i class="fa fa-exchange"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Black</label>
|
||||||
|
<input type="hidden" name="b"/>
|
||||||
|
<div id="edit-pairing-black"></div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Hd</label>
|
||||||
|
<select name="h">
|
||||||
|
#foreach($h in [0..9])
|
||||||
|
<option value="$h">$h</option>
|
||||||
|
#end
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="popup-footer">
|
||||||
|
<button id="cancel-pairing" type="button" class="ui gray right labeled icon floating close button">
|
||||||
|
<i class="times icon"></i>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button id="update-pairing" type="button" class="ui green right labeled icon floating button">
|
||||||
|
<i class="check icon"></i>
|
||||||
|
Update
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@@ -1,24 +1,40 @@
|
|||||||
<div class="tab-content" id="registration-tab">
|
|
||||||
<div id="reg-view">
|
|
||||||
<div id="players-list" class="roundbox">
|
|
||||||
#set($parts = $api.get("tour/${params.id}/part"))
|
#set($parts = $api.get("tour/${params.id}/part"))
|
||||||
#set($pmap = $utils.toMap($parts))
|
#set($pmap = $utils.toMap($parts))
|
||||||
|
<div class="tab-content" id="registration-tab">
|
||||||
|
<div id="reg-view">
|
||||||
|
<div id="list-header">
|
||||||
|
<div id="filter-box" class="ui icon input">
|
||||||
|
<input type="text" id="filter" placeholder="Search..."/>
|
||||||
|
<i class="circular times link icon"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
$parts.size() participants, $utils.countFinals($parts) confirmed.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="players-list" class="roundbox">
|
||||||
<table id="players" class="ui celled selectable striped table">
|
<table id="players" class="ui celled selectable striped table">
|
||||||
<thead>
|
<thead>
|
||||||
|
<th>Reg</th>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>First name</th>
|
<th>First name</th>
|
||||||
<th>Country</th>
|
<th>Country</th>
|
||||||
<th>Club</th>
|
<th>Club</th>
|
||||||
<th>Rank</th>
|
<th>Rank</th>
|
||||||
<th>Rating</th>
|
## TableSort bug which inverts specified sort...
|
||||||
<th>Participation</th>
|
<th data-sort-default="1" aria-sort="ascending">Rating</th>
|
||||||
|
<th data-sort-method="none">Participation</th>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
#foreach($part in $parts)
|
#foreach($part in $parts)
|
||||||
<tr data-id="$part.id">
|
<tr data-id="$part.id" data-text="$esc.html("$part.name.toUpperCase() $part.firstname.toUpperCase() $part.club.toUpperCase()")">
|
||||||
|
<td class="centered reg-status #if($part.final)final#end">
|
||||||
|
<span class="preliminary"><i class="fa fa-question"></i></span>
|
||||||
|
<span class="final"><i class="fa fa-check"></i></span>
|
||||||
|
</td>
|
||||||
<td>$part.name</td>
|
<td>$part.name</td>
|
||||||
<td>$part.firstname</td>
|
<td>$part.firstname</td>
|
||||||
<td>$part.country</td>
|
<td>$part.country.toUpperCase()</td>
|
||||||
<td>$part.club</td>
|
<td>$part.club</td>
|
||||||
<td data-sort="$part.rank">#rank($part.rank)</td>
|
<td data-sort="$part.rank">#rank($part.rank)</td>
|
||||||
<td>$part.rating</td>
|
<td>$part.rating</td>
|
||||||
@@ -141,6 +157,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="inline fields">
|
<div class="inline fields">
|
||||||
|
<label>Final ?</label>
|
||||||
|
<div id="final-reg" class="field">
|
||||||
|
<input name="final" type="hidden"/>
|
||||||
|
<button id="reg-status" type="button" class="ui icon roundedremove mini button">
|
||||||
|
<span class="final"><i class="fa fa-check"></i></span>
|
||||||
|
<span class="preliminary"><i class="fa fa-question"></i></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<label>Participation</label>
|
<label>Participation</label>
|
||||||
#foreach($r in [1..$tour.rounds])
|
#foreach($r in [1..$tour.rounds])
|
||||||
<div class="centered field">
|
<div class="centered field">
|
||||||
@@ -154,7 +178,7 @@
|
|||||||
<button id="cancel-register" type="button" class="ui gray right labeled icon floating close button">
|
<button id="cancel-register" type="button" class="ui gray right labeled icon floating close button">
|
||||||
<i class="times icon"></i>
|
<i class="times icon"></i>
|
||||||
<span class="edition">Close</span>
|
<span class="edition">Close</span>
|
||||||
<span class="creation">Cancel</span>
|
<span class="creation">Close</span>
|
||||||
</button>
|
</button>
|
||||||
<button id="unregister" type="button" class="ui red right labeled icon floating button">
|
<button id="unregister" type="button" class="ui red right labeled icon floating button">
|
||||||
<i class="trash icon"></i>
|
<i class="trash icon"></i>
|
||||||
|
@@ -4,6 +4,8 @@
|
|||||||
<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>
|
||||||
<button class="ui floating choose-round next-round button">»</button>
|
<button class="ui floating choose-round next-round button">»</button>
|
||||||
|
#set($stats = $utils.getResultsStats($games))
|
||||||
|
<span class="norbeak"><span id="known">$stats.known</span> / $stats.total</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="results-list" class="roundbox">
|
<div id="results-list" class="roundbox">
|
||||||
<table id="results-table" class="ui celled striped table">
|
<table id="results-table" class="ui celled striped table">
|
||||||
@@ -14,7 +16,7 @@
|
|||||||
<th>result</th>
|
<th>result</th>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
#set($dispRst = {'?':'?', 'w':'w+', 'b':'b+', '=':'=', 'X':'X', '#':'1-1', '0':'0-0'})
|
#set($dispRst = {'?':'?', 'w':'1-0', 'b':'0-1', '=':'½-½', 'X':'X', '#':'1-1', '0':'0-0'})
|
||||||
#foreach($game in $games)
|
#foreach($game in $games)
|
||||||
#set($white = $pmap[$game.w])
|
#set($white = $pmap[$game.w])
|
||||||
#set($black = $pmap[$game.b])
|
#set($black = $pmap[$game.b])
|
||||||
@@ -23,7 +25,7 @@
|
|||||||
<td>#$game.t</td>
|
<td>#$game.t</td>
|
||||||
<td class="white player #if($game.r == 'w' || $game.r == '#') winner #elseif($game.r == 'b' || $game.r == '0') looser #end" data-id="$white.id" data-sort="$white.name $white.firstname"><span>#if($white)$white.name $white.firstname #rank($white.rank)#{else}BIP#end</span></td>
|
<td class="white player #if($game.r == 'w' || $game.r == '#') winner #elseif($game.r == 'b' || $game.r == '0') looser #end" data-id="$white.id" data-sort="$white.name $white.firstname"><span>#if($white)$white.name $white.firstname #rank($white.rank)#{else}BIP#end</span></td>
|
||||||
<td class="black player #if($game.r == 'b' || $game.r == '#') winner #elseif($game.r == 'w' || $game.r == '0') looser #end" data-id="$black.id" data-sort="$black.name $black.firstname"><span>#if($black)$black.name $black.firstname #rank($black.rank)#{else}BIP#end</span></td>
|
<td class="black player #if($game.r == 'b' || $game.r == '#') winner #elseif($game.r == 'w' || $game.r == '0') looser #end" data-id="$black.id" data-sort="$black.name $black.firstname"><span>#if($black)$black.name $black.firstname #rank($black.rank)#{else}BIP#end</span></td>
|
||||||
<td class="result centered" data-result="$game.r">$dispRst[$game.r]</td>
|
<td class="result centered" data-sort="$game.r" data-result="$game.r">$dispRst[$game.r]</td>
|
||||||
</tr>
|
</tr>
|
||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
|
@@ -66,7 +66,7 @@
|
|||||||
<td>$part.num</td>
|
<td>$part.num</td>
|
||||||
<td>$part.place</td>
|
<td>$part.place</td>
|
||||||
<td>$part.name $part.firstname</td>
|
<td>$part.name $part.firstname</td>
|
||||||
<td>#rank($part.rank)</td>
|
<td data-sort="$part.rank">#rank($part.rank)</td>
|
||||||
<td>$part.country</td>
|
<td>$part.country</td>
|
||||||
<td>$number.format('0.#', $part.NBW)</td>
|
<td>$number.format('0.#', $part.NBW)</td>
|
||||||
#set($mx = $round - 1)
|
#set($mx = $round - 1)
|
||||||
@@ -101,9 +101,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="popup-footer">
|
<div class="popup-footer">
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<button class="ui gray floating cancel button">Cancel</button>
|
<button type="button" class="ui gray floating cancel button">Cancel</button>
|
||||||
<button class="ui blue floating button">EGF</button>
|
<button type="button" class="ui blue floating publish-egf button">EGF</button>
|
||||||
<button class="ui blue floating button">FFG</button>
|
<button type="button" class="ui blue floating publish-ffg button">FFG</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -73,6 +73,8 @@
|
|||||||
const tour_id = ${tour.id};
|
const tour_id = ${tour.id};
|
||||||
const tour_rounds = ${tour.rounds};
|
const tour_rounds = ${tour.rounds};
|
||||||
let activeRound = ${round};
|
let activeRound = ${round};
|
||||||
|
let standingsUpToDate = true;
|
||||||
|
let pairablesUpToDate = true;
|
||||||
// $params
|
// $params
|
||||||
#end
|
#end
|
||||||
#set($datepickerLocale = $translate.datepickerLocale($request.lang, $request.loc))
|
#set($datepickerLocale = $translate.datepickerLocale($request.lang, $request.loc))
|
||||||
@@ -105,6 +107,11 @@
|
|||||||
$('.step').removeClass('active');
|
$('.step').removeClass('active');
|
||||||
$(`.step[data-step="${step}"], #${step}-tab`).addClass('active');
|
$(`.step[data-step="${step}"], #${step}-tab`).addClass('active');
|
||||||
window.location.hash = `#${step}`;
|
window.location.hash = `#${step}`;
|
||||||
|
if (step === 'standings' && !standingsUpToDate) {
|
||||||
|
window.location.reload();
|
||||||
|
} else if (step === 'pairing' && !pairablesUpToDate) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
|
Reference in New Issue
Block a user