Index: apps/plugins/CATEGORIES =================================================================== --- apps/plugins/CATEGORIES (révision 22203) +++ apps/plugins/CATEGORIES (copie de travail) @@ -41,6 +41,7 @@ lua,viewers mandelbrot,demos matrix,demos +mastermind,games maze,games mazezam,games md5sum,apps Index: apps/plugins/SOURCES =================================================================== --- apps/plugins/SOURCES (révision 22203) +++ apps/plugins/SOURCES (copie de travail) @@ -58,6 +58,7 @@ #ifdef HAVE_LCD_COLOR clix.c ppmviewer.c +mastermind.c #endif /* Plugins needing the grayscale lib on low-depth LCDs */ Index: apps/plugins/mastermind.c =================================================================== --- apps/plugins/mastermind.c (révision 0) +++ apps/plugins/mastermind.c (révision 0) @@ -0,0 +1,466 @@ +#include "plugin.h" +#include "lib/playback_control.h" +#include "lib/pluginlib_actions.h" + +PLUGIN_HEADER + +/* Limits */ +#define MAX_PIECES_COUNT 5 +#define MAX_COLORS_COUNT 7 +#define MAX_GUESSES_COUNT 10 + +const struct button_mapping *plugin_contexts[] = + {generic_directions, generic_actions}; + +/* Mapping */ +#define EXIT PLA_QUIT +#define MENU PLA_MENU +#define VALIDATE PLA_FIRE +#define PREV_PIECE PLA_LEFT +#define PREV_PIECE_REPEAT PLA_LEFT_REPEAT +#define NEXT_PIECE PLA_RIGHT +#define NEXT_PIECE_REPEAT PLA_RIGHT_REPEAT +#define PREV_COLOR PLA_UP +#define PREV_COLOR_REPEAT PLA_UP_REPEAT +#define NEXT_COLOR PLA_DOWN +#define NEXT_COLOR_REPEAT PLA_DOWN_REPEAT + +/* + * MasterMind implementation by Clément Pit--Claudel (CFP). + * + * 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 dimensions + * (cf. true_guess_w, true_score_w). The "non-true" equivalents are + * recalculated to exactly match the object's dimension. + * + */ + +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( 92, 53, 102), + LCD_RGBPACK(164, 0, 0), + }; + +/* Flags */ +static bool quit, leave, usb; +static bool found, game_ended; + +/* Settings */ +static int pieces_count = 5; +static int colors_count = 7; +static int guesses_count = 10; + +/* Display */ +static int fg_color; +static int aluminium = LCD_RGBPACK(238, 238, 236); + +static int game_h; +const int inner_margin = 5; +const int margin = LCD_WIDTH / 20; + +static int line_w, line_h; +static int piece_w, tick_w; +static int true_guess_w, true_score_w, guess_w, score_w; +const float score_guess_w_ratio = 0.25; + +/* Guesses and solution */ +struct mm_line solution, hidden; +struct mm_line guesses[MAX_GUESSES_COUNT]; + +/* Alias for pluginlib_getaction */ +inline int get_button(void) { + return pluginlib_getaction(TIMEOUT_BLOCK, plugin_contexts, 2); +} + +/* Computes the margin to center an element */ +inline int get_margin(int width) { + return ((line_w - width) / 2); +} + +inline bool stop_game() { + return (quit || leave || found); +} + +void draw_color_rect(int x, int y, int w, int h, int color) { + int fg = rb->lcd_get_foreground(); + rb->lcd_set_foreground(color); + rb->lcd_fillrect(x, y, w, h); + rb->lcd_set_foreground(fg); +} + +void draw_fatrec(int x, int y, int w, int h) { + rb->lcd_fillrect(x - 2, y - 2, w + 4, h + 4); +} + +/* Compute the score for a given guess (expressed in ticks) */ +void validate_guess(struct mm_line* guess) { + bool solution_match[pieces_count]; + bool guess_match[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; + } + } + } +} + +void draw_guess(int line, struct mm_line* guess, int cur_guess, int cur_piece, bool show_score) { + int cur_y = margin + 2 * line_h * line; + int l_margin = margin + (show_score ? 0 : get_margin(guess_w)); + + int piece; + for (piece = 0; piece < pieces_count; piece++) { + int cur_color = colors[guess->pieces[piece]]; + + if (guess->pieces[piece] == -2) /* Hidden piece */ + cur_color = aluminium; + + int cur_x = l_margin + 2 * piece_w * piece; + + if (line == cur_guess && piece == cur_piece) + draw_fatrec(cur_x, cur_y, piece_w, line_h); + + if (guess->pieces[piece] == -1) /* Uninitialised color */ + rb->lcd_drawrect(cur_x, cur_y, piece_w, line_h); + else + draw_color_rect(cur_x, cur_y, piece_w, line_h, cur_color); + } +} + +void draw_score(int line, struct mm_line* guess) { + int cur_y = margin + 2 * line_h * line; + int l_margin = margin + true_guess_w + inner_margin; + + int tick = 0; + for (; tick < guess->score.correct; tick++) { + int cur_x = l_margin + 2 * tick_w * tick; + + draw_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; + + draw_color_rect(cur_x, cur_y, tick_w, line_h, LCD_RGBPACK(211, 215, 207)); + } +} + +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 = 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 = margin + color * (color_w + colors_margin); + + if (color == guesses[cur_guess].pieces[cur_piece]) + draw_fatrec(cur_x, cur_y, color_w, line_h); + + draw_color_rect(cur_x, cur_y, color_w, line_h, colors[color]); + } + + 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(); +} + +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; + } +} + +void init_board(void) { + fg_color = rb->lcd_get_foreground(); rb->lcd_set_foreground(LCD_WHITE); + + line_w = LCD_WIDTH - (2 * margin); + game_h = LCD_HEIGHT - (2 * margin); + + line_h = game_h / (2 * (guesses_count + 2) - 1); + + true_score_w = line_w * score_guess_w_ratio; + true_guess_w = line_w - (true_score_w + inner_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); +} + +void randomize_solution() { + int piece_id; + for (piece_id = 0; piece_id < pieces_count; piece_id++) + solution.pieces[piece_id] = rb->rand() % colors_count; +} + +void mm_start_menu(void) { + MENUITEM_STRINGLIST(start_menu, "MasterMind menu", NULL, + "New game", "Number of colors", "Number of pieces", + "Number of guesses", "Playback control", "Quit"); + + int result, cur_item; + + rb->lcd_setfont(FONT_UI); + rb->lcd_clear_display(); rb->lcd_update(); + + bool menu_quit = false; + while(!menu_quit) { + result = rb->do_menu(&start_menu, &cur_item, NULL, false); + + switch(result) { + case 0: + menu_quit = true; + break; + + case 1: + rb->set_int("Number of colors", "", UNIT_INT, &colors_count, + NULL, -1, MAX_COLORS_COUNT, 1, NULL); + break; + + case 2: + rb->set_int("Number of pieces", "", UNIT_INT, &pieces_count, + NULL, -1, MAX_PIECES_COUNT, 1, NULL); + break; + + case 3: + rb->set_int("Number of guesses", "", UNIT_INT, &guesses_count, + NULL, -1, MAX_GUESSES_COUNT, 1, NULL); + break; + + case 4: + playback_control(NULL); + break; + + case 5: + quit = menu_quit = true; + break; + + case MENU_ATTACHED_USB: + usb = menu_quit = true; + break; + } + } +} + +void mm_ingame_menu() { + MENUITEM_STRINGLIST(ingame_menu, "In game menu", NULL, + "Back to game", "Back to main menu", + "Playback control", "Quit"); + + int result, cur_item; + + rb->lcd_setfont(FONT_UI); + rb->lcd_clear_display(); rb->lcd_update(); + + bool menu_quit = false; + while(!menu_quit) { + result = rb->do_menu(&ingame_menu, &cur_item, NULL, false); + + switch(result) { + case 0: + menu_quit = true; + break; + + case 1: + leave = menu_quit = true; + break; + + case 2: + playback_control(NULL); + break; + + case 3: + quit = menu_quit = true; + break; + + case MENU_ATTACHED_USB: + usb = menu_quit = true; + break; + } + } +} + +enum plugin_status plugin_start(const void* parameter) { + (void)parameter; + + struct tm* tm = rb->get_time(); + rb->srand( + tm->tm_mon * 30 * 24 * 60 * 60 + + tm->tm_hour * 24 * 60 * 60 + + tm->tm_min * 60 + + tm->tm_sec + ); + + while (!quit) { + init_vars(); + mm_start_menu(); + + init_board(); + randomize_solution(); + + 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); + + if ((button = get_button()) == VALIDATE) break; + + switch (button) { + case MENU: + mm_ingame_menu(); + if (!stop_game()) draw_board(guess, piece); + break; + + case EXIT: + quit = true; + break; + + case NEXT_PIECE: + case NEXT_PIECE_REPEAT: + piece = (piece + 1) % pieces_count; + break; + + case PREV_PIECE: + case PREV_PIECE_REPEAT: + piece = (piece + pieces_count - 1) % pieces_count; + break; + + + case NEXT_COLOR: + case NEXT_COLOR_REPEAT: + guesses[guess].pieces[piece] = + (guesses[guess].pieces[piece] + 1) + % colors_count; + break; + + case PREV_COLOR: + case PREV_COLOR_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; + if (!quit && !leave) { + draw_board(guess, piece); + + if (found) + rb->splash(HZ, "Well done :)"); + else + rb->splash(HZ, "Wooops :("); + + while (!quit) { + button = get_button(); + if (button == EXIT) + quit = true; + else if (button == MENU) + break; + } + } + } + + rb->lcd_set_foreground(fg_color); + return (usb) ? PLUGIN_USB_CONNECTED : PLUGIN_OK; +}