Index: apps/plugins/CATEGORIES =================================================================== --- apps/plugins/CATEGORIES (revision 19752) +++ apps/plugins/CATEGORIES (working copy) @@ -24,6 +24,7 @@ fireworks,demos firmware_flash,apps flipit,games +goban,games greyscale,demos helloworld,demos invadrox,games Index: apps/plugins/viewers.config =================================================================== --- apps/plugins/viewers.config (revision 19752) +++ apps/plugins/viewers.config (working copy) @@ -19,6 +19,7 @@ rsp,viewers/searchengine,8 sok,games/sokoban,1 pgn,games/chessbox,1 +sgf,games/goban,1 ss,games/sudoku,1 wav,viewers/wav2wv,- wav,viewers/mp3_encoder,- Index: apps/plugins/goban/SOURCES =================================================================== --- apps/plugins/goban/SOURCES (revision 0) +++ apps/plugins/goban/SOURCES (revision 0) @@ -0,0 +1,6 @@ +goban.c +board.c +display.c +file_cache.c +game.c +sgf.c Index: apps/plugins/goban/file_cache.c =================================================================== --- apps/plugins/goban/file_cache.c (revision 0) +++ apps/plugins/goban/file_cache.c (revision 0) @@ -0,0 +1,697 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2008 Joshua Simmons * + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "file_cache.h" +#include "types.h" + +static void set_error(file_cache_t * cache); +static void fill_cache(file_cache_t * cache); +static void empty_cache(file_cache_t * cache); +static void advance_cache(file_cache_t * cache); +static void rewind_cache(file_cache_t * cache); + + +bool setup_cache(file_cache_t * cache, + int fd, + uint8_t * buffer, + unsigned int buffer_size) +{ + + cache->error = false; + cache->fd = fd; + cache->buffer = buffer; + cache->buffer_size = buffer_size; + cache->data_size = 0; + cache->position = 0; + cache->dirty = false; + + if (fd < 0 || !buffer || !buffer_size || rb->lseek(fd, 0, SEEK_SET)) + { + set_error(cache); + return false; + } + + return true; +} + +bool is_error(file_cache_t * cache) +{ + return cache->error; +} + +static void set_error(file_cache_t * cache) +{ + cache->error = true; +} + +static void fill_cache(file_cache_t * cache) +{ + unsigned int needed_data; + unsigned int read_data = 0; + int result; + + needed_data = cache->buffer_size - cache->data_size; + + if (needed_data > 0) + { + // position of fd is always set at start of buffer + result = rb->lseek(cache->fd, cache->data_size, SEEK_CUR); + + if (result < 0) + { + set_error(cache); + } + } + else + { + return; + } + + while ((needed_data - read_data) > 0 && !is_error(cache)) + { + result = rb->read(cache->fd, + &(cache->buffer[cache->data_size]), + needed_data - read_data); + if (result < 0) + { + set_error(cache); + } + else if (result == 0) + { + // no more data to read from the file + break; + } + else + { + cache->data_size += result; + read_data += result; + } + } + + result = rb->lseek(cache->fd, -1 * cache->data_size, SEEK_CUR); + + if (result < 0) + { + set_error(cache); + } +} + +static void empty_cache(file_cache_t * cache) +{ + int data_to_write = cache->data_size; + unsigned int written_data = 0; + int result = 0; + + if (!cache->dirty) + { + if (cache->position) + { + result = rb->lseek(cache->fd, + cache->position, + SEEK_CUR); + } + + if (result < 0) + { + set_error(cache); + } + } + else + { + while ((data_to_write - written_data) > 0 && result >= 0) + { + result = rb->write(cache->fd, &(cache->buffer[written_data]), + data_to_write - written_data); + + if (result < 0) + { + set_error(cache); + return; + } + else + { + written_data += result; + } + } + + if (cache->position != written_data) + { + result = rb->lseek(cache->fd, + -1 * written_data + cache->position, + SEEK_CUR); + + if (result < 0) + { + set_error(cache); + } + } + } + + if (!is_error(cache)) + { + cache->data_size = 0; + cache->position = 0; + cache->dirty = false; + } +} + +static void advance_cache(file_cache_t * cache) +{ + if (is_error(cache)) + { + return; + } + + if (cache->position) + { + empty_cache(cache); + } + fill_cache(cache); +} + +static void rewind_cache(file_cache_t * cache) +{ + int max_bytes_to_move; + int result; + + if (!size_before_cache(cache)) + { + return; + } + + empty_cache(cache); + + if (is_error(cache)) + { + return; + } + + max_bytes_to_move = min(size_before_cache(cache), cache->buffer_size); + + result = rb->lseek(cache->fd, -1 * max_bytes_to_move, SEEK_CUR); + + if (result < 0) + { + set_error(cache); + } + + fill_cache(cache); + cache->position = max_bytes_to_move; +} + +size_t size_before_cache(file_cache_t * cache) +{ + int result = rb->lseek(cache->fd, 0, SEEK_CUR); + + if (result < 0) + { + set_error(cache); + return 0; + } + + return result; +} + +size_t size_after_cache(file_cache_t * cache) +{ + int file_size = rb->filesize(cache->fd); + int before_cache = size_before_cache(cache); + + int result; + + if (file_size < 0 || is_error(cache)) + { + set_error(cache); + return 0; + } + + result = file_size - before_cache; + + if (result > 0) + { + if (cache->data_size > (unsigned int) result) + { + result = 0; + } + else + { + result -= cache->data_size; + } + } + else if (result < 0) + { + DEBUGF("IMPOSSIBLE THING IN CACHE!\n"); + debugf_cache(cache); + } + + return result; +} + +int seek_cache(file_cache_t * cache, int seek_size) +{ + int temp; + + + if (is_error(cache)) + { + return 0; + } + + if (seek_size < 0) + { + seek_size *= -1; + temp = min((unsigned int) seek_size, cache->position); + + seek_size -= temp; + cache->position -= temp; + + if (seek_size > 0) + { + seek_size = min((unsigned int) seek_size, size_before_cache(cache)); + temp += seek_size; + } + + while ( (unsigned int) seek_size > cache->position) + { + seek_size -= cache->position; + rewind_cache(cache); + + if (is_error(cache)) + { + return -1 * (temp - seek_size); + } + } + cache->position -= seek_size; + + return -1 * temp; + } + else + { + seek_size = min((unsigned int) seek_size, + (cache->data_size - cache->position) + + size_after_cache(cache)); + temp = seek_size; + while ((unsigned int) seek_size > (cache->data_size - cache->position)) + { + seek_size -= (cache->data_size - cache->position); + cache->position = cache->data_size; + advance_cache(cache); + } + cache->position += seek_size; + return temp; + } +} + +int seek_to_next(file_cache_t * cache, unsigned char goal, bool backwards) +{ + int total_seek_size = 0; + int temp; + int seek_step = backwards ? -1 : 1; + + if (is_error(cache)) + { + return 0; + } + + do + { + temp = seek_cache(cache, seek_step); + if (temp == seek_step) + { + // we moved one more character + total_seek_size++; + } + else + { + // couldn't move, so we're done + break; + } + } while (peek_char_cache(cache) != goal && !is_error(cache)); + + return total_seek_size; +} + +size_t read_cache(file_cache_t * cache, void * buffer, + size_t size) +{ + unsigned int bytes_read = 0; + int temp; + + + if (is_error(cache)) + { + return -1; + } + + while (size) + { + if (cache->position < cache->data_size) + { + temp = min(size, cache->data_size - cache->position); + rb->memcpy(buffer, &(cache->buffer[cache->position]), temp); + size -= temp; + buffer += temp; + bytes_read += temp; + cache->position += temp; + } + else if (size_after_cache(cache) > 0) + { + advance_cache(cache); + } + else + { + break; + } + } + return bytes_read; +} + +size_t write_cache(file_cache_t * cache, void * buffer, + size_t size) +{ + unsigned int bytes_written = 0; + int temp; + + + while (size) + { + if (is_error(cache)) + { + return -1; + } + if (cache->position < cache->buffer_size) + { + temp = min(size, cache->buffer_size - cache->position); + rb->memcpy(&(cache->buffer[cache->position]), buffer, temp); + size -= temp; + buffer += temp; + bytes_written += temp; + cache->position += temp; + cache->dirty = true; + if (cache->position > cache->data_size) + { + cache->data_size = cache->position; + } + } + else + { + advance_cache(cache); + } + } + + return bytes_written; +} + +int read_char_cache(file_cache_t * cache) +{ + int result = peek_char_cache(cache); + + if (result >= 0) + { + cache->position++; + } + + return result; +} + +bool write_char_cache(file_cache_t * cache, char to_write) +{ + return write_cache(cache, (void *) &to_write, 1) == 1; +} + + +bool push_int_cache(file_cache_t * cache, int value) +{ + return write_cache(cache, (void *) &value, sizeof(value)) == sizeof(value); +} + +bool pop_int_cache(file_cache_t * cache, int * dest) +{ + if (seek_cache(cache, -1 * sizeof(*dest)) != + -1 * (signed int) sizeof(*dest)) + { + return false; + } + + return peek_int_cache(cache, dest); +} + +bool peek_int_cache(file_cache_t * cache, int * dest) +{ + int temp; + bool result = true; + + if (is_error(cache)) + { + return false; + } + + temp = read_cache(cache, (void *) dest, sizeof(int)); + + if (temp != sizeof(int)) + { + result = false; + } + + if (temp) + { + if (seek_cache(cache, -1 * sizeof(int)) != + -1 * (signed int) sizeof(int)) + { + result = false; + } + } + + return result; +} + + +int peek_char_cache(file_cache_t * cache) +{ + if (is_error(cache)) + { + return -1; + } + + if (cache->data_size <= cache->position) + { + advance_cache(cache); + } + + if (cache->data_size <= cache->position || is_error(cache)) + { + return -1; + } + else + { + return cache->buffer[cache->position]; + } +} + +bool peek_pos_cache(file_cache_t * cache, pos_t * buffer) +{ + int result; + int temp; + + + if (is_error(cache)) + { + return false; + } + + result = read_cache(cache, buffer, sizeof(pos_t)); + + if (result < 0) + { + return false; + } + + // "undo" the read by stepping back the same amount that we stepped forward + temp = seek_cache(cache, -1 * result); + + if (temp != -1 * result) + { + set_error(cache); + + } + + if ((unsigned int) result < sizeof(pos_t)) + { + return false; + } + else + { + return true; + } +} + +int seek_to_next_pos(file_cache_t * cache, pos_t goal, bool backwards) +{ + + int total_seek_size = 0; + int temp; + pos_t test_pos; + int seek_step = backwards ? -1 * sizeof(goal) : 1 * sizeof(goal); + + + if (is_error(cache)) + { + return 0; + } + + do + { + temp = seek_cache(cache, seek_step); + if (temp == seek_step) + { + // we moved sizeof(goal) characters + total_seek_size += sizeof(goal); + } + else + { + // couldn't move, so we're done + // we should try to undo the partial seek + + if (temp) + { + temp = seek_cache(cache, -1 * temp); + total_seek_size += temp; + } + break; + } + } while (peek_pos_cache(cache, &test_pos) && + test_pos != goal && + !is_error(cache)); + + return total_seek_size; + +} + + +void close_cache(file_cache_t * cache) +{ + if (is_error(cache)) + { + return; + } + + empty_cache(cache); + + rb->close(cache->fd); +} + +bool truncate_cache(file_cache_t * cache) +{ + int result; + + + if (is_error(cache)) + { + return false; + } + + cache->data_size = cache->position; + + result = absolute_position(cache); + + if (result < 0) + { + set_error(cache); + return false; + } + + // truncate to current total data length + result = rb->ftruncate(cache->fd, result); + + if (result < 0) + { + set_error(cache); + return false; + } + + return true; +} + +bool seek_to_beginning(file_cache_t * cache) +{ + int result; + + + if (is_error(cache)) + { + return false; + } + + if (size_before_cache(cache)) + { + empty_cache(cache); // pos goes to beginning + result = rb->lseek(cache->fd, 0, SEEK_SET); + + if (result != 0) + { + set_error(cache); + return false; + } + cache->position = 0; + fill_cache(cache); + } + else + { + cache->position = 0; + } + + return !is_error(cache); +} + +bool cache_has_data(file_cache_t * cache) +{ + if (is_error(cache)) + { + return false; + } + + return cache->data_size != 0 || + size_before_cache(cache) != 0 || + size_after_cache(cache) != 0; // TODO: is this last one possible? + +} + +void debugf_cache(file_cache_t * cache) +{ + DEBUGF("---------------------------------------------\n"); + DEBUGF("fd: %d\n", cache->fd); + DEBUGF("error?: %d\n", is_error(cache)); + DEBUGF("before: %d\n", (int) size_before_cache(cache)); + DEBUGF("after: %d\n", (int) size_after_cache(cache)); + DEBUGF("position: %d\n", (int) cache->position); + DEBUGF("data size: %d\n", (int) cache->data_size); + DEBUGF("buffer size: %d\n", (int) cache->buffer_size); + DEBUGF("abolsute position: %d\n", (int) absolute_position(cache)); + DEBUGF("---------------------------------------------\n"); +} + +int absolute_position(file_cache_t * cache) +{ + if (is_error(cache)) + { + return -1; + } + + return size_before_cache(cache) + cache->position; +} Index: apps/plugins/goban/display.c =================================================================== --- apps/plugins/goban/display.c (revision 0) +++ apps/plugins/goban/display.c (revision 0) @@ -0,0 +1,498 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2008 Joshua Simmons * + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "board.h" +#include "goban.h" +#include "display.h" +#include "game.h" +#include "sgf.h" +#include "lib/fixedpoint.h" + +static int intersection_size = 0; +static int circle_size = 0; +static unsigned char circle_x_precalc[MAX_CIRCLE_SIZE]; + + + +static int board_size = 19; + +#define LINE_OFFSET (intersection_size / 2) + +/* pixel offsets for the board on the LCD */ +static int board_x = 0; +static int board_y = 0; +static int board_pixel_width = 0; +static int board_pixel_height = 0; + + +/* current cursor position in board coordinates (intersections, not pixels) */ +pos_t cursor_pos = POS(0, 0); + + +bool draw_variations = true; + + + +/* function prototypes */ + +static int pixel_x (pos_t pos); +static int pixel_y (pos_t pos); + +static void circle_precalculate (void); + +static void draw_cursor (pos_t pos); +static void draw_stone_raw (int pixel_x, int pixel_y, bool black); +static void draw_stone (pos_t pos, bool black); +static void draw_all_stones (void); + +static void draw_hoshi (pos_t pos); +static void draw_mark (pos_t pos, int type); +static void draw_footer (void); +static void draw_all_variations (void); +static void draw_all_hoshi (void); + + + + + + + + +void +draw_board (void) +{ + int i; + + rb->lcd_set_background (BACKGROUND_COLOR); + rb->lcd_set_foreground (BOARD_COLOR); + + rb->lcd_set_drawmode (DRMODE_FG); + + + rb->lcd_clear_display (); + + rb->lcd_set_foreground (BACKGROUND_COLOR); + + rb->lcd_fillrect (0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1); + + rb->lcd_set_foreground (BOARD_COLOR); + + + rb->lcd_fillrect (board_x, board_y, + intersection_size * board_width, + intersection_size * board_height); + + rb->lcd_set_foreground (LINE_COLOR); + + for (i = 0; i < board_height; ++i) + { + rb->lcd_hline (board_x + LINE_OFFSET, + board_x + LINE_OFFSET + + intersection_size * (board_width - 1), + board_y + LINE_OFFSET + i * intersection_size); + } + + for (i = 0; i < board_width; ++i) + { + rb->lcd_vline (board_x + LINE_OFFSET + i * intersection_size, + board_y + LINE_OFFSET, + board_y + LINE_OFFSET + + intersection_size * (board_height - 1)); + } + + draw_all_hoshi (); + draw_all_stones (); + draw_cursor (cursor_pos); + + if (draw_variations) + { + draw_all_variations (); + } + + draw_footer (); + rb->lcd_update (); +} + + +static void +draw_footer (void) +{ + char captures_buffer[6]; + char end_of_game_mark = ' '; + int size_x, size_y; + + rb->lcd_set_background (BACKGROUND_COLOR); + + rb->lcd_set_foreground (BLACK_COLOR); + + rb->snprintf (captures_buffer, sizeof (captures_buffer), + "%d", white_captures); + + + draw_stone_raw (1, LCD_HEIGHT - circle_size - 1, true); + + rb->lcd_getstringsize (captures_buffer, &size_x, &size_y); + + rb->lcd_putsxy (circle_size + 3, LCD_HEIGHT - size_y, captures_buffer); + + + rb->lcd_set_foreground (WHITE_COLOR); + + rb->snprintf (captures_buffer, sizeof (captures_buffer), + "%d", black_captures); + + rb->lcd_getstringsize (captures_buffer, &size_x, &size_y); + + + draw_stone_raw (LCD_WIDTH - circle_size - 1, + LCD_HEIGHT - circle_size - 1, false); + + rb->lcd_putsxy (LCD_WIDTH - circle_size - 2 - size_x, + LCD_HEIGHT - size_y, captures_buffer); + + + + rb->lcd_set_foreground (BLACK_COLOR); + if (has_more_nodes_sgf()) + { + end_of_game_mark = '+'; + } + else + { + end_of_game_mark = ' '; + } + rb->snprintf (captures_buffer, sizeof (captures_buffer), + "%d%c", move_num, end_of_game_mark); + + rb->lcd_getstringsize (captures_buffer, &size_x, &size_y); + + rb->lcd_putsxy ((LCD_WIDTH - size_x) / 2, + LCD_HEIGHT - size_y - 1, captures_buffer); + +} + + + + + + + +static int +pixel_x (pos_t pos) +{ + return board_x + I(pos) * intersection_size; +} + +static int +pixel_y (pos_t pos) +{ + return board_y + J(pos) * intersection_size; +} + + + + +/* x**2 + y**2 = r**2 + * solve for x, and x = sqrt( r**2 - y**2 ) + * + * we precalculate these so stone drawing doesn't involve lots of + * square roots it only needs to be done once initially and then on + * board resize + * + * this is probably unnecessary, but doing thousands + * of square roots on each board redraw seems stupid + * + * should probably just change this to a table of square roots + */ +static void +circle_precalculate (void) +{ + int i; + int current_y_coord = 0; + + // Radius = diameter^2 / 4 + long radius_squared = (intersection_size * intersection_size) / 4; + + circle_size = intersection_size; + + i = circle_size / 2; + + while (i >= 0) + { + circle_x_precalc[i] = fsqrt (radius_squared - + (current_y_coord * current_y_coord), 0); + ++current_y_coord; + --i; + }; + + for (i = 0; i <= circle_size / 2; ++i) + { + circle_x_precalc[circle_size - i - 1] = circle_x_precalc[i]; + } + +} + + +/* Call every time the board size might have changed! */ +void +setup_graphics (void) +{ + board_size = max (board_width, board_height); + DEBUGF("setting up size: %d = max(%d, %d)\n", + board_size, + board_width, + board_height); + + intersection_size = LCD_MIN_DIMENSION / board_size; + + /* intersections are easier to draw if they are an odd size + * should probably not require this as on some players the + * screen is already too small without wasting up to 19 pixels */ + if (!(intersection_size & 1)) + { + --intersection_size; + } + /* IMPORTANT: YOU CAN'T JUST COMMENT THAT OUT! + * things will probably go wonky if drawing happens outside the screen + * or something like that, because all of my other code assumes + * that intersections are odd sized + */ + + board_x = (LCD_WIDTH - (intersection_size * board_width)) / 2; + board_y = 0; + + circle_precalculate (); + + /* cursor starts on tengen (middle of the board) */ + cursor_pos = POS(board_width / 2, board_height / 2); +} + +#define CURSOR_WIDTH (intersection_size / 2) +static void +draw_cursor (pos_t pos) +{ +/* int saved_draw_mode = rb->lcd_get_drawmode();*/ + + if (!on_board(pos)) + { + return; + } + + rb->lcd_set_foreground (CURSOR_COLOR); + + rb->lcd_drawrect (pixel_x (pos), + pixel_y (pos), intersection_size, intersection_size); +} + +static void +draw_stone_raw (int pixel_x, int pixel_y, bool black) +{ + int i; + + rb->lcd_set_foreground (black ? BLACK_COLOR : WHITE_COLOR); + + for (i = 0; i < circle_size; ++i) + { + rb->lcd_hline (LINE_OFFSET + pixel_x - circle_x_precalc[i], + LINE_OFFSET + pixel_x + circle_x_precalc[i], pixel_y + i); + + /*********************************************************** + * This is the code to add an outline around white circles * + * * + * READ ONLY CODE FOLLOWS, CONTINUE AT YOUR OWN PERIL * + ***********************************************************/ +#if OUTLINE_STONES + if (!black) + { + rb->lcd_set_foreground (BLACK_COLOR); + if (i == 0 || i == circle_size - 1) + { + rb->lcd_hline (LINE_OFFSET + pixel_x - + circle_x_precalc[i], + LINE_OFFSET + pixel_x + + circle_x_precalc[i], pixel_y + i); + } + else + { + int min_off = min (circle_x_precalc[i - 1], + circle_x_precalc[i + 1]); + + + rb->lcd_drawpixel (LINE_OFFSET + pixel_x - + circle_x_precalc[i], pixel_y + i); + rb->lcd_drawpixel (LINE_OFFSET + pixel_x + + circle_x_precalc[i], pixel_y + i); + + if (min_off != circle_x_precalc[i]) + { + ++min_off; + rb->lcd_hline (LINE_OFFSET + pixel_x - + circle_x_precalc[i], + LINE_OFFSET + pixel_x - min_off, pixel_y + i); + rb->lcd_hline (LINE_OFFSET + pixel_x + + circle_x_precalc[i], + LINE_OFFSET + pixel_x + min_off, pixel_y + i); + } + } + rb->lcd_set_foreground (WHITE_COLOR); + } +#endif /* OUTLINE_STONES */ + /**************************************************** + * end of outline drawing code * + * * + * END OF READ ONLY CODE * + ****************************************************/ + } + +} + + +static void +draw_stone (pos_t pos, bool black) +{ + if (!on_board(pos)) + { + return; + } + + draw_stone_raw (pixel_x (pos), pixel_y (pos), black); +} + + +static void +draw_all_stones (void) +{ + int x, y; + pos_t temp_pos; + + for (x = 0; x < board_width; ++x) + { + for (y = 0; y < board_height; ++y) + { + temp_pos = POS(x, y); + if (get_point_board (temp_pos) == EMPTY) + { + continue; + } + + draw_stone (temp_pos, get_point_board (temp_pos) == BLACK); + } + } +} + +static void +draw_hoshi (pos_t pos) +{ + if (!on_board(pos)) + { + return; + } + if (intersection_size > 8) + { + rb->lcd_fillrect (pixel_x (pos) + LINE_OFFSET - 1, + pixel_y (pos) + LINE_OFFSET - 1, 3, 3); + } + else + { + rb->lcd_drawpixel (pixel_x (pos) + LINE_OFFSET - 1, + pixel_y (pos) + LINE_OFFSET - 1); + rb->lcd_drawpixel (pixel_x (pos) + LINE_OFFSET + 1, + pixel_y (pos) + LINE_OFFSET + 1); + } +} + + + + +/* TODO: add specific handling for other mark types */ +static void +draw_mark (pos_t pos, int type) +{ + if (type == MARK_VARIATION) + { + rb->lcd_set_foreground (CURSOR_COLOR); + } + else + { + rb->lcd_set_foreground (MARK_COLOR); + } + + if (intersection_size > 8) + { + rb->lcd_fillrect (pixel_x (pos) + LINE_OFFSET - 1, + pixel_y (pos) + LINE_OFFSET - 1, 3, 3); + } + else + { + rb->lcd_drawpixel (pixel_x (pos) + LINE_OFFSET - 1, + pixel_y (pos) + LINE_OFFSET - 1); + rb->lcd_drawpixel (pixel_x (pos) + LINE_OFFSET + 1, + pixel_y (pos) + LINE_OFFSET + 1); + } +} + +static void +draw_all_variations (void) +{ +} + +static void +draw_all_hoshi (void) +{ + if (board_width != board_height) + { + return; + } + + if (board_size == 19) + { + draw_hoshi (POS(3, 3)); + draw_hoshi (POS(3, 9)); + draw_hoshi (POS(3, 15)); + + draw_hoshi (POS(9, 3)); + draw_hoshi (POS(9, 9)); + draw_hoshi (POS(9, 15)); + + draw_hoshi (POS(15, 3)); + draw_hoshi (POS(15, 9)); + draw_hoshi (POS(15, 15)); + } + else if (board_size == 9) + { + draw_hoshi (POS(2, 2)); + draw_hoshi (POS(2, 6)); + + draw_hoshi (POS(4, 4)); + + draw_hoshi (POS(6, 2)); + draw_hoshi (POS(6, 6)); + } + else if (board_size == 13) + { + draw_hoshi (POS(3, 3)); + draw_hoshi (POS(3, 9)); + + draw_hoshi (POS(6, 6)); + + draw_hoshi (POS(9, 3)); + draw_hoshi (POS(9, 9)); + + } +} Index: apps/plugins/goban/file_cache.h =================================================================== --- apps/plugins/goban/file_cache.h (revision 0) +++ apps/plugins/goban/file_cache.h (revision 0) @@ -0,0 +1,61 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2008 Joshua Simmons * + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#ifndef GOBAN_FILE_CACHE_H +#define GOBAN_FILE_CACHE_H + +#include "types.h" +#include "goban.h" + +bool setup_cache(file_cache_t * cache, + int fd, + uint8_t * buffer, + unsigned int buffer_size); + +bool is_error(file_cache_t * cache); +size_t size_before_cache(file_cache_t * cache); +size_t size_after_cache(file_cache_t * cache); +int seek_cache(file_cache_t * cache, int seek_size); +int seek_to_next(file_cache_t * cache, unsigned char goal, bool backwards); +int seek_to_next_pos(file_cache_t * cache, pos_t goal, bool backwards); +bool seek_to_beginning(file_cache_t * cache); +size_t write_cache(file_cache_t * cache, void * buffer, + size_t size); +size_t read_cache(file_cache_t * cache, void * buffer, + size_t size); +void close_cache(file_cache_t * cache); + +bool write_char_cache(file_cache_t * cache, char to_write); +int read_char_cache(file_cache_t * cache); +int peek_char_cache(file_cache_t * cache); +bool peek_pos_cache(file_cache_t * cache, pos_t * buffer); +bool peek_int_cache(file_cache_t * cache, int * dest); +bool read_int_cache(file_cache_t * cache, int * dest); + +bool pop_int_cache(file_cache_t * cache, int * dest); +bool push_int_cache(file_cache_t * cache, int value); + + +bool truncate_cache(file_cache_t * cache); +bool cache_has_data(file_cache_t * cache); +void debugf_cache(file_cache_t * cache); + +int absolute_position(file_cache_t * cache); + +#endif Index: apps/plugins/goban/game.c =================================================================== --- apps/plugins/goban/game.c (revision 0) +++ apps/plugins/goban/game.c (revision 0) @@ -0,0 +1,247 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2008 Joshua Simmons * + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "game.h" +#include "types.h" +#include "board.h" +#include "goban.h" +#include "sgf.h" + + +char save_file[SAVE_FILE_LENGTH]; +bool game_dirty = false; + +int move_num = 0; + +color_t current_player = BLACK; + +header_t header; + +bool load_game(const char * filename) +{ + rb->memset(&header, 0, sizeof(header)); + + if (rb->strlen(filename) + 1 > SAVE_FILE_LENGTH) + { + return false; + } + + if (!rb->file_exists(filename)) + { + return false; + } + + if (!setup_game(19, 19, 0, 0)) + { + return false; + } + + rb->strcpy(save_file, filename); + + if (!parse_sgf(save_file)) + { + rb->splash(3 * HZ, "Unable to parse SGF file. Saving will overwrite."); + game_dirty = true; + } + + if (header.handicap >= 2) + { + current_player = WHITE; + } + + // this moves to the handicap node in the tree if there was a handicap set + goto_handicap_start_sgf(); + + return true; +} + + +bool save_game(const char * filename) +{ + if (rb->strlen(filename) + 1 > SAVE_FILE_LENGTH) + { + return false; + } + + // we only have to do something if the game is dirty, or we're being + // asked to save to a different location than we loaded from + if (!game_dirty && + rb->strcmp(filename, save_file) == 0) + { + return true; + } + + rb->strcpy(save_file, filename); + + if (output_sgf(save_file)) + { + game_dirty = false; + return true; + } + else + { + return false; + } +} + + + + + +bool setup_game(int width, int height, + int handicap, + fixed_point_t komi) +{ + rb->memset(&header, 0, sizeof(header)); + + clear_caches_sgf(); + free_tree_sgf(); + + // ignored for now + if (!set_size_board(width, height)) + { + return false; + } + + clear_board(); + + // place handicap + + + // TODO: do handicap placement + if (handicap >= 2) + { + current_player = WHITE; + } + else if (handicap < 0) + { + return false; + } + else + { + current_player = BLACK; + } + + header.handicap = handicap; + + setup_handicap_sgf(); + + header.komi = komi; + + if (!setup_undo()) + { + rb->splash(2 * HZ, "Unable to setup undo cache, undo will not work."); + } + + game_dirty = true; + move_num = 0; + + rb->strcpy(save_file, DEFAULT_SAVE); + + return true; +} + +bool has_more_moves_game(void) +{ + return has_more_moves_sgf(); +} + +bool get_header_string_and_size(header_t * header, + param_type_t type, + char ** buffer, + int * size) +{ + if (buffer == 0 || header == 0) + { + return false; + } + + if (type == PARAM_BLACK_NAME) + { + *buffer = header->black; + *size = MAX_NAME; + } + else if (type == PARAM_WHITE_NAME) + { + *buffer = header->white; + *size = MAX_NAME; + } + else if (type == PARAM_BLACK_RANK) + { + *buffer = header->black_rank; + *size = MAX_RANK; + } + else if (type == PARAM_WHITE_RANK) + { + *buffer = header->white_rank; + *size = MAX_RANK; + } + else if (type == PARAM_BLACK_TEAM) + { + *buffer = header->black_team; + *size = MAX_TEAM; + } + else if (type == PARAM_WHITE_TEAM) + { + *buffer = header->white_team; + *size = MAX_TEAM; + } + else if (type == PARAM_DATE) + { + *buffer = header->date; + *size = MAX_DATE; + } + else if (type == PARAM_ROUND) + { + *buffer = header->round; + *size = MAX_ROUND; + } + else if (type == PARAM_EVENT) + { + *buffer = header->event; + *size = MAX_EVENT; + } + else if (type == PARAM_PLACE) + { + *buffer = header->place; + *size = MAX_PLACE; + } + else if (type == PARAM_OVERTIME) + { + *buffer = header->overtime; + *size = MAX_OVERTIME; + } + else if (type == PARAM_RESULT) + { + *buffer = header->result; + *size = MAX_RESULT; + } + else if (type == PARAM_RULESET) + { + *buffer = header->ruleset; + *size = MAX_RULESET; + } + else + { + return false; + } + + return true; +} + Index: apps/plugins/goban/types.h =================================================================== --- apps/plugins/goban/types.h (revision 0) +++ apps/plugins/goban/types.h (revision 0) @@ -0,0 +1,245 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2008 Joshua Simmons * + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#ifndef GOBAN_TYPES_H +#define GOBAN_TYPES_H + +#include "plugin.h" + +#if 0 +#define color_t uint8_t +#define pos_t uint16_t +#endif + +typedef uint8_t color_t; +typedef uint16_t pos_t; +typedef int16_t fixed_point_t; + + +typedef struct +{ + int fd; + size_t buffer_size; + uint8_t * buffer; + size_t position; + size_t data_size; + bool error; + bool dirty; +} file_cache_t; + + +typedef union +{ + pos_t position; + unsigned int number; + int node; +} param_data_t; + +typedef enum _param_types +{ + PARAM_BLACK_MOVE, + PARAM_WHITE_MOVE, + + PARAM_ADD_BLACK, + PARAM_ADD_WHITE, + PARAM_ADD_EMPTY, + + PARAM_PLAYER_TO_PLAY, + PARAM_COMMENT, + + // information about the position reached by the current node + PARAM_EVEN, + PARAM_BLACK_GOOD, + PARAM_WHITE_GOOD, + PARAM_HOTSPOT, + PARAM_UNCLEAR, + PARAM_VALUE, + + // information about the current move + PARAM_BAD, + PARAM_DOUBTFUL, + PARAM_INTERESTING, + PARAM_TESUJI, + + // marks on the board, each takes a list + PARAM_CIRCLE, + PARAM_SQUARE, + PARAM_TRIANGLE, + PARAM_DIM, + PARAM_MARK, + PARAM_SELECTED, + + // labels go on points, names name the node + PARAM_LABEL, + PARAM_NODE_NAME, + + // root params + PARAM_APPLICATION, + PARAM_CHARSET, + PARAM_FILE_FORMAT, + PARAM_GAME, + PARAM_VARIATION_TYPE, + PARAM_SIZE, + + // game info params + PARAM_ANNOTATOR, + PARAM_BLACK_NAME, + PARAM_WHITE_NAME, + PARAM_HANDICAP, + PARAM_KOMI, + PARAM_BLACK_TERRITORY, + PARAM_WHITE_TERRITORY, + PARAM_BLACK_RANK, + PARAM_WHITE_RANK, + PARAM_BLACK_TEAM, + PARAM_WHITE_TEAM, + PARAM_COPYRIGHT, + PARAM_DATE, + PARAM_EVENT, + PARAM_ROUND, + PARAM_GAME_NAME, + PARAM_GAME_COMMENT, + PARAM_OPENING_NAME, + PARAM_OVERTIME, + PARAM_PLACE, + PARAM_RESULT, + PARAM_RULESET, + PARAM_SOURCE, + PARAM_TIME_LIMIT, + PARAM_USER, + + // these are all the X left /after/ the current move + PARAM_BLACK_TIME_LEFT, + PARAM_WHITE_TIME_LEFT, + PARAM_BLACK_STONES_LEFT, // the number of stones left in a canadian style + PARAM_WHITE_STONES_LEFT, // overtime period + + + // these are mostly used for printing, we don't handle these + PARAM_FIGURE, + PARAM_PRINT_MOVE_MODE, + + // view only part of the board. probably don't handle this + PARAM_VIEW, + + // psuedo PARAM types, used for variations and parsing and such + PARAM_VARIATION, + PARAM_INVALID, + PARAM_GENERIC_UNHANDLED, + PARAM_ANY, + PARAM_VARIATION_TO_PROCESS +} param_type_t; + +extern char * param_names[]; +// IMPORTANT: keep this array full of all parameters that we want to +// be able to parse out of an SGF file. This implies that they need +// to go before unparseable (psuedo) params in the above enum, or else +// we need to insert placeholders in the array for unparseables + +#define PARAM_NAMES_SIZE (63) +#define PARAM_NAME_LEN(type) ((type == PARAM_BLACK_MOVE || \ + type == PARAM_WHITE_MOVE) ? 1 : 2) + + +typedef enum _mark_types +{ + MARK_VARIATION, + MARK_SQUARE, + MARK_CIRCLE, + MARK_TRIANGLE, + MARK_LAST_MOVE, + MARK_LABEL +} mark_t; + + +typedef struct +{ + param_data_t data; + int next; + param_type_t type; +} param_t; + + +extern char * ruleset_names[]; + +// IMPORTANT! keep in sync with ruleset_names!!! +typedef enum _rulesets +{ + RULESET_AGA = 0, + RULESET_JAPANESE, + RULESET_CHINESE, + RULESET_NEW_ZEALAND, + RULESET_ING, + __RULESETS_SIZE // make sure i am last! +} ruleset_t; + +#define NUM_RULESETS ((int) __RULESETS_SIZE) + + + + +// possible TODO: change this to an XOR list to get rid of the next pointer +// at the cost of code complexity (and minor efficiency loss) +typedef struct +{ + int params; + int next; + int prev; +} node_t; + + +// convenience union for keeping a mixed array of params and nodes +typedef union +{ + param_t param; + node_t node; +} storage_t; + +#define MAX_NAME 32 +#define MAX_EVENT 64 +#define MAX_RESULT 16 +#define MAX_RANK 16 +#define MAX_TEAM 32 +#define MAX_DATE 32 +#define MAX_ROUND 8 +#define MAX_PLACE 32 +#define MAX_OVERTIME 32 +#define MAX_RULESET 16 + +typedef struct +{ + char white[MAX_NAME]; + char black[MAX_NAME]; + char white_rank[MAX_RANK]; + char black_rank[MAX_RANK]; + char white_team[MAX_TEAM]; + char black_team[MAX_TEAM]; + char date[MAX_DATE]; + char round[MAX_ROUND]; + char event[MAX_EVENT]; + char place[MAX_PLACE]; + char result[MAX_RESULT]; + char overtime[MAX_OVERTIME]; + char ruleset[MAX_RULESET]; + int time_limit; + fixed_point_t komi; + uint8_t handicap; +} header_t; + +#endif Index: apps/plugins/goban/display.h =================================================================== --- apps/plugins/goban/display.h (revision 0) +++ apps/plugins/goban/display.h (revision 0) @@ -0,0 +1,32 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2008 Joshua Simmons * + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#ifndef GOBAN_DISPLAY_H +#define GOBAN_DISPLAY_H + +#include "types.h" + +void setup_graphics (void); +void draw_board (void); + + +extern pos_t cursor_pos; +extern bool draw_variations; + +#endif Index: apps/plugins/goban/game.h =================================================================== --- apps/plugins/goban/game.h (revision 0) +++ apps/plugins/goban/game.h (revision 0) @@ -0,0 +1,49 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2008 Joshua Simmons * + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#ifndef GAME_GOBAN_H +#define GAME_GOBAN_H + +#include "types.h" + +bool setup_game(int width, int height, + int handicap, + fixed_point_t komi); + +bool load_game(const char * filename); +bool save_game(const char * filename); + +bool play_move_game(pos_t pos); +bool undo_game(void); +bool redo_game(void); + +bool has_more_moves_game(void); + +extern int move_num; +extern color_t current_player; +extern char save_file[]; +extern bool game_dirty; +extern header_t header; + +bool get_header_string_and_size(header_t * header, + param_type_t type, + char ** buffer, + int * size); + +#endif Index: apps/plugins/goban/sgf.c =================================================================== --- apps/plugins/goban/sgf.c (revision 0) +++ apps/plugins/goban/sgf.c (revision 0) @@ -0,0 +1,2957 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2008 Joshua Simmons * + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "sgf.h" +#include "types.h" +#include "goban.h" +#include "board.h" +#include "game.h" +#include "file_cache.h" + +#define MIN_STORAGE_BUFFER_SIZE 500 + +// must align by a power of two, this define is one less than the +// requested alignment +#define ALIGNMENT_VAL 7 + + +#define PARSE_STACK_FILE_NAME "/sgf/parse_stack.bin" +#define PARSE_STACK_BUFFER_SIZE (100 * sizeof(int)) + +// used in SGF file parsing and outputting +file_cache_t parse_stack; +uint8_t parse_stack_buffer[PARSE_STACK_BUFFER_SIZE]; + +file_cache_t unhandled_param_list; +uint8_t unhandled_param_list_buffer[128]; + + +#define TEST_SGF_FILE_NAME "/sgf/test.sgf" +#define TEST_OUT_SGF_FILE_NAME "/sgf/test_out.sgf" + +#define UNHANDLED_PARAM_LIST_FILE "/sgf/unhandled.bin" + +#define SGF_FILE_BUFFER_SIZE (1024) +file_cache_t sgf_in_file; +uint8_t sgf_in_file_buffer[SGF_FILE_BUFFER_SIZE]; + +file_cache_t sgf_out_file; +uint8_t sgf_out_file_buffer[SGF_FILE_BUFFER_SIZE]; + + +storage_t * storage_buffer[] = {NULL, NULL}; +size_t storage_buffer_size[] = {0, 0}; + +uint8_t * storage_free_list[] = {NULL, NULL}; +size_t storage_free_list_size[] = {0, 0}; + +bool storage_initialized[] = {false, false}; + +size_t total_storage_size = 0; + +// the next handle to check +int next_free_handle_buffer; +int next_free_handle; + +int tree_head = -1; +int current_node = -1; +int start_node = -1; + + +static bool setup_storage_buffer(char * temp_buffer, size_t size); +static void clear_storage_buffer(int index); +static bool find_free(int * ret_buffer, int * ret_handle); +static bool is_free(int buffer_num, int handle); +static void set_free(int buffer_num, int handle, bool free); +static node_t * get_node(int handle); +static param_t * get_param(int handle); +static bool get_move_from_node(int handle, pos_t * pos, color_t * color); +static bool retreat_node(void); +static bool advance_node(void); +static void debugf_current_node(void); +static bool is_whitespace(int value); +static int peek_char_no_whitespace(file_cache_t * cache); +static int read_char_no_whitespace(file_cache_t * cache); +static void handle_param_value(param_type_t type); +static pos_t sgf_to_pos(char * buffer); +static int read_param_value(char * buffer, size_t buffer_size); +static void parse_param(void); +static void parse_node(void); +static param_type_t parse_param_type(void); +static void eat_gametree(void); +static bool is_handled(param_type_t type); +static void output_param(int param_handle); +static void output_all_params(void); +static bool output_current_node(void); +static void output_gametree(void); +static bool is_important_node(int handle); +static bool goto_next_important_node(bool forward); +static void do_range(param_type_t type, pos_t ul, pos_t br); +static bool do_add_stones(void); +static void output_header_params(void); +static bool output_header_helper(param_type_t type); +static void setup_handicap_helper(pos_t pos); + + + + +static void debugf_current_node(void) +{ + int temp_param = NO_PARAM; + DEBUGF("-----------------------------------------\n"); + DEBUGF("current_node: %d\n", current_node); + DEBUGF("prev/next: %d/%d\n", get_node(current_node)->prev, + get_node(current_node)->next); + DEBUGF("num variations: %d\n", num_variations_sgf()); + DEBUGF("params:\n"); + if (!get_node(current_node) || + (temp_param = get_node(current_node)->params) < 0) + { + DEBUGF("none\n"); + } + + while (1) + { + if (temp_param < 0) + { + break; + } + DEBUGF(" handle: %d\n", temp_param); + DEBUGF(" type: %d ", get_param(temp_param)->type); + if (get_param(temp_param)->type < PARAM_NAMES_SIZE) + { + DEBUGF("(%s)", param_names[get_param(temp_param)->type]); + } + DEBUGF("\n"); + if (get_param(temp_param)->type == PARAM_BLACK_MOVE || + get_param(temp_param)->type == PARAM_WHITE_MOVE || + get_param(temp_param)->type == PARAM_ADD_BLACK || + get_param(temp_param)->type == PARAM_ADD_WHITE || + get_param(temp_param)->type == PARAM_ADD_EMPTY) + { + DEBUGF(" i: %d j: %d\n", I(get_param(temp_param)->data.position), + J(get_param(temp_param)->data.position)); + } + else + { + DEBUGF(" data: %d\n", get_param(temp_param)->data.number); + } + DEBUGF(" next: %d\n", get_param(temp_param)->next); + + temp_param = get_param(temp_param)->next; + if (temp_param >= 0) + { + DEBUGF("\n"); + } + } + + DEBUGF("-----------------------------------------\n"); +} + + + +static bool retreat_node(void) +{ + int result = get_node(current_node)->prev; + + if (result < 0) + { + return false; + } + else + { + current_node = result; + + // go backwards to the next important node (move/add stone/variation/etc.) + goto_next_important_node(false); + debugf_current_node(); + return true; + } +} + +static bool advance_node(void) +{ + int result = get_node(current_node)->next; + + if (result < 0) + { + return false; + } + else + { + current_node = result; + + // go forwards to the next move/add stone/variation/etc. node + goto_next_important_node(true); + debugf_current_node(); + return true; + } +} + + +int num_variations_sgf(void) +{ + int result = 1; + param_t * temp_param; + node_t * temp_node = get_node(current_node); + + if (temp_node == 0) + { + return 0; + } + + temp_param = get_param(temp_node->params); + + while (temp_param) + { + if (temp_param->type == PARAM_VARIATION) + { + ++result; + } + else + { + // variations are at the beginning of the param list + break; + } + + temp_param = get_param(temp_param->next); + } + + return result; +} + +int num_child_variations_sgf(void) +{ + int result; + int saved = current_node; + node_t * node = get_node(current_node); + + if (!node) + { + return 0; + } + + current_node = node->next; + goto_next_important_node(true); + + result = num_variations_sgf(); + current_node = saved; + + return result; +} + +bool go_to_variation_sgf(unsigned int num) +{ + int saved = current_node; + node_t * temp_node = get_node(current_node); + param_t * temp_param; + + if (!temp_node) + { + return false; + } + + temp_node = get_node(temp_node->prev); + + if (!temp_node) + { + return false; + } + + temp_node = get_node(current_node = temp_node->next); + + if (!temp_node) + { + current_node = saved; + return false; + } + + temp_param = get_param(temp_node->params); + + while (num) + { + if (!temp_param || temp_param->type != PARAM_VARIATION) + { + current_node = saved; + return false; + } + + if (num == 1) + { + current_node = temp_param->data.node; + debugf_current_node(); + break; + } + + temp_param = get_param(temp_param->next); + --num; + } + + return true; +} + +int get_matching_child_sgf(pos_t pos, color_t color) +{ + node_t * temp_node; + param_t * temp_param; + pos_t temp_pos; + color_t temp_color; + int variation_count = 0; + int saved; + + // set true later in a loop if we want a param in the first variation + // which means that we shouldn't check that branch for children + bool dont_check_first_var_children = false; + + temp_node = get_node(current_node); + + if (!temp_node) + { + return -3; + } + + temp_node = get_node(temp_node->next); + + if (!temp_node) + { + return -2; + } + + temp_param = get_param(temp_node->params); + + while (temp_param) + { + if (temp_param->type == PARAM_VARIATION) + { + ++variation_count; + saved = current_node; + current_node = temp_param->data.node; + + if (get_move_sgf(&temp_pos, + &temp_color) && + temp_pos == pos && temp_color == color) + { + current_node = saved; + return variation_count; + } + current_node = saved; + } + else if ((temp_param->type == PARAM_BLACK_MOVE && color == BLACK) || + (temp_param->type == PARAM_WHITE_MOVE && color == WHITE)) + { + if (temp_param->data.position == pos) + { + return 0; + } + else + { + return -4; + } + } + else if (temp_param->type == PARAM_ADD_WHITE || + temp_param->type == PARAM_ADD_BLACK || + temp_param->type == PARAM_ADD_EMPTY) + { + dont_check_first_var_children = true; + } + + temp_param = get_param(temp_param->next); + } + + if (dont_check_first_var_children) + { + return -1; + } + else + { + saved = current_node; + current_node = temp_node->next; + + if (get_move_sgf(&temp_pos, + &temp_color) && + temp_pos == pos && temp_color == color) + { + current_node = saved; + return 0; + } + + current_node = saved; + return -1; + } +} + + +int add_child_variation_sgf(void) +{ + node_t * temp_node = get_node(current_node); + param_t * temp_param; + int temp_param_handle; + int new_node = alloc_storage_sgf(); + int new_param = alloc_storage_sgf(); + + if (new_node < 0 || new_param < 0) + { + if (new_node >= 0) + { + free_storage_sgf(new_node); + } + if (new_param >= 0) + { + free_storage_sgf(new_param); + } + + return NO_NODE; + } + + if (!temp_node) + { + free_storage_sgf(new_node); + free_storage_sgf(new_param); + + return NO_NODE; + } + + temp_node = get_node(temp_node->next); + + if (!temp_node) + { + free_storage_sgf(new_node); + free_storage_sgf(new_param); + + return NO_NODE; + } + + get_node(new_node)->prev = current_node; + get_node(new_node)->next = NO_NODE; + get_node(new_node)->params = NO_PARAM; + + get_param(new_param)->type = PARAM_VARIATION; + get_param(new_param)->next = NO_PARAM; + get_param(new_param)->data.node = new_node; + + temp_param_handle = temp_node->params; + + if (temp_param_handle < 0) + { + temp_node->params = new_param; + + return new_node; + } + + if (get_param(temp_param_handle)->type != PARAM_VARIATION) + { + get_param(new_param)->next = temp_node->params; + temp_node->params = new_param; + + return new_node; + } + + // TODO: change this out for generic param adding code + while (1) + { + temp_param = get_param(temp_param_handle); + if (temp_param->next < 0 || + get_param(temp_param->next)->type != PARAM_VARIATION) + { + get_param(new_param)->next = temp_param->next; + temp_param->next = new_param; + + return new_node; + } + + temp_param_handle = temp_param->next; + } +} + + +static bool is_important_node(int handle) +{ + param_t * temp_param; + + if (handle < 0) + { + return false; + } + + temp_param = get_param(get_node(handle)->params); + + while (temp_param) + { + if (temp_param->type == PARAM_BLACK_MOVE || + temp_param->type == PARAM_WHITE_MOVE || + temp_param->type == PARAM_ADD_BLACK || + temp_param->type == PARAM_ADD_WHITE || + temp_param->type == PARAM_ADD_EMPTY || + temp_param->type == PARAM_VARIATION) + { + return true; + } + + temp_param = get_param(temp_param->next); + } + + return false; +} + + + +static bool goto_next_important_node(bool forward) +{ + int temp_node = current_node; + int last_good = temp_node; + + while (temp_node >= 0 && !is_important_node(temp_node)) + { + last_good = temp_node; + + temp_node = forward ? get_node(temp_node)->next : + get_node(temp_node)->prev; + + } + + if (temp_node < 0) + { + current_node = last_good; + } + else + { + current_node = temp_node; + } + + if (temp_node < 0) + { + return false; + } + else + { + return true; + } +} + + + + +bool undo_node_sgf(void) +{ + node_t * node = get_node(current_node); + bool has_add_stones = false; + param_t * temp_param; + + if (!node) + { + // TODO: remove this debug? + DEBUGF("couldn't get current_node! should be impossible!!\n"); + return false; + } + + if (current_node == start_node) + { + // refuse to undo the initial SGF node, which is tree_head if handicap + // == 0 or 1. If handicap >= 2, start_node is the node with the + // handicap crap and added moves on it. don't let the user undo past + // that + DEBUGF("not undoing start_node\n"); + debugf_current_node(); + return false; + } + + temp_param = get_param(node->params); + + while (temp_param) + { + if (temp_param->type == PARAM_ADD_BLACK || + temp_param->type == PARAM_ADD_WHITE || + temp_param->type == PARAM_ADD_EMPTY) + { + has_add_stones = true; + break; + } + temp_param = get_param(temp_param->next); + } + + if (has_add_stones || get_move_sgf(NULL, NULL)) + // TODO: add an || checking for added stones in current_node, + // and do the same action except don't decrement the move number + { + bool result = undo_node_undo(); + if (result) + { + if (!has_add_stones) + { + --move_num; + current_player = OTHER(current_player); + } + + if (!retreat_node()) + { + // TODO: get rid of debug? + DEBUGF("undo_node_sgf successful, but couldn't retreat a node!\n"); + } + } + + if (!result) + { + DEBUGF("undo_node_undo result is false\n"); + } + + return result; + } + + return false; +} + +bool redo_node_sgf(void) +{ + pos_t pos; + color_t color; + + if (!advance_node()) + { + return false; + } + + if (get_move_sgf(&pos, &color)) + { + if (color != current_player) + { + DEBUGF("ERROR: redo_node_sgf: WRONG COLOR!\n"); + debugf_current_node(); + } + + if (!play_move_undo(pos, color)) + { + retreat_node(); + return false; + } + else + { + ++move_num; + current_player = OTHER(color); + return true; + } + } + else if (do_add_stones()) + { + node_finished_undo(); + return true; + } + + + return false; +} + +static bool do_add_stones(void) +{ + // haven't added any yet + bool ret_val = false; + param_t * temp_param; + + if (current_node < 0) + { + return false; + } + + temp_param = get_param(get_node(current_node)->params); + + while (temp_param) + { + if (temp_param->type == PARAM_ADD_BLACK || + temp_param->type == PARAM_ADD_WHITE) + { + add_stone_board(temp_param->data.position, + temp_param->type == PARAM_ADD_BLACK ? BLACK : WHITE); + ret_val = true; + } + else if (temp_param->type == PARAM_ADD_EMPTY) + { + remove_stone_board(temp_param->data.position); + ret_val = true; + } + + temp_param = get_param(temp_param->next); + } + + return ret_val; +} + + + + +int add_child_sgf(void) +{ + int node_handle; + node_t * node; + node_t * current = get_node(current_node); + + if (current->next < 0) + { + node_handle = alloc_storage_sgf(); + node = get_node(node_handle); + + if (node_handle < 0 || !current) + { + return NO_NODE; + } + + node->prev = current_node; + node->next = NO_NODE; + node->params = NO_PARAM; + + current->next = node_handle; + return node_handle; + } + else + { + return add_child_variation_sgf(); + } +} + + +int add_param_sgf(int node, param_type_t type, param_data_t data) +{ + int new_param; + int temp_param_handle; + + if (node < 0) + { + return NO_PARAM; + } + + new_param = alloc_storage_sgf(); + + if (new_param < 0) + { + return NO_PARAM; + } + + get_param(new_param)->type = type; + get_param(new_param)->data = data; + + // check if the new_param goes at the start + if (get_node(node)->params == NO_PARAM || + (type == PARAM_VARIATION && + get_param(get_node(node)->params)->type != PARAM_VARIATION)) + { + + if (get_node(node)->params >= 0) + { + get_param(new_param)->next = get_node(node)->params; + } + else + { + get_param(new_param)->next = NO_PARAM; + } + + get_node(node)->params = new_param; + return new_param; + } + + temp_param_handle = get_node(node)->params; + + while (1) + { + if (get_param(temp_param_handle)->next < 0 || + (get_param(temp_param_handle)->type == type && + get_param(get_param(temp_param_handle)->next)->type != type)) + { + // new_param goes after the current one either because we're at + // the end of the params list, or because we're adding a param + // after the ones of its same type + get_param(new_param)->next = get_param(temp_param_handle)->next; + get_param(temp_param_handle)->next = new_param; + + return new_param; + } + + temp_param_handle = get_param(temp_param_handle)->next; + } +} + +bool add_stone_sgf(pos_t pos, color_t color) +{ + // TODO: finish me + + return false; +} + + +bool add_or_set_param_pos_sgf(param_type_t type, param_data_t data) +{ + int temp_param_handle; + + if (current_node < 0) + { + return false; + } + + temp_param_handle = get_node(current_node)->params; + + while (temp_param_handle >= 0) + { + if ((type == PARAM_ADD_BLACK || + type == PARAM_ADD_WHITE || + type == PARAM_ADD_EMPTY) + && + (get_param(temp_param_handle)->type == PARAM_ADD_BLACK || + get_param(temp_param_handle)->type == PARAM_ADD_WHITE || + get_param(temp_param_handle)->type == PARAM_ADD_EMPTY) + && + (get_param(temp_param_handle)->data.position == + data.position)) + { + // TODO: call into the undo subsection and have it perform + // the correct action based on the contents of its caches + // + // if it returns false (didn't do anything to sgf subsystem), + // then we add the param ourselves (by breaking out of the + // loop maybe) + } + temp_param_handle = get_param(temp_param_handle)->next; + } + + return add_param_sgf(current_node, type, data) >= 0; +} + + +int add_or_set_param_sgf(int node, param_type_t type, param_data_t data) +{ + int new_param; + int temp_param_handle; + + if (node < 0) + { + return NO_PARAM; + } + + temp_param_handle = get_param_sgf(node, type, NULL); + + if (temp_param_handle >= 0) + { + get_param(temp_param_handle)->data = data; + return temp_param_handle; + } + + // there was no param to set, so we need to add one + return add_param_sgf(node, type, data); +} + +int get_param_sgf(int node, param_type_t type, int * previous_param) +{ + int previous_handle = NO_PARAM; + int current_handle; + + if (node < 0) + { + return NO_PARAM; + } + + if (get_node(node)->params < 0) + { + return NO_PARAM; + } + + current_handle = get_node(node)->params; + + while (current_handle >= 0) + { + if (get_param(current_handle)->type == type || + type == PARAM_ANY) + { + if (previous_param) + { + *previous_param = previous_handle; + } + + return current_handle; + } + else + { + previous_handle = current_handle; + current_handle = get_param(current_handle)->next; + } + } + + return NO_PARAM; +} + + +bool delete_param_sgf(int node, param_type_t type) +{ + if (node < 0) + { + return false; + } + + return delete_param_handle_sgf(node, get_param_sgf(node, type, NULL)); +} + +bool delete_param_handle_sgf(int node, int param) +{ + int previous; + int temp; + + if (param < 0 || node < 0 || get_node(node)->params < 0) + { + return false; + } + + if (get_node(node)->params == param) + { + get_node(node)->params = get_param(get_node(node)->params)->next; + free_storage_sgf(param); + return true; + } + + previous = get_node(node)->params; + + while (get_param(previous)->next != param && + get_param(previous)->next >= 0) + { + previous = get_param(previous)->next; + } + + if (get_param(previous)->next < 0) + { + return false; + } + else + { + get_param(previous)->next = get_param(get_param(previous)->next)->next; + free_storage_sgf(param); + return true; + } +} + + + +bool play_move_sgf(pos_t pos, color_t color) +{ + int handle; + int param_handle; + int temp; + int saved = current_node; + + if ((color != BLACK && color != WHITE) || + (!on_board(pos) && pos != PASS_POS)) + { + + return false; + } + + + // go to the node before the next important node (move/add stone/variation) + // this is the right place to look for children, add variations, whatever. + // (if there is no next, we're already at the right place) + + if (get_node(current_node)->next >= 0) + { + current_node = get_node(current_node)->next; + + // true means forward + if (goto_next_important_node(true)) + { + current_node = get_node(current_node)->prev; + } + } + + + if (!play_move_undo(pos, color)) + { + current_node = saved; + return false; + } + + if ((temp = get_matching_child_sgf(pos, color)) >= 0) + { + current_node = get_node(current_node)->next; + go_to_variation_sgf(temp); + + // special handling for variation 0 because PARAM_VARIATIONs + // trip the is_important_node function to true, but if there + // isn't a move at this node, we want to move further + if (temp) + { + goto_next_important_node(true); + current_player = OTHER(color); + ++move_num; + return true; + } + else + { + param_handle = get_node(current_node)->params; + + while (param_handle >= 0) + { + if (get_param(param_handle)->type == PARAM_BLACK_MOVE || + get_param(param_handle)->type == PARAM_WHITE_MOVE) + { + current_player = OTHER(color); + ++move_num; + return true; + } + param_handle = get_param(param_handle)->next; + } + + // if we get here, there wasn't a move in the current node, so + // move to the next important and that HAS to be the right place + // because it's as far as get_matching_child_sgf checks, and it + // told us it found something + + current_node = get_node(current_node)->next; + goto_next_important_node(true); + current_player = OTHER(color); + ++move_num; + return true; + } + } + + handle = add_child_sgf(); + + if (handle < 0) + { + undo_node_undo(); + current_node = saved; + return false; + } + + param_data_t temp_param_data; + temp_param_data.position = pos; + + param_handle = add_param_sgf(handle, + color == BLACK ? PARAM_BLACK_MOVE : + PARAM_WHITE_MOVE, + temp_param_data); + + if (param_handle < 0) + { + // TODO: add code to completely remove the child which we added, + // and then uncomment the following line. probably doens't matter much + // since we're out of memory, but whatever + //free_storage_sgf(handle); + undo_node_undo(); + current_node = saved; + return false; + } + + game_dirty = true; + current_node = handle; + current_player = OTHER(current_player); + ++move_num; + + return true; +} + +bool has_more_moves_sgf(void) +{ + int saved_current = current_node; + bool result; + + result = go_to_next_move_sgf(); + current_node = saved_current; + + return result; +} + +bool has_more_nodes_sgf(void) +{ + int saved = current_node; + bool ret_val = false; + + if (current_node < 0) + { + return false; + } + + current_node = get_node(current_node)->next; + + if (current_node >= 0) + { + // returns true if it finds an important node + ret_val = goto_next_important_node(true); + } + + current_node = saved; + return ret_val; +} + +// logic is different here because the first node in a tree is a valid +// place to go +bool has_prev_nodes_sgf(void) +{ + if (current_node < 0) + { + return false; + } + + return current_node != start_node; +} + + +bool go_to_next_move_sgf(void) +{ + int temp_current; + + temp_current = current_node; + + do + { + temp_current = get_node(temp_current)->next; + + if (temp_current == NO_NODE) + { + return false; + } + else if (get_move_from_node(temp_current, NULL, NULL)) + { + current_node = temp_current; + return true; + } + } while (1); +} + +bool go_to_prev_move_sgf(void) +{ + int temp_current; + + temp_current = current_node; + + do + { + temp_current = get_node(temp_current)->prev; + + if (temp_current == NO_NODE) + { + return false; + } + else if (get_move_from_node(temp_current, NULL, NULL)) + { + current_node = temp_current; + return true; + } + } while (1); +} + + +bool get_move_sgf(pos_t * pos, color_t * color) +{ + bool result = false; + int saved_current = current_node; + + goto_next_important_node(true); + + + if (current_node >= 0 && + get_move_from_node(current_node, pos, color)) + { + result = true; + } + + current_node = saved_current; + return result; +} + +static bool get_move_from_node(int handle, pos_t * pos, color_t * color) +{ + int param_handle; + param_t * temp_param; + + if (handle < 0) + { + return 0; + } + + param_handle = get_node(handle)->params; + + while (param_handle >= 0) + { + if ((temp_param = get_param(param_handle))->type == PARAM_BLACK_MOVE || + temp_param->type == PARAM_WHITE_MOVE) + { + if (pos) + { + *pos = get_param(param_handle)->data.position; + } + + if (color) + { + *color = temp_param->type == PARAM_BLACK_MOVE ? BLACK : WHITE; + } + return true; + } + + param_handle = temp_param->next; + } + + return false; +} + +static void clear_storage_buffer(int index) +{ + int temp; + + // everything starts free + // TODO: use memset + unsigned int i; + for (i = 0; i < storage_free_list_size[index]; ++i) + { + storage_free_list[index][i] = 0xFF; + } + + // if there are extra bits at the end of the free list + // (because storage_buffer_size is not divisible by 8) + // then we set them not free, so we won't end up using + // those ever by accident (shouldn't be possible anyways, + // but makes calculation easier later) + temp = storage_free_list_size[index] * 8 - storage_buffer_size[index]; + storage_free_list[index][storage_free_list_size[index] - 1] ^= + (1 << temp) - 1; +} + +void free_tree_sgf(void) +{ + unsigned int i; + for (i = 0; + i < sizeof(storage_initialized) / + sizeof(storage_initialized[0]); + ++i) + { + if (storage_initialized[i]) + { + clear_storage_buffer(i); + } + } + + tree_head = start_node = current_node = alloc_storage_sgf(); + + get_node(tree_head)->params = NO_PARAM; + get_node(tree_head)->next = NO_NODE; + get_node(tree_head)->prev = NO_NODE; +} + +int alloc_storage_sgf(void) +{ + int buffer_num; + int handle; + int temp_buffer; + int ret_val; + + char * new_storage_buffer; + size_t size; + + if (!find_free(&buffer_num, &handle)) + { + if (!storage_initialized[1]) + { + rb->splash(2*HZ, "Stopping music playback to get more RAM"); + DEBUGF("stealing audio buffer: %d\n", (int) total_storage_size); + + new_storage_buffer = rb->plugin_get_audio_buffer(&size); + setup_storage_buffer(new_storage_buffer, size); + + DEBUGF("after stealing: %d\n", (int) total_storage_size); + } + else + { + return -1; + } + + // try again + if (!find_free(&buffer_num, &handle)) + { + return -1; + } + } + + set_free(buffer_num, handle, false); + + temp_buffer = 0; + ret_val = handle; + + while (temp_buffer != buffer_num) + { + if (storage_initialized[temp_buffer]) + { + ret_val += storage_buffer_size[temp_buffer]; + } + + ++temp_buffer; + } + + return ret_val; +} + +void free_storage_sgf(int handle) +{ + int index; + + // TODO: remove debug + if (handle < 0 || (unsigned int) handle >= total_storage_size) + { + DEBUGF("tried to free an out of bounds handle!!\n"); + } + else + { + index = 0; + while ((unsigned int) handle >= storage_buffer_size[index]) + { + handle -= storage_buffer_size[index++]; + } + rb->memset(&storage_buffer[index][handle], 0xFF, sizeof(storage_t)); + set_free(index, handle, true); + } +} + +static bool find_free(int * ret_buffer, int * ret_handle) +{ + unsigned int buffer_index = next_free_handle_buffer; + unsigned int handle = next_free_handle + 1; + unsigned int start_handle; + unsigned int start_buffer; + + do + { + if (handle >= storage_buffer_size[buffer_index]) + { + handle = 0; + + do { + ++buffer_index; + + if (buffer_index >= sizeof(storage_initialized) / + sizeof(storage_initialized[0])) + { + buffer_index = 0; + } + } while (!storage_initialized[buffer_index]); + + } + + if (is_free(buffer_index, handle)) + { + next_free_handle_buffer = buffer_index; + next_free_handle = handle; + + *ret_buffer = buffer_index; + *ret_handle = handle; + return true; + } + } while (handle != start_handle || buffer_index != start_buffer); + + return false; +} + +static bool is_free(int buffer_num, int handle) +{ + return storage_free_list[buffer_num][handle / 8] & + (1 << (7 - (handle % 8))); +} + +static void set_free(int buffer_num, int handle, bool free) +{ + if (free) + { + // simple, just 'or' the byte with the specific bit switched on + storage_free_list[buffer_num][handle / 8] |= 1 << (7 - (handle % 8)); + } + else + { + // start with a byte with all bits turned on and turn off the + // one we're trying to set to zero. then take that result + // and 'and' it with the current value + storage_free_list[buffer_num][handle /8] &= + 0xFF ^ (1 << (7 - (handle % 8))); + } +} + +bool setup_sgf(void) +{ + size_t size; + char * temp_buffer; + int fd; + + // TODO: make it so more than one buffer can be used + // if we steal the audio buffer (as is, the plugin buffer + // is wasted if we steal the audio buffer) + + temp_buffer = rb->plugin_get_buffer(&size); + setup_storage_buffer(temp_buffer, size); + + DEBUGF("storage_buffer_size: %d\n", (int) total_storage_size); + + + if (total_storage_size < MIN_STORAGE_BUFFER_SIZE) + { + rb->splash(2*HZ, "Stopping music playback to get more RAM"); + DEBUGF("storage_buffer_size < MIN!!: %d\n", (int) total_storage_size); + + temp_buffer = rb->plugin_get_audio_buffer(&size); + setup_storage_buffer(temp_buffer, size); + } + + if (total_storage_size < MIN_STORAGE_BUFFER_SIZE) + { + rb->splash(5*HZ, "Low memory after stopping music. Large files may not load."); + + DEBUGF("storage_buffer_size < MIN!!!!: %d\n", (int) total_storage_size); + } + + DEBUGF("storage_buffer_size: %d\n", (int) total_storage_size); + + + fd = create_or_open_file(PARSE_STACK_FILE_NAME); + + if (fd < 0) + { + return false; + } + + if (!setup_cache(&parse_stack, fd, + (void *) parse_stack_buffer, + sizeof(parse_stack_buffer))) + { + return false; + } + + truncate_cache(&parse_stack); + + if (is_error(&parse_stack)) + { + close_cache(&parse_stack); + return false; + } + + fd = create_or_open_file(UNHANDLED_PARAM_LIST_FILE); + + setup_cache(&unhandled_param_list, fd, + (void *) unhandled_param_list_buffer, + sizeof(unhandled_param_list_buffer)); + + truncate_cache(&unhandled_param_list); + + + return true; +} + + +void clear_caches_sgf(void) +{ + seek_to_beginning(&parse_stack); + seek_to_beginning(&unhandled_param_list); + + truncate_cache(&parse_stack); + truncate_cache(&unhandled_param_list); +} + +void cleanup_sgf(void) +{ + seek_to_beginning(&parse_stack); + truncate_cache(&parse_stack); + seek_to_beginning(&unhandled_param_list); + truncate_cache(&unhandled_param_list); + + close_cache(&unhandled_param_list); + close_cache(&parse_stack); + close_cache(&sgf_in_file); + close_cache(&sgf_out_file); +} + +static bool setup_storage_buffer(char * temp_buffer, size_t size) +{ + unsigned int index = 0; + int temp; + + while (1) + { + if (index >= sizeof(storage_initialized) / + sizeof(storage_initialized[0])) + { + return false; + } + + if (!storage_initialized[index]) + { + break; + } + ++index; + } + + + // alignment + while ((unsigned int) temp_buffer & ALIGNMENT_VAL) + { + temp_buffer++; + size--; + } + + // same as + // temp = size / (sizeof(storage_t) + 1/8) + // + // (we need 1 bit extra for each storage_t, for the + // free list) + // + temp = (8 * (size - ALIGNMENT_VAL - 1)) / (8 * sizeof(storage_t) + 1); + // the - ALIGNMENT_VAL - 1 is for possible wasted space in alignment + // and possible extra byte needed in the free list + + storage_buffer[index] = (void *) temp_buffer; + storage_buffer_size[index] = temp; + + storage_free_list_size[index] = storage_buffer_size[index] / 8; + if (storage_free_list_size[index] * 8 < storage_buffer_size[index]) + { + ++(storage_free_list_size[index]); + } + + temp_buffer += sizeof(storage_t) * temp; + size -= sizeof(storage_t) * temp; + + // alignment + while ((unsigned int) temp_buffer & ALIGNMENT_VAL) + { + temp_buffer++; + size--; + } + + // TODO: remove debug + if (size < storage_free_list_size[index]) + { + DEBUGF("AHHHH NOOOOO shit is fucked up in sgf.c\n"); + } + + storage_free_list[index] = temp_buffer; + + total_storage_size += storage_buffer_size[index]; + + storage_initialized[index] = true; + + clear_storage_buffer(index); + + return true; +} + + +static node_t * get_node(int handle) +{ + if (handle < 0) + { + return NULL; + } + else if ((unsigned int) handle >= total_storage_size) + { + return NULL; + } + + int index = 0; + while ((unsigned int) handle >= storage_buffer_size[index]) + { + handle -= storage_buffer_size[index++]; + } + return &(storage_buffer[index][handle].node); +} + +static param_t * get_param(int handle) +{ + if (handle < 0) + { + return NULL; + } + else if ((unsigned int) handle >= total_storage_size) + { + return NULL; + } + + int index = 0; + while ((unsigned int) handle >= storage_buffer_size[index]) + { + handle -= storage_buffer_size[index++]; + } + return &(storage_buffer[index][handle].param); +} + + +static param_type_t parse_param_type(void) +{ + char buffer[3]; + int pos = 0; + int temp; + + rb->memset(buffer, 0, sizeof(buffer)); + + while (1) + { + temp = peek_char_no_whitespace(&sgf_in_file); + + if (temp == ';' || temp == '[' || temp == '(' || + temp == -1 || temp == ')') + { + if (pos == 1 || pos == 2) + { + break; + } + else + { + return PARAM_INVALID; + } + } + else if (temp >= 'A' && temp <= 'Z') + { + buffer[pos++] = temp; + + if (pos == 2) + { + read_char_no_whitespace(&sgf_in_file); + break; + } + } + + temp = read_char_no_whitespace(&sgf_in_file); + } + + // check if we're still reading a param name, + // in which case we fail (but first we want to + // eat up the rest of the param name) + bool failed = false; + while (peek_char_no_whitespace(&sgf_in_file) != ';' && + peek_char_no_whitespace(&sgf_in_file) != '[' && + peek_char_no_whitespace(&sgf_in_file) != '(' && + peek_char_no_whitespace(&sgf_in_file) != '}' && + peek_char_no_whitespace(&sgf_in_file) != -1) + { + failed = true; + read_char_no_whitespace(&sgf_in_file); + } + + if (failed) + { + return PARAM_INVALID; + } + + int i; + for (i = 0; i < PARAM_NAMES_SIZE; ++i) + { + if (rb->strcmp(buffer, param_names[i]) == 0) + { + return (param_type_t) i; + } + } + return PARAM_INVALID; +} + + +static void eat_gametree(void) +{ + int temp; + int level = 1; + + temp = read_char_no_whitespace(&sgf_in_file); + + if (temp != '(') + { + DEBUGF("eat_gametree called when not at the beginning of a gametree!\n"); + } + + while (level != 0) + { + temp = read_char_no_whitespace(&sgf_in_file); + //////DEBUGF("\\\\\\ %d, %d, %c\n", absolute_position(&sgf_in_file), + ////// level, + ////// temp); + + if (temp == '(') + { + ++level; + } + else if (temp == ')') + { + --level; + } + else if (temp == '[') + { + // skip the param value + // we have to do this in case the value includes '(' or ')' + // (for example, these can easily occur in a Comment) + read_param_value(NULL, 0); + } + else if (temp == -1) + { + return; + } + else + { + // do nothing + } + } +} + + +bool parse_sgf(char * filename) +{ + int saved = current_node; + + // for parsing + int first_handle = 0; // first node in the branch + int file_position = 0; + + int temp; + + int fd = rb->open(filename, O_RDONLY); + + if (fd < 0) + { + return false; + } + + if (!setup_cache(&sgf_in_file, fd, + (void *) sgf_in_file_buffer, sizeof(sgf_in_file_buffer))) + { + return false; + } + + current_node = add_child_sgf(); + + seek_to_beginning(&parse_stack); + truncate_cache(&parse_stack); + + // seek to the first '(' + while (peek_char_no_whitespace(&sgf_in_file) != '(') + { + if (read_char_no_whitespace(&sgf_in_file) == -1) + { + DEBUGF("end of file or error before we found a '('\n"); + debugf_cache(&sgf_in_file); + close_cache(&sgf_in_file); + return false; + } + } + + if (is_error(&sgf_in_file) || is_error(&parse_stack)) + { + DEBUGF("error before real parsing\n"); + close_cache(&sgf_in_file); + return false; + } + + push_int_cache(&parse_stack, absolute_position(&sgf_in_file)); + push_int_cache(&parse_stack, current_node); + + while (pop_int_cache(&parse_stack, &first_handle) && + pop_int_cache(&parse_stack, &file_position)) + { + //////DEBUGF("poped off %d\n", file_position); + + rb->yield(); + + current_node = first_handle; + + if (file_position == -1) + { + temp = read_char_no_whitespace(&sgf_in_file); + //////DEBUGF("trying for sibling: %d %c\n", absolute_position(&sgf_in_file) - 1, temp); + if (temp != '(') + { + // we're here because there may have been a sibling after + // another gametree that was handled, but there's no '(', + // so there wasnt' a sibling, so just go on to any more + // gametrees in the stack + continue; + } + else + { + // there may be more siblings after we process this one + push_int_cache(&parse_stack, -1); + push_int_cache(&parse_stack, first_handle); + } + } + else + { + // check for a sibling after we finish with this node + push_int_cache(&parse_stack, -1); + push_int_cache(&parse_stack, first_handle); + + seek_to_beginning(&sgf_in_file); + seek_cache(&sgf_in_file, file_position); + + // we're at the start of a gametree here, right at the '(' + + temp = read_char_no_whitespace(&sgf_in_file); + + if (temp != '(') + { + DEBUGF("start of gametree doesn't have a '('!\n"); + current_node = saved; + close_cache(&sgf_in_file); + return false; + } + } + + while (1) + { + temp = peek_char_no_whitespace(&sgf_in_file); + //////DEBUGF("||| %d, %c\n", absolute_position(&sgf_in_file), (char) temp); + + if (temp == ';') + { + // cut down on empty nodes (which usually would come about + // at the start of the gametree where we cut out GM, FF, SZ, etc. + // params for special storage). They'll be added back in at the start + // when we output. + // + // Note: exempt the first node because we want an extra there + // for easy tree handling (cuts out special cases in tree functions + // and undos, etc.) + if (get_node(current_node)->params != NO_PARAM || + get_node(current_node)->prev == NO_PARAM) + { + current_node = add_child_sgf(); + } + + read_char_no_whitespace(&sgf_in_file); + parse_node(); + } + else if (temp == ')') + { + // finished this gametree + + // we want to end one past the ')', so eat it up: + read_char_no_whitespace(&sgf_in_file); + break; + } + else if (temp == '(') + { + //////DEBUGF("adding %d\n", absolute_position(&sgf_in_file)); + + push_int_cache(&parse_stack, absolute_position(&sgf_in_file)); + push_int_cache(&parse_stack, current_node); + + break; + } + else if (temp == -1) + { + // TODO: question, should this return? probably not + break; + } + else + { + DEBUGF("extra characters found while parsing: %c\n", temp); + // skip the extras i guess + read_char_no_whitespace(&sgf_in_file); + } + } + } + + current_node = saved; + close_cache(&sgf_in_file); + return true; +} + + + + + + +static void parse_node(void) +{ + int temp; + + + while (1) + { + temp = peek_char_no_whitespace(&sgf_in_file); + + if (temp == -1 || temp == ')' || temp == '(' || temp == ';') + { + return; + } + else + { + parse_param(); + } + } +} + +int start_of_param = 0; + +static void parse_param(void) +{ + param_type_t temp_type = PARAM_INVALID; + int temp; + + + while (1) + { + temp = peek_char_no_whitespace(&sgf_in_file); + + if (temp == -1 || temp == ')' || temp == '(' || temp == ';') + { + return; + } + else if (temp == '[') + { + handle_param_value(temp_type); + } + else + { + start_of_param = absolute_position(&sgf_in_file); + temp_type = parse_param_type(); + } + } +} + +static int read_param_value(char * buffer, size_t buffer_size) +{ + bool escaped = false; + int temp; + int bytes_read = 0; + + // make it a string, the lazy way + rb->memset(buffer, 0, buffer_size); + --buffer_size; + + if (peek_char_cache(&sgf_in_file) == '[') + { + read_char_cache(&sgf_in_file); + } + + while (1) + { + temp = read_char_cache(&sgf_in_file); + if (temp == ']' && !escaped) + { + return bytes_read; + } + else if (temp == '\\') + { + if (escaped) + { + if (buffer && buffer_size) + { + *(buffer++) = temp; + ++bytes_read; + --buffer_size; + } + } + escaped = !escaped; + } + else if (temp == -1) + { + return bytes_read; + } + else + { + escaped = false; + if (buffer && buffer_size) + { + *(buffer++) = temp; + ++bytes_read; + --buffer_size; + } + } + } +} + +static pos_t sgf_to_pos(char * buffer) +{ + if (buffer[0] == 't' && buffer[1] == 't') + { + return PASS_POS; + } + else if (buffer[0] < 'a' || buffer[0] > 'z' || + buffer[1] < 'a' || buffer[1] > 'z') + { + return INVALID_POS; + } + return POS(buffer[0] - 'a', buffer[1] - 'a'); +} + +static void pos_to_sgf(pos_t pos, char * buffer) +{ + if (pos == PASS_POS) + { + // "tt" is a pass per SGF specification + buffer[0] = buffer[1] = 't'; + } + else if (pos != INVALID_POS) + { + buffer[0] = 'a' + I(pos); + buffer[1] = 'a' + J(pos); + } + else + { + DEBUGF("invalid pos converted to SGF\n"); + } +} + +static void handle_param_value(param_type_t type) +{ + // max size of generically supported param values is 6, + // which is 5 for a point range ab:cd and one for the \0 + // + // (this buffer is only used for them, things such as white + // and black player names are stored in different buffers) + + // make it a little bigger for other random crap, like reading in time +#define PARAM_HANDLER_BUFFER_SIZE 16 + + char real_buffer[PARAM_HANDLER_BUFFER_SIZE]; + char * buffer = real_buffer; + + int temp; + param_data_t temp_data; + bool in_param_value = false; + bool escaped = false; + bool done = false; + int temp_width, temp_height; + pos_t temp_pos_ul, temp_pos_br; + int temp_size; + char * temp_buffer; + bool got_value; + + if (!is_handled(type)) + { + //DEBUGF("unhandled param %d\n", (int) type); + + seek_to_beginning(&sgf_in_file); + seek_cache(&sgf_in_file, start_of_param); + + temp_data.number = absolute_position(&unhandled_param_list); + add_param_sgf(current_node, + PARAM_GENERIC_UNHANDLED, + temp_data); + + got_value = false; + while (!done) + { + temp = peek_char_cache(&sgf_in_file); + + switch (temp) + { + case -1: + done = true; + break; + + case '\\': + if (got_value && !in_param_value) + { + done = true; + } + escaped = !escaped; + break; + case '[': + escaped = false; + in_param_value = true; + got_value = true; + break; + case ']': + if (!escaped) + { + in_param_value = false; + } + escaped = false; + break; + case ')': + case '(': + case ';': + if (!in_param_value) + { + done = true; + } + escaped = false; + break; + default: + if (got_value && !in_param_value) + { + if (!is_whitespace(temp)) + { + done = true; + } + } + escaped = false; + break; + }; + + if (done) + { + write_char_cache(&unhandled_param_list, ';'); + } + else + { + write_char_cache(&unhandled_param_list, (char) temp); + read_char_cache(&sgf_in_file); + } + } + + + return; + } + else if (type == PARAM_BLACK_MOVE || + type == PARAM_WHITE_MOVE) + { + //DEBUGF("move param %d\n", (int) type); + + temp = read_param_value(buffer, PARAM_HANDLER_BUFFER_SIZE); + + temp_data.position = INVALID_POS; + + // empty is apparently acceptable as a pass. SGF is stupid + if (temp == 0) + { + temp_data.position = PASS_POS; + } + else if (temp == 2) + { + temp_data.position = sgf_to_pos(buffer); + } + else + { + DEBUGF("invalid move position read in, of wrong size!\n"); + } + + + if (temp_data.position != INVALID_POS) + { + add_param_sgf(current_node, type, temp_data); + } + + return; + } + else if (type == PARAM_ADD_BLACK || + type == PARAM_ADD_WHITE || + type == PARAM_CIRCLE || + type == PARAM_SQUARE || + type == PARAM_TRIANGLE || + type == PARAM_DIM || + type == PARAM_MARK || + type == PARAM_SELECTED) + { + //DEBUGF("add param %d\n", (int) type); + + temp = read_param_value(buffer, PARAM_HANDLER_BUFFER_SIZE); + if (temp == 2) + { + temp_data.position = sgf_to_pos(buffer); + + if (temp_data.position != INVALID_POS && + temp_data.position != PASS_POS) + { + add_param_sgf(current_node, type, temp_data); + } + } + else if (temp == 5) + { + // example: "ab:cd", two positions separated by a colon + temp_pos_ul = sgf_to_pos(buffer); + temp_pos_br = sgf_to_pos(&(buffer[3])); + + if (!on_board(temp_pos_ul) || !on_board(temp_pos_br) || + buffer[2] != ':') + { + DEBUGF("invalid range value!\n"); + } + + do_range(type, temp_pos_ul, temp_pos_br); + } + else + { + DEBUGF("invalid position or range read in. wrong size!\n"); + } + return; + } + else if (type == PARAM_GAME) + { + temp = read_param_value(buffer, PARAM_HANDLER_BUFFER_SIZE); + if (temp != 1 || buffer[0] != '1') + { + rb->splash(2*HZ, "This isn't a Go SGF!! Parsing stopped."); + DEBUGF("incorrect game type loaded!\n"); + + // TODO: is this an okay way to fail? + close_cache(&sgf_in_file); + } + } + else if (type == PARAM_FILE_FORMAT) + { + temp = read_param_value(buffer, PARAM_HANDLER_BUFFER_SIZE); + if (temp != 1 || (buffer[0] != '3' && buffer[0] != '4')) + { + rb->splash(2*HZ, "I don't know how to handle this SGF file version! Parsing stopped."); + DEBUGF("can't handle file format %c\n", buffer[0]); + + // TODO: is this an okay way to fail? + close_cache(&sgf_in_file); + } + } + else if (type == PARAM_APPLICATION || + type == PARAM_CHARSET) + { + // we don't give a shit. on output we'll write our own values for these + read_param_value(NULL, 0); + } + else if (type == PARAM_SIZE) + { + temp = read_param_value(buffer, PARAM_HANDLER_BUFFER_SIZE); + if (temp == 0) + { + rb->splash(HZ, "Invalid board size specified in file."); + } + else + { + temp_width = rb->atoi(buffer); + while (*buffer != ':' && *buffer != '\0') + { + ++buffer; + } + + if (*buffer != '\0') + { + ++buffer; + temp_height = rb->atoi(buffer); + } + else + { + temp_height = temp_width; + } + + + if (!set_size_board(temp_width, temp_height)) + { + rb->splashf(HZ, "Board too big/small! (%dx%d) Stopping parse.", + temp_width, temp_height); + close_cache(&sgf_in_file); + } + else + { + // TODO: maybe check if there's already moves before this in + // the tree and...warn the user? probably not important to do + // as any SGF file that misuses this is way broken anyway + clear_board(); + } + } + } + else if (type == PARAM_KOMI) + { + temp = read_param_value(buffer, PARAM_HANDLER_BUFFER_SIZE); + + if (temp == 0) + { + header.komi = 0; + DEBUGF("invalid komi specification. setting to zero\n"); + } + else + { + header.komi = rb->atoi(buffer) << 1; + // TODO: should i really allow ',' for decimal? + while (*buffer != '.' && *buffer != ',' && *buffer != '\0') + { + ++buffer; + } + + if (buffer != '\0') + { + ++buffer; + + if(*buffer == 0) + { + // do nothing + } + else if (*buffer >= '1' && *buffer <= '9') + { + header.komi += 1; + } + else + { + DEBUGF("extra characters after komi value!\n"); + } + } + } + } + else if (type == PARAM_BLACK_NAME || + type == PARAM_WHITE_NAME || + type == PARAM_BLACK_RANK || + type == PARAM_WHITE_RANK || + type == PARAM_BLACK_TEAM || + type == PARAM_WHITE_TEAM || + type == PARAM_DATE || + type == PARAM_ROUND || + type == PARAM_EVENT || + type == PARAM_PLACE || + type == PARAM_OVERTIME || + type == PARAM_RESULT || + type == PARAM_RULESET) + { + if (!get_header_string_and_size(&header, type, &temp_buffer, &temp_size)) + { + // TODO: remove debug + rb->splash(5 * HZ, "BIG ERROR! how did you get here? sgf.c 244893"); + } + + temp = read_param_value(temp_buffer, temp_size - 1); + + DEBUGF("read %d bytes into header for type: %d\n", temp, type); + DEBUGF("data: %s\n", temp_buffer); + } + else if (type == PARAM_TIME_LIMIT) + { + temp = read_param_value(buffer, PARAM_HANDLER_BUFFER_SIZE); + header.time_limit = rb->atoi(buffer); + DEBUGF("setting time: %d (%s)\n", header.time_limit, buffer); + } + else if (type == PARAM_HANDICAP) + { + temp = read_param_value(buffer, PARAM_HANDLER_BUFFER_SIZE); + if (start_node == tree_head) + { + if (rb->atoi(buffer) >= 2) + { + start_node = current_node; + temp_data.number = header.handicap = rb->atoi(buffer); + add_param_sgf(current_node, type, temp_data); + DEBUGF("setting handicap: %d\n", header.handicap); + } + else + { + DEBUGF("invalid HAndicap param. ignoring\n"); + } + } + else + { + rb->splash(HZ, "extraneous HAndicap param present in file!\n"); + } + } + else + { + DEBUGF("UNHANDLED PARAM TYPE!!!\n"); + read_param_value(NULL, 0); + } +} + + + +// upper-left and bottom right +static void do_range(param_type_t type, pos_t ul, pos_t br) +{ + // this code is overly general and accepts ranges even if ul + // and br aren't the required corners it's easier doing that + // that failing if the input is bad + + bool x_reverse = false; + bool y_reverse = false; + param_data_t temp_data; + + if (I(br) < I(ul)) + { + x_reverse = true; + } + + if (J(br) < J(ul)) + { + y_reverse = true; + } + + int x, y; + for (x = I(ul); + x_reverse ? (x >= I(br)) : (x <= I(br)); + x_reverse ? --x : ++x) + { + for (y = J(ul); + y_reverse ? (y >= J(br)) : (y <= J(br)); + y_reverse ? --y : ++y) + { + temp_data.position = POS(x, y); + + DEBUGF("adding %d %d for range (type %d)\n", + I(temp_data.position), J(temp_data.position), type); + add_param_sgf(current_node, type, temp_data); + } + } +} + + + +static int read_char_no_whitespace(file_cache_t * cache) +{ + int result = peek_char_no_whitespace(cache); + + read_char_cache(cache); + + return result; +} + +static int peek_char_no_whitespace(file_cache_t * cache) +{ + int result; + + while (is_whitespace(result = peek_char_cache(cache))) + { + read_char_cache(cache); + } + + return result; +} + +static bool is_whitespace(int value) +{ + if (value == ' ' || + value == '\t' || + value == '\n' || + value == '\r' || + value == '\f' || + value == '\v') + { + return true; + } + else + { + return false; + } +} + +static bool is_handled(param_type_t type) +{ + if (type == PARAM_BLACK_MOVE || + type == PARAM_WHITE_MOVE || + type == PARAM_ADD_BLACK || + type == PARAM_ADD_WHITE || + type == PARAM_ADD_BLACK || + type == PARAM_CIRCLE || + type == PARAM_SQUARE || + type == PARAM_TRIANGLE || + type == PARAM_DIM || + type == PARAM_MARK || + type == PARAM_SELECTED || + type == PARAM_GAME || + type == PARAM_FILE_FORMAT || + type == PARAM_APPLICATION || + type == PARAM_CHARSET || + type == PARAM_SIZE || + type == PARAM_KOMI || + type == PARAM_BLACK_NAME || + type == PARAM_WHITE_NAME || + type == PARAM_BLACK_RANK || + type == PARAM_WHITE_RANK || + type == PARAM_BLACK_TEAM || + type == PARAM_WHITE_TEAM || + type == PARAM_DATE || + type == PARAM_ROUND || + type == PARAM_EVENT || + type == PARAM_PLACE || + type == PARAM_OVERTIME || + type == PARAM_RESULT || + type == PARAM_TIME_LIMIT || + type == PARAM_RULESET || + type == PARAM_HANDICAP) + { + return true; + } + + return false; +} + +bool output_sgf(char * filename) +{ + int current = -1; + param_data_t temp_data; + int saved = current_node; + + int fd = create_or_open_file(filename); + + if (fd < 0) + { + return false; + } + + DEBUGF("outputting to: %s (%d)\n", filename, fd); + + if (!setup_cache(&sgf_out_file, fd, + (void *) sgf_out_file_buffer, sizeof(sgf_out_file_buffer))) + { + close_cache(&sgf_out_file); + return false; + } + + seek_to_beginning(&parse_stack); + truncate_cache(&parse_stack); + seek_to_beginning(&sgf_out_file); + truncate_cache(&sgf_out_file); + + if (is_error(&parse_stack) || is_error(&sgf_out_file)) + { + return false; + } + + if (tree_head < 0) + { + close_cache(&sgf_out_file); + return false; + } + + push_int_cache(&parse_stack, tree_head); + + while (pop_int_cache(&parse_stack, ¤t)) + { + int var_to_process = 0; + int temp_param = get_param_sgf(current, PARAM_VARIATION_TO_PROCESS, NULL); + + if (temp_param >= 0) + { + var_to_process = get_param(temp_param)->data.number; + } + + current_node = current; + + if (var_to_process > 0) + { + write_char_cache(&sgf_out_file, ')'); + write_char_cache(&sgf_out_file, '\n'); + } + + if (var_to_process == num_variations_sgf()) + { + delete_param_sgf(current, PARAM_VARIATION_TO_PROCESS); + + continue; + } + else + { + write_char_cache(&sgf_out_file, '('); + + // we need to do more processing on this branchpoint, either + // to do more variations or to output the ')' + push_int_cache(&parse_stack, current); + + // increment the stored variation to process + temp_data.number = var_to_process + 1; + add_or_set_param_sgf(current, + PARAM_VARIATION_TO_PROCESS, + temp_data); + } + + rb->yield(); + + // now we did the setup for sibling varaitions to be processed + // so do the actual outputting of a game tree branch + + go_to_variation_sgf(var_to_process); + output_gametree(); + } + + current_node = saved; + close_cache(&sgf_out_file); + DEBUGF("done outputting, file closed\n"); + return true; +} + + +static void output_header_params(void) +{ + char buffer[128]; + + rb->strncpy(buffer, "GM[1]FF[4]", sizeof(buffer)); + write_cache(&sgf_out_file, buffer, rb->strlen(buffer)); + + // board size + if (board_width != board_height) + { + rb->snprintf(buffer, sizeof(buffer), "%s[%d:%d]", + param_names[PARAM_SIZE], board_width, board_height); + } + else + { + rb->snprintf(buffer, sizeof(buffer), "%s[%d]", + param_names[PARAM_SIZE], board_width); + } + + write_cache(&sgf_out_file, buffer, rb->strlen(buffer)); + + /* + rb->snprintf(buffer, sizeof(buffer), "%s[%d]", + param_names[PARAM_HANDICAP], header.handicap); + write_cache(&sgf_out_file, buffer, rb->strlen(buffer)); +*/ + rb->snprintf(buffer, sizeof(buffer), "%s[", param_names[PARAM_KOMI]); + write_cache(&sgf_out_file, buffer, rb->strlen(buffer)); + + snprint_fixed(buffer, sizeof(buffer), header.komi); + write_cache(&sgf_out_file, buffer, rb->strlen(buffer)); + + write_char_cache(&sgf_out_file, ']'); + + output_header_helper(PARAM_RULESET); + output_header_helper(PARAM_RESULT); + + output_header_helper(PARAM_BLACK_NAME); + output_header_helper(PARAM_WHITE_NAME); + output_header_helper(PARAM_BLACK_RANK); + output_header_helper(PARAM_WHITE_RANK); + output_header_helper(PARAM_BLACK_TEAM); + output_header_helper(PARAM_WHITE_TEAM); + + output_header_helper(PARAM_EVENT); + output_header_helper(PARAM_PLACE); + output_header_helper(PARAM_DATE); + + if (output_header_helper(PARAM_OVERTIME) || header.time_limit != 0) + { + rb->snprintf(buffer, sizeof(buffer), "%s[%d]", + param_names[PARAM_TIME_LIMIT], header.time_limit); + write_cache(&sgf_out_file, buffer, rb->strlen(buffer)); + } + + +} + + +static bool output_header_helper(param_type_t type) +{ + char * buffer; + int size; + char temp_buffer[16]; + + if (!get_header_string_and_size(&header, type, &buffer, &size)) + { + DEBUGF("output_header_helper called with invalid param type!!\n"); + return false; + } + + if (rb->strlen(buffer)) + { + rb->snprintf(temp_buffer, sizeof(temp_buffer), "%s[", + param_names[type]); + + write_cache(&sgf_out_file, temp_buffer, rb->strlen(temp_buffer)); + + write_cache(&sgf_out_file, buffer, rb->strlen(buffer)); + + rb->strcpy(temp_buffer, "]"); + + write_cache(&sgf_out_file, temp_buffer, rb->strlen(temp_buffer)); + + return true; + } + + return false; +} + + + +static void output_gametree(void) +{ + + while (output_current_node()) + { + current_node = get_node(current_node)->next; + } + +} + +static bool output_current_node(void) +{ + if (current_node < 0) + { + return false; + } + + if (num_variations_sgf() > 1 && + get_param_sgf(current_node, PARAM_VARIATION_TO_PROCESS, NULL) < 0) + { + // push it up for the gametree stuff to take care of it + // and fail out, stopping the node printing + push_int_cache(&parse_stack, current_node); + return false; + } + + write_char_cache(&sgf_out_file, '\n'); + write_char_cache(&sgf_out_file, ';'); + + // the first node gets the header + if (get_node(current_node)->prev < 0) + { + output_header_params(); + } + + output_all_params(); + + return true; +} + + + +param_type_t last_output_type = PARAM_INVALID; + +static void output_all_params(void) +{ + int temp_handle = get_node(current_node)->params; + + last_output_type = PARAM_INVALID; + + while (temp_handle >= 0) + { + output_param(temp_handle); + temp_handle = get_param(temp_handle)->next; + } +} + +static void output_param(int param_handle) +{ + char buffer[16]; + param_type_t temp_type = get_param(param_handle)->type; + + buffer[0] = 't'; + buffer[1] = 't'; + + if (is_handled(temp_type)) + { + if (temp_type != last_output_type) + { + write_cache(&sgf_out_file, + param_names[temp_type], + PARAM_NAME_LEN(temp_type)); + } + + write_char_cache(&sgf_out_file, '['); + + if (temp_type == PARAM_HANDICAP) + { + rb->snprintf(buffer, sizeof(buffer), "%d", + get_param(param_handle)->data.number); + write_cache(&sgf_out_file, buffer, rb->strlen(buffer)); + } + else + { + pos_to_sgf(get_param(param_handle)->data.position, buffer); + + write_cache(&sgf_out_file, + buffer, + 2); + } + + write_char_cache(&sgf_out_file, ']'); + } + else if (temp_type == PARAM_GENERIC_UNHANDLED) + { + bool escaped = false; + bool in_param_value = false; + int temp; + bool done = false; + + seek_to_beginning(&unhandled_param_list); + seek_cache(&unhandled_param_list, + get_param(param_handle)->data.number); + + while (!done) + { + temp = peek_char_cache(&unhandled_param_list); + + switch (temp) + { + case ';': + escaped = false; + if (in_param_value) + { + break; + } + //otherwise, fall through + case -1: + done = true; + break; + + case '\\': + escaped = !escaped; + break; + + case '[': + escaped = false; + in_param_value = true; + break; + + case ']': + if (!escaped) + { + in_param_value = false; + } + escaped = false; + break; + + default: + escaped = false; + break; + }; + + if (!done) + { + write_char_cache(&sgf_out_file, temp); + read_char_cache(&unhandled_param_list); + } + } + } + + last_output_type = temp_type; +} + + +void setup_handicap_sgf(void) +{ + param_data_t temp_data; + + DEBUGF("1\n"); + if (header.handicap <= 1) + { + return; + } + + current_node = tree_head; + current_node = add_child_sgf(); + + DEBUGF("blah: %d %d\n", tree_head, current_node); + + if (current_node < 0) + { + current_node = tree_head; + return; + } + DEBUGF("2\n"); + + start_node = current_node; + + temp_data.number = header.handicap; + add_param_sgf(current_node, PARAM_HANDICAP, temp_data); + + // now, add the actual stones + + if ((board_width != 19 && board_width != 13 && board_width != 9) || + board_width != board_height || header.handicap > 9) + { + rb->splashf(5 * HZ, "Please use the 'Add Black' tool to add %d handicap stones here before you do anything else!", header.handicap); + return; + } + DEBUGF("3\n"); + + int handicaps_to_place = header.handicap; + + int low_coord, mid_coord, high_coord; + + if (board_width == 19) + { + low_coord = 3; + mid_coord = 9; + high_coord = 15; + } + else if (board_width == 13) + { + low_coord = 3; + mid_coord = 6; + high_coord = 9; + } + else if (board_width == 9) + { + low_coord = 2; + mid_coord = 4; + high_coord = 6; + } + + // first four go in the corners + handicaps_to_place -= 2; + setup_handicap_helper(POS(high_coord, low_coord)); + setup_handicap_helper(POS(low_coord, high_coord)); + + if (!handicaps_to_place) + { + goto done_adding_stones; + } + + --handicaps_to_place; + setup_handicap_helper(POS(high_coord, high_coord)); + + if (!handicaps_to_place) + { + goto done_adding_stones; + } + + --handicaps_to_place; + setup_handicap_helper(POS(low_coord, low_coord)); + + if (!handicaps_to_place) + { + goto done_adding_stones; + } + + // now done with first four, if only one left it goes in the center + if (handicaps_to_place == 1) + { + --handicaps_to_place; + setup_handicap_helper(POS(mid_coord, mid_coord)); + } + else + { + handicaps_to_place -= 2; + setup_handicap_helper(POS(high_coord, mid_coord)); + setup_handicap_helper(POS(low_coord, mid_coord)); + } + + if (!handicaps_to_place) + { + goto done_adding_stones; + } + + // done with first 6 + + if (handicaps_to_place == 1) + { + --handicaps_to_place; + setup_handicap_helper(POS(mid_coord, mid_coord)); + } + else + { + handicaps_to_place -= 2; + setup_handicap_helper(POS(mid_coord, high_coord)); + setup_handicap_helper(POS(mid_coord, low_coord)); + } + + if (!handicaps_to_place) + { + goto done_adding_stones; + } + + // done with first eight, there can only be the tengen remaining + + setup_handicap_helper(POS(mid_coord, mid_coord)); + +done_adding_stones: + goto_handicap_start_sgf(); + return; +} + + +static void setup_handicap_helper(pos_t pos) +{ + param_data_t temp_data; + + temp_data.position = pos; + + add_param_sgf(current_node, PARAM_ADD_BLACK, temp_data); +} + +void goto_handicap_start_sgf(void) +{ + if (start_node != tree_head) + { + current_node = get_node(start_node)->prev; + redo_node_sgf(); + } +} + Index: apps/plugins/goban/goban.make =================================================================== --- apps/plugins/goban/goban.make (revision 0) +++ apps/plugins/goban/goban.make (revision 0) @@ -0,0 +1,22 @@ +# __________ __ ___. +# Open \______ \ ____ ____ | | _\_ |__ _______ ___ +# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +# \/ \/ \/ \/ \/ +# $Id$ +# + + +GOBANSRCDIR := $(APPSDIR)/plugins/goban +GOBANBUILDDIR := $(BUILDDIR)/apps/plugins/goban + +ROCKS += $(GOBANBUILDDIR)/goban.rock + + +GOBAN_SRC := $(call preprocess, $(GOBANSRCDIR)/SOURCES) +GOBAN_OBJ := $(call c2obj, $(GOBAN_SRC)) + +OTHER_SRC += $(GOBAN_SRC) + +$(GOBANBUILDDIR)/goban.rock: $(GOBAN_OBJ) Index: apps/plugins/goban/sgf.h =================================================================== --- apps/plugins/goban/sgf.h (revision 0) +++ apps/plugins/goban/sgf.h (revision 0) @@ -0,0 +1,70 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2008 Joshua Simmons * + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#ifndef GOBAN_SGF_H +#define GOBAN_SGF_H + +#include "types.h" + +bool setup_sgf(void); +void cleanup_sgf(void); +void clear_caches_sgf(void); +void free_tree_sgf(void); + + +int alloc_storage_sgf(void); +void free_storage_sgf(int handle); +bool play_move_sgf(pos_t pos, color_t color); +bool add_stone_sgf(pos_t pos, color_t color); + +bool has_prev_nodes_sgf(void); +bool has_more_nodes_sgf(void); + +bool has_more_moves_sgf(void); +bool go_to_prev_move_sgf(void); +bool go_to_next_move_sgf(void); +bool go_to_variation_sgf(unsigned int num); + +int num_variations_sgf(void); +int num_child_variations_sgf(void); +int add_child_variation_sgf(void); +int add_child_sgf(void); + +int add_param_sgf(int node, param_type_t type, param_data_t data); +bool delete_param_sgf(int node, param_type_t type); +bool delete_param_handle_sgf(int node, int param); +int get_param_sgf(int node, param_type_t type, int * previous_param); +int add_or_set_param_sgf(int node, param_type_t type, param_data_t data); +bool add_or_set_param_pos_sgf(param_type_t type, param_data_t data); +bool get_move_sgf(pos_t * pos, color_t * color); + +bool undo_node_sgf(void); +bool redo_node_sgf(void); + +bool parse_sgf(char * filename); +bool output_sgf(char * filename); + +void setup_handicap_sgf(void); +void goto_handicap_start_sgf(void); + + +#define NO_NODE (-1) +#define NO_PARAM (-1) + +#endif Index: apps/plugins/goban/goban.c =================================================================== --- apps/plugins/goban/goban.c (revision 0) +++ apps/plugins/goban/goban.c (revision 0) @@ -0,0 +1,575 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2008 Joshua Simmons * + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + + +#include "plugin.h" + + +PLUGIN_HEADER +#include "goban.h" +#include "game.h" +#include "board.h" +#include "display.h" +#include "file_cache.h" +#include "sgf.h" +#include "types.h" + +const struct plugin_api *rb; + + + +uint8_t buffer_one[1024]; +uint8_t buffer_two[1024]; +uint8_t buffer_three[517]; + + + +static void global_setup(void); +static void global_cleanup(void); + + +// IMPORTANT: keep in sync with the param_type_t enum +// in types.h +char * param_names[] = {"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" + }; + +char * ruleset_names[] = {"AGA", "Japanese", "Chinese", "New Zealand", "Ing"}; + + + +int create_or_open_file(char const * filename) +{ + int fd; + + if (!rb->file_exists(filename)) + { + fd = rb->creat(filename); + } + else + { + fd = rb->open(filename, O_RDWR); + } + + return fd; +} + + +static void komi_formatter(char * dest, size_t size, int menu_item, + const char* unknown) +{ + (void) unknown; + snprint_fixed(dest, size, menu_item); +} + +static void ruleset_formatter(char * dest, size_t size, int menu_item, + const char* unknown) +{ + (void) unknown; + rb->snprintf(dest, size, "%s", ruleset_names[menu_item]); +} + + +static void time_formatter(char * dest, size_t size, int menu_item, + const char * unknown) +{ + int time_values[4]; // days hours minutes seconds + int min_set, max_set; + int temp; + + (void) unknown; + + time_values[0] = menu_item / (24 * 60 * 60); + menu_item %= (24 * 60 * 60); + time_values[1] = menu_item / (60 * 60); + menu_item %= (60 * 60); + time_values[2] = menu_item / 60; + time_values[3] = menu_item % 60; + + min_set = 500; + max_set = -1; + int i; + for (i = 0; (unsigned int) i < (sizeof(time_values) / sizeof(time_values[0])); ++i) + { + if (time_values[i]) + { + if (i < min_set) + { + min_set = i; + } + + if (i > max_set) + { + max_set = i; + } + } + } + + if (max_set == -1) + { + rb->snprintf(dest, size, "0"); + return; + } + + for (i = min_set; i <= 3; ++i) + { + if (i <= max_set) + { + if (i == 0 || i == 1 || i == min_set) + { + rb->snprintf(dest, size, "%d", time_values[i]); + } + else + { + rb->snprintf(dest, size, "%02d", time_values[i]); + } + temp = rb->strlen(dest); + dest += temp; + size -= temp; + } + else if (i != 3) + { + continue; + } + + if (i == 0) // days + { + rb->snprintf(dest, size, " d "); + } + else if (i == 3) // seconds, print the final units + { + if (min_set == 0 || min_set == 1) + { + rb->snprintf(dest, size, " h"); + } + else if (min_set == 2) + { + rb->snprintf(dest, size, " m"); + } + else + { + rb->snprintf(dest, size, " s"); + } + } + else if (i != max_set) + { + rb->snprintf(dest, size, ":"); + } + + temp = rb->strlen(dest); + dest += temp; + size -= temp; + } +} + + +enum plugin_status +plugin_start (const struct plugin_api *api, const void *parameter) +{ + int btn; + char backup_save_file[SAVE_FILE_LENGTH]; + bool return_val; + + // for "New" in menu + int new_handi = 0, new_bs = 19, new_komi = 15; + + rb = api; + + rb->mkdir ("/sgf"); + + global_setup(); + + if (!(parameter && load_game(parameter))) + { + if (!load_game(DEFAULT_SAVE)) + { + rb->strcpy(save_file, DEFAULT_SAVE); + + // TODO: new game here + if (!setup_game(19, 19, 0, 0)) + { + return PLUGIN_ERROR; + } + } + } + draw_board (); + + int selection = 0; + MENUITEM_STRINGLIST (menu, "Rockbox Goban", NULL, + "New", "Save", "Save As", "Game Info", "Quit"); + + int new_ruleset = 0; + + char * gameinfo_string; + int gameinfo_string_size; + + bool gameinfo_done = false; + int gameinfo_selection = 0; + MENUITEM_STRINGLIST(gameinfo_menu, "Game Info", NULL, + "Time Limit", + "Overtime", + "Result", + "Handicap", + "Komi", + "Ruleset", + "Black Player", + "Black Rank", + "Black Team", + "White Player", + "White Rank", + "white Team", + "Date", + "Event", + "Place", + "Round", + "Done"); + + + for (;;) + { + btn = rb->button_get_w_tmo (HZ * 30); + /* break every 30 seconds to tell rockbox we're not idle */ + rb->reset_poweroff_timer (); + + + switch (btn) + { + case SGFBUTTON_ADVANCE: + case SGFBUTTON_ADVANCE | BUTTON_REPEAT: + if (has_more_nodes_sgf()) + { + if (!redo_node_sgf()) + { + rb->splash(2 * HZ, "Redo failed"); + } + draw_board(); + } + break; + + case SGFBUTTON_RETREAT: + case SGFBUTTON_RETREAT | BUTTON_REPEAT: + if (has_prev_nodes_sgf()) + { + if (!undo_node_sgf()) + { + rb->splash(3 * HZ / 2, "undo failed"); + } + draw_board(); + } + break; + + case SGFBUTTON_PLAY: + if (!play_move_sgf (cursor_pos, current_player)) + { + rb->splash(HZ / 3, "illegal move or error"); + } + draw_board (); + break; + + case SGFBUTTON_RIGHT: + case SGFBUTTON_RIGHT | BUTTON_REPEAT: + cursor_pos = WRAP(EAST(cursor_pos)); + draw_board (); + break; + + case SGFBUTTON_LEFT: + case SGFBUTTON_LEFT | BUTTON_REPEAT: + cursor_pos = WRAP(WEST(cursor_pos)); + draw_board (); + break; + + case SGFBUTTON_DOWN: + case SGFBUTTON_DOWN | BUTTON_REPEAT: + cursor_pos = WRAP(SOUTH(cursor_pos)); + draw_board (); + break; + + case SGFBUTTON_UP: + case SGFBUTTON_UP | BUTTON_REPEAT: + cursor_pos = WRAP(NORTH(cursor_pos)); + draw_board (); + break; + case SGFBUTTON_VAR_TOG: + rb->splash (HZ, "Toggled next move drawing"); + + draw_variations = !draw_variations; + draw_board (); + break; + case SGFBUTTON_MENU: + + rb->lcd_set_foreground (WHITE_COLOR); + rb->lcd_set_background (BLACK_COLOR); + + bool done = false; + + while (!done) + { + selection = rb->do_menu (&menu, &selection, NULL, false); + + switch (selection) + { + case 0: /*"New" */ + + rb->set_int ("board size", "lines", UNIT_INT, + &new_bs, NULL, 1, 3, 19, NULL); + + return_val = rb->set_int ("handicap", "stones", UNIT_INT, + &new_handi, NULL, 1, 0, 9, NULL); + + if (new_handi > 0) + { + new_komi = 1; + } + else + { + new_komi = 13; + } + + rb->set_int ("komi", "moku", UNIT_INT, &new_komi, NULL, + 1, -300, 300, &komi_formatter); + + setup_game(new_bs, new_bs, new_handi, new_komi); + draw_board(); + done = true; + break; + + case 2: /*"Save As" */ + rb->strcpy(backup_save_file, save_file); + + if (!rb->kbd_input(backup_save_file, SAVE_FILE_LENGTH)) + { + break; + } + + if (!save_game(backup_save_file)) + { + rb->splash(2 * HZ, "Save Failed!"); + rb->strcpy(save_file, backup_save_file); + } + else + { + rb->splash(2 * HZ / 3, "Saved."); + } + + done = true; + draw_board(); + break; + case 1: /*"Save" */ + if (!save_game(save_file)) + { + rb->splash(2 * HZ, "Save Failed!"); + } + else + { + rb->splash(2 * HZ / 3, "Saved."); + } + done = true; + draw_board(); + break; + + case 3: /* "Game Info" */ + gameinfo_done = false; + + while (!gameinfo_done) + { + gameinfo_selection = rb->do_menu(&gameinfo_menu, + &gameinfo_selection, + NULL, + false); + + switch (gameinfo_selection) + { + case 1: /* "Overtime", */ + case 2: /* "Result", */ + case 6: /* "Black Player", */ + case 7: /* "Black Rank", */ + case 8: /* "Black Team", */ + case 9: /* "White Player", */ + case 10: /* "White Rank", */ + case 11: /* "white Team", */ + case 12: /* "Date", */ + case 13: /* "Event", */ + case 14: /* "Place", */ + case 15: /* "Round", */ + if (!get_header_string_and_size(&header, + menu_selection_to_param(gameinfo_selection), + &gameinfo_string, + &gameinfo_string_size)) + { + // TODO: remove debug + rb->splash(5 * HZ, "BIG ERROR! how did you get here?\ + goban.c 141425"); + } + + // TODO: test if user cancelled and copy the original + // back + rb->kbd_input(gameinfo_string, gameinfo_string_size); + game_dirty = true; + break; + + // these need special handling in some way, so they are + // separate + case 3: /* "Handicap", */ + rb->splash(2 * HZ, "Handicap cannot be set on existing games, please start a new one!"); + break; + + case 0: /* "Time Limit", */ + rb->set_int("Time Limit", "", UNIT_INT, &header.time_limit, + NULL, 60, 0, 24 * 60 * 60, &time_formatter); + game_dirty = true; + break; + + case 4: /* "Komi", */ + rb->set_int ("Komi", "moku", UNIT_INT, &header.komi, NULL, + 1, -300, 300, &komi_formatter); + game_dirty = true; + break; + + case 5: /* "Ruleset", */ + new_ruleset = 0; + rb->set_int("Ruleset", "", UNIT_INT, &new_ruleset, NULL, + 1, 0, NUM_RULESETS - 1, &ruleset_formatter); + + rb->strcpy(header.ruleset, ruleset_names[new_ruleset]); + game_dirty = true; + break; + + case 16: /*"Done")*/ + case GO_TO_ROOT: + case GO_TO_PREVIOUS: + case MENU_ATTACHED_USB: + gameinfo_done = true; + break; + }; + } + break; + + case MENU_ATTACHED_USB: + case 4: /*"Quit" */ + + if (game_dirty) + { + save_game(DEFAULT_SAVE); + } + + global_cleanup(); + return PLUGIN_OK; + + case GO_TO_ROOT: + case GO_TO_PREVIOUS: + default: + + done = true; + break; + }; + } + + + draw_board (); + break; + default: + if (rb->default_event_handler (btn) == SYS_USB_CONNECTED) + { + return PLUGIN_USB_CONNECTED; + } + break; + }; + + } + return PLUGIN_OK; +} + +static void global_cleanup(void) +{ + cleanup_undo(); + cleanup_sgf(); +} + +static void global_setup(void) +{ + setup_graphics (); + setup_sgf(); +} + + +int snprint_fixed(char * buffer, int buffer_size, fixed_point_t fixed) +{ + if (fixed == -1) + { + return rb->snprintf(buffer, buffer_size, "-0.5"); + } + else + { + return rb->snprintf(buffer, + buffer_size, "%d.%d", + fix_fixed(fixed) >> 1, + (fixed & 1) * 5); + } +} + +param_type_t menu_selection_to_param(int selection) +{ + switch (selection) + { + case 1: /* "Overtime", */ + return PARAM_OVERTIME; + case 2: /* "Result", */ + return PARAM_RESULT; + case 6: /* "Black Player", */ + return PARAM_BLACK_NAME; + case 7: /* "Black Rank", */ + return PARAM_BLACK_RANK; + case 8: /* "Black Team", */ + return PARAM_BLACK_TEAM; + case 9: /* "White Player", */ + return PARAM_WHITE_NAME; + case 10: /* "White Rank", */ + return PARAM_WHITE_RANK; + case 11: /* "white Team", */ + return PARAM_WHITE_TEAM; + case 12: /* "Date", */ + return PARAM_DATE; + case 13: /* "Event", */ + return PARAM_EVENT; + case 14: /* "Place", */ + return PARAM_PLACE; + case 15: /* "Round", */ + return PARAM_ROUND; + default: + DEBUGF("tried to get param from invalid menu selection!!!\n"); + return PARAM_PLACE; // just pick one, there's a major bug if we got here + }; +} + Index: apps/plugins/goban/board.c =================================================================== --- apps/plugins/goban/board.c (revision 0) +++ apps/plugins/goban/board.c (revision 0) @@ -0,0 +1,865 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2008 Joshua Simmons * + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "goban.h" +#include "board.h" +#include "display.h" +#include "file_cache.h" +#include "types.h" + +#include "plugin.h" + +int board_width = MAX_BOARDSIZE; +int board_height = MAX_BOARDSIZE; + +// Board has 'invalid' markers around each border +color_t board_data[(MAX_BOARDSIZE + 2) * (MAX_BOARDSIZE + 2)]; +int white_captures = 0; +int black_captures = 0; + +uint8_t board_marks[(MAX_BOARDSIZE + 2) * (MAX_BOARDSIZE + 2)]; +uint8_t current_mark = 255; + +pos_t ko_pos = INVALID_POS; + +#define UNDO_CACHE_FILE "/sgf/undo_cache.bin" +#define BLACK_CACHE_FILE "/sgf/black_cache.bin" +#define WHITE_CACHE_FILE "/sgf/white_cache.bin" + +bool undo_is_setup = false; + +// The following three file_caches are used to store undo information +// this will allow us an essentially unlimited number of moves to undo +// while not using a ridiculous amount of RAM +// +// the tradeoff, of course, is that we may need to go to the disc for +// information if the user goes back too far and then again when they go +// forward +#define UNDO_BUFFER_SIZE 113 +// TODO: try to figure out what a good number for this define is, +// the current is just a guess +file_cache_t undo_cache; +uint8_t undo_cache_buffer[UNDO_BUFFER_SIZE]; + +// enough space for 25 changes to be cached in memory +// before going to disc +#define CHANGE_BUFFER_SIZE 92 +uint8_t black_cache_buffer[CHANGE_BUFFER_SIZE]; +uint8_t white_cache_buffer[CHANGE_BUFFER_SIZE]; +file_cache_t black_cache; +file_cache_t white_cache; + + +// forward declarations +static void setup_marks (void); +static void make_mark (pos_t pos); +static void set_point (pos_t pos, color_t data); +static int get_liberties (pos_t pos); +static void recurse_libs (pos_t pos, color_t color); +static void capture_group (pos_t pos); +static int do_opponent_capture (pos_t pos, color_t color, bool suicide_check); +static int do_captures (pos_t pos, color_t color); +static bool raw_add_stone (pos_t pos, color_t color); +static bool do_add_stone (pos_t pos, color_t color, bool force); +static void node_finished_undo_helper(file_cache_t * cache, + char record_type); +static bool undo_node_undo_helper(int phase); + + + +// these aren't "board marks" in the marks on the goban sense, they are +// used internally to mark already visited points and the like (such as when +// doing liberty counting for groups) +static void +setup_marks (void) +{ + int x, y; + + current_mark++; + + if (current_mark == 0) + { + current_mark++; + + for (y = 0; y < board_height; ++y) + { + for (x = 0; x < board_width; ++x) + { + board_marks[POS (x, y)] = 0; + } + } + } +} + +static void +make_mark (pos_t pos) +{ + board_marks[pos] = current_mark; +} + +static bool +is_marked (pos_t pos) +{ + return board_marks[pos] == current_mark; +} + +void +clear_board (void) +{ + int i, x, y; + + // for the borders + // + // this is kind of wasteful, but the efficient way is ugly + // and this isn't called much + for (i = 0; i < (2 + MAX_BOARDSIZE) * (2 + MAX_BOARDSIZE); ++i) + { + board_data[i] = INVALID; + } + + // now make the actual board part + for (y = 0; y < board_height; ++y) + { + for (x = 0; x < board_width; ++x) + { + board_data[POS (x, y)] = EMPTY; + } + } + + white_captures = 0; + black_captures = 0; +} + +bool +set_size_board (int width, int height) +{ + if (width < MIN_BOARDSIZE || width > MAX_BOARDSIZE || + height < MIN_BOARDSIZE || height > MAX_BOARDSIZE) + { + return false; + } + else + { + board_width = width; + board_height = height; + setup_graphics(); + return true; + } +} + +color_t +get_point_board (pos_t pos) +{ + return board_data[pos]; +} + +static void +set_point (pos_t pos, color_t data) +{ + board_data[pos] = data; +} + +bool +on_board (pos_t pos) +{ + if (board_data[pos] == INVALID) + { + return false; + } + else + { + return true; + } +} + +bool +add_stone_board (pos_t pos, color_t color) +{ + return do_add_stone (pos, color, false); +} + +static bool +raw_add_stone (pos_t pos, color_t color) +{ + return do_add_stone (pos, color, true); +} + +static bool +do_add_stone (pos_t pos, color_t color, bool force) +{ + if (!force && + ((color != WHITE && color != BLACK) || + !on_board (pos) || get_point_board (pos) != EMPTY)) + { + return false; + } + else + { + add_stone_undo(pos, color); + set_point (pos, color); + + return true; + } +} + + +int counted_libs; + +static int +get_liberties (pos_t pos) +{ + if (get_point_board (pos) == EMPTY) + { + return -1; + } + + setup_marks (); + + counted_libs = 0; + recurse_libs (pos, get_point_board (pos)); + return counted_libs; +} + +static void +recurse_libs (pos_t pos, color_t color) +{ + if (counted_libs >= 2 || is_marked (pos) || !on_board (pos) || get_point_board (pos) == OTHER (color)) + { + return; + } + make_mark (pos); + + if (get_point_board (pos) == EMPTY) + { + ++counted_libs; + } + + recurse_libs (NORTH (pos), color); + recurse_libs (SOUTH (pos), color); + recurse_libs (EAST (pos), color); + recurse_libs (WEST (pos), color); +} + +bool +remove_stone_board (pos_t pos) +{ + if (!on_board (pos) || + (get_point_board (pos) != WHITE && get_point_board (pos) != BLACK)) + { + return false; + } + + remove_stone_undo(pos, get_point_board(pos)); + set_point (pos, EMPTY); + return true; +} + +static void +capture_group (pos_t pos) +{ + color_t color = get_point_board (pos); + + if (color != BLACK && color != WHITE) + { + return; + } + else + { + remove_stone_board (pos); + if (color == BLACK) + { + white_captures++; + } + else + { + black_captures++; + } + } + + if (get_point_board (NORTH (pos)) == color) + { + capture_group (NORTH (pos)); + } + if (get_point_board (SOUTH (pos)) == color) + { + capture_group (SOUTH (pos)); + } + if (get_point_board (EAST (pos)) == color) + { + capture_group (EAST (pos)); + } + if (get_point_board (WEST (pos)) == color) + { + capture_group (WEST (pos)); + } + +} + +static int +do_opponent_capture (pos_t pos, color_t color, bool suicide_check) +{ + if (get_point_board (pos) == OTHER (color) && get_liberties (pos) == 0) + { + if (suicide_check && + get_point_board(NORTH(pos)) != OTHER(color) && + get_point_board(SOUTH(pos)) != OTHER(color) && + get_point_board(EAST(pos)) != OTHER(color) && + get_point_board(WEST(pos)) != OTHER(color)) + { + // if we got here, we're doing a suicide of a single stone + // which isn't allowed, so reject it + + return -1; + } + capture_group (pos); + return 1; + } + else + { + return 0; + } +} + +static int +do_captures (pos_t pos, color_t color) +{ + int result; + // are any of the neighbors enemy pieces that + // are now captured? + // + // these aren't suicides, so suicide_check = false + bool cap_n = do_opponent_capture (NORTH (pos), color, false); + bool cap_s = do_opponent_capture (SOUTH (pos), color, false); + bool cap_e = do_opponent_capture (EAST (pos), color, false); + bool cap_w = do_opponent_capture (WEST (pos), color, false); + + // if nothing was captured above, then see if the move is suicide + if (!(cap_n || cap_s || cap_e || cap_w)) + { + if ((result = do_opponent_capture (pos, OTHER (color), true)) < 0) + { + // suicided one stone, return 0 (aka false) + return 0; + } + else if (result > 0) + { + // suicided multiple stones, return -1 + return -1; + } + } + + // captured normally or captured nothing + return 1; +} + +bool +play_move_undo (pos_t pos, color_t color) +{ + int prev_black = black_captures; + int prev_white = white_captures; + int captures_this_move; + int temp; + + if ((color != WHITE && color != BLACK) || + !on_board (pos) || get_point_board (pos) != EMPTY || pos == INVALID_POS) + { + return false; + } + + // special handling of passes, just finish the move and clear the ko_pos + if (pos == PASS_POS) + { + node_finished_undo(); + ko_pos = INVALID_POS; + return true; + } + + if (pos == ko_pos) + { + return false; + } + + // this logic is a little ugly, but here's the summary: + // + // -set the point to the color we want so that we can try doing captures + // -if the capture fails, we tried to do a single stone suicide (illegal), + // so undo the set_point and return false + // -if the capture succeeds, we undo our temporary set_point and really + // add the stone (so it goes into the undo list) + set_point(pos, color); + if ((temp = do_captures (pos, color))) + { + set_point(pos, EMPTY); + raw_add_stone(pos, color); + if (temp < 0) + { + // if it was a suicide, there's not supposed to be a stone there + set_point(pos, EMPTY); + } + } + else + { + set_point(pos, EMPTY); + return false; + } + + node_finished_undo(); + + // now we need to check if the stone we just played cannot be captured + // because it would violate ko rules + // + // this is true if the following conditions are met: + // + // -we captured one stone + // -we are one stone + // -we have one liberty + + captures_this_move = color == BLACK ? black_captures - prev_black: + white_captures - prev_white; + + if (captures_this_move == 1 && + get_point_board(NORTH(pos)) != color && + get_point_board(SOUTH(pos)) != color && + get_point_board(EAST(pos)) != color && + get_point_board(WEST(pos)) != color && + get_liberties(pos) == 1) + { + // then the point which cannot be played by the opponent, the ko_pos + // is the single liberty of pos + if (get_point_board(NORTH(pos)) == EMPTY) + { + ko_pos = NORTH(pos); + } else if (get_point_board(SOUTH(pos)) == EMPTY) + { + ko_pos = SOUTH(pos); + } else if (get_point_board(EAST(pos)) == EMPTY) + { + ko_pos = EAST(pos); + } else if (get_point_board(WEST(pos)) == EMPTY) + { + ko_pos = WEST(pos); + } + } + else + { + // no ko this move + ko_pos = INVALID_POS; + } + return true; +} + +pos_t WRAP(pos_t pos) +{ + int x, y; + if (on_board(pos)) + { + return pos; + } + else + { + x = I(pos); + y = J(pos); + + if (x < 0) + { + x = board_width - 1; + } + else if (x >= board_width) + { + x = 0; + } + + if (y < 0) + { + y = board_height - 1; + } + else if (y >= board_height) + { + y = 0; + } + return POS(x, y); + } +} + + +bool setup_undo(void) +{ + int fd; + + if (undo_is_setup) + { + return true; + } + + // this function looks confusing, but it's pretty simple + // it simply opens or creates the file for each cache, and then + // sets up the actual file_cache. on failure at any step + // the previous successful steps are undone + + fd = create_or_open_file(UNDO_CACHE_FILE); + + if (fd < 0) + { + return false; + } + + + if (!setup_cache(&undo_cache, fd, undo_cache_buffer, UNDO_BUFFER_SIZE)) + { + rb->close(fd); + return false; + } + + fd = create_or_open_file(BLACK_CACHE_FILE); + if (fd < 0) + { + return false; + } + + if (!setup_cache(&black_cache, fd, black_cache_buffer, CHANGE_BUFFER_SIZE)) + { + rb->close(fd); + close_cache(&undo_cache); + return false; + } + + fd = create_or_open_file(WHITE_CACHE_FILE); + if (fd < 0) + { + return false; + } + + if (!setup_cache(&white_cache, fd, white_cache_buffer, CHANGE_BUFFER_SIZE)) + { + rb->close(fd); + close_cache(&undo_cache); + close_cache(&black_cache); + return false; + } + + truncate_cache(&undo_cache); + truncate_cache(&black_cache); + truncate_cache(&white_cache); + + if (is_error(&undo_cache) || + is_error(&black_cache) || + is_error(&white_cache)) + { + close_cache(&undo_cache); + close_cache(&black_cache); + close_cache(&white_cache); + return false; + } + + undo_is_setup = true; + + return true; +} + +void cleanup_undo(void) +{ + if (undo_is_setup) + { + seek_to_beginning(&undo_cache); + truncate_cache(&undo_cache); + close_cache(&undo_cache); + + seek_to_beginning(&black_cache); + truncate_cache(&black_cache); + close_cache(&black_cache); + + seek_to_beginning(&white_cache); + truncate_cache(&white_cache); + close_cache(&white_cache); + } + + undo_is_setup = false; +} + +void add_stone_undo(pos_t pos, color_t color) +{ + file_cache_t * file_cache; + char temp = 'A'; // A for Add + + if (color != BLACK && color != WHITE) + { + return; + } + + file_cache = color == BLACK ? &black_cache : &white_cache; + write_cache(file_cache, &temp, 1); + write_cache(file_cache, &pos, sizeof(pos)); +} + +void remove_stone_undo(pos_t pos, color_t color) +{ + file_cache_t * file_cache; + char temp = 'D'; // D for Delete + + if (color != BLACK && color != WHITE) + { + return; + } + + file_cache = color == BLACK ? &black_cache : &white_cache; + write_cache(file_cache, &temp, 1); + write_cache(file_cache, &pos, sizeof(pos)); +} + + +void node_finished_undo(void) +{ + pos_t temp; + + temp = UNDO_RECORD_START; + write_cache(&undo_cache, &temp, sizeof(temp)); + write_cache(&undo_cache, &ko_pos, sizeof(ko_pos)); + + + // output all Delete records from black and white caches into + // the undo cache + node_finished_undo_helper(&black_cache, 'D'); + node_finished_undo_helper(&white_cache, 'D'); + + // output all Add records from black and white caches into + // the undo cache + node_finished_undo_helper(&black_cache, 'A'); + node_finished_undo_helper(&white_cache, 'A'); + + // kill the data in the black and white caches to get + // ready for the next move + seek_to_beginning(&white_cache); + truncate_cache(&white_cache); + seek_to_beginning(&black_cache); + truncate_cache(&black_cache); + + ////rb->splashf(HZ / 2, "after nfu: %d", absolute_position(&undo_cache)); +} + + +static void node_finished_undo_helper(file_cache_t * cache, char record_type) +{ + int result; + char temp_buffer[sizeof(pos_t) + 1]; + pos_t temp; + + seek_to_beginning(cache); + while ((result = read_cache(cache, temp_buffer, sizeof(temp_buffer)))) + { + if (result != sizeof(temp_buffer)) + { + break; + } + + if (temp_buffer[0] == record_type) + { + write_cache(&undo_cache, temp_buffer + 1, sizeof(temp_buffer) - 1); + } + } + temp = UNDO_RECORD_SPACER; + write_cache(&undo_cache, &temp, sizeof(temp)); +} + + + +#define ADDED_BLACK_PHASE 0 +#define ADDED_WHITE_PHASE 1 +#define REMOVED_BLACK_PHASE 2 +#define REMOVED_WHITE_PHASE 3 + +bool undo_node_undo(void) +{ + int result; + pos_t temp_pos; + + + if (cache_has_data(&black_cache) || cache_has_data(&white_cache)) + { + // a partial move has already been input but not finished + // there is no good way for this function to know what to do with + // the partial data, so just fail + + DEBUGF("tried to undo_node_undo when there is a partial move\n"); + return false; + } + + // seek backwards for the previous start of record + result = seek_to_next_pos(&undo_cache, UNDO_RECORD_START, true); + + if (result < 2) + { + DEBUGF("undo_node_undo didn't seek far enough: %d\n", result); + // we're probably all fucked up anyways, but I suppose + // we should try to move the cache back to the right spot + if (result == 1) + { + seek_to_next_pos(&undo_cache, UNDO_RECORD_START, false); + DEBUGF("not sure how you got here, undo_node_undo\n"); + } + return false; + } + + result = read_cache(&undo_cache, &temp_pos, sizeof(temp_pos)); + + if (result != sizeof(temp_pos) || temp_pos != UNDO_RECORD_START) + { + DEBUGF("undo_move couldn't read record start\n"); + // we're probably all fucked up anyways, but I suppose + // we should try to move the cache back to the right spot + seek_to_next_pos(&undo_cache, UNDO_RECORD_START, false); + return false; + } + + result = read_cache(&undo_cache, &temp_pos, sizeof(temp_pos)); + + if (result != sizeof(temp_pos)) + { + // couldn't read ko from record, something is seriously wrong + DEBUGF("undo_move couldn't read ko from record: %d\n", result); + + // we're probably all fucked up anyways, but I suppose + // we should try to move the cache back to the right spot + seek_to_next_pos(&undo_cache, UNDO_RECORD_START, false); + return false; + } + + // otherwise, we read in the ko, so set it + ko_pos = temp_pos; + + // everything else is technically optional, but there will be at least one + // added stone to undo unless we are undoing a pass + // + // 1) removed black + // 2) removed white + // action: add the stones and subtract from the appropriate capture count + // (we're undoing a capture) + // + // order: + // 3) added black + // 4) added white + // action: remove the added move, don't modify the capture count + // + // + // NOTE: for right now, we're outputting UNDO_RECORD_SPACERs even if there + // is no content in a section, but those shouldn't be depended on in other + // code (they waste a lot of space in the undo_cache, they'll go away + // when this code becomes less simplistic) + + + // 1) removed black + if (undo_node_undo_helper(REMOVED_BLACK_PHASE)) + { + return true; + } + + // 2) removed white + if (undo_node_undo_helper(REMOVED_WHITE_PHASE)) + { + return true; + } + + // 3) added black + if (undo_node_undo_helper(ADDED_BLACK_PHASE)) + { + return true; + } + + // 4) added white + if (undo_node_undo_helper(ADDED_WHITE_PHASE)) + { + return true; + } + + // go to the start of this record, that's where we should end + seek_to_next_pos(&undo_cache, UNDO_RECORD_START, true); + truncate_cache(&undo_cache); + ////rb->splashf(HZ / 2, "after unu: %d", absolute_position(rb->splashf(HZ / 2, "after unu: %d", absolute_position(&undo_cache));undo_cache)); + + return true; +} + + + +static bool undo_node_undo_helper(int phase) +{ + int result; + pos_t temp_pos; + + + while ((result = read_cache(&undo_cache, &temp_pos, + sizeof(temp_pos))) == sizeof(temp_pos) && + temp_pos != UNDO_RECORD_SPACER && + temp_pos != UNDO_RECORD_START) + { + if (!on_board(temp_pos)) + { + DEBUGF("wtf? undo record says we added/removed an off the board move\n"); + } + else + { + switch (phase) + { + case ADDED_BLACK_PHASE: + case ADDED_WHITE_PHASE: + set_point(temp_pos, EMPTY); + break; + + case REMOVED_BLACK_PHASE: + set_point(temp_pos, BLACK); + white_captures--; + break; + + case REMOVED_WHITE_PHASE: + set_point(temp_pos, WHITE); + black_captures--; + break; + + default: + DEBUGF("error: undo_node_undo_helper called with invalid phase\n"); + break; + }; + } + } + + // see why we exit and act appropriately + if (result != sizeof(temp_pos)) + { + // go to the start of this record, that's where we should end + seek_to_next_pos(&undo_cache, UNDO_RECORD_START, true); + truncate_cache(&undo_cache); + ////////rb->splashf(HZ / 2, "after unu: %d", absolute_position(rb->splashf(HZ / 2, "after unu: %d", absolute_position(&undo_cache));undo_cache)); + + return true; + } + + if (temp_pos == UNDO_RECORD_START) + { + // go to the start of this record, that's where we should end + seek_to_next_pos(&undo_cache, UNDO_RECORD_START, true); + // seek twice because the first one just seeks back to the + // UNDO_RECORD_START that we just read in + seek_to_next_pos(&undo_cache, UNDO_RECORD_START, true); + truncate_cache(&undo_cache); + ////////rb->splashf(HZ / 2, "after unu: %d", absolute_position(rb->splashf(HZ / 2, "after unu: %d", absolute_position(&undo_cache));undo_cache)); + + return true; + } + + return false; +} Index: apps/plugins/goban/goban.h =================================================================== --- apps/plugins/goban/goban.h (revision 0) +++ apps/plugins/goban/goban.h (revision 0) @@ -0,0 +1,156 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2008 Joshua Simmons * + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef GOBAN_MAIN_H +#define GOBAN_MAIN_H + +#include "types.h" + +extern const struct plugin_api *rb; + + +int create_or_open_file(char const * filename); +param_type_t menu_selection_to_param(int selection); + + +#ifdef HAVE_LCD_COLOR +#define BOARD_COLOR LCD_RGBPACK(184,136,72) +#define WHITE_COLOR LCD_RGBPACK(255,255,255) +#define BLACK_COLOR LCD_RGBPACK(0,0,0) +#define LINE_COLOR LCD_RGBPACK(0,0,0) +#define BACKGROUND_COLOR LCD_RGBPACK(41,104,74) +#define CURSOR_COLOR LCD_RGBPACK(222,0,0) +#define MARK_COLOR LCD_RGBPACK(0,0,255) +#elif LCD_DEPTH > 1 /* grayscale */ +#define BOARD_COLOR LCD_LIGHTGRAY +#define WHITE_COLOR LCD_WHITE +#define BLACK_COLOR LCD_BLACK +#define LINE_COLOR LCD_BLACK +#define BACKGROUND_COLOR LCD_DARKGRAY +#define CURSOR_COLOR LCD_DARKGRAY +#define MARK_COLOR LCD_DARKGRAY +#else //LCD DEPTH == 1 +#define BOARD_COLOR LCD_LIGHTGRAY +#define WHITE_COLOR LCD_LIGHTGRAY +#define BLACK_COLOR LCD_DARKGRAY +#define LINE_COLOR LCD_DARKGRAY +#define BACKGROUND_COLOR LCD_DARKGRAY +#define CURSOR_COLOR LCD_DARKGRAY +#define MARK_COLOR LCD_DARKGRAY +#endif + +#if (CONFIG_KEYPAD == SANSA_E200_PAD) +#define SGFBUTTON_UP BUTTON_UP +#define SGFBUTTON_DOWN BUTTON_DOWN +#define SGFBUTTON_RIGHT BUTTON_RIGHT +#define SGFBUTTON_LEFT BUTTON_LEFT +#define SGFBUTTON_RETREAT BUTTON_SCROLL_BACK +#define SGFBUTTON_ADVANCE BUTTON_SCROLL_FWD +#define SGFBUTTON_PLAY BUTTON_SELECT +#define SGFBUTTON_VAR_TOG BUTTON_REC +#define SGFBUTTON_MENU BUTTON_POWER +#elif (CONFIG_KEYPAD == GIGABEAT_PAD) +#define SGFBUTTON_UP BUTTON_UP +#define SGFBUTTON_DOWN BUTTON_DOWN +#define SGFBUTTON_RIGHT BUTTON_RIGHT +#define SGFBUTTON_LEFT BUTTON_LEFT +#define SGFBUTTON_RETREAT BUTTON_VOL_DOWN +#define SGFBUTTON_ADVANCE BUTTON_VOL_UP +#define SGFBUTTON_PLAY BUTTON_SELECT +#define SGFBUTTON_VAR_TOG BUTTON_A +#define SGFBUTTON_MENU BUTTON_MENU +#elif (CONFIG_KEYPAD == IRIVER_H10_PAD) +#define SGFBUTTON_UP BUTTON_SCROLL_BACK +#define SGFBUTTON_DOWN BUTTON_SCROLL_FWD +#define SGFBUTTON_RIGHT BUTTON_RIGHT +#define SGFBUTTON_LEFT BUTTON_LEFT +#define SGFBUTTON_RETREAT BUTTON_FF +#define SGFBUTTON_ADVANCE BUTTON_REW +#define SGFBUTTON_PLAY BUTTON_PLAY | BUTTON_REL +#define SGFBUTTON_VAR_TOG BUTTON_POWER +#define SGFBUTTON_MENU BUTTON_PLAY | BUTTON_REPEAT +#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ + (CONFIG_KEYPAD == IPOD_3G_PAD) +#define SGFBUTTON_UP BUTTON_MENU +#define SGFBUTTON_DOWN BUTTON_PLAY +#define SGFBUTTON_RIGHT BUTTON_RIGHT +#define SGFBUTTON_LEFT BUTTON_LEFT +#define SGFBUTTON_RETREAT BUTTON_SCROLL_FWD +#define SGFBUTTON_ADVANCE BUTTON_SCROLL_BACK +#define SGFBUTTON_PLAY BUTTON_SELECT | BUTTON_REL +#define SGFBUTTON_VAR_TOG BUTTON_NONE /* TODO: what can i do for this? */ +#define SGFBUTTON_MENU BUTTON_SELECT | BUTTON_REPEAT +#elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \ + (CONFIG_KEYPAD == IRIVER_H300_PAD) +#define SGFBUTTON_UP BUTTON_UP +#define SGFBUTTON_DOWN BUTTON_DOWN +#define SGFBUTTON_RIGHT BUTTON_RIGHT +#define SGFBUTTON_LEFT BUTTON_LEFT +#define SGFBUTTON_RETREAT BUTTON_OFF +#define SGFBUTTON_ADVANCE BUTTON_ON +#define SGFBUTTON_PLAY BUTTON_SELECT +#define SGFBUTTON_VAR_TOG BUTTON_REC +#define SGFBUTTON_MENU BUTTON_MODE +#elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD) +#define SGFBUTTON_UP BUTTON_UP +#define SGFBUTTON_DOWN BUTTON_DOWN +#define SGFBUTTON_RIGHT BUTTON_RIGHT +#define SGFBUTTON_LEFT BUTTON_LEFT +#define SGFBUTTON_RETREAT BUTTON_PLAY +#define SGFBUTTON_ADVANCE BUTTON_REC +#define SGFBUTTON_PLAY BUTTON_SELECT | BUTTON_REL +#define SGFBUTTON_VAR_TOG BUTTON_POWER +#define SGFBUTTON_MENU BUTTON_SELECT | BUTTON_REPEAT +#elif (CONFIG_KEYPAD == RECORDER_PAD) +#define SGFBUTTON_UP BUTTON_UP +#define SGFBUTTON_DOWN BUTTON_DOWN +#define SGFBUTTON_RIGHT BUTTON_RIGHT +#define SGFBUTTON_LEFT BUTTON_LEFT +#define SGFBUTTON_RETREAT BUTTON_F1 +#define SGFBUTTON_ADVANCE BUTTON_F2 +#define SGFBUTTON_PLAY BUTTON_PLAY | BUTTON_REL +#define SGFBUTTON_VAR_TOG BUTTON_NONE +#define SGFBUTTON_MENU BUTTON_PLAY | BUTTON_REPEAT + +#else +#error keypad is not set up on your player +#endif + + +#define LCD_MIN_DIMENSION (LCD_HEIGHT > LCD_WIDTH ? LCD_WIDTH : LCD_HEIGHT) + +#define MAX_CIRCLE_SIZE 512 + +#define DEFAULT_SAVE "/sgf/default.sgf" +#define SAVE_FILE_LENGTH 256 + +#if (LCD_DEPTH == 1) +#define OUTLINE_STONES 1 +#else +#define OUTLINE_STONES 0 +#endif + +#define min(x, y) (x < y ? x : y) +#define max(x, y) (x > y ? x : y) +#define fix_fixed(x) (((x < 0) && (x & 1)) ? x + 2 : x) + + +int snprint_fixed(char * buffer, int buffer_size, fixed_point_t fixed); + + +#endif Index: apps/plugins/goban/board.h =================================================================== --- apps/plugins/goban/board.h (revision 0) +++ apps/plugins/goban/board.h (revision 0) @@ -0,0 +1,80 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2008 Joshua Simmons * + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef GOBAN_BOARD_H +#define GOBAN_BOARD_H + +#include "types.h" + + +#define WHITE 1 +#define BLACK 2 +#define EMPTY 0 +#define NONE 0 +#define INVALID 4 + +#define OTHER(color) (color == BLACK ? WHITE : BLACK) + +#define MAX_BOARDSIZE 19 +#define MIN_BOARDSIZE 3 + + +#define INVALID_POS ((pos_t) -5003) +#define PASS_POS ((pos_t) -5002) + +#define UNDO_RECORD_START ((pos_t) -5000) +#define UNDO_RECORD_SPACER ((pos_t) -5001) + +#define POS(i, j) ((i + 1) + (j + 1) * (MAX_BOARDSIZE + 2)) +#define WEST(i) (i - 1) +#define EAST(i) (i + 1) +#define NORTH(i) (i - (MAX_BOARDSIZE + 2)) +#define SOUTH(i) (i + (MAX_BOARDSIZE + 2)) + +pos_t WRAP(pos_t pos); + + +#define I(pos) (pos % (MAX_BOARDSIZE + 2) - 1) +#define J(pos) (pos / (MAX_BOARDSIZE + 2) - 1) + +void clear_board(void); +bool set_size_board(int width, int height); +bool add_stone_board(pos_t pos, color_t color); +bool remove_stone_board(pos_t pos); +bool play_move_undo(pos_t pos, color_t color); +bool on_board(pos_t pos); +color_t get_point_board(pos_t pos); + +bool setup_undo(void); +void cleanup_undo(void); +void remove_stone_undo(pos_t pos, color_t color); +void add_stone_undo(pos_t pos, color_t color); +void node_finished_undo(void); +bool undo_node_undo(void); + + +extern int board_width; +extern int board_height; +extern int black_captures; +extern int white_captures; + + +// TODO: remove this, this is only for debug + +extern file_cache_t undo_cache; +#endif Index: apps/plugins/SUBDIRS =================================================================== --- apps/plugins/SUBDIRS (revision 19752) +++ apps/plugins/SUBDIRS (working copy) @@ -20,6 +20,7 @@ jpeg sudoku reversi +goban #ifndef OLYMPUS_MROBE_500 zxbox #endif