CLRG Scoring Analysis pre-draft

This commit is contained in:
Neale Pickett 2022-10-09 20:59:33 -06:00
parent 5bfb4d34b8
commit 9f8a52beca
13 changed files with 550 additions and 19 deletions

View File

@ -1,9 +1,6 @@
--- ---
date: "2021-12-23T00:00:00Z" title: "Doctor Who S01E27-30: Doctor Who goes to Mexico"
tags: date: 2021-12-23
- drwho
title: 'Doctor Who S01E27-30: Doctor Who goes to Mexico'
url: blog/2021-12-23-drwho-S01E27-mexico/
--- ---
A bunch of white people pretend to be Aztecs, and explore their moral A bunch of white people pretend to be Aztecs, and explore their moral
@ -42,7 +39,7 @@ Barbara abusing her god status to outlaw human sacrifice. The Doctor gets
married or engaged or something. Susan finally expresses an emotion other married or engaged or something. Susan finally expresses an emotion other
than terror: than terror:
{{< video "susan-well-hello-there.mp4" "Well, hello there." >}} {{< video src="susan-well-hello-there.mp4" text="Well, hello there." >}}
I'm back to being uncomfortable with the cultural framing here. I mean, I'm back to being uncomfortable with the cultural framing here. I mean,
maybe this is useful for viewing 1960s British culture, but that's still maybe this is useful for viewing 1960s British culture, but that's still

View File

@ -1,7 +1,6 @@
--- ---
date: "2022-09-06T12:11:00-0600"
title: Truck bling title: Truck bling
url: blog/2022-09-06-truck-bling/ date: "2022-09-06T12:11:00-0600"
--- ---
Yesterday, Yesterday,
@ -18,7 +17,7 @@ the friend soldered a bunch of stuff together,
and we plugged it in. and we plugged it in.
It friggin' worked! It friggin' worked!
{{< video "truck-bling.m4v" "Pickup truck with color-changing ground effects">}} {{< video src="truck-bling.m4v" text="Pickup truck with color-changing ground effects">}}
The really cool part, at least for me, The really cool part, at least for me,
is that now she can hang out with her laptop in the cabin, is that now she can hang out with her laptop in the cabin,

View File

@ -0,0 +1,122 @@
let awardPoints = [
100, // 1
75, // 2
65, // 3
60, // 4
56, // 5
53, // 6
50, // 7
47, // 8
45, // 9
43, // 10
41, // 11
39, // 12
38, // 13
37, // 14
36, // 15
35, // 16
34, // 17
33, // 18
32, // 19
31, // 20
30, // 21
29, // 22
28, // 23
27, // 24
26, // 25
25, // 26
24, // 27
23, // 28
22, // 29
21, // 30
20, // 31
19, // 32
18, // 33
17, // 34
16, // 35
15, // 36
14, // 37
13, // 38
12, // 39
11, // 40
10, // 41
9, // 42
8, // 43
7, // 44
6, // 45
5, // 46
4, // 47
3, // 48
2, // 49
1, // 50
0.75, // 51
0.65, // 52
0.60, // 53
0.56, // 54
0.53, // 55
0.50, // 56
0.47, // 57
0.45, // 58
0.43, // 59
0.41, // 60
0.39, // 61
0.38, // 62
0.37, // 63
0.36, // 64
0.35, // 65
0.34, // 66
0.33, // 67
0.32, // 68
0.31, // 69
0.30, // 70
0.29, // 71
0.28, // 72
0.27, // 73
0.26, // 74
0.25, // 75
0.24, // 76
0.23, // 77
0.22, // 78
0.21, // 79
0.20, // 80
0.19, // 81
0.18, // 82
0.17, // 83
0.16, // 84
0.15, // 85
0.14, // 86
0.13, // 87
0.12, // 88
0.11, // 89
0.10, // 90
0.09, // 91
0.08, // 92
0.07, // 93
0.06, // 94
0.05, // 95
0.04, // 96
0.03, // 97
0.02, // 98
0.01, // 99
0.00, // 100
]
function init() {
for (let tbody of document.querySelectorAll(".awardPoints tbody")) {
for (let i = 0; i < awardPoints.length; i++) {
let tr = tbody.appendChild(document.createElement("tr"))
tr.appendChild(document.createElement("td")).textContent = i + 1
tr.appendChild(document.createElement("td")).textContent = awardPoints[i].toFixed(2)
}
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init)
} else {
init()
}
export {
awardPoints,
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,175 @@
---
title: CLRG Scoring Analyzed
date: 2022-10-09
stylesheets:
- toys.css
scripts:
- speculator.mjs
- scorecard.mjs
---
Let's take a look how how CLRG does its scoring!
*With math!*
I'm going to only view this mathematically.
I know there's a lot of other stuff that goes on, like line-ups.
I'm only going to look at this with numbers.
## How CLRG Scoring Works
As I am given to understand, the scoring works like so:
1. Adjudicators give you a "raw score": a real number between 0 and 100
2. The scoring system ranks each dancer per adjudicator, based on raw scores
3. These rankings are mapped into "award points"
4. All of a dancer's award points are summed
5. Final ranking is determined by comparing total award points
## Raw Scoring
The way raw scores translate into rankings and award points is a little
confusing, so I've made a little tool you can play with to get a feel for how it
works. Essentially, it's a way of normalizing places to an adjudicator: score
weights are only relative to the judge that assigns them.
Adjudicator A can assign scores between 80 and 100;
adjudicator B can assign scores between 1 and 40;
and they'll both have a first, second, third, fourth place, etc.
These places then get translated into award points.
## Award Points
Award points are handed out based on ranking against other dancers for that
adjudicator. I obtained these values from a FeisWorx results page for my kid:
<div class="awardPoints">
<table>
<thead>
<tr>
<th>Ranking</th><th>Award Points</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
If there's a 2-way, 3-way, or n-way tie,
all tied dancers get the average of the next 2, 3, or n award points,
and the next 2, 3, or n rankings are skipped.
### Award Points artifacts
One quirk of awards points is that for any given overall
score, there are only a handful of possible judge rankings that could have led
to it. That means you can make some guesses about how each judge ranked an
individual dancer, based on only their total award points.
Here's a handy calculator!
It (currently) doesn't consider the possibility of a tie.
<div class="scrolly">
<fieldset class="speculator">
<legend>CLRG Award Points Speculator</legend>
<div>
Points: <input name="points" type="number" min=41 max=10000 value=188>
<input name="adjudicators" type="hidden">
</div>
<table class="results">
<caption>Possible Rankings</caption>
<thead>
<tr class="warning"><th>Computing: this could take a while!</th></tr>
</thead>
<tbody></tbody>
</table>
</fieldset>
</div>
## What's with these values?
At first glance, the award points look like the output of an exponential function.
{{<figure src="chart.png" alt="Chart of scores vs. award points">}}
In an effort to figure out where these numbers came from,
I ran some curve fitting against the data.
Here's the best I could come up with:
| Ranking range | Award Points Function | Type of function |
| --: | --: | --- |
| 1 - 11 | 100 * x^-0.358 | Exponential |
| 12 - 50 | 51 - x | Linear |
| 51 - 60 | 14.2 - 0.46x + 0.00385x | Polynomial |
| 61 - 100 | 1 - x/100 | Linear |
If you, dear reader, are a mathematician,
I would love to hear your thoughts on why they went with this algorithm.
There are a few points to note here:
* 1st place is a *huge deal*. Disproportionately huge.
* Places 2-10 are similarly big deals compared to places 3-11.
* Places 12-50 operate the way most people probably assume ranking works: linearly.
* Places 51-60 are a second degree polynomial, but it doesn't matter much for so few points.
* Places 61-100 are all less than 1 point. If you're a judge trying to tank a top dancer, anywhere in this range is equivalent to anywhere else.
## Consequences of Exponential Award Points
Playing around with this,
I've found a few interesting consequences
of the exponential growth in the top 11 places.
### 1st place is super important
1st place is weighted so heavily that it's almost impossible to overcome without
your own 1st.
Take for example this scenario,
in which Adjudicator 1 has promised to give 1st place to Alice:
<table class="scorecard">
<thead>
<tr>
<td></td>
<th>Alice</th>
<th>Bob</th>
</tr>
</thead>
<tbody>
<tr>
<th class="justify-left">Adj. 1</th>
<td><input type="number" min=1 max=11 value=1 readonly></td>
<td><input type="number" min=1 max=11 value=3></td>
</tr>
<tr>
<th class="justify-left">Adj. 2</th>
<td><input type="number" min=1 max=11 value=1></td>
<td><input type="number" min=1 max=11 value=3></td>
</tr>
<tr>
<th class="justify-left">Adj. 3</th>
<td><input type="number" min=1 max=11 value=1></td>
<td><input type="number" min=1 max=11 value=3></td>
</tr>
</tbody>
<tfoot>
<tr>
<th class="justify-left">Award Points</th>
<td class="justify-right"><output name="points"></td>
<td class="justify-right"><output name="points"></td>
</tr>
<tr>
<th class="justify-left">Ranking</th>
<td class="justify-right"><output name="ranking"></td>
<td class="justify-right"><output name="ranking"></td>
</tr>
</tfoot>
</table>

View File

@ -0,0 +1,54 @@
import {awardPoints} from "./awardPoints.mjs"
function scorecardUpdate(scorecard) {
let scores = []
let points = []
let firstRow = scorecard.querySelector("tbody tr")
for (let input of firstRow.querySelectorAll("input")) {
scores.push(0)
points.push(0)
}
for (let row of scorecard.querySelectorAll("tbody tr")) {
let i = 0
for (let input of row.querySelectorAll("input")) {
let ranking = Number(input.value)
scores[i] += ranking
points[i] += awardPoints[ranking]
i += 1
}
}
{
let i = 0
for (let out of scorecard.querySelectorAll("tfoot output[name='points']")) {
out.value = points[i]
i += 1
}
}
{
let i = 0
for (let out of scorecard.querySelectorAll("tfoot output[name='ranking']")) {
out.value = scores[i]
i += 1
}
}
}
function init() {
for (let scorecard of document.querySelectorAll(".scorecard")) {
for (let input of scorecard.querySelectorAll("input")) {
input.addEventListener("input", () => scorecardUpdate(scorecard))
}
scorecardUpdate(scorecard)
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init)
} else {
init()
}

View File

@ -0,0 +1,104 @@
import { awardPoints } from "./awardPoints.mjs"
function arraysEqual(a, b) {
if (a === b) return true;
if (a == null || b == null) return false;
if (a.length != b.length) return false;
for (let i = 0; i < a.length; ++i) {
if (a[i] !== b[i]) return false;
}
return true;
}
function awardPossibilities(total=0, depth=1) {
if (depth == 1) {
if (awardPoints.includes(total)) {
return [[total]]
} else {
return []
}
}
let possibilities = []
for (let p of awardPoints) {
if (p <= total) {
for (let subPossibility of awardPossibilities(total - p, depth - 1)) {
let v = [p].concat(subPossibility)
v.sort((a,b) => b-a)
possibilities.push(v)
}
}
}
possibilities.sort((a,b) => b.reduce((a,b) => a*100+b) - a.reduce((a,b) => a*100+b))
let uniquePossibilities = []
for (let p of possibilities) {
if (uniquePossibilities.length == 0 || !arraysEqual(p, uniquePossibilities[uniquePossibilities.length - 1])) {
uniquePossibilities.push(p)
}
}
return uniquePossibilities
}
function speculate(calc) {
let points = calc.querySelector("[name=points]").value || 0
let adjudicators = calc.querySelector("[name=adjudicators]").value || 3
let results = calc.querySelector(".results tbody")
while (results.firstChild) {
results.removeChild(results.firstChild)
}
for (let warning of calc.querySelectorAll(".warning")) {
if (adjudicators >3) {
warning.classList.add("visible")
setTimeout(() => asyncSpeculate(calc, points, adjudicators), 0)
} else {
warning.classList.remove("visible")
asyncSpeculate(calc, points, adjudicators)
}
}
}
async function asyncSpeculate(calc, points, adjudicators) {
let results = calc.querySelector(".results tbody")
let possibilites = awardPossibilities(points, adjudicators)
if (possibilites.length == 0) {
let row = results.appendChild(document.createElement("tr"))
let cell = row.appendChild(document.createElement("th"))
cell.textContent = "No possible combinations"
} else {
let row = results.appendChild(document.createElement("tr"))
for (let i = 1; i <= adjudicators; ++i) {
let cell = row.appendChild(document.createElement("th"))
cell.textContent = "Adj. " + i
}
for (let possibility of possibilites) {
let row = results.appendChild(document.createElement("tr"))
for (let p of possibility) {
let cell = row.appendChild(document.createElement("td"))
cell.textContent = p
}
}
}
for (let warning of calc.querySelectorAll(".warning")) {
warning.classList.remove("visible")
}
}
function init() {
for (let calc of document.querySelectorAll(".speculator")) {
for (let input of calc.querySelectorAll("input")) {
input.addEventListener("input", () => speculate(calc))
}
speculate(calc)
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init)
} else {
init()
}

View File

@ -0,0 +1,36 @@
.winner {
color:cornsilk;
}
figure img {
max-width: 100%;
}
.warning {
color: #e64;
display: none;
}
.warning.visible {
display: initial;
}
.awardPoints {
display: inline-block;
max-height: 60vh;
overflow-y: auto;
margin: 1em;
}
.awardPoints table {
margin: initial;
}
.awardPoints thead {
position: sticky;
top: 0;
}
.awardPoints tbody {
max-height: 60vh;
overflow-y: auto;
}
.awardPoints td {
text-align: right;
}

View File

@ -5,13 +5,20 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:regular,bold,italic"> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:regular,bold,italic">
<!-- My stuff --> <!-- My stuff -->
<link rel="stylesheet" media="screen" href="{{"/assets/css/default.css" | relURL}}">
<link rel="icon" type="image/png" href="{{"/assets/images/face.png" | relURL}}"> <link rel="icon" type="image/png" href="{{"/assets/images/face.png" | relURL}}">
<link rel="stylesheet" media="screen" href="{{"/assets/css/default.css" | relURL}}">
{{range .AlternativeOutputFormats}} {{range .AlternativeOutputFormats}}
<link rel="{{.Rel}}" type="{{.MediaType.Type}}" title="{{$.Site.Title}}" href="{{.Permalink}}"> <link rel="{{.Rel}}" type="{{.MediaType.Type}}" title="{{$.Site.Title}}" href="{{.Permalink}}">
{{end}} {{end}}
{{range .Params.stylesheets}}
<link rel="stylesheet" media="screen" href="{{($.Page.Resources.GetMatch .).RelPermalink}}">
{{end}}
{{range .Params.scripts}} {{range .Params.scripts}}
<script src="{{.}}"></script> {{end}}
{{range .Params.scripts}}
<script src="{{($.Page.Resources.GetMatch .).RelPermalink}}"
{{- if strings.HasSuffix . ".mjs"}}type="module"{{end -}}
></script>
{{end}} {{end}}
{{range .Params.headers}} {{range .Params.headers}}
{{. | safeHTML}} {{. | safeHTML}}

View File

@ -0,0 +1,4 @@
<figure>
{{- $img := $.Page.Resources.GetMatch (.Get "src")}}
<img src="{{$img.RelPermalink}}" alt="{{.Get "alt"}}">
</figure>

View File

@ -1,4 +1,4 @@
<video autoplay muted loop> <video autoplay muted loop>
<source src="{{.Get 0}}"> <source src="{{($.Page.Resources.GetMatch (.Get "src")).RelPermalink}}">
{{.Get 1}} {{.Get "text"}}
</video> </video>

3
run.sh
View File

@ -4,6 +4,9 @@ case "$(hostname)" in
sweetums) sweetums)
baseURL=http://sweetums.lan:1313/ baseURL=http://sweetums.lan:1313/
;; ;;
penguin)
baseURL=http://penguin.linux.test:1313/
;;
*) *)
baseURL=http://$(hostname --fqdn):1313/ baseURL=http://$(hostname --fqdn):1313/
;; ;;

View File

@ -19,12 +19,12 @@ body {
} }
input { input {
font-family: "Lato", "Roboto", sans-serif; font-family: inherit;
font-size: 13pt; font-size: inherit;
border: 0; border: thin solid #ccc;
outline: 0; }
background: transparent; input:read-only {
border-bottom: 1px solid black; background-color: #eee;
} }
.title, td.main { .title, td.main {
@ -146,6 +146,36 @@ button.big {
background: inherit; background: inherit;
} }
legend {
background-color: #e0e4cc;
}
table {
margin: 1em;
border-collapse: collapse;
}
thead, tfoot {
color: #e64;
background-color: rgba(224, 228, 204, 0.8);
}
tbody tr:nth-of-type(even) {
background-color: rgba(238, 102, 68, 0.05);
}
td, th {
padding: 0.2em 0.5em;
}
caption {
caption-side: bottom;
font-size: small;
}
.justify-right, input[type="number"] {
text-align: right;
}
.justify-left {
text-align: left;
}
.tags { .tags {
font-size: small; font-size: small;
} }