summaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
authorDavid Vazgenovich Shakaryan <dvshakaryan@gmail.com>2022-05-20 15:03:44 -0700
committerDavid Vazgenovich Shakaryan <dvshakaryan@gmail.com>2022-05-20 15:03:44 -0700
commit6fb1750b923b97a377d6ef8ad4a25b501e65b39d (patch)
tree07fd49ffd6ea14b528725c99f511136a1ea54fb3 /web
parent7433d87aef18955468027a79edd9bb50c4db8275 (diff)
downloaddartboat-6fb1750b923b97a377d6ef8ad4a25b501e65b39d.tar.gz
dartboat-6fb1750b923b97a377d6ef8ad4a25b501e65b39d.tar.xz
web: support visit log for 3+ player matches
The new match modes are for testing. They'll eventually be removed in favour of true configurability.
Diffstat (limited to 'web')
-rw-r--r--web/static/dartboat.js4
-rw-r--r--web/static/style.css83
-rw-r--r--web/web_control.c8
-rw-r--r--web/web_dom.c5
-rw-r--r--web/web_dom.h2
-rw-r--r--web/web_match.h3
-rw-r--r--web/web_prompt.c1
-rw-r--r--web/web_scoreboard.c105
8 files changed, 177 insertions, 34 deletions
diff --git a/web/static/dartboat.js b/web/static/dartboat.js
index 5c7f011..ecbee74 100644
--- a/web/static/dartboat.js
+++ b/web/static/dartboat.js
@@ -50,6 +50,10 @@ function elemSetUniqClass(sel, c, sel_set) {
sel && e.matches(selstr) ? 'add' : 'remove'](cstr));
}
+function rootSetStylePropertyInt(prop, val) {
+ document.documentElement.style.setProperty(UTF8ToString(prop), val);
+}
+
function elemScrollToBottom(sel) {
const e = $(UTF8ToString(sel));
e.scrollTop = e.scrollHeight;
diff --git a/web/static/style.css b/web/static/style.css
index 49daa96..f0209f8 100644
--- a/web/static/style.css
+++ b/web/static/style.css
@@ -46,6 +46,10 @@
src: url('fonts/Inter-num.woff2') format('woff2');
}
+:root {
+ --num-players: 0;
+}
+
html {
font-size: clamp(1.5vh, 2.5vw, 2vh);
@@ -601,15 +605,16 @@ body {
justify-content: center;
}
-#visits .visit-p1-name { grid-column: 2 / span 2; }
-#visits .visit-p2-name { grid-column: 5 / span 2; }
-#visits .visit-p1-darts { grid-column: 1; }
-#visits .visit-p1-pts { grid-column: 2; }
-#visits .visit-p1-rem { grid-column: 3; }
-#visits .visit-n { grid-column: 4; }
-#visits .visit-p2-rem { grid-column: 5; }
-#visits .visit-p2-pts { grid-column: 6; }
-#visits .visit-p2-darts { grid-column: 7; }
+#visits .visit-name { grid-column: span 2; }
+#visits:not(.dense) .visit-p1-name { grid-column: 2 / span 2; }
+#visits:not(.dense) .visit-p2-name { grid-column: 5 / span 2; }
+#visits:not(.dense) .visit-p1-darts { grid-column: 1; }
+#visits:not(.dense) .visit-p1-pts { grid-column: 2; }
+#visits:not(.dense) .visit-p1-rem { grid-column: 3; }
+#visits:not(.dense) .visit-n { grid-column: 4; }
+#visits:not(.dense) .visit-p2-rem { grid-column: 5; }
+#visits:not(.dense) .visit-p2-pts { grid-column: 6; }
+#visits:not(.dense) .visit-p2-darts { grid-column: 7; }
#visits .p0 { color: #e00018; }
#visits .p20 { color: #e06000; }
@@ -619,35 +624,45 @@ body {
#visits .p140 { color: #20e018; }
#visits .p180 { color: #20e018; font-weight: bold; }
#visits .throws-first { color: #999; }
-#visits .throws-first:before { content: '\f151'; font-size: 0.6em; }
+#visits .throws-first:before { content: '\f151'; font-size: 0.6em;
+ font-family: 'bootstrap-icons-sub';
+ vertical-align: -0.125em;
+}
-#visits .visit-p1-name, #visits .visit-p2-name {
+#visits .visit-name {
+ margin: 0 0.2rem;
+}
+#visits .visit-name, #visits .visit-p1-name, #visits .visit-p2-name {
color: #555;
border-bottom: 2px solid #555;
}
#visits .visit-n {
color: #555;
}
-#visits .visit-p1-darts, #visits .visit-p2-darts {
+#visits .visit-dense-darts, .visit-p1-darts, #visits .visit-p2-darts {
color: #555;
font-size: 0.75em;
white-space: nowrap;
}
-#visits .visit-p1-darts {
+#visits .visit-dense-darts {
+ margin-top: calc(1.5 * -0.2em / 0.75);
+ grid-column: span 2;
+}
+#visits:not(.dense) .visit-p1-darts {
justify-content: right;
}
-#visits .visit-p1-darts:not(:empty):after {
+#visits:not(.dense) .visit-p1-darts:not(:empty):after {
content: '\2003';
}
-#visits .visit-p2-darts {
+#visits:not(.dense) .visit-p2-darts {
justify-content: left;
}
-#visits .visit-p2-darts:not(:empty):before {
+#visits:not(.dense) .visit-p2-darts:not(:empty):before {
content: '\2003';
}
@media (max-aspect-ratio: 8/5) {
- #visits {
+ #visits:not(.dense) {
grid-template-columns:
min-content
calc(calc(100% - 27ch) / 2)
@@ -655,27 +670,35 @@ body {
calc(calc(100% - 27ch) / 2)
min-content;
}
- #visits .visit-p1-name, #visits .visit-p2-name {
+ #visits:not(.dense) .visit-p1-name, #visits .visit-p2-name {
display: none;
}
- #visits .visit-p1-spacer { grid-column: 1; width: 1ch }
- #visits .visit-p2-spacer { grid-column: 9; width: 1ch }
- #visits .visit-p1-darts { grid-column: 1; }
- #visits .visit-p1-pts { grid-column: 3; }
- #visits .visit-p1-rem { grid-column: 4; }
- #visits .visit-n { grid-column: 5; }
- #visits .visit-p2-rem { grid-column: 6; }
- #visits .visit-p2-pts { grid-column: 7; }
- #visits .visit-p2-darts { grid-column: 9; }
-
- #visits .visit-p1-darts:not(:empty):after {
+ #visits:not(.dense) .visit-p1-spacer { grid-column: 1; width: 1ch }
+ #visits:not(.dense) .visit-p2-spacer { grid-column: 9; width: 1ch }
+ #visits:not(.dense) .visit-p1-darts { grid-column: 1; }
+ #visits:not(.dense) .visit-p1-pts { grid-column: 3; }
+ #visits:not(.dense) .visit-p1-rem { grid-column: 4; }
+ #visits:not(.dense) .visit-n { grid-column: 5; }
+ #visits:not(.dense) .visit-p2-rem { grid-column: 6; }
+ #visits:not(.dense) .visit-p2-pts { grid-column: 7; }
+ #visits:not(.dense) .visit-p2-darts { grid-column: 9; }
+
+ #visits:not(.dense) .visit-p1-darts:not(:empty):after {
content: '\2003…';
}
- #visits .visit-p2-darts:not(:empty):before {
+ #visits:not(.dense) .visit-p2-darts:not(:empty):before {
content: '…\2003';
}
}
+#visits.dense {
+ grid-template-columns: min-content repeat(var(--num-players), min-content min-content);
+}
+
+#visits.dense > * {
+ min-width: 5ch;
+}
+
/* modals */
.modal {
diff --git a/web/web_control.c b/web/web_control.c
index b0a777c..ca45ed4 100644
--- a/web/web_control.c
+++ b/web/web_control.c
@@ -263,6 +263,10 @@ void match_mode_selected(int mode)
} else if (mode == M_PVPVP) {
match_opts->p1_name = "Player 1";
match_opts->p2_name = "Player 2";
+ } else if (mode == M_PVCVCVC) {
+ match_opts->p1_name = "User";
+ match_opts->p2_type = PT_COMP;
+ match_opts->p2_name = "Computer 1";
}
prompt_match_opts();
@@ -278,6 +282,10 @@ void start_match()
match_opts->p2_name ? match_opts->p2_name : "oi");
if (match_opts->mode == M_PVPVP)
match_add_player(match_opts->start_pts, PT_USER, "Player 3");
+ else if (match_opts->mode == M_PVCVCVC) {
+ match_add_player(match_opts->start_pts, PT_COMP, "Computer 2");
+ match_add_player(match_opts->start_pts, PT_COMP, "Computer 3");
+ }
scoreboard_show_info(match_num_players());
for (int i = 1; i <= match_num_players(); ++i) {
diff --git a/web/web_dom.c b/web/web_dom.c
index 162684a..54a6c8d 100644
--- a/web/web_dom.c
+++ b/web/web_dom.c
@@ -144,6 +144,11 @@ inline void dom_set_uniq_class(char *sel, char *class, char *sel_set)
EM_ASM({elemSetUniqClass($0, $1, $2)}, sel, class, sel_set);
}
+inline void dom_set_style_property_int(char *prop, int val)
+{
+ EM_ASM({rootSetStylePropertyInt($0, $1)}, prop, val);
+}
+
inline void dom_scroll_to_bottom(char *sel)
{
EM_ASM({elemScrollToBottom($0)}, sel);
diff --git a/web/web_dom.h b/web/web_dom.h
index 8813d1a..45afd15 100644
--- a/web/web_dom.h
+++ b/web/web_dom.h
@@ -30,6 +30,8 @@ void dom_remove_class(char *sel, char *class);
void dom_toggle_class(char *sel, char *class);
void dom_set_uniq_class(char *sel, char *class, char *sel_set);
+void dom_set_style_property_int(char *prop, int val);
+
void dom_scroll_to_bottom(char *sel);
void dom_scroll_to_center_child(char *sel, char *sel_child);
diff --git a/web/web_match.h b/web/web_match.h
index 0654eb7..fc2dabe 100644
--- a/web/web_match.h
+++ b/web/web_match.h
@@ -10,7 +10,8 @@ enum match_mode {
M_PVP,
M_CVC,
M_PVPVP,
- M_LAST = M_PVPVP
+ M_PVCVCVC,
+ M_LAST = M_PVCVCVC
};
enum player_type {
diff --git a/web/web_prompt.c b/web/web_prompt.c
index f7bfe8d..f65259e 100644
--- a/web/web_prompt.c
+++ b/web/web_prompt.c
@@ -198,6 +198,7 @@ void prompt_select_mode()
add_list_opt("Two-player scoreboard");
add_list_opt("Computer vs computer");
add_list_opt("Three-player scoreboard");
+ add_list_opt("Play against three computers");
flush_list_opts();
}
diff --git a/web/web_scoreboard.c b/web/web_scoreboard.c
index 3543d0f..f298dfe 100644
--- a/web/web_scoreboard.c
+++ b/web/web_scoreboard.c
@@ -279,9 +279,108 @@ static void buf_darts(char *buf, size_t size, struct visit *v)
}
}
+void draw_visits_dense()
+{
+ dom_set_content("#visits", NULL);
+ dom_add_class("#visits", "dense");
+
+ int np = match_num_players();
+ dom_set_style_property_int("--num-players", np);
+
+ // FIXME
+ int n_visits = state->legs[0]->n_visits > state->legs[1]->n_visits ?
+ state->legs[0]->n_visits : state->legs[1]->n_visits;
+
+ struct dom_elem **elemv = malloc(
+ (np * 2 + 1) * (2 * n_visits + 2) * sizeof(*elemv));
+ int elemc = 0;
+
+ elemv[elemc++] = create_div("", "");
+ for (int i = 0; i < np; ++i) {
+ elemv[elemc++] = create_div(state->legs[i]->name,
+ "visit-name");
+ }
+
+ char buf[32], buf2[32];
+ elemv[elemc++] = create_div("0", "visit-n");
+ for (int i = 0; i < np; ++i) {
+ if (i + 1 == match_opts->throws_first)
+ elemv[elemc++] = create_div("", "throws-first");
+ else
+ elemv[elemc++] = create_div("", "");
+ snprintf(buf, sizeof(buf), "%d", state->legs[i]->start);
+ elemv[elemc++] = create_div(buf, "");
+ }
+
+ for (int i = 0; i < n_visits; ++i) {
+ snprintf(buf, sizeof(buf), "%d", i + 1);
+ elemv[elemc++] = create_div(buf, "visit-n");
+
+ for (int j = 0; j < np; ++j) {
+ if (i >= state->legs[j]->n_visits) {
+ elemv[elemc++] = create_div("", "");
+ elemv[elemc++] = create_div("", "");
+ continue;
+ }
+
+ struct visit *v = state->legs[j]->visits + i;
+
+ snprintf(buf, sizeof(buf), "%d", v->points);
+ snprintf(buf2, sizeof(buf2), "%s",
+ points_class(v->points));
+ elemv[elemc++] = create_div(buf, buf2);
+
+ snprintf(buf, sizeof(buf), "%d", v->rem);
+ elemv[elemc++] = create_div(buf, "");
+ }
+
+ bool n_darts = false;
+ for (int j = 0; j < np; ++j) {
+ if (i < state->legs[j]->n_visits &&
+ state->legs[j]->visits[i].n_darts)
+ n_darts = true;
+ }
+ if (!n_darts) continue;
+
+ elemv[elemc++] = create_div("", "");
+ for (int j = 0; j < np; ++j) {
+ if (i >= state->legs[j]->n_visits) {
+ elemv[elemc++] = create_div("",
+ "visit-dense-darts");
+ continue;
+ }
+
+ struct visit *v = state->legs[j]->visits + i;
+
+ if (v->n_darts) {
+ buf_darts(buf, sizeof(buf), v);
+ elemv[elemc++] = create_div(buf,
+ "visit-dense-darts");
+ } else {
+ elemv[elemc++] = create_div("",
+ "visit-dense-darts");
+ }
+ }
+ }
+
+ dom_append_elemv("#visits", elemc, elemv);
+ for (int i = 0; i < elemc; ++i)
+ dom_elem_free(elemv[i]);
+ free(elemv);
+
+ dom_scroll_to_bottom("#visits");
+ dom_scroll_to_center_child("#visits", ".visit-n");
+}
+
void draw_visits()
{
+ if (match_num_players() > 2) {
+ draw_visits_dense();
+ return;
+ }
+
dom_set_content("#visits", NULL);
+ dom_remove_class("#visits", "dense");
int n_visits = state->legs[0]->n_visits > state->legs[1]->n_visits ?
state->legs[0]->n_visits : state->legs[1]->n_visits;
@@ -300,7 +399,7 @@ void draw_visits()
snprintf(buf, sizeof(buf), "%d", state->legs[0]->start);
if (match_opts->throws_first == 1)
elemv[elemc++] = create_div("", // content added via CSS
- "visit-p1-pts throws-first icon");
+ "visit-p1-pts throws-first");
elemv[elemc++] = create_div(buf, "visit-p1-rem");
elemv[elemc++] = create_div("0", "visit-n");
if (match_num_players() != 1) {
@@ -308,7 +407,7 @@ void draw_visits()
elemv[elemc++] = create_div(buf, "visit-p2-rem");
if (match_opts->throws_first == 2)
elemv[elemc++] = create_div("",
- "visit-p2-pts throws-first icon");
+ "visit-p2-pts throws-first");
}
for (int i = 0; i < n_visits; ++i) {
@@ -331,10 +430,10 @@ void draw_visits()
snprintf(buf, sizeof(buf), "%d", v->rem);
elemv[elemc++] = create_div(buf, "visit-p1-rem");
+p2:
snprintf(buf, sizeof(buf), "%d", i + 1);
elemv[elemc++] = create_div(buf, "visit-n");
-p2:
if (i >= state->legs[1]->n_visits)
continue;
v = state->legs[1]->visits + i;