diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES index 9a2b490211..413e5217c3 100644 --- a/apps/plugins/SOURCES +++ b/apps/plugins/SOURCES @@ -36,6 +36,7 @@ flipit.c #ifdef HAVE_LCD_BITMAP /* Not for the Player */ disktidy.c +mazezam.c text_editor.c /* Plugins needing the grayscale lib on low-depth LCDs */ diff --git a/apps/plugins/mazezam.c b/apps/plugins/mazezam.c new file mode 100644 index 0000000000..2a8afc37ab --- /dev/null +++ b/apps/plugins/mazezam.c @@ -0,0 +1,1127 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * ### line of auto-generated stuff I don't understand ### + * + * Copyright (C) 2006 Malcolm Tyrrell + * + * MazezaM - a Rockbox version of my ZX Spectrum game from 2002 + * + * 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 "configfile.h" + +/* Include standard plugin macro */ +PLUGIN_HEADER + +static struct plugin_api* rb; + +#if CONFIG_KEYPAD == RECORDER_PAD +#define MAZEZAM_UP BUTTON_UP +#define MAZEZAM_DOWN BUTTON_DOWN +#define MAZEZAM_LEFT BUTTON_LEFT +#define MAZEZAM_RIGHT BUTTON_RIGHT +#define MAZEZAM_SELECT BUTTON_PLAY + +#define MAZEZAM_RETRY BUTTON_F1 +#define MAZEZAM_RETRY_KEYNAME "[F1]" +#define MAZEZAM_QUIT BUTTON_OFF +#define MAZEZAM_QUIT_KEYNAME "[OFF]" + +#elif CONFIG_KEYPAD == ONDIO_PAD +#define MAZEZAM_UP BUTTON_UP +#define MAZEZAM_DOWN BUTTON_DOWN +#define MAZEZAM_LEFT BUTTON_LEFT +#define MAZEZAM_RIGHT BUTTON_RIGHT +#define MAZEZAM_SELECT BUTTON_RIGHT + +#define MAZEZAM_RETRY BUTTON_MENU +#define MAZEZAM_RETRY_KEYNAME "[MENU]" +#define MAZEZAM_QUIT BUTTON_OFF +#define MAZEZAM_QUIT_KEYNAME "[OFF]" + +#elif (CONFIG_KEYPAD == IAUDIO_X5_PAD) +#define MAZEZAM_UP BUTTON_UP +#define MAZEZAM_DOWN BUTTON_DOWN +#define MAZEZAM_LEFT BUTTON_LEFT +#define MAZEZAM_RIGHT BUTTON_RIGHT +#define MAZEZAM_SELECT BUTTON_SELECT + +#define MAZEZAM_RETRY BUTTON_REC +#define MAZEZAM_RETRY_KEYNAME "[REC]" +#define MAZEZAM_QUIT BUTTON_POWER +#define MAZEZAM_QUIT_KEYNAME "[POWER]" + +#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ + (CONFIG_KEYPAD == IPOD_3G_PAD) +#define MAZEZAM_UP BUTTON_MENU +#define MAZEZAM_DOWN BUTTON_PLAY +#define MAZEZAM_LEFT BUTTON_LEFT +#define MAZEZAM_RIGHT BUTTON_RIGHT +#define MAZEZAM_SELECT BUTTON_SELECT + +#define MAZEZAM_RETRY BUTTON_SELECT +#define MAZEZAM_RETRY_KEYNAME "[SELECT]" +#define MAZEZAM_QUIT (BUTTON_SELECT | BUTTON_REPEAT) +#define MAZEZAM_QUIT_KEYNAME "[SELECT] (held)" + +#elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \ + (CONFIG_KEYPAD == IRIVER_H300_PAD) +#define MAZEZAM_UP BUTTON_UP +#define MAZEZAM_DOWN BUTTON_DOWN +#define MAZEZAM_LEFT BUTTON_LEFT +#define MAZEZAM_RIGHT BUTTON_RIGHT +#define MAZEZAM_SELECT BUTTON_SELECT + +#define MAZEZAM_RETRY BUTTON_ON +#define MAZEZAM_RETRY_KEYNAME "[ON]" +#define MAZEZAM_QUIT BUTTON_OFF +#define MAZEZAM_QUIT_KEYNAME "[OFF]" + +#elif (CONFIG_KEYPAD == GIGABEAT_PAD) +#define MAZEZAM_UP BUTTON_UP +#define MAZEZAM_DOWN BUTTON_DOWN +#define MAZEZAM_LEFT BUTTON_LEFT +#define MAZEZAM_RIGHT BUTTON_RIGHT +#define MAZEZAM_SELECT BUTTON_SELECT + +#define MAZEZAM_RETRY BUTTON_MENU +#define MAZEZAM_RETRY_KEYNAME "[MENU]" +#define MAZEZAM_QUIT BUTTON_A +#define MAZEZAM_QUIT_KEYNAME "[A]" + +#elif (CONFIG_KEYPAD == SANSA_E200_PAD) +#define MAZEZAM_UP BUTTON_UP +#define MAZEZAM_DOWN BUTTON_DOWN +#define MAZEZAM_LEFT BUTTON_LEFT +#define MAZEZAM_RIGHT BUTTON_RIGHT +#define MAZEZAM_SELECT BUTTON_SELECT + +#define MAZEZAM_RETRY BUTTON_REC +#define MAZEZAM_RETRY_KEYNAME "[REC]" +#define MAZEZAM_QUIT BUTTON_POWER +#define MAZEZAM_QUIT_KEYNAME "[POWER]" + +#elif (CONFIG_KEYPAD == IRIVER_H10_PAD) +#define MAZEZAM_UP BUTTON_SCROLL_UP +#define MAZEZAM_DOWN BUTTON_SCROLL_DOWN +#define MAZEZAM_LEFT BUTTON_LEFT +#define MAZEZAM_RIGHT BUTTON_RIGHT +#define MAZEZAM_SELECT BUTTON_PLAY + +#define MAZEZAM_RETRY BUTTON_PLAY +#define MAZEZAM_RETRY_KEYNAME "[PLAY]" +#define MAZEZAM_QUIT BUTTON_POWER +#define MAZEZAM_QUIT_KEYNAME "[POWER]" + +#endif + +/* The gap for the border around the heading in text pages. In fact, 2 is + * really the only acceptable value. + */ +#define MAZEZAM_MENU_BORDER 2 +#define MAZEZAM_EXTRA_LIFE 2 /* get an extra life every _ levels */ +#define MAZEZAM_START_LIVES 3 /* how many lives at game start */ + +#ifdef HAVE_LCD_COLOR +#define MAZEZAM_HEADING_COLOR LCD_RGBPACK(255,255, 0) /* Yellow */ +#define MAZEZAM_BORDER_COLOR LCD_RGBPACK( 0, 0,255) /* Blue */ +#define MAZEZAM_TEXT_COLOR LCD_RGBPACK(255,255,255) /* White */ +#define MAZEZAM_BG_COLOR LCD_RGBPACK( 0, 0, 0) /* Black */ +#define MAZEZAM_WALL_COLOR LCD_RGBPACK(100,100,100) /* Dark gray */ +#define MAZEZAM_PLAYER_COLOR LCD_RGBPACK(255,255,255) /* White */ +#define MAZEZAM_GATE_COLOR LCD_RGBPACK(100,100,100) /* Dark gray */ + +/* the rows are coloured sequentially */ +#define MAZEZAM_NUM_CHUNK_COLORS 8 +static const unsigned chunk_colors[MAZEZAM_NUM_CHUNK_COLORS] = { + LCD_RGBPACK(255,192, 32), /* Orange */ + LCD_RGBPACK(255, 0, 0), /* Red */ + LCD_RGBPACK( 0,255, 0), /* Green */ + LCD_RGBPACK( 0,255,255), /* Cyan */ + LCD_RGBPACK(255,175,175), /* Pink */ + LCD_RGBPACK(255,255, 0), /* Yellow */ + LCD_RGBPACK( 0, 0,255), /* Blue */ + LCD_RGBPACK(255, 0,255), /* Magenta */ +}; + +#elif LCD_DEPTH > 1 + +#define MAZEZAM_HEADING_GRAY LCD_BLACK +#define MAZEZAM_BORDER_GRAY LCD_DARKGRAY +#define MAZEZAM_TEXT_GRAY LCD_BLACK +#define MAZEZAM_BG_GRAY LCD_WHITE +#define MAZEZAM_WALL_GRAY LCD_DARKGRAY +#define MAZEZAM_PLAYER_GRAY LCD_BLACK +#define MAZEZAM_GATE_GRAY LCD_BLACK +#define MAZEZAM_CHUNK_EDGE_GRAY LCD_BLACK + +#define MAZEZAM_NUM_CHUNK_GRAYS 2 +static const unsigned chunk_gray[MAZEZAM_NUM_CHUNK_GRAYS] = { + LCD_LIGHTGRAY, + LCD_DARKGRAY, +}; +/* darker version of the above */ +static const unsigned chunk_gray_shade[MAZEZAM_NUM_CHUNK_GRAYS] = { + LCD_DARKGRAY, + LCD_BLACK, +}; +#endif + +#define MAZEZAM_GAMEOVER_TEXT "Game Over" +#define MAZEZAM_GAMEOVER_DELAY (3 * HZ) / 2 +#define MAZEZAM_LEVEL_LIVES_TEXT "Level %d, Lives %d" +#define MAZEZAM_LEVEL_LIVES_DELAY HZ +#define MAZEZAM_WELLDONE_DELAY 4 * HZ + +/* The maximum number of lines that a text page can display. + * This must be 4 or less if the Archos recorder is to be + * supported. + */ +#define MAZEZAM_TEXT_MAXLINES 4 + +/* A structure for holding text pages */ +struct textpage { + /* Ensure 1 < num_lines <= MAZEZAM_TEXT_MAXLINES */ + short num_lines; + char *line[MAZEZAM_TEXT_MAXLINES]; /* text of lines */ +}; + +/* The text page for the welcome screen */ +static const struct textpage title_page = { + 4, + {"MazezaM", "play game", "instructions", "quit"} +}; + +/* The number of help screens */ +#define MAZEZAM_NUM_HELP_PAGES 4 + +/* The instruction screens */ +static const struct textpage help_page[] = { + {4,{"Instructions","10 mazezams","bar your way","to freedom"}}, + {4,{"Instructions","Push the rows","left and right","to escape"}}, + {4,{"Instructions","Press " MAZEZAM_RETRY_KEYNAME " to","retry a level","(lose 1 life)"}}, + {4,{"Instructions","Press " MAZEZAM_QUIT_KEYNAME,"to quit","the game"}} +}; + +/* the text of the screen that asks for a quit confirmation */ +static const struct textpage confirm_page = { + 4, + {"Quit","Are you sure?","yes","no"} +}; + +/* the text of the screen at the end of the game */ +static const struct textpage welldone_page = { + 3, + {"Well Done","You have","escaped",""} +}; + +/* the text of the screen asking if the user wants to + * resume or start a new game. + */ +static const struct textpage resume_page = { + 3, + {"Checkpoint", "continue", "new game"} +}; + +/* maximum height of a level */ +#define MAZEZAM_MAX_LINES 11 +/* maximum number of chunks on a line */ +#define MAZEZAM_MAX_CHUNKS 5 + +/* A structure for holding levels */ +struct mazezam_level { + short height; /* the number of lines */ + short width; /* the width */ + short entrance; /* the line on which the entrance lies */ + short exit; /* the line on which the exit lies */ + char *line[MAZEZAM_MAX_LINES]; /* the chunk data in string form */ +}; + +/* The number of levels. Note that the instruction screens reference this + * number + */ +#define MAZEZAM_NUM_LEVELS 10 + +/* The levels. In theory, they could be stored in a file so this data + * structure should not be accessed outside parse_level() + * + * These levels are copyright (C) 2002 Malcolm Tyrrell. They're + * probably covered by the GPL as they constitute part of the source + * code of this plugin, but you may distibute them seperately with + * other Free Software if you want. You can download them from: + * http://webpages.dcu.ie/~tyrrelma/MazezaM. + */ +static const struct mazezam_level level_data[MAZEZAM_NUM_LEVELS] = { + {2,7,0,0,{" $ $"," $ $$",NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + NULL}}, + {3,8,2,1,{" $ $$$"," $ $ $"," $ $ $",NULL,NULL,NULL,NULL,NULL,NULL, + NULL,NULL}}, + {4,14,1,3,{" $$$$$ $$ $$"," $$ $$ $$","$$ $ $$ $$$", + " $$$$$$$$ $",NULL,NULL,NULL,NULL,NULL,NULL,NULL}}, + {6,7,4,2,{" $"," $$$$"," $$$ $$"," $ $ $"," $ $$","$ $$", + NULL,NULL,NULL,NULL,NULL}}, + {6,13,0,0,{" $$$$$","$ $$$$$ $$$"," $ $$$ $$$$", + "$ $ $$$$$$$"," $$$ $ $$","$ $ $ $$ $",NULL,NULL, + NULL,NULL,NULL}}, + {11,5,10,0,{" $"," $ $$"," $$","$ $"," $ $"," $$$","$ $", + " $ $"," $ $","$ $$"," $"}}, + {7,16,0,6,{" $$$$$$$"," $$$$ $$$$ $ $","$$ $$ $$$$$$ $ $", + "$ $ $"," $$$$$$$$$$$$$$"," $ $$ $ $$$", + " $ $$$ $$",NULL,NULL,NULL,NULL}}, + {4,15,2,0,{" $$$$ $$$$ $$"," $ $$ $$ $ $$"," $ $$ $$$$ $$", + " $ $$ $$$$ $",NULL,NULL,NULL,NULL,NULL,NULL,NULL}}, + {7,9,6,2,{" $ $$$$"," $ $ $$"," $ $$$$ $","$ $$ $"," $ $$$", + " $$$$$$"," $",NULL,NULL,NULL,NULL}}, + {10,14,8,0,{" $"," $$$$$$$$$$ $"," $$$ $$", + " $ $$$$$$$$ $"," $$$ $$$ $$$"," $$$ $ $$$", + " $ $$$$$$$ $$"," $ $ $ $$$"," $$$$$$$$$$$$", + "",NULL}} +}; + +/* This is the data structure the game uses for managing levels */ +struct chunk_data { + /* the number of chunks on a line */ + short l_num[MAZEZAM_MAX_LINES]; + /* the width of a chunk */ + short c_width[MAZEZAM_MAX_LINES][MAZEZAM_MAX_CHUNKS]; + /* the inset of a chunk */ + short c_inset[MAZEZAM_MAX_LINES][MAZEZAM_MAX_CHUNKS]; +}; + +/* The state and exit code of the level loop */ +enum level_state { + LEVEL_STATE_LOOPING, + LEVEL_STATE_COMPLETED, + LEVEL_STATE_FAILED, + LEVEL_STATE_QUIT, + LEVEL_STATE_PARSE_ERROR, + LEVEL_STATE_USB_CONNECTED, +}; + +/* The state and exit code of the text screens. I use the + * same enum for all of them, even though there are some + * differences. + */ +enum text_state { + TEXT_STATE_LOOPING, + TEXT_STATE_QUIT, + TEXT_STATE_OKAY, + TEXT_STATE_USB_CONNECTED, + TEXT_STATE_PARSE_ERROR, + TEXT_STATE_BACK, +}; + +/* The state and exit code of the game loop */ +enum game_state { + GAME_STATE_LOOPING, + GAME_STATE_QUIT, + GAME_STATE_OKAY, + GAME_STATE_USB_CONNECTED, + GAME_STATE_OVER, + GAME_STATE_COMPLETED, + GAME_STATE_PARSE_ERROR, +}; + +/* The various constants needed for configuration files. + * See apps/plugins/lib/configfile.* + */ +#define MAZEZAM_CONFIG_FILENAME "mazezam.data" +#define MAZEZAM_CONFIG_NUM_ITEMS 1 +#define MAZEZAM_CONFIG_VERSION 0 +#define MAZEZAM_CONFIG_MINVERSION 0 +#define MAZEZAM_CONFIG_LEVELS_NAME "restart_level" + +/* A structure containing the data that is written to + * the configuration file + */ +struct resume_data { + int level; /* level at which to restart the game */ +}; + +/* Display a screen of text. line[0] is the heading. + * line[highlight] will be highlighted, unless highlight == 0 + */ +static void display_text_page(struct textpage text, int highlight) { + int w[text.num_lines], h[text.num_lines]; + int hsum,i,vgap,vnext; + + rb->lcd_clear_display(); + + /* find out how big the text is so we can determine the positioning */ + hsum = 0; + for(i = 0; i < text.num_lines; i++) { + rb->lcd_getstringsize(text.line[i], w+i, h+i); + hsum += h[i]; + } + + vgap = (LCD_HEIGHT-hsum)/(text.num_lines+1); + + /* The Heading */ + +#ifdef HAVE_LCD_COLOR + rb->lcd_set_foreground(MAZEZAM_BORDER_COLOR); +#elif LCD_DEPTH > 1 + rb->lcd_set_foreground(MAZEZAM_BORDER_GRAY); +#endif + rb->lcd_drawrect((LCD_WIDTH-w[0])/2-MAZEZAM_MENU_BORDER, + vgap-MAZEZAM_MENU_BORDER, w[0] + 2*MAZEZAM_MENU_BORDER, + h[0] + 2*MAZEZAM_MENU_BORDER); + rb->lcd_drawrect((LCD_WIDTH-w[0])/2-MAZEZAM_MENU_BORDER*2, + vgap-MAZEZAM_MENU_BORDER*2, w[0] + 4*MAZEZAM_MENU_BORDER, + h[0] + 4*MAZEZAM_MENU_BORDER); + rb->lcd_drawline(0,vgap + h[0]/2,(LCD_WIDTH-w[0])/2-MAZEZAM_MENU_BORDER*2,vgap + h[0]/2); + rb->lcd_drawline((LCD_WIDTH-w[0])/2+w[0]+MAZEZAM_MENU_BORDER*2,vgap + h[0]/2,LCD_WIDTH-1,vgap + h[0]/2); +#ifdef HAVE_LCD_COLOR + rb->lcd_set_foreground(MAZEZAM_HEADING_COLOR); +#elif LCD_DEPTH > 1 + rb->lcd_set_foreground(MAZEZAM_HEADING_GRAY); +#endif + rb->lcd_putsxy((LCD_WIDTH-w[0])/2,vgap,text.line[0]); + + vnext = vgap*2 + h[0]; + + /* The other lines */ + +#ifdef HAVE_LCD_COLOR + rb->lcd_set_foreground(MAZEZAM_TEXT_COLOR); +#elif LCD_DEPTH > 1 + rb->lcd_set_foreground(MAZEZAM_TEXT_GRAY); +#endif + for (i = 1; ilcd_putsxy((LCD_WIDTH-w[i])/2,vnext,text.line[i]); + + /* add underlining if i is the highlighted line */ + if (i == highlight) { + rb->lcd_drawline((LCD_WIDTH-w[i])/2, vnext + h[i] + 1, + (LCD_WIDTH-w[i])/2 + w[i], vnext + h[i] + 1); + } + + vnext += vgap + h[i]; + } + + rb->lcd_update(); +} + + +/* Parse the level data from the level_data structure. This could be + * replaced by a file read. Returns true if the level parsed correctly. + */ +static bool parse_level(short level, struct chunk_data *cd, + short *width, short *height, short *entrance, short *exit) { + int i,j; + char c,clast; + + *width = level_data[level].width; + *height = level_data[level].height; + *entrance = level_data[level].entrance; + *exit = level_data[level].exit; + + /* for each line in the level */ + for (i = 0; il_num[i] = 0; + clast = ' '; /* the character we last considered */ + while ((c = level_data[level].line[i][j]) != '\0') { + if (c != ' ') { + if (clast == ' ') { + cd->l_num[i] += 1; + if (cd->l_num[i] > MAZEZAM_MAX_CHUNKS) + return false; + cd->c_inset[i][cd->l_num[i] - 1] = j; + cd->c_width[i][cd->l_num[i] - 1] = 1; + } + else + cd->c_width[i][cd->l_num[i] - 1] += 1; + } + clast = c; + j++; + } + } + } + return true; +} + +/* Draw the level */ +static void draw_level( + struct chunk_data *cd, /* the data about the chunks */ + short *shift, /* an array of the horizontal offset of the lines */ + short width, + short height, + short entrance, + short exit, + short x, /* player's x and y coords */ + short y) { + /* The number of pixels the side of a square should be */ + short size = (LCD_WIDTH/(width+2)) < (LCD_HEIGHT/height) ? + (LCD_WIDTH/(width+2)) : (LCD_HEIGHT/height); + /* The x and y position (in pixels) of the top left corner of the + * level + */ + short xOff = (LCD_WIDTH - (size*width))/2; + short yOff = (LCD_HEIGHT - (size*height))/2; + /* For drawing the player, taken from the sokoban plugin */ + short max = size - 1; + short middle = max / 2; + short ldelta = (middle + 1) / 2; + short i,j; + short third = size / 3; + short twothirds = (2 * size) / 3; +#ifndef HAVE_LCD_COLOR + /* We #def these out to supress a compiler warning */ + short k; +#if LCD_DEPTH <= 1 + short l; +#endif +#endif + + rb->lcd_clear_display(); + +#ifdef HAVE_LCD_COLOR + rb->lcd_set_foreground(MAZEZAM_WALL_COLOR); +#elif LCD_DEPTH > 1 + rb->lcd_set_foreground(MAZEZAM_WALL_GRAY); +#endif + /* draw the upper wall */ + rb->lcd_fillrect(0,0,xOff,yOff+(size*entrance)); + rb->lcd_fillrect(xOff,0,size*width,yOff); + rb->lcd_fillrect(xOff+(size*width),0,LCD_WIDTH-xOff-(size*width),yOff+(size*exit)); + + /* draw the lower wall */ + rb->lcd_fillrect(0,yOff+(size*entrance)+size,xOff,LCD_HEIGHT-yOff-(size*entrance)-size); + rb->lcd_fillrect(xOff,yOff+(size*height),size*width,LCD_HEIGHT-yOff-(size*height)); + /* Note: the exit is made one pixel thinner than necessary as a visual + * clue that chunks cannot be pushed into it + */ + rb->lcd_fillrect(xOff+(size*width),yOff+(size*exit)+size-1,LCD_WIDTH-xOff+(size*width),LCD_HEIGHT-yOff-(size*exit)-size+1); + + /* draw the chunks */ + for (i = 0; ilcd_set_foreground(chunk_colors[(i+width) % MAZEZAM_NUM_CHUNK_COLORS]); +#endif + for (j = 0; jl_num[i]; j++) { +#ifdef HAVE_LCD_COLOR + rb->lcd_fillrect(xOff+size*shift[i]+size*cd->c_inset[i][j],yOff+size*i, + cd->c_width[i][j]*size,size); +#elif LCD_DEPTH > 1 + rb->lcd_set_foreground(MAZEZAM_CHUNK_EDGE_GRAY); + rb->lcd_drawrect(xOff+size*shift[i]+size*cd->c_inset[i][j],yOff+size*i, + cd->c_width[i][j]*size,size); + + /* draw shade */ + rb->lcd_set_foreground(chunk_gray_shade[(i+width) % MAZEZAM_NUM_CHUNK_GRAYS]); + rb->lcd_drawline(xOff+size*shift[i]+size*cd->c_inset[i][j]+1,yOff+size*i+size-2, + xOff+size*shift[i]+size*cd->c_inset[i][j]+cd->c_width[i][j]*size-3,yOff+size*i+size-2); + rb->lcd_drawline(xOff+size*shift[i]+size*cd->c_inset[i][j]+cd->c_width[i][j]*size-2,yOff+size*i, + xOff+size*shift[i]+size*cd->c_inset[i][j]+cd->c_width[i][j]*size-2,yOff+size*i+size-2); + + /* draw fill */ + rb->lcd_set_foreground(chunk_gray[(i+width) % MAZEZAM_NUM_CHUNK_GRAYS]); + for (k = yOff+size*i+2; k < yOff+size*i+size-2; k += 2) + rb->lcd_drawline(xOff+size*shift[i]+size*cd->c_inset[i][j]+2,k, + xOff+size*shift[i]+size*cd->c_inset[i][j]+cd->c_width[i][j]*size-3,k); +#else + rb->lcd_drawrect(xOff+size*shift[i]+size*cd->c_inset[i][j],yOff+size*i, + cd->c_width[i][j]*size,size); + for (k = xOff+size*shift[i]+size*cd->c_inset[i][j]+2; + k < xOff+size*shift[i]+size*cd->c_inset[i][j]+cd->c_width[i][j]*size; + k += 2 + (i & 1)) + for (l = yOff+size*i+2; l < yOff+size*i+size; l += 2 + (i & 1)) + rb->lcd_drawpixel(k, l); +#endif + } + } + + /* draw the player (mostly copied from the sokoban plugin) */ +#ifdef HAVE_LCD_COLOR + rb->lcd_set_foreground(MAZEZAM_PLAYER_COLOR); +#elif LCD_DEPTH > 1 + rb->lcd_set_foreground(MAZEZAM_PLAYER_GRAY); +#endif + rb->lcd_drawline(xOff+size*x, yOff+size*y+middle, + xOff+size*x+max, yOff+size*y+middle); + rb->lcd_drawline(xOff+size*x+middle, yOff+size*y, + xOff+size*x+middle, yOff+size*y+max-ldelta); + rb->lcd_drawline(xOff+size*x+middle, yOff+size*y+max-ldelta, + xOff+size*x+middle-ldelta, yOff+size*y+max); + rb->lcd_drawline(xOff+size*x+middle, yOff+size*y+max-ldelta, + xOff+size*x+middle+ldelta, yOff+size*y+max); + + /* draw the gate, if the player has moved into the level */ + if (x >= 0) { +#ifdef HAVE_LCD_COLOR + rb->lcd_set_foreground(MAZEZAM_GATE_COLOR); +#elif LCD_DEPTH > 1 + rb->lcd_set_foreground(MAZEZAM_GATE_GRAY); +#endif + rb->lcd_drawline(xOff-size,yOff+entrance*size+third, + xOff-1,yOff+entrance*size+third); + rb->lcd_drawline(xOff-size,yOff+entrance*size+twothirds, + xOff-1,yOff+entrance*size+twothirds); + rb->lcd_drawline(xOff-size+third,yOff+entrance*size, + xOff-size+third,yOff+entrance*size+size-1); + rb->lcd_drawline(xOff-size+twothirds,yOff+entrance*size, + xOff-size+twothirds,yOff+entrance*size+size-1); + } +} + +/* Manage the congratulations screen */ +static enum text_state welldone_screen(void) { + int button = BUTTON_NONE; + enum text_state state = TEXT_STATE_LOOPING; + + display_text_page(welldone_page, 0); + + while (state == TEXT_STATE_LOOPING) { + button = rb->button_get(true); + + switch (button) { + case MAZEZAM_QUIT: + state = TEXT_STATE_QUIT; + break; + + case MAZEZAM_SELECT: +#if CONFIG_KEYPAD != ONDIO_PAD + case MAZEZAM_RIGHT: +#endif + state = TEXT_STATE_OKAY; + break; + + default: + if (rb->default_event_handler(button) == SYS_USB_CONNECTED) + state = TEXT_STATE_USB_CONNECTED; + break; + } + } + + return state; +} + +/* Manage the quit confimation screen */ +static enum text_state quitconfirm_loop(void) { + int button = BUTTON_NONE; + enum text_state state = TEXT_STATE_LOOPING; + short select = 2; + + display_text_page(confirm_page, select + 1); + + /* Wait for a button release. This is useful when a repeated button + * press is used for quit. + */ + while ((rb->button_get(true) & BUTTON_REL) != BUTTON_REL); + + while (state == TEXT_STATE_LOOPING) { + display_text_page(confirm_page, select + 1); + + button = rb->button_get(true); + + switch (button) { + case MAZEZAM_QUIT: + state = TEXT_STATE_QUIT; + break; + + case MAZEZAM_UP: + case MAZEZAM_DOWN: + select = (2 - select) + 1; + break; + + case MAZEZAM_SELECT: +#if CONFIG_KEYPAD != ONDIO_PAD + case MAZEZAM_RIGHT: +#endif + if (select == 1) + state = TEXT_STATE_QUIT; + else + state = TEXT_STATE_OKAY; + break; + + default: + if (rb->default_event_handler(button) == SYS_USB_CONNECTED) + state = TEXT_STATE_USB_CONNECTED; + break; + } + } + + return state; +} + +/* Manage the playing of a level */ +static enum level_state level_loop(short level, short lives) { + struct chunk_data cd; + short shift[MAZEZAM_MAX_LINES]; /* amount each line has been shifted */ + short width; + short height; + short entrance; + short exit; + short i; + short x,y; + int button; + enum level_state state = LEVEL_STATE_LOOPING; + bool blocked; /* is there a chunk in the way of the player? */ + + if (!(parse_level(level,&cd,&width,&height,&entrance,&exit))) + return LEVEL_STATE_PARSE_ERROR; + + for (i = 0; i < height; i++) + shift[i] = 0; + + x = -1; + y = entrance; + + draw_level(&cd, shift, width, height, entrance, exit, x, y); + +#ifdef HAVE_REMOTE_LCD + /* Splash text seems to use the remote display by + * default. I suppose I better keep it tidy! + */ + rb->lcd_remote_clear_display(); +#endif + rb->splash(MAZEZAM_LEVEL_LIVES_DELAY, true, MAZEZAM_LEVEL_LIVES_TEXT, level+1, lives); + + /* ensure keys pressed during the splash screen are ignored */ + rb->button_clear_queue(); + + while (state == LEVEL_STATE_LOOPING) { + draw_level(&cd, shift, width, height, entrance, exit, x, y); + rb->lcd_update(); + button = rb->button_get(true); + blocked = false; + + switch (button) { + case MAZEZAM_UP: + case MAZEZAM_UP | BUTTON_REPEAT: + if ((y > 0) && (x >= 0) && (x < width)) { + for (i = 0; i < cd.l_num[y-1]; i++) + blocked = blocked || ((x>=shift[y-1]+cd.c_inset[y-1][i]) + && (x= 0) && (x < width)) { + for (i = 0; i < cd.l_num[y+1]; i++) + blocked = blocked || ((x>=shift[y+1]+cd.c_inset[y+1][i]) + && (x 0) { + for (i = 0; i < cd.l_num[y]; i++) + blocked = blocked || (x == shift[y]+cd.c_inset[y][i]+cd.c_width[y][i]); + if (!blocked) x -= 1; + else if (shift[y] + cd.c_inset[y][0] > 0) { + x -= 1; + shift[y] -= 1; + } + } + break; + + case MAZEZAM_RIGHT: + case MAZEZAM_RIGHT | BUTTON_REPEAT: + if (x < width-1) { + for (i = 0; i < cd.l_num[y]; i++) + blocked = blocked || (x+1 == shift[y]+cd.c_inset[y][i]); + if (!blocked) x += 1; + else if (shift[y] + cd.c_inset[y][cd.l_num[y]-1] + cd.c_width[y][cd.l_num[y]-1] < width) { + x += 1; + shift[y] += 1; + } + } + else if (x == width) state = LEVEL_STATE_COMPLETED; + else if (y == exit) x += 1; + break; + + case MAZEZAM_RETRY: + state = LEVEL_STATE_FAILED; + break; + + case MAZEZAM_QUIT: + switch (quitconfirm_loop()) { + case TEXT_STATE_QUIT: + state = LEVEL_STATE_QUIT; + break; + + case TEXT_STATE_USB_CONNECTED: + state = LEVEL_STATE_USB_CONNECTED; + break; + + default: + break; + } + break; + + default: + if (rb->default_event_handler(button) == SYS_USB_CONNECTED) + state = LEVEL_STATE_USB_CONNECTED; + break; + } + } + + return state; +} + +/* The loop which manages a full game of MazezaM */ +static enum game_state game_loop(struct resume_data *r) { + enum game_state state = GAME_STATE_LOOPING; + int level = r->level; + int lives = MAZEZAM_START_LIVES; + + rb->lcd_clear_display(); + + while (state == GAME_STATE_LOOPING) + { + switch (level_loop(level,lives)) { + case LEVEL_STATE_COMPLETED: + level += 1; + if (!((level - r->level) % MAZEZAM_EXTRA_LIFE)) + lives += 1; + break; + + case LEVEL_STATE_QUIT: + state = GAME_STATE_QUIT; + break; + + case LEVEL_STATE_FAILED: + lives -= 1; + break; + + case LEVEL_STATE_PARSE_ERROR: + state = GAME_STATE_PARSE_ERROR; + break; + + case LEVEL_STATE_USB_CONNECTED: + state = GAME_STATE_USB_CONNECTED; + break; + + default: + break; + } + if (lives == 0) + state = GAME_STATE_OVER; + else if (level == MAZEZAM_NUM_LEVELS) + state = GAME_STATE_COMPLETED; + } + + switch (state) { + case GAME_STATE_OVER: +#ifdef HAVE_REMOTE_LCD + /* Splash text seems to use the remote display by + * default. I suppose I better keep it tidy! + */ + rb->lcd_remote_clear_display(); +#endif + rb->splash(MAZEZAM_GAMEOVER_DELAY, true, MAZEZAM_GAMEOVER_TEXT); + break; + + case GAME_STATE_COMPLETED: + switch (welldone_screen()) { + case TEXT_STATE_QUIT: + state = GAME_STATE_QUIT; + break; + + case TEXT_STATE_USB_CONNECTED: + state = GAME_STATE_USB_CONNECTED; + break; + + default: + state = GAME_STATE_OKAY; + break; + } + break; + + default: + break; + } + + /* This particular resume game logic is designed to make + * players prove they can solve a level more than once + */ + if (level > r->level + 1) + r->level += 1; + + return state; +} + +/* Manage the instruction screen */ +static enum text_state instruction_loop(void) { + int button; + enum text_state state = TEXT_STATE_LOOPING; + int page = 0; + + while (state == TEXT_STATE_LOOPING) { + display_text_page(help_page[page], 0); + button = rb->button_get(true); + + switch (button) { + case MAZEZAM_LEFT: + page -= 1; + break; + + case MAZEZAM_SELECT: +#if CONFIG_KEYPAD != ONDIO_PAD + case MAZEZAM_RIGHT: +#endif + page += 1; + break; + + case MAZEZAM_QUIT: + state = TEXT_STATE_QUIT; + break; + + default: + if (rb->default_event_handler(button) == SYS_USB_CONNECTED) + state = TEXT_STATE_USB_CONNECTED; + break; + + } + + if ((page < 0) || (page >= MAZEZAM_NUM_HELP_PAGES)) + state = TEXT_STATE_OKAY; + } + + return state; +} + +/* Manage the text screen that offers the user the option of + * resuming or starting a new game + */ +static enum text_state resume_game_loop (struct resume_data *r) { + int button = BUTTON_NONE; + enum text_state state = TEXT_STATE_LOOPING; + short select = 0; + + /* if the resume level is 0, don't bother asking */ + if (r->level == 0) return TEXT_STATE_OKAY; + + display_text_page(resume_page, select + 1); + + while (state == TEXT_STATE_LOOPING) { + display_text_page(resume_page, select + 1); + + button = rb->button_get(true); + + switch (button) { + case MAZEZAM_QUIT: + state = TEXT_STATE_QUIT; + break; + + case MAZEZAM_LEFT: + state = TEXT_STATE_BACK; + break; + + case MAZEZAM_UP: + case MAZEZAM_DOWN: + select = 1 - select; + break; + + case MAZEZAM_SELECT: +#if CONFIG_KEYPAD != ONDIO_PAD + case MAZEZAM_RIGHT: +#endif + if (select == 1) { + /* The player wants to play a new game. I could ask + * for confirmation here, but the only penalty is + * playing through some already completed levels, + * so I don't think it's necessary + */ + r->level = 0; + } + state = TEXT_STATE_OKAY; + break; + + default: + if (rb->default_event_handler(button) == SYS_USB_CONNECTED) + state = TEXT_STATE_USB_CONNECTED; + break; + } + } + + return state; +} + +/* Load the resume data from the config file. The data is + * stored in both r and old. + */ +static void resume_load_data (struct resume_data *r, struct resume_data *old) { + struct configdata config[] = { + {TYPE_INT,0,MAZEZAM_NUM_LEVELS-1,&(r->level),MAZEZAM_CONFIG_LEVELS_NAME,NULL,NULL} + }; + + if (configfile_load(MAZEZAM_CONFIG_FILENAME,config,MAZEZAM_CONFIG_NUM_ITEMS,MAZEZAM_CONFIG_VERSION) < 0) + r->level = 0; + /* an extra precaution */ + else if ((r->level < 0) || (MAZEZAM_NUM_LEVELS <= r->level)) + r->level = 0; + + old->level = r->level; +} + +/* Save the resume data in the config file, but only if necessary */ +static void resume_save_data (struct resume_data *r, struct resume_data *old) { + struct configdata config[] = { + {TYPE_INT,0,MAZEZAM_NUM_LEVELS-1,&(r->level),MAZEZAM_CONFIG_LEVELS_NAME,NULL,NULL} + }; + + /* To reduce disk usage, only write the file if the resume data has + * changed. + */ + if (old->level != r->level) + configfile_save(MAZEZAM_CONFIG_FILENAME,config,MAZEZAM_CONFIG_NUM_ITEMS,MAZEZAM_CONFIG_MINVERSION); +} + +/* The loop which manages the welcome screen and menu */ +static enum text_state welcome_loop(void) { + int button; + short select = 0; + enum text_state state = TEXT_STATE_LOOPING; + struct resume_data r_data, old_data; + + /* Load data */ + resume_load_data(&r_data, &old_data); + + while (state == TEXT_STATE_LOOPING) { + display_text_page(title_page, select + 1); + button = rb->button_get(true); + + switch (button) { + case MAZEZAM_QUIT: + state = TEXT_STATE_QUIT; + break; + + case MAZEZAM_UP: + select = (select + (title_page.num_lines - 2)) % (title_page.num_lines - 1); + break; + + case MAZEZAM_DOWN: + select = (select + 1) % (title_page.num_lines - 1); + break; + + case MAZEZAM_SELECT: +#if CONFIG_KEYPAD != ONDIO_PAD + case MAZEZAM_RIGHT: +#endif + if (select == 0) { /* play game */ + switch (resume_game_loop(&r_data)) { + case TEXT_STATE_QUIT: + state = TEXT_STATE_QUIT; + break; + + case TEXT_STATE_USB_CONNECTED: + state = TEXT_STATE_USB_CONNECTED; + break; + + case TEXT_STATE_BACK: + break; + + default: { /* Ouch! This nesting is too deep! */ + switch (game_loop(&r_data)) { + case GAME_STATE_QUIT: + state = TEXT_STATE_QUIT; + break; + + case GAME_STATE_USB_CONNECTED: + state = TEXT_STATE_USB_CONNECTED; + break; + + case GAME_STATE_PARSE_ERROR: + state = TEXT_STATE_PARSE_ERROR; + break; + + default: + break; + } + break; + } + } + } + else if (select == 1) { /* Instructions */ + switch (instruction_loop()) { + case TEXT_STATE_QUIT: + state = TEXT_STATE_QUIT; + break; + + case TEXT_STATE_USB_CONNECTED: + state = TEXT_STATE_USB_CONNECTED; + break; + + default: + break; + } + } + else /* Quit */ + state = TEXT_STATE_QUIT; + + break; + + default: + if (rb->default_event_handler(button) == SYS_USB_CONNECTED) + state = TEXT_STATE_USB_CONNECTED; + break; + } + } + + /* I'm not sure if it's appropriate to write to disk on USB events. + * Currently, I do so. + */ + resume_save_data(&r_data, &old_data); + + return state; +} + +/* Plugin entry point */ +enum plugin_status plugin_start(struct plugin_api* api, void* parameter) { + enum plugin_status state; + + /* Usual plugin stuff */ + (void)parameter; + rb = api; + +#ifdef HAVE_LCD_COLOR + rb->lcd_set_background(MAZEZAM_BG_COLOR); + rb->lcd_set_backdrop(NULL); +#elif LCD_DEPTH > 1 + rb->lcd_set_background(MAZEZAM_BG_GRAY); +#endif + rb->lcd_setfont(FONT_SYSFIXED); + + /* initialise the config file module */ + configfile_init(rb); + + switch (welcome_loop()) { + case TEXT_STATE_USB_CONNECTED: + state = PLUGIN_USB_CONNECTED; + break; + + case TEXT_STATE_PARSE_ERROR: + state = PLUGIN_ERROR; + break; + + default: + state = PLUGIN_OK; + break; + } + + return state; +} diff --git a/docs/CREDITS b/docs/CREDITS index df511b57f4..14f0264955 100644 --- a/docs/CREDITS +++ b/docs/CREDITS @@ -261,3 +261,4 @@ Tobias Langhoff Steve Gotthardt Greg White Mattieu Favréaux +Malcolm Tyrrell