Enable calendar plugin for non-rtc targets (FS#10786 by Teruaki Kawashima)
[kugel-rb.git] / apps / plugins / codebuster.c
blob2abf5ec81b4a1b8cb8e30f2398ec5ee230038b27
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};
38 * Screen structure:
39 * * (guesses_count) lines of guesses,
40 * * 1 center line of solution (hidden),
41 * * 1 line showing available colors.
43 * Status vars:
44 * * quit: exit the plugin
45 * * leave: restart the plugin (leave the current game)
46 * * game_ended: the game has ended
47 * * found: the combination has been found
49 * Colors used are taken from the Tango project.
51 * Due to integer truncations, 2 vars are used for some objects' dimensions
52 * (eg. true_guess_w, true_score_w). The actual dimension of these objects is
53 * stored in the corresponding var. without the "true" prefix.
56 struct mm_score {
57 int correct;
58 int misplaced;
61 struct mm_line {
62 struct mm_score score;
63 int pieces[MAX_PIECES_COUNT];
66 const int colors[MAX_COLORS_COUNT] = {
67 LCD_RGBPACK(252, 233, 79),
68 LCD_RGBPACK(206, 92, 0),
69 LCD_RGBPACK(143, 89, 2),
70 LCD_RGBPACK( 78, 154, 6),
71 /* LCD_RGBPACK( 32, 74, 135), */
72 LCD_RGBPACK( 52, 101, 164),
73 /* LCD_RGBPACK(114, 159, 207), */
74 LCD_RGBPACK(117, 80, 123),
75 /* LCD_RGBPACK(173, 127, 168), */
76 LCD_RGBPACK(164, 0, 0),
77 LCD_RGBPACK(238, 238, 236),
80 /* Flags */
81 static bool quit, leave, usb;
82 static bool found, game_ended;
84 /* Settings */
85 static int pieces_count;
86 static int colors_count;
87 static int guesses_count;
88 static int pieces_tmp = 5;
89 static int colors_tmp = 7;
90 static int guesses_tmp = 10;
91 static bool labeling = false, framing = false;
93 /* Display */
94 #define ALUMINIUM LCD_RGBPACK(136, 138, 133)
96 #define MARGIN 5
97 #define X_MARGIN (LCD_WIDTH / 20)
98 #define Y_MARGIN (LCD_HEIGHT / 20)
99 #define GAME_H (LCD_HEIGHT - (2 * Y_MARGIN))
100 #define LINE_W (LCD_WIDTH - (2 * X_MARGIN))
102 #define CONFIG_FILE_NAME "codebuster.cfg"
104 static struct configdata config[] = {
105 {TYPE_INT, 0, MAX_PIECES_COUNT, { .int_p = &pieces_tmp }, "pieces", NULL},
106 {TYPE_INT, 0, MAX_COLORS_COUNT, { .int_p = &colors_tmp }, "colors", NULL},
107 {TYPE_INT, 0, MAX_GUESSES_COUNT, { .int_p = &guesses_tmp }, "guesses", NULL},
108 {TYPE_BOOL, 0, 1, { .bool_p = &labeling }, "labeling", NULL},
109 {TYPE_BOOL, 0, 1, { .bool_p = &framing }, "framing", NULL},
111 static bool settings_changed = false;
113 static int line_h;
114 static int piece_w, tick_w;
115 static int true_guess_w, true_score_w, guess_w, score_w;
117 /* Guesses and solution */
118 struct mm_line solution, hidden;
119 struct mm_line guesses[MAX_GUESSES_COUNT];
121 /* Alias for pluginlib_getaction */
122 static inline int get_button(void) {
123 return pluginlib_getaction(TIMEOUT_BLOCK, plugin_contexts, 2);
126 /* Computes the margin to center an element */
127 static inline int get_margin(int width, int full_w) {
128 return ((full_w - width) / 2);
131 static inline bool stop_game(void) {
132 return (quit || leave || found);
135 static void fill_color_rect(int x, int y, int w, int h, int color) {
136 rb->lcd_set_foreground(color);
137 rb->lcd_fillrect(x, y, w, h);
138 rb->lcd_set_foreground(LCD_WHITE);
141 static void overfill_rect(int x, int y, int w, int h) {
142 rb->lcd_fillrect(x - 2, y - 2, w + 4, h + 4);
145 static void draw_piece(int x, int y, int w, int h, int color_id, bool emph) {
146 int color = LCD_BLACK;
148 if (color_id >= 0)
149 color = colors[color_id];
150 else if (color_id == -2) /* Hidden piece */
151 color = ALUMINIUM;
153 if (emph)
154 overfill_rect(x, y, w, h);
156 if (color_id == -1) /* Uninitialised color */
157 rb->lcd_drawrect(x, y, w, h);
158 else
159 fill_color_rect(x, y, w, h, color);
161 if (!emph && framing)
162 rb->lcd_drawrect(x, y, w, h);
164 if (labeling && color_id >= 0) {
165 char text[2];
166 rb->snprintf(text, 2, "%d", color_id);
168 int fw, fh; rb->font_getstringsize(text, &fw, &fh, FONT_SYSFIXED);
169 rb->lcd_putsxy(x + get_margin(fw, w), y + get_margin(fh, h), text);
173 /* Compute the score for a given guess (expressed in ticks) */
174 static void validate_guess(struct mm_line* guess) {
175 bool solution_match[pieces_count];
176 bool guess_match[pieces_count];
178 guess->score.misplaced = 0;
179 guess->score.correct = 0;
181 int guess_pos;
183 /* Initialisation with 0s */
184 for (guess_pos = 0; guess_pos < pieces_count; guess_pos++)
185 solution_match[guess_pos] = guess_match[guess_pos] = false;
187 /* 1st step : detect correctly positioned pieces */
188 for (guess_pos = 0; guess_pos < pieces_count; guess_pos++) {
189 if (solution.pieces[guess_pos] == guess->pieces[guess_pos]) {
190 guess->score.correct += 1;
192 guess_match[guess_pos] = solution_match[guess_pos]
193 = true;
197 /* Second step : detect mispositioned pieces */
198 for (guess_pos = 0; guess_pos < pieces_count; guess_pos++) {
199 if (guess_match[guess_pos]) continue;
201 int sol_pos;
202 for (sol_pos = 0; sol_pos < pieces_count; sol_pos++) {
203 if (guess_match[guess_pos]) break;
204 if (solution_match[sol_pos]) continue;
206 if (guess->pieces[guess_pos] == solution.pieces[sol_pos]) {
207 guess->score.misplaced += 1;
209 solution_match[sol_pos] = true;
210 break;
216 static void draw_guess(int line, struct mm_line* guess, int cur_guess,
217 int cur_piece, bool show_score) {
218 int cur_y = (Y_MARGIN + MARGIN) + 2 * line_h * line;
219 int l_margin = X_MARGIN + (show_score ? 0 : get_margin(guess_w, LINE_W));
221 int piece;
222 for (piece = 0; piece < pieces_count; piece++) {
223 int cur_x = l_margin + 2 * piece_w * piece;
224 draw_piece(cur_x, cur_y, piece_w, line_h, guess->pieces[piece],
225 line == cur_guess && piece == cur_piece);
229 static void draw_score(int line, struct mm_line* guess) {
230 int cur_y = (Y_MARGIN + MARGIN) + 2 * line_h * line;
231 int l_margin = X_MARGIN + true_guess_w + MARGIN;
233 int tick = 0;
234 for (; tick < guess->score.correct; tick++) {
235 int cur_x = l_margin + 2 * tick_w * tick;
237 fill_color_rect(cur_x, cur_y, tick_w, line_h, LCD_RGBPACK(239, 41, 41));
240 for (; tick < guess->score.correct + guess->score.misplaced; tick++) {
241 int cur_x = l_margin + 2 * tick_w * tick;
243 fill_color_rect(cur_x, cur_y, tick_w, line_h, LCD_RGBPACK(211, 215, 207));
247 static void draw_board(int cur_guess, int cur_piece) {
248 rb->lcd_clear_display();
250 int line = 0;
251 for (; line < guesses_count; line++) {
252 draw_guess(line, &guesses[line], cur_guess, cur_piece, true);
253 if (line < cur_guess) draw_score(line, &guesses[line]);
256 int color;
257 int colors_margin = 2;
258 int cur_y = (Y_MARGIN + MARGIN) + 2 * line_h * line;
259 int color_w = (LINE_W - colors_margin * (colors_count - 1)) / colors_count;
261 for (color = 0; color < colors_count; color++) {
262 int cur_x = X_MARGIN + color * (color_w + colors_margin);
263 draw_piece(cur_x, cur_y, color_w, line_h, color,
264 color == guesses[cur_guess].pieces[cur_piece]);
267 line++;
269 if(game_ended)
270 draw_guess(line, &solution, cur_guess, cur_piece, false);
271 else
272 draw_guess(line, &hidden, cur_guess, cur_piece, false);
274 rb->lcd_update();
277 static void init_vars(void) {
278 quit = leave = usb = found = game_ended = false;
280 int guess, piece;
281 for (guess = 0; guess < guesses_count; guess++) {
282 for (piece = 0; piece < pieces_count; piece++)
283 guesses[guess].pieces[piece] = -1;
285 for (piece = 0; piece < pieces_count; piece++) {
286 guesses[0].pieces[piece] = 0;
287 hidden.pieces[piece] = -2;
291 static void init_board(void) {
293 pieces_count = pieces_tmp;
294 colors_count = colors_tmp;
295 guesses_count = guesses_tmp;
297 line_h = GAME_H / (2 * (guesses_count + 2) - 1);
299 true_score_w = LINE_W * 0.25;
300 true_guess_w = LINE_W - (true_score_w + MARGIN);
302 tick_w = true_score_w / (2 * pieces_count - 1);
303 piece_w = true_guess_w / (2 * pieces_count - 1);
305 /* Readjust (due to integer divisions) */
306 score_w = tick_w * (2 * pieces_count - 1);
307 guess_w = piece_w * (2 * pieces_count - 1);
310 static void randomize_solution(void) {
311 int piece_id;
312 for (piece_id = 0; piece_id < pieces_count; piece_id++)
313 solution.pieces[piece_id] = rb->rand() % colors_count;
316 static void settings_menu(void) {
317 MENUITEM_STRINGLIST(settings_menu, "Settings", NULL,
318 "Number of colours", "Number of pegs",
319 "Number of guesses", "Labels", "Frames");
321 int cur_item =0;
323 bool menu_quit = false;
324 while(!menu_quit) {
326 switch(rb->do_menu(&settings_menu, &cur_item, NULL, false)) {
327 case 0:
328 rb->set_int("Number of colours", "", UNIT_INT, &colors_tmp,
329 NULL, -1, MAX_COLORS_COUNT, 1, NULL);
330 break;
331 case 1:
332 rb->set_int("Number of pegs", "", UNIT_INT, &pieces_tmp,
333 NULL, -1, MAX_PIECES_COUNT, 1, NULL);
334 break;
335 case 2:
336 rb->set_int("Number of guesses", "", UNIT_INT, &guesses_tmp,
337 NULL, -1, MAX_GUESSES_COUNT, 1, NULL);
338 break;
339 case 3:
340 rb->set_bool("Display labels", &labeling);
341 break;
342 case 4:
343 rb->set_bool("Display frames", &framing);
344 break;
345 case GO_TO_PREVIOUS:
346 menu_quit = true;
347 break;
348 default:
349 break;
354 static bool resume;
355 static int menu_cb(int action, const struct menu_item_ex *this_item)
357 int i = ((intptr_t)this_item);
358 if ((action == ACTION_REQUEST_MENUITEM) && (!resume && (i==0)))
359 return ACTION_EXIT_MENUITEM;
360 return action;
363 static void main_menu(void) {
364 MENUITEM_STRINGLIST(main_menu, "Codebuster Menu", menu_cb,
365 "Resume Game", "Start New Game", "Settings",
366 "Playback Control", "Quit");
368 int cur_item =0;
370 bool menu_quit = false;
371 while(!menu_quit) {
373 switch(rb->do_menu(&main_menu, &cur_item, NULL, false)) {
374 case 0:
375 resume = true;
376 menu_quit = true;
377 break;
378 case 1:
379 leave = true;
380 menu_quit = true;
381 break;
382 case 2:
383 settings_menu();
384 settings_changed = true;
385 break;
386 case 3:
387 playback_control(NULL);
388 break;
389 case 4:
390 quit = menu_quit = true;
391 break;
392 case MENU_ATTACHED_USB:
393 usb = menu_quit = true;
394 break;
395 default:
396 break;
401 enum plugin_status plugin_start(const void* parameter) {
402 (void)parameter;
404 rb->srand(*rb->current_tick);
405 rb->lcd_setfont(FONT_SYSFIXED);
406 rb->lcd_set_backdrop(NULL);
407 rb->lcd_set_foreground(LCD_WHITE);
408 rb->lcd_set_background(LCD_BLACK);
410 configfile_load(CONFIG_FILE_NAME,config,5,0);
412 main_menu();
413 while (!quit) {
414 init_board();
415 randomize_solution();
416 init_vars();
418 draw_board(0, 0);
419 int button = 0, guess = 0, piece = 0;
420 for (guess = 0; guess < guesses_count && !stop_game(); guess++) {
421 while(!stop_game()) {
422 draw_board(guess, piece);
424 button = get_button();
425 if (button == PLA_FIRE || button == PLA_START)
426 break;
428 switch (button) {
430 /* Exit */
431 case PLA_QUIT:
432 case PLA_MENU:
433 resume = true;
434 main_menu();
435 break;
437 /* Next piece */
438 case PLA_RIGHT:
439 case PLA_RIGHT_REPEAT:
440 piece = (piece + 1) % pieces_count;
441 break;
443 /* Previous piece */
444 case PLA_LEFT:
445 case PLA_LEFT_REPEAT:
446 piece = (piece + pieces_count - 1) % pieces_count;
447 break;
449 /* Next color */
450 case PLA_DOWN:
451 case PLA_DOWN_REPEAT:
452 guesses[guess].pieces[piece] =
453 (guesses[guess].pieces[piece] + 1)
454 % colors_count;
455 break;
457 /* Previous color */
458 case PLA_UP:
459 case PLA_UP_REPEAT:
460 guesses[guess].pieces[piece] =
461 (guesses[guess].pieces[piece] + colors_count - 1)
462 % colors_count;
463 break;
465 default:
466 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
467 quit = usb = true;
470 if (guesses[guess].pieces[piece] == -1)
471 guesses[guess].pieces[piece] = 0;
474 if (!quit) {
475 validate_guess(&guesses[guess]);
477 if (guesses[guess].score.correct == pieces_count)
478 found = true;
480 if (guess + 1 < guesses_count && !found)
481 guesses[guess + 1] = guesses[guess];
485 game_ended = true;
486 resume = false;
487 if (!quit && !leave) {
488 draw_board(guess, piece);
490 if (found)
491 rb->splash(HZ, "Well done :)");
492 else
493 rb->splash(HZ, "Wooops :(");
494 do {
495 button = rb->button_get(true);
496 if (rb->default_event_handler(button) == SYS_USB_CONNECTED) {
497 quit = usb = true;
499 } while( ( button == BUTTON_NONE )
500 || ( button & (BUTTON_REL|BUTTON_REPEAT) ) );
501 main_menu();
504 if (settings_changed)
505 configfile_save(CONFIG_FILE_NAME,config,5,0);
507 rb->lcd_setfont(FONT_UI);
508 return (usb) ? PLUGIN_USB_CONNECTED : PLUGIN_OK;