2292782839
I misunderstood the code a bit when I committed b58b9c2
. The
read_char_no_whitespace() probably have to be done.
Change-Id: I024420149a04dbeed4d714795282bb7f88a8e1da
2238 lines
53 KiB
C
2238 lines
53 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* 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;
|
|
}
|