summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--comp.c2
-rw-r--r--match.c5
-rw-r--r--match.h1
-rw-r--r--web/dartboat_wasm.c27
-rw-r--r--web/static/dartboard.js151
-rw-r--r--web/static/dartboat.js37
-rw-r--r--web/static/index.html9
-rw-r--r--web/static/style.css20
8 files changed, 220 insertions, 32 deletions
diff --git a/comp.c b/comp.c
index c0a673a..9427363 100644
--- a/comp.c
+++ b/comp.c
@@ -84,6 +84,7 @@ void comp_visit(struct leg *l)
{
struct visit *v = l->visits + l->n_visits++;
v->darts = calloc(3, sizeof(*(v->darts)));
+ v->ccoords = calloc(3, sizeof(*(v->ccoords)));
for (int i = 0; i < 3; ++i) {
struct segment ts = next_dart(l->rem - v->points, 3 - i);
@@ -91,6 +92,7 @@ void comp_visit(struct leg *l)
struct pcoords dc = throw_dart(tc);
struct segment ds = get_segment(dc);
+ v->ccoords[v->n_darts] = pol_to_cart(dc); // FIXME double conversion
v->darts[v->n_darts++] = ds;
v->points += segment_points(ds);
diff --git a/match.c b/match.c
index 17ce3b1..08c0a3a 100644
--- a/match.c
+++ b/match.c
@@ -14,9 +14,12 @@ struct leg *leg_init(int points, char *name)
void leg_free(struct leg *l)
{
- for (int i = 0; i < l->n_visits; ++i)
+ for (int i = 0; i < l->n_visits; ++i) {
if (l->visits[i].darts)
free(l->visits[i].darts);
+ if (l->visits[i].ccoords)
+ free(l->visits[i].ccoords);
+ }
free(l->visits);
free(l);
}
diff --git a/match.h b/match.h
index 0b44b45..9cdb261 100644
--- a/match.h
+++ b/match.h
@@ -8,6 +8,7 @@ struct visit {
int rem;
int n_darts;
struct segment *darts;
+ struct ccoords *ccoords;
};
struct leg {
diff --git a/web/dartboat_wasm.c b/web/dartboat_wasm.c
index a4a075c..84cad62 100644
--- a/web/dartboat_wasm.c
+++ b/web/dartboat_wasm.c
@@ -12,7 +12,7 @@
int delay_ms = 500;
enum prompt_mode {
- PM_NONE,
+ PM_DARTBOARD,
PM_VISIT,
PM_NUM_DARTS,
PM_END_MATCH,
@@ -25,12 +25,13 @@ void set_prompt_mode(enum prompt_mode mode)
{
pm = mode;
- if (pm != PM_NONE)
+ if (pm != PM_DARTBOARD)
EM_ASM(setPromptActive());
else
EM_ASM(setPromptInactive());
- EM_ASM({setKeypad($0)}, pm == PM_SELECT_MODE ? "select_mode" : "default");
+ EM_ASM({setKeypad($0)}, pm == PM_DARTBOARD ? "dartboard" :
+ pm == PM_SELECT_MODE ? "select_mode" : "default");
}
enum match_mode {
@@ -60,9 +61,7 @@ void set_active_player(int pn)
if (state->mode == M_PVC && pn == 2) {
EM_ASM({promptMsgL($0)}, "Bot is throwing…");
- EM_ASM({setKeyLabel($0, $1)}, "submit", "OK");
- EM_ASM({setKeyLabel($0, $1)}, "rem", "REMAINING");
- set_prompt_mode(PM_NONE);
+ set_prompt_mode(PM_DARTBOARD);
} else {
EM_ASM({promptMsgL($0)}, "Enter points:");
EM_ASM({setKeyLabel($0, $1)}, "submit", "OK");
@@ -158,7 +157,7 @@ EMSCRIPTEN_KEEPALIVE void update_user_rem_from_pts(int pts)
update_player_rem(state->active_player, state->active_leg->rem - pts);
}
-EMSCRIPTEN_KEEPALIVE void draw_boat_throwing(int pts, char *str)
+EMSCRIPTEN_KEEPALIVE void draw_boat_throwing(int pts, char *str, double x, double y)
{
char pts_str[10];
sprintf(pts_str, "%d", pts);
@@ -167,6 +166,7 @@ EMSCRIPTEN_KEEPALIVE void draw_boat_throwing(int pts, char *str)
state->legs[1]->visits[state->legs[1]->n_visits-2].rem :
state->legs[1]->start;
+ EM_ASM({draw_point($0, $1)}, x, y);
update_player_rem(2, rem - pts);
EM_ASM({setPromptInput($0)}, pts_str);
EM_ASM({promptMsgR($0)}, str);
@@ -176,6 +176,7 @@ EMSCRIPTEN_KEEPALIVE void draw_boat_throwing(int pts, char *str)
void handle_next();
EMSCRIPTEN_KEEPALIVE void end_boat_visit(int rem, double avg)
{
+ EM_ASM(clear_points());
update_player_rem(2, rem);
EM_ASM({updatePlayerAvg($0, $1)}, 2, avg);
EM_ASM({setPromptInput($0)}, "");
@@ -215,10 +216,12 @@ EMSCRIPTEN_KEEPALIVE void boat_visit()
len_str += sprintf(str + len_str, i ? "-%s" : "%s", seg_name);
free(seg_name);
+ struct ccoords c = v->ccoords[i];
+
char *tmp = malloc(len_str + 1); // free in draw_boat_throwing
memcpy(tmp, str, len_str + 1);
- EM_ASM({scheduleCCall($0, $1, $2, $3)}, "draw_boat_throwing",
- delay_ms * (i+1), pts, tmp);
+ EM_ASM({scheduleCCall($0, $1, $2, $3, $4, $5)}, "draw_boat_throwing",
+ delay_ms * (i+1), pts, tmp, c.x, c.y);
}
EM_ASM({scheduleCCall($0, $1, $2, $3)}, "end_boat_visit",
@@ -487,7 +490,7 @@ char *prompt_get()
void prompt_handle_pre(char *command)
{
- if (pm == PM_NONE)
+ if (pm == PM_DARTBOARD)
return;
EM_ASM(clearOi());
@@ -532,7 +535,7 @@ void prompt_handle_append(char *data)
void prompt_handle_backspace()
{
- if (pm == PM_NONE)
+ if (pm == PM_DARTBOARD)
return;
char *str = prompt_get();
@@ -547,7 +550,7 @@ void prompt_handle_backspace()
void prompt_handle_clear()
{
- if (pm == PM_NONE)
+ if (pm == PM_DARTBOARD)
return;
EM_ASM({setPromptInput($0)}, "");
diff --git a/web/static/dartboard.js b/web/static/dartboard.js
new file mode 100644
index 0000000..46e5c4c
--- /dev/null
+++ b/web/static/dartboard.js
@@ -0,0 +1,151 @@
+const diameter = 451.0;
+const dists = [7.13, 16.68, 97.06, 106.62, 159.66, 169.22];
+const wire_width = 1.56;
+const sectors = ['20', '1', '18', '4', '13', '6', '10', '15', '2', '17',
+ '3', '19', '7', '16', '8', '11', '14', '9', '12', '5'];
+
+const c_black = '#272b2c';
+const c_white = '#fbe3ba';
+const c_red = '#f6302f';
+const c_green = '#22912d';
+const c_wire = '#909ca0';
+const c_wire_inner = '#d8e6ec';
+
+const svgns = 'http://www.w3.org/2000/svg';
+
+function p2c(a, r) {
+ const t = a * Math.PI / 180;
+ return [r * Math.cos(t), r * Math.sin(t)];
+}
+
+function gen_arc(a1, a2, r1, r2, c) {
+ const [x1, y1] = p2c(a1 === a2 ? 1 : a2, r1);
+ const [x2, y2] = p2c(a1 === a2 ? 0 : a1, r1);
+ const [x3, y3] = p2c(a1 === a2 ? 0 : a1, r2);
+ const [x4, y4] = p2c(a1 === a2 ? 1 : a2, r2);
+
+ const elem = document.createElementNS(svgns, 'path');
+ elem.setAttribute('d',
+ `M${x1} ${y1}` +
+ `A${r1} ${r1} 0 0 0 ${x2} ${y2}` +
+ (a1 === a2 ? `A${r1} ${r1} 0 1 0 ${x1} ${y1}` : '') +
+ `L${x3} ${y3}` +
+ `A${r2} ${r2} 0 0 1 ${x4} ${y4}` +
+ (a1 === a2 ? `A${r2} ${r2} 0 1 1 ${x3} ${y3}` : '') +
+ 'Z');
+ elem.setAttribute('fill', c)
+ return elem;
+}
+
+function gen_segment(a, r1, r2, c) {
+ return gen_arc(a - 9, a + 9, r1, r2, c);
+}
+
+function gen_ring(r, w, c) {
+ return gen_arc(0, 0, r - w/2, r + w/2, c);
+}
+
+function gen_circle(r, c) {
+ const elem = document.createElementNS(svgns, 'circle');
+ elem.setAttribute('r', r);
+ elem.setAttribute('fill', c)
+ return elem;
+}
+
+function gen_line(a, r1, r2, w, c) {
+ const [x1, y1] = p2c(a, r1);
+ const [x2, y2] = p2c(a, r2);
+
+ const elem = document.createElementNS(svgns, 'line');
+ elem.setAttribute('x1', x1);
+ elem.setAttribute('y1', y1);
+ elem.setAttribute('x2', x2);
+ elem.setAttribute('y2', y2);
+ elem.setAttribute('stroke', c);
+ elem.setAttribute('stroke-width', w);
+ return elem;
+}
+
+function draw_spider(board) {
+ for (let i = 5; i > 1; --i) {
+ board.appendChild(gen_ring(dists[i], wire_width, c_wire));
+ board.appendChild(gen_ring(dists[i], wire_width/2, c_wire_inner));
+ }
+
+ for (let i = 20; i > 0; --i) {
+ let a = 90 - (i * 18) - 9;
+ if (a < 0) a += 360;
+ board.appendChild(gen_line(a, dists[1], dists[5] + 10,
+ wire_width, c_wire));
+ board.appendChild(gen_line(a, dists[1], dists[5] + 10 - wire_width/4,
+ wire_width/2, c_wire_inner));
+ }
+
+ for (let i = 1; i >= 0; --i) {
+ board.appendChild(gen_ring(dists[i], wire_width, c_wire));
+ board.appendChild(gen_ring(dists[i], wire_width/2, c_wire_inner));
+ }
+}
+
+function draw_numbers(board) {
+ board.appendChild(gen_ring(diameter/2 - wire_width*4, wire_width, '#ddd'));
+
+ const r = diameter/2 - 33/2;
+ for (let i = 0; i < 20; ++i) {
+ let a = 90 - (i * 18);
+ if (a < 0) a += 360;
+ const [x, y] = p2c(a, r);
+
+ const elem = document.createElementNS(svgns, 'text');
+ elem.textContent = sectors[i];
+ elem.setAttribute('font-size', '33');
+ elem.setAttribute('fill', '#fff');
+ elem.setAttribute('transform', `scale(1 -1) translate(${x} ${-y}) ` +
+ `rotate(${a <= 180 ? 90 - a : 270 - a})`);
+ elem.setAttribute('text-anchor', 'middle');
+ elem.setAttribute('dominant-baseline', 'central');
+ board.appendChild(elem);
+ }
+}
+
+function draw_board() {
+ const board = document.getElementById('dartboard');
+ const points = document.getElementById('dartboard-points');
+
+ const r = diameter/2;
+ board.setAttribute('transform', `translate(${r} ${r}) scale(1 -1)`);
+ points.setAttribute('transform', `translate(${r} ${r}) scale(1 -1)`);
+ board.appendChild(gen_circle(r, c_black));
+
+ for (let i = 5; i > 1; --i) {
+ for (let j = 0; j < 20; ++j) {
+ let a = 90 - (j * 18);
+ if (a < 0) a += 360;
+
+ const elem = gen_segment(a, dists[i-1], dists[i],
+ i%2 ? (j%2 ? c_green : c_red) : (j%2 ? c_white : c_black));
+ board.appendChild(elem);
+ }
+ }
+
+ board.appendChild(gen_circle(dists[1], c_green));
+ board.appendChild(gen_circle(dists[0], c_red));
+ draw_spider(board);
+ draw_numbers(board);
+}
+
+function draw_point(x, y) {
+ const points = document.getElementById('dartboard-points');
+ elem = gen_circle(8, '#33f');
+ elem.setAttribute('cx', x);
+ elem.setAttribute('cy', y);
+ elem.setAttribute('stroke', '#ff0');
+ elem.setAttribute('stroke-width', '2');
+ points.appendChild(elem);
+}
+
+function clear_points() {
+ document.getElementById('dartboard-points').textContent = '';
+}
+
+document.addEventListener('DOMContentLoaded', () => draw_board());
diff --git a/web/static/dartboat.js b/web/static/dartboat.js
index 51570b4..98bbb37 100644
--- a/web/static/dartboat.js
+++ b/web/static/dartboat.js
@@ -23,10 +23,10 @@ function scheduleCCall(f, ms, ...args) {
}
function toCString(str) { // caller must free
- const len = lengthBytesUTF8(str) + 1;
- const cstr = _malloc(len);
- stringToUTF8(str, cstr, len);
- return cstr;
+ const size = lengthBytesUTF8(str) + 1;
+ const ptr = _malloc(size);
+ stringToUTF8(str, ptr, size);
+ return ptr;
}
function promptGet() { // caller must free
@@ -64,8 +64,8 @@ function promptMsgR(ptr) {
function setKeypad(keypad) {
const keypad_id = `keypad-${UTF8ToString(keypad)}`;
- $$('.keypad').forEach(e => {
- e.style.display = e.id === keypad_id ? '' : 'none'; });
+ $$('.keypad').forEach(e =>
+ e.style.display = e.id === keypad_id ? '' : 'none');
}
function isKeyActive(k) {
@@ -119,13 +119,13 @@ function clearVisits() {
const POINT_CLASSES = [180, 140, 100, 60, 40, 20, 0];
function drawVisit(visit_no, p1_pts, p1_rem, p2_pts, p2_rem, p2_darts) {
- let e = $('#visits');
+ const e = $('#visits');
- for (let [i, ptr] of [
+ for (const [i, ptr] of [
p1_pts, p1_rem, visit_no, p2_rem, p2_pts, p2_darts].entries()) {
- let div = e.appendChild(document.createElement('div'));
- let v = div.textContent = UTF8ToString(ptr);
+ const div = e.appendChild(document.createElement('div'));
+ const v = div.textContent = UTF8ToString(ptr);
div.classList.add(`visit-col${i+1}`);
if (i == 0 || i == 4)
div.classList.add(`p${POINT_CLASSES.find(x => x <= v)}`);
@@ -161,6 +161,13 @@ function readOpts() {
setOpt('stdev', val, false);
}
+function boatAfloat() {
+ if (document.readyState === 'loading')
+ document.addEventListener('DOMContentLoaded', () => _init());
+ else
+ _init();
+}
+
document.addEventListener('DOMContentLoaded', () => {
let f;
@@ -173,7 +180,8 @@ document.addEventListener('DOMContentLoaded', () => {
f = e => $(`#${e.target.dataset.modal}`).style.display = 'block';
$$('[data-modal]').forEach(x => x.addEventListener('click', f));
- f = e => e.target.style.display = 'none';
+ f = e => {
+ if (e.target === e.currentTarget) e.target.style.display = 'none'; };
$$('.modal').forEach(x => x.addEventListener('click', f));
});
@@ -194,10 +202,3 @@ document.addEventListener('keydown', e => {
else if (e.key == 'u')
promptHandle('undo');
});
-
-function boatAfloat() {
- if (document.readyState === 'loading')
- document.addEventListener('DOMContentLoaded', () => _init());
- else
- _init();
-}
diff --git a/web/static/index.html b/web/static/index.html
index 2bc1275..a9c5aea 100644
--- a/web/static/index.html
+++ b/web/static/index.html
@@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, user-scalable=no">
<link rel="stylesheet" type="text/css" href="style.css">
<script src="dartboat.js"></script>
+ <script src="dartboard.js"></script>
<script src="dartboat_wasm.js"></script>
</head>
<body>
@@ -56,6 +57,14 @@
<div data-command="append:2;submit" class="key"><span class="keyboard-val">[2]</span> One-player scoreboard</div>
<div data-command="append:3;submit" class="key"><span class="keyboard-val">[3]</span> Two-player scoreboard</div>
</div>
+ <div id="keypad-dartboard" class="keypad" style="display: none">
+ <div id="dartboard-container">
+ <svg viewBox="0 0 451 451">
+ <g id="dartboard"></g>
+ <g id="dartboard-points"></g>
+ </svg>
+ </div>
+ </div>
</div>
<div id="settings-bar">
<div>dartboat™</div>
diff --git a/web/static/style.css b/web/static/style.css
index 7247a6b..e306177 100644
--- a/web/static/style.css
+++ b/web/static/style.css
@@ -1,5 +1,6 @@
@import url('https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Source+Serif+Pro:ital,wght@0,400;0,700;1,400&display=swap');
+@import url('https://fonts.googleapis.com/css2?family=Inter');
body {
color-scheme: dark;
@@ -21,7 +22,7 @@ div#main {
display: grid;
grid-template-columns: 1fr;
- grid-template-rows: min-content min-content 2fr 3fr;
+ grid-template-rows: min-content min-content minmax(0, 2fr) minmax(0, 3fr);
grid-template-areas: "settings-bar" "info" "visits" "controls";
}
@@ -51,6 +52,23 @@ div#controls #keypad-select_mode .key {
justify-content: left;
}
+div#controls #keypad-dartboard #dartboard-container {
+ grid-column: 1 / -1;
+
+ min-width: 0;
+ min-height: 0;
+ padding: 0.4rem;
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+div#controls #keypad-dartboard #dartboard-container svg {
+ font-family: 'Inter';
+ height: 100%;
+}
+
div#oi {
visibility: hidden;