rockbox/apps/plugins/sokoban.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 = &current_info.board[r][c];
space_next = &current_info.board[r + d_r][c + d_c];
space_prev = &current_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 = &current_info.board[r][c];
space_next = &current_info.board[r + d_r][c + d_c];
space_beyond = &current_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