#include "web_control.h" #include "web_dom.h" #include "web_prompt.h" #include "web_match.h" #include "web_misc.h" #include "web_scoreboard.h" #include #include #include #include static int oi_timeout; enum prompt_mode { PM_DARTBOARD, PM_VISIT, PM_NUM_DARTS, PM_END_MATCH, PM_MENU }; enum prompt_mode pm; static struct { bufstr msgl, msgr, input; } bufs; enum menu { MENU_MAIN, MENU_MATCH_OPTS, MENU_CUSTOM_MATCH_OPTS, MENU_START_PTS, MENU_THROWS_FIRST }; static enum menu menu_stack[10]; static int menu_depth = 0; struct menu_opt { char *label; void (*func)(); int arg; bool takes_arg; }; static struct menu_opt menu_optv[20]; static int menu_optc; void oi() { dom_add_class("#oi", "visible"); oi_timeout = EM_ASM_INT({return scheduleCCall($0, $1)}, "clear_oi", 3000); } EMSCRIPTEN_KEEPALIVE void clear_oi() { if (!oi_timeout) return; dom_remove_class("#oi", "visible"); EM_ASM({clearTimeout($0)}, oi_timeout); oi_timeout = 0; } void prompt_set_msgl(char *str) { bufstr_buf(&bufs.msgl, str); } void prompt_set_msgr(char *str) { bufstr_buf(&bufs.msgr, str); } void prompt_set_input(char *str) { bufstr_buf(&bufs.input, str); } void prompt_flush() { if (bufstr_changed(&bufs.msgl)) dom_set_content("#prompt-msg-l", bufstr_flush(&bufs.msgl)); if (bufstr_changed(&bufs.msgr)) dom_set_content("#prompt-msg-r", bufstr_flush(&bufs.msgr)); if (bufstr_changed(&bufs.input)) dom_set_content("#prompt-input", bufstr_flush(&bufs.input)); } static void set_prompt_mode(enum prompt_mode mode) { if (pm == mode) return; pm = mode; if (pm != PM_DARTBOARD) dom_add_class("#prompt", "active"); else dom_remove_class("#prompt", "active"); dom_set_uniq_class( pm == PM_DARTBOARD ? "#keypad-dartboard" : pm == PM_MENU ? "#keypad-list" : "#keypad-default", "visible", ".keypad"); } static void add_menu_opt(char *label, void (*func)()) { struct menu_opt *m = &menu_optv[menu_optc++]; m->label = strdup(label); m->func = func; m->takes_arg = false; } static void add_menu_opti(char *label, void (*func)(int), int arg) { struct menu_opt *m = &menu_optv[menu_optc++]; m->label = strdup(label); m->func = func; m->arg = arg; m->takes_arg = true; } static void clear_menu_opts() { for (int i = 0; i < menu_optc; ++i) free(menu_optv[i].label); menu_optc = 0; } static void flush_menu_opts() { struct dom_elem **elemv = malloc(menu_optc * sizeof(*elemv)); for (int i = 0; i < menu_optc; ++i) { struct dom_elem *e = dom_elem_init(NULL, "div", 2); dom_elem_add_attr(e, "class", "key"); char buf[64]; sprintf(buf, "append:%d;submit", i + 1); dom_elem_add_attr(e, "data-command", buf); struct dom_elem *child; child = dom_elem_init(NULL, "span", 1); asprintf(&child->content, "[%d]", i + 1); dom_elem_add_attr(child, "class", "keyboard-val"); dom_elem_add_child(e, child); sprintf(buf, "\u2002%s", menu_optv[i].label); child = dom_text_init(buf); dom_elem_add_child(e, child); elemv[i] = e; } dom_set_content("#keypad-list", NULL); dom_append_elemv("#keypad-list", menu_optc, elemv); for (int i = 0; i < menu_optc; ++i) dom_elem_free(elemv[i]); free(elemv); } static void menu_push(int m); static void menu_pop(); static void menu_display(); enum match_mode { MM_PVC, MM_1P, MM_2P, MM_CUST }; static int custom_users_added, custom_comps_added; static void select_mode(int mm) { match_opts_new(); switch ((enum match_mode)mm) { case MM_PVC: match_opts_add_player(PT_USER, "Player"); match_opts_add_player(PT_COMP, "Computer"); break; case MM_1P: match_opts_add_player(PT_USER, "Player"); break; case MM_2P: match_opts_add_player(PT_USER, "Player 1"); match_opts_add_player(PT_USER, "Player 2"); break; case MM_CUST: custom_users_added = custom_comps_added = 0; break; } menu_push(mm == MM_CUST ? MENU_CUSTOM_MATCH_OPTS : MENU_MATCH_OPTS); } static void menu_display_main() { prompt_set_msgl("Select match mode:"); add_menu_opti("Play against computer", select_mode, MM_PVC); add_menu_opti("One-player scoreboard", select_mode, MM_1P); add_menu_opti("Two-player scoreboard", select_mode, MM_2P); add_menu_opti("Custom", select_mode, MM_CUST); } static void menu_display_match_opts() { prompt_set_msgl("Match options:"); add_menu_opt("Start match", start_match); char buf[64]; sprintf(buf, "Starting points: %d", match_opts->start_pts); add_menu_opti(buf, menu_push, MENU_START_PTS); sprintf(buf, "Throws first: %s", match_opts->players[match_opts->throws_first - 1].name); add_menu_opti(buf, menu_push, MENU_THROWS_FIRST); add_menu_opt("Back", menu_pop); } static void custom_start_match() { if (match_opts->throws_first) start_match(); else oi(); } static void custom_add_player(int type) { if (match_opts->num_players >= 10) { oi(); return; } char buf[64]; sprintf(buf, type == PT_USER ? "Player %d" : "Computer %d", type == PT_USER ? ++custom_users_added : ++custom_comps_added); match_opts_add_player(type, buf); menu_display(); } static void custom_remove_player(int pn) { match_opts_remove_player(pn); menu_display(); } static void menu_display_custom_match_opts() { prompt_set_msgl("Match options:"); add_menu_opt("Start match", custom_start_match); char buf[64]; sprintf(buf, "Starting points: %d", match_opts->start_pts); add_menu_opti(buf, menu_push, MENU_START_PTS); sprintf(buf, "Throws first: %s", match_opts->throws_first ? match_opts->players[ match_opts->throws_first - 1].name : "(no players added)"); add_menu_opti(buf, menu_push, MENU_THROWS_FIRST); add_menu_opti("Add user player", custom_add_player, PT_USER); add_menu_opti("Add computer player", custom_add_player, PT_COMP); for (int i = 0; i < match_opts->num_players; ++i) { sprintf(buf, "Remove player: %s", match_opts->players[i].name); add_menu_opti(buf, custom_remove_player, i + 1); } add_menu_opt("Back", menu_pop); } static void set_start_pts(int pts) { match_opts->start_pts = pts; menu_pop(); } static void menu_display_start_pts() { prompt_set_msgl("Starting points:"); add_menu_opti("301", set_start_pts, 301); add_menu_opti("501", set_start_pts, 501); add_menu_opti("701", set_start_pts, 701); add_menu_opti("901", set_start_pts, 901); add_menu_opt("Back", menu_pop); } static void set_throws_first(int pn) { match_opts->throws_first = pn; menu_pop(); } static void menu_display_throws_first() { prompt_set_msgl("Throws first:"); for (int i = 0; i < match_opts->num_players; ++i) add_menu_opti(match_opts->players[i].name, set_throws_first, i + 1); add_menu_opt("Back", menu_pop); } static void menu_display() { set_prompt_mode(PM_MENU); prompt_set_msgl(NULL); prompt_set_msgr(NULL); clear_menu_opts(); switch (menu_stack[menu_depth]) { case MENU_MAIN: menu_display_main(); break; case MENU_MATCH_OPTS: menu_display_match_opts(); break; case MENU_CUSTOM_MATCH_OPTS: menu_display_custom_match_opts(); break; case MENU_START_PTS: menu_display_start_pts(); break; case MENU_THROWS_FIRST: menu_display_throws_first(); break; } flush_menu_opts(); prompt_flush(); } static void menu_pop() { --menu_depth; menu_display(); } static void menu_push(int m) { menu_stack[++menu_depth] = m; menu_display(); } static void menu_submit(int opt) { if (opt < 1 || opt > menu_optc) { oi(); return; } struct menu_opt *m = &menu_optv[opt - 1]; if (m->takes_arg) ((void (*)(int))m->func)(m->arg); else m->func(); } void prompt_main_menu() { menu_depth = 0; menu_stack[0] = MENU_MAIN; menu_display(); scoreboard_hide_info(); dom_set_content("#visits", NULL); } void prompt_user_visit() { set_prompt_mode(PM_VISIT); prompt_set_msgl("Enter points:"); prompt_set_msgr(NULL); dom_set_content("#key-submit", "OK"); dom_set_content("#key-rem", "REMAINING"); } void prompt_comp_visit() { set_prompt_mode(PM_DARTBOARD); char buf[64]; sprintf(buf, "%s is throwing…", state_active_leg()->name); prompt_set_msgl(buf); prompt_set_msgr(NULL); } void prompt_num_darts() { set_prompt_mode(PM_NUM_DARTS); prompt_set_msgl("Darts needed?"); prompt_set_msgr(NULL); dom_set_content("#key-submit", "OK"); dom_set_content("#key-rem", "REMAINING"); } void prompt_end_match() { set_prompt_mode(PM_END_MATCH); char buf[64]; sprintf(buf, "%s wins.", state->legs[match_winning_player() - 1]->name); prompt_set_msgl(buf); prompt_set_msgr(NULL); dom_set_content("#key-submit", "END MATCH"); dom_set_content("#key-rem", "REMATCH"); scoreboard_set_player_active(0); } static bool key_is_active(char *k) { char sel[64]; sprintf(sel, "#key-%s", k); return dom_has_class(sel, "active"); } static void toggle_key(char *k) { char sel[64]; sprintf(sel, "#key-%s", k); dom_toggle_class(sel, "active"); } static void deactivate_key(char *k) { char sel[64]; sprintf(sel, "#key-%s", k); dom_remove_class(sel, "active"); } static char *prompt_get() { return dom_get_content("#prompt-input"); } static void prompt_handle_pre(char *command) { if (pm == PM_DARTBOARD) return; clear_oi(); if ((pm == PM_VISIT || pm == PM_NUM_DARTS || pm == PM_END_MATCH) && strcmp(command, "undo")) deactivate_key("undo"); if (pm == PM_END_MATCH && strcmp(command, "rem")) deactivate_key("rem"); if (pm == PM_END_MATCH && strcmp(command, "submit")) deactivate_key("submit"); if (strcmp(command, "exit")) deactivate_key("exit"); } static void prompt_handle_on_change() { prompt_flush(); if (pm != PM_VISIT) return; char *str = prompt_get(); update_user_rem_from_pts(atoi(str)); free(str); } static void prompt_handle_append(char *data) { if (pm != PM_MENU && pm != PM_VISIT && pm != PM_NUM_DARTS) return; char *str = prompt_get(); size_t len_str = strlen(str); size_t len_data = strlen(data); if (len_str < 3) { str = realloc(str, len_str + len_data + 1); memcpy(str + len_str, data, len_data + 1); prompt_set_input(str); prompt_handle_on_change(); } free(str); } static void prompt_handle_backspace() { if (pm == PM_DARTBOARD) return; char *str = prompt_get(); size_t len_str = strlen(str); if (len_str > 0) { str[len_str-1] = 0; prompt_set_input(str); prompt_handle_on_change(); } free(str); } static void prompt_handle_clear() { if (pm == PM_DARTBOARD) return; prompt_set_input(NULL); prompt_handle_on_change(); } static void prompt_handle_submit() { if (pm == PM_END_MATCH) { if (key_is_active("submit")) end_match(); toggle_key("submit"); return; } if (pm != PM_VISIT && pm != PM_NUM_DARTS && pm != PM_MENU) return; char *str = prompt_get(); prompt_handle_clear(); if (*str) { if (pm == PM_VISIT) user_visit(atoi(str)); else if (pm == PM_NUM_DARTS) user_num_darts(atoi(str)); else if (pm == PM_MENU) menu_submit(atoi(str)); } free(str); } static void prompt_handle_rem() { if (pm == PM_END_MATCH) { if (key_is_active("rem")) start_match(); toggle_key("rem"); return; } if (pm != PM_VISIT) return; char *str = prompt_get(); prompt_handle_clear(); if (*str) user_visit_to_rem(atoi(str)); free(str); } static void prompt_handle_undo() { if (pm != PM_VISIT && pm != PM_NUM_DARTS && pm != PM_END_MATCH) return; prompt_handle_clear(); if (key_is_active("undo")) user_undo(); toggle_key("undo"); } static void prompt_handle_exit() { if (key_is_active("exit")) end_match(); toggle_key("exit"); } EMSCRIPTEN_KEEPALIVE void prompt_handle(char *command, char *data) { prompt_handle_pre(command); if (!strcmp(command, "append")) prompt_handle_append(data); else if (!strcmp(command, "backspace")) prompt_handle_backspace(); else if (!strcmp(command, "clear")) prompt_handle_clear(); else if (!strcmp(command, "submit")) prompt_handle_submit(); else if (!strcmp(command, "rem")) prompt_handle_rem(); else if (!strcmp(command, "undo")) prompt_handle_undo(); else if (!strcmp(command, "exit")) prompt_handle_exit(); }