Add 2008 to the copyright notice.
[Rockbox.git] / apps / plugins / sokoban.c
blobaaf314a154797a8edb5e06a84792be5f26f426cb
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002 Eric Linenberg
11 * February 2003: Robert Hak performs a cleanup/rewrite/feature addition.
12 * Eric smiles. Bjorn cries. Linus say 'huh?'.
13 * March 2007: Sean Morrisey performs a major rewrite/feature addition.
15 * All files in this archive are subject to the GNU General Public License.
16 * See the file COPYING in the source tree root for full license agreement.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 ****************************************************************************/
22 #include "plugin.h"
23 #include "lib/playback_control.h"
25 #ifdef HAVE_LCD_BITMAP
27 PLUGIN_HEADER
29 #if LCD_DEPTH >= 2 && ((LCD_HEIGHT >= 96 && LCD_WIDTH >= 152) || \
30 (LCD_HEIGHT >= 121 && LCD_WIDTH >= 120))
31 extern const fb_data sokoban_tiles[];
32 #endif
34 #define SOKOBAN_TITLE "Sokoban"
36 #define SOKOBAN_LEVELS_FILE PLUGIN_GAMES_DIR "/sokoban.levels"
37 #define SOKOBAN_SAVE_FILE PLUGIN_GAMES_DIR "/sokoban.save"
38 #define SOKOBAN_SAVE_FOLDER "/games"
40 /* Magnify is the number of pixels for each block.
41 * Set dynamically so all targets can support levels
42 * that fill their entire screen, less the stat box.
43 * 16 rows & 20 cols minimum */
44 #if (LCD_HEIGHT >= 224) && (LCD_WIDTH >= 320)
45 #define MAGNIFY 14
46 #define ROWS (LCD_HEIGHT/MAGNIFY)
47 #define COLS ((LCD_WIDTH-40)/MAGNIFY)
48 #elif (LCD_HEIGHT >= 249) && (LCD_WIDTH >= 280)
49 #define MAGNIFY 14
50 #define ROWS ((LCD_HEIGHT-25)/MAGNIFY)
51 #define COLS (LCD_WIDTH/MAGNIFY)
52 #elif (LCD_HEIGHT >= 144) && (LCD_WIDTH >= 220)
53 #define MAGNIFY 9
54 #define ROWS (LCD_HEIGHT/MAGNIFY)
55 #define COLS ((LCD_WIDTH-40)/MAGNIFY)
56 #elif (LCD_HEIGHT >= 169) && (LCD_WIDTH+4 >= 180) /* plus 4 for sansa */
57 #define MAGNIFY 9
58 #define ROWS ((LCD_HEIGHT-25)/MAGNIFY)
59 #define COLS ((LCD_WIDTH+4)/MAGNIFY)
60 #elif (LCD_HEIGHT >= 96) && (LCD_WIDTH >= 160)
61 #define MAGNIFY 6
62 #define ROWS (LCD_HEIGHT/MAGNIFY)
63 #define COLS ((LCD_WIDTH-40)/MAGNIFY)
64 #elif (LCD_HEIGHT >= 121) && (LCD_WIDTH >= 120)
65 #define MAGNIFY 6
66 #define ROWS ((LCD_HEIGHT-25)/MAGNIFY)
67 #define COLS (LCD_WIDTH/MAGNIFY)
68 #else
69 #define MAGNIFY 4
70 #define ROWS 16
71 #define COLS 20
72 #endif
74 /* Use either all but 16k of the plugin buffer for level data
75 * or 128k, which ever is less */
76 #if PLUGIN_BUFFER_SIZE - 0x4000 < 0x20000
77 #define MAX_LEVEL_DATA (PLUGIN_BUFFER_SIZE - 0x4000)
78 #else
79 #define MAX_LEVEL_DATA 0x20000
80 #endif
82 /* Number of levels for which to allocate buffer indexes */
83 #define MAX_LEVELS MAX_LEVEL_DATA/70
85 /* Use 4k plus remaining plugin buffer (-12k for prog) for undo, up to 64k */
86 #if PLUGIN_BUFFER_SIZE - MAX_LEVEL_DATA - 0x3000 > 0x10000
87 #define MAX_UNDOS 0x10000
88 #else
89 #define MAX_UNDOS (PLUGIN_BUFFER_SIZE - MAX_LEVEL_DATA - 0x3000)
90 #endif
92 /* Move/push definitions for undo */
93 #define SOKOBAN_PUSH_LEFT 'L'
94 #define SOKOBAN_PUSH_RIGHT 'R'
95 #define SOKOBAN_PUSH_UP 'U'
96 #define SOKOBAN_PUSH_DOWN 'D'
97 #define SOKOBAN_MOVE_LEFT 'l'
98 #define SOKOBAN_MOVE_RIGHT 'r'
99 #define SOKOBAN_MOVE_UP 'u'
100 #define SOKOBAN_MOVE_DOWN 'd'
102 #define SOKOBAN_MOVE_DIFF (SOKOBAN_MOVE_LEFT-SOKOBAN_PUSH_LEFT)
103 #define SOKOBAN_MOVE_MIN SOKOBAN_MOVE_DOWN
105 /* variable button definitions */
106 #if (CONFIG_KEYPAD == RECORDER_PAD) || \
107 (CONFIG_KEYPAD == ARCHOS_AV300_PAD)
108 #define SOKOBAN_UP BUTTON_UP
109 #define SOKOBAN_DOWN BUTTON_DOWN
110 #define SOKOBAN_MENU BUTTON_OFF
111 #define SOKOBAN_UNDO BUTTON_ON
112 #define SOKOBAN_REDO BUTTON_PLAY
113 #define SOKOBAN_LEVEL_DOWN BUTTON_F1
114 #define SOKOBAN_LEVEL_REPEAT BUTTON_F2
115 #define SOKOBAN_LEVEL_UP BUTTON_F3
116 #define SOKOBAN_PAUSE BUTTON_PLAY
117 #define BUTTON_SAVE BUTTON_ON
118 #define BUTTON_SAVE_NAME "ON"
120 #elif CONFIG_KEYPAD == ONDIO_PAD
121 #define SOKOBAN_UP BUTTON_UP
122 #define SOKOBAN_DOWN BUTTON_DOWN
123 #define SOKOBAN_MENU BUTTON_OFF
124 #define SOKOBAN_UNDO_PRE BUTTON_MENU
125 #define SOKOBAN_UNDO (BUTTON_MENU | BUTTON_REL)
126 #define SOKOBAN_REDO (BUTTON_MENU | BUTTON_DOWN)
127 #define SOKOBAN_LEVEL_DOWN (BUTTON_MENU | BUTTON_LEFT)
128 #define SOKOBAN_LEVEL_REPEAT (BUTTON_MENU | BUTTON_UP)
129 #define SOKOBAN_LEVEL_UP (BUTTON_MENU | BUTTON_RIGHT)
130 #define SOKOBAN_PAUSE BUTTON_MENU
131 #define BUTTON_SAVE BUTTON_MENU
132 #define BUTTON_SAVE_NAME "MENU"
134 #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
135 (CONFIG_KEYPAD == IRIVER_H300_PAD)
136 #define SOKOBAN_UP BUTTON_UP
137 #define SOKOBAN_DOWN BUTTON_DOWN
138 #define SOKOBAN_MENU BUTTON_OFF
139 #define SOKOBAN_UNDO BUTTON_REC
140 #define SOKOBAN_REDO BUTTON_MODE
141 #define SOKOBAN_LEVEL_DOWN (BUTTON_ON | BUTTON_DOWN)
142 #define SOKOBAN_LEVEL_REPEAT BUTTON_ON
143 #define SOKOBAN_LEVEL_UP (BUTTON_ON | BUTTON_UP)
144 #define SOKOBAN_PAUSE BUTTON_ON
145 #define BUTTON_SAVE BUTTON_MODE
146 #define BUTTON_SAVE_NAME "MODE"
148 #define SOKOBAN_RC_MENU BUTTON_RC_STOP
150 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
151 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
152 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
153 #define SOKOBAN_UP BUTTON_MENU
154 #define SOKOBAN_DOWN BUTTON_PLAY
155 #define SOKOBAN_MENU (BUTTON_SELECT | BUTTON_MENU)
156 #define SOKOBAN_UNDO_PRE BUTTON_SELECT
157 #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
158 #define SOKOBAN_REDO (BUTTON_SELECT | BUTTON_PLAY)
159 #define SOKOBAN_LEVEL_DOWN (BUTTON_SELECT | BUTTON_LEFT)
160 #define SOKOBAN_LEVEL_UP (BUTTON_SELECT | BUTTON_RIGHT)
161 #define SOKOBAN_PAUSE BUTTON_SELECT
162 #define BUTTON_SAVE BUTTON_SELECT
163 #define BUTTON_SAVE_NAME "SELECT"
165 /* FIXME: if/when simultaneous button presses work for X5/M5,
166 * add level up/down */
167 #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
168 #define SOKOBAN_UP BUTTON_UP
169 #define SOKOBAN_DOWN BUTTON_DOWN
170 #define SOKOBAN_MENU BUTTON_POWER
171 #define SOKOBAN_UNDO_PRE BUTTON_SELECT
172 #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
173 #define SOKOBAN_LEVEL_REPEAT BUTTON_REC
174 #define SOKOBAN_REDO BUTTON_PLAY
175 #define SOKOBAN_PAUSE BUTTON_PLAY
176 #define BUTTON_SAVE BUTTON_SELECT
177 #define BUTTON_SAVE_NAME "SELECT"
179 #elif CONFIG_KEYPAD == IRIVER_H10_PAD
180 #define SOKOBAN_UP BUTTON_SCROLL_UP
181 #define SOKOBAN_DOWN BUTTON_SCROLL_DOWN
182 #define SOKOBAN_MENU BUTTON_POWER
183 #define SOKOBAN_UNDO_PRE BUTTON_REW
184 #define SOKOBAN_UNDO (BUTTON_REW | BUTTON_REL)
185 #define SOKOBAN_REDO BUTTON_FF
186 #define SOKOBAN_LEVEL_DOWN (BUTTON_PLAY | BUTTON_SCROLL_DOWN)
187 #define SOKOBAN_LEVEL_REPEAT (BUTTON_PLAY | BUTTON_RIGHT)
188 #define SOKOBAN_LEVEL_UP (BUTTON_PLAY | BUTTON_SCROLL_UP)
189 #define SOKOBAN_PAUSE BUTTON_PLAY
190 #define BUTTON_SAVE BUTTON_PLAY
191 #define BUTTON_SAVE_NAME "PLAY"
193 #elif CONFIG_KEYPAD == GIGABEAT_PAD
194 #define SOKOBAN_UP BUTTON_UP
195 #define SOKOBAN_DOWN BUTTON_DOWN
196 #define SOKOBAN_MENU BUTTON_POWER
197 #define SOKOBAN_UNDO BUTTON_SELECT
198 #define SOKOBAN_REDO BUTTON_A
199 #define SOKOBAN_LEVEL_DOWN BUTTON_VOL_DOWN
200 #define SOKOBAN_LEVEL_REPEAT BUTTON_MENU
201 #define SOKOBAN_LEVEL_UP BUTTON_VOL_UP
202 #define SOKOBAN_PAUSE BUTTON_SELECT
203 #define BUTTON_SAVE BUTTON_SELECT
204 #define BUTTON_SAVE_NAME "SELECT"
206 #elif CONFIG_KEYPAD == SANSA_E200_PAD
207 #define SOKOBAN_UP BUTTON_UP
208 #define SOKOBAN_DOWN BUTTON_DOWN
209 #define SOKOBAN_MENU BUTTON_POWER
210 #define SOKOBAN_UNDO_PRE BUTTON_SELECT
211 #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
212 #define SOKOBAN_REDO BUTTON_REC
213 #define SOKOBAN_LEVEL_DOWN (BUTTON_SELECT | BUTTON_DOWN)
214 #define SOKOBAN_LEVEL_REPEAT (BUTTON_SELECT | BUTTON_RIGHT)
215 #define SOKOBAN_LEVEL_UP (BUTTON_SELECT | BUTTON_UP)
216 #define SOKOBAN_PAUSE BUTTON_SELECT
217 #define BUTTON_SAVE BUTTON_SELECT
218 #define BUTTON_SAVE_NAME "SELECT"
220 #elif CONFIG_KEYPAD == GIGABEAT_S_PAD
221 #define SOKOBAN_UP BUTTON_UP
222 #define SOKOBAN_DOWN BUTTON_DOWN
223 #define SOKOBAN_MENU BUTTON_MENU
224 #define SOKOBAN_UNDO BUTTON_VOL_UP
225 #define SOKOBAN_REDO BUTTON_VOL_DOWN
226 #define SOKOBAN_LEVEL_DOWN BUTTON_PREV
227 #define SOKOBAN_LEVEL_REPEAT BUTTON_PLAY
228 #define SOKOBAN_LEVEL_UP BUTTON_NEXT
229 #define SOKOBAN_PAUSE BUTTON_SELECT
230 #define BUTTON_SAVE BUTTON_SELECT
231 #define BUTTON_SAVE_NAME "SELECT"
233 #endif
235 #define SOKOBAN_FONT FONT_SYSFIXED
238 /* The Location, Undo and LevelInfo structs are OO-flavored.
239 * (oooh!-flavored as Schnueff puts it.) It makes more you have to know,
240 * but the overall data layout becomes more manageable. */
242 /* Level data & stats */
243 struct LevelInfo {
244 int index; /* Level index (level number - 1) */
245 int moves; /* Moves & pushes for the stats */
246 int pushes;
247 short boxes_to_go; /* Number of unplaced boxes remaining in level */
248 short height; /* Height & width for centering level display */
249 short width;
252 struct Location {
253 short row;
254 short col;
257 /* Our full undo history */
258 static struct UndoInfo {
259 int count; /* How many undos have been done */
260 int current; /* Which history is the current undo */
261 int max; /* Which history is the max redoable */
262 char history[MAX_UNDOS];
263 } undo_info;
265 /* Our playing board */
266 static struct BoardInfo {
267 char board[ROWS][COLS]; /* The current board data */
268 struct LevelInfo level; /* Level data & stats */
269 struct Location player; /* Where the player is */
270 int max_level; /* The number of levels we have */
271 } current_info;
273 static struct BufferedBoards {
274 char filename[MAX_PATH]; /* Filename of the levelset we're using */
275 char data[MAX_LEVEL_DATA]; /* Buffered level data */
276 int index[MAX_LEVELS + 1]; /* Where each buffered board begins & ends */
277 int start; /* Index of first buffered board */
278 int end; /* Index of last buffered board */
279 short prebuffered_boards; /* Number of boards before current to store */
280 } buffered_boards;
283 static struct plugin_api* rb;
284 MEM_FUNCTION_WRAPPERS(rb);
286 static char buf[ROWS*(COLS + 1)]; /* Enough for a whole board or a filename */
289 static void init_undo(void)
291 undo_info.count = 0;
292 undo_info.current = 0;
293 undo_info.max = 0;
296 static void get_delta(char direction, short *d_r, short *d_c)
298 switch (direction) {
299 case SOKOBAN_PUSH_LEFT:
300 case SOKOBAN_MOVE_LEFT:
301 *d_r = 0;
302 *d_c = -1;
303 break;
304 case SOKOBAN_PUSH_RIGHT:
305 case SOKOBAN_MOVE_RIGHT:
306 *d_r = 0;
307 *d_c = 1;
308 break;
309 case SOKOBAN_PUSH_UP:
310 case SOKOBAN_MOVE_UP:
311 *d_r = -1;
312 *d_c = 0;
313 break;
314 case SOKOBAN_PUSH_DOWN:
315 case SOKOBAN_MOVE_DOWN:
316 *d_r = 1;
317 *d_c = 0;
321 static bool undo(void)
323 char undo;
324 short r, c;
325 short d_r = 0, d_c = 0; /* delta row & delta col */
326 char *space_cur, *space_next, *space_prev;
327 bool undo_push = false;
329 /* If no more undos or we've wrapped all the way around, quit */
330 if (undo_info.count == 0 || undo_info.current - 1 == undo_info.max)
331 return false;
333 /* Move to previous undo in the list */
334 if (undo_info.current == 0 && undo_info.count > 1)
335 undo_info.current = MAX_UNDOS - 1;
336 else
337 undo_info.current--;
339 undo_info.count--;
341 undo = undo_info.history[undo_info.current];
343 if (undo < SOKOBAN_MOVE_MIN)
344 undo_push = true;
346 get_delta(undo, &d_r, &d_c);
348 r = current_info.player.row;
349 c = current_info.player.col;
351 /* Give the 3 spaces we're going to use better names */
352 space_cur = &current_info.board[r][c];
353 space_next = &current_info.board[r + d_r][c + d_c];
354 space_prev = &current_info.board[r - d_r][c - d_c];
356 /* Update board info */
357 if (undo_push) {
358 /* Moving box from goal to floor */
359 if (*space_next == '*' && *space_cur == '@')
360 current_info.level.boxes_to_go++;
361 /* Moving box from floor to goal */
362 else if (*space_next == '$' && *space_cur == '+')
363 current_info.level.boxes_to_go--;
365 /* Move box off of next space... */
366 *space_next = (*space_next == '*' ? '.' : ' ');
367 /* ...and on to current space */
368 *space_cur = (*space_cur == '+' ? '*' : '$');
370 current_info.level.pushes--;
371 } else
372 /* Just move player off of current space */
373 *space_cur = (*space_cur == '+' ? '.' : ' ');
374 /* Move player back to previous space */
375 *space_prev = (*space_prev == '.' ? '+' : '@');
377 /* Update position */
378 current_info.player.row -= d_r;
379 current_info.player.col -= d_c;
381 current_info.level.moves--;
383 return true;
386 static void add_undo(char undo)
388 undo_info.history[undo_info.current] = undo;
390 /* Wrap around if MAX_UNDOS exceeded */
391 if (undo_info.current < (MAX_UNDOS - 1))
392 undo_info.current++;
393 else
394 undo_info.current = 0;
396 if (undo_info.count < MAX_UNDOS)
397 undo_info.count++;
400 static bool move(char direction, bool redo)
402 short r, c;
403 short d_r = 0, d_c = 0; /* delta row & delta col */
404 char *space_cur, *space_next, *space_beyond;
405 bool push = false;
407 get_delta(direction, &d_r, &d_c);
409 r = current_info.player.row;
410 c = current_info.player.col;
412 /* Check for out-of-bounds */
413 if (r + 2*d_r < 0 || r + 2*d_r >= ROWS ||
414 c + 2*d_c < 0 || c + 2*d_c >= COLS)
415 return false;
417 /* Give the 3 spaces we're going to use better names */
418 space_cur = &current_info.board[r][c];
419 space_next = &current_info.board[r + d_r][c + d_c];
420 space_beyond = &current_info.board[r + 2*d_r][c + 2*d_c];
422 if (*space_next == '$' || *space_next == '*') {
423 /* Change direction from move to push for undo */
424 if (direction >= SOKOBAN_MOVE_MIN)
425 direction -= SOKOBAN_MOVE_DIFF;
426 push = true;
428 else if (direction < SOKOBAN_MOVE_MIN)
429 /* Change back to move if redo/solution playback push is invalid */
430 direction += SOKOBAN_MOVE_DIFF;
432 /* Update board info */
433 if (push) {
434 /* Moving box from goal to floor */
435 if (*space_next == '*' && *space_beyond == ' ')
436 current_info.level.boxes_to_go++;
437 /* Moving box from floor to goal */
438 else if (*space_next == '$' && *space_beyond == '.')
439 current_info.level.boxes_to_go--;
440 /* Check for invalid move */
441 else if (*space_beyond != '.' && *space_beyond != ' ')
442 return false;
444 /* Move player onto next space */
445 *space_next = (*space_next == '*' ? '+' : '@');
446 /* Move box onto space beyond next */
447 *space_beyond = (*space_beyond == '.' ? '*' : '$');
449 current_info.level.pushes++;
450 } else {
451 /* Check for invalid move */
452 if (*space_next != '.' && *space_next != ' ')
453 return false;
455 /* Move player onto next space */
456 *space_next = (*space_next == '.' ? '+' : '@');
458 /* Move player off of current space */
459 *space_cur = (*space_cur == '+' ? '.' : ' ');
461 /* Update position */
462 current_info.player.row += d_r;
463 current_info.player.col += d_c;
465 current_info.level.moves++;
467 /* Update undo_info.max to current on every normal move,
468 * except if it's the same as a redo. */
469 /* normal move and either */
470 if (!redo &&
471 /* moves have been undone... */
472 ((undo_info.max != undo_info.current &&
473 /* ...and the current move is NOT the same as the one in history */
474 undo_info.history[undo_info.current] != direction) ||
475 /* or moves have not been undone */
476 undo_info.max == undo_info.current)) {
477 add_undo(direction);
478 undo_info.max = undo_info.current;
479 } else /* redo move or move was same as redo */
480 add_undo(direction); /* add_undo to update current */
482 return true;
485 #ifdef SOKOBAN_REDO
486 static bool redo(void)
488 /* If no moves have been undone, quit */
489 if (undo_info.current == undo_info.max)
490 return false;
492 return move(undo_info.history[(undo_info.current < MAX_UNDOS ?
493 undo_info.current : 0)], true);
495 #endif
497 static void init_boards(void)
499 rb->strncpy(buffered_boards.filename, SOKOBAN_LEVELS_FILE, MAX_PATH);
501 current_info.level.index = 0;
502 current_info.player.row = 0;
503 current_info.player.col = 0;
504 current_info.max_level = 0;
506 buffered_boards.start = 0;
507 buffered_boards.end = 0;
508 buffered_boards.prebuffered_boards = 0;
510 init_undo();
513 static bool read_levels(bool initialize)
515 int fd = 0;
516 short len;
517 short lastlen = 0;
518 short row = 0;
519 int level_count = 0;
521 int i = 0;
522 int level_len = 0;
523 bool index_set = false;
525 /* Get the index of the first level to buffer */
526 if (current_info.level.index > buffered_boards.prebuffered_boards &&
527 !initialize)
528 buffered_boards.start = current_info.level.index -
529 buffered_boards.prebuffered_boards;
530 else
531 buffered_boards.start = 0;
533 if ((fd = rb->open(buffered_boards.filename, O_RDONLY)) < 0) {
534 rb->splash(HZ*2, "Unable to open %s", buffered_boards.filename);
535 return false;
538 do {
539 len = rb->read_line(fd, buf, sizeof(buf));
541 /* Correct len when trailing \r's or \n's are counted */
542 if (len > 2 && buf[len - 2] == '\0')
543 len -= 2;
544 else if (len > 1 && buf[len - 1] == '\0')
545 len--;
547 /* Skip short lines & lines with non-level data */
548 if (len >= 3 && ((buf[0] >= '1' && buf[0] <= '9') || buf[0] == '#' ||
549 buf[0] == ' ' || buf[0] == '-' || buf[0] == '_')) {
550 if (level_count >= buffered_boards.start) {
551 /* Set the index of this level */
552 if (!index_set &&
553 level_count - buffered_boards.start < MAX_LEVELS) {
554 buffered_boards.index[level_count - buffered_boards.start]
555 = i;
556 index_set = true;
558 /* Copy buffer to board data */
559 if (i + level_len + len < MAX_LEVEL_DATA) {
560 rb->memcpy(&buffered_boards.data[i + level_len], buf, len);
561 buffered_boards.data[i + level_len + len] = '\n';
564 level_len += len + 1;
565 row++;
567 /* If newline & level is tall enough or is RLE */
568 } else if (buf[0] == '\0' && (row > 2 || lastlen > 22)) {
569 level_count++;
570 if (level_count >= buffered_boards.start) {
571 i += level_len;
572 if (i < MAX_LEVEL_DATA)
573 buffered_boards.end = level_count;
574 else if (!initialize)
575 break;
577 row = 0;
578 level_len = 0;
579 index_set = false;
581 } else if (len > 22)
582 len = 1;
584 } while ((lastlen = len));
586 /* Set the index of the end of the last level */
587 if (level_count - buffered_boards.start < MAX_LEVELS)
588 buffered_boards.index[level_count - buffered_boards.start] = i;
590 if (initialize) {
591 current_info.max_level = level_count;
592 buffered_boards.prebuffered_boards = buffered_boards.end/2;
595 rb->close(fd);
597 return true;
600 static void load_level(void)
602 int c, r;
603 int i, n;
604 int level_size;
605 int index = current_info.level.index - buffered_boards.start;
606 char *level;
608 /* Get the buffered board index of the current level */
609 if (current_info.level.index < buffered_boards.start ||
610 current_info.level.index >= buffered_boards.end) {
611 read_levels(false);
612 if (current_info.level.index > buffered_boards.prebuffered_boards)
613 index = buffered_boards.prebuffered_boards;
614 else
615 index = current_info.level.index;
617 level = &buffered_boards.data[buffered_boards.index[index]];
619 /* Reset level info */
620 current_info.level.moves = 0;
621 current_info.level.pushes = 0;
622 current_info.level.boxes_to_go = 0;
623 current_info.level.width = 0;
625 /* Clear board */
626 for (r = 0; r < ROWS; r++)
627 for (c = 0; c < COLS; c++)
628 current_info.board[r][c] = 'X';
630 level_size = buffered_boards.index[index + 1] -
631 buffered_boards.index[index];
633 for (r = 0, c = 0, n = 1, i = 0; i < level_size; i++) {
634 if (level[i] == '\n' || level[i] == '|') {
635 if (c > 3) {
636 /* Update max width of level & go to next row */
637 if (c > current_info.level.width)
638 current_info.level.width = c;
639 c = 0;
640 r++;
641 if (r >= ROWS)
642 break;
644 } else if (c < COLS) {
645 /* Read RLE character's length into n */
646 if (level[i] >= '0' && level[i] <= '9') {
647 n = level[i++] - '0';
648 if (level[i] >= '0' && level[i] <= '9')
649 n = n*10 + level[i++] - '0';
652 /* Cleanup & replace */
653 if (level[i] == '%')
654 level[i] = '*';
655 else if (level[i] == '-' || level[i] == '_')
656 level[i] = ' ';
658 if (n > 1) {
659 if (c + n >= COLS)
660 n = COLS - c;
662 if (level[i] == '.')
663 current_info.level.boxes_to_go += n;
665 /* Put RLE character n times */
666 while (n--)
667 current_info.board[r][c++] = level[i];
668 n = 1;
670 } else {
671 if (level[i] == '.' || level[i] == '+')
672 current_info.level.boxes_to_go++;
674 if (level[i] == '@' ||level[i] == '+') {
675 current_info.player.row = r;
676 current_info.player.col = c;
679 current_info.board[r][c++] = level[i];
684 current_info.level.height = r;
686 #if LCD_DEPTH > 2
687 /* Fill in blank space outside level on color targets */
688 for (r = 0; r < ROWS; r++)
689 for (c = 0; current_info.board[r][c] == ' ' && c < COLS; c++)
690 current_info.board[r][c] = 'X';
692 for (c = 0; c < COLS; c++) {
693 for (r = 0; (current_info.board[r][c] == ' ' ||
694 current_info.board[r][c] == 'X') && r < ROWS; r++)
695 current_info.board[r][c] = 'X';
696 for (r = ROWS - 1; (current_info.board[r][c] == ' ' ||
697 current_info.board[r][c] == 'X') && r >= 0; r--)
698 current_info.board[r][c] = 'X';
700 #endif
703 static void update_screen(void)
705 int c, r;
706 int rows, cols;
708 #if LCD_DEPTH < 2 || ((LCD_HEIGHT < 96 || LCD_WIDTH < 152) && \
709 (LCD_HEIGHT < 121 || LCD_WIDTH < 120))
710 int i, j;
711 int max = MAGNIFY - 1;
712 int middle = max/2;
713 int ldelta = (middle + 1)/2;
714 #endif
716 #if LCD_WIDTH - (COLS*MAGNIFY) < 32
717 #define STAT_HEIGHT 25
718 #define STAT_X (LCD_WIDTH - 120)/2
719 #define STAT_Y (LCD_HEIGHT - STAT_HEIGHT)
720 #define BOARD_WIDTH LCD_WIDTH
721 #define BOARD_HEIGHT (LCD_HEIGHT - STAT_HEIGHT)
722 rb->lcd_putsxy(STAT_X + 4, STAT_Y + 4, "Level");
723 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.index + 1);
724 rb->lcd_putsxy(STAT_X + 7, STAT_Y + 14, buf);
725 rb->lcd_putsxy(STAT_X + 41, STAT_Y + 4, "Moves");
726 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.moves);
727 rb->lcd_putsxy(STAT_X + 44, STAT_Y + 14, buf);
728 rb->lcd_putsxy(STAT_X + 79, STAT_Y + 4, "Pushes");
729 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.pushes);
730 rb->lcd_putsxy(STAT_X + 82, STAT_Y + 14, buf);
732 rb->lcd_drawrect(STAT_X, STAT_Y, 38, STAT_HEIGHT);
733 rb->lcd_drawrect(STAT_X + 37, STAT_Y, 39, STAT_HEIGHT);
734 rb->lcd_drawrect(STAT_X + 75, STAT_Y, 45, STAT_HEIGHT);
735 #else
736 #if LCD_WIDTH - (COLS*MAGNIFY) > 40
737 #define STAT_X (LCD_WIDTH - 40)
738 #else
739 #define STAT_X COLS*MAGNIFY
740 #endif
741 #if LCD_HEIGHT >= 70
742 #define STAT_Y (LCD_HEIGHT - 70)/2
743 #else
744 #define STAT_Y (LCD_HEIGHT - 47)/2
745 #endif
746 #define STAT_WIDTH (LCD_WIDTH - STAT_X)
747 #define BOARD_WIDTH (LCD_WIDTH - STAT_WIDTH)
748 #define BOARD_HEIGHT LCD_HEIGHT
749 rb->lcd_putsxy(STAT_X + 1, STAT_Y + 3, "Level");
750 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.index + 1);
751 rb->lcd_putsxy(STAT_X + 4, STAT_Y + 13, buf);
752 rb->lcd_putsxy(STAT_X + 1, STAT_Y + 26, "Moves");
753 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.moves);
754 rb->lcd_putsxy(STAT_X + 4, STAT_Y + 36, buf);
756 rb->lcd_drawrect(STAT_X, STAT_Y + 0, STAT_WIDTH, 24);
757 rb->lcd_drawrect(STAT_X, STAT_Y + 23, STAT_WIDTH, 24);
759 #if LCD_HEIGHT >= 70
760 rb->lcd_putsxy(STAT_X + 1, STAT_Y + 49, "Pushes");
761 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.pushes);
762 rb->lcd_putsxy(STAT_X + 4, STAT_Y + 59, buf);
764 rb->lcd_drawrect(STAT_X, STAT_Y + 46, STAT_WIDTH, 24);
765 #endif
767 #endif
769 /* load the board to the screen */
770 for (rows = 0; rows < ROWS; rows++) {
771 for (cols = 0; cols < COLS; cols++) {
772 c = cols*MAGNIFY +
773 (BOARD_WIDTH - current_info.level.width*MAGNIFY)/2;
774 r = rows*MAGNIFY +
775 (BOARD_HEIGHT - current_info.level.height*MAGNIFY)/2;
777 switch(current_info.board[rows][cols]) {
778 case 'X': /* blank space outside of level */
779 break;
781 #if LCD_DEPTH >= 2 && ((LCD_HEIGHT >= 96 && LCD_WIDTH >= 152) || \
782 (LCD_HEIGHT >= 121 && LCD_WIDTH >= 120))
783 case ' ': /* floor */
784 rb->lcd_bitmap_part(sokoban_tiles, 0, 0*MAGNIFY, MAGNIFY,
785 c, r, MAGNIFY, MAGNIFY);
786 break;
788 case '#': /* wall */
789 rb->lcd_bitmap_part(sokoban_tiles, 0, 1*MAGNIFY, MAGNIFY,
790 c, r, MAGNIFY, MAGNIFY);
791 break;
793 case '$': /* box */
794 rb->lcd_bitmap_part(sokoban_tiles, 0, 2*MAGNIFY, MAGNIFY,
795 c, r, MAGNIFY, MAGNIFY);
796 break;
798 case '*': /* box on goal */
799 rb->lcd_bitmap_part(sokoban_tiles, 0, 3*MAGNIFY, MAGNIFY,
800 c, r, MAGNIFY, MAGNIFY);
801 break;
803 case '.': /* goal */
804 rb->lcd_bitmap_part(sokoban_tiles, 0, 4*MAGNIFY, MAGNIFY,
805 c, r, MAGNIFY, MAGNIFY);
806 break;
808 case '@': /* player */
809 rb->lcd_bitmap_part(sokoban_tiles, 0, 5*MAGNIFY, MAGNIFY,
810 c, r, MAGNIFY, MAGNIFY);
811 break;
813 case '+': /* player on goal */
814 rb->lcd_bitmap_part(sokoban_tiles, 0, 6*MAGNIFY, MAGNIFY,
815 c, r, MAGNIFY, MAGNIFY);
816 break;
817 #else
818 case '#': /* wall */
819 for (i = c; i < c + MAGNIFY; i++)
820 for (j = r; j < r + MAGNIFY; j++)
821 if ((i ^ j) & 1)
822 rb->lcd_drawpixel(i, j);
823 break;
825 case '$': /* box */
826 rb->lcd_drawrect(c, r, MAGNIFY, MAGNIFY);
827 break;
829 case '*': /* box on goal */
830 rb->lcd_drawrect(c, r, MAGNIFY, MAGNIFY);
831 rb->lcd_drawrect(c + MAGNIFY/2 - 1, r + MAGNIFY/2 - 1,
832 MAGNIFY/2, MAGNIFY/2);
833 break;
835 case '.': /* goal */
836 rb->lcd_drawrect(c + MAGNIFY/2 - 1, r + MAGNIFY/2 - 1,
837 MAGNIFY/2, MAGNIFY/2);
838 break;
840 case '@': /* player */
841 case '+': /* player on goal */
842 rb->lcd_drawline(c, r + middle, c + max, r + middle);
843 rb->lcd_drawline(c + middle, r, c + middle,
844 r + max - ldelta);
845 rb->lcd_drawline(c + max - middle, r, c + max - middle,
846 r + max - ldelta);
847 rb->lcd_drawline(c + middle, r + max - ldelta,
848 c + middle - ldelta, r + max);
849 rb->lcd_drawline(c + max - middle, r + max - ldelta,
850 c + max - middle + ldelta, r + max);
851 break;
852 #endif
857 /* print out the screen */
858 rb->lcd_update();
861 static void draw_level(void)
863 load_level();
864 rb->lcd_clear_display();
865 update_screen();
868 static bool save(char *filename, bool solution)
870 int fd;
871 char *loc;
872 DIR *dir;
873 char dirname[MAX_PATH];
875 rb->splash(0, "Saving...");
877 /* Create dir if it doesn't exist */
878 if ((loc = rb->strrchr(filename, '/')) != NULL) {
879 rb->strncpy(dirname, filename, loc - filename);
880 dirname[loc - filename] = '\0';
881 if(!(dir = rb->opendir(dirname)))
882 rb->mkdir(dirname);
883 else
884 rb->closedir(dir);
887 if (filename[0] == '\0' ||
888 (fd = rb->open(filename, O_WRONLY|O_CREAT|O_TRUNC)) < 0) {
889 rb->splash(HZ*2, "Unable to open %s", filename);
890 return false;
893 /* Sokoban: S/P for solution/progress : level number : current undo */
894 rb->snprintf(buf, sizeof(buf), "Sokoban:%c:%d:%d\n", (solution ? 'S' : 'P'),
895 current_info.level.index + 1, undo_info.current);
896 rb->write(fd, buf, rb->strlen(buf));
898 /* Filename of levelset */
899 rb->write(fd, buffered_boards.filename,
900 rb->strlen(buffered_boards.filename));
901 rb->write(fd, "\n", 1);
903 /* Full undo history */
904 rb->write(fd, undo_info.history, undo_info.max);
906 rb->close(fd);
908 return true;
911 static bool load(char *filename, bool silent)
913 int fd;
914 int i, n;
915 int len;
916 int button;
917 bool play_solution;
918 bool paused = false;
919 unsigned short speed = 2;
920 int delay[] = {HZ/2, HZ/3, HZ/4, HZ/6, HZ/8, HZ/12, HZ/16, HZ/25};
922 if (filename[0] == '\0' || (fd = rb->open(filename, O_RDONLY)) < 0) {
923 if (!silent)
924 rb->splash(HZ*2, "Unable to open %s", filename);
925 return false;
928 /* Read header, level number, & current undo */
929 rb->read_line(fd, buf, sizeof(buf));
931 /* If we're opening a level file, not a solution/progress file */
932 if (rb->strncmp(buf, "Sokoban", 7) != 0) {
933 rb->close(fd);
935 rb->strncpy(buffered_boards.filename, filename, MAX_PATH);
936 if (!read_levels(true))
937 return false;
939 current_info.level.index = 0;
940 load_level();
942 /* If there aren't any boxes to go or the player position wasn't set,
943 * the file probably wasn't a Sokoban level file */
944 if (current_info.level.boxes_to_go == 0 ||
945 current_info.player.row == 0 || current_info.player.col == 0) {
946 if (!silent)
947 rb->splash(HZ*2, "File is not a Sokoban level file");
948 return false;
951 } else {
953 /* Read filename of levelset */
954 rb->read_line(fd, buffered_boards.filename,
955 sizeof(buffered_boards.filename));
957 /* Read full undo history */
958 len = rb->read_line(fd, undo_info.history, MAX_UNDOS);
960 /* Correct len when trailing \r's or \n's are counted */
961 if (len > 2 && undo_info.history[len - 2] == '\0')
962 len -= 2;
963 else if (len > 1 && undo_info.history[len - 1] == '\0')
964 len--;
966 rb->close(fd);
968 /* Check to see if we're going to play a solution or resume progress */
969 play_solution = (buf[8] == 'S');
971 /* Get level number */
972 for (n = 0, i = 10; buf[i] >= '0' && buf[i] <= '9' && i < 15; i++)
973 n = n*10 + buf[i] - '0';
974 current_info.level.index = n - 1;
976 /* Get undo index */
977 for (n = 0, i++; buf[i] >= '0' && buf[i] <= '9' && i < 21; i++)
978 n = n*10 + buf[i] - '0';
979 if (n > len)
980 n = len;
981 undo_info.max = len;
983 if (current_info.level.index < 0) {
984 if (!silent)
985 rb->splash(HZ*2, "Error loading level");
986 return false;
988 if (!read_levels(true))
989 return false;
990 if (current_info.level.index >= current_info.max_level) {
991 if (!silent)
992 rb->splash(HZ*2, "Error loading level");
993 return false;
996 load_level();
998 if (play_solution) {
999 rb->lcd_clear_display();
1000 update_screen();
1001 rb->sleep(2*delay[speed]);
1003 /* Replay solution until menu button is pressed */
1004 i = 0;
1005 while (true) {
1006 if (i < len) {
1007 if (!move(undo_info.history[i], true)) {
1008 n = i;
1009 break;
1011 rb->lcd_clear_display();
1012 update_screen();
1013 i++;
1014 } else
1015 paused = true;
1017 rb->sleep(delay[speed]);
1019 while ((button = rb->button_get(false)) || paused) {
1020 switch (button) {
1021 case SOKOBAN_MENU:
1022 /* Pretend the level is complete so we'll quit */
1023 current_info.level.boxes_to_go = 0;
1024 return true;
1026 case SOKOBAN_PAUSE:
1027 /* Toggle pause state */
1028 paused = !paused;
1029 break;
1031 case BUTTON_LEFT:
1032 case BUTTON_LEFT | BUTTON_REPEAT:
1033 /* Go back one move */
1034 if (paused) {
1035 if (undo())
1036 i--;
1037 rb->lcd_clear_display();
1038 update_screen();
1040 break;
1042 case BUTTON_RIGHT:
1043 case BUTTON_RIGHT | BUTTON_REPEAT:
1044 /* Go forward one move */
1045 if (paused) {
1046 if (redo())
1047 i++;
1048 rb->lcd_clear_display();
1049 update_screen();
1051 break;
1053 case SOKOBAN_UP:
1054 case SOKOBAN_UP | BUTTON_REPEAT:
1055 /* Speed up */
1056 if (speed < sizeof(delay)/sizeof(int) - 1)
1057 speed++;
1058 break;
1060 case SOKOBAN_DOWN:
1061 case SOKOBAN_DOWN | BUTTON_REPEAT:
1062 /* Slow down */
1063 if (speed > 0)
1064 speed--;
1067 if (paused)
1068 rb->sleep(HZ/33);
1072 /* If level is complete, wait for keypress before quitting */
1073 if (current_info.level.boxes_to_go == 0)
1074 rb->button_get(true);
1076 } else {
1077 /* Advance to current undo */
1078 for (i = 0; i < n; i++) {
1079 if (!move(undo_info.history[i], true)) {
1080 n = i;
1081 break;
1085 rb->button_clear_queue();
1086 rb->lcd_clear_display();
1089 undo_info.current = n;
1092 return true;
1095 static int sokoban_menu(void)
1097 int button;
1098 int selection = 0;
1099 int i;
1100 bool menu_quit;
1101 int start_selected = 0;
1102 int prev_level = current_info.level.index;
1104 MENUITEM_STRINGLIST(menu, "Sokoban Menu", NULL,
1105 "Resume", "Select Level", "Audio Playback", "Keys",
1106 "Load Default Level Set", "Quit Without Saving",
1107 "Save Progress & Quit");
1109 do {
1110 menu_quit = true;
1111 selection = rb->do_menu(&menu, &start_selected);
1113 switch (selection) {
1114 case 0: /* Resume */
1115 break;
1117 case 1: /* Select level */
1118 current_info.level.index++;
1119 rb->set_int("Select Level", "", UNIT_INT,
1120 &current_info.level.index, NULL, 1, 1,
1121 current_info.max_level, NULL);
1122 current_info.level.index--;
1123 if (prev_level != current_info.level.index) {
1124 init_undo();
1125 draw_level();
1126 } else
1127 menu_quit = false;
1128 break;
1130 case 2: /* Audio playback control */
1131 playback_control(rb);
1132 menu_quit = false;
1133 break;
1135 case 3: /* Keys */
1136 FOR_NB_SCREENS(i)
1137 rb->screens[i]->clear_display();
1138 rb->lcd_setfont(SOKOBAN_FONT);
1140 #if (CONFIG_KEYPAD == RECORDER_PAD) || \
1141 (CONFIG_KEYPAD == ARCHOS_AV300_PAD)
1142 rb->lcd_putsxy(3, 6, "[OFF] Menu");
1143 rb->lcd_putsxy(3, 16, "[ON] Undo");
1144 rb->lcd_putsxy(3, 26, "[PLAY] Redo");
1145 rb->lcd_putsxy(3, 36, "[F1] Down a Level");
1146 rb->lcd_putsxy(3, 46, "[F2] Restart Level");
1147 rb->lcd_putsxy(3, 56, "[F3] Up a Level");
1148 #elif CONFIG_KEYPAD == ONDIO_PAD
1149 rb->lcd_putsxy(3, 6, "[OFF] Menu");
1150 rb->lcd_putsxy(3, 16, "[MODE] Undo");
1151 rb->lcd_putsxy(3, 26, "[MODE+DOWN] Redo");
1152 rb->lcd_putsxy(3, 36, "[MODE+LEFT] Previous Level");
1153 rb->lcd_putsxy(3, 46, "[MODE+UP] Restart Level");
1154 rb->lcd_putsxy(3, 56, "[MODE+RIGHT] Up Level");
1155 #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
1156 (CONFIG_KEYPAD == IRIVER_H300_PAD)
1157 rb->lcd_putsxy(3, 6, "[STOP] Menu");
1158 rb->lcd_putsxy(3, 16, "[REC] Undo");
1159 rb->lcd_putsxy(3, 26, "[MODE] Redo");
1160 rb->lcd_putsxy(3, 36, "[PLAY+DOWN] Previous Level");
1161 rb->lcd_putsxy(3, 46, "[PLAY] Restart Level");
1162 rb->lcd_putsxy(3, 56, "[PLAY+UP] Next Level");
1163 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
1164 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
1165 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
1166 rb->lcd_putsxy(3, 6, "[SELECT+MENU] Menu");
1167 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
1168 rb->lcd_putsxy(3, 26, "[SELECT+PLAY] Redo");
1169 rb->lcd_putsxy(3, 36, "[SELECT+LEFT] Previous Level");
1170 rb->lcd_putsxy(3, 46, "[SELECT+RIGHT] Next Level");
1171 #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
1172 rb->lcd_putsxy(3, 6, "[POWER] Menu");
1173 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
1174 rb->lcd_putsxy(3, 26, "[PLAY] Redo");
1175 rb->lcd_putsxy(3, 36, "[REC] Restart Level");
1176 #elif CONFIG_KEYPAD == IRIVER_H10_PAD
1177 rb->lcd_putsxy(3, 6, "[POWER] Menu");
1178 rb->lcd_putsxy(3, 16, "[REW] Undo");
1179 rb->lcd_putsxy(3, 26, "[FF] Redo");
1180 rb->lcd_putsxy(3, 36, "[PLAY+DOWN] Previous Level");
1181 rb->lcd_putsxy(3, 46, "[PLAY+RIGHT] Restart Level");
1182 rb->lcd_putsxy(3, 56, "[PLAY+UP] Next Level");
1183 #elif CONFIG_KEYPAD == GIGABEAT_PAD
1184 rb->lcd_putsxy(3, 6, "[POWER] Menu");
1185 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
1186 rb->lcd_putsxy(3, 26, "[A] Redo");
1187 rb->lcd_putsxy(3, 36, "[VOL-] Previous Level");
1188 rb->lcd_putsxy(3, 46, "[MENU] Restart Level");
1189 rb->lcd_putsxy(3, 56, "[VOL+] Next Level");
1190 #elif CONFIG_KEYPAD == SANSA_E200_PAD
1191 rb->lcd_putsxy(3, 6, "[POWER] Menu");
1192 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
1193 rb->lcd_putsxy(3, 26, "[REC] Redo");
1194 rb->lcd_putsxy(3, 36, "[SELECT+DOWN] Previous Level");
1195 rb->lcd_putsxy(3, 46, "[SELECT+RIGHT] Restart Level");
1196 rb->lcd_putsxy(3, 56, "[SELECT+UP] Next Level");
1197 #endif
1199 FOR_NB_SCREENS(i)
1200 rb->screens[i]->update();
1202 /* Display until keypress */
1203 do {
1204 rb->sleep(HZ/20);
1205 button = rb->button_get(false);
1206 } while (!button || button & BUTTON_REL ||
1207 button & BUTTON_REPEAT);
1209 menu_quit = false;
1210 break;
1212 case 4: /* Load default levelset */
1213 init_boards();
1214 if (!read_levels(true))
1215 return 5; /* Quit */
1216 load_level();
1217 break;
1219 case 5: /* Quit */
1220 break;
1222 case 6: /* Save & quit */
1223 save(SOKOBAN_SAVE_FILE, false);
1224 rb->reload_directory();
1227 } while (!menu_quit);
1229 /* Restore font */
1230 rb->lcd_setfont(SOKOBAN_FONT);
1232 FOR_NB_SCREENS(i) {
1233 rb->screens[i]->clear_display();
1234 rb->screens[i]->update();
1237 return selection;
1240 static bool sokoban_loop(void)
1242 bool moved;
1243 int i = 0, button = 0, lastbutton = 0;
1244 short r = 0, c = 0;
1245 int w, h;
1246 char *loc;
1248 while (true) {
1249 moved = false;
1251 r = current_info.player.row;
1252 c = current_info.player.col;
1254 button = rb->button_get(true);
1256 switch(button)
1258 #ifdef SOKOBAN_RC_MENU
1259 case SOKOBAN_RC_MENU:
1260 #endif
1261 case SOKOBAN_MENU:
1262 switch (sokoban_menu()) {
1263 case 5: /* Quit */
1264 case 6: /* Save & quit */
1265 return PLUGIN_OK;
1267 update_screen();
1268 break;
1270 case SOKOBAN_UNDO:
1271 #ifdef SOKOBAN_UNDO_PRE
1272 if (lastbutton != SOKOBAN_UNDO_PRE)
1273 break;
1274 #else /* repeat can't work here for Ondio, iPod, et al */
1275 case SOKOBAN_UNDO | BUTTON_REPEAT:
1276 #endif
1277 undo();
1278 rb->lcd_clear_display();
1279 update_screen();
1280 break;
1282 #ifdef SOKOBAN_REDO
1283 case SOKOBAN_REDO:
1284 case SOKOBAN_REDO | BUTTON_REPEAT:
1285 moved = redo();
1286 rb->lcd_clear_display();
1287 update_screen();
1288 break;
1289 #endif
1291 #ifdef SOKOBAN_LEVEL_UP
1292 case SOKOBAN_LEVEL_UP:
1293 case SOKOBAN_LEVEL_UP | BUTTON_REPEAT:
1294 /* next level */
1295 init_undo();
1296 if (current_info.level.index + 1 < current_info.max_level)
1297 current_info.level.index++;
1299 draw_level();
1300 break;
1301 #endif
1303 #ifdef SOKOBAN_LEVEL_DOWN
1304 case SOKOBAN_LEVEL_DOWN:
1305 case SOKOBAN_LEVEL_DOWN | BUTTON_REPEAT:
1306 /* previous level */
1307 init_undo();
1308 if (current_info.level.index > 0)
1309 current_info.level.index--;
1311 draw_level();
1312 break;
1313 #endif
1315 #ifdef SOKOBAN_LEVEL_REPEAT
1316 case SOKOBAN_LEVEL_REPEAT:
1317 case SOKOBAN_LEVEL_REPEAT | BUTTON_REPEAT:
1318 /* same level */
1319 init_undo();
1320 draw_level();
1321 break;
1322 #endif
1324 case BUTTON_LEFT:
1325 case BUTTON_LEFT | BUTTON_REPEAT:
1326 moved = move(SOKOBAN_MOVE_LEFT, false);
1327 break;
1329 case BUTTON_RIGHT:
1330 case BUTTON_RIGHT | BUTTON_REPEAT:
1331 moved = move(SOKOBAN_MOVE_RIGHT, false);
1332 break;
1334 case SOKOBAN_UP:
1335 case SOKOBAN_UP | BUTTON_REPEAT:
1336 moved = move(SOKOBAN_MOVE_UP, false);
1337 break;
1339 case SOKOBAN_DOWN:
1340 case SOKOBAN_DOWN | BUTTON_REPEAT:
1341 moved = move(SOKOBAN_MOVE_DOWN, false);
1342 break;
1344 default:
1345 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
1346 return PLUGIN_USB_CONNECTED;
1347 break;
1350 lastbutton = button;
1352 if (moved) {
1353 rb->lcd_clear_display();
1354 update_screen();
1357 /* We have completed this level */
1358 if (current_info.level.boxes_to_go == 0) {
1360 if (moved) {
1361 rb->lcd_clear_display();
1363 /* Show level complete message & stats */
1364 rb->snprintf(buf, sizeof(buf), "Level %d Complete!",
1365 current_info.level.index + 1);
1366 rb->lcd_getstringsize(buf, &w, &h);
1367 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h*3, buf);
1369 rb->snprintf(buf, sizeof(buf), "%4d Moves ",
1370 current_info.level.moves);
1371 rb->lcd_getstringsize(buf, &w, &h);
1372 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h, buf);
1374 rb->snprintf(buf, sizeof(buf), "%4d Pushes",
1375 current_info.level.pushes);
1376 rb->lcd_getstringsize(buf, &w, &h);
1377 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2, buf);
1379 if (undo_info.count < MAX_UNDOS) {
1380 rb->snprintf(buf, sizeof(buf), "%s: Save solution",
1381 BUTTON_SAVE_NAME);
1382 rb->lcd_getstringsize(buf, &w, &h);
1383 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 + h*2, buf);
1386 rb->lcd_update();
1387 rb->sleep(HZ/4);
1388 rb->button_clear_queue();
1390 /* Display for 4 seconds or until new keypress */
1391 for (i = 0; i < 80; i++) {
1392 rb->sleep(HZ/20);
1393 button = rb->button_get(false);
1394 if (button && !(button & BUTTON_REL) &&
1395 !(button & BUTTON_REPEAT))
1396 break;
1399 if (button == BUTTON_SAVE) {
1400 if (undo_info.count < MAX_UNDOS) {
1401 /* Set filename to current levelset plus level number
1402 * and .sok extension. Use SAVE_FOLDER if using the
1403 * default levelset, since it's in a hidden folder. */
1404 if (rb->strcmp(buffered_boards.filename,
1405 SOKOBAN_LEVELS_FILE) == 0) {
1406 rb->snprintf(buf, sizeof(buf),
1407 "%s/sokoban.%d.sok",
1408 SOKOBAN_SAVE_FOLDER,
1409 current_info.level.index + 1);
1410 } else {
1411 if ((loc = rb->strrchr(buffered_boards.filename,
1412 '.')) != NULL)
1413 *loc = '\0';
1414 rb->snprintf(buf, sizeof(buf), "%s.%d.sok",
1415 buffered_boards.filename,
1416 current_info.level.index + 1);
1417 if (loc != NULL)
1418 *loc = '.';
1421 if (!rb->kbd_input(buf, MAX_PATH))
1422 save(buf, true);
1423 } else
1424 rb->splash(HZ*2, "Solution too long to save");
1426 rb->lcd_setfont(SOKOBAN_FONT); /* Restore font */
1430 FOR_NB_SCREENS(i) {
1431 rb->screens[i]->clear_display();
1432 rb->screens[i]->update();
1435 current_info.level.index++;
1437 /* clear undo stats */
1438 init_undo();
1440 if (current_info.level.index >= current_info.max_level) {
1441 /* Show levelset complete message */
1442 rb->snprintf(buf, sizeof(buf), "You WIN!!");
1443 rb->lcd_getstringsize(buf, &w, &h);
1444 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h/2, buf);
1446 rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
1447 /* Display for 4 seconds or until keypress */
1448 for (i = 0; i < 80; i++) {
1449 rb->lcd_fillrect(0, 0, LCD_WIDTH, LCD_HEIGHT);
1450 rb->lcd_update();
1451 rb->sleep(HZ/10);
1453 button = rb->button_get(false);
1454 if (button && !(button & BUTTON_REL))
1455 break;
1457 rb->lcd_set_drawmode(DRMODE_SOLID);
1459 /* Reset to first level & show quit menu */
1460 current_info.level.index = 0;
1462 switch (sokoban_menu()) {
1463 case 5: /* Quit */
1464 case 6: /* Save & quit */
1465 return PLUGIN_OK;
1469 load_level();
1470 update_screen();
1473 } /* end while */
1475 return PLUGIN_OK;
1479 enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
1481 int w, h;
1483 (void)(parameter);
1484 rb = api;
1486 rb->lcd_setfont(SOKOBAN_FONT);
1488 rb->lcd_clear_display();
1489 rb->lcd_getstringsize(SOKOBAN_TITLE, &w, &h);
1490 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h/2, SOKOBAN_TITLE);
1491 rb->lcd_update();
1492 rb->sleep(HZ); /* Show title for 1 second */
1494 init_boards();
1496 if (parameter == NULL) {
1497 /* Attempt to resume saved progress, otherwise start at beginning */
1498 if (!load(SOKOBAN_SAVE_FILE, true)) {
1499 init_boards();
1500 if (!read_levels(true))
1501 return PLUGIN_OK;
1502 load_level();
1505 } else {
1506 /* The plugin is being used to open a file */
1507 if (load((char*) parameter, false)) {
1508 /* If we loaded & played a solution, quit */
1509 if (current_info.level.boxes_to_go == 0)
1510 return PLUGIN_OK;
1511 } else
1512 return PLUGIN_OK;
1515 rb->lcd_clear_display();
1516 update_screen();
1518 return sokoban_loop();
1521 #endif