rockbox/apps/plugins/goban/sgf.c

2239 lines
53 KiB
C
Raw Normal View History

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "sgf.h"
#include "sgf_storage.h"
#include "types.h"
#include "goban.h"
#include "board.h"
#include "display.h"
#include "game.h"
#include "util.h"
int sgf_fd = -1;
int unhandled_fd = -1;
int tree_head = -1;
int current_node = -1;
int start_node = -1;
bool header_marked = false;
static int add_child_variation (int *variation_number);
static int get_move_from_node (int handle);
static bool is_important_node (int handle);
static bool goto_next_important_node (bool forward);
static bool retreat_node (void);
static bool advance_node (void);
static bool do_add_stones (void);
static void setup_handicap_helper (unsigned short pos);
static int undo_node_helper (void);
static void set_one_mark (unsigned short pos, enum prop_type_t type);
static void set_label_mark (unsigned short pos, char to_set);
bool
play_move_sgf (unsigned short pos, unsigned char color)
{
int handle;
int prop_handle;
int temp;
int temp2;
union prop_data_t temp_data;
int saved = current_node;
if ((color != BLACK && color != WHITE) ||
(!on_board (pos) && pos != PASS_POS))
{
return false;
}
/* go to the node before the next important node (move/add
stone/variation) this is the right place to look for children, add
variations, whatever. (if there is no next, we're already at the
right place) */
if (get_node (current_node)->next >= 0)
{
current_node = get_node (current_node)->next;
/* true means forward */
if (goto_next_important_node (true))
{
current_node = get_node (current_node)->prev;
}
}
if ((temp = get_matching_child_sgf (pos, color)) >= 0)
{
/* don't have to do anything to set up temp as the right variation
number */
}
else
{
/* now either there were no children, or none matched the one we
want so we have to add a new one */
/* first test if it's legal. we don't do this above because SGF
files are allowed to have illegal moves in them, and it seems
to make sense to allow traversing those variations without
making the user change to a different play_mode */
bool suicide_allowed = false;
if (rb->strcmp (header.ruleset, "NZ") == 0 ||
rb->strcmp (header.ruleset, "GOE") == 0)
{
suicide_allowed = true;
}
if (play_mode != MODE_FORCE_PLAY &&
!legal_move_board (pos, color, suicide_allowed))
{
return false;
}
handle = add_child_sgf (NULL);
if (handle < 0)
{
current_node = saved;
return false;
}
union prop_data_t temp_prop_data;
temp_prop_data.position = pos;
prop_handle = add_prop_sgf (handle,
color == BLACK ? PROP_BLACK_MOVE :
PROP_WHITE_MOVE, temp_prop_data);
if (prop_handle < 0)
{
/* TODO: add code to completely remove the child which we
added, and then uncomment the following line. probably
doens't matter much since we're out of memory, but
whatever
free_storage_sgf(handle); */
rb->splash (2 * HZ,
"Out of memory led to invalid state. Please exit.");
current_node = saved;
return false;
}
set_game_modified();
temp = get_matching_child_sgf (pos, color);
}
/* now, one way or another temp has been set to the child variation
number that we should follow, so all we need to do is "choose" it
and redo_node_sgf */
current_node = get_node (current_node)->next;
temp_data.number = temp;
temp2 = add_or_set_prop_sgf (current_node,
PROP_VARIATION_CHOICE, temp_data);
/* free up a superfluous prop */
if (temp == 0)
{
delete_prop_handle_sgf (current_node, temp2);
}
current_node = saved;
return redo_node_sgf ();
}
bool
add_mark_sgf (unsigned short pos, enum prop_type_t type)
{
union prop_data_t temp_data;
int temp_handle;
enum prop_type_t original_type;
if (!on_board (pos) || current_node < 0)
{
return false;
}
if (type == PROP_CIRCLE ||
type == PROP_SQUARE ||
type == PROP_TRIANGLE ||
type == PROP_MARK || type == PROP_DIM || type == PROP_SELECTED)
{
temp_data.position = pos;
if ((temp_handle = get_prop_pos_sgf (type, temp_data)) >= 0)
{
original_type = get_prop (temp_handle)->type;
delete_prop_handle_sgf (current_node, temp_handle);
if (type == original_type)
{
set_one_mark (pos, PROP_INVALID);
return true;
}
}
add_prop_sgf (current_node, type, temp_data);
set_one_mark (pos, type);
return true;
}
else if (type == PROP_LABEL)
{
#define MIN_LABEL 'a'
#define MAX_LABEL 'f'
int temp_prop_handle = get_node (current_node)->props;
while (temp_prop_handle >= 0)
{
struct prop_t *temp_prop = get_prop (temp_prop_handle);
if (temp_prop->type == PROP_LABEL &&
temp_prop->data.position == pos)
{
char to_set = temp_prop->data.label_extra;
++to_set;
if (to_set > MAX_LABEL)
{
delete_prop_handle_sgf (current_node, temp_prop_handle);
set_one_mark (pos, PROP_INVALID);
return true;
}
temp_prop->data.label_extra = to_set;
set_label_mark (pos, to_set);
return true;
}
temp_prop_handle = temp_prop->next;
}
temp_data.label_extra = MIN_LABEL;
temp_data.position = pos;
add_prop_sgf (current_node, type, temp_data);
set_label_mark (pos, MIN_LABEL);
return true;
}
else
{
return false;
}
}
bool
add_stone_sgf (unsigned short pos, unsigned char color)
{
int handle;
int prop_handle;
int saved = current_node;
union prop_data_t temp_data;
enum prop_type_t temp_type;
int var_number;
int temp;
if (!on_board (pos))
{
return false;
}
if (color == get_point_board (pos))
{
return false;
}
if ((!is_important_node (current_node) ||
(current_node == start_node && get_move_sgf () < 0)) ||
(get_prop_sgf (current_node, PROP_ADD_BLACK, NULL) >= 0 ||
get_prop_sgf (current_node, PROP_ADD_WHITE, NULL) >= 0 ||
get_prop_sgf (current_node, PROP_ADD_EMPTY, NULL) >= 0) ||
get_node (current_node)->props < 0)
{
if (color == BLACK)
{
temp_type = PROP_ADD_BLACK;
}
else if (color == WHITE)
{
temp_type = PROP_ADD_WHITE;
}
else
{
temp_type = PROP_ADD_EMPTY;
}
temp_data.position = pos;
handle = get_prop_pos_sgf (temp_type, temp_data);
/* we have to always delete the old one and conditionally create a
new one (instead of trying to reuse the old one by changing
the type of it) because if we don't, our invariant with
respect to like-properties being grouped together in the
property list can easily be violated */
if (handle >= 0)
{
temp_data.stone_extra = get_prop (handle)->data.stone_extra;
delete_prop_handle_sgf (current_node, handle);
}
else
{
temp_data.stone_extra = 0;
if (get_point_board (pos) == EMPTY)
{
temp_data.stone_extra |= FLAG_ORIG_EMPTY;
}
else if (get_point_board (pos) == BLACK)
{
temp_data.stone_extra |= FLAG_ORIG_BLACK;
}
/* else do nothing */
}
/* now we've saved the information about what the board was
originally like, we can do the actual set */
set_point_board (pos, color);
/* test if what we currently did just returned the board back to
its original for this position. if so, we DON'T create a new
PROP_ADD_*, because it's not needed (we already deleted the old
one, so in that case we just return) */
if (((temp_data.stone_extra & FLAG_ORIG_EMPTY) && color == EMPTY) ||
(!(temp_data.stone_extra & FLAG_ORIG_EMPTY) &&
(((temp_data.stone_extra & FLAG_ORIG_BLACK) && color == BLACK) ||
(!(temp_data.stone_extra & FLAG_ORIG_BLACK) && color == WHITE))))
{
/* do nothing, set back to original */
}
else
{
/* we're not set back to original, so add a prop for it */
add_prop_sgf (current_node, temp_type, temp_data);
}
set_game_modified();
return true;
}
else
{
/* we have to make a child variation and add stones in it */
/* go to the node before the next important node (move/add
stone/variation) this is the right place to look for children,
add variations, whatever. (if there is no next, we're already
at the right place) */
if (get_node (current_node)->next >= 0)
{
current_node = get_node (current_node)->next;
/* true means forward */
if (goto_next_important_node (true))
{
current_node = get_node (current_node)->prev;
}
}
handle = add_child_sgf (&var_number);
if (handle < 0)
{
rb->splash (2 * HZ, "Out of memory!");
return false;
}
temp_data.position = pos;
if (color == BLACK)
{
temp_type = PROP_ADD_BLACK;
}
else if (color == WHITE)
{
temp_type = PROP_ADD_WHITE;
}
else
{
temp_type = PROP_ADD_EMPTY;
}
prop_handle = add_prop_sgf (handle, temp_type, temp_data);
if (prop_handle < 0)
{
/* TODO: add code to completely remove the child which we
added, and then uncomment the following line. probably
doens't matter much since we're out of memory, but
whatever
free_storage_sgf(handle); */
rb->splash (2 * HZ, "Out of memory!");
return false;
}
set_game_modified();
/* now, "choose" the variation that we just added */
current_node = get_node (current_node)->next;
temp_data.number = var_number;
temp = add_or_set_prop_sgf (current_node,
PROP_VARIATION_CHOICE, temp_data);
/* free up a superfluous prop */
if (var_number == 0)
{
delete_prop_handle_sgf (current_node, temp);
}
current_node = saved;
/* and follow to our choice, returning since we already did the
work */
return redo_node_sgf ();
}
return false;
}
bool
undo_node_sgf (void)
{
int result = undo_node_helper ();
/* if we undid a ko threat, we need to figure out what the ko_pos is
there's no simple way to do this except to undo one /more/ move,
and then redo back to this location. (we could store it, but this
isn't that bad) Note: this doesn't need to recurse because we don't
care what previous move's ko positions were (since the tree is
already set in stone basically, it wouldn't change anything). */
if (result == 1)
{
int backward_move_num = move_num - 1;
int saved_current = current_node;
while (move_num > backward_move_num)
{
result = undo_node_helper ();
if (result < 0)
{
DEBUGF
("couldn't undo to previous move in ko threat handling!\n");
return false;
}
}
/* now we're backed up to the previous move before our destination
so, let's go forward again until we get to the node we were at
*/
while (current_node != saved_current)
{
if (!redo_node_sgf ())
{
DEBUGF
("redoing to correct node failed on ko threat handling!\n");
return false;
}
}
}
else if (result < 0)
{
DEBUGF ("initial undo failed!\n");
return false;
}
set_all_marks_sgf ();
/* if there is a move in this node, move the screen so that it is
visible */
int handle = get_move_sgf ();
if (handle >= 0)
{
move_display (get_prop (handle)->data.position);
}
return true;
}
static int
undo_node_helper (void)
{
bool ko_threat_move = false;
if (current_node == start_node)
{
/* refuse to undo the initial SGF node, which is tree_head if
handicap == 0 or 1. If handicap >= 2, start_node is the node
with the handicap crap and added moves on it. don't let the
user undo past that */
DEBUGF ("not undoing start_node\n");
return -1;
}
struct prop_t *temp_move = get_prop (get_move_sgf ());
if (temp_move)
{
int undone_caps = 0;
int undone_suicides = 0;
unsigned short move_pos = temp_move->data.position;
unsigned char move_color = temp_move->type == PROP_BLACK_MOVE ? BLACK :
WHITE;
unsigned short flags = temp_move->data.stone_extra;
if (move_pos != PASS_POS)
{
if (flags & FLAG_N_CAP)
{
undone_caps += flood_fill_board (NORTH (move_pos),
OTHER (move_color));
}
if (flags & FLAG_S_CAP)
{
undone_caps += flood_fill_board (SOUTH (move_pos),
OTHER (move_color));
}
if (flags & FLAG_E_CAP)
{
undone_caps += flood_fill_board (EAST (move_pos),
OTHER (move_color));
}
if (flags & FLAG_W_CAP)
{
undone_caps += flood_fill_board (WEST (move_pos),
OTHER (move_color));
}
if (flags & FLAG_SELF_CAP)
{
undone_suicides += flood_fill_board (move_pos, move_color);
}
if (flags & FLAG_ORIG_EMPTY)
{
set_point_board (move_pos, EMPTY);
}
else if (flags & FLAG_ORIG_BLACK)
{
set_point_board (move_pos, BLACK);
}
else
{
set_point_board (move_pos, WHITE);
}
}
if (move_color == BLACK)
{
black_captures -= undone_caps;
white_captures -= undone_suicides;
}
else
{
white_captures -= undone_caps;
black_captures -= undone_suicides;
}
if (flags & FLAG_KO_THREAT)
{
ko_threat_move = true;
}
--move_num;
current_player = OTHER (current_player);
}
else
{
/* test for added stones! */
struct prop_t *temp_prop;
temp_prop = get_prop (get_node (current_node)->props);
while (temp_prop)
{
if ((temp_prop->type == PROP_ADD_BLACK ||
temp_prop->type == PROP_ADD_WHITE ||
temp_prop->type == PROP_ADD_EMPTY) &&
on_board (temp_prop->data.position))
{
if (temp_prop->data.stone_extra & FLAG_ORIG_EMPTY)
{
set_point_board (temp_prop->data.position, EMPTY);
}
else if (temp_prop->data.stone_extra & FLAG_ORIG_BLACK)
{
set_point_board (temp_prop->data.position, BLACK);
}
else
{
set_point_board (temp_prop->data.position, WHITE);
}
}
temp_prop = get_prop (temp_prop->next);
}
}
if (!retreat_node ())
{
return -1;
}
if (ko_threat_move)
{
return 1;
}
return 0;
}
bool
redo_node_sgf (void)
{
if (!advance_node ())
{
return false;
}
set_all_marks_sgf ();
int temp_move = get_move_sgf ();
if (temp_move >= 0)
{
struct prop_t *move_prop = get_prop (temp_move);
unsigned short pos = move_prop->data.position;
unsigned char color =
move_prop->type == PROP_BLACK_MOVE ? BLACK : WHITE;
if (color != current_player)
{
DEBUGF ("redo_node_sgf: wrong color!\n");
}
/* zero out the undo information and set the ko threat flag to the
correct value */
move_prop->data.stone_extra = 0;
if (ko_pos != INVALID_POS)
{
move_prop->data.stone_extra |= FLAG_KO_THREAT;
}
ko_pos = INVALID_POS;
if (pos == PASS_POS)
{
rb->splashf (HZ / 2, "%s Passes",
color == BLACK ? "Black" : "White");
}
else
{
int n_cap, s_cap, e_cap, w_cap, self_cap;
n_cap = s_cap = e_cap = w_cap = self_cap = 0;
if (get_point_board (pos) == EMPTY)
{
move_prop->data.stone_extra |= FLAG_ORIG_EMPTY;
}
else if (get_point_board (pos) == BLACK)
{
move_prop->data.stone_extra |= FLAG_ORIG_BLACK;
}
/* else do nothing */
set_point_board (pos, color);
/* do captures on the 4 cardinal directions, if the opponent
stones are breathless */
if (get_point_board (NORTH (pos)) == OTHER (color) &&
get_liberties_board (NORTH (pos)) == 0)
{
n_cap = flood_fill_board (NORTH (pos), EMPTY);
move_prop->data.stone_extra |= FLAG_N_CAP;
}
if (get_point_board (SOUTH (pos)) == OTHER (color) &&
get_liberties_board (SOUTH (pos)) == 0)
{
s_cap = flood_fill_board (SOUTH (pos), EMPTY);
move_prop->data.stone_extra |= FLAG_S_CAP;
}
if (get_point_board (EAST (pos)) == OTHER (color) &&
get_liberties_board (EAST (pos)) == 0)
{
e_cap = flood_fill_board (EAST (pos), EMPTY);
move_prop->data.stone_extra |= FLAG_E_CAP;
}
if (get_point_board (WEST (pos)) == OTHER (color) &&
get_liberties_board (WEST (pos)) == 0)
{
w_cap = flood_fill_board (WEST (pos), EMPTY);
move_prop->data.stone_extra |= FLAG_W_CAP;
}
/* then check for suicide */
if (get_liberties_board (pos) == 0)
{
self_cap = flood_fill_board (pos, EMPTY);
move_prop->data.stone_extra |= FLAG_SELF_CAP;
}
/* now check for a ko, with the following requirements: 1) we
captured one opponent stone 2) we placed one stone (not
connected to a larger group) 3) we have one liberty */
if (!self_cap &&
(n_cap + s_cap + e_cap + w_cap == 1) &&
get_liberties_board (pos) == 1 &&
get_point_board (NORTH (pos)) != color &&
get_point_board (SOUTH (pos)) != color &&
get_point_board (EAST (pos)) != color &&
get_point_board (WEST (pos)) != color)
{
/* We passed all tests, so there is a ko to set. The
ko_pos is our single liberty location */
if (get_point_board (NORTH (pos)) == EMPTY)
{
ko_pos = NORTH (pos);
}
else if (get_point_board (SOUTH (pos)) == EMPTY)
{
ko_pos = SOUTH (pos);
}
else if (get_point_board (EAST (pos)) == EMPTY)
{
ko_pos = EAST (pos);
}
else
{
ko_pos = WEST (pos);
}
}
if (color == BLACK)
{
black_captures += n_cap + s_cap + e_cap + w_cap;
white_captures += self_cap;
}
else
{
white_captures += n_cap + s_cap + e_cap + w_cap;
black_captures += self_cap;
}
/* this will move the cursor near this move if it was off the
screen */
move_display (pos);
}
++move_num;
current_player = OTHER (color);
goto redo_node_sgf_succeeded;
}
else if (do_add_stones ())
{
goto redo_node_sgf_succeeded;
}
return false;
char comment_buffer[512];
redo_node_sgf_succeeded:
#if !defined(GBN_TEST)
if (auto_show_comments &&
read_comment_sgf (comment_buffer, sizeof (comment_buffer)))
{
unsigned int i;
for (i = 0; i < sizeof (comment_buffer); ++i)
{
/* newlines display badly in rb->splash, so replace them
* with spaces
*/
if (comment_buffer[i] == '\n' ||
comment_buffer[i] == '\r')
{
comment_buffer[i] = ' ';
}
else if (comment_buffer[i] == '\0')
{
break;
}
}
draw_screen_display();
rb->splash(HZ / 3, comment_buffer);
rb->button_clear_queue();
rb->action_userabort(TIMEOUT_BLOCK);
}
#else
(void) comment_buffer;
#endif
return true;
}
int
mark_child_variations_sgf (void)
{
int result;
int saved = current_node;
struct node_t *node = get_node (current_node);
int move_handle;
if (!node)
{
return 0;
}
current_node = node->next;
goto_next_important_node (true);
result = num_variations_sgf ();
if (result > 1)
{
int i;
int branch_node = current_node;
for (i = 0; i < result; ++i)
{
go_to_variation_sgf (i);
goto_next_important_node (true);
move_handle = get_move_sgf ();
if (move_handle >= 0)
{
set_one_mark (get_prop (move_handle)->data.position,
get_prop (move_handle)->type);
}
current_node = branch_node;
}
}
current_node = saved;
return result;
}
void
set_all_marks_sgf (void)
{
struct prop_t *prop = get_prop (get_node (current_node)->props);
while (prop)
{
if (prop->type == PROP_LABEL)
{
set_label_mark (prop->data.position, prop->data.label_extra);
}
else if (prop->type == PROP_COMMENT)
{
set_comment_display (true);
}
else
{
set_one_mark (prop->data.position, prop->type);
}
prop = get_prop (prop->next);
}
}
static void
set_one_mark (unsigned short pos, enum prop_type_t type)
{
switch (type)
{
case PROP_CIRCLE:
set_mark_display (pos, 'c');
break;
case PROP_SQUARE:
set_mark_display (pos, 's');
break;
case PROP_TRIANGLE:
set_mark_display (pos, 't');
break;
case PROP_MARK:
set_mark_display (pos, 'm');
break;
case PROP_DIM:
set_mark_display (pos, 'd');
break;
case PROP_SELECTED:
set_mark_display (pos, 'S');
break;
case PROP_BLACK_MOVE:
set_mark_display (pos, 'b');
break;
case PROP_WHITE_MOVE:
set_mark_display (pos, 'w');
break;
case PROP_INVALID:
set_mark_display (pos, ' ');
default:
break;
}
}
static void
set_label_mark (unsigned short pos, char to_set)
{
set_mark_display (pos, to_set | (1 << 7));
}
static bool
do_add_stones (void)
{
bool ret_val = false;
struct prop_t *temp_prop;
int temp_handle;
if (current_node < 0)
{
return false;
}
temp_handle = get_node (current_node)->props;
temp_prop = get_prop (temp_handle);
while (temp_prop)
{
if (temp_prop->type == PROP_ADD_BLACK ||
temp_prop->type == PROP_ADD_WHITE ||
temp_prop->type == PROP_ADD_EMPTY)
{
temp_prop->data.stone_extra = 0;
/* TODO: we could delete do-nothing PROP_ADD_*s here */
if (get_point_board (temp_prop->data.position) == EMPTY)
{
temp_prop->data.stone_extra |= FLAG_ORIG_EMPTY;
}
else if (get_point_board (temp_prop->data.position) == BLACK)
{
temp_prop->data.stone_extra |= FLAG_ORIG_BLACK;
}
/* else, do nothing */
if (temp_prop->type == PROP_ADD_BLACK ||
temp_prop->type == PROP_ADD_WHITE)
{
set_point_board (temp_prop->data.position,
temp_prop->type == PROP_ADD_BLACK ? BLACK :
WHITE);
ret_val = true;
}
else
{
set_point_board (temp_prop->data.position, EMPTY);
ret_val = true;
}
}
temp_handle = temp_prop->next;
temp_prop = get_prop (temp_handle);
}
return ret_val;
}
int
add_child_sgf (int *variation_number)
{
int node_handle;
struct node_t *node;
struct node_t *current = get_node (current_node);
if (current->next < 0)
{
node_handle = alloc_storage_sgf ();
node = get_node (node_handle);
if (node_handle < 0 || !current)
{
return NO_NODE;
}
node->prev = current_node;
node->next = NO_NODE;
node->props = NO_PROP;
current->next = node_handle;
if (variation_number)
{
*variation_number = 0;
}
return node_handle;
}
else
{
return add_child_variation (variation_number);
}
}
int
add_prop_sgf (int node, enum prop_type_t type, union prop_data_t data)
{
int new_prop;
int temp_prop_handle;
if (node < 0)
{
return NO_PROP;
}
new_prop = alloc_storage_sgf ();
if (new_prop < 0)
{
return NO_PROP;
}
get_prop (new_prop)->type = type;
get_prop (new_prop)->data = data;
/* check if the new_prop goes at the start */
if (get_node (node)->props == NO_PROP ||
(type == PROP_VARIATION &&
get_prop (get_node (node)->props)->type != PROP_VARIATION))
{
if (get_node (node)->props >= 0)
{
get_prop (new_prop)->next = get_node (node)->props;
}
else
{
get_prop (new_prop)->next = NO_PROP;
}
get_node (node)->props = new_prop;
return new_prop;
}
temp_prop_handle = get_node (node)->props;
while (1)
{
if (get_prop (temp_prop_handle)->next < 0 ||
(get_prop (temp_prop_handle)->type == type &&
get_prop (get_prop (temp_prop_handle)->next)->type != type))
{
/* new_prop goes after the current one either because we're at
the end of the props list, or because we're adding a prop
after the ones of its same type */
get_prop (new_prop)->next = get_prop (temp_prop_handle)->next;
get_prop (temp_prop_handle)->next = new_prop;
return new_prop;
}
temp_prop_handle = get_prop (temp_prop_handle)->next;
}
}
int
get_prop_pos_sgf (enum prop_type_t type, union prop_data_t data)
{
int temp_prop_handle;
struct prop_t *prop;
if (current_node < 0)
{
return -1;
}
temp_prop_handle = get_node (current_node)->props;
while (temp_prop_handle >= 0)
{
prop = get_prop (temp_prop_handle);
if ((type == PROP_ADD_BLACK ||
type == PROP_ADD_WHITE ||
type == PROP_ADD_EMPTY)
&&
(prop->type == PROP_ADD_BLACK ||
prop->type == PROP_ADD_WHITE ||
prop->type == PROP_ADD_EMPTY)
&& (prop->data.position == data.position))
{
return temp_prop_handle;
}
if ((type == PROP_CIRCLE ||
type == PROP_SQUARE ||
type == PROP_TRIANGLE ||
type == PROP_MARK ||
type == PROP_DIM ||
type == PROP_SELECTED) &&
(prop->type == PROP_CIRCLE ||
prop->type == PROP_SQUARE ||
prop->type == PROP_TRIANGLE ||
prop->type == PROP_MARK ||
prop->type == PROP_DIM ||
prop->type == PROP_SELECTED) &&
(prop->data.position == data.position))
{
return temp_prop_handle;
}
temp_prop_handle = get_prop (temp_prop_handle)->next;
}
return -1;
}
int
add_or_set_prop_sgf (int node, enum prop_type_t type, union prop_data_t data)
{
int temp_prop_handle;
if (node < 0)
{
return NO_PROP;
}
temp_prop_handle = get_prop_sgf (node, type, NULL);
if (temp_prop_handle >= 0)
{
get_prop (temp_prop_handle)->data = data;
return temp_prop_handle;
}
/* there was no prop to set, so we need to add one */
return add_prop_sgf (node, type, data);
}
int
get_prop_sgf (int node, enum prop_type_t type, int *previous_prop)
{
int previous_handle = NO_PROP;
int current_handle;
if (node < 0)
{
return NO_PROP;
}
if (get_node (node)->props < 0)
{
return NO_PROP;
}
current_handle = get_node (node)->props;
while (current_handle >= 0)
{
if (get_prop (current_handle)->type == type || type == PROP_ANY)
{
if (previous_prop)
{
*previous_prop = previous_handle;
}
return current_handle;
}
else
{
previous_handle = current_handle;
current_handle = get_prop (current_handle)->next;
}
}
return NO_PROP;
}
bool
delete_prop_sgf (int node, enum prop_type_t type)
{
if (node < 0)
{
return false;
}
return delete_prop_handle_sgf (node, get_prop_sgf (node, type, NULL));
}
bool
delete_prop_handle_sgf (int node, int prop)
{
int previous;
if (prop < 0 || node < 0 || get_node (node)->props < 0)
{
return false;
}
if (get_node (node)->props == prop)
{
get_node (node)->props = get_prop (get_node (node)->props)->next;
free_storage_sgf (prop);
return true;
}
previous = get_node (node)->props;
while (get_prop (previous)->next != prop && get_prop (previous)->next >= 0)
{
previous = get_prop (previous)->next;
}
if (get_prop (previous)->next < 0)
{
return false;
}
else
{
get_prop (previous)->next = get_prop (get_prop (previous)->next)->next;
free_storage_sgf (prop);
return true;
}
}
int
read_comment_sgf (char *buffer, size_t buffer_size)
{
size_t bytes_read = 0;
int prop_handle = get_prop_sgf (current_node, PROP_COMMENT, NULL);
if (prop_handle < 0)
{
return 0;
}
if (unhandled_fd < 0)
{
DEBUGF ("unhandled file is closed?!\n");
return 0;
}
if (rb->lseek (unhandled_fd, get_prop (prop_handle)->data.number,
SEEK_SET) < 0)
{
DEBUGF ("couldn't seek in unhandled_fd\n");
return -1;
}
if (!read_char_no_whitespace (unhandled_fd) == 'C' ||
!read_char_no_whitespace (unhandled_fd) == '[')
{
DEBUGF ("comment prop points to incorrect place in unhandled_fd!!\n");
return -1;
}
/* make output a string, the lazy way */
rb->memset (buffer, 0, buffer_size);
++bytes_read;
bool done = false;
bool escaped = false;
while ((buffer_size > bytes_read) && !done)
{
int temp = read_char (unhandled_fd);
switch (temp)
{
case ']':
if (!escaped)
{
done = true;
break;
}
*buffer = temp;
++buffer;
++bytes_read;
escaped = false;
break;
case -1:
DEBUGF ("encountered end of file before end of comment!\n");
done = true;
break;
case '\\':
escaped = !escaped;
if (escaped)
{
break;
}
default:
*buffer = temp;
++buffer;
++bytes_read;
escaped = false;
break;
}
}
return bytes_read;
}
int
write_comment_sgf (char *string)
{
char *orig_string = string;
int prop_handle = get_prop_sgf (current_node, PROP_COMMENT, NULL);
int start_of_comment = -1;
bool overwriting = false;
int bytes_written = 0;
set_game_modified();
if (unhandled_fd < 0)
{
DEBUGF ("unhandled file is closed?!\n");
return 0;
}
if (prop_handle >= 0)
{
if ((start_of_comment = rb->lseek (unhandled_fd,
get_prop (prop_handle)->data.number,
SEEK_SET)) < 0)
{
DEBUGF ("couldn't seek in unhandled_fd\n");
return -1;
}
else
{
overwriting = true;
}
}
else
{
overwriting = false;
}
start_of_write_wcs:
if (overwriting)
{
if (!read_char_no_whitespace (unhandled_fd) == 'C' ||
!read_char_no_whitespace (unhandled_fd) == '[')
{
DEBUGF ("non-comment while overwriting!!\n");
return -1;
}
}
else
{
start_of_comment = rb->lseek (unhandled_fd, 0, SEEK_END);
if (start_of_comment < 0)
{
DEBUGF ("error seeking to end in write_comment_sgf\n");
return -1;
}
write_char (unhandled_fd, 'C');
write_char (unhandled_fd, '[');
}
bool overwrite_escaped = false;
while (*string)
{
if (overwriting)
{
int orig_char = peek_char (unhandled_fd);
switch (orig_char)
{
case '\\':
overwrite_escaped = !overwrite_escaped;
break;
case ']':
if (overwrite_escaped)
{
overwrite_escaped = false;
break;
}
/* otherwise, fall through */
case -1:
/* we reached the end of the part we can put our comment
in, but there's more comment to write, so we should
start again, this time making a new comment (the old
becomes wasted space in unhandled_fd, but it doesn't
really hurt anything except extra space on disk */
overwriting = false;
string = orig_string;
bytes_written = 0;
goto start_of_write_wcs;
break;
default:
overwrite_escaped = false;
break;
}
}
switch (*string)
{
case '\\':
case ']':
write_char (unhandled_fd, '\\');
/* fall through */
default:
write_char (unhandled_fd, *string);
break;
}
++string;
++bytes_written;
}
/* finish out the record */
write_char (unhandled_fd, ']');
write_char (unhandled_fd, ';');
/* and put the reference into the unhandled_fd into the comment prop */
union prop_data_t temp_data;
temp_data.number = start_of_comment;
add_or_set_prop_sgf (current_node, PROP_COMMENT, temp_data);
set_comment_display (true);
return bytes_written;
}
bool
has_more_nodes_sgf (void)
{
int saved = current_node;
bool ret_val = false;
if (current_node < 0)
{
return false;
}
current_node = get_node (current_node)->next;
if (current_node >= 0)
{
/* returns true if it finds an important node */
ret_val = goto_next_important_node (true);
}
current_node = saved;
return ret_val;
}
/* logic is different here because the first node in a tree is a valid
place to go */
bool
has_prev_nodes_sgf (void)
{
if (current_node < 0)
{
return false;
}
return current_node != start_node;
}
int
get_move_sgf (void)
{
int result = -1;
int saved_current = current_node;
goto_next_important_node (true);
result = get_move_from_node (current_node);
current_node = saved_current;
return result;
}
static int
get_move_from_node (int handle)
{
int prop_handle;
if (handle < 0)
{
return -2;
}
prop_handle = get_node (handle)->props;
while (prop_handle >= 0)
{
if (get_prop (prop_handle)->type == PROP_BLACK_MOVE ||
get_prop (prop_handle)->type == PROP_WHITE_MOVE)
{
return prop_handle;
}
prop_handle = get_prop (prop_handle)->next;
}
return -1;
}
static bool
retreat_node (void)
{
int result = get_node (current_node)->prev;
if (current_node == start_node)
{
return false;
}
if (result < 0)
{
return false;
}
else
{
clear_marks_display ();
current_node = result;
/* go backwards to the next important node (move/add
stone/variation/etc.) */
goto_next_important_node (false);
return true;
}
}
static bool
advance_node (void)
{
int result = get_node (current_node)->next;
if (result < 0)
{
return false;
}
else
{
clear_marks_display ();
current_node = result;
/* go forward to the next move/add stone/variation/etc. node */
goto_next_important_node (true);
result = get_prop_sgf (current_node, PROP_VARIATION_CHOICE, NULL);
if (result >= 0)
{
go_to_variation_sgf (get_prop (result)->data.number);
}
return true;
}
}
int
num_variations_sgf (void)
{
int result = 1;
struct prop_t *temp_prop;
struct node_t *temp_node = get_node (current_node);
if (temp_node == 0)
{
return 0;
}
if (temp_node->prev >= 0)
{
temp_node = get_node (get_node (temp_node->prev)->next);
}
temp_prop = get_prop (temp_node->props);
while (temp_prop)
{
if (temp_prop->type == PROP_VARIATION)
{
++result;
}
else
{
/* variations are at the beginning of the prop list */
break;
}
temp_prop = get_prop (temp_prop->next);
}
return result;
}
bool
go_to_variation_sgf (unsigned int num)
{
int saved = current_node;
struct node_t *temp_node = get_node (current_node);
struct prop_t *temp_prop;
if (!temp_node)
{
return false;
}
temp_node = get_node (temp_node->prev);
if (!temp_node)
{
return false;
}
temp_node = get_node (current_node = temp_node->next);
if (!temp_node)
{
current_node = saved;
return false;
}
temp_prop = get_prop (temp_node->props);
while (num)
{
if (!temp_prop || temp_prop->type != PROP_VARIATION)
{
current_node = saved;
return false;
}
if (num == 1)
{
current_node = temp_prop->data.node;
break;
}
temp_prop = get_prop (temp_prop->next);
--num;
}
return true;
}
int
get_matching_child_sgf (unsigned short pos, unsigned char color)
{
struct node_t *temp_node;
struct prop_t *temp_prop;
int variation_count = 0;
int saved;
/* set true later in a loop if we want a prop in the first variation
which means that we shouldn't check that branch for children */
bool dont_check_first_var_children = false;
temp_node = get_node (current_node);
if (!temp_node)
{
return -3;
}
temp_node = get_node (temp_node->next);
if (!temp_node)
{
return -2;
}
temp_prop = get_prop (temp_node->props);
while (temp_prop)
{
if (temp_prop->type == PROP_VARIATION)
{
++variation_count;
saved = current_node;
current_node = temp_prop->data.node;
struct prop_t *temp_move = get_prop (get_move_sgf ());
current_node = saved;
if (temp_move &&
temp_move->data.position == pos &&
((color == BLACK && temp_move->type == PROP_BLACK_MOVE) ||
(color == WHITE && temp_move->type == PROP_WHITE_MOVE)))
{
return variation_count;
}
}
else if ((temp_prop->type == PROP_BLACK_MOVE && color == BLACK) ||
(temp_prop->type == PROP_WHITE_MOVE && color == WHITE))
{
if (temp_prop->data.position == pos)
{
return 0;
}
else
{
return -4;
}
}
else if (temp_prop->type == PROP_ADD_WHITE ||
temp_prop->type == PROP_ADD_BLACK ||
temp_prop->type == PROP_ADD_EMPTY)
{
dont_check_first_var_children = true;
}
temp_prop = get_prop (temp_prop->next);
}
if (dont_check_first_var_children)
{
return -1;
}
else
{
saved = current_node;
current_node = temp_node->next;
struct prop_t *temp_move = get_prop (get_move_sgf ());
if (temp_move &&
pos == temp_move->data.position &&
color == (temp_move->type == PROP_BLACK_MOVE ? BLACK : WHITE))
{
current_node = saved;
return 0;
}
current_node = saved;
return -1;
}
}
int
next_variation_sgf (void)
{
int saved = current_node;
int saved_start = start_node;
union prop_data_t temp_data;
int prop_handle;
int num_vars = 0;
if (current_node < 0 || get_node (current_node)->prev < 0)
{
return -1;
}
start_node = NO_NODE;
if (num_variations_sgf () < 2)
{
current_node = saved;
start_node = saved_start;
return -2;
}
/* now we're at a branch node which we should go to the next variation
(we were at the "chosen" one, so go to the next one after that,
(mod the number of variations)) */
int chosen = 0;
int branch_node = get_node (get_node (current_node)->prev)->next;
prop_handle = get_prop_sgf (branch_node, PROP_VARIATION_CHOICE, NULL);
if (prop_handle >= 0)
{
chosen = get_prop (prop_handle)->data.number;
}
++chosen;
if (chosen >= (num_vars = num_variations_sgf ()))
{
chosen = 0;
}
temp_data.number = chosen;
add_or_set_prop_sgf (branch_node, PROP_VARIATION_CHOICE, temp_data);
if (!undo_node_sgf ())
{
current_node = saved;
start_node = saved_start;
return -3;
}
if (redo_node_sgf ())
{
start_node = saved_start;
return chosen + 1;
}
else
{
current_node = saved;
start_node = saved_start;
return -4;
}
}
int
add_child_variation (int *variation_number)
{
struct node_t *temp_node = get_node (current_node);
struct prop_t *temp_prop;
int temp_prop_handle;
int new_node = alloc_storage_sgf ();
int new_prop = alloc_storage_sgf ();
int temp_variation_number;
if (new_node < 0 || new_prop < 0)
{
if (new_node >= 0)
{
free_storage_sgf (new_node);
}
if (new_prop >= 0)
{
free_storage_sgf (new_prop);
}
return NO_NODE;
}
if (!temp_node)
{
free_storage_sgf (new_node);
free_storage_sgf (new_prop);
return NO_NODE;
}
temp_node = get_node (temp_node->next);
if (!temp_node)
{
free_storage_sgf (new_node);
free_storage_sgf (new_prop);
return NO_NODE;
}
get_node (new_node)->prev = current_node;
get_node (new_node)->next = NO_NODE;
get_node (new_node)->props = NO_PROP;
get_prop (new_prop)->type = PROP_VARIATION;
get_prop (new_prop)->next = NO_PROP;
get_prop (new_prop)->data.node = new_node;
temp_prop_handle = temp_node->props;
if (temp_prop_handle < 0)
{
temp_node->props = new_prop;
if (variation_number)
{
*variation_number = 1;
}
return new_node;
}
if (get_prop (temp_prop_handle)->type != PROP_VARIATION)
{
get_prop (new_prop)->next = temp_node->props;
temp_node->props = new_prop;
if (variation_number)
{
*variation_number = 1;
}
return new_node;
}
/* the lowest it can be, since 1 isn't it */
temp_variation_number = 2;
while (1)
{
temp_prop = get_prop (temp_prop_handle);
if (temp_prop->next < 0 ||
get_prop (temp_prop->next)->type != PROP_VARIATION)
{
get_prop (new_prop)->next = temp_prop->next;
temp_prop->next = new_prop;
if (variation_number)
{
*variation_number = temp_variation_number;
}
return new_node;
}
++temp_variation_number;
temp_prop_handle = temp_prop->next;
}
}
static bool
is_important_node (int handle)
{
struct prop_t *temp_prop;
if (handle < 0)
{
return false;
}
if (handle == start_node)
{
return true;
}
if (get_node (handle)->prev < 0)
{
return true;
}
temp_prop = get_prop (get_node (handle)->props);
while (temp_prop)
{
if (temp_prop->type == PROP_BLACK_MOVE ||
temp_prop->type == PROP_WHITE_MOVE ||
temp_prop->type == PROP_ADD_BLACK ||
temp_prop->type == PROP_ADD_WHITE ||
temp_prop->type == PROP_ADD_EMPTY ||
temp_prop->type == PROP_VARIATION)
{
return true;
}
temp_prop = get_prop (temp_prop->next);
}
return false;
}
static bool
goto_next_important_node (bool forward)
{
int temp_node = current_node;
int last_good = temp_node;
while (temp_node >= 0 && !is_important_node (temp_node))
{
last_good = temp_node;
temp_node = forward ? get_node (temp_node)->next :
get_node (temp_node)->prev;
}
if (temp_node < 0)
{
current_node = last_good;
}
else
{
current_node = temp_node;
}
if (temp_node < 0)
{
return false;
}
else
{
return true;
}
}
bool
is_handled_sgf (enum prop_type_t type)
{
if (type == PROP_BLACK_MOVE ||
type == PROP_WHITE_MOVE ||
type == PROP_ADD_BLACK ||
type == PROP_ADD_WHITE ||
type == PROP_ADD_EMPTY ||
type == PROP_CIRCLE || type == PROP_SQUARE || type == PROP_TRIANGLE ||
#if 0
/* these marks are stupid and nobody uses them. if we could find
a good way to draw them we could do them anyway, but no reason
to unless it's easy */
type == PROP_DIM || type == PROP_SELECTED ||
#endif
type == PROP_COMMENT ||
type == PROP_MARK ||
type == PROP_LABEL ||
type == PROP_GAME ||
type == PROP_FILE_FORMAT ||
type == PROP_APPLICATION ||
type == PROP_CHARSET ||
type == PROP_SIZE ||
type == PROP_KOMI ||
type == PROP_BLACK_NAME ||
type == PROP_WHITE_NAME ||
type == PROP_BLACK_RANK ||
type == PROP_WHITE_RANK ||
type == PROP_BLACK_TEAM ||
type == PROP_WHITE_TEAM ||
type == PROP_DATE ||
type == PROP_ROUND ||
type == PROP_EVENT ||
type == PROP_PLACE ||
type == PROP_OVERTIME ||
type == PROP_RESULT ||
type == PROP_TIME_LIMIT ||
type == PROP_RULESET ||
type == PROP_HANDICAP || type == PROP_VARIATION_TYPE)
{
return true;
}
return false;
}
void
setup_handicap_sgf (void)
{
union prop_data_t temp_data;
if (header.handicap <= 1)
{
return;
}
current_node = start_node;
temp_data.number = header.handicap;
add_prop_sgf (current_node, PROP_HANDICAP, temp_data);
/* now, add the actual stones */
if ((board_width != 19 && board_width != 13 && board_width != 9) ||
board_width != board_height || header.handicap > 9)
{
rb->splashf (5 * HZ,
"Use the 'Add Black' tool to add %d handicap stones!",
header.handicap);
return;
}
int handicaps_to_place = header.handicap;
int low_coord = 0, mid_coord = 0, high_coord = 0;
if (board_width == 19)
{
low_coord = 3;
mid_coord = 9;
high_coord = 15;
}
else if (board_width == 13)
{
low_coord = 3;
mid_coord = 6;
high_coord = 9;
}
else if (board_width == 9)
{
low_coord = 2;
mid_coord = 4;
high_coord = 6;
}
/* first four go in the corners */
handicaps_to_place -= 2;
setup_handicap_helper (POS (high_coord, low_coord));
setup_handicap_helper (POS (low_coord, high_coord));
if (!handicaps_to_place)
{
goto done_adding_stones;
}
--handicaps_to_place;
setup_handicap_helper (POS (high_coord, high_coord));
if (!handicaps_to_place)
{
goto done_adding_stones;
}
--handicaps_to_place;
setup_handicap_helper (POS (low_coord, low_coord));
if (!handicaps_to_place)
{
goto done_adding_stones;
}
/* now done with first four, if only one left it goes in the center */
if (handicaps_to_place == 1)
{
--handicaps_to_place;
setup_handicap_helper (POS (mid_coord, mid_coord));
}
else
{
handicaps_to_place -= 2;
setup_handicap_helper (POS (high_coord, mid_coord));
setup_handicap_helper (POS (low_coord, mid_coord));
}
if (!handicaps_to_place)
{
goto done_adding_stones;
}
/* done with first 6 */
if (handicaps_to_place == 1)
{
--handicaps_to_place;
setup_handicap_helper (POS (mid_coord, mid_coord));
}
else
{
handicaps_to_place -= 2;
setup_handicap_helper (POS (mid_coord, high_coord));
setup_handicap_helper (POS (mid_coord, low_coord));
}
if (!handicaps_to_place)
{
goto done_adding_stones;
}
/* done with first eight, there can only be the tengen remaining */
setup_handicap_helper (POS (mid_coord, mid_coord));
done_adding_stones:
goto_handicap_start_sgf ();
return;
}
static void
setup_handicap_helper (unsigned short pos)
{
union prop_data_t temp_data;
temp_data.position = pos;
add_prop_sgf (current_node, PROP_ADD_BLACK, temp_data);
}
void
goto_handicap_start_sgf (void)
{
if (start_node != tree_head)
{
current_node = get_node (start_node)->prev;
redo_node_sgf ();
}
}
bool
post_game_setup_sgf (void)
{
int temp_handle = alloc_storage_sgf ();
int saved = current_node;
if (temp_handle < 0)
{
return false;
}
union prop_data_t temp_data;
temp_data.number = 0; /* meaningless */
if (!header_marked)
{
add_prop_sgf (tree_head, PROP_ROOT_PROPS, temp_data);
header_marked = true;
}
get_node (temp_handle)->next = current_node;
get_node (temp_handle)->prev = NO_NODE;
get_node (temp_handle)->props = NO_PROP;
current_node = temp_handle;
redo_node_sgf ();
if (current_node == temp_handle)
{
current_node = saved;
}
free_storage_sgf (temp_handle);
return true;
}