forked from len0rd/rockbox
		
	git-svn-id: svn://svn.rockbox.org/rockbox/trunk@24082 a1c6a512-1295-4272-9138-f99709370657
		
			
				
	
	
		
			514 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			514 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /***************************************************************************
 | |
| *             __________               __   ___.
 | |
| *   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"
 | |
| 
 | |
| PLUGIN_HEADER
 | |
| 
 | |
| /* Limits */
 | |
| #define MAX_PIECES_COUNT 5
 | |
| #define MAX_COLORS_COUNT 8
 | |
| #define MAX_GUESSES_COUNT 10
 | |
| 
 | |
| const struct button_mapping *plugin_contexts[] =
 | |
|             {generic_directions, generic_actions};
 | |
| 
 | |
| /*
 | |
|  * 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, 2);
 | |
| }
 | |
| 
 | |
| /* 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, 2, "%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_FIRE || button == PLA_START)
 | |
|                     break;
 | |
| 
 | |
|                 switch (button) {
 | |
| 
 | |
|                     /* Exit */
 | |
|                     case PLA_QUIT:
 | |
|                     case PLA_MENU:
 | |
|                         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 */
 | |
|                     case PLA_DOWN:
 | |
|                     case PLA_DOWN_REPEAT:
 | |
|                          guesses[guess].pieces[piece] =
 | |
|                             (guesses[guess].pieces[piece] + 1)
 | |
|                             % colors_count;
 | |
|                         break;
 | |
| 
 | |
|                     /* Previous color */
 | |
|                     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;
 | |
| }
 |