A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 601 lines 17 kB view raw
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}