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
This commit is contained in:
Mustapha Senhaji 2009-02-11 16:03:17 +00:00
parent 21f0c9a282
commit f2d5c3532f
25 changed files with 9332 additions and 0 deletions

View file

@ -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

354
apps/plugins/goban/board.c Normal file
View file

@ -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);
}
}

100
apps/plugins/goban/board.h Normal file
View file

@ -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

1091
apps/plugins/goban/display.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -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

236
apps/plugins/goban/game.c Normal file
View file

@ -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;
}

59
apps/plugins/goban/game.h Normal file
View file

@ -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

1232
apps/plugins/goban/goban.c Normal file

File diff suppressed because it is too large Load diff

277
apps/plugins/goban/goban.h Normal file
View file

@ -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

View file

@ -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)

2237
apps/plugins/goban/sgf.c Normal file

File diff suppressed because it is too large Load diff

170
apps/plugins/goban/sgf.h Normal file
View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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');
}

View file

@ -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

View file

@ -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);
}

View file

@ -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

289
apps/plugins/goban/types.h Normal file
View file

@ -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

885
apps/plugins/goban/util.c Normal file
View file

@ -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 */

113
apps/plugins/goban/util.h Normal file
View file

@ -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

View file

@ -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,-

View file

@ -448,6 +448,7 @@ Ryan Press
Craig Elliott
Kenderes Tamas
Eric Shattow
Joshua Simmons
The libmad team

243
manual/plugins/goban.tex Normal file
View file

@ -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}

View file

@ -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}}