summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMustapha Senhaji <moos@rockbox.org>2009-02-11 16:03:17 +0000
committerMustapha Senhaji <moos@rockbox.org>2009-02-11 16:03:17 +0000
commitf2d5c3532fd21c04e77aa86732742b578283b697 (patch)
tree1c55cb7e69ed11391f77187c86ff8c435ed85877
parent21f0c9a2829415f52b64cbdf965b01525e78f17a (diff)
downloadrockbox-f2d5c3532fd21c04e77aa86732742b578283b697.tar.gz
rockbox-f2d5c3532fd21c04e77aa86732742b578283b697.tar.bz2
rockbox-f2d5c3532fd21c04e77aa86732742b578283b697.zip
New game plugin by Joshua Simmons FS#7369: Goban plugin, Go/Igo/Weiqi/Baduk game recorder and viewer.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@19972 a1c6a512-1295-4272-9138-f99709370657
-rw-r--r--apps/plugins/goban/SOURCES9
-rw-r--r--apps/plugins/goban/board.c354
-rw-r--r--apps/plugins/goban/board.h100
-rw-r--r--apps/plugins/goban/display.c1091
-rw-r--r--apps/plugins/goban/display.h111
-rw-r--r--apps/plugins/goban/game.c236
-rw-r--r--apps/plugins/goban/game.h59
-rw-r--r--apps/plugins/goban/goban.c1232
-rw-r--r--apps/plugins/goban/goban.h277
-rw-r--r--apps/plugins/goban/goban.make22
-rw-r--r--apps/plugins/goban/sgf.c2237
-rw-r--r--apps/plugins/goban/sgf.h170
-rw-r--r--apps/plugins/goban/sgf_output.c433
-rw-r--r--apps/plugins/goban/sgf_output.h30
-rw-r--r--apps/plugins/goban/sgf_parse.c857
-rw-r--r--apps/plugins/goban/sgf_parse.h30
-rw-r--r--apps/plugins/goban/sgf_storage.c493
-rw-r--r--apps/plugins/goban/sgf_storage.h57
-rw-r--r--apps/plugins/goban/types.h289
-rw-r--r--apps/plugins/goban/util.c885
-rw-r--r--apps/plugins/goban/util.h113
-rw-r--r--apps/plugins/viewers.config1
-rw-r--r--docs/CREDITS1
-rw-r--r--manual/plugins/goban.tex243
-rw-r--r--manual/plugins/main.tex2
25 files changed, 9332 insertions, 0 deletions
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 <mud at majidejima dot com>
+ *
+ * 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 <mud at majidejima dot com>
+ *
+ * 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 <mud at majidejima dot com>
+ *
+ * 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 <mud at majidejima dot com>
+ *
+ * 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 <mud at majidejima dot com>
+ *
+ * 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 <mud at majidejima dot com>
+ *
+ * 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 <mud at majidejima dot com>
+ *
+ * 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 <mud at majidejima dot com>
+ *
+ * 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 <mud at majidejima dot com>
+ *
+ * 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 <mud at majidejima dot com>
+ *
+ * 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 <mud at majidejima dot com>
+ *
+ * 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, &current))
+ {
+ 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 <mud at majidejima dot com>
+ *
+ * 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 <mud at majidejima dot com>
+ *
+ * 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 <mud at majidejima dot com>
+ *
+ * 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 <mud at majidejima dot com>
+ *
+ * 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 <mud at majidejima dot com>
+ *
+ * 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 <mud at majidejima dot com>
+ *
+ * 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 <whatever> 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 <mud at majidejima dot com>
+ *
+ * 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 <mud at majidejima dot com>
+ *
+ * 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}}