Update the discussion of themeing in the manual, and put a note in the wps tags appen...
[kugel-rb.git] / apps / plugins / codebuster.c
blobbfcc25f9b56493194de7c69f060ad06a75d0a53a
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 struct settings {
86 int pieces;
87 int colors;
88 int guesses;
89 bool labeling;
90 bool framing;
92 static struct settings settings = {
93 5, 7, 10, false, false,
95 static struct settings old_settings;
96 static int pieces_count;
97 static int colors_count;
98 static int guesses_count;
100 /* Display */
101 #define ALUMINIUM LCD_RGBPACK(136, 138, 133)
103 #define MARGIN 5
104 #define X_MARGIN (LCD_WIDTH / 20)
105 #define Y_MARGIN (LCD_HEIGHT / 20)
106 #define GAME_H (LCD_HEIGHT - (2 * Y_MARGIN))
107 #define LINE_W (LCD_WIDTH - (2 * X_MARGIN))
109 #define CONFIG_FILE_NAME "codebuster.cfg"
111 static struct configdata config[] = {
112 {TYPE_INT, 0, MAX_PIECES_COUNT, { .int_p = &settings.pieces }, "pieces", NULL},
113 {TYPE_INT, 0, MAX_COLORS_COUNT, { .int_p = &settings.colors }, "colors", NULL},
114 {TYPE_INT, 0, MAX_GUESSES_COUNT, { .int_p = &settings.guesses }, "guesses", NULL},
115 {TYPE_BOOL, 0, 1, { .bool_p = &settings.labeling }, "labeling", NULL},
116 {TYPE_BOOL, 0, 1, { .bool_p = &settings.framing }, "framing", NULL},
119 static int line_h;
120 static int piece_w, tick_w;
121 static int true_guess_w, true_score_w, guess_w, score_w;
123 /* Guesses and solution */
124 struct mm_line solution, hidden;
125 struct mm_line guesses[MAX_GUESSES_COUNT];
127 /* Alias for pluginlib_getaction */
128 static inline int get_button(void) {
129 return pluginlib_getaction(TIMEOUT_BLOCK, plugin_contexts, 2);
132 /* Computes the margin to center an element */
133 static inline int get_margin(int width, int full_w) {
134 return ((full_w - width) / 2);
137 static inline bool stop_game(void) {
138 return (quit || leave || found);
141 static void fill_color_rect(int x, int y, int w, int h, int color) {
142 rb->lcd_set_foreground(color);
143 rb->lcd_fillrect(x, y, w, h);
144 rb->lcd_set_foreground(LCD_WHITE);
147 static void overfill_rect(int x, int y, int w, int h) {
148 rb->lcd_fillrect(x - 2, y - 2, w + 4, h + 4);
151 static void draw_piece(int x, int y, int w, int h, int color_id, bool emph) {
152 int color = LCD_BLACK;
154 if (color_id >= 0)
155 color = colors[color_id];
156 else if (color_id == -2) /* Hidden piece */
157 color = ALUMINIUM;
159 if (emph)
160 overfill_rect(x, y, w, h);
162 if (color_id == -1) /* Uninitialised color */
163 rb->lcd_drawrect(x, y, w, h);
164 else
165 fill_color_rect(x, y, w, h, color);
167 if (!emph && settings.framing)
168 rb->lcd_drawrect(x, y, w, h);
170 if (settings.labeling && color_id >= 0) {
171 char text[2];
172 rb->snprintf(text, 2, "%d", color_id);
174 int fw, fh; rb->font_getstringsize(text, &fw, &fh, FONT_SYSFIXED);
175 rb->lcd_putsxy(x + get_margin(fw, w), y + get_margin(fh, h), text);
179 /* Compute the score for a given guess (expressed in ticks) */
180 static void validate_guess(struct mm_line* guess) {
181 bool solution_match[MAX_PIECES_COUNT];
182 bool guess_match[MAX_PIECES_COUNT];
184 guess->score.misplaced = 0;
185 guess->score.correct = 0;
187 int guess_pos;
189 /* Initialisation with 0s */
190 for (guess_pos = 0; guess_pos < pieces_count; guess_pos++)
191 solution_match[guess_pos] = guess_match[guess_pos] = false;
193 /* 1st step : detect correctly positioned pieces */
194 for (guess_pos = 0; guess_pos < pieces_count; guess_pos++) {
195 if (solution.pieces[guess_pos] == guess->pieces[guess_pos]) {
196 guess->score.correct += 1;
198 guess_match[guess_pos] = solution_match[guess_pos]
199 = true;
203 /* Second step : detect mispositioned pieces */
204 for (guess_pos = 0; guess_pos < pieces_count; guess_pos++) {
205 if (guess_match[guess_pos]) continue;
207 int sol_pos;
208 for (sol_pos = 0; sol_pos < pieces_count; sol_pos++) {
209 if (guess_match[guess_pos]) break;
210 if (solution_match[sol_pos]) continue;
212 if (guess->pieces[guess_pos] == solution.pieces[sol_pos]) {
213 guess->score.misplaced += 1;
215 solution_match[sol_pos] = true;
216 break;
222 static void draw_guess(int line, struct mm_line* guess, int cur_guess,
223 int cur_piece, bool show_score) {
224 int cur_y = (Y_MARGIN + MARGIN) + 2 * line_h * line;
225 int l_margin = X_MARGIN + (show_score ? 0 : get_margin(guess_w, LINE_W));
227 int piece;
228 for (piece = 0; piece < pieces_count; piece++) {
229 int cur_x = l_margin + 2 * piece_w * piece;
230 draw_piece(cur_x, cur_y, piece_w, line_h, guess->pieces[piece],
231 line == cur_guess && piece == cur_piece);
235 static void draw_score(int line, struct mm_line* guess) {
236 int cur_y = (Y_MARGIN + MARGIN) + 2 * line_h * line;
237 int l_margin = X_MARGIN + true_guess_w + MARGIN;
239 int tick = 0;
240 for (; tick < guess->score.correct; 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(239, 41, 41));
246 for (; tick < guess->score.correct + guess->score.misplaced; 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(211, 215, 207));
253 static void draw_board(int cur_guess, int cur_piece) {
254 rb->lcd_clear_display();
256 int line = 0;
257 for (; line < guesses_count; line++) {
258 draw_guess(line, &guesses[line], cur_guess, cur_piece, true);
259 if (line < cur_guess) draw_score(line, &guesses[line]);
262 int color;
263 int colors_margin = 2;
264 int cur_y = (Y_MARGIN + MARGIN) + 2 * line_h * line;
265 int color_w = (LINE_W - colors_margin * (colors_count - 1)) / colors_count;
267 for (color = 0; color < colors_count; color++) {
268 int cur_x = X_MARGIN + color * (color_w + colors_margin);
269 draw_piece(cur_x, cur_y, color_w, line_h, color,
270 color == guesses[cur_guess].pieces[cur_piece]);
273 line++;
275 if(game_ended)
276 draw_guess(line, &solution, cur_guess, cur_piece, false);
277 else
278 draw_guess(line, &hidden, cur_guess, cur_piece, false);
280 rb->lcd_update();
283 static void init_vars(void) {
284 quit = leave = usb = found = game_ended = false;
286 int guess, piece;
287 for (guess = 0; guess < guesses_count; guess++) {
288 for (piece = 0; piece < pieces_count; piece++)
289 guesses[guess].pieces[piece] = -1;
291 for (piece = 0; piece < pieces_count; piece++) {
292 guesses[0].pieces[piece] = 0;
293 hidden.pieces[piece] = -2;
297 static void init_board(void) {
299 pieces_count = settings.pieces;
300 colors_count = settings.colors;
301 guesses_count = settings.guesses;
303 line_h = GAME_H / (2 * (guesses_count + 2) - 1);
305 true_score_w = LINE_W * 0.25;
306 true_guess_w = LINE_W - (true_score_w + MARGIN);
308 tick_w = true_score_w / (2 * pieces_count - 1);
309 piece_w = true_guess_w / (2 * pieces_count - 1);
311 /* Readjust (due to integer divisions) */
312 score_w = tick_w * (2 * pieces_count - 1);
313 guess_w = piece_w * (2 * pieces_count - 1);
316 static void randomize_solution(void) {
317 int piece_id;
318 for (piece_id = 0; piece_id < pieces_count; piece_id++)
319 solution.pieces[piece_id] = rb->rand() % colors_count;
322 static void settings_menu(void) {
323 MENUITEM_STRINGLIST(settings_menu, "Settings", NULL,
324 "Number of colours", "Number of pegs",
325 "Number of guesses",
326 "Display labels", "Display frames");
327 int cur_item = 0;
328 bool menu_quit = false;
330 while(!menu_quit) {
332 switch(rb->do_menu(&settings_menu, &cur_item, NULL, false)) {
333 case 0:
334 rb->set_int("Number of colours", "", UNIT_INT, &settings.colors,
335 NULL, -1, MAX_COLORS_COUNT, 1, NULL);
336 break;
337 case 1:
338 rb->set_int("Number of pegs", "", UNIT_INT, &settings.pieces,
339 NULL, -1, MAX_PIECES_COUNT, 1, NULL);
340 break;
341 case 2:
342 rb->set_int("Number of guesses", "", UNIT_INT, &settings.guesses,
343 NULL, -1, MAX_GUESSES_COUNT, 1, NULL);
344 break;
345 case 3:
346 rb->set_bool("Display labels", &settings.labeling);
347 break;
348 case 4:
349 rb->set_bool("Display frames", &settings.framing);
350 break;
351 case GO_TO_PREVIOUS:
352 menu_quit = true;
353 break;
354 default:
355 break;
360 static bool resume;
361 static int menu_cb(int action, const struct menu_item_ex *this_item)
363 int i = ((intptr_t)this_item);
364 if ((action == ACTION_REQUEST_MENUITEM) && (!resume && (i==0)))
365 return ACTION_EXIT_MENUITEM;
366 return action;
369 static void main_menu(void) {
370 MENUITEM_STRINGLIST(main_menu, "Codebuster Menu", menu_cb,
371 "Resume Game", "Start New Game", "Settings",
372 "Playback Control", "Quit");
373 int cur_item = 0;
374 bool menu_quit = false;
376 while(!menu_quit) {
378 switch(rb->do_menu(&main_menu, &cur_item, NULL, false)) {
379 case 0:
380 resume = true;
381 menu_quit = true;
382 break;
383 case 1:
384 leave = true;
385 menu_quit = true;
386 break;
387 case 2:
388 settings_menu();
389 break;
390 case 3:
391 playback_control(NULL);
392 break;
393 case 4:
394 quit = menu_quit = true;
395 break;
396 case MENU_ATTACHED_USB:
397 usb = menu_quit = true;
398 break;
399 default:
400 break;
405 enum plugin_status plugin_start(const void* parameter) {
406 (void)parameter;
408 rb->srand(*rb->current_tick);
409 rb->lcd_setfont(FONT_SYSFIXED);
410 rb->lcd_set_backdrop(NULL);
411 rb->lcd_set_foreground(LCD_WHITE);
412 rb->lcd_set_background(LCD_BLACK);
414 configfile_load(CONFIG_FILE_NAME, config, ARRAYLEN(config), 0);
415 rb->memcpy(&old_settings, &settings, sizeof(settings));
417 main_menu();
418 while (!quit) {
419 init_board();
420 randomize_solution();
421 init_vars();
423 draw_board(0, 0);
424 int button = 0, guess = 0, piece = 0;
425 for (guess = 0; guess < guesses_count && !stop_game(); guess++) {
426 while(!stop_game()) {
427 draw_board(guess, piece);
429 button = get_button();
430 if (button == PLA_FIRE || button == PLA_START)
431 break;
433 switch (button) {
435 /* Exit */
436 case PLA_QUIT:
437 case PLA_MENU:
438 resume = true;
439 main_menu();
440 break;
442 /* Next piece */
443 case PLA_RIGHT:
444 case PLA_RIGHT_REPEAT:
445 piece = (piece + 1) % pieces_count;
446 break;
448 /* Previous piece */
449 case PLA_LEFT:
450 case PLA_LEFT_REPEAT:
451 piece = (piece + pieces_count - 1) % pieces_count;
452 break;
454 /* Next color */
455 case PLA_DOWN:
456 case PLA_DOWN_REPEAT:
457 guesses[guess].pieces[piece] =
458 (guesses[guess].pieces[piece] + 1)
459 % colors_count;
460 break;
462 /* Previous color */
463 case PLA_UP:
464 case PLA_UP_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 (rb->memcmp(&old_settings, &settings, sizeof(settings)))
510 configfile_save(CONFIG_FILE_NAME, config, ARRAYLEN(config), 0);
512 rb->lcd_setfont(FONT_UI);
513 return (usb) ? PLUGIN_USB_CONNECTED : PLUGIN_OK;