From a4b7489cba57902ec4d8b353f00a6968ff1f9aa5 Mon Sep 17 00:00:00 2001 From: Jean-loup Gailly Date: Sun, 22 Apr 2012 16:34:55 +0200 Subject: [PATCH] Add option -c chat_file to support fancy replies to kgs-chat --- Makefile | 2 +- chat.c | 122 ++++++++++++++++++++++++++++++++++++++++++++++ chat.h | 17 +++++++ distributed/distributed.c | 23 ++++----- engine.h | 2 +- gtp.c | 16 +++--- pachi.c | 10 +++- uct/uct.c | 30 +++++------- 8 files changed, 181 insertions(+), 41 deletions(-) create mode 100644 chat.c create mode 100644 chat.h diff --git a/Makefile b/Makefile index 828e0ef..9db5a69 100644 --- a/Makefile +++ b/Makefile @@ -104,7 +104,7 @@ unexport INCLUDES INCLUDES=-I. -OBJS=board.o gtp.o move.o ownermap.o pattern3.o pattern.o patternsp.o patternprob.o playout.o probdist.o random.o stone.o timeinfo.o network.o fbook.o +OBJS=board.o gtp.o move.o ownermap.o pattern3.o pattern.o patternsp.o patternprob.o playout.o probdist.o random.o stone.o timeinfo.o network.o fbook.o chat.o SUBDIRS=random replay patternscan patternplay joseki montecarlo uct uct/policy playout tactics t-unit distributed all: all-recursive pachi diff --git a/chat.c b/chat.c new file mode 100644 index 0000000..16a7bec --- /dev/null +++ b/chat.c @@ -0,0 +1,122 @@ +#include +#include +#include +#include +#include + +#define DEBUG + +#include "debug.h" +#include "chat.h" +#include "random.h" + +#define MAX_CHAT_PATTERNS 500 + +static struct chat { + double minwin; + double maxwin; + char from[20]; + char regex[100]; + char reply[300]; // in printf format with one param (100*winrate) + + regex_t preg; + bool displayed; + bool match; +} *chat_table; + +static char default_reply[] = "I know all those words, but that sentence makes no sense to me"; +static char not_playing[] = "I'm winning big without playing"; + +/* Read the chat file, a sequence of lines of the form: + * minwin;maxwin;from;regex;reply + * Set minwin, maxwin to -1.0 2.0 for answers to chat other than winrate. + * Set from as one space for replies to anyone. + * Examples: + * -1.0;0.3; ;winrate;%.1f%% I'm losing + * -1.0;2.0;pasky;^when ;Today + */ +void chat_init(char *chat_file) { + if (!chat_file) return; + FILE *f = fopen(chat_file, "r"); + if (!f) { + perror(chat_file); + return; + } + chat_table = calloc2(MAX_CHAT_PATTERNS, sizeof(*chat_table)); + struct chat *entry = chat_table; + while (fscanf(f, "%lf;%lf;%20[^;];%100[^;];%300[^\n]\n", &entry->minwin, &entry->maxwin, + entry->from, entry->regex, entry->reply ) == 5) { + if (!strcmp(entry->from, " ")) + entry->from[0] = '\0'; + int err = regcomp(&entry->preg, entry->regex, REG_EXTENDED | REG_ICASE); + if (err) { + char msg[200]; + regerror(err, &entry->preg, msg, sizeof(msg)); + fprintf(stderr, "Error compiling %s: %s\n", entry->regex, msg); + } else { + entry++; + } + } + if (!feof(f)) + fprintf(stderr, "syntax error around line %ld in %s\n", entry - chat_table, chat_file); + fclose(f); + if (DEBUGL(1)) + fprintf(stderr, "Loaded %ld chat entries from %s\n", entry - chat_table, chat_file); +} + +void chat_done() { + if (chat_table) { + free(chat_table); + chat_table = NULL; + } +} + +/* Reply to a chat. When not playing, color is S_NONE and all remaining parameters are undefined. + * If some matching entries have not yet been displayed we pick randomly among them. Otherwise + * we pick randomly among all matching entries. */ +char +*generic_chat(struct board *b, bool opponent, char *from, char *cmd, enum stone color, coord_t move, + int playouts, int machines, int threads, double winrate, double extra_komi) { + + static char reply[1024]; + if (!chat_table) { + if (strncasecmp(cmd, "winrate", 7)) return NULL; + if (color == S_NONE) return not_playing; + + snprintf(reply, 512, "In %d playouts at %d threads, %s %s can win with %.1f%% probability", + playouts, threads, stone2str(color), coord2sstr(move, b), 100*winrate); + if (abs(extra_komi) >= 0.5) { + snprintf(reply + strlen(reply), 510, ", while self-imposing extra komi %.1f", extra_komi); + } + strcat(reply, "."); + return reply; + } + int matches = 0; + int undisplayed = 0; + for (struct chat *entry = chat_table; entry->regex[0]; entry++) { + entry->match = false; + if (color != S_NONE) { + if (winrate < entry->minwin) continue; + if (winrate > entry->maxwin) continue; + } + if (entry->from[0] && strcmp(entry->from, from)) continue; + if (regexec(&entry->preg, cmd, 0, NULL, 0)) continue; + entry->match = true; + matches++; + if (!entry->displayed) undisplayed++; + } + if (matches == 0) return default_reply; + int choices = undisplayed > 0 ? undisplayed : matches; + int index = fast_random(choices); + for (struct chat *entry = chat_table; entry->regex[0]; entry++) { + if (!entry->match) continue; + if (undisplayed > 0 && entry->displayed) continue; + if (--index < 0) { + entry->displayed = true; + snprintf(reply, sizeof(reply), entry->reply, 100*winrate); + return reply; + } + } + assert(0); + return NULL; +} diff --git a/chat.h b/chat.h new file mode 100644 index 0000000..961cb64 --- /dev/null +++ b/chat.h @@ -0,0 +1,17 @@ +#ifndef PACHI_CHAT_H +#define PACHI_CHAT_H + +#include + +#include "stone.h" +#include "move.h" + +struct board; + +void chat_init(char *chat_file); +void chat_done(); + +char *generic_chat(struct board *b, bool opponent, char *from, char *cmd, enum stone color, coord_t move, + int playouts, int machines, int threads, double winrate, double extra_komi); + +#endif diff --git a/distributed/distributed.c b/distributed/distributed.c index c15206e..ff7ad79 100644 --- a/distributed/distributed.c +++ b/distributed/distributed.c @@ -83,6 +83,7 @@ #include "stats.h" #include "mq.h" #include "debug.h" +#include "chat.h" #include "distributed/distributed.h" #include "distributed/merge.h" @@ -96,6 +97,8 @@ struct distributed { bool slaves_quit; struct move my_last_move; struct move_stats my_last_stats; + int slaves; + int threads; }; /* Default number of simulations to perform per move. @@ -367,6 +370,8 @@ distributed_genmove(struct engine *e, struct board *b, struct time_info *ti, dist->my_last_move.coord = best; dist->my_last_stats.value = stats[best].value; dist->my_last_stats.playouts = (int)stats[best].playouts; + dist->slaves = reply_count; + dist->threads = threads; /* Tell the slaves to commit to the selected move, overwriting * the last "pachi-genmoves" in the command history. */ @@ -397,21 +402,13 @@ distributed_genmove(struct engine *e, struct board *b, struct time_info *ti, } static char * -distributed_chat(struct engine *e, struct board *b, char *cmd) +distributed_chat(struct engine *e, struct board *b, bool opponent, char *from, char *cmd) { struct distributed *dist = e->data; - static char reply[BSIZE]; - - cmd += strspn(cmd, " \n\t"); - if (!strncasecmp(cmd, "winrate", 7)) { - enum stone color = dist->my_last_move.color; - snprintf(reply, BSIZE, "In %d playouts at %d machines, %s %s can win with %.2f%% probability.", - dist->my_last_stats.playouts, active_slaves, stone2str(color), - coord2sstr(dist->my_last_move.coord, b), - 100 * get_value(dist->my_last_stats.value, color)); - return reply; - } - return NULL; + double winrate = get_value(dist->my_last_stats.value, dist->my_last_move.color); + + return generic_chat(b, opponent, from, cmd, dist->my_last_move.color, dist->my_last_move.coord, + dist->my_last_stats.playouts, dist->slaves, dist->threads, winrate, 0.0); } static int diff --git a/engine.h b/engine.h index 8094132..ab712bb 100644 --- a/engine.h +++ b/engine.h @@ -11,7 +11,7 @@ typedef enum parse_code (*engine_notify)(struct engine *e, struct board *b, int typedef char *(*engine_notify_play)(struct engine *e, struct board *b, struct move *m, char *enginearg); typedef char *(*engine_undo)(struct engine *e, struct board *b); typedef char *(*engine_result)(struct engine *e, struct board *b); -typedef char *(*engine_chat)(struct engine *e, struct board *b, char *cmd); +typedef char *(*engine_chat)(struct engine *e, struct board *b, bool in_game, char *from, char *cmd); /* Generate a move. If pass_all_alive is true, shall be generated only * if all stones on the board can be considered alive, without regard to "dead" * considered stones. */ diff --git a/gtp.c b/gtp.c index a8e2d66..2ca0f44 100644 --- a/gtp.c +++ b/gtp.c @@ -539,13 +539,17 @@ next_group:; } else if (!strcasecmp(cmd, "kgs-chat")) { char *loc; next_tok(loc); - char *src; - next_tok(src); - char *msg; - next_tok(msg); + bool opponent = !strcasecmp(loc, "game"); + char *from; + next_tok(from); + char *msg = next; + msg += strspn(msg, " \n\t"); + char *end = index(msg, '\n'); + if (end) *end = '\0'; char *reply = NULL; - if (engine->chat) - reply = engine->chat(engine, board, msg); + if (engine->chat) { + reply = engine->chat(engine, board, opponent, from, msg); + } if (reply) gtp_reply(id, reply, NULL); else diff --git a/pachi.c b/pachi.c index 8f5f2bb..6a1ec67 100644 --- a/pachi.c +++ b/pachi.c @@ -20,6 +20,7 @@ #include "uct/uct.h" #include "distributed/distributed.h" #include "gtp.h" +#include "chat.h" #include "timeinfo.h" #include "random.h" #include "version.h" @@ -86,13 +87,17 @@ int main(int argc, char *argv[]) char *gtp_port = NULL; char *log_port = NULL; int gtp_sock = -1; + char *chatfile = NULL; char *fbookfile = NULL; seed = time(NULL) ^ getpid(); int opt; - while ((opt = getopt(argc, argv, "e:d:Df:g:l:s:t:u:")) != -1) { + while ((opt = getopt(argc, argv, "c:e:d:Df:g:l:s:t:u:")) != -1) { switch (opt) { + case 'c': + chatfile = strdup(optarg); + break; case 'e': if (!strcasecmp(optarg, "random")) { engine = E_RANDOM; @@ -170,6 +175,8 @@ int main(int argc, char *argv[]) ti[S_BLACK] = ti_default; ti[S_WHITE] = ti_default; + chat_init(chatfile); + char *e_arg = NULL; if (optind < argc) e_arg = argv[optind]; @@ -209,5 +216,6 @@ int main(int argc, char *argv[]) open_gtp_connection(>p_sock, gtp_port); } done_engine(e); + chat_done(); return 0; } diff --git a/uct/uct.c b/uct/uct.c index 221e440..d3a7434 100644 --- a/uct/uct.c +++ b/uct/uct.c @@ -10,6 +10,7 @@ #include "debug.h" #include "board.h" #include "gtp.h" +#include "chat.h" #include "move.h" #include "mq.h" #include "joseki/base.h" @@ -211,28 +212,19 @@ uct_result(struct engine *e, struct board *b) } static char * -uct_chat(struct engine *e, struct board *b, char *cmd) +uct_chat(struct engine *e, struct board *b, bool opponent, char *from, char *cmd) { struct uct *u = e->data; - static char reply[1024]; - cmd += strspn(cmd, " \n\t"); - if (!strncasecmp(cmd, "winrate", 7)) { - if (!u->t) - return "no game context (yet?)"; - enum stone color = u->t->root_color; - struct tree_node *n = u->t->root; - snprintf(reply, 1024, "In %d playouts at %d threads, %s %s can win with %.2f%% probability", - n->u.playouts, u->threads, stone2str(color), coord2sstr(node_coord(n), b), - tree_node_get_value(u->t, -1, n->u.value) * 100); - if (u->t->use_extra_komi && abs(u->t->extra_komi) >= 0.5) { - sprintf(reply + strlen(reply), ", while self-imposing extra komi %.1f", - u->t->extra_komi); - } - strcat(reply, "."); - return reply; - } - return NULL; + if (!u->t) + return generic_chat(b, opponent, from, cmd, S_NONE, pass, 0, 1, u->threads, 0.0, 0.0); + + struct tree_node *n = u->t->root; + double winrate = tree_node_get_value(u->t, -1, n->u.value); + double extra_komi = u->t->use_extra_komi && abs(u->t->extra_komi) >= 0.5 ? u->t->extra_komi : 0; + + return generic_chat(b, opponent, from, cmd, u->t->root_color, node_coord(n), n->u.playouts, 1, + u->threads, winrate, extra_komi); } static void -- 2.11.4.GIT