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
);
126 ruleset_formatter (char *dest
, size_t size
, int menu_item
, const char *unknown
)
128 (void)dest
, (void)size
, (void)unknown
;
129 return ruleset_names
[menu_item
];
133 autosave_formatter (char *dest
, size_t size
, int menu_item
, const char *
143 rb
->snprintf (dest
, size
, "%d minute%s", menu_item
,
144 menu_item
== 1 ? "" : "s");
150 time_formatter (char *dest
, size_t size
, int menu_item
, const char *unknown
)
152 int time_values
[4]; /* days hours minutes seconds */
153 int min_set
, max_set
;
158 time_values
[0] = menu_item
/ (24 * 60 * 60);
159 menu_item
%= (24 * 60 * 60);
160 time_values
[1] = menu_item
/ (60 * 60);
161 menu_item
%= (60 * 60);
162 time_values
[2] = menu_item
/ 60;
163 time_values
[3] = menu_item
% 60;
169 (unsigned int) i
< (sizeof (time_values
) / sizeof (time_values
[0]));
191 for (i
= min_set
; i
<= 3; ++i
)
195 if (i
== 0 || i
== 1 || i
== min_set
)
197 rb
->snprintf (dest
, size
, "%d", time_values
[i
]);
201 rb
->snprintf (dest
, size
, "%02d", time_values
[i
]);
203 temp
= rb
->strlen (dest
);
212 if (i
== 0) /* days */
214 rb
->snprintf (dest
, size
, " d ");
216 else if (i
== 3) /* seconds, print the final units */
218 if (min_set
== 0 || min_set
== 1)
220 rb
->snprintf (dest
, size
, " h");
222 else if (min_set
== 2)
224 rb
->snprintf (dest
, size
, " m");
228 rb
->snprintf (dest
, size
, " s");
231 else if (i
!= max_set
)
233 rb
->snprintf (dest
, size
, ":");
236 temp
= rb
->strlen (dest
);
244 plugin_start (const void *parameter
)
248 rb
->mkdir (DEFAULT_SAVE_DIR
);
257 if (!(parameter
&& load_game (parameter
)))
261 rb
->splashf (2 * HZ
, "Loading %s failed.", (char *) parameter
);
264 if (!load_game (DEFAULT_SAVE
))
266 rb
->strcpy (save_file
, DEFAULT_SAVE
);
268 if (!setup_game (MAX_BOARD_SIZE
, MAX_BOARD_SIZE
, 0, 0))
277 if (rb
->strcmp (save_file
, DEFAULT_SAVE
))
279 /* delete the scratch file if we loaded a game and it wasn't
280 * from the scratch file
282 rb
->remove (DEFAULT_SAVE
);
286 draw_screen_display ();
288 autosave_counter
= 0;
291 btn
= rb
->button_get_w_tmo (HZ
* 30);
293 if (disable_shutdown
)
295 /* tell rockbox we're not idle */
296 rb
->reset_poweroff_timer ();
299 bool is_idle
= false;
304 #if defined(GBN_BUTTON_NAV_MODE)
305 case GBN_BUTTON_NAV_MODE
:
306 case GBN_BUTTON_NAV_MODE
| BUTTON_REPEAT
:
307 if (nav_mode
== NAV_MODE_TREE
)
309 nav_mode
= NAV_MODE_BOARD
;
310 rb
->splash (2 * HZ
/ 3, "board navigation mode");
311 draw_screen_display ();
315 nav_mode
= NAV_MODE_TREE
;
316 rb
->splash (2 * HZ
/ 3, "tree navigation mode");
317 draw_screen_display ();
322 #if defined(GBN_BUTTON_ADVANCE)
323 case GBN_BUTTON_ADVANCE
:
324 case GBN_BUTTON_ADVANCE
| BUTTON_REPEAT
:
325 if (has_more_nodes_sgf ())
327 if (!redo_node_sgf ())
329 rb
->splash (2 * HZ
, "redo failed");
331 draw_screen_display ();
336 #if defined(GBN_BUTTON_RETREAT)
337 case GBN_BUTTON_RETREAT
:
338 case GBN_BUTTON_RETREAT
| BUTTON_REPEAT
:
339 if (has_prev_nodes_sgf ())
341 if (!undo_node_sgf ())
343 rb
->splash (3 * HZ
/ 2, "Undo Failed");
345 draw_screen_display ();
350 case GBN_BUTTON_PLAY
:
351 if (play_mode
== MODE_PLAY
|| play_mode
== MODE_FORCE_PLAY
)
353 if (!play_move_sgf (cursor_pos
, current_player
))
355 rb
->splash (HZ
/ 3, "Illegal Move");
358 else if (play_mode
== MODE_ADD_BLACK
)
360 if (!add_stone_sgf (cursor_pos
, BLACK
))
362 rb
->splash (HZ
/ 3, "Illegal");
365 else if (play_mode
== MODE_ADD_WHITE
)
367 if (!add_stone_sgf (cursor_pos
, WHITE
))
369 rb
->splash (HZ
/ 3, "Illegal");
372 else if (play_mode
== MODE_REMOVE
)
374 if (!add_stone_sgf (cursor_pos
, EMPTY
))
376 rb
->splash (HZ
/ 3, "Illegal");
379 else if (play_mode
== MODE_MARK
)
381 if (!add_mark_sgf (cursor_pos
, PROP_MARK
))
383 rb
->splash (HZ
/ 3, "Couldn't Mark");
386 else if (play_mode
== MODE_CIRCLE
)
388 if (!add_mark_sgf (cursor_pos
, PROP_CIRCLE
))
390 rb
->splash (HZ
/ 3, "Couldn't Mark");
393 else if (play_mode
== MODE_SQUARE
)
395 if (!add_mark_sgf (cursor_pos
, PROP_SQUARE
))
397 rb
->splash (HZ
/ 3, "Couldn't Mark");
400 else if (play_mode
== MODE_TRIANGLE
)
402 if (!add_mark_sgf (cursor_pos
, PROP_TRIANGLE
))
404 rb
->splash (HZ
/ 3, "Couldn't Mark");
407 else if (play_mode
== MODE_LABEL
)
409 if (!add_mark_sgf (cursor_pos
, PROP_LABEL
))
411 rb
->splash (HZ
/ 3, "Couldn't Label");
416 rb
->splash (HZ
, "mode not implemented");
419 draw_screen_display ();
422 case GBN_BUTTON_RIGHT
:
423 case GBN_BUTTON_RIGHT
| BUTTON_REPEAT
:
424 #if defined(GBN_BUTTON_NAV_MODE)
425 if (nav_mode
== NAV_MODE_TREE
)
427 if (has_more_nodes_sgf ())
429 if (!redo_node_sgf ())
431 rb
->splash (2 * HZ
, "Redo Failed");
433 draw_screen_display ();
439 cursor_pos
= WRAP (EAST (cursor_pos
));
440 draw_screen_display ();
441 #if defined(GBN_BUTTON_NAV_MODE)
446 case GBN_BUTTON_LEFT
:
447 case GBN_BUTTON_LEFT
| BUTTON_REPEAT
:
448 #if defined(GBN_BUTTON_NAV_MODE)
449 if (nav_mode
== NAV_MODE_TREE
)
451 if (has_prev_nodes_sgf ())
453 if (!undo_node_sgf ())
455 rb
->splash (2 * HZ
, "Undo Failed");
457 draw_screen_display ();
463 cursor_pos
= WRAP (WEST (cursor_pos
));
464 draw_screen_display ();
465 #if defined(GBN_BUTTON_NAV_MODE)
470 case GBN_BUTTON_DOWN
:
471 case GBN_BUTTON_DOWN
| BUTTON_REPEAT
:
472 cursor_pos
= WRAP (SOUTH (cursor_pos
));
473 draw_screen_display ();
477 case GBN_BUTTON_UP
| BUTTON_REPEAT
:
478 cursor_pos
= WRAP (NORTH (cursor_pos
));
479 draw_screen_display ();
482 case GBN_BUTTON_MENU
:
485 save_game (DEFAULT_SAVE
);
491 draw_screen_display ();
494 #if defined(GBN_BUTTON_CONTEXT)
495 case GBN_BUTTON_CONTEXT
:
497 draw_screen_display ();
501 #if defined(GBN_BUTTON_NEXT_VAR)
502 case GBN_BUTTON_NEXT_VAR
:
503 case GBN_BUTTON_NEXT_VAR
| BUTTON_REPEAT
:
506 if ((temp
= next_variation_sgf ()) >= 0)
508 draw_screen_display ();
509 rb
->splashf (2 * HZ
/ 3, "%d of %d", temp
,
510 num_variations_sgf ());
511 draw_screen_display ();
515 if (num_variations_sgf () > 1)
517 rb
->splashf (HZ
, "Error %d in next_variation_sgf", temp
);
519 draw_screen_display ();
528 if (rb
->default_event_handler (btn
) == SYS_USB_CONNECTED
)
530 return PLUGIN_USB_CONNECTED
;
535 if (is_idle
&& autosave_dirty
)
539 if (autosave_time
!= 0 &&
540 autosave_counter
/ 2 >= autosave_time
)
541 /* counter is in 30 second increments, autosave_time is in
545 DEBUGF("autosaving\n");
546 rb
->splash(HZ
/ 4, "Autosaving...");
547 save_game(DEFAULT_SAVE
);
548 draw_screen_display();
549 autosave_counter
= 0;
554 autosave_counter
= 0;
562 global_cleanup (void)
565 configfile_save(SETTINGS_FILENAME
, config
,
566 sizeof(config
)/sizeof(*config
),
573 setup_stack (&parse_stack
, parse_stack_buffer
,
574 sizeof (parse_stack_buffer
));
579 if (configfile_load(SETTINGS_FILENAME
, config
,
580 sizeof(config
)/sizeof(*config
),
581 SETTINGS_MIN_VERSION
) < 0)
583 /* If the loading failed, save a new config file (as the disk is
586 /* set defaults again just in case (don't know if they can ever
587 * be messed up by configfile_load, and it's basically free anyway)
591 configfile_save(SETTINGS_FILENAME
, config
,
592 sizeof(config
)/sizeof(*config
),
597 enum main_menu_selections
614 MENUITEM_STRINGLIST (menu
, "Rockbox Goban", NULL
,
625 /* for "New" in menu */
626 int new_handi
= 0, new_bs
= MAX_BOARD_SIZE
, new_komi
= 15;
628 char new_save_file
[SAVE_FILE_LENGTH
];
635 selection
= rb
->do_menu (&menu
, &selection
, NULL
, false);
640 rb
->set_int ("board size", "lines", UNIT_INT
,
641 &new_bs
, NULL
, 1, MIN_BOARD_SIZE
, MAX_BOARD_SIZE
,
644 rb
->set_int ("handicap", "stones", UNIT_INT
,
645 &new_handi
, NULL
, 1, 0, 9, NULL
);
656 rb
->set_int ("komi", "moku", UNIT_INT
, &new_komi
, NULL
,
657 1, -300, 300, &komi_formatter
);
659 setup_game (new_bs
, new_bs
, new_handi
, new_komi
);
660 draw_screen_display ();
665 if (!save_game (save_file
))
667 rb
->splash (2 * HZ
, "Save Failed!");
671 rb
->splash (2 * HZ
/ 3, "Saved");
674 draw_screen_display ();
678 rb
->strcpy (new_save_file
, save_file
);
680 if (!rb
->kbd_input (new_save_file
, SAVE_FILE_LENGTH
))
685 if (!save_game (new_save_file
))
687 rb
->splash (2 * HZ
, "Save Failed!");
691 rb
->strcpy (save_file
, new_save_file
);
692 rb
->splash (2 * HZ
/ 3, "Saved");
696 draw_screen_display ();
704 if (!audio_stolen_sgf ())
706 playback_control (NULL
);
710 rb
->splash (1 * HZ
, "Audio has been disabled!");
720 draw_screen_display ();
733 case MENU_ATTACHED_USB
:
749 zoom_preview (int current
)
751 set_zoom_display (current
);
752 draw_screen_display ();
753 rb
->splash (0, "Preview");
759 unsigned int zoom_level
;
760 unsigned int old_val
;
763 unsigned int min_val
= min_zoom_display ();
764 unsigned int max_val
= max_zoom_display ();
766 zoom_level
= old_val
= current_zoom_display ();
770 zoom_preview (zoom_level
);
773 switch (action
= rb
->get_action (CONTEXT_LIST
, TIMEOUT_BLOCK
))
776 set_zoom_display (zoom_level
);
778 rb
->splash (HZ
/ 2, "Zoom Set");
779 saved_circle_size
= intersection_size
;
782 case ACTION_STD_CANCEL
:
783 set_zoom_display (old_val
);
785 rb
->splash (HZ
/ 2, "Cancelled");
788 case ACTION_STD_CONTEXT
:
789 zoom_level
= old_val
;
790 zoom_preview (zoom_level
);
793 case ACTION_STD_NEXT
:
794 case ACTION_STD_NEXTREPEAT
:
795 zoom_level
= zoom_level
* 3 / 2;
797 /* 1 * 3 / 2 is 1 again... */
803 if (zoom_level
> max_val
)
805 zoom_level
= min_val
;
808 zoom_preview (zoom_level
);
811 case ACTION_STD_PREV
:
812 case ACTION_STD_PREVREPEAT
:
813 zoom_level
= zoom_level
* 2 / 3;
815 if (zoom_level
< min_val
)
817 zoom_level
= max_val
;
819 zoom_preview (zoom_level
);
826 if (rb
->default_event_handler (action
) == SYS_USB_CONNECTED
)
837 enum gameinfo_menu_selections
839 GINFO_BASIC_INFO
= 0,
861 do_gameinfo_menu (void)
863 MENUITEM_STRINGLIST (gameinfo_menu
, "Game Info", NULL
,
884 * if you edit this string list, make sure you keep
885 * menu_selection_to_prop function in line with it!! (see the bottom
891 char *gameinfo_string
;
892 int gameinfo_string_size
;
899 selection
= rb
->do_menu (&gameinfo_menu
, &selection
, NULL
, false);
905 case GINFO_BLACK_PLAYER
:
906 case GINFO_BLACK_RANK
:
907 case GINFO_BLACK_TEAM
:
908 case GINFO_WHITE_PLAYER
:
909 case GINFO_WHITE_RANK
:
910 case GINFO_WHITE_TEAM
:
915 if (!get_header_string_and_size (&header
,
916 menu_selection_to_prop
917 (selection
), &gameinfo_string
,
918 &gameinfo_string_size
))
920 rb
->splash (3 * HZ
, "Couldn't get header string");
924 rb
->kbd_input (gameinfo_string
, gameinfo_string_size
);
925 sanitize_string (gameinfo_string
);
929 /* these need special handling in some way, so they are
932 case GINFO_BASIC_INFO
:
936 case GINFO_TIME_LIMIT
:
937 rb
->set_int ("Time Limit", "", UNIT_INT
, &header
.time_limit
,
938 NULL
, 60, 0, 24 * 60 * 60, &time_formatter
);
943 rb
->splashf (0, "%d stones. Start a new game to set handicap",
945 rb
->action_userabort(TIMEOUT_BLOCK
);
949 rb
->set_int ("Komi", "moku", UNIT_INT
, &header
.komi
, NULL
,
950 1, -300, 300, &komi_formatter
);
956 rb
->set_int ("Ruleset", "", UNIT_INT
, &new_ruleset
, NULL
,
957 1, 0, NUM_RULESETS
- 1, &ruleset_formatter
);
959 rb
->strcpy (header
.ruleset
, ruleset_names
[new_ruleset
]);
966 case MENU_ATTACHED_USB
:
974 enum context_menu_selections
993 do_context_menu (void)
999 MENUITEM_STRINGLIST (context_menu
, "Context Menu", NULL
,
1000 "Play Mode (default)",
1017 selection
= rb
->do_menu (&context_menu
, &selection
, NULL
, false);
1022 play_mode
= MODE_PLAY
;
1027 play_mode
= MODE_ADD_BLACK
;
1032 play_mode
= MODE_ADD_WHITE
;
1037 play_mode
= MODE_REMOVE
;
1042 if (!play_move_sgf (PASS_POS
, current_player
))
1044 rb
->splash (HZ
, "Error while passing!");
1050 if ((temp
= next_variation_sgf ()) >= 0)
1052 draw_screen_display ();
1053 rb
->splashf (2 * HZ
/ 3, "%d of %d", temp
,
1054 num_variations_sgf ());
1055 draw_screen_display ();
1059 if (num_variations_sgf () > 1)
1061 rb
->splashf (HZ
, "Error %d in next_variation_sgf", temp
);
1065 rb
->splash (HZ
, "No next variation");
1071 play_mode
= MODE_FORCE_PLAY
;
1076 play_mode
= MODE_MARK
;
1081 play_mode
= MODE_CIRCLE
;
1086 play_mode
= MODE_SQUARE
;
1091 play_mode
= MODE_TRIANGLE
;
1096 play_mode
= MODE_LABEL
;
1101 if (!do_comment_edit ())
1103 DEBUGF ("Editing comment failed\n");
1104 rb
->splash (HZ
, "Read or write failed!\n");
1111 case GO_TO_PREVIOUS
:
1112 case MENU_ATTACHED_USB
:
1120 enum options_menu_selections
1122 OMENU_SHOW_VARIATIONS
= 0,
1123 OMENU_DISABLE_POWEROFF
,
1124 OMENU_AUTOSAVE_TIME
,
1129 do_options_menu (void)
1134 MENUITEM_STRINGLIST (options_menu
, "Options Menu", NULL
,
1135 "Show Child Variations?",
1136 "Disable Idle Poweroff?",
1137 "Idle Autosave Time",
1138 "Automatically Show Comments?");
1142 selection
= rb
->do_menu (&options_menu
, &selection
, NULL
, false);
1146 case OMENU_SHOW_VARIATIONS
:
1147 rb
->set_bool("Draw Variations?", &draw_variations
);
1148 clear_marks_display ();
1149 set_all_marks_sgf ();
1150 if (draw_variations
)
1152 mark_child_variations_sgf ();
1156 case OMENU_DISABLE_POWEROFF
:
1157 rb
->set_bool("Disable Idle Poweroff?", &disable_shutdown
);
1160 case OMENU_AUTOSAVE_TIME
:
1161 rb
->set_int("Idle Autosave Time", "minutes", UNIT_INT
,
1162 &autosave_time
, NULL
, 1, 0, MAX_AUTOSAVE
,
1163 &autosave_formatter
);
1164 autosave_counter
= 0;
1167 case OMENU_AUTO_COMMENT
:
1168 rb
->set_bool("Auto Show Comments?", &auto_show_comments
);
1172 case GO_TO_PREVIOUS
:
1173 case MENU_ATTACHED_USB
:
1183 do_comment_edit (void)
1187 rb
->memset (cbuffer
, 0, sizeof (cbuffer
));
1189 if (read_comment_sgf (cbuffer
, sizeof (cbuffer
)) < 0)
1194 if (!rb
->kbd_input (cbuffer
, sizeof (cbuffer
)))
1196 /* user didn't edit, no reason to write it back */
1200 if (write_comment_sgf (cbuffer
) < 0)
1208 static enum prop_type_t
1209 menu_selection_to_prop (int selection
)
1213 case GINFO_OVERTIME
:
1214 return PROP_OVERTIME
;
1217 case GINFO_BLACK_PLAYER
:
1218 return PROP_BLACK_NAME
;
1219 case GINFO_BLACK_RANK
:
1220 return PROP_BLACK_RANK
;
1221 case GINFO_BLACK_TEAM
:
1222 return PROP_BLACK_TEAM
;
1223 case GINFO_WHITE_PLAYER
:
1224 return PROP_WHITE_NAME
;
1225 case GINFO_WHITE_RANK
:
1226 return PROP_WHITE_RANK
;
1227 case GINFO_WHITE_TEAM
:
1228 return PROP_WHITE_TEAM
;
1238 DEBUGF ("Tried to get prop from invalid menu selection!!!\n");
1239 return PROP_PLACE
; /* just pick one, there's a major bug if