added test5(), refactored simple-gtp.c, added gtp input sanitizer, added game-scoring functions

AuthorKonata <konata@posteo.jp>
Date
Commit4cf3cbaacf300b2679ec0253b1fe5a00f34553eb
Parent77454c8
9 files changed, 447 insertions(+), 145 deletions(-)
▾Minclude/simple-go/simple-go.h
@@ -6,6 +6,7 @@
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <cutils/cutils.h>
#define BLACK 'b'
#define WHITE 'w'
@@ -27,9 +28,19 @@ typedef struct game_state
{
go_board* board;
float komi;
unsigned long white_captured;
unsigned long black_captured;
bool black_turn;
} game_state;
typedef struct go_score
{
Vector* white_groups;
Vector* black_groups;
unsigned long white_points;
unsigned long black_points;
} go_score;
go_board* create_board(go_coordinate size);
void delete_board(go_board* board);
@@ -40,15 +51,21 @@ bool check_bounds(const go_board* board, go_coordinate y, go_coordinate x);
char get_board_at(const go_board* board, go_coordinate y, go_coordinate x);
void set_board_at(go_board* board, go_coordinate y, go_coordinate x, char item);
void kill_group(go_board* board, go_board* overlay);
void kill_group(go_board* board, const go_board* overlay);
bool group_killable(game_state* game, go_coordinate y, go_coordinate x);
bool group_attachable(const game_state* game, go_coordinate y, go_coordinate x);
bool play_at(game_state* game, go_coordinate y, go_coordinate x, go_symbol color);
void print_board(go_board* board);
char* board_to_string(go_board* board);
void print_board(const go_board* board);
char* board_to_string(const go_board* board);
void find_group(const go_board* board, go_board* overlay, go_coordinate y, go_coordinate x);
unsigned long count_liberties(go_board* board, go_board* overlay);
go_symbol group_belongs(const go_board* board, const go_board* overlay);
unsigned long count_liberties(const go_board* board, const go_board* overlay);
unsigned long group_size(go_board* group);
go_score* score_game(const game_state* board);
void delete_score(go_score* score);
▾Ainclude/simple-go/simple-gtp-func.h
@@ -0,0 +1,29 @@
#ifndef SIMPLE_GTP_FUNC_H
#define SIMPLE_GTP_FUNC_H
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <simple-go/simple-go.h>
#include <cutils/cutils.h>
extern const char* const known_commands_string ;
extern const char* known_commands_array[];
char* protocol_version_func(const Vector* arguments, const char* id, game_state* game);
char* name_func(const Vector* arguments, const char* id, game_state* game);
char* version_func(const Vector* arguments, const char* id, game_state* game);
char* known_command_func(const Vector* arguments, const char* id, game_state* game);
char* list_commands_func(const Vector* arguments, const char* id, game_state* game);
char* boardsize_func(const Vector* arguments, const char* id, game_state* game);
char* komi_func(const Vector* arguments, const char* id, game_state* game);
char* clear_board_func(const Vector* arguments, const char* id, game_state* game);
char* play_func(const Vector* arguments, const char* id, game_state* game);
char* genmove_func(const Vector* arguments, const char* id, game_state* game);
char* show_board_func(const Vector* arguments, const char* id, game_state* game);
char* final_score_func(const Vector* arguments, const char* id, game_state* game);
#endif //SIMPLE_GTP_FUNC_H
▾Mmeson.build
@@ -1,9 +1,9 @@
project('simple-go', 'c')
CFLAGS = ['-std=c11', '-fstrict-aliasing', '-Wall', '-Wpedantic', '-Wextra']
CFLAGS = ['-std=c11', '-fstrict-aliasing', '-Wall', '-Wpedantic', '-Wextra', '-Wno-unused-parameter']
cutils_sp = subproject('cutils')
dep = cutils_sp.get_variable('cutils_dep')
dep = dependency('cutils', fallback : ['cutils', 'cutils_dep'])
subdir('include')
subdir('src')
▾Asrc/gtp-start.c
@@ -0,0 +1,21 @@
#include <simple-go/simple-go.h>
#include <simple-go/simple-gtp.h>
int main(int argc, char** argv)
{
game_state* game = create_game(19, 6.5);
char input[50] = {0};
char* answer;
while(1)
{
fgets(input, 49, stdin);
if(strcmp(input, "quit\n") == 0)
break;
answer = handle_gtp_cmd(input, game);
printf("%s", answer);
free(answer);
}
delete_game(game);
}
▾Msrc/meson.build
@@ -1,6 +1,7 @@
cutils_sp = subproject('cutils')
dep = cutils_sp.get_variable('cutils_dep')
src = ['simple-go.c', 'simple-gtp.c']
src = ['simple-go.c', 'simple-gtp.c', 'simple-gtp-func.c']
executable('test_go', src + ['test.c'], include_directories : inc, dependencies : dep, c_args: CFLAGS )
executable('test_go', src + ['test.c'], include_directories : inc, dependencies : dep, c_args: CFLAGS )
executable('gtp-start', src + ['gtp-start.c'], include_directories : inc, dependencies : dep, c_args: CFLAGS )
▾Msrc/simple-go.c
@@ -27,6 +27,8 @@ game_state* create_game(go_coordinate size, float komi)
game->board = create_board(size);
game->black_turn = true;
game->komi = komi;
game->white_captured = 0;
game->black_captured = 0;
return game;
}
@@ -45,7 +47,7 @@ bool check_bounds(const go_board* board, go_coordinate y, go_coordinate x)
return false;
}
void kill_group(go_board* board, go_board* overlay)
void kill_group(go_board* board, const go_board* overlay)
{
for(go_coordinate y = 0; y < board->size; y++)
{
@@ -57,7 +59,7 @@ void kill_group(go_board* board, go_board* overlay)
}
}
void print_board(go_board* board)
void print_board(const go_board* board)
{
for(go_coordinate y = 0; y < board->size; y++)
{
@@ -70,7 +72,7 @@ void print_board(go_board* board)
}
}
char* board_to_string(go_board* board)
char* board_to_string(const go_board* board)
{
size_t str_size = (board->size*2+6)*(board->size+2)+2;
char* ret = calloc(str_size+1,1);
@@ -108,10 +110,7 @@ char* board_to_string(go_board* board)
ret[index++] = '\n';
}
return ret;
}
@@ -238,7 +237,60 @@ void find_group(const go_board* board, go_board* overlay, go_coordinate y, go_co
}
}
unsigned long count_liberties(go_board* board, go_board* overlay)
go_symbol group_belongs(const go_board* board, const go_board* overlay)
{
assert(board->size == overlay->size);
go_symbol belongs = EMPTY;
for(go_coordinate y = 0; y < board->size; y++)
{
for(go_coordinate x = 0; x < board->size; x++)
{
if(get_board_at(overlay, y, x) == GROUP)
{
go_symbol up = get_board_at(board,y-1,x);
go_symbol left = get_board_at(board,y,x-1);
go_symbol down = get_board_at(board,y+1,x);
go_symbol right = get_board_at(board,y,x+1);
if(up == WHITE || up == BLACK)
{
belongs = (belongs == EMPTY) ? up : (up != belongs ? NO_FIELD : up);
}
if(left == WHITE || left == BLACK)
{
belongs = (belongs == EMPTY) ? left : (left != belongs ? NO_FIELD : left);
}
if(down == WHITE || down == BLACK)
{
belongs = (belongs == EMPTY) ? down : (down != belongs ? NO_FIELD : down);
}
if(right == WHITE || right == BLACK)
{
belongs = (belongs == EMPTY) ? right : (right != belongs ? NO_FIELD : right);
}
}
}
}
return belongs;
}
unsigned long group_size(go_board* group)
{
unsigned long sum = 0;
for(go_coordinate y = 0; y < group->size; y++)
{
for(go_coordinate x = 0; x < group->size; x++)
{
if(get_board_at(group, y, x) == GROUP)
++sum;
}
}
return sum;
}
unsigned long count_liberties(const go_board* board, const go_board* overlay)
{
assert(board->size == overlay->size);
@@ -283,4 +335,70 @@ unsigned long count_liberties(go_board* board, go_board* overlay)
return liberties;
}
go_score* score_game(const game_state* game)
{
go_score* ret = malloc(sizeof(*ret));
ret->white_groups = new_vector();
ret->black_groups = new_vector();
ret->white_points = game->white_captured;
ret->black_points = game->black_captured;
go_board* board = game->board;
go_board* current;
go_symbol belongs;
for(go_coordinate y = 0; y < board->size; y++)
{
for(go_coordinate x = 0; x < board->size; x++)
{
//if field is empty
if(get_board_at(board, y, x) == EMPTY)
{
//check if field is already counted
for(go_coordinate i = 0; i < ret->white_groups->length; i++)
{
current = vector_at(ret->white_groups, i);
if(get_board_at(current, y, x) == GROUP)
continue;
}
for(go_coordinate i = 0; i < ret->black_groups->length; i++)
{
current = vector_at(ret->black_groups, i);
if(get_board_at(current, y, x) == GROUP)
continue;
}
go_board* overlay = create_board(board->size);
find_group(board, overlay, y, x);
//check if field belongs to a group
if((belongs = group_belongs(board, overlay)) == WHITE)
{
vector_push(ret->white_groups, overlay);
ret->white_points += group_size(overlay);
} else if(belongs == BLACK) {
vector_push(ret->black_groups, overlay);
ret->black_points += group_size(overlay);
} else {
delete_board(overlay);
}
}
}
}
return ret;
}
void delete_board_wrapper(void* board)
{
delete_board(board);
}
void delete_score(go_score* score)
{
delete_vector(score->white_groups, delete_board_wrapper);
delete_vector(score->black_groups, delete_board_wrapper);
free(score);
}
▾Asrc/simple-gtp-func.c
@@ -0,0 +1,170 @@
#include <simple-go/simple-gtp-func.h>
const char* const known_commands_string = "protocol_version\nname\nversion\nknown_command\nlist_commands\nquit\nboardsize\nclear_board\nkomi\nplay\ngenmove\nshowboard";
const char* known_commands_array[] = {"protocol_version","name","version","known_command","list_commands","quit","boardsize","clear_board","komi","play","genmove","showboard", NULL};
static char* cmd_error(const char* msg, const char* id)
{
char* ret = malloc(strlen("? ") + (msg ? strlen(msg) : 0) + (id ? strlen(id) : 0) + 3);
strcpy(ret, "?");
if(id)
strcat(ret, id);
if(msg)
{
strcat(ret, " ");
strcat(ret, msg);
}
strcat(ret, "\n\n");
return ret;
}
static char* cmd_success(const char* msg, const char* id)
{
char* ret = malloc(strlen("= ") + (msg ? strlen(msg) : 0) + (id ? strlen(id) : 0) + 3);
strcpy(ret, "=");
if(id)
strcat(ret, id);
if(msg)
{
strcat(ret, " ");
strcat(ret, msg);
}
strcat(ret, "\n\n");
return ret;
}
char* protocol_version_func(const Vector* arguments, const char* id, game_state* game)
{
return cmd_success("2", id);
}
char* name_func(const Vector* arguments, const char* id, game_state* game)
{
return cmd_success("simple-go", id);
}
char* version_func(const Vector* arguments, const char* id, game_state* game)
{
return cmd_success("0.1", id);
}
char* known_command_func(const Vector* arguments, const char* id, game_state* game)
{
for(size_t i = 0; known_commands_array[i]; i++)
{
if(arguments->length > 0 && strcmp(vector_at(arguments, 0), known_commands_array[i]) == 0)
{
return cmd_success("false", id);
}
}
return cmd_success("false", id);
}
char* list_commands_func(const Vector* arguments, const char* id, game_state* game)
{
return cmd_success(known_commands_string, id);
}
char* boardsize_func(const Vector* arguments, const char* id, game_state* game)
{
unsigned int size;
if(arguments->length == 1 && sscanf(vector_at(arguments,0), "%u", &size) > 0 && 0 < size && size < 26)
{
delete_board(game->board);
game->board = create_board(size);
return cmd_success(NULL, id);
} else {
return cmd_error("unacceptable size", id);
}
}
char* komi_func(const Vector* arguments, const char* id, game_state* game)
{
float komi;
if(arguments->length == 1 && sscanf(vector_at(arguments,0), "%f", &komi))
{
game->komi = komi;
return cmd_success(NULL, id);
} else {
return cmd_error("komi not a float", id);
}
}
char* clear_board_func(const Vector* arguments, const char* id, game_state* game)
{
unsigned int size = game->board->size;
delete_board(game->board);
game->board = create_board(size);
return cmd_success(NULL, id);
}
char* play_func(const Vector* arguments, const char* id, game_state* game)
{
char* color = malloc(6);
char x;
int y;
if((arguments->length == 2 || arguments->length == 3) && //either its "a 10" or "a10"
(snprintf(color, 6, "%s", (char*)vector_at(arguments,0)) > 0) && //"white" and "black" are only 6 chars
(strcmp(color, "white") == 0 || strcmp(color, "black") == 0 ||
strcmp(color, "w") == 0 || strcmp(color, "b") == 0) && // check color
(sscanf(vector_at(arguments,1), "%c", &x) > 0) && // get horizontal coordinate
((unsigned char)(tolower(x) - 'a') < game->board->size) && // check range
((sscanf((char*)vector_at(arguments,1)+1, "%d", &y) > 0) || // get vertical coordinate if no space between
((arguments->length == 3) && (sscanf((char*)vector_at(arguments,2), "%d", &y) > 0))) && // get vertical coordinate if space between
((unsigned int)y <= game->board->size) && y > 0) // check range
{
if(play_at(game, game->board->size-(unsigned int)y, (unsigned char)(x-'a'), (strcmp(color, "white") == 0 || strcmp(color, "w") == 0) ? WHITE : BLACK))
{
free(color);
return cmd_success(NULL, id);
} else {
free(color);
return cmd_error("illegal move", id);
}
} else {
free(color);
return cmd_error("invalid color or coordinate", id);
}
}
char* genmove_func(const Vector* arguments, const char* id, game_state* game)
{
char* color = malloc(6);
if(arguments->length == 1 &&
(snprintf(color, 6, "%s", (char*)vector_at(arguments,0)) > 0) &&
(strcmp(color, "white") == 0 || strcmp(color, "black") == 0 ||
strcmp(color, "w") == 0 || strcmp(color, "b") == 0 ))
{
go_coordinate y;
go_coordinate x;
do
{
y = (go_coordinate)rand() % game->board->size;
x = (go_coordinate)rand() % game->board->size;
} while(!play_at(game, y, x, (strcmp(color, "white") == 0 || strcmp(color, "w") == 0) ? WHITE : BLACK));
free(color);
return cmd_success(NULL, id);
} else {
free(color);
return cmd_error("invalid color or coordinate", id);
}
}
char* show_board_func(const Vector* arguments, const char* id, game_state* game)
{
char* board = board_to_string(game->board);
char* tmp = cmd_success(board, id);
free(board);
return tmp;
}
char* final_score_func(const Vector* arguments, const char* id, game_state* game)
{
return NULL;
}
▾Msrc/simple-gtp.c
@@ -1,4 +1,5 @@
#include <simple-go/simple-gtp.h>
#include <simple-go/simple-gtp-func.h>
typedef struct msg_formatted
{
@@ -7,39 +8,6 @@ typedef struct msg_formatted
char* id;
} msg_formatted;
char* cmd_error(const char* msg, char* id)
{
char* ret = malloc(strlen("? ") + strlen(msg) + (id ? strlen(id) : 0) + 3);
strcpy(ret, "?");
if(id)
{
strcat(ret, id);
free(id);
}
strcat(ret, " ");
strcat(ret, msg);
strcat(ret, "\n\n");
return ret;
}
char* cmd_success(const char* msg, char* id)
{
char* ret = malloc(strlen("= ") + strlen(msg) + (id ? strlen(id) : 0) + 3);
strcpy(ret, "=");
if(id)
{
strcat(ret, id);
free(id);
}
strcat(ret, " ");
strcat(ret, msg);
strcat(ret, "\n\n");
return ret;
}
const char* const known_commands_string = "protocol_version\nname\nversion\nknown_command\nlist_commands\nquit\nboardsize\nclear_board\nkomi\nplay\ngenmove\nshowboard";
const char* known_commands_array[] = {"protocol_version","name","version","known_command","list_commands","quit","boardsize","clear_board","komi","play","genmove","showboard"};
int cmd_count = sizeof(known_commands_array)/sizeof(known_commands_array[0]);
static msg_formatted format_msg(const char* msg)
{
@@ -85,6 +53,27 @@ static msg_formatted format_msg(const char* msg)
return formatted;
}
static char* sanitize(const char* msg)
{
char* ret = calloc(strlen(msg)+1, 1);
for(size_t i = 0, reti = 0; i < strlen(msg); i++)
{
if(msg[i] == '\t')
{
ret[reti++] = ' ';
} else if(iscntrl(msg[i])){
++reti;
} else if(msg[i] == '#'){
break;
} else {
ret[reti++] = msg[i];
}
}
return ret;
}
char* handle_gtp_cmd(const char* msg, game_state* game)
{
if(!msg || !strlen(msg))
@@ -92,116 +81,47 @@ char* handle_gtp_cmd(const char* msg, game_state* game)
char* ret = calloc(1,1);
return ret;
}
char* msg_san = sanitize(msg);
msg_formatted formatted = format_msg(msg);
msg_formatted formatted = format_msg(msg_san);
char* command = formatted.command;
Vector* arguments = formatted.arguments;
char* id = formatted.id;
char* (*func_ptr)(const char*, char* id) = cmd_error;
const char* func_args = "unknown command";
char* ret;
char* (*func_ptr)(const Vector*, const char*, game_state*);
if(strcmp(command, "protocol_version") == 0)
{
func_ptr = cmd_success;
func_args = "2";
func_ptr = protocol_version_func;
} else if(strcmp(command, "name") == 0) {
func_ptr = cmd_success;
func_args = "simple-go";
func_ptr = name_func;
} else if(strcmp(command, "version") == 0) {
func_ptr = cmd_success;
func_args = "0.1";
func_ptr = version_func;
} else if(strcmp(command, "known_command") == 0) {
func_ptr = cmd_success;
func_args = "false";
for(int i = 0; i < cmd_count; i++)
{
if(arguments->length > 0 && strcmp(vector_at(arguments, 0), known_commands_array[i]) == 0)
{
func_args = "true";
break;
}
}
func_ptr = known_command_func;
} else if(strcmp(command, "list_commands") == 0) {
func_ptr = cmd_success;
func_args = known_commands_string;
func_ptr = list_commands_func;
} else if(strcmp(command, "boardsize") == 0) {
unsigned int size;
if(arguments->length == 1 && sscanf(vector_at(arguments,0), "%u", &size) > 0 && 0 < size && size < 26)
{
func_ptr = cmd_success;
func_args = "";
delete_board(game->board);
game->board = create_board(size);
} else {
func_ptr = cmd_error;
func_args = "unacceptable size";
}
func_ptr = boardsize_func;
} else if(strcmp(command, "komi") == 0) {
float komi;
if(arguments->length == 1 && sscanf(vector_at(arguments,0), "%f", &komi))
{
func_ptr = cmd_success;
func_args = "";
game->komi = komi;
} else {
func_ptr = cmd_error;
func_args = "komi not a float";
}
func_ptr = komi_func;
} else if(strcmp(command, "clear_board") == 0) {
func_ptr = cmd_success;
func_args = "";
unsigned int size = game->board->size;
delete_board(game->board);
game->board = create_board(size);
func_ptr = clear_board_func;
} else if(strcmp(command, "play") == 0) {
char* color = malloc(6);
char x;
int y;
if((arguments->length == 2 || arguments->length == 3) && //either its "a 10" or "a10"
(snprintf(color, 6, "%s", (char*)vector_at(arguments,0)) > 0) && //"white" and "black" are only 6 chars
(strcmp(color, "white") == 0 || strcmp(color, "black") == 0) && // check color
(sscanf(vector_at(arguments,1), "%c", &x) > 0) && // get horizontal coordinate
((unsigned char)(tolower(x) - 'a') < game->board->size) && // check range
((sscanf((char*)vector_at(arguments,1)+1, "%d", &y) > 0) || // get vertical coordinate if no space between
((arguments->length == 3) && (sscanf((char*)vector_at(arguments,2), "%d", &y) > 0))) && // get vertical coordinate if space between
((unsigned int)y <= game->board->size) && y > 0) // check range
{
if(play_at(game, game->board->size-(unsigned int)y, (unsigned char)(x-'a'), strcmp(color, "white") == 0 ? WHITE : BLACK))
{
func_ptr = cmd_success;
func_args = "";
} else {
func_ptr = cmd_error;
func_args = "illegal move";
}
} else {
func_ptr = cmd_error;
func_args = "invalid color or coordinate";
}
free(color);
func_ptr = play_func;
} else if(strcmp(command, "genmove") == 0) {
func_ptr = cmd_success;
func_args = "";
go_coordinate y;
go_coordinate x;
do
{
y = (go_coordinate)rand() % game->board->size;
x = (go_coordinate)rand() % game->board->size;
} while(!play_at(game, y, x, NO_FIELD));
func_ptr = genmove_func;
} else if(strcmp(command, "showboard") == 0) {
char* board = board_to_string(game->board);
free(command);
delete_vector(arguments, free);
char* tmp = cmd_success(board, id);
free(board);
return tmp;
func_ptr = show_board_func;
}
ret = func_ptr(arguments, id, game);
free(command);
free(msg_san);
free(id);
delete_vector(arguments, free);
return func_ptr(func_args, id);
return ret;
}
▾Msrc/test.c
@@ -138,20 +138,46 @@ void test4(void)
puts("test4 finished");
}
void test5(void)
{
puts("test5 running");
game_state* game = create_game(19,0);
set_board_at(game->board, 2, 3, WHITE);
set_board_at(game->board, 2, 5, WHITE);
set_board_at(game->board, 1, 4, WHITE);
set_board_at(game->board, 3, 4, WHITE);
set_board_at(game->board, 5, 6, WHITE);
set_board_at(game->board, 5, 8, WHITE);
set_board_at(game->board, 4, 7, WHITE);
set_board_at(game->board, 6, 7, BLACK);
set_board_at(game->board, 5, 2, BLACK);
set_board_at(game->board, 5, 4, BLACK);
set_board_at(game->board, 4, 3, BLACK);
set_board_at(game->board, 6, 3, BLACK);
go_score* score = score_game(game);
print_board(game->board);
printf("White groups: %ld\n", score->white_groups->length);
printf("White points: %ld\n", score->white_points);
printf("Black groups: %ld\n", score->black_groups->length);
printf("Black points: %ld\n", score->black_points);
delete_score(score);
delete_game(game);
puts("test5 finished");
}
int main(int argc, char** argv)
{
game_state* game = create_game(19, 0);
test1();
test2();
test3();
test4();
set_board_at(game->board, 2, 3, WHITE);
char* ret = handle_gtp_cmd("showboard", game);
printf("%s", ret);
free(ret);
delete_game(game);
test5();
}