Index: apps/plugins/CATEGORIES =================================================================== --- apps/plugins/CATEGORIES (revision 19908) +++ 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 19908) +++ apps/plugins/viewers.config (working copy) @@ -19,6 +19,7 @@ rsp,viewers/searchengine,8 sok,games/sokoban,1 pgn,games/chessbox,1 +sgf,games/goban,1 ss,games/sudoku,1 wav,viewers/wav2wv,- wav,viewers/mp3_encoder,- Index: apps/plugins/goban/SOURCES =================================================================== --- apps/plugins/goban/SOURCES (revision 0) +++ apps/plugins/goban/SOURCES (revision 0) @@ -0,0 +1,6 @@ +goban.c +board.c +display.c +game.c +sgf.c +util.c Index: apps/plugins/goban/display.c =================================================================== --- apps/plugins/goban/display.c (revision 0) +++ apps/plugins/goban/display.c (revision 0) @@ -0,0 +1,1323 @@ +/*************************************************************************** + * __________ __ ___. + * 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" + +int intersection_size = 0; + +#define LINE_OFFSET (intersection_size / 2) + +/* pixel offsets for the board on the LCD */ +int board_x = 0; +int board_y = 0; +int board_pixel_width = 0; +int board_pixel_height = 0; + +/* current cursor position in board coordinates (intersections, not pixels) */ +pos_t cursor_pos = POS (0, 0); + +/* we way need to "move" our notion of which intersections to draw + * when the cursor moves, since we can be zoomed in (and often will be) + * + * this is used to signal when we need to do that + */ +pos_t last_cursor_pos = INVALID_POS; +int last_int_size = -1; + +int min_x_int = 0; +int min_y_int = 0; +int num_x_ints = 0; +int num_y_ints = 0; + +int extend_t = 0, extend_b = 0, extend_l = 0, extend_r = 0; + +bool draw_variations = true; +bool has_comment = false; + +unsigned char display_marks[MAX_BOARD_SIZE * MAX_BOARD_SIZE]; + + +/* 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); + +static int unzoomed_int_size(void); +static void cursor_updated(void); + +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; + } + + if ((mark_char == 'b' || mark_char == 'w') && + display_marks[I (pos) + J (pos) * board_width] != ' ') + { + /* don't overwrite real board marks with last-move or + * variation marks */ + 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 = MIN_X; x < MAX_X; ++x) + { + for (y = MIN_Y; y < MAX_Y; ++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); + } + 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 discriminant, 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) +{ +#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 (cursor_pos != last_cursor_pos || + intersection_size != last_int_size) + { + cursor_updated(); + } + +#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 (pixel_x(POS(MIN_X, MIN_Y)), + pixel_y(POS(MIN_X, MIN_Y)), + (MAX_X - MIN_X) * intersection_size, + (MAX_Y - MIN_Y) * intersection_size); + +#if LCD_DEPTH > 1 + rb->lcd_set_foreground (LINE_COLOR); +#else + rb->lcd_set_drawmode (DRMODE_SOLID); +#endif + + int i; + for (i = MIN_Y; i < MAX_Y; ++i) + { + rb->lcd_hline (pixel_x(POS(MIN_X, i)) + LINE_OFFSET + extend_l, + pixel_x(POS(MAX_X - 1, i)) + LINE_OFFSET + extend_r, + pixel_y(POS(MIN_X, i)) + LINE_OFFSET); + } + + for (i = MIN_X; i < MAX_X; ++i) + { + rb->lcd_vline (pixel_x(POS(i, MIN_Y)) + LINE_OFFSET, + pixel_y(POS(i, MIN_Y)) + LINE_OFFSET + extend_t, + pixel_y(POS(i, MAX_Y - 1)) + LINE_OFFSET + extend_b); + } + + 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; + } +} + +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; + int vert_x, vert_y; + + (void) vert_x; + (void) vert_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); + + + rb->lcd_getstringsize (captures_buffer, &size_x, &size_y); +#if defined(GBN_TALL_SCREEN) + rb->lcd_putsxy (size_y + 2, LCD_HEIGHT - size_y, captures_buffer); +#else + vert_string_size (captures_buffer, &vert_x, &vert_y); + if (board_pixel_width + size_x <= LCD_WIDTH) + { + rb->lcd_putsxy (LCD_WIDTH - size_x - 1, + vert_x + 2, captures_buffer); + } + else + { + putsxy_vertical (LCD_WIDTH - vert_x - 1, vert_x + 2, vert_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 - vert_x / 2, + (vert_x / 2), (vert_x - 1) / 2, true); + +#if LCD_DEPTH == 1 + rb->lcd_set_drawmode (DRMODE_SOLID + DRMODE_INVERSEVID); + draw_circle (LCD_WIDTH - 1 - vert_x / 2, + (vert_x / 2), (vert_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); + + rb->lcd_getstringsize (captures_buffer, &size_x, &size_y); +#if defined(GBN_TALL_SCREEN) + 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, &vert_x, &vert_y); + if (board_pixel_width + size_x <= LCD_WIDTH) + { + rb->lcd_putsxy (LCD_WIDTH - size_x - 1, + LCD_HEIGHT - vert_x - size_y - 3, + captures_buffer); + } + else + { + putsxy_vertical (LCD_WIDTH - vert_x - 1, + LCD_HEIGHT - vert_x - 3 - vert_y, + vert_x, captures_buffer); + } + + draw_circle (LCD_WIDTH - 1 - vert_x / 2, + LCD_HEIGHT - 1 - vert_x / 2, (vert_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); + + rb->lcd_getstringsize (captures_buffer, &size_x, &size_y); +#if defined(GBN_TALL_SCREEN) + rb->lcd_putsxy ((LCD_WIDTH - size_x) / 2, + LCD_HEIGHT - size_y, captures_buffer); +#else + if (board_pixel_width + size_x <= LCD_WIDTH) + { + rb->lcd_putsxy (LCD_WIDTH - size_x - 1, + (LCD_HEIGHT - size_y) / 2, + captures_buffer); + } + else + { + vert_string_size (captures_buffer, &vert_x, &vert_y); + putsxy_vertical (LCD_WIDTH - vert_x - 1, + (LCD_HEIGHT - vert_y) / 2, vert_x, captures_buffer); + } +#endif + + + rb->lcd_set_drawmode (DRMODE_SOLID); +} + + + + + + + +static int +pixel_x (pos_t pos) +{ + return board_x + (I (pos) - min_x_int) * intersection_size; +} + +static int +pixel_y (pos_t pos) +{ + return board_y + (J (pos) - min_y_int) * intersection_size; +} + + + +void move_display(pos_t pos) +{ + if (!on_board(pos)) + { + return; + } + + while (I(pos) >= REAL_MAX_X) + { + cursor_pos = EAST(cursor_pos); + cursor_updated(); + } + while (I(pos) < REAL_MIN_X) + { + cursor_pos = WEST(cursor_pos); + cursor_updated(); + } + + while (J(pos) >= REAL_MAX_Y) + { + cursor_pos = SOUTH(cursor_pos); + cursor_updated(); + } + while (J(pos) < REAL_MIN_Y) + { + cursor_pos = NORTH(cursor_pos); + cursor_updated(); + } +} + + +static void cursor_updated(void) +{ + + if (intersection_size != last_int_size || + I(cursor_pos) <= REAL_MIN_X + 1|| + I(cursor_pos) > REAL_MAX_X - 2 || + J(cursor_pos) <= REAL_MIN_Y + 1 || + J(cursor_pos) > REAL_MAX_Y - 2) + { + min_x_int = min(I(cursor_pos) - (num_x_ints / 2), + board_width - num_x_ints); + + min_y_int = min(J(cursor_pos) - (num_y_ints / 2), + board_height - num_y_ints); + + if (min_x_int < 0) + { + min_x_int = 0; + } + + if (min_y_int < 0) + { + min_y_int = 0; + } + } + + /* these are used in line drawing to extend the lines + * if there is more board in that direction + */ + if (MIN_X) + { + extend_l = -1 * LINE_OFFSET; + } + else + { + extend_l = 0; + } + + if (MIN_Y) + { + extend_t = -1 * LINE_OFFSET; + } + else + { + extend_t = 0; + } + + if (MAX_X != board_width) + { + extend_r = LINE_OFFSET; + } + else + { + extend_r = 0; + } + + if (MAX_Y != board_height) + { + extend_b = LINE_OFFSET; + } + else + { + extend_b = 0; + } + + last_cursor_pos = cursor_pos; + last_int_size = intersection_size; +} + +static int unzoomed_int_size(void) +{ + int int_size = min((LCD_BOARD_WIDTH / board_width), + (LCD_BOARD_HEIGHT / board_height)); + + if (!(int_size & 1)) + { + --int_size; + } + + if (int_size < 0) + { + int_size = 1; + } + + return int_size; +} + +unsigned int current_zoom_display(void) +{ + return (intersection_size - unzoomed_int_size()) / 2 + 1; +} + +unsigned int max_zoom_display(void) +{ + return (MAX_INT_SIZE - unzoomed_int_size()) / 2 + 1; +} + +unsigned int min_zoom_display(void) +{ + return max(1, (MIN_INT_SIZE - unzoomed_int_size()) / 2 + 1); +} + +void set_zoom_display(unsigned int zoom_level) +{ + int unzoomed = unzoomed_int_size(); + + if (zoom_level == 0) + { + /* default zoom, we get to set it however we want + */ + intersection_size = max(unzoomed, MIN_DEFAULT_INT_SIZE); + } + else + { + intersection_size = max(unzoomed, MIN_INT_SIZE) + 2 * (zoom_level - 1); + } + + if (intersection_size > MAX_INT_SIZE) + { + intersection_size = MAX_INT_SIZE; + } + + /* now intersection_size has been set appropriately, + * so set up all of the derived values used for display + */ + + num_x_ints = min(LCD_BOARD_WIDTH / intersection_size, + board_width); + num_y_ints = min(LCD_BOARD_HEIGHT / intersection_size, + board_height); + + board_pixel_width = num_x_ints * intersection_size; + board_pixel_height = num_y_ints * intersection_size; + +#if defined(GBN_TALL_SCREEN) + board_x = (LCD_WIDTH - board_pixel_width) / 2; + board_y = 0; +#elif defined(GBN_WIDE_SCREEN) + board_x = 0; + board_y = (LCD_HEIGHT - board_pixel_height) / 2; +#else +#error screen dimensions have not been evaluated properly +#endif +} + + +/* Call every time the board size might have changed! */ +void +setup_display (void) +{ + set_zoom_display(0); /* 0 means set to default */ + /* cursor starts on tengen (middle of the board) */ + int start_x, start_y; + if (board_width >= 7) + { + start_x = board_width - 4; + } + else + { + start_x = board_width / 2; + } + + if (board_height >= 7) + { + start_y = 3;; + } + else + { + start_y = board_height / 2; + } + cursor_pos = POS (start_x, start_y); + last_cursor_pos = INVALID_POS; + last_int_size = -1; + + 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 = MIN_X; x < MAX_X; ++x) + { + for (y = MIN_Y; y < MAX_Y; ++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 (I(pos) < MIN_X || + I(pos) >= MAX_X || + J(pos) < MIN_Y || + J(pos) >= MAX_Y) + { + 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); + } +} + +#if 0 +/* TODO: incorporate these functions (from GNUGo) */ + +/* True if the coordinate is a hoshi point. */ +int +is_hoshi_point(int m, int n) +{ + int hoshi; + int middle; + + /* No hoshi points on these boards. */ + if (board_size == 2 || board_size == 4) + return 0; + + /* In the middle of a 3x3 board. */ + if (board_size == 3) { + if (m == 1 && n == 1) + return 1; + + return 0; + } + + if (board_size == 5) { + if (m == 1 && (n == 1 || n == 3)) + return 1; + if (m == 2 && n == 2) + return 1; + if (m == 3 && (n == 1 || n == 3)) + return 1; + + return 0; + } + + /* 3-3 points are hoshi on sizes 7--11, 4-4 on larger. */ + if (board_size <= 11) + hoshi = 2; + else + hoshi = 3; + + /* Coordinate for midpoint. */ + middle = board_size/2; + + /* Normalize the coordinates by mirroring to the lower numbers. */ + if (m >= middle) + m = board_size - 1 - m; + if (n >= middle) + n = board_size - 1 - n; + + /* Is this a corner hoshi? */ + if (m == hoshi && n == hoshi) + return 1; + + /* If even sized board, only hoshi points in the corner. */ + if (board_size%2 == 0) + return 0; + + /* Less than 12 in board size only middle point. */ + if (board_size < 12) { + if (m == middle && n == middle) + return 1; + + return 0; + } + + /* Is this a midpoint hoshi? */ + if ((m == hoshi || m == middle) + && (n == hoshi || n == middle)) + return 1; + + /* No more chances. */ + return 0; +} + + + + + + +/* ================================================================ */ +/* Set up fixed placement handicap stones for black side */ +/* ================================================================ */ + + +/* Handicap stones are set up according to the following diagrams: + * + * 2 stones: 3 stones: + * + * A B C D E F G H J A B C D E F G H J + * 9 . . . . . . . . . 9 9 . . . . . . . . . 9 + * 8 . . . . . . . . . 8 8 . . . . . . . . . 8 + * 7 . . + . . . X . . 7 7 . . X . . . X . . 7 + * 6 . . . . . . . . . 6 6 . . . . . . . . . 6 + * 5 . . . . + . . . . 5 5 . . . . + . . . . 5 + * 4 . . . . . . . . . 4 4 . . . . . . . . . 4 + * 3 . . X . . . + . . 3 3 . . X . . . + . . 3 + * 2 . . . . . . . . . 2 2 . . . . . . . . . 2 + * 1 . . . . . . . . . 1 1 . . . . . . . . . 1 + * A B C D E F G H J A B C D E F G H J + * + * 4 stones: 5 stones: + * + * A B C D E F G H J A B C D E F G H J + * 9 . . . . . . . . . 9 9 . . . . . . . . . 9 + * 8 . . . . . . . . . 8 8 . . . . . . . . . 8 + * 7 . . X . . . X . . 7 7 . . X . . . X . . 7 + * 6 . . . . . . . . . 6 6 . . . . . . . . . 6 + * 5 . . . . + . . . . 5 5 . . . . X . . . . 5 + * 4 . . . . . . . . . 4 4 . . . . . . . . . 4 + * 3 . . X . . . X . . 3 3 . . X . . . X . . 3 + * 2 . . . . . . . . . 2 2 . . . . . . . . . 2 + * 1 . . . . . . . . . 1 1 . . . . . . . . . 1 + * A B C D E F G H J A B C D E F G H J + * + * 6 stones: 7 stones: + * + * A B C D E F G H J A B C D E F G H J + * 9 . . . . . . . . . 9 9 . . . . . . . . . 9 + * 8 . . . . . . . . . 8 8 . . . . . . . . . 8 + * 7 . . X . . . X . . 7 7 . . X . . . X . . 7 + * 6 . . . . . . . . . 6 6 . . . . . . . . . 6 + * 5 . . X . + . X . . 5 5 . . X . X . X . . 5 + * 4 . . . . . . . . . 4 4 . . . . . . . . . 4 + * 3 . . X . . . X . . 3 3 . . X . . . X . . 3 + * 2 . . . . . . . . . 2 2 . . . . . . . . . 2 + * 1 . . . . . . . . . 1 1 . . . . . . . . . 1 + * A B C D E F G H J A B C D E F G H J + * + * 8 stones: 9 stones: + * + * A B C D E F G H J A B C D E F G H J + * 9 . . . . . . . . . 9 9 . . . . . . . . . 9 + * 8 . . . . . . . . . 8 8 . . . . . . . . . 8 + * 7 . . X . X . X . . 7 7 . . X . X . X . . 7 + * 6 . . . . . . . . . 6 6 . . . . . . . . . 6 + * 5 . . X . + . X . . 5 5 . . X . X . X . . 5 + * 4 . . . . . . . . . 4 4 . . . . . . . . . 4 + * 3 . . X . X . X . . 3 3 . . X . X . X . . 3 + * 2 . . . . . . . . . 2 2 . . . . . . . . . 2 + * 1 . . . . . . . . . 1 1 . . . . . . . . . 1 + * A B C D E F G H J A B C D E F G H J + * + * For odd-sized boards larger than 9x9, the same pattern is followed, + * except that the edge stones are moved to the fourth line for 13x13 + * boards and larger. + * + * For even-sized boards at least 8x8, only the four first diagrams + * are used, because there is no way to place the center stones + * symmetrically. As for odd-sized boards, the edge stones are moved + * to the fourth line for boards larger than 11x11. + * + * At most four stones are placed on 7x7 boards too (this size may or + * may not be supported by the rest of the engine). No handicap stones + * are ever placed on smaller boards. + * + * Notice that this function only deals with fixed handicap placement. + * Larger handicaps can be added by free placement if the used + * interface supports it. + */ + + +/* This table contains the (coded) positions of the stones. + * 2 maps to 2 or 3, depending on board size + * 0 maps to center + * -ve numbers map to board_size - number + * + * The stones are placed in this order, *except* if there are + * 5 or 7 stones, in which case center ({0, 0}) is placed, and + * then as for 4 or 6. + */ + +static const int places[][2] = { + + {2, -2}, {-2, 2}, {2, 2}, {-2, -2}, /* first 4 are easy */ + /* for 5, {0,0} is explicitly placed */ + + {0, 2}, {0, -2}, /* for 6 these two are placed */ + /* for 7, {0,0} is explicitly placed */ + + {2, 0}, {-2, 0}, /* for 8, these two are placed */ + + {0, 0}, /* finally tengen for 9 */ +}; + + +/* + * Sets up fixed handicap placement stones, returning the number of + * placed handicap stones and also setting the global variable + * handicap to the same value. + */ + +int +place_fixed_handicap(int desired_handicap) +{ + int r; + int max_handicap; + int remaining_stones; + int three = board_size > 11 ? 3 : 2; + int mid = board_size/2; + + /* A handicap of 1 just means that B plays first, no komi. + * Black is not told where to play the first stone so no handicap + * is set. + */ + if (desired_handicap < 2) { + handicap = 0; + return 0; + } + + if ((board_size % 2 == 1) && (board_size >= 9)) + max_handicap = 9; + else if (board_size >= 7) + max_handicap = 4; + else + max_handicap = 0; + + /* It's up to the caller of this function to notice if the handicap + * was too large for fixed placement and act upon that. + */ + if (desired_handicap > max_handicap) + handicap = max_handicap; + else + handicap = desired_handicap; + + remaining_stones = handicap; + /* special cases: 5 and 7 */ + if (desired_handicap == 5 || desired_handicap == 7) { + add_stone(POS(mid, mid), BLACK); + remaining_stones--; + } + + for (r = 0; r < remaining_stones; r++) { + int i = places[r][0]; + int j = places[r][1]; + + /* Translate the encoded values to board co-ordinates. */ + if (i == 2) + i = three; /* 2 or 3 */ + else if (i == 0) + i = mid; + else if (i == -2) + i = board_size - 1 - three; + + if (j == 2) + j = three; + else if (j == 0) + j = mid; + else if (j == -2) + j = board_size - 1 - three; + + add_stone(POS(i, j), BLACK); + } + + return handicap; +} +#endif + +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/types.h =================================================================== --- apps/plugins/goban/types.h (revision 0) +++ apps/plugins/goban/types.h (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. + * + ****************************************************************************/ + +#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; + +struct stack_t +{ + char * buffer; + size_t size; + size_t sp; +}; + +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 */ + 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, + PROP_ROOT_PROPS +}; + +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) + +union prop_data_t +{ + unsigned int number; + int node; + struct + { + pos_t position; + union + { + unsigned char label_extra; + unsigned short stone_extra; + }; + }; +}; + + +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 59 +#define MAX_EVENT 100 +#define MAX_RESULT 16 +#define MAX_RANK 10 +#define MAX_TEAM 32 +#define MAX_DATE 32 +#define MAX_ROUND 8 +#define MAX_PLACE 100 +#define MAX_OVERTIME 32 +#define MAX_RULESET 32 + +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; + int 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,274 @@ +/*************************************************************************** + * __________ __ ___. + * 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" + +static void pre_game_setup(void); +static bool post_game_setup(void); + +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) + { + DEBUGF("file name too long\n"); + return false; + } + + if (!rb->file_exists (filename)) + { + DEBUGF("file doesn't exist!\n"); + return false; + } + + pre_game_setup(); + + rb->strcpy (save_file, filename); + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost (true); +#endif + + rb->splash (0, "Loading..."); + + 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; + } + +#if 0 + /* this moves to the handicap node in the tree if there was a handicap set + */ + goto_handicap_start_sgf (); +#endif + + post_game_setup_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 + + rb->splash (0, "Saving..."); + + 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; + } +} + + +static void pre_game_setup(void) +{ + rb->memset (&header, 0, sizeof (header)); + + clear_caches_sgf (); + free_tree_sgf (); + + set_size_board (MAX_BOARD_SIZE, MAX_BOARD_SIZE); + + clear_board (); + + game_dirty = true; + move_num = 0; + + play_mode = MODE_PLAY; + + rb->strcpy (save_file, DEFAULT_SAVE); + + header_marked = false; +} + + +bool +setup_game (int width, int height, int handicap, int komi) +{ + pre_game_setup(); + + if (!set_size_board (width, height)) + { + return false; + } + + clear_board(); + + /* place handicap */ + 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; + + post_game_setup_sgf(); + + 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,79 @@ +/*************************************************************************** + * __________ __ ___. + * 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" +#include "goban.h" + +void setup_display (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); + +void move_display(pos_t pos); + +void set_zoom_display(unsigned int zoom_level); +unsigned int current_zoom_display(void); +unsigned int min_zoom_display(void); +unsigned int max_zoom_display(void); + +#define MIN_DEFAULT_INT_SIZE (11) +#define MIN_INT_SIZE (3) + +#define MAX_INT_SIZE (min((LCD_BOARD_WIDTH & 1) ? LCD_BOARD_WIDTH : \ + LCD_BOARD_WIDTH - 1, \ + (LCD_BOARD_HEIGHT & 1) ? LCD_BOARD_HEIGHT : \ + LCD_BOARD_HEIGHT - 1)) + +/* NOTE: we do one "extra" intersection in each direction which goes + * off the screen, because it makes drawing a little bit prettier (the + * board doesn't end before the edge of the screen this way) + */ + + +/* right is a screen boundary if we're on a tall screen, + * bottom is a screen boundary if we're on a wide screen + */ +#if defined(GBN_TALL_SCREEN) +#define MIN_X (max(0, min_x_int - 1)) +#define MIN_Y (max(0, min_y_int)) /*always flush with top, no need for extra*/ +#define MAX_X (min(min_x_int + num_x_ints + 1, board_width)) +#define MAX_Y (min(min_y_int + num_y_ints, board_height)) +#else +#define MIN_X (max(0, min_x_int)) /*always flush with left, no need for extra*/ +#define MIN_Y (max(0, min_y_int - 1)) +#define MAX_X (min(min_x_int + num_x_ints, board_width)) +#define MAX_Y (min(min_y_int + num_y_ints + 1, board_height)) +#endif + +#define REAL_MIN_X (max(0, min_x_int)) +#define REAL_MIN_Y (max(0, min_y_int)) +#define REAL_MAX_X (min(min_x_int + num_x_ints, board_width)) +#define REAL_MAX_Y (min(min_y_int + num_y_ints, board_height)) + +#endif Index: apps/plugins/goban/util.c =================================================================== --- apps/plugins/goban/util.c (revision 0) +++ apps/plugins/goban/util.c (revision 0) @@ -0,0 +1,629 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007-2009 Joshua Simmons + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "util.h" + + + +#ifdef GBN_TEST + +#include "goban.h" +#include "types.h" +#include "board.h" +#include "game.h" +#include "sgf.h" + +#define gbn_assert(test) if (test) {DEBUGF("%d passed\n", __LINE__);} else {DEBUGF("%d FAILED!\n", __LINE__); rb->splashf(10 * HZ, "Test on line %d of util.c failed!", __LINE__); return;} + +void run_tests(void) +{ + /* setting up, saving and loading */ + gbn_assert(setup_game(MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, 15)); + gbn_assert(setup_game(MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, -30)); + gbn_assert(setup_game(MAX_BOARD_SIZE, MAX_BOARD_SIZE, 4, 1)); + gbn_assert(setup_game(MIN_BOARD_SIZE, MIN_BOARD_SIZE, 1, 1)); + + gbn_assert(setup_game(MIN_BOARD_SIZE, MAX_BOARD_SIZE, 1, 1)); + gbn_assert(setup_game(MAX_BOARD_SIZE, MIN_BOARD_SIZE, 1, 1)); + + gbn_assert(!setup_game(MAX_BOARD_SIZE + 1, MAX_BOARD_SIZE + 1, 0, 15)); + gbn_assert(!setup_game(MIN_BOARD_SIZE - 1, MIN_BOARD_SIZE - 1, 0, 15)); + gbn_assert(!setup_game(MAX_BOARD_SIZE, MAX_BOARD_SIZE, -1, 15)); + + gbn_assert(setup_game(MAX_BOARD_SIZE, MAX_BOARD_SIZE, 1, 1)); + gbn_assert(save_game(DEFAULT_SAVE_DIR "/t1.sgf")); + gbn_assert(load_game(DEFAULT_SAVE_DIR "/t1.sgf")); + gbn_assert(save_game(DEFAULT_SAVE_DIR "/t2.sgf")); + gbn_assert(load_game(DEFAULT_SAVE_DIR "/t2.sgf")); + + gbn_assert(!save_game("/DIR_DOESNT_EXIST/blah.sgf")); + gbn_assert(!load_game("/DIR_DOESNT_EXIST/blah.sgf")); + gbn_assert(!load_game(DEFAULT_SAVE_DIR "/DOESNT_EXIST.sgf")); + + + + /* test of a long game, captures, illegal moves */ + gbn_assert(load_game(DEFAULT_SAVE_DIR "/long.sgf")); + while (move_num < 520) + { + gbn_assert(num_variations_sgf() == 1); + gbn_assert(redo_node_sgf()); + } + + gbn_assert(play_move_sgf(POS(2, 0), BLACK)); + gbn_assert(play_move_sgf(POS(2, 1), WHITE)); + + gbn_assert(move_num == 522); + + gbn_assert(white_captures == 261 && black_captures == 0); + + gbn_assert(play_move_sgf(PASS_POS, BLACK)); + gbn_assert(play_move_sgf(PASS_POS, WHITE)); + + gbn_assert(move_num == 524); + + int x, y; + int b_count, w_count, e_count; + b_count = w_count = e_count = 0; + for (x = 0; x < 19; ++x) + { + for (y = 0; y < 19; ++y) + { + gbn_assert(!legal_move_board(POS(x, y), BLACK, false)); + gbn_assert(!play_move_sgf(POS(x, y), BLACK)); + switch (get_point_board(POS(x, y))) + { + case BLACK: + ++b_count; + break; + case WHITE: + ++w_count; + break; + case EMPTY: + ++e_count; + break; + default: + gbn_assert(false); + } + } + } + + gbn_assert(b_count == 0 && w_count == 261 && e_count == 19 * 19 - 261); + + gbn_assert(undo_node_sgf()); + gbn_assert(move_num == 523); + + int infinite_prevention = 0; + while (move_num > 0) + { + gbn_assert(undo_node_sgf()); + + ++infinite_prevention; + gbn_assert(infinite_prevention < 100000); + } + + gbn_assert(save_game(DEFAULT_SAVE_DIR "/long_out.sgf")); + + + /* test of basic moves, legal moves, adding and removing stones */ + gbn_assert(setup_game(MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, 0)); + gbn_assert(play_move_sgf(POS(MAX_BOARD_SIZE / 2, MAX_BOARD_SIZE / 2), BLACK)); + gbn_assert(move_num == 1 && current_player == WHITE); + gbn_assert(!legal_move_board(POS(MAX_BOARD_SIZE / 2, MAX_BOARD_SIZE / 2), + WHITE, + true)); + + int saved_node = current_node; + gbn_assert(add_stone_sgf(POS(0, 0), BLACK)); + gbn_assert(current_node != saved_node); + gbn_assert(get_point_board(POS(0, 0)) == BLACK); + gbn_assert(move_num == 1 && current_player == WHITE); + + saved_node = current_node; + gbn_assert(add_stone_sgf(POS(0, 1), WHITE)); + gbn_assert(current_node == saved_node); + gbn_assert(get_point_board(POS(0, 1)) == WHITE); + + gbn_assert(add_stone_sgf(POS(0, 0), EMPTY)); + gbn_assert(add_stone_sgf(POS(0, 1), EMPTY)); + gbn_assert(get_point_board(POS(0, 0)) == EMPTY); + gbn_assert(get_point_board(POS(0, 1)) == EMPTY); + + + + gbn_assert(load_game(DEFAULT_SAVE_DIR "/cap.sgf")); + gbn_assert(play_move_sgf(POS(0, 0), BLACK)); + gbn_assert(black_captures == 8); + gbn_assert(undo_node_sgf()); + gbn_assert(black_captures == 0); + + gbn_assert(!play_move_sgf(POS(0, 0), WHITE)); + play_mode = MODE_FORCE_PLAY; + gbn_assert(play_move_sgf(POS(0, 0), WHITE)); + play_mode = MODE_PLAY; + + gbn_assert(black_captures == 9); + gbn_assert(get_point_board(POS(0, 0)) == EMPTY); + gbn_assert(undo_node_sgf()); + gbn_assert(black_captures == 0); + + gbn_assert(play_move_sgf(POS(9, 9), BLACK)); + gbn_assert(black_captures == 44); + + for (x = 0; x < 19; ++x) + { + for (y = 0; y < 19; ++y) + { + gbn_assert(get_point_board(POS(x, y)) == BLACK || + add_stone_sgf(POS(x, y), BLACK)); + } + } + + gbn_assert(get_point_board(POS(0, 0)) == BLACK); + gbn_assert(add_stone_sgf(POS(9, 9), EMPTY)); + gbn_assert(play_move_sgf(POS(9, 9), WHITE)); + gbn_assert(white_captures == 360); + + gbn_assert(undo_node_sgf()); + gbn_assert(white_captures == 0); + + play_mode = MODE_FORCE_PLAY; + gbn_assert(play_move_sgf(POS(9, 9), BLACK)); + play_mode = MODE_PLAY; + gbn_assert(white_captures == 361); + + for (x = 0; x < 19; ++x) + { + for (y = 0; y < 19; ++y) + { + gbn_assert(get_point_board(POS(x, y)) == EMPTY); + } + } + + + /* test ko */ + gbn_assert(setup_game(MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, 15)); + + /* + * Set up the board to look like this: + * -X------ + * XO------ + * O------- + * -------- + */ + gbn_assert(add_stone_sgf(POS(0, 1), BLACK)); + gbn_assert(add_stone_sgf(POS(1, 0), BLACK)); + gbn_assert(add_stone_sgf(POS(1, 1), WHITE)); + gbn_assert(add_stone_sgf(POS(0, 2), WHITE)); + + /* take the ko and make sure black can't take back */ + gbn_assert(play_move_sgf(POS(0, 0), WHITE)); + gbn_assert(!play_move_sgf(POS(0, 1), BLACK)); + + /* make sure white can fill, even with the ko_pos set */ + gbn_assert(play_move_sgf(POS(0, 1), WHITE)); + /* and make sure undo sets the ko again */ + gbn_assert(undo_node_sgf()); + gbn_assert(!play_move_sgf(POS(0, 1), BLACK)); + + /* make sure ko threats clear the ko */ + gbn_assert(play_move_sgf(POS(2, 2), BLACK)); /* ko threat */ + gbn_assert(play_move_sgf(POS(2, 3), WHITE)); /* response */ + gbn_assert(play_move_sgf(POS(0, 1), BLACK)); /* take ko */ + + gbn_assert(undo_node_sgf()); + gbn_assert(undo_node_sgf()); + gbn_assert(undo_node_sgf()); + + /* make sure a pass is counted as a ko threat */ + gbn_assert(!play_move_sgf(POS(0, 1), BLACK)); + gbn_assert(play_move_sgf(PASS_POS, BLACK)); + gbn_assert(play_move_sgf(PASS_POS, WHITE)); + gbn_assert(play_move_sgf(POS(0, 1), BLACK)); + + /* and finally let's make sure that white can't directly retake */ + gbn_assert(!play_move_sgf(POS(0, 0), WHITE)); + + + + /* test some header information saving/loading as well as comment + * saving loading */ + char some_comment[] = "blah blah blah i am a stupid comment. here's some annoying characters: 01234567890!@#$%^&*()[[[[\\\\\\]ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + /* that bit near the end is literally this: \\\] which tests + * escaping of ]s */ + char read_buffer[256]; + + gbn_assert(setup_game(MAX_BOARD_SIZE, MAX_BOARD_SIZE, 5, -20)); + + /* this also tests that ko_pos is reset by setuping up a new game */ + gbn_assert(play_move_sgf(POS(0, 0), WHITE)); + gbn_assert(write_comment_sgf(some_comment)); + gbn_assert(play_move_sgf(POS(0, 1), BLACK)); + rb->strcpy(header.black, "Jack Black"); + rb->strcpy(header.white, "Jill White"); + + gbn_assert(save_game(DEFAULT_SAVE_DIR "/head.sgf")); + + gbn_assert(setup_game(MIN_BOARD_SIZE, MIN_BOARD_SIZE, 1, 1)); + gbn_assert(load_game(DEFAULT_SAVE_DIR "/head.sgf")); + + gbn_assert(header.komi == -20 && header.handicap == 5); + gbn_assert(board_width == MAX_BOARD_SIZE && board_height == MAX_BOARD_SIZE); + gbn_assert(rb->strcmp(header.black, "Jack Black") == 0); + gbn_assert(rb->strcmp(header.white, "Jill White") == 0); + gbn_assert(redo_node_sgf()); + gbn_assert(read_comment_sgf(read_buffer, sizeof(read_buffer))); + gbn_assert(rb->strcmp(read_buffer, some_comment) == 0); + gbn_assert(redo_node_sgf()); + gbn_assert(get_point_board(POS(0, 0)) == WHITE); + gbn_assert(get_point_board(POS(0, 1)) == BLACK); + + + + /* test saving and loading a file with unhandled SGF properties. + * + * this test requires that the user diff unhnd.sgf with unhnd_out.sgf + * (any substantial difference is a bug and should be reported) + * + * the following are NOT substantial differences: + * - reordering of properties in a node + * - whitespace changes outside of a comment value or other property value + * - reordering of property values + */ + gbn_assert(load_game(DEFAULT_SAVE_DIR "/unhnd.sgf")); + gbn_assert(save_game(DEFAULT_SAVE_DIR "/unhnd_out.sgf")); +} +#endif /* GBN_TEST */ + +bool setup_stack(struct stack_t * stack, void * buffer, size_t buffer_size) +{ + if (!stack || !buffer || !buffer_size) + { + DEBUGF("INVALID STACK SETUP!!\n"); + return false; + } + + stack->buffer = buffer; + stack->size = buffer_size; + stack->sp = 0; + + return true; +} + +bool push_stack (struct stack_t * stack, void * buffer, size_t buffer_size) +{ + if (stack->sp + buffer_size > stack->size) + { + DEBUGF("stack full!!\n"); + return false; + } + + rb->memcpy(&stack->buffer[stack->sp], buffer, buffer_size); + + stack->sp += buffer_size; + + return true; +} + +bool pop_stack (struct stack_t * stack, void * buffer, size_t buffer_size) +{ + if (!peek_stack(stack, buffer, buffer_size)) + { + return false; + } + + stack->sp -= buffer_size; + + return true; +} + +bool peek_stack (struct stack_t * stack, void * buffer, size_t buffer_size) +{ + if (stack->sp < buffer_size) + { + return false; + } + + rb->memcpy(buffer, &stack->buffer[stack->sp - buffer_size], buffer_size); + + return true; +} + +void empty_stack(struct stack_t * stack) +{ + stack->sp = 0; +} + +bool push_pos_stack(struct stack_t * stack, pos_t pos) +{ + return push_stack(stack, &pos, sizeof(pos)); +} + +bool push_int_stack(struct stack_t * stack, int num) +{ + return push_stack(stack, &num, sizeof(num)); +} + +bool push_char_stack(struct stack_t * stack, char num) +{ + return push_stack(stack, &num, sizeof(num)); +} + + + + + +/* IMPORTANT: keep in sync with the enum prop_type_t enum in types.h */ +char *prop_names[] = { + /* look up the SGF specification for the meaning of these */ + "B", "W", + "AB", "AW", "AE", + + "PL", "C", + + "DM", "GB", "GW", "HO", "UC", "V", + + "BM", "DO", "IT", "TE", + + "CR", "SQ", "TR", "DD", "MA", "SL", "LB", "N", + + "AP", "CA", "FF", "GM", "ST", "SZ", + + "AN", "PB", "PW", "HA", "KM", "TB", "TW", "BR", "WR", + "BT", "WT", "CP", "DT", "EV", "RO", "GN", "GC", "ON", + "OT", "PC", "RE", "RU", "SO", "TM", "US", + + "BL", "WL", "OB", "OW", "FG", "PM", "VW" +}; + +/* These seems to be specified by the SGF specification. You can do + * free form ones as well, but I haven't implemented that (and don't plan + * to) + */ +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; +} + + + +int +snprint_fixed (char *buffer, int buffer_size, int fixed) +{ + char neg_string[] = "-"; + + return rb->snprintf(buffer, buffer_size, "%s%d.%d", + fixed < 0 ? "-" : "", + abs(fixed) >> 1, + 5 * (fixed & 1)); +#if 0 + 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); + } +#endif +} + + +int +peek_char (int fd) +{ + char peeked_char; + + int result = rb->read (fd, &peeked_char, 1); + + if (result != 1) + { + return -1; + } + + result = rb->lseek (fd, -1, SEEK_CUR); + + if (result < 0) + { + return -1; + } + + return peeked_char; +} + + +int +read_char (int fd) +{ + char read_char; + + int result = rb->read (fd, &read_char, 1); + + if (result != 1) + { + return -1; + } + + return read_char; +} + + +bool +write_char (int fd, char to_write) +{ + int result = write_file (fd, &to_write, 1); + + if (result != 1) + { + return false; + } + + return true; +} + +ssize_t +write_file (int fd, const void *buf, size_t count) +{ + const char *buffer = buf; + int result; + int ret_val = count; + + while (count) + { + result = rb->write (fd, buffer, count); + + if (result < 0) + { + return -1; + } + + count -= result; + buffer += result; + } + + return ret_val; +} + +ssize_t +read_file (int fd, void *buf, size_t count) +{ + char *buffer = buf; + int result; + int ret_val = count; + + while (count) + { + result = rb->read (fd, buffer, count); + + if (result <= 0) + { + return -1; + } + + count -= result; + buffer += result; + } + + return ret_val; +} + +int +read_char_no_whitespace (int fd) +{ + int result = peek_char_no_whitespace (fd); + + read_char (fd); + + return result; +} + +int +peek_char_no_whitespace (int fd) +{ + int result; + + while (is_whitespace (result = peek_char (fd))) + { + read_char (fd); + } + + return result; +} + + +void +close_file (int *fd) +{ + if (*fd >= 0) + { + rb->close (*fd); + } + + *fd = -1; +} + + +bool +is_whitespace (int value) +{ + if (value == ' ' || + value == '\t' || + value == '\n' || value == '\r' || value == '\f' || value == '\v') + { + return true; + } + else + { + return false; + } +} + + +void sanitize_string(char * string) +{ + bool escaped = false; + + if (!string) + { + return; + } + + while (1) + { + switch (*string) + { + case '\0': + return; + case '\\': + escaped = !escaped; + break; + case ']': + if (!escaped) + { + *string = ']'; + } + escaped = false; + break; + default: + break; + }; + ++string; + } +} + 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, int 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,3975 @@ +/*************************************************************************** + * __________ __ ___. + * 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 "util.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 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; + +bool header_marked = false; + +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 int get_move_from_node (int handle); +static bool retreat_node (void); +static bool advance_node (void); +static void debugf_current_node (void); +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 int undo_node_helper (void); + + +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 + { + 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 + { + 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) + { + 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; + int saved = current_node; + struct node_t *node = get_node (current_node); + + int move_handle; + + 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); + + move_handle = get_move_sgf(); + + if (move_handle >= 0) + { + set_one_mark (get_prop (move_handle)->data.position, + get_prop (move_handle)->type); + } + + 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; + + struct prop_t * temp_move = get_prop (get_move_sgf ()); + + current_node = saved; + + if (temp_move && + temp_move->data.position == pos && + ((color == BLACK && temp_move->type == PROP_BLACK_MOVE) || + (color == WHITE && temp_move->type == PROP_WHITE_MOVE))) + { + return variation_count; + } + } + 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; + + struct prop_t * temp_move = get_prop (get_move_sgf ()); + if (temp_move && + pos == temp_move->data.position && + color == (temp_move->type == PROP_BLACK_MOVE ? BLACK : WHITE)) + { + 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) +{ + int result = undo_node_helper(); + + /* if we undid a ko threat, we need to figure out what the ko_pos is + * there's no simple way to do this except to undo one /more/ move, and + * then redo back to this location. (we could store it, but this isn't + * that bad) + * + * Note: this doesn't need to recurse because we don't care what + * previous move's ko positions were (since the tree is already set + * in stone basically, it wouldn't change anything). + */ + if (result == 1) + { + int backward_move_num = move_num - 1; + int saved_current = current_node; + + while (move_num > backward_move_num) + { + result = undo_node_helper(); + + if (result < 0) + { + DEBUGF("couldn't undo to previous move in ko threat handling!\n"); + return false; + } + } + + /* now we're backed up to the previous move before our destination + * so, let's go forward again until we get to the node we were at */ + + while (current_node != saved_current) + { + if (!redo_node_sgf()) + { + DEBUGF("redoing to correct node failed on ko threat handling!\n"); + return false; + } + } + + } + else if (result < 0) + { + DEBUGF("initial undo failed!\n"); + return false; + } + + + do_set_marks (); + + /* if there is a move in this node, move the screen so that it is visible*/ + int handle = get_move_sgf(); + if (handle >= 0) + { + move_display(get_prop (handle)->data.position); + } + + return true; +} + + +static int undo_node_helper (void) +{ + bool ko_threat_move = false; + + if (current_node == start_node) + { + /* refuse to undo the initial SGF node, which is tree_head if handicap + == 0 or 1. If handicap >= 2, start_node is the node with the + handicap crap and added moves on it. don't let the user undo past + that */ + DEBUGF ("not undoing start_node\n"); + return -1; + } + + struct prop_t * temp_move = get_prop (get_move_sgf ()); + + if (temp_move) + { + int undone_caps = 0; + int undone_suicides = 0; + pos_t move_pos = temp_move->data.position; + color_t move_color = temp_move->type == PROP_BLACK_MOVE ? BLACK : + WHITE; + + unsigned short flags = temp_move->data.stone_extra; + + if (move_pos != PASS_POS) + { + + if (flags & FLAG_N_CAP) + { + undone_caps += flood_fill_board(NORTH(move_pos), + OTHER(move_color)); + } + if (flags & FLAG_S_CAP) + { + undone_caps += flood_fill_board(SOUTH(move_pos), + OTHER(move_color)); + } + if (flags & FLAG_E_CAP) + { + undone_caps += flood_fill_board(EAST(move_pos), + OTHER(move_color)); + } + if (flags & FLAG_W_CAP) + { + undone_caps += flood_fill_board(WEST(move_pos), + OTHER(move_color)); + } + + if (flags & FLAG_SELF_CAP) + { + undone_suicides += flood_fill_board(move_pos, move_color); + } + + if (flags & FLAG_ORIG_EMPTY) + { + set_point_board(move_pos, EMPTY); + } + else if (flags & FLAG_ORIG_BLACK) + { + set_point_board(move_pos, BLACK); + } + else + { + set_point_board(move_pos, WHITE); + } + } + + if (move_color == BLACK) + { + black_captures -= undone_caps; + white_captures -= undone_suicides; + } + else + { + white_captures -= undone_caps; + black_captures -= undone_suicides; + } + + if (flags & FLAG_KO_THREAT) + { + ko_threat_move = true; + } + + --move_num; + current_player = OTHER (current_player); + } + else + { + /* test for added stones! */ + struct prop_t * temp_prop; + + 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 || + temp_prop->type == PROP_ADD_EMPTY) && + on_board (temp_prop->data.position)) + { + if (temp_prop->data.stone_extra & FLAG_ORIG_EMPTY) + { + set_point_board (temp_prop->data.position, EMPTY); + } + else if (temp_prop->data.stone_extra & FLAG_ORIG_BLACK) + { + set_point_board (temp_prop->data.position, BLACK); + } + else + { + set_point_board (temp_prop->data.position, WHITE); + } + } + + temp_prop = get_prop (temp_prop->next); + } + } + + if (!retreat_node ()) + { + return -1; + } + + if (ko_threat_move) + { + return 1; + } + + return 0; +} + + + 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.position, + prop->data.label_extra); + } + 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) +{ + if (!advance_node ()) + { + return false; + } + + do_set_marks (); + + int temp_move = get_move_sgf (); + if (temp_move >= 0) + { + struct prop_t * move_prop = get_prop (temp_move); + pos_t pos = move_prop->data.position; + color_t color = move_prop->type == PROP_BLACK_MOVE ? BLACK : WHITE; + + if (color != current_player) + { + DEBUGF ("redo_node_sgf: wrong color!\n"); + } + + /* zero out the undo information and set the ko threat flag + * to the correct value */ + + move_prop->data.stone_extra = 0; + + if (ko_pos != INVALID_POS) + { + move_prop->data.stone_extra |= FLAG_KO_THREAT; + } + + ko_pos = INVALID_POS; + + if (pos == PASS_POS) + { + rb->splashf (HZ / 2, "%s passes", + color == BLACK ? "black" : "white"); + } + else + { + int n_cap, s_cap, e_cap, w_cap, self_cap; + + n_cap = s_cap = e_cap = w_cap = self_cap = 0; + + if (get_point_board (pos) == EMPTY) + { + move_prop->data.stone_extra |= FLAG_ORIG_EMPTY; + } + else if (get_point_board (pos) == BLACK) + { + move_prop->data.stone_extra |= FLAG_ORIG_BLACK; + } + /* else do nothing */ + + set_point_board(pos, color); + + /* do captures on the 4 cardinal directions, if the + * opponent stones are breathless */ + if (get_point_board (NORTH (pos)) == OTHER(color) && + get_liberties_board (NORTH (pos)) == 0) + { + n_cap = flood_fill_board(NORTH (pos), EMPTY); + move_prop->data.stone_extra |= FLAG_N_CAP; + } + if (get_point_board (SOUTH (pos)) == OTHER(color) && + get_liberties_board (SOUTH (pos)) == 0) + { + s_cap = flood_fill_board(SOUTH (pos), EMPTY); + move_prop->data.stone_extra |= FLAG_S_CAP; + } + if (get_point_board (EAST (pos)) == OTHER(color) && + get_liberties_board (EAST (pos)) == 0) + { + e_cap = flood_fill_board(EAST (pos), EMPTY); + move_prop->data.stone_extra |= FLAG_E_CAP; + } + if (get_point_board (WEST (pos)) == OTHER(color) && + get_liberties_board (WEST (pos)) == 0) + { + w_cap = flood_fill_board(WEST (pos), EMPTY); + move_prop->data.stone_extra |= FLAG_W_CAP; + } + + /* then check for suicide */ + if (get_liberties_board (pos) == 0) + { + self_cap = flood_fill_board(pos, EMPTY); + move_prop->data.stone_extra |= FLAG_SELF_CAP; + } + + + /* now check for a ko, with the following requirements: + * 1) we captured one opponent stone + * 2) we placed one stone (not connected to a larger group) + * 3) we have one liberty + */ + + if (!self_cap && + (n_cap + s_cap + e_cap + w_cap == 1) && + get_liberties_board (pos) == 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) + { + /* We passed all tests, so there is a ko to set. + * The ko_pos is our single liberty location + */ + + 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 + { + ko_pos = WEST (pos); + } + } + + if (color == BLACK) + { + black_captures += n_cap + s_cap + e_cap + w_cap; + white_captures += self_cap; + } + else + { + white_captures += n_cap + s_cap + e_cap + w_cap; + black_captures += self_cap; + } + + /* this will move the cursor near this move if + * it was off the screen */ + move_display(pos); + } + + ++move_num; + current_player = OTHER (color); + + 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; + int temp_handle; + + if (current_node < 0) + { + return false; + } + + temp_handle = get_node (current_node)->props; + temp_prop = get_prop (temp_handle); + + while (temp_prop) + { + if (temp_prop->type == PROP_ADD_BLACK || + temp_prop->type == PROP_ADD_WHITE || + temp_prop->type == PROP_ADD_EMPTY) + { + + temp_prop->data.stone_extra = 0; + + /* TODO: we could delete do-nothing PROP_ADD_*s here */ + + if (get_point_board (temp_prop->data.position) == EMPTY) + { + temp_prop->data.stone_extra |= FLAG_ORIG_EMPTY; + } + else if (get_point_board (temp_prop->data.position) == BLACK) + { + temp_prop->data.stone_extra |= FLAG_ORIG_BLACK; + } + /* else, do nothing */ + + if (temp_prop->type == PROP_ADD_BLACK || + temp_prop->type == PROP_ADD_WHITE) + { + set_point_board(temp_prop->data.position, + temp_prop->type == PROP_ADD_BLACK ? BLACK : + WHITE); + ret_val = true; + } + else + { + set_point_board(temp_prop->data.position, EMPTY); + + ret_val = true; + } + } + + temp_handle = temp_prop->next; + temp_prop = get_prop (temp_handle); + } + + 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 0 + if (get_move_sgf (&temp_pos, NULL)) + { + if (temp_pos == pos) + { + return false; + } + } +#endif + 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.position == pos) + { + char to_set = temp_prop->data.label_extra; + ++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_extra = to_set; + set_label_mark (pos, to_set); + return true; + } + + temp_prop_handle = temp_prop->next; + } + + temp_data.label_extra = MIN_LABEL; + temp_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; + } + + /* TODO: should we really disallow adding stones to nodes + * which are already parents? probably not. */ + 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)) + { + + 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); + + /* we have to always delete the old one and conditionally create + * a new one (instead of trying to reuse the old one by changing + * the type of it) because if we don't, our invariant with respect + * to like-properties being grouped together in the property + * list can easily be violated */ + if (handle >= 0) + { + temp_data.stone_extra = get_prop (handle)->data.stone_extra; + delete_prop_handle_sgf (current_node, handle); + } + else + { + temp_data.stone_extra = 0; + if (get_point_board (pos) == EMPTY) + { + temp_data.stone_extra |= FLAG_ORIG_EMPTY; + } + else if (get_point_board (pos) == BLACK) + { + temp_data.stone_extra |= FLAG_ORIG_BLACK; + } + /* else do nothing */ + } + + /* now we've saved the information about what the board was + * originally like, we can do the actual set */ + + set_point_board (pos, color); + + /* test if what we currently did just returned the board back + * to its original for this position. if so, we DON'T create + * a new PROP_ADD_*, because it's not needed (we already deleted + * the old one, so in that case we just return) */ + if (((temp_data.stone_extra & FLAG_ORIG_EMPTY) && color == EMPTY) || + (!(temp_data.stone_extra & FLAG_ORIG_EMPTY) && + (((temp_data.stone_extra & FLAG_ORIG_BLACK) && color == BLACK) || + (!(temp_data.stone_extra & FLAG_ORIG_BLACK) && color == WHITE)))) + { + /* do nothing, set back to original */ + } + else + { + /* we're not set back to original, so add a prop for it */ + add_prop_sgf (current_node, temp_type, temp_data); + } + + game_dirty = true; + + return true; + } + 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; + } + *buffer = temp; + ++buffer; + ++bytes_read; + 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; +} + + + int +get_move_sgf (void) +{ + int result = -1; + int saved_current = current_node; + + goto_next_important_node (true); + + result = get_move_from_node (current_node); + + current_node = saved_current; + return result; +} + + static int +get_move_from_node (int handle) +{ + int prop_handle; + + if (handle < 0) + { + return -2; + } + + prop_handle = get_node (handle)->props; + + + while (prop_handle >= 0) + { + if (get_prop (prop_handle)->type == PROP_BLACK_MOVE || + get_prop (prop_handle)->type == PROP_WHITE_MOVE) + { + return prop_handle; + } + + prop_handle = get_prop (prop_handle)->next; + } + + return -1; +} + + 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; +} + + + bool +audio_stolen_sgf (void) +{ + return storage_initialized[1]; +} + + 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; + + 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); + + + 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); + + empty_stack(&parse_stack); + + return true; +} + + + void +clear_caches_sgf (void) +{ + empty_stack(&parse_stack); + + rb->lseek (unhandled_fd, 0, SEEK_SET); + rb->ftruncate (unhandled_fd, 0); +} + + void +cleanup_sgf (void) +{ + empty_stack(&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; + } + + empty_stack(&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; + } + } + + push_int_stack (&parse_stack, rb->lseek (sgf_fd, 0, SEEK_CUR)); + push_int_stack (&parse_stack, current_node); + + while (pop_int_stack (&parse_stack, &first_handle) && + pop_int_stack (&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_stack (&parse_stack, -1); + push_int_stack (&parse_stack, first_handle); + } + } + else + { + /* check for a sibling after we finish with this node */ + push_int_stack (&parse_stack, -1); + push_int_stack (&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 == ';') + { + /* fill the tree_head node before moving on */ + if (current_node != tree_head || + get_node (current_node)->props >= 0) + { + 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_stack (&parse_stack, rb->lseek (sgf_fd, 0, SEEK_CUR)); + push_int_stack (&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; + + /* special extra handling for root properties, set a marker + * telling us the right place to spit the values out in output_sgf */ + if (type == PROP_GAME || + type == PROP_APPLICATION || + type == PROP_CHARSET || + type == PROP_SIZE || + type == PROP_FILE_FORMAT || + type == PROP_VARIATION_TYPE) + { + header_marked = true; + + temp_data.number = 0; /* meaningless */ + + /* don't add more than one, so just set it if we found one + * already */ + add_or_set_prop_sgf(current_node, PROP_ROOT_PROPS, temp_data); + } + + + 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.position = sgf_to_pos (buffer); + + if (!on_board (temp_data.position)) + { + DEBUGF ("LaBel set on invalid position!\n"); + } + + temp_data.label_extra = 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 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 + /* these marks are stupid and nobody uses them. if we could find + * a good way to draw them we could do them anyway, but no reason + * to unless it's easy + */ + 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); + + empty_stack(&parse_stack); + + rb->lseek (sgf_fd, 0, SEEK_SET); + rb->ftruncate (sgf_fd, 0); + + if (sgf_fd < 0) + { + return false; + } + + if (tree_head < 0) + { + close_file (&sgf_fd); + return false; + } + + push_int_stack (&parse_stack, tree_head); + + while (pop_int_stack (&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_stack (&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]ST[2]\n\n", + 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[", 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)); + } + + write_char (sgf_fd, '\n'); + write_char (sgf_fd, '\n'); +} + + + 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_stack (&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, ';'); + +#if 0 + /* the first node gets the header */ + if (get_node (current_node)->prev < 0) + { + output_header_props (); + DEBUGF ("outputted header bullshit\n"); + } +#endif + 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.position, + buffer); + buffer[2] = '\0'; + + rb->snprintf (&buffer[2], sizeof (buffer) - 2, ":%c", + get_prop (prop_handle)->data.label_extra); + + 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_ROOT_PROPS) + { + output_header_props(); + } + 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; + } +#if 0 + current_node = tree_head; + current_node = add_child_sgf (NULL); + + if (current_node < 0) + { + current_node = tree_head; + return; + } + + start_node = current_node; +#endif + + current_node = start_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 (); + } +} + +bool post_game_setup_sgf(void) +{ + int temp_handle = alloc_storage_sgf(); + int saved = current_node; + + if (temp_handle < 0) + { + return false; + } + + union prop_data_t temp_data; + temp_data.number = 0; /* meaningless */ + + if (!header_marked) + { + add_prop_sgf(tree_head, PROP_ROOT_PROPS, temp_data); + header_marked = true; + } + + get_node (temp_handle)->next = current_node; + get_node (temp_handle)->prev = NO_NODE; + get_node (temp_handle)->props = NO_PROP; + + current_node = temp_handle; + + redo_node_sgf(); + + if (current_node == temp_handle) + { + current_node = saved; + } + + free_storage_sgf(temp_handle); + + return true; +} 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/util.h =================================================================== --- apps/plugins/goban/util.h (revision 0) +++ apps/plugins/goban/util.h (revision 0) @@ -0,0 +1,81 @@ +/*************************************************************************** + * __________ __ ___. + * 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_UTIL_H +#define GOBAN_UTIL_H + +#include "types.h" +#include "goban.h" + +bool setup_stack(struct stack_t * stack, void * buffer, size_t buffer_size); + +bool push_stack (struct stack_t * stack, void * buffer, size_t buffer_size); +bool pop_stack (struct stack_t * stack, void * buffer, size_t buffer_size); +bool peek_stack (struct stack_t * stack, void * buffer, size_t buffer_size); + +void empty_stack(struct stack_t * stack); + +#define pop_pos_stack(stack, pos) pop_stack(stack, pos, sizeof (pos_t)) +#define peek_pos_stack(stack, pos) peek_stack(stack, pos, sizeof (pos_t)) + +#define pop_int_stack(stack, num) pop_stack(stack, num, sizeof (int)) +#define peek_int_stack(stack, num) peek_stack(stack, num, sizeof (int)) + +#define pop_char_stack(stack, num) pop_stack(stack, num, sizeof (char)) +#define peek_char_stack(stack, num) peek_stack(stack, num, sizeof (char)) + +bool push_pos_stack(struct stack_t * stack, pos_t pos); +bool push_int_stack(struct stack_t * stack, int num); +bool push_char_stack(struct stack_t * stack, char num); + + +#define min(x, y) (x < y ? x : y) +#define max(x, y) (x > y ? x : y) + +/* this is used to help map fixed_point variables to their actual values + * (especially negative numbers)*/ +#define fix_fixed(x) (((x < 0) && (x & 1)) ? x + 2 : x) + + +int create_or_open_file (const char *filename); +int snprint_fixed (char *buffer, int buffer_size, int fixed); + + +ssize_t read_file (int fd, void *buf, size_t count); +ssize_t write_file (int fd, const void *buf, size_t count); +void close_file (int *fd); + +int peek_char (int fd); +int read_char (int fd); +bool write_char (int fd, char to_write); + +int peek_char_no_whitespace (int fd); +int read_char_no_whitespace (int fd); +bool is_whitespace (int value); + +void sanitize_string(char * string); + + +#ifdef GBN_TEST +void run_tests(void); +#endif + +#endif Index: apps/plugins/goban/sgf.h =================================================================== --- apps/plugins/goban/sgf.h (revision 0) +++ apps/plugins/goban/sgf.h (revision 0) @@ -0,0 +1,96 @@ +/*************************************************************************** + * __________ __ ___. + * 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); + +bool audio_stolen_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); +int get_move_sgf (void); + + +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); + +bool post_game_setup_sgf(void); + +#define NO_NODE (-1) +#define NO_PROP (-1) + + +#define FLAG_ORIG_EMPTY ((uint8_t) (1 << 7)) +#define FLAG_ORIG_BLACK ((uint8_t) (1 << 6)) +#define FLAG_KO_THREAT ((uint8_t) (1 << 5)) + +#define FLAG_SELF_CAP ((uint8_t) (1 << 4)) +#define FLAG_W_CAP ((uint8_t) (1 << 3)) +#define FLAG_E_CAP ((uint8_t) (1 << 2)) +#define FLAG_S_CAP ((uint8_t) (1 << 1)) +#define FLAG_N_CAP ((uint8_t) (1)) + +#ifdef GBN_TEST +extern int current_node; +#endif + +extern bool header_marked; + +#endif Index: apps/plugins/goban/goban.c =================================================================== --- apps/plugins/goban/goban.c (revision 0) +++ apps/plugins/goban/goban.c (revision 0) @@ -0,0 +1,985 @@ +/*************************************************************************** + * __________ __ ___. + * 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" +#include "lib/playback_control.h" + + +PLUGIN_HEADER +#include "goban.h" +#include "game.h" +#include "board.h" +#include "display.h" +#include "sgf.h" +#include "types.h" +#include "util.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 + + +#define PARSE_STACK_BUFFER_SIZE (max(MAX_BOARD_SIZE * MAX_BOARD_SIZE * sizeof(pos_t), 50 * sizeof(int))) + +/* used in SGF file parsing and outputting as well as in + * liberty counting and capturing/uncapturing */ +struct stack_t parse_stack; +char parse_stack_buffer[PARSE_STACK_BUFFER_SIZE]; + + +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); +static enum prop_type_t menu_selection_to_prop (int selection); +static bool do_zoom (void); + + 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 (); + +#ifdef GBN_TEST + run_tests(); + return 0; +#endif + + if (!(parameter && load_game (parameter))) + { + if (!load_game (DEFAULT_SAVE)) + { + rb->strcpy (save_file, DEFAULT_SAVE); + + /* TODO: new game here */ + if (!setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 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_sgf (); +} + + static void +global_setup (void) +{ + setup_stack(&parse_stack, parse_stack_buffer, sizeof(parse_stack_buffer)); + setup_display (); + setup_sgf (); +} + + + static bool +do_main_menu (void) +{ + /* for "New" in menu */ + int new_handi = 0, new_bs = MAX_BOARD_SIZE, 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", + "Playback Control", + "Zoom Level", + "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_BOARD_SIZE, MAX_BOARD_SIZE, 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 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 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 3: /* "Game Info" */ + do_gameinfo_menu (); + break; + + case 4: /* Playback Control */ + if (!audio_stolen_sgf()) + { + playback_control(NULL); + } + else + { + rb->splash(1 * HZ, "Audio has been disabled!"); + } + break; + + case 5: /* Zoom Level */ + if (do_zoom()) + { + return true; + } + done = true; + draw_board(); + break; + + case 6: /* "Context Menu" */ + do_context_menu (); + done = true; + break; + + case MENU_ATTACHED_USB: + case 7: /* "Quit" */ + return true; + + case GO_TO_ROOT: + case GO_TO_PREVIOUS: + default: + + done = true; + break; + }; + } + + return false; +} + + +void zoom_preview(int current) +{ + set_zoom_display(current); + draw_board(); + rb->splash(0, "Preview"); +} + +static bool +do_zoom (void) +{ + unsigned int zoom_level; + unsigned int old_val; + bool done = false; + + unsigned int min_val = min_zoom_display(); + unsigned int max_val = max_zoom_display(); + + zoom_level = old_val = current_zoom_display(); + + int action; + + zoom_preview(zoom_level); + while (!done) + { + switch (action = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK)) + { + case ACTION_STD_OK: + set_zoom_display(zoom_level); + done = true; + rb->splash(HZ / 2, "Zoom Set"); + break; + + case ACTION_STD_CANCEL: + set_zoom_display(old_val); + done = true; + rb->splash(HZ / 2, "Cancelled"); + break; + + case ACTION_STD_CONTEXT: + zoom_level = old_val; + zoom_preview(zoom_level); + break; + + case ACTION_STD_NEXT: + case ACTION_STD_NEXTREPEAT: + zoom_level = zoom_level * 3 / 2; + + /* 1 * 3 / 2 is 1 again... */ + if (zoom_level == 1) + { + ++zoom_level; + } + + if (zoom_level > max_val) + { + zoom_level = min_val; + } + zoom_preview(zoom_level); + break; + + case ACTION_STD_PREV: + case ACTION_STD_PREVREPEAT: + zoom_level = zoom_level * 2 / 3; + + if (zoom_level < min_val) + { + zoom_level = max_val; + } + zoom_preview(zoom_level); + break; + + case ACTION_NONE: + break; + + default: + if (rb->default_event_handler(action) == SYS_USB_CONNECTED) + { + return true; + } + break; + } + } + + return false; + + + + + +#if 0 + + rb->lcd_set_drawmode(DRMODE_BG); + rb->set_int ("Zoom Level", "", UNIT_INT, &zoom_level, + zoom_preview, 1, min_zoom_display(), max_zoom_display(), NULL); + rb->lcd_set_drawmode(DRMODE_SOLID); + set_zoom_display(zoom_level); +#endif +} + + 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); + sanitize_string (gameinfo_string); + game_dirty = true; + break; + + /* these need special handling in some way, so they are separate */ + case 3: /* "Handicap", */ + rb->splash (2 * HZ, + "Handicap cannot be set on existing games, please start a new one!"); + break; + + case 0: /* "Time Limit", */ + rb->set_int ("Time Limit", "", UNIT_INT, &header.time_limit, + NULL, 60, 0, 24 * 60 * 60, &time_formatter); + game_dirty = true; + break; + + case 4: /* "Komi", */ + rb->set_int ("Komi", "moku", UNIT_INT, &header.komi, NULL, + 1, -300, 300, &komi_formatter); + game_dirty = true; + break; + + case 5: /* "Ruleset", */ + new_ruleset = 0; + rb->set_int ("Ruleset", "", UNIT_INT, &new_ruleset, NULL, + 1, 0, NUM_RULESETS - 1, &ruleset_formatter); + + rb->strcpy (header.ruleset, ruleset_names[new_ruleset]); + game_dirty = true; + break; + + case 16: /* "Done") */ + case GO_TO_ROOT: + case GO_TO_PREVIOUS: + case MENU_ATTACHED_USB: + 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/Edit Comment", + "Done"); + + 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: /* "Done" */ + 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; +} + + static 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,355 @@ +/*************************************************************************** + * __________ __ ___. + * 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 "types.h" +#include "game.h" +#include "util.h" + +#include "plugin.h" + +int board_width = MAX_BOARD_SIZE; +int board_height = MAX_BOARD_SIZE; + +/* Board has 'invalid' markers around each border */ +color_t board_data[(MAX_BOARD_SIZE + 2) * (MAX_BOARD_SIZE + 2)]; +int white_captures = 0; +int black_captures = 0; + +uint8_t board_marks[(MAX_BOARD_SIZE + 2) * (MAX_BOARD_SIZE + 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_BOARD_SIZE * MAX_BOARD_SIZE]; + +pos_t ko_pos = INVALID_POS; + +/* forward declarations */ +static void setup_marks (void); +static void make_mark (pos_t pos); +static int get_liberties_helper(pos_t pos, color_t orig_color); +static int flood_fill_helper(pos_t pos, color_t orig_color, color_t color); + + +/* 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_BOARD_SIZE) * (2 + MAX_BOARD_SIZE); ++i) + { + board_data[i] = INVALID; + } + + /* now make the actual board part */ + for (y = 0; y < board_height; ++y) + { + for (x = 0; x < board_width; ++x) + { + board_data[POS (x, y)] = EMPTY; + } + } + + white_captures = 0; + black_captures = 0; + + ko_pos = INVALID_POS; +} + +bool +set_size_board (int width, int height) +{ + if (width < MIN_BOARD_SIZE || width > MAX_BOARD_SIZE || + height < MIN_BOARD_SIZE || height > MAX_BOARD_SIZE) + { + return false; + } + else + { + board_width = width; + board_height = height; + setup_display (); + return true; + } +} + +color_t +get_point_board (pos_t pos) +{ + return board_data[pos]; +} + +void +set_point_board (pos_t pos, color_t color) +{ + board_data[pos] = color; +} + +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; + } +} + +int +get_liberties_board (pos_t pos) +{ + if (!on_board(pos) || get_point_board (pos) == EMPTY) + { + return -1; + } + + setup_marks (); + + int ret_val = 0; + color_t orig_color = get_point_board (pos); + + empty_stack(&parse_stack); + push_pos_stack(&parse_stack, pos); + + /* Since we only ever test for liberties in order to determine + * captures and the like, there's no reason to count any liberties + * higher than 2 (we sometimes need to know if something has 1 liberty + * for dealing with ko) */ + while (pop_pos_stack(&parse_stack, &pos) && + ret_val < 2) + { + ret_val += get_liberties_helper(NORTH (pos), orig_color); + ret_val += get_liberties_helper(SOUTH (pos), orig_color); + ret_val += get_liberties_helper(EAST (pos), orig_color); + ret_val += get_liberties_helper(WEST (pos), orig_color); + } + + /* if there's more than two liberties, the stack isn't empty, so + * empty it */ + empty_stack(&parse_stack); + + return ret_val; +} + +static int get_liberties_helper(pos_t pos, color_t orig_color) +{ + if (on_board (pos) && + get_point_board (pos) != OTHER(orig_color) && + !is_marked (pos)) + { + make_mark (pos); + + if (get_point_board (pos) == EMPTY) + { + return 1; + } + else + { + push_pos_stack(&parse_stack, pos); + } + } + + return 0; +} + + +int flood_fill_board(pos_t pos, color_t color) +{ + if (!on_board (pos) || get_point_board (pos) == color) + { + return 0; + } + + empty_stack(&parse_stack); + + int ret_val = 0; + + color_t orig_color = get_point_board (pos); + + set_point_board (pos, color); + ++ret_val; + push_pos_stack(&parse_stack, pos); + + while (pop_pos_stack(&parse_stack, &pos)) + { + ret_val += flood_fill_helper(NORTH (pos), orig_color, color); + ret_val += flood_fill_helper(SOUTH (pos), orig_color, color); + ret_val += flood_fill_helper(EAST (pos), orig_color, color); + ret_val += flood_fill_helper(WEST (pos), orig_color, color); + } + + return ret_val; +} + + +static int flood_fill_helper(pos_t pos, color_t orig_color, color_t color) +{ + if (on_board (pos) && get_point_board (pos) == orig_color) + { + set_point_board (pos, color); + push_pos_stack(&parse_stack, pos); + return 1; + } + + return 0; +} + +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? */ + 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_board (pos, color); + + /* if we have liberties, it can't be illegal */ + if (get_liberties_board (pos) > 0 || + /* if we can capture something, it can't be illegal */ + (get_point_board (NORTH (pos)) == OTHER (color) && + !get_liberties_board (NORTH (pos))) || + (get_point_board (SOUTH (pos)) == OTHER (color) && + !get_liberties_board (SOUTH (pos))) || + (get_point_board (EAST (pos)) == OTHER (color) && + !get_liberties_board (EAST (pos))) || + (get_point_board (WEST (pos)) == OTHER (color) && + !get_liberties_board (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_board (pos, EMPTY); + return true; + } + else + { + /* undo our previous set */ + set_point_board (pos, EMPTY); + return false; + } +} + + +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); + } +} + Index: apps/plugins/goban/goban.h =================================================================== --- apps/plugins/goban/goban.h (revision 0) +++ apps/plugins/goban/goban.h (revision 0) @@ -0,0 +1,250 @@ +/*************************************************************************** + * __________ __ ___. + * 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" +#include "util.h" + +#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_UP +#define GBN_BUTTON_DOWN BUTTON_SCROLL_DOWN +#define GBN_BUTTON_LEFT BUTTON_LEFT +#define GBN_BUTTON_RIGHT BUTTON_RIGHT +#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) +#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 == IAUDIO_M3_PAD +/* TODO: these are basically complete guesses, I have no manual to go by */ +#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 */ + +#define FOOTER_RESERVE (16) + +#if (LCD_WIDTH > LCD_HEIGHT) + +#define GBN_WIDE_SCREEN + +#define LCD_BOARD_WIDTH (LCD_WIDTH - FOOTER_RESERVE) +#define LCD_BOARD_HEIGHT LCD_HEIGHT + +#else + +#define GBN_TALL_SCREEN + +#define LCD_BOARD_WIDTH LCD_WIDTH +#define LCD_BOARD_HEIGHT (LCD_HEIGHT - FOOTER_RESERVE) + +#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 DEFAULT_SAVE_DIR "/sgf" + +extern enum play_mode_t play_mode; +extern struct stack_t parse_stack; + +#endif Index: apps/plugins/goban/board.h =================================================================== --- apps/plugins/goban/board.h (revision 0) +++ apps/plugins/goban/board.h (revision 0) @@ -0,0 +1,72 @@ +/*************************************************************************** + * __________ __ ___. + * 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) + + /* MAX_BOARD_SIZE no longer dependent on screen size + * since zooming was implemented + */ +#define MAX_BOARD_SIZE 19 +#define MIN_BOARD_SIZE 1 + +#define INVALID_POS ((pos_t) -5003) +#define PASS_POS ((pos_t) -5002) + +#define POS(i, j) ((i + 1) + (j + 1) * (MAX_BOARD_SIZE + 2)) +#define WEST(i) (i - 1) +#define EAST(i) (i + 1) +#define NORTH(i) (i - (MAX_BOARD_SIZE + 2)) +#define SOUTH(i) (i + (MAX_BOARD_SIZE + 2)) + +pos_t WRAP (pos_t pos); + +#define I(pos) (pos % (MAX_BOARD_SIZE + 2) - 1) +#define J(pos) (pos / (MAX_BOARD_SIZE + 2) - 1) + +void clear_board (void); +bool set_size_board (int width, int height); +bool legal_move_board (pos_t pos, color_t color, bool allow_suicide); +bool on_board (pos_t pos); +color_t get_point_board (pos_t pos); +void set_point_board (pos_t pos, color_t data); +int get_liberties_board (pos_t pos); +int flood_fill_board(pos_t pos, color_t color); + +extern int board_width; +extern int board_height; +extern int black_captures; +extern int white_captures; + +extern pos_t ko_pos; + +#endif Index: apps/plugins/SUBDIRS =================================================================== --- apps/plugins/SUBDIRS (revision 19908) +++ apps/plugins/SUBDIRS (working copy) @@ -20,6 +20,7 @@ jpeg sudoku reversi +goban #ifndef OLYMPUS_MROBE_500 zxbox #endif Index: manual/plugins/goban.tex =================================================================== --- manual/plugins/goban.tex (revision 0) +++ manual/plugins/goban.tex (revision 0) @@ -0,0 +1,155 @@ +\subsection{Goban} +\screenshot{plugins/images/ss-goban}{Goban}{The Rockbox Goban plugin} +Goban is a a plugin for playing, viewing and recording games of Go (also known +as Weiqi, Baduk, Igo and Goe). It uses standard Smart Game Format (SGF) files +for saving and loading games. + +You can find a short introduction to Go at +\url{http://senseis.xmp.net/?WhatIsGo} and more information about SGF files +can be read at \url{http://senseis.xmp.net/?SmartGameFormat} or the SGF +specification at +\url{http://www.red-bean.com/sgf/}. + +This plugin can load all modern SGF files (file format 3 or 4) with few problems. +It attempts to preserve SGF properties which it doesn't understand, and most common +SGF properties are handled fully. It is possible to view (and edit if you +like) Kogo's Joseki Dictionary (\url{http://waterfire.us/joseki.htm}) with this +plugin, although the load and save times can be on the order of a minute or two on +particularly slow devices. Large SGF files may stop audio playback for the +duration of the plugin's run in order to free up more memory and some very large +SGF files will not even load on devices with little available memory. + +The information panel which displays the current move number may also contain +these markers: \\ +\begin{tabularx}{\textwidth}{lX}\toprule +\textbf{Mark} & \textbf{Meaning} \\ \midrule + \emph{+ } & There are nodes after the current node in the SGF tree. \\ + \emph{* } & There are sibling variations which can be navigated to using the + \emph{Next Variation} menu option of the \emph{Context + Menu}\opt{SANSA_E200_PAD}{ or the {\ButtonRec} button}. \\ + \emph{C } & There is a comment at the current node. It can be viewed/edited using + the \emph{Add/Edit Comment} menu option of the \emph{Context Menu}. \\ +\bottomrule +\end{tabularx} + +\subsection{Controls} +\begin{table} + \begin{btnmap}{}{} + \opt{SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,MROBE100_PAD,IAUDIO_X5_PAD,RECORDER_PAD,ONDIO_PAD}{\ButtonUp}\opt{IPOD_1G2G_PAD,IPOD_3G_PAD,IPOD_4G_PAD}{\ButtonMenu}\opt{IRIVER_H10_PAD}{\ButtonScrollUp} & Move cursor up \\ + \opt{SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,MROBE100_PAD,IAUDIO_X5_PAD,RECORDER_PAD,ONDIO_PAD}{\ButtonDown}\opt{IPOD_1G2G_PAD,IPOD_3G_PAD,IPOD_4G_PAD}{\ButtonPlay}\opt{IRIVER_H10_PAD}{\ButtonScrollDown} & Move cursor down \\ + \opt{SANSA_E200_PAD,SANSA_FUZE_PAD,IPOD_1G2G_PAD,IPOD_3G_PAD,IPOD_4G_PAD,SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD,IRIVER_H10_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,MROBE100_PAD,IAUDIO_X5_PAD,RECORDER_PAD,ONDIO_PAD}{\ButtonLeft} + & Move cursor left \opt{ONDIO_PAD}{if in \emph{board} navigation mode, or + retreat one node in the game tree if in \emph{tree} navigation mode}\\ + \opt{SANSA_E200_PAD,SANSA_FUZE_PAD,IPOD_1G2G_PAD,IPOD_3G_PAD,IPOD_4G_PAD,SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD,IRIVER_H10_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,MROBE100_PAD,IAUDIO_X5_PAD,RECORDER_PAD,ONDIO_PAD}{\ButtonRight} & Move cursor right \opt{ONDIO_PAD}{if in \emph{board} navigation mode, or + advance one node in the game tree if in \emph{tree} navigation mode}\\ + \opt{ONDIO_PAD}{{\ButtonOff} & Toggle between \emph{board} and \emph{tree} + navigation modes \\} + \opt{SANSA_E200_PAD,SANSA_FUZE_PAD,IPOD_1G2G_PAD,IPOD_3G_PAD,IPOD_4G_PAD,SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,MROBE100_PAD,IAUDIO_X5_PAD}{\ButtonSelect}\opt{IRIVER_H10_PAD}{\ButtonPlay}\opt{RECORDER_PAD}{\ButtonPlay}\opt{ONDIO_PAD}{\ButtonMenu} & Play a move or use whatever play-mode + tool was chosen. \\ + \nopt{ONDIO_PAD}{\opt{SANSA_E200_PAD,SANSA_FUZE_PAD,IPOD_1G2G_PAD,IPOD_3G_PAD,IPOD_4G_PAD}{\ButtonScrollBack}\opt{SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD}{\ButtonVolDown}\opt{IRIVER_H10_PAD}{\ButtonFF}\opt{IRIVER_H100_PAD,IRIVER_H300_PAD}{\ButtonOff}\opt{MROBE100_PAD}{\ButtonMenu}\opt{IAUDIO_X5_PAD}{\ButtonPlay}\opt{RECORDER_PAD}{\ButtonFOne} & Retreat one + node in the game tree \\ + \opt{SANSA_E200_PAD,SANSA_FUZE_PAD,IPOD_1G2G_PAD,IPOD_3G_PAD,IPOD_4G_PAD,IPOD_1G2G_PAD,IPOD_3G_PAD,IPOD_4G_PAD}{\ButtonScrollFwd}\opt{SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD}{\ButtonVolUp}\opt{IRIVER_H10_PAD}{\ButtonRew}\opt{IRIVER_H100_PAD,IRIVER_H300_PAD}{\ButtonOn}\opt{MROBE100_PAD}{\ButtonPlay}\opt{IAUDIO_X5_PAD}{\ButtonRec}\opt{RECORDER_PAD}{\ButtonFTwo} + & Advance one node in the game tree \\ } + \opt{SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,IRIVER_H10_PAD,MROBE100_PAD,IAUDIO_X5_PAD}{\ButtonPower}\opt{IPOD_1G2G_PAD,IPOD_3G_PAD,IPOD_4G_PAD}{Long + \ButtonSelect}\opt{GIGABEAT_PAD,GIGABEAT_S_PAD}{\ButtonMenu}\opt{IRIVER_H100_PAD,IRIVER_H300_PAD}{\ButtonMode}\opt{RECORDER_PAD}{Long + \ButtonPlay}\opt{ONDIO_PAD}{Long + \ButtonMenu} + & Main Menu \\ + \opt{SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,MROBE100_PAD,IAUDIO_X5_PAD,IRIVER_H10_PAD}{\opt{SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,MROBE100_PAD,IAUDIO_X5_PAD}{Long + \ButtonSelect}\opt{IRIVER_H10_PAD}{Long \ButtonPlay} & Context + Menu \\ } + \opt{SANSA_E200_PAD,SANSA_FUZE_PAD}{\ButtonRec & Go to the next + variation when at the first move in a branch \\ } + \end{btnmap} +\end{table} + + +\subsection{Menus} +\begin {description} +\item [Main Menu. ] + The main menu for game setup and access to other menus. + + \emph {New. } Create a new game with your choice of board size and handicaps. \\ + \emph {Save. } Save the current state of the game. It will be saved to + \fname {"/sgf/gbn\_def.sgf"} unless otherwise set. \\ + \emph {Save As. } Save to a specified file. \\ + \emph {Game Info. } View and modify the metadata of the current game. \\ + \emph {Playback Control. } Control the playback of the current playlist and + modify the volume of your player. \\ + \emph {Zoom Level. } Zoom in or out on the board. \\ + \emph {Context Menu. } Open the Context Menu which allows you to set play modes + and other tools. \\ + \emph {Quit. } Leave the plugin. Any unsaved changes are saved to + \fname {"/sgf/gbn\_def.sgf"}. \\ + +\item [Game Info. ] + The menu for modifying game info (metadata) of the current game. This + information will be saved to the SGF file and can be viewed in almost all + SGF readers. + + \emph {Time Limit. } The time limit of the current game. \\ + \emph {Overtime. } The overtime settings of the current game. \\ + \emph {Result. } The result of the current game. This text must follow the + format specified at \url{http://www.red-bean.com/sgf/properties.html#RE} to + be read by other SGF readers. Some examples are \emph {B+R} (Black wins by + resignation), \emph {B+5.5} (Black wins by 5.5 points), \emph {W+T} (White wins + on Time). \\ + \emph {Handicap. } The the handicap of the current game. \\ + \emph {Komi. } The komi of the current game (compensation to the white + player for black having the first move). \\ + \emph {Ruleset. } The name of the ruleset in use for this game. \\ + \emph {Black Player. } The name of the black player. \\ + \emph {Black Rank. } Black's rank, in dan or kyu. \\ + \emph {Black Team. } The name of black's team, if any. \\ + \emph {White Player. } The name of the white player. \\ + \emph {White Rank. } White's rank, in dan or kyu. \\ + \emph {White Team. } The name of white's team, if any. \\ + \emph {Date. } The date that this game took place. This text must follow the + format specified at \url{http://www.red-bean.com/sgf/properties.html#DT} to + be read by other SGF readers. \\ + \emph {Event. } The name of the event which this game was a part of, if any. + \\ + \emph {Place. } The place that this game took place. \\ + \emph {Round. } If part of a tournament, the round number for this game. \\ + \emph {Done. } Return to the previous menu. \\ + +\item [Context Menu. ] + The menu for choosing different play modes and tools, adding or editing + comments, adding pass moves, or switching between sibling variations. + + \emph {Play Mode. } Play moves normally on the board. If there are + child moves from the current node, this mode will let you follow variations + by simply playing the first move in the sequence. Unless it is following a + variation, this mode will not allow you to play illegal moves. This is the + default mode before another is set after loading a game or creating a new + one. \\ + \emph {Add Black Mode. } Add black stones to the board as desired. These + stones are not moves and do not perform captures or count as ko threats. \\ + \emph {Add White Mode. } Add white stones to the board as desired. These + stones are not moves and do not perform captures or count as ko threats. \\ + \emph {Erase Stone Mode. } Remove stones from the board as desired. These + removed stones are not counted as captured, they are simply removed. \\ + \emph {Pass. } Play a single pass move. This does not change the mode of + play. \\ + \emph {Next Variation. } If the game is at the first move in a variation, + this will navigate to the next variation after the current one. This is + the only way to reach variations which start with adding or removing + stones, as you cannot follow them by "playing" the same move. \\ + \emph {Force Play Mode. } The same as Play Mode except that this mode will + allow you to play illegal moves such as retaking a ko immediately without a + ko threat, suicide on rulesets which don't allow it (including single stone + suicide), and playing a move where there is already a stone. \\ + \emph {Mark Mode. } Add generic marks to the board, or remove them. \\ + \emph {Circle Mode. } Add circle marks to the board, or remove them. \\ + \emph {Square Mode. } Add square marks to the board, or remove them. \\ + \emph {Triangle Mode. } Add triangle marks to the board, or remove them. \\ + \emph {Label Mode. } Add one character labels to the board. Each label + starts at the letter 'a' and each subsequent application of a label will + increment the letter. To remove a label, click on it until it cycles + through the allowed letters and disappears. \\ + \emph {Add/Edit Comment. } Add or edit a comment at the current node. \\ + \emph {Done. } Go back to the previous screen. \\ + + +\end{description} + Index: manual/plugins/main.tex =================================================================== --- manual/plugins/main.tex (revision 19908) +++ manual/plugins/main.tex (working copy) @@ -35,6 +35,8 @@ {\input{plugins/flipit.tex}} +\opt{lcd_bitmap}{\input{plugins/goban.tex}} + \opt{player}{\input{plugins/jackpot.tex}} \opt{lcd_bitmap}{\input{plugins/jewels.tex}} Index: manual/plugins/images/ss-goban-132x80x16.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: manual/plugins/images/ss-goban-132x80x16.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: manual/plugins/images/ss-goban-176x220x16.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: manual/plugins/images/ss-goban-176x220x16.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: manual/plugins/images/ss-goban-160x128x16.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: manual/plugins/images/ss-goban-160x128x16.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: manual/plugins/images/ss-goban-128x128x16.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: manual/plugins/images/ss-goban-128x128x16.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: manual/plugins/images/ss-goban-138x110x2.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: manual/plugins/images/ss-goban-138x110x2.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: manual/plugins/images/ss-goban-320x240x16.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: manual/plugins/images/ss-goban-320x240x16.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: manual/plugins/images/ss-goban-176x132x16.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: manual/plugins/images/ss-goban-176x132x16.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: manual/plugins/images/ss-goban-160x128x1.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: manual/plugins/images/ss-goban-160x128x1.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: manual/plugins/images/ss-goban-240x320x16.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: manual/plugins/images/ss-goban-240x320x16.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: manual/plugins/images/ss-goban-160x128x2.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: manual/plugins/images/ss-goban-160x128x2.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: manual/plugins/images/ss-goban-112x64x1.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: manual/plugins/images/ss-goban-112x64x1.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: manual/plugins/images/ss-goban-128x96x2.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: manual/plugins/images/ss-goban-128x96x2.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: manual/plugins/images/ss-goban-220x176x16.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: manual/plugins/images/ss-goban-220x176x16.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream