/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * * Copyright (C) 2006, 2008 Malcolm Tyrrell * * MazezaM - a Rockbox version of my ZX Spectrum game from 2002 * * 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/helper.h" #include "lib/playback_control.h" /* Include standard plugin macro */ #if (CONFIG_KEYPAD == IPOD_3G_PAD) # define MAZEZAM_MENU BUTTON_MENU # define MAZEZAM_RIGHT BUTTON_RIGHT # define MAZEZAM_LEFT BUTTON_LEFT # define MAZEZAM_UP BUTTON_SCROLL_BACK # define MAZEZAM_DOWN BUTTON_SCROLL_FWD # define MAZEZAM_RIGHT_REPEAT (BUTTON_RIGHT|BUTTON_REPEAT) # define MAZEZAM_LEFT_REPEAT (BUTTON_LEFT|BUTTON_REPEAT) # define MAZEZAM_UP_REPEAT (BUTTON_SCROLL_BACK|BUTTON_REPEAT) # define MAZEZAM_DOWN_REPEAT (BUTTON_SCROLL_FWD|BUTTON_REPEAT) #else # include "lib/pluginlib_actions.h" # define MAZEZAM_MENU PLA_CANCEL # define MAZEZAM_RIGHT PLA_RIGHT # define MAZEZAM_LEFT PLA_LEFT # define MAZEZAM_UP PLA_UP # define MAZEZAM_DOWN PLA_DOWN # define MAZEZAM_RIGHT_REPEAT PLA_RIGHT_REPEAT # define MAZEZAM_LEFT_REPEAT PLA_LEFT_REPEAT # define MAZEZAM_UP_REPEAT PLA_UP_REPEAT # define MAZEZAM_DOWN_REPEAT PLA_DOWN_REPEAT const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; #endif /* All the text is here */ #define MAZEZAM_TEXT_GAME_OVER "Game Over" #define MAZEZAM_TEXT_LIVES "Level %d, Lives %d" #define MAZEZAM_TEXT_CHECKPOINT "Checkpoint reached" #define MAZEZAM_TEXT_WELLDONE_TITLE "You have escaped!" #define MAZEZAM_TEXT_WELLDONE_OPTION "Goodbye" #define MAZEZAM_TEXT_MAZEZAM_MENU "MazezaM Menu" #define MAZEZAM_TEXT_RETRY_LEVEL "Retry level" #define MAZEZAM_TEXT_AUDIO_PLAYBACK "Playback Control" #define MAZEZAM_TEXT_QUIT "Quit" #define MAZEZAM_TEXT_BACK "Resume Game" #define MAZEZAM_TEXT_MAIN_MENU "MazezaM" #define MAZEZAM_TEXT_CONTINUE "Play from checkpoint" #define MAZEZAM_TEXT_PLAY_NEW_GAME "Start New Game" #define MAZEZAM_START_LIVES 3 /* how many lives at game start */ #define MAZEZAM_FIRST_CHECKPOINT 3 /* The level at the first checkpoint */ #define MAZEZAM_CHECKPOINT_INTERVAL 4 /* A checkpoint every _ levels */ #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_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_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_DELAY_CHECKPOINT HZ #define MAZEZAM_DELAY_LIVES HZ #define MAZEZAM_DELAY_GAME_OVER (3 * HZ) / 2 /* 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 storing level data in unparsed form */ 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. */ #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,{" $ $"," $ $$"}}, {3,8,2,1,{" $ $$$"," $ $ $"," $ $ $"}}, {4,14,1,3,{" $$$$$ $$ $$"," $$ $$ $$","$$ $ $$ $$$", " $$$$$$$$ $"}}, {6,7,4,2,{" $"," $$$$"," $$$ $$"," $ $ $"," $ $$","$ $$"}}, {6,13,0,0,{" $$$$$","$ $$$$$ $$$"," $ $$$ $$$$", "$ $ $$$$$$$"," $$$ $ $$","$ $ $ $$ $"}}, {11,5,10,0,{" $"," $ $$"," $$","$ $"," $ $"," $$$","$ $", " $ $"," $ $","$ $$"," $"}}, {7,16,0,6,{" $$$$$$$"," $$$$ $$$$ $ $","$$ $$ $$$$$$ $ $", "$ $ $"," $$$$$$$$$$$$$$"," $ $$ $ $$$", " $ $$$ $$"}}, {4,15,2,0,{" $$$$ $$$$ $$"," $ $$ $$ $ $$"," $ $$ $$$$ $$", " $ $$ $$$$ $"}}, {7,9,6,2,{" $ $$$$"," $ $ $$"," $ $$$$ $","$ $$ $"," $ $$$", " $$$$$$"," $"}}, {10,14,8,0,{" $"," $$$$$$$$$$ $"," $$$ $$", " $ $$$$$$$$ $"," $$$ $$$ $$$"," $$$ $ $$$", " $ $$$$$$$ $$"," $ $ $ $$$"," $$$$$$$$$$$$", ""}} }; /* This data structure which holds information about the rows */ 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]; }; /* Parsed level data */ struct level_info { short width; short height; short entrance; short exit; struct chunk_data cd; }; /* The state variable used to hold the state of the plugin */ static enum { STATE_QUIT, /* The player wants to quit */ STATE_USB_CONNECTED, /* A USB cable has been inserted */ STATE_PARSE_ERROR, /* There's a parse error in the levels */ STATE_WELLDONE, /* The player has finished the game */ STATE_IN_APPLICATION, STATE_MAIN_MENU /* The player is at the main menu */ = STATE_IN_APPLICATION, STATE_GAME_OVER, /* The player is out of lives */ STATE_IN_GAME, STATE_COMPLETED /* A level has been completed */ = STATE_IN_GAME, STATE_FAILED, /* The player wants to retry the level */ STATE_GAME_MENU, /* The player wan't to access the in-game menu */ STATE_IN_LEVEL, } state; /* 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 */ }; #if LCD_DEPTH > 1 /* Store the display settings so they are reintroduced during menus */ static struct { fb_data* backdrop; unsigned foreground; unsigned background; } lcd_settings; #endif /***************************************************************************** * Store the LCD settings ******************************************************************************/ static void store_lcd_settings(void) { /* Store the old settings */ #if LCD_DEPTH > 1 lcd_settings.backdrop = rb->lcd_get_backdrop(); lcd_settings.foreground = rb->lcd_get_foreground(); lcd_settings.background = rb->lcd_get_background(); #endif } /***************************************************************************** * Restore the LCD settings to their defaults ******************************************************************************/ static void restore_lcd_settings(void) { /* Turn on backlight timeout (revert to settings) */ backlight_use_settings(); /* Restore the old settings */ #if LCD_DEPTH > 1 rb->lcd_set_foreground(lcd_settings.foreground); rb->lcd_set_background(lcd_settings.background); rb->lcd_set_backdrop(lcd_settings.backdrop); #endif } /***************************************************************************** * Adjust the LCD settings to suit MazezaM levels ******************************************************************************/ static void plugin_lcd_settings(void) { /* Turn off backlight timeout */ backlight_ignore_timeout(); /* Set the new settings */ #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); rb->lcd_set_backdrop(NULL); #endif } /***************************************************************************** * 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 level_info* li) { int i,j; char c,clast; li->width = level_data[level].width; li->height = level_data[level].height; li->entrance = level_data[level].entrance; li->exit = level_data[level].exit; /* for each line in the level */ for (i = 0; icd.l_num[i] = 0; clast = ' '; /* the character we last considered */ while ((c = level_data[level].line[i][j]) != '\0') { if (c != ' ') { if (clast == ' ') { li->cd.l_num[i] += 1; if (li->cd.l_num[i] > MAZEZAM_MAX_CHUNKS) return false; li->cd.c_inset[i][li->cd.l_num[i] - 1] = j; li->cd.c_width[i][li->cd.l_num[i] - 1] = 1; } else li->cd.c_width[i][li->cd.l_num[i] - 1] += 1; } clast = c; j++; } } } return true; } /***************************************************************************** * Draw the walls of a level ******************************************************************************/ static void draw_walls( short size, short xOff, short yOff, short width, short height, short entrance, short exit) { #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 chunk row i ******************************************************************************/ static void draw_row( short size, short xOff, short yOff, short width, short i, /* the row number */ struct chunk_data *cd, /* the data about the chunks */ short *shift /* an array of the horizontal offset of the lines */ ) { /* The assignment below is just a hack to make supress a warning on * non color targets */ short j = width; #ifndef HAVE_LCD_COLOR /* We #def these out to supress a compiler warning */ short k; #if LCD_DEPTH <= 1 short l; #endif #endif #ifdef HAVE_LCD_COLOR /* adding width to i should have a fixed, but randomising effect on * the choice of the colours of the top line of chunks */ rb->lcd_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_hline(xOff+size*shift[i]+size*cd->c_inset[i][j]+1, xOff+size*shift[i]+size*cd->c_inset[i][j]+ cd->c_width[i][j]*size-3, yOff+size*i+size-2); rb->lcd_vline(xOff+size*shift[i]+size*cd->c_inset[i][j]+ cd->c_width[i][j]*size-2, yOff+size*i, 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_hline(xOff+size*shift[i]+size*cd->c_inset[i][j]+2, 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 ******************************************************************************/ static void draw_player( short size, short xOff, short yOff, short x, short y) { /* For drawing the player, taken from the sokoban plugin */ short max = size - 1; short middle = max / 2; short ldelta = (middle + 1) / 2; /* 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_hline(xOff+size*x, xOff+size*x+max, yOff+size*y+middle); rb->lcd_vline(xOff+size*x+middle, yOff+size*y, 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 ******************************************************************************/ static void draw_gate( short size, short xOff, short yOff, short entrance) { short third = size / 3; short twothirds = (2 * size) / 3; #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_hline(xOff-size,xOff-1,yOff+entrance*size+third); rb->lcd_hline(xOff-size,xOff-1,yOff+entrance*size+twothirds); rb->lcd_vline(xOff-size+third,yOff+entrance*size, yOff+entrance*size+size-1); rb->lcd_vline(xOff-size+twothirds,yOff+entrance*size, yOff+entrance*size+size-1); } /***************************************************************************** * Draw the level ******************************************************************************/ static void draw_level( struct level_info* li, short *shift, /* an array of the horizontal offset of the lines */ short x, /* player's x and y coords */ short y) { /* First we calculate the draw info */ /* The number of pixels the side of a square should be */ short size = (LCD_WIDTH/(li->width+2)) < (LCD_HEIGHT/li->height) ? (LCD_WIDTH/(li->width+2)) : (LCD_HEIGHT/li->height); /* The x and y position (in pixels) of the top left corner of the * level */ short xOff = (LCD_WIDTH - (size*li->width))/2; short yOff = (LCD_HEIGHT - (size*li->height))/2; short i; rb->lcd_clear_display(); draw_walls(size,xOff,yOff,li->width, li->height, li->entrance, li->exit); /* draw the chunks */ for (i = 0; iheight; i++) { draw_row(size,xOff,yOff,li->width,i,&(li->cd),shift); } draw_player(size,xOff,yOff,x,y); /* if the player has moved into the level, draw the gate */ if (x >= 0) draw_gate(size,xOff,yOff,li->entrance); } /***************************************************************************** * Manage the congratulations screen ******************************************************************************/ static void welldone_screen(void) { int start_selection = 0; MENUITEM_STRINGLIST(menu,MAZEZAM_TEXT_WELLDONE_TITLE,NULL, MAZEZAM_TEXT_WELLDONE_OPTION); switch(rb->do_menu(&menu, &start_selection, NULL, true)){ case MENU_ATTACHED_USB: state = STATE_USB_CONNECTED; break; } } /***************************************************************************** * Manage the playing of a level ******************************************************************************/ static void level_loop(struct level_info* li, short* shift, short *x, short *y) { int i; int button; bool blocked; /* is there a chunk in the way of the player? */ while (state >= STATE_IN_LEVEL) { draw_level(li, shift, *x, *y); rb->lcd_update(); #ifdef __PLUGINLIB_ACTIONS_H__ button = pluginlib_getaction(TIMEOUT_BLOCK, plugin_contexts, ARRAYLEN(plugin_contexts)); #else button = rb->button_get(true); #endif blocked = false; switch (button) { case MAZEZAM_UP: case MAZEZAM_UP_REPEAT: if ((*y > 0) && (*x >= 0) && (*x < li->width)) { for (i = 0; i < li->cd.l_num[*y-1]; i++) blocked = blocked || ((*x>=shift[*y-1]+li->cd.c_inset[*y-1][i]) && (*xcd.c_inset[*y-1][i]+ li->cd.c_width[*y-1][i])); if (!blocked) *y -= 1; } break; case MAZEZAM_DOWN: case MAZEZAM_DOWN_REPEAT: if ((*y < li->height-1) && (*x >= 0) && (*x < li->width)) { for (i = 0; i < li->cd.l_num[*y+1]; i++) blocked = blocked || ((*x>=shift[*y+1]+li->cd.c_inset[*y+1][i]) && (*xcd.c_inset[*y+1][i]+ li->cd.c_width[*y+1][i])); if (!blocked) *y += 1; } break; case MAZEZAM_LEFT: case MAZEZAM_LEFT_REPEAT: if (*x > 0) { for (i = 0; i < li->cd.l_num[*y]; i++) blocked = blocked || (*x == shift[*y]+li->cd.c_inset[*y][i]+ li->cd.c_width[*y][i]); if (!blocked) *x -= 1; else if (shift[*y] + li->cd.c_inset[*y][0] > 0) { *x -= 1; shift[*y] -= 1; } } break; case MAZEZAM_RIGHT: case MAZEZAM_RIGHT_REPEAT: if (*x < li->width-1) { for (i = 0; i < li->cd.l_num[*y]; i++) blocked = blocked || (*x+1 == shift[*y]+li->cd.c_inset[*y][i]); if (!blocked) *x += 1; else if (shift[*y] + li->cd.c_inset[*y][li->cd.l_num[*y]-1] + li->cd.c_width[*y][li->cd.l_num[*y]-1] < li->width) { *x += 1; shift[*y] += 1; } } else if (*x == li->width) state = STATE_COMPLETED; else if (*y == li->exit) *x += 1; break; case MAZEZAM_MENU: state = STATE_GAME_MENU; break; default: if (rb->default_event_handler(button) == SYS_USB_CONNECTED) state = STATE_USB_CONNECTED; break; } } } /***************************************************************************** * Manage the in game menu ******************************************************************************/ static void in_game_menu(void) { /* The initial option is retry level */ int start_selection = 1; MENUITEM_STRINGLIST(menu,MAZEZAM_TEXT_MAZEZAM_MENU, NULL, MAZEZAM_TEXT_BACK, MAZEZAM_TEXT_RETRY_LEVEL, MAZEZAM_TEXT_AUDIO_PLAYBACK, MAZEZAM_TEXT_QUIT); /* Don't show the status bar */ switch(rb->do_menu(&menu, &start_selection, NULL, false)){ case 1: /* retry */ state = STATE_FAILED; break; case 2: /* Audio playback */ playback_control(NULL); state = STATE_IN_LEVEL; break; case 3: /* quit */ state = STATE_QUIT; break; case MENU_ATTACHED_USB: state = STATE_USB_CONNECTED; break; default: /* Back */ state = STATE_IN_LEVEL; break; } } /***************************************************************************** * Is the level a checkpoint ******************************************************************************/ static bool at_checkpoint(int level) { if (level <= MAZEZAM_FIRST_CHECKPOINT) return level == MAZEZAM_FIRST_CHECKPOINT; else { level = level - MAZEZAM_FIRST_CHECKPOINT; return level % MAZEZAM_CHECKPOINT_INTERVAL == 0; } } /***************************************************************************** * Set up and play a level * new_level should be true if this is the first time we've encountered * this level ******************************************************************************/ static void play_level(short level, short lives, bool new_level) { struct level_info li; short shift[MAZEZAM_MAX_LINES]; /* amount each line has been shifted */ short x,y; int i; state = STATE_IN_LEVEL; if (!(parse_level(level,&li))) state = STATE_PARSE_ERROR; for (i = 0; i < li.height; i++) shift[i] = 0; x = -1; y = li.entrance; plugin_lcd_settings(); rb->lcd_clear_display(); draw_level(&li, shift, x, y); /* If we've just reached a checkpoint, then alert the player */ if (new_level && at_checkpoint(level)) { rb->splash(MAZEZAM_DELAY_CHECKPOINT, MAZEZAM_TEXT_CHECKPOINT); /* Clear the splash */ draw_level(&li, shift, 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->splashf(MAZEZAM_DELAY_LIVES, MAZEZAM_TEXT_LIVES, level+1, lives); /* ensure keys pressed during the splash screen are ignored */ rb->button_clear_queue(); /* this little loop just ensures we return to the game if the player * doesn't perform an interesting action during the in game menu */ while (state >= STATE_IN_LEVEL) { level_loop(&li, shift, &x, &y); if (state == STATE_GAME_MENU) { restore_lcd_settings(); in_game_menu(); plugin_lcd_settings(); } } restore_lcd_settings(); } /***************************************************************************** * Update the resume data based on the level reached ******************************************************************************/ static void update_resume_data(struct resume_data *r, int level) { if (at_checkpoint(level)) r->level = level; } /***************************************************************************** * The loop which manages a full game of MazezaM. ******************************************************************************/ static void game_loop(struct resume_data *r) { int level = r->level; int lives = MAZEZAM_START_LIVES; /* We want to know when a player reaches a level for the first time, * so we keep a second copy of the level. */ int old_level = level; state = STATE_IN_GAME; while (state >= STATE_IN_GAME) { play_level(level, lives, old_level < level); old_level = level; switch (state) { case STATE_COMPLETED: level += 1; if (level == MAZEZAM_NUM_LEVELS) state = STATE_WELLDONE; break; case STATE_FAILED: lives -= 1; if (lives == 0) state = STATE_GAME_OVER; break; default: break; } update_resume_data(r,level); } switch (state) { case STATE_GAME_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_DELAY_GAME_OVER, MAZEZAM_TEXT_GAME_OVER); break; case STATE_WELLDONE: r->level = 0; welldone_screen(); break; default: break; } } /***************************************************************************** * 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, { .int_p = &(r->level) }, MAZEZAM_CONFIG_LEVELS_NAME,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, {.int_p = &(r->level) }, MAZEZAM_CONFIG_LEVELS_NAME,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); } /***************************************************************************** * Manages the main menu ******************************************************************************/ static bool have_continue; static int main_menu_cb(int action, const struct menu_item_ex *this_item) { if(action == ACTION_REQUEST_MENUITEM && !have_continue && ((intptr_t)this_item)==0) return ACTION_EXIT_MENUITEM; return action; } static void main_menu(void) { /* The initial option is "play game" */ int start_selection = 0; int choice = 0; struct resume_data r_data, old_data; /* Load data */ resume_load_data(&r_data, &old_data); MENUITEM_STRINGLIST(menu,MAZEZAM_TEXT_MAIN_MENU,main_menu_cb, MAZEZAM_TEXT_CONTINUE, MAZEZAM_TEXT_PLAY_NEW_GAME, MAZEZAM_TEXT_AUDIO_PLAYBACK, MAZEZAM_TEXT_QUIT); while (state >= STATE_IN_APPLICATION) { have_continue = (r_data.level != 0); choice = rb->do_menu(&menu, &start_selection, NULL, false); switch(choice) { case 0: /* Continue */ state = STATE_IN_GAME; game_loop(&r_data); break; case 1: /* Play new game */ r_data.level = 0; state = STATE_IN_GAME; game_loop(&r_data); break; case 2: /* Audio playback */ playback_control(NULL); break; case MENU_ATTACHED_USB: state = STATE_USB_CONNECTED; break; default: /* Quit */ state = STATE_QUIT; 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); } /***************************************************************************** * Plugin entry point ******************************************************************************/ enum plugin_status plugin_start(const void* parameter) { enum plugin_status plugin_state; /* Usual plugin stuff */ (void)parameter; store_lcd_settings(); state = STATE_MAIN_MENU; main_menu(); switch (state) { case STATE_USB_CONNECTED: plugin_state = PLUGIN_USB_CONNECTED; break; case STATE_PARSE_ERROR: plugin_state = PLUGIN_ERROR; break; default: plugin_state = PLUGIN_OK; break; } return plugin_state; }