#include "web_control.h" #include "web_dom.h" #include "web_match.h" #include "web_opts.h" #include "web_prompt.h" #include "web_scoreboard.h" #include "web_svg.h" #include "comp.h" #include #include #include #include #include static int curr_match_id = 0; void set_active_player(int pn) { state->active_player = pn; scoreboard_set_player_active(pn); if (state->mode == M_PVC && pn == 2) prompt_bot_visit(); else prompt_visit(); } void toggle_active_player() { set_active_player(3 - state->active_player); } void update_user_rem_from_pts(int pts) { update_player_rem(state->active_player, state_active_leg()->rem - pts); scoreboard_flush_player_info(state->active_player); } EMSCRIPTEN_KEEPALIVE void end_boat_visit(int rem, double avg, int match_id) { if (!state || match_id != curr_match_id) return; svg_clear_points(); update_player_rem(2, rem); scoreboard_set_player_avg(2, avg); prompt_set_input(NULL); prompt_set_msgr(NULL); draw_visits(); handle_next(); } EMSCRIPTEN_KEEPALIVE void draw_boat_throwing(int pts, char *str, double x, double y, int match_id) { if (!state || match_id != curr_match_id) return; char pts_str[10]; sprintf(pts_str, "%d", pts); int rem = state->legs[1]->n_visits > 1 ? state->legs[1]->visits[state->legs[1]->n_visits-2].rem : state->legs[1]->start; svg_draw_point(x, y); update_player_rem(2, rem - pts); prompt_set_input(pts_str); prompt_set_msgr(str); free(str); prompt_flush(); scoreboard_flush_player_info(2); } static void schedule_boat_visit_draws(struct leg *l, struct visit *v, double avg) { int pts = 0; char str[15] = {0}; size_t len_str = 0; for (int i = 0; i < v->n_darts; ++i) { pts += segment_points(v->darts[i]); char *seg_name = segment_name(v->darts[i]); len_str += sprintf(str + len_str, i ? "-%s" : "%s", seg_name); free(seg_name); struct ccoords c = v->ccoords[i]; int delay = delay_ms * (i + 1); if (delay_ms >= SVG_THROW_ANIM_MS) delay -= SVG_THROW_ANIM_MS; char *tmp = malloc(len_str + 1); // free in draw_boat_throwing memcpy(tmp, str, len_str + 1); EM_ASM({scheduleCCall($0, $1, $2, $3, $4, $5, $6)}, "draw_boat_throwing", delay, pts, tmp, c.x, c.y, curr_match_id); } EM_ASM({scheduleCCall($0, $1, $2, $3, $4)}, "end_boat_visit", delay_ms * (v->n_darts + 1), l->rem, avg, curr_match_id); } void boat_visit() { struct leg *l = state->legs[1]; if (state->boat_undone) { --state->boat_undone; l->rem = l->visits[l->n_visits++].rem; } else { comp_visit(l); } struct visit *v = l->visits + l->n_visits - 1; double avg = v->rem > 0 ? (double)(l->start - l->rem) / l->n_visits : (double)l->start / (((l->n_visits - 1) * 3) + v->n_darts) * 3; if (delay_ms) schedule_boat_visit_draws(l, v, avg); else end_boat_visit(l->rem, avg, curr_match_id); } void handle_next() { if (!state) { prompt_select_mode(); } else if (match_is_over()) { if (state->num_darts || (state->mode == M_PVC && state->legs[1]->rem <= 0)) prompt_end_match(); else prompt_num_darts(); } else { if (state->mode == M_P) { set_active_player(1); } else { if (state->active_player) toggle_active_player(); else set_active_player(match_opts->throws_first); if (state->mode == M_PVC && state->active_player == 2) boat_visit(); } } prompt_flush(); scoreboard_flush(); } void user_visit(int points) { if (!is_points_valid(points, state_active_leg()->rem)) { oi(); return; } struct leg *l = state_active_leg(); if (l->n_visits == l->size_visits) leg_grow_visits(l); struct visit *v = l->visits + l->n_visits++; v->points = points; l->rem -= points; v->rem = l->rem; update_player_rem(state->active_player, l->rem); if (v->rem > 0) update_player_avg(state->active_player, 0); draw_visits(); handle_next(); } void user_visit_to_rem(int rem) { user_visit(state->legs[0]->rem - rem); } static int num_players() { if (state->mode == M_P) return 1; return 2; } static int prev_throw_player() { if (match_is_over()) return state->active_player; if (state->active_player == 1) return num_players(); else return state->active_player - 1; } static bool player_is_comp(int pn) { return state->mode == M_PVC && pn == 2; } static void undo_active() { struct leg *l = state_active_leg(); if (player_is_comp(state->active_player)) { l->rem += l->visits[--l->n_visits].points; ++state->boat_undone; } else { struct visit *v = l->visits + --l->n_visits; l->rem += v->points; memset(v, 0, sizeof(*v)); } update_player_avg(state->active_player, 0); update_player_rem(state->active_player, l->rem); state->active_player = prev_throw_player(); } static bool first_user_has_thrown() { for (int i = 0, np = num_players(); i < np; ++i) { int pn = match_opts->throws_first + i; if (pn > np) pn -= np; if (!player_is_comp(pn)) return !!state->legs[pn - 1]->n_visits; } return false; } void user_undo() { if (!first_user_has_thrown()) { oi(); return; } if (state->num_darts) { state->num_darts = 0; struct leg *l = state_active_leg(); scoreboard_set_player_avg(state->active_player, ((double)(l->start - l->visits[l->n_visits-2].rem) / (l->n_visits - 1))); scoreboard_set_player_active(state->active_player); handle_next(); return; } state->active_player = prev_throw_player(); while (player_is_comp(state->active_player)) undo_active(); undo_active(); draw_visits(); handle_next(); } void user_num_darts(int n) { if (n < 1 || n > 3) { oi(); return; } state->num_darts = n; update_player_avg(state->active_player, n); handle_next(); } void match_mode_selected(int mode) { if (mode < M_FIRST || mode > M_LAST) { oi(); return; } if (match_opts) match_opts_free(); match_opts = calloc(1, sizeof(*match_opts)); match_opts->mode = mode; match_opts->start_pts = 501; match_opts->throws_first = 1; // names need to be freed if we stop using string literals if (mode == M_PVC) { match_opts->p1_name = "User"; match_opts->p2_name = "Computer"; } else if (mode == M_P) { match_opts->p1_name = "Player 1"; match_opts->p2_name = NULL; } else if (mode == M_PVP) { match_opts->p1_name = "Player 1"; match_opts->p2_name = "Player 2"; } prompt_match_opts(); prompt_flush(); } void start_match() { if (state) free_state(); // rematch gets us here ++curr_match_id; state = calloc(1, sizeof(*state)); state->mode = match_opts->mode; state->legs[0] = leg_init(match_opts->start_pts, match_opts->p1_name); state->legs[1] = leg_init(match_opts->start_pts, match_opts->p2_name ? match_opts->p2_name : "oi"); show_player_info(1); update_player_name(1, state->legs[0]->name); update_player_rem(1, state->legs[0]->rem); update_player_avg(1, 0); if (state->mode != M_P) { show_player_info(2); update_player_name(2, state->legs[1]->name); update_player_rem(2, state->legs[1]->rem); update_player_avg(2, 0); } dom_enable_exit_dialogue(true); dom_add_class("#key-exit", "visible"); draw_visits(); handle_next(); } void end_match() { // clean up in case match was ended early svg_clear_points(); prompt_set_input(NULL); scoreboard_set_player_active(-1); dom_remove_class("#key-exit", "visible"); dom_enable_exit_dialogue(false); if (state) free_state(); handle_next(); }