mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-01-22 01:30:35 -05:00
Purge "Menu" or "Rockbox" from plugin menu titles and match application names from manual. Exception: "Main Menu" is left unchanged as the title for the Main Menu Configuration plugin, since it appears in Rockbox's Settings menu and therefore should match the name of the setting. E.g.: "Rockbox Goban" => "Goban" "Image Viewer Menu" => "Image Viewer" "Viewer Menu" => "Text Viewer" "Menu" => "Chess Clock" "Do What?" => "Text Editor" "Mpegplayer Menu" => "MPEG Player" "Multiboot Settings" => "Multiboot" "Disktidy" => "Disk Tidy" ... Change-Id: Ie6d3be7557f31a36309489037ad8b2b27b06706e
601 lines
17 KiB
C
601 lines
17 KiB
C
#include "plugin.h"
|
|
#include "lib/display_text.h"
|
|
#include "lib/helper.h"
|
|
#include "lib/playback_control.h"
|
|
#include "lib/pluginlib_exit.h"
|
|
#include "lib/pluginlib_actions.h"
|
|
|
|
#define BUTTON_ROWS 6
|
|
#define BUTTON_COLS 5
|
|
|
|
#define REC_HEIGHT (int)(LCD_HEIGHT / (BUTTON_ROWS + 1))
|
|
#define REC_WIDTH (int)(LCD_WIDTH / BUTTON_COLS)
|
|
|
|
#define Y_7_POS (LCD_HEIGHT) /* Leave room for the border */
|
|
#define Y_6_POS (Y_7_POS - REC_HEIGHT) /* y6 = 63 */
|
|
#define Y_5_POS (Y_6_POS - REC_HEIGHT) /* y5 = 53 */
|
|
#define Y_4_POS (Y_5_POS - REC_HEIGHT) /* y4 = 43 */
|
|
#define Y_3_POS (Y_4_POS - REC_HEIGHT) /* y3 = 33 */
|
|
#define Y_2_POS (Y_3_POS - REC_HEIGHT) /* y2 = 23 */
|
|
#define Y_1_POS (Y_2_POS - REC_HEIGHT) /* y1 = 13 */
|
|
#define Y_0_POS 0 /* y0 = 0 */
|
|
|
|
#define X_0_POS 0 /* x0 = 0 */
|
|
#define X_1_POS (X_0_POS + REC_WIDTH) /* x1 = 22 */
|
|
#define X_2_POS (X_1_POS + REC_WIDTH) /* x2 = 44 */
|
|
#define X_3_POS (X_2_POS + REC_WIDTH) /* x3 = 66 */
|
|
#define X_4_POS (X_3_POS + REC_WIDTH) /* x4 = 88 */
|
|
#define X_5_POS (X_4_POS + REC_WIDTH) /* x5 = 110, column 111 left blank */
|
|
|
|
#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || (CONFIG_KEYPAD == IPOD_4G_PAD)
|
|
#define DARTS_QUIT PLA_SELECT_REPEAT
|
|
#else
|
|
#define DARTS_QUIT PLA_CANCEL
|
|
#endif
|
|
#define DARTS_SELECT PLA_SELECT
|
|
#define DARTS_RIGHT PLA_RIGHT
|
|
#define DARTS_LEFT PLA_LEFT
|
|
#define DARTS_UP PLA_UP
|
|
#define DARTS_DOWN PLA_DOWN
|
|
#define DARTS_RRIGHT PLA_RIGHT_REPEAT
|
|
#define DARTS_RLEFT PLA_LEFT_REPEAT
|
|
#define DARTS_RUP PLA_UP_REPEAT
|
|
#define DARTS_RDOWN PLA_DOWN_REPEAT
|
|
|
|
#define RESUME_FILE PLUGIN_GAMES_DATA_DIR "/dart_scorer.save"
|
|
/* leave first line blank on bitmap display, for pause icon */
|
|
#define FIRST_LINE 1
|
|
|
|
#define NUM_PLAYERS 2
|
|
#define MAX_UNDO 100
|
|
|
|
static const struct button_mapping *plugin_contexts[] = {pla_main_ctx};
|
|
|
|
/* game data structures */
|
|
enum game_mode
|
|
{
|
|
five,
|
|
three
|
|
};
|
|
static struct settings_struct
|
|
{
|
|
enum game_mode mode;
|
|
int scores[2];
|
|
bool turn;
|
|
int throws;
|
|
int history[MAX_UNDO];
|
|
int history_ptr;
|
|
} settings;
|
|
|
|
/* temporary data */
|
|
static bool loaded = false; /* has a save been loaded? */
|
|
int btn_row, btn_col; /* current position index for button */
|
|
int prev_btn_row, prev_btn_col; /* previous cursor position */
|
|
unsigned char *buttonChar[6][5] = {
|
|
{"", "Single", "Double", "Triple", ""},
|
|
{"1", "2", "3", "4", "5"},
|
|
{"6", "7", "8", "9", "10"},
|
|
{"11", "12", "13", "14", "15"},
|
|
{"16", "17", "18", "19", "20"},
|
|
{"", "Missed", "Bull", "Undo", ""}};
|
|
int modifier;
|
|
|
|
static int do_dart_scorer_pause_menu(void);
|
|
static void drawButtons(void);
|
|
|
|
/* First, increases *dimen1 by dimen1_delta modulo dimen1_modulo.
|
|
If dimen1 wraps, increases *dimen2 by dimen2_delta modulo dimen2_modulo.
|
|
*/
|
|
static void move_with_wrap_and_shift(
|
|
int *dimen1, int dimen1_delta, int dimen1_modulo,
|
|
int *dimen2, int dimen2_delta, int dimen2_modulo)
|
|
{
|
|
bool wrapped = false;
|
|
|
|
*dimen1 += dimen1_delta;
|
|
if (*dimen1 < 0)
|
|
{
|
|
*dimen1 = dimen1_modulo - 1;
|
|
wrapped = true;
|
|
}
|
|
else if (*dimen1 >= dimen1_modulo)
|
|
{
|
|
*dimen1 = 0;
|
|
wrapped = true;
|
|
}
|
|
|
|
if (wrapped)
|
|
{
|
|
/* Make the dividend always positive to be sure about the result.
|
|
Adding dimen2_modulo does not change it since we do it modulo. */
|
|
*dimen2 = (*dimen2 + dimen2_modulo + dimen2_delta) % dimen2_modulo;
|
|
}
|
|
}
|
|
|
|
static void drawButtons()
|
|
{
|
|
int i, j, w, h;
|
|
for (i = 0; i <= 5; i++)
|
|
{
|
|
for (j = 0; j <= 4; j++)
|
|
{
|
|
unsigned char button_text[16];
|
|
char *selected_prefix = (i == 0 && modifier > 0 && j == modifier) ? "*" : "";
|
|
rb->snprintf(button_text, sizeof(button_text), "%s%s", selected_prefix, buttonChar[i][j]);
|
|
rb->lcd_getstringsize(button_text, &w, &h);
|
|
if (i == btn_row && j == btn_col) /* selected item */
|
|
rb->lcd_set_drawmode(DRMODE_SOLID);
|
|
else
|
|
rb->lcd_set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID);
|
|
rb->lcd_fillrect(X_0_POS + j * REC_WIDTH,
|
|
Y_1_POS + i * REC_HEIGHT,
|
|
REC_WIDTH, REC_HEIGHT + 1);
|
|
if (i == btn_row && j == btn_col) /* selected item */
|
|
rb->lcd_set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID);
|
|
else
|
|
rb->lcd_set_drawmode(DRMODE_SOLID);
|
|
rb->lcd_putsxy(X_0_POS + j * REC_WIDTH + (REC_WIDTH - w) / 2,
|
|
Y_1_POS + i * REC_HEIGHT + (REC_HEIGHT - h) / 2 + 1,
|
|
button_text);
|
|
}
|
|
}
|
|
rb->lcd_set_drawmode(DRMODE_SOLID);
|
|
}
|
|
|
|
static void draw(void)
|
|
{
|
|
rb->lcd_clear_display();
|
|
|
|
char buf[32];
|
|
|
|
int x = 5;
|
|
int y = 10;
|
|
for (int i = 0; i < NUM_PLAYERS; ++i, x = x + 95)
|
|
{
|
|
char *turn_marker = (i == settings.turn) ? "*" : "";
|
|
rb->snprintf(buf, sizeof(buf), "%sPlayer %d: %d", turn_marker, i + 1, settings.scores[i]);
|
|
rb->lcd_putsxy(x, y, buf);
|
|
}
|
|
int throws_x = (LCD_WIDTH / 2) - 10;
|
|
char throws_buf[3];
|
|
for (int i = 0; i < settings.throws; ++i, throws_x += 5)
|
|
{
|
|
rb->strcat(throws_buf, "1");
|
|
rb->lcd_putsxy(throws_x, y, "|");
|
|
}
|
|
|
|
drawButtons();
|
|
|
|
rb->lcd_update();
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* save_game() saves the current game state.
|
|
******************************************************************************/
|
|
static void save_game(void)
|
|
{
|
|
int fd = rb->open(RESUME_FILE, O_WRONLY | O_CREAT, 0666);
|
|
if (fd < 0)
|
|
return;
|
|
|
|
rb->write(fd, &settings, sizeof(struct settings_struct));
|
|
|
|
rb->close(fd);
|
|
rb->lcd_update();
|
|
}
|
|
|
|
/* load_game() loads the saved game and returns load success.*/
|
|
static bool load_game(void)
|
|
{
|
|
signed int fd;
|
|
bool loaded = false;
|
|
|
|
/* open game file */
|
|
fd = rb->open(RESUME_FILE, O_RDONLY);
|
|
if (fd < 0)
|
|
return false;
|
|
|
|
/* read in saved game */
|
|
if (rb->read(fd, &settings, sizeof(struct settings_struct)) == (long)sizeof(struct settings_struct))
|
|
{
|
|
loaded = true;
|
|
}
|
|
|
|
rb->close(fd);
|
|
|
|
return loaded;
|
|
return false;
|
|
}
|
|
|
|
/* asks the user if they wish to quit */
|
|
static bool confirm_quit(void)
|
|
{
|
|
const struct text_message prompt = {(const char *[]){"Are you sure?", "This will clear your current game."}, 2};
|
|
enum yesno_res response = rb->gui_syncyesno_run(&prompt, NULL, NULL);
|
|
if (response == YESNO_NO)
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
/* displays the help text */
|
|
static bool do_help(void)
|
|
{
|
|
|
|
#ifdef HAVE_LCD_COLOR
|
|
rb->lcd_set_foreground(LCD_WHITE);
|
|
rb->lcd_set_background(LCD_BLACK);
|
|
#endif
|
|
|
|
rb->lcd_setfont(FONT_UI);
|
|
|
|
static char *help_text[] = {"Dart Scorer", "", "", "Keep score of your darts game."};
|
|
|
|
struct style_text style[] = {
|
|
{0, TEXT_CENTER | TEXT_UNDERLINE},
|
|
};
|
|
|
|
return display_text(ARRAYLEN(help_text), help_text, style, NULL, true);
|
|
}
|
|
|
|
static void undo(void)
|
|
{
|
|
if (!settings.history_ptr)
|
|
{
|
|
rb->splash(HZ * 2, "Out of undos!");
|
|
return;
|
|
}
|
|
|
|
/* jumping back to previous player? */
|
|
int turn = settings.throws == 3 ? !settings.turn : settings.turn;
|
|
if (turn != settings.turn)
|
|
{
|
|
settings.throws = 0;
|
|
settings.turn ^= true;
|
|
}
|
|
|
|
if (settings.history[settings.history_ptr - 1] >= 0)
|
|
{
|
|
settings.scores[turn] += settings.history[--settings.history_ptr];
|
|
++settings.throws;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
negative history means we bust. negative filled for all skipped throws
|
|
from being bust so consume back until no more
|
|
*/
|
|
for (; settings.throws < 3 && settings.history[settings.history_ptr - 1] < 0; --settings.history_ptr, ++settings.throws)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
static void init_game(bool newgame)
|
|
{
|
|
if (newgame)
|
|
{
|
|
/* initialize the game context */
|
|
modifier = 1;
|
|
btn_row = 1;
|
|
btn_col = 0;
|
|
|
|
int game_mode = -1;
|
|
MENUITEM_STRINGLIST(menu, "Game Mode", NULL, "501", "301");
|
|
while (game_mode < 0)
|
|
{
|
|
switch (rb->do_menu(&menu, &game_mode, NULL, false))
|
|
{
|
|
case 0:
|
|
{
|
|
settings.mode = five;
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
settings.mode = three;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < NUM_PLAYERS; ++i)
|
|
{
|
|
settings.scores[i] = (settings.mode == five) ? 501 : 301;
|
|
}
|
|
settings.turn = false;
|
|
settings.throws = 3;
|
|
settings.history_ptr = 0;
|
|
rb->lcd_clear_display();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* main game loop */
|
|
static enum plugin_status do_game(bool newgame)
|
|
{
|
|
init_game(newgame);
|
|
draw();
|
|
|
|
while (1)
|
|
{
|
|
/* wait for button press */
|
|
int button = pluginlib_getaction(-1, plugin_contexts, ARRAYLEN(plugin_contexts));
|
|
unsigned char *selected = buttonChar[btn_row][btn_col];
|
|
switch (button)
|
|
{
|
|
case DARTS_SELECT:
|
|
if ((!rb->strcmp(selected, "")) || (!rb->strcmp(selected, "Single")))
|
|
modifier = 1;
|
|
else if (!rb->strcmp(selected, "Double"))
|
|
modifier = 2;
|
|
else if (!rb->strcmp(selected, "Triple"))
|
|
modifier = 3;
|
|
else if (!rb->strcmp(selected, "Undo"))
|
|
{
|
|
undo();
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
/* main logic of score keeping */
|
|
if (modifier == 0)
|
|
modifier = 1;
|
|
int hit = (!rb->strcmp(selected, "Bull")) ? 25 : rb->atoi(selected);
|
|
if (hit == 25 && modifier == 3)
|
|
{
|
|
/* no triple bullseye! */
|
|
rb->splash(HZ * 2, "Triple Bull... Don't be silly!");
|
|
continue;
|
|
}
|
|
hit *= modifier;
|
|
if (hit > settings.scores[settings.turn])
|
|
{
|
|
rb->splash(HZ * 2, "Bust! End of turn.");
|
|
for (int i = 0; i < settings.throws; ++i)
|
|
settings.history[settings.history_ptr++] = -1;
|
|
settings.throws = 0;
|
|
}
|
|
else if (hit == settings.scores[settings.turn] - 1)
|
|
{
|
|
rb->splash(HZ * 2, "1 left! Must checkout with a double. End of turn.");
|
|
for (int i = 0; i < settings.throws; ++i)
|
|
settings.history[settings.history_ptr++] = -1;
|
|
settings.throws = 0;
|
|
}
|
|
else if (hit == settings.scores[settings.turn] && modifier != 2)
|
|
{
|
|
rb->splash(HZ * 2, "Must checkout with a double! End of turn.");
|
|
for (int i = 0; i < settings.throws; ++i)
|
|
settings.history[settings.history_ptr++] = -1;
|
|
settings.throws = 0;
|
|
}
|
|
else
|
|
{
|
|
settings.scores[settings.turn] -= hit;
|
|
--settings.throws;
|
|
settings.history[settings.history_ptr++] = hit;
|
|
modifier = 1;
|
|
if (!settings.scores[settings.turn])
|
|
goto GAMEOVER;
|
|
}
|
|
|
|
if (!settings.throws)
|
|
{
|
|
settings.throws = 3;
|
|
settings.turn ^= true;
|
|
}
|
|
}
|
|
break;
|
|
case DARTS_LEFT:
|
|
case DARTS_RLEFT:
|
|
move_with_wrap_and_shift(
|
|
&btn_col, -1, BUTTON_COLS,
|
|
&btn_row, 0, BUTTON_ROWS);
|
|
break;
|
|
case DARTS_RIGHT:
|
|
case DARTS_RRIGHT:
|
|
move_with_wrap_and_shift(
|
|
&btn_col, 1, BUTTON_COLS,
|
|
&btn_row, 0, BUTTON_ROWS);
|
|
break;
|
|
#ifdef DARTS_UP
|
|
case DARTS_UP:
|
|
case DARTS_RUP:
|
|
#ifdef HAVE_SCROLLWHEEL
|
|
case PLA_SCROLL_BACK:
|
|
case PLA_SCROLL_BACK_REPEAT:
|
|
#endif
|
|
move_with_wrap_and_shift(
|
|
&btn_row, -1, BUTTON_ROWS,
|
|
&btn_col, 0, BUTTON_COLS);
|
|
break;
|
|
#endif
|
|
#ifdef DARTS_DOWN
|
|
case DARTS_DOWN:
|
|
case DARTS_RDOWN:
|
|
#ifdef HAVE_SCROLLWHEEL
|
|
case PLA_SCROLL_FWD:
|
|
case PLA_SCROLL_FWD_REPEAT:
|
|
#endif
|
|
move_with_wrap_and_shift(
|
|
&btn_row, 1, BUTTON_ROWS,
|
|
&btn_col, 0, BUTTON_COLS);
|
|
break;
|
|
#endif
|
|
case DARTS_QUIT:
|
|
switch (do_dart_scorer_pause_menu())
|
|
{
|
|
case 0: /* resume */
|
|
break;
|
|
case 1:
|
|
init_game(true);
|
|
continue;
|
|
case 2: /* quit w/o saving */
|
|
rb->remove(RESUME_FILE);
|
|
return PLUGIN_ERROR;
|
|
case 3: /* save & quit */
|
|
save_game();
|
|
return PLUGIN_ERROR;
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
exit_on_usb(button); /* handle poweroff and USB */
|
|
break;
|
|
}
|
|
}
|
|
draw();
|
|
}
|
|
|
|
GAMEOVER:
|
|
rb->splashf(HZ * 3, "Gameover. Player %d wins!", settings.turn + 1);
|
|
|
|
return PLUGIN_OK;
|
|
}
|
|
|
|
/* decide if this_item should be shown in the main menu */
|
|
/* used to hide resume option when there is no save */
|
|
static int mainmenu_cb(int action,
|
|
const struct menu_item_ex *this_item,
|
|
struct gui_synclist *this_list)
|
|
{
|
|
(void)this_list;
|
|
int idx = ((intptr_t)this_item);
|
|
if (action == ACTION_REQUEST_MENUITEM && !loaded && (idx == 0 || idx == 5))
|
|
return ACTION_EXIT_MENUITEM;
|
|
return action;
|
|
}
|
|
|
|
/* show the pause menu */
|
|
static int do_dart_scorer_pause_menu(void)
|
|
{
|
|
int sel = 0;
|
|
MENUITEM_STRINGLIST(menu, "Dart Scorer", NULL,
|
|
"Resume Game",
|
|
"Start New Game",
|
|
"Playback Control",
|
|
"Help",
|
|
"Quit without Saving",
|
|
"Quit");
|
|
while (1)
|
|
{
|
|
switch (rb->do_menu(&menu, &sel, NULL, false))
|
|
{
|
|
case 0:
|
|
{
|
|
rb->splash(HZ * 2, "Resume");
|
|
return 0;
|
|
}
|
|
case 1:
|
|
{
|
|
if (!confirm_quit())
|
|
break;
|
|
else
|
|
{
|
|
rb->splash(HZ * 2, "New Game");
|
|
return 1;
|
|
}
|
|
}
|
|
case 2:
|
|
playback_control(NULL);
|
|
break;
|
|
case 3:
|
|
do_help();
|
|
break;
|
|
case 4: /* quit w/o saving */
|
|
{
|
|
if (!confirm_quit())
|
|
break;
|
|
else
|
|
{
|
|
return 2;
|
|
}
|
|
}
|
|
case 5:
|
|
return 3;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* show the main menu */
|
|
static enum plugin_status do_dart_scorer_menu(void)
|
|
{
|
|
int sel = 0;
|
|
loaded = load_game();
|
|
MENUITEM_STRINGLIST(menu,
|
|
"Dart Scorer",
|
|
mainmenu_cb,
|
|
"Resume Game",
|
|
"Start New Game",
|
|
"Playback Control",
|
|
"Help",
|
|
"Quit without Saving",
|
|
"Quit");
|
|
while (true)
|
|
{
|
|
switch (rb->do_menu(&menu, &sel, NULL, false))
|
|
{
|
|
case 0: /* Start new game or resume a game */
|
|
case 1:
|
|
{
|
|
if (sel == 1 && loaded)
|
|
{
|
|
if (!confirm_quit())
|
|
break;
|
|
}
|
|
enum plugin_status ret = do_game(sel == 1);
|
|
switch (ret)
|
|
{
|
|
case PLUGIN_OK:
|
|
{
|
|
loaded = false;
|
|
rb->remove(RESUME_FILE);
|
|
break;
|
|
}
|
|
case PLUGIN_USB_CONNECTED:
|
|
save_game();
|
|
return ret;
|
|
case PLUGIN_ERROR: /* exit without menu */
|
|
return PLUGIN_OK;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case 2:
|
|
playback_control(NULL);
|
|
break;
|
|
case 3:
|
|
do_help();
|
|
break;
|
|
case 4:
|
|
if (confirm_quit())
|
|
return PLUGIN_OK;
|
|
break;
|
|
case 5:
|
|
if (loaded)
|
|
save_game();
|
|
return PLUGIN_OK;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* prepares for exit */
|
|
static void cleanup(void)
|
|
{
|
|
backlight_use_settings();
|
|
}
|
|
|
|
enum plugin_status plugin_start(const void *parameter)
|
|
{
|
|
(void)parameter;
|
|
/* now start the game menu */
|
|
enum plugin_status ret = do_dart_scorer_menu();
|
|
cleanup();
|
|
return ret;
|
|
}
|