/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2009 Clément Pit--Claudel * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "plugin.h" #include "lib/configfile.h" #include "lib/playback_control.h" #include "lib/pluginlib_actions.h" /* Limits */ #define MAX_PIECES_COUNT 5 #define MAX_COLORS_COUNT 8 #define MAX_GUESSES_COUNT 10 const struct button_mapping *plugin_contexts[] = { pla_main_ctx, #ifdef HAVE_REMOTE_LCD pla_remote_ctx, #endif }; /* * Screen structure: * * (guesses_count) lines of guesses, * * 1 center line of solution (hidden), * * 1 line showing available colors. * * Status vars: * * quit: exit the plugin * * leave: restart the plugin (leave the current game) * * game_ended: the game has ended * * found: the combination has been found * * Colors used are taken from the Tango project. * * Due to integer truncations, 2 vars are used for some objects' dimensions * (eg. true_guess_w, true_score_w). The actual dimension of these objects is * stored in the corresponding var. without the "true" prefix. */ struct mm_score { int correct; int misplaced; }; struct mm_line { struct mm_score score; int pieces[MAX_PIECES_COUNT]; }; const int colors[MAX_COLORS_COUNT] = { LCD_RGBPACK(252, 233, 79), LCD_RGBPACK(206, 92, 0), LCD_RGBPACK(143, 89, 2), LCD_RGBPACK( 78, 154, 6), /* LCD_RGBPACK( 32, 74, 135), */ LCD_RGBPACK( 52, 101, 164), /* LCD_RGBPACK(114, 159, 207), */ LCD_RGBPACK(117, 80, 123), /* LCD_RGBPACK(173, 127, 168), */ LCD_RGBPACK(164, 0, 0), LCD_RGBPACK(238, 238, 236), }; /* Flags */ static bool quit, leave, usb; static bool found, game_ended; /* Settings */ struct settings { int pieces; int colors; int guesses; bool labeling; bool framing; }; static struct settings settings = { 5, 7, 10, false, false, }; static struct settings old_settings; static int pieces_count; static int colors_count; static int guesses_count; /* Display */ #define ALUMINIUM LCD_RGBPACK(136, 138, 133) #define MARGIN 5 #define X_MARGIN (LCD_WIDTH / 20) #define Y_MARGIN (LCD_HEIGHT / 20) #define GAME_H (LCD_HEIGHT - (2 * Y_MARGIN)) #define LINE_W (LCD_WIDTH - (2 * X_MARGIN)) #define CONFIG_FILE_NAME "codebuster.cfg" static struct configdata config[] = { {TYPE_INT, 0, MAX_PIECES_COUNT, { .int_p = &settings.pieces }, "pieces", NULL}, {TYPE_INT, 0, MAX_COLORS_COUNT, { .int_p = &settings.colors }, "colors", NULL}, {TYPE_INT, 0, MAX_GUESSES_COUNT, { .int_p = &settings.guesses }, "guesses", NULL}, {TYPE_BOOL, 0, 1, { .bool_p = &settings.labeling }, "labeling", NULL}, {TYPE_BOOL, 0, 1, { .bool_p = &settings.framing }, "framing", NULL}, }; static int line_h; static int piece_w, tick_w; static int true_guess_w, true_score_w, guess_w, score_w; /* Guesses and solution */ struct mm_line solution, hidden; struct mm_line guesses[MAX_GUESSES_COUNT]; /* Alias for pluginlib_getaction */ static inline int get_button(void) { return pluginlib_getaction(TIMEOUT_BLOCK, plugin_contexts, ARRAYLEN(plugin_contexts)); } /* Computes the margin to center an element */ static inline int get_margin(int width, int full_w) { return ((full_w - width) / 2); } static inline bool stop_game(void) { return (quit || leave || found); } static void fill_color_rect(int x, int y, int w, int h, int color) { rb->lcd_set_foreground(color); rb->lcd_fillrect(x, y, w, h); rb->lcd_set_foreground(LCD_WHITE); } static void overfill_rect(int x, int y, int w, int h) { rb->lcd_fillrect(x - 2, y - 2, w + 4, h + 4); } static void draw_piece(int x, int y, int w, int h, int color_id, bool emph) { int color = LCD_BLACK; if (color_id >= 0) color = colors[color_id]; else if (color_id == -2) /* Hidden piece */ color = ALUMINIUM; if (emph) overfill_rect(x, y, w, h); if (color_id == -1) /* Uninitialised color */ rb->lcd_drawrect(x, y, w, h); else fill_color_rect(x, y, w, h, color); if (!emph && settings.framing) rb->lcd_drawrect(x, y, w, h); if (settings.labeling && color_id >= 0) { char text[2]; rb->snprintf(text, sizeof(text), "%d", color_id); int fw, fh; rb->font_getstringsize(text, &fw, &fh, FONT_SYSFIXED); rb->lcd_putsxy(x + get_margin(fw, w), y + get_margin(fh, h), text); } } /* Compute the score for a given guess (expressed in ticks) */ static void validate_guess(struct mm_line* guess) { bool solution_match[MAX_PIECES_COUNT]; bool guess_match[MAX_PIECES_COUNT]; guess->score.misplaced = 0; guess->score.correct = 0; int guess_pos; /* Initialisation with 0s */ for (guess_pos = 0; guess_pos < pieces_count; guess_pos++) solution_match[guess_pos] = guess_match[guess_pos] = false; /* 1st step : detect correctly positioned pieces */ for (guess_pos = 0; guess_pos < pieces_count; guess_pos++) { if (solution.pieces[guess_pos] == guess->pieces[guess_pos]) { guess->score.correct += 1; guess_match[guess_pos] = solution_match[guess_pos] = true; } } /* Second step : detect mispositioned pieces */ for (guess_pos = 0; guess_pos < pieces_count; guess_pos++) { if (guess_match[guess_pos]) continue; int sol_pos; for (sol_pos = 0; sol_pos < pieces_count; sol_pos++) { if (guess_match[guess_pos]) break; if (solution_match[sol_pos]) continue; if (guess->pieces[guess_pos] == solution.pieces[sol_pos]) { guess->score.misplaced += 1; solution_match[sol_pos] = true; break; } } } } static void draw_guess(int line, struct mm_line* guess, int cur_guess, int cur_piece, bool show_score) { int cur_y = (Y_MARGIN + MARGIN) + 2 * line_h * line; int l_margin = X_MARGIN + (show_score ? 0 : get_margin(guess_w, LINE_W)); int piece; for (piece = 0; piece < pieces_count; piece++) { int cur_x = l_margin + 2 * piece_w * piece; draw_piece(cur_x, cur_y, piece_w, line_h, guess->pieces[piece], line == cur_guess && piece == cur_piece); } } static void draw_score(int line, struct mm_line* guess) { int cur_y = (Y_MARGIN + MARGIN) + 2 * line_h * line; int l_margin = X_MARGIN + true_guess_w + MARGIN; int tick = 0; for (; tick < guess->score.correct; tick++) { int cur_x = l_margin + 2 * tick_w * tick; fill_color_rect(cur_x, cur_y, tick_w, line_h, LCD_RGBPACK(239, 41, 41)); } for (; tick < guess->score.correct + guess->score.misplaced; tick++) { int cur_x = l_margin + 2 * tick_w * tick; fill_color_rect(cur_x, cur_y, tick_w, line_h, LCD_RGBPACK(211, 215, 207)); } } static void draw_board(int cur_guess, int cur_piece) { rb->lcd_clear_display(); int line = 0; for (; line < guesses_count; line++) { draw_guess(line, &guesses[line], cur_guess, cur_piece, true); if (line < cur_guess) draw_score(line, &guesses[line]); } int color; int colors_margin = 2; int cur_y = (Y_MARGIN + MARGIN) + 2 * line_h * line; int color_w = (LINE_W - colors_margin * (colors_count - 1)) / colors_count; for (color = 0; color < colors_count; color++) { int cur_x = X_MARGIN + color * (color_w + colors_margin); draw_piece(cur_x, cur_y, color_w, line_h, color, color == guesses[cur_guess].pieces[cur_piece]); } line++; if(game_ended) draw_guess(line, &solution, cur_guess, cur_piece, false); else draw_guess(line, &hidden, cur_guess, cur_piece, false); rb->lcd_update(); } static void init_vars(void) { quit = leave = usb = found = game_ended = false; int guess, piece; for (guess = 0; guess < guesses_count; guess++) { for (piece = 0; piece < pieces_count; piece++) guesses[guess].pieces[piece] = -1; } for (piece = 0; piece < pieces_count; piece++) { guesses[0].pieces[piece] = 0; hidden.pieces[piece] = -2; } } static void init_board(void) { pieces_count = settings.pieces; colors_count = settings.colors; guesses_count = settings.guesses; line_h = GAME_H / (2 * (guesses_count + 2) - 1); true_score_w = LINE_W * 0.25; true_guess_w = LINE_W - (true_score_w + MARGIN); tick_w = true_score_w / (2 * pieces_count - 1); piece_w = true_guess_w / (2 * pieces_count - 1); /* Readjust (due to integer divisions) */ score_w = tick_w * (2 * pieces_count - 1); guess_w = piece_w * (2 * pieces_count - 1); } static void randomize_solution(void) { int piece_id; for (piece_id = 0; piece_id < pieces_count; piece_id++) solution.pieces[piece_id] = rb->rand() % colors_count; } static void settings_menu(void) { MENUITEM_STRINGLIST(settings_menu, "Settings", NULL, "Number of colours", "Number of pegs", "Number of guesses", "Display labels", "Display frames"); int cur_item = 0; bool menu_quit = false; while(!menu_quit) { switch(rb->do_menu(&settings_menu, &cur_item, NULL, false)) { case 0: rb->set_int("Number of colours", "", UNIT_INT, &settings.colors, NULL, -1, MAX_COLORS_COUNT, 1, NULL); break; case 1: rb->set_int("Number of pegs", "", UNIT_INT, &settings.pieces, NULL, -1, MAX_PIECES_COUNT, 1, NULL); break; case 2: rb->set_int("Number of guesses", "", UNIT_INT, &settings.guesses, NULL, -1, MAX_GUESSES_COUNT, 1, NULL); break; case 3: rb->set_bool("Display labels", &settings.labeling); break; case 4: rb->set_bool("Display frames", &settings.framing); break; case GO_TO_PREVIOUS: menu_quit = true; break; default: break; } } } static bool resume; static int menu_cb(int action, const struct menu_item_ex *this_item) { int i = ((intptr_t)this_item); if ((action == ACTION_REQUEST_MENUITEM) && (!resume && (i==0))) return ACTION_EXIT_MENUITEM; return action; } static void main_menu(void) { MENUITEM_STRINGLIST(main_menu, "Codebuster Menu", menu_cb, "Resume Game", "Start New Game", "Settings", "Playback Control", "Quit"); int cur_item = 0; bool menu_quit = false; while(!menu_quit) { switch(rb->do_menu(&main_menu, &cur_item, NULL, false)) { case 0: resume = true; menu_quit = true; break; case 1: leave = true; menu_quit = true; break; case 2: settings_menu(); break; case 3: playback_control(NULL); break; case 4: quit = menu_quit = true; break; case MENU_ATTACHED_USB: usb = menu_quit = true; break; default: break; } } } enum plugin_status plugin_start(const void* parameter) { (void)parameter; rb->srand(*rb->current_tick); rb->lcd_setfont(FONT_SYSFIXED); rb->lcd_set_backdrop(NULL); rb->lcd_set_foreground(LCD_WHITE); rb->lcd_set_background(LCD_BLACK); configfile_load(CONFIG_FILE_NAME, config, ARRAYLEN(config), 0); rb->memcpy(&old_settings, &settings, sizeof(settings)); main_menu(); while (!quit) { init_board(); randomize_solution(); init_vars(); draw_board(0, 0); int button = 0, guess = 0, piece = 0; for (guess = 0; guess < guesses_count && !stop_game(); guess++) { while(!stop_game()) { draw_board(guess, piece); button = get_button(); if (button == PLA_SELECT) break; switch (button) { /* Exit */ case PLA_EXIT: case PLA_CANCEL: resume = true; main_menu(); break; /* Next piece */ case PLA_RIGHT: case PLA_RIGHT_REPEAT: piece = (piece + 1) % pieces_count; break; /* Previous piece */ case PLA_LEFT: case PLA_LEFT_REPEAT: piece = (piece + pieces_count - 1) % pieces_count; break; /* Next color */ #ifdef HAVE_SCROLLWHEEL case PLA_SCROLL_FWD: case PLA_SCROLL_FWD_REPEAT: #endif case PLA_DOWN: case PLA_DOWN_REPEAT: guesses[guess].pieces[piece] = (guesses[guess].pieces[piece] + 1) % colors_count; break; /* Previous color */ #ifdef HAVE_SCROLLWHEEL case PLA_SCROLL_BACK: case PLA_SCROLL_BACK_REPEAT: #endif case PLA_UP: case PLA_UP_REPEAT: guesses[guess].pieces[piece] = (guesses[guess].pieces[piece] + colors_count - 1) % colors_count; break; default: if (rb->default_event_handler(button) == SYS_USB_CONNECTED) quit = usb = true; } if (guesses[guess].pieces[piece] == -1) guesses[guess].pieces[piece] = 0; } if (!quit) { validate_guess(&guesses[guess]); if (guesses[guess].score.correct == pieces_count) found = true; if (guess + 1 < guesses_count && !found) guesses[guess + 1] = guesses[guess]; } } game_ended = true; resume = false; if (!quit && !leave) { draw_board(guess, piece); if (found) rb->splash(HZ, "Well done :)"); else rb->splash(HZ, "Wooops :("); do { button = rb->button_get(true); if (rb->default_event_handler(button) == SYS_USB_CONNECTED) { quit = usb = true; } } while( ( button == BUTTON_NONE ) || ( button & (BUTTON_REL|BUTTON_REPEAT) ) ); main_menu(); } } if (rb->memcmp(&old_settings, &settings, sizeof(settings))) configfile_save(CONFIG_FILE_NAME, config, ARRAYLEN(config), 0); rb->lcd_setfont(FONT_UI); return (usb) ? PLUGIN_USB_CONNECTED : PLUGIN_OK; }