#include "checkouts.h" #include "comp.h" #include #include #include #include #include #define M_PVC 1 #define M_P 2 #define M_PVP 3 int delay_ms = 500; // TODO refactor *everything* struct match_state { int mode; struct leg *l1, *l2, *active_l; int active_p; int undone_count; int complete; }; struct match_state *state; void switch_active_user() { state->active_p = 3 - state->active_p; if (state->active_p == 1) state->active_l = state->l1; else state->active_l = state->l2; EM_ASM({setPlayerActive($0)}, state->active_p); } void set_user_active() { EM_ASM({setPlayerActive($0)}, 1); EM_ASM({setPromptHandler($0)}, "visit"); } void set_boat_active() { EM_ASM({setPlayerActive($0)}, 2); EM_ASM({setPromptHandler()}, ""); } void suggested(int rem, char *buf) { buf[0] = 0; if (rem > 1 && rem <= 170) { char *target = CHECKOUTS[2][rem-1]; if (target) { int trem = rem - segment_points(segment_from_name(target)); int len = sprintf(buf, "%s", target); if (trem) { target = CHECKOUTS[1][trem-1]; len += sprintf(buf + len, "-%s", target); trem = trem - segment_points(segment_from_name(target)); if (trem) { target = CHECKOUTS[0][trem-1]; len += sprintf(buf + len, "-%s", target); } } } } } EMSCRIPTEN_KEEPALIVE void get_suggested(int rem, int player) { static char buf[20]; suggested(rem, buf); EM_ASM({updatePlayerSugg($0, $1)}, player, buf); } EMSCRIPTEN_KEEPALIVE int is_match_over() { if (state->l1->rem <= 0 || state->l2->rem <= 0) return 1; return 0; } void match_over() { EM_ASM({setPromptHandler($0)}, "match_over"); EM_ASM({promptMsgR($0)}, "Press OK to end match."); EM_ASM(setPlayerActive()); } EMSCRIPTEN_KEEPALIVE void draw_match() { struct leg *l1 = state->l1; struct leg *l2 = state->l2; char visit_no[10], u_pts[10], u_rem[10], b_pts[10], b_rem[10], b_darts[100]; sprintf(u_rem, "%d", l1->start); sprintf(b_rem, "%d", l2->start); EM_ASM({drawVisit($0, $1, $2, $3, $4, $5)}, "0", "", u_rem, "", state->mode == M_P ? "" : b_rem, ""); int n_visits = l1->n_visits > l2->n_visits ? l1->n_visits : l2->n_visits; for (int i = 0; i < n_visits; ++i) { visit_no[0] = u_pts[0] = u_rem[0] = b_pts[0] = b_rem[0] = b_darts[0] = 0; sprintf(visit_no, "%d", i + 1); struct visit *v = l1->visits + i; sprintf(u_pts, "%d", v->points); sprintf(u_rem, "%d", v->rem); if (i < l2->n_visits) { v = l2->visits + i; sprintf(b_pts, "%d", v->points); sprintf(b_rem, "%d", v->rem); for (int j = 0; j < v->n_darts; ++j) { char *n = segment_name(v->darts[j]); sprintf(b_darts + (j ? (j * 5 - 1) : 0), j == 0 ? "%4s" : " %4s", n); free(n); } } EM_ASM({drawVisit($0, $1, $2, $3, $4, $5)}, visit_no, u_pts, u_rem, b_pts, b_rem, b_darts); } if (l1->rem <= 0) { EM_ASM({promptMsgL($0)}, "Darts needed?"); EM_ASM({setPromptHandler($0)}, "num_darts"); } else if (l2->rem <= 0) { if (state->mode == M_PVC) { EM_ASM({promptMsgL($0)}, "Bot wins. :("); match_over(); } else { EM_ASM({promptMsgL($0)}, "Darts needed?"); EM_ASM({setPromptHandler($0)}, "num_darts"); } } else { EM_ASM({promptMsgL($0)}, "Enter points:"); } } char *str_for_rem_state(int rem) { char *buf = malloc(10); if (rem < 0 || rem == 1) strcpy(buf, "BUST"); else sprintf(buf, "%d", rem); return buf; } EMSCRIPTEN_KEEPALIVE void update_user_rem(int rem) { char *str = str_for_rem_state(rem); EM_ASM({updatePlayerRem($0, $1)}, state->active_p, str); free(str); get_suggested(rem, state->active_p); } EMSCRIPTEN_KEEPALIVE void update_user_rem_from_pts(int pts) { update_user_rem(state->active_l->rem - pts); } EMSCRIPTEN_KEEPALIVE void update_boat_rem(int rem) { char *str = str_for_rem_state(rem); EM_ASM({updatePlayerRem($0, $1)}, 2, str); free(str); //get_suggested(rem, 2); } void end_boat_throwing(int, double); // FIXME EMSCRIPTEN_KEEPALIVE void boat_visit() { if (state->l1->rem <= 0 || state->l2->rem <= 0) return; EM_ASM({promptMsgL($0)}, "Bot is throwing…"); struct leg *l = state->l2; if (state->undone_count) { --state->undone_count; l->rem = l->visits[l->n_visits++].rem; } else { comp_visit(l); } struct visit *v = l->visits + l->n_visits - 1; double avg; if (v->rem > 0) avg = (double)(l->start - l->rem) / l->n_visits; else avg = ((double)l->start / (((l->n_visits - 1) * 3) + v->n_darts)) * 3; if (!delay_ms) { end_boat_throwing(l->rem, avg); return; } int pts = 0; char str[20] = {0}; for (int i = 0; i < v->n_darts; ++i) { pts += segment_points(v->darts[i]); char *seg_name = segment_name(v->darts[i]); if (i) strcat(str, "-"); strcat(str, seg_name); free(seg_name); EM_ASM({scheduleCCall($0, $1, $2, $3, $4, $5)}, "draw_boat_throwing", delay_ms * (i+1), "number", pts, "string", str); } EM_ASM({scheduleCCall($0, $1, $2, $3, $4, $5)}, "end_boat_throwing", delay_ms * (v->n_darts + 1), "number", l->rem, "number", avg); } EMSCRIPTEN_KEEPALIVE bool user_visit(int points) { if (state->l1->rem <= 0 || state->l2->rem <= 0 || points > state->active_l->rem || state->active_l->rem - points == 1 || points > 180 || points == 179 || points == 178 || points == 176 || points == 175 || points == 173 || points == 172 || points == 169 || points == 166 || points == 163 || (state->active_l->rem - points == 0 && (points == 168 || points == 165 || points == 162 || points == 159))) { EM_ASM(oi()); return false; } struct leg *l = state->active_l; struct visit *v = l->visits + l->n_visits++; v->points = points; l->rem -= points; v->rem = l->rem; update_user_rem(l->rem); if (v->rem > 0) EM_ASM({updatePlayerAvg($0, $1)}, state->active_p, (double)(l->start-l->rem)/l->n_visits); EM_ASM({clearVisits()}); draw_match(); if (!is_match_over()) { if (state->mode == M_PVC) { set_boat_active(); boat_visit(); } else if (state->mode == M_PVP) { switch_active_user(); } } return true; } // FIXME neg EMSCRIPTEN_KEEPALIVE void user_visit_to_rem(int rem) { user_visit(state->l1->rem - rem); } EMSCRIPTEN_KEEPALIVE bool user_undo() { if (!state->l1->n_visits) { EM_ASM(oi()); return false; } if (state->mode == M_PVP) { if (is_match_over()) EM_ASM({setPlayerActive($0)}, state->active_p); else switch_active_user(); } if (state->mode == M_PVC) EM_ASM({setPlayerActive($0)}, state->active_p); struct leg *l = state->active_l; struct visit *v = l->visits + --l->n_visits; l->rem += v->points; if (state->mode == M_PVC && state->l2->n_visits > l->n_visits) { state->l2->rem += state->l2->visits[--state->l2->n_visits].points; ++state->undone_count; } EM_ASM({updatePlayerAvg($0, $1)}, 1, l->n_visits ? (double)(l->start-l->rem)/l->n_visits : 0); EM_ASM({updatePlayerAvg($0, $1)}, 2, state->l2->n_visits ? (double)(state->l2->start-state->l2->rem)/state->l2->n_visits : 0); update_user_rem(l->rem); if (state->mode == M_PVC) update_boat_rem(state->l2->rem); memcpy(v, 0, sizeof(*v)); EM_ASM({clearVisits()}); EM_ASM({promptMsgR($0)}, ""); EM_ASM({setPromptHandler($0)}, "visit"); draw_match(); return true; } EMSCRIPTEN_KEEPALIVE void draw_boat_throwing(int pts, char *str) { char buf[10]; sprintf(buf, "%d", pts); int rem = state->l2->n_visits > 1 ? state->l2->visits[state->l2->n_visits-2].rem : state->l2->start; update_boat_rem(rem - pts); EM_ASM({setPromptInput($0)}, buf); EM_ASM({promptMsgR($0)}, str); } EMSCRIPTEN_KEEPALIVE void end_boat_throwing(int rem, double avg) { update_boat_rem(rem); EM_ASM({updatePlayerAvg($0, $1)}, 2, avg); EM_ASM({setPromptInput($0)}, ""); EM_ASM(clearVisits()); EM_ASM({promptMsgR($0)}, ""); draw_match(); if (!is_match_over()) set_user_active(); } EMSCRIPTEN_KEEPALIVE void resp_numdarts(int n_darts) { if (n_darts < 1 || n_darts > 3) { EM_ASM(oi()); return; } struct leg *l = state->l1; EM_ASM({updatePlayerAvg($0, $1)}, state->active_p, ((double)l->start / (((l->n_visits - 1) * 3) + n_darts)) * 3); EM_ASM({promptMsgL($0)}, "You win! :)"); match_over(); } void init_boat() { static int ran; if (ran) return; ran = 1; srand(time(NULL)); init_board(); EM_ASM({updateStdev($0)}, horizontal_stdev); EM_ASM({updateDelay($0)}, delay_ms); } EMSCRIPTEN_KEEPALIVE void free_match() { leg_free(state->l1); state->l2->n_visits += state->undone_count; // FIXME messy way to avoid memory leak leg_free(state->l2); free(state); state = NULL; } EMSCRIPTEN_KEEPALIVE void match_init() { for (int i = 1; i < 3; ++i) { EM_ASM({hidePlayerInfo($0)}, i); EM_ASM({updatePlayerName($0, $1)}, i, ""); EM_ASM({updatePlayerRem($0, $1)}, i, ""); EM_ASM({updatePlayerSugg($0, $1)}, i, ""); EM_ASM({updatePlayerAvg($0, $1)}, i, 0); } EM_ASM(clearVisits()); EM_ASM({setPromptHandler($0)}, "init"); EM_ASM({promptMsgR($0)}, ""); EM_ASM({promptMsgL($0)}, "Select match mode:"); } EMSCRIPTEN_KEEPALIVE void start_match(int mode) { if (mode != M_PVP && mode != M_PVC && mode != M_P) { EM_ASM(oi()); return; } if (state) free_match(); init_boat(); state = calloc(1, sizeof(*state)); state->mode = mode; if (mode == M_PVC) { state->l1 = leg_init(501, "User"); state->l2 = leg_init(501, "Bot"); } else { state->l1 = leg_init(501, "Player 1"); state->l2 = leg_init(501, "Player 2"); } EM_ASM({showPlayerInfo($0)}, 1); if (mode != M_P) EM_ASM({showPlayerInfo($0)}, 2); state->active_p = 1; state->active_l = state->l1; EM_ASM({updatePlayerName($0, $1)}, 1, state->l1->name); EM_ASM({updatePlayerName($0, $1)}, 2, state->l2->name); update_user_rem(state->l1->rem); update_boat_rem(state->l2->rem); EM_ASM({updatePlayerAvg($0, $1)}, 1, 0); EM_ASM({updatePlayerAvg($0, $1)}, 2, 0); EM_ASM(clearVisits()); set_user_active(); EM_ASM({promptMsgR($0)}, ""); draw_match(); } EMSCRIPTEN_KEEPALIVE void set_stdev(float hstdev, float vstdev) { horizontal_stdev = hstdev; vertical_stdev = vstdev; } EMSCRIPTEN_KEEPALIVE void set_delay(int delay) { delay_ms = delay; } int main() { EM_ASM(read_opts()); init_boat(); match_init(); }