1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
10 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
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.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
23 #include "lib/playback_control.h"
24 #include "lib/configfile.h"
33 #include "sgf_storage.h"
37 enum play_mode_t play_mode
= MODE_PLAY
;
39 #if defined(GBN_BUTTON_NAV_MODE)
41 #define NAV_MODE_BOARD 0
42 #define NAV_MODE_TREE 1
44 int nav_mode
= NAV_MODE_BOARD
;
48 /* the stack that uses this buffer is used for both storing intersections
49 * in board algorithms (could store one short for each board location), and
50 * for parsing (could store an indefinite number of ints, related to how
51 * many levels deep branches go (in other words, how many branches off of
52 * branches there are). 50 should take care of any reasonable file.
54 #define PARSE_STACK_BUFFER_SIZE (max(MAX_BOARD_SIZE * MAX_BOARD_SIZE * sizeof(unsigned short), 50 * sizeof(int)))
56 /* used in SGF file parsing and outputting as well as in liberty counting
57 and capturing/uncapturing */
58 struct stack_t parse_stack
;
59 char parse_stack_buffer
[PARSE_STACK_BUFFER_SIZE
];
61 static void global_setup (void);
62 static void global_cleanup (void);
64 static bool do_main_menu (void);
65 static void do_gameinfo_menu (void);
66 static enum prop_type_t
menu_selection_to_prop (int selection
);
67 static void do_context_menu (void);
68 static void do_options_menu (void);
70 static bool do_comment_edit (void);
71 static bool do_zoom (void);
72 static void set_defaults (void);
74 bool auto_show_comments
= true;
75 bool disable_shutdown
= false;
76 unsigned int autosave_time
= 0;
77 unsigned int autosave_counter
= 0;
79 #define SETTINGS_VERSION 2
80 #define SETTINGS_MIN_VERSION 1
81 #define SETTINGS_FILENAME "goban.cfg"
83 static struct configdata config
[] =
84 { /* INT in MAX_INT_SIZE is intersection, not integer */
85 {TYPE_INT
, 0, MAX_INT_SIZE
,
86 { .int_p
= &saved_circle_size
},
89 { .bool_p
= &draw_variations
},
93 { .bool_p
= &auto_show_comments
},
97 { .bool_p
= &disable_shutdown
},
100 {TYPE_INT
, 0, MAX_AUTOSAVE
,
101 { .int_p
= &autosave_time
},
109 saved_circle_size
= 0;
110 draw_variations
= true;
111 auto_show_comments
= true;
113 disable_shutdown
= false;
118 komi_formatter (char *dest
, size_t size
, int menu_item
, const char *unknown
)
121 snprint_fixed (dest
, size
, menu_item
);
125 ruleset_formatter (char *dest
, size_t size
, int menu_item
, const char *unknown
)
128 rb
->snprintf (dest
, size
, "%s", ruleset_names
[menu_item
]);
132 autosave_formatter (char *dest
, size_t size
, int menu_item
, const char *
138 rb
->snprintf (dest
, size
, "Off");
142 rb
->snprintf (dest
, size
, "%d minute%s", menu_item
,
143 menu_item
== 1 ? "" : "s");
148 time_formatter (char *dest
, size_t size
, int menu_item
, const char *unknown
)
150 int time_values
[4]; /* days hours minutes seconds */
151 int min_set
, max_set
;
156 time_values
[0] = menu_item
/ (24 * 60 * 60);
157 menu_item
%= (24 * 60 * 60);
158 time_values
[1] = menu_item
/ (60 * 60);
159 menu_item
%= (60 * 60);
160 time_values
[2] = menu_item
/ 60;
161 time_values
[3] = menu_item
% 60;
167 (unsigned int) i
< (sizeof (time_values
) / sizeof (time_values
[0]));
186 rb
->snprintf (dest
, size
, "0");
190 for (i
= min_set
; i
<= 3; ++i
)
194 if (i
== 0 || i
== 1 || i
== min_set
)
196 rb
->snprintf (dest
, size
, "%d", time_values
[i
]);
200 rb
->snprintf (dest
, size
, "%02d", time_values
[i
]);
202 temp
= rb
->strlen (dest
);
211 if (i
== 0) /* days */
213 rb
->snprintf (dest
, size
, " d ");
215 else if (i
== 3) /* seconds, print the final units */
217 if (min_set
== 0 || min_set
== 1)
219 rb
->snprintf (dest
, size
, " h");
221 else if (min_set
== 2)
223 rb
->snprintf (dest
, size
, " m");
227 rb
->snprintf (dest
, size
, " s");
230 else if (i
!= max_set
)
232 rb
->snprintf (dest
, size
, ":");
235 temp
= rb
->strlen (dest
);
242 plugin_start (UNUSED_ATTR
const void *parameter
)
246 rb
->mkdir (DEFAULT_SAVE_DIR
);
255 if (!(parameter
&& load_game (parameter
)))
259 rb
->splashf (2 * HZ
, "Loading %s failed.", (char *) parameter
);
262 if (!load_game (DEFAULT_SAVE
))
264 rb
->strcpy (save_file
, DEFAULT_SAVE
);
266 if (!setup_game (MAX_BOARD_SIZE
, MAX_BOARD_SIZE
, 0, 0))
275 if (rb
->strcmp (save_file
, DEFAULT_SAVE
))
277 /* delete the scratch file if we loaded a game and it wasn't
278 * from the scratch file
280 rb
->remove (DEFAULT_SAVE
);
284 draw_screen_display ();
286 autosave_counter
= 0;
289 btn
= rb
->button_get_w_tmo (HZ
* 30);
291 if (disable_shutdown
)
293 /* tell rockbox we're not idle */
294 rb
->reset_poweroff_timer ();
297 bool is_idle
= false;
302 #if defined(GBN_BUTTON_NAV_MODE)
303 case GBN_BUTTON_NAV_MODE
:
304 case GBN_BUTTON_NAV_MODE
| BUTTON_REPEAT
:
305 if (nav_mode
== NAV_MODE_TREE
)
307 nav_mode
= NAV_MODE_BOARD
;
308 rb
->splash (2 * HZ
/ 3, "board navigation mode");
309 draw_screen_display ();
313 nav_mode
= NAV_MODE_TREE
;
314 rb
->splash (2 * HZ
/ 3, "tree navigation mode");
315 draw_screen_display ();
320 #if defined(GBN_BUTTON_ADVANCE)
321 case GBN_BUTTON_ADVANCE
:
322 case GBN_BUTTON_ADVANCE
| BUTTON_REPEAT
:
323 if (has_more_nodes_sgf ())
325 if (!redo_node_sgf ())
327 rb
->splash (2 * HZ
, "redo failed");
329 draw_screen_display ();
334 #if defined(GBN_BUTTON_RETREAT)
335 case GBN_BUTTON_RETREAT
:
336 case GBN_BUTTON_RETREAT
| BUTTON_REPEAT
:
337 if (has_prev_nodes_sgf ())
339 if (!undo_node_sgf ())
341 rb
->splash (3 * HZ
/ 2, "Undo Failed");
343 draw_screen_display ();
348 case GBN_BUTTON_PLAY
:
349 if (play_mode
== MODE_PLAY
|| play_mode
== MODE_FORCE_PLAY
)
351 if (!play_move_sgf (cursor_pos
, current_player
))
353 rb
->splash (HZ
/ 3, "Illegal Move");
356 else if (play_mode
== MODE_ADD_BLACK
)
358 if (!add_stone_sgf (cursor_pos
, BLACK
))
360 rb
->splash (HZ
/ 3, "Illegal");
363 else if (play_mode
== MODE_ADD_WHITE
)
365 if (!add_stone_sgf (cursor_pos
, WHITE
))
367 rb
->splash (HZ
/ 3, "Illegal");
370 else if (play_mode
== MODE_REMOVE
)
372 if (!add_stone_sgf (cursor_pos
, EMPTY
))
374 rb
->splash (HZ
/ 3, "Illegal");
377 else if (play_mode
== MODE_MARK
)
379 if (!add_mark_sgf (cursor_pos
, PROP_MARK
))
381 rb
->splash (HZ
/ 3, "Couldn't Mark");
384 else if (play_mode
== MODE_CIRCLE
)
386 if (!add_mark_sgf (cursor_pos
, PROP_CIRCLE
))
388 rb
->splash (HZ
/ 3, "Couldn't Mark");
391 else if (play_mode
== MODE_SQUARE
)
393 if (!add_mark_sgf (cursor_pos
, PROP_SQUARE
))
395 rb
->splash (HZ
/ 3, "Couldn't Mark");
398 else if (play_mode
== MODE_TRIANGLE
)
400 if (!add_mark_sgf (cursor_pos
, PROP_TRIANGLE
))
402 rb
->splash (HZ
/ 3, "Couldn't Mark");
405 else if (play_mode
== MODE_LABEL
)
407 if (!add_mark_sgf (cursor_pos
, PROP_LABEL
))
409 rb
->splash (HZ
/ 3, "Couldn't Label");
414 rb
->splash (HZ
, "mode not implemented");
417 draw_screen_display ();
420 case GBN_BUTTON_RIGHT
:
421 case GBN_BUTTON_RIGHT
| BUTTON_REPEAT
:
422 #if defined(GBN_BUTTON_NAV_MODE)
423 if (nav_mode
== NAV_MODE_TREE
)
425 if (has_more_nodes_sgf ())
427 if (!redo_node_sgf ())
429 rb
->splash (2 * HZ
, "Redo Failed");
431 draw_screen_display ();
437 cursor_pos
= WRAP (EAST (cursor_pos
));
438 draw_screen_display ();
439 #if defined(GBN_BUTTON_NAV_MODE)
444 case GBN_BUTTON_LEFT
:
445 case GBN_BUTTON_LEFT
| BUTTON_REPEAT
:
446 #if defined(GBN_BUTTON_NAV_MODE)
447 if (nav_mode
== NAV_MODE_TREE
)
449 if (has_prev_nodes_sgf ())
451 if (!undo_node_sgf ())
453 rb
->splash (2 * HZ
, "Undo Failed");
455 draw_screen_display ();
461 cursor_pos
= WRAP (WEST (cursor_pos
));
462 draw_screen_display ();
463 #if defined(GBN_BUTTON_NAV_MODE)
468 case GBN_BUTTON_DOWN
:
469 case GBN_BUTTON_DOWN
| BUTTON_REPEAT
:
470 cursor_pos
= WRAP (SOUTH (cursor_pos
));
471 draw_screen_display ();
475 case GBN_BUTTON_UP
| BUTTON_REPEAT
:
476 cursor_pos
= WRAP (NORTH (cursor_pos
));
477 draw_screen_display ();
480 case GBN_BUTTON_MENU
:
483 save_game (DEFAULT_SAVE
);
489 draw_screen_display ();
492 #if defined(GBN_BUTTON_CONTEXT)
493 case GBN_BUTTON_CONTEXT
:
495 draw_screen_display ();
499 #if defined(GBN_BUTTON_NEXT_VAR)
500 case GBN_BUTTON_NEXT_VAR
:
501 case GBN_BUTTON_NEXT_VAR
| BUTTON_REPEAT
:
504 if ((temp
= next_variation_sgf ()) >= 0)
506 draw_screen_display ();
507 rb
->splashf (2 * HZ
/ 3, "%d of %d", temp
,
508 num_variations_sgf ());
509 draw_screen_display ();
513 if (num_variations_sgf () > 1)
515 rb
->splashf (HZ
, "Error %d in next_variation_sgf", temp
);
517 draw_screen_display ();
526 if (rb
->default_event_handler (btn
) == SYS_USB_CONNECTED
)
528 return PLUGIN_USB_CONNECTED
;
533 if (is_idle
&& autosave_dirty
)
537 if (autosave_time
!= 0 &&
538 autosave_counter
/ 2 >= autosave_time
)
539 /* counter is in 30 second increments, autosave_time is in
543 DEBUGF("autosaving\n");
544 rb
->splash(HZ
/ 4, "Autosaving...");
545 save_game(DEFAULT_SAVE
);
546 draw_screen_display();
547 autosave_counter
= 0;
552 autosave_counter
= 0;
560 global_cleanup (void)
563 configfile_save(SETTINGS_FILENAME
, config
,
564 sizeof(config
)/sizeof(*config
),
571 setup_stack (&parse_stack
, parse_stack_buffer
,
572 sizeof (parse_stack_buffer
));
577 if (configfile_load(SETTINGS_FILENAME
, config
,
578 sizeof(config
)/sizeof(*config
),
579 SETTINGS_MIN_VERSION
) < 0)
581 /* If the loading failed, save a new config file (as the disk is
584 /* set defaults again just in case (don't know if they can ever
585 * be messed up by configfile_load, and it's basically free anyway)
589 configfile_save(SETTINGS_FILENAME
, config
,
590 sizeof(config
)/sizeof(*config
),
595 enum main_menu_selections
612 MENUITEM_STRINGLIST (menu
, "Rockbox Goban", NULL
,
623 /* for "New" in menu */
624 int new_handi
= 0, new_bs
= MAX_BOARD_SIZE
, new_komi
= 15;
626 char new_save_file
[SAVE_FILE_LENGTH
];
633 selection
= rb
->do_menu (&menu
, &selection
, NULL
, false);
638 rb
->set_int ("board size", "lines", UNIT_INT
,
639 &new_bs
, NULL
, 1, MIN_BOARD_SIZE
, MAX_BOARD_SIZE
,
642 rb
->set_int ("handicap", "stones", UNIT_INT
,
643 &new_handi
, NULL
, 1, 0, 9, NULL
);
654 rb
->set_int ("komi", "moku", UNIT_INT
, &new_komi
, NULL
,
655 1, -300, 300, &komi_formatter
);
657 setup_game (new_bs
, new_bs
, new_handi
, new_komi
);
658 draw_screen_display ();
663 if (!save_game (save_file
))
665 rb
->splash (2 * HZ
, "Save Failed!");
669 rb
->splash (2 * HZ
/ 3, "Saved");
672 draw_screen_display ();
676 rb
->strcpy (new_save_file
, save_file
);
678 if (!rb
->kbd_input (new_save_file
, SAVE_FILE_LENGTH
))
683 if (!save_game (new_save_file
))
685 rb
->splash (2 * HZ
, "Save Failed!");
689 rb
->strcpy (save_file
, new_save_file
);
690 rb
->splash (2 * HZ
/ 3, "Saved");
694 draw_screen_display ();
702 if (!audio_stolen_sgf ())
704 playback_control (NULL
);
708 rb
->splash (1 * HZ
, "Audio has been disabled!");
718 draw_screen_display ();
731 case MENU_ATTACHED_USB
:
747 zoom_preview (int current
)
749 set_zoom_display (current
);
750 draw_screen_display ();
751 rb
->splash (0, "Preview");
757 unsigned int zoom_level
;
758 unsigned int old_val
;
761 unsigned int min_val
= min_zoom_display ();
762 unsigned int max_val
= max_zoom_display ();
764 zoom_level
= old_val
= current_zoom_display ();
768 zoom_preview (zoom_level
);
771 switch (action
= rb
->get_action (CONTEXT_LIST
, TIMEOUT_BLOCK
))
774 set_zoom_display (zoom_level
);
776 rb
->splash (HZ
/ 2, "Zoom Set");
777 saved_circle_size
= intersection_size
;
780 case ACTION_STD_CANCEL
:
781 set_zoom_display (old_val
);
783 rb
->splash (HZ
/ 2, "Cancelled");
786 case ACTION_STD_CONTEXT
:
787 zoom_level
= old_val
;
788 zoom_preview (zoom_level
);
791 case ACTION_STD_NEXT
:
792 case ACTION_STD_NEXTREPEAT
:
793 zoom_level
= zoom_level
* 3 / 2;
795 /* 1 * 3 / 2 is 1 again... */
801 if (zoom_level
> max_val
)
803 zoom_level
= min_val
;
806 zoom_preview (zoom_level
);
809 case ACTION_STD_PREV
:
810 case ACTION_STD_PREVREPEAT
:
811 zoom_level
= zoom_level
* 2 / 3;
813 if (zoom_level
< min_val
)
815 zoom_level
= max_val
;
817 zoom_preview (zoom_level
);
824 if (rb
->default_event_handler (action
) == SYS_USB_CONNECTED
)
835 enum gameinfo_menu_selections
837 GINFO_BASIC_INFO
= 0,
859 do_gameinfo_menu (void)
861 MENUITEM_STRINGLIST (gameinfo_menu
, "Game Info", NULL
,
882 * if you edit this string list, make sure you keep
883 * menu_selection_to_prop function in line with it!! (see the bottom
889 char *gameinfo_string
;
890 int gameinfo_string_size
;
897 selection
= rb
->do_menu (&gameinfo_menu
, &selection
, NULL
, false);
903 case GINFO_BLACK_PLAYER
:
904 case GINFO_BLACK_RANK
:
905 case GINFO_BLACK_TEAM
:
906 case GINFO_WHITE_PLAYER
:
907 case GINFO_WHITE_RANK
:
908 case GINFO_WHITE_TEAM
:
913 if (!get_header_string_and_size (&header
,
914 menu_selection_to_prop
915 (selection
), &gameinfo_string
,
916 &gameinfo_string_size
))
918 rb
->splash (3 * HZ
, "Couldn't get header string");
922 rb
->kbd_input (gameinfo_string
, gameinfo_string_size
);
923 sanitize_string (gameinfo_string
);
927 /* these need special handling in some way, so they are
930 case GINFO_BASIC_INFO
:
934 case GINFO_TIME_LIMIT
:
935 rb
->set_int ("Time Limit", "", UNIT_INT
, &header
.time_limit
,
936 NULL
, 60, 0, 24 * 60 * 60, &time_formatter
);
941 rb
->splashf (0, "%d stones. Start a new game to set handicap",
943 rb
->action_userabort(TIMEOUT_BLOCK
);
947 rb
->set_int ("Komi", "moku", UNIT_INT
, &header
.komi
, NULL
,
948 1, -300, 300, &komi_formatter
);
954 rb
->set_int ("Ruleset", "", UNIT_INT
, &new_ruleset
, NULL
,
955 1, 0, NUM_RULESETS
- 1, &ruleset_formatter
);
957 rb
->strcpy (header
.ruleset
, ruleset_names
[new_ruleset
]);
964 case MENU_ATTACHED_USB
:
972 enum context_menu_selections
991 do_context_menu (void)
997 MENUITEM_STRINGLIST (context_menu
, "Context Menu", NULL
,
998 "Play Mode (default)",
1015 selection
= rb
->do_menu (&context_menu
, &selection
, NULL
, false);
1020 play_mode
= MODE_PLAY
;
1025 play_mode
= MODE_ADD_BLACK
;
1030 play_mode
= MODE_ADD_WHITE
;
1035 play_mode
= MODE_REMOVE
;
1040 if (!play_move_sgf (PASS_POS
, current_player
))
1042 rb
->splash (HZ
, "Error while passing!");
1048 if ((temp
= next_variation_sgf ()) >= 0)
1050 draw_screen_display ();
1051 rb
->splashf (2 * HZ
/ 3, "%d of %d", temp
,
1052 num_variations_sgf ());
1053 draw_screen_display ();
1057 if (num_variations_sgf () > 1)
1059 rb
->splashf (HZ
, "Error %d in next_variation_sgf", temp
);
1063 rb
->splash (HZ
, "No next variation");
1069 play_mode
= MODE_FORCE_PLAY
;
1074 play_mode
= MODE_MARK
;
1079 play_mode
= MODE_CIRCLE
;
1084 play_mode
= MODE_SQUARE
;
1089 play_mode
= MODE_TRIANGLE
;
1094 play_mode
= MODE_LABEL
;
1099 if (!do_comment_edit ())
1101 DEBUGF ("Editing comment failed\n");
1102 rb
->splash (HZ
, "Read or write failed!\n");
1109 case GO_TO_PREVIOUS
:
1110 case MENU_ATTACHED_USB
:
1118 enum options_menu_selections
1120 OMENU_SHOW_VARIATIONS
= 0,
1121 OMENU_DISABLE_POWEROFF
,
1122 OMENU_AUTOSAVE_TIME
,
1127 do_options_menu (void)
1132 MENUITEM_STRINGLIST (options_menu
, "Options Menu", NULL
,
1133 "Show Child Variations?",
1134 "Disable Idle Poweroff?",
1135 "Idle Autosave Time",
1136 "Automatically Show Comments?");
1140 selection
= rb
->do_menu (&options_menu
, &selection
, NULL
, false);
1144 case OMENU_SHOW_VARIATIONS
:
1145 rb
->set_bool("Draw Variations?", &draw_variations
);
1146 clear_marks_display ();
1147 set_all_marks_sgf ();
1148 if (draw_variations
)
1150 mark_child_variations_sgf ();
1154 case OMENU_DISABLE_POWEROFF
:
1155 rb
->set_bool("Disable Idle Poweroff?", &disable_shutdown
);
1158 case OMENU_AUTOSAVE_TIME
:
1159 rb
->set_int("Idle Autosave Time", "minutes", UNIT_INT
,
1160 &autosave_time
, NULL
, 1, 0, MAX_AUTOSAVE
,
1161 &autosave_formatter
);
1162 autosave_counter
= 0;
1165 case OMENU_AUTO_COMMENT
:
1166 rb
->set_bool("Auto Show Comments?", &auto_show_comments
);
1170 case GO_TO_PREVIOUS
:
1171 case MENU_ATTACHED_USB
:
1181 do_comment_edit (void)
1185 rb
->memset (cbuffer
, 0, sizeof (cbuffer
));
1187 if (read_comment_sgf (cbuffer
, sizeof (cbuffer
)) < 0)
1192 if (!rb
->kbd_input (cbuffer
, sizeof (cbuffer
)))
1194 /* user didn't edit, no reason to write it back */
1198 if (write_comment_sgf (cbuffer
) < 0)
1206 static enum prop_type_t
1207 menu_selection_to_prop (int selection
)
1211 case GINFO_OVERTIME
:
1212 return PROP_OVERTIME
;
1215 case GINFO_BLACK_PLAYER
:
1216 return PROP_BLACK_NAME
;
1217 case GINFO_BLACK_RANK
:
1218 return PROP_BLACK_RANK
;
1219 case GINFO_BLACK_TEAM
:
1220 return PROP_BLACK_TEAM
;
1221 case GINFO_WHITE_PLAYER
:
1222 return PROP_WHITE_NAME
;
1223 case GINFO_WHITE_RANK
:
1224 return PROP_WHITE_RANK
;
1225 case GINFO_WHITE_TEAM
:
1226 return PROP_WHITE_TEAM
;
1236 DEBUGF ("Tried to get prop from invalid menu selection!!!\n");
1237 return PROP_PLACE
; /* just pick one, there's a major bug if