From 15ff00e056a98ab4210dde3d4461a88849d6c1d4 Mon Sep 17 00:00:00 2001
From: David Vazgenovich Shakaryan <dvshakaryan@gmail.com>
Date: Mon, 23 May 2022 15:23:48 -0700
Subject: move a bunch of general match logic from web to lib

---
 dartboat.c           |  59 +++++++++++++++++-----------
 match.c              | 106 +++++++++++++++++++++++++++++++++++++++++++++++----
 match.h              |  41 +++++++++++++++++---
 web/static/sw.js     |   2 +-
 web/web_control.c    |  79 +++++++++++++++++---------------------
 web/web_match.c      |  85 +++--------------------------------------
 web/web_match.h      |  18 +--------
 web/web_prompt.c     |   5 ++-
 web/web_scoreboard.c |  63 +++++++++++++++---------------
 9 files changed, 251 insertions(+), 207 deletions(-)

diff --git a/dartboat.c b/dartboat.c
index c265031..7061b9a 100644
--- a/dartboat.c
+++ b/dartboat.c
@@ -11,7 +11,7 @@
 
 void test_match(int start_points)
 {
-	struct leg *l = leg_init(start_points, NULL);
+	struct leg *l = leg_init(start_points);
 
 	while (l->rem > 0)
 		comp_visit(l);
@@ -32,14 +32,14 @@ void test_match(int start_points)
 	leg_free(l);
 }
 
-void user_visit(struct leg *l)
+void user_visit(struct leg *l, char *name)
 {
 	if (l->n_visits == l->size_visits)
 		leg_grow_visits(l);
 	struct visit *v = l->visits + l->n_visits++;
 
 	char status[100];
-	int len = sprintf(status, " %s has %d remaining", l->name, l->rem);
+	int len = sprintf(status, " %s has %d remaining", name, l->rem);
 	if (l->rem <= 170) {
 		char *target = CHECKOUTS[2][l->rem-1];
 		if (target) {
@@ -89,52 +89,67 @@ void user_visit(struct leg *l)
 	v->rem = l->rem;
 }
 
-void curses_match(int start_points, char *n1, void (*f1)(struct leg *),
-	char *n2, void (*f2)(struct leg *))
+static void player_visit(struct match *m, int pn)
+{
+	if (m->players[pn - 1].type == PT_USER)
+		user_visit(m->legs[pn - 1], m->players[pn - 1].name);
+	else
+		comp_visit(m->legs[pn - 1]);
+}
+
+void curses_match(struct match *m)
 {
 	init_curses();
 
-	struct leg *l1 = leg_init(start_points, n1);
-	struct leg *l2 = leg_init(start_points, n2);
+	struct leg *l1 = m->legs[0];
+	struct leg *l2 = m->legs[1];
 
 	curses_draw(l1, l2);
 
 	while (l1->rem > 0 && l2->rem > 0) {
-		(*f1)(l1);
+		player_visit(m, 1);
 		curses_draw(l1, l2);
 
-		if (l1->rem > 0)
-			(*f2)(l2);
-
-		curses_draw(l1, l2);
+		if (l1->rem > 0) {
+			player_visit(m, 2);
+			curses_draw(l1, l2);
+		}
 	}
 
 	char status[100];
-	sprintf(status, " %s wins", l1->rem <= 0 ? l1->name : l2->name);
+	sprintf(status, " %s wins",
+		m->players[match_winning_player(m) - 1].name);
 	curses_status(status);
 
-	leg_free(l1);
-	leg_free(l2);
 	wgetch(w);
 	free_curses();
 }
 
 void cvc_curses_match(int start_points)
 {
-	curses_match(start_points, "Dartboat 1", comp_visit,
-		"Dartboat 2", comp_visit);
+	struct match *m = match_init();
+	match_add_player(m, PT_COMP, "Computer 1", start_points);
+	match_add_player(m, PT_COMP, "Computer 2", start_points);
+	curses_match(m);
+	match_free(m);
 }
 
 void pvc_curses_match(int start_points)
 {
-	curses_match(start_points, "David", user_visit,
-		"Dartboat", comp_visit);
+	struct match *m = match_init();
+	match_add_player(m, PT_USER, "Player", start_points);
+	match_add_player(m, PT_COMP, "Computer", start_points);
+	curses_match(m);
+	match_free(m);
 }
 
 void pvp_curses_match(int start_points)
 {
-	curses_match(start_points, "David", user_visit,
-		"Davidn't", user_visit);
+	struct match *m = match_init();
+	match_add_player(m, PT_USER, "Player 1", start_points);
+	match_add_player(m, PT_USER, "Player 2", start_points);
+	curses_match(m);
+	match_free(m);
 }
 
 void test_averages()
@@ -146,7 +161,7 @@ void test_averages()
 
 		int darts = 0;
 		for (int i = 0; i < rounds; ++i) {
-			struct leg *l = leg_init(501, NULL);
+			struct leg *l = leg_init(501);
 			while (l->rem > 0)
 				comp_visit(l);
 			leg_free(l);
diff --git a/match.c b/match.c
index 1e131f0..03e91fd 100644
--- a/match.c
+++ b/match.c
@@ -1,12 +1,13 @@
 #include "match.h"
 
+#include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>
 
-struct leg *leg_init(int points, char *name)
+struct leg *leg_init(int points)
 {
 	struct leg *l = calloc(1, sizeof(*l));
-	l->name = strdup(name);
+
 	l->start = l->rem = points;
 	l->size_visits = 16;
 	l->visits = calloc(l->size_visits, sizeof(*(l->visits)));
@@ -16,26 +17,115 @@ struct leg *leg_init(int points, char *name)
 
 void leg_free(struct leg *l)
 {
+	// readd undone visits before free to avoid memory leak
+	l->n_visits += l->undone_visits;
+
 	for (int i = 0; i < l->n_visits; ++i) {
-		if (l->visits[i].darts)
-			free(l->visits[i].darts);
-		if (l->visits[i].ccoords)
-			free(l->visits[i].ccoords);
+		free(l->visits[i].darts);
+		free(l->visits[i].ccoords);
 	}
 	free(l->visits);
-	free(l->name);
 	free(l);
 }
 
 void leg_grow_visits(struct leg *l)
 {
-
 	size_t bytes = l->size_visits * sizeof(*(l->visits));
 	l->size_visits *= 2;
 	l->visits = realloc(l->visits, 2 * bytes);
 	memset((char *)l->visits + bytes, 0, bytes);
 }
 
+void leg_undo_visit(struct leg *l)
+{
+	l->rem += l->visits[--l->n_visits].points;
+	++l->undone_visits;
+}
+
+void leg_redo_visit(struct leg *l)
+{
+	l->rem = l->visits[l->n_visits++].rem;
+	--l->undone_visits;
+}
+
+struct match *match_init()
+{
+	struct match *m = calloc(1, sizeof(*m));
+
+	m->size_players = 2;
+	m->players = malloc(m->size_players * sizeof(*m->players));
+	m->legs = malloc(m->size_players * sizeof(*m->legs));
+
+	return m;
+}
+
+void match_free(struct match *m)
+{
+	for (int i = 0; i < m->n_players; ++i) {
+		free(m->players[i].name);
+		leg_free(m->legs[i]);
+	}
+
+	free(m->players);
+	free(m->legs);
+	free(m);
+}
+
+static void match_grow_players(struct match *m)
+{
+	m->size_players *= 2;
+	m->players = realloc(m->players,
+		m->size_players * sizeof(*m->players));
+	m->legs = realloc(m->legs,
+		m->size_players * sizeof(*m->legs));
+}
+
+void match_add_player(struct match *m, enum player_type type, char *name,
+	int start_pts)
+{
+	if (m->n_players == m->size_players)
+		match_grow_players(m);
+	int i = m->n_players++;
+
+	struct player *p = m->players + i;
+	p->type = type;
+	p->name = strdup(name);
+
+	m->legs[i] = leg_init(start_pts);
+}
+
+int match_next_player(struct match *m)
+{
+	if (m->active_player == m->n_players)
+		return 1;
+	else
+		return m->active_player + 1;
+}
+
+int match_prev_player(struct match *m)
+{
+	if (m->active_player == 1)
+		return m->n_players;
+	else
+		return m->active_player - 1;
+}
+
+int match_last_player_to_throw(struct match *m)
+{
+	int p = match_winning_player(m);
+	return p ? p : match_prev_player(m);
+}
+
+int match_winning_player(struct match *m)
+{
+	for (int i = 0; i < m->n_players; ++i) {
+		if (m->legs[i]->rem == 0)
+			return i + 1;
+	}
+
+	return 0;
+}
+
 bool is_points_valid(int pts, int rem)
 {
 	return pts <= rem &&
diff --git a/match.h b/match.h
index ce52717..a699f77 100644
--- a/match.h
+++ b/match.h
@@ -3,6 +3,18 @@
 
 #include "board.h"
 
+#include <stdbool.h>
+
+enum player_type {
+	PT_USER,
+	PT_COMP
+};
+
+struct player {
+	char *name;
+	enum player_type type;
+};
+
 struct visit {
 	int points;
 	int rem;
@@ -12,17 +24,36 @@ struct visit {
 };
 
 struct leg {
-	char *name;
-	int start;
-	int rem;
-	int n_visits, size_visits;
+	int start, rem;
+	int n_visits, size_visits, undone_visits;
 	struct visit *visits;
 };
 
-struct leg *leg_init(int points, char *name);
+struct match {
+	struct player *players;
+	int n_players, size_players;
+	int active_player;
+
+	struct leg **legs;
+};
+
+struct leg *leg_init(int points);
 void leg_free(struct leg *l);
 void leg_grow_visits(struct leg *l);
 
+void leg_undo_visit(struct leg *l);
+void leg_redo_visit(struct leg *l);
+
+struct match *match_init();
+void match_free(struct match *m);
+void match_add_player(struct match *m, enum player_type type, char *name,
+	int start_pts);
+
+int match_next_player(struct match *m);
+int match_prev_player(struct match *m);
+int match_last_player_to_throw(struct match *m);
+int match_winning_player(struct match *m);
+
 bool is_points_valid(int pts, int rem);
 
 #endif
diff --git a/web/static/sw.js b/web/static/sw.js
index 3b2d49b..56f5626 100644
--- a/web/static/sw.js
+++ b/web/static/sw.js
@@ -1,5 +1,5 @@
 const CACHE_PREFIX = 'dartboat-'
-const CACHE_VERSION = '21';
+const CACHE_VERSION = '22';
 const CACHE_NAME = `${CACHE_PREFIX}${CACHE_VERSION}`;
 
 const CACHE_FILES = [
diff --git a/web/web_control.c b/web/web_control.c
index c561250..d06e17e 100644
--- a/web/web_control.c
+++ b/web/web_control.c
@@ -17,7 +17,7 @@
 
 void set_active_player(int pn)
 {
-	state->active_player = pn;
+	state->m->active_player = pn;
 	scoreboard_set_player_active(pn);
 
 	if (match_player_is_comp(pn))
@@ -28,8 +28,9 @@ void set_active_player(int pn)
 
 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);
+	update_player_rem(state->m->active_player,
+		state_active_leg()->rem - pts);
+	scoreboard_flush_player_info(state->m->active_player);
 }
 
 EMSCRIPTEN_KEEPALIVE
@@ -39,8 +40,8 @@ void end_boat_visit(int rem, double avg, int match_id)
 		return;
 
 	svg_clear_points();
-	update_player_rem(state->active_player, rem);
-	scoreboard_set_player_avg(state->active_player, avg);
+	update_player_rem(state->m->active_player, rem);
+	scoreboard_set_player_avg(state->m->active_player, avg);
 	prompt_set_input(NULL);
 	prompt_set_msgr(NULL);
 
@@ -61,13 +62,13 @@ void draw_boat_throwing(int pts, char *str, double x, double y, int match_id)
 	int rem = l->n_visits > 1 ? l->visits[l->n_visits-2].rem : l->start;
 
 	svg_draw_point(x, y);
-	update_player_rem(state->active_player, rem - pts);
+	update_player_rem(state->m->active_player, rem - pts);
 	prompt_set_input(pts_str);
 	prompt_set_msgr(str);
 	free(str);
 
 	prompt_flush();
-	scoreboard_flush_player_info(state->active_player);
+	scoreboard_flush_player_info(state->m->active_player);
 }
 
 static void schedule_boat_visit_draws(struct leg *l, struct visit *v,
@@ -103,12 +104,10 @@ static void schedule_boat_visit_draws(struct leg *l, struct visit *v,
 void boat_visit()
 {
 	struct leg *l = state_active_leg();
-	if (state->comp_undone[state->active_player - 1]) {
-		--state->comp_undone[state->active_player - 1];
-		l->rem = l->visits[l->n_visits++].rem;
-	} else {
+	if (l->undone_visits)
+		leg_redo_visit(l);
+	else
 		comp_visit(l);
-	}
 
 	struct visit *v = l->visits + l->n_visits - 1;
 	double avg = v->rem > 0 ?
@@ -123,21 +122,22 @@ void boat_visit()
 
 void handle_next()
 {
+	int wp;
+
 	if (!state) {
 		prompt_main_menu();
-	} else if (match_is_over()) {
-		if (state->num_darts ||
-			match_player_is_comp(match_winning_player()))
+	} else if ((wp = match_winning_player(state->m))) {
+		if (state->num_darts || match_player_is_comp(wp))
 			prompt_end_match();
 		else
 			prompt_num_darts();
 	} else {
-		if (state->active_player)
-			set_active_player(match_next_player());
+		if (state->m->active_player)
+			set_active_player(match_next_player(state->m));
 		else
 			set_active_player(match_opts->throws_first);
 
-		if (match_player_is_comp(state->active_player))
+		if (match_player_is_comp(state->m->active_player))
 			boat_visit();
 	}
 
@@ -159,10 +159,10 @@ void user_visit(int points)
 	v->points = points;
 	l->rem -= points;
 	v->rem = l->rem;
-	update_player_rem(state->active_player, l->rem);
+	update_player_rem(state->m->active_player, l->rem);
 
 	if (v->rem > 0)
-		update_player_avg(state->active_player, 0);
+		update_player_avg(state->m->active_player, 0);
 
 	draw_visits();
 	handle_next();
@@ -176,20 +176,12 @@ void user_visit_to_rem(int rem)
 static void undo_active()
 {
 	struct leg *l = state_active_leg();
+	leg_undo_visit(l);
 
-	if (match_player_is_comp(state->active_player)) {
-		l->rem += l->visits[--l->n_visits].points;
-		++state->comp_undone[state->active_player - 1];
-	} 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);
+	update_player_avg(state->m->active_player, 0);
+	update_player_rem(state->m->active_player, l->rem);
 
-	state->active_player = match_prev_throw_player();
+	state->m->active_player = match_last_player_to_throw(state->m);
 }
 
 void user_undo()
@@ -202,16 +194,16 @@ void user_undo()
 	if (state->num_darts) {
 		state->num_darts = 0;
 		struct leg *l = state_active_leg();
-		scoreboard_set_player_avg(state->active_player,
+		scoreboard_set_player_avg(state->m->active_player,
 			((double)(l->start - l->visits[l->n_visits-2].rem) /
 				(l->n_visits - 1)));
-		scoreboard_set_player_active(state->active_player);
+		scoreboard_set_player_active(state->m->active_player);
 		handle_next();
 		return;
 	}
 
-	state->active_player = match_prev_throw_player();
-	while (match_player_is_comp(state->active_player))
+	state->m->active_player = match_last_player_to_throw(state->m);
+	while (match_player_is_comp(state->m->active_player))
 		undo_active();
 	undo_active();
 
@@ -227,7 +219,7 @@ void user_num_darts(int n)
 	}
 
 	state->num_darts = n;
-	update_player_avg(state->active_player, n);
+	update_player_avg(state->m->active_player, n);
 
 	handle_next();
 }
@@ -237,14 +229,15 @@ void start_match()
 	match_new();
 
 	for (int i = 0; i < match_opts->num_players; ++i)
-		match_add_player(match_opts->start_pts,
+		match_add_player(state->m,
 			match_opts->players[i].type,
-			match_opts->players[i].name);
+			match_opts->players[i].name,
+			match_opts->start_pts);
 
-	scoreboard_show_info(match_num_players());
-	for (int i = 1; i <= match_num_players(); ++i) {
-		update_player_name(i, state->legs[i - 1]->name);
-		update_player_rem(i, state->legs[i - 1]->rem);
+	scoreboard_show_info(state->m->n_players);
+	for (int i = 1; i <= state->m->n_players; ++i) {
+		update_player_name(i, state->m->players[i - 1].name);
+		update_player_rem(i, state->m->legs[i - 1]->rem);
 		update_player_avg(i, 0);
 	}
 
diff --git a/web/web_match.c b/web/web_match.c
index 58b0aca..d4dac50 100644
--- a/web/web_match.c
+++ b/web/web_match.c
@@ -17,41 +17,12 @@ void match_new()
 
 	state = calloc(1, sizeof(*state));
 	state->id = ++curr_id;
-
-	state->size_players = 2;
-	state->legs = malloc(state->size_players * sizeof(*(state->legs)));
-	state->player_types = malloc(state->size_players *
-		sizeof(*(state->player_types)));
-	state->comp_undone = malloc(state->size_players *
-		sizeof(*(state->comp_undone)));
-}
-
-void match_add_player(int start_pts, enum player_type type, char *name)
-{
-	if (state->num_players == state->size_players) {
-		state->size_players +=
-			state->size_players ? state->size_players : 1;
-		state->legs = realloc(state->legs,
-			state->size_players * sizeof(*(state->legs)));
-		state->player_types = realloc(state->player_types,
-			state->size_players * sizeof(*(state->player_types)));
-		state->comp_undone = realloc(state->comp_undone,
-			state->size_players * sizeof(*(state->comp_undone)));
-	}
-
-	state->legs[state->num_players] = leg_init(start_pts, name);
-	state->player_types[state->num_players] = type;
-	state->comp_undone[state->num_players++] = 0;
+	state->m = match_init();
 }
 
 void free_state()
 {
-	for (int i = 0; i < state->num_players; ++i) {
-		// undone visits readded before free to avoid memory leak
-		state->legs[i]->n_visits += state->comp_undone[i];
-		leg_free(state->legs[i]);
-	}
-	free(state->legs);
+	free(state->m);
 	free(state);
 	state = NULL;
 }
@@ -114,67 +85,23 @@ void match_opts_free()
 
 struct leg *state_active_leg()
 {
-	return state->legs[state->active_player - 1];
-}
-
-int match_num_players()
-{
-	return state->num_players;
-}
-
-int match_winning_player()
-{
-	for (int i = 0; i < match_num_players(); ++i) {
-		if (state->legs[i]->rem == 0)
-			return i + 1;
-	}
-
-	return 0;
-}
-
-bool match_is_over()
-{
-	return match_winning_player() != 0;
-}
-
-int match_prev_player()
-{
-	if (state->active_player == 1)
-		return match_num_players();
-	else
-		return state->active_player - 1;
-}
-
-int match_next_player()
-{
-	if (state->active_player == match_num_players())
-		return 1;
-	else
-		return state->active_player + 1;
-}
-
-int match_prev_throw_player()
-{
-	if (match_is_over())
-		return state->active_player;
-
-	return match_prev_player();
+	return state->m->legs[state->m->active_player - 1];
 }
 
 bool match_player_is_comp(int pn)
 {
-	return state->player_types[pn - 1] == PT_COMP;
+	return state->m->players[pn - 1].type == PT_COMP;
 }
 
 bool match_first_user_has_thrown()
 {
-	for (int i = 0, np = match_num_players(); i < np; ++i) {
+	for (int i = 0, np = state->m->n_players; i < np; ++i) {
 		int pn = match_opts->throws_first + i;
 		if (pn > np)
 			pn -= np;
 
 		if (!match_player_is_comp(pn))
-			return !!state->legs[pn - 1]->n_visits;
+			return !!state->m->legs[pn - 1]->n_visits;
 	}
 
 	return false;
diff --git a/web/web_match.h b/web/web_match.h
index 8fb2056..04664f4 100644
--- a/web/web_match.h
+++ b/web/web_match.h
@@ -3,20 +3,11 @@
 
 #include "match.h"
 
-enum player_type {
-	PT_USER,
-	PT_COMP
-};
-
 struct match_state {
 	int id;
 
-	struct leg **legs;
-	enum player_type *player_types;
-	int *comp_undone;
-	int num_players, size_players;
+	struct match *m;
 
-	int active_player;
 	int num_darts;
 };
 
@@ -36,7 +27,6 @@ extern struct match_state *state;
 extern struct match_opts *match_opts;
 
 void match_new();
-void match_add_player(int start_pts, enum player_type type, char *name);
 void free_state();
 void match_opts_new();
 void match_opts_add_player(enum player_type type, char *name);
@@ -45,12 +35,6 @@ void match_opts_free();
 
 struct leg *state_active_leg();
 
-int match_num_players();
-int match_winning_player();
-bool match_is_over();
-int match_prev_player();
-int match_next_player();
-int match_prev_throw_player();
 bool match_player_is_comp(int pn);
 bool match_first_user_has_thrown();
 
diff --git a/web/web_prompt.c b/web/web_prompt.c
index 86cc97f..37745e8 100644
--- a/web/web_prompt.c
+++ b/web/web_prompt.c
@@ -399,7 +399,8 @@ void prompt_comp_visit()
 	set_prompt_mode(PM_DARTBOARD);
 
 	char buf[64];
-	sprintf(buf, "%s is throwing…", state_active_leg()->name);
+	sprintf(buf, "%s is throwing…",
+		state->m->players[state->m->active_player - 1].name);
 	prompt_set_msgl(buf);
 	prompt_set_msgr(NULL);
 }
@@ -421,7 +422,7 @@ void prompt_end_match()
 
 	char buf[64];
 	sprintf(buf, "%s wins.",
-		state->legs[match_winning_player() - 1]->name);
+		state->m->players[match_winning_player(state->m) - 1].name);
 	prompt_set_msgl(buf);
 	prompt_set_msgr(NULL);
 
diff --git a/web/web_scoreboard.c b/web/web_scoreboard.c
index 2916ae8..907b1e5 100644
--- a/web/web_scoreboard.c
+++ b/web/web_scoreboard.c
@@ -75,7 +75,7 @@ void scoreboard_prev_slot()
 	for (int i = NUM_SLOTS; i > 1; --i)
 		set_slot_player(i, slots[i - 2]);
 	int prev = slots[0];
-	set_slot_player(1, prev == 1 ? match_num_players() : prev - 1);
+	set_slot_player(1, prev == 1 ? state->m->n_players : prev - 1);
 	set_slot_active(player_slot(active_pn));
 }
 
@@ -85,7 +85,7 @@ void scoreboard_next_slot()
 	for (int i = 1; i < NUM_SLOTS; ++i)
 		set_slot_player(i, slots[i]);
 	int prev = slots[NUM_SLOTS - 1];
-	set_slot_player(NUM_SLOTS, prev == match_num_players() ? 1 : prev + 1);
+	set_slot_player(NUM_SLOTS, prev == state->m->n_players ? 1 : prev + 1);
 	set_slot_active(player_slot(active_pn));
 }
 
@@ -98,7 +98,7 @@ void scoreboard_set_player_active(int pn)
 		return;
 	}
 
-	int np = match_num_players();
+	int np = state->m->n_players;
 	if (np > NUM_SLOTS) {
 		for (int i = 0; i < NUM_SLOTS; ++i)
 			set_slot_player(i + 1, ((pn + i - 1) % np) + 1);
@@ -202,7 +202,7 @@ static double calc_avg(struct leg *l, int n_darts) {
 
 void update_player_avg(int pn, int n_darts)
 {
-	struct leg *l = state->legs[pn-1];
+	struct leg *l = state->m->legs[pn-1];
 	scoreboard_set_player_avg(pn, calc_avg(l, n_darts));
 }
 
@@ -284,13 +284,13 @@ void draw_visits_dense()
 	dom_set_content("#visits", NULL);
 	dom_add_class("#visits", "dense");
 
-	int np = match_num_players();
+	int np = state->m->n_players;
 	dom_set_style_property_int("--num-players", np);
 
 	int n_visits = 0;
 	for (int i = 0; i < np; ++i) {
-		if (state->legs[i]->n_visits > n_visits)
-			n_visits = state->legs[i]->n_visits;
+		if (state->m->legs[i]->n_visits > n_visits)
+			n_visits = state->m->legs[i]->n_visits;
 	}
 
 	struct dom_elem **elemv = malloc(
@@ -301,7 +301,7 @@ void draw_visits_dense()
 	elemv[elemc++] = create_div("", "");
 	for (int i = 0; i < np; ++i) {
 		sprintf(buf, "visit-name visit-name-%d", i + 1);
-		elemv[elemc++] = create_div(state->legs[i]->name, buf);
+		elemv[elemc++] = create_div(state->m->players[i].name, buf);
 	}
 
 	elemv[elemc++] = create_div("0", "visit-n");
@@ -310,7 +310,7 @@ void draw_visits_dense()
 			elemv[elemc++] = create_div("", "throws-first");
 		else
 			elemv[elemc++] = create_div("", "");
-		snprintf(buf, sizeof(buf), "%d", state->legs[i]->start);
+		snprintf(buf, sizeof(buf), "%d", state->m->legs[i]->start);
 		elemv[elemc++] = create_div(buf, "");
 	}
 
@@ -319,13 +319,13 @@ void draw_visits_dense()
 		elemv[elemc++] = create_div(buf, "visit-n");
 
 		for (int j = 0; j < np; ++j) {
-			if (i >= state->legs[j]->n_visits) {
+			if (i >= state->m->legs[j]->n_visits) {
 				elemv[elemc++] = create_div("", "");
 				elemv[elemc++] = create_div("", "");
 				continue;
 			}
 
-			struct visit *v = state->legs[j]->visits + i;
+			struct visit *v = state->m->legs[j]->visits + i;
 
 			snprintf(buf, sizeof(buf), "%d", v->points);
 			snprintf(buf2, sizeof(buf2), "%s",
@@ -338,21 +338,21 @@ void draw_visits_dense()
 
 		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)
+			if (i < state->m->legs[j]->n_visits &&
+				state->m->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) {
+			if (i >= state->m->legs[j]->n_visits) {
 				elemv[elemc++] = create_div("",
 					"visit-dense-darts");
 				continue;
 			}
 
-			struct visit *v = state->legs[j]->visits + i;
+			struct visit *v = state->m->legs[j]->visits + i;
 
 			if (v->n_darts) {
 				buf_darts(buf, sizeof(buf), v);
@@ -371,14 +371,15 @@ void draw_visits_dense()
 	free(elemv);
 
 	dom_scroll_to_bottom("#visits");
-	int pn = match_is_over() ? state->active_player : match_next_player();
+	int pn = match_winning_player(state->m);
+	if (!pn) pn = match_next_player(state->m);
 	sprintf(buf, ".visit-name-%d", pn);
 	dom_scroll_to_center_child("#visits", buf);
 }
 
 void draw_visits()
 {
-	if (match_num_players() > 2) {
+	if (state->m->n_players > 2) {
 		draw_visits_dense();
 		return;
 	}
@@ -386,29 +387,30 @@ void draw_visits()
 	dom_set_content("#visits", NULL);
 	dom_remove_class("#visits", "dense");
 
-	int n_visits = state->legs[0]->n_visits;
-	if (match_num_players() != 1 && state->legs[1]->n_visits > n_visits)
-		n_visits = state->legs[1]->n_visits;
+	int n_visits = state->m->legs[0]->n_visits;
+	if (state->m->n_players != 1 && state->m->legs[1]->n_visits > n_visits)
+		n_visits = state->m->legs[1]->n_visits;
 
 	struct dom_elem **elemv = malloc(7 * (n_visits + 1) * sizeof(*elemv));
 	int elemc = 0;
 
 	elemv[elemc++] = create_div("", "visit-p1-spacer");
-	elemv[elemc++] = create_div(state->legs[0]->name, "visit-p1-name");
-	if (match_num_players() != 1)
-		elemv[elemc++] = create_div(state->legs[1]->name,
+	elemv[elemc++] = create_div(state->m->players[0].name,
+		"visit-p1-name");
+	if (state->m->n_players != 1)
+		elemv[elemc++] = create_div(state->m->players[1].name,
 			"visit-p2-name");
 	elemv[elemc++] = create_div("", "visit-p2-spacer");
 
 	char buf[32], buf2[32];
-	snprintf(buf, sizeof(buf), "%d", state->legs[0]->start);
+	snprintf(buf, sizeof(buf), "%d", state->m->legs[0]->start);
 	if (match_opts->throws_first == 1)
 		elemv[elemc++] = create_div("", // content added via CSS
 			"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) {
-		snprintf(buf, sizeof(buf), "%d", state->legs[1]->start);
+	if (state->m->n_players != 1) {
+		snprintf(buf, sizeof(buf), "%d", state->m->legs[1]->start);
 		elemv[elemc++] = create_div(buf, "visit-p2-rem");
 		if (match_opts->throws_first == 2)
 			elemv[elemc++] = create_div("",
@@ -418,9 +420,9 @@ void draw_visits()
 	for (int i = 0; i < n_visits; ++i) {
 		struct visit *v;
 
-		if (i >= state->legs[0]->n_visits)
+		if (i >= state->m->legs[0]->n_visits)
 			goto p2;
-		v = state->legs[0]->visits + i;
+		v = state->m->legs[0]->visits + i;
 
 		if (v->n_darts) {
 			buf_darts(buf, sizeof(buf), v);
@@ -439,9 +441,10 @@ p2:
 		snprintf(buf, sizeof(buf), "%d", i + 1);
 		elemv[elemc++] = create_div(buf, "visit-n");
 
-		if (match_num_players() == 1 || i >= state->legs[1]->n_visits)
+		if (state->m->n_players == 1 ||
+			i >= state->m->legs[1]->n_visits)
 			continue;
-		v = state->legs[1]->visits + i;
+		v = state->m->legs[1]->visits + i;
 
 		snprintf(buf, sizeof(buf), "%d", v->rem);
 		elemv[elemc++] = create_div(buf, "visit-p2-rem");
-- 
cgit v1.2.3-70-g09d2