diff options
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 @@ | |||
1 | goban.c | ||
2 | board.c | ||
3 | display.c | ||
4 | game.c | ||
5 | sgf.c | ||
6 | sgf_output.c | ||
7 | sgf_parse.c | ||
8 | sgf_storage.c | ||
9 | util.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 | |||
31 | unsigned int board_width = MAX_BOARD_SIZE; | ||
32 | unsigned int board_height = MAX_BOARD_SIZE; | ||
33 | |||
34 | /* Board has 'invalid' markers around each border */ | ||
35 | unsigned char board_data[(MAX_BOARD_SIZE + 2) * (MAX_BOARD_SIZE + 2)]; | ||
36 | int white_captures = 0; | ||
37 | int black_captures = 0; | ||
38 | |||
39 | uint8_t board_marks[(MAX_BOARD_SIZE + 2) * (MAX_BOARD_SIZE + 2)]; | ||
40 | uint8_t current_mark = 255; | ||
41 | |||
42 | /* there can't be any changes off of the board, so no need to add the | ||
43 | borders */ | ||
44 | uint8_t board_changes[MAX_BOARD_SIZE * MAX_BOARD_SIZE]; | ||
45 | |||
46 | unsigned short ko_pos = INVALID_POS; | ||
47 | |||
48 | /* forward declarations */ | ||
49 | static void setup_marks (void); | ||
50 | static void make_mark (unsigned short pos); | ||
51 | static int get_liberties_helper (unsigned short pos, unsigned char orig_color); | ||
52 | static 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) */ | ||
59 | static void | ||
60 | setup_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 | |||
80 | static void | ||
81 | make_mark (unsigned short pos) | ||
82 | { | ||
83 | board_marks[pos] = current_mark; | ||
84 | } | ||
85 | |||
86 | static bool | ||
87 | is_marked (unsigned short pos) | ||
88 | { | ||
89 | return board_marks[pos] == current_mark; | ||
90 | } | ||
91 | |||
92 | void | ||
93 | clear_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 | |||
118 | bool | ||
119 | set_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 | |||
135 | unsigned char | ||
136 | get_point_board (unsigned short pos) | ||
137 | { | ||
138 | return board_data[pos]; | ||
139 | } | ||
140 | |||
141 | void | ||
142 | set_point_board (unsigned short pos, unsigned char color) | ||
143 | { | ||
144 | board_data[pos] = color; | ||
145 | } | ||
146 | |||
147 | bool | ||
148 | on_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 | |||
162 | int | ||
163 | get_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 | |||
197 | static int | ||
198 | get_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 | |||
219 | int | ||
220 | flood_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 | |||
249 | static int | ||
250 | flood_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 | |||
263 | bool | ||
264 | legal_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 | |||
322 | unsigned short | ||
323 | WRAP (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 | |||
50 | unsigned 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 */ | ||
57 | void 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) */ | ||
62 | bool 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 */ | ||
66 | bool legal_move_board (unsigned short pos, unsigned char color, | ||
67 | bool allow_suicide); | ||
68 | |||
69 | /* Returns true if the pos is on the board */ | ||
70 | bool 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. */ | ||
74 | unsigned char get_point_board (unsigned short pos); | ||
75 | |||
76 | /* Set the color of point at pos, which must be on the board */ | ||
77 | void 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. */ | ||
82 | int 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. */ | ||
86 | int flood_fill_board (unsigned short pos, unsigned char color); | ||
87 | |||
88 | /* The size of the board */ | ||
89 | extern unsigned int board_width; | ||
90 | extern unsigned int board_height; | ||
91 | |||
92 | /* The number of captures for each player */ | ||
93 | extern int black_captures; | ||
94 | extern 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. */ | ||
98 | extern 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 | |||
31 | unsigned int intersection_size = 0; | ||
32 | |||
33 | #define LINE_OFFSET (intersection_size / 2) | ||
34 | |||
35 | /* pixel offsets for the board on the LCD */ | ||
36 | int board_x = 0; | ||
37 | int board_y = 0; | ||
38 | int board_pixel_width = 0; | ||
39 | int board_pixel_height = 0; | ||
40 | |||
41 | /* current cursor position in board coordinates (intersections, not | ||
42 | pixels) */ | ||
43 | unsigned 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 */ | ||
48 | unsigned short last_cursor_pos = INVALID_POS; | ||
49 | unsigned int last_int_size = MAX_INT_SIZE + 1; | ||
50 | |||
51 | unsigned int min_x_int = 0; | ||
52 | unsigned int min_y_int = 0; | ||
53 | unsigned int num_x_ints = 0; | ||
54 | unsigned int num_y_ints = 0; | ||
55 | |||
56 | int extend_t = 0, extend_b = 0, extend_l = 0, extend_r = 0; | ||
57 | |||
58 | bool draw_variations = true; | ||
59 | unsigned int saved_circle_size = 0; | ||
60 | bool has_comment = false; | ||
61 | |||
62 | unsigned char display_marks[MAX_BOARD_SIZE * MAX_BOARD_SIZE]; | ||
63 | |||
64 | |||
65 | /* function prototypes */ | ||
66 | |||
67 | static int pixel_x (unsigned short pos); | ||
68 | static int pixel_y (unsigned short pos); | ||
69 | |||
70 | static void draw_circle (int c_x, int c_y, int r, bool filled); | ||
71 | |||
72 | static void draw_cursor (unsigned short pos); | ||
73 | static void draw_stone_raw (int pixel_x, int pixel_y, bool black); | ||
74 | static void draw_stone (unsigned short pos, bool black); | ||
75 | static void draw_all_stones (void); | ||
76 | |||
77 | static void draw_hoshi (unsigned short pos); | ||
78 | static void draw_footer (void); | ||
79 | static void draw_all_marks (void); | ||
80 | static void draw_all_hoshi (void); | ||
81 | |||
82 | static unsigned int unzoomed_int_size (void); | ||
83 | static void cursor_updated (void); | ||
84 | |||
85 | void | ||
86 | clear_marks_display (void) | ||
87 | { | ||
88 | rb->memset (display_marks, ' ', sizeof (display_marks)); | ||
89 | has_comment = false; | ||
90 | } | ||
91 | |||
92 | void | ||
93 | set_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 | |||
112 | void | ||
113 | set_comment_display (bool new_val) | ||
114 | { | ||
115 | has_comment = new_val; | ||
116 | } | ||
117 | |||
118 | |||
119 | static void | ||
120 | draw_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 | |||
299 | static void | ||
300 | draw_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 | |||
368 | void | ||
369 | draw_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 */ | ||
447 | static void | ||
448 | vert_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 | |||
491 | static void | ||
492 | putsxy_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 | |||
520 | static void | ||
521 | draw_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 | |||
669 | static int | ||
670 | pixel_x (unsigned short pos) | ||
671 | { | ||
672 | return board_x + (I (pos) - min_x_int) * intersection_size; | ||
673 | } | ||
674 | |||
675 | static int | ||
676 | pixel_y (unsigned short pos) | ||
677 | { | ||
678 | return board_y + (J (pos) - min_y_int) * intersection_size; | ||
679 | } | ||
680 | |||
681 | |||
682 | |||
683 | void | ||
684 | move_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 | |||
715 | static void | ||
716 | cursor_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 | |||
793 | static unsigned int | ||
794 | unzoomed_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 | |||
812 | unsigned int | ||
813 | current_zoom_display (void) | ||
814 | { | ||
815 | return (intersection_size - unzoomed_int_size ()) / 2 + 1; | ||
816 | } | ||
817 | |||
818 | unsigned int | ||
819 | max_zoom_display (void) | ||
820 | { | ||
821 | return (MAX_INT_SIZE - unzoomed_int_size ()) / 2 + 1; | ||
822 | } | ||
823 | |||
824 | unsigned int | ||
825 | min_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 | |||
837 | void | ||
838 | set_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! */ | ||
885 | void | ||
886 | setup_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 | |||
915 | static void | ||
916 | draw_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 | |||
937 | static void | ||
938 | draw_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 | |||
982 | static void | ||
983 | draw_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 | |||
994 | static void | ||
995 | draw_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 | |||
1015 | static void | ||
1016 | draw_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 | |||
1048 | static void | ||
1049 | draw_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 */ | ||
29 | void setup_display (void); | ||
30 | |||
31 | /* Draw the board and the "footer" */ | ||
32 | void draw_screen_display (void); | ||
33 | |||
34 | /* The location of the cursor */ | ||
35 | extern unsigned short cursor_pos; | ||
36 | |||
37 | /* True if we should draw variations */ | ||
38 | extern bool draw_variations; | ||
39 | |||
40 | /* Used to set the zoom level, loaded in from the config file */ | ||
41 | unsigned int saved_circle_size; | ||
42 | |||
43 | /* the size of one intersection on the board, in pixels */ | ||
44 | unsigned int intersection_size; | ||
45 | |||
46 | /* Clear the marks from the board */ | ||
47 | void clear_marks_display (void); | ||
48 | |||
49 | /* Add a mark to the display */ | ||
50 | void 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 */ | ||
53 | void 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) */ | ||
57 | void 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 */ | ||
61 | void set_zoom_display (unsigned int zoom_level); | ||
62 | unsigned int current_zoom_display (void); | ||
63 | unsigned int min_zoom_display (void); | ||
64 | unsigned 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 | |||
32 | static void pre_game_setup (void); | ||
33 | |||
34 | char save_file[SAVE_FILE_LENGTH]; | ||
35 | bool game_dirty = false; | ||
36 | bool autosave_dirty = false; | ||
37 | |||
38 | int move_num = 0; | ||
39 | |||
40 | unsigned char current_player = BLACK; | ||
41 | |||
42 | struct header_t header; | ||
43 | |||
44 | void | ||
45 | set_game_modified (void) | ||
46 | { | ||
47 | game_dirty = true; | ||
48 | autosave_dirty = true; | ||
49 | } | ||
50 | |||
51 | bool | ||
52 | load_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 | |||
112 | bool | ||
113 | save_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 | |||
179 | static void | ||
180 | pre_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 | |||
202 | bool | ||
203 | setup_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 */ | ||
28 | void 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 */ | ||
33 | bool 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 */ | ||
38 | bool load_game (const char *filename); | ||
39 | |||
40 | /* Save the data in the SGF tree to a file. Returns false on failure */ | ||
41 | bool save_game (const char *filename); | ||
42 | |||
43 | /* The number of the current move (starts at 0, first move is 1) */ | ||
44 | extern int move_num; | ||
45 | |||
46 | /* The color of the current_player, either BLACK or WHITE */ | ||
47 | extern unsigned char current_player; | ||
48 | /* Where should we save to if explicitly saved? */ | ||
49 | extern char save_file[]; | ||
50 | /* True if there are unsaved changes in the file */ | ||
51 | extern bool game_dirty; | ||
52 | |||
53 | /* True if there are changes that have been neither autosaved nor saved */ | ||
54 | extern bool autosave_dirty; | ||
55 | |||
56 | /* The game metadata */ | ||
57 | extern 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 | |||
26 | PLUGIN_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 | |||
37 | enum 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 | |||
44 | int 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 */ | ||
52 | struct stack_t parse_stack; | ||
53 | char parse_stack_buffer[PARSE_STACK_BUFFER_SIZE]; | ||
54 | |||
55 | static void global_setup (void); | ||
56 | static void global_cleanup (void); | ||
57 | |||
58 | static bool do_main_menu (void); | ||
59 | static void do_gameinfo_menu (void); | ||
60 | static enum prop_type_t menu_selection_to_prop (int selection); | ||
61 | static void do_context_menu (void); | ||
62 | static void do_options_menu (void); | ||
63 | |||
64 | static bool do_comment_edit (void); | ||
65 | static bool do_zoom (void); | ||
66 | static void set_defaults (void); | ||
67 | |||
68 | bool auto_show_comments = true; | ||
69 | bool disable_shutdown = false; | ||
70 | unsigned int autosave_time = 0; | ||
71 | unsigned int autosave_counter = 0; | ||
72 | |||
73 | #define SETTINGS_VERSION 2 | ||
74 | #define SETTINGS_MIN_VERSION 1 | ||
75 | #define SETTINGS_FILENAME "goban.cfg" | ||
76 | |||
77 | static 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 | |||
100 | static void | ||
101 | set_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 | |||
111 | static void | ||
112 | komi_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 | |||
118 | static void | ||
119 | ruleset_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 | |||
125 | static void | ||
126 | autosave_formatter (char *dest, size_t size, int menu_item, const char * | ||
127 | unknown) | ||
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 | |||
141 | static void | ||
142 | time_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 | |||
235 | enum plugin_status | ||
236 | plugin_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 | |||
551 | static void | ||
552 | global_cleanup (void) | ||
553 | { | ||
554 | cleanup_sgf (); | ||
555 | configfile_save(SETTINGS_FILENAME, config, | ||
556 | sizeof(config)/sizeof(*config), | ||
557 | SETTINGS_VERSION); | ||
558 | } | ||
559 | |||
560 | static void | ||
561 | global_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 | |||
587 | enum 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 | |||
600 | static bool | ||
601 | do_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 | |||
738 | void | ||
739 | zoom_preview (int current) | ||
740 | { | ||
741 | set_zoom_display (current); | ||
742 | draw_screen_display (); | ||
743 | rb->splash (0, "Preview"); | ||
744 | } | ||
745 | |||
746 | static bool | ||
747 | do_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 | |||
827 | enum 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 | |||
850 | static void | ||
851 | do_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 | |||
964 | enum 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 | |||
982 | static void | ||
983 | do_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 | |||
1110 | enum options_menu_selections | ||
1111 | { | ||
1112 | OMENU_SHOW_VARIATIONS = 0, | ||
1113 | OMENU_DISABLE_POWEROFF, | ||
1114 | OMENU_AUTOSAVE_TIME, | ||
1115 | OMENU_AUTO_COMMENT | ||
1116 | }; | ||
1117 | |||
1118 | static void | ||
1119 | do_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 | |||
1172 | static bool | ||
1173 | do_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 | |||
1198 | static enum prop_type_t | ||
1199 | menu_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 */ | ||
268 | extern enum play_mode_t play_mode; | ||
269 | |||
270 | /* Show comments when redoing onto a move? */ | ||
271 | extern 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 */ | ||
275 | extern 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 | |||
11 | GOBANSRCDIR := $(APPSDIR)/plugins/goban | ||
12 | GOBANBUILDDIR := $(BUILDDIR)/apps/plugins/goban | ||
13 | |||
14 | ROCKS += $(GOBANBUILDDIR)/goban.rock | ||
15 | |||
16 | |||
17 | GOBAN_SRC := $(call preprocess, $(GOBANSRCDIR)/SOURCES) | ||
18 | GOBAN_OBJ := $(call c2obj, $(GOBAN_SRC)) | ||
19 | |||
20 | OTHER_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 | |||
31 | int sgf_fd = -1; | ||
32 | int unhandled_fd = -1; | ||
33 | |||
34 | int tree_head = -1; | ||
35 | int current_node = -1; | ||
36 | int start_node = -1; | ||
37 | |||
38 | bool header_marked = false; | ||
39 | |||
40 | static int add_child_variation (int *variation_number); | ||
41 | |||
42 | static int get_move_from_node (int handle); | ||
43 | |||
44 | static bool is_important_node (int handle); | ||
45 | static bool goto_next_important_node (bool forward); | ||
46 | static bool retreat_node (void); | ||
47 | static bool advance_node (void); | ||
48 | |||
49 | static bool do_add_stones (void); | ||
50 | static void setup_handicap_helper (unsigned short pos); | ||
51 | |||
52 | static int undo_node_helper (void); | ||
53 | |||
54 | static void set_one_mark (unsigned short pos, enum prop_type_t type); | ||
55 | static void set_label_mark (unsigned short pos, char to_set); | ||
56 | |||
57 | bool | ||
58 | play_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 | |||
173 | bool | ||
174 | add_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 | |||
252 | bool | ||
253 | add_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 | |||
430 | bool | ||
431 | undo_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 | |||
491 | static int | ||
492 | undo_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 | |||
625 | bool | ||
626 | redo_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 | |||
780 | redo_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 | |||
812 | int | ||
813 | mark_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 | |||
857 | void | ||
858 | set_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 | |||
880 | static void | ||
881 | set_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 | |||
916 | static void | ||
917 | set_label_mark (unsigned short pos, char to_set) | ||
918 | { | ||
919 | set_mark_display (pos, to_set | (1 << 7)); | ||
920 | } | ||
921 | |||
922 | static bool | ||
923 | do_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 | |||
981 | int | ||
982 | add_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 | |||
1017 | int | ||
1018 | add_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 | |||
1078 | int | ||
1079 | get_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 | |||
1130 | int | ||
1131 | add_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 | |||
1152 | int | ||
1153 | get_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 | |||
1191 | bool | ||
1192 | delete_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 | |||
1202 | bool | ||
1203 | delete_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 | |||
1238 | int | ||
1239 | read_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 | |||
1319 | int | ||
1320 | write_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 | |||
1456 | bool | ||
1457 | has_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 */ | ||
1481 | bool | ||
1482 | has_prev_nodes_sgf (void) | ||
1483 | { | ||
1484 | if (current_node < 0) | ||
1485 | { | ||
1486 | return false; | ||
1487 | } | ||
1488 | |||
1489 | return current_node != start_node; | ||
1490 | } | ||
1491 | |||
1492 | int | ||
1493 | get_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 | |||
1506 | static int | ||
1507 | get_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 | |||
1533 | static bool | ||
1534 | retreat_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 | |||
1560 | static bool | ||
1561 | advance_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 | |||
1588 | int | ||
1589 | num_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 | |||
1625 | bool | ||
1626 | go_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 | |||
1675 | int | ||
1676 | get_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 | |||
1768 | int | ||
1769 | next_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 | |||
1838 | int | ||
1839 | add_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 | |||
1940 | static bool | ||
1941 | is_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 | |||
1980 | static bool | ||
1981 | goto_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 | |||
2014 | bool | ||
2015 | is_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 | |||
2060 | void | ||
2061 | setup_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 | |||
2181 | static void | ||
2182 | setup_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 | |||
2191 | void | ||
2192 | goto_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 | |||
2201 | bool | ||
2202 | post_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. */ | ||
31 | bool 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) */ | ||
37 | bool 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. */ | ||
41 | bool 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 */ | ||
45 | bool has_prev_nodes_sgf (void); | ||
46 | bool 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. */ | ||
50 | bool 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 */ | ||
54 | int 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 */ | ||
58 | int 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. */ | ||
62 | void 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. */ | ||
67 | int 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 */ | ||
71 | int next_variation_sgf (void); | ||
72 | |||
73 | /* ints in these are handles to storage locations, bools mean failure if | ||
74 | false */ | ||
75 | int add_prop_sgf (int node, enum prop_type_t type, union prop_data_t data); | ||
76 | bool delete_prop_sgf (int node, enum prop_type_t type); | ||
77 | bool delete_prop_handle_sgf (int node, int prop); | ||
78 | int 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. */ | ||
83 | int 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 */ | ||
91 | int 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. */ | ||
94 | int 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. */ | ||
99 | int 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. */ | ||
104 | int 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 | . */ | ||
110 | bool undo_node_sgf (void); | ||
111 | bool 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 | */ | ||
119 | bool is_handled_sgf (enum prop_type_t type); | ||
120 | |||
121 | /* Sets up the handicap on the board (based on header.handicap) */ | ||
122 | void setup_handicap_sgf (void); | ||
123 | |||
124 | /* Goes to the start of a handicap game. */ | ||
125 | void 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) */ | ||
130 | bool 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 | */ | ||
137 | int 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 | */ | ||
159 | extern int current_node; | ||
160 | extern int tree_head; | ||
161 | extern int start_node; | ||
162 | |||
163 | extern int sgf_fd; | ||
164 | extern int unhandled_fd; | ||
165 | |||
166 | /* true if the header location has already been marked in the current | ||
167 | game, false otherwise */ | ||
168 | extern 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 | |||
30 | static void pos_to_sgf (unsigned short pos, char *buffer); | ||
31 | |||
32 | static void output_prop (int prop_handle); | ||
33 | static void output_all_props (void); | ||
34 | static bool output_current_node (void); | ||
35 | static void output_gametree (void); | ||
36 | static void output_header_props (void); | ||
37 | static bool output_header_helper (enum prop_type_t type); | ||
38 | static int stupid_num_variations (void); | ||
39 | |||
40 | bool | ||
41 | output_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, ¤t)) | ||
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 | |||
128 | static void | ||
129 | output_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 | |||
184 | static bool | ||
185 | output_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 | |||
216 | bool first_node_in_tree = true; | ||
217 | static void | ||
218 | output_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 | |||
229 | static bool | ||
230 | output_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 | |||
261 | enum prop_type_t last_output_type = PROP_INVALID; | ||
262 | static void | ||
263 | output_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 | |||
276 | static void | ||
277 | output_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 | |||
384 | static void | ||
385 | pos_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 | |||
403 | static int | ||
404 | stupid_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 */ | ||
28 | bool 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 | |||
30 | static void handle_prop_value (enum prop_type_t type); | ||
31 | static int read_prop_value (char *buffer, size_t buffer_size); | ||
32 | static void do_range (enum prop_type_t type, unsigned short ul, | ||
33 | unsigned short br); | ||
34 | static void parse_prop (void); | ||
35 | static void parse_node (void); | ||
36 | static enum prop_type_t parse_prop_type (void); | ||
37 | |||
38 | static unsigned short sgf_to_pos (char *buffer); | ||
39 | |||
40 | bool | ||
41 | parse_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 | |||
242 | static void | ||
243 | parse_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 | |||
264 | int start_of_prop = 0; | ||
265 | static void | ||
266 | parse_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 | } | ||
291 | static enum prop_type_t | ||
292 | parse_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 | |||
359 | static int | ||
360 | read_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 | |||
412 | static void | ||
413 | handle_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 */ | ||
804 | static void | ||
805 | do_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 | |||
843 | static unsigned short | ||
844 | sgf_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 */ | ||
28 | bool 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 | |||
30 | union storage_t *storage_buffer[] = { NULL, NULL }; | ||
31 | size_t storage_buffer_size[] = { 0, 0 }; | ||
32 | |||
33 | uint8_t *storage_free_list[] = { NULL, NULL }; | ||
34 | size_t storage_free_list_size[] = { 0, 0 }; | ||
35 | |||
36 | bool storage_initialized[] = { false, false }; | ||
37 | |||
38 | size_t total_storage_size = 0; | ||
39 | |||
40 | /* the next handle to check */ | ||
41 | int next_free_handle_buffer; | ||
42 | int next_free_handle; | ||
43 | |||
44 | static bool setup_storage_buffer (char *temp_buffer, size_t size); | ||
45 | static void clear_storage_buffer (int index); | ||
46 | static bool find_free (int *ret_buffer, int *ret_handle); | ||
47 | static bool is_free (int buffer_num, int handle); | ||
48 | static void set_free (int buffer_num, int handle, bool free); | ||
49 | |||
50 | #if 0 | ||
51 | static void debugf_current_node (void); | ||
52 | |||
53 | static void | ||
54 | debugf_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 | |||
115 | static void | ||
116 | clear_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 | |||
135 | void | ||
136 | free_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 | |||
163 | bool | ||
164 | audio_stolen_sgf (void) | ||
165 | { | ||
166 | return storage_initialized[1]; | ||
167 | } | ||
168 | |||
169 | int | ||
170 | alloc_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 | |||
222 | void | ||
223 | free_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 | |||
244 | static bool | ||
245 | find_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 | |||
293 | static bool | ||
294 | is_free (int buffer_num, int handle) | ||
295 | { | ||
296 | return storage_free_list[buffer_num][handle / 8] & | ||
297 | (1 << (7 - (handle % 8))); | ||
298 | } | ||
299 | |||
300 | static void | ||
301 | set_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 | |||
318 | bool | ||
319 | setup_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 | |||
363 | void | ||
364 | clear_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 | |||
372 | void | ||
373 | cleanup_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 | |||
384 | static bool | ||
385 | setup_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 | |||
454 | struct node_t * | ||
455 | get_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 | |||
474 | struct prop_t * | ||
475 | get_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 */ | ||
29 | bool setup_sgf (void); | ||
30 | |||
31 | /* Do cleanup, call before exiting the plugin. You must not use any SGF | ||
32 | subsystem functions after calling this */ | ||
33 | void cleanup_sgf (void); | ||
34 | |||
35 | /* Get ready for a new game (either loaded or blank) */ | ||
36 | void clear_caches_sgf (void); | ||
37 | |||
38 | /* Clear the SGF tree and get it ready for a new game (loaded or blank) */ | ||
39 | void free_tree_sgf (void); | ||
40 | |||
41 | /* Returns true if the Rockbox audio buffer has been stolen */ | ||
42 | bool audio_stolen_sgf (void); | ||
43 | |||
44 | /* Returns a handle to a struct storage_t (NOT a pointer) < 0 handles are | ||
45 | invalid */ | ||
46 | int alloc_storage_sgf (void); | ||
47 | |||
48 | /* Free one storage location */ | ||
49 | void free_storage_sgf (int handle); | ||
50 | |||
51 | /* Get a pointer to a node or property which corresponds to the given | ||
52 | * storage handle | ||
53 | */ | ||
54 | struct node_t *get_node (int handle); | ||
55 | struct 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) */ | ||
28 | struct 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/ */ | ||
45 | enum 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 | |||
153 | extern 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)) */ | ||
169 | union 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 */ | ||
185 | enum 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 */ | ||
200 | enum 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 */ | ||
212 | struct 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. */ | ||
221 | extern char *ruleset_names[]; | ||
222 | |||
223 | /* IMPORTANT! keep in sync with ruleset_names!!! */ | ||
224 | enum 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. */ | ||
240 | struct 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 */ | ||
249 | union 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 | |||
269 | struct 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 | |||
26 | void 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 | |||
73 | void * | ||
74 | align_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 | |||
96 | bool | ||
97 | setup_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 | |||
120 | bool | ||
121 | push_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 | |||
136 | bool | ||
137 | pop_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 | |||
149 | bool | ||
150 | peek_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 | |||
162 | void | ||
163 | empty_stack (struct stack_t *stack) | ||
164 | { | ||
165 | stack->sp = 0; | ||
166 | } | ||
167 | |||
168 | bool | ||
169 | push_pos_stack (struct stack_t *stack, unsigned short pos) | ||
170 | { | ||
171 | return push_stack (stack, &pos, sizeof (pos)); | ||
172 | } | ||
173 | |||
174 | bool | ||
175 | push_int_stack (struct stack_t *stack, int num) | ||
176 | { | ||
177 | return push_stack (stack, &num, sizeof (num)); | ||
178 | } | ||
179 | |||
180 | bool | ||
181 | push_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 */ | ||
189 | char *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) */ | ||
213 | char *ruleset_names[] = { "AGA", "Japanese", "Chinese", "NZ", "GOE" }; | ||
214 | |||
215 | |||
216 | |||
217 | int | ||
218 | create_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 | |||
235 | int | ||
236 | snprint_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 | |||
244 | int | ||
245 | peek_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 | |||
267 | int | ||
268 | read_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 | |||
283 | bool | ||
284 | write_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 | |||
296 | ssize_t | ||
297 | write_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 | |||
319 | ssize_t | ||
320 | read_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 | |||
342 | int | ||
343 | read_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 | |||
352 | int | ||
353 | peek_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 | |||
366 | void | ||
367 | close_file (int *fd) | ||
368 | { | ||
369 | if (*fd >= 0) | ||
370 | { | ||
371 | rb->close (*fd); | ||
372 | } | ||
373 | |||
374 | *fd = -1; | ||
375 | } | ||
376 | |||
377 | bool | ||
378 | is_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 | |||
392 | void | ||
393 | sanitize_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 | |||
426 | bool | ||
427 | get_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 | |||
530 | void | ||
531 | run_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 */ | ||
29 | bool 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) */ | ||
33 | bool push_stack (struct stack_t *stack, void *buffer, size_t buffer_size); | ||
34 | bool pop_stack (struct stack_t *stack, void *buffer, size_t buffer_size); | ||
35 | bool 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 */ | ||
39 | void 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 | |||
52 | bool push_pos_stack (struct stack_t *stack, unsigned short pos); | ||
53 | bool push_int_stack (struct stack_t *stack, int num); | ||
54 | bool 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 */ | ||
61 | int create_or_open_file (const char *filename); | ||
62 | |||
63 | /* Returns the number of characters printed */ | ||
64 | int 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 */ | ||
69 | ssize_t read_file (int fd, void *buf, size_t count); | ||
70 | ssize_t write_file (int fd, const void *buf, size_t count); | ||
71 | void close_file (int *fd); | ||
72 | |||
73 | int peek_char (int fd); | ||
74 | int read_char (int fd); | ||
75 | bool 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 */ | ||
80 | int peek_char_no_whitespace (int fd); | ||
81 | /* Same deal, with reading -1 on EOF or error */ | ||
82 | int 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. */ | ||
87 | bool 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 */ | ||
92 | void 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. */ | ||
96 | void *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. */ | ||
101 | bool 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 | */ | ||
107 | void metadata_summary (void); | ||
108 | |||
109 | #ifdef GBN_TEST | ||
110 | void 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 | |||
19 | rsp,viewers/searchengine,8 | 19 | rsp,viewers/searchengine,8 |
20 | sok,games/sokoban,1 | 20 | sok,games/sokoban,1 |
21 | pgn,games/chessbox,1 | 21 | pgn,games/chessbox,1 |
22 | sgf,games/goban,1 | ||
22 | ss,games/sudoku,1 | 23 | ss,games/sudoku,1 |
23 | wav,viewers/wav2wv,- | 24 | wav,viewers/wav2wv,- |
24 | wav,viewers/mp3_encoder,- | 25 | wav,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 | |||
448 | Craig Elliott | 448 | Craig Elliott |
449 | Kenderes Tamas | 449 | Kenderes Tamas |
450 | Eric Shattow | 450 | Eric Shattow |
451 | Joshua Simmons | ||
451 | 452 | ||
452 | 453 | ||
453 | The libmad team | 454 | The 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} | ||
3 | Goban is a a plugin for playing, viewing and recording games of Go (also known | ||
4 | as Weiqi, Baduk, Igo and Goe). It uses standard Smart Game Format (SGF) files | ||
5 | for saving and loading games. | ||
6 | |||
7 | You can find a short introduction to Go at | ||
8 | \url{http://senseis.xmp.net/?WhatIsGo} and more information about SGF files | ||
9 | can be read at \url{http://senseis.xmp.net/?SmartGameFormat} or the SGF | ||
10 | specification at | ||
11 | \url{http://www.red-bean.com/sgf/}. | ||
12 | |||
13 | This plugin can load all modern SGF files (file format 3 or 4) with few problems. | ||
14 | It attempts to preserve SGF properties which it doesn't understand, and most common | ||
15 | SGF properties are handled fully. It is possible to view (and edit if you like) | ||
16 | Kogo's Joseki Dictionary (\url{http://waterfire.us/joseki.htm}) with this plugin, | ||
17 | although the load and save times can be on the order of a minute or two on | ||
18 | particularly slow devices. Large SGF files may stop audio playback for the duration | ||
19 | of the plugin's run in order to free up more memory and some very large SGF files will | ||
20 | not even load on devices with little available memory. | ||
21 | |||
22 | \emph{Note: } The plugin does \emph{NOT} support SGF files with multiple games in | ||
23 | one file. These are rare, but if you have one don't even try it (the file will most | ||
24 | likely be corrupted if you save over it). You have been warned. | ||
25 | |||
26 | The file \fname {"/sgf/gbn\_def.sgf"} is used by the plugin to store any unsaved | ||
27 | changes in the most recently loaded game. This means that if you forget to save your | ||
28 | changes, you should load \fname {"/sgf/gbn\_def.sgf"} immediately to offload the changes | ||
29 | to another file. If you load another file first then your changes will be lost | ||
30 | permanently. The \fname {"/sgf/gbn\_def.sgf"} file is also the file loaded if another | ||
31 | is not selected. | ||
32 | |||
33 | The information panel which displays the current move number may also contain | ||
34 | these 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}} |