summaryrefslogtreecommitdiff
path: root/apps/menus (follow)
Commit message (Expand)AuthorAge
* Blind commit a 'fix' for automatic resume on HWCODEC since I don't understand...Michael Giacomelli2011-01-02
* Commit part of FS#11748 by Michael Hohmuth. Adds support for automatically r...Michael Giacomelli2011-01-02
* FS#11777: enhancement for rockbox_browse()Teruaki Kawashima2010-12-14
* Get rid of get_user_file_path and do the path handling in wrappers for open()...Thomas Martitz2010-12-06
* Undo the part of r28480 that caused the simulator to also use host malloc.Thomas Martitz2010-11-05
* RaaA: Use the host's malloc() for the skin engine.Jonathan Gordon2010-11-04
* Separate mas35xx lowlevel stuff. Move SH specific bits to target tree. FS#111...Marcin Bukat2010-10-31
* don't reload the skins if color isn't actually changed.Teruaki Kawashima2010-10-08
* New setting to control the file browser start location.Jonathan Gordon2010-10-04
* eq menu: remove write-only variableRafaël Carré2010-09-21
* playback_callback(): remove write-only old_repeat_modeRafaël Carré2010-09-21
* eq menu: remove write-only variable present since r8606Rafaël Carré2010-09-21
* Fix red, stupid typoFrank Gevaerts2010-09-17
* Make disabling HAVE_PITCHSCREEN actually work without breaking the buildFrank Gevaerts2010-09-17
* Another major skin backend update/hopefully bugfix:Jonathan Gordon2010-09-14
* Fix reds. The battery_capacity setting is used even for fixed-battery targets.Thomas Martitz2010-09-01
* Safeguard against possible stack corruption when the string in the tempbuffer...Nils Wallménius2010-08-21
* Rockbox as an application: add get_user_file_path().Thomas Martitz2010-08-01
* FS#11470 - new skin code, finally svn uses the new parser from the theme edit...Jonathan Gordon2010-07-29
* Use browse_folder() for browsing eq presets and languages.Thomas Martitz2010-07-25
* Delete useless include.Nils Wallménius2010-07-24
* Provide the option to automatically update existing bookmark files on stop, w...Torne Wuff2010-07-05
* Remove some redundant #include'sBertrik Sikken2010-06-29
* Rockbox as an application: Replace many occurences of #ifdef SIMULATOR with #...Thomas Martitz2010-06-21
* Fix FS#111395. Call settings_apply_skins() after changing theme colours.Andree Buschmann2010-06-12
* Use SYS_FS_CHANGED and a slight rework in the recalculation of free disk spac...Thomas Martitz2010-06-06
* Remove the jump scroll settings since they don't do anything, drop the releva...Nils Wallménius2010-06-05
* Generate C file / header for svn version stringRafaël Carré2010-05-27
* Give the radio screen setting(s) in the themes menu its own language strings ...Marianne Arnold2010-05-26
* Hopefully get some green back from r26051.Michael Sevakis2010-05-15
* Gigabeat S: Fully enable access to hardware tone controls and 3-D effect feat...Michael Sevakis2010-05-15
* FS#10853 - Skin support in the radio screen! Check CustomWPS for the new tagsJonathan Gordon2010-05-12
* FS#11250: Hotkey setting method changed to menu item vs button pres in contex...Jeffrey Goode2010-05-09
* Fix redJeffrey Goode2010-05-07
* Hotkey menu items have their own iconJeffrey Goode2010-05-07
* Move c/h files implementing/defining standard library stuff into a new libc d...Thomas Martitz2010-05-06
* Fix redMagnus Holmgren2010-04-24
* FS#11099 - Rewind before resumeMagnus Holmgren2010-04-24
* FS#11195, plus. Simplified hotkey struct, thanks alle!Jeffrey Goode2010-04-11
* Reset the number of lines to 0 when initializing the simple listAlexander Levin2010-04-10
* Use a special constant so that we don't have to care about the actual line nu...Alexander Levin2010-04-10
* Rename lang strings to something more appropriateJeffrey Goode2010-04-08
* Hotkey: better settings handling, fewer saved variables, localizable hotkey i...Jeffrey Goode2010-04-08
* Make the displayed hotkey settings scroll (fix FS#11170)Alexander Levin2010-04-04
* FS#11081 - Hotkey patch. Many targets supported, but some keymaps need work ...Jeffrey Goode2010-04-01
* Submit FS#11065. Introduce a new system setting for en-/disabling the Line-ou...Andree Buschmann2010-03-20
* Restore the 'read_disk' flag for settings_apply that was removed in r24922. I...Magnus Holmgren2010-03-06
* cleanup statusbar+base skin relationship...Jonathan Gordon2010-03-06
* fix typo in talk value of histogram settingPeter D'Hoye2010-03-04
* Histogram display on recording screen. Based on the work of Jvo Studer in FS ...Peter D'Hoye2010-03-03
443'>443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527
/***************************************************************************
*             __________               __   ___.
*   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, sizeof(text), "%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;
}