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:
parent
21f0c9a282
commit
f2d5c3532f
25 changed files with 9332 additions and 0 deletions
9
apps/plugins/goban/SOURCES
Normal file
9
apps/plugins/goban/SOURCES
Normal 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
354
apps/plugins/goban/board.c
Normal 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
100
apps/plugins/goban/board.h
Normal 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
1091
apps/plugins/goban/display.c
Normal file
File diff suppressed because it is too large
Load diff
111
apps/plugins/goban/display.h
Normal file
111
apps/plugins/goban/display.h
Normal 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
236
apps/plugins/goban/game.c
Normal 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
59
apps/plugins/goban/game.h
Normal 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
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
277
apps/plugins/goban/goban.h
Normal 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
|
22
apps/plugins/goban/goban.make
Normal file
22
apps/plugins/goban/goban.make
Normal 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
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
170
apps/plugins/goban/sgf.h
Normal 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
|
433
apps/plugins/goban/sgf_output.c
Normal file
433
apps/plugins/goban/sgf_output.c
Normal 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, ¤t))
|
||||
{
|
||||
int var_to_process = 0;
|
||||
int temp_prop =
|
||||
get_prop_sgf (current, PROP_VARIATION_TO_PROCESS, NULL);
|
||||
|
||||
if (temp_prop >= 0)
|
||||
{
|
||||
var_to_process = get_prop (temp_prop)->data.number;
|
||||
}
|
||||
|
||||
current_node = current;
|
||||
|
||||
if (var_to_process > 0)
|
||||
{
|
||||
write_char (sgf_fd, ')');
|
||||
}
|
||||
|
||||
if (var_to_process == stupid_num_variations ())
|
||||
{
|
||||
delete_prop_sgf (current, PROP_VARIATION_TO_PROCESS);
|
||||
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
write_char (sgf_fd, '\n');
|
||||
write_char (sgf_fd, '(');
|
||||
|
||||
/* we need to do more processing on this branchpoint, either
|
||||
to do more variations or to output the ')' */
|
||||
push_int_stack (&parse_stack, current);
|
||||
|
||||
/* increment the stored variation to process */
|
||||
temp_data.number = var_to_process + 1;
|
||||
add_or_set_prop_sgf (current,
|
||||
PROP_VARIATION_TO_PROCESS, temp_data);
|
||||
}
|
||||
|
||||
rb->yield ();
|
||||
|
||||
/* now we did the setup for sibling varaitions to be processed so
|
||||
do the actual outputting of a game tree branch */
|
||||
|
||||
go_to_variation_sgf (var_to_process);
|
||||
output_gametree ();
|
||||
}
|
||||
|
||||
current_node = saved;
|
||||
close_file (&sgf_fd);
|
||||
DEBUGF ("done outputting, file closed\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
output_header_props (void)
|
||||
{
|
||||
char buffer[128];
|
||||
|
||||
rb->strncpy (buffer, "GM[1]FF[4]CA[UTF-8]AP[Rockbox Goban:1.0]ST[2]\n\n",
|
||||
sizeof (buffer));
|
||||
write_file (sgf_fd, buffer, rb->strlen (buffer));
|
||||
|
||||
/* board size */
|
||||
if (board_width != board_height)
|
||||
{
|
||||
rb->snprintf (buffer, sizeof (buffer), "%s[%d:%d]",
|
||||
prop_names[PROP_SIZE], board_width, board_height);
|
||||
}
|
||||
else
|
||||
{
|
||||
rb->snprintf (buffer, sizeof (buffer), "%s[%d]",
|
||||
prop_names[PROP_SIZE], board_width);
|
||||
}
|
||||
|
||||
write_file (sgf_fd, buffer, rb->strlen (buffer));
|
||||
|
||||
rb->snprintf (buffer, sizeof (buffer), "%s[", prop_names[PROP_KOMI]);
|
||||
write_file (sgf_fd, buffer, rb->strlen (buffer));
|
||||
|
||||
snprint_fixed (buffer, sizeof (buffer), header.komi);
|
||||
write_file (sgf_fd, buffer, rb->strlen (buffer));
|
||||
|
||||
write_char (sgf_fd, ']');
|
||||
|
||||
output_header_helper (PROP_RULESET);
|
||||
output_header_helper (PROP_RESULT);
|
||||
|
||||
output_header_helper (PROP_BLACK_NAME);
|
||||
output_header_helper (PROP_WHITE_NAME);
|
||||
output_header_helper (PROP_BLACK_RANK);
|
||||
output_header_helper (PROP_WHITE_RANK);
|
||||
output_header_helper (PROP_BLACK_TEAM);
|
||||
output_header_helper (PROP_WHITE_TEAM);
|
||||
|
||||
output_header_helper (PROP_EVENT);
|
||||
output_header_helper (PROP_PLACE);
|
||||
output_header_helper (PROP_DATE);
|
||||
|
||||
if (output_header_helper (PROP_OVERTIME) || header.time_limit != 0)
|
||||
{
|
||||
rb->snprintf (buffer, sizeof (buffer), "%s[%d]",
|
||||
prop_names[PROP_TIME_LIMIT], header.time_limit);
|
||||
write_file (sgf_fd, buffer, rb->strlen (buffer));
|
||||
}
|
||||
|
||||
write_char (sgf_fd, '\n');
|
||||
write_char (sgf_fd, '\n');
|
||||
}
|
||||
|
||||
static bool
|
||||
output_header_helper (enum prop_type_t type)
|
||||
{
|
||||
char *buffer;
|
||||
int size;
|
||||
char temp_buffer[16];
|
||||
|
||||
if (!get_header_string_and_size (&header, type, &buffer, &size))
|
||||
{
|
||||
DEBUGF ("output_header_helper called with invalid prop type!!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rb->strlen (buffer))
|
||||
{
|
||||
rb->snprintf (temp_buffer, sizeof (temp_buffer), "%s[",
|
||||
prop_names[type]);
|
||||
|
||||
write_file (sgf_fd, temp_buffer, rb->strlen (temp_buffer));
|
||||
|
||||
write_file (sgf_fd, buffer, rb->strlen (buffer));
|
||||
|
||||
rb->strcpy (temp_buffer, "]");
|
||||
|
||||
write_file (sgf_fd, temp_buffer, rb->strlen (temp_buffer));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool first_node_in_tree = true;
|
||||
static void
|
||||
output_gametree (void)
|
||||
{
|
||||
first_node_in_tree = true;
|
||||
|
||||
while (output_current_node ())
|
||||
{
|
||||
current_node = get_node (current_node)->next;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static bool
|
||||
output_current_node (void)
|
||||
{
|
||||
if (current_node < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stupid_num_variations () > 1 &&
|
||||
get_prop_sgf (current_node, PROP_VARIATION_TO_PROCESS, NULL) < 0)
|
||||
{
|
||||
/* push it up for the gametree stuff to take care of it and fail
|
||||
out, stopping the node printing */
|
||||
push_int_stack (&parse_stack, current_node);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (first_node_in_tree)
|
||||
{
|
||||
first_node_in_tree = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
write_char (sgf_fd, '\n');
|
||||
}
|
||||
write_char (sgf_fd, ';');
|
||||
|
||||
output_all_props ();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
enum prop_type_t last_output_type = PROP_INVALID;
|
||||
static void
|
||||
output_all_props (void)
|
||||
{
|
||||
int temp_handle = get_node (current_node)->props;
|
||||
|
||||
last_output_type = PROP_INVALID;
|
||||
|
||||
while (temp_handle >= 0)
|
||||
{
|
||||
output_prop (temp_handle);
|
||||
temp_handle = get_prop (temp_handle)->next;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
output_prop (int prop_handle)
|
||||
{
|
||||
char buffer[16];
|
||||
enum prop_type_t temp_type = get_prop (prop_handle)->type;
|
||||
|
||||
buffer[0] = 't';
|
||||
buffer[1] = 't';
|
||||
|
||||
if (is_handled_sgf (temp_type) && temp_type != PROP_COMMENT)
|
||||
{
|
||||
if (temp_type != last_output_type)
|
||||
{
|
||||
write_file (sgf_fd, prop_names[temp_type],
|
||||
PROP_NAME_LEN (temp_type));
|
||||
}
|
||||
|
||||
write_char (sgf_fd, '[');
|
||||
|
||||
if (temp_type == PROP_HANDICAP)
|
||||
{
|
||||
rb->snprintf (buffer, sizeof (buffer), "%d",
|
||||
get_prop (prop_handle)->data.number);
|
||||
write_file (sgf_fd, buffer, rb->strlen (buffer));
|
||||
}
|
||||
else if (temp_type == PROP_LABEL)
|
||||
{
|
||||
pos_to_sgf (get_prop (prop_handle)->data.position, buffer);
|
||||
buffer[2] = '\0';
|
||||
|
||||
rb->snprintf (&buffer[2], sizeof (buffer) - 2, ":%c",
|
||||
get_prop (prop_handle)->data.label_extra);
|
||||
|
||||
write_file (sgf_fd, buffer, rb->strlen (buffer));
|
||||
}
|
||||
else
|
||||
{
|
||||
pos_to_sgf (get_prop (prop_handle)->data.position, buffer);
|
||||
|
||||
write_file (sgf_fd, buffer, 2);
|
||||
}
|
||||
|
||||
write_char (sgf_fd, ']');
|
||||
}
|
||||
else if (temp_type == PROP_ROOT_PROPS)
|
||||
{
|
||||
output_header_props ();
|
||||
}
|
||||
else if (temp_type == PROP_GENERIC_UNHANDLED || temp_type == PROP_COMMENT)
|
||||
{
|
||||
bool escaped = false;
|
||||
bool in_prop_value = false;
|
||||
int temp;
|
||||
bool done = false;
|
||||
|
||||
rb->lseek (unhandled_fd, get_prop (prop_handle)->data.number,
|
||||
SEEK_SET);
|
||||
|
||||
while (!done)
|
||||
{
|
||||
temp = peek_char (unhandled_fd);
|
||||
|
||||
switch (temp)
|
||||
{
|
||||
case ';':
|
||||
escaped = false;
|
||||
if (in_prop_value)
|
||||
{
|
||||
break;
|
||||
}
|
||||
/* otherwise, fall through */
|
||||
case -1:
|
||||
done = true;
|
||||
break;
|
||||
|
||||
case '\\':
|
||||
escaped = !escaped;
|
||||
break;
|
||||
|
||||
case '[':
|
||||
escaped = false;
|
||||
in_prop_value = true;
|
||||
break;
|
||||
|
||||
case ']':
|
||||
if (!escaped)
|
||||
{
|
||||
in_prop_value = false;
|
||||
}
|
||||
escaped = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
escaped = false;
|
||||
break;
|
||||
};
|
||||
|
||||
if (!done)
|
||||
{
|
||||
write_char (sgf_fd, temp);
|
||||
read_char (unhandled_fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
last_output_type = temp_type;
|
||||
}
|
||||
|
||||
static void
|
||||
pos_to_sgf (unsigned short pos, char *buffer)
|
||||
{
|
||||
if (pos == PASS_POS)
|
||||
{
|
||||
/* "tt" is a pass per SGF specification */
|
||||
buffer[0] = buffer[1] = 't';
|
||||
}
|
||||
else if (pos != INVALID_POS)
|
||||
{
|
||||
buffer[0] = 'a' + I (pos);
|
||||
buffer[1] = 'a' + J (pos);
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUGF ("invalid pos converted to SGF\n");
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
stupid_num_variations (void)
|
||||
{
|
||||
int result = 1;
|
||||
struct prop_t *temp_prop;
|
||||
struct node_t *temp_node = get_node (current_node);
|
||||
|
||||
if (temp_node == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
temp_prop = get_prop (temp_node->props);
|
||||
|
||||
while (temp_prop)
|
||||
{
|
||||
if (temp_prop->type == PROP_VARIATION)
|
||||
{
|
||||
++result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// variations are at the beginning of the prop list
|
||||
break;
|
||||
}
|
||||
|
||||
temp_prop = get_prop (temp_prop->next);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
30
apps/plugins/goban/sgf_output.h
Normal file
30
apps/plugins/goban/sgf_output.h
Normal 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
|
857
apps/plugins/goban/sgf_parse.c
Normal file
857
apps/plugins/goban/sgf_parse.c
Normal 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');
|
||||
}
|
||||
|
30
apps/plugins/goban/sgf_parse.h
Normal file
30
apps/plugins/goban/sgf_parse.h
Normal 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
|
493
apps/plugins/goban/sgf_storage.c
Normal file
493
apps/plugins/goban/sgf_storage.c
Normal 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);
|
||||
}
|
||||
|
57
apps/plugins/goban/sgf_storage.h
Normal file
57
apps/plugins/goban/sgf_storage.h
Normal 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
289
apps/plugins/goban/types.h
Normal 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
885
apps/plugins/goban/util.c
Normal 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
113
apps/plugins/goban/util.h
Normal 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
|
|
@ -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,-
|
||||
|
|
|
@ -448,6 +448,7 @@ Ryan Press
|
|||
Craig Elliott
|
||||
Kenderes Tamas
|
||||
Eric Shattow
|
||||
Joshua Simmons
|
||||
|
||||
|
||||
The libmad team
|
||||
|
|
243
manual/plugins/goban.tex
Normal file
243
manual/plugins/goban.tex
Normal 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}
|
||||
|
|
@ -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}}
|
||||
|
|
Loading…
Reference in a new issue