pachi_py/pachi/joseki/joseki.c (162 lines of code) (raw):
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include "board.h"
#include "debug.h"
#include "engine.h"
#include "move.h"
#include "joseki/joseki.h"
#include "joseki/base.h"
/* Internal engine state. */
struct joseki_engine {
int debug_level;
bool discard;
int size;
struct joseki_dict *jdict;
struct board *b[16]; // boards with reversed color, mirrored and rotated
};
/* We will record the joseki positions into incrementally-built
* jdict->patterns[]. */
static char *
joseki_play(struct engine *e, struct board *b, struct move *m, char *enginearg)
{
struct joseki_engine *j = e->data;
if (!b->moves) {
/* New game, reset state. */
j->size = board_size(b);
if (j->jdict)
assert(j->size == j->jdict->bsize);
else
j->jdict = joseki_init(j->size);
j->discard = false;
for (int i = 0; i < 16; i++) {
board_resize(j->b[i], j->size - 2);
board_clear(j->b[i]);
}
}
//printf("%s %d\n", coord2sstr(m->coord, b), coord_quadrant(m->coord, b));
assert(!is_resign(m->coord));
if (is_pass(m->coord))
return NULL;
/* Ignore moves in different quadrants. */
if (coord_quadrant(m->coord, b) > 0)
return NULL;
if (coord_x(m->coord, b) == board_size(b) / 2 || coord_y(m->coord, b) == board_size(b) / 2) {
/* This is troublesome, since it cannot mirror properly:
* it won't be hashed in some quadrants. Better just discard
* the rest of the sequence for now. (TODO: Make quadrants
* overlap.) */
j->discard = true;
}
if (j->discard)
return NULL;
//printf("%"PRIhash" %"PRIhash"\n", j->b[0]->qhash[0], b->qhash[0]);
assert(j->b[0]->qhash[0] == b->qhash[0]);
/* Record next move in all rotations and update the hash. */
for (int i = 0; i < 16; i++) {
#define HASH_VMIRROR 1
#define HASH_HMIRROR 2
#define HASH_XYFLIP 4
#define HASH_OCOLOR 8
int quadrant = 0;
coord_t coord = m->coord;
if (i & HASH_VMIRROR) {
coord = coord_xy(b, coord_x(coord, b), board_size(b) - 1 - coord_y(coord, b));
quadrant += 2;
}
if (i & HASH_HMIRROR) {
coord = coord_xy(b, board_size(b) - 1 - coord_x(coord, b), coord_y(coord, b));
quadrant++;
}
if (i & HASH_XYFLIP) {
coord = coord_xy(b, coord_y(coord, b), coord_x(coord, b));
if (quadrant == 1)
quadrant = 2;
else if (quadrant == 2)
quadrant = 1;
}
enum stone color = m->color;
if (i & HASH_OCOLOR)
color = stone_other(color);
coord_t **ccp = &j->jdict->patterns[j->b[i]->qhash[quadrant] & joseki_hash_mask].moves[color - 1];
int count = 1;
if (*ccp) {
for (coord_t *cc = *ccp; !is_pass(*cc); cc++) {
count++;
if (*cc == coord) {
//printf("%d,%d (%"PRIhash", %d) !+ %s\n", i, quadrant, j->b[i]->qhash[quadrant], count, coord2sstr(coord, b));
goto already_have;
}
}
}
//printf("%d,%d (%"PRIhash", %d) =+ %s\n", i, quadrant, j->b[i]->qhash[quadrant], count, coord2sstr(coord, b));
*ccp = realloc(*ccp, (count + 1) * sizeof(coord_t));
(*ccp)[count - 1] = coord;
(*ccp)[count] = pass;
already_have: {
struct move m2 = { .coord = coord, .color = color };
board_play(j->b[i], &m2);
}
}
return NULL;
}
static coord_t *
joseki_genmove(struct engine *e, struct board *b, struct time_info *ti, enum stone color, bool pass_all_alive)
{
fprintf(stderr, "genmove command not available in joseki scan!\n");
exit(EXIT_FAILURE);
}
void
engine_joseki_done(struct engine *e)
{
struct joseki_engine *j = e->data;
struct board *b = board_init(NULL);
board_resize(b, j->size - 2);
board_clear(b);
for (hash_t i = 0; i < 1 << joseki_hash_bits; i++) {
for (int s = 0; s < 2; s++) {
static const char cs[] = "bw";
if (!j->jdict->patterns[i].moves[s])
continue;
printf("%" PRIhash " %c", i, cs[s]);
coord_t *cc = j->jdict->patterns[i].moves[s];
int count = 0;
while (!is_pass(*cc)) {
printf(" %s", coord2sstr(*cc, b));
cc++, count++;
}
printf(" %d\n", count);
}
}
board_done(b);
joseki_done(j->jdict);
}
struct joseki_engine *
joseki_state_init(char *arg)
{
struct joseki_engine *j = calloc2(1, sizeof(struct joseki_engine));
for (int i = 0; i < 16; i++)
j->b[i] = board_init(NULL);
j->debug_level = 1;
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)
j->debug_level = atoi(optval);
else
j->debug_level++;
} else {
fprintf(stderr, "joseki: Invalid engine argument %s or missing value\n", optname);
exit(EXIT_FAILURE);
}
}
}
return j;
}
struct engine *
engine_joseki_init(char *arg, struct board *b)
{
struct joseki_engine *j = joseki_state_init(arg);
struct engine *e = calloc2(1, sizeof(struct engine));
e->name = "Joseki";
e->comment = "You cannot play Pachi with this engine, it is intended for special development use - scanning of joseki sequences fed to it within the GTP stream.";
e->genmove = joseki_genmove;
e->notify_play = joseki_play;
e->done = engine_joseki_done;
e->data = j;
// clear_board does not concern us, we like to work over many games
e->keep_on_clear = true;
return e;
}