diff --git a/apps/plugins/goban/SOURCES b/apps/plugins/goban/SOURCES new file mode 100644 index 0000000000..806c5404f2 --- /dev/null +++ b/apps/plugins/goban/SOURCES @@ -0,0 +1,9 @@ +goban.c +board.c +display.c +game.c +sgf.c +sgf_output.c +sgf_parse.c +sgf_storage.c +util.c diff --git a/apps/plugins/goban/board.c b/apps/plugins/goban/board.c new file mode 100644 index 0000000000..bc6c5347dc --- /dev/null +++ b/apps/plugins/goban/board.c @@ -0,0 +1,354 @@ +/*************************************************************************** + * __________ __ ___. + * 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" + +unsigned int board_width = MAX_BOARD_SIZE; +unsigned int board_height = MAX_BOARD_SIZE; + +/* Board has 'invalid' markers around each border */ +unsigned char 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]; + +unsigned short ko_pos = INVALID_POS; + +/* forward declarations */ +static void setup_marks (void); +static void make_mark (unsigned short pos); +static int get_liberties_helper (unsigned short pos, unsigned char orig_color); +static int flood_fill_helper (unsigned short pos, unsigned char orig_color, + unsigned char 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) +{ + unsigned 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 (unsigned short pos) +{ + board_marks[pos] = current_mark; +} + +static bool +is_marked (unsigned short pos) +{ + return board_marks[pos] == current_mark; +} + +void +clear_board (void) +{ + unsigned int i, x, y; + + /* for the borders */ + 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; + } +} + +unsigned char +get_point_board (unsigned short pos) +{ + return board_data[pos]; +} + +void +set_point_board (unsigned short pos, unsigned char color) +{ + board_data[pos] = color; +} + +bool +on_board (unsigned short 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 (unsigned short pos) +{ + if (!on_board (pos) || get_point_board (pos) == EMPTY) + { + return -1; + } + + setup_marks (); + + int ret_val = 0; + unsigned char 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 (unsigned short pos, unsigned char 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 (unsigned short pos, unsigned char color) +{ + if (!on_board (pos) || get_point_board (pos) == color) + { + return 0; + } + + empty_stack (&parse_stack); + + int ret_val = 0; + + unsigned char 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 (unsigned short pos, unsigned char orig_color, + unsigned char 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 (unsigned short pos, unsigned char color, bool allow_suicide) +{ + /* you can always pass */ + if (pos == PASS_POS) + { + return true; + } + + if (!on_board (pos) || (color != BLACK && color != WHITE)) + { + return false; + } + + 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; + } +} + + +unsigned short +WRAP (unsigned short 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 ((unsigned int) x >= board_width) + { + x = 0; + } + + if (y < 0) + { + y = board_height - 1; + } + else if ((unsigned int) y >= board_height) + { + y = 0; + } + return POS (x, y); + } +} diff --git a/apps/plugins/goban/board.h b/apps/plugins/goban/board.h new file mode 100644 index 0000000000..cd6f01e79a --- /dev/null +++ b/apps/plugins/goban/board.h @@ -0,0 +1,100 @@ +/*************************************************************************** + * __________ __ ___. + * 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 ((unsigned short) -5003) +#define PASS_POS ((unsigned short) -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)) + +unsigned short WRAP (unsigned short pos); + +#define I(pos) (pos % (MAX_BOARD_SIZE + 2) - 1) +#define J(pos) (pos / (MAX_BOARD_SIZE + 2) - 1) + +/* Clear the data from the board, including marks and such. Should be + called after setting the board size */ +void clear_board (void); + +/* Set the size of the board. Follow with a call to clear_board() and a + call to setup_display(). Returns false on failure (generally, invalid + width or height) */ +bool set_size_board (int width, int height); + +/* Returns true if the given move is legal. allow_suicide should be true + if suicide is a valid move with the current ruleset */ +bool legal_move_board (unsigned short pos, unsigned char color, + bool allow_suicide); + +/* Returns true if the pos is on the board */ +bool on_board (unsigned short pos); + +/* Get the color on the board at the given pos. Should be + BLACK/WHITE/EMPTY, and sometimes INVALID. */ +unsigned char get_point_board (unsigned short pos); + +/* Set the color of point at pos, which must be on the board */ +void set_point_board (unsigned short pos, unsigned char color); + +/* Get the number of liberties of the group of which pos is a stone. + Returns less than zero if pos is empty. If the number of liberties of + the group is greater than 2, 2 is returned. */ +int get_liberties_board (unsigned short pos); + +/* A simple flood fill algorithm for capturing or uncapturing stones. + Returns the number of locations changed. */ +int flood_fill_board (unsigned short pos, unsigned char color); + +/* The size of the board */ +extern unsigned int board_width; +extern unsigned int board_height; + +/* The number of captures for each player */ +extern int black_captures; +extern int white_captures; + +/* If there is a ko which cannot be retaken, this is set to the point + which may not be played at. Otherwise this is INVALID_POS. */ +extern unsigned short ko_pos; + +#endif diff --git a/apps/plugins/goban/display.c b/apps/plugins/goban/display.c new file mode 100644 index 0000000000..35a5de45f6 --- /dev/null +++ b/apps/plugins/goban/display.c @@ -0,0 +1,1091 @@ +/*************************************************************************** + * __________ __ ___. + * 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" + +unsigned 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) */ +unsigned short 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 */ +unsigned short last_cursor_pos = INVALID_POS; +unsigned int last_int_size = MAX_INT_SIZE + 1; + +unsigned int min_x_int = 0; +unsigned int min_y_int = 0; +unsigned int num_x_ints = 0; +unsigned int num_y_ints = 0; + +int extend_t = 0, extend_b = 0, extend_l = 0, extend_r = 0; + +bool draw_variations = true; +unsigned int saved_circle_size = 0; +bool has_comment = false; + +unsigned char display_marks[MAX_BOARD_SIZE * MAX_BOARD_SIZE]; + + +/* function prototypes */ + +static int pixel_x (unsigned short pos); +static int pixel_y (unsigned short pos); + +static void draw_circle (int c_x, int c_y, int r, bool filled); + +static void draw_cursor (unsigned short pos); +static void draw_stone_raw (int pixel_x, int pixel_y, bool black); +static void draw_stone (unsigned short pos, bool black); +static void draw_all_stones (void); + +static void draw_hoshi (unsigned short pos); +static void draw_footer (void); +static void draw_all_marks (void); +static void draw_all_hoshi (void); + +static unsigned 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 (unsigned short 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) +{ + unsigned 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_screen_display (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 + + unsigned 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 (unsigned short pos) +{ + return board_x + (I (pos) - min_x_int) * intersection_size; +} + +static int +pixel_y (unsigned short pos) +{ + return board_y + (J (pos) - min_y_int) * intersection_size; +} + + + +void +move_display (unsigned short pos) +{ + if (!on_board (pos)) + { + return; + } + + while ((unsigned) I (pos) >= REAL_MAX_X) + { + cursor_pos = EAST (cursor_pos); + cursor_updated (); + } + while ((unsigned) I (pos) < REAL_MIN_X) + { + cursor_pos = WEST (cursor_pos); + cursor_updated (); + } + + while ((unsigned) J (pos) >= REAL_MAX_Y) + { + cursor_pos = SOUTH (cursor_pos); + cursor_updated (); + } + while ((unsigned) J (pos) < REAL_MIN_Y) + { + cursor_pos = NORTH (cursor_pos); + cursor_updated (); + } +} + + +static void +cursor_updated (void) +{ + if (!on_board(cursor_pos)) + { + cursor_pos = WRAP (cursor_pos); + } + + if (intersection_size != last_int_size || + ((unsigned) I (cursor_pos)) < REAL_MIN_X + 1 || + ((unsigned) I (cursor_pos)) > REAL_MAX_X - 2 || + ((unsigned) J (cursor_pos)) < REAL_MIN_Y + 1 || + ((unsigned) J (cursor_pos)) > REAL_MAX_Y - 2) + { + if ((unsigned) I (cursor_pos) < (num_x_ints / 2)) + { + min_x_int = 0; + } + else + { + min_x_int = min (I (cursor_pos) - (num_x_ints / 2), + board_width - num_x_ints); + } + + if ((unsigned) J (cursor_pos) < (num_y_ints / 2)) + { + min_y_int = 0; + } + else + { + + min_y_int = min (J (cursor_pos) - (num_y_ints / 2), + board_height - num_y_ints); + } + } + + /* 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 unsigned 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 max(int_size, MIN_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) +{ + if (MIN_INT_SIZE >= unzoomed_int_size()) + { + return (MIN_INT_SIZE - unzoomed_int_size ()) / 2 + 1; + } + else + { + return 1; + } +} + +void +set_zoom_display (unsigned int zoom_level) +{ + unsigned int unzoomed = unzoomed_int_size (); + + if (saved_circle_size < MIN_INT_SIZE || + saved_circle_size > MAX_INT_SIZE) + { + saved_circle_size = MIN_DEFAULT_INT_SIZE; + } + + if (zoom_level == 0) + { + /* default zoom, we get to set it however we want */ + intersection_size = max (unzoomed, saved_circle_size); + } + else + { + intersection_size = unzoomed + 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 (unsigned short 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) +{ +#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 (unsigned short pos, bool black) +{ + if (!on_board (pos)) + { + return; + } + + draw_stone_raw (pixel_x (pos), pixel_y (pos), black); +} + + +static void +draw_all_stones (void) +{ + unsigned int x, y; + unsigned short 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 (unsigned short pos) +{ + /* color and drawmode are already set before this function (all lines + and hoshi and stuff are drawn together) */ + + if (!on_board(pos)) + { + return; + } + + if ((unsigned) I (pos) < MIN_X || + (unsigned) I (pos) >= MAX_X || + (unsigned) J (pos) < MIN_Y || + (unsigned) 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); + } +} + + +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)); + + } +} diff --git a/apps/plugins/goban/display.h b/apps/plugins/goban/display.h new file mode 100644 index 0000000000..2f64f1b6ca --- /dev/null +++ b/apps/plugins/goban/display.h @@ -0,0 +1,111 @@ +/*************************************************************************** + * __________ __ ___. + * 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" + +/* Call before using the display */ +void setup_display (void); + +/* Draw the board and the "footer" */ +void draw_screen_display (void); + +/* The location of the cursor */ +extern unsigned short cursor_pos; + +/* True if we should draw variations */ +extern bool draw_variations; + +/* Used to set the zoom level, loaded in from the config file */ +unsigned int saved_circle_size; + +/* the size of one intersection on the board, in pixels */ +unsigned int intersection_size; + +/* Clear the marks from the board */ +void clear_marks_display (void); + +/* Add a mark to the display */ +void set_mark_display (unsigned short pos, unsigned char mark_char); + +/* Set to indicate if we should display the 'C' in the footer or not */ +void set_comment_display (bool new_val); + +/* Move the display so that the position pos is in view, very useful if + we're zoomed in (otherwise does nothing) */ +void move_display (unsigned short pos); + +/* These should all be obvious. Zoom levels start at 1. set_zoom_display + will set the default zoom level if called with zoom_level == 0 */ +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); + +/* MIN and MAX intersection sizes */ +#define MIN_DEFAULT_INT_SIZE (11) + +/* The absolute minimum that is allowed */ +#define MIN_INT_SIZE (3) + +/* Don't allow one bigger than the size of the screen */ +#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 ((min_x_int == 0) ? 0 : min_x_int - 1) +#define MIN_Y (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 (min_x_int) +/* always flush with left, no need for extra */ + +#define MIN_Y ((min_y_int == 0) ? 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 + +/* These are the same as above, except without the extra intersection is + board-boundary directions */ +#define REAL_MIN_X (min_x_int) +#define REAL_MIN_Y (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 diff --git a/apps/plugins/goban/game.c b/apps/plugins/goban/game.c new file mode 100644 index 0000000000..9ecf836f5b --- /dev/null +++ b/apps/plugins/goban/game.c @@ -0,0 +1,236 @@ +/*************************************************************************** + * __________ __ ___. + * 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" +#include "sgf_output.h" +#include "sgf_parse.h" +#include "sgf_storage.h" +#include "display.h" + +static void pre_game_setup (void); + +char save_file[SAVE_FILE_LENGTH]; +bool game_dirty = false; +bool autosave_dirty = false; + +int move_num = 0; + +unsigned char current_player = BLACK; + +struct header_t header; + +void +set_game_modified (void) +{ + game_dirty = true; + autosave_dirty = true; +} + +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..."); + + bool parse_failed = false; + if (!parse_sgf (save_file)) + { + rb->splash (3 * HZ, "Unable to parse SGF file. Will overwrite."); + parse_failed = true; + } + + game_dirty = false; + autosave_dirty = false; + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost (false); +#endif + + if (header.handicap >= 2) + { + current_player = WHITE; + } + + post_game_setup_sgf (); + + if (!parse_failed) + { + draw_screen_display(); + if (rb->strcmp(filename, DEFAULT_SAVE)) + { + metadata_summary(); + } + } + + 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 the game isn't dirty and we're being asked to save to default, + we also don't have to do anything.*/ + if (!game_dirty && + (rb->strcmp (filename, save_file) == 0 || + rb->strcmp (filename, DEFAULT_SAVE) == 0)) + { + return true; + } + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost (true); +#endif + + rb->splash (0, "Saving..."); + + if (output_sgf (filename)) + { + /* saving only "cleans" the game if it's not a save to default, + * or if our save_file is actually default + * + * (so autosaves won't prevent legitimate saves to a Save As or + * loaded file) + */ + if (rb->strcmp (filename, DEFAULT_SAVE) || + rb->strcmp (save_file, DEFAULT_SAVE) == 0) + { + game_dirty = false; + } + + /* but saving anywhere means that autosave isn't dirty */ + autosave_dirty = false; + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost (false); +#endif + /* The save succeeded. Now, if we saved to an actual file (not to the + * DEFAULT_SAVE), then we should delete the DEFAULT_SAVE file because + * the changes stored in it are no longer unsaved. + */ + if (rb->strcmp (filename, DEFAULT_SAVE)) + { + rb->remove(DEFAULT_SAVE); + } + + 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; +} diff --git a/apps/plugins/goban/game.h b/apps/plugins/goban/game.h new file mode 100644 index 0000000000..6a351fb676 --- /dev/null +++ b/apps/plugins/goban/game.h @@ -0,0 +1,59 @@ +/*************************************************************************** + * __________ __ ___. + * 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" + +/* Call whenever anything saveable in the SGF file might have changed */ +void set_game_modified (void); + +/* Setup a new game, clearing anything currently in the SGF tree Returns + false on failure, in which case the old data is NOT guaranteed to be + available anymore */ +bool setup_game (int width, int height, int handicap, int komi); + +/* Load a game from a file, clearing anything currently in the SGF tree + Returns false on failure, in which case the old data is NOT guaranteed + to be available anymore */ +bool load_game (const char *filename); + +/* Save the data in the SGF tree to a file. Returns false on failure */ +bool save_game (const char *filename); + +/* The number of the current move (starts at 0, first move is 1) */ +extern int move_num; + +/* The color of the current_player, either BLACK or WHITE */ +extern unsigned char current_player; +/* Where should we save to if explicitly saved? */ +extern char save_file[]; +/* True if there are unsaved changes in the file */ +extern bool game_dirty; + +/* True if there are changes that have been neither autosaved nor saved */ +extern bool autosave_dirty; + +/* The game metadata */ +extern struct header_t header; + +#endif diff --git a/apps/plugins/goban/goban.c b/apps/plugins/goban/goban.c new file mode 100644 index 0000000000..e5943c0ba6 --- /dev/null +++ b/apps/plugins/goban/goban.c @@ -0,0 +1,1232 @@ +/*************************************************************************** + * __________ __ ___. + * 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" +#include "lib/configfile.h" + +PLUGIN_HEADER + +#include "goban.h" +#include "game.h" +#include "board.h" +#include "display.h" +#include "sgf.h" +#include "sgf_storage.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(unsigned short), 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 enum prop_type_t menu_selection_to_prop (int selection); +static void do_context_menu (void); +static void do_options_menu (void); + +static bool do_comment_edit (void); +static bool do_zoom (void); +static void set_defaults (void); + +bool auto_show_comments = true; +bool disable_shutdown = false; +unsigned int autosave_time = 0; +unsigned int autosave_counter = 0; + +#define SETTINGS_VERSION 2 +#define SETTINGS_MIN_VERSION 1 +#define SETTINGS_FILENAME "goban.cfg" + +static struct configdata config[] = +{ /* INT in MAX_INT_SIZE is intersection, not integer */ + {TYPE_INT, 0, MAX_INT_SIZE, + { .int_p = &saved_circle_size }, + "stone size", NULL}, + {TYPE_BOOL, 0, 1, + { .bool_p = &draw_variations }, + "draw variations", + NULL}, + {TYPE_BOOL, 0, 1, + { .bool_p = &auto_show_comments }, + "auto show comments", + NULL}, + {TYPE_BOOL, 0, 1, + { .bool_p = &disable_shutdown }, + "disable shutdown", + NULL}, + {TYPE_INT, 0, MAX_AUTOSAVE, + { .int_p = &autosave_time }, + "autosave time", + NULL} +}; + +static void +set_defaults (void) +{ + saved_circle_size = 0; + draw_variations = true; + auto_show_comments = true; + + disable_shutdown = false; + autosave_time = 7; +} + +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 +autosave_formatter (char *dest, size_t size, int menu_item, const char * +unknown) +{ + (void) unknown; + if (menu_item == 0) + { + rb->snprintf (dest, size, "Off"); + } + else + { + rb->snprintf (dest, size, "%d minute%s", menu_item, + menu_item == 1 ? "" : "s"); + } +} + +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 (parameter) + { + rb->splashf (2 * HZ, "Loading %s failed.", (char *) parameter); + } + + if (!load_game (DEFAULT_SAVE)) + { + rb->strcpy (save_file, DEFAULT_SAVE); + + if (!setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, 0)) + { + return PLUGIN_ERROR; + } + } + } + else + { + /* game loaded */ + if (rb->strcmp (save_file, DEFAULT_SAVE)) + { + /* delete the scratch file if we loaded a game and it wasn't + * from the scratch file + */ + rb->remove (DEFAULT_SAVE); + } + } + + draw_screen_display (); + + autosave_counter = 0; + for (;;) + { + btn = rb->button_get_w_tmo (HZ * 30); + + if (disable_shutdown) + { + /* tell rockbox we're not idle */ + rb->reset_poweroff_timer (); + } + + bool is_idle = false; + + 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_screen_display (); + } + else + { + nav_mode = NAV_MODE_TREE; + rb->splash (2 * HZ / 3, "tree navigation mode"); + draw_screen_display (); + } + 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_screen_display (); + } + 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_screen_display (); + } + 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"); + } + } + else if (play_mode == MODE_ADD_BLACK) + { + if (!add_stone_sgf (cursor_pos, BLACK)) + { + rb->splash (HZ / 3, "Illegal"); + } + } + else if (play_mode == MODE_ADD_WHITE) + { + if (!add_stone_sgf (cursor_pos, WHITE)) + { + rb->splash (HZ / 3, "Illegal"); + } + } + else if (play_mode == MODE_REMOVE) + { + if (!add_stone_sgf (cursor_pos, EMPTY)) + { + rb->splash (HZ / 3, "Illegal"); + } + } + 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"); + } + + draw_screen_display (); + 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_screen_display (); + } + } + else + { +#endif + cursor_pos = WRAP (EAST (cursor_pos)); + draw_screen_display (); +#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_screen_display (); + } + } + else + { +#endif + cursor_pos = WRAP (WEST (cursor_pos)); + draw_screen_display (); +#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_screen_display (); + break; + + case GBN_BUTTON_UP: + case GBN_BUTTON_UP | BUTTON_REPEAT: + cursor_pos = WRAP (NORTH (cursor_pos)); + draw_screen_display (); + break; + + case GBN_BUTTON_MENU: + if (do_main_menu ()) + { + save_game (DEFAULT_SAVE); + + global_cleanup (); + return PLUGIN_OK; + } + + draw_screen_display (); + break; + +#if defined(GBN_BUTTON_CONTEXT) + case GBN_BUTTON_CONTEXT: + do_context_menu (); + draw_screen_display (); + 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_screen_display (); + rb->splashf (2 * HZ / 3, "%d of %d", temp, + num_variations_sgf ()); + draw_screen_display (); + } + else + { + if (num_variations_sgf () > 1) + { + rb->splashf (HZ, "Error %d in next_variation_sgf", temp); + } + draw_screen_display (); + } + break; +#endif + + case BUTTON_NONE: + is_idle = true; + default: + if (rb->default_event_handler (btn) == SYS_USB_CONNECTED) + { + return PLUGIN_USB_CONNECTED; + } + break; + }; + + if (is_idle && autosave_dirty) + { + ++autosave_counter; + + if (autosave_time != 0 && + autosave_counter / 2 >= autosave_time) + /* counter is in 30 second increments, autosave_time is in + * minutes + */ + { + DEBUGF("autosaving\n"); + rb->splash(HZ / 4, "Autosaving..."); + save_game(DEFAULT_SAVE); + draw_screen_display(); + autosave_counter = 0; + } + } + else + { + autosave_counter = 0; + } + } + + return PLUGIN_OK; +} + +static void +global_cleanup (void) +{ + cleanup_sgf (); + configfile_save(SETTINGS_FILENAME, config, + sizeof(config)/sizeof(*config), + SETTINGS_VERSION); +} + +static void +global_setup (void) +{ + setup_stack (&parse_stack, parse_stack_buffer, + sizeof (parse_stack_buffer)); + setup_display (); + setup_sgf (); + + set_defaults(); + if (configfile_load(SETTINGS_FILENAME, config, + sizeof(config)/sizeof(*config), + SETTINGS_MIN_VERSION ) < 0) + { + /* If the loading failed, save a new config file (as the disk is + already spinning) */ + + /* set defaults again just in case (don't know if they can ever + * be messed up by configfile_load, and it's basically free anyway) + */ + set_defaults(); + + configfile_save(SETTINGS_FILENAME, config, + sizeof(config)/sizeof(*config), + SETTINGS_VERSION); + } +} + +enum main_menu_selections +{ + MAIN_NEW = 0, + MAIN_SAVE, + MAIN_SAVE_AS, + MAIN_GAME_INFO, + MAIN_PLAYBACK, + MAIN_ZOOM, + MAIN_OPTIONS, + MAIN_CONTEXT, + MAIN_QUIT +}; + +static bool +do_main_menu (void) +{ + int selection = 0; + MENUITEM_STRINGLIST (menu, "Rockbox Goban", NULL, + "New", + "Save", + "Save As", + "Game Info", + "Playback Control", + "Zoom Level", + "Options", + "Context Menu", + "Quit"); + + /* for "New" in menu */ + int new_handi = 0, new_bs = MAX_BOARD_SIZE, new_komi = 15; + + char new_save_file[SAVE_FILE_LENGTH]; + + + bool done = false; + + while (!done) + { + selection = rb->do_menu (&menu, &selection, NULL, false); + + switch (selection) + { + case MAIN_NEW: + rb->set_int ("board size", "lines", UNIT_INT, + &new_bs, NULL, 1, MIN_BOARD_SIZE, MAX_BOARD_SIZE, + NULL); + + 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_screen_display (); + done = true; + break; + + case MAIN_SAVE: + if (!save_game (save_file)) + { + rb->splash (2 * HZ, "Save Failed!"); + } + else + { + rb->splash (2 * HZ / 3, "Saved"); + } + done = true; + draw_screen_display (); + break; + + case MAIN_SAVE_AS: + rb->strcpy (new_save_file, save_file); + + if (!rb->kbd_input (new_save_file, SAVE_FILE_LENGTH)) + { + break; + } + + if (!save_game (new_save_file)) + { + rb->splash (2 * HZ, "Save Failed!"); + } + else + { + rb->strcpy (save_file, new_save_file); + rb->splash (2 * HZ / 3, "Saved"); + } + + done = true; + draw_screen_display (); + break; + + case MAIN_GAME_INFO: + do_gameinfo_menu (); + break; + + case MAIN_PLAYBACK: + if (!audio_stolen_sgf ()) + { + playback_control (NULL); + } + else + { + rb->splash (1 * HZ, "Audio has been disabled!"); + } + break; + + case MAIN_ZOOM: + if (do_zoom ()) + { + return true; + } + done = true; + draw_screen_display (); + break; + + case MAIN_OPTIONS: + do_options_menu(); + break; + + case MAIN_CONTEXT: + do_context_menu (); + done = true; + break; + + case MAIN_QUIT: + case MENU_ATTACHED_USB: + 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_screen_display (); + 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"); + saved_circle_size = intersection_size; + 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; +} + +enum gameinfo_menu_selections +{ + GINFO_BASIC_INFO = 0, + GINFO_TIME_LIMIT, + GINFO_OVERTIME, + GINFO_RESULT, + GINFO_HANDICAP, + GINFO_KOMI, + GINFO_RULESET, + GINFO_BLACK_PLAYER, + GINFO_BLACK_RANK, + GINFO_BLACK_TEAM, + GINFO_WHITE_PLAYER, + GINFO_WHITE_RANK, + GINFO_WHITE_TEAM, + GINFO_DATE, + GINFO_EVENT, + GINFO_PLACE, + GINFO_ROUND, + GINFO_DONE +}; + + +static void +do_gameinfo_menu (void) +{ + MENUITEM_STRINGLIST (gameinfo_menu, "Game Info", NULL, + "Basic Info", + "Time Limit", + "Overtime", + "Result", + "Handicap", + "Komi", + "Ruleset", + "Black Player", + "Black Rank", + "Black Team", + "White Player", + "White Rank", + "White Team", + "Date", + "Event", + "Place", + "Round", + "Done"); + /* IMPORTANT: + * + * if you edit this string list, make sure you keep + * menu_selection_to_prop function in line with it!! (see the bottom + * of this file). + */ + + int new_ruleset = 0; + + char *gameinfo_string; + int gameinfo_string_size; + + bool done = false; + int selection = 0; + + while (!done) + { + selection = rb->do_menu (&gameinfo_menu, &selection, NULL, false); + + switch (selection) + { + case GINFO_OVERTIME: + case GINFO_RESULT: + case GINFO_BLACK_PLAYER: + case GINFO_BLACK_RANK: + case GINFO_BLACK_TEAM: + case GINFO_WHITE_PLAYER: + case GINFO_WHITE_RANK: + case GINFO_WHITE_TEAM: + case GINFO_DATE: + case GINFO_EVENT: + case GINFO_PLACE: + case GINFO_ROUND: + if (!get_header_string_and_size (&header, + menu_selection_to_prop + (selection), &gameinfo_string, + &gameinfo_string_size)) + { + rb->splash (3 * HZ, "Couldn't get header string"); + break; + } + + rb->kbd_input (gameinfo_string, gameinfo_string_size); + sanitize_string (gameinfo_string); + set_game_modified(); + break; + + /* these need special handling in some way, so they are + separate */ + + case GINFO_BASIC_INFO: + metadata_summary(); + break; + + case GINFO_TIME_LIMIT: + rb->set_int ("Time Limit", "", UNIT_INT, &header.time_limit, + NULL, 60, 0, 24 * 60 * 60, &time_formatter); + set_game_modified(); + break; + + case GINFO_HANDICAP: + rb->splashf (0, "%d stones. Start a new game to set handicap", + header.handicap); + rb->action_userabort(TIMEOUT_BLOCK); + break; + + case GINFO_KOMI: + rb->set_int ("Komi", "moku", UNIT_INT, &header.komi, NULL, + 1, -300, 300, &komi_formatter); + set_game_modified(); + break; + + case GINFO_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]); + set_game_modified(); + break; + + case GINFO_DONE: + case GO_TO_ROOT: + case GO_TO_PREVIOUS: + case MENU_ATTACHED_USB: + default: + done = true; + break; + }; + } +} + +enum context_menu_selections +{ + CTX_PLAY = 0, + CTX_ADD_BLACK, + CTX_ADD_WHITE, + CTX_ERASE, + CTX_PASS, + CTX_NEXT_VAR, + CTX_FORCE, + CTX_MARK, + CTX_CIRCLE, + CTX_SQUARE, + CTX_TRIANGLE, + CTX_LABEL, + CTX_COMMENT, + CTX_DONE +}; + +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 CTX_PLAY: + play_mode = MODE_PLAY; + done = true; + break; + + case CTX_ADD_BLACK: + play_mode = MODE_ADD_BLACK; + done = true; + break; + + case CTX_ADD_WHITE: + play_mode = MODE_ADD_WHITE; + done = true; + break; + + case CTX_ERASE: + play_mode = MODE_REMOVE; + done = true; + break; + + case CTX_PASS: + if (!play_move_sgf (PASS_POS, current_player)) + { + rb->splash (HZ, "Error while passing!"); + } + done = true; + break; + + case CTX_NEXT_VAR: + if ((temp = next_variation_sgf ()) >= 0) + { + draw_screen_display (); + rb->splashf (2 * HZ / 3, "%d of %d", temp, + num_variations_sgf ()); + draw_screen_display (); + } + 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 CTX_FORCE: + play_mode = MODE_FORCE_PLAY; + done = true; + break; + + case CTX_MARK: + play_mode = MODE_MARK; + done = true; + break; + + case CTX_CIRCLE: + play_mode = MODE_CIRCLE; + done = true; + break; + + case CTX_SQUARE: + play_mode = MODE_SQUARE; + done = true; + break; + + case CTX_TRIANGLE: + play_mode = MODE_TRIANGLE; + done = true; + break; + + case CTX_LABEL: + play_mode = MODE_LABEL; + done = true; + break; + + case CTX_COMMENT: + if (!do_comment_edit ()) + { + DEBUGF ("Editing comment failed\n"); + rb->splash (HZ, "Read or write failed!\n"); + } + done = true; + break; + + case CTX_DONE: + case GO_TO_ROOT: + case GO_TO_PREVIOUS: + case MENU_ATTACHED_USB: + default: + done = true; + break; + }; + } +} + +enum options_menu_selections +{ + OMENU_SHOW_VARIATIONS = 0, + OMENU_DISABLE_POWEROFF, + OMENU_AUTOSAVE_TIME, + OMENU_AUTO_COMMENT +}; + +static void +do_options_menu (void) +{ + int selection; + bool done = false; + + MENUITEM_STRINGLIST (options_menu, "Options Menu", NULL, + "Show Child Variations?", + "Disable Idle Poweroff?", + "Idle Autosave Time", + "Automatically Show Comments?"); + + while (!done) + { + selection = rb->do_menu (&options_menu, &selection, NULL, false); + + switch (selection) + { + case OMENU_SHOW_VARIATIONS: + rb->set_bool("Draw Variations?", &draw_variations); + clear_marks_display (); + set_all_marks_sgf (); + if (draw_variations) + { + mark_child_variations_sgf (); + } + break; + + case OMENU_DISABLE_POWEROFF: + rb->set_bool("Disable Idle Poweroff?", &disable_shutdown); + break; + + case OMENU_AUTOSAVE_TIME: + rb->set_int("Idle Autosave Time", "minutes", UNIT_INT, + &autosave_time, NULL, 1, 0, MAX_AUTOSAVE, + &autosave_formatter); + autosave_counter = 0; + break; + + case OMENU_AUTO_COMMENT: + rb->set_bool("Auto Show Comments?", &auto_show_comments); + break; + + case GO_TO_ROOT: + case GO_TO_PREVIOUS: + case MENU_ATTACHED_USB: + default: + done = true; + break; + }; + } + +} + +static bool +do_comment_edit (void) +{ + char cbuffer[512]; + + 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 GINFO_OVERTIME: + return PROP_OVERTIME; + case GINFO_RESULT: + return PROP_RESULT; + case GINFO_BLACK_PLAYER: + return PROP_BLACK_NAME; + case GINFO_BLACK_RANK: + return PROP_BLACK_RANK; + case GINFO_BLACK_TEAM: + return PROP_BLACK_TEAM; + case GINFO_WHITE_PLAYER: + return PROP_WHITE_NAME; + case GINFO_WHITE_RANK: + return PROP_WHITE_RANK; + case GINFO_WHITE_TEAM: + return PROP_WHITE_TEAM; + case GINFO_DATE: + return PROP_DATE; + case GINFO_EVENT: + return PROP_EVENT; + case GINFO_PLACE: + return PROP_PLACE; + case GINFO_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 */ + }; +} diff --git a/apps/plugins/goban/goban.h b/apps/plugins/goban/goban.h new file mode 100644 index 0000000000..84866d5b47 --- /dev/null +++ b/apps/plugins/goban/goban.h @@ -0,0 +1,277 @@ +/*************************************************************************** + * __________ __ ___. + * 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 + +/* Enable this to run test mode. (see the end of util.c) */ +#if 0 +#define GBN_TEST +#endif + +#include "types.h" +#include "util.h" + + +/* Colors of various things. The colors on mono bitmap targets is fixed + based on the background/foreground color. */ +#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 +#endif + +/* Key setups */ +#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 +#define GBN_BUTTON_NEXT_VAR BUTTON_REC + +#elif (CONFIG_KEYPAD == GIGABEAT_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 +#define GBN_BUTTON_NEXT_VAR BUTTON_A + +#elif (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 +#define GBN_BUTTON_NEXT_VAR BUTTON_PLAY + +#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 +#define GBN_BUTTON_NEXT_VAR BUTTON_REC + +#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_DISPLAY +#define GBN_BUTTON_PLAY BUTTON_SELECT | BUTTON_REL +#define GBN_BUTTON_CONTEXT BUTTON_SELECT | BUTTON_REPEAT +#define GBN_BUTTON_NEXT_VAR BUTTON_POWER + +#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_F3 +#define GBN_BUTTON_MENU BUTTON_F2 +#define GBN_BUTTON_PLAY BUTTON_PLAY | BUTTON_REL +#define GBN_BUTTON_CONTEXT BUTTON_PLAY | BUTTON_REPEAT +#define GBN_BUTTON_NEXT_VAR BUTTON_ON + +#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 + + +/* The smallest dimension of the LCD */ +#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 + + +/* The directory we default to for saving crap */ +#define DEFAULT_SAVE_DIR "/sgf" + +/* The default file we save to */ +#define DEFAULT_SAVE (DEFAULT_SAVE_DIR "/gbn_def.sgf") + +/* The size of the buffer we store filenames in (1 reserved for '\0') */ +#define SAVE_FILE_LENGTH 256 + +/* The maximum setting for idle autosave time, in minutes */ +#define MAX_AUTOSAVE (30) + +/* On mono targets, draw while stones with a black outline so they are + actually visibile instead of being white on white */ +#if (LCD_DEPTH == 1) +#define OUTLINE_STONES +#endif + +/* The current play mode */ +extern enum play_mode_t play_mode; + +/* Show comments when redoing onto a move? */ +extern bool auto_show_comments; + +/* A stack used for parsing/outputting as well as some board functions + such as counting liberties and filling in/ removing stones */ +extern struct stack_t parse_stack; + +#endif diff --git a/apps/plugins/goban/goban.make b/apps/plugins/goban/goban.make new file mode 100644 index 0000000000..f3f96ff5c6 --- /dev/null +++ b/apps/plugins/goban/goban.make @@ -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) diff --git a/apps/plugins/goban/sgf.c b/apps/plugins/goban/sgf.c new file mode 100644 index 0000000000..ad6e4a4e05 --- /dev/null +++ b/apps/plugins/goban/sgf.c @@ -0,0 +1,2237 @@ +/*************************************************************************** + * __________ __ ___. + * 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 "sgf_storage.h" +#include "types.h" +#include "goban.h" +#include "board.h" +#include "display.h" +#include "game.h" +#include "util.h" + +int sgf_fd = -1; +int unhandled_fd = -1; + +int tree_head = -1; +int current_node = -1; +int start_node = -1; + +bool header_marked = false; + +static int add_child_variation (int *variation_number); + +static int get_move_from_node (int handle); + +static bool is_important_node (int handle); +static bool goto_next_important_node (bool forward); +static bool retreat_node (void); +static bool advance_node (void); + +static bool do_add_stones (void); +static void setup_handicap_helper (unsigned short pos); + +static int undo_node_helper (void); + +static void set_one_mark (unsigned short pos, enum prop_type_t type); +static void set_label_mark (unsigned short pos, char to_set); + +bool +play_move_sgf (unsigned short pos, unsigned char 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); */ + rb->splash (2 * HZ, + "Out of memory led to invalid state. Please exit."); + current_node = saved; + return false; + } + + set_game_modified(); + + temp = get_matching_child_sgf (pos, color); + } + + /* 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 +add_mark_sgf (unsigned short pos, enum prop_type_t type) +{ + 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 (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 (unsigned short pos, unsigned char 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; + int temp; + + if (!on_board (pos)) + { + return false; + } + + if (color == get_point_board (pos)) + { + return false; + } + + if ((!is_important_node (current_node) || + (current_node == start_node && get_move_sgf () < 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) || + get_node (current_node)->props < 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); + } + + set_game_modified(); + + 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; + } + + set_game_modified(); + + /* 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; +} + +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; + } + + set_all_marks_sgf (); + + /* 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; + unsigned short move_pos = temp_move->data.position; + unsigned char 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; +} + +bool +redo_node_sgf (void) +{ + if (!advance_node ()) + { + return false; + } + + set_all_marks_sgf (); + + int temp_move = get_move_sgf (); + if (temp_move >= 0) + { + struct prop_t *move_prop = get_prop (temp_move); + unsigned short pos = move_prop->data.position; + unsigned char 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); + + goto redo_node_sgf_succeeded; + } + else if (do_add_stones ()) + { + goto redo_node_sgf_succeeded; + } + + return false; + char comment_buffer[512]; + +redo_node_sgf_succeeded: +#if !defined(GBN_TEST) + if (auto_show_comments && + read_comment_sgf (comment_buffer, sizeof (comment_buffer))) + { + unsigned int i; + for (i = 0; i < sizeof (comment_buffer); ++i) + { + /* newlines display badly in rb->splash, so replace them + * with spaces + */ + if (comment_buffer[i] == '\n') + { + comment_buffer[i] = ' '; + } + else if (comment_buffer[i] == '\0') + { + break; + } + } + draw_screen_display(); + rb->splash(HZ / 3, comment_buffer); + rb->button_clear_queue(); + rb->action_userabort(TIMEOUT_BLOCK); + } +#else + (void) comment_buffer; +#endif + + return true; +} + +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; +} + +void +set_all_marks_sgf (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 (unsigned short 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 (unsigned short pos, char to_set) +{ + set_mark_display (pos, to_set | (1 << 7)); +} + +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 (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; + } +} + +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 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; + + set_game_modified(); + + 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); + set_comment_display (true); + return bytes_written; +} + +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 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; +} + +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 (unsigned short pos, unsigned char color) +{ + struct node_t *temp_node; + struct prop_t *temp_prop; + 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 (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 +is_handled_sgf (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 || type == PROP_VARIATION_TYPE) + { + return true; + } + + return false; +} + +void +setup_handicap_sgf (void) +{ + union prop_data_t temp_data; + + if (header.handicap <= 1) + { + return; + } + + 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, + "Use the 'Add Black' tool to add %d handicap stones!", + 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 (unsigned short 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; +} diff --git a/apps/plugins/goban/sgf.h b/apps/plugins/goban/sgf.h new file mode 100644 index 0000000000..d2aca81ebb --- /dev/null +++ b/apps/plugins/goban/sgf.h @@ -0,0 +1,170 @@ +/*************************************************************************** + * __________ __ ___. + * 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" + +/* Play one move. If play_mode is not PLAY_MODE_FORCE, this will fail if + an illegal move is played (plays by the "wrong" color are not counted + as illegal. pos may be a POS() on the board, or PASS_POS. Returns true + if the move was successfully played. */ +bool play_move_sgf (unsigned short pos, unsigned char color); + +/* Add a stone to the board, or remove one if color == EMPTY. returns true + if the stone is successfully added/removed (will return false if there + is nothing to add/remove there, eg. when adding a black stone when a + black stone is already there) */ +bool add_stone_sgf (unsigned short pos, unsigned char color); + +/* Add a mark to the board at the current node pos must be on the board + Returns false on failure. */ +bool add_mark_sgf (unsigned short pos, enum prop_type_t type); + +/* Returns true if there are more nodes before or after (respectively) the + current node */ +bool has_prev_nodes_sgf (void); +bool has_more_nodes_sgf (void); + +/* When at the start of a branch, this will follow one of the branches. + Variations are numbered from 0. Returns false on failure. */ +bool go_to_variation_sgf (unsigned int num); + +/* Get the number of variations at the current move (will be 1 unless the + current node is a branch node */ +int num_variations_sgf (void); + +/* Calls into the display subsystem to mark any child variations of the + current node. Returns the number of variations marked */ +int mark_child_variations_sgf (void); + +/* Calls into the display subsystem to pass marks to be drawn on the board + for the current node. Does not do child variation marks. */ +void set_all_marks_sgf (void); + +/* Add a child regardless of if there is a child node already or not. + *variation_number will be set to the variation number of the added + variation Returns the handle of the new node. */ +int add_child_sgf (int *variation_number); + +/* Goes to the next variation after the current one if the current node is + a branch node. Returns the number of the new variation */ +int next_variation_sgf (void); + +/* ints in these are handles to storage locations, bools mean failure if + false */ +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); + +/* If there is already a property of the same type, it will be + overwritten, otherwise a new one is added Returns the handle of the + added property. */ +int add_or_set_prop_sgf (int node, enum prop_type_t type, + union prop_data_t data); + +/* Find a property of similar type with the same position in the current + node. (for example, if type == PROP_ADD_BLACK and pos == POS(0, 0), it + will find a prop with type == PROP_ADD_WHITE and pos == POS(0, 0), but + not any of the mark types with pos == POS(0, 0). returns the handle of + the found property */ +int get_prop_pos_sgf (enum prop_type_t type, union prop_data_t data); + +/* If there is a move in the current node, return its handle. */ +int get_move_sgf (void); + +/* If there is a comment in the current node, this will read it out into + the buffer. Returns the size of the comment read (including the '\0'). + The buffer can be treated as a string. */ +int read_comment_sgf (char *buffer, size_t buffer_size); + +/* Write a comment property to the current node. This will overwrite any + comment that currently exists. Returns the number of characters + written. */ +int write_comment_sgf (char *string); + +/* Move forward or back in the SGF tree, following any chosen variations + (variations are "chosen" every time you go through one) These will + update the board showing any moves/added or removed stones/ marks/etc. + . */ +bool undo_node_sgf (void); +bool redo_node_sgf (void); + +/* Returns true if the SGF property type is handled in some way. For + * real SGF properties (in other words, ones that can actually be read from + * a file, not psuedo-properties), if they are unhandled that just means that + * we copy them verbatim from the old file to the new, keeping them with the + * correct node + */ +bool is_handled_sgf (enum prop_type_t type); + +/* Sets up the handicap on the board (based on header.handicap) */ +void setup_handicap_sgf (void); + +/* Goes to the start of a handicap game. */ +void goto_handicap_start_sgf (void); + +/* Must be called after setting up a new game, either blank or loaded from + a file. (Picks a place to put the header properties if none was found, + and may do other stuff) */ +bool post_game_setup_sgf (void); + +/* Get the child that matches the given move. + * + * Returns the variation number of the matching move, or negative if + * none is found. + */ +int get_matching_child_sgf (unsigned short pos, unsigned char color); + +#define NO_NODE (-1) +#define NO_PROP (-1) + +/* These flags are used in undo handling for moves and added/removed + stones */ +#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)) + +#define MIN_STORAGE_BUFFER_SIZE 200 +#define UNHANDLED_PROP_LIST_FILE (PLUGIN_GAMES_DIR "/gbn_misc.bin") + +/* Handle of the current node, the start of the game, and the root + * of the tree + */ +extern int current_node; +extern int tree_head; +extern int start_node; + +extern int sgf_fd; +extern int unhandled_fd; + +/* true if the header location has already been marked in the current + game, false otherwise */ +extern bool header_marked; + +#endif diff --git a/apps/plugins/goban/sgf_output.c b/apps/plugins/goban/sgf_output.c new file mode 100644 index 0000000000..e798dcd510 --- /dev/null +++ b/apps/plugins/goban/sgf_output.c @@ -0,0 +1,433 @@ +/*************************************************************************** + * __________ __ ___. + * 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 "sgf_output.h" +#include "sgf.h" +#include "sgf_storage.h" +#include "util.h" +#include "board.h" +#include "game.h" + +static void pos_to_sgf (unsigned short pos, char *buffer); + +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 void output_header_props (void); +static bool output_header_helper (enum prop_type_t type); +static int stupid_num_variations (void); + +bool +output_sgf (const 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, ';'); + + 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_sgf (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; +} + +static void +pos_to_sgf (unsigned short 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 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; +} diff --git a/apps/plugins/goban/sgf_output.h b/apps/plugins/goban/sgf_output.h new file mode 100644 index 0000000000..dfc6319797 --- /dev/null +++ b/apps/plugins/goban/sgf_output.h @@ -0,0 +1,30 @@ +/*************************************************************************** + * __________ __ ___. + * 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_OUTPUT_H +#define GOBAN_SGF_OUTPUT_H + +#include "types.h" + +/* Do the actual outputting of an SGF file. Return false on failure */ +bool output_sgf (const char *filename); + +#endif diff --git a/apps/plugins/goban/sgf_parse.c b/apps/plugins/goban/sgf_parse.c new file mode 100644 index 0000000000..e0fa8fd2df --- /dev/null +++ b/apps/plugins/goban/sgf_parse.c @@ -0,0 +1,857 @@ +/*************************************************************************** + * __________ __ ___. + * 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 "sgf_parse.h" +#include "sgf.h" +#include "sgf_storage.h" +#include "util.h" +#include "board.h" +#include "game.h" + +static void handle_prop_value (enum prop_type_t type); +static int read_prop_value (char *buffer, size_t buffer_size); +static void do_range (enum prop_type_t type, unsigned short ul, + unsigned short br); +static void parse_prop (void); +static void parse_node (void); +static enum prop_type_t parse_prop_type (void); + +static unsigned short sgf_to_pos (char *buffer); + +bool +parse_sgf (const 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); + + + /* 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) + { + int temp = add_child_sgf (NULL); + + if (temp >= 0) + { + current_node = temp; + } + else + { + rb->splash (2 * HZ, "Out of memory while parsing!"); + return false; + } + } + + + 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) + { + 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 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; +} + +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 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; + unsigned short 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_sgf (type) || type == PROP_COMMENT) + { + /* DEBUGF("unhandled prop %d\n", (int) type); */ + rb->lseek (sgf_fd, start_of_prop, SEEK_SET); + + 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); + } + 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"); + + 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, "Wrong SGF file version! Parsing stopped."); + DEBUGF ("can't handle file format %c\n", buffer[0]); + + close_file (&sgf_fd); + } + } + else if (type == PROP_APPLICATION || + type == PROP_CHARSET || type == PROP_VARIATION_TYPE) + { + /* we don't care. 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 + { + 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; + 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)) + { + rb->splash (5 * HZ, + "Error getting header string. Report this."); + } + else + { + temp = read_prop_value (temp_buffer, temp_size - 1); +#if 0 + DEBUGF ("read %d bytes into header for type: %d\n", temp, type); + DEBUGF ("data: %s\n", temp_buffer); +#endif + } + } + 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 (3 * HZ, + "A SGF prop was not dealt with. Please report this"); + read_prop_value (NULL, 0); + } +} + + + +/* upper-left and bottom right */ +static void +do_range (enum prop_type_t type, unsigned short ul, unsigned short 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 unsigned short +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'); +} + diff --git a/apps/plugins/goban/sgf_parse.h b/apps/plugins/goban/sgf_parse.h new file mode 100644 index 0000000000..869d343bd7 --- /dev/null +++ b/apps/plugins/goban/sgf_parse.h @@ -0,0 +1,30 @@ +/*************************************************************************** + * __________ __ ___. + * 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_PARSE_H +#define GOBAN_SGF_PARSE_H + +#include "types.h" + +/* Do the actual parsing of an SGF file. Return false on failure */ +bool parse_sgf (const char *filename); + +#endif diff --git a/apps/plugins/goban/sgf_storage.c b/apps/plugins/goban/sgf_storage.c new file mode 100644 index 0000000000..1c92625f7d --- /dev/null +++ b/apps/plugins/goban/sgf_storage.c @@ -0,0 +1,493 @@ + +/*************************************************************************** + * __________ __ ___. + * 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 "sgf_storage.h" +#include "sgf.h" +#include "util.h" + +#define ALIGNMENT_VAL (sizeof (union storage_t)) + +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; + +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); + +#if 0 +static void debugf_current_node (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"); +} +#endif + +static void +clear_storage_buffer (int index) +{ + int temp; + + + /* everything starts free */ + rb->memset (storage_free_list[index], + (unsigned char) 0xFF, + storage_free_list_size[index]); + + /* 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 (); + + if (tree_head < 0) + { + rb->splash (5 * HZ, + "Error allocating first node! Please exit immediately."); + } + + 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; + + 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 handle = next_free_handle; + unsigned int buffer_index = next_free_handle_buffer; + + /* so we know where we started, to prevent infinite loop */ + unsigned int start_handle = handle; + unsigned int start_buffer = buffer_index; + + + 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); + + 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. 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; + } + + temp_buffer = align_buffer (temp_buffer, &size); + if (!temp_buffer || !size) + { + return false; + } + + /* 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; + + temp_buffer = align_buffer (temp_buffer, &size); + if (!temp_buffer || !size) + { + return false; + } + + if (size < storage_free_list_size[index]) + { + DEBUGF ("Big problem on line %d in sgf.c\n", __LINE__); + rb->splashf (5 * HZ, + "Error in allocating storage buffer! Exit and report this!\n"); + return false; + } + + storage_free_list[index] = temp_buffer; + total_storage_size += storage_buffer_size[index]; + storage_initialized[index] = true; + + clear_storage_buffer (index); + + return true; +} + +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); +} + +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); +} + diff --git a/apps/plugins/goban/sgf_storage.h b/apps/plugins/goban/sgf_storage.h new file mode 100644 index 0000000000..4835c8c9d2 --- /dev/null +++ b/apps/plugins/goban/sgf_storage.h @@ -0,0 +1,57 @@ +/*************************************************************************** + * __________ __ ___. + * 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_STORAGE_H +#define GOBAN_SGF_STORAGE_H + +#include "types.h" + +/* Must be called (and return true) before using anything in the SGF + subsystem returns false on failure */ +bool setup_sgf (void); + +/* Do cleanup, call before exiting the plugin. You must not use any SGF + subsystem functions after calling this */ +void cleanup_sgf (void); + +/* Get ready for a new game (either loaded or blank) */ +void clear_caches_sgf (void); + +/* Clear the SGF tree and get it ready for a new game (loaded or blank) */ +void free_tree_sgf (void); + +/* Returns true if the Rockbox audio buffer has been stolen */ +bool audio_stolen_sgf (void); + +/* Returns a handle to a struct storage_t (NOT a pointer) < 0 handles are + invalid */ +int alloc_storage_sgf (void); + +/* Free one storage location */ +void free_storage_sgf (int handle); + +/* Get a pointer to a node or property which corresponds to the given + * storage handle + */ +struct node_t *get_node (int handle); +struct prop_t *get_prop (int handle); + +#endif diff --git a/apps/plugins/goban/types.h b/apps/plugins/goban/types.h new file mode 100644 index 0000000000..216d41bc21 --- /dev/null +++ b/apps/plugins/goban/types.h @@ -0,0 +1,289 @@ +/*************************************************************************** + * __________ __ ___. + * 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" + +/* A generic stack sp is the stack pointer (0 for an empty stack) */ +struct stack_t +{ + size_t size; + size_t sp; + char *buffer; +}; + +/* All of the types of SGF properties that we understand. Not all of these + are handled, even if we understand them, unhandled properties are + handled differently (basically they are copied to a secondary location + and copied back if we output the tree) The ones at the end aren't real + SGF properties, they are only to help us in parsing/outputting/keeping + track of variations/etc. IMPORTANT: if you edit this, you must be + careful to keep it in line with prop_names, PROP_NAMES_SIZE, and + PROP_NAME_LEN otherwise really bad things will happen. There is too much + information on each of these to list here, please see + http://www.red-bean.com/sgf/ */ +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 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 overtime period */ + PROP_WHITE_STONES_LEFT, /* same for white */ + + + /* 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, /* used for branches */ + PROP_INVALID, + PROP_GENERIC_UNHANDLED, /* used to mark the place where an + unhandled property was copied to + secondary storage (so we can output it + again when we output the game tree) */ + PROP_ANY, /* Used as a parameter when any property + type is supposed to match */ + PROP_VARIATION_TO_PROCESS, /* Used in parsing/outputting */ + PROP_VARIATION_CHOICE, /* Used to store which variation we should + follow when we get to a branch */ + PROP_ROOT_PROPS /* Marks the place where we should output + the information from struct header_t + header */ +}; + +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 next part assumes that they 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) + +/* The only one character property names are the moves, everything else is + two characters */ +#define PROP_NAME_LEN(type) ((type == PROP_BLACK_MOVE || \ + type == PROP_WHITE_MOVE) ? 1 : 2) + +/* Data for a property. Can be a number, a node handle (for variations), + or a position and either a short or a character (the extras are for + label characters, and stone undo data (moves and added/removed)) */ +union prop_data_t +{ + unsigned int number; + int node; + struct + { + unsigned short position; + union + { + unsigned short stone_extra; + unsigned char label_extra; + }; + }; +}; + +/* What should happen when the user "plays" a move */ +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 +}; + +/* Different types of board marks */ +enum mark_t +{ + MARK_VARIATION, + MARK_SQUARE, + MARK_CIRCLE, + MARK_TRIANGLE, + MARK_LAST_MOVE, + MARK_LABEL +}; + +/* An SGF property next is the handle to the next property in the node, or + less than zero if there isn't another */ +struct prop_t +{ + union prop_data_t data; + enum prop_type_t type; + int next; +}; + + +/* The names of the rulesets, ex. "AGA", "Japanese", etc. */ +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) + + +/* One SGF node which can contain an indefinite number of SGF properties + Less than zero for any of these means that there is nothing in that + direction. */ +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; +}; + + +/* The game metadata which can be stored in the SGF file */ + +#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 diff --git a/apps/plugins/goban/util.c b/apps/plugins/goban/util.c new file mode 100644 index 0000000000..e9966311ef --- /dev/null +++ b/apps/plugins/goban/util.c @@ -0,0 +1,885 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007-2009 Joshua Simmons + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "util.h" +#include "game.h" + + +void metadata_summary (void) +{ + char buffer[256] = ""; + + if (rb->strlen (header.black) || + rb->strlen (header.white) || + rb->strlen (header.black_rank) || + rb->strlen (header.white_rank)) + rb->snprintf (buffer, sizeof(buffer), + "%s [%s] v. %s [%s] ", + header.black, header.black_rank, + header.white, header.white_rank); + + if (header.handicap > 1) + { + rb->snprintf (buffer + rb->strlen(buffer), + sizeof (buffer) - rb->strlen (buffer), + "%d stones ", header.handicap); + } + + if (header.komi != 0 && !(header.komi == 1 && header.handicap > 1)) + { + snprint_fixed (buffer + rb->strlen(buffer), + sizeof (buffer) - rb->strlen (buffer), + header.komi); + rb->snprintf (buffer + rb->strlen(buffer), + sizeof (buffer) - rb->strlen (buffer), + " komi "); + } + + if (rb->strlen(header.result)) + { + rb->snprintf (buffer + rb->strlen(buffer), + sizeof (buffer) - rb->strlen (buffer), + "(%s)", header.result); + } + + /* waiting for user input messes up the testing code, so ifdef it*/ +#if !defined(GBN_TEST) + if (rb->strlen(buffer)) + { + rb->splash(0, buffer); + rb->action_userabort(TIMEOUT_BLOCK); + } +#endif +} + +void * +align_buffer (void *buffer, size_t * buffer_size) +{ + unsigned int wasted = (-(long) buffer) & 3; + + if (!buffer || !buffer_size) + { + return NULL; + } + + if (*buffer_size <= wasted) + { + *buffer_size = 0; + return NULL; + } + + *buffer_size -= wasted; + + return (void *) (((char *) buffer) + wasted); +} + + + +bool +setup_stack (struct stack_t *stack, void *buffer, size_t buffer_size) +{ + if (!stack || !buffer || !buffer_size) + { + DEBUGF ("INVALID STACK SETUP!!\n"); + return false; + } + + buffer = align_buffer (buffer, &buffer_size); + + if (!buffer || !buffer_size) + { + DEBUGF ("Buffer disappeared after alignment!\n"); + return false; + } + + stack->buffer = buffer; + stack->size = buffer_size; + stack->sp = 0; + + return true; +} + +bool +push_stack (struct stack_t * stack, void *buffer, size_t buffer_size) +{ + if (stack->sp + buffer_size > stack->size) + { + DEBUGF ("stack full!!\n"); + return false; + } + + rb->memcpy (&stack->buffer[stack->sp], buffer, buffer_size); + + stack->sp += buffer_size; + + return true; +} + +bool +pop_stack (struct stack_t * stack, void *buffer, size_t buffer_size) +{ + if (!peek_stack (stack, buffer, buffer_size)) + { + return false; + } + + stack->sp -= buffer_size; + + return true; +} + +bool +peek_stack (struct stack_t * stack, void *buffer, size_t buffer_size) +{ + if (stack->sp < buffer_size) + { + return false; + } + + rb->memcpy (buffer, &stack->buffer[stack->sp - buffer_size], buffer_size); + + return true; +} + +void +empty_stack (struct stack_t *stack) +{ + stack->sp = 0; +} + +bool +push_pos_stack (struct stack_t *stack, unsigned short pos) +{ + return push_stack (stack, &pos, sizeof (pos)); +} + +bool +push_int_stack (struct stack_t *stack, int num) +{ + return push_stack (stack, &num, sizeof (num)); +} + +bool +push_char_stack (struct stack_t *stack, char num) +{ + return push_stack (stack, &num, sizeof (num)); +} + + + +/* IMPORTANT: keep in sync with the enum prop_type_t enum in types.h */ +char *prop_names[] = { + /* look up the SGF specification for the meaning of these */ + "B", "W", + "AB", "AW", "AE", + + "PL", "C", + + "DM", "GB", "GW", "HO", "UC", "V", + + "BM", "DO", "IT", "TE", + + "CR", "SQ", "TR", "DD", "MA", "SL", "LB", "N", + + "AP", "CA", "FF", "GM", "ST", "SZ", + + "AN", "PB", "PW", "HA", "KM", "TB", "TW", "BR", "WR", + "BT", "WT", "CP", "DT", "EV", "RO", "GN", "GC", "ON", + "OT", "PC", "RE", "RU", "SO", "TM", "US", + + "BL", "WL", "OB", "OW", "FG", "PM", "VW" +}; + +/* These seems to be specified by the SGF specification. You can do free + form ones as well, but I haven't implemented that (and don't plan to) */ +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) +{ + return rb->snprintf (buffer, buffer_size, "%s%d.%d", + fixed < 0 ? "-" : "", + abs (fixed) >> 1, 5 * (fixed & 1)); +} + + +int +peek_char (int fd) +{ + char peeked_char; + + int result = rb->read (fd, &peeked_char, 1); + + if (result != 1) + { + return -1; + } + + result = rb->lseek (fd, -1, SEEK_CUR); + + if (result < 0) + { + return -1; + } + + return peeked_char; +} + + +int +read_char (int fd) +{ + char read_char; + + int result = rb->read (fd, &read_char, 1); + + if (result != 1) + { + return -1; + } + + return read_char; +} + + +bool +write_char (int fd, char to_write) +{ + int result = write_file (fd, &to_write, 1); + + if (result != 1) + { + return false; + } + + return true; +} + +ssize_t +write_file (int fd, const void *buf, size_t count) +{ + const char *buffer = buf; + int result; + int ret_val = count; + + while (count) + { + result = rb->write (fd, buffer, count); + + if (result < 0) + { + return -1; + } + + count -= result; + buffer += result; + } + + return ret_val; +} + +ssize_t +read_file (int fd, void *buf, size_t count) +{ + char *buffer = buf; + int result; + int ret_val = count; + + while (count) + { + result = rb->read (fd, buffer, count); + + if (result <= 0) + { + return -1; + } + + count -= result; + buffer += result; + } + + return ret_val; +} + +int +read_char_no_whitespace (int fd) +{ + int result = peek_char_no_whitespace (fd); + + read_char (fd); + + return result; +} + +int +peek_char_no_whitespace (int fd) +{ + int result; + + while (is_whitespace (result = peek_char (fd))) + { + read_char (fd); + } + + return result; +} + + +void +close_file (int *fd) +{ + if (*fd >= 0) + { + rb->close (*fd); + } + + *fd = -1; +} + +bool +is_whitespace (int value) +{ + if (value == ' ' || + value == '\t' || + value == '\n' || value == '\r' || value == '\f' || value == '\v') + { + return true; + } + else + { + return false; + } +} + +void +sanitize_string (char *string) +{ + bool escaped = false; + + if (!string) + { + return; + } + + while (1) + { + switch (*string) + { + case '\0': + return; + case '\\': + escaped = !escaped; + break; + case ']': + if (!escaped) + { + *string = ']'; + } + escaped = false; + break; + default: + break; + }; + ++string; + } +} + + +bool +get_header_string_and_size (struct header_t *header, + enum prop_type_t type, char **buffer, int *size) +{ + if (buffer == 0 || header == 0) + { + return false; + } + + if (type == PROP_BLACK_NAME) + { + *buffer = header->black; + *size = MAX_NAME; + } + else if (type == PROP_WHITE_NAME) + { + *buffer = header->white; + *size = MAX_NAME; + } + else if (type == PROP_BLACK_RANK) + { + *buffer = header->black_rank; + *size = MAX_RANK; + } + else if (type == PROP_WHITE_RANK) + { + *buffer = header->white_rank; + *size = MAX_RANK; + } + else if (type == PROP_BLACK_TEAM) + { + *buffer = header->black_team; + *size = MAX_TEAM; + } + else if (type == PROP_WHITE_TEAM) + { + *buffer = header->white_team; + *size = MAX_TEAM; + } + else if (type == PROP_DATE) + { + *buffer = header->date; + *size = MAX_DATE; + } + else if (type == PROP_ROUND) + { + *buffer = header->round; + *size = MAX_ROUND; + } + else if (type == PROP_EVENT) + { + *buffer = header->event; + *size = MAX_EVENT; + } + else if (type == PROP_PLACE) + { + *buffer = header->place; + *size = MAX_PLACE; + } + else if (type == PROP_OVERTIME) + { + *buffer = header->overtime; + *size = MAX_OVERTIME; + } + else if (type == PROP_RESULT) + { + *buffer = header->result; + *size = MAX_RESULT; + } + else if (type == PROP_RULESET) + { + *buffer = header->ruleset; + *size = MAX_RULESET; + } + else + { + return false; + } + + return true; +} + + +/* TEST CODE BEGINS HERE define GBN_TEST to run this, either in goban.h or + in the CFLAGS. The tests will be run when the plugin starts, after + which the plugin will exit. Any error stops testing since many tests + depend on previous setup. Note: The testing can take a while as there + are some big loops. Be patient. */ + +#ifdef GBN_TEST + +#include "goban.h" +#include "types.h" +#include "board.h" +#include "game.h" +#include "sgf.h" +#include "sgf_storage.h" + +/* If this isn't on a single line, the line numbers it reports will be wrong. + * + * I'm sure there's a way to make it better, but it's not really worth it. + */ +#define gbn_assert(test) if (test) {DEBUGF("%d passed\n", __LINE__);} else {DEBUGF("%d FAILED!\n", __LINE__); rb->splashf(10 * HZ, "Test on line %d of util.c failed!", __LINE__); return;} + +void +run_tests (void) +{ + rb->splash (3 * HZ, "Running tests. Failures will stop testing."); + + + + /* allocating and freeing storage units */ + + gbn_assert (alloc_storage_sgf ()); + + int prevent_infinite = 100000000; + + int count = 1; + while (alloc_storage_sgf () >= 0 && --prevent_infinite) + { + ++count; + } + + gbn_assert (prevent_infinite); + gbn_assert (count > 100); + + /* make sure it fails a few times */ + gbn_assert (alloc_storage_sgf () < 0); + gbn_assert (alloc_storage_sgf () < 0); + gbn_assert (alloc_storage_sgf () < 0); + + free_storage_sgf (0); + + gbn_assert (alloc_storage_sgf () == 0); + + gbn_assert (alloc_storage_sgf () < 0); + + int i; + for (i = 0; i <= count; ++i) + { + free_storage_sgf (i); + } + + gbn_assert (alloc_storage_sgf () >= 0); + --count; + + for (i = 0; i < count; ++i) + { + gbn_assert (alloc_storage_sgf () >= 0); + } + + free_tree_sgf (); + + + + /* setting up, saving and loading */ + gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, 15)); + gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, -30)); + gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 4, 1)); + gbn_assert (setup_game (MIN_BOARD_SIZE, MIN_BOARD_SIZE, 1, 1)); + + gbn_assert (setup_game (MIN_BOARD_SIZE, MAX_BOARD_SIZE, 1, 1)); + gbn_assert (setup_game (MAX_BOARD_SIZE, MIN_BOARD_SIZE, 1, 1)); + + gbn_assert (!setup_game (MAX_BOARD_SIZE + 1, MAX_BOARD_SIZE + 1, 0, 15)); + gbn_assert (!setup_game (MIN_BOARD_SIZE - 1, MIN_BOARD_SIZE - 1, 0, 15)); + gbn_assert (!setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, -1, 15)); + + gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 1, 1)); + gbn_assert (save_game (DEFAULT_SAVE_DIR "/t1.sgf")); + gbn_assert (load_game (DEFAULT_SAVE_DIR "/t1.sgf")); + gbn_assert (save_game (DEFAULT_SAVE_DIR "/t2.sgf")); + gbn_assert (load_game (DEFAULT_SAVE_DIR "/t2.sgf")); + + gbn_assert (!save_game ("/DIR_DOESNT_EXIST/blah.sgf")); + gbn_assert (!load_game ("/DIR_DOESNT_EXIST/blah.sgf")); + gbn_assert (!load_game (DEFAULT_SAVE_DIR "/DOESNT_EXIST.sgf")); + + + + /* test of a long game, captures, illegal moves */ + gbn_assert (load_game (DEFAULT_SAVE_DIR "/long.sgf")); + while (move_num < 520) + { + gbn_assert (num_variations_sgf () == 1); + gbn_assert (redo_node_sgf ()); + } + + gbn_assert (play_move_sgf (POS (2, 0), BLACK)); + gbn_assert (play_move_sgf (POS (2, 1), WHITE)); + + gbn_assert (move_num == 522); + + gbn_assert (white_captures == 261 && black_captures == 0); + + gbn_assert (play_move_sgf (PASS_POS, BLACK)); + gbn_assert (play_move_sgf (PASS_POS, WHITE)); + + gbn_assert (move_num == 524); + + int x, y; + int b_count, w_count, e_count; + b_count = w_count = e_count = 0; + for (x = 0; x < 19; ++x) + { + for (y = 0; y < 19; ++y) + { + gbn_assert (!legal_move_board (POS (x, y), BLACK, false)); + gbn_assert (!play_move_sgf (POS (x, y), BLACK)); + switch (get_point_board (POS (x, y))) + { + case BLACK: + ++b_count; + break; + case WHITE: + ++w_count; + break; + case EMPTY: + ++e_count; + break; + default: + gbn_assert (false); + } + } + } + + gbn_assert (b_count == 0 && w_count == 261 && e_count == 19 * 19 - 261); + + gbn_assert (undo_node_sgf ()); + gbn_assert (move_num == 523); + + int infinite_prevention = 0; + while (move_num > 0) + { + gbn_assert (undo_node_sgf ()); + + ++infinite_prevention; + gbn_assert (infinite_prevention < 100000); + } + + gbn_assert (save_game (DEFAULT_SAVE_DIR "/long_out.sgf")); + + + /* test of basic moves, legal moves, adding and removing stones */ + gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, 0)); + gbn_assert (play_move_sgf + (POS (MAX_BOARD_SIZE / 2, MAX_BOARD_SIZE / 2), BLACK)); + gbn_assert (move_num == 1 && current_player == WHITE); + gbn_assert (!legal_move_board + (POS (MAX_BOARD_SIZE / 2, MAX_BOARD_SIZE / 2), WHITE, true)); + + int saved_node = current_node; + gbn_assert (add_stone_sgf (POS (0, 0), BLACK)); + gbn_assert (current_node != saved_node); + gbn_assert (get_point_board (POS (0, 0)) == BLACK); + gbn_assert (move_num == 1 && current_player == WHITE); + + saved_node = current_node; + gbn_assert (add_stone_sgf (POS (0, 1), WHITE)); + gbn_assert (current_node == saved_node); + gbn_assert (get_point_board (POS (0, 1)) == WHITE); + + gbn_assert (add_stone_sgf (POS (0, 0), EMPTY)); + gbn_assert (add_stone_sgf (POS (0, 1), EMPTY)); + gbn_assert (get_point_board (POS (0, 0)) == EMPTY); + gbn_assert (get_point_board (POS (0, 1)) == EMPTY); + + + /* test captures */ + gbn_assert (load_game (DEFAULT_SAVE_DIR "/cap.sgf")); + gbn_assert (play_move_sgf (POS (0, 0), BLACK)); + gbn_assert (black_captures == 8); + gbn_assert (undo_node_sgf ()); + gbn_assert (black_captures == 0); + + gbn_assert (!play_move_sgf (POS (0, 0), WHITE)); + play_mode = MODE_FORCE_PLAY; + gbn_assert (play_move_sgf (POS (0, 0), WHITE)); + play_mode = MODE_PLAY; + + gbn_assert (black_captures == 9); + gbn_assert (get_point_board (POS (0, 0)) == EMPTY); + gbn_assert (undo_node_sgf ()); + gbn_assert (black_captures == 0); + + gbn_assert (play_move_sgf (POS (9, 9), BLACK)); + gbn_assert (black_captures == 44); + + for (x = 0; x < 19; ++x) + { + for (y = 0; y < 19; ++y) + { + gbn_assert (get_point_board (POS (x, y)) == BLACK || + add_stone_sgf (POS (x, y), BLACK)); + } + } + + gbn_assert (get_point_board (POS (0, 0)) == BLACK); + gbn_assert (add_stone_sgf (POS (9, 9), EMPTY)); + gbn_assert (play_move_sgf (POS (9, 9), WHITE)); + gbn_assert (white_captures == 360); + + gbn_assert (undo_node_sgf ()); + gbn_assert (white_captures == 0); + + play_mode = MODE_FORCE_PLAY; + gbn_assert (play_move_sgf (POS (9, 9), BLACK)); + play_mode = MODE_PLAY; + gbn_assert (white_captures == 361); + + for (x = 0; x < 19; ++x) + { + for (y = 0; y < 19; ++y) + { + gbn_assert (get_point_board (POS (x, y)) == EMPTY); + } + } + + + /* test ko */ + gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, 15)); + + /* + * Set up the board to look like this: + * -X------ + * XO------ + * O------- + * -------- + */ + gbn_assert (add_stone_sgf (POS (0, 1), BLACK)); + gbn_assert (add_stone_sgf (POS (1, 0), BLACK)); + gbn_assert (add_stone_sgf (POS (1, 1), WHITE)); + gbn_assert (add_stone_sgf (POS (0, 2), WHITE)); + + /* take the ko and make sure black can't take back */ + gbn_assert (play_move_sgf (POS (0, 0), WHITE)); + gbn_assert (!play_move_sgf (POS (0, 1), BLACK)); + + /* make sure white can fill, even with the ko_pos set */ + gbn_assert (play_move_sgf (POS (0, 1), WHITE)); + /* and make sure undo sets the ko again */ + gbn_assert (undo_node_sgf ()); + gbn_assert (!play_move_sgf (POS (0, 1), BLACK)); + + /* make sure ko threats clear the ko */ + gbn_assert (play_move_sgf (POS (2, 2), BLACK)); /* ko threat */ + gbn_assert (play_move_sgf (POS (2, 3), WHITE)); /* response */ + gbn_assert (play_move_sgf (POS (0, 1), BLACK)); /* take ko */ + + gbn_assert (undo_node_sgf ()); + gbn_assert (undo_node_sgf ()); + gbn_assert (undo_node_sgf ()); + + /* make sure a pass is counted as a ko threat */ + gbn_assert (!play_move_sgf (POS (0, 1), BLACK)); + gbn_assert (play_move_sgf (PASS_POS, BLACK)); + gbn_assert (play_move_sgf (PASS_POS, WHITE)); + gbn_assert (play_move_sgf (POS (0, 1), BLACK)); + + /* and finally let's make sure that white can't directly retake */ + gbn_assert (!play_move_sgf (POS (0, 0), WHITE)); + + + + /* test some header information saving/loading as well as comment + saving loading */ + char some_comment[] = + "blah blah blah i am a stupid comment. here's some annoying characters: 01234567890!@#$%^&*()[[[[\\\\\\]ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + /* that bit near the end is literally this: \\\] which tests escaping + of ]s */ + char read_buffer[256]; + + gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 5, -20)); + + /* this also tests that ko_pos is reset by setuping up a new game */ + gbn_assert (play_move_sgf (POS (0, 0), WHITE)); + gbn_assert (write_comment_sgf (some_comment) > 0); + gbn_assert (play_move_sgf (POS (0, 1), BLACK)); + rb->strcpy (header.black, "Jack Black"); + rb->strcpy (header.white, "Jill White"); + + gbn_assert (save_game (DEFAULT_SAVE_DIR "/head.sgf")); + + gbn_assert (setup_game (MIN_BOARD_SIZE, MIN_BOARD_SIZE, 1, 1)); + gbn_assert (load_game (DEFAULT_SAVE_DIR "/head.sgf")); + + gbn_assert (header.komi == -20 && header.handicap == 5); + gbn_assert (board_width == MAX_BOARD_SIZE + && board_height == MAX_BOARD_SIZE); + gbn_assert (rb->strcmp (header.black, "Jack Black") == 0); + gbn_assert (rb->strcmp (header.white, "Jill White") == 0); + gbn_assert (redo_node_sgf ()); + gbn_assert (read_comment_sgf (read_buffer, sizeof (read_buffer))); + gbn_assert (rb->strcmp (read_buffer, some_comment) == 0); + gbn_assert (redo_node_sgf ()); + gbn_assert (get_point_board (POS (0, 0)) == WHITE); + gbn_assert (get_point_board (POS (0, 1)) == BLACK); + + + + /* test saving and loading a file with unhandled SGF properties. this + test requires that the user diff unhnd.sgf with unhnd_out.sgf (any + substantial difference is a bug and should be reported) the + following are NOT substantial differences: - reordering of + properties in a node - whitespace changes outside of a comment + value or other property value - reordering of property values */ + gbn_assert (load_game (DEFAULT_SAVE_DIR "/unhnd.sgf")); + gbn_assert (save_game (DEFAULT_SAVE_DIR "/unhnd_out.sgf")); + + + + /* Test variations a bit */ + gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, 13)); + /* start at a move, otherwise add_stone won't create a variation */ + gbn_assert (play_move_sgf (POS (5, 5), BLACK)); + /* make sure it doesn't */ + gbn_assert (undo_node_sgf ()); + gbn_assert (add_stone_sgf (POS (4, 5), WHITE)); + gbn_assert (!undo_node_sgf ()); + gbn_assert (num_variations_sgf () == 1); + gbn_assert (play_move_sgf (POS (5, 5), BLACK)); + + gbn_assert (play_move_sgf (POS (0, 0), BLACK)); + gbn_assert (num_variations_sgf () == 1); + gbn_assert (undo_node_sgf ()); + gbn_assert (play_move_sgf (POS (0, 1), BLACK)); + gbn_assert (num_variations_sgf () == 2); + gbn_assert (undo_node_sgf ()); + gbn_assert (play_move_sgf (POS (0, 1), BLACK)); + gbn_assert (num_variations_sgf () == 2); + gbn_assert (undo_node_sgf ()); + gbn_assert (play_move_sgf (POS (0, 2), BLACK)); + gbn_assert (num_variations_sgf () == 3); + gbn_assert (undo_node_sgf ()); + gbn_assert (play_move_sgf (POS (0, 3), WHITE)); + gbn_assert (num_variations_sgf () == 4); + gbn_assert (undo_node_sgf ()); + gbn_assert (play_move_sgf (PASS_POS, BLACK)); + gbn_assert (num_variations_sgf () == 5); + gbn_assert (undo_node_sgf ()); + gbn_assert (add_stone_sgf (POS (1, 1), BLACK)); + gbn_assert (add_stone_sgf (POS (1, 2), BLACK)); + gbn_assert (add_stone_sgf (POS (1, 3), WHITE)); + gbn_assert (num_variations_sgf () == 6); + gbn_assert (undo_node_sgf ()); + gbn_assert (add_stone_sgf (POS (1, 1), BLACK)); + gbn_assert (add_stone_sgf (POS (1, 2), BLACK)); + gbn_assert (add_stone_sgf (POS (1, 3), WHITE)); + gbn_assert (num_variations_sgf () == 7); + gbn_assert (next_variation_sgf ()); + gbn_assert (get_point_board (POS (0, 0)) == BLACK); + gbn_assert (get_point_board (POS (0, 1)) == EMPTY); + gbn_assert (get_point_board (POS (0, 2)) == EMPTY); + gbn_assert (get_point_board (POS (1, 1)) == EMPTY); + gbn_assert (get_point_board (POS (1, 2)) == EMPTY); + gbn_assert (get_point_board (POS (1, 3)) == EMPTY); + + rb->splash (10 * HZ, "All tests passed. Exiting"); +} +#endif /* GBN_TEST */ diff --git a/apps/plugins/goban/util.h b/apps/plugins/goban/util.h new file mode 100644 index 0000000000..83dc880ac7 --- /dev/null +++ b/apps/plugins/goban/util.h @@ -0,0 +1,113 @@ +/*************************************************************************** + * __________ __ ___. + * 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" + +/* Call before using a stack, returns false on setup failure */ +bool setup_stack (struct stack_t *stack, void *buffer, size_t buffer_size); + +/* Push, pop or peek from the stack. Returns false on failure (usually + stack full or empty, depending on the function) */ +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); + +/* Clear all of the data from the stack and move the stack pointer to the + beginning */ +void empty_stack (struct stack_t *stack); + +/* Convenience functions for pushing/poping/peeking standard value types + to a stack */ +#define pop_pos_stack(stack, pos) pop_stack(stack, pos, sizeof (unsigned short)) +#define peek_pos_stack(stack, pos) peek_stack(stack, pos, sizeof (unsigned short)) + +#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, unsigned short 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) + +/* Returns the fd of the file */ +int create_or_open_file (const char *filename); + +/* Returns the number of characters printed */ +int snprint_fixed (char *buffer, int buffer_size, int fixed); + +/* These should all be obvious, they are simply wrappers on the normal + rockbox file functions which will loop several times if the rockbox + file functions don't deal with all of the data at once */ +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); + +/* Seek to the next non-whitespace character (doesn't go anywhere if the + current character is already non-whitespace), and then peek it -1 on + EOF or error */ +int peek_char_no_whitespace (int fd); +/* Same deal, with reading -1 on EOF or error */ +int read_char_no_whitespace (int fd); + +/* Returns true if a character is whitespace. Should /NOT/ be called with + anything but the return value of one of the peeking/reading functions or + a standard character. */ +bool is_whitespace (int value); + +/* Gets rid of ']' characdters from the string by overwritting them. This + is needed in header strings because they would otherwise corrupt the + SGF file when outputted */ +void sanitize_string (char *string); + +/* Return an aligned version of the bufer, with the size updated Returns + NULL if the buffer is too small to align. */ +void *align_buffer (void *buffer, size_t * buffer_size); + +/* Get the string and buffer size for a SGF property which is stored in + the header. Returns false on failure, in which case any information set + in **buffer and *size are not to be trusted or used. */ +bool get_header_string_and_size (struct header_t *header, + enum prop_type_t type, + char **buffer, int *size); + +/* Output a summary of the game metadata (Game Info) + */ +void metadata_summary (void); + +#ifdef GBN_TEST +void run_tests (void); +#endif + +#endif diff --git a/apps/plugins/viewers.config b/apps/plugins/viewers.config index 2ec8fe9299..3d5d7e1d35 100644 --- a/apps/plugins/viewers.config +++ b/apps/plugins/viewers.config @@ -19,6 +19,7 @@ rmi,viewers/midi,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,- diff --git a/docs/CREDITS b/docs/CREDITS index 4df253feb3..0a0b05da75 100644 --- a/docs/CREDITS +++ b/docs/CREDITS @@ -448,6 +448,7 @@ Ryan Press Craig Elliott Kenderes Tamas Eric Shattow +Joshua Simmons The libmad team diff --git a/manual/plugins/goban.tex b/manual/plugins/goban.tex new file mode 100644 index 0000000000..45ac980afc --- /dev/null +++ b/manual/plugins/goban.tex @@ -0,0 +1,243 @@ +\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. + +\emph{Note: } The plugin does \emph{NOT} support SGF files with multiple games in +one file. These are rare, but if you have one don't even try it (the file will most +likely be corrupted if you save over it). You have been warned. + +The file \fname {"/sgf/gbn\_def.sgf"} is used by the plugin to store any unsaved +changes in the most recently loaded game. This means that if you forget to save your +changes, you should load \fname {"/sgf/gbn\_def.sgf"} immediately to offload the changes +to another file. If you load another file first then your changes will be lost +permanently. The \fname {"/sgf/gbn\_def.sgf"} file is also the file loaded if another +is not selected. + +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,SANSA_C200_PAD,SANSA_FUZE_PAD,RECORDER_PAD,% + MROBE100_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD,IRIVER_H100_PAD,% + IRIVER_H300_PAD}{ or the % + \opt{SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD}{\ButtonRec}% + \opt{RECORDER_PAD}{\ButtonOn}% + \opt{MROBE100_PAD}{\ButtonPower}% + \opt{GIGABEAT_PAD}{\ButtonA}% + \opt{GIGABEAT_S_PAD}{\ButtonPlay}% + \opt{IRIVER_H100_PAD,IRIVER_H300_PAD}{\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,RECORDER_PAD}{\ButtonPlay}% + \opt{ONDIO_PAD}{\ButtonMenu} & Play a move (or use a tool if play-mode has + been changed). \\ + \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}{\ButtonFThree} & Advance one node in the game tree \\ } + \opt{SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,IRIVER_H10_PAD,% + IAUDIO_X5_PAD}{\ButtonPower}% + \opt{MROBE100_PAD}{\ButtonDisplay}% + \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}{\ButtonFTwo}% + \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_C200_PAD,SANSA_FUZE_PAD,RECORDER_PAD,MROBE100_PAD,% + GIGABEAT_PAD,GIGABEAT_S_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD}{% + \opt{SANSA_E200_PAD,SANSA_FUZE_PAD}{\ButtonRec}% + \opt{SANSA_C200_PAD}{\ButtonRec}% + \opt{RECORDER_PAD}{\ButtonOn}% + \opt{MROBE100_PAD}{\ButtonPower}% + \opt{GIGABEAT_PAD}{\ButtonA}% + \opt{GIGABEAT_S_PAD}{\ButtonPlay}% + \opt{IRIVER_H100_PAD,IRIVER_H300_PAD}{\ButtonRec} & Go to the next variation % + when at the first node 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. If you set the zoom level, it + will be saved and used again the next time you open this plugin. \\ + \emph {Options. } Open the Options Menu. \\ + \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 {Basic Info. } Shows a quick view of the basic game metadata, if any + has been set (otherwise does nothing). This option does not allow editing. \\ + \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 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. The \emph{NZ} + and \emph{GOE} rulesets include suicide as a legal move (for multi-stone + suicide only); the rest do not. \\ + \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 [Options. ] + Customize the behavior of the plugin in certain ways. + + \emph {Show Child Variations? } Enable this to mark child variations on the board + if there are more than one. Note: variations which don't start with a move are + not visible in this way. \\ + \emph {Disable Idle Poweroff? } Enable this if you do not want the \dap{} to turn + off after a certain period of inactivity (depends on your global Rockbox + settings). \\ + \emph {Idle Autosave Time. } Set the amount of idle time to wait before + automatically saving any unsaved changes. These autosaves go to the file + \fname {"/sgf/gbn\_def.sgf"} regardless of if you have loaded a game or used + \emph {Save As} to save the game before or not. Set to \emph {Off} to disable + this functionality completely. \\ + \emph {Automatically Show Comments? } If this is enabled and you navigate to a + node containing game comments, they will automatically be displayed. \\ + +\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} + diff --git a/manual/plugins/main.tex b/manual/plugins/main.tex index e8aadb093f..73e1648b94 100644 --- a/manual/plugins/main.tex +++ b/manual/plugins/main.tex @@ -35,6 +35,8 @@ text files% {\input{plugins/flipit.tex}} +\opt{lcd_bitmap}{\input{plugins/goban.tex}} + \opt{player}{\input{plugins/jackpot.tex}} \opt{lcd_bitmap}{\input{plugins/jewels.tex}}