A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
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}