diff options
-rw-r--r-- | comp.c | 2 | ||||
-rw-r--r-- | match.c | 5 | ||||
-rw-r--r-- | match.h | 1 | ||||
-rw-r--r-- | web/dartboat_wasm.c | 27 | ||||
-rw-r--r-- | web/static/dartboard.js | 151 | ||||
-rw-r--r-- | web/static/dartboat.js | 37 | ||||
-rw-r--r-- | web/static/index.html | 9 | ||||
-rw-r--r-- | web/static/style.css | 20 |
8 files changed, 220 insertions, 32 deletions
@@ -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); @@ -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); } @@ -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; |