rockbox/apps/plugins/codebuster.c
Thomas Martitz abdc5935be Introduce plugin_crt0.c that every plugin links.
It handles exit() properly, calling the handler also when the plugin returns
normally (also it makes exit() more standard compliant while at it).
It also holds PLUGIN_HEADER, so that it doesn't need to be in each plugin anymore.

To work better together with callbacks passed to rb->default_event_handler_ex introduce exit_on_usb() which will call the exit handler before showing the usb screen and exit() after it.
In most cases it was passed a callback which was manually called at all other return points. This can now be done via atexit().

In future plugin_crt0.c could also handle clearing bss, initializing iram and more.

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@27862 a1c6a512-1295-4272-9138-f99709370657
2010-08-23 16:56:49 +00:00

527 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"
/* 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, 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_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;
}