Exemple #1
0
static bool
test_sar(struct board *b, char *arg)
{
	enum stone color = str2stone(arg);
	arg += 2;
	coord_t *cc = str2coord(arg, board_size(b));
	coord_t c = *cc; coord_done(cc);
	arg += strcspn(arg, " ") + 1;
	int eres = atoi(arg);

	board_print_test(2, b);
	if (DEBUGL(1))
		printf("sar %s %s %d...\t", stone2str(color), coord2sstr(c, b), eres);

	assert(board_at(b, c) == S_NONE);
	int rres = is_bad_selfatari(b, color, c);

	if (rres == eres) {
		if (DEBUGL(1))
			printf("OK\n");
	} else {
		if (debug_level <= 2) {
			board_print_test(0, b);
			printf("sar %s %s %d...\t", stone2str(color), coord2sstr(c, b), eres);
		}
		printf("FAILED (%d)\n", rres);
	}
	return rres == eres;
}
Exemple #2
0
static bool
test_useful_ladder(struct board *b, char *arg)
{
	enum stone color = str2stone(arg);
	arg += 2;
	coord_t *cc = str2coord(arg, board_size(b));
	coord_t c = *cc; coord_done(cc);
	arg += strcspn(arg, " ") + 1;
	int eres = atoi(arg);

	board_print_test(2, b);
	if (DEBUGL(1))
		printf("useful_ladder %s %s %d...\t", stone2str(color), coord2sstr(c, b), eres);
	
	assert(board_at(b, c) == S_NONE);
	group_t atari_neighbor = board_get_atari_neighbor(b, c, color);
	assert(atari_neighbor);
	int ladder = is_ladder(b, c, atari_neighbor, true);  assert(ladder);
	int rres = useful_ladder(b, atari_neighbor);
	
	if (rres == eres) {
		if (DEBUGL(1))
			printf("OK\n");
	} else {
		if (debug_level <= 2) {
			board_print_test(0, b);
			printf("useful_ladder %s %s %d...\t", stone2str(color), coord2sstr(c, b), eres);
		}
		printf("FAILED (%d)\n", rres);
	}

	return (rres == eres);
}
Exemple #3
0
static floating_t
linear_permove(struct uct_dynkomi *d, struct board *b, struct tree *tree)
{
	struct dynkomi_linear *l = d->data;
	enum stone color = d->uct->pondering ? tree->root_color : stone_other(tree->root_color);
	int lmoves = l->moves[color];
	floating_t extra_komi;

	if (b->moves < lmoves) {
		floating_t base_komi = board_effective_handicap(b, l->handicap_value[color]);
		extra_komi = base_komi * (lmoves - b->moves) / lmoves;
		return extra_komi;
	} else {
		extra_komi = floor(tree->extra_komi);
	}

	/* Do not take decisions on unstable value. */
        if (tree->root->u.playouts < GJ_MINGAMES) return extra_komi;

	floating_t my_value = tree_node_get_value(tree, 1, tree->root->u.value);
	/*  We normalize komi as in komi_by_value(), > 0 when winning. */
	extra_komi = komi_by_color(extra_komi, color);
	if (extra_komi < 0 && DEBUGL(3))
		fprintf(stderr, "XXX: extra_komi %.3f < 0 (color %s tree ek %.3f)\n", extra_komi, stone2str(color), tree->extra_komi);
	// assert(extra_komi >= 0);
	floating_t orig_komi = extra_komi;

	if (my_value < 0.5 && l->komi_ratchet > 0 && l->komi_ratchet != INFINITY) {
		if (DEBUGL(0))
			fprintf(stderr, "losing %f extra komi %.1f ratchet %.1f -> 0\n",
				my_value, extra_komi, l->komi_ratchet);
		/* Disable dynkomi completely, too dangerous in this game. */
		extra_komi = l->komi_ratchet = 0;

	} else if (my_value < l->orange_zone && extra_komi > 0) {
		extra_komi = l->komi_ratchet  = fmax(extra_komi - l->drop_step, 0.0);
		if (extra_komi != orig_komi && DEBUGL(3))
			fprintf(stderr, "dropping to %f, extra komi %.1f -> ratchet %.1f\n",
				my_value, orig_komi, extra_komi);

	} else if (my_value > l->green_zone && extra_komi + 1 <= l->komi_ratchet) {
		extra_komi += 1;
		if (extra_komi != orig_komi && DEBUGL(3))
			fprintf(stderr, "winning %f extra_komi %.1f -> %.1f, ratchet %.1f\n",
				my_value, orig_komi, extra_komi, l->komi_ratchet);
	}
	return komi_by_color(extra_komi, color);
}
Exemple #4
0
static bool
test_moggy_moves(struct board *b, char *arg)
{
	int runs = 1000;	
	
	coord_t *cc = str2coord(arg, board_size(b));
	struct move last;
	last.coord = *cc; coord_done(cc);
	last.color = board_at(b, last.coord);
	assert(last.color == S_BLACK || last.color == S_WHITE);
	enum stone color = stone_other(last.color);
	arg += strcspn(arg, " ") + 1;

	b->last_move = last;
	board_print(b, stderr);  // Always print board so we see last move

	char e_arg[128];  sprintf(e_arg, "runs=%i", runs);
	struct engine *e = engine_replay_init(e_arg, b);
	
	if (DEBUGL(1))
		printf("moggy moves %s, %s to play. Sampling moves (%i runs)...\n\n", 
		       coord2sstr(last.coord, b), stone2str(color), runs);

        int played_[b->size2 + 2];		memset(played_, 0, sizeof(played_));
	int *played = played_ + 2;		// allow storing pass/resign
	int most_played = 0;
	replay_sample_moves(e, b, color, played, &most_played);

	/* Show moves stats */	
	for (int k = most_played; k > 0; k--)
		for (coord_t c = resign; c < b->size2; c++)
			if (played[c] == k)
				printf("%3s: %.2f%%\n", coord2str(c, b), (float)k * 100 / runs);
	
	engine_done(e);
	return true;   // Not much of a unit test right now =)
}
Exemple #5
0
int
uct_playout(struct uct *u, struct board *b, enum stone player_color, struct tree *t)
{
	struct board b2;
	board_copy(&b2, b);

	struct playout_amafmap amaf;
	amaf.gamelen = amaf.game_baselen = 0;

	/* Walk the tree until we find a leaf, then expand it and do
	 * a random playout. */
	struct tree_node *n = t->root;
	enum stone node_color = stone_other(player_color);
	assert(node_color == t->root_color);

	/* Make sure the root node is expanded. */
	if (tree_leaf_node(n) && !__sync_lock_test_and_set(&n->is_expanded, 1))
		tree_expand_node(t, n, &b2, player_color, u, 1);

	/* Tree descent history. */
	/* XXX: This is somewhat messy since @n and descent[dlen-1].node are
	 * redundant. */
	struct uct_descent descent[DESCENT_DLEN];
	descent[0].node = n; descent[0].lnode = NULL;
	int dlen = 1;
	/* Total value of the sequence. */
	struct move_stats seq_value = { .playouts = 0 };
	/* The last "significant" node along the descent (i.e. node
	 * with higher than configured number of playouts). For black
	 * and white. */
	struct tree_node *significant[2] = { NULL, NULL };
	if (n->u.playouts >= u->significant_threshold)
		significant[node_color - 1] = n;

	int result;
	int pass_limit = (board_size(&b2) - 2) * (board_size(&b2) - 2) / 2;
	int passes = is_pass(b->last_move.coord) && b->moves > 0;

	/* debug */
	static char spaces[] = "\0                                                      ";
	/* /debug */
	if (UDEBUGL(8))
		fprintf(stderr, "--- UCT walk with color %d\n", player_color);

	while (!tree_leaf_node(n) && passes < 2) {
		spaces[dlen - 1] = ' '; spaces[dlen] = 0;


		/*** Choose a node to descend to: */

		/* Parity is chosen already according to the child color, since
		 * it is applied to children. */
		node_color = stone_other(node_color);
		int parity = (node_color == player_color ? 1 : -1);

		assert(dlen < DESCENT_DLEN);
		descent[dlen] = descent[dlen - 1];
		if (u->local_tree && (!descent[dlen].lnode || descent[dlen].node->d >= u->tenuki_d)) {
			/* Start new local sequence. */
			/* Remember that node_color already holds color of the
			 * to-be-found child. */
			descent[dlen].lnode = node_color == S_BLACK ? t->ltree_black : t->ltree_white;
		}

		if (!u->random_policy_chance || fast_random(u->random_policy_chance))
			u->policy->descend(u->policy, t, &descent[dlen], parity, b2.moves > pass_limit);
		else
			u->random_policy->descend(u->random_policy, t, &descent[dlen], parity, b2.moves > pass_limit);


		/*** Perform the descent: */

		if (descent[dlen].node->u.playouts >= u->significant_threshold) {
			significant[node_color - 1] = descent[dlen].node;
		}

		seq_value.playouts += descent[dlen].value.playouts;
		seq_value.value += descent[dlen].value.value * descent[dlen].value.playouts;
		n = descent[dlen++].node;
		assert(n == t->root || n->parent);
		if (UDEBUGL(7))
			fprintf(stderr, "%s+-- UCT sent us to [%s:%d] %d,%f\n",
			        spaces, coord2sstr(node_coord(n), t->board),
				node_coord(n), n->u.playouts,
				tree_node_get_value(t, parity, n->u.value));

		/* Add virtual loss if we need to; this is used to discourage
		 * other threads from visiting this node in case of multiple
		 * threads doing the tree search. */
		if (u->virtual_loss)
			stats_add_result(&n->u, node_color == S_BLACK ? 0.0 : 1.0, u->virtual_loss);

		assert(node_coord(n) >= -1);
		record_amaf_move(&amaf, node_coord(n));

		struct move m = { node_coord(n), node_color };
		int res = board_play(&b2, &m);

		if (res < 0 || (!is_pass(m.coord) && !group_at(&b2, m.coord)) /* suicide */
		    || b2.superko_violation) {
			if (UDEBUGL(4)) {
				for (struct tree_node *ni = n; ni; ni = ni->parent)
					fprintf(stderr, "%s<%"PRIhash"> ", coord2sstr(node_coord(ni), t->board), ni->hash);
				fprintf(stderr, "marking invalid %s node %d,%d res %d group %d spk %d\n",
				        stone2str(node_color), coord_x(node_coord(n),b), coord_y(node_coord(n),b),
					res, group_at(&b2, m.coord), b2.superko_violation);
			}
			n->hints |= TREE_HINT_INVALID;
			result = 0;
			goto end;
		}

		if (is_pass(node_coord(n)))
			passes++;
		else
			passes = 0;

		enum stone next_color = stone_other(node_color);
		/* We need to make sure only one thread expands the node. If
		 * we are unlucky enough for two threads to meet in the same
		 * node, the latter one will simply do another simulation from
		 * the node itself, no big deal. t->nodes_size may exceed
		 * the maximum in multi-threaded case but not by much so it's ok.
		 * The size test must be before the test&set not after, to allow
		 * expansion of the node later if enough nodes have been freed. */
		if (tree_leaf_node(n)
		    && n->u.playouts - u->virtual_loss >= u->expand_p && t->nodes_size < u->max_tree_size
		    && !__sync_lock_test_and_set(&n->is_expanded, 1))
			tree_expand_node(t, n, &b2, next_color, u, -parity);
	}

	amaf.game_baselen = amaf.gamelen;

	if (t->use_extra_komi && u->dynkomi->persim) {
		b2.komi += round(u->dynkomi->persim(u->dynkomi, &b2, t, n));
	}

	if (passes >= 2) {
		/* XXX: No dead groups support. */
		floating_t score = board_official_score(&b2, NULL);
		/* Result from black's perspective (no matter who
		 * the player; black's perspective is always
		 * what the tree stores. */
		result = - (score * 2);

		if (UDEBUGL(5))
			fprintf(stderr, "[%d..%d] %s p-p scoring playout result %d (W %f)\n",
				player_color, node_color, coord2sstr(node_coord(n), t->board), result, score);
		if (UDEBUGL(6))
			board_print(&b2, stderr);

		board_ownermap_fill(&u->ownermap, &b2);

	} else { // assert(tree_leaf_node(n));
		/* In case of parallel tree search, the assertion might
		 * not hold if two threads chew on the same node. */
		result = uct_leaf_node(u, &b2, player_color, &amaf, descent, &dlen, significant, t, n, node_color, spaces);
	}

	if (u->policy->wants_amaf && u->playout_amaf_cutoff) {
		unsigned int cutoff = amaf.game_baselen;
		cutoff += (amaf.gamelen - amaf.game_baselen) * u->playout_amaf_cutoff / 100;
		amaf.gamelen = cutoff;
	}

	/* Record the result. */

	assert(n == t->root || n->parent);
	floating_t rval = scale_value(u, b, result);
	u->policy->update(u->policy, t, n, node_color, player_color, &amaf, &b2, rval);

	if (t->use_extra_komi) {
		stats_add_result(&u->dynkomi->score, result / 2, 1);
		stats_add_result(&u->dynkomi->value, rval, 1);
	}

	if (u->local_tree && n->parent && !is_pass(node_coord(n)) && dlen > 0) {
		/* Get the local sequences and record them in ltree. */
		/* We will look for sequence starts in our descent
		 * history, then run record_local_sequence() for each
		 * found sequence start; record_local_sequence() may
		 * pick longer sequences from descent history then,
		 * which is expected as it will create new lnodes. */
		enum stone seq_color = player_color;
		/* First move always starts a sequence. */
		record_local_sequence(u, t, &b2, descent, dlen, 1, seq_color);
		seq_color = stone_other(seq_color);
		for (int dseqi = 2; dseqi < dlen; dseqi++, seq_color = stone_other(seq_color)) {
			if (u->local_tree_allseq) {
				/* We are configured to record all subsequences. */
				record_local_sequence(u, t, &b2, descent, dlen, dseqi, seq_color);
				continue;
			}
			if (descent[dseqi].node->d >= u->tenuki_d) {
				/* Tenuki! Record the fresh sequence. */
				record_local_sequence(u, t, &b2, descent, dlen, dseqi, seq_color);
				continue;
			}
			if (descent[dseqi].lnode && !descent[dseqi].lnode) {
				/* Record result for in-descent picked sequence. */
				record_local_sequence(u, t, &b2, descent, dlen, dseqi, seq_color);
				continue;
			}
		}
	}

end:
	/* We need to undo the virtual loss we added during descend. */
	if (u->virtual_loss) {
		floating_t loss = node_color == S_BLACK ? 0.0 : 1.0;
		for (; n->parent; n = n->parent) {
			stats_rm_result(&n->u, loss, u->virtual_loss);
			loss = 1.0 - loss;
		}
	}

	board_done_noalloc(&b2);
	return result;
}

int
uct_playouts(struct uct *u, struct board *b, enum stone color, struct tree *t, struct time_info *ti)
{
	int i;
	if (ti && ti->dim == TD_GAMES) {
		for (i = 0; t->root->u.playouts <= ti->len.games && !uct_halt; i++)
			uct_playout(u, b, color, t);
	} else {
		for (i = 0; !uct_halt; i++)
			uct_playout(u, b, color, t);
	}
	return i;
}
Exemple #6
0
static void
record_local_sequence(struct uct *u, struct tree *t, struct board *endb,
                      struct uct_descent *descent, int dlen, int di,
		      enum stone seq_color)
{
#define LTREE_DEBUG if (UDEBUGL(6))

	/* Ignore pass sequences. */
	if (is_pass(node_coord(descent[di].node)))
		return;

	LTREE_DEBUG board_print(endb, stderr);
	LTREE_DEBUG fprintf(stderr, "recording local %s sequence: ",
		stone2str(seq_color));

	/* Sequences starting deeper are less relevant in general. */
	int pval = LTREE_PLAYOUTS_MULTIPLIER;
	if (u->local_tree && u->local_tree_depth_decay > 0)
		pval = ((floating_t) pval) / pow(u->local_tree_depth_decay, di - 1);
	if (!pval) {
		LTREE_DEBUG fprintf(stderr, "too deep @%d\n", di);
		return;
	}

	/* Pick the right local tree root... */
	struct tree_node *lnode = seq_color == S_BLACK ? t->ltree_black : t->ltree_white;
	lnode->u.playouts++;

	double sval = 0.5;
	if (u->local_tree_rootgoal) {
		sval = local_value(u, endb, node_coord(descent[di].node), seq_color);
		LTREE_DEBUG fprintf(stderr, "(goal %s[%s %1.3f][%d]) ",
			coord2sstr(node_coord(descent[di].node), t->board),
			stone2str(seq_color), sval, descent[di].node->d);
	}

	/* ...and record the sequence. */
	int di0 = di;
	while (di < dlen && (di == di0 || descent[di].node->d < u->tenuki_d)) {
		enum stone color = (di - di0) % 2 ? stone_other(seq_color) : seq_color;
		double rval;
		if (u->local_tree_rootgoal)
			rval = sval;
		else
			rval = local_value(u, endb, node_coord(descent[di].node), color);
		LTREE_DEBUG fprintf(stderr, "%s[%s %1.3f][%d] ",
			coord2sstr(node_coord(descent[di].node), t->board),
			stone2str(color), rval, descent[di].node->d);
		lnode = tree_get_node(t, lnode, node_coord(descent[di++].node), true);
		assert(lnode);
		stats_add_result(&lnode->u, rval, pval);
	}

	/* Add lnode for tenuki (pass) if we descended further. */
	if (di < dlen) {
		double rval = u->local_tree_rootgoal ? sval : 0.5;
		LTREE_DEBUG fprintf(stderr, "pass ");
		lnode = tree_get_node(t, lnode, pass, true);
		assert(lnode);
		stats_add_result(&lnode->u, rval, pval);
	}
	
	LTREE_DEBUG fprintf(stderr, "\n");
}
Exemple #7
0
/* Syntax:
 *   moggy status (last_move) coord [coord...]
 *         Play number of random games starting from last_move
 * 
 *   moggy status     coord [coord...]
 *   moggy status (b) coord [coord...]
 *         Black to play, pick random white last move
 *
 *   moggy status (w) coord [coord...]  
 *         White to play, pick random black last move
 */
static bool
test_moggy_status(struct board *board, char *arg)
{
	int games = 4000;
	coord_t status_at[10];
	int n = 0;
	enum stone color = S_BLACK;
	int pick_random = true;  // Pick random last move for each game

	while (*arg && *arg != '#') {
		if (*arg == ' ' || *arg == '\t') {  arg++; continue;  }		
		if (!strncmp(arg, "(b)", 3))
			color = S_BLACK;
		else if (!strncmp(arg, "(w)", 3))
			color = S_WHITE;
		else if (*arg == '(') {  /* Optional "(last_move)" argument */
			arg++;	assert(isalpha(*arg));
			pick_random = false;
			struct move last;
			last.coord = str2scoord(arg, board_size(board));
			last.color = board_at(board, last.coord);
			assert(last.color == S_BLACK || last.color == S_WHITE);
			color = stone_other(last.color);
			board->last_move = last;
		}
		else {
			assert(isalpha(*arg));
			status_at[n++] = str2scoord(arg, board_size(board));
		}
		arg += strcspn(arg, " \t");
	}
	
	board_print(board, stderr);
	if (DEBUGL(1)) {
		printf("moggy status ");
		for (int i = 0; i < n; i++)
			printf("%s%s", coord2sstr(status_at[i], board), (i != n-1 ? " " : ""));
		printf(", %s to play. Playing %i games %s...\n", 
		       stone2str(color), games, (pick_random ? "(random last move) " : ""));
	}
	
	struct playout_policy *policy = playout_moggy_init(NULL, board, NULL);
	struct playout_setup setup = { .gamelen = MAX_GAMELEN };
	struct board_ownermap ownermap;

	ownermap.playouts = 0;
	ownermap.map = malloc2(board_size2(board) * sizeof(ownermap.map[0]));
	memset(ownermap.map, 0, board_size2(board) * sizeof(ownermap.map[0]));	


	/* Get final status estimate after a number of moggy games */
	int wr = 0;
	double time_start = time_now();
	for (int i = 0; i < games; i++)  {
		struct board b;
		board_copy(&b, board);
		if (pick_random)
			pick_random_last_move(&b, color);
		
		int score = play_random_game(&setup, &b, color, NULL, &ownermap, policy);
		if (color == S_WHITE)
			score = -score;
		wr += (score > 0);
		board_done_noalloc(&b);
	}
	double elapsed = time_now() - time_start;
	printf("moggy status in %.1fs, %i games/s\n\n", elapsed, (int)((float)games / elapsed));
	
	int wr_black = wr * 100 / games;
	int wr_white = (games - wr) * 100 / games;
	if (wr_black > wr_white)
		printf("Winrate: [ black %i%% ]  white %i%%\n\n", wr_black, wr_white);
	else
		printf("Winrate: black %i%%  [ white %i%% ]\n\n", wr_black, wr_white);

	board_print_ownermap(board, stderr, &ownermap);

	for (int i = 0; i < n; i++) {
		coord_t c = status_at[i];
		enum stone color = (ownermap.map[c][S_BLACK] > ownermap.map[c][S_WHITE] ? S_BLACK : S_WHITE);
		fprintf(stderr, "%3s owned by %s: %i%%\n", 
			coord2sstr(c, board), stone2str(color), 
			ownermap.map[c][color] * 100 / ownermap.playouts);
	}
	
	free(ownermap.map);
	playout_policy_done(policy);
	return true;   // Not much of a unit test right now =)
}
Exemple #8
0
static void
board_load(struct board *b, FILE *f, unsigned int size)
{
	board_printed = false;
	board_resize(b, size);
	board_clear(b);
	for (int y = size - 1; y >= 0; y--) {
		char line[256];
		if (!fgets(line, sizeof(line), f)) {
			fprintf(stderr, "Premature EOF.\n");
			exit(EXIT_FAILURE);
		}
		line[strlen(line) - 1] = 0; // chomp
		if (strlen(line) != size * 2 - 1) {
			fprintf(stderr, "Line not %d char long: %s\n", size * 2 - 1, line);
			exit(EXIT_FAILURE);
		}
		for (unsigned int i = 0; i < size * 2; i++) {
			enum stone s;
			switch (line[i]) {
				case '.': s = S_NONE; break;
				case 'X': s = S_BLACK; break;
				case 'O': s = S_WHITE; break;
				default: fprintf(stderr, "Invalid stone '%c'\n", line[i]);
					 exit(EXIT_FAILURE);
			}
			i++;
			if (line[i] != ' ' && i/2 < size - 1) {
				fprintf(stderr, "No space after stone %i: '%c'\n", i/2 + 1, line[i]);
				exit(EXIT_FAILURE);
			}
			if (s == S_NONE) continue;
			struct move m = { .color = s, .coord = coord_xy(b, i/2 + 1, y + 1) };
			if (board_play(b, &m) < 0) {
				fprintf(stderr, "Failed to play %s %s\n",
					stone2str(s), coord2sstr(m.coord, b));
				board_print(b, stderr);
				exit(EXIT_FAILURE);
			}
		}
	}
	int suicides = b->captures[S_BLACK] || b->captures[S_WHITE];
	assert(!suicides);
}

static void
set_ko(struct board *b, char *arg)
{
	assert(isalpha(*arg));
	struct move last;
	last.coord = str2scoord(arg, board_size(b));
	last.color = board_at(b, last.coord);
	assert(last.color == S_BLACK || last.color == S_WHITE);
	b->last_move = last;

	/* Sanity checks */
	group_t g = group_at(b, last.coord);
	assert(board_group_info(b, g).libs == 1);
	assert(group_stone_count(b, g, 2) == 1);
	coord_t lib = board_group_info(b, g).lib[0];
	assert(board_is_eyelike(b, lib, last.color));

	b->ko.coord = lib;
	b->ko.color = stone_other(last.color);
}