summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJJ Style <style.jj@protonmail.com>2023-09-11 23:57:21 +0100
committerSolomon Peachy <pizza@shaftnet.org>2024-01-23 22:32:22 -0500
commit49910eca4b400c817d1a6b65a53348280374927a (patch)
treee9c0ad714de5a25e9fe345802a660892df20888a
parentd50470bc7db942105c3bd774ea08a836b842d58c (diff)
downloadrockbox-49910eca4b400c817d1a6b65a53348280374927a.tar.gz
rockbox-49910eca4b400c817d1a6b65a53348280374927a.zip
Implement dart scorer plugin application.
Edit: - Add name to credits - Add entry in manual Change-Id: I0e0b062e001ae9134db3ee6e4fba21e93ddd04ee
-rw-r--r--apps/plugins/CATEGORIES1
-rw-r--r--apps/plugins/SOURCES1
-rw-r--r--apps/plugins/dart_scorer.c601
-rw-r--r--docs/CREDITS1
-rw-r--r--manual/plugins/dart_scorer.tex17
-rw-r--r--manual/plugins/main.tex2
6 files changed, 623 insertions, 0 deletions
diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES
index 49016fef13..5320015772 100644
--- a/apps/plugins/CATEGORIES
+++ b/apps/plugins/CATEGORIES
@@ -22,6 +22,7 @@ clock,apps
22codebuster,games 22codebuster,games
23credits,viewers 23credits,viewers
24cube,demos 24cube,demos
25dart_scorer,apps
25db_commit,apps 26db_commit,apps
26db_folder_select,viewers 27db_folder_select,viewers
27demystify,demos 28demystify,demos
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES
index b857f26ba5..bf36d50a40 100644
--- a/apps/plugins/SOURCES
+++ b/apps/plugins/SOURCES
@@ -9,6 +9,7 @@ tagcache/tagcache.c
9chessclock.c 9chessclock.c
10credits.c 10credits.c
11cube.c 11cube.c
12dart_scorer.c
12dict.c 13dict.c
13jackpot.c 14jackpot.c
14keybox.c 15keybox.c
diff --git a/apps/plugins/dart_scorer.c b/apps/plugins/dart_scorer.c
new file mode 100644
index 0000000000..1e8dd8f37b
--- /dev/null
+++ b/apps/plugins/dart_scorer.c
@@ -0,0 +1,601 @@
1#include "plugin.h"
2#include "lib/display_text.h"
3#include "lib/helper.h"
4#include "lib/playback_control.h"
5#include "lib/pluginlib_exit.h"
6#include "lib/pluginlib_actions.h"
7
8#define BUTTON_ROWS 6
9#define BUTTON_COLS 5
10
11#define REC_HEIGHT (int)(LCD_HEIGHT / (BUTTON_ROWS + 1))
12#define REC_WIDTH (int)(LCD_WIDTH / BUTTON_COLS)
13
14#define Y_7_POS (LCD_HEIGHT) /* Leave room for the border */
15#define Y_6_POS (Y_7_POS - REC_HEIGHT) /* y6 = 63 */
16#define Y_5_POS (Y_6_POS - REC_HEIGHT) /* y5 = 53 */
17#define Y_4_POS (Y_5_POS - REC_HEIGHT) /* y4 = 43 */
18#define Y_3_POS (Y_4_POS - REC_HEIGHT) /* y3 = 33 */
19#define Y_2_POS (Y_3_POS - REC_HEIGHT) /* y2 = 23 */
20#define Y_1_POS (Y_2_POS - REC_HEIGHT) /* y1 = 13 */
21#define Y_0_POS 0 /* y0 = 0 */
22
23#define X_0_POS 0 /* x0 = 0 */
24#define X_1_POS (X_0_POS + REC_WIDTH) /* x1 = 22 */
25#define X_2_POS (X_1_POS + REC_WIDTH) /* x2 = 44 */
26#define X_3_POS (X_2_POS + REC_WIDTH) /* x3 = 66 */
27#define X_4_POS (X_3_POS + REC_WIDTH) /* x4 = 88 */
28#define X_5_POS (X_4_POS + REC_WIDTH) /* x5 = 110, column 111 left blank */
29
30#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || (CONFIG_KEYPAD == IPOD_4G_PAD)
31#define DARTS_QUIT PLA_SELECT_REPEAT
32#else
33#define DARTS_QUIT PLA_CANCEL
34#endif
35#define DARTS_SELECT PLA_SELECT
36#define DARTS_RIGHT PLA_RIGHT
37#define DARTS_LEFT PLA_LEFT
38#define DARTS_UP PLA_UP
39#define DARTS_DOWN PLA_DOWN
40#define DARTS_RRIGHT PLA_RIGHT_REPEAT
41#define DARTS_RLEFT PLA_LEFT_REPEAT
42#define DARTS_RUP PLA_UP_REPEAT
43#define DARTS_RDOWN PLA_DOWN_REPEAT
44
45#define RESUME_FILE PLUGIN_GAMES_DATA_DIR "/dart_scorer.save"
46/* leave first line blank on bitmap display, for pause icon */
47#define FIRST_LINE 1
48
49#define NUM_PLAYERS 2
50#define MAX_UNDO 100
51
52static const struct button_mapping *plugin_contexts[] = {pla_main_ctx};
53
54/* game data structures */
55enum game_mode
56{
57 five,
58 three
59};
60static struct settings_struct
61{
62 enum game_mode mode;
63 int scores[2];
64 bool turn;
65 int throws;
66 int history[MAX_UNDO];
67 int history_ptr;
68} settings;
69
70/* temporary data */
71static bool loaded = false; /* has a save been loaded? */
72int btn_row, btn_col; /* current position index for button */
73int prev_btn_row, prev_btn_col; /* previous cursor position */
74unsigned char *buttonChar[6][5] = {
75 {"", "Single", "Double", "Triple", ""},
76 {"1", "2", "3", "4", "5"},
77 {"6", "7", "8", "9", "10"},
78 {"11", "12", "13", "14", "15"},
79 {"16", "17", "18", "19", "20"},
80 {"", "Missed", "Bull", "Undo", ""}};
81int modifier;
82
83static int do_dart_scorer_pause_menu(void);
84static void drawButtons(void);
85
86/* First, increases *dimen1 by dimen1_delta modulo dimen1_modulo.
87 If dimen1 wraps, increases *dimen2 by dimen2_delta modulo dimen2_modulo.
88*/
89static void move_with_wrap_and_shift(
90 int *dimen1, int dimen1_delta, int dimen1_modulo,
91 int *dimen2, int dimen2_delta, int dimen2_modulo)
92{
93 bool wrapped = false;
94
95 *dimen1 += dimen1_delta;
96 if (*dimen1 < 0)
97 {
98 *dimen1 = dimen1_modulo - 1;
99 wrapped = true;
100 }
101 else if (*dimen1 >= dimen1_modulo)
102 {
103 *dimen1 = 0;
104 wrapped = true;
105 }
106
107 if (wrapped)
108 {
109 /* Make the dividend always positive to be sure about the result.
110 Adding dimen2_modulo does not change it since we do it modulo. */
111 *dimen2 = (*dimen2 + dimen2_modulo + dimen2_delta) % dimen2_modulo;
112 }
113}
114
115static void drawButtons()
116{
117 int i, j, w, h;
118 for (i = 0; i <= 5; i++)
119 {
120 for (j = 0; j <= 4; j++)
121 {
122 unsigned char button_text[16];
123 char *selected_prefix = (i == 0 && modifier > 0 && j == modifier) ? "*" : "";
124 rb->snprintf(button_text, sizeof(button_text), "%s%s", selected_prefix, buttonChar[i][j]);
125 rb->lcd_getstringsize(button_text, &w, &h);
126 if (i == btn_row && j == btn_col) /* selected item */
127 rb->lcd_set_drawmode(DRMODE_SOLID);
128 else
129 rb->lcd_set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID);
130 rb->lcd_fillrect(X_0_POS + j * REC_WIDTH,
131 Y_1_POS + i * REC_HEIGHT,
132 REC_WIDTH, REC_HEIGHT + 1);
133 if (i == btn_row && j == btn_col) /* selected item */
134 rb->lcd_set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID);
135 else
136 rb->lcd_set_drawmode(DRMODE_SOLID);
137 rb->lcd_putsxy(X_0_POS + j * REC_WIDTH + (REC_WIDTH - w) / 2,
138 Y_1_POS + i * REC_HEIGHT + (REC_HEIGHT - h) / 2 + 1,
139 button_text);
140 }
141 }
142 rb->lcd_set_drawmode(DRMODE_SOLID);
143}
144
145static void draw(void)
146{
147 rb->lcd_clear_display();
148
149 char buf[32];
150
151 int x = 5;
152 int y = 10;
153 for (int i = 0; i < NUM_PLAYERS; ++i, x = x + 95)
154 {
155 char *turn_marker = (i == settings.turn) ? "*" : "";
156 rb->snprintf(buf, sizeof(buf), "%sPlayer %d: %d", turn_marker, i + 1, settings.scores[i]);
157 rb->lcd_putsxy(x, y, buf);
158 }
159 int throws_x = (LCD_WIDTH / 2) - 10;
160 char throws_buf[3];
161 for (int i = 0; i < settings.throws; ++i, throws_x += 5)
162 {
163 rb->strcat(throws_buf, "1");
164 rb->lcd_putsxy(throws_x, y, "|");
165 }
166
167 drawButtons();
168
169 rb->lcd_update();
170}
171
172/*****************************************************************************
173* save_game() saves the current game state.
174******************************************************************************/
175static void save_game(void)
176{
177 int fd = rb->open(RESUME_FILE, O_WRONLY | O_CREAT, 0666);
178 if (fd < 0)
179 return;
180
181 rb->write(fd, &settings, sizeof(struct settings_struct));
182
183 rb->close(fd);
184 rb->lcd_update();
185}
186
187/* load_game() loads the saved game and returns load success.*/
188static bool load_game(void)
189{
190 signed int fd;
191 bool loaded = false;
192
193 /* open game file */
194 fd = rb->open(RESUME_FILE, O_RDONLY);
195 if (fd < 0)
196 return false;
197
198 /* read in saved game */
199 if (rb->read(fd, &settings, sizeof(struct settings_struct)) == (long)sizeof(struct settings_struct))
200 {
201 loaded = true;
202 }
203
204 rb->close(fd);
205
206 return loaded;
207 return false;
208}
209
210/* asks the user if they wish to quit */
211static bool confirm_quit(void)
212{
213 const struct text_message prompt = {(const char *[]){"Are you sure?", "This will clear your current game."}, 2};
214 enum yesno_res response = rb->gui_syncyesno_run(&prompt, NULL, NULL);
215 if (response == YESNO_NO)
216 return false;
217 else
218 return true;
219}
220
221/* displays the help text */
222static bool do_help(void)
223{
224
225#ifdef HAVE_LCD_COLOR
226 rb->lcd_set_foreground(LCD_WHITE);
227 rb->lcd_set_background(LCD_BLACK);
228#endif
229
230 rb->lcd_setfont(FONT_UI);
231
232 static char *help_text[] = {"Dart Scorer", "", "", "Keep score of your darts game."};
233
234 struct style_text style[] = {
235 {0, TEXT_CENTER | TEXT_UNDERLINE},
236 };
237
238 return display_text(ARRAYLEN(help_text), help_text, style, NULL, true);
239}
240
241static void undo(void)
242{
243 if (!settings.history_ptr)
244 {
245 rb->splash(HZ * 2, "Out of undos!");
246 return;
247 }
248
249 /* jumping back to previous player? */
250 int turn = settings.throws == 3 ? !settings.turn : settings.turn;
251 if (turn != settings.turn)
252 {
253 settings.throws = 0;
254 settings.turn ^= true;
255 }
256
257 if (settings.history[settings.history_ptr - 1] >= 0)
258 {
259 settings.scores[turn] += settings.history[--settings.history_ptr];
260 ++settings.throws;
261 }
262 else
263 {
264 /*
265 negative history means we bust. negative filled for all skipped throws
266 from being bust so consume back until no more
267 */
268 for (; settings.throws < 3 && settings.history[settings.history_ptr - 1] < 0; --settings.history_ptr, ++settings.throws)
269 {
270 }
271 }
272}
273
274static void init_game(bool newgame)
275{
276 if (newgame)
277 {
278 /* initialize the game context */
279 modifier = 1;
280 btn_row = 1;
281 btn_col = 0;
282
283 int game_mode = -1;
284 MENUITEM_STRINGLIST(menu, "Game Mode", NULL, "501", "301");
285 while (game_mode < 0)
286 {
287 switch (rb->do_menu(&menu, &game_mode, NULL, false))
288 {
289 case 0:
290 {
291 settings.mode = five;
292 break;
293 }
294 case 1:
295 {
296 settings.mode = three;
297 break;
298 }
299 }
300
301 for (int i = 0; i < NUM_PLAYERS; ++i)
302 {
303 settings.scores[i] = (settings.mode == five) ? 501 : 301;
304 }
305 settings.turn = false;
306 settings.throws = 3;
307 settings.history_ptr = 0;
308 rb->lcd_clear_display();
309 }
310 }
311}
312
313/* main game loop */
314static enum plugin_status do_game(bool newgame)
315{
316 init_game(newgame);
317 draw();
318
319 while (1)
320 {
321 /* wait for button press */
322 int button = pluginlib_getaction(-1, plugin_contexts, ARRAYLEN(plugin_contexts));
323 unsigned char *selected = buttonChar[btn_row][btn_col];
324 switch (button)
325 {
326 case DARTS_SELECT:
327 if ((!rb->strcmp(selected, "")) || (!rb->strcmp(selected, "Single")))
328 modifier = 1;
329 else if (!rb->strcmp(selected, "Double"))
330 modifier = 2;
331 else if (!rb->strcmp(selected, "Triple"))
332 modifier = 3;
333 else if (!rb->strcmp(selected, "Undo"))
334 {
335 undo();
336 continue;
337 }
338 else
339 {
340 /* main logic of score keeping */
341 if (modifier == 0)
342 modifier = 1;
343 int hit = (!rb->strcmp(selected, "Bull")) ? 25 : rb->atoi(selected);
344 if (hit == 25 && modifier == 3)
345 {
346 /* no triple bullseye! */
347 rb->splash(HZ * 2, "Triple Bull... Don't be silly!");
348 continue;
349 }
350 hit *= modifier;
351 if (hit > settings.scores[settings.turn])
352 {
353 rb->splash(HZ * 2, "Bust! End of turn.");
354 for (int i = 0; i < settings.throws; ++i)
355 settings.history[settings.history_ptr++] = -1;
356 settings.throws = 0;
357 }
358 else if (hit == settings.scores[settings.turn] - 1)
359 {
360 rb->splash(HZ * 2, "1 left! Must checkout with a double. End of turn.");
361 for (int i = 0; i < settings.throws; ++i)
362 settings.history[settings.history_ptr++] = -1;
363 settings.throws = 0;
364 }
365 else if (hit == settings.scores[settings.turn] && modifier != 2)
366 {
367 rb->splash(HZ * 2, "Must checkout with a double! End of turn.");
368 for (int i = 0; i < settings.throws; ++i)
369 settings.history[settings.history_ptr++] = -1;
370 settings.throws = 0;
371 }
372 else
373 {
374 settings.scores[settings.turn] -= hit;
375 --settings.throws;
376 settings.history[settings.history_ptr++] = hit;
377 modifier = 1;
378 if (!settings.scores[settings.turn])
379 goto GAMEOVER;
380 }
381
382 if (!settings.throws)
383 {
384 settings.throws = 3;
385 settings.turn ^= true;
386 }
387 }
388 break;
389 case DARTS_LEFT:
390 case DARTS_RLEFT:
391 move_with_wrap_and_shift(
392 &btn_col, -1, BUTTON_COLS,
393 &btn_row, 0, BUTTON_ROWS);
394 break;
395 case DARTS_RIGHT:
396 case DARTS_RRIGHT:
397 move_with_wrap_and_shift(
398 &btn_col, 1, BUTTON_COLS,
399 &btn_row, 0, BUTTON_ROWS);
400 break;
401#ifdef DARTS_UP
402 case DARTS_UP:
403 case DARTS_RUP:
404#ifdef HAVE_SCROLLWHEEL
405 case PLA_SCROLL_BACK:
406 case PLA_SCROLL_BACK_REPEAT:
407#endif
408 move_with_wrap_and_shift(
409 &btn_row, -1, BUTTON_ROWS,
410 &btn_col, 0, BUTTON_COLS);
411 break;
412#endif
413#ifdef DARTS_DOWN
414 case DARTS_DOWN:
415 case DARTS_RDOWN:
416#ifdef HAVE_SCROLLWHEEL
417 case PLA_SCROLL_FWD:
418 case PLA_SCROLL_FWD_REPEAT:
419#endif
420 move_with_wrap_and_shift(
421 &btn_row, 1, BUTTON_ROWS,
422 &btn_col, 0, BUTTON_COLS);
423 break;
424#endif
425 case DARTS_QUIT:
426 switch (do_dart_scorer_pause_menu())
427 {
428 case 0: /* resume */
429 break;
430 case 1:
431 init_game(true);
432 continue;
433 case 2: /* quit w/o saving */
434 rb->remove(RESUME_FILE);
435 return PLUGIN_ERROR;
436 case 3: /* save & quit */
437 save_game();
438 return PLUGIN_ERROR;
439 break;
440 }
441 break;
442 default:
443 {
444 exit_on_usb(button); /* handle poweroff and USB */
445 break;
446 }
447 }
448 draw();
449 }
450
451GAMEOVER:
452 rb->splashf(HZ * 3, "Gameover. Player %d wins!", settings.turn + 1);
453
454 return PLUGIN_OK;
455}
456
457/* decide if this_item should be shown in the main menu */
458/* used to hide resume option when there is no save */
459static int mainmenu_cb(int action,
460 const struct menu_item_ex *this_item,
461 struct gui_synclist *this_list)
462{
463 (void)this_list;
464 int idx = ((intptr_t)this_item);
465 if (action == ACTION_REQUEST_MENUITEM && !loaded && (idx == 0 || idx == 5))
466 return ACTION_EXIT_MENUITEM;
467 return action;
468}
469
470/* show the pause menu */
471static int do_dart_scorer_pause_menu(void)
472{
473 int sel = 0;
474 MENUITEM_STRINGLIST(menu, "Dart Scorer", NULL,
475 "Resume Game",
476 "Start New Game",
477 "Playback Control",
478 "Help",
479 "Quit without Saving",
480 "Quit");
481 while (1)
482 {
483 switch (rb->do_menu(&menu, &sel, NULL, false))
484 {
485 case 0:
486 {
487 rb->splash(HZ * 2, "Resume");
488 return 0;
489 }
490 case 1:
491 {
492 if (!confirm_quit())
493 break;
494 else
495 {
496 rb->splash(HZ * 2, "New Game");
497 return 1;
498 }
499 }
500 case 2:
501 playback_control(NULL);
502 break;
503 case 3:
504 do_help();
505 break;
506 case 4: /* quit w/o saving */
507 {
508 if (!confirm_quit())
509 break;
510 else
511 {
512 return 2;
513 }
514 }
515 case 5:
516 return 3;
517 default:
518 break;
519 }
520 }
521}
522
523/* show the main menu */
524static enum plugin_status do_dart_scorer_menu(void)
525{
526 int sel = 0;
527 loaded = load_game();
528 MENUITEM_STRINGLIST(menu,
529 "Dart Scorer Menu",
530 mainmenu_cb,
531 "Resume Game",
532 "Start New Game",
533 "Playback Control",
534 "Help",
535 "Quit without Saving",
536 "Quit");
537 while (true)
538 {
539 switch (rb->do_menu(&menu, &sel, NULL, false))
540 {
541 case 0: /* Start new game or resume a game */
542 case 1:
543 {
544 if (sel == 1 && loaded)
545 {
546 if (!confirm_quit())
547 break;
548 }
549 enum plugin_status ret = do_game(sel == 1);
550 switch (ret)
551 {
552 case PLUGIN_OK:
553 {
554 loaded = false;
555 rb->remove(RESUME_FILE);
556 break;
557 }
558 case PLUGIN_USB_CONNECTED:
559 save_game();
560 return ret;
561 case PLUGIN_ERROR: /* exit without menu */
562 return PLUGIN_OK;
563 default:
564 break;
565 }
566 break;
567 }
568 case 2:
569 playback_control(NULL);
570 break;
571 case 3:
572 do_help();
573 break;
574 case 4:
575 if (confirm_quit())
576 return PLUGIN_OK;
577 break;
578 case 5:
579 if (loaded)
580 save_game();
581 return PLUGIN_OK;
582 default:
583 break;
584 }
585 }
586}
587
588/* prepares for exit */
589static void cleanup(void)
590{
591 backlight_use_settings();
592}
593
594enum plugin_status plugin_start(const void *parameter)
595{
596 (void)parameter;
597 /* now start the game menu */
598 enum plugin_status ret = do_dart_scorer_menu();
599 cleanup();
600 return ret;
601}
diff --git a/docs/CREDITS b/docs/CREDITS
index b9e54f5cf3..4783033466 100644
--- a/docs/CREDITS
+++ b/docs/CREDITS
@@ -716,6 +716,7 @@ Roman Artiukhin
716Richard Goedeken 716Richard Goedeken
717Mihaly 'Hermit' Horvath 717Mihaly 'Hermit' Horvath
718Uwe Kleine-König 718Uwe Kleine-König
719JJ Style
719 720
720The libmad team 721The libmad team
721The wavpack team 722The wavpack team
diff --git a/manual/plugins/dart_scorer.tex b/manual/plugins/dart_scorer.tex
new file mode 100644
index 0000000000..aab46894c6
--- /dev/null
+++ b/manual/plugins/dart_scorer.tex
@@ -0,0 +1,17 @@
1\subsection{Dart Scorer}
2
3The dart scorer plugin allows scoring a game of darts (301/501) for two players. It supports modifiers for double/triple hits, bullseye, undo, and game saving and resuming.
4
5\begin{btnmap}
6 \PluginUp, \PluginDown, \PluginLeft, \PluginRight
7 \opt{HAVEREMOTEKEYMAP}{& \PluginRCUp, \PluginRCDown, \PluginRCLeft, \PluginRCRight}
8 & Move cursor\\
9
10 \PluginSelect
11 & Select button under cursor\\
12
13 \nopt{IPOD_4G_PAD,IPOD_3G_PAD}{\PluginCancel}
14 \opt{IPOD_4G_PAD,IPOD_3G_PAD}{Long \ButtonSelect}
15 \opt{HAVEREMOTEKEYMAP}{& \PluginRCCancel}
16 & Go to menu\\
17\end{btnmap}
diff --git a/manual/plugins/main.tex b/manual/plugins/main.tex
index 24fe2b75ee..4725818029 100644
--- a/manual/plugins/main.tex
+++ b/manual/plugins/main.tex
@@ -246,6 +246,8 @@ option from the \setting{Context Menu} (see \reference{ref:Contextmenu}).}
246 246
247\opt{rtc}{\input{plugins/clock.tex}} 247\opt{rtc}{\input{plugins/clock.tex}}
248 248
249\input{plugins/dart_scorer.tex}
250
249\input{plugins/dict.tex} 251\input{plugins/dict.tex}
250 252
251\input{plugins/disktidy.tex} 253\input{plugins/disktidy.tex}