A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd

Implement dart scorer plugin application.

Edit:
- Add name to credits
- Add entry in manual

Change-Id: I0e0b062e001ae9134db3ee6e4fba21e93ddd04ee

authored by

JJ Style and committed by
Solomon Peachy
49910eca d50470bc

+623
+1
apps/plugins/CATEGORIES
··· 22 22 codebuster,games 23 23 credits,viewers 24 24 cube,demos 25 + dart_scorer,apps 25 26 db_commit,apps 26 27 db_folder_select,viewers 27 28 demystify,demos
+1
apps/plugins/SOURCES
··· 9 9 chessclock.c 10 10 credits.c 11 11 cube.c 12 + dart_scorer.c 12 13 dict.c 13 14 jackpot.c 14 15 keybox.c
+601
apps/plugins/dart_scorer.c
··· 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 + 52 + static const struct button_mapping *plugin_contexts[] = {pla_main_ctx}; 53 + 54 + /* game data structures */ 55 + enum game_mode 56 + { 57 + five, 58 + three 59 + }; 60 + static 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 */ 71 + static bool loaded = false; /* has a save been loaded? */ 72 + int btn_row, btn_col; /* current position index for button */ 73 + int prev_btn_row, prev_btn_col; /* previous cursor position */ 74 + unsigned 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", ""}}; 81 + int modifier; 82 + 83 + static int do_dart_scorer_pause_menu(void); 84 + static 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 + */ 89 + static 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 + 115 + static 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 + 145 + static 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 + ******************************************************************************/ 175 + static 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.*/ 188 + static 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 */ 211 + static 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 */ 222 + static 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 + 241 + static 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 + 274 + static 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 */ 314 + static 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 + 451 + GAMEOVER: 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 */ 459 + static 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 */ 471 + static 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 */ 524 + static 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 */ 589 + static void cleanup(void) 590 + { 591 + backlight_use_settings(); 592 + } 593 + 594 + enum 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 + }
+1
docs/CREDITS
··· 716 716 Richard Goedeken 717 717 Mihaly 'Hermit' Horvath 718 718 Uwe Kleine-König 719 + JJ Style 719 720 720 721 The libmad team 721 722 The wavpack team
+17
manual/plugins/dart_scorer.tex
··· 1 + \subsection{Dart Scorer} 2 + 3 + The 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}
+2
manual/plugins/main.tex
··· 246 246 247 247 \opt{rtc}{\input{plugins/clock.tex}} 248 248 249 + \input{plugins/dart_scorer.tex} 250 + 249 251 \input{plugins/dict.tex} 250 252 251 253 \input{plugins/disktidy.tex}