Skip to content

Commit

Permalink
tools/match_win: add
Browse files Browse the repository at this point in the history
  • Loading branch information
theonlypwner committed Nov 7, 2024
1 parent 7d5f8a0 commit b1bf4a9
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 0 deletions.
3 changes: 3 additions & 0 deletions docs/_data/nav/tools.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@
- name: Time Perception
desc: It's later than you think!
href: tools/time
- name: Match Win Calculator
desc: Calculate the probability of winning first-to-n/best-of-(2n-1)
href: tools/match_win
18 changes: 18 additions & 0 deletions docs/tools/match_win.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
title: Match Win Calculator
date: "2024-11-06 21:48:00 -0700"
entry: app_tools_match_win
---
<p>
Many games and sports play multiple rounds, also known as first-to-n or best-of-(2n-1).
This structure amplifies skill gaps, as the underdog is more likely to lose the match than an individual round, which <a href="https://stats.stackexchange.com/a/458808">can be calculated with a negative binomial distribution</a>.
</p>

<p>
In the table below, the row corresponds to the number of points for the opponent, and
the column corresponds to the number of points for the current player.
The cell shows the probability that the current player wins the match.
Double-click the slider for P to reset to 0.5.
</p>

{% include app.html %}
5 changes: 5 additions & 0 deletions scripts/app_tools_match_win.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import AppToolsMatchWin from './tools/match_win/AppToolsMatchWin.svelte'

import loadApp from './victorz/app'

loadApp(AppToolsMatchWin)
59 changes: 59 additions & 0 deletions scripts/tools/match_win/AppToolsMatchWin.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<script lang="ts">
import { pStore } from '@/util/svelte'
const n = pStore('tool/match_win/n', 10)
const p = pStore('tool/match_win/p', 0.5)
$: memo = ((n, p) => {
const r = Array(n).fill(undefined).map(() => Array(n).fill(undefined))
for (let i = n-1; i >= 0; i--) {
for (let j = n-1; j >= 0; j--) {
const i1 = i + 1
const j1 = j + 1
r[i][j] = p * (j1 == n ? 1 : r[i][j1]) + (i1 == n ? 0 : (1 - p) * r[i1][j])
}
}
return r
})($n, $p)
</script>

<p>N (number of rounds to win)</p>
<input type="number" class="form-control" min=1 bind:value={$n}>
<input type="range" class="form-range" min=1 max=32 bind:value={$n}>

<p>P (probability of winning a round)</p>
<input type="number" class="form-control" min=0 max=1 step=0.01 bind:value={$p}>
<input type="range" class="form-range" min=0 max=1 step=0.001 bind:value={$p} on:dblclick={() => $p = 0.5}>

<table class="table table-striped table-bordered table-hover caption-top w-auto">
<caption>Probability of winning first-to-{$n}/best-of-{2*$n-1}</caption>
<thead>
<tr>
<th rowspan=2 colspan=2></th>
<th colspan={$n}>Player score</th>
</tr>
<tr>
{#each memo as _, i}
<th>{i}</th>
{/each}
</tr>
</thead>
<tbody>
{#each memo as row, i}
<tr>
{#if !i}
<th rowspan={$n}><div style="writing-mode: vertical-lr; text-align: end; transform: rotate(180deg)">Opponent score</div></th>
{/if}
<th>{i}</th>
{#each row as v}
{@const balanced = v * (1-v) * 2}
<td
title={`${v}\n${v / (1-v)}:1\n1:${1/v - 1}`}
style="background-color: hsl({v * v * 180} {40 + balanced * 60} {80 + balanced * 20})">
{v.toFixed(5)}
</td>
{/each}
</tr>
{/each}
</tbody>
</table>

0 comments on commit b1bf4a9

Please sign in to comment.