From 05dd0652a768ac05740d1465d30e1792b874414f Mon Sep 17 00:00:00 2001 From: David Vazgenovich Shakaryan Date: Wed, 20 Apr 2022 11:00:24 -0700 Subject: add web interface using Wasm --- web/static/dartboat.js | 260 +++++++++++++++++++++++++++++++++++++++++ web/static/index.html | 52 +++++++++ web/static/style.css | 305 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 617 insertions(+) create mode 100644 web/static/dartboat.js create mode 100644 web/static/index.html create mode 100644 web/static/style.css (limited to 'web/static') diff --git a/web/static/dartboat.js b/web/static/dartboat.js new file mode 100644 index 0000000..b261f28 --- /dev/null +++ b/web/static/dartboat.js @@ -0,0 +1,260 @@ +let match_active = false; +let prompt_disable = false; +let match_state, user_rem, boat_rem; +let oi_timeout; +let delay_ms = 1000; + +const POINT_CLASSES = [180, 140, 100, 60, 40, 20, 1, 0]; + +function stcall(f, ret_type, arg_types, args) { + return Module.ccall(f, ret_type, + arg_types ? ['number'].concat(arg_types) : ['number'], + args ? [match_state].concat(args) : [match_state]); +} + +function initMatch() { + stcall('free_match'); + updateDelay(delay_ms); + match_state = stcall('start_match', 'number'); + match_active = true; + document.getElementById('user-rem').className = 'active'; + document.getElementById('match').textContent = ''; + promptSuggStr(""); + stcall('draw_match'); +} + +function oi() { + document.getElementById('oi').textContent = 'oi!'; + + oi_timeout = setTimeout(function() { oi_timeout = null; clearOi() }, 3000); +} + +function clearOi() { + document.getElementById('oi').textContent = ''; + + if (oi_timeout) { + clearTimeout(oi_timeout); + oi_timeout = null; + } +} + +function updateUserRem(rem) { + user_rem = document.getElementById('user-rem').textContent = rem; +} + +function updateBoatRem(rem) { + boat_rem = document.getElementById('boat-rem').textContent = rem; +} + +function promptMsg(p) { + document.getElementById('prompt-msg').textContent = UTF8ToString(p); +} + +function promptMsgStr(str) { + document.getElementById('prompt-msg').textContent = str; +} + +function promptSugg(p) { + document.getElementById('prompt-sugg').textContent = UTF8ToString(p); +} + +function promptSuggStr(str) { + document.getElementById('prompt-sugg').textContent = str; +} + +function promptUpdateRem() { + let elem = document.getElementById('user-rem'); + let pts = document.getElementById('prompt').textContent; + if (pts) + elem.textContent = `${user_rem} » ${user_rem - pts}`; + else + elem.textContent = user_rem; +} + +function boatTempRem(pts, str) { + let elem = document.getElementById('boat-rem'); + elem.textContent = `${boat_rem - pts} « ${boat_rem}`; + + document.getElementById('prompt').textContent = pts; + promptSuggStr(str); +} + +function promptAppend(val) { + if (!match_active || prompt_disable) return; + clearOi(); + + let elem = document.getElementById('prompt'); + if (elem.textContent.length < 3) { + elem.textContent += val; + promptUpdateRem(); + } +} + +function promptClear() { + if (!match_active || prompt_disable) return; + clearOi(); + + document.getElementById('prompt').textContent = ''; + promptUpdateRem(); +} + +function promptBackspace() { + if (!match_active || prompt_disable) return; + clearOi(); + + let elem = document.getElementById('prompt'); + elem.textContent = elem.textContent.slice(0, -1); + promptUpdateRem(); +} + +function setBoatActive() { + document.getElementById('user-rem').className = ''; + document.getElementById('boat-rem').className = 'active'; + promptMsgStr("Bot is throwing…"); + prompt_disable = true; +} + +function setUserActive() { + document.getElementById('user-rem').className = 'active'; + document.getElementById('boat-rem').className = ''; + prompt_disable = false; +} + +function promptSubmit() { + clearOi(); + if (prompt_disable) return; + if (!match_active) { + initMatch(); + return; + } + + let elem = document.getElementById('prompt'); + let p_user = elem.textContent; + if (!p_user) return; + promptClear(); + + if (!stcall('user_visit', 'number', ['number'], [p_user])) { + oi(); + return; + } + + elem = document.getElementById('match'); + elem.textContent = ''; + stcall('draw_match'); + + if (!stcall('is_match_over', 'number')) { + setBoatActive(); + stcall('boat_visit', 'number'); + } +} + +function boatVisitRes(rem, n, p1, p2, p3, ptr1, ptr2, ptr3) { + // cannot convert in timeout func because strings are freed in c func + let s1 = UTF8ToString(ptr1); + let s2 = UTF8ToString(ptr2); + let s3 = UTF8ToString(ptr3); + + if (delay_ms == 0) { + updateBoatRem(rem); + document.getElementById('match').textContent = ''; + stcall('draw_match'); + setUserActive(); + } else { + // backup to restore after bot is done + let user_sugg = document.getElementById('prompt-sugg').textContent; + promptSuggStr(''); + + setTimeout(function() { boatTempRem(p1, s1); }, delay_ms); + + if (n > 1) { + setTimeout(function() { boatTempRem(p1 + p2, `${s1}-${s2}`); }, delay_ms * 2); + } + + if (n > 2) { + setTimeout(function() { boatTempRem(p1 + p2 + p3, `${s1}-${s2}-${s3}`); }, delay_ms * 3); + } + + setTimeout(function() { + updateBoatRem(rem); + document.getElementById('prompt').textContent = ''; + document.getElementById('match').textContent = ''; + promptSuggStr(user_sugg); + stcall('draw_match'); + setUserActive(); + }, delay_ms * (n + 1)); + } +} + +function matchOver() { + match_active = false; + promptSuggStr("Press OK to play again."); + document.getElementById('user-rem').className = ''; +} + +function drawVisitNames(n1, n2) { + let elem = document.getElementById('match'); + for (let [k, v] of Object.entries({ 'user-name': n1, 'boat-name': n2 })) { + let div = document.createElement('div'); + div.className = k; + div.textContent = UTF8ToString(v); + elem.append(div); + } +} + +function drawVisit(visit_no, u_pts, u_rem, b_pts, b_rem, b_darts) { + let elem = document.getElementById('match'); + + for (let [i, v] of [visit_no, u_pts, u_rem, b_rem, b_pts, b_darts].entries()) { + let div = document.createElement('div'); + let vv = div.textContent = UTF8ToString(v); + if (i == 0) { + div.className = 'visit-no'; + } else if (i == 1 || i == 4) { + div.className = `p${POINT_CLASSES.find(x => x <= vv)}`; + } else if (i == 5) { + div.className = 'darts'; + } + elem.append(div); + } + + elem.scrollTop = elem.scrollHeight; +} + +function stdevChanged(val) { + Module.ccall('change_stdev', null, ['number', 'number'], [val, val]); +} + +function updateStdev(val) { + document.getElementById('stdev').value = val; +} + +function updateDelay(val) { + document.getElementById('delay').value = val; +} + +function delayChanged(val) { + delay_ms = val; +} + +function processKey(data) { + if (data.altKey || data.ctrlKey || data.metaKey || data.target.type == 'text') + return; + let key = data.key; + + if (isFinite(key)) + promptAppend(key); + else if (key == 'Enter') + promptSubmit(); + else if (key == 'Backspace') + promptBackspace(); +} + +function modal(id) { + document.getElementById(id).style.display = 'block'; +} + +window.onclick = function(e) { + if (e.target.classList.contains('modal')) e.target.style.display = 'none'; +} + +document.addEventListener('keydown', processKey); diff --git a/web/static/index.html b/web/static/index.html new file mode 100644 index 0000000..861adc5 --- /dev/null +++ b/web/static/index.html @@ -0,0 +1,52 @@ + + + + dartboat™ + + + + + + + +
+
+
+
+
+
+
+
+
+
+
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
CLEAR
+
0
+
OK
+
+
+
dartboat™
+
delay
+
stdev
+
?
+
+
+
+ + + diff --git a/web/static/style.css b/web/static/style.css new file mode 100644 index 0000000..f22e29c --- /dev/null +++ b/web/static/style.css @@ -0,0 +1,305 @@ +@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'); + +body { + color: #ddd; + background-color: #1a1a1a; + font-family: 'Lato', sans-serif; + user-select: none; + + margin: 0; + padding: 0; +} + +div#main { + height: calc(100vh - 2*2px); + width: calc(100vw - 2*2px); + margin: 2px; + + display: grid; + grid-template-columns: 1fr 2fr; + grid-template-rows: min-content 1fr; + grid-template-areas: "keypad settings-bar" "keypad match"; +} + +div#keypad { + grid-area: keypad; + + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-template-rows: min-content 2fr; + grid-auto-rows: 1fr; +} + +div#oi { + font-size: 4vh; + + display: flex; + align-items: center; + justify-content: center; +} + +div#user-rem, div#boat-rem { + background-color: #333; + font-size: 3vh; + + margin: 2px; + padding: 0.25em; + + display: flex; + align-items: center; + justify-content: left; +} + +div#boat-rem { + grid-column: 3; + + justify-content: right; +} + +div#user-rem.active, div#boat-rem.active { + background-color: #240; +} + +div#prompt-container { + grid-column: 1 / span 3; + + background-color: #333; + + margin: 2px; + + display: grid; + grid-template-rows: 1fr 3fr 1fr; + grid-template-columns: 1fr; +} + +div#prompt-msg, div#prompt, div#prompt-sugg { + font-size: 3vh; + + display: grid; + align-items: center; + justify-content: center; +} + +div#prompt { + font-size: 10vh; +} + +div#prompt-sugg { + color: #aaa; +} + +div.key { + color: #aaa; + background-color: #282828; + font-size: 3vh; + + margin: 2px; + + display: flex; + align-items: center; + justify-content: center; +} + +@media (hover: hover) and (pointer: fine) { + div.key:hover { + background-color: #240; + } +} + +div.key:active { + color: #fff; + background-color: #360; +} + +div.key.num { + font-size: 5vh; + font-weight: 700; +} + +div#settings-bar { + grid-area: settings-bar; + + background-color: #333; + font-size: 3vh; + + margin: 2px; + padding: 0.25em; + + display: flex; + align-items: center; + justify-content: left; + gap: 0.25em; +} + +div#settings-bar div.input.first { + margin-left: auto; +} + +div#settings-bar div.input { + background-color: #282828; + + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; +} + +div#settings-bar span { + padding: 0 0.25em; +} + +div#settings-bar input { + color: #ddd; + background-color: #304; + font-family: inherit; + font-size: 3vh; + text-align: right; + + width: 4ch; + padding: 0 0.25em; + border: 0; +} + +div#settings-bar div.help-button { + background-color: #304; + font-weight: bold; + + padding: 0 0.5em; +} + +@media (hover: hover) and (pointer: fine) { + div#settings-bar input:hover { + background-color: #608; + } + + div#settings-bar div.help-button:hover { + background-color: #608; + } +} + +div#settings-bar input#stdev:focus { + color: #fff; + background-color: #80a; +} + +div#settings-bar div.help-button:active { + color: #fff; + background-color: #80a; +} + +div#settings-bar input#stdev:focus { + outline: #5a0 solid 3px; +} + +div#match { + grid-area: match; + + background-color: #333; + font-size: 3vh; + + overflow-y: scroll; + margin: 2px; + padding: 0.25em; + + display: grid; + grid-template-columns: repeat(6, max-content); + grid-auto-rows: min-content; + grid-row-gap: 0.2em; + grid-column-gap: 1em; +} + +div#match div { + font-family: monospace; + white-space: pre; + + min-width: 3ch; + + display: flex; + align-items: center; + justify-content: center; +} + +div#match .user-name { color: #aaa; background-color: #444; grid-column: 2 / span 2; justify-content: center; } +div#match .boat-name { color: #aaa; background-color: #444; grid-column: 4 / span 2; justify-content: center; } +div#match .visit-no { color: #888; grid-column: 1; } +div#match .p0 { color: #f00; font-weight: bold; } +div#match .p1 { color: #f00; } +div#match .p20 { color: #f60; } +div#match .p40 { color: #fa0; } +div#match .p60 { color: #dd0; } +div#match .p100 { color: #2c2; } +div#match .p140 { color: #0f0; } +div#match .p180 { color: #0f0; font-weight: bold; } +div#match .darts { color: #888; justify-content: left; } + +.modal { + background-color: rgba(0, 0, 0, 0.6); + + width: 100%; + height: 100%; + + display: none; + position: fixed; + top: 0; + left: 0; + z-index: 1; +} + +.modal-content { + background-color: #333; + font-size: 1.4em; + font-family: 'Source Serif Pro', serif; + + width: calc(min(40em, 90vw) - 4em); + max-height: calc(calc(100vh - 10vw) - 4em); + margin: 5vw auto; + padding: 2em; + outline: #5a0 solid 3px; + overflow: scroll; +} + +@media (max-width: 400px) { + .modal-content { + font-size: 1em; + } +} + +.modal-content p { + margin: 0; +} + +.modal-content p + p { + margin-top: 1em; +} + +a { + color: #5a0; +} + +a:hover { + color: #7d0; +} + +@media (max-aspect-ratio: 1/1) { + div#main { + grid-template-columns: 1fr; + grid-template-rows: min-content 1fr 3fr; + grid-template-areas: "settings-bar" "match" "keypad"; + } +} + +@media (max-aspect-ratio: 3/5) { + div#settings-bar div.input { + display: flex; + flex-direction: column; + } + + div#settings-bar span { + padding: 0 0.25em; + } + + div#settings-bar .help-button { + font-size: 6vh; + } +} -- cgit v1.2.3-70-g09d2