Index: apps/plugins/CATEGORIES =================================================================== --- apps/plugins/CATEGORIES (revision 19806) +++ 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 19806) +++ 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/file_cache.c =================================================================== --- apps/plugins/goban/file_cache.c (revision 0) +++ apps/plugins/goban/file_cache.c (revision 0) @@ -0,0 +1,727 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007-2009 Joshua Simmons + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "file_cache.h" +#include "types.h" + +static void set_error (struct file_cache_t *cache); +static void fill_cache (struct file_cache_t *cache); +static void empty_cache (struct file_cache_t *cache); +static void advance_cache (struct file_cache_t *cache); +static void rewind_cache (struct file_cache_t *cache); + + +bool +setup_cache (struct 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 (struct file_cache_t * cache) +{ + return cache->error; +} + +static void +set_error (struct file_cache_t *cache) +{ + cache->error = true; +} + +static void +fill_cache (struct 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 (struct 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 (struct file_cache_t *cache) +{ + if (is_error (cache)) + { + return; + } + + if (cache->position) + { + empty_cache (cache); + } + fill_cache (cache); +} + +static void +rewind_cache (struct 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 (struct 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 (struct 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 (struct 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 (struct 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 (struct 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 (struct 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 (struct file_cache_t *cache) +{ + int result = peek_char_cache (cache); + + if (result >= 0) + { + cache->position++; + } + + return result; +} + +bool +write_char_cache (struct file_cache_t * cache, char to_write) +{ + return write_cache (cache, (void *) &to_write, 1) == 1; +} + + +bool +push_int_cache (struct file_cache_t *cache, int value) +{ + return write_cache (cache, (void *) &value, + sizeof (value)) == sizeof (value); +} + +bool +pop_int_cache (struct 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 (struct 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 (struct 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 (struct 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 (struct 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 (struct file_cache_t *cache) +{ + if (is_error (cache)) + { + return; + } + + empty_cache (cache); + + rb->close (cache->fd); +} + +bool +truncate_cache (struct 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 (struct 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 (struct 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 (struct 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 (struct file_cache_t *cache) +{ + if (is_error (cache)) + { + return -1; + } + + return size_before_cache (cache) + cache->position; +} + + + 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/display.c =================================================================== --- apps/plugins/goban/display.c (revision 0) +++ apps/plugins/goban/display.c (revision 0) @@ -0,0 +1,858 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007-2009 Joshua Simmons + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "board.h" +#include "goban.h" +#include "display.h" +#include "game.h" +#include "sgf.h" + +/* for xlcd_filltriangle */ +#include "lib/xlcd.h" + +static int intersection_size = 0; + +static int board_size = MAX_BOARDSIZE; + +#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; +bool has_comment = false; + +unsigned char display_marks[MAX_BOARDSIZE * MAX_BOARDSIZE]; + + +/* function prototypes */ + +static int pixel_x (pos_t pos); +static int pixel_y (pos_t pos); + +static void draw_circle (int c_x, int c_y, int r, bool filled); + +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_footer (void); +static void draw_all_marks (void); +static void draw_all_hoshi (void); + +MEM_FUNCTION_WRAPPERS void +clear_marks_display (void) +{ + rb->memset (display_marks, ' ', sizeof (display_marks)); + has_comment = false; +} + +void +set_mark_display (pos_t pos, unsigned char mark_char) +{ + if (!on_board (pos)) + { + return; + } + + display_marks[I (pos) + J (pos) * board_width] = mark_char; +} + + +void +set_comment_display (bool new_val) +{ + has_comment = new_val; +} + + +static void +draw_all_marks (void) +{ + int x, y; + for (x = 0; x < board_width; ++x) + { + for (y = 0; y < board_height; ++y) + { + if (display_marks[x + y * board_width] != ' ') + { +#if LCD_DEPTH > 1 + if (display_marks[x + y * board_width] != 'b' && + display_marks[x + y * board_width] != 'w') + { + rb->lcd_set_foreground (MARK_COLOR); + } + else + { + rb->lcd_set_foreground (CURSOR_COLOR); + } + rb->lcd_set_drawmode (DRMODE_FG); +#else + rb->lcd_set_drawmode (DRMODE_FG + DRMODE_COMPLEMENT); +#endif + + if (display_marks[x + y * board_width] & (1 << 7)) + { + char to_display[2]; + int width, height; + + if (intersection_size < 7) + { + DEBUGF ("screen too small to draw labels\n"); + } + + to_display[0] = + display_marks[x + y * board_width] & (~(1 << 7)); + to_display[1] = '\0'; + + rb->lcd_getstringsize (to_display, &width, &height); + + int display_x = + pixel_x (POS (x, y)) + LINE_OFFSET - (width / 2); + int display_y = + pixel_y (POS (x, y)) + LINE_OFFSET - (height / 2); + + if (display_x < 0) + { + display_x = 0; + } + + if (display_y < 0) + { + display_y = 0; + } + + if (display_x + width >= LCD_WIDTH) + { + display_x = LCD_WIDTH - 1 - width; + } + + if (display_y + height >= LCD_HEIGHT) + { + display_y = LCD_HEIGHT - 1 - height; + } + + rb->lcd_putsxy (display_x, display_y, to_display); + continue; + } + + switch (display_marks[x + y * board_width]) + { + // moves, 'mark', 'square' + case 'b': + case 'w': + if (intersection_size <= 5) + { + DEBUGF ("screen is too small to mark current move\n"); + break; + } + case 'm': + if (intersection_size <= 5) + { + rb->lcd_drawpixel (pixel_x (POS (x, y)) + LINE_OFFSET + + 1, + pixel_y (POS (x, y)) + LINE_OFFSET + + 1); + rb->lcd_drawpixel (pixel_x (POS (x, y)) + LINE_OFFSET - + 1, + pixel_y (POS (x, y)) + LINE_OFFSET - + 1); + } + else + { + rb->lcd_drawrect (pixel_x (POS (x, y)) + LINE_OFFSET - + intersection_size / 6, + pixel_y (POS (x, y)) + LINE_OFFSET - + intersection_size / 6, + (intersection_size / 6) * 2 + 1, + (intersection_size / 6) * 2 + 1); + } + break; + case 's': + if (intersection_size <= 5) + { + rb->lcd_drawpixel (pixel_x (POS (x, y)) + LINE_OFFSET + + 1, + pixel_y (POS (x, y)) + LINE_OFFSET + + 1); + rb->lcd_drawpixel (pixel_x (POS (x, y)) + LINE_OFFSET - + 1, + pixel_y (POS (x, y)) + LINE_OFFSET - + 1); + } + else + { + rb->lcd_fillrect (pixel_x (POS (x, y)) + LINE_OFFSET - + intersection_size / 6, + pixel_y (POS (x, y)) + LINE_OFFSET - + intersection_size / 6, + (intersection_size / 6) * 2 + 1, + (intersection_size / 6) * 2 + 1); + } + break; + + case 'c': + if (intersection_size > 7) + { + draw_circle (pixel_x (POS (x, y)) + LINE_OFFSET, + pixel_y (POS (x, y)) + LINE_OFFSET, + (intersection_size - 1) / 4, true); + break; + } + + /* purposely don't break here, draw small the same as a + triangle */ + + case 't': + if (intersection_size <= 7) + { + rb->lcd_drawpixel (pixel_x (POS (x, y)) + LINE_OFFSET - + 1, + pixel_y (POS (x, y)) + LINE_OFFSET + + 1); + rb->lcd_drawpixel (pixel_x (POS (x, y)) + LINE_OFFSET + + 1, + pixel_y (POS (x, y)) + LINE_OFFSET - + 1); + } + else + { + xlcd_filltriangle (pixel_x (POS (x, y)) + LINE_OFFSET, + pixel_y (POS (x, y)) + LINE_OFFSET - + intersection_size / 4, + pixel_x (POS (x, y)) + LINE_OFFSET + + intersection_size / 4, + pixel_y (POS (x, y)) + LINE_OFFSET + + intersection_size / 4, + pixel_x (POS (x, y)) + LINE_OFFSET - + intersection_size / 4, + pixel_y (POS (x, y)) + LINE_OFFSET + + intersection_size / 4); +#if 0 + rb->lcd_drawline (pixel_x (POS (x, y)) + LINE_OFFSET, + pixel_y (POS (x, y)) + LINE_OFFSET - + intersection_size / 4, + pixel_x (POS (x, y)) + LINE_OFFSET + + intersection_size / 4, + pixel_y (POS (x, y)) + LINE_OFFSET + + intersection_size / 4); + + rb->lcd_hline (pixel_x (POS (x, y)) + LINE_OFFSET + + intersection_size / 4, + pixel_x (POS (x, y)) + LINE_OFFSET - + intersection_size / 4, + pixel_y (POS (x, y)) + LINE_OFFSET + + intersection_size / 4); + + rb->lcd_drawline (pixel_x (POS (x, y)) + LINE_OFFSET, + pixel_y (POS (x, y)) + LINE_OFFSET - + intersection_size / 4, + pixel_x (POS (x, y)) + LINE_OFFSET - + intersection_size / 4, + pixel_y (POS (x, y)) + LINE_OFFSET + + intersection_size / 4); +#endif /* if 0 */ + } + break; + default: + DEBUGF ("tried to display unknown mark '%c' %d\n", + display_marks[x + y * board_width], + display_marks[x + y * board_width]); + break; + }; + + rb->lcd_set_drawmode (DRMODE_SOLID); + /* don't have to undo the colors for LCD_DEPTH > 1, most + functions assume bg and fg get clobbered */ + } + } + } +} + + +static void +draw_circle (int c_x, int c_y, int r, bool filled) +{ + int f = 1 - r; + int x = 0; + int y = r; + + /* draw the points on the axes to make the loop easier */ + rb->lcd_drawpixel (c_x, c_y + r); + rb->lcd_drawpixel (c_x, c_y - r); + + if (filled) + { + rb->lcd_hline (c_x - r, c_x + r, c_y); + } + else + { + rb->lcd_drawpixel (c_x + r, c_y); + rb->lcd_drawpixel (c_x - r, c_y); + } + + /* Now walk from the very top of the circle to 1/8th of the way around to + the right. For each point, draw the 8 symmetrical points. */ + while (x < y) + { + /* walk one pixel to the right */ + ++x; + + /* And then adjust our discriminate, and adjust y if we've ventured + outside of the circle. This boils down to walking a tightrope + between being inside and outside the circle. The updating + functions are taken from expanding the discriminant function f(x, + y) = x^2 + y^2 - r^2 after substituting in x + 1 and y - 1 and + then subtracting out f(x, y) */ + if (f <= 0) + { + f += 2 * x + 1; + } + else + { + --y; + f += (x - y) * 2 + 1; + } + + if (filled) + { + /* each line takes care of 2 points on the circle so we only need + 4 */ + rb->lcd_hline (c_x - y, c_x + y, c_y + x); + rb->lcd_hline (c_x - y, c_x + y, c_y - x); + rb->lcd_hline (c_x - x, c_x + x, c_y + y); + rb->lcd_hline (c_x - x, c_x + x, c_y - y); + } + else + { + /* Draw all 8 symmetrical points */ + rb->lcd_drawpixel (c_x + x, c_y + y); + rb->lcd_drawpixel (c_x + y, c_y + x); + rb->lcd_drawpixel (c_x + y, c_y - x); + rb->lcd_drawpixel (c_x + x, c_y - y); + rb->lcd_drawpixel (c_x - x, c_y + y); + rb->lcd_drawpixel (c_x - y, c_y + x); + rb->lcd_drawpixel (c_x - y, c_y - x); + rb->lcd_drawpixel (c_x - x, c_y - y); + } + } +} + + +void +draw_board (void) +{ + int i; +#if LCD_DEPTH > 1 + int saved_fg = rb->lcd_get_foreground (); + int saved_bg = rb->lcd_get_background (); +#endif + int saved_drmode = rb->lcd_get_drawmode (); + +#if LCD_DEPTH > 1 + rb->lcd_set_backdrop (NULL); + + rb->lcd_set_foreground (BOARD_COLOR); + rb->lcd_set_background (BACKGROUND_COLOR); + rb->lcd_set_drawmode (DRMODE_SOLID); + +#else + rb->lcd_set_drawmode (DRMODE_SOLID + DRMODE_INVERSEVID); +#endif + + rb->lcd_clear_display (); + + rb->lcd_fillrect (board_x, board_y, + intersection_size * board_width, + intersection_size * board_height); + +#if LCD_DEPTH > 1 + rb->lcd_set_foreground (LINE_COLOR); +#else + rb->lcd_set_drawmode (DRMODE_SOLID); +#endif + + 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) + { + mark_child_variations_sgf (); + } + + draw_all_marks (); + + draw_footer (); + rb->lcd_update (); + +#if LCD_DEPTH > 1 + rb->lcd_set_foreground (saved_fg); + rb->lcd_set_background (saved_bg); +#endif + rb->lcd_set_drawmode (saved_drmode); +} + + + +#if defined(GBN_WIDE_SCREEN) + +/* the size of the string, in pixels, when drawn vertically */ +static void +vert_string_size (char *string, int *width, int *height) +{ + int temp_width = 0; + int temp_height = 0; + + int ret_width = 0; + int ret_height = 0; + + char temp_buffer[2]; + + temp_buffer[0] = temp_buffer[1] = 0; + + if (!string) + { + return; + } + + while (*string) + { + temp_buffer[0] = *string; + rb->lcd_getstringsize (temp_buffer, &temp_width, &temp_height); + + ret_height += temp_height; + + if (ret_width < temp_width) + { + ret_width = temp_width; + } + + ++string; + } + + if (width) + { + *width = ret_width; + } + + if (height) + { + *height = ret_height; + } + + DEBUGF ("vert returned: %d %d\n", ret_width, ret_height); +} + +static void +putsxy_vertical (int x, int y, int width, char *str) +{ + int temp_width = 0; + int temp_height = 0; + char temp_buffer[2]; + + temp_buffer[0] = temp_buffer[1] = 0; + + if (!str) + { + return; + } + + while (*str) + { + temp_buffer[0] = *str; + rb->lcd_getstringsize (temp_buffer, &temp_width, &temp_height); + DEBUGF ("putting %s at %d %d\n", temp_buffer, + x + (width - temp_width) / 2, y); + rb->lcd_putsxy (x + (width - temp_width) / 2, y, temp_buffer); + y += temp_height; + ++str; + } +} + +#endif /* GBN_WIDE_SCREEN */ + + +static void +draw_footer (void) +{ + char captures_buffer[16]; + char display_flags[16] = ""; + int size_x, size_y; + +#if LCD_DEPTH > 1 + rb->lcd_set_background (BACKGROUND_COLOR); + rb->lcd_set_foreground (BLACK_COLOR); +#else + rb->lcd_set_drawmode (DRMODE_SOLID + DRMODE_INVERSEVID); +#endif + + rb->snprintf (captures_buffer, sizeof (captures_buffer), + "%d", white_captures); + +#if defined(GBN_TALL_SCREEN) + rb->lcd_getstringsize (captures_buffer, &size_x, &size_y); + rb->lcd_putsxy (size_y + 2, LCD_HEIGHT - size_y, captures_buffer); +#else + vert_string_size (captures_buffer, &size_x, &size_y); + putsxy_vertical (LCD_WIDTH - size_x - 1, size_x + 2, size_x, + captures_buffer); +#endif + +#if LCD_DEPTH == 1 + rb->lcd_set_drawmode (DRMODE_SOLID); +#endif + +#if defined(GBN_TALL_SCREEN) + draw_circle (size_y / 2, + LCD_HEIGHT - (size_y / 2), (size_y - 1) / 2, true); + +#if LCD_DEPTH == 1 + rb->lcd_set_drawmode (DRMODE_SOLID + DRMODE_INVERSEVID); + draw_circle (size_y / 2, + LCD_HEIGHT - (size_y / 2), (size_y - 1) / 2, false); +#endif /* LCD_DEPTH */ + +#else /* GBN_TALL_SCREEN */ + draw_circle (LCD_WIDTH - 1 - size_x / 2, + (size_x / 2), (size_x - 1) / 2, true); + +#if LCD_DEPTH == 1 + rb->lcd_set_drawmode (DRMODE_SOLID + DRMODE_INVERSEVID); + draw_circle (LCD_WIDTH - 1 - size_x / 2, + (size_x / 2), (size_x - 1) / 2, false); +#endif /* LCD_DEPTH */ + +#endif /* GBN_TALL_SCREEN */ + + +#if LCD_DEPTH > 1 + rb->lcd_set_foreground (WHITE_COLOR); +#endif + rb->snprintf (captures_buffer, sizeof (captures_buffer), + "%d", black_captures); + +#if defined(GBN_TALL_SCREEN) + rb->lcd_getstringsize (captures_buffer, &size_x, &size_y); + rb->lcd_putsxy (LCD_WIDTH - (size_y + 1) - size_x, + LCD_HEIGHT - size_y, captures_buffer); + + draw_circle (LCD_WIDTH - (size_y / 2), + LCD_HEIGHT - (size_y / 2), (size_y - 1) / 2, true); +#else + vert_string_size (captures_buffer, &size_x, &size_y); + putsxy_vertical (LCD_WIDTH - size_x - 1, + LCD_HEIGHT - size_x - 3 - size_y, + size_x, captures_buffer); + + draw_circle (LCD_WIDTH - 1 - size_x / 2, + LCD_HEIGHT - 1 - size_x / 2, (size_x - 1) / 2, true); +#endif + + +#if LCD_DEPTH > 1 + rb->lcd_set_foreground (BLACK_COLOR); +#endif + + if (has_comment) + { + rb->strcat (display_flags, "C"); + } + + if (has_more_nodes_sgf ()) + { + rb->strcat (display_flags, "+"); + } + + if (num_variations_sgf () > 1) + { + rb->strcat (display_flags, "*"); + } + + + + rb->snprintf (captures_buffer, sizeof (captures_buffer), + "%d%s", move_num, display_flags); + + +#if defined(GBN_TALL_SCREEN) + rb->lcd_getstringsize (captures_buffer, &size_x, &size_y); + rb->lcd_putsxy ((LCD_WIDTH - size_x) / 2, + LCD_HEIGHT - size_y, captures_buffer); +#else + vert_string_size (captures_buffer, &size_x, &size_y); + putsxy_vertical (LCD_WIDTH - size_x - 1, + (LCD_HEIGHT - size_y) / 2, size_x, captures_buffer); +#endif + + + rb->lcd_set_drawmode (DRMODE_SOLID); +} + + + + + + + +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; +} + + +/* Call every time the board size might have changed! */ +void +setup_graphics (void) +{ + board_size = max (board_width, board_height); + + intersection_size = LCD_BOARD_SIZE / board_size; + + /* it's really unfortunate to have to do this, but boards look very awful + with even intersection sizes, especially at low resolution (which is + the only place where it would really matter) */ + if (!(intersection_size & 1)) + { + --intersection_size; + } + +#if defined(GBN_TALL_SCREEN) + board_x = (LCD_WIDTH - (intersection_size * board_width)) / 2; + board_y = 0; +#elif defined(GBN_WIDE_SCREEN) + board_x = 0; + board_y = (LCD_HEIGHT - (intersection_size * board_height)) / 2; +#else +#error screen dimensions have not been evaluated properly +#endif + + /* cursor starts on tengen (middle of the board) */ + cursor_pos = POS (board_width / 2, board_height / 2); + + clear_marks_display (); +} + +static void +draw_cursor (pos_t pos) +{ + /* int saved_draw_mode = rb->lcd_get_drawmode(); */ + + if (!on_board (pos)) + { + return; + } + +#if LCD_DEPTH > 1 + rb->lcd_set_foreground (CURSOR_COLOR); +#else + rb->lcd_set_drawmode (DRMODE_COMPLEMENT); +#endif + + rb->lcd_drawrect (pixel_x (pos), + pixel_y (pos), intersection_size, intersection_size); + + rb->lcd_set_drawmode (DRMODE_SOLID); +} + +static void +draw_stone_raw (int pixel_x, int pixel_y, bool black) +{ + int i; + +#if LCD_DEPTH > 1 + rb->lcd_set_foreground (black ? BLACK_COLOR : WHITE_COLOR); +#else + if (black) + { + rb->lcd_set_drawmode (DRMODE_SOLID); + } + else + { + rb->lcd_set_drawmode (DRMODE_SOLID + DRMODE_INVERSEVID); + } +#endif + + draw_circle (pixel_x + LINE_OFFSET, + pixel_y + LINE_OFFSET, LINE_OFFSET, true); + +#if defined(OUTLINE_STONES) +#if LCD_DEPTH > 1 + rb->lcd_set_foreground (black ? WHITE_COLOR : BLACK_COLOR); +#else + if (black) + { + rb->lcd_set_drawmode (DRMODE_SOLID + DRMODE_INVERSEVID); + } + else + { + rb->lcd_set_drawmode (DRMODE_SOLID); + } +#endif /* LCD_DEPTH > 1 */ + + if (!black) + { + draw_circle (pixel_x + LINE_OFFSET, + pixel_y + LINE_OFFSET, LINE_OFFSET, false); + } + +#endif /* OUTLINE_STONES */ + + rb->lcd_set_drawmode (DRMODE_SOLID); +} + + +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) +{ + /* color and drawmode are already set before this function (all lines and + hoshi and stuff are drawn together) */ + + 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); + } +} + + + +static void +draw_all_hoshi (void) +{ + if (board_width != board_height) + { + return; + } + + if (board_width == 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_width == 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_width == 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,60 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007-2009 Joshua Simmons + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#ifndef GOBAN_FILE_CACHE_H +#define GOBAN_FILE_CACHE_H + +#include "types.h" +#include "goban.h" + +bool setup_cache (struct file_cache_t * cache, + int fd, uint8_t * buffer, unsigned int buffer_size); + +bool is_error (struct file_cache_t *cache); +size_t size_before_cache (struct file_cache_t *cache); +size_t size_after_cache (struct file_cache_t *cache); +int seek_cache (struct file_cache_t *cache, int seek_size); +int seek_to_next (struct file_cache_t *cache, unsigned char goal, + bool backwards); +int seek_to_next_pos (struct file_cache_t *cache, pos_t goal, bool backwards); +bool seek_to_beginning (struct file_cache_t *cache); +size_t write_cache (struct file_cache_t *cache, void *buffer, size_t size); +size_t read_cache (struct file_cache_t *cache, void *buffer, size_t size); +void close_cache (struct file_cache_t *cache); + +bool write_char_cache (struct file_cache_t *cache, char to_write); +int read_char_cache (struct file_cache_t *cache); +int peek_char_cache (struct file_cache_t *cache); +bool peek_pos_cache (struct file_cache_t *cache, pos_t * buffer); +bool peek_int_cache (struct file_cache_t *cache, int *dest); +bool read_int_cache (struct file_cache_t *cache, int *dest); + +bool pop_int_cache (struct file_cache_t *cache, int *dest); +bool push_int_cache (struct file_cache_t *cache, int value); + + +bool truncate_cache (struct file_cache_t *cache); +bool cache_has_data (struct file_cache_t *cache); +void debugf_cache (struct file_cache_t *cache); + +int absolute_position (struct file_cache_t *cache); + +#endif Index: apps/plugins/goban/types.h =================================================================== --- apps/plugins/goban/types.h (revision 0) +++ apps/plugins/goban/types.h (revision 0) @@ -0,0 +1,272 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007-2009 Joshua Simmons + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#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; + + + +enum prop_type_t +{ + PROP_BLACK_MOVE, + PROP_WHITE_MOVE, + + PROP_ADD_BLACK, + PROP_ADD_WHITE, + PROP_ADD_EMPTY, + + PROP_PLAYER_TO_PLAY, + PROP_COMMENT, + + /* information about the position reached by the current node */ + PROP_EVEN, + PROP_BLACK_GOOD, + PROP_WHITE_GOOD, + PROP_HOTSPOT, + PROP_UNCLEAR, + PROP_VALUE, + + /* information about the current move */ + PROP_BAD, + PROP_DOUBTFUL, + PROP_INTERESTING, + PROP_TESUJI, + + /* marks on the board, each takes a list */ + PROP_CIRCLE, + PROP_SQUARE, + PROP_TRIANGLE, + PROP_DIM, + PROP_MARK, + PROP_SELECTED, + + /* labels go on points, names name the node */ + PROP_LABEL, + PROP_NODE_NAME, + + /* root props */ + PROP_APPLICATION, + PROP_CHARSET, + PROP_FILE_FORMAT, + PROP_GAME, + PROP_VARIATION_TYPE, + PROP_SIZE, + + /* game info props */ + PROP_ANNOTATOR, + PROP_BLACK_NAME, + PROP_WHITE_NAME, + PROP_HANDICAP, + PROP_KOMI, + PROP_BLACK_TERRITORY, + PROP_WHITE_TERRITORY, + PROP_BLACK_RANK, + PROP_WHITE_RANK, + PROP_BLACK_TEAM, + PROP_WHITE_TEAM, + PROP_COPYRIGHT, + PROP_DATE, + PROP_EVENT, + PROP_ROUND, + PROP_GAME_NAME, + PROP_GAME_COMMENT, + PROP_OPENING_NAME, + PROP_OVERTIME, + PROP_PLACE, + PROP_RESULT, + PROP_RULESET, + PROP_SOURCE, + PROP_TIME_LIMIT, + PROP_USER, + + /* these are all the X left /after/ the current move */ + PROP_BLACK_TIME_LEFT, + PROP_WHITE_TIME_LEFT, + PROP_BLACK_STONES_LEFT, /* the number of stones left in a canadian + style */ + PROP_WHITE_STONES_LEFT, /* overtime period */ + + + /* these are mostly used for printing, we don't handle these */ + PROP_FIGURE, + PROP_PRINT_MOVE_MODE, + + /* view only part of the board. probably don't handle this */ + PROP_VIEW, + + /* psuedo PROP types, used for variations and parsing and such */ + PROP_VARIATION, + PROP_INVALID, + PROP_GENERIC_UNHANDLED, + PROP_ANY, + PROP_VARIATION_TO_PROCESS, + PROP_VARIATION_CHOICE +}; + +extern char *prop_names[]; +/* IMPORTANT: keep this array full of all properties that we want to be able + to parse out of an SGF file. This implies that they need to go before + unparseable (psuedo) props in the above enum, or else we need to insert + placeholders in the array for unparseables */ + +#define PROP_NAMES_SIZE (63) +#define PROP_NAME_LEN(type) ((type == PROP_BLACK_MOVE || \ + type == PROP_WHITE_MOVE) ? 1 : 2) + + +struct file_cache_t +{ + int fd; + size_t buffer_size; + uint8_t *buffer; + size_t position; + size_t data_size; + bool error; + bool dirty; +}; + + +struct label_data_t +{ + pos_t position; + char character; +}; + + +union prop_data_t +{ + pos_t position; + unsigned int number; + int node; + struct label_data_t label_data; +}; + + +enum play_mode_t +{ + MODE_PLAY, + MODE_FORCE_PLAY, + MODE_ADD_BLACK, + MODE_ADD_WHITE, + MODE_REMOVE, + MODE_MARK, + MODE_CIRCLE, + MODE_SQUARE, + MODE_TRIANGLE, + MODE_LABEL +}; + +enum mark_t +{ + MARK_VARIATION, + MARK_SQUARE, + MARK_CIRCLE, + MARK_TRIANGLE, + MARK_LAST_MOVE, + MARK_LABEL +}; + + +struct prop_t +{ + union prop_data_t data; + enum prop_type_t type; + int next; +}; + + +extern char *ruleset_names[]; + +/* IMPORTANT! keep in sync with ruleset_names!!! */ +enum ruleset_t +{ + RULESET_AGA = 0, + RULESET_JAPANESE, + RULESET_CHINESE, + RULESET_NEW_ZEALAND, + RULESET_ING, + __RULESETS_SIZE /* make sure i am last! */ +}; + +#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) */ +struct node_t +{ + int props; + int next; + int prev; +}; + + +/* convenience union for keeping a mixed array of props and nodes */ +union storage_t +{ + struct prop_t prop; + struct node_t node; +}; + +#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 + +struct header_t +{ + 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; +}; + +#endif Index: apps/plugins/goban/game.c =================================================================== --- apps/plugins/goban/game.c (revision 0) +++ apps/plugins/goban/game.c (revision 0) @@ -0,0 +1,264 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007-2009 Joshua Simmons + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "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; + +struct 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); + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost (true); +#endif + + if (!parse_sgf (save_file)) + { + rb->splash (3 * HZ, "Unable to parse SGF file. Will overwrite."); + } + + game_dirty = false; + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost (false); +#endif + + 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); + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost (true); +#endif + + if (output_sgf (save_file)) + { + game_dirty = false; +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost (false); +#endif + return true; + } + else + { +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost (false); +#endif + 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; + + play_mode = MODE_PLAY; + + rb->strcpy (save_file, DEFAULT_SAVE); + + return true; +} + +bool +get_header_string_and_size (struct header_t * header, + enum prop_type_t type, char **buffer, int *size) +{ + if (buffer == 0 || header == 0) + { + return false; + } + + if (type == PROP_BLACK_NAME) + { + *buffer = header->black; + *size = MAX_NAME; + } + else if (type == PROP_WHITE_NAME) + { + *buffer = header->white; + *size = MAX_NAME; + } + else if (type == PROP_BLACK_RANK) + { + *buffer = header->black_rank; + *size = MAX_RANK; + } + else if (type == PROP_WHITE_RANK) + { + *buffer = header->white_rank; + *size = MAX_RANK; + } + else if (type == PROP_BLACK_TEAM) + { + *buffer = header->black_team; + *size = MAX_TEAM; + } + else if (type == PROP_WHITE_TEAM) + { + *buffer = header->white_team; + *size = MAX_TEAM; + } + else if (type == PROP_DATE) + { + *buffer = header->date; + *size = MAX_DATE; + } + else if (type == PROP_ROUND) + { + *buffer = header->round; + *size = MAX_ROUND; + } + else if (type == PROP_EVENT) + { + *buffer = header->event; + *size = MAX_EVENT; + } + else if (type == PROP_PLACE) + { + *buffer = header->place; + *size = MAX_PLACE; + } + else if (type == PROP_OVERTIME) + { + *buffer = header->overtime; + *size = MAX_OVERTIME; + } + else if (type == PROP_RESULT) + { + *buffer = header->result; + *size = MAX_RESULT; + } + else if (type == PROP_RULESET) + { + *buffer = header->ruleset; + *size = MAX_RULESET; + } + else + { + return false; + } + + return true; +} Index: apps/plugins/goban/display.h =================================================================== --- apps/plugins/goban/display.h (revision 0) +++ apps/plugins/goban/display.h (revision 0) @@ -0,0 +1,39 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007-2009 Joshua Simmons + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#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; + +void clear_marks_display (void); +void set_mark_display (pos_t pos, unsigned char mark_char); +void set_comment_display (bool new_val); + + +#endif Index: apps/plugins/goban/game.h =================================================================== --- apps/plugins/goban/game.h (revision 0) +++ apps/plugins/goban/game.h (revision 0) @@ -0,0 +1,42 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007-2009 Joshua Simmons + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#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); + +extern int move_num; +extern color_t current_player; +extern char save_file[]; +extern bool game_dirty; +extern struct header_t header; + +bool get_header_string_and_size (struct header_t *header, + enum prop_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,3773 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007-2009 Joshua Simmons + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "sgf.h" +#include "types.h" +#include "goban.h" +#include "board.h" +#include "display.h" +#include "game.h" +#include "file_cache.h" + +#define MIN_STORAGE_BUFFER_SIZE 5000 + +/* 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 (PLUGIN_GAMES_DIR "/gbn_prse.bin") +#define PARSE_STACK_BUFFER_SIZE (1024 * sizeof(int)) + +/* used in SGF file parsing and outputting */ +struct file_cache_t parse_stack; +uint8_t parse_stack_buffer[PARSE_STACK_BUFFER_SIZE]; + +struct file_cache_t unhandled_prop_list; +uint8_t unhandled_prop_list_buffer[128]; + + +#define UNHANDLED_PROP_LIST_FILE (PLUGIN_GAMES_DIR "/gbn_misc.bin") + +int sgf_fd = -1; +int unhandled_fd = -1; + +union 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 struct node_t *get_node (int handle); +static struct prop_t *get_prop (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 void handle_prop_value (enum prop_type_t type); +static pos_t sgf_to_pos (char *buffer); +static int read_prop_value (char *buffer, size_t buffer_size); +static void parse_prop (void); +static void parse_node (void); +static enum prop_type_t parse_prop_type (void); +static bool is_handled (enum prop_type_t type); +static void output_prop (int prop_handle); +static void output_all_props (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 (enum prop_type_t type, pos_t ul, pos_t br); +static bool do_add_stones (void); +static void output_header_props (void); +static bool output_header_helper (enum prop_type_t type); +static void setup_handicap_helper (pos_t pos); +static void do_set_marks (void); +static void set_one_mark (pos_t pos, enum prop_type_t type); +static void set_label_mark (pos_t pos, char to_set); +static int stupid_num_variations (void); + +static void close_file (int *fd); +static ssize_t read_file (int fd, void *buf, size_t count); +static ssize_t write_file (int fd, const void *buf, size_t count); +static int peek_char (int fd); +static int read_char (int fd); +static int peek_char_no_whitespace (int fd); +static int read_char_no_whitespace (int fd); +static bool write_char (int fd, char to_write); + +static void +debugf_current_node (void) +{ + int temp_prop = NO_PROP; + if (current_node < 0) + { + DEBUGF ("CURRENT_NODE < 0 ON DEBUGF_CURRENT_NODE!!!!\n"); + return; + } + DEBUGF ("-----------------------------------------\n"); + DEBUGF ("current_node: %d\n", current_node); + DEBUGF ("start_node %d %d\n", start_node, tree_head); + 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 ("props:\n"); + if (!get_node (current_node) || + (temp_prop = get_node (current_node)->props) < 0) + { + DEBUGF ("none\n"); + } + + while (1) + { + if (temp_prop < 0) + { + break; + } + DEBUGF (" handle: %d\n", temp_prop); + DEBUGF (" type: %d ", get_prop (temp_prop)->type); + if (get_prop (temp_prop)->type < PROP_NAMES_SIZE) + { + DEBUGF ("(%s)", prop_names[get_prop (temp_prop)->type]); + } + DEBUGF ("\n"); + if (get_prop (temp_prop)->type == PROP_BLACK_MOVE || + get_prop (temp_prop)->type == PROP_WHITE_MOVE || + get_prop (temp_prop)->type == PROP_ADD_BLACK || + get_prop (temp_prop)->type == PROP_ADD_WHITE || + get_prop (temp_prop)->type == PROP_ADD_EMPTY) + { + DEBUGF (" i: %d j: %d\n", + I (get_prop (temp_prop)->data.position), + J (get_prop (temp_prop)->data.position)); + } + else + { + DEBUGF (" data: %d\n", get_prop (temp_prop)->data.number); + } + DEBUGF (" next: %d\n", get_prop (temp_prop)->next); + + temp_prop = get_prop (temp_prop)->next; + if (temp_prop >= 0) + { + DEBUGF ("\n"); + } + } + + DEBUGF ("-----------------------------------------\n"); +} + + + +static bool +retreat_node (void) +{ + int result = get_node (current_node)->prev; + + if (current_node == start_node) + { + return false; + } + + if (result < 0) + { + return false; + } + else + { + node_finished_undo (); + undo_node_undo (); + clear_marks_display (); + + current_node = result; + + /* go backwards to the next important node (move/add + stone/variation/etc.) */ + goto_next_important_node (false); + return true; + } +} + +static bool +advance_node (void) +{ + int result = get_node (current_node)->next; + + if (result < 0) + { + return false; + } + else + { + node_finished_undo (); + clear_marks_display (); + + current_node = result; + + /* go forward to the next move/add stone/variation/etc. node */ + goto_next_important_node (true); + result = get_prop_sgf (current_node, PROP_VARIATION_CHOICE, NULL); + + if (result >= 0) + { + DEBUGF ("chosen variation: %d\n", get_prop (result)->data.number); + go_to_variation_sgf (get_prop (result)->data.number); + } + + return true; + } +} + + +int +num_variations_sgf (void) +{ + int result = 1; + struct prop_t *temp_prop; + struct node_t *temp_node = get_node (current_node); + + if (temp_node == 0) + { + return 0; + } + + if (temp_node->prev >= 0) + { + temp_node = get_node (get_node (temp_node->prev)->next); + } + + temp_prop = get_prop (temp_node->props); + + while (temp_prop) + { + if (temp_prop->type == PROP_VARIATION) + { + ++result; + } + else + { + /* variations are at the beginning of the prop list */ + break; + } + + temp_prop = get_prop (temp_prop->next); + } + + return result; +} + +int +mark_child_variations_sgf (void) +{ + int result; + pos_t temp_pos; + color_t temp_color; + int saved = current_node; + struct node_t *node = get_node (current_node); + + if (!node) + { + return 0; + } + + current_node = node->next; + goto_next_important_node (true); + + result = num_variations_sgf (); + + if (result > 1) + { + int i; + int branch_node = current_node; + for (i = 0; i < result; ++i) + { + go_to_variation_sgf (i); + goto_next_important_node (true); + if (get_move_sgf (&temp_pos, &temp_color)) + { + set_one_mark (temp_pos, temp_color == BLACK ? PROP_BLACK_MOVE : + PROP_WHITE_MOVE); + } + + current_node = branch_node; + } + } + + current_node = saved; + + return result; +} + +bool +go_to_variation_sgf (unsigned int num) +{ + int saved = current_node; + struct node_t *temp_node = get_node (current_node); + struct prop_t *temp_prop; + + 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_prop = get_prop (temp_node->props); + + while (num) + { + if (!temp_prop || temp_prop->type != PROP_VARIATION) + { + current_node = saved; + return false; + } + + if (num == 1) + { + current_node = temp_prop->data.node; + break; + } + + temp_prop = get_prop (temp_prop->next); + --num; + } + + return true; +} + +int +get_matching_child_sgf (pos_t pos, color_t color) +{ + struct node_t *temp_node; + struct prop_t *temp_prop; + pos_t temp_pos; + color_t temp_color; + int variation_count = 0; + int saved; + + /* set true later in a loop if we want a prop 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_prop = get_prop (temp_node->props); + + while (temp_prop) + { + if (temp_prop->type == PROP_VARIATION) + { + ++variation_count; + saved = current_node; + current_node = temp_prop->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_prop->type == PROP_BLACK_MOVE && color == BLACK) || + (temp_prop->type == PROP_WHITE_MOVE && color == WHITE)) + { + if (temp_prop->data.position == pos) + { + return 0; + } + else + { + return -4; + } + } + else if (temp_prop->type == PROP_ADD_WHITE || + temp_prop->type == PROP_ADD_BLACK || + temp_prop->type == PROP_ADD_EMPTY) + { + dont_check_first_var_children = true; + } + + temp_prop = get_prop (temp_prop->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 +next_variation_sgf (void) +{ + int saved = current_node; + int saved_start = start_node; + union prop_data_t temp_data; + int prop_handle; + int num_vars = 0; + + if (current_node < 0 || get_node (current_node)->prev < 0) + { + return -1; + } + + start_node = NO_NODE; + + + + if (num_variations_sgf () < 2) + { + + current_node = saved; + start_node = saved_start; + return -2; + } + + /* now we're at a branch node which we should go to the next variation (we + were at the "chosen" one, so go to the next one after that, (mod the + number of variations)) */ + + int chosen = 0; + int branch_node = get_node (get_node (current_node)->prev)->next; + + prop_handle = get_prop_sgf (branch_node, PROP_VARIATION_CHOICE, NULL); + + if (prop_handle >= 0) + { + chosen = get_prop (prop_handle)->data.number; + } + + ++chosen; + + if (chosen >= (num_vars = num_variations_sgf ())) + { + chosen = 0; + } + + temp_data.number = chosen; + add_or_set_prop_sgf (branch_node, PROP_VARIATION_CHOICE, temp_data); + + if (!undo_node_sgf ()) + { + current_node = saved; + start_node = saved_start; + return -3; + } + + if (redo_node_sgf ()) + { + start_node = saved_start; + return chosen + 1; + } + else + { + current_node = saved; + start_node = saved_start; + return -4; + } +} + + + + + +int +add_child_variation_sgf (int *variation_number) +{ + struct node_t *temp_node = get_node (current_node); + struct prop_t *temp_prop; + int temp_prop_handle; + int new_node = alloc_storage_sgf (); + int new_prop = alloc_storage_sgf (); + int temp_variation_number; + + if (new_node < 0 || new_prop < 0) + { + if (new_node >= 0) + { + free_storage_sgf (new_node); + } + if (new_prop >= 0) + { + free_storage_sgf (new_prop); + } + + return NO_NODE; + } + + if (!temp_node) + { + free_storage_sgf (new_node); + free_storage_sgf (new_prop); + + return NO_NODE; + } + + temp_node = get_node (temp_node->next); + + if (!temp_node) + { + free_storage_sgf (new_node); + free_storage_sgf (new_prop); + + return NO_NODE; + } + + get_node (new_node)->prev = current_node; + get_node (new_node)->next = NO_NODE; + get_node (new_node)->props = NO_PROP; + + get_prop (new_prop)->type = PROP_VARIATION; + get_prop (new_prop)->next = NO_PROP; + get_prop (new_prop)->data.node = new_node; + + temp_prop_handle = temp_node->props; + + if (temp_prop_handle < 0) + { + temp_node->props = new_prop; + + if (variation_number) + { + *variation_number = 1; + } + + return new_node; + } + + if (get_prop (temp_prop_handle)->type != PROP_VARIATION) + { + get_prop (new_prop)->next = temp_node->props; + temp_node->props = new_prop; + + if (variation_number) + { + *variation_number = 1; + } + + return new_node; + } + + /* the lowest it can be, since 1 isn't it */ + temp_variation_number = 2; + + while (1) + { + temp_prop = get_prop (temp_prop_handle); + if (temp_prop->next < 0 || + get_prop (temp_prop->next)->type != PROP_VARIATION) + { + get_prop (new_prop)->next = temp_prop->next; + temp_prop->next = new_prop; + + if (variation_number) + { + *variation_number = temp_variation_number; + } + + return new_node; + } + + ++temp_variation_number; + temp_prop_handle = temp_prop->next; + } +} + + +static bool +is_important_node (int handle) +{ + struct prop_t *temp_prop; + + if (handle < 0) + { + return false; + } + + if (handle == start_node) + { + return true; + } + + if (get_node (handle)->prev < 0) + { + return true; + } + + temp_prop = get_prop (get_node (handle)->props); + + while (temp_prop) + { + if (temp_prop->type == PROP_BLACK_MOVE || + temp_prop->type == PROP_WHITE_MOVE || + temp_prop->type == PROP_ADD_BLACK || + temp_prop->type == PROP_ADD_WHITE || + temp_prop->type == PROP_ADD_EMPTY || + temp_prop->type == PROP_VARIATION) + { + return true; + } + + temp_prop = get_prop (temp_prop->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) +{ + 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"); + return false; + } + + if (get_move_sgf (NULL, NULL)) + { + --move_num; + current_player = OTHER (current_player); + } + + if (!retreat_node ()) + { + return false; + } + + do_set_marks (); + return true; +} + +static void +do_set_marks (void) +{ + struct prop_t *prop = get_prop (get_node (current_node)->props); + + while (prop) + { + if (prop->type == PROP_LABEL) + { + set_label_mark (prop->data.label_data.position, + prop->data.label_data.character); + } + else if (prop->type == PROP_COMMENT) + { + set_comment_display (true); + } + else + { + set_one_mark (prop->data.position, prop->type); + } + prop = get_prop (prop->next); + } +} + +static void +set_one_mark (pos_t pos, enum prop_type_t type) +{ + switch (type) + { + case PROP_CIRCLE: + set_mark_display (pos, 'c'); + break; + case PROP_SQUARE: + set_mark_display (pos, 's'); + break; + case PROP_TRIANGLE: + set_mark_display (pos, 't'); + break; + case PROP_MARK: + set_mark_display (pos, 'm'); + break; + case PROP_DIM: + set_mark_display (pos, 'd'); + break; + case PROP_SELECTED: + set_mark_display (pos, 'S'); + break; + case PROP_BLACK_MOVE: + set_mark_display (pos, 'b'); + break; + case PROP_WHITE_MOVE: + set_mark_display (pos, 'w'); + break; + case PROP_INVALID: + set_mark_display (pos, ' '); + default: + break; + } +} + +static void +set_label_mark (pos_t pos, char to_set) +{ + set_mark_display (pos, to_set | (1 << 7)); +} + +bool +redo_node_sgf (void) +{ + pos_t pos; + color_t color; + + if (!advance_node ()) + { + return false; + } + + do_set_marks (); + + if (get_move_sgf (&pos, &color)) + { + if (color != current_player) + { + DEBUGF ("redo_node_sgf: wrong color!\n"); + } + + if (!play_move_undo (pos, color)) + { + retreat_node (); + + return false; + } + else + { + ++move_num; + current_player = OTHER (color); + if (pos == PASS_POS) + { + rb->splashf (HZ / 2, "%s passes", + color == BLACK ? "black" : "white"); + } + + return true; + } + } + else if (do_add_stones ()) + { + return true; + } + + return false; +} + +static bool +do_add_stones (void) +{ + bool ret_val = false; + struct prop_t *temp_prop; + + if (current_node < 0) + { + return false; + } + + temp_prop = get_prop (get_node (current_node)->props); + + while (temp_prop) + { + if (temp_prop->type == PROP_ADD_BLACK || + temp_prop->type == PROP_ADD_WHITE) + { + add_stone_board (temp_prop->data.position, + temp_prop->type == PROP_ADD_BLACK ? BLACK : WHITE, + NULL); + ret_val = true; + } + else if (temp_prop->type == PROP_ADD_EMPTY) + { + remove_stone_board (temp_prop->data.position, false, NULL); + ret_val = true; + } + + temp_prop = get_prop (temp_prop->next); + } + + return ret_val; +} + + +int +add_child_sgf (int *variation_number) +{ + int node_handle; + struct node_t *node; + struct 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->props = NO_PROP; + + current->next = node_handle; + + if (variation_number) + { + *variation_number = 0; + } + + return node_handle; + } + else + { + return add_child_variation_sgf (variation_number); + } +} + + +int +add_prop_sgf (int node, enum prop_type_t type, union prop_data_t data) +{ + int new_prop; + int temp_prop_handle; + + if (node < 0) + { + return NO_PROP; + } + + new_prop = alloc_storage_sgf (); + + if (new_prop < 0) + { + return NO_PROP; + } + + get_prop (new_prop)->type = type; + get_prop (new_prop)->data = data; + + /* check if the new_prop goes at the start */ + if (get_node (node)->props == NO_PROP || + (type == PROP_VARIATION && + get_prop (get_node (node)->props)->type != PROP_VARIATION)) + { + + if (get_node (node)->props >= 0) + { + get_prop (new_prop)->next = get_node (node)->props; + } + else + { + get_prop (new_prop)->next = NO_PROP; + } + + get_node (node)->props = new_prop; + return new_prop; + } + + temp_prop_handle = get_node (node)->props; + + while (1) + { + if (get_prop (temp_prop_handle)->next < 0 || + (get_prop (temp_prop_handle)->type == type && + get_prop (get_prop (temp_prop_handle)->next)->type != type)) + { + /* new_prop goes after the current one either because we're at the + end of the props list, or because we're adding a prop after the + ones of its same type */ + get_prop (new_prop)->next = get_prop (temp_prop_handle)->next; + get_prop (temp_prop_handle)->next = new_prop; + + return new_prop; + } + + temp_prop_handle = get_prop (temp_prop_handle)->next; + } +} + +bool +add_mark_sgf (pos_t pos, enum prop_type_t type) +{ + pos_t temp_pos = INVALID_POS; + union prop_data_t temp_data; + int temp_handle; + enum prop_type_t original_type; + + if (!on_board (pos) || current_node < 0) + { + return false; + } + + if (get_move_sgf (&temp_pos, NULL)) + { + if (temp_pos == pos) + { + return false; + } + } + + if (type == PROP_CIRCLE || + type == PROP_SQUARE || + type == PROP_TRIANGLE || + type == PROP_MARK || type == PROP_DIM || type == PROP_SELECTED) + { + temp_data.position = pos; + + if ((temp_handle = get_prop_pos_sgf (type, temp_data)) >= 0) + { + original_type = get_prop (temp_handle)->type; + delete_prop_handle_sgf (current_node, temp_handle); + + if (type == original_type) + { + set_one_mark (pos, PROP_INVALID); + return true; + } + } + + add_prop_sgf (current_node, type, temp_data); + set_one_mark (pos, type); + + return true; + } + else if (type == PROP_LABEL) + { +#define MIN_LABEL 'a' +#define MAX_LABEL 'f' + int temp_prop_handle = get_node (current_node)->props; + + while (temp_prop_handle >= 0) + { + struct prop_t *temp_prop = get_prop (temp_prop_handle); + + if (temp_prop->type == PROP_LABEL && + temp_prop->data.label_data.position == pos) + { + char to_set = temp_prop->data.label_data.character; + ++to_set; + if (to_set > MAX_LABEL) + { + delete_prop_handle_sgf (current_node, temp_prop_handle); + set_one_mark (pos, PROP_INVALID); + return true; + } + + temp_prop->data.label_data.character = to_set; + set_label_mark (pos, to_set); + return true; + } + + temp_prop_handle = temp_prop->next; + } + + temp_data.label_data.character = MIN_LABEL; + temp_data.label_data.position = pos; + + add_prop_sgf (current_node, type, temp_data); + set_label_mark (pos, MIN_LABEL); + return true; + } + else + { + return false; + } + +} + + +bool +add_stone_sgf (pos_t pos, color_t color) +{ + int handle; + int prop_handle; + int saved = current_node; + union prop_data_t temp_data; + enum prop_type_t temp_type; + int var_number; + bool returned_to_original; + int temp; + + if (!on_board (pos)) + { + return false; + } + + if (color == get_point_board (pos)) + { + return false; + } + + if (get_node (current_node)->next < 0 && + (get_prop_sgf (current_node, PROP_ADD_BLACK, NULL) >= 0 || + get_prop_sgf (current_node, PROP_ADD_WHITE, NULL) >= 0 || + get_prop_sgf (current_node, PROP_ADD_EMPTY, NULL) >= 0)) + { + /* current node is an okay place to add stones, so let's do it */ + if (((color == BLACK || color == WHITE) && + add_stone_board (pos, color, &returned_to_original)) || + remove_stone_board (pos, false, &returned_to_original)) + { + if (color == BLACK) + { + temp_type = PROP_ADD_BLACK; + } + else if (color == WHITE) + { + temp_type = PROP_ADD_WHITE; + } + else + { + temp_type = PROP_ADD_EMPTY; + } + + temp_data.position = pos; + + handle = get_prop_pos_sgf (temp_type, temp_data); + + if (handle >= 0) + { + delete_prop_handle_sgf (current_node, handle); + } + + if (!returned_to_original) + { + add_prop_sgf (current_node, temp_type, temp_data); + } + + game_dirty = true; + + return true; + } + else + { + DEBUGF ("add or remove stone call failed!\n"); + return false; + } + } + else + { + /* we have to make a child variation and add stones in it */ + + /* 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; + } + } + + handle = add_child_sgf (&var_number); + + if (handle < 0) + { + rb->splash (2 * HZ, "Out of memory!"); + return false; + } + + temp_data.position = pos; + + if (color == BLACK) + { + temp_type = PROP_ADD_BLACK; + } + else if (color == WHITE) + { + temp_type = PROP_ADD_WHITE; + } + else + { + temp_type = PROP_ADD_EMPTY; + } + + prop_handle = add_prop_sgf (handle, temp_type, temp_data); + + if (prop_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); */ + rb->splash (2 * HZ, "Out of memory!"); + return false; + } + + game_dirty = true; + + /* now, "choose" the variation that we just added */ + + current_node = get_node (current_node)->next; + temp_data.number = var_number; + + temp = add_or_set_prop_sgf (current_node, + PROP_VARIATION_CHOICE, temp_data); + + /* free up a superfluous prop */ + if (var_number == 0) + { + delete_prop_handle_sgf (current_node, temp); + } + + current_node = saved; + + /* and follow to our choice, returning since we already did the work */ + return redo_node_sgf (); + } + + return false; +} + +int +get_prop_pos_sgf (enum prop_type_t type, union prop_data_t data) +{ + int temp_prop_handle; + struct prop_t *prop; + + if (current_node < 0) + { + return -1; + } + + temp_prop_handle = get_node (current_node)->props; + + while (temp_prop_handle >= 0) + { + prop = get_prop (temp_prop_handle); + + if ((type == PROP_ADD_BLACK || + type == PROP_ADD_WHITE || + type == PROP_ADD_EMPTY) + && + (prop->type == PROP_ADD_BLACK || + prop->type == PROP_ADD_WHITE || + prop->type == PROP_ADD_EMPTY) + && (prop->data.position == data.position)) + { + return temp_prop_handle; + } + + if ((type == PROP_CIRCLE || + type == PROP_SQUARE || + type == PROP_TRIANGLE || + type == PROP_MARK || + type == PROP_DIM || + type == PROP_SELECTED) && + (prop->type == PROP_CIRCLE || + prop->type == PROP_SQUARE || + prop->type == PROP_TRIANGLE || + prop->type == PROP_MARK || + prop->type == PROP_DIM || + prop->type == PROP_SELECTED) && + (prop->data.position == data.position)) + { + return temp_prop_handle; + } + + temp_prop_handle = get_prop (temp_prop_handle)->next; + } + + return -1; +} + + +int +add_or_set_prop_sgf (int node, enum prop_type_t type, union prop_data_t data) +{ + int temp_prop_handle; + + if (node < 0) + { + return NO_PROP; + } + + temp_prop_handle = get_prop_sgf (node, type, NULL); + + if (temp_prop_handle >= 0) + { + get_prop (temp_prop_handle)->data = data; + return temp_prop_handle; + } + + /* there was no prop to set, so we need to add one */ + return add_prop_sgf (node, type, data); +} + +int +get_prop_sgf (int node, enum prop_type_t type, int *previous_prop) +{ + int previous_handle = NO_PROP; + int current_handle; + + if (node < 0) + { + return NO_PROP; + } + + if (get_node (node)->props < 0) + { + return NO_PROP; + } + + current_handle = get_node (node)->props; + + while (current_handle >= 0) + { + if (get_prop (current_handle)->type == type || type == PROP_ANY) + { + if (previous_prop) + { + *previous_prop = previous_handle; + } + + return current_handle; + } + else + { + previous_handle = current_handle; + current_handle = get_prop (current_handle)->next; + } + } + + return NO_PROP; +} + + +bool +delete_prop_sgf (int node, enum prop_type_t type) +{ + if (node < 0) + { + return false; + } + + return delete_prop_handle_sgf (node, get_prop_sgf (node, type, NULL)); +} + + +bool +delete_prop_handle_sgf (int node, int prop) +{ + int previous; + + if (prop < 0 || node < 0 || get_node (node)->props < 0) + { + return false; + } + + if (get_node (node)->props == prop) + { + get_node (node)->props = get_prop (get_node (node)->props)->next; + free_storage_sgf (prop); + return true; + } + + previous = get_node (node)->props; + + while (get_prop (previous)->next != prop && get_prop (previous)->next >= 0) + { + previous = get_prop (previous)->next; + } + + if (get_prop (previous)->next < 0) + { + return false; + } + else + { + get_prop (previous)->next = get_prop (get_prop (previous)->next)->next; + free_storage_sgf (prop); + return true; + } +} + + +int +read_comment_sgf (char *buffer, size_t buffer_size) +{ + size_t bytes_read = 0; + + int prop_handle = get_prop_sgf (current_node, PROP_COMMENT, NULL); + + if (prop_handle < 0) + { + return 0; + } + + if (unhandled_fd < 0) + { + DEBUGF ("unhandled file is closed?!\n"); + return 0; + } + + if (rb->lseek (unhandled_fd, get_prop (prop_handle)->data.number, + SEEK_SET) < 0) + { + DEBUGF ("couldn't seek in unhandled_fd\n"); + return -1; + } + + if (!read_char_no_whitespace (unhandled_fd) == 'C' || + !read_char_no_whitespace (unhandled_fd) == '[') + { + DEBUGF ("comment prop points to incorrect place in unhandled_fd!!\n"); + return -1; + } + + /* make output a string, the lazy way */ + rb->memset (buffer, 0, buffer_size); + ++bytes_read; + + bool done = false; + bool escaped = false; + + while ((buffer_size > bytes_read) && !done) + { + int temp = read_char (unhandled_fd); + + switch (temp) + { + case ']': + if (!escaped) + { + done = true; + break; + } + escaped = false; + break; + + case -1: + DEBUGF + ("encountered end of unhandled_fd file before end of comment!\n"); + done = true; + break; + + case '\\': + escaped = !escaped; + if (escaped) + { + break; + } + + default: + *buffer = temp; + ++buffer; + ++bytes_read; + escaped = false; + break; + } + } + + return bytes_read; +} + + +int +write_comment_sgf (char *string) +{ + char *orig_string = string; + + int prop_handle = get_prop_sgf (current_node, PROP_COMMENT, NULL); + + int start_of_comment = -1; + bool overwriting = false; + + int bytes_written = 0; + + game_dirty = true; + + if (unhandled_fd < 0) + { + DEBUGF ("unhandled file is closed?!\n"); + return 0; + } + + if (prop_handle >= 0) + { + if ((start_of_comment = rb->lseek (unhandled_fd, + get_prop (prop_handle)->data.number, + SEEK_SET)) < 0) + { + DEBUGF ("couldn't seek in unhandled_fd\n"); + return -1; + } + else + { + overwriting = true; + } + } + else + { + overwriting = false; + } + + start_of_write_wcs: + + if (overwriting) + { + if (!read_char_no_whitespace (unhandled_fd) == 'C' || + !read_char_no_whitespace (unhandled_fd) == '[') + { + DEBUGF ("non-comment while overwriting!!\n"); + return -1; + } + } + else + { + start_of_comment = rb->lseek (unhandled_fd, 0, SEEK_END); + + if (start_of_comment < 0) + { + DEBUGF ("error seeking to end in write_comment_sgf\n"); + return -1; + } + + write_char (unhandled_fd, 'C'); + write_char (unhandled_fd, '['); + } + + + bool overwrite_escaped = false; + + while (*string) + { + if (overwriting) + { + int orig_char = peek_char (unhandled_fd); + + switch (orig_char) + { + case '\\': + overwrite_escaped = !overwrite_escaped; + break; + + case ']': + if (overwrite_escaped) + { + overwrite_escaped = false; + break; + } + /* otherwise, fall through */ + + case -1: + + /* we reached the end of the part we can put our comment in, + but there's more comment to write, so we should start + again, this time making a new comment (the old becomes + wasted space in unhandled_fd, but it doesn't really hurt + anything except extra space on disk */ + + overwriting = false; + string = orig_string; + bytes_written = 0; + goto start_of_write_wcs; + break; + + default: + overwrite_escaped = false; + break; + } + } + + switch (*string) + { + case '\\': + case ']': + write_char (unhandled_fd, '\\'); + + /* fall through */ + + default: + write_char (unhandled_fd, *string); + break; + } + + ++string; + ++bytes_written; + } + + /* finish out the record */ + write_char (unhandled_fd, ']'); + write_char (unhandled_fd, ';'); + + /* and put the reference into the unhandled_fd into the comment prop */ + union prop_data_t temp_data; + temp_data.number = start_of_comment; + + add_or_set_prop_sgf (current_node, PROP_COMMENT, temp_data); + return bytes_written; +} + + +bool +play_move_sgf (pos_t pos, color_t color) +{ + int handle; + int prop_handle; + int temp; + int temp2; + union prop_data_t temp_data; + 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 ((temp = get_matching_child_sgf (pos, color)) >= 0) + { + /* don't have to do anything to set up temp as the right variation + number */ + + } + else + { + /* now either there were no children, or none matched the one we want + so we have to add a new one */ + + /* first test if it's legal. we don't do this above because SGF files + are allowed to have illegal moves in them, and it seems to make + sense to allow traversing those variations without making the user + change to a different play_mode */ + + bool suicide_allowed = false; + + if (rb->strcmp (header.ruleset, "NZ") == 0 || + rb->strcmp (header.ruleset, "GOE") == 0) + { + suicide_allowed = true; + } + + if (play_mode != MODE_FORCE_PLAY && + !legal_move_board (pos, color, suicide_allowed)) + { + return false; + } + + handle = add_child_sgf (NULL); + + if (handle < 0) + { + current_node = saved; + return false; + } + + union prop_data_t temp_prop_data; + temp_prop_data.position = pos; + + prop_handle = add_prop_sgf (handle, + color == BLACK ? PROP_BLACK_MOVE : + PROP_WHITE_MOVE, temp_prop_data); + + if (prop_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); */ + current_node = saved; + return false; + } + + game_dirty = true; + + temp = get_matching_child_sgf (pos, color); + + if (temp < 0) + { + DEBUGF ("impossible error in play_move_sgf!! 324234\n"); + rb->splash (5 * HZ, + "Impossible error while playing move!! 324234"); + } + } + + /* now, one way or another temp has been set to the child variation number + that we should follow, so all we need to do is "choose" it and + redo_node_sgf */ + + current_node = get_node (current_node)->next; + temp_data.number = temp; + + temp2 = add_or_set_prop_sgf (current_node, + PROP_VARIATION_CHOICE, temp_data); + /* free up a superfluous prop */ + if (temp == 0) + { + delete_prop_handle_sgf (current_node, temp2); + } + + current_node = saved; + return redo_node_sgf (); +} + +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 +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 prop_handle; + struct prop_t *temp_prop; + + if (handle < 0) + { + return 0; + } + + prop_handle = get_node (handle)->props; + + while (prop_handle >= 0) + { + if ((temp_prop = get_prop (prop_handle))->type == PROP_BLACK_MOVE || + temp_prop->type == PROP_WHITE_MOVE) + { + if (pos) + { + *pos = get_prop (prop_handle)->data.position; + } + + if (color) + { + *color = temp_prop->type == PROP_BLACK_MOVE ? BLACK : WHITE; + } + return true; + } + + prop_handle = temp_prop->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)->props = NO_PROP; + 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 space"); + 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 (union 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; + unsigned int start_handle = handle; + unsigned int start_buffer = 0; + + do + { + ++handle; + + 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; + + 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 space"); + 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; + } + + unhandled_fd = create_or_open_file (UNHANDLED_PROP_LIST_FILE); + + + if (unhandled_fd < 0) + { + return false; + } + + rb->lseek (unhandled_fd, 0, SEEK_SET); + rb->ftruncate (unhandled_fd, 0); + + return true; +} + + +void +clear_caches_sgf (void) +{ + seek_to_beginning (&parse_stack); + truncate_cache (&parse_stack); + + rb->lseek (unhandled_fd, 0, SEEK_SET); + rb->ftruncate (unhandled_fd, 0); +} + +void +cleanup_sgf (void) +{ + seek_to_beginning (&parse_stack); + truncate_cache (&parse_stack); + close_cache (&parse_stack); + + rb->lseek (unhandled_fd, 0, SEEK_SET); + rb->ftruncate (unhandled_fd, 0); + close_file (&unhandled_fd); + + close_file (&sgf_fd); +} + +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 ((int) temp_buffer & ALIGNMENT_VAL) + { + temp_buffer++; + size--; + } + + /* same as temp = size / (sizeof(union storage_t) + 1/8) + + (we need 1 bit extra for each union storage_t, for the free list) */ + temp = + (8 * (size - ALIGNMENT_VAL - 1)) / (8 * sizeof (union 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 (union storage_t) * temp; + size -= sizeof (union storage_t) * temp; + + /* alignment */ + while ((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 struct 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 struct prop_t * +get_prop (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].prop); +} + + +static enum prop_type_t +parse_prop_type (void) +{ + char buffer[3]; + int pos = 0; + int temp; + + rb->memset (buffer, 0, sizeof (buffer)); + + while (1) + { + temp = peek_char_no_whitespace (sgf_fd); + + if (temp == ';' || temp == '[' || temp == '(' || + temp == -1 || temp == ')') + { + if (pos == 1 || pos == 2) + { + break; + } + else + { + return PROP_INVALID; + } + } + else if (temp >= 'A' && temp <= 'Z') + { + buffer[pos++] = temp; + + if (pos == 2) + { + read_char_no_whitespace (sgf_fd); + break; + } + } + + temp = read_char_no_whitespace (sgf_fd); + } + + /* check if we're still reading a prop name, in which case we fail (but + first we want to eat up the rest of the prop name) */ + bool failed = false; + while (peek_char_no_whitespace (sgf_fd) != ';' && + peek_char_no_whitespace (sgf_fd) != '[' && + peek_char_no_whitespace (sgf_fd) != '(' && + peek_char_no_whitespace (sgf_fd) != '}' && + peek_char_no_whitespace (sgf_fd) != -1) + { + failed = true; + read_char_no_whitespace (sgf_fd); + } + + if (failed) + { + return PROP_INVALID; + } + + int i; + for (i = 0; i < PROP_NAMES_SIZE; ++i) + { + if (rb->strcmp (buffer, prop_names[i]) == 0) + { + return (enum prop_type_t) i; + } + } + return PROP_INVALID; +} + + +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; + + close_file (&sgf_fd); + + sgf_fd = rb->open (filename, O_RDONLY); + + if (sgf_fd < 0) + { + return false; + } + + current_node = start_node; + + if (current_node < 0) + { + current_node = saved; + return false; + } + + seek_to_beginning (&parse_stack); + truncate_cache (&parse_stack); + + /* seek to the first '(' */ + while (peek_char_no_whitespace (sgf_fd) != '(') + { + if (read_char_no_whitespace (sgf_fd) == -1) + { + DEBUGF ("end of file or error before we found a '('\n"); + current_node = saved; + return false; + } + } + + if (is_error (&parse_stack)) + { + DEBUGF ("error before real parsing\n"); + current_node = saved; + return false; + } + + push_int_cache (&parse_stack, rb->lseek (sgf_fd, 0, SEEK_CUR)); + 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_fd); + 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); + + rb->lseek (sgf_fd, file_position, SEEK_SET); + /* clear_peek(); */ + + + /* we're at the start of a gametree here, right at the '(' */ + temp = read_char_no_whitespace (sgf_fd); + + if (temp != '(') + { + DEBUGF ("start of gametree doesn't have a '('!\n"); + current_node = saved; + return false; + } + } + + while (1) + { + temp = peek_char_no_whitespace (sgf_fd); + /* DEBUGF("||| %d, %c\n", absolute_position(), (char) temp); */ + + if (temp == ';') + { + current_node = add_child_sgf (NULL); + + + read_char_no_whitespace (sgf_fd); + parse_node (); + } + else if (temp == ')') + { + /* finished this gametree */ + + /* we want to end one past the ')', so eat it up: */ + read_char_no_whitespace (sgf_fd); + break; + } + else if (temp == '(') + { + DEBUGF ("adding %d\n", (int) rb->lseek (sgf_fd, 0, SEEK_CUR)); + + push_int_cache (&parse_stack, rb->lseek (sgf_fd, 0, SEEK_CUR)); + 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_fd); + } + } + } + + current_node = get_node (tree_head)->next; + while (current_node >= 0 && get_node (current_node)->props < 0) + { + temp = current_node; /* to be freed later */ + + /* update the ->prev pointed on all branches of the next node */ + current_node = get_node (current_node)->next; + /* DEBUGF("trying to set prev for branch %d\n", current_node); */ + if (current_node >= 0) + { + get_node (current_node)->prev = tree_head; + + struct prop_t *loop_prop = + get_prop (get_node (current_node)->props); + + while (loop_prop != 0) + { + if (loop_prop->type == PROP_VARIATION) + { + get_node (loop_prop->data.number)->prev = tree_head; + } + else + { + /* all of the variations have to be up front, so we can + quit here */ + break; + } + loop_prop = get_prop (loop_prop->next); + } + } + + /* update the tree head */ + get_node (tree_head)->next = get_node (temp)->next; + /* DEBUGF("freeing %d %d %d\n", temp, start_node, saved); */ + if (start_node == temp || saved == temp) + { + start_node = saved = tree_head; + } + free_storage_sgf (temp); + + current_node = get_node (tree_head)->next; + } + + current_node = saved; + + /* DEBUGF("got past!\n"); */ + close_file (&sgf_fd); + return true; +} + + + + + + +static void +parse_node (void) +{ + int temp; + + while (1) + { + temp = peek_char_no_whitespace (sgf_fd); + + if (temp == -1 || temp == ')' || temp == '(' || temp == ';') + { + return; + } + else + { + parse_prop (); + } + } +} + + + +int start_of_prop = 0; +static void +parse_prop (void) +{ + enum prop_type_t temp_type = PROP_INVALID; + int temp; + + + while (1) + { + temp = peek_char_no_whitespace (sgf_fd); + + if (temp == -1 || temp == ')' || temp == '(' || temp == ';') + { + return; + } + else if (temp == '[') + { + handle_prop_value (temp_type); + } + else + { + start_of_prop = rb->lseek (sgf_fd, 0, SEEK_CUR); + temp_type = parse_prop_type (); + } + } +} + +static int +read_prop_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 (sgf_fd) == '[') + { + read_char (sgf_fd); + } + + while (1) + { + temp = read_char (sgf_fd); + 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_prop_value (enum prop_type_t type) +{ + /* max size of generically supported prop 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 PROP_HANDLER_BUFFER_SIZE 16 + + char real_buffer[PROP_HANDLER_BUFFER_SIZE]; + char *buffer = real_buffer; + + int temp; + union prop_data_t temp_data; + bool in_prop_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) || type == PROP_COMMENT) + { + /* DEBUGF("unhandled prop %d\n", (int) type); */ + handle_unhandled_prop_hpv: + rb->lseek (sgf_fd, start_of_prop, SEEK_SET); + /* clear_peek(); */ + + temp_data.number = rb->lseek (unhandled_fd, 0, SEEK_CUR); + /* absolute_position(&unhandled_prop_list); */ + + add_prop_sgf (current_node, + type == PROP_COMMENT ? PROP_COMMENT : + PROP_GENERIC_UNHANDLED, temp_data); + + got_value = false; + while (!done) + { + temp = peek_char (sgf_fd); + + switch (temp) + { + case -1: + done = true; + break; + + case '\\': + if (got_value && !in_prop_value) + { + done = true; + } + escaped = !escaped; + break; + case '[': + escaped = false; + in_prop_value = true; + got_value = true; + break; + case ']': + if (!escaped) + { + in_prop_value = false; + } + escaped = false; + break; + case ')': + case '(': + case ';': + if (!in_prop_value) + { + done = true; + } + escaped = false; + break; + default: + if (got_value && !in_prop_value) + { + if (!is_whitespace (temp)) + { + done = true; + } + } + escaped = false; + break; + }; + + if (done) + { + write_char (unhandled_fd, ';'); + } + else + { + /* don't write out-of-prop whitespace */ + if (in_prop_value || !is_whitespace (temp)) + { + write_char (unhandled_fd, (char) temp); + } + + read_char (sgf_fd); + } + } + + + return; + } + else if (type == PROP_BLACK_MOVE || type == PROP_WHITE_MOVE) + { + /* DEBUGF("move prop %d\n", (int) type); */ + + temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE); + + temp_data.position = INVALID_POS; + + /* empty is apparently acceptable as a pass */ + 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_prop_sgf (current_node, type, temp_data); + } + + return; + } + else if (type == PROP_ADD_BLACK || + type == PROP_ADD_WHITE || + type == PROP_ADD_EMPTY || + type == PROP_CIRCLE || + type == PROP_SQUARE || + type == PROP_TRIANGLE || + type == PROP_DIM || type == PROP_MARK || type == PROP_SELECTED) + { + /* DEBUGF("add prop %d\n", (int) type); */ + + temp = read_prop_value (buffer, PROP_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_prop_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 == PROP_LABEL) + { + temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE); + + if (temp < 4 || buffer[2] != ':') + { + DEBUGF ("invalid LaBel property '%s'", buffer); + + /* TODO: should i do this? goto handle_unhandled_prop_hpv; */ + } + temp_data.label_data.position = sgf_to_pos (buffer); + + if (!on_board (temp_data.label_data.position)) + { + DEBUGF ("LaBel set on invalid position!\n"); + } + + temp_data.label_data.character = buffer[3]; + + add_prop_sgf (current_node, type, temp_data); + return; + } + else if (type == PROP_GAME) + { + temp = read_prop_value (buffer, PROP_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_file (&sgf_fd); + } + } + else if (type == PROP_FILE_FORMAT) + { + temp = read_prop_value (buffer, PROP_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_file (&sgf_fd); + } + } + else if (type == PROP_APPLICATION || type == PROP_CHARSET) + { + /* we don't give a shit. on output we'll write our own values for + these */ + read_prop_value (NULL, 0); + } + else if (type == PROP_SIZE) + { + temp = read_prop_value (buffer, PROP_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_file (&sgf_fd); + } + 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 == PROP_KOMI) + { + temp = read_prop_value (buffer, PROP_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 + { + if (*buffer != '0') + { + DEBUGF ("extra characters after komi value!\n"); + } + } + } + } + } + else if (type == PROP_BLACK_NAME || + type == PROP_WHITE_NAME || + type == PROP_BLACK_RANK || + type == PROP_WHITE_RANK || + type == PROP_BLACK_TEAM || + type == PROP_WHITE_TEAM || + type == PROP_DATE || + type == PROP_ROUND || + type == PROP_EVENT || + type == PROP_PLACE || + type == PROP_OVERTIME || + type == PROP_RESULT || type == PROP_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_prop_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 == PROP_TIME_LIMIT) + { + temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE); + header.time_limit = rb->atoi (buffer); + DEBUGF ("setting time: %d (%s)\n", header.time_limit, buffer); + } + else if (type == PROP_HANDICAP) + { + temp = read_prop_value (buffer, PROP_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_prop_sgf (current_node, type, temp_data); + DEBUGF ("setting handicap: %d\n", header.handicap); + } + else + { + DEBUGF ("invalid HAndicap prop. ignoring\n"); + } + } + else + { + rb->splash (HZ, "extraneous HAndicap prop present in file!\n"); + } + } + else + { + DEBUGF ("UNHANDLED PROP TYPE!!!\n"); + rb->splash (4 * HZ, + "a property that should have been handled was not, please inform the developers of this bug (send a copy of the original SGF file if possible)"); + read_prop_value (NULL, 0); + } +} + + + +/* upper-left and bottom right */ +static void +do_range (enum prop_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; + union prop_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_prop_sgf (current_node, type, temp_data); + } + } +} + + +static int +peek_char (int fd) +{ + char peeked_char; + + int result = rb->read (fd, &peeked_char, 1); + + if (result != 1) + { + return -1; + } + + result = rb->lseek (fd, -1, SEEK_CUR); + + if (result < 0) + { + return -1; + } + + return peeked_char; +} + + +static int +read_char (int fd) +{ + char read_char; + + int result = rb->read (fd, &read_char, 1); + + if (result != 1) + { + return -1; + } + + return read_char; +} + + +static bool +write_char (int fd, char to_write) +{ + int result = write_file (fd, &to_write, 1); + + if (result != 1) + { + return false; + } + + return true; +} + +static ssize_t +write_file (int fd, const void *buf, size_t count) +{ + const char *buffer = buf; + int result; + int ret_val = count; + + while (count) + { + result = rb->write (fd, buffer, count); + + if (result < 0) + { + return -1; + } + + count -= result; + buffer += result; + } + + return ret_val; +} + +static ssize_t +read_file (int fd, void *buf, size_t count) +{ + char *buffer = buf; + int result; + int ret_val = count; + + while (count) + { + result = rb->read (fd, buffer, count); + + if (result <= 0) + { + return -1; + } + + count -= result; + buffer += result; + } + + return ret_val; +} + +static int +read_char_no_whitespace (int fd) +{ + int result = peek_char_no_whitespace (fd); + + read_char (fd); + + return result; +} + +static int +peek_char_no_whitespace (int fd) +{ + int result; + + while (is_whitespace (result = peek_char (fd))) + { + read_char (fd); + } + + return result; +} + + +static void +close_file (int *fd) +{ + if (*fd >= 0) + { + rb->close (*fd); + } + + *fd = -1; +} + +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 (enum prop_type_t type) +{ + if (type == PROP_BLACK_MOVE || + type == PROP_WHITE_MOVE || + type == PROP_ADD_BLACK || + type == PROP_ADD_WHITE || + type == PROP_ADD_EMPTY || + type == PROP_CIRCLE || type == PROP_SQUARE || type == PROP_TRIANGLE || +#if 0 + type == PROP_DIM || type == PROP_SELECTED || +#endif + type == PROP_COMMENT || + type == PROP_MARK || + type == PROP_LABEL || + type == PROP_GAME || + type == PROP_FILE_FORMAT || + type == PROP_APPLICATION || + type == PROP_CHARSET || + type == PROP_SIZE || + type == PROP_KOMI || + type == PROP_BLACK_NAME || + type == PROP_WHITE_NAME || + type == PROP_BLACK_RANK || + type == PROP_WHITE_RANK || + type == PROP_BLACK_TEAM || + type == PROP_WHITE_TEAM || + type == PROP_DATE || + type == PROP_ROUND || + type == PROP_EVENT || + type == PROP_PLACE || + type == PROP_OVERTIME || + type == PROP_RESULT || + type == PROP_TIME_LIMIT || + type == PROP_RULESET || type == PROP_HANDICAP) + { + return true; + } + + return false; +} + + + +static int +stupid_num_variations (void) +{ + int result = 1; + struct prop_t *temp_prop; + struct node_t *temp_node = get_node (current_node); + + if (temp_node == 0) + { + return 0; + } + + temp_prop = get_prop (temp_node->props); + + while (temp_prop) + { + if (temp_prop->type == PROP_VARIATION) + { + ++result; + } + else + { + // variations are at the beginning of the prop list + break; + } + + temp_prop = get_prop (temp_prop->next); + } + + return result; +} + + + +bool +output_sgf (char *filename) +{ + int current = -1; + union prop_data_t temp_data; + int saved = current_node; + + sgf_fd = create_or_open_file (filename); + + if (sgf_fd < 0) + { + return false; + } + + DEBUGF ("outputting to: %s (%d)\n", filename, sgf_fd); + + seek_to_beginning (&parse_stack); + truncate_cache (&parse_stack); + + rb->lseek (sgf_fd, 0, SEEK_SET); + rb->ftruncate (sgf_fd, 0); + + if (is_error (&parse_stack) || sgf_fd < 0) + { + return false; + } + + if (tree_head < 0) + { + close_file (&sgf_fd); + return false; + } + + push_int_cache (&parse_stack, tree_head); + + while (pop_int_cache (&parse_stack, ¤t)) + { + int var_to_process = 0; + int temp_prop = + get_prop_sgf (current, PROP_VARIATION_TO_PROCESS, NULL); + + if (temp_prop >= 0) + { + var_to_process = get_prop (temp_prop)->data.number; + } + + current_node = current; + + if (var_to_process > 0) + { + write_char (sgf_fd, ')'); + } + + if (var_to_process == stupid_num_variations ()) + { + delete_prop_sgf (current, PROP_VARIATION_TO_PROCESS); + + continue; + } + else + { + write_char (sgf_fd, '\n'); + write_char (sgf_fd, '('); + + /* 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_prop_sgf (current, + PROP_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_file (&sgf_fd); + DEBUGF ("done outputting, file closed\n"); + return true; +} + + +static void +output_header_props (void) +{ + char buffer[128]; + + rb->strncpy (buffer, "GM[1]FF[4]CA[UTF-8]AP[Rockbox Goban:1.0]", + sizeof (buffer)); + write_file (sgf_fd, buffer, rb->strlen (buffer)); + + /* board size */ + if (board_width != board_height) + { + rb->snprintf (buffer, sizeof (buffer), "%s[%d:%d]", + prop_names[PROP_SIZE], board_width, board_height); + } + else + { + rb->snprintf (buffer, sizeof (buffer), "%s[%d]", + prop_names[PROP_SIZE], board_width); + } + + write_file (sgf_fd, buffer, rb->strlen (buffer)); + + /* + rb->snprintf(buffer, sizeof(buffer), "%s[%d]", + prop_names[PROP_HANDICAP], header.handicap); write_cache(buffer, + rb->strlen(buffer)); */ + rb->snprintf (buffer, sizeof (buffer), "%s[", prop_names[PROP_KOMI]); + write_file (sgf_fd, buffer, rb->strlen (buffer)); + + snprint_fixed (buffer, sizeof (buffer), header.komi); + write_file (sgf_fd, buffer, rb->strlen (buffer)); + + write_char (sgf_fd, ']'); + + output_header_helper (PROP_RULESET); + output_header_helper (PROP_RESULT); + + output_header_helper (PROP_BLACK_NAME); + output_header_helper (PROP_WHITE_NAME); + output_header_helper (PROP_BLACK_RANK); + output_header_helper (PROP_WHITE_RANK); + output_header_helper (PROP_BLACK_TEAM); + output_header_helper (PROP_WHITE_TEAM); + + output_header_helper (PROP_EVENT); + output_header_helper (PROP_PLACE); + output_header_helper (PROP_DATE); + + if (output_header_helper (PROP_OVERTIME) || header.time_limit != 0) + { + rb->snprintf (buffer, sizeof (buffer), "%s[%d]", + prop_names[PROP_TIME_LIMIT], header.time_limit); + write_file (sgf_fd, buffer, rb->strlen (buffer)); + } + + +} + + +static bool +output_header_helper (enum prop_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 prop type!!\n"); + return false; + } + + if (rb->strlen (buffer)) + { + rb->snprintf (temp_buffer, sizeof (temp_buffer), "%s[", + prop_names[type]); + + write_file (sgf_fd, temp_buffer, rb->strlen (temp_buffer)); + + write_file (sgf_fd, buffer, rb->strlen (buffer)); + + rb->strcpy (temp_buffer, "]"); + + write_file (sgf_fd, temp_buffer, rb->strlen (temp_buffer)); + + return true; + } + + return false; +} + + +bool first_node_in_tree = true; +static void +output_gametree (void) +{ + first_node_in_tree = true; + + while (output_current_node ()) + { + current_node = get_node (current_node)->next; + } + +} + +static bool +output_current_node (void) +{ + if (current_node < 0) + { + return false; + } + + if (stupid_num_variations () > 1 && + get_prop_sgf (current_node, PROP_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; + } + + if (first_node_in_tree) + { + first_node_in_tree = false; + } + else + { + write_char (sgf_fd, '\n'); + } + write_char (sgf_fd, ';'); + /* the first node gets the header */ + if (get_node (current_node)->prev < 0) + { + output_header_props (); + DEBUGF ("outputted header bullshit\n"); + } + + output_all_props (); + + return true; +} + + + +enum prop_type_t last_output_type = PROP_INVALID; +static void +output_all_props (void) +{ + int temp_handle = get_node (current_node)->props; + + last_output_type = PROP_INVALID; + + while (temp_handle >= 0) + { + output_prop (temp_handle); + temp_handle = get_prop (temp_handle)->next; + } +} + +static void +output_prop (int prop_handle) +{ + char buffer[16]; + enum prop_type_t temp_type = get_prop (prop_handle)->type; + + buffer[0] = 't'; + buffer[1] = 't'; + + if (is_handled (temp_type) && temp_type != PROP_COMMENT) + { + if (temp_type != last_output_type) + { + write_file (sgf_fd, prop_names[temp_type], + PROP_NAME_LEN (temp_type)); + } + + write_char (sgf_fd, '['); + + if (temp_type == PROP_HANDICAP) + { + rb->snprintf (buffer, sizeof (buffer), "%d", + get_prop (prop_handle)->data.number); + write_file (sgf_fd, buffer, rb->strlen (buffer)); + } + else if (temp_type == PROP_LABEL) + { + pos_to_sgf (get_prop (prop_handle)->data.label_data.position, + buffer); + buffer[2] = '\0'; + + rb->snprintf (&buffer[2], sizeof (buffer) - 2, ":%c", + get_prop (prop_handle)->data.label_data.character); + + write_file (sgf_fd, buffer, rb->strlen (buffer)); + } + else + { + pos_to_sgf (get_prop (prop_handle)->data.position, buffer); + + write_file (sgf_fd, buffer, 2); + } + + write_char (sgf_fd, ']'); + } + else if (temp_type == PROP_GENERIC_UNHANDLED || temp_type == PROP_COMMENT) + { + bool escaped = false; + bool in_prop_value = false; + int temp; + bool done = false; + + rb->lseek (unhandled_fd, get_prop (prop_handle)->data.number, + SEEK_SET); + + while (!done) + { + temp = peek_char (unhandled_fd); + + switch (temp) + { + case ';': + escaped = false; + if (in_prop_value) + { + break; + } + /* otherwise, fall through */ + case -1: + done = true; + break; + + case '\\': + escaped = !escaped; + break; + + case '[': + escaped = false; + in_prop_value = true; + break; + + case ']': + if (!escaped) + { + in_prop_value = false; + } + escaped = false; + break; + + default: + escaped = false; + break; + }; + + if (!done) + { + write_char (sgf_fd, temp); + read_char (unhandled_fd); + } + } + } + + last_output_type = temp_type; +} + + +void +setup_handicap_sgf (void) +{ + union prop_data_t temp_data; + + if (header.handicap <= 1) + { + return; + } + + current_node = tree_head; + current_node = add_child_sgf (NULL); + + if (current_node < 0) + { + current_node = tree_head; + return; + } + + start_node = current_node; + + temp_data.number = header.handicap; + add_prop_sgf (current_node, PROP_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; + } + + int handicaps_to_place = header.handicap; + + int low_coord = 0, mid_coord = 0, high_coord = 0; + + 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) +{ + union prop_data_t temp_data; + + temp_data.position = pos; + + add_prop_sgf (current_node, PROP_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,76 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007-2009 Joshua Simmons + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#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 go_to_variation_sgf (unsigned int num); + +int num_variations_sgf (void); +int mark_child_variations_sgf (void); +int add_child_variation_sgf (int *variation_number); +int add_child_sgf (int *variation_number); +int next_variation_sgf (void); + +int add_prop_sgf (int node, enum prop_type_t type, union prop_data_t data); +bool delete_prop_sgf (int node, enum prop_type_t type); +bool delete_prop_handle_sgf (int node, int prop); +int get_prop_sgf (int node, enum prop_type_t type, int *previous_prop); +int add_or_set_prop_sgf (int node, enum prop_type_t type, + union prop_data_t data); +int get_prop_pos_sgf (enum prop_type_t type, union prop_data_t data); +bool get_move_sgf (pos_t * pos, color_t * color); + + +int read_comment_sgf (char *buffer, size_t buffer_size); +int write_comment_sgf (char *string); + +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); + +bool add_mark_sgf (pos_t pos, enum prop_type_t type); + +#define NO_NODE (-1) +#define NO_PROP (-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,899 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007-2009 Joshua Simmons + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "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" +enum play_mode_t play_mode = MODE_PLAY; + +#if defined(GBN_BUTTON_NAV_MODE) + +#define NAV_MODE_BOARD 0 +#define NAV_MODE_TREE 1 + +int nav_mode = NAV_MODE_BOARD; + +#endif + + +static void global_setup (void); +static void global_cleanup (void); +static bool do_main_menu (void); +static void do_gameinfo_menu (void); +static void do_context_menu (void); +static bool do_comment_edit (void); + + +/* IMPORTANT: keep in sync with the enum prop_type_t enum in types.h */ +char *prop_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", "NZ", "GOE" }; + + + +int +create_or_open_file (const char *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 void *parameter) +{ + int btn; + int temp; + + 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 (MAX_BOARDSIZE, MAX_BOARDSIZE, 0, 0)) + { + return PLUGIN_ERROR; + } + } + } + draw_board (); + + + + + 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) + { + +#if defined(GBN_BUTTON_NAV_MODE) + case GBN_BUTTON_NAV_MODE: + case GBN_BUTTON_NAV_MODE | BUTTON_REPEAT: + if (nav_mode == NAV_MODE_TREE) + { + nav_mode = NAV_MODE_BOARD; + rb->splash (2 * HZ / 3, "board navigation mode"); + draw_board (); + } + else + { + nav_mode = NAV_MODE_TREE; + rb->splash (2 * HZ / 3, "tree navigation mode"); + draw_board (); + } + break; +#endif + +#if defined(GBN_BUTTON_ADVANCE) + case GBN_BUTTON_ADVANCE: + case GBN_BUTTON_ADVANCE | BUTTON_REPEAT: + if (has_more_nodes_sgf ()) + { + if (!redo_node_sgf ()) + { + rb->splash (2 * HZ, "redo failed"); + } + draw_board (); + } + break; +#endif + +#if defined(GBN_BUTTON_RETREAT) + case GBN_BUTTON_RETREAT: + case GBN_BUTTON_RETREAT | BUTTON_REPEAT: + if (has_prev_nodes_sgf ()) + { + if (!undo_node_sgf ()) + { + rb->splash (3 * HZ / 2, "undo failed"); + } + draw_board (); + } + break; +#endif + + case GBN_BUTTON_PLAY: + if (play_mode == MODE_PLAY || play_mode == MODE_FORCE_PLAY) + { + if (!play_move_sgf (cursor_pos, current_player)) + { + rb->splash (HZ / 3, "illegal move or error"); + } + } + else if (play_mode == MODE_ADD_BLACK) + { + if (!add_stone_sgf (cursor_pos, BLACK)) + { + rb->splash (HZ / 3, "illegal or error"); + } + } + else if (play_mode == MODE_ADD_WHITE) + { + if (!add_stone_sgf (cursor_pos, WHITE)) + { + rb->splash (HZ / 3, "illegal or error"); + } + } + else if (play_mode == MODE_REMOVE) + { + if (!add_stone_sgf (cursor_pos, EMPTY)) + { + rb->splash (HZ / 3, "illegal or error"); + } + } + else if (play_mode == MODE_MARK) + { + if (!add_mark_sgf (cursor_pos, PROP_MARK)) + { + rb->splash (HZ / 3, "couldn't mark"); + } + } + else if (play_mode == MODE_CIRCLE) + { + if (!add_mark_sgf (cursor_pos, PROP_CIRCLE)) + { + rb->splash (HZ / 3, "couldn't mark"); + } + } + else if (play_mode == MODE_SQUARE) + { + if (!add_mark_sgf (cursor_pos, PROP_SQUARE)) + { + rb->splash (HZ / 3, "couldn't mark"); + } + } + else if (play_mode == MODE_TRIANGLE) + { + if (!add_mark_sgf (cursor_pos, PROP_TRIANGLE)) + { + rb->splash (HZ / 3, "couldn't mark"); + } + } + else if (play_mode == MODE_LABEL) + { + if (!add_mark_sgf (cursor_pos, PROP_LABEL)) + { + rb->splash (HZ / 3, "couldn't label"); + } + } + else + { + rb->splash (HZ, "mode not implemented yet"); + } + + draw_board (); + break; + + case GBN_BUTTON_RIGHT: + case GBN_BUTTON_RIGHT | BUTTON_REPEAT: +#if defined(GBN_BUTTON_NAV_MODE) + if (nav_mode == NAV_MODE_TREE) + { + if (has_more_nodes_sgf ()) + { + if (!redo_node_sgf ()) + { + rb->splash (2 * HZ, "redo failed"); + } + draw_board (); + } + } + else + { +#endif + cursor_pos = WRAP (EAST (cursor_pos)); + draw_board (); +#if defined(GBN_BUTTON_NAV_MODE) + } +#endif + break; + + case GBN_BUTTON_LEFT: + case GBN_BUTTON_LEFT | BUTTON_REPEAT: +#if defined(GBN_BUTTON_NAV_MODE) + if (nav_mode == NAV_MODE_TREE) + { + if (has_prev_nodes_sgf ()) + { + if (!undo_node_sgf ()) + { + rb->splash (2 * HZ, "undo failed"); + } + draw_board (); + } + } + else + { +#endif + cursor_pos = WRAP (WEST (cursor_pos)); + draw_board (); +#if defined(GBN_BUTTON_NAV_MODE) + } +#endif + break; + + case GBN_BUTTON_DOWN: + case GBN_BUTTON_DOWN | BUTTON_REPEAT: + cursor_pos = WRAP (SOUTH (cursor_pos)); + draw_board (); + break; + + case GBN_BUTTON_UP: + case GBN_BUTTON_UP | BUTTON_REPEAT: + cursor_pos = WRAP (NORTH (cursor_pos)); + draw_board (); + break; + + case GBN_BUTTON_MENU: + if (do_main_menu ()) + { + if (game_dirty) + { + save_game (DEFAULT_SAVE); + } + + global_cleanup (); + return PLUGIN_OK; + } + + draw_board (); + break; + +#if defined(GBN_BUTTON_CONTEXT) + case GBN_BUTTON_CONTEXT: + do_context_menu (); + draw_board (); + break; +#endif + +#if defined(GBN_BUTTON_NEXT_VAR) + case GBN_BUTTON_NEXT_VAR: + case GBN_BUTTON_NEXT_VAR | BUTTON_REPEAT: + if ((temp = next_variation_sgf ()) >= 0) + { + draw_board (); + rb->splashf (2 * HZ / 3, "%d of %d", temp, + num_variations_sgf ()); + draw_board (); + } + else + { + if (num_variations_sgf () > 1) + { + rb->splashf (HZ, "error %d in next_variation_sgf", temp); + } + draw_board (); + } + break; +#endif + + 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); + } +} + + +static bool +do_main_menu (void) +{ + /* for "New" in menu */ + int new_handi = 0, new_bs = MAX_BOARDSIZE, new_komi = 15; + + char backup_save_file[SAVE_FILE_LENGTH]; + bool return_val; + + int selection = 0; + MENUITEM_STRINGLIST (menu, "Rockbox Goban", NULL, + "New", + "Save", + "Save As", "Game Info", "Context Menu", "Quit"); + + 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, MIN_BOARDSIZE, MAX_BOARDSIZE, 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" */ + do_gameinfo_menu (); + break; + + case 4: /* "Context Menu" */ + do_context_menu (); + done = true; + break; + + case MENU_ATTACHED_USB: + case 5: /* "Quit" */ + return true; + + case GO_TO_ROOT: + case GO_TO_PREVIOUS: + default: + + done = true; + break; + }; + } + + return false; +} + +static void +do_gameinfo_menu (void) +{ + int new_ruleset = 0; + + char *gameinfo_string; + int gameinfo_string_size; + + bool done = false; + int 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"); + + while (!done) + { + selection = rb->do_menu (&gameinfo_menu, &selection, NULL, false); + + switch (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_prop + (selection), &gameinfo_string, + &gameinfo_string_size)) + { + /* TODO: remove debug */ + rb->splash (5 * HZ, "BIG ERROR! how did you get here?\ + goban.c 141425"); + } + + 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, (void *) &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: + default: + done = true; + break; + }; + } +} + +static void +do_context_menu (void) +{ + int selection; + bool done = false; + int temp; + + MENUITEM_STRINGLIST (context_menu, "Context Menu", NULL, + "Play Mode (default)", + "Add Black Mode", + "Add White Mode", + "Erase Stone Mode", + "Pass", + "Next Variation", + "Force Play Mode", + "Mark Mode", + "Circle Mode", + "Square Mode", + "Triangle Mode", + "Label Mode", "Add/View/Edit Comment", "Cancel"); + + while (!done) + { + selection = rb->do_menu (&context_menu, &selection, NULL, false); + + switch (selection) + { + case 0: /* "Play Mode", */ + play_mode = MODE_PLAY; + done = true; + break; + + case 1: /* "Add Black Mode" */ + play_mode = MODE_ADD_BLACK; + done = true; + break; + + case 2: /* "Add White Mode" */ + play_mode = MODE_ADD_WHITE; + done = true; + break; + + case 3: /* "Erase Stone Mode" */ + play_mode = MODE_REMOVE; + done = true; + break; + + case 4: /* "Pass" */ + if (!play_move_sgf (PASS_POS, current_player)) + { + rb->splash (HZ, "error while passing!"); + } + done = true; + break; + + case 5: /* Next Variation */ + if ((temp = next_variation_sgf ()) >= 0) + { + draw_board (); + rb->splashf (2 * HZ / 3, "%d of %d", temp, + num_variations_sgf ()); + draw_board (); + } + else + { + if (num_variations_sgf () > 1) + { + rb->splashf (HZ, "error %d in next_variation_sgf", temp); + } + else + { + rb->splash (HZ, "no next variation"); + } + } + break; + + case 6: /* Force Play */ + play_mode = MODE_FORCE_PLAY; + done = true; + break; + + case 7: /* "Mark Mode" */ + play_mode = MODE_MARK; + done = true; + break; + + case 8: /* Circle Mode */ + play_mode = MODE_CIRCLE; + done = true; + break; + + case 9: /* Square Mode */ + play_mode = MODE_SQUARE; + done = true; + break; + + case 10: /* Triangle Mode */ + play_mode = MODE_TRIANGLE; + done = true; + break; + + case 11: /* Label Mode */ + play_mode = MODE_LABEL; + done = true; + break; + + case 12: /* Comment */ + if (!do_comment_edit ()) + { + DEBUGF ("editing comment failed\n"); + rb->splash (HZ, "read or write failed!\n"); + } + done = true; + break; + + case 13: /* "Cancel" */ + case GO_TO_ROOT: + case GO_TO_PREVIOUS: + case MENU_ATTACHED_USB: + default: + done = true; + break; + }; + } +} + + +static bool +do_comment_edit (void) +{ + char cbuffer[256]; + + rb->memset (cbuffer, 0, sizeof (cbuffer)); + + if (read_comment_sgf (cbuffer, sizeof (cbuffer)) < 0) + { + return false; + } + + if (!rb->kbd_input (cbuffer, sizeof (cbuffer))) + { + /* user didn't edit, no reason to write it back */ + return true; + } + + if (write_comment_sgf (cbuffer) < 0) + { + return false; + } + + return true; +} + +enum prop_type_t +menu_selection_to_prop (int selection) +{ + switch (selection) + { + case 1: /* "Overtime", */ + return PROP_OVERTIME; + case 2: /* "Result", */ + return PROP_RESULT; + case 6: /* "Black Player", */ + return PROP_BLACK_NAME; + case 7: /* "Black Rank", */ + return PROP_BLACK_RANK; + case 8: /* "Black Team", */ + return PROP_BLACK_TEAM; + case 9: /* "White Player", */ + return PROP_WHITE_NAME; + case 10: /* "White Rank", */ + return PROP_WHITE_RANK; + case 11: /* "white Team", */ + return PROP_WHITE_TEAM; + case 12: /* "Date", */ + return PROP_DATE; + case 13: /* "Event", */ + return PROP_EVENT; + case 14: /* "Place", */ + return PROP_PLACE; + case 15: /* "Round", */ + return PROP_ROUND; + default: + DEBUGF ("tried to get prop from invalid menu selection!!!\n"); + return PROP_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,866 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007-2009 Joshua Simmons + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "goban.h" +#include "board.h" +#include "display.h" +#include "file_cache.h" +#include "types.h" +#include "game.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; + +/* there can't be any changes off of the board, so no need to add the borders */ +uint8_t board_changes[MAX_BOARDSIZE * MAX_BOARDSIZE]; + +pos_t ko_pos = INVALID_POS; + +#define UNDO_CACHE_FILE (PLUGIN_GAMES_DIR "/gbn_undo.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 2048 +/* TODO: try to figure out what a good number for this define is, the current + is just a guess */ +struct file_cache_t undo_cache; +uint8_t undo_cache_buffer[UNDO_BUFFER_SIZE]; + + +/* 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 bool do_opponent_capture (pos_t pos, color_t color); +static void 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 bool undo_node_undo_helper(int phase); */ +static void init_change_record (pos_t pos, uint8_t * change_record); +static bool null_change_record (uint8_t * change_record); + +/* these aren't "board marks" in the marks on the SGF 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; + } + } + + rb->memset (board_changes, 0, sizeof (board_changes)); + + 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 (pos < POS (0, 0) || + pos > POS (board_width - 1, board_height - 1) || + get_point_board (pos) == INVALID) + { + return false; + } + else + { + return true; + } +} + +bool +add_stone_board (pos_t pos, color_t color, bool * is_orig) +{ + bool ret_val = do_add_stone (pos, color, false); + + if (ret_val && is_orig) + { + *is_orig = null_change_record (&board_changes[I (pos) + + J (pos) * board_width]); + + DEBUGF ("is_orig: %d\nchange_rec: %x\n", *is_orig, + board_changes[I (pos) + J (pos) * board_width]); + } + + return ret_val; +} + +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))) + { + return false; + } + else + { + add_stone_undo (pos, color); + set_point (pos, color); + + return true; + } +} + + +/* used in recursive liberty counting */ +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; + return; + } + + 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, bool capture, bool * is_orig) +{ + if (!on_board (pos) || + (get_point_board (pos) != WHITE && get_point_board (pos) != BLACK)) + { + return false; + } + + remove_stone_undo (pos, capture); + set_point (pos, EMPTY); + + if (is_orig) + { + *is_orig = null_change_record (&board_changes[I (pos) + + J (pos) * board_width]); + } + + 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, true, NULL); + 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 bool +do_opponent_capture (pos_t pos, color_t color) +{ + if (get_point_board (pos) == OTHER (color) && get_liberties (pos) == 0) + { + capture_group (pos); + return true; + } + else + { + return false; + } +} + +static void +do_captures (pos_t pos, color_t color) +{ + /* are any of the neighbors enemy pieces that are now captured? */ + bool cap_n = do_opponent_capture (NORTH (pos), color); + bool cap_s = do_opponent_capture (SOUTH (pos), color); + bool cap_e = do_opponent_capture (EAST (pos), color); + bool cap_w = do_opponent_capture (WEST (pos), color); + + /* if nothing was captured above, then see if the move is suicide */ + if (!(cap_n || cap_s || cap_e || cap_w)) + { + do_opponent_capture (pos, OTHER (color)); + } +} + + +bool +legal_move_board (pos_t pos, color_t color, bool allow_suicide) +{ + /* you can always pass */ + if (pos == PASS_POS) + { + return true; + } + + if (!on_board (pos) || (color != BLACK && color != WHITE)) + { + return false; + } + + /* TODO: is this player testing necessary? and correct? */ + if (pos == ko_pos && color == current_player) + { + return false; + } + + if (get_point_board (pos) != EMPTY) + { + return false; + } + + /* don't need to save the current state, because it's always empty since + we tested for that above */ + set_point (pos, color); + + /* if we have liberties, it can't be illegal */ + if (get_liberties (pos) > 0 || + /* if we can capture something, it can't be illegal */ + (get_point_board (NORTH (pos)) == OTHER (color) && + !get_liberties (NORTH (pos))) || + (get_point_board (SOUTH (pos)) == OTHER (color) && + !get_liberties (SOUTH (pos))) || + (get_point_board (EAST (pos)) == OTHER (color) && + !get_liberties (EAST (pos))) || + (get_point_board (WEST (pos)) == OTHER (color) && + !get_liberties (WEST (pos))) || + /* if we're allowed to suicide, only multi-stone suicide is legal (no + ruleset allows single-stone suicide that I know of) */ + (allow_suicide && (get_point_board (NORTH (pos)) == color || + get_point_board (SOUTH (pos)) == color || + get_point_board (EAST (pos)) == color || + get_point_board (WEST (pos)) == color))) + { + /* undo our previous set */ + set_point (pos, EMPTY); + return true; + } + else + { + /* undo our previous set */ + set_point (pos, EMPTY); + return false; + } +} + + + + +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) && pos != PASS_POS) || pos == INVALID_POS) + { + return false; + } + + /* special handling of passes, just finish the move and clear the ko_pos */ + if (pos == PASS_POS) + { + ko_pos = INVALID_POS; + return true; + } + + if (get_point_board (pos) != EMPTY) + { + DEBUGF ("playing move on non-empty position\n"); + } + + if (pos == ko_pos) + { + DEBUGF ("Took ko out of turn\n"); + rb->splash (HZ, "Took ko out of turn"); + } + + /* Stone playing logic: (source, SGF specification) 1) Set the point to + the correct color (previous color/status ignored) 2) Capture + surrounding enemy stones if they are breathless (no liberties) 3) + Capture played stone if it is breathless (no liberties) + + Note: illegal moves ARE required to be allowed by the SGF spec. a + different function will test legality of moves for input by the user */ + raw_add_stone (pos, color); + do_captures (pos, color); + + /* 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; + } + + /* open or create the file for each cache, and then set 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) + { + rb->splashf (2 * HZ, "1: %d", fd); + return false; + } + + + if (!setup_cache (&undo_cache, fd, undo_cache_buffer, UNDO_BUFFER_SIZE)) + { + rb->close (fd); + rb->splash (2 * HZ, "2"); + + return false; + } + + truncate_cache (&undo_cache); + + if (is_error (&undo_cache)) + { + rb->splash (2 * HZ, "3"); + close_cache (&undo_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); + } + + undo_is_setup = false; +} + +static void +init_change_record (pos_t pos, uint8_t * change_record) +{ + if (FLAG_VALID & *change_record) + { + return; + } + + *change_record = 0; + + *change_record |= FLAG_VALID; + + if (get_point_board (pos) == BLACK) + { + *change_record |= FLAG_ORIG_BLACK; + } + + if (get_point_board (pos) == EMPTY) + { + *change_record |= FLAG_ORIG_EMPTY; + } +} + +static bool +null_change_record (uint8_t * change_record) +{ + return ((!!(*change_record & FLAG_BLACK) == + !!(*change_record & FLAG_ORIG_BLACK)) && + (!!(*change_record & FLAG_EMPTY) == + !!(*change_record & FLAG_ORIG_EMPTY))); +} + +void +add_stone_undo (pos_t pos, color_t color) +{ + uint8_t *change_record; + + change_record = &board_changes[I (pos) + J (pos) * board_width]; + + if ((color != BLACK && color != WHITE) || get_point_board (pos) == color) + { + return; + } + + init_change_record (pos, change_record); + + if (color == BLACK) + { + *change_record |= FLAG_BLACK; + } + else + { + *change_record &= ~FLAG_BLACK; + } + + *change_record &= ~FLAG_EMPTY; + + if (null_change_record (change_record) && !(*change_record & FLAG_CAPTURE)) + { + *change_record = 0; + } +} + +void +remove_stone_undo (pos_t pos, bool capture) +{ + uint8_t *change_record; + + change_record = &board_changes[I (pos) + J (pos) * board_width]; + + if (get_point_board (pos) != BLACK && get_point_board (pos) != WHITE) + { + return; + } + + init_change_record (pos, change_record); + + if (capture) + { + *change_record |= FLAG_CAPTURE; + + if (get_point_board (pos) == BLACK) + { + *change_record |= FLAG_CAPTURE_BLACK; + } + else + { + *change_record &= ~FLAG_CAPTURE_BLACK; + } + } + + *change_record |= FLAG_EMPTY; + *change_record &= ~FLAG_BLACK; + + if (null_change_record (change_record) && !(*change_record & FLAG_CAPTURE)) + { + *change_record = 0; + } +} + + +void +node_finished_undo (void) +{ + pos_t temp_pos; + uint16_t bytes_written = 0; + + int x, y; + for (x = 0; x < board_width; ++x) + { + for (y = 0; y < board_width; ++y) + { + if (board_changes[x + y * board_width] & FLAG_VALID) + { + temp_pos = POS (x, y); + write_cache (&undo_cache, &temp_pos, sizeof (temp_pos)); + write_cache (&undo_cache, + &board_changes[x + y * board_width], + sizeof (board_changes[0])); + bytes_written += sizeof (temp_pos) + sizeof (board_changes[0]); + } + } + } + + if (ko_pos != INVALID_POS) + { + write_cache (&undo_cache, &ko_pos, sizeof (ko_pos)); + bytes_written += sizeof (ko_pos); + } + + /* write the size */ + write_cache (&undo_cache, &bytes_written, sizeof (bytes_written)); + + truncate_cache (&undo_cache); + + rb->memset (board_changes, 0, sizeof (board_changes)); +} + + +bool +load_changes_undo (void) +{ + uint16_t bytes_to_read = 0; + uint16_t bytes_read; + pos_t temp_pos; + bool ko_set = false; + + if (is_error (&undo_cache)) + { + return false; + } + + seek_cache (&undo_cache, -1 * (signed int) sizeof (bytes_to_read)); + read_cache (&undo_cache, &bytes_to_read, sizeof (bytes_to_read)); + + /* for seeking later */ + bytes_read = bytes_to_read; + + seek_cache (&undo_cache, -1 * (sizeof (bytes_to_read) + bytes_to_read)); + + while (bytes_to_read) + { + if (bytes_to_read >= (sizeof (temp_pos) + sizeof (board_changes[0]))) + { + read_cache (&undo_cache, &temp_pos, sizeof (temp_pos)); + read_cache (&undo_cache, &board_changes[I (temp_pos) + + J (temp_pos) * + board_width], + sizeof (board_changes[0])); + bytes_to_read -= sizeof (temp_pos) + sizeof (board_changes[0]); + } + else if (bytes_to_read == sizeof (ko_pos)) + { + read_cache (&undo_cache, &ko_pos, sizeof (ko_pos)); + bytes_to_read -= sizeof (ko_pos); + ko_set = true; + } + else + { + DEBUGF ("undo_cache is unparseable!!\n"); + rb->splash (5 * HZ, + "There was a problem parsing the undo record! Undo \ + will not be available any longer"); + close_cache (&undo_cache); + return false; + } + } + + if (!ko_set) + { + ko_pos = INVALID_POS; + } + + seek_cache (&undo_cache, -1 * bytes_read); + truncate_cache (&undo_cache); + + return true; +} + +bool +undo_node_undo (void) +{ + int x, y; + uint8_t changes_record; + + if (!load_changes_undo ()) + { + DEBUGF ("couldn't load changes!\n"); + return false; + } + + + for (x = 0; x < board_width; ++x) + { + for (y = 0; y < board_height; ++y) + { + + changes_record = board_changes[x + y * board_width]; + + if (!(FLAG_VALID & changes_record)) + { + continue; + } + + if (FLAG_CAPTURE & changes_record) + { + if (FLAG_CAPTURE_BLACK & changes_record) + { + --white_captures; + } + else + { + --black_captures; + } + } + + if (FLAG_ORIG_EMPTY & changes_record) + { + set_point (POS (x, y), EMPTY); + } + else + { + if (FLAG_ORIG_BLACK & changes_record) + { + set_point (POS (x, y), BLACK); + } + else + { + set_point (POS (x, y), WHITE); + } + } + } + } + + rb->memset (board_changes, 0, sizeof (board_changes)); + + if (!load_changes_undo ()) + { + DEBUGF ("couldn't load changes! (second load)\n"); + return false; + } + + return true; +} Index: apps/plugins/goban/goban.h =================================================================== --- apps/plugins/goban/goban.h (revision 0) +++ apps/plugins/goban/goban.h (revision 0) @@ -0,0 +1,265 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007-2009 Joshua Simmons + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#ifndef GOBAN_MAIN_H +#define GOBAN_MAIN_H + +#include "types.h" + +int create_or_open_file (const char *filename); +enum prop_type_t menu_selection_to_prop (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 + + +#ifdef HAVE_TOUCHSCREEN +#define GBN_BUTTON_UP BUTTON_TOPMIDDLE +#define GBN_BUTTON_DOWN BUTTON_BOTTOMMIDDLE +#define GBN_BUTTON_LEFT BUTTON_MIDLEFT +#define GBN_BUTTON_RIGHT BUTTON_MIDRIGHT +#define GBN_BUTTON_RETREAT BUTTON_BOTTOMLEFT +#define GBN_BUTTON_ADVANCE BUTTON_BOTTOMRIGHT +#define GBN_BUTTON_MENU BUTTON_TOPLEFT +#define GBN_BUTTON_PLAY BUTTON_CENTER | BUTTON_REL +#define GBN_BUTTON_CONTEXT BUTTON_CENTER | BUTTON_REPEAT +#define GBN_BUTTON_NEXT_VAR BUTTON_TOPRIGHT + +#elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) +#define GBN_BUTTON_UP BUTTON_MENU +#define GBN_BUTTON_DOWN BUTTON_PLAY +#define GBN_BUTTON_LEFT BUTTON_LEFT +#define GBN_BUTTON_RIGHT BUTTON_RIGHT +#define GBN_BUTTON_RETREAT BUTTON_SCROLL_BACK +#define GBN_BUTTON_ADVANCE BUTTON_SCROLL_FWD +#define GBN_BUTTON_PLAY BUTTON_SELECT | BUTTON_REL +#define GBN_BUTTON_MENU BUTTON_SELECT | BUTTON_REPEAT +/* no context */ +/* no next var */ + +#elif (CONFIG_KEYPAD == SANSA_E200_PAD) \ + || (CONFIG_KEYPAD == SANSA_FUZE_PAD) +#define GBN_BUTTON_UP BUTTON_UP +#define GBN_BUTTON_DOWN BUTTON_DOWN +#define GBN_BUTTON_LEFT BUTTON_LEFT +#define GBN_BUTTON_RIGHT BUTTON_RIGHT +#define GBN_BUTTON_RETREAT BUTTON_SCROLL_BACK +#define GBN_BUTTON_ADVANCE BUTTON_SCROLL_FWD +#define GBN_BUTTON_MENU BUTTON_POWER +#define GBN_BUTTON_PLAY BUTTON_SELECT | BUTTON_REL +#define GBN_BUTTON_CONTEXT BUTTON_SELECT | BUTTON_REPEAT +#define GBN_BUTTON_NEXT_VAR BUTTON_REC + +#elif (CONFIG_KEYPAD == SANSA_C200_PAD) +#define GBN_BUTTON_UP BUTTON_UP +#define GBN_BUTTON_DOWN BUTTON_DOWN +#define GBN_BUTTON_LEFT BUTTON_LEFT +#define GBN_BUTTON_RIGHT BUTTON_RIGHT +#define GBN_BUTTON_RETREAT BUTTON_VOL_DOWN +#define GBN_BUTTON_ADVANCE BUTTON_VOL_UP +#define GBN_BUTTON_MENU BUTTON_POWER +#define GBN_BUTTON_PLAY BUTTON_SELECT | BUTTON_REL +#define GBN_BUTTON_CONTEXT BUTTON_SELECT | BUTTON_REPEAT +/* no next var */ + +#elif (CONFIG_KEYPAD == GIGABEAT_PAD) \ + || (CONFIG_KEYPAD == GIGABEAT_S_PAD) +#define GBN_BUTTON_UP BUTTON_UP +#define GBN_BUTTON_DOWN BUTTON_DOWN +#define GBN_BUTTON_LEFT BUTTON_LEFT +#define GBN_BUTTON_RIGHT BUTTON_RIGHT +#define GBN_BUTTON_RETREAT BUTTON_VOL_DOWN +#define GBN_BUTTON_ADVANCE BUTTON_VOL_UP +#define GBN_BUTTON_MENU BUTTON_MENU +#define GBN_BUTTON_PLAY BUTTON_SELECT | BUTTON_REL +#define GBN_BUTTON_CONTEXT BUTTON_SELECT | BUTTON_REPEAT +/* no next var */ + +#elif (CONFIG_KEYPAD == IRIVER_H10_PAD) +#define GBN_BUTTON_UP BUTTON_SCROLL_BACK +#define GBN_BUTTON_DOWN BUTTON_SCROLL_FWD +#define GBN_BUTTON_RIGHT BUTTON_RIGHT +#define GBN_BUTTON_LEFT BUTTON_LEFT +#define GBN_BUTTON_RETREAT BUTTON_FF +#define GBN_BUTTON_ADVANCE BUTTON_REW +#define GBN_BUTTON_MENU BUTTON_POWER +#define GBN_BUTTON_PLAY BUTTON_PLAY | BUTTON_REL +#define GBN_BUTTON_CONTEXT BUTTON_PLAY | BUTTON_REPEAT +/* no next var */ + +#elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \ + (CONFIG_KEYPAD == IRIVER_H300_PAD) +#define GBN_BUTTON_UP BUTTON_UP +#define GBN_BUTTON_DOWN BUTTON_DOWN +#define GBN_BUTTON_LEFT BUTTON_LEFT +#define GBN_BUTTON_RIGHT BUTTON_RIGHT +#define GBN_BUTTON_RETREAT BUTTON_OFF +#define GBN_BUTTON_ADVANCE BUTTON_ON +#define GBN_BUTTON_MENU BUTTON_MODE +#define GBN_BUTTON_PLAY BUTTON_SELECT | BUTTON_REL +#define GBN_BUTTON_CONTEXT BUTTON_SELECT | BUTTON_REPEAT +/* no next var */ + +#elif (CONFIG_KEYPAD == MROBE100_PAD) +/* TODO: these buttons are particularly shady seeming find out more info */ +#define GBN_BUTTON_UP BUTTON_UP +#define GBN_BUTTON_DOWN BUTTON_DOWN +#define GBN_BUTTON_LEFT BUTTON_LEFT +#define GBN_BUTTON_RIGHT BUTTON_RIGHT +#define GBN_BUTTON_RETREAT BUTTON_MENU +#define GBN_BUTTON_ADVANCE BUTTON_PLAY +#define GBN_BUTTON_MENU BUTTON_POWER +#define GBN_BUTTON_PLAY BUTTON_SELECT | BUTTON_REL +#define GBN_BUTTON_CONTEXT BUTTON_SELECT | BUTTON_REPEAT +/* no next var */ + +#elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD) +#define GBN_BUTTON_UP BUTTON_UP +#define GBN_BUTTON_DOWN BUTTON_DOWN +#define GBN_BUTTON_LEFT BUTTON_LEFT +#define GBN_BUTTON_RIGHT BUTTON_RIGHT +#define GBN_BUTTON_RETREAT BUTTON_PLAY +#define GBN_BUTTON_ADVANCE BUTTON_REC +#define GBN_BUTTON_MENU BUTTON_POWER +#define GBN_BUTTON_PLAY BUTTON_SELECT | BUTTON_REL +#define GBN_BUTTON_CONTEXT BUTTON_SELECT | BUTTON_REPEAT +/* no next var */ + +#elif (CONFIG_KEYPAD == COWOND2_PAD) +#elif CONFIG_KEYPAD == IAUDIO_M3_PAD +/* TODO: these are basically complete guesses */ +#define GBN_BUTTON_UP BUTTON_RC_VOL_UP +#define GBN_BUTTON_DOWN BUTTON_RC_VOL_DOWN +#define GBN_BUTTON_LEFT BUTTON_RC_REW +#define GBN_BUTTON_RIGHT BUTTON_RC_FF +#define GBN_BUTTON_RETREAT BUTTON_VOL_DOWN +#define GBN_BUTTON_ADVANCE BUTTON_VOL_UP +#define GBN_BUTTON_MENU BUTTON_MODE +#define GBN_BUTTON_PLAY BUTTON_PLAY | BUTTON_REL +#define GBN_BUTTON_CONTEXT BUTTON_PLAY | BUTTON_REPEAT +/* no next var */ + +#elif (CONFIG_KEYPAD == RECORDER_PAD) +#define GBN_BUTTON_UP BUTTON_UP +#define GBN_BUTTON_DOWN BUTTON_DOWN +#define GBN_BUTTON_LEFT BUTTON_LEFT +#define GBN_BUTTON_RIGHT BUTTON_RIGHT +#define GBN_BUTTON_RETREAT BUTTON_F1 +#define GBN_BUTTON_ADVANCE BUTTON_F2 +#define GBN_BUTTON_MENU BUTTON_PLAY | BUTTON_REPEAT +#define GBN_BUTTON_PLAY BUTTON_PLAY | BUTTON_REL +/* No context */ +/* no next var */ + +#elif (CONFIG_KEYPAD == ONDIO_PAD) +#define GBN_BUTTON_UP BUTTON_UP +#define GBN_BUTTON_DOWN BUTTON_DOWN +#define GBN_BUTTON_LEFT BUTTON_LEFT +#define GBN_BUTTON_RIGHT BUTTON_RIGHT +#define GBN_BUTTON_MENU BUTTON_MENU | BUTTON_REPEAT +#define GBN_BUTTON_PLAY BUTTON_MENU | BUTTON_REL +#define GBN_BUTTON_NAV_MODE BUTTON_OFF +/* No context No advance/retreat */ +/* no next var */ + +#else +#error Unsupported keypad +#endif + + + +#define LCD_MIN_DIMENSION (LCD_HEIGHT > LCD_WIDTH ? LCD_WIDTH : LCD_HEIGHT) + + +/* determine if we have a wide screen or a tall screen this is used to place + the board and footer in acceptable locations also, set the LCD_BOARD_SIZE, + making sure that we have at least 16 pixels for the "footer" on either the + bottom or the right */ + +#if (LCD_WIDTH > LCD_HEIGHT) + +#define GBN_WIDE_SCREEN +#if (LCD_WIDTH > (16 + LCD_HEIGHT)) +#define LCD_BOARD_SIZE LCD_MIN_DIMENSION +#else +#define LCD_BOARD_SIZE (LCD_HEIGHT - (16 - (LCD_WIDTH - LCD_HEIGHT))) +#endif + +#else + +#define GBN_TALL_SCREEN + +#if (LCD_HEIGHT > (16 + LCD_WIDTH)) +#define LCD_BOARD_SIZE LCD_MIN_DIMENSION +#else +#define LCD_BOARD_SIZE (LCD_WIDTH - (16 - (LCD_HEIGHT - LCD_WIDTH))) +#endif + +#endif // LCD_WIDTH > LCD_HEIGHT + + +#define DEFAULT_SAVE ("/sgf/gbn_def.sgf") +#define SAVE_FILE_LENGTH 256 + +#if (LCD_DEPTH == 1) +#define OUTLINE_STONES +#endif + +#define min(x, y) (x < y ? x : y) +#define max(x, y) (x > y ? x : y) + +/* this is a hack to get fixed point values to work correctly for negative + values, including -0.5. there is almost certainly a cleaner solution, but + this works */ +#define fix_fixed(x) (((x < 0) && (x & 1)) ? x + 2 : x) + + +int snprint_fixed (char *buffer, int buffer_size, fixed_point_t fixed); + +extern enum play_mode_t play_mode; + +#endif Index: apps/plugins/goban/board.h =================================================================== --- apps/plugins/goban/board.h (revision 0) +++ apps/plugins/goban/board.h (revision 0) @@ -0,0 +1,93 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007-2009 Joshua Simmons + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#ifndef GOBAN_BOARD_H +#define GOBAN_BOARD_H + +#include "types.h" +#include "goban.h" /* for LCD_BOARD_SIZE */ + +#define WHITE 1 +#define BLACK 2 +#define EMPTY 0 +#define NONE 0 +#define INVALID 4 + +#define OTHER(color) (color == BLACK ? WHITE : BLACK) + + + /* intersection sizes smaller than 3 make it impossible + to draw stones (they'd just be one point, which + doesn't make any sense */ +#define MAX_BOARDSIZE min(19, LCD_BOARD_SIZE / 3) +#define MIN_BOARDSIZE 2 + +#define FLAG_VALID ((uint8_t) (1 << 7)) +#define FLAG_CAPTURE ((uint8_t) (1 << 6)) +#define FLAG_CAPTURE_BLACK ((uint8_t) (1 << 5)) +#define FLAG_ORIG_EMPTY ((uint8_t) (1 << 4)) +#define FLAG_ORIG_BLACK ((uint8_t) (1 << 3)) +#define FLAG_EMPTY ((uint8_t) (1 << 1)) +#define FLAG_BLACK ((uint8_t) (1)) + + +#define INVALID_POS ((pos_t) -5003) +#define PASS_POS ((pos_t) -5002) + +#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 * is_orig); +bool remove_stone_board (pos_t pos, bool capture, bool * is_orig); +bool legal_move_board (pos_t pos, color_t color, bool allow_suicide); +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, bool capture); +void add_stone_undo (pos_t pos, color_t color); +void node_finished_undo (void); +bool undo_node_undo (void); +bool load_changes_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 struct file_cache_t undo_cache; +#endif Index: apps/plugins/SUBDIRS =================================================================== --- apps/plugins/SUBDIRS (revision 19806) +++ apps/plugins/SUBDIRS (working copy) @@ -20,6 +20,7 @@ jpeg sudoku reversi +goban #ifndef OLYMPUS_MROBE_500 zxbox #endif