summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMustapha Senhaji <moos@rockbox.org>2009-02-11 16:03:17 +0000
committerMustapha Senhaji <moos@rockbox.org>2009-02-11 16:03:17 +0000
commitf2d5c3532fd21c04e77aa86732742b578283b697 (patch)
tree1c55cb7e69ed11391f77187c86ff8c435ed85877
parent21f0c9a2829415f52b64cbdf965b01525e78f17a (diff)
downloadrockbox-f2d5c3532fd21c04e77aa86732742b578283b697.tar.gz
rockbox-f2d5c3532fd21c04e77aa86732742b578283b697.zip
New game plugin by Joshua Simmons FS#7369: Goban plugin, Go/Igo/Weiqi/Baduk game recorder and viewer.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@19972 a1c6a512-1295-4272-9138-f99709370657
-rw-r--r--apps/plugins/goban/SOURCES9
-rw-r--r--apps/plugins/goban/board.c354
-rw-r--r--apps/plugins/goban/board.h100
-rw-r--r--apps/plugins/goban/display.c1091
-rw-r--r--apps/plugins/goban/display.h111
-rw-r--r--apps/plugins/goban/game.c236
-rw-r--r--apps/plugins/goban/game.h59
-rw-r--r--apps/plugins/goban/goban.c1232
-rw-r--r--apps/plugins/goban/goban.h277
-rw-r--r--apps/plugins/goban/goban.make22
-rw-r--r--apps/plugins/goban/sgf.c2237
-rw-r--r--apps/plugins/goban/sgf.h170
-rw-r--r--apps/plugins/goban/sgf_output.c433
-rw-r--r--apps/plugins/goban/sgf_output.h30
-rw-r--r--apps/plugins/goban/sgf_parse.c857
-rw-r--r--apps/plugins/goban/sgf_parse.h30
-rw-r--r--apps/plugins/goban/sgf_storage.c493
-rw-r--r--apps/plugins/goban/sgf_storage.h57
-rw-r--r--apps/plugins/goban/types.h289
-rw-r--r--apps/plugins/goban/util.c885
-rw-r--r--apps/plugins/goban/util.h113
-rw-r--r--apps/plugins/viewers.config1
-rw-r--r--docs/CREDITS1
-rw-r--r--manual/plugins/goban.tex243
-rw-r--r--manual/plugins/main.tex2
25 files changed, 9332 insertions, 0 deletions
diff --git a/apps/plugins/goban/SOURCES b/apps/plugins/goban/SOURCES
new file mode 100644
index 0000000000..806c5404f2
--- /dev/null
+++ b/apps/plugins/goban/SOURCES
@@ -0,0 +1,9 @@
1goban.c
2board.c
3display.c
4game.c
5sgf.c
6sgf_output.c
7sgf_parse.c
8sgf_storage.c
9util.c
diff --git a/apps/plugins/goban/board.c b/apps/plugins/goban/board.c
new file mode 100644
index 0000000000..bc6c5347dc
--- /dev/null
+++ b/apps/plugins/goban/board.c
@@ -0,0 +1,354 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "goban.h"
23#include "board.h"
24#include "display.h"
25#include "types.h"
26#include "game.h"
27#include "util.h"
28
29#include "plugin.h"
30
31unsigned int board_width = MAX_BOARD_SIZE;
32unsigned int board_height = MAX_BOARD_SIZE;
33
34/* Board has 'invalid' markers around each border */
35unsigned char board_data[(MAX_BOARD_SIZE + 2) * (MAX_BOARD_SIZE + 2)];
36int white_captures = 0;
37int black_captures = 0;
38
39uint8_t board_marks[(MAX_BOARD_SIZE + 2) * (MAX_BOARD_SIZE + 2)];
40uint8_t current_mark = 255;
41
42/* there can't be any changes off of the board, so no need to add the
43 borders */
44uint8_t board_changes[MAX_BOARD_SIZE * MAX_BOARD_SIZE];
45
46unsigned short ko_pos = INVALID_POS;
47
48/* forward declarations */
49static void setup_marks (void);
50static void make_mark (unsigned short pos);
51static int get_liberties_helper (unsigned short pos, unsigned char orig_color);
52static int flood_fill_helper (unsigned short pos, unsigned char orig_color,
53 unsigned char color);
54
55
56/* these aren't "board marks" in the marks on the SGF sense, they are used
57 internally to mark already visited points and the like (such as when
58 doing liberty counting for groups) */
59static void
60setup_marks (void)
61{
62 unsigned int x, y;
63
64 current_mark++;
65
66 if (current_mark == 0)
67 {
68 current_mark++;
69
70 for (y = 0; y < board_height; ++y)
71 {
72 for (x = 0; x < board_width; ++x)
73 {
74 board_marks[POS (x, y)] = 0;
75 }
76 }
77 }
78}
79
80static void
81make_mark (unsigned short pos)
82{
83 board_marks[pos] = current_mark;
84}
85
86static bool
87is_marked (unsigned short pos)
88{
89 return board_marks[pos] == current_mark;
90}
91
92void
93clear_board (void)
94{
95 unsigned int i, x, y;
96
97 /* for the borders */
98 for (i = 0; i < (2 + MAX_BOARD_SIZE) * (2 + MAX_BOARD_SIZE); ++i)
99 {
100 board_data[i] = INVALID;
101 }
102
103 /* now make the actual board part */
104 for (y = 0; y < board_height; ++y)
105 {
106 for (x = 0; x < board_width; ++x)
107 {
108 board_data[POS (x, y)] = EMPTY;
109 }
110 }
111
112 white_captures = 0;
113 black_captures = 0;
114
115 ko_pos = INVALID_POS;
116}
117
118bool
119set_size_board (int width, int height)
120{
121 if (width < MIN_BOARD_SIZE || width > MAX_BOARD_SIZE ||
122 height < MIN_BOARD_SIZE || height > MAX_BOARD_SIZE)
123 {
124 return false;
125 }
126 else
127 {
128 board_width = width;
129 board_height = height;
130 setup_display ();
131 return true;
132 }
133}
134
135unsigned char
136get_point_board (unsigned short pos)
137{
138 return board_data[pos];
139}
140
141void
142set_point_board (unsigned short pos, unsigned char color)
143{
144 board_data[pos] = color;
145}
146
147bool
148on_board (unsigned short pos)
149{
150 if (pos < POS (0, 0) ||
151 pos > POS (board_width - 1, board_height - 1) ||
152 get_point_board (pos) == INVALID)
153 {
154 return false;
155 }
156 else
157 {
158 return true;
159 }
160}
161
162int
163get_liberties_board (unsigned short pos)
164{
165 if (!on_board (pos) || get_point_board (pos) == EMPTY)
166 {
167 return -1;
168 }
169
170 setup_marks ();
171
172 int ret_val = 0;
173 unsigned char orig_color = get_point_board (pos);
174
175 empty_stack (&parse_stack);
176 push_pos_stack (&parse_stack, pos);
177
178 /* Since we only ever test for liberties in order to determine
179 captures and the like, there's no reason to count any liberties
180 higher than 2 (we sometimes need to know if something has 1 liberty
181 for dealing with ko) */
182 while (pop_pos_stack (&parse_stack, &pos) && ret_val < 2)
183 {
184 ret_val += get_liberties_helper (NORTH (pos), orig_color);
185 ret_val += get_liberties_helper (SOUTH (pos), orig_color);
186 ret_val += get_liberties_helper (EAST (pos), orig_color);
187 ret_val += get_liberties_helper (WEST (pos), orig_color);
188 }
189
190 /* if there's more than two liberties, the stack isn't empty, so empty
191 it */
192 empty_stack (&parse_stack);
193
194 return ret_val;
195}
196
197static int
198get_liberties_helper (unsigned short pos, unsigned char orig_color)
199{
200 if (on_board (pos) &&
201 get_point_board (pos) != OTHER (orig_color) && !is_marked (pos))
202 {
203 make_mark (pos);
204
205 if (get_point_board (pos) == EMPTY)
206 {
207 return 1;
208 }
209 else
210 {
211 push_pos_stack (&parse_stack, pos);
212 }
213 }
214
215 return 0;
216}
217
218
219int
220flood_fill_board (unsigned short pos, unsigned char color)
221{
222 if (!on_board (pos) || get_point_board (pos) == color)
223 {
224 return 0;
225 }
226
227 empty_stack (&parse_stack);
228
229 int ret_val = 0;
230
231 unsigned char orig_color = get_point_board (pos);
232
233 set_point_board (pos, color);
234 ++ret_val;
235 push_pos_stack (&parse_stack, pos);
236
237 while (pop_pos_stack (&parse_stack, &pos))
238 {
239 ret_val += flood_fill_helper (NORTH (pos), orig_color, color);
240 ret_val += flood_fill_helper (SOUTH (pos), orig_color, color);
241 ret_val += flood_fill_helper (EAST (pos), orig_color, color);
242 ret_val += flood_fill_helper (WEST (pos), orig_color, color);
243 }
244
245 return ret_val;
246}
247
248
249static int
250flood_fill_helper (unsigned short pos, unsigned char orig_color,
251 unsigned char color)
252{
253 if (on_board (pos) && get_point_board (pos) == orig_color)
254 {
255 set_point_board (pos, color);
256 push_pos_stack (&parse_stack, pos);
257 return 1;
258 }
259
260 return 0;
261}
262
263bool
264legal_move_board (unsigned short pos, unsigned char color, bool allow_suicide)
265{
266 /* you can always pass */
267 if (pos == PASS_POS)
268 {
269 return true;
270 }
271
272 if (!on_board (pos) || (color != BLACK && color != WHITE))
273 {
274 return false;
275 }
276
277 if (pos == ko_pos && color == current_player)
278 {
279 return false;
280 }
281
282 if (get_point_board (pos) != EMPTY)
283 {
284 return false;
285 }
286
287 /* don't need to save the current state, because it's always empty
288 since we tested for that above */
289 set_point_board (pos, color);
290
291 /* if we have liberties, it can't be illegal */
292 if (get_liberties_board (pos) > 0 ||
293 /* if we can capture something, it can't be illegal */
294 (get_point_board (NORTH (pos)) == OTHER (color) &&
295 !get_liberties_board (NORTH (pos))) ||
296 (get_point_board (SOUTH (pos)) == OTHER (color) &&
297 !get_liberties_board (SOUTH (pos))) ||
298 (get_point_board (EAST (pos)) == OTHER (color) &&
299 !get_liberties_board (EAST (pos))) ||
300 (get_point_board (WEST (pos)) == OTHER (color) &&
301 !get_liberties_board (WEST (pos))) ||
302 /* if we're allowed to suicide, only multi-stone suicide is legal
303 (no ruleset allows single-stone suicide that I know of) */
304 (allow_suicide && (get_point_board (NORTH (pos)) == color ||
305 get_point_board (SOUTH (pos)) == color ||
306 get_point_board (EAST (pos)) == color ||
307 get_point_board (WEST (pos)) == color)))
308 {
309 /* undo our previous set */
310 set_point_board (pos, EMPTY);
311 return true;
312 }
313 else
314 {
315 /* undo our previous set */
316 set_point_board (pos, EMPTY);
317 return false;
318 }
319}
320
321
322unsigned short
323WRAP (unsigned short pos)
324{
325 int x, y;
326 if (on_board (pos))
327 {
328 return pos;
329 }
330 else
331 {
332 x = I (pos);
333 y = J (pos);
334
335 if (x < 0)
336 {
337 x = board_width - 1;
338 }
339 else if ((unsigned int) x >= board_width)
340 {
341 x = 0;
342 }
343
344 if (y < 0)
345 {
346 y = board_height - 1;
347 }
348 else if ((unsigned int) y >= board_height)
349 {
350 y = 0;
351 }
352 return POS (x, y);
353 }
354}
diff --git a/apps/plugins/goban/board.h b/apps/plugins/goban/board.h
new file mode 100644
index 0000000000..cd6f01e79a
--- /dev/null
+++ b/apps/plugins/goban/board.h
@@ -0,0 +1,100 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#ifndef GOBAN_BOARD_H
23#define GOBAN_BOARD_H
24
25#include "types.h"
26#include "goban.h" /* for LCD_BOARD_SIZE */
27
28#define WHITE 1
29#define BLACK 2
30#define EMPTY 0
31#define NONE 0
32#define INVALID 4
33
34#define OTHER(color) (color == BLACK ? WHITE : BLACK)
35
36 /* MAX_BOARD_SIZE no longer dependent on screen
37 size since zooming was implemented */
38#define MAX_BOARD_SIZE 19
39#define MIN_BOARD_SIZE 1
40
41#define INVALID_POS ((unsigned short) -5003)
42#define PASS_POS ((unsigned short) -5002)
43
44#define POS(i, j) ((i + 1) + (j + 1) * (MAX_BOARD_SIZE + 2))
45#define WEST(i) (i - 1)
46#define EAST(i) (i + 1)
47#define NORTH(i) (i - (MAX_BOARD_SIZE + 2))
48#define SOUTH(i) (i + (MAX_BOARD_SIZE + 2))
49
50unsigned short WRAP (unsigned short pos);
51
52#define I(pos) (pos % (MAX_BOARD_SIZE + 2) - 1)
53#define J(pos) (pos / (MAX_BOARD_SIZE + 2) - 1)
54
55/* Clear the data from the board, including marks and such. Should be
56 called after setting the board size */
57void clear_board (void);
58
59/* Set the size of the board. Follow with a call to clear_board() and a
60 call to setup_display(). Returns false on failure (generally, invalid
61 width or height) */
62bool set_size_board (int width, int height);
63
64/* Returns true if the given move is legal. allow_suicide should be true
65 if suicide is a valid move with the current ruleset */
66bool legal_move_board (unsigned short pos, unsigned char color,
67 bool allow_suicide);
68
69/* Returns true if the pos is on the board */
70bool on_board (unsigned short pos);
71
72/* Get the color on the board at the given pos. Should be
73 BLACK/WHITE/EMPTY, and sometimes INVALID. */
74unsigned char get_point_board (unsigned short pos);
75
76/* Set the color of point at pos, which must be on the board */
77void set_point_board (unsigned short pos, unsigned char color);
78
79/* Get the number of liberties of the group of which pos is a stone.
80 Returns less than zero if pos is empty. If the number of liberties of
81 the group is greater than 2, 2 is returned. */
82int get_liberties_board (unsigned short pos);
83
84/* A simple flood fill algorithm for capturing or uncapturing stones.
85 Returns the number of locations changed. */
86int flood_fill_board (unsigned short pos, unsigned char color);
87
88/* The size of the board */
89extern unsigned int board_width;
90extern unsigned int board_height;
91
92/* The number of captures for each player */
93extern int black_captures;
94extern int white_captures;
95
96/* If there is a ko which cannot be retaken, this is set to the point
97 which may not be played at. Otherwise this is INVALID_POS. */
98extern unsigned short ko_pos;
99
100#endif
diff --git a/apps/plugins/goban/display.c b/apps/plugins/goban/display.c
new file mode 100644
index 0000000000..35a5de45f6
--- /dev/null
+++ b/apps/plugins/goban/display.c
@@ -0,0 +1,1091 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "board.h"
23#include "goban.h"
24#include "display.h"
25#include "game.h"
26#include "sgf.h"
27
28/* for xlcd_filltriangle */
29#include "lib/xlcd.h"
30
31unsigned int intersection_size = 0;
32
33#define LINE_OFFSET (intersection_size / 2)
34
35/* pixel offsets for the board on the LCD */
36int board_x = 0;
37int board_y = 0;
38int board_pixel_width = 0;
39int board_pixel_height = 0;
40
41/* current cursor position in board coordinates (intersections, not
42 pixels) */
43unsigned short cursor_pos = POS (0, 0);
44
45/* we way need to "move" our notion of which intersections to draw when
46 the cursor moves, since we can be zoomed in (and often will be) this is
47 used to signal when we need to do that */
48unsigned short last_cursor_pos = INVALID_POS;
49unsigned int last_int_size = MAX_INT_SIZE + 1;
50
51unsigned int min_x_int = 0;
52unsigned int min_y_int = 0;
53unsigned int num_x_ints = 0;
54unsigned int num_y_ints = 0;
55
56int extend_t = 0, extend_b = 0, extend_l = 0, extend_r = 0;
57
58bool draw_variations = true;
59unsigned int saved_circle_size = 0;
60bool has_comment = false;
61
62unsigned char display_marks[MAX_BOARD_SIZE * MAX_BOARD_SIZE];
63
64
65/* function prototypes */
66
67static int pixel_x (unsigned short pos);
68static int pixel_y (unsigned short pos);
69
70static void draw_circle (int c_x, int c_y, int r, bool filled);
71
72static void draw_cursor (unsigned short pos);
73static void draw_stone_raw (int pixel_x, int pixel_y, bool black);
74static void draw_stone (unsigned short pos, bool black);
75static void draw_all_stones (void);
76
77static void draw_hoshi (unsigned short pos);
78static void draw_footer (void);
79static void draw_all_marks (void);
80static void draw_all_hoshi (void);
81
82static unsigned int unzoomed_int_size (void);
83static void cursor_updated (void);
84
85void
86clear_marks_display (void)
87{
88 rb->memset (display_marks, ' ', sizeof (display_marks));
89 has_comment = false;
90}
91
92void
93set_mark_display (unsigned short pos, unsigned char mark_char)
94{
95 if (!on_board (pos))
96 {
97 return;
98 }
99
100 if ((mark_char == 'b' || mark_char == 'w') &&
101 display_marks[I (pos) + J (pos) * board_width] != ' ')
102 {
103 /* don't overwrite real board marks with last-move or variation
104 marks */
105 return;
106 }
107
108 display_marks[I (pos) + J (pos) * board_width] = mark_char;
109}
110
111
112void
113set_comment_display (bool new_val)
114{
115 has_comment = new_val;
116}
117
118
119static void
120draw_all_marks (void)
121{
122 unsigned int x, y;
123 for (x = MIN_X; x < MAX_X; ++x)
124 {
125 for (y = MIN_Y; y < MAX_Y; ++y)
126 {
127 if (display_marks[x + y * board_width] != ' ')
128 {
129#if LCD_DEPTH > 1
130 if (display_marks[x + y * board_width] != 'b' &&
131 display_marks[x + y * board_width] != 'w')
132 {
133 rb->lcd_set_foreground (MARK_COLOR);
134 }
135 else
136 {
137 rb->lcd_set_foreground (CURSOR_COLOR);
138 }
139 rb->lcd_set_drawmode (DRMODE_FG);
140#else
141 rb->lcd_set_drawmode (DRMODE_FG + DRMODE_COMPLEMENT);
142#endif
143
144 if (display_marks[x + y * board_width] & (1 << 7))
145 {
146 char to_display[2];
147 int width, height;
148
149 if (intersection_size < 7)
150 {
151 DEBUGF ("screen too small to draw labels\n");
152 }
153
154 to_display[0] =
155 display_marks[x + y * board_width] & (~(1 << 7));
156 to_display[1] = '\0';
157
158 rb->lcd_getstringsize (to_display, &width, &height);
159
160 int display_x =
161 pixel_x (POS (x, y)) + LINE_OFFSET - (width / 2);
162 int display_y =
163 pixel_y (POS (x, y)) + LINE_OFFSET - (height / 2);
164
165 if (display_x < 0)
166 {
167 display_x = 0;
168 }
169
170 if (display_y < 0)
171 {
172 display_y = 0;
173 }
174
175 if (display_x + width >= LCD_WIDTH)
176 {
177 display_x = LCD_WIDTH - 1 - width;
178 }
179
180 if (display_y + height >= LCD_HEIGHT)
181 {
182 display_y = LCD_HEIGHT - 1 - height;
183 }
184
185 rb->lcd_putsxy (display_x, display_y, to_display);
186 continue;
187 }
188
189 switch (display_marks[x + y * board_width])
190 {
191 // moves, 'mark', 'square'
192 case 'b':
193 case 'w':
194 if (intersection_size <= 5)
195 {
196 DEBUGF ("screen is too small to mark current move\n");
197 break;
198 }
199 case 'm':
200 if (intersection_size <= 5)
201 {
202 rb->lcd_drawpixel (pixel_x (POS (x, y)) + LINE_OFFSET +
203 1,
204 pixel_y (POS (x, y)) + LINE_OFFSET +
205 1);
206 rb->lcd_drawpixel (pixel_x (POS (x, y)) + LINE_OFFSET -
207 1,
208 pixel_y (POS (x, y)) + LINE_OFFSET -
209 1);
210 }
211 else
212 {
213 rb->lcd_drawrect (pixel_x (POS (x, y)) + LINE_OFFSET -
214 intersection_size / 6,
215 pixel_y (POS (x, y)) + LINE_OFFSET -
216 intersection_size / 6,
217 (intersection_size / 6) * 2 + 1,
218 (intersection_size / 6) * 2 + 1);
219 }
220 break;
221 case 's':
222 if (intersection_size <= 5)
223 {
224 rb->lcd_drawpixel (pixel_x (POS (x, y)) + LINE_OFFSET +
225 1,
226 pixel_y (POS (x, y)) + LINE_OFFSET +
227 1);
228 rb->lcd_drawpixel (pixel_x (POS (x, y)) + LINE_OFFSET -
229 1,
230 pixel_y (POS (x, y)) + LINE_OFFSET -
231 1);
232 }
233 else
234 {
235 rb->lcd_fillrect (pixel_x (POS (x, y)) + LINE_OFFSET -
236 intersection_size / 6,
237 pixel_y (POS (x, y)) + LINE_OFFSET -
238 intersection_size / 6,
239 (intersection_size / 6) * 2 + 1,
240 (intersection_size / 6) * 2 + 1);
241 }
242 break;
243
244 case 'c':
245 if (intersection_size > 7)
246 {
247 draw_circle (pixel_x (POS (x, y)) + LINE_OFFSET,
248 pixel_y (POS (x, y)) + LINE_OFFSET,
249 (intersection_size - 1) / 4, true);
250 break;
251 }
252
253 /* purposely don't break here, draw small the same as
254 a triangle */
255
256 case 't':
257 if (intersection_size <= 7)
258 {
259 rb->lcd_drawpixel (pixel_x (POS (x, y)) + LINE_OFFSET -
260 1,
261 pixel_y (POS (x, y)) + LINE_OFFSET +
262 1);
263 rb->lcd_drawpixel (pixel_x (POS (x, y)) + LINE_OFFSET +
264 1,
265 pixel_y (POS (x, y)) + LINE_OFFSET -
266 1);
267 }
268 else
269 {
270 xlcd_filltriangle (pixel_x (POS (x, y)) + LINE_OFFSET,
271 pixel_y (POS (x, y)) + LINE_OFFSET -
272 intersection_size / 4,
273 pixel_x (POS (x, y)) + LINE_OFFSET +
274 intersection_size / 4,
275 pixel_y (POS (x, y)) + LINE_OFFSET +
276 intersection_size / 4,
277 pixel_x (POS (x, y)) + LINE_OFFSET -
278 intersection_size / 4,
279 pixel_y (POS (x, y)) + LINE_OFFSET +
280 intersection_size / 4);
281 }
282 break;
283 default:
284 DEBUGF ("tried to display unknown mark '%c' %d\n",
285 display_marks[x + y * board_width],
286 display_marks[x + y * board_width]);
287 break;
288 };
289
290 rb->lcd_set_drawmode (DRMODE_SOLID);
291 /* don't have to undo the colors for LCD_DEPTH > 1, most
292 functions assume bg and fg get clobbered */
293 }
294 }
295 }
296}
297
298
299static void
300draw_circle (int c_x, int c_y, int r, bool filled)
301{
302 int f = 1 - r;
303 int x = 0;
304 int y = r;
305
306 /* draw the points on the axes to make the loop easier */
307 rb->lcd_drawpixel (c_x, c_y + r);
308 rb->lcd_drawpixel (c_x, c_y - r);
309
310 if (filled)
311 {
312 rb->lcd_hline (c_x - r, c_x + r, c_y);
313 }
314 else
315 {
316 rb->lcd_drawpixel (c_x + r, c_y);
317 rb->lcd_drawpixel (c_x - r, c_y);
318 }
319
320 /* Now walk from the very top of the circle to 1/8th of the way around
321 to the right. For each point, draw the 8 symmetrical points. */
322 while (x < y)
323 {
324 /* walk one pixel to the right */
325 ++x;
326
327 /* And then adjust our discriminant, and adjust y if we've
328 ventured outside of the circle. This boils down to walking a
329 tightrope between being inside and outside the circle. The
330 updating functions are taken from expanding the discriminant
331 function f(x, y) = x^2 + y^2 - r^2 after substituting in x + 1
332 and y - 1 and then subtracting out f(x, y) */
333 if (f <= 0)
334 {
335 f += 2 * x + 1;
336 }
337 else
338 {
339 --y;
340 f += (x - y) * 2 + 1;
341 }
342
343 if (filled)
344 {
345 /* each line takes care of 2 points on the circle so we only
346 need 4 */
347 rb->lcd_hline (c_x - y, c_x + y, c_y + x);
348 rb->lcd_hline (c_x - y, c_x + y, c_y - x);
349 rb->lcd_hline (c_x - x, c_x + x, c_y + y);
350 rb->lcd_hline (c_x - x, c_x + x, c_y - y);
351 }
352 else
353 {
354 /* Draw all 8 symmetrical points */
355 rb->lcd_drawpixel (c_x + x, c_y + y);
356 rb->lcd_drawpixel (c_x + y, c_y + x);
357 rb->lcd_drawpixel (c_x + y, c_y - x);
358 rb->lcd_drawpixel (c_x + x, c_y - y);
359 rb->lcd_drawpixel (c_x - x, c_y + y);
360 rb->lcd_drawpixel (c_x - y, c_y + x);
361 rb->lcd_drawpixel (c_x - y, c_y - x);
362 rb->lcd_drawpixel (c_x - x, c_y - y);
363 }
364 }
365}
366
367
368void
369draw_screen_display (void)
370{
371#if LCD_DEPTH > 1
372 int saved_fg = rb->lcd_get_foreground ();
373 int saved_bg = rb->lcd_get_background ();
374#endif
375 int saved_drmode = rb->lcd_get_drawmode ();
376
377 if (cursor_pos != last_cursor_pos || intersection_size != last_int_size)
378 {
379 cursor_updated ();
380 }
381
382#if LCD_DEPTH > 1
383 rb->lcd_set_backdrop (NULL);
384
385 rb->lcd_set_foreground (BOARD_COLOR);
386 rb->lcd_set_background (BACKGROUND_COLOR);
387 rb->lcd_set_drawmode (DRMODE_SOLID);
388
389#else
390 rb->lcd_set_drawmode (DRMODE_SOLID + DRMODE_INVERSEVID);
391#endif
392
393 rb->lcd_clear_display ();
394
395 rb->lcd_fillrect (pixel_x (POS (MIN_X, MIN_Y)),
396 pixel_y (POS (MIN_X, MIN_Y)),
397 (MAX_X - MIN_X) * intersection_size,
398 (MAX_Y - MIN_Y) * intersection_size);
399
400#if LCD_DEPTH > 1
401 rb->lcd_set_foreground (LINE_COLOR);
402#else
403 rb->lcd_set_drawmode (DRMODE_SOLID);
404#endif
405
406 unsigned int i;
407 for (i = MIN_Y; i < MAX_Y; ++i)
408 {
409 rb->lcd_hline (pixel_x (POS (MIN_X, i)) + LINE_OFFSET + extend_l,
410 pixel_x (POS (MAX_X - 1, i)) + LINE_OFFSET + extend_r,
411 pixel_y (POS (MIN_X, i)) + LINE_OFFSET);
412 }
413
414 for (i = MIN_X; i < MAX_X; ++i)
415 {
416 rb->lcd_vline (pixel_x (POS (i, MIN_Y)) + LINE_OFFSET,
417 pixel_y (POS (i, MIN_Y)) + LINE_OFFSET + extend_t,
418 pixel_y (POS (i, MAX_Y - 1)) + LINE_OFFSET + extend_b);
419 }
420
421 draw_all_hoshi ();
422 draw_all_stones ();
423 draw_cursor (cursor_pos);
424
425 if (draw_variations)
426 {
427 mark_child_variations_sgf ();
428 }
429
430 draw_all_marks ();
431
432 draw_footer ();
433 rb->lcd_update ();
434
435#if LCD_DEPTH > 1
436 rb->lcd_set_foreground (saved_fg);
437 rb->lcd_set_background (saved_bg);
438#endif
439 rb->lcd_set_drawmode (saved_drmode);
440}
441
442
443
444#if defined(GBN_WIDE_SCREEN)
445
446/* the size of the string, in pixels, when drawn vertically */
447static void
448vert_string_size (char *string, int *width, int *height)
449{
450 int temp_width = 0;
451 int temp_height = 0;
452
453 int ret_width = 0;
454 int ret_height = 0;
455
456 char temp_buffer[2];
457
458 temp_buffer[0] = temp_buffer[1] = 0;
459
460 if (!string)
461 {
462 return;
463 }
464
465 while (*string)
466 {
467 temp_buffer[0] = *string;
468 rb->lcd_getstringsize (temp_buffer, &temp_width, &temp_height);
469
470 ret_height += temp_height;
471
472 if (ret_width < temp_width)
473 {
474 ret_width = temp_width;
475 }
476
477 ++string;
478 }
479
480 if (width)
481 {
482 *width = ret_width;
483 }
484
485 if (height)
486 {
487 *height = ret_height;
488 }
489}
490
491static void
492putsxy_vertical (int x, int y, int width, char *str)
493{
494 int temp_width = 0;
495 int temp_height = 0;
496 char temp_buffer[2];
497
498 temp_buffer[0] = temp_buffer[1] = 0;
499
500 if (!str)
501 {
502 return;
503 }
504
505 while (*str)
506 {
507 temp_buffer[0] = *str;
508 rb->lcd_getstringsize (temp_buffer, &temp_width, &temp_height);
509 DEBUGF ("putting %s at %d %d\n", temp_buffer,
510 x + (width - temp_width) / 2, y);
511 rb->lcd_putsxy (x + (width - temp_width) / 2, y, temp_buffer);
512 y += temp_height;
513 ++str;
514 }
515}
516
517#endif /* GBN_WIDE_SCREEN */
518
519
520static void
521draw_footer (void)
522{
523 char captures_buffer[16];
524 char display_flags[16] = "";
525 int size_x, size_y;
526 int vert_x, vert_y;
527
528 (void) vert_x;
529 (void) vert_y;
530
531#if LCD_DEPTH > 1
532 rb->lcd_set_background (BACKGROUND_COLOR);
533 rb->lcd_set_foreground (BLACK_COLOR);
534#else
535 rb->lcd_set_drawmode (DRMODE_SOLID + DRMODE_INVERSEVID);
536#endif
537
538 rb->snprintf (captures_buffer, sizeof (captures_buffer),
539 "%d", white_captures);
540
541
542 rb->lcd_getstringsize (captures_buffer, &size_x, &size_y);
543#if defined(GBN_TALL_SCREEN)
544 rb->lcd_putsxy (size_y + 2, LCD_HEIGHT - size_y, captures_buffer);
545#else
546 vert_string_size (captures_buffer, &vert_x, &vert_y);
547 if (board_pixel_width + size_x <= LCD_WIDTH)
548 {
549 rb->lcd_putsxy (LCD_WIDTH - size_x - 1, vert_x + 2, captures_buffer);
550 }
551 else
552 {
553 putsxy_vertical (LCD_WIDTH - vert_x - 1, vert_x + 2, vert_x,
554 captures_buffer);
555 }
556#endif
557
558#if LCD_DEPTH == 1
559 rb->lcd_set_drawmode (DRMODE_SOLID);
560#endif
561
562#if defined(GBN_TALL_SCREEN)
563 draw_circle (size_y / 2,
564 LCD_HEIGHT - (size_y / 2), (size_y - 1) / 2, true);
565
566#if LCD_DEPTH == 1
567 rb->lcd_set_drawmode (DRMODE_SOLID + DRMODE_INVERSEVID);
568 draw_circle (size_y / 2,
569 LCD_HEIGHT - (size_y / 2), (size_y - 1) / 2, false);
570#endif /* LCD_DEPTH */
571
572#else /* !GBN_TALL_SCREEN */
573 draw_circle (LCD_WIDTH - 1 - vert_x / 2,
574 (vert_x / 2), (vert_x - 1) / 2, true);
575
576#if LCD_DEPTH == 1
577 rb->lcd_set_drawmode (DRMODE_SOLID + DRMODE_INVERSEVID);
578 draw_circle (LCD_WIDTH - 1 - vert_x / 2,
579 (vert_x / 2), (vert_x - 1) / 2, false);
580#endif /* LCD_DEPTH */
581
582#endif /* GBN_TALL_SCREEN */
583
584
585#if LCD_DEPTH > 1
586 rb->lcd_set_foreground (WHITE_COLOR);
587#endif
588 rb->snprintf (captures_buffer, sizeof (captures_buffer),
589 "%d", black_captures);
590
591 rb->lcd_getstringsize (captures_buffer, &size_x, &size_y);
592#if defined(GBN_TALL_SCREEN)
593 rb->lcd_putsxy (LCD_WIDTH - (size_y + 1) - size_x,
594 LCD_HEIGHT - size_y, captures_buffer);
595
596 draw_circle (LCD_WIDTH - (size_y / 2),
597 LCD_HEIGHT - (size_y / 2), (size_y - 1) / 2, true);
598#else
599 vert_string_size (captures_buffer, &vert_x, &vert_y);
600 if (board_pixel_width + size_x <= LCD_WIDTH)
601 {
602 rb->lcd_putsxy (LCD_WIDTH - size_x - 1,
603 LCD_HEIGHT - vert_x - size_y - 3, captures_buffer);
604 }
605 else
606 {
607 putsxy_vertical (LCD_WIDTH - vert_x - 1,
608 LCD_HEIGHT - vert_x - 3 - vert_y,
609 vert_x, captures_buffer);
610 }
611
612 draw_circle (LCD_WIDTH - 1 - vert_x / 2,
613 LCD_HEIGHT - 1 - vert_x / 2, (vert_x - 1) / 2, true);
614#endif
615
616
617#if LCD_DEPTH > 1
618 rb->lcd_set_foreground (BLACK_COLOR);
619#endif
620
621 if (has_comment)
622 {
623 rb->strcat (display_flags, "C");
624 }
625
626 if (has_more_nodes_sgf ())
627 {
628 rb->strcat (display_flags, "+");
629 }
630
631 if (num_variations_sgf () > 1)
632 {
633 rb->strcat (display_flags, "*");
634 }
635
636
637
638 rb->snprintf (captures_buffer, sizeof (captures_buffer),
639 "%d%s", move_num, display_flags);
640
641 rb->lcd_getstringsize (captures_buffer, &size_x, &size_y);
642#if defined(GBN_TALL_SCREEN)
643 rb->lcd_putsxy ((LCD_WIDTH - size_x) / 2,
644 LCD_HEIGHT - size_y, captures_buffer);
645#else
646 if (board_pixel_width + size_x <= LCD_WIDTH)
647 {
648 rb->lcd_putsxy (LCD_WIDTH - size_x - 1,
649 (LCD_HEIGHT - size_y) / 2, captures_buffer);
650 }
651 else
652 {
653 vert_string_size (captures_buffer, &vert_x, &vert_y);
654 putsxy_vertical (LCD_WIDTH - vert_x - 1,
655 (LCD_HEIGHT - vert_y) / 2, vert_x, captures_buffer);
656 }
657#endif
658
659
660 rb->lcd_set_drawmode (DRMODE_SOLID);
661}
662
663
664
665
666
667
668
669static int
670pixel_x (unsigned short pos)
671{
672 return board_x + (I (pos) - min_x_int) * intersection_size;
673}
674
675static int
676pixel_y (unsigned short pos)
677{
678 return board_y + (J (pos) - min_y_int) * intersection_size;
679}
680
681
682
683void
684move_display (unsigned short pos)
685{
686 if (!on_board (pos))
687 {
688 return;
689 }
690
691 while ((unsigned) I (pos) >= REAL_MAX_X)
692 {
693 cursor_pos = EAST (cursor_pos);
694 cursor_updated ();
695 }
696 while ((unsigned) I (pos) < REAL_MIN_X)
697 {
698 cursor_pos = WEST (cursor_pos);
699 cursor_updated ();
700 }
701
702 while ((unsigned) J (pos) >= REAL_MAX_Y)
703 {
704 cursor_pos = SOUTH (cursor_pos);
705 cursor_updated ();
706 }
707 while ((unsigned) J (pos) < REAL_MIN_Y)
708 {
709 cursor_pos = NORTH (cursor_pos);
710 cursor_updated ();
711 }
712}
713
714
715static void
716cursor_updated (void)
717{
718 if (!on_board(cursor_pos))
719 {
720 cursor_pos = WRAP (cursor_pos);
721 }
722
723 if (intersection_size != last_int_size ||
724 ((unsigned) I (cursor_pos)) < REAL_MIN_X + 1 ||
725 ((unsigned) I (cursor_pos)) > REAL_MAX_X - 2 ||
726 ((unsigned) J (cursor_pos)) < REAL_MIN_Y + 1 ||
727 ((unsigned) J (cursor_pos)) > REAL_MAX_Y - 2)
728 {
729 if ((unsigned) I (cursor_pos) < (num_x_ints / 2))
730 {
731 min_x_int = 0;
732 }
733 else
734 {
735 min_x_int = min (I (cursor_pos) - (num_x_ints / 2),
736 board_width - num_x_ints);
737 }
738
739 if ((unsigned) J (cursor_pos) < (num_y_ints / 2))
740 {
741 min_y_int = 0;
742 }
743 else
744 {
745
746 min_y_int = min (J (cursor_pos) - (num_y_ints / 2),
747 board_height - num_y_ints);
748 }
749 }
750
751 /* these are used in line drawing to extend the lines if there is more
752 board in that direction */
753 if (MIN_X)
754 {
755 extend_l = -1 * LINE_OFFSET;
756 }
757 else
758 {
759 extend_l = 0;
760 }
761
762 if (MIN_Y)
763 {
764 extend_t = -1 * LINE_OFFSET;
765 }
766 else
767 {
768 extend_t = 0;
769 }
770
771 if (MAX_X != board_width)
772 {
773 extend_r = LINE_OFFSET;
774 }
775 else
776 {
777 extend_r = 0;
778 }
779
780 if (MAX_Y != board_height)
781 {
782 extend_b = LINE_OFFSET;
783 }
784 else
785 {
786 extend_b = 0;
787 }
788
789 last_cursor_pos = cursor_pos;
790 last_int_size = intersection_size;
791}
792
793static unsigned int
794unzoomed_int_size (void)
795{
796 int int_size = min ((LCD_BOARD_WIDTH / board_width),
797 (LCD_BOARD_HEIGHT / board_height));
798
799 if (!(int_size & 1))
800 {
801 --int_size;
802 }
803
804 if (int_size < 0)
805 {
806 int_size = 1;
807 }
808
809 return max(int_size, MIN_INT_SIZE);
810}
811
812unsigned int
813current_zoom_display (void)
814{
815 return (intersection_size - unzoomed_int_size ()) / 2 + 1;
816}
817
818unsigned int
819max_zoom_display (void)
820{
821 return (MAX_INT_SIZE - unzoomed_int_size ()) / 2 + 1;
822}
823
824unsigned int
825min_zoom_display (void)
826{
827 if (MIN_INT_SIZE >= unzoomed_int_size())
828 {
829 return (MIN_INT_SIZE - unzoomed_int_size ()) / 2 + 1;
830 }
831 else
832 {
833 return 1;
834 }
835}
836
837void
838set_zoom_display (unsigned int zoom_level)
839{
840 unsigned int unzoomed = unzoomed_int_size ();
841
842 if (saved_circle_size < MIN_INT_SIZE ||
843 saved_circle_size > MAX_INT_SIZE)
844 {
845 saved_circle_size = MIN_DEFAULT_INT_SIZE;
846 }
847
848 if (zoom_level == 0)
849 {
850 /* default zoom, we get to set it however we want */
851 intersection_size = max (unzoomed, saved_circle_size);
852 }
853 else
854 {
855 intersection_size = unzoomed + 2 * (zoom_level - 1);
856 }
857
858 if (intersection_size > MAX_INT_SIZE)
859 {
860 intersection_size = MAX_INT_SIZE;
861 }
862
863 /* now intersection_size has been set appropriately, so set up all of
864 the derived values used for display */
865
866 num_x_ints = min (LCD_BOARD_WIDTH / intersection_size, board_width);
867 num_y_ints = min (LCD_BOARD_HEIGHT / intersection_size, board_height);
868
869 board_pixel_width = num_x_ints * intersection_size;
870 board_pixel_height = num_y_ints * intersection_size;
871
872#if defined(GBN_TALL_SCREEN)
873 board_x = (LCD_WIDTH - board_pixel_width) / 2;
874 board_y = 0;
875#elif defined(GBN_WIDE_SCREEN)
876 board_x = 0;
877 board_y = (LCD_HEIGHT - board_pixel_height) / 2;
878#else
879#error screen dimensions have not been evaluated properly
880#endif
881}
882
883
884/* Call every time the board size might have changed! */
885void
886setup_display (void)
887{
888 set_zoom_display (0); /* 0 means set to default */
889 /* cursor starts on tengen (middle of the board) */
890 int start_x, start_y;
891 if (board_width >= 7)
892 {
893 start_x = board_width - 4;
894 }
895 else
896 {
897 start_x = board_width / 2;
898 }
899
900 if (board_height >= 7)
901 {
902 start_y = 3;;
903 }
904 else
905 {
906 start_y = board_height / 2;
907 }
908 cursor_pos = POS (start_x, start_y);
909 last_cursor_pos = INVALID_POS;
910 last_int_size = -1;
911
912 clear_marks_display ();
913}
914
915static void
916draw_cursor (unsigned short pos)
917{
918 /* int saved_draw_mode = rb->lcd_get_drawmode(); */
919
920 if (!on_board (pos))
921 {
922 return;
923 }
924
925#if LCD_DEPTH > 1
926 rb->lcd_set_foreground (CURSOR_COLOR);
927#else
928 rb->lcd_set_drawmode (DRMODE_COMPLEMENT);
929#endif
930
931 rb->lcd_drawrect (pixel_x (pos),
932 pixel_y (pos), intersection_size, intersection_size);
933
934 rb->lcd_set_drawmode (DRMODE_SOLID);
935}
936
937static void
938draw_stone_raw (int pixel_x, int pixel_y, bool black)
939{
940#if LCD_DEPTH > 1
941 rb->lcd_set_foreground (black ? BLACK_COLOR : WHITE_COLOR);
942#else
943 if (black)
944 {
945 rb->lcd_set_drawmode (DRMODE_SOLID);
946 }
947 else
948 {
949 rb->lcd_set_drawmode (DRMODE_SOLID + DRMODE_INVERSEVID);
950 }
951#endif
952
953 draw_circle (pixel_x + LINE_OFFSET,
954 pixel_y + LINE_OFFSET, LINE_OFFSET, true);
955
956#if defined(OUTLINE_STONES)
957#if LCD_DEPTH > 1
958 rb->lcd_set_foreground (black ? WHITE_COLOR : BLACK_COLOR);
959#else
960 if (black)
961 {
962 rb->lcd_set_drawmode (DRMODE_SOLID + DRMODE_INVERSEVID);
963 }
964 else
965 {
966 rb->lcd_set_drawmode (DRMODE_SOLID);
967 }
968#endif /* LCD_DEPTH > 1 */
969
970 if (!black)
971 {
972 draw_circle (pixel_x + LINE_OFFSET,
973 pixel_y + LINE_OFFSET, LINE_OFFSET, false);
974 }
975
976#endif /* OUTLINE_STONES */
977
978 rb->lcd_set_drawmode (DRMODE_SOLID);
979}
980
981
982static void
983draw_stone (unsigned short pos, bool black)
984{
985 if (!on_board (pos))
986 {
987 return;
988 }
989
990 draw_stone_raw (pixel_x (pos), pixel_y (pos), black);
991}
992
993
994static void
995draw_all_stones (void)
996{
997 unsigned int x, y;
998 unsigned short temp_pos;
999
1000 for (x = MIN_X; x < MAX_X; ++x)
1001 {
1002 for (y = MIN_Y; y < MAX_Y; ++y)
1003 {
1004 temp_pos = POS (x, y);
1005 if (get_point_board (temp_pos) == EMPTY)
1006 {
1007 continue;
1008 }
1009
1010 draw_stone (temp_pos, get_point_board (temp_pos) == BLACK);
1011 }
1012 }
1013}
1014
1015static void
1016draw_hoshi (unsigned short pos)
1017{
1018 /* color and drawmode are already set before this function (all lines
1019 and hoshi and stuff are drawn together) */
1020
1021 if (!on_board(pos))
1022 {
1023 return;
1024 }
1025
1026 if ((unsigned) I (pos) < MIN_X ||
1027 (unsigned) I (pos) >= MAX_X ||
1028 (unsigned) J (pos) < MIN_Y ||
1029 (unsigned) J (pos) >= MAX_Y)
1030 {
1031 return;
1032 }
1033 if (intersection_size > 8)
1034 {
1035 rb->lcd_fillrect (pixel_x (pos) + LINE_OFFSET - 1,
1036 pixel_y (pos) + LINE_OFFSET - 1, 3, 3);
1037 }
1038 else
1039 {
1040 rb->lcd_drawpixel (pixel_x (pos) + LINE_OFFSET - 1,
1041 pixel_y (pos) + LINE_OFFSET - 1);
1042 rb->lcd_drawpixel (pixel_x (pos) + LINE_OFFSET + 1,
1043 pixel_y (pos) + LINE_OFFSET + 1);
1044 }
1045}
1046
1047
1048static void
1049draw_all_hoshi (void)
1050{
1051 if (board_width != board_height)
1052 {
1053 return;
1054 }
1055
1056 if (board_width == 19)
1057 {
1058 draw_hoshi (POS (3, 3));
1059 draw_hoshi (POS (3, 9));
1060 draw_hoshi (POS (3, 15));
1061
1062 draw_hoshi (POS (9, 3));
1063 draw_hoshi (POS (9, 9));
1064 draw_hoshi (POS (9, 15));
1065
1066 draw_hoshi (POS (15, 3));
1067 draw_hoshi (POS (15, 9));
1068 draw_hoshi (POS (15, 15));
1069 }
1070 else if (board_width == 9)
1071 {
1072 draw_hoshi (POS (2, 2));
1073 draw_hoshi (POS (2, 6));
1074
1075 draw_hoshi (POS (4, 4));
1076
1077 draw_hoshi (POS (6, 2));
1078 draw_hoshi (POS (6, 6));
1079 }
1080 else if (board_width == 13)
1081 {
1082 draw_hoshi (POS (3, 3));
1083 draw_hoshi (POS (3, 9));
1084
1085 draw_hoshi (POS (6, 6));
1086
1087 draw_hoshi (POS (9, 3));
1088 draw_hoshi (POS (9, 9));
1089
1090 }
1091}
diff --git a/apps/plugins/goban/display.h b/apps/plugins/goban/display.h
new file mode 100644
index 0000000000..2f64f1b6ca
--- /dev/null
+++ b/apps/plugins/goban/display.h
@@ -0,0 +1,111 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#ifndef GOBAN_DISPLAY_H
23#define GOBAN_DISPLAY_H
24
25#include "types.h"
26#include "goban.h"
27
28/* Call before using the display */
29void setup_display (void);
30
31/* Draw the board and the "footer" */
32void draw_screen_display (void);
33
34/* The location of the cursor */
35extern unsigned short cursor_pos;
36
37/* True if we should draw variations */
38extern bool draw_variations;
39
40/* Used to set the zoom level, loaded in from the config file */
41unsigned int saved_circle_size;
42
43/* the size of one intersection on the board, in pixels */
44unsigned int intersection_size;
45
46/* Clear the marks from the board */
47void clear_marks_display (void);
48
49/* Add a mark to the display */
50void set_mark_display (unsigned short pos, unsigned char mark_char);
51
52/* Set to indicate if we should display the 'C' in the footer or not */
53void set_comment_display (bool new_val);
54
55/* Move the display so that the position pos is in view, very useful if
56 we're zoomed in (otherwise does nothing) */
57void move_display (unsigned short pos);
58
59/* These should all be obvious. Zoom levels start at 1. set_zoom_display
60 will set the default zoom level if called with zoom_level == 0 */
61void set_zoom_display (unsigned int zoom_level);
62unsigned int current_zoom_display (void);
63unsigned int min_zoom_display (void);
64unsigned int max_zoom_display (void);
65
66/* MIN and MAX intersection sizes */
67#define MIN_DEFAULT_INT_SIZE (11)
68
69/* The absolute minimum that is allowed */
70#define MIN_INT_SIZE (3)
71
72/* Don't allow one bigger than the size of the screen */
73#define MAX_INT_SIZE (min((LCD_BOARD_WIDTH & 1) ? LCD_BOARD_WIDTH : \
74 LCD_BOARD_WIDTH - 1, \
75 (LCD_BOARD_HEIGHT & 1) ? LCD_BOARD_HEIGHT : \
76 LCD_BOARD_HEIGHT - 1))
77
78
79
80
81/* NOTE: we do one "extra" intersection in each direction which goes off
82 the screen, because it makes drawing a little bit prettier (the board
83 doesn't end before the edge of the screen this way) */
84
85
86/* right is a screen boundary if we're on a tall screen, bottom is a
87 screen boundary if we're on a wide screen */
88#if defined(GBN_TALL_SCREEN)
89#define MIN_X ((min_x_int == 0) ? 0 : min_x_int - 1)
90#define MIN_Y (min_y_int)
91/* always flush with top, no need for extra */
92
93#define MAX_X (min(min_x_int + num_x_ints + 1, board_width))
94#define MAX_Y (min(min_y_int + num_y_ints, board_height))
95#else
96#define MIN_X (min_x_int)
97/* always flush with left, no need for extra */
98
99#define MIN_Y ((min_y_int == 0) ? 0 : min_y_int - 1)
100#define MAX_X (min(min_x_int + num_x_ints, board_width))
101#define MAX_Y (min(min_y_int + num_y_ints + 1, board_height))
102#endif
103
104/* These are the same as above, except without the extra intersection is
105 board-boundary directions */
106#define REAL_MIN_X (min_x_int)
107#define REAL_MIN_Y (min_y_int)
108#define REAL_MAX_X (min(min_x_int + num_x_ints, board_width))
109#define REAL_MAX_Y (min(min_y_int + num_y_ints, board_height))
110
111#endif
diff --git a/apps/plugins/goban/game.c b/apps/plugins/goban/game.c
new file mode 100644
index 0000000000..9ecf836f5b
--- /dev/null
+++ b/apps/plugins/goban/game.c
@@ -0,0 +1,236 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "game.h"
23#include "types.h"
24#include "board.h"
25#include "goban.h"
26#include "sgf.h"
27#include "sgf_output.h"
28#include "sgf_parse.h"
29#include "sgf_storage.h"
30#include "display.h"
31
32static void pre_game_setup (void);
33
34char save_file[SAVE_FILE_LENGTH];
35bool game_dirty = false;
36bool autosave_dirty = false;
37
38int move_num = 0;
39
40unsigned char current_player = BLACK;
41
42struct header_t header;
43
44void
45set_game_modified (void)
46{
47 game_dirty = true;
48 autosave_dirty = true;
49}
50
51bool
52load_game (const char *filename)
53{
54 rb->memset (&header, 0, sizeof (header));
55
56 if (rb->strlen (filename) + 1 > SAVE_FILE_LENGTH)
57 {
58 DEBUGF ("file name too long\n");
59 return false;
60 }
61
62 if (!rb->file_exists (filename))
63 {
64 DEBUGF ("file doesn't exist!\n");
65 return false;
66 }
67
68 pre_game_setup ();
69
70 rb->strcpy (save_file, filename);
71
72#ifdef HAVE_ADJUSTABLE_CPU_FREQ
73 rb->cpu_boost (true);
74#endif
75
76 rb->splash (0, "Loading...");
77
78 bool parse_failed = false;
79 if (!parse_sgf (save_file))
80 {
81 rb->splash (3 * HZ, "Unable to parse SGF file. Will overwrite.");
82 parse_failed = true;
83 }
84
85 game_dirty = false;
86 autosave_dirty = false;
87
88#ifdef HAVE_ADJUSTABLE_CPU_FREQ
89 rb->cpu_boost (false);
90#endif
91
92 if (header.handicap >= 2)
93 {
94 current_player = WHITE;
95 }
96
97 post_game_setup_sgf ();
98
99 if (!parse_failed)
100 {
101 draw_screen_display();
102 if (rb->strcmp(filename, DEFAULT_SAVE))
103 {
104 metadata_summary();
105 }
106 }
107
108 return true;
109}
110
111
112bool
113save_game (const char *filename)
114{
115 if (rb->strlen (filename) + 1 > SAVE_FILE_LENGTH)
116 {
117 return false;
118 }
119
120 /* We only have to do something if the game is dirty, or we're being
121 asked to save to a different location than we loaded from.
122
123 If the game isn't dirty and we're being asked to save to default,
124 we also don't have to do anything.*/
125 if (!game_dirty &&
126 (rb->strcmp (filename, save_file) == 0 ||
127 rb->strcmp (filename, DEFAULT_SAVE) == 0))
128 {
129 return true;
130 }
131
132#ifdef HAVE_ADJUSTABLE_CPU_FREQ
133 rb->cpu_boost (true);
134#endif
135
136 rb->splash (0, "Saving...");
137
138 if (output_sgf (filename))
139 {
140 /* saving only "cleans" the game if it's not a save to default,
141 * or if our save_file is actually default
142 *
143 * (so autosaves won't prevent legitimate saves to a Save As or
144 * loaded file)
145 */
146 if (rb->strcmp (filename, DEFAULT_SAVE) ||
147 rb->strcmp (save_file, DEFAULT_SAVE) == 0)
148 {
149 game_dirty = false;
150 }
151
152 /* but saving anywhere means that autosave isn't dirty */
153 autosave_dirty = false;
154
155#ifdef HAVE_ADJUSTABLE_CPU_FREQ
156 rb->cpu_boost (false);
157#endif
158 /* The save succeeded. Now, if we saved to an actual file (not to the
159 * DEFAULT_SAVE), then we should delete the DEFAULT_SAVE file because
160 * the changes stored in it are no longer unsaved.
161 */
162 if (rb->strcmp (filename, DEFAULT_SAVE))
163 {
164 rb->remove(DEFAULT_SAVE);
165 }
166
167 return true;
168 }
169 else
170 {
171#ifdef HAVE_ADJUSTABLE_CPU_FREQ
172 rb->cpu_boost (false);
173#endif
174 return false;
175 }
176}
177
178
179static void
180pre_game_setup (void)
181{
182 rb->memset (&header, 0, sizeof (header));
183
184 clear_caches_sgf ();
185 free_tree_sgf ();
186
187 set_size_board (MAX_BOARD_SIZE, MAX_BOARD_SIZE);
188
189 clear_board ();
190
191 game_dirty = true;
192 move_num = 0;
193
194 play_mode = MODE_PLAY;
195
196 rb->strcpy (save_file, DEFAULT_SAVE);
197
198 header_marked = false;
199}
200
201
202bool
203setup_game (int width, int height, int handicap, int komi)
204{
205 pre_game_setup ();
206
207 if (!set_size_board (width, height))
208 {
209 return false;
210 }
211
212 clear_board ();
213
214 /* place handicap */
215 if (handicap >= 2)
216 {
217 current_player = WHITE;
218 }
219 else if (handicap < 0)
220 {
221 return false;
222 }
223 else
224 {
225 current_player = BLACK;
226 }
227
228 header.handicap = handicap;
229 setup_handicap_sgf ();
230
231 header.komi = komi;
232
233 post_game_setup_sgf ();
234
235 return true;
236}
diff --git a/apps/plugins/goban/game.h b/apps/plugins/goban/game.h
new file mode 100644
index 0000000000..6a351fb676
--- /dev/null
+++ b/apps/plugins/goban/game.h
@@ -0,0 +1,59 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#ifndef GAME_GOBAN_H
23#define GAME_GOBAN_H
24
25#include "types.h"
26
27/* Call whenever anything saveable in the SGF file might have changed */
28void set_game_modified (void);
29
30/* Setup a new game, clearing anything currently in the SGF tree Returns
31 false on failure, in which case the old data is NOT guaranteed to be
32 available anymore */
33bool setup_game (int width, int height, int handicap, int komi);
34
35/* Load a game from a file, clearing anything currently in the SGF tree
36 Returns false on failure, in which case the old data is NOT guaranteed
37 to be available anymore */
38bool load_game (const char *filename);
39
40/* Save the data in the SGF tree to a file. Returns false on failure */
41bool save_game (const char *filename);
42
43/* The number of the current move (starts at 0, first move is 1) */
44extern int move_num;
45
46/* The color of the current_player, either BLACK or WHITE */
47extern unsigned char current_player;
48/* Where should we save to if explicitly saved? */
49extern char save_file[];
50/* True if there are unsaved changes in the file */
51extern bool game_dirty;
52
53/* True if there are changes that have been neither autosaved nor saved */
54extern bool autosave_dirty;
55
56/* The game metadata */
57extern struct header_t header;
58
59#endif
diff --git a/apps/plugins/goban/goban.c b/apps/plugins/goban/goban.c
new file mode 100644
index 0000000000..e5943c0ba6
--- /dev/null
+++ b/apps/plugins/goban/goban.c
@@ -0,0 +1,1232 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "plugin.h"
23#include "lib/playback_control.h"
24#include "lib/configfile.h"
25
26PLUGIN_HEADER
27
28#include "goban.h"
29#include "game.h"
30#include "board.h"
31#include "display.h"
32#include "sgf.h"
33#include "sgf_storage.h"
34#include "types.h"
35#include "util.h"
36
37enum play_mode_t play_mode = MODE_PLAY;
38
39#if defined(GBN_BUTTON_NAV_MODE)
40
41#define NAV_MODE_BOARD 0
42#define NAV_MODE_TREE 1
43
44int nav_mode = NAV_MODE_BOARD;
45
46#endif
47
48#define PARSE_STACK_BUFFER_SIZE (max(MAX_BOARD_SIZE * MAX_BOARD_SIZE * sizeof(unsigned short), 50 * sizeof(int)))
49
50/* used in SGF file parsing and outputting as well as in liberty counting
51 and capturing/uncapturing */
52struct stack_t parse_stack;
53char parse_stack_buffer[PARSE_STACK_BUFFER_SIZE];
54
55static void global_setup (void);
56static void global_cleanup (void);
57
58static bool do_main_menu (void);
59static void do_gameinfo_menu (void);
60static enum prop_type_t menu_selection_to_prop (int selection);
61static void do_context_menu (void);
62static void do_options_menu (void);
63
64static bool do_comment_edit (void);
65static bool do_zoom (void);
66static void set_defaults (void);
67
68bool auto_show_comments = true;
69bool disable_shutdown = false;
70unsigned int autosave_time = 0;
71unsigned int autosave_counter = 0;
72
73#define SETTINGS_VERSION 2
74#define SETTINGS_MIN_VERSION 1
75#define SETTINGS_FILENAME "goban.cfg"
76
77static struct configdata config[] =
78{ /* INT in MAX_INT_SIZE is intersection, not integer */
79 {TYPE_INT, 0, MAX_INT_SIZE,
80 { .int_p = &saved_circle_size },
81 "stone size", NULL},
82 {TYPE_BOOL, 0, 1,
83 { .bool_p = &draw_variations },
84 "draw variations",
85 NULL},
86 {TYPE_BOOL, 0, 1,
87 { .bool_p = &auto_show_comments },
88 "auto show comments",
89 NULL},
90 {TYPE_BOOL, 0, 1,
91 { .bool_p = &disable_shutdown },
92 "disable shutdown",
93 NULL},
94 {TYPE_INT, 0, MAX_AUTOSAVE,
95 { .int_p = &autosave_time },
96 "autosave time",
97 NULL}
98};
99
100static void
101set_defaults (void)
102{
103 saved_circle_size = 0;
104 draw_variations = true;
105 auto_show_comments = true;
106
107 disable_shutdown = false;
108 autosave_time = 7;
109}
110
111static void
112komi_formatter (char *dest, size_t size, int menu_item, const char *unknown)
113{
114 (void) unknown;
115 snprint_fixed (dest, size, menu_item);
116}
117
118static void
119ruleset_formatter (char *dest, size_t size, int menu_item, const char *unknown)
120{
121 (void) unknown;
122 rb->snprintf (dest, size, "%s", ruleset_names[menu_item]);
123}
124
125static void
126autosave_formatter (char *dest, size_t size, int menu_item, const char *
127unknown)
128{
129 (void) unknown;
130 if (menu_item == 0)
131 {
132 rb->snprintf (dest, size, "Off");
133 }
134 else
135 {
136 rb->snprintf (dest, size, "%d minute%s", menu_item,
137 menu_item == 1 ? "" : "s");
138 }
139}
140
141static void
142time_formatter (char *dest, size_t size, int menu_item, const char *unknown)
143{
144 int time_values[4]; /* days hours minutes seconds */
145 int min_set, max_set;
146 int temp;
147
148 (void) unknown;
149
150 time_values[0] = menu_item / (24 * 60 * 60);
151 menu_item %= (24 * 60 * 60);
152 time_values[1] = menu_item / (60 * 60);
153 menu_item %= (60 * 60);
154 time_values[2] = menu_item / 60;
155 time_values[3] = menu_item % 60;
156
157 min_set = 500;
158 max_set = -1;
159 int i;
160 for (i = 0;
161 (unsigned int) i < (sizeof (time_values) / sizeof (time_values[0]));
162 ++i)
163 {
164 if (time_values[i])
165 {
166 if (i < min_set)
167 {
168 min_set = i;
169 }
170
171 if (i > max_set)
172 {
173 max_set = i;
174 }
175 }
176 }
177
178 if (max_set == -1)
179 {
180 rb->snprintf (dest, size, "0");
181 return;
182 }
183
184 for (i = min_set; i <= 3; ++i)
185 {
186 if (i <= max_set)
187 {
188 if (i == 0 || i == 1 || i == min_set)
189 {
190 rb->snprintf (dest, size, "%d", time_values[i]);
191 }
192 else
193 {
194 rb->snprintf (dest, size, "%02d", time_values[i]);
195 }
196 temp = rb->strlen (dest);
197 dest += temp;
198 size -= temp;
199 }
200 else if (i != 3)
201 {
202 continue;
203 }
204
205 if (i == 0) /* days */
206 {
207 rb->snprintf (dest, size, " d ");
208 }
209 else if (i == 3) /* seconds, print the final units */
210 {
211 if (min_set == 0 || min_set == 1)
212 {
213 rb->snprintf (dest, size, " h");
214 }
215 else if (min_set == 2)
216 {
217 rb->snprintf (dest, size, " m");
218 }
219 else
220 {
221 rb->snprintf (dest, size, " s");
222 }
223 }
224 else if (i != max_set)
225 {
226 rb->snprintf (dest, size, ":");
227 }
228
229 temp = rb->strlen (dest);
230 dest += temp;
231 size -= temp;
232 }
233}
234
235enum plugin_status
236plugin_start (const void *parameter)
237{
238 int btn;
239 int temp;
240
241 rb->mkdir ("/sgf");
242
243 global_setup ();
244
245#ifdef GBN_TEST
246 run_tests ();
247 return 0;
248#endif
249
250 if (!(parameter && load_game (parameter)))
251 {
252 if (parameter)
253 {
254 rb->splashf (2 * HZ, "Loading %s failed.", (char *) parameter);
255 }
256
257 if (!load_game (DEFAULT_SAVE))
258 {
259 rb->strcpy (save_file, DEFAULT_SAVE);
260
261 if (!setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, 0))
262 {
263 return PLUGIN_ERROR;
264 }
265 }
266 }
267 else
268 {
269 /* game loaded */
270 if (rb->strcmp (save_file, DEFAULT_SAVE))
271 {
272 /* delete the scratch file if we loaded a game and it wasn't
273 * from the scratch file
274 */
275 rb->remove (DEFAULT_SAVE);
276 }
277 }
278
279 draw_screen_display ();
280
281 autosave_counter = 0;
282 for (;;)
283 {
284 btn = rb->button_get_w_tmo (HZ * 30);
285
286 if (disable_shutdown)
287 {
288 /* tell rockbox we're not idle */
289 rb->reset_poweroff_timer ();
290 }
291
292 bool is_idle = false;
293
294 switch (btn)
295 {
296
297#if defined(GBN_BUTTON_NAV_MODE)
298 case GBN_BUTTON_NAV_MODE:
299 case GBN_BUTTON_NAV_MODE | BUTTON_REPEAT:
300 if (nav_mode == NAV_MODE_TREE)
301 {
302 nav_mode = NAV_MODE_BOARD;
303 rb->splash (2 * HZ / 3, "board navigation mode");
304 draw_screen_display ();
305 }
306 else
307 {
308 nav_mode = NAV_MODE_TREE;
309 rb->splash (2 * HZ / 3, "tree navigation mode");
310 draw_screen_display ();
311 }
312 break;
313#endif
314
315#if defined(GBN_BUTTON_ADVANCE)
316 case GBN_BUTTON_ADVANCE:
317 case GBN_BUTTON_ADVANCE | BUTTON_REPEAT:
318 if (has_more_nodes_sgf ())
319 {
320 if (!redo_node_sgf ())
321 {
322 rb->splash (2 * HZ, "redo failed");
323 }
324 draw_screen_display ();
325 }
326 break;
327#endif
328
329#if defined(GBN_BUTTON_RETREAT)
330 case GBN_BUTTON_RETREAT:
331 case GBN_BUTTON_RETREAT | BUTTON_REPEAT:
332 if (has_prev_nodes_sgf ())
333 {
334 if (!undo_node_sgf ())
335 {
336 rb->splash (3 * HZ / 2, "Undo Failed");
337 }
338 draw_screen_display ();
339 }
340 break;
341#endif
342
343 case GBN_BUTTON_PLAY:
344 if (play_mode == MODE_PLAY || play_mode == MODE_FORCE_PLAY)
345 {
346 if (!play_move_sgf (cursor_pos, current_player))
347 {
348 rb->splash (HZ / 3, "Illegal Move");
349 }
350 }
351 else if (play_mode == MODE_ADD_BLACK)
352 {
353 if (!add_stone_sgf (cursor_pos, BLACK))
354 {
355 rb->splash (HZ / 3, "Illegal");
356 }
357 }
358 else if (play_mode == MODE_ADD_WHITE)
359 {
360 if (!add_stone_sgf (cursor_pos, WHITE))
361 {
362 rb->splash (HZ / 3, "Illegal");
363 }
364 }
365 else if (play_mode == MODE_REMOVE)
366 {
367 if (!add_stone_sgf (cursor_pos, EMPTY))
368 {
369 rb->splash (HZ / 3, "Illegal");
370 }
371 }
372 else if (play_mode == MODE_MARK)
373 {
374 if (!add_mark_sgf (cursor_pos, PROP_MARK))
375 {
376 rb->splash (HZ / 3, "Couldn't Mark");
377 }
378 }
379 else if (play_mode == MODE_CIRCLE)
380 {
381 if (!add_mark_sgf (cursor_pos, PROP_CIRCLE))
382 {
383 rb->splash (HZ / 3, "Couldn't Mark");
384 }
385 }
386 else if (play_mode == MODE_SQUARE)
387 {
388 if (!add_mark_sgf (cursor_pos, PROP_SQUARE))
389 {
390 rb->splash (HZ / 3, "Couldn't Mark");
391 }
392 }
393 else if (play_mode == MODE_TRIANGLE)
394 {
395 if (!add_mark_sgf (cursor_pos, PROP_TRIANGLE))
396 {
397 rb->splash (HZ / 3, "Couldn't Mark");
398 }
399 }
400 else if (play_mode == MODE_LABEL)
401 {
402 if (!add_mark_sgf (cursor_pos, PROP_LABEL))
403 {
404 rb->splash (HZ / 3, "Couldn't Label");
405 }
406 }
407 else
408 {
409 rb->splash (HZ, "mode not implemented");
410 }
411
412 draw_screen_display ();
413 break;
414
415 case GBN_BUTTON_RIGHT:
416 case GBN_BUTTON_RIGHT | BUTTON_REPEAT:
417#if defined(GBN_BUTTON_NAV_MODE)
418 if (nav_mode == NAV_MODE_TREE)
419 {
420 if (has_more_nodes_sgf ())
421 {
422 if (!redo_node_sgf ())
423 {
424 rb->splash (2 * HZ, "Redo Failed");
425 }
426 draw_screen_display ();
427 }
428 }
429 else
430 {
431#endif
432 cursor_pos = WRAP (EAST (cursor_pos));
433 draw_screen_display ();
434#if defined(GBN_BUTTON_NAV_MODE)
435 }
436#endif
437 break;
438
439 case GBN_BUTTON_LEFT:
440 case GBN_BUTTON_LEFT | BUTTON_REPEAT:
441#if defined(GBN_BUTTON_NAV_MODE)
442 if (nav_mode == NAV_MODE_TREE)
443 {
444 if (has_prev_nodes_sgf ())
445 {
446 if (!undo_node_sgf ())
447 {
448 rb->splash (2 * HZ, "Undo Failed");
449 }
450 draw_screen_display ();
451 }
452 }
453 else
454 {
455#endif
456 cursor_pos = WRAP (WEST (cursor_pos));
457 draw_screen_display ();
458#if defined(GBN_BUTTON_NAV_MODE)
459 }
460#endif
461 break;
462
463 case GBN_BUTTON_DOWN:
464 case GBN_BUTTON_DOWN | BUTTON_REPEAT:
465 cursor_pos = WRAP (SOUTH (cursor_pos));
466 draw_screen_display ();
467 break;
468
469 case GBN_BUTTON_UP:
470 case GBN_BUTTON_UP | BUTTON_REPEAT:
471 cursor_pos = WRAP (NORTH (cursor_pos));
472 draw_screen_display ();
473 break;
474
475 case GBN_BUTTON_MENU:
476 if (do_main_menu ())
477 {
478 save_game (DEFAULT_SAVE);
479
480 global_cleanup ();
481 return PLUGIN_OK;
482 }
483
484 draw_screen_display ();
485 break;
486
487#if defined(GBN_BUTTON_CONTEXT)
488 case GBN_BUTTON_CONTEXT:
489 do_context_menu ();
490 draw_screen_display ();
491 break;
492#endif
493
494#if defined(GBN_BUTTON_NEXT_VAR)
495 case GBN_BUTTON_NEXT_VAR:
496 case GBN_BUTTON_NEXT_VAR | BUTTON_REPEAT:
497 if ((temp = next_variation_sgf ()) >= 0)
498 {
499 draw_screen_display ();
500 rb->splashf (2 * HZ / 3, "%d of %d", temp,
501 num_variations_sgf ());
502 draw_screen_display ();
503 }
504 else
505 {
506 if (num_variations_sgf () > 1)
507 {
508 rb->splashf (HZ, "Error %d in next_variation_sgf", temp);
509 }
510 draw_screen_display ();
511 }
512 break;
513#endif
514
515 case BUTTON_NONE:
516 is_idle = true;
517 default:
518 if (rb->default_event_handler (btn) == SYS_USB_CONNECTED)
519 {
520 return PLUGIN_USB_CONNECTED;
521 }
522 break;
523 };
524
525 if (is_idle && autosave_dirty)
526 {
527 ++autosave_counter;
528
529 if (autosave_time != 0 &&
530 autosave_counter / 2 >= autosave_time)
531 /* counter is in 30 second increments, autosave_time is in
532 * minutes
533 */
534 {
535 DEBUGF("autosaving\n");
536 rb->splash(HZ / 4, "Autosaving...");
537 save_game(DEFAULT_SAVE);
538 draw_screen_display();
539 autosave_counter = 0;
540 }
541 }
542 else
543 {
544 autosave_counter = 0;
545 }
546 }
547
548 return PLUGIN_OK;
549}
550
551static void
552global_cleanup (void)
553{
554 cleanup_sgf ();
555 configfile_save(SETTINGS_FILENAME, config,
556 sizeof(config)/sizeof(*config),
557 SETTINGS_VERSION);
558}
559
560static void
561global_setup (void)
562{
563 setup_stack (&parse_stack, parse_stack_buffer,
564 sizeof (parse_stack_buffer));
565 setup_display ();
566 setup_sgf ();
567
568 set_defaults();
569 if (configfile_load(SETTINGS_FILENAME, config,
570 sizeof(config)/sizeof(*config),
571 SETTINGS_MIN_VERSION ) < 0)
572 {
573 /* If the loading failed, save a new config file (as the disk is
574 already spinning) */
575
576 /* set defaults again just in case (don't know if they can ever
577 * be messed up by configfile_load, and it's basically free anyway)
578 */
579 set_defaults();
580
581 configfile_save(SETTINGS_FILENAME, config,
582 sizeof(config)/sizeof(*config),
583 SETTINGS_VERSION);
584 }
585}
586
587enum main_menu_selections
588{
589 MAIN_NEW = 0,
590 MAIN_SAVE,
591 MAIN_SAVE_AS,
592 MAIN_GAME_INFO,
593 MAIN_PLAYBACK,
594 MAIN_ZOOM,
595 MAIN_OPTIONS,
596 MAIN_CONTEXT,
597 MAIN_QUIT
598};
599
600static bool
601do_main_menu (void)
602{
603 int selection = 0;
604 MENUITEM_STRINGLIST (menu, "Rockbox Goban", NULL,
605 "New",
606 "Save",
607 "Save As",
608 "Game Info",
609 "Playback Control",
610 "Zoom Level",
611 "Options",
612 "Context Menu",
613 "Quit");
614
615 /* for "New" in menu */
616 int new_handi = 0, new_bs = MAX_BOARD_SIZE, new_komi = 15;
617
618 char new_save_file[SAVE_FILE_LENGTH];
619
620
621 bool done = false;
622
623 while (!done)
624 {
625 selection = rb->do_menu (&menu, &selection, NULL, false);
626
627 switch (selection)
628 {
629 case MAIN_NEW:
630 rb->set_int ("board size", "lines", UNIT_INT,
631 &new_bs, NULL, 1, MIN_BOARD_SIZE, MAX_BOARD_SIZE,
632 NULL);
633
634 rb->set_int ("handicap", "stones", UNIT_INT,
635 &new_handi, NULL, 1, 0, 9, NULL);
636
637 if (new_handi > 0)
638 {
639 new_komi = 1;
640 }
641 else
642 {
643 new_komi = 13;
644 }
645
646 rb->set_int ("komi", "moku", UNIT_INT, &new_komi, NULL,
647 1, -300, 300, &komi_formatter);
648
649 setup_game (new_bs, new_bs, new_handi, new_komi);
650 draw_screen_display ();
651 done = true;
652 break;
653
654 case MAIN_SAVE:
655 if (!save_game (save_file))
656 {
657 rb->splash (2 * HZ, "Save Failed!");
658 }
659 else
660 {
661 rb->splash (2 * HZ / 3, "Saved");
662 }
663 done = true;
664 draw_screen_display ();
665 break;
666
667 case MAIN_SAVE_AS:
668 rb->strcpy (new_save_file, save_file);
669
670 if (!rb->kbd_input (new_save_file, SAVE_FILE_LENGTH))
671 {
672 break;
673 }
674
675 if (!save_game (new_save_file))
676 {
677 rb->splash (2 * HZ, "Save Failed!");
678 }
679 else
680 {
681 rb->strcpy (save_file, new_save_file);
682 rb->splash (2 * HZ / 3, "Saved");
683 }
684
685 done = true;
686 draw_screen_display ();
687 break;
688
689 case MAIN_GAME_INFO:
690 do_gameinfo_menu ();
691 break;
692
693 case MAIN_PLAYBACK:
694 if (!audio_stolen_sgf ())
695 {
696 playback_control (NULL);
697 }
698 else
699 {
700 rb->splash (1 * HZ, "Audio has been disabled!");
701 }
702 break;
703
704 case MAIN_ZOOM:
705 if (do_zoom ())
706 {
707 return true;
708 }
709 done = true;
710 draw_screen_display ();
711 break;
712
713 case MAIN_OPTIONS:
714 do_options_menu();
715 break;
716
717 case MAIN_CONTEXT:
718 do_context_menu ();
719 done = true;
720 break;
721
722 case MAIN_QUIT:
723 case MENU_ATTACHED_USB:
724 return true;
725
726 case GO_TO_ROOT:
727 case GO_TO_PREVIOUS:
728 default:
729
730 done = true;
731 break;
732 };
733 }
734
735 return false;
736}
737
738void
739zoom_preview (int current)
740{
741 set_zoom_display (current);
742 draw_screen_display ();
743 rb->splash (0, "Preview");
744}
745
746static bool
747do_zoom (void)
748{
749 unsigned int zoom_level;
750 unsigned int old_val;
751 bool done = false;
752
753 unsigned int min_val = min_zoom_display ();
754 unsigned int max_val = max_zoom_display ();
755
756 zoom_level = old_val = current_zoom_display ();
757
758 int action;
759
760 zoom_preview (zoom_level);
761 while (!done)
762 {
763 switch (action = rb->get_action (CONTEXT_LIST, TIMEOUT_BLOCK))
764 {
765 case ACTION_STD_OK:
766 set_zoom_display (zoom_level);
767 done = true;
768 rb->splash (HZ / 2, "Zoom Set");
769 saved_circle_size = intersection_size;
770 break;
771
772 case ACTION_STD_CANCEL:
773 set_zoom_display (old_val);
774 done = true;
775 rb->splash (HZ / 2, "Cancelled");
776 break;
777
778 case ACTION_STD_CONTEXT:
779 zoom_level = old_val;
780 zoom_preview (zoom_level);
781 break;
782
783 case ACTION_STD_NEXT:
784 case ACTION_STD_NEXTREPEAT:
785 zoom_level = zoom_level * 3 / 2;
786
787 /* 1 * 3 / 2 is 1 again... */
788 if (zoom_level == 1)
789 {
790 ++zoom_level;
791 }
792
793 if (zoom_level > max_val)
794 {
795 zoom_level = min_val;
796 }
797
798 zoom_preview (zoom_level);
799 break;
800
801 case ACTION_STD_PREV:
802 case ACTION_STD_PREVREPEAT:
803 zoom_level = zoom_level * 2 / 3;
804
805 if (zoom_level < min_val)
806 {
807 zoom_level = max_val;
808 }
809 zoom_preview (zoom_level);
810 break;
811
812 case ACTION_NONE:
813 break;
814
815 default:
816 if (rb->default_event_handler (action) == SYS_USB_CONNECTED)
817 {
818 return true;
819 }
820 break;
821 }
822 }
823
824 return false;
825}
826
827enum gameinfo_menu_selections
828{
829 GINFO_BASIC_INFO = 0,
830 GINFO_TIME_LIMIT,
831 GINFO_OVERTIME,
832 GINFO_RESULT,
833 GINFO_HANDICAP,
834 GINFO_KOMI,
835 GINFO_RULESET,
836 GINFO_BLACK_PLAYER,
837 GINFO_BLACK_RANK,
838 GINFO_BLACK_TEAM,
839 GINFO_WHITE_PLAYER,
840 GINFO_WHITE_RANK,
841 GINFO_WHITE_TEAM,
842 GINFO_DATE,
843 GINFO_EVENT,
844 GINFO_PLACE,
845 GINFO_ROUND,
846 GINFO_DONE
847};
848
849
850static void
851do_gameinfo_menu (void)
852{
853 MENUITEM_STRINGLIST (gameinfo_menu, "Game Info", NULL,
854 "Basic Info",
855 "Time Limit",
856 "Overtime",
857 "Result",
858 "Handicap",
859 "Komi",
860 "Ruleset",
861 "Black Player",
862 "Black Rank",
863 "Black Team",
864 "White Player",
865 "White Rank",
866 "White Team",
867 "Date",
868 "Event",
869 "Place",
870 "Round",
871 "Done");
872 /* IMPORTANT:
873 *
874 * if you edit this string list, make sure you keep
875 * menu_selection_to_prop function in line with it!! (see the bottom
876 * of this file).
877 */
878
879 int new_ruleset = 0;
880
881 char *gameinfo_string;
882 int gameinfo_string_size;
883
884 bool done = false;
885 int selection = 0;
886
887 while (!done)
888 {
889 selection = rb->do_menu (&gameinfo_menu, &selection, NULL, false);
890
891 switch (selection)
892 {
893 case GINFO_OVERTIME:
894 case GINFO_RESULT:
895 case GINFO_BLACK_PLAYER:
896 case GINFO_BLACK_RANK:
897 case GINFO_BLACK_TEAM:
898 case GINFO_WHITE_PLAYER:
899 case GINFO_WHITE_RANK:
900 case GINFO_WHITE_TEAM:
901 case GINFO_DATE:
902 case GINFO_EVENT:
903 case GINFO_PLACE:
904 case GINFO_ROUND:
905 if (!get_header_string_and_size (&header,
906 menu_selection_to_prop
907 (selection), &gameinfo_string,
908 &gameinfo_string_size))
909 {
910 rb->splash (3 * HZ, "Couldn't get header string");
911 break;
912 }
913
914 rb->kbd_input (gameinfo_string, gameinfo_string_size);
915 sanitize_string (gameinfo_string);
916 set_game_modified();
917 break;
918
919 /* these need special handling in some way, so they are
920 separate */
921
922 case GINFO_BASIC_INFO:
923 metadata_summary();
924 break;
925
926 case GINFO_TIME_LIMIT:
927 rb->set_int ("Time Limit", "", UNIT_INT, &header.time_limit,
928 NULL, 60, 0, 24 * 60 * 60, &time_formatter);
929 set_game_modified();
930 break;
931
932 case GINFO_HANDICAP:
933 rb->splashf (0, "%d stones. Start a new game to set handicap",
934 header.handicap);
935 rb->action_userabort(TIMEOUT_BLOCK);
936 break;
937
938 case GINFO_KOMI:
939 rb->set_int ("Komi", "moku", UNIT_INT, &header.komi, NULL,
940 1, -300, 300, &komi_formatter);
941 set_game_modified();
942 break;
943
944 case GINFO_RULESET:
945 new_ruleset = 0;
946 rb->set_int ("Ruleset", "", UNIT_INT, &new_ruleset, NULL,
947 1, 0, NUM_RULESETS - 1, &ruleset_formatter);
948
949 rb->strcpy (header.ruleset, ruleset_names[new_ruleset]);
950 set_game_modified();
951 break;
952
953 case GINFO_DONE:
954 case GO_TO_ROOT:
955 case GO_TO_PREVIOUS:
956 case MENU_ATTACHED_USB:
957 default:
958 done = true;
959 break;
960 };
961 }
962}
963
964enum context_menu_selections
965{
966 CTX_PLAY = 0,
967 CTX_ADD_BLACK,
968 CTX_ADD_WHITE,
969 CTX_ERASE,
970 CTX_PASS,
971 CTX_NEXT_VAR,
972 CTX_FORCE,
973 CTX_MARK,
974 CTX_CIRCLE,
975 CTX_SQUARE,
976 CTX_TRIANGLE,
977 CTX_LABEL,
978 CTX_COMMENT,
979 CTX_DONE
980};
981
982static void
983do_context_menu (void)
984{
985 int selection;
986 bool done = false;
987 int temp;
988
989 MENUITEM_STRINGLIST (context_menu, "Context Menu", NULL,
990 "Play Mode (default)",
991 "Add Black Mode",
992 "Add White Mode",
993 "Erase Stone Mode",
994 "Pass",
995 "Next Variation",
996 "Force Play Mode",
997 "Mark Mode",
998 "Circle Mode",
999 "Square Mode",
1000 "Triangle Mode",
1001 "Label Mode",
1002 "Add/Edit Comment",
1003 "Done");
1004
1005 while (!done)
1006 {
1007 selection = rb->do_menu (&context_menu, &selection, NULL, false);
1008
1009 switch (selection)
1010 {
1011 case CTX_PLAY:
1012 play_mode = MODE_PLAY;
1013 done = true;
1014 break;
1015
1016 case CTX_ADD_BLACK:
1017 play_mode = MODE_ADD_BLACK;
1018 done = true;
1019 break;
1020
1021 case CTX_ADD_WHITE:
1022 play_mode = MODE_ADD_WHITE;
1023 done = true;
1024 break;
1025
1026 case CTX_ERASE:
1027 play_mode = MODE_REMOVE;
1028 done = true;
1029 break;
1030
1031 case CTX_PASS:
1032 if (!play_move_sgf (PASS_POS, current_player))
1033 {
1034 rb->splash (HZ, "Error while passing!");
1035 }
1036 done = true;
1037 break;
1038
1039 case CTX_NEXT_VAR:
1040 if ((temp = next_variation_sgf ()) >= 0)
1041 {
1042 draw_screen_display ();
1043 rb->splashf (2 * HZ / 3, "%d of %d", temp,
1044 num_variations_sgf ());
1045 draw_screen_display ();
1046 }
1047 else
1048 {
1049 if (num_variations_sgf () > 1)
1050 {
1051 rb->splashf (HZ, "Error %d in next_variation_sgf", temp);
1052 }
1053 else
1054 {
1055 rb->splash (HZ, "No next variation");
1056 }
1057 }
1058 break;
1059
1060 case CTX_FORCE:
1061 play_mode = MODE_FORCE_PLAY;
1062 done = true;
1063 break;
1064
1065 case CTX_MARK:
1066 play_mode = MODE_MARK;
1067 done = true;
1068 break;
1069
1070 case CTX_CIRCLE:
1071 play_mode = MODE_CIRCLE;
1072 done = true;
1073 break;
1074
1075 case CTX_SQUARE:
1076 play_mode = MODE_SQUARE;
1077 done = true;
1078 break;
1079
1080 case CTX_TRIANGLE:
1081 play_mode = MODE_TRIANGLE;
1082 done = true;
1083 break;
1084
1085 case CTX_LABEL:
1086 play_mode = MODE_LABEL;
1087 done = true;
1088 break;
1089
1090 case CTX_COMMENT:
1091 if (!do_comment_edit ())
1092 {
1093 DEBUGF ("Editing comment failed\n");
1094 rb->splash (HZ, "Read or write failed!\n");
1095 }
1096 done = true;
1097 break;
1098
1099 case CTX_DONE:
1100 case GO_TO_ROOT:
1101 case GO_TO_PREVIOUS:
1102 case MENU_ATTACHED_USB:
1103 default:
1104 done = true;
1105 break;
1106 };
1107 }
1108}
1109
1110enum options_menu_selections
1111{
1112 OMENU_SHOW_VARIATIONS = 0,
1113 OMENU_DISABLE_POWEROFF,
1114 OMENU_AUTOSAVE_TIME,
1115 OMENU_AUTO_COMMENT
1116};
1117
1118static void
1119do_options_menu (void)
1120{
1121 int selection;
1122 bool done = false;
1123
1124 MENUITEM_STRINGLIST (options_menu, "Options Menu", NULL,
1125 "Show Child Variations?",
1126 "Disable Idle Poweroff?",
1127 "Idle Autosave Time",
1128 "Automatically Show Comments?");
1129
1130 while (!done)
1131 {
1132 selection = rb->do_menu (&options_menu, &selection, NULL, false);
1133
1134 switch (selection)
1135 {
1136 case OMENU_SHOW_VARIATIONS:
1137 rb->set_bool("Draw Variations?", &draw_variations);
1138 clear_marks_display ();
1139 set_all_marks_sgf ();
1140 if (draw_variations)
1141 {
1142 mark_child_variations_sgf ();
1143 }
1144 break;
1145
1146 case OMENU_DISABLE_POWEROFF:
1147 rb->set_bool("Disable Idle Poweroff?", &disable_shutdown);
1148 break;
1149
1150 case OMENU_AUTOSAVE_TIME:
1151 rb->set_int("Idle Autosave Time", "minutes", UNIT_INT,
1152 &autosave_time, NULL, 1, 0, MAX_AUTOSAVE,
1153 &autosave_formatter);
1154 autosave_counter = 0;
1155 break;
1156
1157 case OMENU_AUTO_COMMENT:
1158 rb->set_bool("Auto Show Comments?", &auto_show_comments);
1159 break;
1160
1161 case GO_TO_ROOT:
1162 case GO_TO_PREVIOUS:
1163 case MENU_ATTACHED_USB:
1164 default:
1165 done = true;
1166 break;
1167 };
1168 }
1169
1170}
1171
1172static bool
1173do_comment_edit (void)
1174{
1175 char cbuffer[512];
1176
1177 rb->memset (cbuffer, 0, sizeof (cbuffer));
1178
1179 if (read_comment_sgf (cbuffer, sizeof (cbuffer)) < 0)
1180 {
1181 return false;
1182 }
1183
1184 if (!rb->kbd_input (cbuffer, sizeof (cbuffer)))
1185 {
1186 /* user didn't edit, no reason to write it back */
1187 return true;
1188 }
1189
1190 if (write_comment_sgf (cbuffer) < 0)
1191 {
1192 return false;
1193 }
1194
1195 return true;
1196}
1197
1198static enum prop_type_t
1199menu_selection_to_prop (int selection)
1200{
1201 switch (selection)
1202 {
1203 case GINFO_OVERTIME:
1204 return PROP_OVERTIME;
1205 case GINFO_RESULT:
1206 return PROP_RESULT;
1207 case GINFO_BLACK_PLAYER:
1208 return PROP_BLACK_NAME;
1209 case GINFO_BLACK_RANK:
1210 return PROP_BLACK_RANK;
1211 case GINFO_BLACK_TEAM:
1212 return PROP_BLACK_TEAM;
1213 case GINFO_WHITE_PLAYER:
1214 return PROP_WHITE_NAME;
1215 case GINFO_WHITE_RANK:
1216 return PROP_WHITE_RANK;
1217 case GINFO_WHITE_TEAM:
1218 return PROP_WHITE_TEAM;
1219 case GINFO_DATE:
1220 return PROP_DATE;
1221 case GINFO_EVENT:
1222 return PROP_EVENT;
1223 case GINFO_PLACE:
1224 return PROP_PLACE;
1225 case GINFO_ROUND:
1226 return PROP_ROUND;
1227 default:
1228 DEBUGF ("Tried to get prop from invalid menu selection!!!\n");
1229 return PROP_PLACE; /* just pick one, there's a major bug if
1230 we got here */
1231 };
1232}
diff --git a/apps/plugins/goban/goban.h b/apps/plugins/goban/goban.h
new file mode 100644
index 0000000000..84866d5b47
--- /dev/null
+++ b/apps/plugins/goban/goban.h
@@ -0,0 +1,277 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#ifndef GOBAN_MAIN_H
23#define GOBAN_MAIN_H
24
25/* Enable this to run test mode. (see the end of util.c) */
26#if 0
27#define GBN_TEST
28#endif
29
30#include "types.h"
31#include "util.h"
32
33
34/* Colors of various things. The colors on mono bitmap targets is fixed
35 based on the background/foreground color. */
36#ifdef HAVE_LCD_COLOR
37#define BOARD_COLOR LCD_RGBPACK(184,136,72)
38#define WHITE_COLOR LCD_RGBPACK(255,255,255)
39#define BLACK_COLOR LCD_RGBPACK(0,0,0)
40#define LINE_COLOR LCD_RGBPACK(0,0,0)
41#define BACKGROUND_COLOR LCD_RGBPACK(41,104,74)
42#define CURSOR_COLOR LCD_RGBPACK(222,0,0)
43#define MARK_COLOR LCD_RGBPACK(0,0,255)
44#elif LCD_DEPTH > 1 /* grayscale */
45#define BOARD_COLOR LCD_LIGHTGRAY
46#define WHITE_COLOR LCD_WHITE
47#define BLACK_COLOR LCD_BLACK
48#define LINE_COLOR LCD_BLACK
49#define BACKGROUND_COLOR LCD_DARKGRAY
50#define CURSOR_COLOR LCD_DARKGRAY
51#define MARK_COLOR LCD_DARKGRAY
52#endif
53
54/* Key setups */
55#ifdef HAVE_TOUCHSCREEN
56#define GBN_BUTTON_UP BUTTON_TOPMIDDLE
57#define GBN_BUTTON_DOWN BUTTON_BOTTOMMIDDLE
58#define GBN_BUTTON_LEFT BUTTON_MIDLEFT
59#define GBN_BUTTON_RIGHT BUTTON_MIDRIGHT
60#define GBN_BUTTON_RETREAT BUTTON_BOTTOMLEFT
61#define GBN_BUTTON_ADVANCE BUTTON_BOTTOMRIGHT
62#define GBN_BUTTON_MENU BUTTON_TOPLEFT
63#define GBN_BUTTON_PLAY BUTTON_CENTER | BUTTON_REL
64#define GBN_BUTTON_CONTEXT BUTTON_CENTER | BUTTON_REPEAT
65#define GBN_BUTTON_NEXT_VAR BUTTON_TOPRIGHT
66
67#elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
68 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
69 || (CONFIG_KEYPAD == IPOD_4G_PAD)
70#define GBN_BUTTON_UP BUTTON_MENU
71#define GBN_BUTTON_DOWN BUTTON_PLAY
72#define GBN_BUTTON_LEFT BUTTON_LEFT
73#define GBN_BUTTON_RIGHT BUTTON_RIGHT
74#define GBN_BUTTON_RETREAT BUTTON_SCROLL_BACK
75#define GBN_BUTTON_ADVANCE BUTTON_SCROLL_FWD
76#define GBN_BUTTON_PLAY BUTTON_SELECT | BUTTON_REL
77#define GBN_BUTTON_MENU BUTTON_SELECT | BUTTON_REPEAT
78/* no context */
79/* no next var */
80
81#elif (CONFIG_KEYPAD == SANSA_E200_PAD) \
82 || (CONFIG_KEYPAD == SANSA_FUZE_PAD)
83#define GBN_BUTTON_UP BUTTON_UP
84#define GBN_BUTTON_DOWN BUTTON_DOWN
85#define GBN_BUTTON_LEFT BUTTON_LEFT
86#define GBN_BUTTON_RIGHT BUTTON_RIGHT
87#define GBN_BUTTON_RETREAT BUTTON_SCROLL_BACK
88#define GBN_BUTTON_ADVANCE BUTTON_SCROLL_FWD
89#define GBN_BUTTON_MENU BUTTON_POWER
90#define GBN_BUTTON_PLAY BUTTON_SELECT | BUTTON_REL
91#define GBN_BUTTON_CONTEXT BUTTON_SELECT | BUTTON_REPEAT
92#define GBN_BUTTON_NEXT_VAR BUTTON_REC
93
94#elif (CONFIG_KEYPAD == SANSA_C200_PAD)
95#define GBN_BUTTON_UP BUTTON_UP
96#define GBN_BUTTON_DOWN BUTTON_DOWN
97#define GBN_BUTTON_LEFT BUTTON_LEFT
98#define GBN_BUTTON_RIGHT BUTTON_RIGHT
99#define GBN_BUTTON_RETREAT BUTTON_VOL_DOWN
100#define GBN_BUTTON_ADVANCE BUTTON_VOL_UP
101#define GBN_BUTTON_MENU BUTTON_POWER
102#define GBN_BUTTON_PLAY BUTTON_SELECT | BUTTON_REL
103#define GBN_BUTTON_CONTEXT BUTTON_SELECT | BUTTON_REPEAT
104#define GBN_BUTTON_NEXT_VAR BUTTON_REC
105
106#elif (CONFIG_KEYPAD == GIGABEAT_PAD) \
107#define GBN_BUTTON_UP BUTTON_UP
108#define GBN_BUTTON_DOWN BUTTON_DOWN
109#define GBN_BUTTON_LEFT BUTTON_LEFT
110#define GBN_BUTTON_RIGHT BUTTON_RIGHT
111#define GBN_BUTTON_RETREAT BUTTON_VOL_DOWN
112#define GBN_BUTTON_ADVANCE BUTTON_VOL_UP
113#define GBN_BUTTON_MENU BUTTON_MENU
114#define GBN_BUTTON_PLAY BUTTON_SELECT | BUTTON_REL
115#define GBN_BUTTON_CONTEXT BUTTON_SELECT | BUTTON_REPEAT
116#define GBN_BUTTON_NEXT_VAR BUTTON_A
117
118#elif (CONFIG_KEYPAD == GIGABEAT_S_PAD)
119#define GBN_BUTTON_UP BUTTON_UP
120#define GBN_BUTTON_DOWN BUTTON_DOWN
121#define GBN_BUTTON_LEFT BUTTON_LEFT
122#define GBN_BUTTON_RIGHT BUTTON_RIGHT
123#define GBN_BUTTON_RETREAT BUTTON_VOL_DOWN
124#define GBN_BUTTON_ADVANCE BUTTON_VOL_UP
125#define GBN_BUTTON_MENU BUTTON_MENU
126#define GBN_BUTTON_PLAY BUTTON_SELECT | BUTTON_REL
127#define GBN_BUTTON_CONTEXT BUTTON_SELECT | BUTTON_REPEAT
128#define GBN_BUTTON_NEXT_VAR BUTTON_PLAY
129
130#elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
131#define GBN_BUTTON_UP BUTTON_SCROLL_UP
132#define GBN_BUTTON_DOWN BUTTON_SCROLL_DOWN
133#define GBN_BUTTON_LEFT BUTTON_LEFT
134#define GBN_BUTTON_RIGHT BUTTON_RIGHT
135#define GBN_BUTTON_RETREAT BUTTON_FF
136#define GBN_BUTTON_ADVANCE BUTTON_REW
137#define GBN_BUTTON_MENU BUTTON_POWER
138#define GBN_BUTTON_PLAY BUTTON_PLAY | BUTTON_REL
139#define GBN_BUTTON_CONTEXT BUTTON_PLAY | BUTTON_REPEAT
140/* No next var */
141
142#elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
143 (CONFIG_KEYPAD == IRIVER_H300_PAD)
144#define GBN_BUTTON_UP BUTTON_UP
145#define GBN_BUTTON_DOWN BUTTON_DOWN
146#define GBN_BUTTON_LEFT BUTTON_LEFT
147#define GBN_BUTTON_RIGHT BUTTON_RIGHT
148#define GBN_BUTTON_RETREAT BUTTON_OFF
149#define GBN_BUTTON_ADVANCE BUTTON_ON
150#define GBN_BUTTON_MENU BUTTON_MODE
151#define GBN_BUTTON_PLAY BUTTON_SELECT | BUTTON_REL
152#define GBN_BUTTON_CONTEXT BUTTON_SELECT | BUTTON_REPEAT
153#define GBN_BUTTON_NEXT_VAR BUTTON_REC
154
155#elif (CONFIG_KEYPAD == MROBE100_PAD)
156#define GBN_BUTTON_UP BUTTON_UP
157#define GBN_BUTTON_DOWN BUTTON_DOWN
158#define GBN_BUTTON_LEFT BUTTON_LEFT
159#define GBN_BUTTON_RIGHT BUTTON_RIGHT
160#define GBN_BUTTON_RETREAT BUTTON_MENU
161#define GBN_BUTTON_ADVANCE BUTTON_PLAY
162#define GBN_BUTTON_MENU BUTTON_DISPLAY
163#define GBN_BUTTON_PLAY BUTTON_SELECT | BUTTON_REL
164#define GBN_BUTTON_CONTEXT BUTTON_SELECT | BUTTON_REPEAT
165#define GBN_BUTTON_NEXT_VAR BUTTON_POWER
166
167#elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD)
168#define GBN_BUTTON_UP BUTTON_UP
169#define GBN_BUTTON_DOWN BUTTON_DOWN
170#define GBN_BUTTON_LEFT BUTTON_LEFT
171#define GBN_BUTTON_RIGHT BUTTON_RIGHT
172#define GBN_BUTTON_RETREAT BUTTON_PLAY
173#define GBN_BUTTON_ADVANCE BUTTON_REC
174#define GBN_BUTTON_MENU BUTTON_POWER
175#define GBN_BUTTON_PLAY BUTTON_SELECT | BUTTON_REL
176#define GBN_BUTTON_CONTEXT BUTTON_SELECT | BUTTON_REPEAT
177/* no next var */
178
179#elif CONFIG_KEYPAD == IAUDIO_M3_PAD
180/* TODO: these are basically complete guesses, I have no manual to go by */
181#define GBN_BUTTON_UP BUTTON_RC_VOL_UP
182#define GBN_BUTTON_DOWN BUTTON_RC_VOL_DOWN
183#define GBN_BUTTON_LEFT BUTTON_RC_REW
184#define GBN_BUTTON_RIGHT BUTTON_RC_FF
185#define GBN_BUTTON_RETREAT BUTTON_VOL_DOWN
186#define GBN_BUTTON_ADVANCE BUTTON_VOL_UP
187#define GBN_BUTTON_MENU BUTTON_MODE
188#define GBN_BUTTON_PLAY BUTTON_PLAY | BUTTON_REL
189#define GBN_BUTTON_CONTEXT BUTTON_PLAY | BUTTON_REPEAT
190/* no next var */
191
192#elif (CONFIG_KEYPAD == RECORDER_PAD)
193#define GBN_BUTTON_UP BUTTON_UP
194#define GBN_BUTTON_DOWN BUTTON_DOWN
195#define GBN_BUTTON_LEFT BUTTON_LEFT
196#define GBN_BUTTON_RIGHT BUTTON_RIGHT
197#define GBN_BUTTON_RETREAT BUTTON_F1
198#define GBN_BUTTON_ADVANCE BUTTON_F3
199#define GBN_BUTTON_MENU BUTTON_F2
200#define GBN_BUTTON_PLAY BUTTON_PLAY | BUTTON_REL
201#define GBN_BUTTON_CONTEXT BUTTON_PLAY | BUTTON_REPEAT
202#define GBN_BUTTON_NEXT_VAR BUTTON_ON
203
204#elif (CONFIG_KEYPAD == ONDIO_PAD)
205#define GBN_BUTTON_UP BUTTON_UP
206#define GBN_BUTTON_DOWN BUTTON_DOWN
207#define GBN_BUTTON_LEFT BUTTON_LEFT
208#define GBN_BUTTON_RIGHT BUTTON_RIGHT
209#define GBN_BUTTON_MENU BUTTON_MENU | BUTTON_REPEAT
210#define GBN_BUTTON_PLAY BUTTON_MENU | BUTTON_REL
211#define GBN_BUTTON_NAV_MODE BUTTON_OFF
212/* No context */
213/* No advance/retreat */
214/* no next var */
215
216#else
217#error Unsupported keypad
218#endif
219
220
221/* The smallest dimension of the LCD */
222#define LCD_MIN_DIMENSION (LCD_HEIGHT > LCD_WIDTH ? LCD_WIDTH : LCD_HEIGHT)
223
224
225/* Determine if we have a wide screen or a tall screen. This is used to
226 place the board and footer in acceptable locations also, set the
227 LCD_BOARD_SIZE, making sure that we have at least 16 pixels for the
228 "footer" on either the bottom or the right. */
229
230#define FOOTER_RESERVE (16)
231
232#if (LCD_WIDTH > LCD_HEIGHT)
233
234#define GBN_WIDE_SCREEN
235
236#define LCD_BOARD_WIDTH (LCD_WIDTH - FOOTER_RESERVE)
237#define LCD_BOARD_HEIGHT LCD_HEIGHT
238
239#else
240
241#define GBN_TALL_SCREEN
242
243#define LCD_BOARD_WIDTH LCD_WIDTH
244#define LCD_BOARD_HEIGHT (LCD_HEIGHT - FOOTER_RESERVE)
245
246#endif // LCD_WIDTH > LCD_HEIGHT
247
248
249/* The directory we default to for saving crap */
250#define DEFAULT_SAVE_DIR "/sgf"
251
252/* The default file we save to */
253#define DEFAULT_SAVE (DEFAULT_SAVE_DIR "/gbn_def.sgf")
254
255/* The size of the buffer we store filenames in (1 reserved for '\0') */
256#define SAVE_FILE_LENGTH 256
257
258/* The maximum setting for idle autosave time, in minutes */
259#define MAX_AUTOSAVE (30)
260
261/* On mono targets, draw while stones with a black outline so they are
262 actually visibile instead of being white on white */
263#if (LCD_DEPTH == 1)
264#define OUTLINE_STONES
265#endif
266
267/* The current play mode */
268extern enum play_mode_t play_mode;
269
270/* Show comments when redoing onto a move? */
271extern bool auto_show_comments;
272
273/* A stack used for parsing/outputting as well as some board functions
274 such as counting liberties and filling in/ removing stones */
275extern struct stack_t parse_stack;
276
277#endif
diff --git a/apps/plugins/goban/goban.make b/apps/plugins/goban/goban.make
new file mode 100644
index 0000000000..f3f96ff5c6
--- /dev/null
+++ b/apps/plugins/goban/goban.make
@@ -0,0 +1,22 @@
1# __________ __ ___.
2# Open \______ \ ____ ____ | | _\_ |__ _______ ___
3# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
4# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
5# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
6# \/ \/ \/ \/ \/
7# $Id$
8#
9
10
11GOBANSRCDIR := $(APPSDIR)/plugins/goban
12GOBANBUILDDIR := $(BUILDDIR)/apps/plugins/goban
13
14ROCKS += $(GOBANBUILDDIR)/goban.rock
15
16
17GOBAN_SRC := $(call preprocess, $(GOBANSRCDIR)/SOURCES)
18GOBAN_OBJ := $(call c2obj, $(GOBAN_SRC))
19
20OTHER_SRC += $(GOBAN_SRC)
21
22$(GOBANBUILDDIR)/goban.rock: $(GOBAN_OBJ)
diff --git a/apps/plugins/goban/sgf.c b/apps/plugins/goban/sgf.c
new file mode 100644
index 0000000000..ad6e4a4e05
--- /dev/null
+++ b/apps/plugins/goban/sgf.c
@@ -0,0 +1,2237 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "sgf.h"
23#include "sgf_storage.h"
24#include "types.h"
25#include "goban.h"
26#include "board.h"
27#include "display.h"
28#include "game.h"
29#include "util.h"
30
31int sgf_fd = -1;
32int unhandled_fd = -1;
33
34int tree_head = -1;
35int current_node = -1;
36int start_node = -1;
37
38bool header_marked = false;
39
40static int add_child_variation (int *variation_number);
41
42static int get_move_from_node (int handle);
43
44static bool is_important_node (int handle);
45static bool goto_next_important_node (bool forward);
46static bool retreat_node (void);
47static bool advance_node (void);
48
49static bool do_add_stones (void);
50static void setup_handicap_helper (unsigned short pos);
51
52static int undo_node_helper (void);
53
54static void set_one_mark (unsigned short pos, enum prop_type_t type);
55static void set_label_mark (unsigned short pos, char to_set);
56
57bool
58play_move_sgf (unsigned short pos, unsigned char color)
59{
60 int handle;
61 int prop_handle;
62 int temp;
63 int temp2;
64 union prop_data_t temp_data;
65 int saved = current_node;
66
67 if ((color != BLACK && color != WHITE) ||
68 (!on_board (pos) && pos != PASS_POS))
69 {
70 return false;
71 }
72
73
74 /* go to the node before the next important node (move/add
75 stone/variation) this is the right place to look for children, add
76 variations, whatever. (if there is no next, we're already at the
77 right place) */
78
79 if (get_node (current_node)->next >= 0)
80 {
81 current_node = get_node (current_node)->next;
82
83 /* true means forward */
84 if (goto_next_important_node (true))
85 {
86 current_node = get_node (current_node)->prev;
87 }
88 }
89
90
91 if ((temp = get_matching_child_sgf (pos, color)) >= 0)
92 {
93 /* don't have to do anything to set up temp as the right variation
94 number */
95
96 }
97 else
98 {
99 /* now either there were no children, or none matched the one we
100 want so we have to add a new one */
101
102 /* first test if it's legal. we don't do this above because SGF
103 files are allowed to have illegal moves in them, and it seems
104 to make sense to allow traversing those variations without
105 making the user change to a different play_mode */
106
107 bool suicide_allowed = false;
108
109 if (rb->strcmp (header.ruleset, "NZ") == 0 ||
110 rb->strcmp (header.ruleset, "GOE") == 0)
111 {
112 suicide_allowed = true;
113 }
114
115 if (play_mode != MODE_FORCE_PLAY &&
116 !legal_move_board (pos, color, suicide_allowed))
117 {
118 return false;
119 }
120
121 handle = add_child_sgf (NULL);
122
123 if (handle < 0)
124 {
125 current_node = saved;
126 return false;
127 }
128
129 union prop_data_t temp_prop_data;
130 temp_prop_data.position = pos;
131
132 prop_handle = add_prop_sgf (handle,
133 color == BLACK ? PROP_BLACK_MOVE :
134 PROP_WHITE_MOVE, temp_prop_data);
135
136 if (prop_handle < 0)
137 {
138 /* TODO: add code to completely remove the child which we
139 added, and then uncomment the following line. probably
140 doens't matter much since we're out of memory, but
141 whatever
142 free_storage_sgf(handle); */
143 rb->splash (2 * HZ,
144 "Out of memory led to invalid state. Please exit.");
145 current_node = saved;
146 return false;
147 }
148
149 set_game_modified();
150
151 temp = get_matching_child_sgf (pos, color);
152 }
153
154 /* now, one way or another temp has been set to the child variation
155 number that we should follow, so all we need to do is "choose" it
156 and redo_node_sgf */
157
158 current_node = get_node (current_node)->next;
159 temp_data.number = temp;
160
161 temp2 = add_or_set_prop_sgf (current_node,
162 PROP_VARIATION_CHOICE, temp_data);
163 /* free up a superfluous prop */
164 if (temp == 0)
165 {
166 delete_prop_handle_sgf (current_node, temp2);
167 }
168
169 current_node = saved;
170 return redo_node_sgf ();
171}
172
173bool
174add_mark_sgf (unsigned short pos, enum prop_type_t type)
175{
176 union prop_data_t temp_data;
177 int temp_handle;
178 enum prop_type_t original_type;
179
180 if (!on_board (pos) || current_node < 0)
181 {
182 return false;
183 }
184
185 if (type == PROP_CIRCLE ||
186 type == PROP_SQUARE ||
187 type == PROP_TRIANGLE ||
188 type == PROP_MARK || type == PROP_DIM || type == PROP_SELECTED)
189 {
190 temp_data.position = pos;
191
192 if ((temp_handle = get_prop_pos_sgf (type, temp_data)) >= 0)
193 {
194 original_type = get_prop (temp_handle)->type;
195 delete_prop_handle_sgf (current_node, temp_handle);
196
197 if (type == original_type)
198 {
199 set_one_mark (pos, PROP_INVALID);
200 return true;
201 }
202 }
203
204 add_prop_sgf (current_node, type, temp_data);
205 set_one_mark (pos, type);
206
207 return true;
208 }
209 else if (type == PROP_LABEL)
210 {
211#define MIN_LABEL 'a'
212#define MAX_LABEL 'f'
213 int temp_prop_handle = get_node (current_node)->props;
214
215 while (temp_prop_handle >= 0)
216 {
217 struct prop_t *temp_prop = get_prop (temp_prop_handle);
218
219 if (temp_prop->type == PROP_LABEL &&
220 temp_prop->data.position == pos)
221 {
222 char to_set = temp_prop->data.label_extra;
223 ++to_set;
224 if (to_set > MAX_LABEL)
225 {
226 delete_prop_handle_sgf (current_node, temp_prop_handle);
227 set_one_mark (pos, PROP_INVALID);
228 return true;
229 }
230
231 temp_prop->data.label_extra = to_set;
232 set_label_mark (pos, to_set);
233 return true;
234 }
235
236 temp_prop_handle = temp_prop->next;
237 }
238
239 temp_data.label_extra = MIN_LABEL;
240 temp_data.position = pos;
241
242 add_prop_sgf (current_node, type, temp_data);
243 set_label_mark (pos, MIN_LABEL);
244 return true;
245 }
246 else
247 {
248 return false;
249 }
250}
251
252bool
253add_stone_sgf (unsigned short pos, unsigned char color)
254{
255 int handle;
256 int prop_handle;
257 int saved = current_node;
258 union prop_data_t temp_data;
259 enum prop_type_t temp_type;
260 int var_number;
261 int temp;
262
263 if (!on_board (pos))
264 {
265 return false;
266 }
267
268 if (color == get_point_board (pos))
269 {
270 return false;
271 }
272
273 if ((!is_important_node (current_node) ||
274 (current_node == start_node && get_move_sgf () < 0)) ||
275 (get_prop_sgf (current_node, PROP_ADD_BLACK, NULL) >= 0 ||
276 get_prop_sgf (current_node, PROP_ADD_WHITE, NULL) >= 0 ||
277 get_prop_sgf (current_node, PROP_ADD_EMPTY, NULL) >= 0) ||
278 get_node (current_node)->props < 0)
279 {
280
281 if (color == BLACK)
282 {
283 temp_type = PROP_ADD_BLACK;
284 }
285 else if (color == WHITE)
286 {
287 temp_type = PROP_ADD_WHITE;
288 }
289 else
290 {
291 temp_type = PROP_ADD_EMPTY;
292 }
293
294 temp_data.position = pos;
295
296 handle = get_prop_pos_sgf (temp_type, temp_data);
297
298 /* we have to always delete the old one and conditionally create a
299 new one (instead of trying to reuse the old one by changing
300 the type of it) because if we don't, our invariant with
301 respect to like-properties being grouped together in the
302 property list can easily be violated */
303 if (handle >= 0)
304 {
305 temp_data.stone_extra = get_prop (handle)->data.stone_extra;
306 delete_prop_handle_sgf (current_node, handle);
307 }
308 else
309 {
310 temp_data.stone_extra = 0;
311 if (get_point_board (pos) == EMPTY)
312 {
313 temp_data.stone_extra |= FLAG_ORIG_EMPTY;
314 }
315 else if (get_point_board (pos) == BLACK)
316 {
317 temp_data.stone_extra |= FLAG_ORIG_BLACK;
318 }
319 /* else do nothing */
320 }
321
322 /* now we've saved the information about what the board was
323 originally like, we can do the actual set */
324
325 set_point_board (pos, color);
326
327 /* test if what we currently did just returned the board back to
328 its original for this position. if so, we DON'T create a new
329 PROP_ADD_*, because it's not needed (we already deleted the old
330 one, so in that case we just return) */
331 if (((temp_data.stone_extra & FLAG_ORIG_EMPTY) && color == EMPTY) ||
332 (!(temp_data.stone_extra & FLAG_ORIG_EMPTY) &&
333 (((temp_data.stone_extra & FLAG_ORIG_BLACK) && color == BLACK) ||
334 (!(temp_data.stone_extra & FLAG_ORIG_BLACK) && color == WHITE))))
335 {
336 /* do nothing, set back to original */
337 }
338 else
339 {
340 /* we're not set back to original, so add a prop for it */
341 add_prop_sgf (current_node, temp_type, temp_data);
342 }
343
344 set_game_modified();
345
346 return true;
347 }
348 else
349 {
350 /* we have to make a child variation and add stones in it */
351
352 /* go to the node before the next important node (move/add
353 stone/variation) this is the right place to look for children,
354 add variations, whatever. (if there is no next, we're already
355 at the right place) */
356
357 if (get_node (current_node)->next >= 0)
358 {
359 current_node = get_node (current_node)->next;
360
361 /* true means forward */
362 if (goto_next_important_node (true))
363 {
364 current_node = get_node (current_node)->prev;
365 }
366 }
367
368 handle = add_child_sgf (&var_number);
369
370 if (handle < 0)
371 {
372 rb->splash (2 * HZ, "Out of memory!");
373 return false;
374 }
375
376 temp_data.position = pos;
377
378 if (color == BLACK)
379 {
380 temp_type = PROP_ADD_BLACK;
381 }
382 else if (color == WHITE)
383 {
384 temp_type = PROP_ADD_WHITE;
385 }
386 else
387 {
388 temp_type = PROP_ADD_EMPTY;
389 }
390
391 prop_handle = add_prop_sgf (handle, temp_type, temp_data);
392
393 if (prop_handle < 0)
394 {
395 /* TODO: add code to completely remove the child which we
396 added, and then uncomment the following line. probably
397 doens't matter much since we're out of memory, but
398 whatever
399 free_storage_sgf(handle); */
400 rb->splash (2 * HZ, "Out of memory!");
401 return false;
402 }
403
404 set_game_modified();
405
406 /* now, "choose" the variation that we just added */
407
408 current_node = get_node (current_node)->next;
409 temp_data.number = var_number;
410
411 temp = add_or_set_prop_sgf (current_node,
412 PROP_VARIATION_CHOICE, temp_data);
413
414 /* free up a superfluous prop */
415 if (var_number == 0)
416 {
417 delete_prop_handle_sgf (current_node, temp);
418 }
419
420 current_node = saved;
421
422 /* and follow to our choice, returning since we already did the
423 work */
424 return redo_node_sgf ();
425 }
426
427 return false;
428}
429
430bool
431undo_node_sgf (void)
432{
433 int result = undo_node_helper ();
434
435 /* if we undid a ko threat, we need to figure out what the ko_pos is
436 there's no simple way to do this except to undo one /more/ move,
437 and then redo back to this location. (we could store it, but this
438 isn't that bad) Note: this doesn't need to recurse because we don't
439 care what previous move's ko positions were (since the tree is
440 already set in stone basically, it wouldn't change anything). */
441 if (result == 1)
442 {
443 int backward_move_num = move_num - 1;
444 int saved_current = current_node;
445
446 while (move_num > backward_move_num)
447 {
448 result = undo_node_helper ();
449
450 if (result < 0)
451 {
452 DEBUGF
453 ("couldn't undo to previous move in ko threat handling!\n");
454 return false;
455 }
456 }
457
458 /* now we're backed up to the previous move before our destination
459 so, let's go forward again until we get to the node we were at
460 */
461
462 while (current_node != saved_current)
463 {
464 if (!redo_node_sgf ())
465 {
466 DEBUGF
467 ("redoing to correct node failed on ko threat handling!\n");
468 return false;
469 }
470 }
471 }
472 else if (result < 0)
473 {
474 DEBUGF ("initial undo failed!\n");
475 return false;
476 }
477
478 set_all_marks_sgf ();
479
480 /* if there is a move in this node, move the screen so that it is
481 visible */
482 int handle = get_move_sgf ();
483 if (handle >= 0)
484 {
485 move_display (get_prop (handle)->data.position);
486 }
487
488 return true;
489}
490
491static int
492undo_node_helper (void)
493{
494 bool ko_threat_move = false;
495
496 if (current_node == start_node)
497 {
498 /* refuse to undo the initial SGF node, which is tree_head if
499 handicap == 0 or 1. If handicap >= 2, start_node is the node
500 with the handicap crap and added moves on it. don't let the
501 user undo past that */
502 DEBUGF ("not undoing start_node\n");
503 return -1;
504 }
505
506 struct prop_t *temp_move = get_prop (get_move_sgf ());
507
508 if (temp_move)
509 {
510 int undone_caps = 0;
511 int undone_suicides = 0;
512 unsigned short move_pos = temp_move->data.position;
513 unsigned char move_color = temp_move->type == PROP_BLACK_MOVE ? BLACK :
514 WHITE;
515
516 unsigned short flags = temp_move->data.stone_extra;
517
518 if (move_pos != PASS_POS)
519 {
520
521 if (flags & FLAG_N_CAP)
522 {
523 undone_caps += flood_fill_board (NORTH (move_pos),
524 OTHER (move_color));
525 }
526 if (flags & FLAG_S_CAP)
527 {
528 undone_caps += flood_fill_board (SOUTH (move_pos),
529 OTHER (move_color));
530 }
531 if (flags & FLAG_E_CAP)
532 {
533 undone_caps += flood_fill_board (EAST (move_pos),
534 OTHER (move_color));
535 }
536 if (flags & FLAG_W_CAP)
537 {
538 undone_caps += flood_fill_board (WEST (move_pos),
539 OTHER (move_color));
540 }
541
542 if (flags & FLAG_SELF_CAP)
543 {
544 undone_suicides += flood_fill_board (move_pos, move_color);
545 }
546
547 if (flags & FLAG_ORIG_EMPTY)
548 {
549 set_point_board (move_pos, EMPTY);
550 }
551 else if (flags & FLAG_ORIG_BLACK)
552 {
553 set_point_board (move_pos, BLACK);
554 }
555 else
556 {
557 set_point_board (move_pos, WHITE);
558 }
559 }
560
561 if (move_color == BLACK)
562 {
563 black_captures -= undone_caps;
564 white_captures -= undone_suicides;
565 }
566 else
567 {
568 white_captures -= undone_caps;
569 black_captures -= undone_suicides;
570 }
571
572 if (flags & FLAG_KO_THREAT)
573 {
574 ko_threat_move = true;
575 }
576
577 --move_num;
578 current_player = OTHER (current_player);
579 }
580 else
581 {
582 /* test for added stones! */
583 struct prop_t *temp_prop;
584
585 temp_prop = get_prop (get_node (current_node)->props);
586
587 while (temp_prop)
588 {
589 if ((temp_prop->type == PROP_ADD_BLACK ||
590 temp_prop->type == PROP_ADD_WHITE ||
591 temp_prop->type == PROP_ADD_EMPTY) &&
592 on_board (temp_prop->data.position))
593 {
594 if (temp_prop->data.stone_extra & FLAG_ORIG_EMPTY)
595 {
596 set_point_board (temp_prop->data.position, EMPTY);
597 }
598 else if (temp_prop->data.stone_extra & FLAG_ORIG_BLACK)
599 {
600 set_point_board (temp_prop->data.position, BLACK);
601 }
602 else
603 {
604 set_point_board (temp_prop->data.position, WHITE);
605 }
606 }
607
608 temp_prop = get_prop (temp_prop->next);
609 }
610 }
611
612 if (!retreat_node ())
613 {
614 return -1;
615 }
616
617 if (ko_threat_move)
618 {
619 return 1;
620 }
621
622 return 0;
623}
624
625bool
626redo_node_sgf (void)
627{
628 if (!advance_node ())
629 {
630 return false;
631 }
632
633 set_all_marks_sgf ();
634
635 int temp_move = get_move_sgf ();
636 if (temp_move >= 0)
637 {
638 struct prop_t *move_prop = get_prop (temp_move);
639 unsigned short pos = move_prop->data.position;
640 unsigned char color =
641 move_prop->type == PROP_BLACK_MOVE ? BLACK : WHITE;
642
643 if (color != current_player)
644 {
645 DEBUGF ("redo_node_sgf: wrong color!\n");
646 }
647
648 /* zero out the undo information and set the ko threat flag to the
649 correct value */
650
651 move_prop->data.stone_extra = 0;
652
653 if (ko_pos != INVALID_POS)
654 {
655 move_prop->data.stone_extra |= FLAG_KO_THREAT;
656 }
657
658 ko_pos = INVALID_POS;
659
660 if (pos == PASS_POS)
661 {
662 rb->splashf (HZ / 2, "%s Passes",
663 color == BLACK ? "Black" : "White");
664 }
665 else
666 {
667 int n_cap, s_cap, e_cap, w_cap, self_cap;
668
669 n_cap = s_cap = e_cap = w_cap = self_cap = 0;
670
671 if (get_point_board (pos) == EMPTY)
672 {
673 move_prop->data.stone_extra |= FLAG_ORIG_EMPTY;
674 }
675 else if (get_point_board (pos) == BLACK)
676 {
677 move_prop->data.stone_extra |= FLAG_ORIG_BLACK;
678 }
679 /* else do nothing */
680
681 set_point_board (pos, color);
682
683 /* do captures on the 4 cardinal directions, if the opponent
684 stones are breathless */
685 if (get_point_board (NORTH (pos)) == OTHER (color) &&
686 get_liberties_board (NORTH (pos)) == 0)
687 {
688 n_cap = flood_fill_board (NORTH (pos), EMPTY);
689 move_prop->data.stone_extra |= FLAG_N_CAP;
690 }
691 if (get_point_board (SOUTH (pos)) == OTHER (color) &&
692 get_liberties_board (SOUTH (pos)) == 0)
693 {
694 s_cap = flood_fill_board (SOUTH (pos), EMPTY);
695 move_prop->data.stone_extra |= FLAG_S_CAP;
696 }
697 if (get_point_board (EAST (pos)) == OTHER (color) &&
698 get_liberties_board (EAST (pos)) == 0)
699 {
700 e_cap = flood_fill_board (EAST (pos), EMPTY);
701 move_prop->data.stone_extra |= FLAG_E_CAP;
702 }
703 if (get_point_board (WEST (pos)) == OTHER (color) &&
704 get_liberties_board (WEST (pos)) == 0)
705 {
706 w_cap = flood_fill_board (WEST (pos), EMPTY);
707 move_prop->data.stone_extra |= FLAG_W_CAP;
708 }
709
710 /* then check for suicide */
711 if (get_liberties_board (pos) == 0)
712 {
713 self_cap = flood_fill_board (pos, EMPTY);
714 move_prop->data.stone_extra |= FLAG_SELF_CAP;
715 }
716
717
718 /* now check for a ko, with the following requirements: 1) we
719 captured one opponent stone 2) we placed one stone (not
720 connected to a larger group) 3) we have one liberty */
721
722 if (!self_cap &&
723 (n_cap + s_cap + e_cap + w_cap == 1) &&
724 get_liberties_board (pos) == 1 &&
725 get_point_board (NORTH (pos)) != color &&
726 get_point_board (SOUTH (pos)) != color &&
727 get_point_board (EAST (pos)) != color &&
728 get_point_board (WEST (pos)) != color)
729 {
730 /* We passed all tests, so there is a ko to set. The
731 ko_pos is our single liberty location */
732
733 if (get_point_board (NORTH (pos)) == EMPTY)
734 {
735 ko_pos = NORTH (pos);
736 }
737 else if (get_point_board (SOUTH (pos)) == EMPTY)
738 {
739 ko_pos = SOUTH (pos);
740 }
741 else if (get_point_board (EAST (pos)) == EMPTY)
742 {
743 ko_pos = EAST (pos);
744 }
745 else
746 {
747 ko_pos = WEST (pos);
748 }
749 }
750
751 if (color == BLACK)
752 {
753 black_captures += n_cap + s_cap + e_cap + w_cap;
754 white_captures += self_cap;
755 }
756 else
757 {
758 white_captures += n_cap + s_cap + e_cap + w_cap;
759 black_captures += self_cap;
760 }
761
762 /* this will move the cursor near this move if it was off the
763 screen */
764 move_display (pos);
765 }
766
767 ++move_num;
768 current_player = OTHER (color);
769
770 goto redo_node_sgf_succeeded;
771 }
772 else if (do_add_stones ())
773 {
774 goto redo_node_sgf_succeeded;
775 }
776
777 return false;
778 char comment_buffer[512];
779
780redo_node_sgf_succeeded:
781#if !defined(GBN_TEST)
782 if (auto_show_comments &&
783 read_comment_sgf (comment_buffer, sizeof (comment_buffer)))
784 {
785 unsigned int i;
786 for (i = 0; i < sizeof (comment_buffer); ++i)
787 {
788 /* newlines display badly in rb->splash, so replace them
789 * with spaces
790 */
791 if (comment_buffer[i] == '\n')
792 {
793 comment_buffer[i] = ' ';
794 }
795 else if (comment_buffer[i] == '\0')
796 {
797 break;
798 }
799 }
800 draw_screen_display();
801 rb->splash(HZ / 3, comment_buffer);
802 rb->button_clear_queue();
803 rb->action_userabort(TIMEOUT_BLOCK);
804 }
805#else
806 (void) comment_buffer;
807#endif
808
809 return true;
810}
811
812int
813mark_child_variations_sgf (void)
814{
815 int result;
816 int saved = current_node;
817 struct node_t *node = get_node (current_node);
818
819 int move_handle;
820
821 if (!node)
822 {
823 return 0;
824 }
825
826 current_node = node->next;
827 goto_next_important_node (true);
828
829 result = num_variations_sgf ();
830
831 if (result > 1)
832 {
833 int i;
834 int branch_node = current_node;
835 for (i = 0; i < result; ++i)
836 {
837 go_to_variation_sgf (i);
838 goto_next_important_node (true);
839
840 move_handle = get_move_sgf ();
841
842 if (move_handle >= 0)
843 {
844 set_one_mark (get_prop (move_handle)->data.position,
845 get_prop (move_handle)->type);
846 }
847
848 current_node = branch_node;
849 }
850 }
851
852 current_node = saved;
853
854 return result;
855}
856
857void
858set_all_marks_sgf (void)
859{
860 struct prop_t *prop = get_prop (get_node (current_node)->props);
861
862 while (prop)
863 {
864 if (prop->type == PROP_LABEL)
865 {
866 set_label_mark (prop->data.position, prop->data.label_extra);
867 }
868 else if (prop->type == PROP_COMMENT)
869 {
870 set_comment_display (true);
871 }
872 else
873 {
874 set_one_mark (prop->data.position, prop->type);
875 }
876 prop = get_prop (prop->next);
877 }
878}
879
880static void
881set_one_mark (unsigned short pos, enum prop_type_t type)
882{
883 switch (type)
884 {
885 case PROP_CIRCLE:
886 set_mark_display (pos, 'c');
887 break;
888 case PROP_SQUARE:
889 set_mark_display (pos, 's');
890 break;
891 case PROP_TRIANGLE:
892 set_mark_display (pos, 't');
893 break;
894 case PROP_MARK:
895 set_mark_display (pos, 'm');
896 break;
897 case PROP_DIM:
898 set_mark_display (pos, 'd');
899 break;
900 case PROP_SELECTED:
901 set_mark_display (pos, 'S');
902 break;
903 case PROP_BLACK_MOVE:
904 set_mark_display (pos, 'b');
905 break;
906 case PROP_WHITE_MOVE:
907 set_mark_display (pos, 'w');
908 break;
909 case PROP_INVALID:
910 set_mark_display (pos, ' ');
911 default:
912 break;
913 }
914}
915
916static void
917set_label_mark (unsigned short pos, char to_set)
918{
919 set_mark_display (pos, to_set | (1 << 7));
920}
921
922static bool
923do_add_stones (void)
924{
925 bool ret_val = false;
926 struct prop_t *temp_prop;
927 int temp_handle;
928
929 if (current_node < 0)
930 {
931 return false;
932 }
933
934 temp_handle = get_node (current_node)->props;
935 temp_prop = get_prop (temp_handle);
936
937 while (temp_prop)
938 {
939 if (temp_prop->type == PROP_ADD_BLACK ||
940 temp_prop->type == PROP_ADD_WHITE ||
941 temp_prop->type == PROP_ADD_EMPTY)
942 {
943
944 temp_prop->data.stone_extra = 0;
945
946 /* TODO: we could delete do-nothing PROP_ADD_*s here */
947
948 if (get_point_board (temp_prop->data.position) == EMPTY)
949 {
950 temp_prop->data.stone_extra |= FLAG_ORIG_EMPTY;
951 }
952 else if (get_point_board (temp_prop->data.position) == BLACK)
953 {
954 temp_prop->data.stone_extra |= FLAG_ORIG_BLACK;
955 }
956 /* else, do nothing */
957
958 if (temp_prop->type == PROP_ADD_BLACK ||
959 temp_prop->type == PROP_ADD_WHITE)
960 {
961 set_point_board (temp_prop->data.position,
962 temp_prop->type == PROP_ADD_BLACK ? BLACK :
963 WHITE);
964 ret_val = true;
965 }
966 else
967 {
968 set_point_board (temp_prop->data.position, EMPTY);
969
970 ret_val = true;
971 }
972 }
973
974 temp_handle = temp_prop->next;
975 temp_prop = get_prop (temp_handle);
976 }
977
978 return ret_val;
979}
980
981int
982add_child_sgf (int *variation_number)
983{
984 int node_handle;
985 struct node_t *node;
986 struct node_t *current = get_node (current_node);
987
988 if (current->next < 0)
989 {
990 node_handle = alloc_storage_sgf ();
991 node = get_node (node_handle);
992
993 if (node_handle < 0 || !current)
994 {
995 return NO_NODE;
996 }
997
998 node->prev = current_node;
999 node->next = NO_NODE;
1000 node->props = NO_PROP;
1001
1002 current->next = node_handle;
1003
1004 if (variation_number)
1005 {
1006 *variation_number = 0;
1007 }
1008
1009 return node_handle;
1010 }
1011 else
1012 {
1013 return add_child_variation (variation_number);
1014 }
1015}
1016
1017int
1018add_prop_sgf (int node, enum prop_type_t type, union prop_data_t data)
1019{
1020 int new_prop;
1021 int temp_prop_handle;
1022
1023 if (node < 0)
1024 {
1025 return NO_PROP;
1026 }
1027
1028 new_prop = alloc_storage_sgf ();
1029
1030 if (new_prop < 0)
1031 {
1032 return NO_PROP;
1033 }
1034
1035 get_prop (new_prop)->type = type;
1036 get_prop (new_prop)->data = data;
1037
1038 /* check if the new_prop goes at the start */
1039 if (get_node (node)->props == NO_PROP ||
1040 (type == PROP_VARIATION &&
1041 get_prop (get_node (node)->props)->type != PROP_VARIATION))
1042 {
1043
1044 if (get_node (node)->props >= 0)
1045 {
1046 get_prop (new_prop)->next = get_node (node)->props;
1047 }
1048 else
1049 {
1050 get_prop (new_prop)->next = NO_PROP;
1051 }
1052
1053 get_node (node)->props = new_prop;
1054 return new_prop;
1055 }
1056
1057 temp_prop_handle = get_node (node)->props;
1058
1059 while (1)
1060 {
1061 if (get_prop (temp_prop_handle)->next < 0 ||
1062 (get_prop (temp_prop_handle)->type == type &&
1063 get_prop (get_prop (temp_prop_handle)->next)->type != type))
1064 {
1065 /* new_prop goes after the current one either because we're at
1066 the end of the props list, or because we're adding a prop
1067 after the ones of its same type */
1068 get_prop (new_prop)->next = get_prop (temp_prop_handle)->next;
1069 get_prop (temp_prop_handle)->next = new_prop;
1070
1071 return new_prop;
1072 }
1073
1074 temp_prop_handle = get_prop (temp_prop_handle)->next;
1075 }
1076}
1077
1078int
1079get_prop_pos_sgf (enum prop_type_t type, union prop_data_t data)
1080{
1081 int temp_prop_handle;
1082 struct prop_t *prop;
1083
1084 if (current_node < 0)
1085 {
1086 return -1;
1087 }
1088
1089 temp_prop_handle = get_node (current_node)->props;
1090
1091 while (temp_prop_handle >= 0)
1092 {
1093 prop = get_prop (temp_prop_handle);
1094
1095 if ((type == PROP_ADD_BLACK ||
1096 type == PROP_ADD_WHITE ||
1097 type == PROP_ADD_EMPTY)
1098 &&
1099 (prop->type == PROP_ADD_BLACK ||
1100 prop->type == PROP_ADD_WHITE ||
1101 prop->type == PROP_ADD_EMPTY)
1102 && (prop->data.position == data.position))
1103 {
1104 return temp_prop_handle;
1105 }
1106
1107 if ((type == PROP_CIRCLE ||
1108 type == PROP_SQUARE ||
1109 type == PROP_TRIANGLE ||
1110 type == PROP_MARK ||
1111 type == PROP_DIM ||
1112 type == PROP_SELECTED) &&
1113 (prop->type == PROP_CIRCLE ||
1114 prop->type == PROP_SQUARE ||
1115 prop->type == PROP_TRIANGLE ||
1116 prop->type == PROP_MARK ||
1117 prop->type == PROP_DIM ||
1118 prop->type == PROP_SELECTED) &&
1119 (prop->data.position == data.position))
1120 {
1121 return temp_prop_handle;
1122 }
1123
1124 temp_prop_handle = get_prop (temp_prop_handle)->next;
1125 }
1126
1127 return -1;
1128}
1129
1130int
1131add_or_set_prop_sgf (int node, enum prop_type_t type, union prop_data_t data)
1132{
1133 int temp_prop_handle;
1134
1135 if (node < 0)
1136 {
1137 return NO_PROP;
1138 }
1139
1140 temp_prop_handle = get_prop_sgf (node, type, NULL);
1141
1142 if (temp_prop_handle >= 0)
1143 {
1144 get_prop (temp_prop_handle)->data = data;
1145 return temp_prop_handle;
1146 }
1147
1148 /* there was no prop to set, so we need to add one */
1149 return add_prop_sgf (node, type, data);
1150}
1151
1152int
1153get_prop_sgf (int node, enum prop_type_t type, int *previous_prop)
1154{
1155 int previous_handle = NO_PROP;
1156 int current_handle;
1157
1158 if (node < 0)
1159 {
1160 return NO_PROP;
1161 }
1162
1163 if (get_node (node)->props < 0)
1164 {
1165 return NO_PROP;
1166 }
1167
1168 current_handle = get_node (node)->props;
1169
1170 while (current_handle >= 0)
1171 {
1172 if (get_prop (current_handle)->type == type || type == PROP_ANY)
1173 {
1174 if (previous_prop)
1175 {
1176 *previous_prop = previous_handle;
1177 }
1178
1179 return current_handle;
1180 }
1181 else
1182 {
1183 previous_handle = current_handle;
1184 current_handle = get_prop (current_handle)->next;
1185 }
1186 }
1187
1188 return NO_PROP;
1189}
1190
1191bool
1192delete_prop_sgf (int node, enum prop_type_t type)
1193{
1194 if (node < 0)
1195 {
1196 return false;
1197 }
1198
1199 return delete_prop_handle_sgf (node, get_prop_sgf (node, type, NULL));
1200}
1201
1202bool
1203delete_prop_handle_sgf (int node, int prop)
1204{
1205 int previous;
1206
1207 if (prop < 0 || node < 0 || get_node (node)->props < 0)
1208 {
1209 return false;
1210 }
1211
1212 if (get_node (node)->props == prop)
1213 {
1214 get_node (node)->props = get_prop (get_node (node)->props)->next;
1215 free_storage_sgf (prop);
1216 return true;
1217 }
1218
1219 previous = get_node (node)->props;
1220
1221 while (get_prop (previous)->next != prop && get_prop (previous)->next >= 0)
1222 {
1223 previous = get_prop (previous)->next;
1224 }
1225
1226 if (get_prop (previous)->next < 0)
1227 {
1228 return false;
1229 }
1230 else
1231 {
1232 get_prop (previous)->next = get_prop (get_prop (previous)->next)->next;
1233 free_storage_sgf (prop);
1234 return true;
1235 }
1236}
1237
1238int
1239read_comment_sgf (char *buffer, size_t buffer_size)
1240{
1241 size_t bytes_read = 0;
1242
1243 int prop_handle = get_prop_sgf (current_node, PROP_COMMENT, NULL);
1244
1245 if (prop_handle < 0)
1246 {
1247 return 0;
1248 }
1249
1250 if (unhandled_fd < 0)
1251 {
1252 DEBUGF ("unhandled file is closed?!\n");
1253 return 0;
1254 }
1255
1256 if (rb->lseek (unhandled_fd, get_prop (prop_handle)->data.number,
1257 SEEK_SET) < 0)
1258 {
1259 DEBUGF ("couldn't seek in unhandled_fd\n");
1260 return -1;
1261 }
1262
1263 if (!read_char_no_whitespace (unhandled_fd) == 'C' ||
1264 !read_char_no_whitespace (unhandled_fd) == '[')
1265 {
1266 DEBUGF ("comment prop points to incorrect place in unhandled_fd!!\n");
1267 return -1;
1268 }
1269
1270 /* make output a string, the lazy way */
1271 rb->memset (buffer, 0, buffer_size);
1272 ++bytes_read;
1273
1274 bool done = false;
1275 bool escaped = false;
1276
1277 while ((buffer_size > bytes_read) && !done)
1278 {
1279 int temp = read_char (unhandled_fd);
1280
1281 switch (temp)
1282 {
1283 case ']':
1284 if (!escaped)
1285 {
1286 done = true;
1287 break;
1288 }
1289 *buffer = temp;
1290 ++buffer;
1291 ++bytes_read;
1292 escaped = false;
1293 break;
1294
1295 case -1:
1296 DEBUGF ("encountered end of file before end of comment!\n");
1297 done = true;
1298 break;
1299
1300 case '\\':
1301 escaped = !escaped;
1302 if (escaped)
1303 {
1304 break;
1305 }
1306
1307 default:
1308 *buffer = temp;
1309 ++buffer;
1310 ++bytes_read;
1311 escaped = false;
1312 break;
1313 }
1314 }
1315
1316 return bytes_read;
1317}
1318
1319int
1320write_comment_sgf (char *string)
1321{
1322 char *orig_string = string;
1323
1324 int prop_handle = get_prop_sgf (current_node, PROP_COMMENT, NULL);
1325
1326 int start_of_comment = -1;
1327 bool overwriting = false;
1328
1329 int bytes_written = 0;
1330
1331 set_game_modified();
1332
1333 if (unhandled_fd < 0)
1334 {
1335 DEBUGF ("unhandled file is closed?!\n");
1336 return 0;
1337 }
1338
1339 if (prop_handle >= 0)
1340 {
1341 if ((start_of_comment = rb->lseek (unhandled_fd,
1342 get_prop (prop_handle)->data.number,
1343 SEEK_SET)) < 0)
1344 {
1345 DEBUGF ("couldn't seek in unhandled_fd\n");
1346 return -1;
1347 }
1348 else
1349 {
1350 overwriting = true;
1351 }
1352 }
1353 else
1354 {
1355 overwriting = false;
1356 }
1357
1358 start_of_write_wcs:
1359
1360 if (overwriting)
1361 {
1362 if (!read_char_no_whitespace (unhandled_fd) == 'C' ||
1363 !read_char_no_whitespace (unhandled_fd) == '[')
1364 {
1365 DEBUGF ("non-comment while overwriting!!\n");
1366 return -1;
1367 }
1368 }
1369 else
1370 {
1371 start_of_comment = rb->lseek (unhandled_fd, 0, SEEK_END);
1372
1373 if (start_of_comment < 0)
1374 {
1375 DEBUGF ("error seeking to end in write_comment_sgf\n");
1376 return -1;
1377 }
1378
1379 write_char (unhandled_fd, 'C');
1380 write_char (unhandled_fd, '[');
1381 }
1382
1383
1384 bool overwrite_escaped = false;
1385
1386 while (*string)
1387 {
1388 if (overwriting)
1389 {
1390 int orig_char = peek_char (unhandled_fd);
1391
1392 switch (orig_char)
1393 {
1394 case '\\':
1395 overwrite_escaped = !overwrite_escaped;
1396 break;
1397
1398 case ']':
1399 if (overwrite_escaped)
1400 {
1401 overwrite_escaped = false;
1402 break;
1403 }
1404 /* otherwise, fall through */
1405
1406 case -1:
1407
1408 /* we reached the end of the part we can put our comment
1409 in, but there's more comment to write, so we should
1410 start again, this time making a new comment (the old
1411 becomes wasted space in unhandled_fd, but it doesn't
1412 really hurt anything except extra space on disk */
1413
1414 overwriting = false;
1415 string = orig_string;
1416 bytes_written = 0;
1417 goto start_of_write_wcs;
1418 break;
1419
1420 default:
1421 overwrite_escaped = false;
1422 break;
1423 }
1424 }
1425
1426 switch (*string)
1427 {
1428 case '\\':
1429 case ']':
1430 write_char (unhandled_fd, '\\');
1431
1432 /* fall through */
1433
1434 default:
1435 write_char (unhandled_fd, *string);
1436 break;
1437 }
1438
1439 ++string;
1440 ++bytes_written;
1441 }
1442
1443 /* finish out the record */
1444 write_char (unhandled_fd, ']');
1445 write_char (unhandled_fd, ';');
1446
1447 /* and put the reference into the unhandled_fd into the comment prop */
1448 union prop_data_t temp_data;
1449 temp_data.number = start_of_comment;
1450
1451 add_or_set_prop_sgf (current_node, PROP_COMMENT, temp_data);
1452 set_comment_display (true);
1453 return bytes_written;
1454}
1455
1456bool
1457has_more_nodes_sgf (void)
1458{
1459 int saved = current_node;
1460 bool ret_val = false;
1461
1462 if (current_node < 0)
1463 {
1464 return false;
1465 }
1466
1467 current_node = get_node (current_node)->next;
1468
1469 if (current_node >= 0)
1470 {
1471 /* returns true if it finds an important node */
1472 ret_val = goto_next_important_node (true);
1473 }
1474
1475 current_node = saved;
1476 return ret_val;
1477}
1478
1479/* logic is different here because the first node in a tree is a valid
1480 place to go */
1481bool
1482has_prev_nodes_sgf (void)
1483{
1484 if (current_node < 0)
1485 {
1486 return false;
1487 }
1488
1489 return current_node != start_node;
1490}
1491
1492int
1493get_move_sgf (void)
1494{
1495 int result = -1;
1496 int saved_current = current_node;
1497
1498 goto_next_important_node (true);
1499
1500 result = get_move_from_node (current_node);
1501
1502 current_node = saved_current;
1503 return result;
1504}
1505
1506static int
1507get_move_from_node (int handle)
1508{
1509 int prop_handle;
1510
1511 if (handle < 0)
1512 {
1513 return -2;
1514 }
1515
1516 prop_handle = get_node (handle)->props;
1517
1518
1519 while (prop_handle >= 0)
1520 {
1521 if (get_prop (prop_handle)->type == PROP_BLACK_MOVE ||
1522 get_prop (prop_handle)->type == PROP_WHITE_MOVE)
1523 {
1524 return prop_handle;
1525 }
1526
1527 prop_handle = get_prop (prop_handle)->next;
1528 }
1529
1530 return -1;
1531}
1532
1533static bool
1534retreat_node (void)
1535{
1536 int result = get_node (current_node)->prev;
1537
1538 if (current_node == start_node)
1539 {
1540 return false;
1541 }
1542
1543 if (result < 0)
1544 {
1545 return false;
1546 }
1547 else
1548 {
1549 clear_marks_display ();
1550
1551 current_node = result;
1552
1553 /* go backwards to the next important node (move/add
1554 stone/variation/etc.) */
1555 goto_next_important_node (false);
1556 return true;
1557 }
1558}
1559
1560static bool
1561advance_node (void)
1562{
1563 int result = get_node (current_node)->next;
1564
1565 if (result < 0)
1566 {
1567 return false;
1568 }
1569 else
1570 {
1571 clear_marks_display ();
1572
1573 current_node = result;
1574
1575 /* go forward to the next move/add stone/variation/etc. node */
1576 goto_next_important_node (true);
1577 result = get_prop_sgf (current_node, PROP_VARIATION_CHOICE, NULL);
1578
1579 if (result >= 0)
1580 {
1581 go_to_variation_sgf (get_prop (result)->data.number);
1582 }
1583
1584 return true;
1585 }
1586}
1587
1588int
1589num_variations_sgf (void)
1590{
1591 int result = 1;
1592 struct prop_t *temp_prop;
1593 struct node_t *temp_node = get_node (current_node);
1594
1595 if (temp_node == 0)
1596 {
1597 return 0;
1598 }
1599
1600 if (temp_node->prev >= 0)
1601 {
1602 temp_node = get_node (get_node (temp_node->prev)->next);
1603 }
1604
1605 temp_prop = get_prop (temp_node->props);
1606
1607 while (temp_prop)
1608 {
1609 if (temp_prop->type == PROP_VARIATION)
1610 {
1611 ++result;
1612 }
1613 else
1614 {
1615 /* variations are at the beginning of the prop list */
1616 break;
1617 }
1618
1619 temp_prop = get_prop (temp_prop->next);
1620 }
1621
1622 return result;
1623}
1624
1625bool
1626go_to_variation_sgf (unsigned int num)
1627{
1628 int saved = current_node;
1629 struct node_t *temp_node = get_node (current_node);
1630 struct prop_t *temp_prop;
1631
1632 if (!temp_node)
1633 {
1634 return false;
1635 }
1636
1637 temp_node = get_node (temp_node->prev);
1638
1639 if (!temp_node)
1640 {
1641 return false;
1642 }
1643
1644 temp_node = get_node (current_node = temp_node->next);
1645
1646 if (!temp_node)
1647 {
1648 current_node = saved;
1649 return false;
1650 }
1651
1652 temp_prop = get_prop (temp_node->props);
1653
1654 while (num)
1655 {
1656 if (!temp_prop || temp_prop->type != PROP_VARIATION)
1657 {
1658 current_node = saved;
1659 return false;
1660 }
1661
1662 if (num == 1)
1663 {
1664 current_node = temp_prop->data.node;
1665 break;
1666 }
1667
1668 temp_prop = get_prop (temp_prop->next);
1669 --num;
1670 }
1671
1672 return true;
1673}
1674
1675int
1676get_matching_child_sgf (unsigned short pos, unsigned char color)
1677{
1678 struct node_t *temp_node;
1679 struct prop_t *temp_prop;
1680 int variation_count = 0;
1681 int saved;
1682
1683 /* set true later in a loop if we want a prop in the first variation
1684 which means that we shouldn't check that branch for children */
1685 bool dont_check_first_var_children = false;
1686
1687 temp_node = get_node (current_node);
1688
1689 if (!temp_node)
1690 {
1691 return -3;
1692 }
1693
1694 temp_node = get_node (temp_node->next);
1695
1696 if (!temp_node)
1697 {
1698 return -2;
1699 }
1700
1701 temp_prop = get_prop (temp_node->props);
1702
1703 while (temp_prop)
1704 {
1705 if (temp_prop->type == PROP_VARIATION)
1706 {
1707 ++variation_count;
1708 saved = current_node;
1709 current_node = temp_prop->data.node;
1710
1711 struct prop_t *temp_move = get_prop (get_move_sgf ());
1712
1713 current_node = saved;
1714
1715 if (temp_move &&
1716 temp_move->data.position == pos &&
1717 ((color == BLACK && temp_move->type == PROP_BLACK_MOVE) ||
1718 (color == WHITE && temp_move->type == PROP_WHITE_MOVE)))
1719 {
1720 return variation_count;
1721 }
1722 }
1723 else if ((temp_prop->type == PROP_BLACK_MOVE && color == BLACK) ||
1724 (temp_prop->type == PROP_WHITE_MOVE && color == WHITE))
1725 {
1726 if (temp_prop->data.position == pos)
1727 {
1728 return 0;
1729 }
1730 else
1731 {
1732 return -4;
1733 }
1734 }
1735 else if (temp_prop->type == PROP_ADD_WHITE ||
1736 temp_prop->type == PROP_ADD_BLACK ||
1737 temp_prop->type == PROP_ADD_EMPTY)
1738 {
1739 dont_check_first_var_children = true;
1740 }
1741
1742 temp_prop = get_prop (temp_prop->next);
1743 }
1744
1745 if (dont_check_first_var_children)
1746 {
1747 return -1;
1748 }
1749 else
1750 {
1751 saved = current_node;
1752 current_node = temp_node->next;
1753
1754 struct prop_t *temp_move = get_prop (get_move_sgf ());
1755 if (temp_move &&
1756 pos == temp_move->data.position &&
1757 color == (temp_move->type == PROP_BLACK_MOVE ? BLACK : WHITE))
1758 {
1759 current_node = saved;
1760 return 0;
1761 }
1762
1763 current_node = saved;
1764 return -1;
1765 }
1766}
1767
1768int
1769next_variation_sgf (void)
1770{
1771 int saved = current_node;
1772 int saved_start = start_node;
1773 union prop_data_t temp_data;
1774 int prop_handle;
1775 int num_vars = 0;
1776
1777 if (current_node < 0 || get_node (current_node)->prev < 0)
1778 {
1779 return -1;
1780 }
1781
1782 start_node = NO_NODE;
1783
1784
1785
1786 if (num_variations_sgf () < 2)
1787 {
1788
1789 current_node = saved;
1790 start_node = saved_start;
1791 return -2;
1792 }
1793
1794 /* now we're at a branch node which we should go to the next variation
1795 (we were at the "chosen" one, so go to the next one after that,
1796 (mod the number of variations)) */
1797
1798 int chosen = 0;
1799 int branch_node = get_node (get_node (current_node)->prev)->next;
1800
1801 prop_handle = get_prop_sgf (branch_node, PROP_VARIATION_CHOICE, NULL);
1802
1803 if (prop_handle >= 0)
1804 {
1805 chosen = get_prop (prop_handle)->data.number;
1806 }
1807
1808 ++chosen;
1809
1810 if (chosen >= (num_vars = num_variations_sgf ()))
1811 {
1812 chosen = 0;
1813 }
1814
1815 temp_data.number = chosen;
1816 add_or_set_prop_sgf (branch_node, PROP_VARIATION_CHOICE, temp_data);
1817
1818 if (!undo_node_sgf ())
1819 {
1820 current_node = saved;
1821 start_node = saved_start;
1822 return -3;
1823 }
1824
1825 if (redo_node_sgf ())
1826 {
1827 start_node = saved_start;
1828 return chosen + 1;
1829 }
1830 else
1831 {
1832 current_node = saved;
1833 start_node = saved_start;
1834 return -4;
1835 }
1836}
1837
1838int
1839add_child_variation (int *variation_number)
1840{
1841 struct node_t *temp_node = get_node (current_node);
1842 struct prop_t *temp_prop;
1843 int temp_prop_handle;
1844 int new_node = alloc_storage_sgf ();
1845 int new_prop = alloc_storage_sgf ();
1846 int temp_variation_number;
1847
1848 if (new_node < 0 || new_prop < 0)
1849 {
1850 if (new_node >= 0)
1851 {
1852 free_storage_sgf (new_node);
1853 }
1854 if (new_prop >= 0)
1855 {
1856 free_storage_sgf (new_prop);
1857 }
1858
1859 return NO_NODE;
1860 }
1861
1862 if (!temp_node)
1863 {
1864 free_storage_sgf (new_node);
1865 free_storage_sgf (new_prop);
1866
1867 return NO_NODE;
1868 }
1869
1870 temp_node = get_node (temp_node->next);
1871
1872 if (!temp_node)
1873 {
1874 free_storage_sgf (new_node);
1875 free_storage_sgf (new_prop);
1876
1877 return NO_NODE;
1878 }
1879
1880 get_node (new_node)->prev = current_node;
1881 get_node (new_node)->next = NO_NODE;
1882 get_node (new_node)->props = NO_PROP;
1883
1884 get_prop (new_prop)->type = PROP_VARIATION;
1885 get_prop (new_prop)->next = NO_PROP;
1886 get_prop (new_prop)->data.node = new_node;
1887
1888 temp_prop_handle = temp_node->props;
1889
1890 if (temp_prop_handle < 0)
1891 {
1892 temp_node->props = new_prop;
1893
1894 if (variation_number)
1895 {
1896 *variation_number = 1;
1897 }
1898
1899 return new_node;
1900 }
1901
1902 if (get_prop (temp_prop_handle)->type != PROP_VARIATION)
1903 {
1904 get_prop (new_prop)->next = temp_node->props;
1905 temp_node->props = new_prop;
1906
1907 if (variation_number)
1908 {
1909 *variation_number = 1;
1910 }
1911
1912 return new_node;
1913 }
1914
1915 /* the lowest it can be, since 1 isn't it */
1916 temp_variation_number = 2;
1917
1918 while (1)
1919 {
1920 temp_prop = get_prop (temp_prop_handle);
1921 if (temp_prop->next < 0 ||
1922 get_prop (temp_prop->next)->type != PROP_VARIATION)
1923 {
1924 get_prop (new_prop)->next = temp_prop->next;
1925 temp_prop->next = new_prop;
1926
1927 if (variation_number)
1928 {
1929 *variation_number = temp_variation_number;
1930 }
1931
1932 return new_node;
1933 }
1934
1935 ++temp_variation_number;
1936 temp_prop_handle = temp_prop->next;
1937 }
1938}
1939
1940static bool
1941is_important_node (int handle)
1942{
1943 struct prop_t *temp_prop;
1944
1945 if (handle < 0)
1946 {
1947 return false;
1948 }
1949
1950 if (handle == start_node)
1951 {
1952 return true;
1953 }
1954
1955 if (get_node (handle)->prev < 0)
1956 {
1957 return true;
1958 }
1959
1960 temp_prop = get_prop (get_node (handle)->props);
1961
1962 while (temp_prop)
1963 {
1964 if (temp_prop->type == PROP_BLACK_MOVE ||
1965 temp_prop->type == PROP_WHITE_MOVE ||
1966 temp_prop->type == PROP_ADD_BLACK ||
1967 temp_prop->type == PROP_ADD_WHITE ||
1968 temp_prop->type == PROP_ADD_EMPTY ||
1969 temp_prop->type == PROP_VARIATION)
1970 {
1971 return true;
1972 }
1973
1974 temp_prop = get_prop (temp_prop->next);
1975 }
1976
1977 return false;
1978}
1979
1980static bool
1981goto_next_important_node (bool forward)
1982{
1983 int temp_node = current_node;
1984 int last_good = temp_node;
1985
1986 while (temp_node >= 0 && !is_important_node (temp_node))
1987 {
1988 last_good = temp_node;
1989
1990 temp_node = forward ? get_node (temp_node)->next :
1991 get_node (temp_node)->prev;
1992
1993 }
1994
1995 if (temp_node < 0)
1996 {
1997 current_node = last_good;
1998 }
1999 else
2000 {
2001 current_node = temp_node;
2002 }
2003
2004 if (temp_node < 0)
2005 {
2006 return false;
2007 }
2008 else
2009 {
2010 return true;
2011 }
2012}
2013
2014bool
2015is_handled_sgf (enum prop_type_t type)
2016{
2017 if (type == PROP_BLACK_MOVE ||
2018 type == PROP_WHITE_MOVE ||
2019 type == PROP_ADD_BLACK ||
2020 type == PROP_ADD_WHITE ||
2021 type == PROP_ADD_EMPTY ||
2022 type == PROP_CIRCLE || type == PROP_SQUARE || type == PROP_TRIANGLE ||
2023#if 0
2024 /* these marks are stupid and nobody uses them. if we could find
2025 a good way to draw them we could do them anyway, but no reason
2026 to unless it's easy */
2027 type == PROP_DIM || type == PROP_SELECTED ||
2028#endif
2029 type == PROP_COMMENT ||
2030 type == PROP_MARK ||
2031 type == PROP_LABEL ||
2032 type == PROP_GAME ||
2033 type == PROP_FILE_FORMAT ||
2034 type == PROP_APPLICATION ||
2035 type == PROP_CHARSET ||
2036 type == PROP_SIZE ||
2037 type == PROP_KOMI ||
2038 type == PROP_BLACK_NAME ||
2039 type == PROP_WHITE_NAME ||
2040 type == PROP_BLACK_RANK ||
2041 type == PROP_WHITE_RANK ||
2042 type == PROP_BLACK_TEAM ||
2043 type == PROP_WHITE_TEAM ||
2044 type == PROP_DATE ||
2045 type == PROP_ROUND ||
2046 type == PROP_EVENT ||
2047 type == PROP_PLACE ||
2048 type == PROP_OVERTIME ||
2049 type == PROP_RESULT ||
2050 type == PROP_TIME_LIMIT ||
2051 type == PROP_RULESET ||
2052 type == PROP_HANDICAP || type == PROP_VARIATION_TYPE)
2053 {
2054 return true;
2055 }
2056
2057 return false;
2058}
2059
2060void
2061setup_handicap_sgf (void)
2062{
2063 union prop_data_t temp_data;
2064
2065 if (header.handicap <= 1)
2066 {
2067 return;
2068 }
2069
2070 current_node = start_node;
2071
2072 temp_data.number = header.handicap;
2073 add_prop_sgf (current_node, PROP_HANDICAP, temp_data);
2074
2075 /* now, add the actual stones */
2076
2077 if ((board_width != 19 && board_width != 13 && board_width != 9) ||
2078 board_width != board_height || header.handicap > 9)
2079 {
2080 rb->splashf (5 * HZ,
2081 "Use the 'Add Black' tool to add %d handicap stones!",
2082 header.handicap);
2083 return;
2084 }
2085
2086 int handicaps_to_place = header.handicap;
2087
2088 int low_coord = 0, mid_coord = 0, high_coord = 0;
2089
2090 if (board_width == 19)
2091 {
2092 low_coord = 3;
2093 mid_coord = 9;
2094 high_coord = 15;
2095 }
2096 else if (board_width == 13)
2097 {
2098 low_coord = 3;
2099 mid_coord = 6;
2100 high_coord = 9;
2101 }
2102 else if (board_width == 9)
2103 {
2104 low_coord = 2;
2105 mid_coord = 4;
2106 high_coord = 6;
2107 }
2108
2109 /* first four go in the corners */
2110 handicaps_to_place -= 2;
2111 setup_handicap_helper (POS (high_coord, low_coord));
2112 setup_handicap_helper (POS (low_coord, high_coord));
2113
2114 if (!handicaps_to_place)
2115 {
2116 goto done_adding_stones;
2117 }
2118
2119 --handicaps_to_place;
2120 setup_handicap_helper (POS (high_coord, high_coord));
2121
2122 if (!handicaps_to_place)
2123 {
2124 goto done_adding_stones;
2125 }
2126
2127 --handicaps_to_place;
2128 setup_handicap_helper (POS (low_coord, low_coord));
2129
2130 if (!handicaps_to_place)
2131 {
2132 goto done_adding_stones;
2133 }
2134
2135 /* now done with first four, if only one left it goes in the center */
2136 if (handicaps_to_place == 1)
2137 {
2138 --handicaps_to_place;
2139 setup_handicap_helper (POS (mid_coord, mid_coord));
2140 }
2141 else
2142 {
2143 handicaps_to_place -= 2;
2144 setup_handicap_helper (POS (high_coord, mid_coord));
2145 setup_handicap_helper (POS (low_coord, mid_coord));
2146 }
2147
2148 if (!handicaps_to_place)
2149 {
2150 goto done_adding_stones;
2151 }
2152
2153 /* done with first 6 */
2154
2155 if (handicaps_to_place == 1)
2156 {
2157 --handicaps_to_place;
2158 setup_handicap_helper (POS (mid_coord, mid_coord));
2159 }
2160 else
2161 {
2162 handicaps_to_place -= 2;
2163 setup_handicap_helper (POS (mid_coord, high_coord));
2164 setup_handicap_helper (POS (mid_coord, low_coord));
2165 }
2166
2167 if (!handicaps_to_place)
2168 {
2169 goto done_adding_stones;
2170 }
2171
2172 /* done with first eight, there can only be the tengen remaining */
2173
2174 setup_handicap_helper (POS (mid_coord, mid_coord));
2175
2176 done_adding_stones:
2177 goto_handicap_start_sgf ();
2178 return;
2179}
2180
2181static void
2182setup_handicap_helper (unsigned short pos)
2183{
2184 union prop_data_t temp_data;
2185
2186 temp_data.position = pos;
2187
2188 add_prop_sgf (current_node, PROP_ADD_BLACK, temp_data);
2189}
2190
2191void
2192goto_handicap_start_sgf (void)
2193{
2194 if (start_node != tree_head)
2195 {
2196 current_node = get_node (start_node)->prev;
2197 redo_node_sgf ();
2198 }
2199}
2200
2201bool
2202post_game_setup_sgf (void)
2203{
2204 int temp_handle = alloc_storage_sgf ();
2205 int saved = current_node;
2206
2207 if (temp_handle < 0)
2208 {
2209 return false;
2210 }
2211
2212 union prop_data_t temp_data;
2213 temp_data.number = 0; /* meaningless */
2214
2215 if (!header_marked)
2216 {
2217 add_prop_sgf (tree_head, PROP_ROOT_PROPS, temp_data);
2218 header_marked = true;
2219 }
2220
2221 get_node (temp_handle)->next = current_node;
2222 get_node (temp_handle)->prev = NO_NODE;
2223 get_node (temp_handle)->props = NO_PROP;
2224
2225 current_node = temp_handle;
2226
2227 redo_node_sgf ();
2228
2229 if (current_node == temp_handle)
2230 {
2231 current_node = saved;
2232 }
2233
2234 free_storage_sgf (temp_handle);
2235
2236 return true;
2237}
diff --git a/apps/plugins/goban/sgf.h b/apps/plugins/goban/sgf.h
new file mode 100644
index 0000000000..d2aca81ebb
--- /dev/null
+++ b/apps/plugins/goban/sgf.h
@@ -0,0 +1,170 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#ifndef GOBAN_SGF_H
23#define GOBAN_SGF_H
24
25#include "types.h"
26
27/* Play one move. If play_mode is not PLAY_MODE_FORCE, this will fail if
28 an illegal move is played (plays by the "wrong" color are not counted
29 as illegal. pos may be a POS() on the board, or PASS_POS. Returns true
30 if the move was successfully played. */
31bool play_move_sgf (unsigned short pos, unsigned char color);
32
33/* Add a stone to the board, or remove one if color == EMPTY. returns true
34 if the stone is successfully added/removed (will return false if there
35 is nothing to add/remove there, eg. when adding a black stone when a
36 black stone is already there) */
37bool add_stone_sgf (unsigned short pos, unsigned char color);
38
39/* Add a mark to the board at the current node pos must be on the board
40 Returns false on failure. */
41bool add_mark_sgf (unsigned short pos, enum prop_type_t type);
42
43/* Returns true if there are more nodes before or after (respectively) the
44 current node */
45bool has_prev_nodes_sgf (void);
46bool has_more_nodes_sgf (void);
47
48/* When at the start of a branch, this will follow one of the branches.
49 Variations are numbered from 0. Returns false on failure. */
50bool go_to_variation_sgf (unsigned int num);
51
52/* Get the number of variations at the current move (will be 1 unless the
53 current node is a branch node */
54int num_variations_sgf (void);
55
56/* Calls into the display subsystem to mark any child variations of the
57 current node. Returns the number of variations marked */
58int mark_child_variations_sgf (void);
59
60/* Calls into the display subsystem to pass marks to be drawn on the board
61 for the current node. Does not do child variation marks. */
62void set_all_marks_sgf (void);
63
64/* Add a child regardless of if there is a child node already or not.
65 *variation_number will be set to the variation number of the added
66 variation Returns the handle of the new node. */
67int add_child_sgf (int *variation_number);
68
69/* Goes to the next variation after the current one if the current node is
70 a branch node. Returns the number of the new variation */
71int next_variation_sgf (void);
72
73/* ints in these are handles to storage locations, bools mean failure if
74 false */
75int add_prop_sgf (int node, enum prop_type_t type, union prop_data_t data);
76bool delete_prop_sgf (int node, enum prop_type_t type);
77bool delete_prop_handle_sgf (int node, int prop);
78int get_prop_sgf (int node, enum prop_type_t type, int *previous_prop);
79
80/* If there is already a property of the same type, it will be
81 overwritten, otherwise a new one is added Returns the handle of the
82 added property. */
83int add_or_set_prop_sgf (int node, enum prop_type_t type,
84 union prop_data_t data);
85
86/* Find a property of similar type with the same position in the current
87 node. (for example, if type == PROP_ADD_BLACK and pos == POS(0, 0), it
88 will find a prop with type == PROP_ADD_WHITE and pos == POS(0, 0), but
89 not any of the mark types with pos == POS(0, 0). returns the handle of
90 the found property */
91int get_prop_pos_sgf (enum prop_type_t type, union prop_data_t data);
92
93/* If there is a move in the current node, return its handle. */
94int get_move_sgf (void);
95
96/* If there is a comment in the current node, this will read it out into
97 the buffer. Returns the size of the comment read (including the '\0').
98 The buffer can be treated as a string. */
99int read_comment_sgf (char *buffer, size_t buffer_size);
100
101/* Write a comment property to the current node. This will overwrite any
102 comment that currently exists. Returns the number of characters
103 written. */
104int write_comment_sgf (char *string);
105
106/* Move forward or back in the SGF tree, following any chosen variations
107 (variations are "chosen" every time you go through one) These will
108 update the board showing any moves/added or removed stones/ marks/etc.
109 . */
110bool undo_node_sgf (void);
111bool redo_node_sgf (void);
112
113/* Returns true if the SGF property type is handled in some way. For
114 * real SGF properties (in other words, ones that can actually be read from
115 * a file, not psuedo-properties), if they are unhandled that just means that
116 * we copy them verbatim from the old file to the new, keeping them with the
117 * correct node
118 */
119bool is_handled_sgf (enum prop_type_t type);
120
121/* Sets up the handicap on the board (based on header.handicap) */
122void setup_handicap_sgf (void);
123
124/* Goes to the start of a handicap game. */
125void goto_handicap_start_sgf (void);
126
127/* Must be called after setting up a new game, either blank or loaded from
128 a file. (Picks a place to put the header properties if none was found,
129 and may do other stuff) */
130bool post_game_setup_sgf (void);
131
132/* Get the child that matches the given move.
133 *
134 * Returns the variation number of the matching move, or negative if
135 * none is found.
136 */
137int get_matching_child_sgf (unsigned short pos, unsigned char color);
138
139#define NO_NODE (-1)
140#define NO_PROP (-1)
141
142/* These flags are used in undo handling for moves and added/removed
143 stones */
144#define FLAG_ORIG_EMPTY ((uint8_t) (1 << 7))
145#define FLAG_ORIG_BLACK ((uint8_t) (1 << 6))
146#define FLAG_KO_THREAT ((uint8_t) (1 << 5))
147#define FLAG_SELF_CAP ((uint8_t) (1 << 4))
148#define FLAG_W_CAP ((uint8_t) (1 << 3))
149#define FLAG_E_CAP ((uint8_t) (1 << 2))
150#define FLAG_S_CAP ((uint8_t) (1 << 1))
151#define FLAG_N_CAP ((uint8_t) (1))
152
153#define MIN_STORAGE_BUFFER_SIZE 200
154#define UNHANDLED_PROP_LIST_FILE (PLUGIN_GAMES_DIR "/gbn_misc.bin")
155
156/* Handle of the current node, the start of the game, and the root
157 * of the tree
158 */
159extern int current_node;
160extern int tree_head;
161extern int start_node;
162
163extern int sgf_fd;
164extern int unhandled_fd;
165
166/* true if the header location has already been marked in the current
167 game, false otherwise */
168extern bool header_marked;
169
170#endif
diff --git a/apps/plugins/goban/sgf_output.c b/apps/plugins/goban/sgf_output.c
new file mode 100644
index 0000000000..e798dcd510
--- /dev/null
+++ b/apps/plugins/goban/sgf_output.c
@@ -0,0 +1,433 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "goban.h"
23#include "sgf_output.h"
24#include "sgf.h"
25#include "sgf_storage.h"
26#include "util.h"
27#include "board.h"
28#include "game.h"
29
30static void pos_to_sgf (unsigned short pos, char *buffer);
31
32static void output_prop (int prop_handle);
33static void output_all_props (void);
34static bool output_current_node (void);
35static void output_gametree (void);
36static void output_header_props (void);
37static bool output_header_helper (enum prop_type_t type);
38static int stupid_num_variations (void);
39
40bool
41output_sgf (const char *filename)
42{
43 int current = -1;
44 union prop_data_t temp_data;
45 int saved = current_node;
46
47 sgf_fd = create_or_open_file (filename);
48
49 if (sgf_fd < 0)
50 {
51 return false;
52 }
53
54 DEBUGF ("outputting to: %s (%d)\n", filename, sgf_fd);
55
56 empty_stack (&parse_stack);
57
58 rb->lseek (sgf_fd, 0, SEEK_SET);
59 rb->ftruncate (sgf_fd, 0);
60
61 if (sgf_fd < 0)
62 {
63 return false;
64 }
65
66 if (tree_head < 0)
67 {
68 close_file (&sgf_fd);
69 return false;
70 }
71
72 push_int_stack (&parse_stack, tree_head);
73
74 while (pop_int_stack (&parse_stack, &current))
75 {
76 int var_to_process = 0;
77 int temp_prop =
78 get_prop_sgf (current, PROP_VARIATION_TO_PROCESS, NULL);
79
80 if (temp_prop >= 0)
81 {
82 var_to_process = get_prop (temp_prop)->data.number;
83 }
84
85 current_node = current;
86
87 if (var_to_process > 0)
88 {
89 write_char (sgf_fd, ')');
90 }
91
92 if (var_to_process == stupid_num_variations ())
93 {
94 delete_prop_sgf (current, PROP_VARIATION_TO_PROCESS);
95
96 continue;
97 }
98 else
99 {
100 write_char (sgf_fd, '\n');
101 write_char (sgf_fd, '(');
102
103 /* we need to do more processing on this branchpoint, either
104 to do more variations or to output the ')' */
105 push_int_stack (&parse_stack, current);
106
107 /* increment the stored variation to process */
108 temp_data.number = var_to_process + 1;
109 add_or_set_prop_sgf (current,
110 PROP_VARIATION_TO_PROCESS, temp_data);
111 }
112
113 rb->yield ();
114
115 /* now we did the setup for sibling varaitions to be processed so
116 do the actual outputting of a game tree branch */
117
118 go_to_variation_sgf (var_to_process);
119 output_gametree ();
120 }
121
122 current_node = saved;
123 close_file (&sgf_fd);
124 DEBUGF ("done outputting, file closed\n");
125 return true;
126}
127
128static void
129output_header_props (void)
130{
131 char buffer[128];
132
133 rb->strncpy (buffer, "GM[1]FF[4]CA[UTF-8]AP[Rockbox Goban:1.0]ST[2]\n\n",
134 sizeof (buffer));
135 write_file (sgf_fd, buffer, rb->strlen (buffer));
136
137 /* board size */
138 if (board_width != board_height)
139 {
140 rb->snprintf (buffer, sizeof (buffer), "%s[%d:%d]",
141 prop_names[PROP_SIZE], board_width, board_height);
142 }
143 else
144 {
145 rb->snprintf (buffer, sizeof (buffer), "%s[%d]",
146 prop_names[PROP_SIZE], board_width);
147 }
148
149 write_file (sgf_fd, buffer, rb->strlen (buffer));
150
151 rb->snprintf (buffer, sizeof (buffer), "%s[", prop_names[PROP_KOMI]);
152 write_file (sgf_fd, buffer, rb->strlen (buffer));
153
154 snprint_fixed (buffer, sizeof (buffer), header.komi);
155 write_file (sgf_fd, buffer, rb->strlen (buffer));
156
157 write_char (sgf_fd, ']');
158
159 output_header_helper (PROP_RULESET);
160 output_header_helper (PROP_RESULT);
161
162 output_header_helper (PROP_BLACK_NAME);
163 output_header_helper (PROP_WHITE_NAME);
164 output_header_helper (PROP_BLACK_RANK);
165 output_header_helper (PROP_WHITE_RANK);
166 output_header_helper (PROP_BLACK_TEAM);
167 output_header_helper (PROP_WHITE_TEAM);
168
169 output_header_helper (PROP_EVENT);
170 output_header_helper (PROP_PLACE);
171 output_header_helper (PROP_DATE);
172
173 if (output_header_helper (PROP_OVERTIME) || header.time_limit != 0)
174 {
175 rb->snprintf (buffer, sizeof (buffer), "%s[%d]",
176 prop_names[PROP_TIME_LIMIT], header.time_limit);
177 write_file (sgf_fd, buffer, rb->strlen (buffer));
178 }
179
180 write_char (sgf_fd, '\n');
181 write_char (sgf_fd, '\n');
182}
183
184static bool
185output_header_helper (enum prop_type_t type)
186{
187 char *buffer;
188 int size;
189 char temp_buffer[16];
190
191 if (!get_header_string_and_size (&header, type, &buffer, &size))
192 {
193 DEBUGF ("output_header_helper called with invalid prop type!!\n");
194 return false;
195 }
196
197 if (rb->strlen (buffer))
198 {
199 rb->snprintf (temp_buffer, sizeof (temp_buffer), "%s[",
200 prop_names[type]);
201
202 write_file (sgf_fd, temp_buffer, rb->strlen (temp_buffer));
203
204 write_file (sgf_fd, buffer, rb->strlen (buffer));
205
206 rb->strcpy (temp_buffer, "]");
207
208 write_file (sgf_fd, temp_buffer, rb->strlen (temp_buffer));
209
210 return true;
211 }
212
213 return false;
214}
215
216bool first_node_in_tree = true;
217static void
218output_gametree (void)
219{
220 first_node_in_tree = true;
221
222 while (output_current_node ())
223 {
224 current_node = get_node (current_node)->next;
225 }
226
227}
228
229static bool
230output_current_node (void)
231{
232 if (current_node < 0)
233 {
234 return false;
235 }
236
237 if (stupid_num_variations () > 1 &&
238 get_prop_sgf (current_node, PROP_VARIATION_TO_PROCESS, NULL) < 0)
239 {
240 /* push it up for the gametree stuff to take care of it and fail
241 out, stopping the node printing */
242 push_int_stack (&parse_stack, current_node);
243 return false;
244 }
245
246 if (first_node_in_tree)
247 {
248 first_node_in_tree = false;
249 }
250 else
251 {
252 write_char (sgf_fd, '\n');
253 }
254 write_char (sgf_fd, ';');
255
256 output_all_props ();
257
258 return true;
259}
260
261enum prop_type_t last_output_type = PROP_INVALID;
262static void
263output_all_props (void)
264{
265 int temp_handle = get_node (current_node)->props;
266
267 last_output_type = PROP_INVALID;
268
269 while (temp_handle >= 0)
270 {
271 output_prop (temp_handle);
272 temp_handle = get_prop (temp_handle)->next;
273 }
274}
275
276static void
277output_prop (int prop_handle)
278{
279 char buffer[16];
280 enum prop_type_t temp_type = get_prop (prop_handle)->type;
281
282 buffer[0] = 't';
283 buffer[1] = 't';
284
285 if (is_handled_sgf (temp_type) && temp_type != PROP_COMMENT)
286 {
287 if (temp_type != last_output_type)
288 {
289 write_file (sgf_fd, prop_names[temp_type],
290 PROP_NAME_LEN (temp_type));
291 }
292
293 write_char (sgf_fd, '[');
294
295 if (temp_type == PROP_HANDICAP)
296 {
297 rb->snprintf (buffer, sizeof (buffer), "%d",
298 get_prop (prop_handle)->data.number);
299 write_file (sgf_fd, buffer, rb->strlen (buffer));
300 }
301 else if (temp_type == PROP_LABEL)
302 {
303 pos_to_sgf (get_prop (prop_handle)->data.position, buffer);
304 buffer[2] = '\0';
305
306 rb->snprintf (&buffer[2], sizeof (buffer) - 2, ":%c",
307 get_prop (prop_handle)->data.label_extra);
308
309 write_file (sgf_fd, buffer, rb->strlen (buffer));
310 }
311 else
312 {
313 pos_to_sgf (get_prop (prop_handle)->data.position, buffer);
314
315 write_file (sgf_fd, buffer, 2);
316 }
317
318 write_char (sgf_fd, ']');
319 }
320 else if (temp_type == PROP_ROOT_PROPS)
321 {
322 output_header_props ();
323 }
324 else if (temp_type == PROP_GENERIC_UNHANDLED || temp_type == PROP_COMMENT)
325 {
326 bool escaped = false;
327 bool in_prop_value = false;
328 int temp;
329 bool done = false;
330
331 rb->lseek (unhandled_fd, get_prop (prop_handle)->data.number,
332 SEEK_SET);
333
334 while (!done)
335 {
336 temp = peek_char (unhandled_fd);
337
338 switch (temp)
339 {
340 case ';':
341 escaped = false;
342 if (in_prop_value)
343 {
344 break;
345 }
346 /* otherwise, fall through */
347 case -1:
348 done = true;
349 break;
350
351 case '\\':
352 escaped = !escaped;
353 break;
354
355 case '[':
356 escaped = false;
357 in_prop_value = true;
358 break;
359
360 case ']':
361 if (!escaped)
362 {
363 in_prop_value = false;
364 }
365 escaped = false;
366 break;
367
368 default:
369 escaped = false;
370 break;
371 };
372
373 if (!done)
374 {
375 write_char (sgf_fd, temp);
376 read_char (unhandled_fd);
377 }
378 }
379 }
380
381 last_output_type = temp_type;
382}
383
384static void
385pos_to_sgf (unsigned short pos, char *buffer)
386{
387 if (pos == PASS_POS)
388 {
389 /* "tt" is a pass per SGF specification */
390 buffer[0] = buffer[1] = 't';
391 }
392 else if (pos != INVALID_POS)
393 {
394 buffer[0] = 'a' + I (pos);
395 buffer[1] = 'a' + J (pos);
396 }
397 else
398 {
399 DEBUGF ("invalid pos converted to SGF\n");
400 }
401}
402
403static int
404stupid_num_variations (void)
405{
406 int result = 1;
407 struct prop_t *temp_prop;
408 struct node_t *temp_node = get_node (current_node);
409
410 if (temp_node == 0)
411 {
412 return 0;
413 }
414
415 temp_prop = get_prop (temp_node->props);
416
417 while (temp_prop)
418 {
419 if (temp_prop->type == PROP_VARIATION)
420 {
421 ++result;
422 }
423 else
424 {
425 // variations are at the beginning of the prop list
426 break;
427 }
428
429 temp_prop = get_prop (temp_prop->next);
430 }
431
432 return result;
433}
diff --git a/apps/plugins/goban/sgf_output.h b/apps/plugins/goban/sgf_output.h
new file mode 100644
index 0000000000..dfc6319797
--- /dev/null
+++ b/apps/plugins/goban/sgf_output.h
@@ -0,0 +1,30 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#ifndef GOBAN_SGF_OUTPUT_H
23#define GOBAN_SGF_OUTPUT_H
24
25#include "types.h"
26
27/* Do the actual outputting of an SGF file. Return false on failure */
28bool output_sgf (const char *filename);
29
30#endif
diff --git a/apps/plugins/goban/sgf_parse.c b/apps/plugins/goban/sgf_parse.c
new file mode 100644
index 0000000000..e0fa8fd2df
--- /dev/null
+++ b/apps/plugins/goban/sgf_parse.c
@@ -0,0 +1,857 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "goban.h"
23#include "sgf_parse.h"
24#include "sgf.h"
25#include "sgf_storage.h"
26#include "util.h"
27#include "board.h"
28#include "game.h"
29
30static void handle_prop_value (enum prop_type_t type);
31static int read_prop_value (char *buffer, size_t buffer_size);
32static void do_range (enum prop_type_t type, unsigned short ul,
33 unsigned short br);
34static void parse_prop (void);
35static void parse_node (void);
36static enum prop_type_t parse_prop_type (void);
37
38static unsigned short sgf_to_pos (char *buffer);
39
40bool
41parse_sgf (const char *filename)
42{
43 int saved = current_node;
44
45 /* for parsing */
46 int first_handle = 0; /* first node in the branch */
47 int file_position = 0;
48
49 int temp;
50
51 close_file (&sgf_fd);
52
53 sgf_fd = rb->open (filename, O_RDONLY);
54
55 if (sgf_fd < 0)
56 {
57 return false;
58 }
59
60 current_node = start_node;
61
62 if (current_node < 0)
63 {
64 current_node = saved;
65 return false;
66 }
67
68 empty_stack (&parse_stack);
69
70 /* seek to the first '(' */
71 while (peek_char_no_whitespace (sgf_fd) != '(')
72 {
73 if (read_char_no_whitespace (sgf_fd) == -1)
74 {
75 DEBUGF ("end of file or error before we found a '('\n");
76 current_node = saved;
77 return false;
78 }
79 }
80
81 push_int_stack (&parse_stack, rb->lseek (sgf_fd, 0, SEEK_CUR));
82 push_int_stack (&parse_stack, current_node);
83
84 while (pop_int_stack (&parse_stack, &first_handle) &&
85 pop_int_stack (&parse_stack, &file_position))
86 {
87 /* DEBUGF("poped off %d\n", file_position); */
88
89 rb->yield ();
90
91 current_node = first_handle;
92
93 if (file_position == -1)
94 {
95 temp = read_char_no_whitespace (sgf_fd);
96 if (temp != '(')
97 {
98 /* we're here because there may have been a sibling after
99 another gametree that was handled, but there's no '(',
100 so there wasnt' a sibling, so just go on to any more
101 gametrees in the stack */
102 continue;
103 }
104 else
105 {
106 /* there may be more siblings after we process this one */
107 push_int_stack (&parse_stack, -1);
108 push_int_stack (&parse_stack, first_handle);
109 }
110 }
111 else
112 {
113 /* check for a sibling after we finish with this node */
114 push_int_stack (&parse_stack, -1);
115 push_int_stack (&parse_stack, first_handle);
116
117 rb->lseek (sgf_fd, file_position, SEEK_SET);
118
119
120 /* we're at the start of a gametree here, right at the '(' */
121 temp = read_char_no_whitespace (sgf_fd);
122
123 if (temp != '(')
124 {
125 DEBUGF ("start of gametree doesn't have a '('!\n");
126 current_node = saved;
127 return false;
128 }
129 }
130
131 while (1)
132 {
133 temp = peek_char_no_whitespace (sgf_fd);
134 /* DEBUGF("||| %d, %c\n", absolute_position(), (char) temp); */
135
136 if (temp == ';')
137 {
138 /* fill the tree_head node before moving on */
139 if (current_node != tree_head ||
140 get_node (current_node)->props >= 0)
141 {
142 int temp = add_child_sgf (NULL);
143
144 if (temp >= 0)
145 {
146 current_node = temp;
147 }
148 else
149 {
150 rb->splash (2 * HZ, "Out of memory while parsing!");
151 return false;
152 }
153 }
154
155
156 read_char_no_whitespace (sgf_fd);
157 parse_node ();
158 }
159 else if (temp == ')')
160 {
161 /* finished this gametree */
162
163 /* we want to end one past the ')', so eat it up: */
164 read_char_no_whitespace (sgf_fd);
165 break;
166 }
167 else if (temp == '(')
168 {
169 /*
170 DEBUGF ("adding %d\n", (int) rb->lseek (sgf_fd, 0,
171 SEEK_CUR)); */
172 push_int_stack (&parse_stack, rb->lseek (sgf_fd, 0, SEEK_CUR));
173 push_int_stack (&parse_stack, current_node);
174
175 break;
176 }
177 else if (temp == -1)
178 {
179 break;
180 }
181 else
182 {
183 DEBUGF ("extra characters found while parsing: %c\n", temp);
184 /* skip the extras i guess */
185 read_char_no_whitespace (sgf_fd);
186 }
187 }
188 }
189
190 current_node = get_node (tree_head)->next;
191 while (current_node >= 0 && get_node (current_node)->props < 0)
192 {
193 temp = current_node; /* to be freed later */
194
195 /* update the ->prev pointed on all branches of the next node */
196 current_node = get_node (current_node)->next;
197 /* DEBUGF("trying to set prev for branch %d\n", current_node); */
198 if (current_node >= 0)
199 {
200 get_node (current_node)->prev = tree_head;
201
202 struct prop_t *loop_prop =
203 get_prop (get_node (current_node)->props);
204
205 while (loop_prop != 0)
206 {
207 if (loop_prop->type == PROP_VARIATION)
208 {
209 get_node (loop_prop->data.number)->prev = tree_head;
210 }
211 else
212 {
213 /* all of the variations have to be up front, so we
214 can quit here */
215 break;
216 }
217 loop_prop = get_prop (loop_prop->next);
218 }
219 }
220
221 /* update the tree head */
222 get_node (tree_head)->next = get_node (temp)->next;
223 /* DEBUGF("freeing %d %d %d\n", temp, start_node, saved); */
224 if (start_node == temp || saved == temp)
225 {
226 start_node = saved = tree_head;
227 }
228 free_storage_sgf (temp);
229
230 current_node = get_node (tree_head)->next;
231 }
232
233 current_node = saved;
234
235
236 /* DEBUGF("got past!\n"); */
237 close_file (&sgf_fd);
238 return true;
239}
240
241
242static void
243parse_node (void)
244{
245 int temp;
246
247 while (1)
248 {
249 temp = peek_char_no_whitespace (sgf_fd);
250
251 if (temp == -1 || temp == ')' || temp == '(' || temp == ';')
252 {
253 return;
254 }
255 else
256 {
257 parse_prop ();
258 }
259 }
260}
261
262
263
264int start_of_prop = 0;
265static void
266parse_prop (void)
267{
268 enum prop_type_t temp_type = PROP_INVALID;
269 int temp;
270
271
272 while (1)
273 {
274 temp = peek_char_no_whitespace (sgf_fd);
275
276 if (temp == -1 || temp == ')' || temp == '(' || temp == ';')
277 {
278 return;
279 }
280 else if (temp == '[')
281 {
282 handle_prop_value (temp_type);
283 }
284 else
285 {
286 start_of_prop = rb->lseek (sgf_fd, 0, SEEK_CUR);
287 temp_type = parse_prop_type ();
288 }
289 }
290}
291static enum prop_type_t
292parse_prop_type (void)
293{
294 char buffer[3];
295 int pos = 0;
296 int temp;
297
298 rb->memset (buffer, 0, sizeof (buffer));
299
300 while (1)
301 {
302 temp = peek_char_no_whitespace (sgf_fd);
303
304 if (temp == ';' || temp == '[' || temp == '(' ||
305 temp == -1 || temp == ')')
306 {
307 if (pos == 1 || pos == 2)
308 {
309 break;
310 }
311 else
312 {
313 return PROP_INVALID;
314 }
315 }
316 else if (temp >= 'A' && temp <= 'Z')
317 {
318 buffer[pos++] = temp;
319
320 if (pos == 2)
321 {
322 read_char_no_whitespace (sgf_fd);
323 break;
324 }
325 }
326
327 temp = read_char_no_whitespace (sgf_fd);
328 }
329
330 /* check if we're still reading a prop name, in which case we fail
331 (but first we want to eat up the rest of the prop name) */
332 bool failed = false;
333 while (peek_char_no_whitespace (sgf_fd) != ';' &&
334 peek_char_no_whitespace (sgf_fd) != '[' &&
335 peek_char_no_whitespace (sgf_fd) != '(' &&
336 peek_char_no_whitespace (sgf_fd) != '}' &&
337 peek_char_no_whitespace (sgf_fd) != -1)
338 {
339 failed = true;
340 read_char_no_whitespace (sgf_fd);
341 }
342
343 if (failed)
344 {
345 return PROP_INVALID;
346 }
347
348 int i;
349 for (i = 0; i < PROP_NAMES_SIZE; ++i)
350 {
351 if (rb->strcmp (buffer, prop_names[i]) == 0)
352 {
353 return (enum prop_type_t) i;
354 }
355 }
356 return PROP_INVALID;
357}
358
359static int
360read_prop_value (char *buffer, size_t buffer_size)
361{
362 bool escaped = false;
363 int temp;
364 int bytes_read = 0;
365
366 /* make it a string, the lazy way */
367 rb->memset (buffer, 0, buffer_size);
368 --buffer_size;
369
370 if (peek_char (sgf_fd) == '[')
371 {
372 read_char (sgf_fd);
373 }
374
375 while (1)
376 {
377 temp = read_char (sgf_fd);
378 if (temp == ']' && !escaped)
379 {
380 return bytes_read;
381 }
382 else if (temp == '\\')
383 {
384 if (escaped)
385 {
386 if (buffer && buffer_size)
387 {
388 *(buffer++) = temp;
389 ++bytes_read;
390 --buffer_size;
391 }
392 }
393 escaped = !escaped;
394 }
395 else if (temp == -1)
396 {
397 return bytes_read;
398 }
399 else
400 {
401 escaped = false;
402 if (buffer && buffer_size)
403 {
404 *(buffer++) = temp;
405 ++bytes_read;
406 --buffer_size;
407 }
408 }
409 }
410}
411
412static void
413handle_prop_value (enum prop_type_t type)
414{
415 /* max size of generically supported prop values is 6, which is 5 for
416 a point range ab:cd and one for the \0
417
418 (this buffer is only used for them, things such as white and black
419 player names are stored in different buffers) */
420
421 /* make it a little bigger for other random crap, like reading in time
422 */
423#define PROP_HANDLER_BUFFER_SIZE 16
424
425 char real_buffer[PROP_HANDLER_BUFFER_SIZE];
426 char *buffer = real_buffer;
427
428 int temp;
429 union prop_data_t temp_data;
430 bool in_prop_value = false;
431 bool escaped = false;
432 bool done = false;
433 int temp_width, temp_height;
434 unsigned short temp_pos_ul, temp_pos_br;
435 int temp_size;
436 char *temp_buffer;
437 bool got_value;
438
439 /* special extra handling for root properties, set a marker telling us
440 the right place to spit the values out in output_sgf */
441 if (type == PROP_GAME ||
442 type == PROP_APPLICATION ||
443 type == PROP_CHARSET ||
444 type == PROP_SIZE ||
445 type == PROP_FILE_FORMAT || type == PROP_VARIATION_TYPE)
446 {
447 header_marked = true;
448
449 temp_data.number = 0; /* meaningless */
450
451 /* don't add more than one, so just set it if we found one already
452 */
453 add_or_set_prop_sgf (current_node, PROP_ROOT_PROPS, temp_data);
454 }
455
456
457 if (!is_handled_sgf (type) || type == PROP_COMMENT)
458 {
459 /* DEBUGF("unhandled prop %d\n", (int) type); */
460 rb->lseek (sgf_fd, start_of_prop, SEEK_SET);
461
462 temp_data.number = rb->lseek (unhandled_fd, 0, SEEK_CUR);
463 /* absolute_position(&unhandled_prop_list); */
464
465 add_prop_sgf (current_node,
466 type == PROP_COMMENT ? PROP_COMMENT :
467 PROP_GENERIC_UNHANDLED, temp_data);
468
469 got_value = false;
470 while (!done)
471 {
472 temp = peek_char (sgf_fd);
473
474 switch (temp)
475 {
476 case -1:
477 done = true;
478 break;
479
480 case '\\':
481 if (got_value && !in_prop_value)
482 {
483 done = true;
484 }
485 escaped = !escaped;
486 break;
487 case '[':
488 escaped = false;
489 in_prop_value = true;
490 got_value = true;
491 break;
492 case ']':
493 if (!escaped)
494 {
495 in_prop_value = false;
496 }
497 escaped = false;
498 break;
499 case ')':
500 case '(':
501 case ';':
502 if (!in_prop_value)
503 {
504 done = true;
505 }
506 escaped = false;
507 break;
508 default:
509 if (got_value && !in_prop_value)
510 {
511 if (!is_whitespace (temp))
512 {
513 done = true;
514 }
515 }
516 escaped = false;
517 break;
518 };
519
520 if (done)
521 {
522 write_char (unhandled_fd, ';');
523 }
524 else
525 {
526 /* don't write out-of-prop whitespace */
527 if (in_prop_value || !is_whitespace (temp))
528 {
529 write_char (unhandled_fd, (char) temp);
530 }
531
532 read_char (sgf_fd);
533 }
534 }
535
536
537 return;
538 }
539 else if (type == PROP_BLACK_MOVE || type == PROP_WHITE_MOVE)
540 {
541 /* DEBUGF("move prop %d\n", (int) type); */
542
543 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
544
545 temp_data.position = INVALID_POS;
546
547 /* empty is apparently acceptable as a pass */
548 if (temp == 0)
549 {
550 temp_data.position = PASS_POS;
551 }
552 else if (temp == 2)
553 {
554 temp_data.position = sgf_to_pos (buffer);
555 }
556 else
557 {
558 DEBUGF ("invalid move position read in, of wrong size!\n");
559 }
560
561
562 if (temp_data.position != INVALID_POS)
563 {
564 add_prop_sgf (current_node, type, temp_data);
565 }
566
567 return;
568 }
569 else if (type == PROP_ADD_BLACK ||
570 type == PROP_ADD_WHITE ||
571 type == PROP_ADD_EMPTY ||
572 type == PROP_CIRCLE ||
573 type == PROP_SQUARE ||
574 type == PROP_TRIANGLE ||
575 type == PROP_DIM || type == PROP_MARK || type == PROP_SELECTED)
576 {
577 /* DEBUGF("add prop %d\n", (int) type); */
578
579 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
580 if (temp == 2)
581 {
582 temp_data.position = sgf_to_pos (buffer);
583
584 if (temp_data.position != INVALID_POS &&
585 temp_data.position != PASS_POS)
586 {
587 add_prop_sgf (current_node, type, temp_data);
588 }
589 }
590 else if (temp == 5)
591 {
592 /* example: "ab:cd", two positions separated by a colon */
593 temp_pos_ul = sgf_to_pos (buffer);
594 temp_pos_br = sgf_to_pos (&(buffer[3]));
595
596 if (!on_board (temp_pos_ul) || !on_board (temp_pos_br) ||
597 buffer[2] != ':')
598 {
599 DEBUGF ("invalid range value!\n");
600 }
601
602 do_range (type, temp_pos_ul, temp_pos_br);
603 }
604 else
605 {
606 DEBUGF ("invalid position or range read in. wrong size!\n");
607 }
608 return;
609 }
610 else if (type == PROP_LABEL)
611 {
612 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
613
614 if (temp < 4 || buffer[2] != ':')
615 {
616 DEBUGF ("invalid LaBel property '%s'", buffer);
617 }
618 temp_data.position = sgf_to_pos (buffer);
619
620 if (!on_board (temp_data.position))
621 {
622 DEBUGF ("LaBel set on invalid position!\n");
623 }
624
625 temp_data.label_extra = buffer[3];
626
627 add_prop_sgf (current_node, type, temp_data);
628 return;
629 }
630 else if (type == PROP_GAME)
631 {
632 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
633 if (temp != 1 || buffer[0] != '1')
634 {
635 rb->splash (2 * HZ, "This isn't a Go SGF!! Parsing stopped.");
636 DEBUGF ("incorrect game type loaded!\n");
637
638 close_file (&sgf_fd);
639 }
640 }
641 else if (type == PROP_FILE_FORMAT)
642 {
643 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
644 if (temp != 1 || (buffer[0] != '3' && buffer[0] != '4'))
645 {
646 rb->splash (2 * HZ, "Wrong SGF file version! Parsing stopped.");
647 DEBUGF ("can't handle file format %c\n", buffer[0]);
648
649 close_file (&sgf_fd);
650 }
651 }
652 else if (type == PROP_APPLICATION ||
653 type == PROP_CHARSET || type == PROP_VARIATION_TYPE)
654 {
655 /* we don't care. on output we'll write our own values for these */
656 read_prop_value (NULL, 0);
657 }
658 else if (type == PROP_SIZE)
659 {
660 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
661 if (temp == 0)
662 {
663 rb->splash (HZ, "Invalid board size specified in file.");
664 }
665 else
666 {
667 temp_width = rb->atoi (buffer);
668 while (*buffer != ':' && *buffer != '\0')
669 {
670 ++buffer;
671 }
672
673 if (*buffer != '\0')
674 {
675 ++buffer;
676 temp_height = rb->atoi (buffer);
677 }
678 else
679 {
680 temp_height = temp_width;
681 }
682
683
684 if (!set_size_board (temp_width, temp_height))
685 {
686 rb->splashf (HZ,
687 "Board too big/small! (%dx%d) Stopping parse.",
688 temp_width, temp_height);
689 close_file (&sgf_fd);
690 }
691 else
692 {
693 clear_board ();
694 }
695 }
696 }
697 else if (type == PROP_KOMI)
698 {
699 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
700
701 if (temp == 0)
702 {
703 header.komi = 0;
704 DEBUGF ("invalid komi specification. setting to zero\n");
705 }
706 else
707 {
708 header.komi = rb->atoi (buffer) << 1;
709 while (*buffer != '.' && *buffer != ',' && *buffer != '\0')
710 {
711 ++buffer;
712 }
713
714 if (buffer != '\0')
715 {
716 ++buffer;
717
718 if (*buffer == 0)
719 {
720 /* do nothing */
721 }
722 else if (*buffer >= '1' && *buffer <= '9')
723 {
724 header.komi += 1;
725 }
726 else
727 {
728 if (*buffer != '0')
729 {
730 DEBUGF ("extra characters after komi value!\n");
731 }
732 }
733 }
734 }
735 }
736 else if (type == PROP_BLACK_NAME ||
737 type == PROP_WHITE_NAME ||
738 type == PROP_BLACK_RANK ||
739 type == PROP_WHITE_RANK ||
740 type == PROP_BLACK_TEAM ||
741 type == PROP_WHITE_TEAM ||
742 type == PROP_DATE ||
743 type == PROP_ROUND ||
744 type == PROP_EVENT ||
745 type == PROP_PLACE ||
746 type == PROP_OVERTIME ||
747 type == PROP_RESULT || type == PROP_RULESET)
748 {
749 if (!get_header_string_and_size
750 (&header, type, &temp_buffer, &temp_size))
751 {
752 rb->splash (5 * HZ,
753 "Error getting header string. Report this.");
754 }
755 else
756 {
757 temp = read_prop_value (temp_buffer, temp_size - 1);
758#if 0
759 DEBUGF ("read %d bytes into header for type: %d\n", temp, type);
760 DEBUGF ("data: %s\n", temp_buffer);
761#endif
762 }
763 }
764 else if (type == PROP_TIME_LIMIT)
765 {
766 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
767 header.time_limit = rb->atoi (buffer);
768 DEBUGF ("setting time: %d (%s)\n", header.time_limit, buffer);
769 }
770 else if (type == PROP_HANDICAP)
771 {
772 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
773 if (start_node == tree_head)
774 {
775 if (rb->atoi (buffer) >= 2)
776 {
777 start_node = current_node;
778 temp_data.number = header.handicap = rb->atoi (buffer);
779 add_prop_sgf (current_node, type, temp_data);
780 DEBUGF ("setting handicap: %d\n", header.handicap);
781 }
782 else
783 {
784 DEBUGF ("invalid HAndicap prop. ignoring\n");
785 }
786 }
787 else
788 {
789 rb->splash (HZ, "extraneous HAndicap prop present in file!\n");
790 }
791 }
792 else
793 {
794 DEBUGF ("UNHANDLED PROP TYPE!!!\n");
795 rb->splash (3 * HZ,
796 "A SGF prop was not dealt with. Please report this");
797 read_prop_value (NULL, 0);
798 }
799}
800
801
802
803/* upper-left and bottom right */
804static void
805do_range (enum prop_type_t type, unsigned short ul, unsigned short br)
806{
807 /* this code is overly general and accepts ranges even if ul and br
808 aren't the required corners it's easier doing that that failing if
809 the input is bad */
810
811 bool x_reverse = false;
812 bool y_reverse = false;
813 union prop_data_t temp_data;
814
815 if (I (br) < I (ul))
816 {
817 x_reverse = true;
818 }
819
820 if (J (br) < J (ul))
821 {
822 y_reverse = true;
823 }
824
825 int x, y;
826 for (x = I (ul);
827 x_reverse ? (x >= I (br)) : (x <= I (br)); x_reverse ? --x : ++x)
828 {
829 for (y = J (ul);
830 y_reverse ? (y >= J (br)) : (y <= J (br)); y_reverse ? --y : ++y)
831 {
832 temp_data.position = POS (x, y);
833
834 DEBUGF ("adding %d %d for range (type %d)\n",
835 I (temp_data.position), J (temp_data.position), type);
836 add_prop_sgf (current_node, type, temp_data);
837 }
838 }
839}
840
841
842
843static unsigned short
844sgf_to_pos (char *buffer)
845{
846 if (buffer[0] == 't' && buffer[1] == 't')
847 {
848 return PASS_POS;
849 }
850 else if (buffer[0] < 'a' || buffer[0] > 'z' ||
851 buffer[1] < 'a' || buffer[1] > 'z')
852 {
853 return INVALID_POS;
854 }
855 return POS (buffer[0] - 'a', buffer[1] - 'a');
856}
857
diff --git a/apps/plugins/goban/sgf_parse.h b/apps/plugins/goban/sgf_parse.h
new file mode 100644
index 0000000000..869d343bd7
--- /dev/null
+++ b/apps/plugins/goban/sgf_parse.h
@@ -0,0 +1,30 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#ifndef GOBAN_SGF_PARSE_H
23#define GOBAN_SGF_PARSE_H
24
25#include "types.h"
26
27/* Do the actual parsing of an SGF file. Return false on failure */
28bool parse_sgf (const char *filename);
29
30#endif
diff --git a/apps/plugins/goban/sgf_storage.c b/apps/plugins/goban/sgf_storage.c
new file mode 100644
index 0000000000..1c92625f7d
--- /dev/null
+++ b/apps/plugins/goban/sgf_storage.c
@@ -0,0 +1,493 @@
1
2/***************************************************************************
3 * __________ __ ___.
4 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
5 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
6 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
7 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
8 * \/ \/ \/ \/ \/
9 * $Id$
10 *
11 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ****************************************************************************/
22
23#include "goban.h"
24#include "sgf_storage.h"
25#include "sgf.h"
26#include "util.h"
27
28#define ALIGNMENT_VAL (sizeof (union storage_t))
29
30union storage_t *storage_buffer[] = { NULL, NULL };
31size_t storage_buffer_size[] = { 0, 0 };
32
33uint8_t *storage_free_list[] = { NULL, NULL };
34size_t storage_free_list_size[] = { 0, 0 };
35
36bool storage_initialized[] = { false, false };
37
38size_t total_storage_size = 0;
39
40/* the next handle to check */
41int next_free_handle_buffer;
42int next_free_handle;
43
44static bool setup_storage_buffer (char *temp_buffer, size_t size);
45static void clear_storage_buffer (int index);
46static bool find_free (int *ret_buffer, int *ret_handle);
47static bool is_free (int buffer_num, int handle);
48static void set_free (int buffer_num, int handle, bool free);
49
50#if 0
51static void debugf_current_node (void);
52
53static void
54debugf_current_node (void)
55{
56 int temp_prop = NO_PROP;
57 if (current_node < 0)
58 {
59 DEBUGF ("CURRENT_NODE < 0 ON DEBUGF_CURRENT_NODE!!!!\n");
60 return;
61 }
62 DEBUGF ("-----------------------------------------\n");
63 DEBUGF ("current_node: %d\n", current_node);
64 DEBUGF ("start_node %d %d\n", start_node, tree_head);
65 DEBUGF ("prev/next: %d/%d\n", get_node (current_node)->prev,
66 get_node (current_node)->next);
67 DEBUGF ("num variations: %d\n", num_variations_sgf ());
68 DEBUGF ("props:\n");
69 if (!get_node (current_node) ||
70 (temp_prop = get_node (current_node)->props) < 0)
71 {
72 DEBUGF ("none\n");
73 }
74
75 while (1)
76 {
77 if (temp_prop < 0)
78 {
79 break;
80 }
81 DEBUGF (" handle: %d\n", temp_prop);
82 DEBUGF (" type: %d ", get_prop (temp_prop)->type);
83 if (get_prop (temp_prop)->type < PROP_NAMES_SIZE)
84 {
85 DEBUGF ("(%s)", prop_names[get_prop (temp_prop)->type]);
86 }
87 DEBUGF ("\n");
88 if (get_prop (temp_prop)->type == PROP_BLACK_MOVE ||
89 get_prop (temp_prop)->type == PROP_WHITE_MOVE ||
90 get_prop (temp_prop)->type == PROP_ADD_BLACK ||
91 get_prop (temp_prop)->type == PROP_ADD_WHITE ||
92 get_prop (temp_prop)->type == PROP_ADD_EMPTY)
93 {
94 DEBUGF (" i: %d j: %d\n",
95 I (get_prop (temp_prop)->data.position),
96 J (get_prop (temp_prop)->data.position));
97 }
98 else
99 {
100 DEBUGF (" data: %d\n", get_prop (temp_prop)->data.number);
101 }
102 DEBUGF (" next: %d\n", get_prop (temp_prop)->next);
103
104 temp_prop = get_prop (temp_prop)->next;
105 if (temp_prop >= 0)
106 {
107 DEBUGF ("\n");
108 }
109 }
110
111 DEBUGF ("-----------------------------------------\n");
112}
113#endif
114
115static void
116clear_storage_buffer (int index)
117{
118 int temp;
119
120
121 /* everything starts free */
122 rb->memset (storage_free_list[index],
123 (unsigned char) 0xFF,
124 storage_free_list_size[index]);
125
126 /* if there are extra bits at the end of the free list (because
127 storage_buffer_size is not divisible by 8) then we set them not
128 free, so we won't end up using those ever by accident (shouldn't be
129 possible anyways, but makes calculation easier later) */
130 temp = storage_free_list_size[index] * 8 - storage_buffer_size[index];
131 storage_free_list[index][storage_free_list_size[index] - 1] ^=
132 (1 << temp) - 1;
133}
134
135void
136free_tree_sgf (void)
137{
138 unsigned int i;
139 for (i = 0;
140 i < sizeof (storage_initialized) /
141 sizeof (storage_initialized[0]); ++i)
142 {
143 if (storage_initialized[i])
144 {
145 clear_storage_buffer (i);
146 }
147 }
148
149 tree_head = start_node = current_node = alloc_storage_sgf ();
150
151 if (tree_head < 0)
152 {
153 rb->splash (5 * HZ,
154 "Error allocating first node! Please exit immediately.");
155 }
156
157 get_node (tree_head)->props = NO_PROP;
158 get_node (tree_head)->next = NO_NODE;
159 get_node (tree_head)->prev = NO_NODE;
160}
161
162
163bool
164audio_stolen_sgf (void)
165{
166 return storage_initialized[1];
167}
168
169int
170alloc_storage_sgf (void)
171{
172 int buffer_num;
173 int handle;
174 int temp_buffer;
175 int ret_val;
176
177 char *new_storage_buffer;
178 size_t size;
179
180 if (!find_free (&buffer_num, &handle))
181 {
182 if (!storage_initialized[1])
183 {
184 rb->splash (2 * HZ, "Stopping music playback to get more space");
185 DEBUGF ("stealing audio buffer: %d\n", (int) total_storage_size);
186
187 new_storage_buffer = rb->plugin_get_audio_buffer (&size);
188 setup_storage_buffer (new_storage_buffer, size);
189
190 DEBUGF ("after stealing: %d\n", (int) total_storage_size);
191 }
192 else
193 {
194 return -1;
195 }
196
197 /* try again */
198 if (!find_free (&buffer_num, &handle))
199 {
200 return -1;
201 }
202 }
203
204 set_free (buffer_num, handle, false);
205
206 temp_buffer = 0;
207 ret_val = handle;
208
209 while (temp_buffer != buffer_num)
210 {
211 if (storage_initialized[temp_buffer])
212 {
213 ret_val += storage_buffer_size[temp_buffer];
214 }
215
216 ++temp_buffer;
217 }
218
219 return ret_val;
220}
221
222void
223free_storage_sgf (int handle)
224{
225 int index;
226
227 if (handle < 0 || (unsigned int) handle >= total_storage_size)
228 {
229 DEBUGF ("tried to free an out of bounds handle!!\n");
230 }
231 else
232 {
233 index = 0;
234 while ((unsigned int) handle >= storage_buffer_size[index])
235 {
236 handle -= storage_buffer_size[index++];
237 }
238 rb->memset (&storage_buffer[index][handle], 0xFF,
239 sizeof (union storage_t));
240 set_free (index, handle, true);
241 }
242}
243
244static bool
245find_free (int *ret_buffer, int *ret_handle)
246{
247 unsigned int handle = next_free_handle;
248 unsigned int buffer_index = next_free_handle_buffer;
249
250 /* so we know where we started, to prevent infinite loop */
251 unsigned int start_handle = handle;
252 unsigned int start_buffer = buffer_index;
253
254
255 do
256 {
257 ++handle;
258
259 if (handle >= storage_buffer_size[buffer_index])
260 {
261 handle = 0;
262
263 do
264 {
265 ++buffer_index;
266
267 if (buffer_index >= sizeof (storage_initialized) /
268 sizeof (storage_initialized[0]))
269 {
270 buffer_index = 0;
271 }
272 }
273 while (!storage_initialized[buffer_index]);
274
275 }
276
277 if (is_free (buffer_index, handle))
278 {
279 next_free_handle_buffer = buffer_index;
280 next_free_handle = handle;
281
282 *ret_buffer = buffer_index;
283 *ret_handle = handle;
284
285 return true;
286 }
287 }
288 while (handle != start_handle || buffer_index != start_buffer);
289
290 return false;
291}
292
293static bool
294is_free (int buffer_num, int handle)
295{
296 return storage_free_list[buffer_num][handle / 8] &
297 (1 << (7 - (handle % 8)));
298}
299
300static void
301set_free (int buffer_num, int handle, bool free)
302{
303 if (free)
304 {
305 /* simple, just 'or' the byte with the specific bit switched on */
306 storage_free_list[buffer_num][handle / 8] |= 1 << (7 - (handle % 8));
307 }
308 else
309 {
310 /* start with a byte with all bits turned on and turn off the one
311 we're trying to set to zero. then take that result and 'and'
312 it with the current value */
313 storage_free_list[buffer_num][handle / 8] &=
314 0xFF ^ (1 << (7 - (handle % 8)));
315 }
316}
317
318bool
319setup_sgf (void)
320{
321 size_t size;
322 char *temp_buffer;
323
324 temp_buffer = rb->plugin_get_buffer (&size);
325 setup_storage_buffer (temp_buffer, size);
326
327 if (total_storage_size < MIN_STORAGE_BUFFER_SIZE)
328 {
329 rb->splash (2 * HZ, "Stopping music playback to get more space");
330 DEBUGF ("storage_buffer_size < MIN!!: %d\n", (int) total_storage_size);
331
332 temp_buffer = rb->plugin_get_audio_buffer (&size);
333 setup_storage_buffer (temp_buffer, size);
334 }
335
336 if (total_storage_size < MIN_STORAGE_BUFFER_SIZE)
337 {
338 rb->splash (5 * HZ, "Low memory. Large files may not load.");
339
340 DEBUGF ("storage_buffer_size < MIN!!!!: %d\n",
341 (int) total_storage_size);
342 }
343
344 DEBUGF ("storage_buffer_size: %d\n", (int) total_storage_size);
345
346
347 unhandled_fd = create_or_open_file (UNHANDLED_PROP_LIST_FILE);
348
349 if (unhandled_fd < 0)
350 {
351 return false;
352 }
353
354 rb->lseek (unhandled_fd, 0, SEEK_SET);
355 rb->ftruncate (unhandled_fd, 0);
356
357 empty_stack (&parse_stack);
358
359 return true;
360}
361
362
363void
364clear_caches_sgf (void)
365{
366 empty_stack (&parse_stack);
367
368 rb->lseek (unhandled_fd, 0, SEEK_SET);
369 rb->ftruncate (unhandled_fd, 0);
370}
371
372void
373cleanup_sgf (void)
374{
375 empty_stack (&parse_stack);
376
377 rb->lseek (unhandled_fd, 0, SEEK_SET);
378 rb->ftruncate (unhandled_fd, 0);
379 close_file (&unhandled_fd);
380
381 close_file (&sgf_fd);
382}
383
384static bool
385setup_storage_buffer (char *temp_buffer, size_t size)
386{
387 unsigned int index = 0;
388 int temp;
389
390 while (1)
391 {
392 if (index >= sizeof (storage_initialized) /
393 sizeof (storage_initialized[0]))
394 {
395 return false;
396 }
397
398 if (!storage_initialized[index])
399 {
400 break;
401 }
402 ++index;
403 }
404
405 temp_buffer = align_buffer (temp_buffer, &size);
406 if (!temp_buffer || !size)
407 {
408 return false;
409 }
410
411 /* same as temp = size / (sizeof(union storage_t) + 1/8)
412
413 (we need 1 bit extra for each union storage_t, for the free list) */
414 temp =
415 (8 * (size - ALIGNMENT_VAL - 1)) / (8 * sizeof (union storage_t) + 1);
416 /* the - ALIGNMENT_VAL - 1 is for possible wasted space in alignment
417 and possible extra byte needed in the free list */
418
419 storage_buffer[index] = (void *) temp_buffer;
420 storage_buffer_size[index] = temp;
421
422 storage_free_list_size[index] = storage_buffer_size[index] / 8;
423 if (storage_free_list_size[index] * 8 < storage_buffer_size[index])
424 {
425 ++(storage_free_list_size[index]);
426 }
427
428 temp_buffer += sizeof (union storage_t) * temp;
429 size -= sizeof (union storage_t) * temp;
430
431 temp_buffer = align_buffer (temp_buffer, &size);
432 if (!temp_buffer || !size)
433 {
434 return false;
435 }
436
437 if (size < storage_free_list_size[index])
438 {
439 DEBUGF ("Big problem on line %d in sgf.c\n", __LINE__);
440 rb->splashf (5 * HZ,
441 "Error in allocating storage buffer! Exit and report this!\n");
442 return false;
443 }
444
445 storage_free_list[index] = temp_buffer;
446 total_storage_size += storage_buffer_size[index];
447 storage_initialized[index] = true;
448
449 clear_storage_buffer (index);
450
451 return true;
452}
453
454struct node_t *
455get_node (int handle)
456{
457 if (handle < 0)
458 {
459 return NULL;
460 }
461 else if ((unsigned int) handle >= total_storage_size)
462 {
463 return NULL;
464 }
465
466 int index = 0;
467 while ((unsigned int) handle >= storage_buffer_size[index])
468 {
469 handle -= storage_buffer_size[index++];
470 }
471 return &(storage_buffer[index][handle].node);
472}
473
474struct prop_t *
475get_prop (int handle)
476{
477 if (handle < 0)
478 {
479 return NULL;
480 }
481 else if ((unsigned int) handle >= total_storage_size)
482 {
483 return NULL;
484 }
485
486 int index = 0;
487 while ((unsigned int) handle >= storage_buffer_size[index])
488 {
489 handle -= storage_buffer_size[index++];
490 }
491 return &(storage_buffer[index][handle].prop);
492}
493
diff --git a/apps/plugins/goban/sgf_storage.h b/apps/plugins/goban/sgf_storage.h
new file mode 100644
index 0000000000..4835c8c9d2
--- /dev/null
+++ b/apps/plugins/goban/sgf_storage.h
@@ -0,0 +1,57 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#ifndef GOBAN_SGF_STORAGE_H
23#define GOBAN_SGF_STORAGE_H
24
25#include "types.h"
26
27/* Must be called (and return true) before using anything in the SGF
28 subsystem returns false on failure */
29bool setup_sgf (void);
30
31/* Do cleanup, call before exiting the plugin. You must not use any SGF
32 subsystem functions after calling this */
33void cleanup_sgf (void);
34
35/* Get ready for a new game (either loaded or blank) */
36void clear_caches_sgf (void);
37
38/* Clear the SGF tree and get it ready for a new game (loaded or blank) */
39void free_tree_sgf (void);
40
41/* Returns true if the Rockbox audio buffer has been stolen */
42bool audio_stolen_sgf (void);
43
44/* Returns a handle to a struct storage_t (NOT a pointer) < 0 handles are
45 invalid */
46int alloc_storage_sgf (void);
47
48/* Free one storage location */
49void free_storage_sgf (int handle);
50
51/* Get a pointer to a node or property which corresponds to the given
52 * storage handle
53 */
54struct node_t *get_node (int handle);
55struct prop_t *get_prop (int handle);
56
57#endif
diff --git a/apps/plugins/goban/types.h b/apps/plugins/goban/types.h
new file mode 100644
index 0000000000..216d41bc21
--- /dev/null
+++ b/apps/plugins/goban/types.h
@@ -0,0 +1,289 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#ifndef GOBAN_TYPES_H
23#define GOBAN_TYPES_H
24
25#include "plugin.h"
26
27/* A generic stack sp is the stack pointer (0 for an empty stack) */
28struct stack_t
29{
30 size_t size;
31 size_t sp;
32 char *buffer;
33};
34
35/* All of the types of SGF properties that we understand. Not all of these
36 are handled, even if we understand them, unhandled properties are
37 handled differently (basically they are copied to a secondary location
38 and copied back if we output the tree) The ones at the end aren't real
39 SGF properties, they are only to help us in parsing/outputting/keeping
40 track of variations/etc. IMPORTANT: if you edit this, you must be
41 careful to keep it in line with prop_names, PROP_NAMES_SIZE, and
42 PROP_NAME_LEN otherwise really bad things will happen. There is too much
43 information on each of these to list here, please see
44 http://www.red-bean.com/sgf/ */
45enum prop_type_t
46{
47 PROP_BLACK_MOVE,
48 PROP_WHITE_MOVE,
49
50 PROP_ADD_BLACK,
51 PROP_ADD_WHITE,
52 PROP_ADD_EMPTY,
53
54 PROP_PLAYER_TO_PLAY,
55 PROP_COMMENT,
56
57 /* information about the position reached by the current node */
58 PROP_EVEN,
59 PROP_BLACK_GOOD,
60 PROP_WHITE_GOOD,
61 PROP_HOTSPOT,
62 PROP_UNCLEAR,
63 PROP_VALUE,
64
65 /* information about the current move */
66 PROP_BAD,
67 PROP_DOUBTFUL,
68 PROP_INTERESTING,
69 PROP_TESUJI,
70
71 /* marks on the board */
72 PROP_CIRCLE,
73 PROP_SQUARE,
74 PROP_TRIANGLE,
75 PROP_DIM,
76 PROP_MARK,
77 PROP_SELECTED,
78
79 /* labels go on points, names name the node */
80 PROP_LABEL,
81 PROP_NODE_NAME,
82
83 /* root props */
84 PROP_APPLICATION,
85 PROP_CHARSET,
86 PROP_FILE_FORMAT,
87 PROP_GAME,
88 PROP_VARIATION_TYPE,
89 PROP_SIZE,
90
91 /* game info props */
92 PROP_ANNOTATOR,
93 PROP_BLACK_NAME,
94 PROP_WHITE_NAME,
95 PROP_HANDICAP,
96 PROP_KOMI,
97 PROP_BLACK_TERRITORY,
98 PROP_WHITE_TERRITORY,
99 PROP_BLACK_RANK,
100 PROP_WHITE_RANK,
101 PROP_BLACK_TEAM,
102 PROP_WHITE_TEAM,
103 PROP_COPYRIGHT,
104 PROP_DATE,
105 PROP_EVENT,
106 PROP_ROUND,
107 PROP_GAME_NAME,
108 PROP_GAME_COMMENT,
109 PROP_OPENING_NAME,
110 PROP_OVERTIME,
111 PROP_PLACE,
112 PROP_RESULT,
113 PROP_RULESET,
114 PROP_SOURCE,
115 PROP_TIME_LIMIT,
116 PROP_USER,
117
118 /* these are all the <whatever> left /after/ the current move */
119 PROP_BLACK_TIME_LEFT,
120 PROP_WHITE_TIME_LEFT,
121 PROP_BLACK_STONES_LEFT, /* the number of stones left in a canadian
122 style overtime period */
123 PROP_WHITE_STONES_LEFT, /* same for white */
124
125
126 /* these are mostly used for printing, we don't handle these */
127 PROP_FIGURE,
128 PROP_PRINT_MOVE_MODE,
129
130 /* view only part of the board. probably don't handle this */
131 PROP_VIEW,
132
133
134
135 /* psuedo PROP types, used for variations and parsing and such */
136
137 PROP_VARIATION, /* used for branches */
138 PROP_INVALID,
139 PROP_GENERIC_UNHANDLED, /* used to mark the place where an
140 unhandled property was copied to
141 secondary storage (so we can output it
142 again when we output the game tree) */
143 PROP_ANY, /* Used as a parameter when any property
144 type is supposed to match */
145 PROP_VARIATION_TO_PROCESS, /* Used in parsing/outputting */
146 PROP_VARIATION_CHOICE, /* Used to store which variation we should
147 follow when we get to a branch */
148 PROP_ROOT_PROPS /* Marks the place where we should output
149 the information from struct header_t
150 header */
151};
152
153extern char *prop_names[];
154/* IMPORTANT: keep this array full of all properties that we want to be
155 able to parse out of an SGF file. This next part assumes that they go
156 before unparseable (psuedo) props in the above enum, or else we need to
157 insert placeholders in the array for unparseables */
158
159#define PROP_NAMES_SIZE (63)
160
161/* The only one character property names are the moves, everything else is
162 two characters */
163#define PROP_NAME_LEN(type) ((type == PROP_BLACK_MOVE || \
164 type == PROP_WHITE_MOVE) ? 1 : 2)
165
166/* Data for a property. Can be a number, a node handle (for variations),
167 or a position and either a short or a character (the extras are for
168 label characters, and stone undo data (moves and added/removed)) */
169union prop_data_t
170{
171 unsigned int number;
172 int node;
173 struct
174 {
175 unsigned short position;
176 union
177 {
178 unsigned short stone_extra;
179 unsigned char label_extra;
180 };
181 };
182};
183
184/* What should happen when the user "plays" a move */
185enum play_mode_t
186{
187 MODE_PLAY,
188 MODE_FORCE_PLAY,
189 MODE_ADD_BLACK,
190 MODE_ADD_WHITE,
191 MODE_REMOVE,
192 MODE_MARK,
193 MODE_CIRCLE,
194 MODE_SQUARE,
195 MODE_TRIANGLE,
196 MODE_LABEL
197};
198
199/* Different types of board marks */
200enum mark_t
201{
202 MARK_VARIATION,
203 MARK_SQUARE,
204 MARK_CIRCLE,
205 MARK_TRIANGLE,
206 MARK_LAST_MOVE,
207 MARK_LABEL
208};
209
210/* An SGF property next is the handle to the next property in the node, or
211 less than zero if there isn't another */
212struct prop_t
213{
214 union prop_data_t data;
215 enum prop_type_t type;
216 int next;
217};
218
219
220/* The names of the rulesets, ex. "AGA", "Japanese", etc. */
221extern char *ruleset_names[];
222
223/* IMPORTANT! keep in sync with ruleset_names!!! */
224enum ruleset_t
225{
226 RULESET_AGA = 0,
227 RULESET_JAPANESE,
228 RULESET_CHINESE,
229 RULESET_NEW_ZEALAND,
230 RULESET_ING,
231 __RULESETS_SIZE /* make sure i am last! */
232};
233
234#define NUM_RULESETS ((int) __RULESETS_SIZE)
235
236
237/* One SGF node which can contain an indefinite number of SGF properties
238 Less than zero for any of these means that there is nothing in that
239 direction. */
240struct node_t
241{
242 int props;
243 int next;
244 int prev;
245};
246
247
248/* convenience union for keeping a mixed array of props and nodes */
249union storage_t
250{
251 struct prop_t prop;
252 struct node_t node;
253};
254
255
256/* The game metadata which can be stored in the SGF file */
257
258#define MAX_NAME 59
259#define MAX_EVENT 100
260#define MAX_RESULT 16
261#define MAX_RANK 10
262#define MAX_TEAM 32
263#define MAX_DATE 32
264#define MAX_ROUND 8
265#define MAX_PLACE 100
266#define MAX_OVERTIME 32
267#define MAX_RULESET 32
268
269struct header_t
270{
271 char white[MAX_NAME];
272 char black[MAX_NAME];
273 char white_rank[MAX_RANK];
274 char black_rank[MAX_RANK];
275 char white_team[MAX_TEAM];
276 char black_team[MAX_TEAM];
277 char date[MAX_DATE];
278 char round[MAX_ROUND];
279 char event[MAX_EVENT];
280 char place[MAX_PLACE];
281 char result[MAX_RESULT];
282 char overtime[MAX_OVERTIME];
283 char ruleset[MAX_RULESET];
284 int time_limit;
285 int komi;
286 uint8_t handicap;
287};
288
289#endif
diff --git a/apps/plugins/goban/util.c b/apps/plugins/goban/util.c
new file mode 100644
index 0000000000..e9966311ef
--- /dev/null
+++ b/apps/plugins/goban/util.c
@@ -0,0 +1,885 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "util.h"
23#include "game.h"
24
25
26void metadata_summary (void)
27{
28 char buffer[256] = "";
29
30 if (rb->strlen (header.black) ||
31 rb->strlen (header.white) ||
32 rb->strlen (header.black_rank) ||
33 rb->strlen (header.white_rank))
34 rb->snprintf (buffer, sizeof(buffer),
35 "%s [%s] v. %s [%s] ",
36 header.black, header.black_rank,
37 header.white, header.white_rank);
38
39 if (header.handicap > 1)
40 {
41 rb->snprintf (buffer + rb->strlen(buffer),
42 sizeof (buffer) - rb->strlen (buffer),
43 "%d stones ", header.handicap);
44 }
45
46 if (header.komi != 0 && !(header.komi == 1 && header.handicap > 1))
47 {
48 snprint_fixed (buffer + rb->strlen(buffer),
49 sizeof (buffer) - rb->strlen (buffer),
50 header.komi);
51 rb->snprintf (buffer + rb->strlen(buffer),
52 sizeof (buffer) - rb->strlen (buffer),
53 " komi ");
54 }
55
56 if (rb->strlen(header.result))
57 {
58 rb->snprintf (buffer + rb->strlen(buffer),
59 sizeof (buffer) - rb->strlen (buffer),
60 "(%s)", header.result);
61 }
62
63 /* waiting for user input messes up the testing code, so ifdef it*/
64#if !defined(GBN_TEST)
65 if (rb->strlen(buffer))
66 {
67 rb->splash(0, buffer);
68 rb->action_userabort(TIMEOUT_BLOCK);
69 }
70#endif
71}
72
73void *
74align_buffer (void *buffer, size_t * buffer_size)
75{
76 unsigned int wasted = (-(long) buffer) & 3;
77
78 if (!buffer || !buffer_size)
79 {
80 return NULL;
81 }
82
83 if (*buffer_size <= wasted)
84 {
85 *buffer_size = 0;
86 return NULL;
87 }
88
89 *buffer_size -= wasted;
90
91 return (void *) (((char *) buffer) + wasted);
92}
93
94
95
96bool
97setup_stack (struct stack_t *stack, void *buffer, size_t buffer_size)
98{
99 if (!stack || !buffer || !buffer_size)
100 {
101 DEBUGF ("INVALID STACK SETUP!!\n");
102 return false;
103 }
104
105 buffer = align_buffer (buffer, &buffer_size);
106
107 if (!buffer || !buffer_size)
108 {
109 DEBUGF ("Buffer disappeared after alignment!\n");
110 return false;
111 }
112
113 stack->buffer = buffer;
114 stack->size = buffer_size;
115 stack->sp = 0;
116
117 return true;
118}
119
120bool
121push_stack (struct stack_t * stack, void *buffer, size_t buffer_size)
122{
123 if (stack->sp + buffer_size > stack->size)
124 {
125 DEBUGF ("stack full!!\n");
126 return false;
127 }
128
129 rb->memcpy (&stack->buffer[stack->sp], buffer, buffer_size);
130
131 stack->sp += buffer_size;
132
133 return true;
134}
135
136bool
137pop_stack (struct stack_t * stack, void *buffer, size_t buffer_size)
138{
139 if (!peek_stack (stack, buffer, buffer_size))
140 {
141 return false;
142 }
143
144 stack->sp -= buffer_size;
145
146 return true;
147}
148
149bool
150peek_stack (struct stack_t * stack, void *buffer, size_t buffer_size)
151{
152 if (stack->sp < buffer_size)
153 {
154 return false;
155 }
156
157 rb->memcpy (buffer, &stack->buffer[stack->sp - buffer_size], buffer_size);
158
159 return true;
160}
161
162void
163empty_stack (struct stack_t *stack)
164{
165 stack->sp = 0;
166}
167
168bool
169push_pos_stack (struct stack_t *stack, unsigned short pos)
170{
171 return push_stack (stack, &pos, sizeof (pos));
172}
173
174bool
175push_int_stack (struct stack_t *stack, int num)
176{
177 return push_stack (stack, &num, sizeof (num));
178}
179
180bool
181push_char_stack (struct stack_t *stack, char num)
182{
183 return push_stack (stack, &num, sizeof (num));
184}
185
186
187
188/* IMPORTANT: keep in sync with the enum prop_type_t enum in types.h */
189char *prop_names[] = {
190 /* look up the SGF specification for the meaning of these */
191 "B", "W",
192 "AB", "AW", "AE",
193
194 "PL", "C",
195
196 "DM", "GB", "GW", "HO", "UC", "V",
197
198 "BM", "DO", "IT", "TE",
199
200 "CR", "SQ", "TR", "DD", "MA", "SL", "LB", "N",
201
202 "AP", "CA", "FF", "GM", "ST", "SZ",
203
204 "AN", "PB", "PW", "HA", "KM", "TB", "TW", "BR", "WR",
205 "BT", "WT", "CP", "DT", "EV", "RO", "GN", "GC", "ON",
206 "OT", "PC", "RE", "RU", "SO", "TM", "US",
207
208 "BL", "WL", "OB", "OW", "FG", "PM", "VW"
209};
210
211/* These seems to be specified by the SGF specification. You can do free
212 form ones as well, but I haven't implemented that (and don't plan to) */
213char *ruleset_names[] = { "AGA", "Japanese", "Chinese", "NZ", "GOE" };
214
215
216
217int
218create_or_open_file (const char *filename)
219{
220 int fd;
221
222 if (!rb->file_exists (filename))
223 {
224 fd = rb->creat (filename);
225 }
226 else
227 {
228 fd = rb->open (filename, O_RDWR);
229 }
230
231 return fd;
232}
233
234
235int
236snprint_fixed (char *buffer, int buffer_size, int fixed)
237{
238 return rb->snprintf (buffer, buffer_size, "%s%d.%d",
239 fixed < 0 ? "-" : "",
240 abs (fixed) >> 1, 5 * (fixed & 1));
241}
242
243
244int
245peek_char (int fd)
246{
247 char peeked_char;
248
249 int result = rb->read (fd, &peeked_char, 1);
250
251 if (result != 1)
252 {
253 return -1;
254 }
255
256 result = rb->lseek (fd, -1, SEEK_CUR);
257
258 if (result < 0)
259 {
260 return -1;
261 }
262
263 return peeked_char;
264}
265
266
267int
268read_char (int fd)
269{
270 char read_char;
271
272 int result = rb->read (fd, &read_char, 1);
273
274 if (result != 1)
275 {
276 return -1;
277 }
278
279 return read_char;
280}
281
282
283bool
284write_char (int fd, char to_write)
285{
286 int result = write_file (fd, &to_write, 1);
287
288 if (result != 1)
289 {
290 return false;
291 }
292
293 return true;
294}
295
296ssize_t
297write_file (int fd, const void *buf, size_t count)
298{
299 const char *buffer = buf;
300 int result;
301 int ret_val = count;
302
303 while (count)
304 {
305 result = rb->write (fd, buffer, count);
306
307 if (result < 0)
308 {
309 return -1;
310 }
311
312 count -= result;
313 buffer += result;
314 }
315
316 return ret_val;
317}
318
319ssize_t
320read_file (int fd, void *buf, size_t count)
321{
322 char *buffer = buf;
323 int result;
324 int ret_val = count;
325
326 while (count)
327 {
328 result = rb->read (fd, buffer, count);
329
330 if (result <= 0)
331 {
332 return -1;
333 }
334
335 count -= result;
336 buffer += result;
337 }
338
339 return ret_val;
340}
341
342int
343read_char_no_whitespace (int fd)
344{
345 int result = peek_char_no_whitespace (fd);
346
347 read_char (fd);
348
349 return result;
350}
351
352int
353peek_char_no_whitespace (int fd)
354{
355 int result;
356
357 while (is_whitespace (result = peek_char (fd)))
358 {
359 read_char (fd);
360 }
361
362 return result;
363}
364
365
366void
367close_file (int *fd)
368{
369 if (*fd >= 0)
370 {
371 rb->close (*fd);
372 }
373
374 *fd = -1;
375}
376
377bool
378is_whitespace (int value)
379{
380 if (value == ' ' ||
381 value == '\t' ||
382 value == '\n' || value == '\r' || value == '\f' || value == '\v')
383 {
384 return true;
385 }
386 else
387 {
388 return false;
389 }
390}
391
392void
393sanitize_string (char *string)
394{
395 bool escaped = false;
396
397 if (!string)
398 {
399 return;
400 }
401
402 while (1)
403 {
404 switch (*string)
405 {
406 case '\0':
407 return;
408 case '\\':
409 escaped = !escaped;
410 break;
411 case ']':
412 if (!escaped)
413 {
414 *string = ']';
415 }
416 escaped = false;
417 break;
418 default:
419 break;
420 };
421 ++string;
422 }
423}
424
425
426bool
427get_header_string_and_size (struct header_t *header,
428 enum prop_type_t type, char **buffer, int *size)
429{
430 if (buffer == 0 || header == 0)
431 {
432 return false;
433 }
434
435 if (type == PROP_BLACK_NAME)
436 {
437 *buffer = header->black;
438 *size = MAX_NAME;
439 }
440 else if (type == PROP_WHITE_NAME)
441 {
442 *buffer = header->white;
443 *size = MAX_NAME;
444 }
445 else if (type == PROP_BLACK_RANK)
446 {
447 *buffer = header->black_rank;
448 *size = MAX_RANK;
449 }
450 else if (type == PROP_WHITE_RANK)
451 {
452 *buffer = header->white_rank;
453 *size = MAX_RANK;
454 }
455 else if (type == PROP_BLACK_TEAM)
456 {
457 *buffer = header->black_team;
458 *size = MAX_TEAM;
459 }
460 else if (type == PROP_WHITE_TEAM)
461 {
462 *buffer = header->white_team;
463 *size = MAX_TEAM;
464 }
465 else if (type == PROP_DATE)
466 {
467 *buffer = header->date;
468 *size = MAX_DATE;
469 }
470 else if (type == PROP_ROUND)
471 {
472 *buffer = header->round;
473 *size = MAX_ROUND;
474 }
475 else if (type == PROP_EVENT)
476 {
477 *buffer = header->event;
478 *size = MAX_EVENT;
479 }
480 else if (type == PROP_PLACE)
481 {
482 *buffer = header->place;
483 *size = MAX_PLACE;
484 }
485 else if (type == PROP_OVERTIME)
486 {
487 *buffer = header->overtime;
488 *size = MAX_OVERTIME;
489 }
490 else if (type == PROP_RESULT)
491 {
492 *buffer = header->result;
493 *size = MAX_RESULT;
494 }
495 else if (type == PROP_RULESET)
496 {
497 *buffer = header->ruleset;
498 *size = MAX_RULESET;
499 }
500 else
501 {
502 return false;
503 }
504
505 return true;
506}
507
508
509/* TEST CODE BEGINS HERE define GBN_TEST to run this, either in goban.h or
510 in the CFLAGS. The tests will be run when the plugin starts, after
511 which the plugin will exit. Any error stops testing since many tests
512 depend on previous setup. Note: The testing can take a while as there
513 are some big loops. Be patient. */
514
515#ifdef GBN_TEST
516
517#include "goban.h"
518#include "types.h"
519#include "board.h"
520#include "game.h"
521#include "sgf.h"
522#include "sgf_storage.h"
523
524/* If this isn't on a single line, the line numbers it reports will be wrong.
525 *
526 * I'm sure there's a way to make it better, but it's not really worth it.
527 */
528#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;}
529
530void
531run_tests (void)
532{
533 rb->splash (3 * HZ, "Running tests. Failures will stop testing.");
534
535
536
537 /* allocating and freeing storage units */
538
539 gbn_assert (alloc_storage_sgf ());
540
541 int prevent_infinite = 100000000;
542
543 int count = 1;
544 while (alloc_storage_sgf () >= 0 && --prevent_infinite)
545 {
546 ++count;
547 }
548
549 gbn_assert (prevent_infinite);
550 gbn_assert (count > 100);
551
552 /* make sure it fails a few times */
553 gbn_assert (alloc_storage_sgf () < 0);
554 gbn_assert (alloc_storage_sgf () < 0);
555 gbn_assert (alloc_storage_sgf () < 0);
556
557 free_storage_sgf (0);
558
559 gbn_assert (alloc_storage_sgf () == 0);
560
561 gbn_assert (alloc_storage_sgf () < 0);
562
563 int i;
564 for (i = 0; i <= count; ++i)
565 {
566 free_storage_sgf (i);
567 }
568
569 gbn_assert (alloc_storage_sgf () >= 0);
570 --count;
571
572 for (i = 0; i < count; ++i)
573 {
574 gbn_assert (alloc_storage_sgf () >= 0);
575 }
576
577 free_tree_sgf ();
578
579
580
581 /* setting up, saving and loading */
582 gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, 15));
583 gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, -30));
584 gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 4, 1));
585 gbn_assert (setup_game (MIN_BOARD_SIZE, MIN_BOARD_SIZE, 1, 1));
586
587 gbn_assert (setup_game (MIN_BOARD_SIZE, MAX_BOARD_SIZE, 1, 1));
588 gbn_assert (setup_game (MAX_BOARD_SIZE, MIN_BOARD_SIZE, 1, 1));
589
590 gbn_assert (!setup_game (MAX_BOARD_SIZE + 1, MAX_BOARD_SIZE + 1, 0, 15));
591 gbn_assert (!setup_game (MIN_BOARD_SIZE - 1, MIN_BOARD_SIZE - 1, 0, 15));
592 gbn_assert (!setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, -1, 15));
593
594 gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 1, 1));
595 gbn_assert (save_game (DEFAULT_SAVE_DIR "/t1.sgf"));
596 gbn_assert (load_game (DEFAULT_SAVE_DIR "/t1.sgf"));
597 gbn_assert (save_game (DEFAULT_SAVE_DIR "/t2.sgf"));
598 gbn_assert (load_game (DEFAULT_SAVE_DIR "/t2.sgf"));
599
600 gbn_assert (!save_game ("/DIR_DOESNT_EXIST/blah.sgf"));
601 gbn_assert (!load_game ("/DIR_DOESNT_EXIST/blah.sgf"));
602 gbn_assert (!load_game (DEFAULT_SAVE_DIR "/DOESNT_EXIST.sgf"));
603
604
605
606 /* test of a long game, captures, illegal moves */
607 gbn_assert (load_game (DEFAULT_SAVE_DIR "/long.sgf"));
608 while (move_num < 520)
609 {
610 gbn_assert (num_variations_sgf () == 1);
611 gbn_assert (redo_node_sgf ());
612 }
613
614 gbn_assert (play_move_sgf (POS (2, 0), BLACK));
615 gbn_assert (play_move_sgf (POS (2, 1), WHITE));
616
617 gbn_assert (move_num == 522);
618
619 gbn_assert (white_captures == 261 && black_captures == 0);
620
621 gbn_assert (play_move_sgf (PASS_POS, BLACK));
622 gbn_assert (play_move_sgf (PASS_POS, WHITE));
623
624 gbn_assert (move_num == 524);
625
626 int x, y;
627 int b_count, w_count, e_count;
628 b_count = w_count = e_count = 0;
629 for (x = 0; x < 19; ++x)
630 {
631 for (y = 0; y < 19; ++y)
632 {
633 gbn_assert (!legal_move_board (POS (x, y), BLACK, false));
634 gbn_assert (!play_move_sgf (POS (x, y), BLACK));
635 switch (get_point_board (POS (x, y)))
636 {
637 case BLACK:
638 ++b_count;
639 break;
640 case WHITE:
641 ++w_count;
642 break;
643 case EMPTY:
644 ++e_count;
645 break;
646 default:
647 gbn_assert (false);
648 }
649 }
650 }
651
652 gbn_assert (b_count == 0 && w_count == 261 && e_count == 19 * 19 - 261);
653
654 gbn_assert (undo_node_sgf ());
655 gbn_assert (move_num == 523);
656
657 int infinite_prevention = 0;
658 while (move_num > 0)
659 {
660 gbn_assert (undo_node_sgf ());
661
662 ++infinite_prevention;
663 gbn_assert (infinite_prevention < 100000);
664 }
665
666 gbn_assert (save_game (DEFAULT_SAVE_DIR "/long_out.sgf"));
667
668
669 /* test of basic moves, legal moves, adding and removing stones */
670 gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, 0));
671 gbn_assert (play_move_sgf
672 (POS (MAX_BOARD_SIZE / 2, MAX_BOARD_SIZE / 2), BLACK));
673 gbn_assert (move_num == 1 && current_player == WHITE);
674 gbn_assert (!legal_move_board
675 (POS (MAX_BOARD_SIZE / 2, MAX_BOARD_SIZE / 2), WHITE, true));
676
677 int saved_node = current_node;
678 gbn_assert (add_stone_sgf (POS (0, 0), BLACK));
679 gbn_assert (current_node != saved_node);
680 gbn_assert (get_point_board (POS (0, 0)) == BLACK);
681 gbn_assert (move_num == 1 && current_player == WHITE);
682
683 saved_node = current_node;
684 gbn_assert (add_stone_sgf (POS (0, 1), WHITE));
685 gbn_assert (current_node == saved_node);
686 gbn_assert (get_point_board (POS (0, 1)) == WHITE);
687
688 gbn_assert (add_stone_sgf (POS (0, 0), EMPTY));
689 gbn_assert (add_stone_sgf (POS (0, 1), EMPTY));
690 gbn_assert (get_point_board (POS (0, 0)) == EMPTY);
691 gbn_assert (get_point_board (POS (0, 1)) == EMPTY);
692
693
694 /* test captures */
695 gbn_assert (load_game (DEFAULT_SAVE_DIR "/cap.sgf"));
696 gbn_assert (play_move_sgf (POS (0, 0), BLACK));
697 gbn_assert (black_captures == 8);
698 gbn_assert (undo_node_sgf ());
699 gbn_assert (black_captures == 0);
700
701 gbn_assert (!play_move_sgf (POS (0, 0), WHITE));
702 play_mode = MODE_FORCE_PLAY;
703 gbn_assert (play_move_sgf (POS (0, 0), WHITE));
704 play_mode = MODE_PLAY;
705
706 gbn_assert (black_captures == 9);
707 gbn_assert (get_point_board (POS (0, 0)) == EMPTY);
708 gbn_assert (undo_node_sgf ());
709 gbn_assert (black_captures == 0);
710
711 gbn_assert (play_move_sgf (POS (9, 9), BLACK));
712 gbn_assert (black_captures == 44);
713
714 for (x = 0; x < 19; ++x)
715 {
716 for (y = 0; y < 19; ++y)
717 {
718 gbn_assert (get_point_board (POS (x, y)) == BLACK ||
719 add_stone_sgf (POS (x, y), BLACK));
720 }
721 }
722
723 gbn_assert (get_point_board (POS (0, 0)) == BLACK);
724 gbn_assert (add_stone_sgf (POS (9, 9), EMPTY));
725 gbn_assert (play_move_sgf (POS (9, 9), WHITE));
726 gbn_assert (white_captures == 360);
727
728 gbn_assert (undo_node_sgf ());
729 gbn_assert (white_captures == 0);
730
731 play_mode = MODE_FORCE_PLAY;
732 gbn_assert (play_move_sgf (POS (9, 9), BLACK));
733 play_mode = MODE_PLAY;
734 gbn_assert (white_captures == 361);
735
736 for (x = 0; x < 19; ++x)
737 {
738 for (y = 0; y < 19; ++y)
739 {
740 gbn_assert (get_point_board (POS (x, y)) == EMPTY);
741 }
742 }
743
744
745 /* test ko */
746 gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, 15));
747
748 /*
749 * Set up the board to look like this:
750 * -X------
751 * XO------
752 * O-------
753 * --------
754 */
755 gbn_assert (add_stone_sgf (POS (0, 1), BLACK));
756 gbn_assert (add_stone_sgf (POS (1, 0), BLACK));
757 gbn_assert (add_stone_sgf (POS (1, 1), WHITE));
758 gbn_assert (add_stone_sgf (POS (0, 2), WHITE));
759
760 /* take the ko and make sure black can't take back */
761 gbn_assert (play_move_sgf (POS (0, 0), WHITE));
762 gbn_assert (!play_move_sgf (POS (0, 1), BLACK));
763
764 /* make sure white can fill, even with the ko_pos set */
765 gbn_assert (play_move_sgf (POS (0, 1), WHITE));
766 /* and make sure undo sets the ko again */
767 gbn_assert (undo_node_sgf ());
768 gbn_assert (!play_move_sgf (POS (0, 1), BLACK));
769
770 /* make sure ko threats clear the ko */
771 gbn_assert (play_move_sgf (POS (2, 2), BLACK)); /* ko threat */
772 gbn_assert (play_move_sgf (POS (2, 3), WHITE)); /* response */
773 gbn_assert (play_move_sgf (POS (0, 1), BLACK)); /* take ko */
774
775 gbn_assert (undo_node_sgf ());
776 gbn_assert (undo_node_sgf ());
777 gbn_assert (undo_node_sgf ());
778
779 /* make sure a pass is counted as a ko threat */
780 gbn_assert (!play_move_sgf (POS (0, 1), BLACK));
781 gbn_assert (play_move_sgf (PASS_POS, BLACK));
782 gbn_assert (play_move_sgf (PASS_POS, WHITE));
783 gbn_assert (play_move_sgf (POS (0, 1), BLACK));
784
785 /* and finally let's make sure that white can't directly retake */
786 gbn_assert (!play_move_sgf (POS (0, 0), WHITE));
787
788
789
790 /* test some header information saving/loading as well as comment
791 saving loading */
792 char some_comment[] =
793 "blah blah blah i am a stupid comment. here's some annoying characters: 01234567890!@#$%^&*()[[[[\\\\\\]ABCDEFGHIJKLMNOPQRSTUVWXYZ";
794 /* that bit near the end is literally this: \\\] which tests escaping
795 of ]s */
796 char read_buffer[256];
797
798 gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 5, -20));
799
800 /* this also tests that ko_pos is reset by setuping up a new game */
801 gbn_assert (play_move_sgf (POS (0, 0), WHITE));
802 gbn_assert (write_comment_sgf (some_comment) > 0);
803 gbn_assert (play_move_sgf (POS (0, 1), BLACK));
804 rb->strcpy (header.black, "Jack Black");
805 rb->strcpy (header.white, "Jill White");
806
807 gbn_assert (save_game (DEFAULT_SAVE_DIR "/head.sgf"));
808
809 gbn_assert (setup_game (MIN_BOARD_SIZE, MIN_BOARD_SIZE, 1, 1));
810 gbn_assert (load_game (DEFAULT_SAVE_DIR "/head.sgf"));
811
812 gbn_assert (header.komi == -20 && header.handicap == 5);
813 gbn_assert (board_width == MAX_BOARD_SIZE
814 && board_height == MAX_BOARD_SIZE);
815 gbn_assert (rb->strcmp (header.black, "Jack Black") == 0);
816 gbn_assert (rb->strcmp (header.white, "Jill White") == 0);
817 gbn_assert (redo_node_sgf ());
818 gbn_assert (read_comment_sgf (read_buffer, sizeof (read_buffer)));
819 gbn_assert (rb->strcmp (read_buffer, some_comment) == 0);
820 gbn_assert (redo_node_sgf ());
821 gbn_assert (get_point_board (POS (0, 0)) == WHITE);
822 gbn_assert (get_point_board (POS (0, 1)) == BLACK);
823
824
825
826 /* test saving and loading a file with unhandled SGF properties. this
827 test requires that the user diff unhnd.sgf with unhnd_out.sgf (any
828 substantial difference is a bug and should be reported) the
829 following are NOT substantial differences: - reordering of
830 properties in a node - whitespace changes outside of a comment
831 value or other property value - reordering of property values */
832 gbn_assert (load_game (DEFAULT_SAVE_DIR "/unhnd.sgf"));
833 gbn_assert (save_game (DEFAULT_SAVE_DIR "/unhnd_out.sgf"));
834
835
836
837 /* Test variations a bit */
838 gbn_assert (setup_game (MAX_BOARD_SIZE, MAX_BOARD_SIZE, 0, 13));
839 /* start at a move, otherwise add_stone won't create a variation */
840 gbn_assert (play_move_sgf (POS (5, 5), BLACK));
841 /* make sure it doesn't */
842 gbn_assert (undo_node_sgf ());
843 gbn_assert (add_stone_sgf (POS (4, 5), WHITE));
844 gbn_assert (!undo_node_sgf ());
845 gbn_assert (num_variations_sgf () == 1);
846 gbn_assert (play_move_sgf (POS (5, 5), BLACK));
847
848 gbn_assert (play_move_sgf (POS (0, 0), BLACK));
849 gbn_assert (num_variations_sgf () == 1);
850 gbn_assert (undo_node_sgf ());
851 gbn_assert (play_move_sgf (POS (0, 1), BLACK));
852 gbn_assert (num_variations_sgf () == 2);
853 gbn_assert (undo_node_sgf ());
854 gbn_assert (play_move_sgf (POS (0, 1), BLACK));
855 gbn_assert (num_variations_sgf () == 2);
856 gbn_assert (undo_node_sgf ());
857 gbn_assert (play_move_sgf (POS (0, 2), BLACK));
858 gbn_assert (num_variations_sgf () == 3);
859 gbn_assert (undo_node_sgf ());
860 gbn_assert (play_move_sgf (POS (0, 3), WHITE));
861 gbn_assert (num_variations_sgf () == 4);
862 gbn_assert (undo_node_sgf ());
863 gbn_assert (play_move_sgf (PASS_POS, BLACK));
864 gbn_assert (num_variations_sgf () == 5);
865 gbn_assert (undo_node_sgf ());
866 gbn_assert (add_stone_sgf (POS (1, 1), BLACK));
867 gbn_assert (add_stone_sgf (POS (1, 2), BLACK));
868 gbn_assert (add_stone_sgf (POS (1, 3), WHITE));
869 gbn_assert (num_variations_sgf () == 6);
870 gbn_assert (undo_node_sgf ());
871 gbn_assert (add_stone_sgf (POS (1, 1), BLACK));
872 gbn_assert (add_stone_sgf (POS (1, 2), BLACK));
873 gbn_assert (add_stone_sgf (POS (1, 3), WHITE));
874 gbn_assert (num_variations_sgf () == 7);
875 gbn_assert (next_variation_sgf ());
876 gbn_assert (get_point_board (POS (0, 0)) == BLACK);
877 gbn_assert (get_point_board (POS (0, 1)) == EMPTY);
878 gbn_assert (get_point_board (POS (0, 2)) == EMPTY);
879 gbn_assert (get_point_board (POS (1, 1)) == EMPTY);
880 gbn_assert (get_point_board (POS (1, 2)) == EMPTY);
881 gbn_assert (get_point_board (POS (1, 3)) == EMPTY);
882
883 rb->splash (10 * HZ, "All tests passed. Exiting");
884}
885#endif /* GBN_TEST */
diff --git a/apps/plugins/goban/util.h b/apps/plugins/goban/util.h
new file mode 100644
index 0000000000..83dc880ac7
--- /dev/null
+++ b/apps/plugins/goban/util.h
@@ -0,0 +1,113 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#ifndef GOBAN_UTIL_H
23#define GOBAN_UTIL_H
24
25#include "types.h"
26#include "goban.h"
27
28/* Call before using a stack, returns false on setup failure */
29bool setup_stack (struct stack_t *stack, void *buffer, size_t buffer_size);
30
31/* Push, pop or peek from the stack. Returns false on failure (usually
32 stack full or empty, depending on the function) */
33bool push_stack (struct stack_t *stack, void *buffer, size_t buffer_size);
34bool pop_stack (struct stack_t *stack, void *buffer, size_t buffer_size);
35bool peek_stack (struct stack_t *stack, void *buffer, size_t buffer_size);
36
37/* Clear all of the data from the stack and move the stack pointer to the
38 beginning */
39void empty_stack (struct stack_t *stack);
40
41/* Convenience functions for pushing/poping/peeking standard value types
42 to a stack */
43#define pop_pos_stack(stack, pos) pop_stack(stack, pos, sizeof (unsigned short))
44#define peek_pos_stack(stack, pos) peek_stack(stack, pos, sizeof (unsigned short))
45
46#define pop_int_stack(stack, num) pop_stack(stack, num, sizeof (int))
47#define peek_int_stack(stack, num) peek_stack(stack, num, sizeof (int))
48
49#define pop_char_stack(stack, num) pop_stack(stack, num, sizeof (char))
50#define peek_char_stack(stack, num) peek_stack(stack, num, sizeof (char))
51
52bool push_pos_stack (struct stack_t *stack, unsigned short pos);
53bool push_int_stack (struct stack_t *stack, int num);
54bool push_char_stack (struct stack_t *stack, char num);
55
56
57#define min(x, y) (x < y ? x : y)
58#define max(x, y) (x > y ? x : y)
59
60/* Returns the fd of the file */
61int create_or_open_file (const char *filename);
62
63/* Returns the number of characters printed */
64int snprint_fixed (char *buffer, int buffer_size, int fixed);
65
66/* These should all be obvious, they are simply wrappers on the normal
67 rockbox file functions which will loop several times if the rockbox
68 file functions don't deal with all of the data at once */
69ssize_t read_file (int fd, void *buf, size_t count);
70ssize_t write_file (int fd, const void *buf, size_t count);
71void close_file (int *fd);
72
73int peek_char (int fd);
74int read_char (int fd);
75bool write_char (int fd, char to_write);
76
77/* Seek to the next non-whitespace character (doesn't go anywhere if the
78 current character is already non-whitespace), and then peek it -1 on
79 EOF or error */
80int peek_char_no_whitespace (int fd);
81/* Same deal, with reading -1 on EOF or error */
82int read_char_no_whitespace (int fd);
83
84/* Returns true if a character is whitespace. Should /NOT/ be called with
85 anything but the return value of one of the peeking/reading functions or
86 a standard character. */
87bool is_whitespace (int value);
88
89/* Gets rid of ']' characdters from the string by overwritting them. This
90 is needed in header strings because they would otherwise corrupt the
91 SGF file when outputted */
92void sanitize_string (char *string);
93
94/* Return an aligned version of the bufer, with the size updated Returns
95 NULL if the buffer is too small to align. */
96void *align_buffer (void *buffer, size_t * buffer_size);
97
98/* Get the string and buffer size for a SGF property which is stored in
99 the header. Returns false on failure, in which case any information set
100 in **buffer and *size are not to be trusted or used. */
101bool get_header_string_and_size (struct header_t *header,
102 enum prop_type_t type,
103 char **buffer, int *size);
104
105/* Output a summary of the game metadata (Game Info)
106 */
107void metadata_summary (void);
108
109#ifdef GBN_TEST
110void run_tests (void);
111#endif
112
113#endif
diff --git a/apps/plugins/viewers.config b/apps/plugins/viewers.config
index 2ec8fe9299..3d5d7e1d35 100644
--- a/apps/plugins/viewers.config
+++ b/apps/plugins/viewers.config
@@ -19,6 +19,7 @@ rmi,viewers/midi,7
19rsp,viewers/searchengine,8 19rsp,viewers/searchengine,8
20sok,games/sokoban,1 20sok,games/sokoban,1
21pgn,games/chessbox,1 21pgn,games/chessbox,1
22sgf,games/goban,1
22ss,games/sudoku,1 23ss,games/sudoku,1
23wav,viewers/wav2wv,- 24wav,viewers/wav2wv,-
24wav,viewers/mp3_encoder,- 25wav,viewers/mp3_encoder,-
diff --git a/docs/CREDITS b/docs/CREDITS
index 4df253feb3..0a0b05da75 100644
--- a/docs/CREDITS
+++ b/docs/CREDITS
@@ -448,6 +448,7 @@ Ryan Press
448Craig Elliott 448Craig Elliott
449Kenderes Tamas 449Kenderes Tamas
450Eric Shattow 450Eric Shattow
451Joshua Simmons
451 452
452 453
453The libmad team 454The libmad team
diff --git a/manual/plugins/goban.tex b/manual/plugins/goban.tex
new file mode 100644
index 0000000000..45ac980afc
--- /dev/null
+++ b/manual/plugins/goban.tex
@@ -0,0 +1,243 @@
1\subsection{Goban}
2\screenshot{plugins/images/ss-goban}{Goban}{The Rockbox Goban plugin}
3Goban is a a plugin for playing, viewing and recording games of Go (also known
4as Weiqi, Baduk, Igo and Goe). It uses standard Smart Game Format (SGF) files
5for saving and loading games.
6
7You can find a short introduction to Go at
8\url{http://senseis.xmp.net/?WhatIsGo} and more information about SGF files
9can be read at \url{http://senseis.xmp.net/?SmartGameFormat} or the SGF
10specification at
11\url{http://www.red-bean.com/sgf/}.
12
13This plugin can load all modern SGF files (file format 3 or 4) with few problems.
14It attempts to preserve SGF properties which it doesn't understand, and most common
15SGF properties are handled fully. It is possible to view (and edit if you like)
16Kogo's Joseki Dictionary (\url{http://waterfire.us/joseki.htm}) with this plugin,
17although the load and save times can be on the order of a minute or two on
18particularly slow devices. Large SGF files may stop audio playback for the duration
19of the plugin's run in order to free up more memory and some very large SGF files will
20not even load on devices with little available memory.
21
22\emph{Note: } The plugin does \emph{NOT} support SGF files with multiple games in
23one file. These are rare, but if you have one don't even try it (the file will most
24likely be corrupted if you save over it). You have been warned.
25
26The file \fname {"/sgf/gbn\_def.sgf"} is used by the plugin to store any unsaved
27changes in the most recently loaded game. This means that if you forget to save your
28changes, you should load \fname {"/sgf/gbn\_def.sgf"} immediately to offload the changes
29to another file. If you load another file first then your changes will be lost
30permanently. The \fname {"/sgf/gbn\_def.sgf"} file is also the file loaded if another
31is not selected.
32
33The information panel which displays the current move number may also contain
34these markers: \\
35\begin{tabularx}{\textwidth}{lX}\toprule
36\textbf{Mark} & \textbf{Meaning} \\ \midrule
37 \emph{+ } & There are nodes after the current node in the SGF tree. \\
38 \emph{* } & There are sibling variations which can be navigated to using the %
39 \emph{Next Variation} menu option of the \emph{Context Menu}%
40 \opt{SANSA_E200_PAD,SANSA_C200_PAD,SANSA_FUZE_PAD,RECORDER_PAD,%
41 MROBE100_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD,IRIVER_H100_PAD,%
42 IRIVER_H300_PAD}{ or the %
43 \opt{SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD}{\ButtonRec}%
44 \opt{RECORDER_PAD}{\ButtonOn}%
45 \opt{MROBE100_PAD}{\ButtonPower}%
46 \opt{GIGABEAT_PAD}{\ButtonA}%
47 \opt{GIGABEAT_S_PAD}{\ButtonPlay}%
48 \opt{IRIVER_H100_PAD,IRIVER_H300_PAD}{\ButtonRec} button}. \\
49 \emph{C } & There is a comment at the current node. It can be viewed/edited using
50 the \emph{Add/Edit Comment} menu option of the \emph{Context Menu}. \\
51\bottomrule
52\end{tabularx}
53
54\subsection{Controls}
55\begin{table}
56 \begin{btnmap}{}{}
57 \opt{SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,GIGABEAT_PAD,%
58 GIGABEAT_S_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,MROBE100_PAD,%
59 IAUDIO_X5_PAD,RECORDER_PAD,ONDIO_PAD}{\ButtonUp}%
60 \opt{IPOD_1G2G_PAD,IPOD_3G_PAD,IPOD_4G_PAD}{\ButtonMenu}%
61 \opt{IRIVER_H10_PAD}{\ButtonScrollUp} & Move cursor up \\
62 \opt{SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD,
63 IRIVER_H100_PAD,IRIVER_H300_PAD,MROBE100_PAD,IAUDIO_X5_PAD,RECORDER_PAD,
64 ONDIO_PAD}{\ButtonDown}%
65 \opt{IPOD_1G2G_PAD,IPOD_3G_PAD,IPOD_4G_PAD}{\ButtonPlay}%
66 \opt{IRIVER_H10_PAD}{\ButtonScrollDown} & Move cursor down \\
67 \opt{SANSA_E200_PAD,SANSA_FUZE_PAD,IPOD_1G2G_PAD,IPOD_3G_PAD,IPOD_4G_PAD,%
68 SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD,IRIVER_H10_PAD,IRIVER_H100_PAD,%
69 IRIVER_H300_PAD,MROBE100_PAD,IAUDIO_X5_PAD,RECORDER_PAD,%
70 ONDIO_PAD}{\ButtonLeft} & Move cursor left %
71 \opt{ONDIO_PAD}{if in \emph{board} navigation mode, or %
72 retreat one node in the game tree if in %
73 \emph{tree} navigation mode} \\
74 \opt{SANSA_E200_PAD,SANSA_FUZE_PAD,IPOD_1G2G_PAD,IPOD_3G_PAD,IPOD_4G_PAD,%
75 SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD,IRIVER_H10_PAD,IRIVER_H100_PAD,%
76 IRIVER_H300_PAD,MROBE100_PAD,IAUDIO_X5_PAD,RECORDER_PAD,%
77 ONDIO_PAD}{\ButtonRight} & Move cursor right
78 \opt{ONDIO_PAD}{if in \emph{board} navigation mode, or advance one node in
79 the game tree if in \emph{tree} navigation mode} \\
80 \opt{ONDIO_PAD}{{\ButtonOff} & Toggle between \emph{board} and \emph{tree}
81 navigation modes \\}
82 \opt{SANSA_E200_PAD,SANSA_FUZE_PAD,IPOD_1G2G_PAD,IPOD_3G_PAD,IPOD_4G_PAD,%
83 SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,%
84 MROBE100_PAD,IAUDIO_X5_PAD}{\ButtonSelect}%
85 \opt{IRIVER_H10_PAD,RECORDER_PAD}{\ButtonPlay}%
86 \opt{ONDIO_PAD}{\ButtonMenu} & Play a move (or use a tool if play-mode has
87 been changed). \\
88 \nopt{ONDIO_PAD}{
89 \opt{SANSA_E200_PAD,SANSA_FUZE_PAD,IPOD_1G2G_PAD,IPOD_3G_PAD,%
90 IPOD_4G_PAD}{\ButtonScrollBack}%
91 \opt{SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD}{\ButtonVolDown}%
92 \opt{IRIVER_H10_PAD}{\ButtonFF}%
93 \opt{IRIVER_H100_PAD,IRIVER_H300_PAD}{\ButtonOff}%
94 \opt{MROBE100_PAD}{\ButtonMenu}%
95 \opt{IAUDIO_X5_PAD}{\ButtonPlay}%
96 \opt{RECORDER_PAD}{\ButtonFOne} & Retreat one node in the game tree \\
97 \opt{SANSA_E200_PAD,SANSA_FUZE_PAD,IPOD_1G2G_PAD,IPOD_3G_PAD,IPOD_4G_PAD,%
98 IPOD_1G2G_PAD,IPOD_3G_PAD,IPOD_4G_PAD}{\ButtonScrollFwd}%
99 \opt{SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD}{\ButtonVolUp}%
100 \opt{IRIVER_H10_PAD}{\ButtonRew}%
101 \opt{IRIVER_H100_PAD,IRIVER_H300_PAD}{\ButtonOn}%
102 \opt{MROBE100_PAD}{\ButtonPlay}%
103 \opt{IAUDIO_X5_PAD}{\ButtonRec}%
104 \opt{RECORDER_PAD}{\ButtonFThree} & Advance one node in the game tree \\ }
105 \opt{SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,IRIVER_H10_PAD,%
106 IAUDIO_X5_PAD}{\ButtonPower}%
107 \opt{MROBE100_PAD}{\ButtonDisplay}%
108 \opt{IPOD_1G2G_PAD,IPOD_3G_PAD,IPOD_4G_PAD}{Long \ButtonSelect}%
109 \opt{GIGABEAT_PAD,GIGABEAT_S_PAD}{\ButtonMenu}%
110 \opt{IRIVER_H100_PAD,IRIVER_H300_PAD}{\ButtonMode}%
111 \opt{RECORDER_PAD}{\ButtonFTwo}%
112 \opt{ONDIO_PAD}{Long \ButtonMenu} & Main Menu \\
113 \opt{SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD,%
114 IRIVER_H100_PAD,IRIVER_H300_PAD,MROBE100_PAD,IAUDIO_X5_PAD,%
115 IRIVER_H10_PAD}{%
116 \opt{SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,GIGABEAT_PAD,%
117 GIGABEAT_S_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,MROBE100_PAD,%
118 IAUDIO_X5_PAD}{Long \ButtonSelect}%
119 \opt{IRIVER_H10_PAD}{Long \ButtonPlay} & Context Menu \\ }
120 \opt{SANSA_E200_PAD,SANSA_C200_PAD,SANSA_FUZE_PAD,RECORDER_PAD,MROBE100_PAD,%
121 GIGABEAT_PAD,GIGABEAT_S_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD}{%
122 \opt{SANSA_E200_PAD,SANSA_FUZE_PAD}{\ButtonRec}%
123 \opt{SANSA_C200_PAD}{\ButtonRec}%
124 \opt{RECORDER_PAD}{\ButtonOn}%
125 \opt{MROBE100_PAD}{\ButtonPower}%
126 \opt{GIGABEAT_PAD}{\ButtonA}%
127 \opt{GIGABEAT_S_PAD}{\ButtonPlay}%
128 \opt{IRIVER_H100_PAD,IRIVER_H300_PAD}{\ButtonRec} & Go to the next variation %
129 when at the first node in %
130 a branch \\ }
131 \end{btnmap}
132\end{table}
133
134\subsection{Menus}
135\begin {description}
136\item [Main Menu. ]
137 The main menu for game setup and access to other menus.
138
139 \emph {New. } Create a new game with your choice of board size and handicaps. \\
140 \emph {Save. } Save the current state of the game. It will be saved to
141 \fname {"/sgf/gbn\_def.sgf"} unless otherwise set. \\
142 \emph {Save As. } Save to a specified file. \\
143 \emph {Game Info. } View and modify the metadata of the current game. \\
144 \emph {Playback Control. } Control the playback of the current playlist and
145 modify the volume of your player. \\
146 \emph {Zoom Level. } Zoom in or out on the board. If you set the zoom level, it
147 will be saved and used again the next time you open this plugin. \\
148 \emph {Options. } Open the Options Menu. \\
149 \emph {Context Menu. } Open the Context Menu which allows you to set play modes
150 and other tools. \\
151 \emph {Quit. } Leave the plugin. Any unsaved changes are saved to
152 \fname {"/sgf/gbn\_def.sgf"}. \\
153
154\item [Game Info. ]
155 The menu for modifying game info (metadata) of the current game. This
156 information will be saved to the SGF file and can be viewed in almost all
157 SGF readers.
158
159 \emph {Basic Info. } Shows a quick view of the basic game metadata, if any
160 has been set (otherwise does nothing). This option does not allow editing. \\
161 \emph {Time Limit. } The time limit of the current game. \\
162 \emph {Overtime. } The overtime settings of the current game. \\
163 \emph {Result. } The result of the current game. This text must follow the
164 format specified at \url{http://www.red-bean.com/sgf/properties.html#RE} to
165 be read by other SGF readers. Some examples are \emph {B+R} (Black wins by
166 resignation), \emph {B+5.5} (Black wins by 5.5 points), \emph {W+T} (White wins
167 on Time). \\
168 \emph {Handicap. } The handicap of the current game. \\
169 \emph {Komi. } The komi of the current game (compensation to the white
170 player for black having the first move). \\
171 \emph {Ruleset. } The name of the ruleset in use for this game. The \emph{NZ}
172 and \emph{GOE} rulesets include suicide as a legal move (for multi-stone
173 suicide only); the rest do not. \\
174 \emph {Black Player. } The name of the black player. \\
175 \emph {Black Rank. } Black's rank, in dan or kyu. \\
176 \emph {Black Team. } The name of black's team, if any. \\
177 \emph {White Player. } The name of the white player. \\
178 \emph {White Rank. } White's rank, in dan or kyu. \\
179 \emph {White Team. } The name of white's team, if any. \\
180 \emph {Date. } The date that this game took place. This text must follow the
181 format specified at \url{http://www.red-bean.com/sgf/properties.html#DT} to
182 be read by other SGF readers. \\
183 \emph {Event. } The name of the event which this game was a part of, if any.
184 \\
185 \emph {Place. } The place that this game took place. \\
186 \emph {Round. } If part of a tournament, the round number for this game. \\
187 \emph {Done. } Return to the previous menu. \\
188
189\item [Options. ]
190 Customize the behavior of the plugin in certain ways.
191
192 \emph {Show Child Variations? } Enable this to mark child variations on the board
193 if there are more than one. Note: variations which don't start with a move are
194 not visible in this way. \\
195 \emph {Disable Idle Poweroff? } Enable this if you do not want the \dap{} to turn
196 off after a certain period of inactivity (depends on your global Rockbox
197 settings). \\
198 \emph {Idle Autosave Time. } Set the amount of idle time to wait before
199 automatically saving any unsaved changes. These autosaves go to the file
200 \fname {"/sgf/gbn\_def.sgf"} regardless of if you have loaded a game or used
201 \emph {Save As} to save the game before or not. Set to \emph {Off} to disable
202 this functionality completely. \\
203 \emph {Automatically Show Comments? } If this is enabled and you navigate to a
204 node containing game comments, they will automatically be displayed. \\
205
206\item [Context Menu. ]
207 The menu for choosing different play modes and tools, adding or editing
208 comments, adding pass moves, or switching between sibling variations.
209
210 \emph {Play Mode. } Play moves normally on the board. If there are
211 child moves from the current node, this mode will let you follow variations
212 by simply playing the first move in the sequence. Unless it is following a
213 variation, this mode will not allow you to play illegal moves. This is the
214 default mode before another is set after loading a game or creating a new
215 one. \\
216 \emph {Add Black Mode. } Add black stones to the board as desired. These
217 stones are not moves and do not perform captures or count as ko threats. \\
218 \emph {Add White Mode. } Add white stones to the board as desired. These
219 stones are not moves and do not perform captures or count as ko threats. \\
220 \emph {Erase Stone Mode. } Remove stones from the board as desired. These
221 removed stones are not counted as captured, they are simply removed. \\
222 \emph {Pass. } Play a single pass move. This does not change the mode of
223 play. \\
224 \emph {Next Variation. } If the game is at the first move in a variation,
225 this will navigate to the next variation after the current one. This is
226 the only way to reach variations which start with adding or removing
227 stones, as you cannot follow them by "playing" the same move. \\
228 \emph {Force Play Mode. } The same as Play Mode except that this mode will
229 allow you to play illegal moves such as retaking a ko immediately without a
230 ko threat, suicide on rulesets which don't allow it (including single stone
231 suicide), and playing a move where there is already a stone. \\
232 \emph {Mark Mode. } Add generic marks to the board, or remove them. \\
233 \emph {Circle Mode. } Add circle marks to the board, or remove them. \\
234 \emph {Square Mode. } Add square marks to the board, or remove them. \\
235 \emph {Triangle Mode. } Add triangle marks to the board, or remove them. \\
236 \emph {Label Mode. } Add one character labels to the board. Each label
237 starts at the letter 'a' and each subsequent application of a label will
238 increment the letter. To remove a label, click on it until it cycles
239 through the allowed letters and disappears. \\
240 \emph {Add/Edit Comment. } Add or edit a comment at the current node. \\
241 \emph {Done. } Go back to the previous screen. \\
242\end{description}
243
diff --git a/manual/plugins/main.tex b/manual/plugins/main.tex
index e8aadb093f..73e1648b94 100644
--- a/manual/plugins/main.tex
+++ b/manual/plugins/main.tex
@@ -35,6 +35,8 @@ text files%
35 35
36{\input{plugins/flipit.tex}} 36{\input{plugins/flipit.tex}}
37 37
38\opt{lcd_bitmap}{\input{plugins/goban.tex}}
39
38\opt{player}{\input{plugins/jackpot.tex}} 40\opt{player}{\input{plugins/jackpot.tex}}
39 41
40\opt{lcd_bitmap}{\input{plugins/jewels.tex}} 42\opt{lcd_bitmap}{\input{plugins/jewels.tex}}