rockbox/apps/plugins/sokoban.c

1027 lines
34 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 SOKOBAN_TITLE_FONT 2
#define LEVELS_FILE PLUGIN_DIR "/sokoban.levels"
#define ROWS 16
#define COLS 20
/* Use all but 8k of the plugin buffer for board data */
#define SOKOBAN_LEVEL_SIZE (ROWS*COLS)
#define MAX_BUFFERED_BOARDS (PLUGIN_BUFFER_SIZE - 0x3000)/SOKOBAN_LEVEL_SIZE
#define MAX_UNDOS 5
/* 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_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_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_ON
#define SOKOBAN_LEVEL_UP BUTTON_MODE
#define SOKOBAN_LEVEL_DOWN BUTTON_REC
#define SOKOBAN_LEVEL_REPEAT BUTTON_SELECT
#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_LEVEL_UP (BUTTON_SELECT | BUTTON_RIGHT)
#define SOKOBAN_LEVEL_DOWN (BUTTON_SELECT | BUTTON_LEFT)
#define SOKOBAN_LEVEL_REPEAT (BUTTON_SELECT | BUTTON_PLAY)
#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 | BUTTON_UP)
#define SOKOBAN_LEVEL_DOWN (BUTTON_PLAY | BUTTON_DOWN)
#define SOKOBAN_LEVEL_REPEAT 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_MENU
#define SOKOBAN_LEVEL_UP (BUTTON_POWER | BUTTON_UP)
#define SOKOBAN_LEVEL_DOWN (BUTTON_POWER | BUTTON_DOWN)
#define SOKOBAN_LEVEL_REPEAT BUTTON_SELECT
#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_UP)
#define SOKOBAN_LEVEL_UP (BUTTON_SELECT | BUTTON_RIGHT)
#define SOKOBAN_LEVEL_DOWN (BUTTON_SELECT | BUTTON_LEFT)
#define SOKOBAN_LEVEL_REPEAT BUTTON_REC
#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_LEVEL_UP (BUTTON_PLAY | BUTTON_SCROLL_UP)
#define SOKOBAN_LEVEL_DOWN (BUTTON_PLAY | BUTTON_SCROLL_DOWN)
#define SOKOBAN_LEVEL_REPEAT BUTTON_FF
#endif
#ifdef HAVE_LCD_COLOR
#define BG_COLOR LCD_RGBPACK(181,199,231) /* Background color. Default Rockbox light blue. */
#elif LCD_DEPTH >= 2
#define MEDIUM_GRAY LCD_BRIGHTNESS(127)
#endif
static void init_undo(void);
static void undo(void);
static void add_undo(int button);
static int read_levels(int initialize_count);
static void load_level(void);
static void draw_level(void);
static void init_boards(void);
static void update_screen(void);
static bool sokoban_loop(void);
/* 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. */
/* We use the same three values in 2 structs. Makeing them a struct
* hopefully ensures that if you change things in one, the other changes
* as well. */
struct LevelInfo {
short level;
short moves;
short boxes_to_go;
};
/* What a given location on the board looks like at a given time */
struct Location {
char spot;
short row;
short col;
};
/* A single level of undo. Each undo move can affect upto,
* but not more then, 3 spots on the board */
struct Undo {
struct LevelInfo level;
struct Location location[3];
};
struct Board {
char spaces[ROWS][COLS];
};
/* Our full undo history */
static struct UndoInfo {
short count; /* How many undos are there in history */
short current; /* Which history is the current undo */
struct Undo 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[MAX_BUFFERED_BOARDS];
int low;
} buffered_boards;
static struct plugin_api* rb;
static void init_undo(void)
{
undo_info.count = 0;
undo_info.current = 0;
}
static void undo(void)
{
struct Undo *undo;
int i = 0;
short row, col;
if (undo_info.count == 0)
return;
/* Update board info */
undo = &undo_info.history[undo_info.current];
rb->memcpy(&current_info.level, &undo->level, sizeof(undo->level));
rb->memcpy(&current_info.player, &undo->location[0], sizeof(undo->location[0]));
row = undo->location[0].row;
col = undo->location[0].col;
current_info.board[row][col] = '@';
/* Update the two other possible spots */
for (i = 1; i < 3; i++) {
if (undo->location[i].spot != '\0') {
row = undo->location[i].row;
col = undo->location[i].col;
current_info.board[row][col] = undo->location[i].spot;
undo->location[i].spot = '\0';
}
}
/* Remove this undo from the list */
if (undo_info.current == 0) {
if (undo_info.count > 1)
undo_info.current = MAX_UNDOS - 1;
} else {
undo_info.current--;
}
undo_info.count--;
return;
}
static void add_undo(int button)
{
struct Undo *undo;
int row, col, i;
bool storable;
if ((button != BUTTON_LEFT) && (button != BUTTON_RIGHT) &&
(button != SOKOBAN_UP) && (button != SOKOBAN_DOWN))
return;
if (undo_info.count != 0) {
if (undo_info.current < (MAX_UNDOS - 1))
undo_info.current++;
else
undo_info.current = 0;
}
/* Make what follows more readable */
undo = &undo_info.history[undo_info.current];
/* Store our level info */
rb->memcpy(&undo->level, &current_info.level, sizeof(undo->level));
/* Store our player info */
rb->memcpy(&undo->location[0], &current_info.player, sizeof(undo->location[0]));
/* Now we need to store upto 2 blocks that may be affected.
* If player.spot is NULL, then there is no info stored
* for that block */
row = current_info.player.row;
col = current_info.player.col;
/* This must stay as _1_ because the first block (0) is the player */
for (i = 1; i < 3; i++) {
storable = true;
switch (button) {
case BUTTON_LEFT:
col--;
if (col < 0)
storable = false;
break;
case BUTTON_RIGHT:
col++;
if (col >= COLS)
storable = false;
break;
case SOKOBAN_UP:
row--;
if (row < 0)
storable = false;
break;
case SOKOBAN_DOWN:
row++;
if (row >= ROWS)
storable = false;
break;
default:
return;
}
if (storable) {
undo->location[i].col = col;
undo->location[i].row = row;
undo->location[i].spot = current_info.board[row][col];
} else {
undo->location[i].spot = '\0';
}
}
if (undo_info.count < MAX_UNDOS)
undo_info.count++;
}
static void init_boards(void)
{
current_info.level.level = 0;
current_info.level.moves = 0;
current_info.level.boxes_to_go = 0;
current_info.player.row = 0;
current_info.player.col = 0;
current_info.player.spot = ' ';
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 - MAX_BUFFERED_BOARDS;
if (endpoint < 0) endpoint = 0;
buffered_boards.low = endpoint;
endpoint += MAX_BUFFERED_BOARDS;
if ((fd = rb->open(LEVELS_FILE, O_RDONLY)) < 0) {
rb->splash(0, 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(0, 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(0, 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 >= MAX_BUFFERED_BOARDS) {
read_levels(false);
index=index<0?MAX_BUFFERED_BOARDS-1:0;
}
level = &buffered_boards.levels[index];
current_info.player.spot=' ';
current_info.level.boxes_to_go = 0;
current_info.level.moves = 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.level.boxes_to_go++;
else if (current_info.board[r][c] == '@') {
current_info.player.row = r;
current_info.player.col = c;
}
}
}
}
#define STAT_WIDTH (LCD_WIDTH-(COLS * magnify))
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 >= 240 /* ipod 5g */
int magnify = 14;
#elif LCD_HEIGHT >= 176 /* h3x0, ipod color/photo */
int magnify = 9;
#elif LCD_HEIGHT >= 110 /* h1x0, ipod nano, ipod mini */
int magnify = 6;
#else /* other */
int magnify = 4;
#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
{
int i, j;
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 home location */
#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 a box */
#if LCD_DEPTH >= 2
rb->lcd_bitmap_part( sokoban_tiles, 0, 2*magnify, magnify,
c, b, magnify, magnify );
#else
rb->lcd_drawrect(c, b, magnify, magnify); /* Free boxes are not filled in */
#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
int max = magnify - 1;
int middle = max / 2;
int ldelta = (middle + 1) / 2;
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 a box on a home spot */
#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;
#if LCD_DEPTH >= 2
default:
rb->lcd_bitmap_part( sokoban_tiles, 0, 0*magnify, magnify,
c, b, magnify, magnify );
#endif
}
}
}
rb->snprintf(s, sizeof(s), "%d", current_info.level.level);
rb->lcd_putsxy(LCD_WIDTH-STAT_WIDTH+4, 22, s);
rb->snprintf(s, sizeof(s), "%d", current_info.level.moves);
rb->lcd_putsxy(LCD_WIDTH-STAT_WIDTH+4, 54, s);
rb->lcd_drawrect(LCD_WIDTH-STAT_WIDTH,0,STAT_WIDTH,32);
rb->lcd_drawrect(LCD_WIDTH-STAT_WIDTH,32,STAT_WIDTH,LCD_HEIGHT-32);
rb->lcd_putsxy(LCD_WIDTH-STAT_WIDTH+1, 10, "Level");
rb->lcd_putsxy(LCD_WIDTH-STAT_WIDTH+1, 42, "Moves");
/* 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)
{
char new_spot;
bool moved = true;
int i = 0, button = 0, lastbutton = 0;
short r = 0, c = 0;
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);
add_undo(button);
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 */
case SOKOBAN_UNDO | BUTTON_REPEAT:
#endif
/* this is UNDO */
undo();
rb->lcd_clear_display();
update_screen();
moved = false;
break;
case SOKOBAN_LEVEL_UP:
case SOKOBAN_LEVEL_UP | BUTTON_REPEAT:
/* increase level */
init_undo();
current_info.level.boxes_to_go=0;
moved = true;
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;
case SOKOBAN_LEVEL_REPEAT:
case SOKOBAN_LEVEL_REPEAT | BUTTON_REPEAT:
/* same level */
init_undo();
draw_level();
moved = false;
break;
case BUTTON_LEFT:
switch(current_info.board[r][c-1])
{
case ' ': /* if it is a blank spot */
case '.': /* if it is a home spot */
new_spot = current_info.board[r][c-1];
current_info.board[r][c-1] = '@';
current_info.board[r][c] = current_info.player.spot;
current_info.player.spot = new_spot;
break;
case '$':
switch(current_info.board[r][c-2])
{
case ' ': /* going from blank to blank */
current_info.board[r][c-2] = current_info.board[r][c-1];
current_info.board[r][c-1] = current_info.board[r][c];
current_info.board[r][c] = current_info.player.spot;
current_info.player.spot = ' ';
break;
case '.': /* going from a blank to home */
current_info.board[r][c-2] = '%';
current_info.board[r][c-1] = current_info.board[r][c];
current_info.board[r][c] = current_info.player.spot;
current_info.player.spot = ' ';
current_info.level.boxes_to_go--;
break;
default:
moved = false;
break;
}
break;
case '%':
switch(current_info.board[r][c-2]) {
case ' ': /* we are going from a home to a blank */
current_info.board[r][c-2] = '$';
current_info.board[r][c-1] = current_info.board[r][c];
current_info.board[r][c] = current_info.player.spot;
current_info.player.spot = '.';
current_info.level.boxes_to_go++;
break;
case '.': /* if we are going from a home to home */
current_info.board[r][c-2] = '%';
current_info.board[r][c-1] = current_info.board[r][c];
current_info.board[r][c] = current_info.player.spot;
current_info.player.spot = '.';
break;
default:
moved = false;
break;
}
break;
default:
moved = false;
break;
}
if (moved)
current_info.player.col--;
break;
case BUTTON_RIGHT: /* if it is a blank spot */
switch(current_info.board[r][c+1]) {
case ' ':
case '.': /* if it is a home spot */
new_spot = current_info.board[r][c+1];
current_info.board[r][c+1] = '@';
current_info.board[r][c] = current_info.player.spot;
current_info.player.spot = new_spot;
break;
case '$':
switch(current_info.board[r][c+2]) {
case ' ': /* going from blank to blank */
current_info.board[r][c+2] = current_info.board[r][c+1];
current_info.board[r][c+1] = current_info.board[r][c];
current_info.board[r][c] = current_info.player.spot;
current_info.player.spot = ' ';
break;
case '.': /* going from a blank to home */
current_info.board[r][c+2] = '%';
current_info.board[r][c+1] = current_info.board[r][c];
current_info.board[r][c] = current_info.player.spot;
current_info.player.spot = ' ';
current_info.level.boxes_to_go--;
break;
default:
moved = false;
break;
}
break;
case '%':
switch(current_info.board[r][c+2]) {
case ' ': /* going from a home to a blank */
current_info.board[r][c+2] = '$';
current_info.board[r][c+1] = current_info.board[r][c];
current_info.board[r][c] = current_info.player.spot;
current_info.player.spot = '.';
current_info.level.boxes_to_go++;
break;
case '.':
current_info.board[r][c+2] = '%';
current_info.board[r][c+1] = current_info.board[r][c];
current_info.board[r][c] = current_info.player.spot;
current_info.player.spot = '.';
break;
default:
moved = false;
break;
}
break;
default:
moved = false;
break;
}
if (moved)
current_info.player.col++;
break;
case SOKOBAN_UP:
switch(current_info.board[r-1][c]) {
case ' ': /* if it is a blank spot */
case '.': /* if it is a home spot */
new_spot = current_info.board[r-1][c];
current_info.board[r-1][c] = '@';
current_info.board[r][c] = current_info.player.spot;
current_info.player.spot = new_spot;
break;
case '$':
switch(current_info.board[r-2][c]) {
case ' ': /* going from blank to blank */
current_info.board[r-2][c] = current_info.board[r-1][c];
current_info.board[r-1][c] = current_info.board[r][c];
current_info.board[r][c] = current_info.player.spot;
current_info.player.spot = ' ';
break;
case '.': /* going from a blank to home */
current_info.board[r-2][c] = '%';
current_info.board[r-1][c] = current_info.board[r][c];
current_info.board[r][c] = current_info.player.spot;
current_info.player.spot = ' ';
current_info.level.boxes_to_go--;
break;
default:
moved = false;
break;
}
break;
case '%':
switch(current_info.board[r-2][c]) {
case ' ': /* we are going from a home to a blank */
current_info.board[r-2][c] = '$';
current_info.board[r-1][c] = current_info.board[r][c];
current_info.board[r][c] = current_info.player.spot;
current_info.player.spot = '.';
current_info.level.boxes_to_go++;
break;
case '.': /* if we are going from a home to home */
current_info.board[r-2][c] = '%';
current_info.board[r-1][c] = current_info.board[r][c];
current_info.board[r][c] = current_info.player.spot;
current_info.player.spot = '.';
break;
default:
moved = false;
break;
}
break;
default:
moved = false;
break;
}
if (moved)
current_info.player.row--;
break;
case SOKOBAN_DOWN:
switch(current_info.board[r+1][c]) {
case ' ': /* if it is a blank spot */
case '.': /* if it is a home spot */
new_spot = current_info.board[r+1][c];
current_info.board[r+1][c] = '@';
current_info.board[r][c] = current_info.player.spot;
current_info.player.spot = new_spot;
break;
case '$':
switch(current_info.board[r+2][c]) {
case ' ': /* going from blank to blank */
current_info.board[r+2][c] = current_info.board[r+1][c];
current_info.board[r+1][c] = current_info.board[r][c];
current_info.board[r][c] = current_info.player.spot;
current_info.player.spot = ' ';
break;
case '.': /* going from a blank to home */
current_info.board[r+2][c] = '%';
current_info.board[r+1][c] = current_info.board[r][c];
current_info.board[r][c] = current_info.player.spot;
current_info.player.spot = ' ';
current_info.level.boxes_to_go--;
break;
default:
moved = false;
break;
}
break;
case '%':
switch(current_info.board[r+2][c]) {
case ' ': /* going from a home to a blank */
current_info.board[r+2][c] = '$';
current_info.board[r+1][c] = current_info.board[r][c];
current_info.board[r][c] = current_info.player.spot;
current_info.player.spot = '.';
current_info.level.boxes_to_go++;
break;
case '.': /* going from a home to home */
current_info.board[r+2][c] = '%';
current_info.board[r+1][c] = current_info.board[r][c];
current_info.board[r][c] = current_info.player.spot;
current_info.player.spot = '.';
break;
default:
moved = false;
break;
}
break;
default:
moved = false;
break;
}
if (moved)
current_info.player.row++;
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) {
current_info.level.moves++;
rb->lcd_clear_display();
update_screen();
}
/* We have completed this level */
if (current_info.level.boxes_to_go == 0) {
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->lcd_putsxy(LCD_WIDTH/2 - 27,(LCD_HEIGHT/2) - 4 ,
"You WIN!!");
rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
/* Pattern Fills whole screen now on all screen sizes */
for (i = 0; i < 30000 ; 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 len;
(void)(parameter);
rb = api;
rb->lcd_setfont(FONT_SYSFIXED);
rb->lcd_getstringsize(SOKOBAN_TITLE, &w, &h);
#ifdef HAVE_LCD_COLOR
rb->lcd_set_background(BG_COLOR);
#endif
/* Get horizontel centering for text */
len = w;
if (len%2 != 0)
len =((len+1)/2)+(w/2);
else
len /= 2;
if (h%2 != 0)
h = (h/2)+1;
else
h /= 2;
rb->lcd_clear_display();
rb->lcd_putsxy(LCD_WIDTH/2-len,(LCD_HEIGHT/2)-h, SOKOBAN_TITLE);
rb->lcd_update();
rb->sleep(HZ*2);
rb->lcd_clear_display();
#if CONFIG_KEYPAD == RECORDER_PAD
rb->lcd_putsxy(3, 6, "[OFF] Quit");
rb->lcd_putsxy(3, 16, "[ON] Undo");
rb->lcd_putsxy(3, 26, "[F1] Down a Level");
rb->lcd_putsxy(3, 36, "[F2] Restart Level");
rb->lcd_putsxy(3, 46, "[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, "[M-LEFT] Down a Level");
rb->lcd_putsxy(3, 36, "[M-UP] Restart Level");
rb->lcd_putsxy(3, 46, "[M-RIGHT] Up Level");
#elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
(CONFIG_KEYPAD == IRIVER_H300_PAD)
rb->lcd_putsxy(3, 6, "[STOP] Stop");
rb->lcd_putsxy(3, 16, "[PLAY] Undo");
rb->lcd_putsxy(3, 26, "[REC] Down a Level");
rb->lcd_putsxy(3, 36, "[SELECT] Restart Level");
rb->lcd_putsxy(3, 46, "[MODE] + Up a Level");
#endif
rb->lcd_update();
rb->sleep(HZ*2);
rb->lcd_clear_display();
init_boards();
if (read_levels(1) != 0) {
rb->splash(HZ*2, true, "Failed loading levels!");
return PLUGIN_OK;
}
return sokoban_loop();
}
#endif