summaryrefslogtreecommitdiffstats
path: root/apps/plugins/dart_scorer.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/dart_scorer.c')
-rw-r--r--apps/plugins/dart_scorer.c601
1 files changed, 601 insertions, 0 deletions
diff --git a/apps/plugins/dart_scorer.c b/apps/plugins/dart_scorer.c
new file mode 100644
index 0000000000..1e8dd8f37b
--- /dev/null
+++ b/apps/plugins/dart_scorer.c
@@ -0,0 +1,601 @@
+#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 Menu",
+ 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;
+}