Beta version of explain page
This commit is contained in:
@@ -18,11 +18,6 @@ import kotlin.math.min
|
|||||||
|
|
||||||
fun Tournament<*>.getSortedPairables(round: Int, includePreliminary: Boolean = false): List<Json.Object> {
|
fun Tournament<*>.getSortedPairables(round: Int, includePreliminary: Boolean = false): List<Json.Object> {
|
||||||
|
|
||||||
fun Pairable.mmBase(): Double {
|
|
||||||
if (pairing !is MacMahon) throw Error("invalid call: tournament is not Mac Mahon")
|
|
||||||
return min(max(rank, pairing.mmFloor), pairing.mmBar) + MacMahonSolver.mmsZero + mmsCorrection
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frozen != null) {
|
if (frozen != null) {
|
||||||
return ArrayList(frozen!!.map { it -> it as Json.Object })
|
return ArrayList(frozen!!.map { it -> it as Json.Object })
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,68 @@
|
|||||||
|
package org.jeudego.pairgoth.api
|
||||||
|
|
||||||
|
import com.republicate.kson.Json
|
||||||
|
import com.republicate.kson.toJsonArray
|
||||||
|
import com.republicate.kson.toJsonObject
|
||||||
|
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
||||||
|
import org.jeudego.pairgoth.model.toJson
|
||||||
|
import org.jeudego.pairgoth.pairing.solver.CollectingListener
|
||||||
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
import javax.servlet.http.HttpServletResponse
|
||||||
|
|
||||||
|
object ExplainHandler: PairgothApiHandler {
|
||||||
|
|
||||||
|
override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
||||||
|
val tournament = getTournament(request)
|
||||||
|
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
|
||||||
|
if (round > tournament.lastRound() + 1) badRequest("invalid round: previous round has not been played")
|
||||||
|
val paired = tournament.games(round).values.flatMap {
|
||||||
|
listOf(it.black, it.white)
|
||||||
|
}.filter {
|
||||||
|
it != 0
|
||||||
|
}.map {
|
||||||
|
tournament.pairables[it] ?: throw Error("Unknown pairable ID: $it")
|
||||||
|
}
|
||||||
|
val games = tournament.games(round).map { it.value }.toList()
|
||||||
|
// build the scores map by redoing the whole pairing
|
||||||
|
tournament.unpair(round)
|
||||||
|
val history = tournament.historyHelper(round)
|
||||||
|
val weightsCollector = CollectingListener()
|
||||||
|
tournament.pair(round, paired, false, weightsCollector)
|
||||||
|
val weights = weightsCollector.out
|
||||||
|
// Since weights are generally in two groups towards the min and the max,
|
||||||
|
// compute the max of the low group ("low") and the min of the high group ("high")
|
||||||
|
// to improve coloring.
|
||||||
|
// Total weights axis:
|
||||||
|
// ----[min]xxxx[low]----[middle]----[high]xxxx[max]---->
|
||||||
|
val min = weights.values.minOfOrNull { it.values.sum() } ?: 0.0
|
||||||
|
val max = weights.values.maxOfOrNull { it.values.sum() } ?: 0.0
|
||||||
|
val middle = (max - min) / 2.0
|
||||||
|
val low = weights.values.map { it.values.sum() }.filter { it < middle }.maxOrNull() ?: middle
|
||||||
|
val high = weights.values.map { it.values.sum() }.filter { it > middle }.minOrNull() ?: middle
|
||||||
|
val ret = Json.Object(
|
||||||
|
"paired" to paired.sortedByDescending { 1000 * (history.scores[it.id] ?: 0.0) + (history.sos[it.id] ?: 0.0) }.map {
|
||||||
|
it.toMutableJson().apply {
|
||||||
|
put("score", history.scores[it.id])
|
||||||
|
put("wins", history.wins[it.id])
|
||||||
|
put("sos", history.sos[it.id])
|
||||||
|
put("dudd", history.drawnUpDown[it.id])
|
||||||
|
}
|
||||||
|
}.toJsonArray(),
|
||||||
|
// "games" to games.map { it.toJson() }.toJsonArray(),
|
||||||
|
"games" to games.associateBy { "${it.white}-${it.black}" }.mapValues { it.value.toJson() }.toJsonObject(),
|
||||||
|
"weights" to weights.entries.map { (key, value) ->
|
||||||
|
Pair(
|
||||||
|
"${key.first}-${key.second}",
|
||||||
|
value.also {
|
||||||
|
it.put("total", it.values.sum())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}.toJsonObject(),
|
||||||
|
"min" to min,
|
||||||
|
"low" to low,
|
||||||
|
"high" to high,
|
||||||
|
"max" to max
|
||||||
|
)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
}
|
@@ -133,8 +133,8 @@ sealed class Pairing(
|
|||||||
val pairingParams: PairingParams,
|
val pairingParams: PairingParams,
|
||||||
val placementParams: PlacementParams) {
|
val placementParams: PlacementParams) {
|
||||||
companion object {}
|
companion object {}
|
||||||
abstract fun solver(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): Solver
|
internal abstract fun solver(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): Solver
|
||||||
fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>, legacyMode: Boolean = false, listener: PairingListener? = null): List<Game> {
|
internal fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>, legacyMode: Boolean = false, listener: PairingListener? = null): List<Game> {
|
||||||
return solver(tournament, round, pairables)
|
return solver(tournament, round, pairables)
|
||||||
.also { solver ->
|
.also { solver ->
|
||||||
solver.legacyMode = legacyMode
|
solver.legacyMode = legacyMode
|
||||||
|
@@ -100,14 +100,17 @@ sealed class Tournament <P: Pairable>(
|
|||||||
|
|
||||||
fun lastRound() = max(1, games.size)
|
fun lastRound() = max(1, games.size)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recompute DUDD for a specific game
|
||||||
|
*/
|
||||||
fun recomputeDUDD(round: Int, gameID: ID) {
|
fun recomputeDUDD(round: Int, gameID: ID) {
|
||||||
// Instantiate solver with game history
|
// Instantiate solver with game history
|
||||||
val solver = pairing.solver(this, round, pairables.values.toList())
|
val solver = pairing.solver(this, round, emptyList())
|
||||||
|
|
||||||
// Recomputes DUDD and hd
|
// Recomputes DUDD and hd
|
||||||
val game = games(round)[gameID]!!
|
val game = games(round)[gameID]!!
|
||||||
val white = solver.pairables.find { p-> p.id == game.white }!!
|
val white = pairables[game.white]!!
|
||||||
val black = solver.pairables.find { p-> p.id == game.black }!!
|
val black = pairables[game.black]!!
|
||||||
game.drawnUpDown = solver.dudd(black, white)
|
game.drawnUpDown = solver.dudd(black, white)
|
||||||
game.handicap = solver.hd(white = white, black = black)
|
game.handicap = solver.hd(white = white, black = black)
|
||||||
}
|
}
|
||||||
@@ -119,17 +122,16 @@ sealed class Tournament <P: Pairable>(
|
|||||||
fun recomputeDUDD(round: Int) {
|
fun recomputeDUDD(round: Int) {
|
||||||
if (pairables.isEmpty() || games(1).isEmpty()) return;
|
if (pairables.isEmpty() || games(1).isEmpty()) return;
|
||||||
// Instantiate solver with game history
|
// Instantiate solver with game history
|
||||||
val solver = pairing.solver(this, round, pairables.values.toList())
|
val solver = pairing.solver(this, round, emptyList())
|
||||||
for (game in games(round).values) {
|
for (game in games(round).values) {
|
||||||
if (game.black != 0 && game.white != 0) {
|
if (game.black != 0 && game.white != 0) {
|
||||||
val white = solver.pairables.find { p-> p.id == game.white }!!
|
val white = pairables[game.white]!!
|
||||||
val black = solver.pairables.find { p-> p.id == game.black }!!
|
val black = pairables[game.black]!!
|
||||||
game.drawnUpDown = solver.dudd(black, white)
|
game.drawnUpDown = solver.dudd(black, white)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recompute DUDD for all rounds
|
* Recompute DUDD for all rounds
|
||||||
*/
|
*/
|
||||||
|
@@ -78,17 +78,17 @@ abstract class BasePairingHelper(
|
|||||||
protected fun Pairable.played(other: Pairable) = history.playedTogether(this, other)
|
protected fun Pairable.played(other: Pairable) = history.playedTogether(this, other)
|
||||||
|
|
||||||
// color balance (nw - nb)
|
// color balance (nw - nb)
|
||||||
protected val Pairable.colorBalance: Int get() = history.colorBalance(this) ?: 0
|
protected val Pairable.colorBalance: Int get() = history.colorBalance[id] ?: 0
|
||||||
|
|
||||||
protected val Pairable.group: Int get() = _groups[id]!!
|
protected val Pairable.group: Int get() = _groups[id]!!
|
||||||
|
|
||||||
protected val Pairable.drawnUpDown: Pair<Int, Int> get() = history.drawnUpDown(this) ?: Pair(0, 0)
|
protected val Pairable.drawnUpDown: Pair<Int, Int> get() = history.drawnUpDown[id] ?: Pair(0, 0)
|
||||||
|
|
||||||
protected val Pairable.nbBye: Int get() = history.nbPlayedWithBye(this) ?: 0
|
protected val Pairable.nbBye: Int get() = history.nbPlayedWithBye(this) ?: 0
|
||||||
|
|
||||||
val Pairable.score: Double get() = history.scores[id] ?: 0.0
|
val Pairable.score: Double get() = history.scores[id] ?: 0.0
|
||||||
val Pairable.scoreX: Double get() = history.scoresX[id] ?: 0.0
|
val Pairable.scoreX: Double get() = history.scoresX[id] ?: 0.0
|
||||||
val Pairable.nbW: Double get() = history.nbW(this) ?: 0.0
|
val Pairable.nbW: Double get() = history.wins[id] ?: 0.0
|
||||||
val Pairable.sos: Double get() = history.sos[id] ?: 0.0
|
val Pairable.sos: Double get() = history.sos[id] ?: 0.0
|
||||||
val Pairable.sosm1: Double get() = history.sosm1[id] ?: 0.0
|
val Pairable.sosm1: Double get() = history.sosm1[id] ?: 0.0
|
||||||
val Pairable.sosm2: Double get() = history.sosm2[id] ?: 0.0
|
val Pairable.sosm2: Double get() = history.sosm2[id] ?: 0.0
|
||||||
|
@@ -33,9 +33,6 @@ open class HistoryHelper(
|
|||||||
open fun playedTogether(p1: Pairable, p2: Pairable) = paired.contains(Pair(p1.id, p2.id))
|
open fun playedTogether(p1: Pairable, p2: Pairable) = paired.contains(Pair(p1.id, p2.id))
|
||||||
open fun colorBalance(p: Pairable) = colorBalance[p.id]
|
open fun colorBalance(p: Pairable) = colorBalance[p.id]
|
||||||
open fun nbPlayedWithBye(p: Pairable) = nbPlayedWithBye[p.id]
|
open fun nbPlayedWithBye(p: Pairable) = nbPlayedWithBye[p.id]
|
||||||
open fun nbW(p: Pairable) = wins[p.id]
|
|
||||||
|
|
||||||
fun drawnUpDown(p: Pairable) = drawnUpDown[p.id]
|
|
||||||
|
|
||||||
protected val paired: Set<Pair<ID, ID>> by lazy {
|
protected val paired: Set<Pair<ID, ID>> by lazy {
|
||||||
(history.flatten().map { game ->
|
(history.flatten().map { game ->
|
||||||
@@ -47,7 +44,7 @@ open class HistoryHelper(
|
|||||||
|
|
||||||
// Returns the number of games played as white minus the number of games played as black
|
// Returns the number of games played as white minus the number of games played as black
|
||||||
// Only count games without handicap
|
// Only count games without handicap
|
||||||
private val colorBalance: Map<ID, Int> by lazy {
|
val colorBalance: Map<ID, Int> by lazy {
|
||||||
history.flatten().filter { game ->
|
history.flatten().filter { game ->
|
||||||
game.handicap == 0
|
game.handicap == 0
|
||||||
}.filter { game ->
|
}.filter { game ->
|
||||||
|
@@ -5,6 +5,7 @@ import com.github.benmanes.caffeine.cache.Caffeine
|
|||||||
import com.republicate.kson.Json
|
import com.republicate.kson.Json
|
||||||
import org.apache.commons.io.input.BOMInputStream
|
import org.apache.commons.io.input.BOMInputStream
|
||||||
import org.jeudego.pairgoth.api.ApiHandler
|
import org.jeudego.pairgoth.api.ApiHandler
|
||||||
|
import org.jeudego.pairgoth.api.ExplainHandler
|
||||||
import org.jeudego.pairgoth.api.PairingHandler
|
import org.jeudego.pairgoth.api.PairingHandler
|
||||||
import org.jeudego.pairgoth.api.PlayerHandler
|
import org.jeudego.pairgoth.api.PlayerHandler
|
||||||
import org.jeudego.pairgoth.api.ResultsHandler
|
import org.jeudego.pairgoth.api.ResultsHandler
|
||||||
@@ -99,6 +100,7 @@ class ApiServlet: HttpServlet() {
|
|||||||
if ("token" == selector) TokenHandler
|
if ("token" == selector) TokenHandler
|
||||||
else when (subEntity) {
|
else when (subEntity) {
|
||||||
null -> TournamentHandler
|
null -> TournamentHandler
|
||||||
|
"explain" -> ExplainHandler
|
||||||
"part" -> PlayerHandler
|
"part" -> PlayerHandler
|
||||||
"pair" -> PairingHandler
|
"pair" -> PairingHandler
|
||||||
"res" -> ResultsHandler
|
"res" -> ResultsHandler
|
||||||
|
141
view-webapp/src/main/sass/explain.scss
Normal file
141
view-webapp/src/main/sass/explain.scss
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
@layer pairgoth {
|
||||||
|
/* explain section */
|
||||||
|
|
||||||
|
#pairing-table-wrapper {
|
||||||
|
padding: 8em 2em 1em 2em;
|
||||||
|
}
|
||||||
|
#pairing-table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
thead {
|
||||||
|
//border-collapse: collapse;
|
||||||
|
tr {
|
||||||
|
&:first-child {
|
||||||
|
min-height: 8rem;
|
||||||
|
th:first-child {
|
||||||
|
background: linear-gradient(to right top, #ffffff 0%,#ffffff 49.9%,#000000 50%,#000000 51%,#ffffff 51.1%,#ffffff 100%);
|
||||||
|
position: relative;
|
||||||
|
min-width: 8rem;
|
||||||
|
label.top-right {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
label.bottom-left {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
th:not(:first-child) {
|
||||||
|
z-index: 5;
|
||||||
|
width: 55px;
|
||||||
|
height: 140px;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
position: relative;
|
||||||
|
> div {
|
||||||
|
transform: translate(35px, 51px) rotate(315deg);
|
||||||
|
width: 30px;
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
> span {
|
||||||
|
background-color: rgba(0, 0, 255, 0.2) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> span {
|
||||||
|
border-bottom: 1px solid gray;
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> pre {
|
||||||
|
display: none;
|
||||||
|
font-size: smaller;
|
||||||
|
line-height: 1em;
|
||||||
|
text-align: left;
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
top: 100%;
|
||||||
|
left: 100%;
|
||||||
|
background-color: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
> pre {
|
||||||
|
display: block;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
tr {
|
||||||
|
th {
|
||||||
|
text-align: left;
|
||||||
|
padding-right: 1em;
|
||||||
|
position: relative;
|
||||||
|
> pre {
|
||||||
|
display: none;
|
||||||
|
font-size: smaller;
|
||||||
|
line-height: 1em;
|
||||||
|
text-align: left;
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
top: 100%;
|
||||||
|
left: 100%;
|
||||||
|
background-color: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: rgba(0, 0, 255, 0.2) !important;
|
||||||
|
pre {
|
||||||
|
display: block;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
height: 55px;
|
||||||
|
border: solid 1px gray;
|
||||||
|
position: relative;
|
||||||
|
&.game::after {
|
||||||
|
position: absolute;
|
||||||
|
content: "X";
|
||||||
|
color: blue;
|
||||||
|
text-align: center;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.weights {
|
||||||
|
display: none;
|
||||||
|
font-size: smaller;
|
||||||
|
line-height: 1em;
|
||||||
|
text-align: left;
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
top: 100%;
|
||||||
|
left: 100%;
|
||||||
|
background-color: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(0, 0, 255, 0.7) !important;
|
||||||
|
cursor: pointer;
|
||||||
|
.weights {
|
||||||
|
display: block;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#captures {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@@ -570,7 +570,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* TODO - plenty of those elements could just use the .noprint class */
|
/* TODO - plenty of those elements could just use the .noprint class */
|
||||||
#header, #logo, #lang, .steps, #filter-box, #reglist-mode, #footer, #unpairables, #pairing-buttons, button, #standings-params, #logout, .pairing-stats, .result-sheets, .toggle, #overview, .tables-exclusion, .button, .noprint {
|
#header, #logo, #lang, .steps, #filter-box, #reglist-mode, #footer, #unpairables, #pairing-buttons, button, #standings-params, #logout, .pairing-stats, .pairing-post-actions, .toggle, #overview, .tables-exclusion, .button, .noprint {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -418,8 +418,10 @@
|
|||||||
padding: 0.2em 0.8em;
|
padding: 0.2em 0.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.result-sheets {
|
.pairing-post-actions {
|
||||||
margin-top: 0.2em;
|
margin-top: 0.2em;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-pairing-actions {
|
.bottom-pairing-actions {
|
||||||
|
114
view-webapp/src/main/webapp/explain.html
Normal file
114
view-webapp/src/main/webapp/explain.html
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
#macro(rank $rank)#if( $rank<0 )#set( $k = -$rank )${k}k#else#set( $d=$rank+1 )${d}d#end#end
|
||||||
|
#if (!$tour)
|
||||||
|
<div class="section">
|
||||||
|
<h2 class="error">Invalid tournament id</h2>
|
||||||
|
</div>
|
||||||
|
#stop
|
||||||
|
#end
|
||||||
|
#set($round = $math.toInteger($!params.round))
|
||||||
|
#if(!$round)
|
||||||
|
#set($round = 1)
|
||||||
|
#else
|
||||||
|
#set($round = $math.min($math.max($round, 1), $tour.rounds))
|
||||||
|
#end
|
||||||
|
#if($tour.type == 'INDIVIDUAL' || $tour.type.startsWith('TEAM'))
|
||||||
|
#set($parts = $api.get("tour/${params.id}/part"))
|
||||||
|
#else
|
||||||
|
#set($parts = $api.get("tour/${params.id}/team"))
|
||||||
|
#end
|
||||||
|
#set($pmap = $utils.toMap($parts))
|
||||||
|
#set($roundPairing = $api.get("tour/${params.id}/pair/$round"))
|
||||||
|
#if($roundPairing.error)
|
||||||
|
<script type="text/javascript">
|
||||||
|
onLoad(() => {
|
||||||
|
showError("$roundPairing.error")
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
#stop
|
||||||
|
#end
|
||||||
|
#set($explain = $api.get("tour/${params.id}/explain/$round"))
|
||||||
|
<div id="pairing-table-wrapper">
|
||||||
|
<table id="pairing-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<label class="top-right">white</label>
|
||||||
|
<label class="bottom-left">black</label>
|
||||||
|
</th>
|
||||||
|
#foreach($white in $explain.paired)
|
||||||
|
<th data-white="$white.id">
|
||||||
|
<div>
|
||||||
|
<span>
|
||||||
|
$white.name $white.firstname
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<pre>$white.toPrettyString()</pre>
|
||||||
|
</th>
|
||||||
|
#end
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
#foreach($black in $explain.paired)
|
||||||
|
<tr>
|
||||||
|
<th data-black="$black.id">
|
||||||
|
<div>
|
||||||
|
<span>
|
||||||
|
$black.name $black.firstname
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<pre>$black.toPrettyString()</pre>
|
||||||
|
</th>
|
||||||
|
#foreach($white in $explain.paired)
|
||||||
|
#if($white.id != $black.id)
|
||||||
|
#set($key = "$white.id-$black.id")
|
||||||
|
#set($weights = $explain.weights[$key])
|
||||||
|
#set($toMax = $explain.max - $weights.total)
|
||||||
|
#set($toMin = $weights.total - $explain.min)
|
||||||
|
#if ($toMax > $toMin)
|
||||||
|
## total is close to min
|
||||||
|
#set($percent = ($weights.total - $explain.min) / ($explain.low - $explain.min) * 40)
|
||||||
|
#else
|
||||||
|
## total is close to max
|
||||||
|
#set($percent = 60 + ($explain.max - $weights.total) / ($explain.max - $explain.high) * 40)
|
||||||
|
#end
|
||||||
|
<!-- $!percent -->
|
||||||
|
#set($game = $explain.games[$key])
|
||||||
|
<td data-wb="$white.id-$black.id" #if($game)class="game"#end style="background-color: color-mix(in srgb, rgb(0 255 0) ${percent}%, rgb(255 0 0));">
|
||||||
|
<div class="weights">
|
||||||
|
<pre>$weights.toPrettyString()</pre>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
#else
|
||||||
|
<td></td>
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
</tr>
|
||||||
|
#end
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="captures"></div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
// #[[
|
||||||
|
onLoad(() => {
|
||||||
|
$('#header').hide();
|
||||||
|
$('td').on('click', e => {
|
||||||
|
const td = e.target.closest('td');
|
||||||
|
const ids = td.data('wb')?.split(/-/);
|
||||||
|
if (ids) {
|
||||||
|
const white = $(`th[data-white="${ids[0]}"] div span`)?.text();
|
||||||
|
const black = $(`th[data-white="${ids[1]}"] div span`)?.text();
|
||||||
|
const weights = td.find('.weights pre').text();
|
||||||
|
const captures = $('#captures')[0];
|
||||||
|
captures.insertAdjacentHTML('beforeend', `<div>${white} vs ${black}<pre>${weights}</pre></div>`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
$('th[data-white], th[data-black]').on('click', e => {
|
||||||
|
const th = e.target.closest('th');
|
||||||
|
const name = th.find('span').text();
|
||||||
|
const info = th.find('pre').text();
|
||||||
|
captures.insertAdjacentHTML('beforeend', `<div>${name}<pre>${info}</pre></div>`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// ]]#
|
||||||
|
</script>
|
@@ -3,6 +3,7 @@
|
|||||||
<div class="section">
|
<div class="section">
|
||||||
<h2 class="error">Invalid tournament id</h2>
|
<h2 class="error">Invalid tournament id</h2>
|
||||||
</div>
|
</div>
|
||||||
|
#stop
|
||||||
#end
|
#end
|
||||||
#set($round = $math.toInteger($!params.round))
|
#set($round = $math.toInteger($!params.round))
|
||||||
#if(!$round)
|
#if(!$round)
|
||||||
|
@@ -83,7 +83,10 @@
|
|||||||
#end
|
#end
|
||||||
</div>
|
</div>
|
||||||
#if(!$tour.type.startsWith('TEAM'))
|
#if(!$tour.type.startsWith('TEAM'))
|
||||||
<div class="result-sheets"><a href="result-sheets?id=${tour.id}&round=${round}" target="_blank" class="ui mini floating icon button">result sheets <i class="fa fa-external-link"></i></a></div>
|
<div class="pairing-post-actions">
|
||||||
|
<a href="result-sheets?id=${tour.id}&round=${round}" target="_blank" class="ui mini floating icon button">result sheets <i class="fa fa-external-link"></i></a>
|
||||||
|
<a href="explain?id=${tour.id}&round=${round}" target="_blank" class="ui mini floating icon button">explain pairing <i class="fa fa-external-link"></i></a>
|
||||||
|
</div>
|
||||||
#end
|
#end
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user