rockbox/apps/plugins/jewels.c
Marianne Arnold 40c0569cef Oops, what did that do there?
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@14774 a1c6a512-1295-4272-9138-f99709370657
2007-09-20 11:06:31 +00:00

1869 lines
62 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2005 Adam Boot
*
* Color graphics from Gweled (http://sebdelestaing.free.fr/gweled/)
*
* All files in this archive are subject to the GNU General Public License.
* See the file COPYING in the source tree root for full license agreement.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "plugin.h"
#include "playback_control.h"
#ifdef HAVE_LCD_BITMAP
PLUGIN_HEADER
/* button definitions */
#if CONFIG_KEYPAD == RECORDER_PAD
#define JEWELS_UP BUTTON_UP
#define JEWELS_DOWN BUTTON_DOWN
#define JEWELS_LEFT BUTTON_LEFT
#define JEWELS_RIGHT BUTTON_RIGHT
#define JEWELS_SELECT BUTTON_PLAY
#define JEWELS_CANCEL BUTTON_OFF
#elif CONFIG_KEYPAD == ARCHOS_AV300_PAD
#define JEWELS_UP BUTTON_UP
#define JEWELS_DOWN BUTTON_DOWN
#define JEWELS_LEFT BUTTON_LEFT
#define JEWELS_RIGHT BUTTON_RIGHT
#define JEWELS_SELECT BUTTON_SELECT
#define JEWELS_CANCEL BUTTON_OFF
#elif CONFIG_KEYPAD == ONDIO_PAD
#define JEWELS_UP BUTTON_UP
#define JEWELS_DOWN BUTTON_DOWN
#define JEWELS_LEFT BUTTON_LEFT
#define JEWELS_RIGHT BUTTON_RIGHT
#define JEWELS_SELECT BUTTON_MENU
#define JEWELS_CANCEL BUTTON_OFF
#elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || (CONFIG_KEYPAD == IRIVER_H300_PAD)
#define JEWELS_UP BUTTON_UP
#define JEWELS_DOWN BUTTON_DOWN
#define JEWELS_LEFT BUTTON_LEFT
#define JEWELS_RIGHT BUTTON_RIGHT
#define JEWELS_SELECT BUTTON_SELECT
#define JEWELS_CANCEL BUTTON_OFF
#define JEWELS_RC_CANCEL BUTTON_RC_STOP
#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || \
(CONFIG_KEYPAD == IPOD_1G2G_PAD)
#define JEWELS_SCROLLWHEEL
#define JEWELS_UP BUTTON_MENU
#define JEWELS_DOWN BUTTON_PLAY
#define JEWELS_LEFT BUTTON_LEFT
#define JEWELS_RIGHT BUTTON_RIGHT
#define JEWELS_PREV BUTTON_SCROLL_BACK
#define JEWELS_NEXT BUTTON_SCROLL_FWD
#define JEWELS_SELECT BUTTON_SELECT
#elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
#define JEWELS_UP BUTTON_UP
#define JEWELS_DOWN BUTTON_DOWN
#define JEWELS_LEFT BUTTON_LEFT
#define JEWELS_RIGHT BUTTON_RIGHT
#define JEWELS_SELECT BUTTON_SELECT
#define JEWELS_CANCEL BUTTON_PLAY
#elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
#define JEWELS_UP BUTTON_UP
#define JEWELS_DOWN BUTTON_DOWN
#define JEWELS_LEFT BUTTON_LEFT
#define JEWELS_RIGHT BUTTON_RIGHT
#define JEWELS_SELECT BUTTON_SELECT
#define JEWELS_CANCEL BUTTON_PLAY
#elif CONFIG_KEYPAD == GIGABEAT_PAD
#define JEWELS_UP BUTTON_UP
#define JEWELS_DOWN BUTTON_DOWN
#define JEWELS_LEFT BUTTON_LEFT
#define JEWELS_RIGHT BUTTON_RIGHT
#define JEWELS_SELECT BUTTON_SELECT
#define JEWELS_CANCEL BUTTON_POWER
#elif (CONFIG_KEYPAD == SANSA_E200_PAD) || \
(CONFIG_KEYPAD == SANSA_C200_PAD)
#define JEWELS_UP BUTTON_UP
#define JEWELS_DOWN BUTTON_DOWN
#define JEWELS_LEFT BUTTON_LEFT
#define JEWELS_RIGHT BUTTON_RIGHT
#define JEWELS_SELECT BUTTON_SELECT
#define JEWELS_CANCEL BUTTON_POWER
#elif CONFIG_KEYPAD == IRIVER_H10_PAD
#define JEWELS_UP BUTTON_SCROLL_UP
#define JEWELS_DOWN BUTTON_SCROLL_DOWN
#define JEWELS_LEFT BUTTON_LEFT
#define JEWELS_RIGHT BUTTON_RIGHT
#define JEWELS_SELECT BUTTON_PLAY
#define JEWELS_CANCEL BUTTON_POWER
#else
#error JEWELS: Unsupported keypad
#endif
/* use 30x30 tiles (iPod Video, Gigabeat) */
#if (LCD_HEIGHT == 240) && (LCD_WIDTH == 320) || \
((LCD_HEIGHT == 320) && (LCD_WIDTH == 240))
#define TILE_WIDTH 30
#define TILE_HEIGHT 30
#define YOFS 0
#define NUM_SCORES 10
/* use 22x22 tiles (H300, iPod Color) */
#elif ((LCD_HEIGHT == 176) && (LCD_WIDTH == 220)) || \
((LCD_HEIGHT == 220) && (LCD_WIDTH == 176))
#define TILE_WIDTH 22
#define TILE_HEIGHT 22
#define YOFS 0
#define NUM_SCORES 10
/* use 16x16 tiles (iPod Nano) */
#elif (LCD_HEIGHT == 132) && (LCD_WIDTH == 176)
#define TILE_WIDTH 16
#define TILE_HEIGHT 16
#define YOFS 4
#define NUM_SCORES 10
/* use 16x16 tiles (H100, iAudio X5, iPod 3G, iPod 4G grayscale) */
#elif (LCD_HEIGHT == 128) && (LCD_WIDTH == 160)
#define TILE_WIDTH 16
#define TILE_HEIGHT 16
#define YOFS 0
#define NUM_SCORES 10
/* use 14x14 tiles (H10 5/6 GB) */
#elif (LCD_HEIGHT == 128) && (LCD_WIDTH == 128)
#define TILE_WIDTH 14
#define TILE_HEIGHT 14
#define YOFS 0
#define NUM_SCORES 10
/* use 13x13 tiles (iPod Mini) */
#elif (LCD_HEIGHT == 110) && (LCD_WIDTH == 138)
#define TILE_WIDTH 13
#define TILE_HEIGHT 13
#define YOFS 6
#define NUM_SCORES 10
/* use 10x10 tiles (Sansa c200) */
#elif (LCD_HEIGHT == 80) && (LCD_WIDTH == 132)
#define TILE_WIDTH 10
#define TILE_HEIGHT 10
#define YOFS 0
#define NUM_SCORES 8
/* use 10x8 tiles (iFP 700) */
#elif (LCD_HEIGHT == 64) && (LCD_WIDTH == 128)
#define TILE_WIDTH 10
#define TILE_HEIGHT 8
#define YOFS 0
#define NUM_SCORES 8
/* use 10x8 tiles (Recorder, Ondio) */
#elif (LCD_HEIGHT == 64) && (LCD_WIDTH == 112)
#define TILE_WIDTH 10
#define TILE_HEIGHT 8
#define YOFS 0
#define NUM_SCORES 8
#else
#error JEWELS: Unsupported LCD
#endif
/* save files */
#define SCORE_FILE PLUGIN_GAMES_DIR "/jewels.score"
#define SAVE_FILE PLUGIN_GAMES_DIR "/jewels.save"
/* final game return status */
#define BJ_QUIT_FROM_GAME 4
#define BJ_END 3
#define BJ_USB 2
#define BJ_QUIT 1
#define BJ_LOSE 0
/* swap directions */
#define SWAP_UP 0
#define SWAP_RIGHT 1
#define SWAP_DOWN 2
#define SWAP_LEFT 3
/* play board dimension */
#define BJ_HEIGHT 9
#define BJ_WIDTH 8
/* next level threshold */
#define LEVEL_PTS 100
/* animation frame rate */
#define MAX_FPS 20
/* Game types */
enum game_type {
GAME_TYPE_NORMAL,
GAME_TYPE_PUZZLE
};
/* menu values */
#define FONT_HEIGHT 8
#define MAX_MITEMS 6
#define MENU_WIDTH 100
/* menu results */
enum menu_result {
MRES_NONE,
MRES_NEW,
MRES_PUZZLE,
MRES_SAVE,
MRES_RESUME,
MRES_SCORES,
MRES_HELP,
MRES_QUIT,
MRES_PLAYBACK,
MRES_EXIT
};
/* menu commands */
enum menu_cmd {
MCMD_NONE,
MCMD_NEXT,
MCMD_PREV,
MCMD_SELECT
};
/* menus */
struct jewels_menu {
char *title;
bool hasframe;
int selected;
int itemcnt;
struct jewels_menuitem {
char *text;
enum menu_result res;
} items[MAX_MITEMS];
} bjmenu[] = {
{"Jewels", false, 0, 6,
{{"New Game", MRES_NEW},
{"Puzzle", MRES_PUZZLE},
{"Resume Game", MRES_RESUME},
{"High Scores", MRES_SCORES},
{"Help", MRES_HELP},
{"Quit", MRES_QUIT}}},
{"Menu", true, 0, 5,
{{"Audio Playback", MRES_PLAYBACK },
{"Resume Game", MRES_RESUME},
{"Save Game", MRES_SAVE},
{"End Game", MRES_QUIT},
{"Exit Jewels", MRES_EXIT}}}
};
/* global rockbox api */
static struct plugin_api* rb;
/* external bitmaps */
extern const fb_data jewels[];
/* tile background colors */
#ifdef HAVE_LCD_COLOR
static const unsigned jewels_bkgd[2] = {
LCD_RGBPACK(104, 63, 63),
LCD_RGBPACK(83, 44, 44)
};
#endif
/* the tile struct
* type is the jewel number 0-7
* falling if the jewel is falling
* delete marks the jewel for deletion
*/
struct tile {
int type;
bool falling;
bool delete;
};
/* the game context struct
* score is the current level score
* segments is the number of cleared segments in the current run
* level is the current level
* type is the game type (normal or puzzle)
* highscores is the list of high scores
* resume denotes whether to resume the currently loaded game
* dirty denotes whether the high scores are out of sync with the saved file
* playboard is the game playing board (first row is hidden)
* num_jewels is the number of different jewels to use
*/
struct game_context {
unsigned int score;
unsigned int segments;
unsigned int level;
unsigned int type;
unsigned int highscores[NUM_SCORES];
bool resume;
bool dirty;
struct tile playboard[BJ_HEIGHT][BJ_WIDTH];
unsigned int num_jewels;
};
#define MAX_NUM_JEWELS 7
#define MAX_PUZZLE_TILES 4
#define NUM_PUZZLE_LEVELS 10
struct puzzle_tile {
int x;
int y;
int tile_type;
};
struct puzzle_level {
unsigned int num_jewels;
unsigned int num_tiles;
struct puzzle_tile tiles[MAX_PUZZLE_TILES];
};
#define PUZZLE_TILE_UP 1
#define PUZZLE_TILE_DOWN 2
#define PUZZLE_TILE_LEFT 4
#define PUZZLE_TILE_RIGHT 8
struct puzzle_level puzzle_levels[NUM_PUZZLE_LEVELS] = {
{ 5, 2, { {3, 3, PUZZLE_TILE_RIGHT},
{4, 2, PUZZLE_TILE_LEFT} } },
{ 5, 2, { {3, 2, PUZZLE_TILE_DOWN},
{3, 4, PUZZLE_TILE_UP} } },
{ 6, 3, { {3, 2, PUZZLE_TILE_DOWN},
{3, 4, PUZZLE_TILE_UP|PUZZLE_TILE_DOWN},
{3, 6, PUZZLE_TILE_UP} } },
{ 6, 3, { {3, 2, PUZZLE_TILE_RIGHT},
{4, 3, PUZZLE_TILE_LEFT|PUZZLE_TILE_RIGHT},
{5, 4, PUZZLE_TILE_LEFT} } },
{ 6, 2, { {3, 4, PUZZLE_TILE_RIGHT},
{4, 2, PUZZLE_TILE_LEFT} } },
{ 6, 2, { {3, 2, PUZZLE_TILE_DOWN},
{4, 4, PUZZLE_TILE_UP} } },
{ 7, 4, { {3, 2, PUZZLE_TILE_RIGHT|PUZZLE_TILE_DOWN},
{4, 3, PUZZLE_TILE_LEFT|PUZZLE_TILE_DOWN},
{3, 4, PUZZLE_TILE_RIGHT|PUZZLE_TILE_UP},
{4, 4, PUZZLE_TILE_LEFT|PUZZLE_TILE_UP} } },
{ 6, 3, { {3, 2, PUZZLE_TILE_DOWN},
{4, 4, PUZZLE_TILE_UP|PUZZLE_TILE_DOWN},
{3, 6, PUZZLE_TILE_UP} } },
{ 7, 3, { {2, 2, PUZZLE_TILE_RIGHT},
{4, 1, PUZZLE_TILE_LEFT|PUZZLE_TILE_RIGHT},
{5, 4, PUZZLE_TILE_LEFT} } },
{ 7, 4, { {3, 0, PUZZLE_TILE_RIGHT|PUZZLE_TILE_DOWN},
{5, 0, PUZZLE_TILE_LEFT|PUZZLE_TILE_DOWN},
{2, 7, PUZZLE_TILE_RIGHT|PUZZLE_TILE_UP},
{4, 7, PUZZLE_TILE_LEFT|PUZZLE_TILE_UP} } },
};
/*****************************************************************************
* jewels_init() initializes jewels data structures.
******************************************************************************/
static void jewels_init(struct game_context* bj) {
/* seed the rand generator */
rb->srand(*rb->current_tick);
/* check for resumed game */
if(bj->resume) {
bj->resume = false;
return;
}
/* reset scoring */
bj->level = 1;
bj->score = 0;
bj->segments = 0;
/* clear playing board */
rb->memset(bj->playboard, 0, sizeof(bj->playboard));
}
/*****************************************************************************
* jewels_setcolors() set the foreground and background colors.
******************************************************************************/
static inline void jewels_setcolors(void) {
#ifdef HAVE_LCD_COLOR
rb->lcd_set_background(LCD_RGBPACK(49, 26, 26));
rb->lcd_set_foreground(LCD_RGBPACK(210, 181, 181));
#endif
}
/*****************************************************************************
* jewels_drawboard() redraws the entire game board.
******************************************************************************/
static void jewels_drawboard(struct game_context* bj) {
int i, j;
int w, h;
unsigned int tempscore;
char *title = "Level";
char str[10];
tempscore = (bj->score>LEVEL_PTS ? LEVEL_PTS : bj->score);
/* clear screen */
rb->lcd_clear_display();
/* dispay playing board */
for(i=0; i<BJ_HEIGHT-1; i++){
for(j=0; j<BJ_WIDTH; j++){
#ifdef HAVE_LCD_COLOR
rb->lcd_set_foreground(jewels_bkgd[(i+j)%2]);
rb->lcd_fillrect(j*TILE_WIDTH, i*TILE_HEIGHT+YOFS,
TILE_WIDTH, TILE_HEIGHT);
rb->lcd_bitmap_transparent_part(jewels,
0, TILE_HEIGHT*(bj->playboard[i+1][j].type),
TILE_WIDTH, j*TILE_WIDTH, i*TILE_HEIGHT+YOFS,
TILE_WIDTH, TILE_HEIGHT);
#else
rb->lcd_bitmap_part(jewels,
0, TILE_HEIGHT*(bj->playboard[i+1][j].type),
TILE_WIDTH, j*TILE_WIDTH, i*TILE_HEIGHT+YOFS,
TILE_WIDTH, TILE_HEIGHT);
#endif
}
}
#if LCD_WIDTH > LCD_HEIGHT /* horizontal layout */
/* draw separator lines */
jewels_setcolors();
rb->lcd_vline(BJ_WIDTH*TILE_WIDTH, 0, LCD_HEIGHT-1);
rb->lcd_hline(BJ_WIDTH*TILE_WIDTH, LCD_WIDTH-1, 18);
rb->lcd_hline(BJ_WIDTH*TILE_WIDTH, LCD_WIDTH-1, LCD_HEIGHT-10);
/* draw progress bar */
#ifdef HAVE_LCD_COLOR
rb->lcd_set_foreground(LCD_RGBPACK(104, 63, 63));
#endif
rb->lcd_fillrect(BJ_WIDTH*TILE_WIDTH+(LCD_WIDTH-BJ_WIDTH*TILE_WIDTH)/4,
(LCD_HEIGHT-10)-(((LCD_HEIGHT-10)-18)*
tempscore/LEVEL_PTS),
(LCD_WIDTH-BJ_WIDTH*TILE_WIDTH)/2,
((LCD_HEIGHT-10)-18)*tempscore/LEVEL_PTS);
#ifdef HAVE_LCD_COLOR
rb->lcd_set_foreground(LCD_RGBPACK(83, 44, 44));
rb->lcd_drawrect(BJ_WIDTH*TILE_WIDTH+(LCD_WIDTH-BJ_WIDTH*TILE_WIDTH)/4+1,
(LCD_HEIGHT-10)-(((LCD_HEIGHT-10)-18)*
tempscore/LEVEL_PTS)+1,
(LCD_WIDTH-BJ_WIDTH*TILE_WIDTH)/2-2,
((LCD_HEIGHT-10)-18)*tempscore/LEVEL_PTS-1);
jewels_setcolors();
rb->lcd_drawrect(BJ_WIDTH*TILE_WIDTH+(LCD_WIDTH-BJ_WIDTH*TILE_WIDTH)/4,
(LCD_HEIGHT-10)-(((LCD_HEIGHT-10)-18)*
tempscore/LEVEL_PTS),
(LCD_WIDTH-BJ_WIDTH*TILE_WIDTH)/2,
((LCD_HEIGHT-10)-18)*tempscore/LEVEL_PTS+1);
#endif
/* print text */
rb->lcd_getstringsize(title, &w, &h);
rb->lcd_putsxy(LCD_WIDTH-(LCD_WIDTH-BJ_WIDTH*TILE_WIDTH)/2-w/2, 1, title);
rb->snprintf(str, 4, "%d", bj->level);
rb->lcd_getstringsize(str, &w, &h);
rb->lcd_putsxy(LCD_WIDTH-(LCD_WIDTH-BJ_WIDTH*TILE_WIDTH)/2-w/2, 10, str);
rb->snprintf(str, 6, "%d", (bj->level-1)*LEVEL_PTS+bj->score);
rb->lcd_getstringsize(str, &w, &h);
rb->lcd_putsxy(LCD_WIDTH-(LCD_WIDTH-BJ_WIDTH*TILE_WIDTH)/2-w/2,
LCD_HEIGHT-8, str);
#elif LCD_WIDTH < LCD_HEIGHT /* vertical layout */
/* draw separator lines */
jewels_setcolors();
rb->lcd_hline(0, LCD_WIDTH-1, 8*TILE_HEIGHT+YOFS);
rb->lcd_hline(0, LCD_WIDTH-1, LCD_HEIGHT-14);
rb->lcd_vline(LCD_WIDTH/2, LCD_HEIGHT-14, LCD_HEIGHT-1);
/* draw progress bar */
#ifdef HAVE_LCD_COLOR
rb->lcd_set_foreground(LCD_RGBPACK(104, 63, 63));
#endif
rb->lcd_fillrect(0, (8*TILE_HEIGHT+YOFS)
+(LCD_HEIGHT-14-(8*TILE_HEIGHT+YOFS))/4,
LCD_WIDTH*tempscore/LEVEL_PTS,
(LCD_HEIGHT-14-(8*TILE_HEIGHT+YOFS))/2);
#ifdef HAVE_LCD_COLOR
rb->lcd_set_foreground(LCD_RGBPACK(83, 44, 44));
rb->lcd_drawrect(1, (8*TILE_HEIGHT+YOFS)
+(LCD_HEIGHT-14-(8*TILE_HEIGHT+YOFS))/4+1,
LCD_WIDTH*tempscore/LEVEL_PTS-1,
(LCD_HEIGHT-14-(8*TILE_HEIGHT+YOFS))/2-2);
jewels_setcolors();
rb->lcd_drawrect(0, (8*TILE_HEIGHT+YOFS)
+(LCD_HEIGHT-14-(8*TILE_HEIGHT+YOFS))/4,
LCD_WIDTH*tempscore/LEVEL_PTS+1,
(LCD_HEIGHT-14-(8*TILE_HEIGHT+YOFS))/2);
#endif
/* print text */
rb->snprintf(str, 10, "%s %d", title, bj->level);
rb->lcd_putsxy(1, LCD_HEIGHT-10, str);
rb->snprintf(str, 6, "%d", (bj->level-1)*LEVEL_PTS+bj->score);
rb->lcd_getstringsize(str, &w, &h);
rb->lcd_putsxy((LCD_WIDTH-2)-w, LCD_HEIGHT-10, str);
#else /* square layout */
/* draw separator lines */
jewels_setcolors();
rb->lcd_hline(0, LCD_WIDTH-1, 8*TILE_HEIGHT+YOFS);
rb->lcd_vline(BJ_WIDTH*TILE_WIDTH, 0, 8*TILE_HEIGHT+YOFS);
rb->lcd_vline(LCD_WIDTH/2, 8*TILE_HEIGHT+YOFS, LCD_HEIGHT-1);
/* draw progress bar */
#ifdef HAVE_LCD_COLOR
rb->lcd_set_foreground(LCD_RGBPACK(104, 63, 63));
#endif
rb->lcd_fillrect(BJ_WIDTH*TILE_WIDTH+(LCD_WIDTH-BJ_WIDTH*TILE_WIDTH)/4,
(8*TILE_HEIGHT+YOFS)-(8*TILE_HEIGHT+YOFS)
*tempscore/LEVEL_PTS,
(LCD_WIDTH-BJ_WIDTH*TILE_WIDTH)/2,
(8*TILE_HEIGHT+YOFS)*tempscore/LEVEL_PTS);
#ifdef HAVE_LCD_COLOR
rb->lcd_set_foreground(LCD_RGBPACK(83, 44, 44));
rb->lcd_drawrect(BJ_WIDTH*TILE_WIDTH+(LCD_WIDTH-BJ_WIDTH*TILE_WIDTH)/4+1,
(8*TILE_HEIGHT+YOFS)-(8*TILE_HEIGHT+YOFS)
*tempscore/LEVEL_PTS+1,
(LCD_WIDTH-BJ_WIDTH*TILE_WIDTH)/2-2,
(8*TILE_HEIGHT+YOFS)*tempscore/LEVEL_PTS-1);
jewels_setcolors();
rb->lcd_drawrect(BJ_WIDTH*TILE_WIDTH+(LCD_WIDTH-BJ_WIDTH*TILE_WIDTH)/4,
(8*TILE_HEIGHT+YOFS)-(8*TILE_HEIGHT+YOFS)
*tempscore/LEVEL_PTS,
(LCD_WIDTH-BJ_WIDTH*TILE_WIDTH)/2,
(8*TILE_HEIGHT+YOFS)*tempscore/LEVEL_PTS+1);
#endif
/* print text */
rb->snprintf(str, 10, "%s %d", title, bj->level);
rb->lcd_putsxy(1, LCD_HEIGHT-(LCD_HEIGHT-(8*TILE_HEIGHT+YOFS))/2-3, str);
rb->snprintf(str, 6, "%d", (bj->level-1)*LEVEL_PTS+bj->score);
rb->lcd_getstringsize(str, &w, &h);
rb->lcd_putsxy((LCD_WIDTH-2)-w,
LCD_HEIGHT-(LCD_HEIGHT-(8*TILE_HEIGHT+YOFS))/2-3, str);
#endif /* layout */
rb->lcd_update();
}
/*****************************************************************************
* jewels_showmenu() displays the chosen menu after performing the chosen
* menu command.
******************************************************************************/
static enum menu_result jewels_showmenu(struct jewels_menu* menu,
enum menu_cmd cmd) {
int i;
int w, h;
int firstline;
int adj;
/* handle menu command */
switch(cmd) {
case MCMD_NEXT:
menu->selected = (menu->selected+1)%menu->itemcnt;
break;
case MCMD_PREV:
menu->selected = (menu->selected-1+menu->itemcnt)%menu->itemcnt;
break;
case MCMD_SELECT:
return menu->items[menu->selected].res;
default:
break;
}
/* clear menu area */
firstline = (LCD_HEIGHT/FONT_HEIGHT-(menu->itemcnt+3))/2;
rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
rb->lcd_fillrect((LCD_WIDTH-MENU_WIDTH)/2, firstline*FONT_HEIGHT,
MENU_WIDTH, (menu->itemcnt+3)*FONT_HEIGHT);
rb->lcd_set_drawmode(DRMODE_SOLID);
if(menu->hasframe) {
rb->lcd_drawrect((LCD_WIDTH-MENU_WIDTH)/2-1, firstline*FONT_HEIGHT-1,
MENU_WIDTH+2, (menu->itemcnt+3)*FONT_HEIGHT+2);
rb->lcd_hline((LCD_WIDTH-MENU_WIDTH)/2-1,
(LCD_WIDTH-MENU_WIDTH)/2-1+MENU_WIDTH+2,
(firstline+1)*FONT_HEIGHT);
}
/* draw menu items */
rb->lcd_getstringsize(menu->title, &w, &h);
rb->lcd_putsxy((LCD_WIDTH-w)/2, firstline*FONT_HEIGHT, menu->title);
for(i=0; i<menu->itemcnt; i++) {
if(i == menu->selected) {
rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
}
rb->lcd_putsxy((LCD_WIDTH-MENU_WIDTH)/2, (firstline+i+2)*FONT_HEIGHT,
menu->items[i].text);
if(i == menu->selected) {
rb->lcd_set_drawmode(DRMODE_SOLID);
}
}
adj = (firstline == 0 ? 0 : 1);
rb->lcd_update_rect((LCD_WIDTH-MENU_WIDTH)/2-1, firstline*FONT_HEIGHT-adj,
MENU_WIDTH+2, (menu->itemcnt+3)*FONT_HEIGHT+2*adj);
return MRES_NONE;
}
/*****************************************************************************
* jewels_putjewels() makes the jewels fall to fill empty spots and adds
* new random jewels at the empty spots at the top of each row.
******************************************************************************/
static void jewels_putjewels(struct game_context* bj){
int i, j, k;
bool mark, done;
long lasttick, currenttick;
/* loop to make all the jewels fall */
while(true) {
/* mark falling jewels and add new jewels to hidden top row*/
mark = false;
done = true;
for(j=0; j<BJ_WIDTH; j++) {
if(bj->playboard[1][j].type == 0) {
bj->playboard[0][j].type = rb->rand()%bj->num_jewels+1;
}
for(i=BJ_HEIGHT-2; i>=0; i--) {
if(!mark && bj->playboard[i+1][j].type == 0) {
mark = true;
done = false;
}
if(mark) bj->playboard[i][j].falling = true;
}
/*if(bj->playboard[1][j].falling) {
bj->playboard[0][j].type = rb->rand()%bj->num_jewels+1;
bj->playboard[0][j].falling = true;
}*/
mark = false;
}
/* break if there are no falling jewels */
if(done) break;
/* animate falling jewels */
lasttick = *rb->current_tick;
for(k=1; k<=8; k++) {
for(i=BJ_HEIGHT-2; i>=0; i--) {
for(j=0; j<BJ_WIDTH; j++) {
if(bj->playboard[i][j].falling &&
bj->playboard[i][j].type != 0) {
/* clear old position */
#ifdef HAVE_LCD_COLOR
if(i == 0 && YOFS) {
rb->lcd_set_foreground(rb->lcd_get_background());
} else {
rb->lcd_set_foreground(jewels_bkgd[(i-1+j)%2]);
}
rb->lcd_fillrect(j*TILE_WIDTH, (i-1)*TILE_HEIGHT+YOFS,
TILE_WIDTH, TILE_HEIGHT);
if(bj->playboard[i+1][j].type == 0) {
rb->lcd_set_foreground(jewels_bkgd[(i+j)%2]);
rb->lcd_fillrect(j*TILE_WIDTH, i*TILE_HEIGHT+YOFS,
TILE_WIDTH, TILE_HEIGHT);
}
#else
rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
rb->lcd_fillrect(j*TILE_WIDTH, (i-1)*TILE_HEIGHT+YOFS,
TILE_WIDTH, TILE_HEIGHT);
if(bj->playboard[i+1][j].type == 0) {
rb->lcd_fillrect(j*TILE_WIDTH, i*TILE_HEIGHT+YOFS,
TILE_WIDTH, TILE_HEIGHT);
}
rb->lcd_set_drawmode(DRMODE_SOLID);
#endif
/* draw new position */
#ifdef HAVE_LCD_COLOR
rb->lcd_bitmap_transparent_part(jewels, 0,
TILE_HEIGHT*(bj->playboard[i][j].type),
TILE_WIDTH, j*TILE_WIDTH,
(i-1)*TILE_HEIGHT+YOFS+
((((TILE_HEIGHT<<10)*k)/8)>>10),
TILE_WIDTH, TILE_HEIGHT);
#else
rb->lcd_bitmap_part(jewels, 0,
TILE_HEIGHT*(bj->playboard[i][j].type),
TILE_WIDTH, j*TILE_WIDTH,
(i-1)*TILE_HEIGHT+YOFS+
((((TILE_HEIGHT<<10)*k)/8)>>10),
TILE_WIDTH, TILE_HEIGHT);
#endif
}
}
}
rb->lcd_update_rect(0, 0, TILE_WIDTH*8, LCD_HEIGHT);
jewels_setcolors();
/* framerate limiting */
currenttick = *rb->current_tick;
if(currenttick-lasttick < HZ/MAX_FPS) {
rb->sleep((HZ/MAX_FPS)-(currenttick-lasttick));
} else {
rb->yield();
}
lasttick = currenttick;
}
/* shift jewels down */
for(j=0; j<BJ_WIDTH; j++) {
for(i=BJ_HEIGHT-1; i>=1; i--) {
if(bj->playboard[i-1][j].falling) {
bj->playboard[i][j].type = bj->playboard[i-1][j].type;
}
}
}
/* clear out top row */
for(j=0; j<BJ_WIDTH; j++) {
bj->playboard[0][j].type = 0;
}
/* mark everything not falling */
for(i=0; i<BJ_HEIGHT; i++) {
for(j=0; j<BJ_WIDTH; j++) {
bj->playboard[i][j].falling = false;
}
}
}
}
/*****************************************************************************
* jewels_clearjewels() finds all the connected rows and columns and
* calculates and returns the points earned.
******************************************************************************/
static unsigned int jewels_clearjewels(struct game_context* bj) {
int i, j;
int last, run;
unsigned int points = 0;
/* check for connected rows */
for(i=1; i<BJ_HEIGHT; i++) {
last = 0;
run = 1;
for(j=0; j<BJ_WIDTH; j++) {
if(bj->playboard[i][j].type == last &&
bj->playboard[i][j].type != 0 &&
bj->playboard[i][j].type <= MAX_NUM_JEWELS) {
run++;
if(run == 3) {
bj->segments++;
points += bj->segments;
bj->playboard[i][j].delete = true;
bj->playboard[i][j-1].delete = true;
bj->playboard[i][j-2].delete = true;
} else if(run > 3) {
points++;
bj->playboard[i][j].delete = true;
}
} else {
run = 1;
last = bj->playboard[i][j].type;
}
}
}
/* check for connected columns */
for(j=0; j<BJ_WIDTH; j++) {
last = 0;
run = 1;
for(i=1; i<BJ_HEIGHT; i++) {
if(bj->playboard[i][j].type != 0 &&
bj->playboard[i][j].type == last &&
bj->playboard[i][j].type <= MAX_NUM_JEWELS) {
run++;
if(run == 3) {
bj->segments++;
points += bj->segments;
bj->playboard[i][j].delete = true;
bj->playboard[i-1][j].delete = true;
bj->playboard[i-2][j].delete = true;
} else if(run > 3) {
points++;
bj->playboard[i][j].delete = true;
}
} else {
run = 1;
last = bj->playboard[i][j].type;
}
}
}
/* clear deleted jewels */
for(i=1; i<BJ_HEIGHT; i++) {
for(j=0; j<BJ_WIDTH; j++) {
if(bj->playboard[i][j].delete) {
bj->playboard[i][j].delete = false;
bj->playboard[i][j].type = 0;
}
}
}
return points;
}
/*****************************************************************************
* jewels_runboard() runs the board until it settles in a fixed state and
* returns points earned.
******************************************************************************/
static unsigned int jewels_runboard(struct game_context* bj) {
unsigned int points = 0;
unsigned int ret;
bj->segments = 0;
while((ret = jewels_clearjewels(bj)) > 0) {
points += ret;
jewels_drawboard(bj);
jewels_putjewels(bj);
}
return points;
}
/*****************************************************************************
* jewels_swapjewels() swaps two jewels as long as it results in points and
* returns points earned.
******************************************************************************/
static unsigned int jewels_swapjewels(struct game_context* bj,
int x, int y, int direc) {
int k;
int horzmod, vertmod;
int movelen = 0;
bool undo = false;
unsigned int points = 0;
long lasttick, currenttick;
/* check for invalid parameters */
if(x < 0 || x >= BJ_WIDTH || y < 0 || y >= BJ_HEIGHT-1 ||
direc < SWAP_UP || direc > SWAP_LEFT) {
return 0;
}
/* check for invalid directions */
if((x == 0 && direc == SWAP_LEFT) ||
(x == BJ_WIDTH-1 && direc == SWAP_RIGHT) ||
(y == 0 && direc == SWAP_UP) ||
(y == BJ_HEIGHT-2 && direc == SWAP_DOWN)) {
return 0;
}
/* set direction variables */
horzmod = 0;
vertmod = 0;
switch(direc) {
case SWAP_UP:
vertmod = -1;
movelen = TILE_HEIGHT;
break;
case SWAP_RIGHT:
horzmod = 1;
movelen = TILE_WIDTH;
break;
case SWAP_DOWN:
vertmod = 1;
movelen = TILE_HEIGHT;
break;
case SWAP_LEFT:
horzmod = -1;
movelen = TILE_WIDTH;
break;
}
while(true) {
lasttick = *rb->current_tick;
/* animate swapping jewels */
for(k=0; k<=8; k++) {
/* clear old position */
#ifdef HAVE_LCD_COLOR
rb->lcd_set_foreground(jewels_bkgd[(x+y)%2]);
rb->lcd_fillrect(x*TILE_WIDTH,
y*TILE_HEIGHT+YOFS,
TILE_WIDTH, TILE_HEIGHT);
rb->lcd_set_foreground(jewels_bkgd[(x+horzmod+y+vertmod)%2]);
rb->lcd_fillrect((x+horzmod)*TILE_WIDTH,
(y+vertmod)*TILE_HEIGHT+YOFS,
TILE_WIDTH, TILE_HEIGHT);
#else
rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
rb->lcd_fillrect(x*TILE_WIDTH,
y*TILE_HEIGHT+YOFS,
TILE_WIDTH, TILE_HEIGHT);
rb->lcd_fillrect((x+horzmod)*TILE_WIDTH,
(y+vertmod)*TILE_HEIGHT+YOFS,
TILE_WIDTH, TILE_HEIGHT);
rb->lcd_set_drawmode(DRMODE_SOLID);
#endif
/* draw new position */
#ifdef HAVE_LCD_COLOR
rb->lcd_bitmap_transparent_part(jewels,
0, TILE_HEIGHT*(bj->playboard
[y+1+vertmod][x+horzmod].type), TILE_WIDTH,
(x+horzmod)*TILE_WIDTH-horzmod*
((((movelen<<10)*k)/8)>>10),
(y+vertmod)*TILE_HEIGHT-vertmod*
((((movelen<<10)*k)/8)>>10)+YOFS,
TILE_WIDTH, TILE_HEIGHT);
rb->lcd_bitmap_transparent_part(jewels,
0, TILE_HEIGHT*(bj->playboard[y+1][x].type),
TILE_WIDTH, x*TILE_WIDTH+horzmod*
((((movelen<<10)*k)/8)>>10),
y*TILE_HEIGHT+vertmod*
((((movelen<<10)*k)/8)>>10)+YOFS,
TILE_WIDTH, TILE_HEIGHT);
#else
rb->lcd_bitmap_part(jewels,
0, TILE_HEIGHT*(bj->playboard
[y+1+vertmod][x+horzmod].type), TILE_WIDTH,
(x+horzmod)*TILE_WIDTH-horzmod*
((((movelen<<10)*k)/8)>>10),
(y+vertmod)*TILE_HEIGHT-vertmod*
((((movelen<<10)*k)/8)>>10)+YOFS,
TILE_WIDTH, TILE_HEIGHT);
rb->lcd_set_drawmode(DRMODE_FG);
rb->lcd_bitmap_part(jewels,
0, TILE_HEIGHT*(bj->playboard[y+1][x].type),
TILE_WIDTH, x*TILE_WIDTH+horzmod*
((((movelen<<10)*k)/8)>>10),
y*TILE_HEIGHT+vertmod*
((((movelen<<10)*k)/8)>>10)+YOFS,
TILE_WIDTH, TILE_HEIGHT);
rb->lcd_set_drawmode(DRMODE_SOLID);
#endif
rb->lcd_update_rect(0, 0, TILE_WIDTH*8, LCD_HEIGHT);
jewels_setcolors();
/* framerate limiting */
currenttick = *rb->current_tick;
if(currenttick-lasttick < HZ/MAX_FPS) {
rb->sleep((HZ/MAX_FPS)-(currenttick-lasttick));
} else {
rb->yield();
}
lasttick = currenttick;
}
/* swap jewels */
int temp = bj->playboard[y+1][x].type;
bj->playboard[y+1][x].type =
bj->playboard[y+1+vertmod][x+horzmod].type;
bj->playboard[y+1+vertmod][x+horzmod].type = temp;
if(undo) break;
points = jewels_runboard(bj);
if(points == 0) {
undo = true;
} else {
break;
}
}
return points;
}
/*****************************************************************************
* jewels_movesavail() uses pattern matching to see if there are any
* available move left.
******************************************************************************/
static bool jewels_movesavail(struct game_context* bj) {
int i, j;
bool moves = false;
int mytype;
for(i=1; i<BJ_HEIGHT; i++) {
for(j=0; j<BJ_WIDTH; j++) {
mytype = bj->playboard[i][j].type;
if(mytype == 0 || mytype > MAX_NUM_JEWELS) continue;
/* check horizontal patterns */
if(j <= BJ_WIDTH-3) {
if(i > 1) {
if(bj->playboard[i-1][j+1].type == mytype) {
if(bj->playboard[i-1][j+2].type == mytype)
{moves = true; break;}
if(bj->playboard[i][j+2].type == mytype)
{moves = true; break;}
}
if(bj->playboard[i][j+1].type == mytype) {
if(bj->playboard[i-1][j+2].type == mytype)
{moves = true; break;}
}
}
if(j <= BJ_WIDTH-4) {
if(bj->playboard[i][j+3].type == mytype) {
if(bj->playboard[i][j+1].type == mytype)
{moves = true; break;}
if(bj->playboard[i][j+2].type == mytype)
{moves = true; break;}
}
}
if(i < BJ_HEIGHT-1) {
if(bj->playboard[i][j+1].type == mytype) {
if(bj->playboard[i+1][j+2].type == mytype)
{moves = true; break;}
}
if(bj->playboard[i+1][j+1].type == mytype) {
if(bj->playboard[i][j+2].type == mytype)
{moves = true; break;}
if(bj->playboard[i+1][j+2].type == mytype)
{moves = true; break;}
}
}
}
/* check vertical patterns */
if(i <= BJ_HEIGHT-3) {
if(j > 0) {
if(bj->playboard[i+1][j-1].type == mytype) {
if(bj->playboard[i+2][j-1].type == mytype)
{moves = true; break;}
if(bj->playboard[i+2][j].type == mytype)
{moves = true; break;}
}
if(bj->playboard[i+1][j].type == mytype) {
if(bj->playboard[i+2][j-1].type == mytype)
{moves = true; break;}
}
}
if(i <= BJ_HEIGHT-4) {
if(bj->playboard[i+3][j].type == mytype) {
if(bj->playboard[i+1][j].type == mytype)
{moves = true; break;}
if(bj->playboard[i+2][j].type == mytype)
{moves = true; break;}
}
}
if(j < BJ_WIDTH-1) {
if(bj->playboard[i+1][j].type == mytype) {
if(bj->playboard[i+2][j+1].type == mytype)
{moves = true; break;}
}
if(bj->playboard[i+1][j+1].type == mytype) {
if(bj->playboard[i+2][j].type == mytype)
{moves = true; break;}
if (bj->playboard[i+2][j+1].type == mytype)
{moves = true; break;}
}
}
}
}
if(moves) break;
}
return moves;
}
/*****************************************************************************
* jewels_puzzle_is_finished(bj) checks if the puzzle is finished.
******************************************************************************/
static int jewels_puzzle_is_finished(struct game_context* bj) {
unsigned int i, j;
for(i=0; i<BJ_HEIGHT; i++) {
for(j=0; j<BJ_WIDTH; j++) {
int mytype = bj->playboard[i][j].type;
if(mytype>MAX_NUM_JEWELS) {
mytype -= MAX_NUM_JEWELS;
if(mytype&PUZZLE_TILE_UP) {
if(i==0 || bj->playboard[i-1][j].type<=MAX_NUM_JEWELS ||
!((bj->playboard[i-1][j].type-MAX_NUM_JEWELS)
&PUZZLE_TILE_DOWN))
return 0;
}
if(mytype&PUZZLE_TILE_DOWN) {
if(i==BJ_HEIGHT-1 ||
bj->playboard[i+1][j].type<=MAX_NUM_JEWELS ||
!((bj->playboard[i+1][j].type-MAX_NUM_JEWELS)
&PUZZLE_TILE_UP))
return 0;
}
if(mytype&PUZZLE_TILE_LEFT) {
if(j==0 || bj->playboard[i][j-1].type<=MAX_NUM_JEWELS ||
!((bj->playboard[i][j-1].type-MAX_NUM_JEWELS)
&PUZZLE_TILE_RIGHT))
return 0;
}
if(mytype&PUZZLE_TILE_RIGHT) {
if(j==BJ_WIDTH-1 ||
bj->playboard[i][j+1].type<=MAX_NUM_JEWELS ||
!((bj->playboard[i][j+1].type-MAX_NUM_JEWELS)
&PUZZLE_TILE_LEFT))
return 0;
}
}
}
}
return 1;
}
/*****************************************************************************
* jewels_initlevel() initialises a level.
******************************************************************************/
static unsigned int jewels_initlevel(struct game_context* bj) {
unsigned int points = 0;
switch(bj->type) {
case GAME_TYPE_NORMAL:
bj->num_jewels = MAX_NUM_JEWELS;
break;
case GAME_TYPE_PUZZLE:
{
unsigned int i, j;
struct puzzle_tile *tile;
bj->num_jewels = puzzle_levels[bj->level-1].num_jewels;
for(i=0; i<BJ_HEIGHT; i++) {
for(j=0; j<BJ_WIDTH; j++) {
bj->playboard[i][j].type = (rb->rand()%bj->num_jewels)+1;
bj->playboard[i][j].falling = false;
bj->playboard[i][j].delete = false;
}
}
jewels_runboard(bj);
tile = puzzle_levels[bj->level-1].tiles;
for(i=0; i<puzzle_levels[bj->level-1].num_tiles; i++, tile++) {
bj->playboard[tile->y+1][tile->x].type = MAX_NUM_JEWELS
+tile->tile_type;
}
}
break;
}
jewels_drawboard(bj);
/* run the play board */
jewels_putjewels(bj);
points += jewels_runboard(bj);
return points;
}
/*****************************************************************************
* jewels_nextlevel() advances the game to the next level and returns
* points earned.
******************************************************************************/
static unsigned int jewels_nextlevel(struct game_context* bj) {
int i, x, y;
unsigned int points = 0;
switch(bj->type) {
case GAME_TYPE_NORMAL:
/* roll over score, change and display level */
while(bj->score >= LEVEL_PTS) {
bj->score -= LEVEL_PTS;
bj->level++;
rb->splash(HZ*2, "Level %d", bj->level);
jewels_drawboard(bj);
}
/* randomly clear some jewels */
for(i=0; i<16; i++) {
x = rb->rand()%8;
y = rb->rand()%8;
if(bj->playboard[y][x].type != 0) {
points++;
bj->playboard[y][x].type = 0;
}
}
break;
case GAME_TYPE_PUZZLE:
bj->level++;
if(bj->level>NUM_PUZZLE_LEVELS) {
rb->splash(HZ*2, "You win!");
bj->level = 1;
} else {
rb->splash(HZ*2, "Level %d", bj->level);
}
break;
}
return jewels_initlevel(bj);
}
/*****************************************************************************
* jewels_recordscore() inserts a high score into the high scores list and
* returns the high score position.
******************************************************************************/
static int jewels_recordscore(struct game_context* bj) {
int i;
int position = 0;
unsigned int current, temp;
/* calculate total score */
current = (bj->level-1)*LEVEL_PTS+bj->score;
if(current <= 0) return 0;
/* insert the current score into the high scores */
for(i=0; i<NUM_SCORES; i++) {
if(current >= bj->highscores[i]) {
if(!position) {
position = i+1;
bj->dirty = true;
}
temp = bj->highscores[i];
bj->highscores[i] = current;
current = temp;
}
}
return position;
}
/*****************************************************************************
* jewels_loadscores() loads the high scores saved file.
******************************************************************************/
static void jewels_loadscores(struct game_context* bj) {
int fd;
bj->dirty = false;
/* clear high scores */
rb->memset(bj->highscores, 0, sizeof(bj->highscores));
/* open scores file */
fd = rb->open(SCORE_FILE, O_RDONLY);
if(fd < 0) return;
/* read in high scores */
if(rb->read(fd, bj->highscores, sizeof(bj->highscores)) <= 0) {
/* scores are bad, reset */
rb->memset(bj->highscores, 0, sizeof(bj->highscores));
}
rb->close(fd);
}
/*****************************************************************************
* jewels_savescores() saves the high scores saved file.
******************************************************************************/
static void jewels_savescores(struct game_context* bj) {
int fd;
/* write out the high scores to the save file */
fd = rb->open(SCORE_FILE, O_WRONLY|O_CREAT);
rb->write(fd, bj->highscores, sizeof(bj->highscores));
rb->close(fd);
bj->dirty = false;
}
/*****************************************************************************
* jewels_loadgame() loads the saved game and returns load success.
******************************************************************************/
static bool jewels_loadgame(struct game_context* bj) {
int fd;
bool loaded = false;
/* open game file */
fd = rb->open(SAVE_FILE, O_RDONLY);
if(fd < 0) return loaded;
/* read in saved game */
while(true) {
if(rb->read(fd, &bj->score, sizeof(bj->score)) <= 0) break;
if(rb->read(fd, &bj->level, sizeof(bj->level)) <= 0) break;
if(rb->read(fd, &bj->type, sizeof(bj->type)) <= 0) break;
if(rb->read(fd, bj->playboard, sizeof(bj->playboard)) <= 0) break;
bj->resume = true;
loaded = true;
break;
}
rb->close(fd);
/* delete saved file */
rb->remove(SAVE_FILE);
return loaded;
}
/*****************************************************************************
* jewels_savegame() saves the current game state.
******************************************************************************/
static void jewels_savegame(struct game_context* bj) {
int fd;
/* write out the game state to the save file */
fd = rb->open(SAVE_FILE, O_WRONLY|O_CREAT);
rb->write(fd, &bj->score, sizeof(bj->score));
rb->write(fd, &bj->level, sizeof(bj->level));
rb->write(fd, &bj->type, sizeof(bj->type));
rb->write(fd, bj->playboard, sizeof(bj->playboard));
rb->close(fd);
bj->resume = true;
}
/*****************************************************************************
* jewels_callback() is the default event handler callback which is called
* on usb connect and shutdown.
******************************************************************************/
static void jewels_callback(void* param) {
struct game_context* bj = (struct game_context*) param;
if(bj->dirty) {
rb->splash(HZ, "Saving high scores...");
jewels_savescores(bj);
}
}
/*****************************************************************************
* jewels_main() is the main game subroutine, it returns the final game status.
******************************************************************************/
static int jewels_main(struct game_context* bj) {
int i, j;
int w, h;
int button;
char str[18];
bool startgame = false;
bool inmenu = false;
bool selected = false;
enum menu_cmd cmd = MCMD_NONE;
enum menu_result res;
/* the cursor coordinates */
int x=0, y=0;
/* don't resume by default */
bj->resume = false;
/********************
* menu *
********************/
rb->lcd_clear_display();
while(!startgame) {
res = jewels_showmenu(&bjmenu[0], cmd);
cmd = MCMD_NONE;
rb->snprintf(str, 18, "High Score: %d", bj->highscores[0]);
rb->lcd_getstringsize(str, &w, &h);
rb->lcd_putsxy((LCD_WIDTH-w)/2, LCD_HEIGHT-8, str);
rb->lcd_update();
switch(res) {
case MRES_NEW:
startgame = true;
bj->type = GAME_TYPE_NORMAL;
continue;
case MRES_PUZZLE:
startgame = true;
bj->type = GAME_TYPE_PUZZLE;
continue;
case MRES_RESUME:
if(!jewels_loadgame(bj)) {
rb->splash(HZ*2, "Nothing to resume");
rb->lcd_clear_display();
} else {
startgame = true;
}
continue;
case MRES_SCORES:
rb->lcd_clear_display();
/* room for a title? */
j = 0;
if(LCD_HEIGHT-NUM_SCORES*8 >= 8) {
rb->snprintf(str, 12, "%s", "High Scores");
rb->lcd_getstringsize(str, &w, &h);
rb->lcd_putsxy((LCD_WIDTH-w)/2, 0, str);
j = 2;
}
/* print high scores */
for(i=0; i<NUM_SCORES; i++) {
rb->snprintf(str, 11, "#%02d: %d", i+1, bj->highscores[i]);
rb->lcd_puts(0, i+j, str);
}
rb->lcd_update();
while(true) {
button = rb->button_get(true);
if(button != BUTTON_NONE && !(button&BUTTON_REL)) break;
}
rb->lcd_clear_display();
continue;
case MRES_HELP:
/* welcome screen to display key bindings */
rb->lcd_clear_display();
rb->snprintf(str, 5, "%s", "Help");
rb->lcd_getstringsize(str, &w, &h);
rb->lcd_putsxy((LCD_WIDTH-w)/2, 0, str);
#if CONFIG_KEYPAD == RECORDER_PAD
rb->lcd_puts(0, 2, "Controls:");
rb->lcd_puts(0, 3, "Directions = move");
rb->lcd_puts(0, 4, "PLAY = select");
rb->lcd_puts(0, 5, "Long PLAY = menu");
rb->lcd_puts(0, 6, "OFF = cancel");
#elif CONFIG_KEYPAD == ONDIO_PAD
rb->lcd_puts(0, 2, "Controls:");
rb->lcd_puts(0, 3, "Directions = move");
rb->lcd_puts(0, 4, "MENU = select");
rb->lcd_puts(0, 5, "Long MENU = menu");
rb->lcd_puts(0, 6, "OFF = cancel");
#elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
rb->lcd_puts(0, 2, "Controls:");
rb->lcd_puts(0, 3, "Directions = move");
rb->lcd_puts(0, 4, "SELECT = select");
rb->lcd_puts(0, 5, "Long SELECT = menu");
rb->lcd_puts(0, 6, "PLAY = cancel");
#elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || (CONFIG_KEYPAD == IRIVER_H300_PAD)
rb->lcd_puts(0, 2, "Swap pairs of jewels to");
rb->lcd_puts(0, 3, "form connected segments");
rb->lcd_puts(0, 4, "of three or more of the");
rb->lcd_puts(0, 5, "same type.");
rb->lcd_puts(0, 7, "Controls:");
rb->lcd_puts(0, 8, "Directions to move");
rb->lcd_puts(0, 9, "SELECT to select");
rb->lcd_puts(0, 10, "Long SELECT to show menu");
rb->lcd_puts(0, 11, "OFF to cancel");
#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || \
(CONFIG_KEYPAD == IPOD_1G2G_PAD)
rb->lcd_puts(0, 2, "Swap pairs of jewels to");
rb->lcd_puts(0, 3, "form connected segments");
rb->lcd_puts(0, 4, "of three or more of the");
rb->lcd_puts(0, 5, "same type.");
rb->lcd_puts(0, 7, "Controls:");
rb->lcd_puts(0, 8, "Directions or scroll to move");
rb->lcd_puts(0, 9, "SELECT to select");
rb->lcd_puts(0, 10, "Long SELECT to show menu");
#elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
rb->lcd_puts(0, 2, "Swap pairs of jewels to");
rb->lcd_puts(0, 3, "form connected segments");
rb->lcd_puts(0, 4, "of three or more of the");
rb->lcd_puts(0, 5, "same type.");
rb->lcd_puts(0, 7, "Controls:");
rb->lcd_puts(0, 8, "Directions to move");
rb->lcd_puts(0, 9, "SELECT to select");
rb->lcd_puts(0, 10, "Long SELECT to show menu");
rb->lcd_puts(0, 11, "PLAY to cancel");
#elif CONFIG_KEYPAD == GIGABEAT_PAD
rb->lcd_puts(0, 2, "Swap pairs of jewels to");
rb->lcd_puts(0, 3, "form connected segments");
rb->lcd_puts(0, 4, "of three or more of the");
rb->lcd_puts(0, 5, "same type.");
rb->lcd_puts(0, 7, "Controls:");
rb->lcd_puts(0, 8, "Directions to move");
rb->lcd_puts(0, 9, "SELECT to select");
rb->lcd_puts(0, 10, "Long SELECT to show menu");
rb->lcd_puts(0, 11, "POWER to cancel");
#elif CONFIG_KEYPAD == SANSA_E200_PAD \
|| CONFIG_KEYPAD == SANSA_C200_PAD
rb->lcd_puts(0, 2, "Swap pairs of jewels to");
rb->lcd_puts(0, 3, "form connected segments");
rb->lcd_puts(0, 4, "of three or more of the");
rb->lcd_puts(0, 5, "same type.");
rb->lcd_puts(0, 7, "Controls:");
rb->lcd_puts(0, 8, "Directions to move");
rb->lcd_puts(0, 9, "SELECT to select");
rb->lcd_puts(0, 10, "Long SELECT to show menu");
rb->lcd_puts(0, 11, "POWER to cancel");
#elif CONFIG_KEYPAD == IRIVER_H10_PAD
rb->lcd_puts(0, 2, "Swap pairs of jewels");
rb->lcd_puts(0, 3, "to form connected");
rb->lcd_puts(0, 4, "segments of three or ");
rb->lcd_puts(0, 5, "more of the");
rb->lcd_puts(0, 6, "same type.");
rb->lcd_puts(0, 8, "Controls:");
rb->lcd_puts(0, 9, "Directions or scroll to move");
rb->lcd_puts(0, 10, "PLAY to select");
rb->lcd_puts(0, 11, "Long PLAY for menu");
rb->lcd_puts(0, 12, "POWER to cancel");
#else
#warning: missing help text.
#endif
rb->lcd_update();
while(true) {
button = rb->button_get(true);
if(button != BUTTON_NONE && !(button&BUTTON_REL)) break;
}
rb->lcd_clear_display();
continue;
case MRES_QUIT:
return BJ_QUIT;
default:
break;
}
/* handle menu button presses */
button = rb->button_get(true);
switch(button){
#ifdef JEWELS_SCROLLWHEEL
case JEWELS_PREV:
case (JEWELS_PREV|BUTTON_REPEAT):
#endif
case JEWELS_UP:
case (JEWELS_UP|BUTTON_REPEAT):
cmd = MCMD_PREV;
break;
#ifdef JEWELS_SCROLLWHEEL
case JEWELS_NEXT:
case (JEWELS_NEXT|BUTTON_REPEAT):
#endif
case JEWELS_DOWN:
case (JEWELS_DOWN|BUTTON_REPEAT):
cmd = MCMD_NEXT;
break;
case JEWELS_SELECT:
case JEWELS_RIGHT:
cmd = MCMD_SELECT;
break;
#ifdef JEWELS_CANCEL
#ifdef JEWELS_RC_CANCEL
case JEWELS_RC_CANCEL:
#endif
case JEWELS_CANCEL:
return BJ_QUIT;
#endif
default:
if(rb->default_event_handler_ex(button, jewels_callback,
(void*) bj) == SYS_USB_CONNECTED)
return BJ_USB;
break;
}
}
/********************
* init *
********************/
jewels_init(bj);
/********************
* setup the board *
********************/
bj->score += jewels_initlevel(bj);
if (!jewels_movesavail(bj)) {
switch(bj->type) {
case GAME_TYPE_NORMAL:
return BJ_LOSE;
case GAME_TYPE_PUZZLE:
do {
rb->splash(2*HZ, "No more moves!");
bj->score += jewels_initlevel(bj);
} while(!jewels_movesavail(bj));
break;
}
}
/**********************
* play *
**********************/
while(true) {
int no_movesavail = false;
if(!inmenu) {
/* refresh the board */
jewels_drawboard(bj);
/* display the cursor */
if(selected) {
rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
rb->lcd_fillrect(x*TILE_WIDTH, y*TILE_HEIGHT+YOFS,
TILE_WIDTH, TILE_HEIGHT);
rb->lcd_set_drawmode(DRMODE_SOLID);
} else {
rb->lcd_drawrect(x*TILE_WIDTH, y*TILE_HEIGHT+YOFS,
TILE_WIDTH, TILE_HEIGHT);
}
rb->lcd_update_rect(x*TILE_WIDTH, y*TILE_HEIGHT+YOFS,
TILE_WIDTH, TILE_HEIGHT);
} else {
res = jewels_showmenu(&bjmenu[1], cmd);
cmd = MCMD_NONE;
switch(res) {
case MRES_RESUME:
inmenu = false;
selected = false;
continue;
case MRES_PLAYBACK:
playback_control(rb);
rb->lcd_setfont(FONT_SYSFIXED);
inmenu = false;
selected = false;
break;
case MRES_SAVE:
rb->splash(HZ, "Saving game...");
jewels_savegame(bj);
return BJ_END;
case MRES_QUIT:
return BJ_END;
case MRES_EXIT:
return BJ_QUIT_FROM_GAME;
default:
break;
}
}
/* handle game button presses */
button = rb->button_get(true);
switch(button){
case JEWELS_LEFT: /* move cursor left */
case (JEWELS_LEFT|BUTTON_REPEAT):
if(!inmenu) {
if(selected) {
bj->score += jewels_swapjewels(bj, x, y, SWAP_LEFT);
selected = false;
if (!jewels_movesavail(bj)) no_movesavail = true;
} else {
x = (x+BJ_WIDTH-1)%BJ_WIDTH;
}
}
break;
case JEWELS_RIGHT: /* move cursor right */
case (JEWELS_RIGHT|BUTTON_REPEAT):
if(!inmenu) {
if(selected) {
bj->score += jewels_swapjewels(bj, x, y, SWAP_RIGHT);
selected = false;
if (!jewels_movesavail(bj)) no_movesavail = true;
} else {
x = (x+1)%BJ_WIDTH;
}
} else {
cmd = MCMD_SELECT;
}
break;
case JEWELS_DOWN: /* move cursor down */
case (JEWELS_DOWN|BUTTON_REPEAT):
if(!inmenu) {
if(selected) {
bj->score += jewels_swapjewels(bj, x, y, SWAP_DOWN);
selected = false;
if (!jewels_movesavail(bj)) no_movesavail = true;
} else {
y = (y+1)%(BJ_HEIGHT-1);
}
} else {
cmd = MCMD_NEXT;
}
break;
case JEWELS_UP: /* move cursor up */
case (JEWELS_UP|BUTTON_REPEAT):
if(!inmenu) {
if(selected) {
bj->score += jewels_swapjewels(bj, x, y, SWAP_UP);
selected = false;
if (!jewels_movesavail(bj)) no_movesavail = true;
} else {
y = (y+(BJ_HEIGHT-1)-1)%(BJ_HEIGHT-1);
}
} else {
cmd = MCMD_PREV;
}
break;
#ifdef JEWELS_SCROLLWHEEL
case JEWELS_PREV: /* scroll backwards */
case (JEWELS_PREV|BUTTON_REPEAT):
if(!inmenu) {
if(!selected) {
if(x == 0) {
y = (y+(BJ_HEIGHT-1)-1)%(BJ_HEIGHT-1);
}
x = (x+BJ_WIDTH-1)%BJ_WIDTH;
}
} else {
cmd = MCMD_PREV;
}
break;
case JEWELS_NEXT: /* scroll forwards */
case (JEWELS_NEXT|BUTTON_REPEAT):
if(!inmenu) {
if(!selected) {
if(x == BJ_WIDTH-1) {
y = (y+1)%(BJ_HEIGHT-1);
}
x = (x+1)%BJ_WIDTH;
}
} else {
cmd = MCMD_NEXT;
}
break;
#endif
case JEWELS_SELECT: /* toggle selected */
if(!inmenu) {
selected = !selected;
} else {
cmd = MCMD_SELECT;
}
break;
case (JEWELS_SELECT|BUTTON_REPEAT): /* show menu */
if(!inmenu) inmenu = true;
break;
#ifdef JEWELS_CANCEL
#ifdef JEWELS_RC_CANCEL
case JEWELS_RC_CANCEL:
#endif
case JEWELS_CANCEL: /* end game */
return BJ_END;
break;
#endif
default:
if(rb->default_event_handler_ex(button, jewels_callback,
(void*) bj) == SYS_USB_CONNECTED)
return BJ_USB;
break;
}
if (no_movesavail) {
switch(bj->type) {
case GAME_TYPE_NORMAL:
return BJ_LOSE;
case GAME_TYPE_PUZZLE:
do {
rb->splash(2*HZ, "No more moves!");
bj->score += jewels_initlevel(bj);
} while(!jewels_movesavail(bj));
break;
}
}
switch(bj->type) {
case GAME_TYPE_NORMAL:
if(bj->score >= LEVEL_PTS) bj->score = jewels_nextlevel(bj);
break;
case GAME_TYPE_PUZZLE:
if(jewels_puzzle_is_finished(bj))
bj->score += jewels_nextlevel(bj);
break;
}
}
}
/*****************************************************************************
* plugin entry point.
******************************************************************************/
enum plugin_status plugin_start(struct plugin_api* api, void* parameter) {
struct game_context bj;
bool exit = false;
int position;
char str[19];
/* plugin init */
(void)parameter;
rb = api;
/* end of plugin init */
/* load high scores */
jewels_loadscores(&bj);
rb->lcd_setfont(FONT_SYSFIXED);
#if LCD_DEPTH > 1
rb->lcd_set_backdrop(NULL);
#endif
jewels_setcolors();
while(!exit) {
switch(jewels_main(&bj)){
case BJ_LOSE:
rb->splash(HZ*2, "No more moves!");
/* fall through to BJ_END */
case BJ_END:
if(!bj.resume) {
if((position = jewels_recordscore(&bj))) {
rb->snprintf(str, 19, "New high score #%d!", position);
rb->splash(HZ*2, str);
}
}
break;
case BJ_USB:
rb->lcd_setfont(FONT_UI);
return PLUGIN_USB_CONNECTED;
case BJ_QUIT:
if(bj.dirty) {
rb->splash(HZ, "Saving high scores...");
jewels_savescores(&bj);
}
exit = true;
break;
case BJ_QUIT_FROM_GAME:
if(!bj.resume) {
if((position = jewels_recordscore(&bj))) {
rb->snprintf(str, 19, "New high score #%d!", position);
rb->splash(HZ*2, str);
}
}
if(bj.dirty) {
rb->splash(HZ, "Saving high scores...");
jewels_savescores(&bj);
}
exit = true;
break;
default:
break;
}
}
rb->lcd_setfont(FONT_UI);
return PLUGIN_OK;
}
#endif /* HAVE_LCD_BITMAP */