ffbdb25e67
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@12294 a1c6a512-1295-4272-9138-f99709370657
1012 lines
31 KiB
C
1012 lines
31 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2002 Eric Linenberg
|
|
* February 2003: Robert Hak performs a cleanup/rewrite/feature addition.
|
|
* Eric smiles. Bjorn cries. Linus say 'huh?'.
|
|
*
|
|
* 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"
|
|
|
|
#ifdef HAVE_LCD_BITMAP
|
|
|
|
PLUGIN_HEADER
|
|
|
|
#if LCD_DEPTH >= 2
|
|
extern const fb_data sokoban_tiles[];
|
|
#endif
|
|
|
|
#define SOKOBAN_TITLE "Sokoban"
|
|
|
|
#define LEVELS_FILE PLUGIN_DIR "/sokoban.levels"
|
|
|
|
#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
|
|
#else
|
|
#define NUM_BUFFERED_BOARDS MAX_BUFFERED_BOARDS
|
|
#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
|
|
#else
|
|
#define MAX_UNDOS PLUGIN_BUFFER_SIZE - \
|
|
NUM_BUFFERED_BOARDS*SOKOBAN_LEVEL_SIZE - 0x2000
|
|
#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
|
|
|
|
/* variable button definitions */
|
|
#if CONFIG_KEYPAD == RECORDER_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
|
|
|
|
#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
|
|
|
|
#elif CONFIG_KEYPAD == ONDIO_PAD
|
|
#define SOKOBAN_UP BUTTON_UP
|
|
#define SOKOBAN_DOWN BUTTON_DOWN
|
|
#define SOKOBAN_QUIT 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)
|
|
|
|
#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_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_RC_QUIT BUTTON_RC_STOP
|
|
|
|
#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
|
|
(CONFIG_KEYPAD == IPOD_3G_PAD)
|
|
#define SOKOBAN_UP BUTTON_MENU
|
|
#define SOKOBAN_DOWN BUTTON_PLAY
|
|
#define SOKOBAN_QUIT (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)
|
|
|
|
/* fixme: if/when simultaneous button presses work for X5,
|
|
add redo & level repeat */
|
|
#elif (CONFIG_KEYPAD == IAUDIO_X5_PAD)
|
|
#define SOKOBAN_UP BUTTON_UP
|
|
#define SOKOBAN_DOWN BUTTON_DOWN
|
|
#define SOKOBAN_QUIT 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
|
|
|
|
#elif (CONFIG_KEYPAD == GIGABEAT_PAD)
|
|
#define SOKOBAN_UP BUTTON_UP
|
|
#define SOKOBAN_DOWN BUTTON_DOWN
|
|
#define SOKOBAN_QUIT BUTTON_A
|
|
#define SOKOBAN_UNDO BUTTON_SELECT
|
|
#define SOKOBAN_REDO BUTTON_POWER
|
|
#define SOKOBAN_LEVEL_UP (BUTTON_MENU | BUTTON_UP)
|
|
#define SOKOBAN_LEVEL_DOWN (BUTTON_MENU | BUTTON_DOWN)
|
|
#define SOKOBAN_LEVEL_REPEAT BUTTON_MENU
|
|
|
|
#elif (CONFIG_KEYPAD == SANSA_E200_PAD)
|
|
#define SOKOBAN_UP BUTTON_UP
|
|
#define SOKOBAN_DOWN BUTTON_DOWN
|
|
#define SOKOBAN_QUIT 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)
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_LCD_COLOR
|
|
/* Background color. Default Rockbox light blue. */
|
|
#define BG_COLOR LCD_RGBPACK(181, 199, 231)
|
|
|
|
#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,
|
|
* but the overall data layout becomes more manageable. */
|
|
|
|
/* Level data & stats */
|
|
struct LevelInfo {
|
|
short level;
|
|
short moves;
|
|
short pushes;
|
|
short boxes_to_go;
|
|
};
|
|
|
|
struct Location {
|
|
short row;
|
|
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 */
|
|
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 */
|
|
} current_info;
|
|
|
|
static struct BufferedBoards {
|
|
struct Board levels[NUM_BUFFERED_BOARDS];
|
|
int low;
|
|
} buffered_boards;
|
|
|
|
static struct plugin_api* rb;
|
|
|
|
static void init_undo(void)
|
|
{
|
|
undo_info.count = 0;
|
|
undo_info.current = -1;
|
|
undo_info.max = -1;
|
|
}
|
|
|
|
static void get_delta(char direction, short *d_r, short *d_c)
|
|
{
|
|
switch (direction) {
|
|
case SOKOBAN_PUSH_LEFT:
|
|
case SOKOBAN_MOVE_LEFT:
|
|
*d_r = 0;
|
|
*d_c = -1;
|
|
break;
|
|
case SOKOBAN_PUSH_RIGHT:
|
|
case SOKOBAN_MOVE_RIGHT:
|
|
*d_r = 0;
|
|
*d_c = 1;
|
|
break;
|
|
case SOKOBAN_PUSH_UP:
|
|
case SOKOBAN_MOVE_UP:
|
|
*d_r = -1;
|
|
*d_c = 0;
|
|
break;
|
|
case SOKOBAN_PUSH_DOWN:
|
|
case SOKOBAN_MOVE_DOWN:
|
|
*d_r = 1;
|
|
*d_c = 0;
|
|
}
|
|
}
|
|
|
|
static void undo(void)
|
|
{
|
|
char undo;
|
|
short r, c;
|
|
short d_r = 0, d_c = 0; /* delta row & delta col */
|
|
char *space_cur, *space_next, *space_prev;
|
|
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)
|
|
return;
|
|
|
|
undo = undo_info.history[undo_info.current];
|
|
|
|
if (undo < SOKOBAN_MOVE_MIN)
|
|
undo_push = true;
|
|
|
|
get_delta(undo, &d_r, &d_c);
|
|
|
|
r = current_info.player.row;
|
|
c = current_info.player.col;
|
|
|
|
/* Give the 3 spaces we're going to use better names */
|
|
space_cur = ¤t_info.board[r][c];
|
|
space_next = ¤t_info.board[r + d_r][c + d_c];
|
|
space_prev = ¤t_info.board[r - d_r][c - d_c];
|
|
|
|
/* Update board info */
|
|
if (undo_push) {
|
|
/* Moving box from goal to blank */
|
|
if (*space_next == '%' && *space_cur == '@')
|
|
current_info.level.boxes_to_go++;
|
|
/* Moving box from blank to goal */
|
|
else if (*space_next == '$' && *space_cur == '+')
|
|
current_info.level.boxes_to_go--;
|
|
|
|
/* Move box off of next space... */
|
|
*space_next = (*space_next == '%' ? '.' : ' ');
|
|
/* ...and on to current space */
|
|
*space_cur = (*space_cur == '+' ? '%' : '$');
|
|
|
|
current_info.level.pushes--;
|
|
} else
|
|
/* Just move player off of current space */
|
|
*space_cur = (*space_cur == '+' ? '.' : ' ');
|
|
/* Move player back to previous space */
|
|
*space_prev = (*space_prev == '.' ? '+' : '@');
|
|
|
|
/* Update position */
|
|
current_info.player.row -= d_r;
|
|
current_info.player.col -= d_c;
|
|
|
|
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)
|
|
{
|
|
/* 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++;
|
|
}
|
|
|
|
static bool move(char direction, bool redo)
|
|
{
|
|
short r, c;
|
|
short d_r = 0, d_c = 0; /* delta row & delta col */
|
|
char *space_cur, *space_next, *space_beyond;
|
|
bool push = false;
|
|
|
|
get_delta(direction, &d_r, &d_c);
|
|
|
|
r = current_info.player.row;
|
|
c = current_info.player.col;
|
|
|
|
/* Check for out-of-bounds */
|
|
if (r + 2*d_r < 0 || r + 2*d_r >= ROWS ||
|
|
c + 2*d_c < 0 || c + 2*d_c >= COLS)
|
|
return false;
|
|
|
|
/* Give the 3 spaces we're going to use better names */
|
|
space_cur = ¤t_info.board[r][c];
|
|
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 == '%') {
|
|
/* Change direction from move to push for undo */
|
|
if (direction >= SOKOBAN_MOVE_MIN)
|
|
direction -= SOKOBAN_MOVE_DIFF;
|
|
push = true;
|
|
}
|
|
|
|
/* Update board info */
|
|
if (push) {
|
|
/* Moving box from goal to blank */
|
|
if (*space_next == '%' && *space_beyond == ' ')
|
|
current_info.level.boxes_to_go++;
|
|
/* Moving box from blank to goal */
|
|
else if (*space_next == '$' && *space_beyond == '.')
|
|
current_info.level.boxes_to_go--;
|
|
/* Check for illegal move */
|
|
else if (*space_beyond != '.' && *space_beyond != ' ')
|
|
return false;
|
|
|
|
/* Move player onto next space */
|
|
*space_next = (*space_next == '%' ? '+' : '@');
|
|
/* Move box onto space beyond next */
|
|
*space_beyond = (*space_beyond == '.' ? '%' : '$');
|
|
|
|
current_info.level.pushes++;
|
|
} else {
|
|
/* Check for illegal move */
|
|
if (*space_next == '#' || *space_next == 'X')
|
|
return false;
|
|
|
|
/* Move player onto next space */
|
|
*space_next = (*space_next == '.' ? '+' : '@');
|
|
}
|
|
/* Move player off of current space */
|
|
*space_cur = (*space_cur == '+' ? '.' : ' ');
|
|
|
|
/* Update position */
|
|
current_info.player.row += d_r;
|
|
current_info.player.col += d_c;
|
|
|
|
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 */
|
|
if (!redo &&
|
|
/* 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) ||
|
|
/* 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) */
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef SOKOBAN_REDO
|
|
static bool redo(void)
|
|
{
|
|
/* If no moves have been undone, quit */
|
|
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);
|
|
}
|
|
#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;
|
|
current_info.player.row = 0;
|
|
current_info.player.col = 0;
|
|
current_info.max_level = 0;
|
|
current_info.loaded_level = 0;
|
|
|
|
buffered_boards.low = 0;
|
|
|
|
init_undo();
|
|
}
|
|
|
|
static int read_levels(int initialize_count)
|
|
{
|
|
int fd = 0;
|
|
int len;
|
|
int lastlen = 0;
|
|
int 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;
|
|
|
|
buffered_boards.low = endpoint;
|
|
endpoint += NUM_BUFFERED_BOARDS;
|
|
|
|
if ((fd = rb->open(LEVELS_FILE, O_RDONLY)) < 0) {
|
|
rb->splash(HZ*2, true, "Unable to open %s", LEVELS_FILE);
|
|
return -1;
|
|
}
|
|
|
|
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, true, "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);
|
|
}
|
|
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, true,
|
|
"Error in levels file: short board");
|
|
return -1;
|
|
}
|
|
row = 0;
|
|
}
|
|
}
|
|
} while ((lastlen=len));
|
|
|
|
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;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* 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) {
|
|
read_levels(false);
|
|
index = index < 0 ? NUM_BUFFERED_BOARDS-1 : 0;
|
|
}
|
|
level = &buffered_boards.levels[index];
|
|
|
|
current_info.level.boxes_to_go = 0;
|
|
current_info.level.moves = 0;
|
|
current_info.level.pushes = 0;
|
|
current_info.loaded_level = current_info.level.level;
|
|
|
|
for (r = 0; r < ROWS; r++) {
|
|
for (c = 0; c < COLS; c++) {
|
|
current_info.board[r][c] = level->spaces[r][c];
|
|
|
|
if (current_info.board[r][c] == '.' ||
|
|
current_info.board[r][c] == '+')
|
|
current_info.level.boxes_to_go++;
|
|
|
|
if (current_info.board[r][c] == '@' ||
|
|
current_info.board[r][c] == '+') {
|
|
current_info.player.row = r;
|
|
current_info.player.col = c;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
|
|
#if LCD_DEPTH < 2
|
|
int i, j;
|
|
int max = MAGNIFY - 1;
|
|
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);
|
|
#else
|
|
for (i = c; i < c + MAGNIFY; i++)
|
|
for (j = b; j < b + MAGNIFY; j++)
|
|
if ((i ^ j) & 1)
|
|
rb->lcd_drawpixel(i, j);
|
|
#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);
|
|
#else
|
|
/* Free boxes are not filled in */
|
|
rb->lcd_drawrect(c, b, MAGNIFY, MAGNIFY);
|
|
#endif
|
|
break;
|
|
|
|
case '*':
|
|
case '%': /* this is a box on a goal */
|
|
|
|
#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);
|
|
#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;
|
|
|
|
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;
|
|
|
|
#if LCD_DEPTH >= 2
|
|
default:
|
|
rb->lcd_bitmap_part(sokoban_tiles, 0, 0*MAGNIFY, MAGNIFY,
|
|
c, b, MAGNIFY, MAGNIFY );
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
#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);
|
|
|
|
#else
|
|
#define STAT_POS COLS*MAGNIFY
|
|
#define STAT_SIZE LCD_WIDTH-STAT_POS
|
|
|
|
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);
|
|
|
|
rb->lcd_drawrect(STAT_POS, 0, STAT_SIZE, 24);
|
|
rb->lcd_drawrect(STAT_POS, 23, STAT_SIZE, 24);
|
|
|
|
#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);
|
|
|
|
rb->lcd_drawrect(STAT_POS, 46, STAT_SIZE, 24);
|
|
#endif
|
|
#endif
|
|
|
|
/* print out the screen */
|
|
rb->lcd_update();
|
|
}
|
|
|
|
static void draw_level(void)
|
|
{
|
|
load_level();
|
|
rb->lcd_clear_display();
|
|
update_screen();
|
|
}
|
|
|
|
static bool sokoban_loop(void)
|
|
{
|
|
bool moved = true;
|
|
int i = 0, button = 0, lastbutton = 0;
|
|
short r = 0, c = 0;
|
|
int w, h;
|
|
char s[25];
|
|
|
|
current_info.level.level = 1;
|
|
|
|
load_level();
|
|
update_screen();
|
|
|
|
while (1) {
|
|
moved = true;
|
|
|
|
r = current_info.player.row;
|
|
c = current_info.player.col;
|
|
|
|
button = rb->button_get(true);
|
|
|
|
switch(button)
|
|
{
|
|
#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_UNDO:
|
|
#ifdef SOKOBAN_UNDO_PRE
|
|
if (lastbutton != SOKOBAN_UNDO_PRE)
|
|
break;
|
|
#else /* repeat can't work here for Ondio et al */
|
|
case SOKOBAN_UNDO | BUTTON_REPEAT:
|
|
#endif
|
|
undo();
|
|
rb->lcd_clear_display();
|
|
update_screen();
|
|
moved = false;
|
|
break;
|
|
|
|
#ifdef SOKOBAN_REDO
|
|
case SOKOBAN_REDO:
|
|
case SOKOBAN_REDO | BUTTON_REPEAT:
|
|
moved = redo();
|
|
rb->lcd_clear_display();
|
|
update_screen();
|
|
break;
|
|
#endif
|
|
|
|
case SOKOBAN_LEVEL_UP:
|
|
case SOKOBAN_LEVEL_UP | BUTTON_REPEAT:
|
|
/* next level */
|
|
init_undo();
|
|
if (current_info.level.level < current_info.max_level)
|
|
current_info.level.level++;
|
|
|
|
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--;
|
|
|
|
draw_level();
|
|
moved = false;
|
|
break;
|
|
|
|
#ifdef SOKOBAN_LEVEL_REPEAT
|
|
case SOKOBAN_LEVEL_REPEAT:
|
|
case SOKOBAN_LEVEL_REPEAT | BUTTON_REPEAT:
|
|
/* same level */
|
|
init_undo();
|
|
draw_level();
|
|
moved = false;
|
|
break;
|
|
#endif
|
|
|
|
case BUTTON_LEFT:
|
|
case BUTTON_LEFT | BUTTON_REPEAT:
|
|
moved = move(SOKOBAN_MOVE_LEFT, false);
|
|
break;
|
|
|
|
case BUTTON_RIGHT:
|
|
case BUTTON_RIGHT | BUTTON_REPEAT:
|
|
moved = move(SOKOBAN_MOVE_RIGHT, false);
|
|
break;
|
|
|
|
case SOKOBAN_UP:
|
|
case SOKOBAN_UP | BUTTON_REPEAT:
|
|
moved = move(SOKOBAN_MOVE_UP, false);
|
|
break;
|
|
|
|
case SOKOBAN_DOWN:
|
|
case SOKOBAN_DOWN | BUTTON_REPEAT:
|
|
moved = move(SOKOBAN_MOVE_DOWN, false);
|
|
break;
|
|
|
|
default:
|
|
if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
|
|
return PLUGIN_USB_CONNECTED;
|
|
|
|
moved = false;
|
|
break;
|
|
}
|
|
|
|
if (button != BUTTON_NONE)
|
|
lastbutton = button;
|
|
|
|
if (moved) {
|
|
rb->lcd_clear_display();
|
|
update_screen();
|
|
}
|
|
|
|
/* We have completed this level */
|
|
if (current_info.level.boxes_to_go == 0) {
|
|
|
|
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 ",
|
|
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",
|
|
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_update();
|
|
rb->button_get(false);
|
|
|
|
rb->sleep(HZ/2);
|
|
for (i = 0; i < 30; i++) {
|
|
rb->sleep(HZ/20);
|
|
button = rb->button_get(false);
|
|
if (button && ((button & BUTTON_REL) != BUTTON_REL))
|
|
break;
|
|
}
|
|
}
|
|
|
|
current_info.level.level++;
|
|
|
|
/* 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);
|
|
|
|
rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
|
|
/* Display for 10 seconds or until keypress */
|
|
for (i = 0; i < 200; i++) {
|
|
rb->lcd_fillrect(0, 0, LCD_WIDTH, LCD_HEIGHT);
|
|
rb->lcd_update();
|
|
rb->sleep(HZ/20);
|
|
|
|
button = rb->button_get(false);
|
|
if (button && ((button & BUTTON_REL) != BUTTON_REL))
|
|
break;
|
|
}
|
|
rb->lcd_set_drawmode(DRMODE_SOLID);
|
|
|
|
return PLUGIN_OK;
|
|
}
|
|
|
|
load_level();
|
|
update_screen();
|
|
}
|
|
|
|
} /* end while */
|
|
|
|
return PLUGIN_OK;
|
|
}
|
|
|
|
|
|
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_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->lcd_clear_display();
|
|
|
|
#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_X5_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, "[A] Quit");
|
|
rb->lcd_putsxy(3, 16, "[SELECT] Undo");
|
|
rb->lcd_putsxy(3, 26, "[POWER] Redo");
|
|
rb->lcd_putsxy(3, 36, "[MENU+DOWN] Down a Level");
|
|
rb->lcd_putsxy(3, 46, "[MENU] Restart Level");
|
|
rb->lcd_putsxy(3, 56, "[MENU+UP] 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
|
|
|
|
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;
|
|
}
|
|
rb->lcd_clear_display();
|
|
|
|
init_boards();
|
|
|
|
if (read_levels(1) != 0)
|
|
return PLUGIN_OK;
|
|
|
|
return sokoban_loop();
|
|
}
|
|
|
|
#endif
|