From f91d06de7bf724e8e0aa580c18efa3eb345f88f9 Mon Sep 17 00:00:00 2001 From: Antoine Cellerier Date: Thu, 28 Jun 2007 20:45:00 +0000 Subject: Apply FS #6702: More Sokoban Improvements. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@13731 a1c6a512-1295-4272-9138-f99709370657 --- apps/plugins/sokoban.c | 1355 +++++++++++++++++++++++++++++++----------------- 1 file changed, 880 insertions(+), 475 deletions(-) (limited to 'apps/plugins/sokoban.c') diff --git a/apps/plugins/sokoban.c b/apps/plugins/sokoban.c index 40a2f549ad..976f552dbd 100644 --- a/apps/plugins/sokoban.c +++ b/apps/plugins/sokoban.c @@ -10,6 +10,7 @@ * Copyright (C) 2002 Eric Linenberg * February 2003: Robert Hak performs a cleanup/rewrite/feature addition. * Eric smiles. Bjorn cries. Linus say 'huh?'. + * March 2007: Sean Morrisey performs a major rewrite/feature addition. * * 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. @@ -19,95 +20,126 @@ * ****************************************************************************/ #include "plugin.h" +#include "lib/playback_control.h" #ifdef HAVE_LCD_BITMAP PLUGIN_HEADER -#if LCD_DEPTH >= 2 +#if LCD_DEPTH >= 2 && ((LCD_HEIGHT >= 96 && LCD_WIDTH >= 152) || \ + (LCD_HEIGHT >= 121 && LCD_WIDTH >= 120)) extern const fb_data sokoban_tiles[]; #endif -#define SOKOBAN_TITLE "Sokoban" +#define SOKOBAN_TITLE "Sokoban" -#define LEVELS_FILE PLUGIN_DIR "/sokoban.levels" +#define SOKOBAN_LEVELS_FILE PLUGIN_DIR "/sokobanlevels.sok" +#define SOKOBAN_SAVE_FILE PLUGIN_DIR "/sokobansave.sok" -#define ROWS 16 -#define COLS 20 -#define SOKOBAN_LEVEL_SIZE (ROWS*COLS) -#define MAX_BUFFERED_BOARDS 500 -/* Use either all but 12k of the plugin buffer for board data - or just enough for MAX_BUFFERED_BOARDS, which ever is less */ -#if (PLUGIN_BUFFER_SIZE - 0x3000)/SOKOBAN_LEVEL_SIZE < MAX_BUFFERED_BOARDS -#define NUM_BUFFERED_BOARDS (PLUGIN_BUFFER_SIZE - 0x3000)/SOKOBAN_LEVEL_SIZE +/* Magnify is the number of pixels for each block. + * Set dynamically so all targets can support levels + * that fill their entire screen, less the stat box. + * 16 rows & 20 cols minimum */ +#if (LCD_HEIGHT >= 224) && (LCD_WIDTH >= 320) +#define MAGNIFY 14 +#define ROWS (LCD_HEIGHT/MAGNIFY) +#define COLS ((LCD_WIDTH-40)/MAGNIFY) +#elif (LCD_HEIGHT >= 249) && (LCD_WIDTH >= 280) +#define MAGNIFY 14 +#define ROWS ((LCD_HEIGHT-25)/MAGNIFY) +#define COLS (LCD_WIDTH/MAGNIFY) +#elif (LCD_HEIGHT >= 144) && (LCD_WIDTH >= 220) +#define MAGNIFY 9 +#define ROWS (LCD_HEIGHT/MAGNIFY) +#define COLS ((LCD_WIDTH-40)/MAGNIFY) +#elif (LCD_HEIGHT >= 169) && (LCD_WIDTH+4 >= 180) /* plus 4 for sansa */ +#define MAGNIFY 9 +#define ROWS ((LCD_HEIGHT-25)/MAGNIFY) +#define COLS ((LCD_WIDTH+4)/MAGNIFY) +#elif (LCD_HEIGHT >= 96) && (LCD_WIDTH >= 160) +#define MAGNIFY 6 +#define ROWS (LCD_HEIGHT/MAGNIFY) +#define COLS ((LCD_WIDTH-40)/MAGNIFY) +#elif (LCD_HEIGHT >= 121) && (LCD_WIDTH >= 120) +#define MAGNIFY 6 +#define ROWS ((LCD_HEIGHT-25)/MAGNIFY) +#define COLS (LCD_WIDTH/MAGNIFY) #else -#define NUM_BUFFERED_BOARDS MAX_BUFFERED_BOARDS +#define MAGNIFY 4 +#define ROWS 16 +#define COLS 20 #endif -/* Use 4k plus remaining plugin buffer (-8k for prog) for undo, up to 32k */ -#if PLUGIN_BUFFER_SIZE - NUM_BUFFERED_BOARDS*SOKOBAN_LEVEL_SIZE - 0x2000 > \ - 0x7FFF -#define MAX_UNDOS 0x7FFF + +/* Use either all but 16k of the plugin buffer for level data + * or 128k, which ever is less */ +#if PLUGIN_BUFFER_SIZE - 0x4000 < 0x20000 +#define MAX_LEVEL_DATA (PLUGIN_BUFFER_SIZE - 0x4000) +#else +#define MAX_LEVEL_DATA 0x20000 +#endif + +/* Number of levels for which to allocate buffer indexes */ +#define MAX_LEVELS MAX_LEVEL_DATA/70 + +/* Use 4k plus remaining plugin buffer (-12k for prog) for undo, up to 64k */ +#if PLUGIN_BUFFER_SIZE - MAX_LEVEL_DATA - 0x3000 > 0x10000 +#define MAX_UNDOS 0x10000 #else -#define MAX_UNDOS PLUGIN_BUFFER_SIZE - \ - NUM_BUFFERED_BOARDS*SOKOBAN_LEVEL_SIZE - 0x2000 +#define MAX_UNDOS (PLUGIN_BUFFER_SIZE - MAX_LEVEL_DATA - 0x3000) #endif /* Move/push definitions for undo */ -enum { - SOKOBAN_PUSH_LEFT, - SOKOBAN_PUSH_RIGHT, - SOKOBAN_PUSH_UP, - SOKOBAN_PUSH_DOWN, - SOKOBAN_MOVE_LEFT, - SOKOBAN_MOVE_RIGHT, - SOKOBAN_MOVE_UP, - SOKOBAN_MOVE_DOWN -}; -#define SOKOBAN_MOVE_DIFF (SOKOBAN_MOVE_LEFT-SOKOBAN_PUSH_LEFT) -#define SOKOBAN_MOVE_MIN SOKOBAN_MOVE_LEFT +#define SOKOBAN_PUSH_LEFT 'L' +#define SOKOBAN_PUSH_RIGHT 'R' +#define SOKOBAN_PUSH_UP 'U' +#define SOKOBAN_PUSH_DOWN 'D' +#define SOKOBAN_MOVE_LEFT 'l' +#define SOKOBAN_MOVE_RIGHT 'r' +#define SOKOBAN_MOVE_UP 'u' +#define SOKOBAN_MOVE_DOWN 'd' + +#define SOKOBAN_MOVE_DIFF (SOKOBAN_MOVE_LEFT-SOKOBAN_PUSH_LEFT) +#define SOKOBAN_MOVE_MIN SOKOBAN_MOVE_DOWN /* variable button definitions */ -#if CONFIG_KEYPAD == RECORDER_PAD +#if (CONFIG_KEYPAD == RECORDER_PAD) || \ + (CONFIG_KEYPAD == ARCHOS_AV300_PAD) #define SOKOBAN_UP BUTTON_UP #define SOKOBAN_DOWN BUTTON_DOWN -#define SOKOBAN_QUIT BUTTON_OFF +#define SOKOBAN_MENU BUTTON_OFF #define SOKOBAN_UNDO BUTTON_ON #define SOKOBAN_REDO BUTTON_PLAY -#define SOKOBAN_LEVEL_UP BUTTON_F3 #define SOKOBAN_LEVEL_DOWN BUTTON_F1 #define SOKOBAN_LEVEL_REPEAT BUTTON_F2 - -#elif CONFIG_KEYPAD == ARCHOS_AV300_PAD -#define SOKOBAN_UP BUTTON_UP -#define SOKOBAN_DOWN BUTTON_DOWN -#define SOKOBAN_QUIT BUTTON_OFF -#define SOKOBAN_UNDO BUTTON_ON -#define SOKOBAN_REDO BUTTON_PLAY #define SOKOBAN_LEVEL_UP BUTTON_F3 -#define SOKOBAN_LEVEL_DOWN BUTTON_F1 -#define SOKOBAN_LEVEL_REPEAT BUTTON_F2 +#define BUTTON_SAVE BUTTON_ON +#define BUTTON_SAVE_NAME "ON" #elif CONFIG_KEYPAD == ONDIO_PAD #define SOKOBAN_UP BUTTON_UP #define SOKOBAN_DOWN BUTTON_DOWN -#define SOKOBAN_QUIT BUTTON_OFF +#define SOKOBAN_MENU BUTTON_OFF #define SOKOBAN_UNDO_PRE BUTTON_MENU #define SOKOBAN_UNDO (BUTTON_MENU | BUTTON_REL) #define SOKOBAN_REDO (BUTTON_MENU | BUTTON_DOWN) -#define SOKOBAN_LEVEL_UP (BUTTON_MENU | BUTTON_RIGHT) #define SOKOBAN_LEVEL_DOWN (BUTTON_MENU | BUTTON_LEFT) #define SOKOBAN_LEVEL_REPEAT (BUTTON_MENU | BUTTON_UP) +#define SOKOBAN_LEVEL_UP (BUTTON_MENU | BUTTON_RIGHT) +#define BUTTON_SAVE BUTTON_MENU +#define BUTTON_SAVE_NAME "MENU" #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \ (CONFIG_KEYPAD == IRIVER_H300_PAD) #define SOKOBAN_UP BUTTON_UP #define SOKOBAN_DOWN BUTTON_DOWN -#define SOKOBAN_QUIT BUTTON_OFF +#define SOKOBAN_MENU BUTTON_OFF #define SOKOBAN_UNDO BUTTON_REC #define SOKOBAN_REDO BUTTON_MODE -#define SOKOBAN_LEVEL_UP (BUTTON_ON | BUTTON_UP) #define SOKOBAN_LEVEL_DOWN (BUTTON_ON | BUTTON_DOWN) #define SOKOBAN_LEVEL_REPEAT BUTTON_ON +#define SOKOBAN_LEVEL_UP (BUTTON_ON | BUTTON_UP) +#define BUTTON_SAVE BUTTON_MODE +#define BUTTON_SAVE_NAME "MODE" #define SOKOBAN_RC_QUIT BUTTON_RC_STOP @@ -115,65 +147,70 @@ enum { (CONFIG_KEYPAD == IPOD_3G_PAD) #define SOKOBAN_UP BUTTON_MENU #define SOKOBAN_DOWN BUTTON_PLAY -#define SOKOBAN_QUIT (BUTTON_SELECT | BUTTON_MENU) +#define SOKOBAN_MENU (BUTTON_SELECT | BUTTON_MENU) #define SOKOBAN_UNDO_PRE BUTTON_SELECT #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL) #define SOKOBAN_REDO (BUTTON_SELECT | BUTTON_PLAY) -#define SOKOBAN_LEVEL_UP (BUTTON_SELECT | BUTTON_RIGHT) #define SOKOBAN_LEVEL_DOWN (BUTTON_SELECT | BUTTON_LEFT) +#define SOKOBAN_LEVEL_UP (BUTTON_SELECT | BUTTON_RIGHT) +#define BUTTON_SAVE BUTTON_SELECT +#define BUTTON_SAVE_NAME "SELECT" -/* fixme: if/when simultaneous button presses work for X5, - add redo & level repeat */ -#elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD) +/* FIXME: if/when simultaneous button presses work for X5, + * add redo & level repeat */ +#elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD #define SOKOBAN_UP BUTTON_UP #define SOKOBAN_DOWN BUTTON_DOWN -#define SOKOBAN_QUIT BUTTON_POWER +#define SOKOBAN_MENU BUTTON_POWER #define SOKOBAN_UNDO_PRE BUTTON_SELECT #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL) -#define SOKOBAN_LEVEL_UP BUTTON_PLAY #define SOKOBAN_LEVEL_DOWN BUTTON_REC +#define SOKOBAN_LEVEL_UP BUTTON_PLAY +#define BUTTON_SAVE BUTTON_SELECT +#define BUTTON_SAVE_NAME "SELECT" -#elif (CONFIG_KEYPAD == GIGABEAT_PAD) +#elif CONFIG_KEYPAD == IRIVER_H10_PAD +#define SOKOBAN_UP BUTTON_SCROLL_UP +#define SOKOBAN_DOWN BUTTON_SCROLL_DOWN +#define SOKOBAN_MENU BUTTON_POWER +#define SOKOBAN_UNDO_PRE BUTTON_REW +#define SOKOBAN_UNDO (BUTTON_REW | BUTTON_REL) +#define SOKOBAN_REDO BUTTON_FF +#define SOKOBAN_LEVEL_DOWN (BUTTON_PLAY | BUTTON_SCROLL_DOWN) +#define SOKOBAN_LEVEL_REPEAT (BUTTON_PLAY | BUTTON_RIGHT) +#define SOKOBAN_LEVEL_UP (BUTTON_PLAY | BUTTON_SCROLL_UP) +#define BUTTON_SAVE BUTTON_PLAY +#define BUTTON_SAVE_NAME "PLAY" + +#elif CONFIG_KEYPAD == GIGABEAT_PAD #define SOKOBAN_UP BUTTON_UP #define SOKOBAN_DOWN BUTTON_DOWN -#define SOKOBAN_QUIT BUTTON_POWER +#define SOKOBAN_MENU BUTTON_POWER #define SOKOBAN_UNDO BUTTON_SELECT #define SOKOBAN_REDO BUTTON_A -#define SOKOBAN_LEVEL_UP BUTTON_VOL_UP #define SOKOBAN_LEVEL_DOWN BUTTON_VOL_DOWN #define SOKOBAN_LEVEL_REPEAT BUTTON_MENU +#define SOKOBAN_LEVEL_UP BUTTON_VOL_UP +#define BUTTON_SAVE BUTTON_SELECT +#define BUTTON_SAVE_NAME "SELECT" -#elif (CONFIG_KEYPAD == SANSA_E200_PAD) +#elif CONFIG_KEYPAD == SANSA_E200_PAD #define SOKOBAN_UP BUTTON_UP #define SOKOBAN_DOWN BUTTON_DOWN -#define SOKOBAN_QUIT BUTTON_POWER +#define SOKOBAN_MENU BUTTON_POWER #define SOKOBAN_UNDO_PRE BUTTON_SELECT #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL) #define SOKOBAN_REDO BUTTON_REC -#define SOKOBAN_LEVEL_UP (BUTTON_SELECT | BUTTON_UP) #define SOKOBAN_LEVEL_DOWN (BUTTON_SELECT | BUTTON_DOWN) #define SOKOBAN_LEVEL_REPEAT (BUTTON_SELECT | BUTTON_RIGHT) - -#elif (CONFIG_KEYPAD == IRIVER_H10_PAD) -#define SOKOBAN_UP BUTTON_SCROLL_UP -#define SOKOBAN_DOWN BUTTON_SCROLL_DOWN -#define SOKOBAN_QUIT BUTTON_POWER -#define SOKOBAN_UNDO_PRE BUTTON_REW -#define SOKOBAN_UNDO (BUTTON_REW | BUTTON_REL) -#define SOKOBAN_REDO BUTTON_FF -#define SOKOBAN_LEVEL_UP (BUTTON_PLAY | BUTTON_SCROLL_UP) -#define SOKOBAN_LEVEL_DOWN (BUTTON_PLAY | BUTTON_SCROLL_DOWN) -#define SOKOBAN_LEVEL_REPEAT (BUTTON_PLAY | BUTTON_RIGHT) +#define SOKOBAN_LEVEL_UP (BUTTON_SELECT | BUTTON_UP) +#define BUTTON_SAVE BUTTON_SELECT +#define BUTTON_SAVE_NAME "SELECT" #endif -#ifdef HAVE_LCD_COLOR -/* Background color. Default Rockbox light blue. */ -#define BG_COLOR LCD_RGBPACK(181, 199, 231) +#define SOKOBAN_FONT FONT_SYSFIXED -#elif LCD_DEPTH >= 2 -#define MEDIUM_GRAY LCD_BRIGHTNESS(127) -#endif /* The Location, Undo and LevelInfo structs are OO-flavored. * (oooh!-flavored as Schnueff puts it.) It makes more you have to know, @@ -181,10 +218,12 @@ enum { /* Level data & stats */ struct LevelInfo { - short level; - short moves; - short pushes; - short boxes_to_go; + short index; /* Level index (level number - 1) */ + int moves; /* Moves & pushes for the stats */ + int pushes; + short boxes_to_go; /* Number of unplaced boxes remaining in level */ + short height; /* Height & width for centering level display */ + short width; }; struct Location { @@ -192,39 +231,42 @@ struct Location { short col; }; -struct Board { - char spaces[ROWS][COLS]; -}; - /* Our full undo history */ static struct UndoInfo { - short count; /* How many undos are left */ - short current; /* Which history is the current undo */ - short max; /* Which history is the max redoable */ + int count; /* How many undos have been done */ + int current; /* Which history is the current undo */ + int max; /* Which history is the max redoable */ char history[MAX_UNDOS]; } undo_info; /* Our playing board */ static struct BoardInfo { - char board[ROWS][COLS]; - struct LevelInfo level; - struct Location player; - int max_level; /* How many levels do we have? */ - int loaded_level; /* Which level is in memory */ + char board[ROWS][COLS]; /* The current board data */ + struct LevelInfo level; /* Level data & stats */ + struct Location player; /* Where the player is */ + int max_level; /* The number of levels we have */ } current_info; static struct BufferedBoards { - struct Board levels[NUM_BUFFERED_BOARDS]; - int low; + char filename[MAX_PATH]; /* Filename of the levelset we're using */ + char data[MAX_LEVEL_DATA]; /* Buffered level data */ + int index[MAX_LEVELS + 1]; /* Where each buffered board begins & ends */ + int start; /* Index of first buffered board */ + int end; /* Index of last buffered board */ + short prebuffered_boards; /* Number of boards before current to store */ } buffered_boards; + static struct plugin_api* rb; +static char buf[ROWS*(COLS + 1)]; /* Enough for a whole board or a filename */ + + static void init_undo(void) { undo_info.count = 0; - undo_info.current = -1; - undo_info.max = -1; + undo_info.current = 0; + undo_info.max = 0; } static void get_delta(char direction, short *d_r, short *d_c) @@ -261,9 +303,17 @@ static void undo(void) bool undo_push = false; /* If no more undos or we've wrapped all the way around, quit */ - if (undo_info.count == 0 || undo_info.current-1 == undo_info.max) + if (undo_info.count == 0 || undo_info.current - 1 == undo_info.max) return; + /* Move to previous undo in the list */ + if (undo_info.current == 0 && undo_info.count > 1) + undo_info.current = MAX_UNDOS - 1; + else + undo_info.current--; + + undo_info.count--; + undo = undo_info.history[undo_info.current]; if (undo < SOKOBAN_MOVE_MIN) @@ -281,17 +331,17 @@ static void undo(void) /* Update board info */ if (undo_push) { - /* Moving box from goal to blank */ - if (*space_next == '%' && *space_cur == '@') + /* Moving box from goal to floor */ + if (*space_next == '*' && *space_cur == '@') current_info.level.boxes_to_go++; - /* Moving box from blank to goal */ + /* Moving box from floor to goal */ else if (*space_next == '$' && *space_cur == '+') current_info.level.boxes_to_go--; /* Move box off of next space... */ - *space_next = (*space_next == '%' ? '.' : ' '); + *space_next = (*space_next == '*' ? '.' : ' '); /* ...and on to current space */ - *space_cur = (*space_cur == '+' ? '%' : '$'); + *space_cur = (*space_cur == '+' ? '*' : '$'); current_info.level.pushes--; } else @@ -306,27 +356,19 @@ static void undo(void) current_info.level.moves--; - /* Move to previous undo in the list */ - if (undo_info.current == 0 && undo_info.count > 1) - undo_info.current = MAX_UNDOS - 1; - else - undo_info.current--; - - undo_info.count--; - return; } static void add_undo(char undo) { + undo_info.history[undo_info.current] = undo; + /* Wrap around if MAX_UNDOS exceeded */ if (undo_info.current < (MAX_UNDOS - 1)) undo_info.current++; else undo_info.current = 0; - undo_info.history[undo_info.current] = undo; - if (undo_info.count < MAX_UNDOS) undo_info.count++; } @@ -353,34 +395,37 @@ static bool move(char direction, bool redo) space_next = ¤t_info.board[r + d_r][c + d_c]; space_beyond = ¤t_info.board[r + 2*d_r][c + 2*d_c]; - if (*space_next == '$' || *space_next == '%') { + if (*space_next == '$' || *space_next == '*') { /* Change direction from move to push for undo */ if (direction >= SOKOBAN_MOVE_MIN) direction -= SOKOBAN_MOVE_DIFF; push = true; } + else if (direction < SOKOBAN_MOVE_MIN) + /* Change back to move if redo/solution playback push is invalid */ + direction += SOKOBAN_MOVE_DIFF; /* Update board info */ if (push) { - /* Moving box from goal to blank */ - if (*space_next == '%' && *space_beyond == ' ') + /* Moving box from goal to floor */ + if (*space_next == '*' && *space_beyond == ' ') current_info.level.boxes_to_go++; - /* Moving box from blank to goal */ + /* Moving box from floor to goal */ else if (*space_next == '$' && *space_beyond == '.') current_info.level.boxes_to_go--; - /* Check for illegal move */ + /* Check for invalid move */ else if (*space_beyond != '.' && *space_beyond != ' ') return false; /* Move player onto next space */ - *space_next = (*space_next == '%' ? '+' : '@'); + *space_next = (*space_next == '*' ? '+' : '@'); /* Move box onto space beyond next */ - *space_beyond = (*space_beyond == '.' ? '%' : '$'); + *space_beyond = (*space_beyond == '.' ? '*' : '$'); current_info.level.pushes++; } else { - /* Check for illegal move */ - if (*space_next == '#' || *space_next == 'X') + /* Check for invalid move */ + if (*space_next != '.' && *space_next != ' ') return false; /* Move player onto next space */ @@ -396,19 +441,19 @@ static bool move(char direction, bool redo) current_info.level.moves++; /* Update undo_info.max to current on every normal move, - except if it's the same as a redo. */ - /* normal move */ + * except if it's the same as a redo. */ + /* normal move and either */ if (!redo && - /* moves have been undone */ + /* moves have been undone... */ ((undo_info.max != undo_info.current && - /* and the current move is NOT the same as the one in history */ - undo_info.history[undo_info.current+1] != direction) || + /* ...and the current move is NOT the same as the one in history */ + undo_info.history[undo_info.current] != direction) || /* or moves have not been undone */ undo_info.max == undo_info.current)) { add_undo(direction); undo_info.max = undo_info.current; } else /* redo move or move was same as redo */ - add_undo(direction); /* (just to update current) */ + add_undo(direction); /* add_undo to update current */ return true; } @@ -420,290 +465,370 @@ static bool redo(void) if (undo_info.current == undo_info.max) return false; - return move(undo_info.history[(undo_info.current+1 < MAX_UNDOS ? - undo_info.current+1 : 0)], true); + return move(undo_info.history[(undo_info.current < MAX_UNDOS ? + undo_info.current : 0)], true); } #endif static void init_boards(void) { - current_info.level.level = 0; - current_info.level.moves = 0; - current_info.level.pushes = 0; - current_info.level.boxes_to_go = 0; + rb->strncpy(buffered_boards.filename, SOKOBAN_LEVELS_FILE, MAX_PATH); + + current_info.level.index = 0; current_info.player.row = 0; current_info.player.col = 0; current_info.max_level = 0; - current_info.loaded_level = 0; - buffered_boards.low = 0; + buffered_boards.start = 0; + buffered_boards.end = 0; + buffered_boards.prebuffered_boards = 0; init_undo(); } -static int read_levels(int initialize_count) +static bool read_levels(bool initialize) { int fd = 0; - int len; - int lastlen = 0; - int row = 0; + short len; + short lastlen = 0; + short row = 0; int level_count = 0; - char buffer[COLS + 3]; /* COLS plus CR/LF and \0 */ - int endpoint = current_info.level.level-1; - - if (endpoint < buffered_boards.low) - endpoint = current_info.level.level - NUM_BUFFERED_BOARDS; - if (endpoint < 0) endpoint = 0; + int i = 0; + int level_len = 0; + bool index_set = false; - buffered_boards.low = endpoint; - endpoint += NUM_BUFFERED_BOARDS; + /* Get the index of the first level to buffer */ + if (current_info.level.index > buffered_boards.prebuffered_boards && + !initialize) + buffered_boards.start = current_info.level.index - + buffered_boards.prebuffered_boards; + else + buffered_boards.start = 0; - if ((fd = rb->open(LEVELS_FILE, O_RDONLY)) < 0) { - rb->splash(HZ*2, "Unable to open %s", LEVELS_FILE); - return -1; + if ((fd = rb->open(buffered_boards.filename, O_RDONLY)) < 0) { + rb->splash(HZ*2, "Unable to open %s", buffered_boards.filename); + return false; } do { - len = rb->read_line(fd, buffer, sizeof(buffer)); - if (len >= 3) { - /* This finds lines that are more than 1 or 2 characters - * shorter than they should be. Due to the possibility of - * a mixed unix and dos CR/LF file format, I'm not going to - * do a precise check */ - if (len < COLS) { - rb->splash(HZ*2, "Error in levels file: short line"); - return -1; - } - if (level_count >= buffered_boards.low && level_count < endpoint) { - int index = level_count - buffered_boards.low; - rb->memcpy( - buffered_boards.levels[index].spaces[row], buffer, COLS); + len = rb->read_line(fd, buf, sizeof(buf)); + + /* Correct len when trailing \r's or \n's are counted */ + if (len > 2 && buf[len - 2] == '\0') + len -= 2; + else if (len > 1 && buf[len - 1] == '\0') + len--; + + /* Skip short lines & lines with non-level data */ + if (len >= 3 && ((buf[0] >= '1' && buf[0] <= '9') || buf[0] == '#' || + buf[0] == ' ' || buf[0] == '-' || buf[0] == '_')) { + if (level_count >= buffered_boards.start) { + /* Set the index of this level */ + if (!index_set && + level_count - buffered_boards.start < MAX_LEVELS) { + buffered_boards.index[level_count - buffered_boards.start] + = i; + index_set = true; + } + /* Copy buffer to board data */ + if (i + level_len + len < MAX_LEVEL_DATA) { + rb->memcpy(&buffered_boards.data[i + level_len], buf, len); + buffered_boards.data[i + level_len + len] = '\n'; + } } + level_len += len + 1; row++; - } else if (len) { - if (lastlen < 3) { - /* Two short lines in a row means new level */ - level_count++; - if (level_count >= endpoint && !initialize_count) break; - if (level_count && row != ROWS) { - rb->splash(HZ*2, "Error in levels file: short board"); - return -1; - } - row = 0; + + /* If newline & level is tall enough or is RLE */ + } else if (buf[0] == '\0' && (row > 2 || lastlen > 22)) { + level_count++; + if (level_count >= buffered_boards.start) { + i += level_len; + if (i < MAX_LEVEL_DATA) + buffered_boards.end = level_count; + else if (!initialize) + break; } - } - } while ((lastlen=len)); + row = 0; + level_len = 0; + index_set = false; - rb->close(fd); - if (initialize_count) { - /* Plus one because there aren't trailing short lines in the file */ - current_info.max_level = level_count + 1; + } else if (len > 22) + len = 1; + + } while ((lastlen = len)); + + /* Set the index of the end of the last level */ + if (level_count - buffered_boards.start < MAX_LEVELS) + buffered_boards.index[level_count - buffered_boards.start] = i; + + if (initialize) { + current_info.max_level = level_count; + buffered_boards.prebuffered_boards = buffered_boards.end/2; } - return 0; + + rb->close(fd); + + return true; } -/* return non-zero on error */ static void load_level(void) { - int c = 0; - int r = 0; - int index = current_info.level.level - buffered_boards.low - 1; - struct Board *level; - - if (index < 0 || index >= NUM_BUFFERED_BOARDS) { + int c, r; + int i, n; + int level_size; + int index = current_info.level.index - buffered_boards.start; + char *level; + + /* Get the buffered board index of the current level */ + if (current_info.level.index < buffered_boards.start || + current_info.level.index >= buffered_boards.end) { read_levels(false); - index = index < 0 ? NUM_BUFFERED_BOARDS-1 : 0; + if (current_info.level.index > buffered_boards.prebuffered_boards) + index = buffered_boards.prebuffered_boards; + else + index = current_info.level.index; } - level = &buffered_boards.levels[index]; + level = &buffered_boards.data[buffered_boards.index[index]]; - current_info.level.boxes_to_go = 0; + /* Reset level info */ current_info.level.moves = 0; current_info.level.pushes = 0; - current_info.loaded_level = current_info.level.level; + current_info.level.boxes_to_go = 0; + current_info.level.width = 0; + + /* Clear board */ + for (r = 0; r < ROWS; r++) + for (c = 0; c < COLS; c++) + current_info.board[r][c] = 'X'; + + level_size = buffered_boards.index[index + 1] - + buffered_boards.index[index]; + + for (r = 0, c = 0, n = 1, i = 0; i < level_size; i++) { + if (level[i] == '\n' || level[i] == '|') { + if (c > 3) { + /* Update max width of level & go to next row */ + if (c > current_info.level.width) + current_info.level.width = c; + c = 0; + r++; + if (r >= ROWS) + break; + } + } else if (c < COLS) { + /* Read RLE character's length into n */ + if (level[i] >= '0' && level[i] <= '9') { + n = level[i++] - '0'; + if (level[i] >= '0' && level[i] <= '9') + n = n*10 + level[i++] - '0'; + } + + /* Cleanup & replace */ + if (level[i] == '%') + level[i] = '*'; + else if (level[i] == '-' || level[i] == '_') + level[i] = ' '; + + if (n > 1) { + if (c + n >= COLS) + n = COLS - c; - for (r = 0; r < ROWS; r++) { - for (c = 0; c < COLS; c++) { - current_info.board[r][c] = level->spaces[r][c]; + if (level[i] == '.') + current_info.level.boxes_to_go += n; - if (current_info.board[r][c] == '.' || - current_info.board[r][c] == '+') - current_info.level.boxes_to_go++; + /* Put RLE character n times */ + while (n--) + current_info.board[r][c++] = level[i]; + n = 1; - if (current_info.board[r][c] == '@' || - current_info.board[r][c] == '+') { - current_info.player.row = r; - current_info.player.col = c; + } else { + if (level[i] == '.' || level[i] == '+') + current_info.level.boxes_to_go++; + + if (level[i] == '@' ||level[i] == '+') { + current_info.player.row = r; + current_info.player.col = c; + } + + current_info.board[r][c++] = level[i]; } } } + + current_info.level.height = r; + +#if LCD_DEPTH > 2 + /* Fill in blank space outside level on color targets */ + for (r = 0; r < ROWS; r++) + for (c = 0; current_info.board[r][c] == ' ' && c < COLS; c++) + current_info.board[r][c] = 'X'; + + for (c = 0; c < COLS; c++) { + for (r = 0; (current_info.board[r][c] == ' ' || + current_info.board[r][c] == 'X') && r < ROWS; r++) + current_info.board[r][c] = 'X'; + for (r = ROWS - 1; (current_info.board[r][c] == ' ' || + current_info.board[r][c] == 'X') && r >= 0; r--) + current_info.board[r][c] = 'X'; + } +#endif } static void update_screen(void) { - int b = 0, c = 0; - int rows = 0, cols = 0; - char s[25]; - -/* magnify is the number of pixels for each block */ -#if (LCD_HEIGHT >= 224) && (LCD_WIDTH >= 312) || \ - (LCD_HEIGHT >= 249) && (LCD_WIDTH >= 280) /* ipod 5g */ -#define MAGNIFY 14 -#elif (LCD_HEIGHT >= 144) && (LCD_WIDTH >= 212) || \ - (LCD_HEIGHT >= 169) && (LCD_WIDTH >= 180-4) /* h3x0, ipod color/photo */ -#define MAGNIFY 9 -#elif (LCD_HEIGHT >= 96) && (LCD_WIDTH >= 152) || \ - (LCD_HEIGHT >= 121) && (LCD_WIDTH >= 120) /* h1x0, ipod nano/mini */ -#define MAGNIFY 6 -#else /* other */ -#define MAGNIFY 4 -#endif + int c, r; + int rows, cols; -#if LCD_DEPTH < 2 +#if LCD_DEPTH < 2 || ((LCD_HEIGHT < 96 || LCD_WIDTH < 152) && \ + (LCD_HEIGHT < 121 || LCD_WIDTH < 120)) int i, j; int max = MAGNIFY - 1; - int middle = max / 2; - int ldelta = (middle + 1) / 2; + int middle = max/2; + int ldelta = (middle + 1)/2; #endif - /* load the board to the screen */ - for (rows=0; rows < ROWS; rows++) { - for (cols = 0; cols < COLS; cols++) { - c = cols * MAGNIFY; - b = rows * MAGNIFY; - - switch(current_info.board[rows][cols]) { - case 'X': /* black space */ - break; - - case '#': /* this is a wall */ -#if LCD_DEPTH >= 2 - rb->lcd_bitmap_part(sokoban_tiles, 0, 1*MAGNIFY, MAGNIFY, - c, b, MAGNIFY, MAGNIFY); +#if LCD_WIDTH - (COLS*MAGNIFY) < 32 +#define STAT_HEIGHT 25 +#define STAT_X (LCD_WIDTH - 120)/2 +#define STAT_Y (LCD_HEIGHT - STAT_HEIGHT) +#define BOARD_WIDTH LCD_WIDTH +#define BOARD_HEIGHT (LCD_HEIGHT - STAT_HEIGHT) + rb->lcd_putsxy(STAT_X + 4, STAT_Y + 4, "Level"); + rb->snprintf(buf, sizeof(buf), "%d", current_info.level.index + 1); + rb->lcd_putsxy(STAT_X + 7, STAT_Y + 14, buf); + rb->lcd_putsxy(STAT_X + 41, STAT_Y + 4, "Moves"); + rb->snprintf(buf, sizeof(buf), "%d", current_info.level.moves); + rb->lcd_putsxy(STAT_X + 44, STAT_Y + 14, buf); + rb->lcd_putsxy(STAT_X + 79, STAT_Y + 4, "Pushes"); + rb->snprintf(buf, sizeof(buf), "%d", current_info.level.pushes); + rb->lcd_putsxy(STAT_X + 82, STAT_Y + 14, buf); + + rb->lcd_drawrect(STAT_X, STAT_Y, 38, STAT_HEIGHT); + rb->lcd_drawrect(STAT_X + 37, STAT_Y, 39, STAT_HEIGHT); + rb->lcd_drawrect(STAT_X + 75, STAT_Y, 45, STAT_HEIGHT); #else - for (i = c; i < c + MAGNIFY; i++) - for (j = b; j < b + MAGNIFY; j++) - if ((i ^ j) & 1) - rb->lcd_drawpixel(i, j); +#if LCD_WIDTH - (COLS*MAGNIFY) > 40 +#define STAT_X (LCD_WIDTH - 40) +#else +#define STAT_X COLS*MAGNIFY #endif - break; - - case '$': /* this is a box */ -#if LCD_DEPTH >= 2 - rb->lcd_bitmap_part(sokoban_tiles, 0, 2*MAGNIFY, MAGNIFY, - c, b, MAGNIFY, MAGNIFY); +#if LCD_HEIGHT >= 70 +#define STAT_Y (LCD_HEIGHT - 70)/2 #else - /* Free boxes are not filled in */ - rb->lcd_drawrect(c, b, MAGNIFY, MAGNIFY); +#define STAT_Y (LCD_HEIGHT - 47)/2 #endif - break; +#define STAT_WIDTH (LCD_WIDTH - STAT_X) +#define BOARD_WIDTH (LCD_WIDTH - STAT_WIDTH) +#define BOARD_HEIGHT LCD_HEIGHT + rb->lcd_putsxy(STAT_X + 1, STAT_Y + 3, "Level"); + rb->snprintf(buf, sizeof(buf), "%d", current_info.level.index + 1); + rb->lcd_putsxy(STAT_X + 4, STAT_Y + 13, buf); + rb->lcd_putsxy(STAT_X + 1, STAT_Y + 26, "Moves"); + rb->snprintf(buf, sizeof(buf), "%d", current_info.level.moves); + rb->lcd_putsxy(STAT_X + 4, STAT_Y + 36, buf); + + rb->lcd_drawrect(STAT_X, STAT_Y + 0, STAT_WIDTH, 24); + rb->lcd_drawrect(STAT_X, STAT_Y + 23, STAT_WIDTH, 24); - case '*': - case '%': /* this is a box on a goal */ +#if LCD_HEIGHT >= 70 + rb->lcd_putsxy(STAT_X + 1, STAT_Y + 49, "Pushes"); + rb->snprintf(buf, sizeof(buf), "%d", current_info.level.pushes); + rb->lcd_putsxy(STAT_X + 4, STAT_Y + 59, buf); -#if LCD_DEPTH >= 2 - rb->lcd_bitmap_part(sokoban_tiles, 0, 3*MAGNIFY, MAGNIFY, - c, b, MAGNIFY, MAGNIFY ); -#else - rb->lcd_drawrect(c, b, MAGNIFY, MAGNIFY); - rb->lcd_drawrect(c+(MAGNIFY/2)-1, b+(MAGNIFY/2)-1, MAGNIFY/2, - MAGNIFY/2); + rb->lcd_drawrect(STAT_X, STAT_Y + 46, STAT_WIDTH, 24); #endif - break; - case '.': /* this is a goal */ -#if LCD_DEPTH >= 2 - rb->lcd_bitmap_part(sokoban_tiles, 0, 4*MAGNIFY, MAGNIFY, - c, b, MAGNIFY, MAGNIFY); -#else - rb->lcd_drawrect(c+(MAGNIFY/2)-1, b+(MAGNIFY/2)-1, MAGNIFY/2, - MAGNIFY/2); #endif - break; - case '@': /* this is you */ -#if LCD_DEPTH >= 2 - rb->lcd_bitmap_part(sokoban_tiles, 0, 5*MAGNIFY, MAGNIFY, - c, b, MAGNIFY, MAGNIFY); -#else - rb->lcd_drawline(c, b+middle, c+max, b+middle); - rb->lcd_drawline(c+middle, b, c+middle, b+max-ldelta); - rb->lcd_drawline(c+max-middle, b, - c+max-middle, b+max-ldelta); - rb->lcd_drawline(c+middle, b+max-ldelta, - c+middle-ldelta, b+max); - rb->lcd_drawline(c+max-middle, b+max-ldelta, - c+max-middle+ldelta, b+max); -#endif - break; + /* load the board to the screen */ + for (rows = 0; rows < ROWS; rows++) { + for (cols = 0; cols < COLS; cols++) { + c = cols*MAGNIFY + + (BOARD_WIDTH - current_info.level.width*MAGNIFY)/2; + r = rows*MAGNIFY + + (BOARD_HEIGHT - current_info.level.height*MAGNIFY)/2; - case '+': /* this is you on drugs, erm, on a goal */ -#if LCD_DEPTH >= 2 - rb->lcd_bitmap_part(sokoban_tiles, 0, 6*MAGNIFY, MAGNIFY, - c, b, MAGNIFY, MAGNIFY ); -#else - rb->lcd_drawline(c, b+middle, c+max, b+middle); - rb->lcd_drawline(c+middle, b, c+middle, b+max-ldelta); - rb->lcd_drawline(c+max-middle, b, c+max-middle, b+max-ldelta); - rb->lcd_drawline(c+middle, b+max-ldelta, c+middle-ldelta, - b+max); - rb->lcd_drawline(c+max-middle, b+max-ldelta, - c+max-middle+ldelta, b+max); - rb->lcd_drawline(c+middle-1, b+middle+1, c+max-middle+1, - b+middle+1); -#endif - break; + switch(current_info.board[rows][cols]) { + case 'X': /* blank space outside of level */ + break; -#if LCD_DEPTH >= 2 - default: - rb->lcd_bitmap_part(sokoban_tiles, 0, 0*MAGNIFY, MAGNIFY, - c, b, MAGNIFY, MAGNIFY ); -#endif - } - } - } +#if LCD_DEPTH >= 2 && ((LCD_HEIGHT >= 96 && LCD_WIDTH >= 152) || \ + (LCD_HEIGHT >= 121 && LCD_WIDTH >= 120)) + case ' ': /* floor */ + rb->lcd_bitmap_part(sokoban_tiles, 0, 0*MAGNIFY, MAGNIFY, + c, r, MAGNIFY, MAGNIFY); + break; -#if LCD_WIDTH-(COLS*MAGNIFY) < 32 -#define STAT_SIZE 25 -#define STAT_POS LCD_HEIGHT-STAT_SIZE -#define STAT_CENTER (LCD_WIDTH-120)/2 - - rb->lcd_putsxy(4+STAT_CENTER, STAT_POS+4, "Level"); - rb->snprintf(s, sizeof(s), "%d", current_info.level.level); - rb->lcd_putsxy(7+STAT_CENTER, STAT_POS+14, s); - rb->lcd_putsxy(41+STAT_CENTER, STAT_POS+4, "Moves"); - rb->snprintf(s, sizeof(s), "%d", current_info.level.moves); - rb->lcd_putsxy(44+STAT_CENTER, STAT_POS+14, s); - rb->lcd_putsxy(79+STAT_CENTER, STAT_POS+4, "Pushes"); - rb->snprintf(s, sizeof(s), "%d", current_info.level.pushes); - rb->lcd_putsxy(82+STAT_CENTER, STAT_POS+14, s); - - rb->lcd_drawrect(STAT_CENTER, STAT_POS, 38, STAT_SIZE); - rb->lcd_drawrect(37+STAT_CENTER, STAT_POS, 39, STAT_SIZE); - rb->lcd_drawrect(75+STAT_CENTER, STAT_POS, 45, STAT_SIZE); + case '#': /* wall */ + rb->lcd_bitmap_part(sokoban_tiles, 0, 1*MAGNIFY, MAGNIFY, + c, r, MAGNIFY, MAGNIFY); + break; + + case '$': /* box */ + rb->lcd_bitmap_part(sokoban_tiles, 0, 2*MAGNIFY, MAGNIFY, + c, r, MAGNIFY, MAGNIFY); + break; + case '*': /* box on goal */ + rb->lcd_bitmap_part(sokoban_tiles, 0, 3*MAGNIFY, MAGNIFY, + c, r, MAGNIFY, MAGNIFY); + break; + + case '.': /* goal */ + rb->lcd_bitmap_part(sokoban_tiles, 0, 4*MAGNIFY, MAGNIFY, + c, r, MAGNIFY, MAGNIFY); + break; + + case '@': /* player */ + rb->lcd_bitmap_part(sokoban_tiles, 0, 5*MAGNIFY, MAGNIFY, + c, r, MAGNIFY, MAGNIFY); + break; + + case '+': /* player on goal */ + rb->lcd_bitmap_part(sokoban_tiles, 0, 6*MAGNIFY, MAGNIFY, + c, r, MAGNIFY, MAGNIFY); + break; #else -#define STAT_POS COLS*MAGNIFY -#define STAT_SIZE LCD_WIDTH-STAT_POS + case '#': /* wall */ + for (i = c; i < c + MAGNIFY; i++) + for (j = r; j < r + MAGNIFY; j++) + if ((i ^ j) & 1) + rb->lcd_drawpixel(i, j); + break; - rb->lcd_putsxy(STAT_POS+1, 3, "Level"); - rb->snprintf(s, sizeof(s), "%d", current_info.level.level); - rb->lcd_putsxy(STAT_POS+4, 13, s); - rb->lcd_putsxy(STAT_POS+1, 26, "Moves"); - rb->snprintf(s, sizeof(s), "%d", current_info.level.moves); - rb->lcd_putsxy(STAT_POS+4, 36, s); + case '$': /* box */ + rb->lcd_drawrect(c, r, MAGNIFY, MAGNIFY); + break; - rb->lcd_drawrect(STAT_POS, 0, STAT_SIZE, 24); - rb->lcd_drawrect(STAT_POS, 23, STAT_SIZE, 24); + case '*': /* box on goal */ + rb->lcd_drawrect(c, r, MAGNIFY, MAGNIFY); + rb->lcd_drawrect(c + MAGNIFY/2 - 1, r + MAGNIFY/2 - 1, + MAGNIFY/2, MAGNIFY/2); + break; -#if LCD_HEIGHT >= 70 - rb->lcd_putsxy(STAT_POS+1, 49, "Pushes"); - rb->snprintf(s, sizeof(s), "%d", current_info.level.pushes); - rb->lcd_putsxy(STAT_POS+4, 59, s); + case '.': /* goal */ + rb->lcd_drawrect(c + MAGNIFY/2 - 1, r + MAGNIFY/2 - 1, + MAGNIFY/2, MAGNIFY/2); + break; - rb->lcd_drawrect(STAT_POS, 46, STAT_SIZE, 24); -#endif + case '@': /* player */ + case '+': /* player on goal */ + rb->lcd_drawline(c, r + middle, c + max, r + middle); + rb->lcd_drawline(c + middle, r, c + middle, + r + max - ldelta); + rb->lcd_drawline(c + max - middle, r, c + max - middle, + r + max - ldelta); + rb->lcd_drawline(c + middle, r + max - ldelta, + c + middle - ldelta, r + max); + rb->lcd_drawline(c + max - middle, r + max - ldelta, + c + max - middle + ldelta, r + max); + break; #endif + } + } + } /* print out the screen */ rb->lcd_update(); @@ -716,21 +841,320 @@ static void draw_level(void) update_screen(); } +static bool save(char *filename, bool solution) +{ + int fd; + + rb->splash(0, "Saving..."); + + if (filename[0] == '\0' || + (fd = rb->open(filename, O_WRONLY|O_CREAT|O_TRUNC)) < 0) { + rb->splash(HZ*2, "Unable to open %s", filename); + return false; + } + + /* Sokoban: S/P for solution/progress : level number : current undo */ + rb->snprintf(buf, sizeof(buf), "Sokoban:%c:%d:%d\n", (solution ? 'S' : 'P'), + current_info.level.index + 1, undo_info.current); + rb->write(fd, buf, rb->strlen(buf)); + + /* Filename of levelset */ + rb->write(fd, buffered_boards.filename, + rb->strlen(buffered_boards.filename)); + rb->write(fd, "\n", 1); + + /* Full undo history */ + rb->write(fd, undo_info.history, undo_info.max); + + rb->close(fd); + + return true; +} + +static bool load(char *filename, bool silent) +{ + int fd; + int i = 0, n; + int len; + bool play_solution; + int button; + int step_delay = HZ/4; + + if (filename[0] == '\0' || (fd = rb->open(filename, O_RDONLY)) < 0) { + if (!silent) + rb->splash(HZ*2, "Unable to open %s", filename); + return false; + } + + /* Read header, level number, & current undo */ + rb->read_line(fd, buf, sizeof(buf)); + + /* If we're opening a level file, not a solution/progress file */ + if (rb->strncmp(buf, "Sokoban", 7) != 0) { + rb->close(fd); + + rb->strncpy(buffered_boards.filename, filename, MAX_PATH); + if (!read_levels(true)) + return false; + + current_info.level.index = 0; + load_level(); + + /* If there aren't any boxes to go or the player position wasn't set, + * the file probably wasn't a Sokoban level file */ + if (current_info.level.boxes_to_go == 0 || + current_info.player.row == 0 || current_info.player.col == 0) { + if (!silent) + rb->splash(HZ*2, "File is not a Sokoban level file"); + return false; + } + + } else { + + /* Read filename of levelset */ + rb->read_line(fd, buffered_boards.filename, + sizeof(buffered_boards.filename)); + + /* Read full undo history */ + len = rb->read_line(fd, undo_info.history, MAX_UNDOS); + + /* Correct len when trailing \r's or \n's are counted */ + if (len > 2 && undo_info.history[len - 2] == '\0') + len -= 2; + else if (len > 1 && undo_info.history[len - 1] == '\0') + len--; + + rb->close(fd); + + /* Check to see if we're going to play a solution or resume progress */ + play_solution = (buf[8] == 'S'); + + /* Get level number */ + for (n = 0, i = 10; buf[i] >= '0' && buf[i] <= '9' && i < 15; i++) + n = n*10 + buf[i] - '0'; + current_info.level.index = n - 1; + + /* Get current undo index */ + for (n = 0, i++; buf[i] >= '0' && buf[i] <= '9' && i < 21; i++) + n = n*10 + buf[i] - '0'; + if (n > len) + n = len; + + if (current_info.level.index < 0) { + if (!silent) + rb->splash(HZ*2, "Error loading level"); + return false; + } + if (!read_levels(true)) + return false; + if (current_info.level.index >= current_info.max_level) { + if (!silent) + rb->splash(HZ*2, "Error loading level"); + return false; + } + + load_level(); + + if (play_solution) { + rb->lcd_clear_display(); + update_screen(); + rb->sleep(2*step_delay); + + /* Replay solution until the end or quit button is pressed */ + for (i = 0; i < len; i++) { + if (!move(undo_info.history[i], true)) { + n = i; + break; + } + + rb->lcd_clear_display(); + update_screen(); + rb->sleep(step_delay); + + /* Ignore keypresses except for quit & changing speed */ + while ((button = rb->button_get(false)) != BUTTON_NONE) { + switch (button) { + case SOKOBAN_MENU: + /* Pretend the level is complete so we'll quit */ + current_info.level.boxes_to_go = 0; + return true; + + case SOKOBAN_UP: + if (step_delay > HZ/12) + step_delay = 5*step_delay/6; + break; + + case SOKOBAN_DOWN: + if (step_delay < 3*HZ/4) + step_delay = 6*step_delay/5; + } + } + } + + /* If level complete, wait for keypress before quitting */ + if (current_info.level.boxes_to_go == 0) + rb->button_get(true); + + } else { + /* Advance to current undo */ + for (i = 0; i < n; i++) { + if (!move(undo_info.history[i], true)) { + n = i; + break; + } + } + + rb->button_clear_queue(); + rb->lcd_clear_display(); + } + + undo_info.max = len; + undo_info.current = n; + } + + return true; +} + +static int sokoban_menu(void) +{ + int button; + int selection = 0; + int i; + bool menu_quit; + int start_selected = 0; + + MENUITEM_STRINGLIST(menu, "Sokoban Menu", NULL, + "Resume", "Audio Playback", "Keys", + "Load Default Level Set", "Quit Without Saving", + "Save Progress & Quit"); + + do { + menu_quit = true; + selection = rb->do_menu(&menu, &start_selected); + + switch (selection) { + case 0: /* Resume */ + break; + + case 1: /* Audio playback control */ + playback_control(rb); + menu_quit = false; + break; + + case 2: /* Keys */ + FOR_NB_SCREENS(i) + rb->screens[i]->clear_display(); + rb->lcd_setfont(SOKOBAN_FONT); + +#if (CONFIG_KEYPAD == RECORDER_PAD) || \ + (CONFIG_KEYPAD == ARCHOS_AV300_PAD) + rb->lcd_putsxy(3, 6, "[OFF] Menu"); + rb->lcd_putsxy(3, 16, "[ON] Undo"); + rb->lcd_putsxy(3, 26, "[PLAY] Redo"); + rb->lcd_putsxy(3, 36, "[F1] Down a Level"); + rb->lcd_putsxy(3, 46, "[F2] Restart Level"); + rb->lcd_putsxy(3, 56, "[F3] Up a Level"); +#elif CONFIG_KEYPAD == ONDIO_PAD + rb->lcd_putsxy(3, 6, "[OFF] Menu"); + rb->lcd_putsxy(3, 16, "[MODE] Undo"); + rb->lcd_putsxy(3, 26, "[MODE+DOWN] Redo"); + rb->lcd_putsxy(3, 36, "[MODE+LEFT] Previous Level"); + rb->lcd_putsxy(3, 46, "[MODE+UP] Restart Level"); + rb->lcd_putsxy(3, 56, "[MODE+RIGHT] Up Level"); +#elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \ + (CONFIG_KEYPAD == IRIVER_H300_PAD) + rb->lcd_putsxy(3, 6, "[STOP] Menu"); + rb->lcd_putsxy(3, 16, "[REC] Undo"); + rb->lcd_putsxy(3, 26, "[MODE] Redo"); + rb->lcd_putsxy(3, 36, "[PLAY+DOWN] Previous Level"); + rb->lcd_putsxy(3, 46, "[PLAY] Restart Level"); + rb->lcd_putsxy(3, 56, "[PLAY+UP] Next Level"); +#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ + (CONFIG_KEYPAD == IPOD_3G_PAD) + rb->lcd_putsxy(3, 6, "[SELECT+MENU] Menu"); + rb->lcd_putsxy(3, 16, "[SELECT] Undo"); + rb->lcd_putsxy(3, 26, "[SELECT+PLAY] Redo"); + rb->lcd_putsxy(3, 36, "[SELECT+LEFT] Previous Level"); + rb->lcd_putsxy(3, 46, "[SELECT+RIGHT] Next Level"); +#elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD + rb->lcd_putsxy(3, 6, "[POWER] Menu"); + rb->lcd_putsxy(3, 16, "[SELECT] Undo"); + rb->lcd_putsxy(3, 26, "[REC] Previous Level"); + rb->lcd_putsxy(3, 36, "[PLAY] Next Level"); +#elif CONFIG_KEYPAD == IRIVER_H10_PAD + rb->lcd_putsxy(3, 6, "[POWER] Menu"); + rb->lcd_putsxy(3, 16, "[REW] Undo"); + rb->lcd_putsxy(3, 26, "[FF] Redo"); + rb->lcd_putsxy(3, 36, "[PLAY+DOWN] Previous Level"); + rb->lcd_putsxy(3, 46, "[PLAY+RIGHT] Restart Level"); + rb->lcd_putsxy(3, 56, "[PLAY+UP] Next Level"); +#elif CONFIG_KEYPAD == GIGABEAT_PAD + rb->lcd_putsxy(3, 6, "[POWER] Menu"); + rb->lcd_putsxy(3, 16, "[SELECT] Undo"); + rb->lcd_putsxy(3, 26, "[A] Redo"); + rb->lcd_putsxy(3, 36, "[VOL-] Previous Level"); + rb->lcd_putsxy(3, 46, "[MENU] Restart Level"); + rb->lcd_putsxy(3, 56, "[VOL+] Next Level"); +#elif CONFIG_KEYPAD == SANSA_E200_PAD + rb->lcd_putsxy(3, 6, "[POWER] Menu"); + rb->lcd_putsxy(3, 16, "[SELECT] Undo"); + rb->lcd_putsxy(3, 26, "[REC] Redo"); + rb->lcd_putsxy(3, 36, "[SELECT+DOWN] Previous Level"); + rb->lcd_putsxy(3, 46, "[SELECT+RIGHT] Restart Level"); + rb->lcd_putsxy(3, 56, "[SELECT+UP] Next Level"); +#endif + + FOR_NB_SCREENS(i) + rb->screens[i]->update(); + + /* Display until keypress */ + do { + rb->sleep(HZ/20); + button = rb->button_get(false); + } while (!button || button & BUTTON_REL || + button & BUTTON_REPEAT); + + menu_quit = false; + break; + + case 3: /* Load default levelset */ + init_boards(); + if (!read_levels(true)) + return 4; + load_level(); + break; + + case 4: /* Quit */ + break; + + case 5: /* Save & quit */ + save(SOKOBAN_SAVE_FILE, false); + rb->reload_directory(); + } + + } while (!menu_quit); + + /* Restore font */ + rb->lcd_setfont(SOKOBAN_FONT); + + FOR_NB_SCREENS(i) { + rb->screens[i]->clear_display(); + rb->screens[i]->update(); + } + + return selection; +} + static bool sokoban_loop(void) { - bool moved = true; + bool moved; int i = 0, button = 0, lastbutton = 0; short r = 0, c = 0; int w, h; - char s[25]; + char *loc; - current_info.level.level = 1; - - load_level(); - update_screen(); - - while (1) { - moved = true; + while (true) { + moved = false; r = current_info.player.row; c = current_info.player.col; @@ -742,24 +1166,25 @@ static bool sokoban_loop(void) #ifdef SOKOBAN_RC_QUIT case SOKOBAN_RC_QUIT: #endif - case SOKOBAN_QUIT: - /* get out of here */ -#ifdef HAVE_LCD_COLOR /* reset background color */ - rb->lcd_set_background(rb->global_settings->bg_color); -#endif - return PLUGIN_OK; + case SOKOBAN_MENU: + switch (sokoban_menu()) { + case 4: /* Quit */ + case 5: /* Save & quit */ + return PLUGIN_OK; + } + update_screen(); + break; case SOKOBAN_UNDO: #ifdef SOKOBAN_UNDO_PRE if (lastbutton != SOKOBAN_UNDO_PRE) break; -#else /* repeat can't work here for Ondio et al */ +#else /* repeat can't work here for Ondio, iPod, et al */ case SOKOBAN_UNDO | BUTTON_REPEAT: #endif undo(); rb->lcd_clear_display(); update_screen(); - moved = false; break; #ifdef SOKOBAN_REDO @@ -775,22 +1200,20 @@ static bool sokoban_loop(void) case SOKOBAN_LEVEL_UP | BUTTON_REPEAT: /* next level */ init_undo(); - if (current_info.level.level < current_info.max_level) - current_info.level.level++; + if (current_info.level.index + 1 < current_info.max_level) + current_info.level.index++; draw_level(); - moved = false; break; case SOKOBAN_LEVEL_DOWN: case SOKOBAN_LEVEL_DOWN | BUTTON_REPEAT: /* previous level */ init_undo(); - if (current_info.level.level > 1) - current_info.level.level--; + if (current_info.level.index > 0) + current_info.level.index--; draw_level(); - moved = false; break; #ifdef SOKOBAN_LEVEL_REPEAT @@ -799,7 +1222,6 @@ static bool sokoban_loop(void) /* same level */ init_undo(); draw_level(); - moved = false; break; #endif @@ -826,13 +1248,10 @@ static bool sokoban_loop(void) default: if (rb->default_event_handler(button) == SYS_USB_CONNECTED) return PLUGIN_USB_CONNECTED; - - moved = false; break; } - if (button != BUTTON_NONE) - lastbutton = button; + lastbutton = button; if (moved) { rb->lcd_clear_display(); @@ -844,58 +1263,102 @@ static bool sokoban_loop(void) if (moved) { rb->lcd_clear_display(); - /* Center level completed message */ - rb->snprintf(s, sizeof(s), "Level %d Complete!", - current_info.level.level); - rb->lcd_getstringsize(s, &w, &h); - rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - 16 , s); - rb->snprintf(s, sizeof(s), "%4d Moves ", + + /* Show level complete message & stats */ + rb->snprintf(buf, sizeof(buf), "Level %d Complete!", + current_info.level.index + 1); + rb->lcd_getstringsize(buf, &w, &h); + rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h*3, buf); + + rb->snprintf(buf, sizeof(buf), "%4d Moves ", current_info.level.moves); - rb->lcd_getstringsize(s, &w, &h); - rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 + 0 , s); - rb->snprintf(s, sizeof(s), "%4d Pushes", + rb->lcd_getstringsize(buf, &w, &h); + rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h, buf); + + rb->snprintf(buf, sizeof(buf), "%4d Pushes", current_info.level.pushes); - rb->lcd_getstringsize(s, &w, &h); - rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 + 8 , s); + rb->lcd_getstringsize(buf, &w, &h); + rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2, buf); + + if (undo_info.count < MAX_UNDOS) { + rb->snprintf(buf, sizeof(buf), "%s: Save solution", + BUTTON_SAVE_NAME); + rb->lcd_getstringsize(buf, &w, &h); + rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 + h*2, buf); + } + rb->lcd_update(); - rb->button_get(false); + rb->sleep(HZ/4); + rb->button_clear_queue(); - rb->sleep(HZ/2); - for (i = 0; i < 30; i++) { + /* Display for 4 seconds or until new keypress */ + for (i = 0; i < 75; i++) { rb->sleep(HZ/20); button = rb->button_get(false); - if (button && ((button & BUTTON_REL) != BUTTON_REL)) + if (button && !(button & BUTTON_REL) && + !(button & BUTTON_REPEAT)) break; } + + if (button == BUTTON_SAVE) { + if (undo_info.count < MAX_UNDOS) { + /* Default filename to current levelset plus + * level number and .sok extension */ + loc = rb->strrchr(buffered_boards.filename, '.'); + if (loc != NULL) + *loc = '\0'; + rb->snprintf(buf, sizeof(buf), "%s.%d.sok", + buffered_boards.filename, + current_info.level.index + 1); + if (loc != NULL) + *loc = '.'; + + if (!rb->kbd_input(buf, MAX_PATH)) + save(buf, true); + } else + rb->splash(HZ*2, "Solution too long to save"); + + rb->lcd_setfont(SOKOBAN_FONT); /* Restore font */ + } } - current_info.level.level++; + FOR_NB_SCREENS(i) { + rb->screens[i]->clear_display(); + rb->screens[i]->update(); + } + + current_info.level.index++; /* clear undo stats */ init_undo(); - rb->lcd_clear_display(); - - if (current_info.level.level > current_info.max_level) { - /* Center "You WIN!!" on all screen sizes */ - rb->snprintf(s, sizeof(s), "You WIN!!"); - rb->lcd_getstringsize(s, &w, &h); - rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h/2, s); + if (current_info.level.index >= current_info.max_level) { + /* Show levelset complete message */ + rb->snprintf(buf, sizeof(buf), "You WIN!!"); + rb->lcd_getstringsize(buf, &w, &h); + rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h/2, buf); rb->lcd_set_drawmode(DRMODE_COMPLEMENT); - /* Display for 10 seconds or until keypress */ - for (i = 0; i < 200; i++) { + /* Display for 4 seconds or until keypress */ + for (i = 0; i < 80; i++) { rb->lcd_fillrect(0, 0, LCD_WIDTH, LCD_HEIGHT); rb->lcd_update(); - rb->sleep(HZ/20); + rb->sleep(HZ/10); button = rb->button_get(false); - if (button && ((button & BUTTON_REL) != BUTTON_REL)) + if (button && !(button & BUTTON_REL)) break; } rb->lcd_set_drawmode(DRMODE_SOLID); - return PLUGIN_OK; + /* Reset to first level & show quit menu */ + current_info.level.index = 0; + + switch (sokoban_menu()) { + case 4: /* Quit */ + case 5: /* Save & quit */ + return PLUGIN_OK; + } } load_level(); @@ -911,99 +1374,41 @@ static bool sokoban_loop(void) enum plugin_status plugin_start(struct plugin_api* api, void* parameter) { int w, h; - int i; - int button = 0; (void)(parameter); rb = api; - rb->lcd_setfont(FONT_SYSFIXED); - -#ifdef HAVE_LCD_COLOR - rb->lcd_set_background(BG_COLOR); -#endif + rb->lcd_setfont(SOKOBAN_FONT); rb->lcd_clear_display(); rb->lcd_getstringsize(SOKOBAN_TITLE, &w, &h); rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h/2, SOKOBAN_TITLE); rb->lcd_update(); - rb->sleep(HZ); + rb->sleep(HZ); /* Show title for 1 second */ - rb->lcd_clear_display(); + init_boards(); -#if (CONFIG_KEYPAD == RECORDER_PAD) || \ - (CONFIG_KEYPAD == ARCHOS_AV300_PAD) - rb->lcd_putsxy(3, 6, "[OFF] Quit"); - rb->lcd_putsxy(3, 16, "[ON] Undo"); - rb->lcd_putsxy(3, 26, "[PLAY] Redo"); - rb->lcd_putsxy(3, 36, "[F1] Down a Level"); - rb->lcd_putsxy(3, 46, "[F2] Restart Level"); - rb->lcd_putsxy(3, 56, "[F3] Up a Level"); -#elif CONFIG_KEYPAD == ONDIO_PAD - rb->lcd_putsxy(3, 6, "[OFF] Quit"); - rb->lcd_putsxy(3, 16, "[MODE] Undo"); - rb->lcd_putsxy(3, 26, "[MODE+DOWN] Redo"); - rb->lcd_putsxy(3, 36, "[MODE+LEFT] Down a Level"); - rb->lcd_putsxy(3, 46, "[MODE+UP] Restart Level"); - rb->lcd_putsxy(3, 56, "[MODE+RIGHT] Up Level"); -#elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \ - (CONFIG_KEYPAD == IRIVER_H300_PAD) - rb->lcd_putsxy(3, 6, "[STOP] Quit"); - rb->lcd_putsxy(3, 16, "[REC] Undo"); - rb->lcd_putsxy(3, 26, "[MODE] Redo"); - rb->lcd_putsxy(3, 36, "[PLAY+DOWN] Down a Level"); - rb->lcd_putsxy(3, 46, "[PLAY] Restart Level"); - rb->lcd_putsxy(3, 56, "[PLAY+UP] Up a Level"); -#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ - (CONFIG_KEYPAD == IPOD_3G_PAD) - rb->lcd_putsxy(3, 6, "[SELECT+MENU] Quit"); - rb->lcd_putsxy(3, 16, "[SELECT] Undo"); - rb->lcd_putsxy(3, 26, "[SELECT+PLAY] Redo"); - rb->lcd_putsxy(3, 36, "[SELECT+LEFT] Down a Level"); - rb->lcd_putsxy(3, 46, "[SELECT+RIGHT] Up a Level"); -#elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD - rb->lcd_putsxy(3, 6, "[POWER] Quit"); - rb->lcd_putsxy(3, 16, "[SELECT] Undo"); - rb->lcd_putsxy(3, 26, "[REC] Down a Level"); - rb->lcd_putsxy(3, 36, "[PLAY] Up Level"); -#elif CONFIG_KEYPAD == GIGABEAT_PAD - rb->lcd_putsxy(3, 6, "[POWER] Quit"); - rb->lcd_putsxy(3, 16, "[SELECT] Undo"); - rb->lcd_putsxy(3, 26, "[A] Redo"); - rb->lcd_putsxy(3, 36, "[VOL-] Down a Level"); - rb->lcd_putsxy(3, 46, "[MENU] Restart Level"); - rb->lcd_putsxy(3, 56, "[VOL+] Up Level"); -#elif CONFIG_KEYPAD == SANSA_E200_PAD - rb->lcd_putsxy(3, 6, "[POWER] Quit"); - rb->lcd_putsxy(3, 16, "[SELECT] Undo"); - rb->lcd_putsxy(3, 26, "[REC] Redo"); - rb->lcd_putsxy(3, 36, "[SELECT+DOWN] Down a Level"); - rb->lcd_putsxy(3, 46, "[SELECT+RIGHT] Restart Level"); - rb->lcd_putsxy(3, 56, "[SELECT+UP] Up Level"); -#elif CONFIG_KEYPAD == IRIVER_H10_PAD - rb->lcd_putsxy(3, 6, "[POWER] Quit"); - rb->lcd_putsxy(3, 16, "[REW] Undo"); - rb->lcd_putsxy(3, 26, "[FF] Redo"); - rb->lcd_putsxy(3, 36, "[PLAY+DOWN] Down a Level"); - rb->lcd_putsxy(3, 46, "[PLAY+RIGHT] Restart Level"); - rb->lcd_putsxy(3, 56, "[PLAY+UP] Up Level"); -#endif + if (parameter == NULL) { + /* Attempt to resume saved progress, otherwise start at beginning */ + if (!load(SOKOBAN_SAVE_FILE, true)) { + init_boards(); + if (!read_levels(true)) + return PLUGIN_OK; + load_level(); + } - rb->lcd_update(); - rb->button_get(false); - /* Display for 3 seconds or until keypress */ - for (i = 0; i < 60; i++) { - rb->sleep(HZ/20); - button = rb->button_get(false); - if (button && ((button & BUTTON_REL) != BUTTON_REL)) - break; + } else { + /* The plugin is being used to open a file */ + if (load((char*) parameter, false)) { + /* If we loaded & played a solution, quit */ + if (current_info.level.boxes_to_go == 0) + return PLUGIN_OK; + } else + return PLUGIN_OK; } - rb->lcd_clear_display(); - - init_boards(); - if (read_levels(1) != 0) - return PLUGIN_OK; + rb->lcd_clear_display(); + update_screen(); return sokoban_loop(); } -- cgit v1.2.3