Beispiel #1
0
static bool
test_two_eyes(struct board *b, char *arg)
{
	coord_t c = str2scoord(arg, board_size(b));
	arg += strcspn(arg, " ") + 1;
	int eres = atoi(arg);

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

	enum stone color = board_at(b, c);
	assert(color == S_BLACK || color == S_WHITE);
	int rres = dragon_is_safe(b, group_at(b, c), color);

	if (rres == eres) {
		if (DEBUGL(1))
			printf("OK\n");
	} else {
		if (debug_level <= 2) {
			board_print_test(0, b);
			printf("two_eyes %s %d...\t", coord2sstr(c, b), eres);
		}
		printf("FAILED (%d)\n", rres);
	}
	return rres == eres;
}
Beispiel #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);
}
Beispiel #3
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;
}
Beispiel #4
0
static floating_t
adaptive_permove(struct uct_dynkomi *d, struct board *b, struct tree *tree)
{
	struct dynkomi_adaptive *a = d->data;
	enum stone color = stone_other(tree->root_color);

	/* We do not use extra komi at the game end - we are not
	 * to fool ourselves at this point. */
	if (a->no_komi_at_game_end && board_estimated_moves_left(b) <= MIN_MOVES_LEFT) {
		tree->use_extra_komi = false;
		return 0;
	}

	if (DEBUGL(4))
		fprintf(stderr, "m %d/%d ekomi %f permove %f/%d\n",
			b->moves, a->lead_moves, tree->extra_komi,
			d->score.value, d->score.playouts);

	if (b->moves <= a->lead_moves)
		return bounded_komi(a, b, color,
		                    board_effective_handicap(b, 7 /* XXX */),
		                    a->max_losing_komi);

	floating_t komi = a->indicator(d, b, tree, color);
	if (DEBUGL(4))
		fprintf(stderr, "dynkomi: %f -> %f\n", tree->extra_komi, komi);
	return bounded_komi(a, b, color, komi, a->max_losing_komi);
}
Beispiel #5
0
static bool
test_can_countercapture(struct board *b, char *arg)
{
	coord_t c = str2scoord(arg, board_size(b));
	arg += strcspn(arg, " ") + 1;
	int eres = atoi(arg);

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

	enum stone color = board_at(b, c);
	group_t g = group_at(b, c);
	assert(color == S_BLACK || color == S_WHITE);
	int rres = can_countercapture(b, g, NULL, 0);

	if (rres == eres) {
		if (DEBUGL(1))
			printf("OK\n");
	} else {
		if (debug_level <= 2) {
			board_print_test(0, b);
			printf("can_countercap %s %d...\t", coord2sstr(c, b), eres);
		}
		printf("FAILED (%d)\n", rres);
	}
	return rres == eres;
}
Beispiel #6
0
/* Check if we can make a move along the fbook right away.
 * Otherwise return pass. */
coord_t
fbook_check(struct board *board)
{
	if (!board->fbook) return pass;

	hash_t hi = board->hash;
	coord_t cf = pass;
	while (!is_pass(board->fbook->moves[hi & fbook_hash_mask])) {
		if (board->fbook->hashes[hi & fbook_hash_mask] == board->hash) {
			cf = board->fbook->moves[hi & fbook_hash_mask];
			break;
		}
		hi++;
	}
	if (!is_pass(cf)) {
		if (DEBUGL(1))
			fprintf(stderr, "fbook match %"PRIhash":%"PRIhash"\n", board->hash, board->hash & fbook_hash_mask);
	} else {
		/* No match, also prevent further fbook usage
		 * until the next clear_board. */
		if (DEBUGL(4))
			fprintf(stderr, "fbook out %"PRIhash":%"PRIhash"\n", board->hash, board->hash & fbook_hash_mask);
		fbook_done(board->fbook);
		board->fbook = NULL;
	}
	return cf;
}
Beispiel #7
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);
}
Beispiel #8
0
static coord_t *
replay_genmove(struct engine *e, struct board *b, struct time_info *ti, enum stone color, bool pass_all_alive)
{
	struct replay *r = e->data;
	struct playout_setup s; memset(&s, 0, sizeof(s));

	coord_t coord = r->playout->choose(r->playout, &s, b, color);

	if (!is_pass(coord)) {
		struct move m;
		m.coord = coord; m.color = color;
		if (board_play(b, &m) >= 0)
			goto have_move;

		if (DEBUGL(2)) {
			fprintf(stderr, "Pre-picked move %d,%d is ILLEGAL:\n",
				coord_x(coord, b), coord_y(coord, b));
			board_print(b, stderr);
		}
	}

	/* Defer to uniformly random move choice. */
	board_play_random(b, color, &coord, (ppr_permit) r->playout->permit, r->playout);

have_move:
	if (!group_at(b, coord)) {
		/* This was suicide. Just pass. */
		/* XXX: We should check for non-suicide alternatives. */
		return coord_pass();
	}

	return coord_copy(coord);
}
Beispiel #9
0
void
showSubst (Term t)
{
#ifdef DEBUG
  if (!DEBUGL (5))
    return;

  indent ();
  eprintf ("Substituting ");
  termPrint (t);
  eprintf (", typed ");
  termlistPrint (t->stype);
  if (realTermLeaf (t->subst))
    {
      eprintf ("->");
      termlistPrint (t->subst->stype);
    }
  else
    {
      eprintf (", composite term");
    }
  if (t->type != VARIABLE)
    {
      eprintf (" (bound roleconstant)");
    }
  eprintf ("\n");
#endif
}
Beispiel #10
0
/* Wait for at least one new reply. Return when at least
 * min_replies slaves have already replied, or when the
 * given absolute time is passed.
 * The replies are returned in gtp_replies[0..reply_count-1]
 * slave_lock is held on entry and on return. */
void
get_replies(double time_limit, int min_replies)
{
	for (;;) {
		if (reply_count > 0) {
			struct timespec ts;
			double sec;
			ts.tv_nsec = (int)(modf(time_limit, &sec)*1000000000.0);
			ts.tv_sec = (int)sec;
			pthread_cond_timedwait(&reply_cond, &slave_lock, &ts);
		} else {
			pthread_cond_wait(&reply_cond, &slave_lock);
		}
		if (reply_count == 0) continue;
		if (reply_count >= min_replies || reply_count >= active_slaves) return;
		if (time_now() >= time_limit) break;
	}
	if (DEBUGL(1)) {
		char buf[1024];
		snprintf(buf, sizeof(buf),
			 "get_replies timeout %.3f >= %.3f, replies %d < min %d, active %d\n",
			 time_now() - start_time, time_limit - start_time,
			 reply_count, min_replies, active_slaves);
		logline(NULL, "? ", buf);
	}
	assert(reply_count > 0);
}
Beispiel #11
0
static void
board_print_test(int level, struct board *b)
{
	if (!DEBUGL(level) || board_printed)
		return;
	board_print(b, stderr);
	board_printed = true;
}
Beispiel #12
0
int ausb_bulk_write(ausb_dev_handle *ah, int ep,
		    char *bytes, int size,
		    int timeout) {
  DEBUGL(ah, "Write:", bytes, size);

  if (ah->bulkWriteFn)
    return ah->bulkWriteFn(ah, ep, bytes, size, timeout);
  return -1;
}
Beispiel #13
0
/* Send the gtp command to_send and get a reply from the slave machine.
 * Write the reply in buf which must have at least CMDS_SIZE bytes.
 * If *bin_size > 0, send bin_buf after the gtp command.
 * Return any binary reply in bin_buf and set its size in bin_size.
 * bin_buf is private to the slave and need not be copied.
 * Return the gtp command id, or -1 if error.
 * slave_lock is held on both entry and exit of this function. */
static int
send_command(char *to_send, void *bin_buf, int *bin_size,
	     FILE *f, struct slave_state *sstate, char *buf)
{
	assert(to_send && gtp_cmd && bin_buf && bin_size);
	strncpy(buf, to_send, CMDS_SIZE);
	bool resend = to_send != gtp_cmd;

	pthread_mutex_unlock(&slave_lock);

	if (DEBUGL(1) && resend)
		logline(&sstate->client, "? ",
			to_send == gtp_cmds ? "resend all\n" : "partial resend\n");

	double start = time_now();
	fputs(buf, f);

	if (*bin_size)
		fwrite(bin_buf, 1, *bin_size, f);
	fflush(f);

	if (DEBUGV(strchr(buf, '@'), 2)) {
		double ms = (time_now() - start) * 1000.0;
		if (!DEBUGL(3)) {
			char *s = strchr(buf, '\n');
			if (s) s[1] = '\0';
		}
		logline(&sstate->client, ">>", buf);
		if (*bin_size) {
			char b[1024];
			snprintf(b, sizeof(b),
				 "sent cmd %d+%d bytes in %.4fms\n",
				 (int)strlen(buf), *bin_size, ms);
			logline(&sstate->client, "= ", b);
		}
	}

	/* Reuse the buffers for the reply. */
	*bin_size = sstate->max_buf_size;
	int reply_id = get_reply(f, sstate->client, buf, bin_buf, bin_size);

	pthread_mutex_lock(&slave_lock);
	return reply_id;
}
Beispiel #14
0
bool
is_border_ladder(Board *b, Coord coord, Stone lcolor)
{
	int x = X(coord), y = Y(coord);

  /*
	if (DEBUGL(5))
		fprintf(stderr, "border ladder\n");
  */
	/* Direction along border; xd is horiz. border, yd vertical. */
	int xd = 0, yd = 0;
	if (b->_infos[L(coord)].color == S_OFFBOARD || b->_infos[R(coord)].color == S_OFFBOARD)
		yd = 1;
	else
		xd = 1;
	/* Direction from the border; -1 is above/left, 1 is below/right. */
	int dd = (board_atxy(b, x + yd, y + xd) == S_OFFBOARD) ? 1 : -1;
	if (DEBUGL(6))
		fprintf(stderr, "xd %d yd %d dd %d\n", xd, yd, dd);
	/* | ? ?
	 * | . O #
	 * | c X #
	 * | . O #
	 * | ? ?   */
	/* This is normally caught, unless we have friends both above
	 * and below... */
	if (board_atxy(b, x + xd * 2, y + yd * 2) == lcolor
	    && board_atxy(b, x - xd * 2, y - yd * 2) == lcolor)
		return false;

	/* ...or can't block where we need because of shortage
	 * of liberties. */
	group_t g1 = group_atxy(b, x + xd - yd * dd, y + yd - xd * dd);
	int libs1 = b->_groups[g1].liberties;
	group_t g2 = group_atxy(b, x - xd - yd * dd, y - yd - xd * dd);
	int libs2 = b->_groups[g2].liberties;
  /*
	if (DEBUGL(6))
		fprintf(stderr, "libs1 %d libs2 %d\n", libs1, libs2);
  */
	/* Already in atari? */
	if (libs1 < 2 || libs2 < 2)
		return false;

  Coord libs_g1[2], libs_g2[2];
  get_nlibs_of_group(b, g1, 2, libs_g1);
  get_nlibs_of_group(b, g2, 2, libs_g2);

	/* Would be self-atari? */
	if (libs1 < 3 && (board_atxy(b, x + xd * 2, y + yd * 2) != S_NONE || NEIGHBOR4(libs_g1[0], libs_g1[1])))
		return false;
	if (libs2 < 3 && (board_atxy(b, x - xd * 2, y - yd * 2) != S_NONE || NEIGHBOR4(libs_g2[0], libs_g2[1])))
		return false;
	return true;
}
Beispiel #15
0
/* Thread sending gtp commands to one slave machine, and
 * reading replies. If a slave machine dies, this thread waits
 * for a connection from another slave.
 * The large buffers are allocated only once we get a first
 * connection, to avoid wasting memory if max_slaves is too large.
 * We do not invalidate the received buffers if a slave disconnects;
 * they are still useful for other slaves. */
static void *
slave_thread(void *arg)
{
	struct slave_state sstate = default_sstate;
	sstate.thread_id = (long)arg;

	assert(sstate.slave_sock >= 0);
	char reply_buf[CMDS_SIZE];
	bool resend = false;

	for (;;) {
		/* Wait for a connection from any slave. */
		struct in_addr client;
		int conn = open_server_connection(sstate.slave_sock, &client);

		FILE *f = fdopen(conn, "r+");
		if (DEBUGL(2)) {
			snprintf(reply_buf, sizeof(reply_buf),
				 "new slave, id %d\n", sstate.thread_id);
			logline(&client, "= ", reply_buf);
		}
		if (!is_pachi_slave(f, &client)) continue;

		if (!resend) slave_state_alloc(&sstate);
		sstate.client = client;

		pthread_mutex_lock(&slave_lock);
		active_slaves++;
		slave_loop(f, reply_buf, &sstate, resend);

		assert(active_slaves > 0);
		active_slaves--;
		// Unblock main thread if it was waiting for this slave.
		pthread_cond_signal(&reply_cond);
		pthread_mutex_unlock(&slave_lock);

		resend = true;
		if (DEBUGL(2))
			logline(&client, "= ", "lost slave\n");
		fclose(f);
	}
}
Beispiel #16
0
void
prob_dict_init(char *filename, pattern_config_t *pc)
{
	assert(!prob_dict);
	if (!filename)  filename = "patterns_mm.gamma";
	FILE *f = fopen_data_file(filename, "r");
	if (!f) {
		if (DEBUGL(1))  fprintf(stderr, "%s not found, will not use mm patterns.\n", filename);
		return;
	}

	prob_dict = calloc2(1, prob_dict_t);
	prob_dict->table = calloc2(spat_dict->nspatials + 1, pattern_prob_t*);

	int i = 0;
	char sbuf[1024];
	while (fgets(sbuf, sizeof(sbuf), f)) {
		pattern_prob_t *pb = calloc2(1, pattern_prob_t);
		//int c, o;

		char *buf = sbuf;
		if (buf[0] == '#') continue;
		while (isspace(*buf)) buf++;
		float gamma = strtof(buf, &buf);
		pb->gamma = gamma;
		while (isspace(*buf)) buf++;
		str2pattern(buf, &pb->p);
		assert(pb->p.n == 1);				/* One gamma per feature, please ! */

		uint32_t spi = feature2spatial(pc, &pb->p.f[0]);
		assert(spi <= spat_dict->nspatials);		/* Bad patterns.spat / patterns.prob ? */
		if (feature_has_gamma(pc, &pb->p.f[0]))
			die("%s: multiple gammas for feature %s\n", filename, pattern2sstr(&pb->p));
		pb->next = prob_dict->table[spi];
		prob_dict->table[spi] = pb;

		i++;
	}

	fclose(f);
	if (DEBUGL(1))  fprintf(stderr, "Loaded %d gammas.\n", i);
}
Beispiel #17
0
/* Clear the receive queue. The buffer pointers do not have to be cleared
 * here, this is done as each buffer is recycled.
 * slave_lock is held on both entry and exit of this function. */
void
clear_receive_queue(void)
{
	if (DEBUGL(3)) {
		char buf[1024];
		snprintf(buf, sizeof(buf), "clear queue, old length %d age %d\n",
			 queue_length, queue_age);
		logline(NULL, "? ", buf);
	}
	queue_length = 0;
	queue_age++;
}
Beispiel #18
0
static bool
can_play_on_lib(struct board *b, group_t g, enum stone to_play)
{
    coord_t capture = board_group_info(b, g).lib[0];
    if (DEBUGL(6))
        fprintf(stderr, "can capture group %d (%s)?\n",
                g, coord2sstr(capture, b));
    /* Does playing on the liberty usefully capture the group? */
    if (board_is_valid_play(b, to_play, capture)
            && !is_bad_selfatari(b, to_play, capture))
        return true;

    return false;
}
Beispiel #19
0
int ausb_bulk_read(ausb_dev_handle *ah, int ep,
		   char *bytes, int size,
		   int timeout) {
  if (ah->bulkReadFn) {
    int rv;

    DEBUGP(ah, "Reading up to %d bytes", size);
    rv=ah->bulkReadFn(ah, ep, bytes, size, timeout);
    if (rv>=0) {
      DEBUGL(ah, "Read:", bytes, rv);
    }
    return rv;
  }
  return -1;
}
Beispiel #20
0
/* Get a reply to one gtp command. Return the gtp command id,
 * or -1 if error. reply must have at least CMDS_SIZE bytes.
 * The ascii reply ends with an empty line; if the first line
 * contains "@size", a binary reply of size bytes follows the
 * empty line. @size is not standard gtp, it is only used
 * internally by Pachi for the genmoves command; it must be the
 * last parameter on the line.
 * *bin_size is the maximum size upon entry, actual size on return.
 * slave_lock is not held on either entry or exit of this function. */
static int
get_reply(FILE *f, struct in_addr client, char *reply, void *bin_reply, int *bin_size)
{
	double start = time_now();

	int reply_id = -1;
	*reply = '\0';
	if (!fgets(reply, CMDS_SIZE, f)) return -1;

	/* Check for binary reply. */
	char *s = strchr(reply, '@');
	int size = 0;
	if (s) size = atoi(s+1);
	assert(size <= *bin_size);
	*bin_size = size;

	if (DEBUGV(s, 2))
		logline(&client, "<<", reply);
	if ((*reply == '=' || *reply == '?') && isdigit(reply[1]))
		reply_id = atoi(reply+1);

	/* Read the rest of the ascii reply */
	char *line = reply + strlen(reply);
	while (fgets(line, reply + CMDS_SIZE - line, f) && *line != '\n') {
		if (DEBUGL(3))
			logline(&client, "<<", line);
		line += strlen(line);
	}
	if (*line != '\n') return -1;

	/* Read the binary reply if any. */
	int len;
	while (size && (len = fread(bin_reply, 1, size, f)) > 0) {
		bin_reply = (char *)bin_reply + len;
		size -= len;
	}
	if (*bin_size && DEBUGVV(2)) {
		char buf[1024];
		snprintf(buf, sizeof(buf), "read reply %d+%d bytes in %.4fms\n",
			 (int)strlen(reply), *bin_size,
			 (time_now() - start)*1000);
		logline(&client, "= ", buf);
	}
	return size ? -1 : reply_id;
}
Beispiel #21
0
static floating_t
komi_by_score(struct uct_dynkomi *d, struct board *b, struct tree *tree, enum stone color)
{
	struct dynkomi_adaptive *a = d->data;
	if (d->score.playouts < TRUSTWORTHY_KOMI_PLAYOUTS)
		return tree->extra_komi;

	struct move_stats score = d->score;
	/* Almost-reset tree->score to gather fresh stats. */
	d->score.playouts = 1;

	/* Look at average score and push extra_komi in that direction. */
	floating_t p = a->adapter(d, b);
	p = a->adapt_base + p * (1 - a->adapt_base);
	if (p > 0.9) p = 0.9; // don't get too eager!
	floating_t extra_komi = tree->extra_komi + p * score.value;
	if (DEBUGL(3))
		fprintf(stderr, "mC += %f * %f\n", p, score.value);
	return extra_komi;
}
Beispiel #22
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 =)
}
Beispiel #23
0
int main(int argc, char *argv[])
{
	enum engine_id engine = E_UCT;
	struct time_info ti_default = { .period = TT_NULL };
	char *testfile = NULL;
	char *gtp_port = NULL;
	char *log_port = NULL;
	int gtp_sock = -1;
	char *chatfile = NULL;
	char *fbookfile = NULL;
	char *ruleset = NULL;

	seed = time(NULL) ^ getpid();

	int opt;
	while ((opt = getopt(argc, argv, "c:e:d:Df:g:l:r:s:t:u:")) != -1) {
		switch (opt) {
			case 'c':
				chatfile = strdup(optarg);
				break;
			case 'e':
				if (!strcasecmp(optarg, "random")) {
					engine = E_RANDOM;
				} else if (!strcasecmp(optarg, "replay")) {
					engine = E_REPLAY;
				} else if (!strcasecmp(optarg, "montecarlo")) {
					engine = E_MONTECARLO;
				} else if (!strcasecmp(optarg, "uct")) {
					engine = E_UCT;
				} else if (!strcasecmp(optarg, "distributed")) {
					engine = E_DISTRIBUTED;
				} else if (!strcasecmp(optarg, "patternscan")) {
					engine = E_PATTERNSCAN;
				} else if (!strcasecmp(optarg, "patternplay")) {
					engine = E_PATTERNPLAY;
				} else if (!strcasecmp(optarg, "joseki")) {
					engine = E_JOSEKI;
				} else {
					fprintf(stderr, "%s: Invalid -e argument %s\n", argv[0], optarg);
					exit(1);
				}
				break;
			case 'd':
				debug_level = atoi(optarg);
				break;
			case 'D':
				debug_boardprint = false;
				break;
			case 'f':
				fbookfile = strdup(optarg);
				break;
			case 'g':
				gtp_port = strdup(optarg);
				break;
			case 'l':
				log_port = strdup(optarg);
				break;
			case 'r':
				ruleset = strdup(optarg);
				break;
			case 's':
				seed = atoi(optarg);
				break;
			case 't':
				/* Time settings to follow; if specified,
				 * GTP time information is ignored. Useful
				 * e.g. when you want to force your bot to
				 * play weaker while giving the opponent
				 * reasonable time to play, or force play
				 * by number of simulations in timed games. */
				/* Please see timeinfo.h:time_parse()
				 * description for syntax details. */
				if (!time_parse(&ti_default, optarg)) {
					fprintf(stderr, "%s: Invalid -t argument %s\n", argv[0], optarg);
					exit(1);
				}
				ti_default.ignore_gtp = true;
				assert(ti_default.period != TT_NULL);
				break;
			case 'u':
				testfile = strdup(optarg);
				break;
			default: /* '?' */
				usage(argv[0]);
				exit(1);
		}
	}

	if (log_port)
		open_log_port(log_port);

	fast_srandom(seed);
	if (DEBUGL(0))
		fprintf(stderr, "Random seed: %d\n", seed);

	struct board *b = board_init(fbookfile);
	if (ruleset) {
		if (!board_set_rules(b, ruleset)) {
			fprintf(stderr, "Unknown ruleset: %s\n", ruleset);
			exit(1);
		}
	}

	struct time_info ti[S_MAX];
	ti[S_BLACK] = ti_default;
	ti[S_WHITE] = ti_default;

	chat_init(chatfile);

	char *e_arg = NULL;
	if (optind < argc)
		e_arg = argv[optind];
	struct engine *e = init_engine(engine, e_arg, b);

	if (testfile) {
		unittest(testfile);
		return 0;
	}

	if (gtp_port) {
		open_gtp_connection(&gtp_sock, gtp_port);
	}

	for (;;) {
		char buf[4096];
		while (fgets(buf, 4096, stdin)) {
			if (DEBUGL(1))
				fprintf(stderr, "IN: %s", buf);

			enum parse_code c = gtp_parse(b, e, ti, buf);
			if (c == P_ENGINE_RESET) {
				ti[S_BLACK] = ti_default;
				ti[S_WHITE] = ti_default;
				if (!e->keep_on_clear) {
					b->es = NULL;
					done_engine(e);
					e = init_engine(engine, e_arg, b);
				}
			} else if (c == P_UNKNOWN_COMMAND && gtp_port) {
				/* The gtp command is a weak identity check,
				 * close the connection with a wrong peer. */
				break;
			}
		}
		if (!gtp_port) break;
		open_gtp_connection(&gtp_sock, gtp_port);
	}
	done_engine(e);
	chat_done();
	free(testfile);
	free(gtp_port);
	free(log_port);
	free(chatfile);
	free(fbookfile);
	free(ruleset);
	return 0;
}
Beispiel #24
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 =)
}
Beispiel #25
0
static floating_t
komi_by_value(struct uct_dynkomi *d, struct board *b, struct tree *tree, enum stone color)
{
	struct dynkomi_adaptive *a = d->data;
	if (d->value.playouts < TRUSTWORTHY_KOMI_PLAYOUTS)
		return tree->extra_komi;

	struct move_stats value = d->value;
	/* Almost-reset tree->value to gather fresh stats. */
	d->value.playouts = 1;
	/* Correct color POV. */
	if (color == S_WHITE)
		value.value = 1 - value.value;

	/* We have three "value zones":
	 * red zone | yellow zone | green zone
	 *        ~45%           ~60%
	 * red zone: reduce komi
	 * yellow zone: do not touch komi
	 * green zone: enlage komi.
	 *
	 * Also, at some point komi will be tuned in such way
	 * that it will be in green zone but increasing it will
	 * be unfeasible. Thus, we have a _ratchet_ - we will
	 * remember the last komi that has put us into the
	 * red zone, and not use it or go over it. We use the
	 * ratchet only when giving extra komi, we always want
	 * to try to reduce extra komi we take.
	 *
	 * TODO: Make the ratchet expire after a while. */

	/* We use komi_by_color() first to normalize komi
	 * additions/subtractions, then apply it again on
	 * return value to restore original komi parity. */
	/* Positive extra_komi means that we are _giving_
	 * komi (winning), negative extra_komi is _taking_
	 * komi (losing). */
	floating_t extra_komi = komi_by_color(tree->extra_komi, color);
	int score_step_red = -a->score_step;
	int score_step_green = a->score_step;

	if (a->score_step_byavg != 0) {
		struct move_stats score = d->score;
		/* Almost-reset tree->score to gather fresh stats. */
		d->score.playouts = 1;
		/* Correct color POV. */
		if (color == S_WHITE)
			score.value = - score.value;
		if (score.value > 0)
			score_step_green = round(score.value * a->score_step_byavg);
		else
			score_step_red = round(-score.value * a->score_step_byavg);
		if (score_step_green < 0 || score_step_red > 0) {
			/* The steps are in bad direction - keep still. */
			return komi_by_color(extra_komi, color);
		}
	}

	/* Wear out the ratchet. */
	if (a->use_komi_ratchet && a->komi_ratchet_maxage > 0) {
		a->komi_ratchet_age += value.playouts;
		if (a->komi_ratchet_age > a->komi_ratchet_maxage) {
			a->komi_ratchet = 1000;
			a->komi_ratchet_age = 0;
		}
	}

	if (value.value < a->zone_red) {
		/* Red zone. Take extra komi. */
		if (DEBUGL(3))
			fprintf(stderr, "[red] %f, step %d | komi ratchet %f age %d/%d -> %f\n",
				value.value, score_step_red, a->komi_ratchet, a->komi_ratchet_age, a->komi_ratchet_maxage, extra_komi);
		if (a->losing_komi_ratchet || extra_komi > 0) {
			a->komi_ratchet = extra_komi;
			a->komi_ratchet_age = 0;
		}
		extra_komi += score_step_red;
		return komi_by_color(extra_komi, color);

	} else if (value.value < a->zone_green) {
		/* Yellow zone, do nothing. */
		return komi_by_color(extra_komi, color);

	} else {
		/* Green zone. Give extra komi. */
		if (DEBUGL(3))
			fprintf(stderr, "[green] %f, step %d | komi ratchet %f age %d/%d\n",
				value.value, score_step_green, a->komi_ratchet, a->komi_ratchet_age, a->komi_ratchet_maxage);
		extra_komi += score_step_green;
		if (a->use_komi_ratchet && extra_komi >= a->komi_ratchet)
			extra_komi = a->komi_ratchet - 1;
		return komi_by_color(extra_komi, color);
	}
}
Beispiel #26
0
struct fbook *
fbook_init(char *filename, struct board *b)
{
	if (fbcache && fbcache->bsize == board_size(b)
	    && fbcache->handicap == b->handicap)
		return fbcache;

	FILE *f = fopen(filename, "r");
	if (!f) {
		perror(filename);
		return NULL;
	}

	struct fbook *fbook = calloc(1, sizeof(*fbook));
	fbook->bsize = board_size(b);
	fbook->handicap = b->handicap;
	/* We do not set handicap=1 in case of too low komi on purpose;
	 * we want to go with the no-handicap fbook for now. */
	for (int i = 0; i < 1<<fbook_hash_bits; i++)
		fbook->moves[i] = pass;

	if (DEBUGL(1))
		fprintf(stderr, "Loading opening fbook %s...\n", filename);

	/* Scratch board where we lay out the sequence;
	 * one for each transposition. */
	struct board *bs[8];
	for (int i = 0; i < 8; i++) {
		bs[i] = board_init(NULL);
		board_resize(bs[i], fbook->bsize - 2);
	}

	char linebuf[1024];
	while (fgets(linebuf, sizeof(linebuf), f)) {
		char *line = linebuf;
		linebuf[strlen(linebuf) - 1] = 0; // chop

		/* Format of line is:
		 * BSIZE COORD COORD COORD... | COORD
		 * BSIZE/HANDI COORD COORD COORD... | COORD
		 * We descend up to |, then add the new node
		 * with value minimax(1000), forcing UCT to
		 * always pick that node immediately. */
		int bsize = strtol(line, &line, 10);
		if (bsize != fbook->bsize - 2)
			continue;
		int handi = 0;
		if (*line == '/') {
			line++;
			handi = strtol(line, &line, 10);
		}
		if (handi != fbook->handicap)
			continue;
		while (isspace(*line)) line++;

		for (int i = 0; i < 8; i++) {
			board_clear(bs[i]);
			bs[i]->last_move.color = S_WHITE;
		}

		while (*line != '|') {
			coord_t *c = str2coord(line, fbook->bsize);

			for (int i = 0; i < 8; i++) {
				coord_t coord = coord_transform(b, *c, i);
				struct move m = { .coord = coord, .color = stone_other(bs[i]->last_move.color) };
				int ret = board_play(bs[i], &m);
				assert(ret >= 0);
			}

			coord_done(c);
			while (!isspace(*line)) line++;
			while (isspace(*line)) line++;
		}

		line++;
		while (isspace(*line)) line++;

		/* In case of multiple candidates, pick one with
		 * exponentially decreasing likelihood. */
		while (strchr(line, ' ') && fast_random(2)) {
			line = strchr(line, ' ');
			while (isspace(*line)) line++;
			// fprintf(stderr, "<%s> skip to %s\n", linebuf, line);
		}

		coord_t *c = str2coord(line, fbook->bsize);
		for (int i = 0; i < 8; i++) {
			coord_t coord = coord_transform(b, *c, i);
#if 0
			char conflict = is_pass(fbook->moves[bs[i]->hash & fbook_hash_mask]) ? '+' : 'C';
			if (conflict == 'C')
				for (int j = 0; j < i; j++)
					if (bs[i]->hash == bs[j]->hash)
						conflict = '+';
			if (conflict == 'C') {
				hash_t hi = bs[i]->hash;
				while (!is_pass(fbook->moves[hi & fbook_hash_mask]) && fbook->hashes[hi & fbook_hash_mask] != bs[i]->hash)
					hi++;
				if (fbook->hashes[hi & fbook_hash_mask] == bs[i]->hash)
					hi = 'c';
			}
			fprintf(stderr, "%c %"PRIhash":%"PRIhash" (<%d> %s)\n", conflict,
			        bs[i]->hash & fbook_hash_mask, bs[i]->hash, i, linebuf);
#endif
			hash_t hi = bs[i]->hash;
			while (!is_pass(fbook->moves[hi & fbook_hash_mask]) && fbook->hashes[hi & fbook_hash_mask] != bs[i]->hash)
				hi++;
			fbook->moves[hi & fbook_hash_mask] = coord;
			fbook->hashes[hi & fbook_hash_mask] = bs[i]->hash;
			fbook->movecnt++;
		}
		coord_done(c);
	}

	for (int i = 0; i < 8; i++) {
		board_done(bs[i]);
	}

	fclose(f);

	if (!fbook->movecnt) {
		/* Empty book is not worth the hassle. */
		fbook_done(fbook);
		return NULL;
	}

	struct fbook *fbold = fbcache;
	fbcache = fbook;
	if (fbold)
		fbook_done(fbold);

	return fbook;
}

void fbook_done(struct fbook *fbook)
{
	if (fbook != fbcache)
		free(fbook);
}
Beispiel #27
0
static coord_t *
montecarlo_genmove(struct engine *e, struct board *b, struct time_info *ti, enum stone color, bool pass_all_alive)
{
	struct montecarlo *mc = e->data;

	if (ti->dim == TD_WALLTIME) {
		fprintf(stderr, "Warning: TD_WALLTIME time mode not supported, resetting to defaults.\n");
		ti->period = TT_NULL;
	}
	if (ti->period == TT_NULL) {
		ti->period = TT_MOVE;
		ti->dim = TD_GAMES;
		ti->len.games = MC_GAMES;
	}
	struct time_stop stop;
	time_stop_conditions(ti, b, 20, 40, 3.0, &stop);

	/* resign when the hope for win vanishes */
	coord_t top_coord = resign;
	floating_t top_ratio = mc->resign_ratio;

	/* We use [0] for pass. Normally, this is an inaccessible corner
	 * of board margin. */
	struct move_stat moves[board_size2(b)];
	memset(moves, 0, sizeof(moves));

	int losses = 0;
	int i, superko = 0, good_games = 0;
	for (i = 0; i < stop.desired.playouts; i++) {
		assert(!b->superko_violation);

		struct board b2;
		board_copy(&b2, b);

		coord_t coord;
		board_play_random(&b2, color, &coord, NULL, NULL);
		if (!is_pass(coord) && !group_at(&b2, coord)) {
			/* Multi-stone suicide. We play chinese rules,
			 * so we can't consider this. (Note that we
			 * unfortunately still consider this in playouts.) */
			if (DEBUGL(4)) {
				fprintf(stderr, "SUICIDE DETECTED at %d,%d:\n", coord_x(coord, b), coord_y(coord, b));
				board_print(b, stderr);
			}
			continue;
		}

		if (DEBUGL(3))
			fprintf(stderr, "[%d,%d color %d] playing random game\n", coord_x(coord, b), coord_y(coord, b), color);

		struct playout_setup ps = { .gamelen = mc->gamelen };
		int result = play_random_game(&ps, &b2, color, NULL, NULL, mc->playout);

		board_done_noalloc(&b2);

		if (result == 0) {
			/* Superko. We just ignore this playout.
			 * And play again. */
			if (unlikely(superko > 2 * stop.desired.playouts)) {
				/* Uhh. Triple ko, or something? */
				if (MCDEBUGL(0))
					fprintf(stderr, "SUPERKO LOOP. I will pass. Did we hit triple ko?\n");
				goto pass_wins;
			}
			/* This playout didn't count; we should not
			 * disadvantage moves that lead to a superko.
			 * And it is supposed to be rare. */
			i--, superko++;
			continue;
		}

		if (MCDEBUGL(3))
			fprintf(stderr, "\tresult for other player: %d\n", result);

		int pos = is_pass(coord) ? 0 : coord;

		good_games++;
		moves[pos].games++;

		losses += result > 0;
		moves[pos].wins += 1 - (result > 0);

		if (unlikely(!losses && i == mc->loss_threshold)) {
			/* We played out many games and didn't lose once yet.
			 * This game is over. */
			break;
		}
	}

	if (!good_games) {
		/* No moves to try??? */
		if (MCDEBUGL(0)) {
			fprintf(stderr, "OUT OF MOVES! I will pass. But how did this happen?\n");
			board_print(b, stderr);
		}
pass_wins:
		top_coord = pass; top_ratio = 0.5;
		goto move_found;
	}

	foreach_point(b) {
		if (b->moves < 3) {
			/* Simple heuristic: avoid opening too low. Do not
			 * play on second or first line as first white or
			 * first two black moves.*/
			if (coord_x(c, b) < 3 || coord_x(c, b) > board_size(b) - 4
			    || coord_y(c, b) < 3 || coord_y(c, b) > board_size(b) - 4)
				continue;
		}

		floating_t ratio = (floating_t) moves[c].wins / moves[c].games;
		/* Since pass is [0,0], we will pass only when we have nothing
		 * better to do. */
		if (ratio >= top_ratio) {
			top_ratio = ratio;
			top_coord = c == 0 ? pass : c;
		}
	} foreach_point_end;

	if (MCDEBUGL(2)) {
		board_stats_print(b, moves, stderr);
	}

move_found:
	if (MCDEBUGL(1))
		fprintf(stderr, "*** WINNER is %d,%d with score %1.4f (%d games, %d superko)\n", coord_x(top_coord, b), coord_y(top_coord, b), top_ratio, i, superko);

	return coord_copy(top_coord);
}

static void
montecarlo_done(struct engine *e)
{
	struct montecarlo *mc = e->data;
	playout_policy_done(mc->playout);
	joseki_done(mc->jdict);
}

struct montecarlo *
montecarlo_state_init(char *arg, struct board *b)
{
	struct montecarlo *mc = calloc2(1, sizeof(struct montecarlo));

	mc->debug_level = 1;
	mc->gamelen = MC_GAMELEN;
	mc->jdict = joseki_load(b->size);

	if (arg) {
		char *optspec, *next = arg;
		while (*next) {
			optspec = next;
			next += strcspn(next, ",");
			if (*next) { *next++ = 0; } else { *next = 0; }

			char *optname = optspec;
			char *optval = strchr(optspec, '=');
			if (optval) *optval++ = 0;

			if (!strcasecmp(optname, "debug")) {
				if (optval)
					mc->debug_level = atoi(optval);
				else
					mc->debug_level++;
			} else if (!strcasecmp(optname, "gamelen") && optval) {
				mc->gamelen = atoi(optval);
			} else if (!strcasecmp(optname, "playout") && optval) {
				char *playoutarg = strchr(optval, ':');
				if (playoutarg)
					*playoutarg++ = 0;
				if (!strcasecmp(optval, "moggy")) {
					mc->playout = playout_moggy_init(playoutarg, b, mc->jdict);
				} else if (!strcasecmp(optval, "light")) {
					mc->playout = playout_light_init(playoutarg, b);
				} else {
					fprintf(stderr, "MonteCarlo: Invalid playout policy %s\n", optval);
				}
			} else {
				fprintf(stderr, "MonteCarlo: Invalid engine argument %s or missing value\n", optname);
			}
		}
	}

	if (!mc->playout)
		mc->playout = playout_light_init(NULL, b);
	mc->playout->debug_level = mc->debug_level;

	mc->resign_ratio = 0.1; /* Resign when most games are lost. */
	mc->loss_threshold = 5000; /* Stop reading if no loss encountered in first 5000 games. */

	return mc;
}


struct engine *
engine_montecarlo_init(char *arg, struct board *b)
{
	struct montecarlo *mc = montecarlo_state_init(arg, b);
	struct engine *e = calloc2(1, sizeof(struct engine));
	e->name = "MonteCarlo";
	e->comment = "I'm playing in Monte Carlo. When we both pass, I will consider all the stones on the board alive. If you are reading this, write 'yes'. Please bear with me at the game end, I need to fill the whole board; if you help me, we will both be happier. Filling the board will not lose points (NZ rules).";
	e->genmove = montecarlo_genmove;
	e->done = montecarlo_done;
	e->data = mc;

	return e;
}
Beispiel #28
0
/* Pre-process time_info for search control and sets the desired stopping conditions. */
void
time_stop_conditions(struct time_info *ti, struct board *b, int fuseki_end, int yose_start,
		     floating_t max_maintime_ratio, struct time_stop *stop)
{
	/* We must have _some_ limits by now, be it random default values! */
	assert(ti->period != TT_NULL);

	/* Special-case limit by number of simulations. */
	if (ti->dim == TD_GAMES) {
		if (ti->period == TT_TOTAL) {
			ti->period = TT_MOVE;
			ti->len.games /= board_estimated_moves_left(b);
		}

		stop->desired.playouts = ti->len.games;
		/* We force worst == desired, so note that we will NOT loop
		 * until best == winner. */
		stop->worst.playouts = ti->len.games;
		return;
	}

	assert(ti->dim == TD_WALLTIME);


	/* Minimum net lag (seconds) to be reserved in the time for move. */
	double net_lag = MAX_NET_LAG;
	net_lag += time_now() - ti->len.t.timer_start;
	// TODO: keep statistics to get good estimate of lag not just current move


	if (ti->period == TT_TOTAL && time_in_byoyomi(ti)) {
		/* Technically, we are still in main time, but we can
		 * effectively switch to byoyomi scheduling since we
		 * have less time available than one byoyomi move takes. */
		ti->period = TT_MOVE;
	}


	if (ti->period == TT_MOVE) {
		/* We are in byoyomi, or almost! */

		/* The period can still include some tiny remnant of main
		 * time if we are just switching to byoyomi. */
		double period_len = ti->len.t.byoyomi_time + ti->len.t.main_time;

		stop->worst.time = period_len;
		assert(ti->len.t.byoyomi_stones > 0);
		stop->desired.time = period_len / ti->len.t.byoyomi_stones;

		/* Use a larger safety margin if we risk losing on time on
		 * this move; it makes no sense to have 30s byoyomi and wait
		 * until 28s to play our move). */
		if (stop->desired.time >= period_len - net_lag) {
			double safe_margin = RESERVED_BYOYOMI_PERCENT * stop->desired.time / 100;
			if (safe_margin > net_lag)
				net_lag = safe_margin;
		}

		/* Make recommended_old == average(recommended_new, max) */
		double worst_time = stop->desired.time * MAX_BYOYOMI_TIME_RATIO;
		if (worst_time < stop->worst.time)
			stop->worst.time = worst_time;
		stop->desired.time *= (2 - MAX_BYOYOMI_TIME_RATIO);

	} else { assert(ti->period == TT_TOTAL);
		/* We are in main time. */

		assert(ti->len.t.main_time > 0);
		/* Set worst.time to all available remaining time, to be spread
		 * over returned number of moves. */
		int moves_left = time_stop_set_remaining(ti, b, net_lag, stop);

		/* Allocate even slice of the remaining time for next move. */
		stop->desired.time = stop->worst.time / moves_left;
		assert(stop->desired.time > 0 && stop->worst.time > 0);
		assert(stop->desired.time <= stop->worst.time + 0.001);

		/* Furthermore, tweak the slice based on the game phase. */
		time_stop_phase_adjust(b, fuseki_end, yose_start, stop);

		/* Put final upper bound on maximal time spent on the move.
		 * Keep enough time for sudden death (or near SD) games. */
		double worst_time = stop->desired.time;
		if (ti->len.t.byoyomi_time_max > ti->len.t.byoyomi_stones_max) {
			worst_time *= max_maintime_ratio;
		} else {
			worst_time *= MAX_SUDDEN_DEATH_RATIO;
		}
		if (worst_time < stop->worst.time)
			stop->worst.time = worst_time;
		if (stop->desired.time > stop->worst.time)
			stop->desired.time = stop->worst.time;
	}

	if (DEBUGL(1))
		fprintf(stderr, "desired %0.2f, worst %0.2f, clock [%d] %0.2f + %0.2f/%d*%d, lag %0.2f\n",
			stop->desired.time, stop->worst.time,
			ti->dim, ti->len.t.main_time,
			ti->len.t.byoyomi_time, ti->len.t.byoyomi_stones,
			ti->len.t.byoyomi_periods, net_lag);

	/* Account for lag. */
	lag_adjust(&stop->desired.time, net_lag);
	lag_adjust(&stop->worst.time, net_lag);
}