/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2007-2009 Joshua Simmons * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "util.h" #include "game.h" void metadata_summary (void) { char buffer[256] = ""; if (rb->strlen (header.black) || rb->strlen (header.white) || rb->strlen (header.black_rank) || rb->strlen (header.white_rank)) rb->snprintf (buffer, sizeof(buffer), "%s [%s] v. %s [%s] ", header.black, header.black_rank, header.white, header.white_rank); if (header.handicap > 1) { rb->snprintf (buffer + rb->strlen(buffer), sizeof (buffer) - rb->strlen (buffer), "%d stones ", header.handicap); } if (header.komi != 0 && !(header.komi == 1 && header.handicap > 1)) { snprint_fixed (buffer + rb->strlen(buffer), sizeof (buffer) - rb->strlen (buffer), header.komi); rb->snprintf (buffer + rb->strlen(buffer), sizeof (buffer) - rb->strlen (buffer), " komi "); } if (rb->strlen(header.result)) { rb->snprintf (buffer + rb->strlen(buffer), sizeof (buffer) - rb->strlen (buffer), "(%s)", header.result); } /* waiting for user input messes up the testing code, so ifdef it*/ #if !defined(GBN_TEST) if (rb->strlen(buffer)) { rb->splash(0, buffer); rb->action_userabort(TIMEOUT_BLOCK); } #endif } void * align_buffer (void *buffer, size_t * buffer_size) { unsigned int wasted = (-(long) buffer) & 3; if (!buffer || !buffer_size) { return NULL; } if (*buffer_size <= wasted) { *buffer_size = 0; return NULL; } *buffer_size -= wasted; return (void *) (((char *) buffer) + wasted); } bool setup_stack (struct stack_t *stack, void *buffer, size_t buffer_size) { if (!stack || !buffer || !buffer_size) { DEBUGF ("INVALID STACK SETUP!!\n"); return false; } buffer = align_buffer (buffer, &buffer_size); if (!buffer || !buffer_size) { DEBUGF ("Buffer disappeared after alignment!\n"); return false; } stack->buffer = buffer; stack->size = buffer_size; stack->sp = 0; return true; } bool push_stack (struct stack_t * stack, void *buffer, size_t buffer_size) { if (stack->sp + buffer_size > stack->size) { DEBUGF ("stack full!!\n"); return false; } rb->memcpy (&stack->buffer[stack->sp], buffer, buffer_size); stack->sp += buffer_size; return true; } bool pop_stack (struct stack_t * stack, void *buffer, size_t buffer_size) { if (!peek_stack (stack, buffer, buffer_size)) { return false; } stack->sp -= buffer_size; return true; } bool peek_stack (struct stack_t * stack, void *buffer, size_t buffer_size) { if (stack->sp < buffer_size) { return false; } rb->memcpy (buffer, &stack->buffer[stack->sp - buffer_size], buffer_size); return true; } void empty_stack (struct stack_t *stack) { stack->sp = 0; } bool push_pos_stack (struct stack_t *stack, unsigned short pos) { return push_stack (stack, &pos, sizeof (pos)); } bool push_int_stack (struct stack_t *stack, int num) { return push_stack (stack, &num, sizeof (num)); } bool push_char_stack (struct stack_t *stack, char num) { return push_stack (stack, &num, sizeof (num)); } /* IMPORTANT: keep in sync with the enum prop_type_t enum in types.h */ char *prop_names[] = { /* look up the SGF specification for the meaning of these */ "B", "W", "AB", "AW", "AE", "PL", "C", "DM", "GB", "GW", "HO", "UC", "V", "BM", "DO", "IT", "TE", "CR", "SQ", "TR", "DD", "MA", "SL", "LB", "N", "AP", "CA", "FF", "GM", "ST", "SZ", "AN", "PB", "PW", "HA", "KM", "TB", "TW", "BR", "WR", "BT", "WT", "CP", "DT", "EV", "RO", "GN", "GC", "ON", "OT", "PC", "RE", "RU", "SO", "TM", "US", "BL", "WL", "OB", "OW", "FG", "PM", "VW" }; /* These seems to be specified by the SGF specification. You can do free form ones as well, but I haven't implemented that (and don't plan to) */ const char *ruleset_names[] = { "AGA", "Japanese", "Chinese", "NZ", "GOE" }; int create_or_open_file (const char *filename) { int fd; if (!rb->file_exists (filename)) { fd = rb->creat(filename, 0666); } else { fd = rb->open (filename, O_RDWR); } return fd; } int snprint_fixed (char *buffer, int buffer_size, int fixed) { return rb->snprintf (buffer, buffer_size, "%s%d.%d", fixed < 0 ? "-" : "", abs (fixed) >> 1, 5 * (fixed & 1)); } int peek_char (int fd) { char peeked_char; int result = rb->read (fd, &peeked_char, 1); if (result != 1) { return -1; } result = rb->lseek (fd, -1, SEEK_CUR); if (result < 0) { return -1; } return peeked_char; } int read_char (int fd) { char read_char; int result = rb->read (fd, &read_char, 1); if (result != 1) { return -1; } return read_char; } bool write_char (int fd, char to_write) { int result = write_file (fd, &to_write, 1); if (result != 1) { return false; } return true; } ssize_t write_file (int fd, const void *buf, size_t count) { const char *buffer = buf; int result; int ret_val = count; while (count) { result = rb->write (fd, buffer, count); if (result < 0) { return -1; } count -= result; buffer += result; } return ret_val; } ssize_t read_file (int fd, void *buf, size_t count) { char *buffer = buf; int result; int ret_val = count; while (count) { result = rb->read (fd, buffer, count); if (result <= 0) { return -1; } count -= result; buffer += result; } return ret_val; } int read_char_no_whitespace (int fd) { int result = peek_char_no_whitespace (fd); read_char (fd); return result; } int peek_char_no_whitespace (int fd) { int result; while (is_whitespace (result = peek_char (fd))) { read_char (fd); } return result; } void close_file (int *fd) { if (*fd >= 0) { rb->close (*fd); } *fd = -1; } bool is_whitespace (int value) { if (value == ' ' || value == '\t' || value == '\n' || value == '\r' || value == '\f' || value == '\v') { return true; } else { return false; } } void sanitize_string (char *string) { bool escaped = false; if (!string) { return; } while (1) { switch (*string) { case '\0': return; case '\\': escaped = !escaped; break; case ']': if (!escaped) { *string = ']'; } escaped = false; break; default: break; }; ++string; } } bool get_header_string_and_size (struct header_t *header, enum prop_type_t type, char **buffer, int *size) { if (buffer == 0 || header == 0) { return false; } if (type == PROP_BLACK_NAME) { *buffer = header->black; *size = MAX_NAME; } else if (type == PROP_WHITE_NAME) { *buffer = header->white; *size = MAX_NAME; } else if (type == PROP_BLACK_RANK) { *buffer = header->black_rank; *size = MAX_RANK; } else if (type == PROP_WHITE_RANK) { *buffer = header->white_rank; *size = MAX_RANK; } else if (type == PROP_BLACK_TEAM) { *buffer = header->black_team; *size = MAX_TEAM; } else if (type == PROP_WHITE_TEAM) { *buffer = header->white_team; *size = MAX_TEAM; } else if (type == PROP_DATE) { *buffer = header->date; *size = MAX_DATE; } else if (type == PROP_ROUND) { *buffer = header->round; *size = MAX_ROUND; } else if (type == PROP_EVENT) { *buffer = header->event; *size = MAX_EVENT; } else if (type == PROP_PLACE) { *buffer = header->place; *size = MAX_PLACE; } else if (type == PROP_OVERTIME) { *buffer = header->overtime; *size = MAX_OVERTIME; } else if (type == PROP_RESULT) { *buffer = header->result; *size = MAX_RESULT; } else if (type == PROP_RULESET) { *buffer = header->ruleset; *size = MAX_RULESET; } else { return false; } return true; } /* TEST CODE BEGINS HERE define GBN_TEST to run this, either in goban.h or in the CFLAGS. The tests will be run when the plugin starts, after which the plugin will exit. Any error stops testing since many tests depend on previous setup. Note: The testing can take a while as there are some big loops. Be patient. */ #ifdef GBN_TEST #include "goban.h" #include "types.h" #include "board.h" #include "game.h" #include "sgf.h" #include "sgf_storage.h" /* If this isn't on a single line, the line numbers it reports will be wrong. * * I'm sure there's a way to make it better, but it's not really worth it. */ #define gbn_assert(test) if (test) {DEBUGF("%d passed\n", __LINE__);} else {DEBUGF("%d FAILED!\n", __LINE__); rb->splashf(10 * HZ, "Test on line %d of util.c failed!", __LINE__); return;} void run_tests (void) { rb->splash (3 * HZ, "Running tests. Failures will stop testing."); /* allocating and freeing storage units */ gbn_assert (alloc_storage_sgf ()); int prevent_infinite = 100000000; int count = 1; while (alloc_storage_sgf () >= 0 && --prevent_infinite) { ++count; } gbn_assert (prevent_infinite); gbn_assert (count > 100); /* make sure it fails a few times */ gbn_assert (alloc_storage_sgf () < 0); gbn_assert (alloc_storage_sgf () < 0); gbn_assert (alloc_storage_sgf () < 0); free_storage_sgf (0); gbn_assert (alloc_storage_sgf () == 0); gbn_assert (alloc_storage_sgf () < 0); int i; for (i = 0; i <= count; ++i) { free_storage_sgf (i); } gbn_assert (alloc_storage_sgf () >= 0); --count; for (i = 0; i < count; ++i) { gbn_assert (alloc_storage_sgf () >= 0); } free_tree_sgf (); /* setting up, saving and loading */ gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, 15)); gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, -30)); gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 4, 1)); gbn_assert (setup_game (MIN_BOARD_SIZE, MIN_BOARD_SIZE, 1, 1)); gbn_assert (setup_game (MIN_BOARD_SIZE, MAX_BOARD_SIZE, 1, 1)); gbn_assert (setup_game (MAX_BOARD_SIZE, MIN_BOARD_SIZE, 1, 1)); gbn_assert (!setup_game (MAX_BOARD_SIZE + 1, MAX_BOARD_SIZE + 1, 0, 15)); gbn_assert (!setup_game (MIN_BOARD_SIZE - 1, MIN_BOARD_SIZE - 1, 0, 15)); gbn_assert (!setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, -1, 15)); gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 1, 1)); gbn_assert (save_game (DEFAULT_SAVE_DIR "/t1.sgf")); gbn_assert (load_game (DEFAULT_SAVE_DIR "/t1.sgf")); gbn_assert (save_game (DEFAULT_SAVE_DIR "/t2.sgf")); gbn_assert (load_game (DEFAULT_SAVE_DIR "/t2.sgf")); gbn_assert (!save_game ("/DIR_DOESNT_EXIST/blah.sgf")); gbn_assert (!load_game ("/DIR_DOESNT_EXIST/blah.sgf")); gbn_assert (!load_game (DEFAULT_SAVE_DIR "/DOESNT_EXIST.sgf")); /* test of a long game, captures, illegal moves */ gbn_assert (load_game (DEFAULT_SAVE_DIR "/long.sgf")); while (move_num < 520) { gbn_assert (num_variations_sgf () == 1); gbn_assert (redo_node_sgf ()); } gbn_assert (play_move_sgf (POS (2, 0), BLACK)); gbn_assert (play_move_sgf (POS (2, 1), WHITE)); gbn_assert (move_num == 522); gbn_assert (white_captures == 261 && black_captures == 0); gbn_assert (play_move_sgf (PASS_POS, BLACK)); gbn_assert (play_move_sgf (PASS_POS, WHITE)); gbn_assert (move_num == 524); int x, y; int b_count, w_count, e_count; b_count = w_count = e_count = 0; for (x = 0; x < 19; ++x) { for (y = 0; y < 19; ++y) { gbn_assert (!legal_move_board (POS (x, y), BLACK, false)); gbn_assert (!play_move_sgf (POS (x, y), BLACK)); switch (get_point_board (POS (x, y))) { case BLACK: ++b_count; break; case WHITE: ++w_count; break; case EMPTY: ++e_count; break; default: gbn_assert (false); } } } gbn_assert (b_count == 0 && w_count == 261 && e_count == 19 * 19 - 261); gbn_assert (undo_node_sgf ()); gbn_assert (move_num == 523); int infinite_prevention = 0; while (move_num > 0) { gbn_assert (undo_node_sgf ()); ++infinite_prevention; gbn_assert (infinite_prevention < 100000); } gbn_assert (save_game (DEFAULT_SAVE_DIR "/long_out.sgf")); /* test of basic moves, legal moves, adding and removing stones */ gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, 0)); gbn_assert (play_move_sgf (POS (MAX_BOARD_SIZE / 2, MAX_BOARD_SIZE / 2), BLACK)); gbn_assert (move_num == 1 && current_player == WHITE); gbn_assert (!legal_move_board (POS (MAX_BOARD_SIZE / 2, MAX_BOARD_SIZE / 2), WHITE, true)); int saved_node = current_node; gbn_assert (add_stone_sgf (POS (0, 0), BLACK)); gbn_assert (current_node != saved_node); gbn_assert (get_point_board (POS (0, 0)) == BLACK); gbn_assert (move_num == 1 && current_player == WHITE); saved_node = current_node; gbn_assert (add_stone_sgf (POS (0, 1), WHITE)); gbn_assert (current_node == saved_node); gbn_assert (get_point_board (POS (0, 1)) == WHITE); gbn_assert (add_stone_sgf (POS (0, 0), EMPTY)); gbn_assert (add_stone_sgf (POS (0, 1), EMPTY)); gbn_assert (get_point_board (POS (0, 0)) == EMPTY); gbn_assert (get_point_board (POS (0, 1)) == EMPTY); /* test captures */ gbn_assert (load_game (DEFAULT_SAVE_DIR "/cap.sgf")); gbn_assert (play_move_sgf (POS (0, 0), BLACK)); gbn_assert (black_captures == 8); gbn_assert (undo_node_sgf ()); gbn_assert (black_captures == 0); gbn_assert (!play_move_sgf (POS (0, 0), WHITE)); play_mode = MODE_FORCE_PLAY; gbn_assert (play_move_sgf (POS (0, 0), WHITE)); play_mode = MODE_PLAY; gbn_assert (black_captures == 9); gbn_assert (get_point_board (POS (0, 0)) == EMPTY); gbn_assert (undo_node_sgf ()); gbn_assert (black_captures == 0); gbn_assert (play_move_sgf (POS (9, 9), BLACK)); gbn_assert (black_captures == 44); for (x = 0; x < 19; ++x) { for (y = 0; y < 19; ++y) { gbn_assert (get_point_board (POS (x, y)) == BLACK || add_stone_sgf (POS (x, y), BLACK)); } } gbn_assert (get_point_board (POS (0, 0)) == BLACK); gbn_assert (add_stone_sgf (POS (9, 9), EMPTY)); gbn_assert (play_move_sgf (POS (9, 9), WHITE)); gbn_assert (white_captures == 360); gbn_assert (undo_node_sgf ()); gbn_assert (white_captures == 0); play_mode = MODE_FORCE_PLAY; gbn_assert (play_move_sgf (POS (9, 9), BLACK)); play_mode = MODE_PLAY; gbn_assert (white_captures == 361); for (x = 0; x < 19; ++x) { for (y = 0; y < 19; ++y) { gbn_assert (get_point_board (POS (x, y)) == EMPTY); } } /* test ko */ gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, 15)); /* * Set up the board to look like this: * -X------ * XO------ * O------- * -------- */ gbn_assert (add_stone_sgf (POS (0, 1), BLACK)); gbn_assert (add_stone_sgf (POS (1, 0), BLACK)); gbn_assert (add_stone_sgf (POS (1, 1), WHITE)); gbn_assert (add_stone_sgf (POS (0, 2), WHITE)); /* take the ko and make sure black can't take back */ gbn_assert (play_move_sgf (POS (0, 0), WHITE)); gbn_assert (!play_move_sgf (POS (0, 1), BLACK)); /* make sure white can fill, even with the ko_pos set */ gbn_assert (play_move_sgf (POS (0, 1), WHITE)); /* and make sure undo sets the ko again */ gbn_assert (undo_node_sgf ()); gbn_assert (!play_move_sgf (POS (0, 1), BLACK)); /* make sure ko threats clear the ko */ gbn_assert (play_move_sgf (POS (2, 2), BLACK)); /* ko threat */ gbn_assert (play_move_sgf (POS (2, 3), WHITE)); /* response */ gbn_assert (play_move_sgf (POS (0, 1), BLACK)); /* take ko */ gbn_assert (undo_node_sgf ()); gbn_assert (undo_node_sgf ()); gbn_assert (undo_node_sgf ()); /* make sure a pass is counted as a ko threat */ gbn_assert (!play_move_sgf (POS (0, 1), BLACK)); gbn_assert (play_move_sgf (PASS_POS, BLACK)); gbn_assert (play_move_sgf (PASS_POS, WHITE)); gbn_assert (play_move_sgf (POS (0, 1), BLACK)); /* and finally let's make sure that white can't directly retake */ gbn_assert (!play_move_sgf (POS (0, 0), WHITE)); /* test some header information saving/loading as well as comment saving loading */ char some_comment[] = "blah blah blah i am a stupid comment. here's some annoying characters: 01234567890!@#$%^&*()[[[[\\\\\\]ABCDEFGHIJKLMNOPQRSTUVWXYZ"; /* that bit near the end is literally this: \\\] which tests escaping of ]s */ char read_buffer[256]; gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 5, -20)); /* this also tests that ko_pos is reset by setuping up a new game */ gbn_assert (play_move_sgf (POS (0, 0), WHITE)); gbn_assert (write_comment_sgf (some_comment) > 0); gbn_assert (play_move_sgf (POS (0, 1), BLACK)); rb->strcpy (header.black, "Jack Black"); rb->strcpy (header.white, "Jill White"); gbn_assert (save_game (DEFAULT_SAVE_DIR "/head.sgf")); gbn_assert (setup_game (MIN_BOARD_SIZE, MIN_BOARD_SIZE, 1, 1)); gbn_assert (load_game (DEFAULT_SAVE_DIR "/head.sgf")); gbn_assert (header.komi == -20 && header.handicap == 5); gbn_assert (board_width == MAX_BOARD_SIZE && board_height == MAX_BOARD_SIZE); gbn_assert (rb->strcmp (header.black, "Jack Black") == 0); gbn_assert (rb->strcmp (header.white, "Jill White") == 0); gbn_assert (redo_node_sgf ()); gbn_assert (read_comment_sgf (read_buffer, sizeof (read_buffer))); gbn_assert (rb->strcmp (read_buffer, some_comment) == 0); gbn_assert (redo_node_sgf ()); gbn_assert (get_point_board (POS (0, 0)) == WHITE); gbn_assert (get_point_board (POS (0, 1)) == BLACK); /* test saving and loading a file with unhandled SGF properties. this test requires that the user diff unhnd.sgf with unhnd_out.sgf (any substantial difference is a bug and should be reported) the following are NOT substantial differences: - reordering of properties in a node - whitespace changes outside of a comment value or other property value - reordering of property values */ gbn_assert (load_game (DEFAULT_SAVE_DIR "/unhnd.sgf")); gbn_assert (save_game (DEFAULT_SAVE_DIR "/unhnd_out.sgf")); /* Test variations a bit */ gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, 13)); /* start at a move, otherwise add_stone won't create a variation */ gbn_assert (play_move_sgf (POS (5, 5), BLACK)); /* make sure it doesn't */ gbn_assert (undo_node_sgf ()); gbn_assert (add_stone_sgf (POS (4, 5), WHITE)); gbn_assert (!undo_node_sgf ()); gbn_assert (num_variations_sgf () == 1); gbn_assert (play_move_sgf (POS (5, 5), BLACK)); gbn_assert (play_move_sgf (POS (0, 0), BLACK)); gbn_assert (num_variations_sgf () == 1); gbn_assert (undo_node_sgf ()); gbn_assert (play_move_sgf (POS (0, 1), BLACK)); gbn_assert (num_variations_sgf () == 2); gbn_assert (undo_node_sgf ()); gbn_assert (play_move_sgf (POS (0, 1), BLACK)); gbn_assert (num_variations_sgf () == 2); gbn_assert (undo_node_sgf ()); gbn_assert (play_move_sgf (POS (0, 2), BLACK)); gbn_assert (num_variations_sgf () == 3); gbn_assert (undo_node_sgf ()); gbn_assert (play_move_sgf (POS (0, 3), WHITE)); gbn_assert (num_variations_sgf () == 4); gbn_assert (undo_node_sgf ()); gbn_assert (play_move_sgf (PASS_POS, BLACK)); gbn_assert (num_variations_sgf () == 5); gbn_assert (undo_node_sgf ()); gbn_assert (add_stone_sgf (POS (1, 1), BLACK)); gbn_assert (add_stone_sgf (POS (1, 2), BLACK)); gbn_assert (add_stone_sgf (POS (1, 3), WHITE)); gbn_assert (num_variations_sgf () == 6); gbn_assert (undo_node_sgf ()); gbn_assert (add_stone_sgf (POS (1, 1), BLACK)); gbn_assert (add_stone_sgf (POS (1, 2), BLACK)); gbn_assert (add_stone_sgf (POS (1, 3), WHITE)); gbn_assert (num_variations_sgf () == 7); gbn_assert (next_variation_sgf ()); gbn_assert (get_point_board (POS (0, 0)) == BLACK); gbn_assert (get_point_board (POS (0, 1)) == EMPTY); gbn_assert (get_point_board (POS (0, 2)) == EMPTY); gbn_assert (get_point_board (POS (1, 1)) == EMPTY); gbn_assert (get_point_board (POS (1, 2)) == EMPTY); gbn_assert (get_point_board (POS (1, 3)) == EMPTY); rb->splash (10 * HZ, "All tests passed. Exiting"); } #endif /* GBN_TEST */