Gigabeat S: Do simple direct keypad scanning rather than triggering a separate scan...
[kugel-rb.git] / apps / plugins / codebuster.c
blobca36d5d01b6bc13985f0fcac49838dc3cfb217ca
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2009 Clément Pit--Claudel
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.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
22 #include "plugin.h"
23 #include "lib/configfile.h"
24 #include "lib/playback_control.h"
25 #include "lib/pluginlib_actions.h"
29 /* Limits */
30 #define MAX_PIECES_COUNT 5
31 #define MAX_COLORS_COUNT 8
32 #define MAX_GUESSES_COUNT 10
34 const struct button_mapping *plugin_contexts[] = {
35 pla_main_ctx,
36 #ifdef HAVE_REMOTE_LCD
37 pla_remote_ctx,
38 #endif
42 * Screen structure:
43 * * (guesses_count) lines of guesses,
44 * * 1 center line of solution (hidden),
45 * * 1 line showing available colors.
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
53 * Colors used are taken from the Tango project.
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.
60 struct mm_score {
61 int correct;
62 int misplaced;
65 struct mm_line {
66 struct mm_score score;
67 int pieces[MAX_PIECES_COUNT];
70 const 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),
84 /* Flags */
85 static bool quit, leave, usb;
86 static bool found, game_ended;
88 /* Settings */
89 struct settings {
90 int pieces;
91 int colors;
92 int guesses;
93 bool labeling;
94 bool framing;
96 static struct settings settings = {
97 5, 7, 10, false, false,
99 static struct settings old_settings;
100 static int pieces_count;
101 static int colors_count;
102 static int guesses_count;
104 /* Display */
105 #define ALUMINIUM LCD_RGBPACK(136, 138, 133)
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))
113 #define CONFIG_FILE_NAME "codebuster.cfg"
115 static 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},
123 static int line_h;
124 static int piece_w, tick_w;
125 static int true_guess_w, true_score_w, guess_w, score_w;
127 /* Guesses and solution */
128 struct mm_line solution, hidden;
129 struct mm_line guesses[MAX_GUESSES_COUNT];
131 /* Alias for pluginlib_getaction */
132 static inline int get_button(void) {
133 return pluginlib_getaction(TIMEOUT_BLOCK, plugin_contexts,
134 ARRAYLEN(plugin_contexts));
137 /* Computes the margin to center an element */
138 static inline int get_margin(int width, int full_w) {
139 return ((full_w - width) / 2);
142 static inline bool stop_game(void) {
143 return (quit || leave || found);
146 static 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);
152 static void overfill_rect(int x, int y, int w, int h) {
153 rb->lcd_fillrect(x - 2, y - 2, w + 4, h + 4);
156 static void draw_piece(int x, int y, int w, int h, int color_id, bool emph) {
157 int color = LCD_BLACK;
159 if (color_id >= 0)
160 color = colors[color_id];
161 else if (color_id == -2) /* Hidden piece */
162 color = ALUMINIUM;
164 if (emph)
165 overfill_rect(x, y, w, h);
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);
172 if (!emph && settings.framing)
173 rb->lcd_drawrect(x, y, w, h);
175 if (settings.labeling && color_id >= 0) {
176 char text[2];
177 rb->snprintf(text, sizeof(text), "%d", color_id);
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);
184 /* Compute the score for a given guess (expressed in ticks) */
185 static void validate_guess(struct mm_line* guess) {
186 bool solution_match[MAX_PIECES_COUNT];
187 bool guess_match[MAX_PIECES_COUNT];
189 guess->score.misplaced = 0;
190 guess->score.correct = 0;
192 int guess_pos;
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;
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;
203 guess_match[guess_pos] = solution_match[guess_pos]
204 = true;
208 /* Second step : detect mispositioned pieces */
209 for (guess_pos = 0; guess_pos < pieces_count; guess_pos++) {
210 if (guess_match[guess_pos]) continue;
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;
217 if (guess->pieces[guess_pos] == solution.pieces[sol_pos]) {
218 guess->score.misplaced += 1;
220 solution_match[sol_pos] = true;
221 break;
227 static 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));
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);
240 static 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;
244 int tick = 0;
245 for (; tick < guess->score.correct; tick++) {
246 int cur_x = l_margin + 2 * tick_w * tick;
248 fill_color_rect(cur_x, cur_y, tick_w, line_h, LCD_RGBPACK(239, 41, 41));
251 for (; tick < guess->score.correct + guess->score.misplaced; tick++) {
252 int cur_x = l_margin + 2 * tick_w * tick;
254 fill_color_rect(cur_x, cur_y, tick_w, line_h, LCD_RGBPACK(211, 215, 207));
258 static void draw_board(int cur_guess, int cur_piece) {
259 rb->lcd_clear_display();
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]);
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;
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]);
278 line++;
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);
285 rb->lcd_update();
288 static void init_vars(void) {
289 quit = leave = usb = found = game_ended = false;
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;
296 for (piece = 0; piece < pieces_count; piece++) {
297 guesses[0].pieces[piece] = 0;
298 hidden.pieces[piece] = -2;
302 static void init_board(void) {
304 pieces_count = settings.pieces;
305 colors_count = settings.colors;
306 guesses_count = settings.guesses;
308 line_h = GAME_H / (2 * (guesses_count + 2) - 1);
310 true_score_w = LINE_W * 0.25;
311 true_guess_w = LINE_W - (true_score_w + MARGIN);
313 tick_w = true_score_w / (2 * pieces_count - 1);
314 piece_w = true_guess_w / (2 * pieces_count - 1);
316 /* Readjust (due to integer divisions) */
317 score_w = tick_w * (2 * pieces_count - 1);
318 guess_w = piece_w * (2 * pieces_count - 1);
321 static 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;
327 static 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;
335 while(!menu_quit) {
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;
365 static bool resume;
366 static int menu_cb(int action, const struct menu_item_ex *this_item)
368 int i = ((intptr_t)this_item);
369 if ((action == ACTION_REQUEST_MENUITEM) && (!resume && (i==0)))
370 return ACTION_EXIT_MENUITEM;
371 return action;
374 static void main_menu(void) {
375 MENUITEM_STRINGLIST(main_menu, "Codebuster Menu", menu_cb,
376 "Resume Game", "Start New Game", "Settings",
377 "Playback Control", "Quit");
378 int cur_item = 0;
379 bool menu_quit = false;
381 while(!menu_quit) {
383 switch(rb->do_menu(&main_menu, &cur_item, NULL, false)) {
384 case 0:
385 resume = true;
386 menu_quit = true;
387 break;
388 case 1:
389 leave = true;
390 menu_quit = true;
391 break;
392 case 2:
393 settings_menu();
394 break;
395 case 3:
396 playback_control(NULL);
397 break;
398 case 4:
399 quit = menu_quit = true;
400 break;
401 case MENU_ATTACHED_USB:
402 usb = menu_quit = true;
403 break;
404 default:
405 break;
410 enum plugin_status plugin_start(const void* parameter) {
411 (void)parameter;
413 rb->srand(*rb->current_tick);
414 rb->lcd_setfont(FONT_SYSFIXED);
415 rb->lcd_set_backdrop(NULL);
416 rb->lcd_set_foreground(LCD_WHITE);
417 rb->lcd_set_background(LCD_BLACK);
419 configfile_load(CONFIG_FILE_NAME, config, ARRAYLEN(config), 0);
420 rb->memcpy(&old_settings, &settings, sizeof(settings));
422 main_menu();
423 while (!quit) {
424 init_board();
425 randomize_solution();
426 init_vars();
428 draw_board(0, 0);
429 int button = 0, guess = 0, piece = 0;
430 for (guess = 0; guess < guesses_count && !stop_game(); guess++) {
431 while(!stop_game()) {
432 draw_board(guess, piece);
434 button = get_button();
435 if (button == PLA_SELECT)
436 break;
438 switch (button) {
440 /* Exit */
441 case PLA_EXIT:
442 case PLA_CANCEL:
443 resume = true;
444 main_menu();
445 break;
447 /* Next piece */
448 case PLA_RIGHT:
449 case PLA_RIGHT_REPEAT:
450 piece = (piece + 1) % pieces_count;
451 break;
453 /* Previous piece */
454 case PLA_LEFT:
455 case PLA_LEFT_REPEAT:
456 piece = (piece + pieces_count - 1) % pieces_count;
457 break;
459 /* Next color */
460 #ifdef HAVE_SCROLLWHEEL
461 case PLA_SCROLL_FWD:
462 case PLA_SCROLL_FWD_REPEAT:
463 #endif
464 case PLA_DOWN:
465 case PLA_DOWN_REPEAT:
466 guesses[guess].pieces[piece] =
467 (guesses[guess].pieces[piece] + 1)
468 % colors_count;
469 break;
471 /* Previous color */
472 #ifdef HAVE_SCROLLWHEEL
473 case PLA_SCROLL_BACK:
474 case PLA_SCROLL_BACK_REPEAT:
475 #endif
476 case PLA_UP:
477 case PLA_UP_REPEAT:
478 guesses[guess].pieces[piece] =
479 (guesses[guess].pieces[piece] + colors_count - 1)
480 % colors_count;
481 break;
483 default:
484 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
485 quit = usb = true;
488 if (guesses[guess].pieces[piece] == -1)
489 guesses[guess].pieces[piece] = 0;
492 if (!quit) {
493 validate_guess(&guesses[guess]);
495 if (guesses[guess].score.correct == pieces_count)
496 found = true;
498 if (guess + 1 < guesses_count && !found)
499 guesses[guess + 1] = guesses[guess];
503 game_ended = true;
504 resume = false;
505 if (!quit && !leave) {
506 draw_board(guess, piece);
508 if (found)
509 rb->splash(HZ, "Well done :)");
510 else
511 rb->splash(HZ, "Wooops :(");
512 do {
513 button = rb->button_get(true);
514 if (rb->default_event_handler(button) == SYS_USB_CONNECTED) {
515 quit = usb = true;
517 } while( ( button == BUTTON_NONE )
518 || ( button & (BUTTON_REL|BUTTON_REPEAT) ) );
519 main_menu();
522 if (rb->memcmp(&old_settings, &settings, sizeof(settings)))
523 configfile_save(CONFIG_FILE_NAME, config, ARRAYLEN(config), 0);
525 rb->lcd_setfont(FONT_UI);
526 return (usb) ? PLUGIN_USB_CONNECTED : PLUGIN_OK;