new skin token: %cx - 24 hour time format enabled in the setting.. e.g %?cx<24 hour...
[kugel-rb.git] / apps / plugins / codebuster.c
blob2de1ce3b4d30be1e8f705bd78c6f70a3a16b6703
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"
27 PLUGIN_HEADER
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 {generic_directions, generic_actions};
37 /* Mapping */
38 #define EXIT PLA_QUIT
39 #define VALIDATE PLA_FIRE
40 #define PREV_PIECE PLA_LEFT
41 #define PREV_PIECE_REPEAT PLA_LEFT_REPEAT
42 #define NEXT_PIECE PLA_RIGHT
43 #define NEXT_PIECE_REPEAT PLA_RIGHT_REPEAT
44 #define PREV_COLOR PLA_UP
45 #define PREV_COLOR_REPEAT PLA_UP_REPEAT
46 #define NEXT_COLOR PLA_DOWN
47 #define NEXT_COLOR_REPEAT PLA_DOWN_REPEAT
50 * Screen structure:
51 * * (guesses_count) lines of guesses,
52 * * 1 center line of solution (hidden),
53 * * 1 line showing available colors.
55 * Status vars:
56 * * quit: exit the plugin
57 * * leave: restart the plugin (leave the current game)
58 * * game_ended: the game has ended
59 * * found: the combination has been found
61 * Colors used are taken from the Tango project.
63 * Due to integer truncations, 2 vars are used for some objects' dimensions
64 * (eg. true_guess_w, true_score_w). The actual dimension of these objects is
65 * stored in the corresponding var. without the "true" prefix.
68 struct mm_score {
69 int correct;
70 int misplaced;
73 struct mm_line {
74 struct mm_score score;
75 int pieces[MAX_PIECES_COUNT];
78 const int colors[MAX_COLORS_COUNT] = {
79 LCD_RGBPACK(252, 233, 79),
80 LCD_RGBPACK(206, 92, 0),
81 LCD_RGBPACK(143, 89, 2),
82 LCD_RGBPACK( 78, 154, 6),
83 /* LCD_RGBPACK( 32, 74, 135), */
84 LCD_RGBPACK( 52, 101, 164),
85 /* LCD_RGBPACK(114, 159, 207), */
86 LCD_RGBPACK(117, 80, 123),
87 /* LCD_RGBPACK(173, 127, 168), */
88 LCD_RGBPACK(164, 0, 0),
89 LCD_RGBPACK(238, 238, 236),
92 /* Flags */
93 static bool quit, leave, usb;
94 static bool found, game_ended;
96 /* Settings */
97 static int pieces_count;
98 static int colors_count;
99 static int guesses_count;
100 static int pieces_tmp = 5;
101 static int colors_tmp = 7;
102 static int guesses_tmp = 10;
103 static bool labeling = false, framing = false;
105 /* Display */
106 #define ALUMINIUM LCD_RGBPACK(136, 138, 133)
108 #define MARGIN 5
109 #define X_MARGIN (LCD_WIDTH / 20)
110 #define Y_MARGIN (LCD_HEIGHT / 20)
111 #define GAME_H (LCD_HEIGHT - (2 * Y_MARGIN))
112 #define LINE_W (LCD_WIDTH - (2 * X_MARGIN))
114 #define CONFIG_FILE_NAME "codebuster.cfg"
116 static struct configdata config[] = {
117 {TYPE_INT, 0, MAX_PIECES_COUNT, { .int_p = &pieces_tmp }, "pieces", NULL},
118 {TYPE_INT, 0, MAX_COLORS_COUNT, { .int_p = &colors_tmp }, "colors", NULL},
119 {TYPE_INT, 0, MAX_GUESSES_COUNT, { .int_p = &guesses_tmp }, "guesses", NULL},
120 {TYPE_BOOL, 0, 1, { .bool_p = &labeling }, "labeling", NULL},
121 {TYPE_BOOL, 0, 1, { .bool_p = &framing }, "framing", NULL},
123 static bool settings_changed = false;
125 static int line_h;
126 static int piece_w, tick_w;
127 static int true_guess_w, true_score_w, guess_w, score_w;
129 /* Guesses and solution */
130 struct mm_line solution, hidden;
131 struct mm_line guesses[MAX_GUESSES_COUNT];
133 /* Alias for pluginlib_getaction */
134 static inline int get_button(void) {
135 return pluginlib_getaction(TIMEOUT_BLOCK, plugin_contexts, 2);
138 /* Computes the margin to center an element */
139 static inline int get_margin(int width, int full_w) {
140 return ((full_w - width) / 2);
143 static inline bool stop_game(void) {
144 return (quit || leave || found);
147 static void fill_color_rect(int x, int y, int w, int h, int color) {
148 rb->lcd_set_foreground(color);
149 rb->lcd_fillrect(x, y, w, h);
150 rb->lcd_set_foreground(LCD_WHITE);
153 static void overfill_rect(int x, int y, int w, int h) {
154 rb->lcd_fillrect(x - 2, y - 2, w + 4, h + 4);
157 static void draw_piece(int x, int y, int w, int h, int color_id, bool emph) {
158 int color = LCD_BLACK;
160 if (color_id >= 0)
161 color = colors[color_id];
162 else if (color_id == -2) /* Hidden piece */
163 color = ALUMINIUM;
165 if (emph)
166 overfill_rect(x, y, w, h);
168 if (color_id == -1) /* Uninitialised color */
169 rb->lcd_drawrect(x, y, w, h);
170 else
171 fill_color_rect(x, y, w, h, color);
173 if (!emph && framing)
174 rb->lcd_drawrect(x, y, w, h);
176 if (labeling && color_id >= 0) {
177 char text[2];
178 rb->snprintf(text, 2, "%d", color_id);
180 int fw, fh; rb->font_getstringsize(text, &fw, &fh, FONT_SYSFIXED);
181 rb->lcd_putsxy(x + get_margin(fw, w), y + get_margin(fh, h), text);
185 /* Compute the score for a given guess (expressed in ticks) */
186 static void validate_guess(struct mm_line* guess) {
187 bool solution_match[pieces_count];
188 bool guess_match[pieces_count];
190 guess->score.misplaced = 0;
191 guess->score.correct = 0;
193 int guess_pos;
195 /* Initialisation with 0s */
196 for (guess_pos = 0; guess_pos < pieces_count; guess_pos++)
197 solution_match[guess_pos] = guess_match[guess_pos] = false;
199 /* 1st step : detect correctly positioned pieces */
200 for (guess_pos = 0; guess_pos < pieces_count; guess_pos++) {
201 if (solution.pieces[guess_pos] == guess->pieces[guess_pos]) {
202 guess->score.correct += 1;
204 guess_match[guess_pos] = solution_match[guess_pos]
205 = true;
209 /* Second step : detect mispositioned pieces */
210 for (guess_pos = 0; guess_pos < pieces_count; guess_pos++) {
211 if (guess_match[guess_pos]) continue;
213 int sol_pos;
214 for (sol_pos = 0; sol_pos < pieces_count; sol_pos++) {
215 if (guess_match[guess_pos]) break;
216 if (solution_match[sol_pos]) continue;
218 if (guess->pieces[guess_pos] == solution.pieces[sol_pos]) {
219 guess->score.misplaced += 1;
221 solution_match[sol_pos] = true;
222 break;
228 static void draw_guess(int line, struct mm_line* guess, int cur_guess,
229 int cur_piece, bool show_score) {
230 int cur_y = (Y_MARGIN + MARGIN) + 2 * line_h * line;
231 int l_margin = X_MARGIN + (show_score ? 0 : get_margin(guess_w, LINE_W));
233 int piece;
234 for (piece = 0; piece < pieces_count; piece++) {
235 int cur_x = l_margin + 2 * piece_w * piece;
236 draw_piece(cur_x, cur_y, piece_w, line_h, guess->pieces[piece],
237 line == cur_guess && piece == cur_piece);
241 static void draw_score(int line, struct mm_line* guess) {
242 int cur_y = (Y_MARGIN + MARGIN) + 2 * line_h * line;
243 int l_margin = X_MARGIN + true_guess_w + MARGIN;
245 int tick = 0;
246 for (; tick < guess->score.correct; tick++) {
247 int cur_x = l_margin + 2 * tick_w * tick;
249 fill_color_rect(cur_x, cur_y, tick_w, line_h, LCD_RGBPACK(239, 41, 41));
252 for (; tick < guess->score.correct + guess->score.misplaced; tick++) {
253 int cur_x = l_margin + 2 * tick_w * tick;
255 fill_color_rect(cur_x, cur_y, tick_w, line_h, LCD_RGBPACK(211, 215, 207));
259 static void draw_board(int cur_guess, int cur_piece) {
260 rb->lcd_clear_display();
262 int line = 0;
263 for (; line < guesses_count; line++) {
264 draw_guess(line, &guesses[line], cur_guess, cur_piece, true);
265 if (line < cur_guess) draw_score(line, &guesses[line]);
268 int color;
269 int colors_margin = 2;
270 int cur_y = (Y_MARGIN + MARGIN) + 2 * line_h * line;
271 int color_w = (LINE_W - colors_margin * (colors_count - 1)) / colors_count;
273 for (color = 0; color < colors_count; color++) {
274 int cur_x = X_MARGIN + color * (color_w + colors_margin);
275 draw_piece(cur_x, cur_y, color_w, line_h, color,
276 color == guesses[cur_guess].pieces[cur_piece]);
279 line++;
281 if(game_ended)
282 draw_guess(line, &solution, cur_guess, cur_piece, false);
283 else
284 draw_guess(line, &hidden, cur_guess, cur_piece, false);
286 rb->lcd_update();
289 static void init_vars(void) {
290 quit = leave = usb = found = game_ended = false;
292 int guess, piece;
293 for (guess = 0; guess < guesses_count; guess++) {
294 for (piece = 0; piece < pieces_count; piece++)
295 guesses[guess].pieces[piece] = -1;
297 for (piece = 0; piece < pieces_count; piece++) {
298 guesses[0].pieces[piece] = 0;
299 hidden.pieces[piece] = -2;
303 static void init_board(void) {
305 pieces_count = pieces_tmp;
306 colors_count = colors_tmp;
307 guesses_count = guesses_tmp;
309 line_h = GAME_H / (2 * (guesses_count + 2) - 1);
311 true_score_w = LINE_W * 0.25;
312 true_guess_w = LINE_W - (true_score_w + MARGIN);
314 tick_w = true_score_w / (2 * pieces_count - 1);
315 piece_w = true_guess_w / (2 * pieces_count - 1);
317 /* Readjust (due to integer divisions) */
318 score_w = tick_w * (2 * pieces_count - 1);
319 guess_w = piece_w * (2 * pieces_count - 1);
322 static void randomize_solution(void) {
323 int piece_id;
324 for (piece_id = 0; piece_id < pieces_count; piece_id++)
325 solution.pieces[piece_id] = rb->rand() % colors_count;
328 static void settings_menu(void) {
329 MENUITEM_STRINGLIST(settings_menu, "Settings", NULL,
330 "Number of colours", "Number of pegs",
331 "Number of guesses", "Labels", "Frames");
333 int cur_item =0;
335 bool menu_quit = false;
336 while(!menu_quit) {
338 switch(rb->do_menu(&settings_menu, &cur_item, NULL, false)) {
339 case 0:
340 rb->set_int("Number of colours", "", UNIT_INT, &colors_tmp,
341 NULL, -1, MAX_COLORS_COUNT, 1, NULL);
342 break;
343 case 1:
344 rb->set_int("Number of pegs", "", UNIT_INT, &pieces_tmp,
345 NULL, -1, MAX_PIECES_COUNT, 1, NULL);
346 break;
347 case 2:
348 rb->set_int("Number of guesses", "", UNIT_INT, &guesses_tmp,
349 NULL, -1, MAX_GUESSES_COUNT, 1, NULL);
350 break;
351 case 3:
352 rb->set_bool("Display labels", &labeling);
353 break;
354 case 4:
355 rb->set_bool("Display frames", &framing);
356 break;
357 case GO_TO_PREVIOUS:
358 menu_quit = true;
359 break;
360 default:
361 break;
366 static bool resume;
367 static int menu_cb(int action, const struct menu_item_ex *this_item)
369 int i = ((intptr_t)this_item);
370 if ((action == ACTION_REQUEST_MENUITEM) && (!resume && (i==0)))
371 return ACTION_EXIT_MENUITEM;
372 return action;
375 static void main_menu(void) {
376 MENUITEM_STRINGLIST(main_menu, "Codebuster Menu", menu_cb,
377 "Resume Game", "Start New Game", "Settings",
378 "Playback Control", "Quit");
380 int cur_item =0;
382 bool menu_quit = false;
383 while(!menu_quit) {
385 switch(rb->do_menu(&main_menu, &cur_item, NULL, false)) {
386 case 0:
387 resume = true;
388 menu_quit = true;
389 break;
390 case 1:
391 leave = true;
392 menu_quit = true;
393 break;
394 case 2:
395 settings_menu();
396 settings_changed = true;
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;
413 enum plugin_status plugin_start(const void* parameter) {
414 (void)parameter;
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);
422 configfile_load(CONFIG_FILE_NAME,config,5,0);
424 main_menu();
425 while (!quit) {
426 init_board();
427 randomize_solution();
428 init_vars();
430 draw_board(0, 0);
431 int button = 0, guess = 0, piece = 0;
432 for (guess = 0; guess < guesses_count && !stop_game(); guess++) {
433 while(!stop_game()) {
434 draw_board(guess, piece);
436 if ((button = get_button()) == VALIDATE) break;
438 switch (button) {
440 case EXIT:
441 resume = true;
442 main_menu();
443 break;
445 case NEXT_PIECE:
446 case NEXT_PIECE_REPEAT:
447 piece = (piece + 1) % pieces_count;
448 break;
450 case PREV_PIECE:
451 case PREV_PIECE_REPEAT:
452 piece = (piece + pieces_count - 1) % pieces_count;
453 break;
456 case NEXT_COLOR:
457 case NEXT_COLOR_REPEAT:
458 guesses[guess].pieces[piece] =
459 (guesses[guess].pieces[piece] + 1)
460 % colors_count;
461 break;
463 case PREV_COLOR:
464 case PREV_COLOR_REPEAT:
465 guesses[guess].pieces[piece] =
466 (guesses[guess].pieces[piece] + colors_count - 1)
467 % colors_count;
468 break;
470 default:
471 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
472 quit = usb = true;
475 if (guesses[guess].pieces[piece] == -1)
476 guesses[guess].pieces[piece] = 0;
479 if (!quit) {
480 validate_guess(&guesses[guess]);
482 if (guesses[guess].score.correct == pieces_count)
483 found = true;
485 if (guess + 1 < guesses_count && !found)
486 guesses[guess + 1] = guesses[guess];
490 game_ended = true;
491 resume = false;
492 if (!quit && !leave) {
493 draw_board(guess, piece);
495 if (found)
496 rb->splash(HZ, "Well done :)");
497 else
498 rb->splash(HZ, "Wooops :(");
499 do {
500 button = rb->button_get(true);
501 if (rb->default_event_handler(button) == SYS_USB_CONNECTED) {
502 quit = usb = true;
504 } while( ( button == BUTTON_NONE )
505 || ( button & (BUTTON_REL|BUTTON_REPEAT) ) );
506 main_menu();
509 if (settings_changed)
510 configfile_save(CONFIG_FILE_NAME,config,5,0);
512 rb->lcd_setfont(FONT_UI);
513 return (usb) ? PLUGIN_USB_CONNECTED : PLUGIN_OK;