A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 537 lines 16 kB view raw
1/*************************************************************************** 2* __________ __ ___. 3* Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7* \/ \/ \/ \/ \/ 8* $Id$ 9* 10* Copyright (C) 2009 Clément Pit--Claudel 11* 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. 16* 17* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18* KIND, either express or implied. 19* 20****************************************************************************/ 21 22#include "plugin.h" 23#include "lib/configfile.h" 24#include "lib/playback_control.h" 25#include "lib/pluginlib_actions.h" 26 27 28 29/* Limits */ 30#define MAX_PIECES_COUNT 5 31#define MAX_COLORS_COUNT 8 32#define MAX_GUESSES_COUNT 10 33 34const struct button_mapping *plugin_contexts[] = { 35 pla_main_ctx, 36#ifdef HAVE_REMOTE_LCD 37 pla_remote_ctx, 38#endif 39}; 40 41/* 42 * Screen structure: 43 * * (guesses_count) lines of guesses, 44 * * 1 center line of solution (hidden), 45 * * 1 line showing available colors. 46 * 47 * Status vars: 48 * * quit: exit the plugin 49 * * leave: restart the plugin (leave the current game) 50 * * game_ended: the game has ended 51 * * found: the combination has been found 52 * 53 * Colors used are taken from the Tango project. 54 * 55 * Due to integer truncations, 2 vars are used for some objects' dimensions 56 * (eg. true_guess_w, true_score_w). The actual dimension of these objects is 57 * stored in the corresponding var. without the "true" prefix. 58 */ 59 60struct mm_score { 61 int correct; 62 int misplaced; 63}; 64 65struct mm_line { 66 struct mm_score score; 67 int pieces[MAX_PIECES_COUNT]; 68}; 69 70const int colors[MAX_COLORS_COUNT] = { 71 LCD_RGBPACK(252, 233, 79), 72 LCD_RGBPACK(206, 92, 0), 73 LCD_RGBPACK(143, 89, 2), 74 LCD_RGBPACK( 78, 154, 6), 75 /* LCD_RGBPACK( 32, 74, 135), */ 76 LCD_RGBPACK( 52, 101, 164), 77 /* LCD_RGBPACK(114, 159, 207), */ 78 LCD_RGBPACK(117, 80, 123), 79 /* LCD_RGBPACK(173, 127, 168), */ 80 LCD_RGBPACK(164, 0, 0), 81 LCD_RGBPACK(238, 238, 236), 82}; 83 84/* Flags */ 85static bool quit, leave, usb; 86static bool found, game_ended; 87 88/* Settings */ 89struct settings { 90 int pieces; 91 int colors; 92 int guesses; 93 bool labeling; 94 bool framing; 95}; 96static struct settings settings = { 97 5, 7, 10, false, false, 98}; 99static struct settings old_settings; 100static int pieces_count; 101static int colors_count; 102static int guesses_count; 103 104/* Display */ 105#define ALUMINIUM LCD_RGBPACK(136, 138, 133) 106 107#define MARGIN 5 108#define X_MARGIN (LCD_WIDTH / 20) 109#define Y_MARGIN (LCD_HEIGHT / 20) 110#define GAME_H (LCD_HEIGHT - (2 * Y_MARGIN)) 111#define LINE_W (LCD_WIDTH - (2 * X_MARGIN)) 112 113#define CONFIG_FILE_NAME "codebuster.cfg" 114 115static struct configdata config[] = { 116 {TYPE_INT, 0, MAX_PIECES_COUNT, { .int_p = &settings.pieces }, "pieces", NULL}, 117 {TYPE_INT, 0, MAX_COLORS_COUNT, { .int_p = &settings.colors }, "colors", NULL}, 118 {TYPE_INT, 0, MAX_GUESSES_COUNT, { .int_p = &settings.guesses }, "guesses", NULL}, 119 {TYPE_BOOL, 0, 1, { .bool_p = &settings.labeling }, "labeling", NULL}, 120 {TYPE_BOOL, 0, 1, { .bool_p = &settings.framing }, "framing", NULL}, 121}; 122 123static int line_h; 124static int piece_w, tick_w; 125static int true_guess_w, true_score_w, guess_w, score_w; 126 127/* Guesses and solution */ 128struct mm_line solution, hidden; 129struct mm_line guesses[MAX_GUESSES_COUNT]; 130 131/* Alias for pluginlib_getaction */ 132static inline int get_button(void) { 133 return pluginlib_getaction(TIMEOUT_BLOCK, plugin_contexts, 134 ARRAYLEN(plugin_contexts)); 135} 136 137/* Computes the margin to center an element */ 138static inline int get_margin(int width, int full_w) { 139 return ((full_w - width) / 2); 140} 141 142static inline bool stop_game(void) { 143 return (quit || leave || found); 144} 145 146static void fill_color_rect(int x, int y, int w, int h, int color) { 147 rb->lcd_set_foreground(color); 148 rb->lcd_fillrect(x, y, w, h); 149 rb->lcd_set_foreground(LCD_WHITE); 150} 151 152static void overfill_rect(int x, int y, int w, int h) { 153 rb->lcd_fillrect(x - 2, y - 2, w + 4, h + 4); 154} 155 156static void draw_piece(int x, int y, int w, int h, int color_id, bool emph) { 157 int color = LCD_BLACK; 158 159 if (color_id >= 0) 160 color = colors[color_id]; 161 else if (color_id == -2) /* Hidden piece */ 162 color = ALUMINIUM; 163 164 if (emph) 165 overfill_rect(x, y, w, h); 166 167 if (color_id == -1) /* Uninitialised color */ 168 rb->lcd_drawrect(x, y, w, h); 169 else 170 fill_color_rect(x, y, w, h, color); 171 172 if (!emph && settings.framing) 173 rb->lcd_drawrect(x, y, w, h); 174 175 if (settings.labeling && color_id >= 0) { 176 char text[2]; 177 rb->snprintf(text, sizeof(text), "%d", color_id); 178 179 int fw, fh; rb->font_getstringsize(text, &fw, &fh, FONT_SYSFIXED); 180 rb->lcd_putsxy(x + get_margin(fw, w), y + get_margin(fh, h), text); 181 } 182} 183 184/* Compute the score for a given guess (expressed in ticks) */ 185static void validate_guess(struct mm_line* guess) { 186 bool solution_match[MAX_PIECES_COUNT]; 187 bool guess_match[MAX_PIECES_COUNT]; 188 189 guess->score.misplaced = 0; 190 guess->score.correct = 0; 191 192 int guess_pos; 193 194 /* Initialisation with 0s */ 195 for (guess_pos = 0; guess_pos < pieces_count; guess_pos++) 196 solution_match[guess_pos] = guess_match[guess_pos] = false; 197 198 /* 1st step : detect correctly positioned pieces */ 199 for (guess_pos = 0; guess_pos < pieces_count; guess_pos++) { 200 if (solution.pieces[guess_pos] == guess->pieces[guess_pos]) { 201 guess->score.correct += 1; 202 203 guess_match[guess_pos] = solution_match[guess_pos] 204 = true; 205 } 206 } 207 208 /* Second step : detect mispositioned pieces */ 209 for (guess_pos = 0; guess_pos < pieces_count; guess_pos++) { 210 if (guess_match[guess_pos]) continue; 211 212 int sol_pos; 213 for (sol_pos = 0; sol_pos < pieces_count; sol_pos++) { 214 if (guess_match[guess_pos]) break; 215 if (solution_match[sol_pos]) continue; 216 217 if (guess->pieces[guess_pos] == solution.pieces[sol_pos]) { 218 guess->score.misplaced += 1; 219 220 solution_match[sol_pos] = true; 221 break; 222 } 223 } 224 } 225} 226 227static void draw_guess(int line, struct mm_line* guess, int cur_guess, 228 int cur_piece, bool show_score) { 229 int cur_y = (Y_MARGIN + MARGIN) + 2 * line_h * line; 230 int l_margin = X_MARGIN + (show_score ? 0 : get_margin(guess_w, LINE_W)); 231 232 int piece; 233 for (piece = 0; piece < pieces_count; piece++) { 234 int cur_x = l_margin + 2 * piece_w * piece; 235 draw_piece(cur_x, cur_y, piece_w, line_h, guess->pieces[piece], 236 line == cur_guess && piece == cur_piece); 237 } 238} 239 240static void draw_score(int line, struct mm_line* guess) { 241 int cur_y = (Y_MARGIN + MARGIN) + 2 * line_h * line; 242 int l_margin = X_MARGIN + true_guess_w + MARGIN; 243 244 int tick = 0; 245 for (; tick < guess->score.correct; tick++) { 246 int cur_x = l_margin + 2 * tick_w * tick; 247 248 fill_color_rect(cur_x, cur_y, tick_w, line_h, LCD_RGBPACK(239, 41, 41)); 249 } 250 251 for (; tick < guess->score.correct + guess->score.misplaced; tick++) { 252 int cur_x = l_margin + 2 * tick_w * tick; 253 254 fill_color_rect(cur_x, cur_y, tick_w, line_h, LCD_RGBPACK(211, 215, 207)); 255 } 256} 257 258static void draw_board(int cur_guess, int cur_piece) { 259 rb->lcd_clear_display(); 260 261 int line = 0; 262 for (; line < guesses_count; line++) { 263 draw_guess(line, &guesses[line], cur_guess, cur_piece, true); 264 if (line < cur_guess) draw_score(line, &guesses[line]); 265 } 266 267 int color; 268 int colors_margin = 2; 269 int cur_y = (Y_MARGIN + MARGIN) + 2 * line_h * line; 270 int color_w = (LINE_W - colors_margin * (colors_count - 1)) / colors_count; 271 272 for (color = 0; color < colors_count; color++) { 273 int cur_x = X_MARGIN + color * (color_w + colors_margin); 274 draw_piece(cur_x, cur_y, color_w, line_h, color, 275 color == guesses[cur_guess].pieces[cur_piece]); 276 } 277 278 line++; 279 280 if(game_ended) 281 draw_guess(line, &solution, cur_guess, cur_piece, false); 282 else 283 draw_guess(line, &hidden, cur_guess, cur_piece, false); 284 285 rb->lcd_update(); 286} 287 288static void init_vars(void) { 289 quit = leave = usb = found = game_ended = false; 290 291 int guess, piece; 292 for (guess = 0; guess < guesses_count; guess++) { 293 for (piece = 0; piece < pieces_count; piece++) 294 guesses[guess].pieces[piece] = -1; 295 } 296 for (piece = 0; piece < pieces_count; piece++) { 297 guesses[0].pieces[piece] = 0; 298 hidden.pieces[piece] = -2; 299 } 300} 301 302static void init_board(void) { 303 304 pieces_count = settings.pieces; 305 colors_count = settings.colors; 306 guesses_count = settings.guesses; 307 308 line_h = GAME_H / (2 * (guesses_count + 2) - 1); 309 310 true_score_w = LINE_W * 0.25; 311 true_guess_w = LINE_W - (true_score_w + MARGIN); 312 313 tick_w = true_score_w / (2 * pieces_count - 1); 314 piece_w = true_guess_w / (2 * pieces_count - 1); 315 316 /* Readjust (due to integer divisions) */ 317 score_w = tick_w * (2 * pieces_count - 1); 318 guess_w = piece_w * (2 * pieces_count - 1); 319} 320 321static void randomize_solution(void) { 322 int piece_id; 323 for (piece_id = 0; piece_id < pieces_count; piece_id++) 324 solution.pieces[piece_id] = rb->rand() % colors_count; 325} 326 327static void settings_menu(void) { 328 MENUITEM_STRINGLIST(settings_menu, "Settings", NULL, 329 "Number of colours", "Number of pegs", 330 "Number of guesses", 331 "Display labels", "Display frames"); 332 int cur_item = 0; 333 bool menu_quit = false; 334 335 while(!menu_quit) { 336 337 switch(rb->do_menu(&settings_menu, &cur_item, NULL, false)) { 338 case 0: 339 rb->set_int("Number of colours", "", UNIT_INT, &settings.colors, 340 NULL, -1, MAX_COLORS_COUNT, 1, NULL); 341 break; 342 case 1: 343 rb->set_int("Number of pegs", "", UNIT_INT, &settings.pieces, 344 NULL, -1, MAX_PIECES_COUNT, 1, NULL); 345 break; 346 case 2: 347 rb->set_int("Number of guesses", "", UNIT_INT, &settings.guesses, 348 NULL, -1, MAX_GUESSES_COUNT, 1, NULL); 349 break; 350 case 3: 351 rb->set_bool("Display labels", &settings.labeling); 352 break; 353 case 4: 354 rb->set_bool("Display frames", &settings.framing); 355 break; 356 case GO_TO_PREVIOUS: 357 menu_quit = true; 358 break; 359 default: 360 break; 361 } 362 } 363} 364 365static bool resume; 366static int menu_cb(int action, 367 const struct menu_item_ex *this_item, 368 struct gui_synclist *this_list) 369{ 370 (void)this_list; 371 int i = ((intptr_t)this_item); 372 if ((action == ACTION_REQUEST_MENUITEM) && (!resume && (i==0))) 373 return ACTION_EXIT_MENUITEM; 374 return action; 375} 376 377static void main_menu(void) { 378 MENUITEM_STRINGLIST(main_menu, "Codebuster Menu", menu_cb, 379 "Resume Game", "Start New Game", "Settings", 380 "Playback Control", "Quit"); 381 int cur_item = 0; 382 bool menu_quit = false; 383 384 while(!menu_quit) { 385 386 switch(rb->do_menu(&main_menu, &cur_item, NULL, false)) { 387 case 0: 388 resume = true; 389 menu_quit = true; 390 break; 391 case 1: 392 leave = true; 393 menu_quit = true; 394 break; 395 case 2: 396 settings_menu(); 397 break; 398 case 3: 399 playback_control(NULL); 400 break; 401 case 4: 402 quit = menu_quit = true; 403 break; 404 case MENU_ATTACHED_USB: 405 usb = menu_quit = true; 406 break; 407 default: 408 break; 409 } 410 } 411} 412 413enum plugin_status plugin_start(const void* parameter) { 414 (void)parameter; 415 416 rb->srand(*rb->current_tick); 417 rb->lcd_setfont(FONT_SYSFIXED); 418 rb->lcd_set_backdrop(NULL); 419 rb->lcd_set_foreground(LCD_WHITE); 420 rb->lcd_set_background(LCD_BLACK); 421 422 configfile_load(CONFIG_FILE_NAME, config, ARRAYLEN(config), 0); 423 rb->memcpy(&old_settings, &settings, sizeof(settings)); 424 425 main_menu(); 426 while (!quit) { 427 init_board(); 428 randomize_solution(); 429 init_vars(); 430 431 draw_board(0, 0); 432 int button = 0, guess = 0, piece = 0; 433 for (guess = 0; guess < guesses_count && !stop_game(); guess++) { 434 while(!stop_game()) { 435 draw_board(guess, piece); 436 437 button = get_button(); 438 if (button == PLA_SELECT) 439 break; 440 441#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ 442 || (CONFIG_KEYPAD == IPOD_3G_PAD) \ 443 || (CONFIG_KEYPAD == IPOD_4G_PAD) 444 if (button == PLA_UP) /* Menu button */ 445 button = PLA_CANCEL; 446#endif 447 448 switch (button) { 449 450 /* Exit */ 451 case PLA_EXIT: 452 case PLA_CANCEL: 453 resume = true; 454 main_menu(); 455 break; 456 457 /* Next piece */ 458 case PLA_RIGHT: 459 case PLA_RIGHT_REPEAT: 460 piece = (piece + 1) % pieces_count; 461 break; 462 463 /* Previous piece */ 464 case PLA_LEFT: 465 case PLA_LEFT_REPEAT: 466 piece = (piece + pieces_count - 1) % pieces_count; 467 break; 468 469 /* Next color */ 470#ifdef HAVE_SCROLLWHEEL 471 case PLA_SCROLL_FWD: 472 case PLA_SCROLL_FWD_REPEAT: 473#endif 474 case PLA_DOWN: 475 case PLA_DOWN_REPEAT: 476 guesses[guess].pieces[piece] = 477 (guesses[guess].pieces[piece] + 1) 478 % colors_count; 479 break; 480 481 /* Previous color */ 482#ifdef HAVE_SCROLLWHEEL 483 case PLA_SCROLL_BACK: 484 case PLA_SCROLL_BACK_REPEAT: 485#endif 486 case PLA_UP: 487 case PLA_UP_REPEAT: 488 guesses[guess].pieces[piece] = 489 (guesses[guess].pieces[piece] + colors_count - 1) 490 % colors_count; 491 break; 492 493 default: 494 if (rb->default_event_handler(button) == SYS_USB_CONNECTED) 495 quit = usb = true; 496 } 497 498 if (guesses[guess].pieces[piece] == -1) 499 guesses[guess].pieces[piece] = 0; 500 } 501 502 if (!quit) { 503 validate_guess(&guesses[guess]); 504 505 if (guesses[guess].score.correct == pieces_count) 506 found = true; 507 508 if (guess + 1 < guesses_count && !found) 509 guesses[guess + 1] = guesses[guess]; 510 } 511 } 512 513 game_ended = true; 514 resume = false; 515 if (!quit && !leave) { 516 draw_board(guess, piece); 517 518 if (found) 519 rb->splash(HZ, "Well done :)"); 520 else 521 rb->splash(HZ, "Wooops :("); 522 do { 523 button = rb->button_get(true); 524 if (rb->default_event_handler(button) == SYS_USB_CONNECTED) { 525 quit = usb = true; 526 } 527 } while( ( button == BUTTON_NONE ) 528 || ( button & (BUTTON_REL|BUTTON_REPEAT) ) ); 529 main_menu(); 530 } 531 } 532 if (rb->memcmp(&old_settings, &settings, sizeof(settings))) 533 configfile_save(CONFIG_FILE_NAME, config, ARRAYLEN(config), 0); 534 535 rb->lcd_setfont(FONT_UI); 536 return (usb) ? PLUGIN_USB_CONNECTED : PLUGIN_OK; 537}