M:Robe 100: add button definition/bitmaps to plugins and enable compilation
[Rockbox.git] / apps / plugins / sokoban.c
blob66492fd3d5a8f4c53734c9320d27c7a0b9e40aad
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 #elif CONFIG_KEYPAD == MROBE100_PAD
234 #define SOKOBAN_UP BUTTON_UP
235 #define SOKOBAN_DOWN BUTTON_DOWN
236 #define SOKOBAN_MENU BUTTON_POWER
237 #define SOKOBAN_UNDO BUTTON_SELECT
238 #define SOKOBAN_REDO BUTTON_MENU
239 #define SOKOBAN_LEVEL_DOWN (BUTTON_DISPLAY | BUTTON_DOWN)
240 #define SOKOBAN_LEVEL_REPEAT (BUTTON_DISPLAY | BUTTON_RIGHT)
241 #define SOKOBAN_LEVEL_UP (BUTTON_DISPLAY | BUTTON_UP)
242 #define SOKOBAN_PAUSE BUTTON_SELECT
243 #define BUTTON_SAVE BUTTON_SELECT
244 #define BUTTON_SAVE_NAME "SELECT"
246 #else
247 #error No keymap defined!
248 #endif
250 #define SOKOBAN_FONT FONT_SYSFIXED
253 /* The Location, Undo and LevelInfo structs are OO-flavored.
254 * (oooh!-flavored as Schnueff puts it.) It makes more you have to know,
255 * but the overall data layout becomes more manageable. */
257 /* Level data & stats */
258 struct LevelInfo {
259 int index; /* Level index (level number - 1) */
260 int moves; /* Moves & pushes for the stats */
261 int pushes;
262 short boxes_to_go; /* Number of unplaced boxes remaining in level */
263 short height; /* Height & width for centering level display */
264 short width;
267 struct Location {
268 short row;
269 short col;
272 /* Our full undo history */
273 static struct UndoInfo {
274 int count; /* How many undos have been done */
275 int current; /* Which history is the current undo */
276 int max; /* Which history is the max redoable */
277 char history[MAX_UNDOS];
278 } undo_info;
280 /* Our playing board */
281 static struct BoardInfo {
282 char board[ROWS][COLS]; /* The current board data */
283 struct LevelInfo level; /* Level data & stats */
284 struct Location player; /* Where the player is */
285 int max_level; /* The number of levels we have */
286 } current_info;
288 static struct BufferedBoards {
289 char filename[MAX_PATH]; /* Filename of the levelset we're using */
290 char data[MAX_LEVEL_DATA]; /* Buffered level data */
291 int index[MAX_LEVELS + 1]; /* Where each buffered board begins & ends */
292 int start; /* Index of first buffered board */
293 int end; /* Index of last buffered board */
294 short prebuffered_boards; /* Number of boards before current to store */
295 } buffered_boards;
298 static struct plugin_api* rb;
299 MEM_FUNCTION_WRAPPERS(rb);
301 static char buf[ROWS*(COLS + 1)]; /* Enough for a whole board or a filename */
304 static void init_undo(void)
306 undo_info.count = 0;
307 undo_info.current = 0;
308 undo_info.max = 0;
311 static void get_delta(char direction, short *d_r, short *d_c)
313 switch (direction) {
314 case SOKOBAN_PUSH_LEFT:
315 case SOKOBAN_MOVE_LEFT:
316 *d_r = 0;
317 *d_c = -1;
318 break;
319 case SOKOBAN_PUSH_RIGHT:
320 case SOKOBAN_MOVE_RIGHT:
321 *d_r = 0;
322 *d_c = 1;
323 break;
324 case SOKOBAN_PUSH_UP:
325 case SOKOBAN_MOVE_UP:
326 *d_r = -1;
327 *d_c = 0;
328 break;
329 case SOKOBAN_PUSH_DOWN:
330 case SOKOBAN_MOVE_DOWN:
331 *d_r = 1;
332 *d_c = 0;
336 static bool undo(void)
338 char undo;
339 short r, c;
340 short d_r = 0, d_c = 0; /* delta row & delta col */
341 char *space_cur, *space_next, *space_prev;
342 bool undo_push = false;
344 /* If no more undos or we've wrapped all the way around, quit */
345 if (undo_info.count == 0 || undo_info.current - 1 == undo_info.max)
346 return false;
348 /* Move to previous undo in the list */
349 if (undo_info.current == 0 && undo_info.count > 1)
350 undo_info.current = MAX_UNDOS - 1;
351 else
352 undo_info.current--;
354 undo_info.count--;
356 undo = undo_info.history[undo_info.current];
358 if (undo < SOKOBAN_MOVE_MIN)
359 undo_push = true;
361 get_delta(undo, &d_r, &d_c);
363 r = current_info.player.row;
364 c = current_info.player.col;
366 /* Give the 3 spaces we're going to use better names */
367 space_cur = &current_info.board[r][c];
368 space_next = &current_info.board[r + d_r][c + d_c];
369 space_prev = &current_info.board[r - d_r][c - d_c];
371 /* Update board info */
372 if (undo_push) {
373 /* Moving box from goal to floor */
374 if (*space_next == '*' && *space_cur == '@')
375 current_info.level.boxes_to_go++;
376 /* Moving box from floor to goal */
377 else if (*space_next == '$' && *space_cur == '+')
378 current_info.level.boxes_to_go--;
380 /* Move box off of next space... */
381 *space_next = (*space_next == '*' ? '.' : ' ');
382 /* ...and on to current space */
383 *space_cur = (*space_cur == '+' ? '*' : '$');
385 current_info.level.pushes--;
386 } else
387 /* Just move player off of current space */
388 *space_cur = (*space_cur == '+' ? '.' : ' ');
389 /* Move player back to previous space */
390 *space_prev = (*space_prev == '.' ? '+' : '@');
392 /* Update position */
393 current_info.player.row -= d_r;
394 current_info.player.col -= d_c;
396 current_info.level.moves--;
398 return true;
401 static void add_undo(char undo)
403 undo_info.history[undo_info.current] = undo;
405 /* Wrap around if MAX_UNDOS exceeded */
406 if (undo_info.current < (MAX_UNDOS - 1))
407 undo_info.current++;
408 else
409 undo_info.current = 0;
411 if (undo_info.count < MAX_UNDOS)
412 undo_info.count++;
415 static bool move(char direction, bool redo)
417 short r, c;
418 short d_r = 0, d_c = 0; /* delta row & delta col */
419 char *space_cur, *space_next, *space_beyond;
420 bool push = false;
422 get_delta(direction, &d_r, &d_c);
424 r = current_info.player.row;
425 c = current_info.player.col;
427 /* Check for out-of-bounds */
428 if (r + 2*d_r < 0 || r + 2*d_r >= ROWS ||
429 c + 2*d_c < 0 || c + 2*d_c >= COLS)
430 return false;
432 /* Give the 3 spaces we're going to use better names */
433 space_cur = &current_info.board[r][c];
434 space_next = &current_info.board[r + d_r][c + d_c];
435 space_beyond = &current_info.board[r + 2*d_r][c + 2*d_c];
437 if (*space_next == '$' || *space_next == '*') {
438 /* Change direction from move to push for undo */
439 if (direction >= SOKOBAN_MOVE_MIN)
440 direction -= SOKOBAN_MOVE_DIFF;
441 push = true;
443 else if (direction < SOKOBAN_MOVE_MIN)
444 /* Change back to move if redo/solution playback push is invalid */
445 direction += SOKOBAN_MOVE_DIFF;
447 /* Update board info */
448 if (push) {
449 /* Moving box from goal to floor */
450 if (*space_next == '*' && *space_beyond == ' ')
451 current_info.level.boxes_to_go++;
452 /* Moving box from floor to goal */
453 else if (*space_next == '$' && *space_beyond == '.')
454 current_info.level.boxes_to_go--;
455 /* Check for invalid move */
456 else if (*space_beyond != '.' && *space_beyond != ' ')
457 return false;
459 /* Move player onto next space */
460 *space_next = (*space_next == '*' ? '+' : '@');
461 /* Move box onto space beyond next */
462 *space_beyond = (*space_beyond == '.' ? '*' : '$');
464 current_info.level.pushes++;
465 } else {
466 /* Check for invalid move */
467 if (*space_next != '.' && *space_next != ' ')
468 return false;
470 /* Move player onto next space */
471 *space_next = (*space_next == '.' ? '+' : '@');
473 /* Move player off of current space */
474 *space_cur = (*space_cur == '+' ? '.' : ' ');
476 /* Update position */
477 current_info.player.row += d_r;
478 current_info.player.col += d_c;
480 current_info.level.moves++;
482 /* Update undo_info.max to current on every normal move,
483 * except if it's the same as a redo. */
484 /* normal move and either */
485 if (!redo &&
486 /* moves have been undone... */
487 ((undo_info.max != undo_info.current &&
488 /* ...and the current move is NOT the same as the one in history */
489 undo_info.history[undo_info.current] != direction) ||
490 /* or moves have not been undone */
491 undo_info.max == undo_info.current)) {
492 add_undo(direction);
493 undo_info.max = undo_info.current;
494 } else /* redo move or move was same as redo */
495 add_undo(direction); /* add_undo to update current */
497 return true;
500 #ifdef SOKOBAN_REDO
501 static bool redo(void)
503 /* If no moves have been undone, quit */
504 if (undo_info.current == undo_info.max)
505 return false;
507 return move(undo_info.history[(undo_info.current < MAX_UNDOS ?
508 undo_info.current : 0)], true);
510 #endif
512 static void init_boards(void)
514 rb->strncpy(buffered_boards.filename, SOKOBAN_LEVELS_FILE, MAX_PATH);
516 current_info.level.index = 0;
517 current_info.player.row = 0;
518 current_info.player.col = 0;
519 current_info.max_level = 0;
521 buffered_boards.start = 0;
522 buffered_boards.end = 0;
523 buffered_boards.prebuffered_boards = 0;
525 init_undo();
528 static bool read_levels(bool initialize)
530 int fd = 0;
531 short len;
532 short lastlen = 0;
533 short row = 0;
534 int level_count = 0;
536 int i = 0;
537 int level_len = 0;
538 bool index_set = false;
540 /* Get the index of the first level to buffer */
541 if (current_info.level.index > buffered_boards.prebuffered_boards &&
542 !initialize)
543 buffered_boards.start = current_info.level.index -
544 buffered_boards.prebuffered_boards;
545 else
546 buffered_boards.start = 0;
548 if ((fd = rb->open(buffered_boards.filename, O_RDONLY)) < 0) {
549 rb->splash(HZ*2, "Unable to open %s", buffered_boards.filename);
550 return false;
553 do {
554 len = rb->read_line(fd, buf, sizeof(buf));
556 /* Correct len when trailing \r's or \n's are counted */
557 if (len > 2 && buf[len - 2] == '\0')
558 len -= 2;
559 else if (len > 1 && buf[len - 1] == '\0')
560 len--;
562 /* Skip short lines & lines with non-level data */
563 if (len >= 3 && ((buf[0] >= '1' && buf[0] <= '9') || buf[0] == '#' ||
564 buf[0] == ' ' || buf[0] == '-' || buf[0] == '_')) {
565 if (level_count >= buffered_boards.start) {
566 /* Set the index of this level */
567 if (!index_set &&
568 level_count - buffered_boards.start < MAX_LEVELS) {
569 buffered_boards.index[level_count - buffered_boards.start]
570 = i;
571 index_set = true;
573 /* Copy buffer to board data */
574 if (i + level_len + len < MAX_LEVEL_DATA) {
575 rb->memcpy(&buffered_boards.data[i + level_len], buf, len);
576 buffered_boards.data[i + level_len + len] = '\n';
579 level_len += len + 1;
580 row++;
582 /* If newline & level is tall enough or is RLE */
583 } else if (buf[0] == '\0' && (row > 2 || lastlen > 22)) {
584 level_count++;
585 if (level_count >= buffered_boards.start) {
586 i += level_len;
587 if (i < MAX_LEVEL_DATA)
588 buffered_boards.end = level_count;
589 else if (!initialize)
590 break;
592 row = 0;
593 level_len = 0;
594 index_set = false;
596 } else if (len > 22)
597 len = 1;
599 } while ((lastlen = len));
601 /* Set the index of the end of the last level */
602 if (level_count - buffered_boards.start < MAX_LEVELS)
603 buffered_boards.index[level_count - buffered_boards.start] = i;
605 if (initialize) {
606 current_info.max_level = level_count;
607 buffered_boards.prebuffered_boards = buffered_boards.end/2;
610 rb->close(fd);
612 return true;
615 static void load_level(void)
617 int c, r;
618 int i, n;
619 int level_size;
620 int index = current_info.level.index - buffered_boards.start;
621 char *level;
623 /* Get the buffered board index of the current level */
624 if (current_info.level.index < buffered_boards.start ||
625 current_info.level.index >= buffered_boards.end) {
626 read_levels(false);
627 if (current_info.level.index > buffered_boards.prebuffered_boards)
628 index = buffered_boards.prebuffered_boards;
629 else
630 index = current_info.level.index;
632 level = &buffered_boards.data[buffered_boards.index[index]];
634 /* Reset level info */
635 current_info.level.moves = 0;
636 current_info.level.pushes = 0;
637 current_info.level.boxes_to_go = 0;
638 current_info.level.width = 0;
640 /* Clear board */
641 for (r = 0; r < ROWS; r++)
642 for (c = 0; c < COLS; c++)
643 current_info.board[r][c] = 'X';
645 level_size = buffered_boards.index[index + 1] -
646 buffered_boards.index[index];
648 for (r = 0, c = 0, n = 1, i = 0; i < level_size; i++) {
649 if (level[i] == '\n' || level[i] == '|') {
650 if (c > 3) {
651 /* Update max width of level & go to next row */
652 if (c > current_info.level.width)
653 current_info.level.width = c;
654 c = 0;
655 r++;
656 if (r >= ROWS)
657 break;
659 } else if (c < COLS) {
660 /* Read RLE character's length into n */
661 if (level[i] >= '0' && level[i] <= '9') {
662 n = level[i++] - '0';
663 if (level[i] >= '0' && level[i] <= '9')
664 n = n*10 + level[i++] - '0';
667 /* Cleanup & replace */
668 if (level[i] == '%')
669 level[i] = '*';
670 else if (level[i] == '-' || level[i] == '_')
671 level[i] = ' ';
673 if (n > 1) {
674 if (c + n >= COLS)
675 n = COLS - c;
677 if (level[i] == '.')
678 current_info.level.boxes_to_go += n;
680 /* Put RLE character n times */
681 while (n--)
682 current_info.board[r][c++] = level[i];
683 n = 1;
685 } else {
686 if (level[i] == '.' || level[i] == '+')
687 current_info.level.boxes_to_go++;
689 if (level[i] == '@' ||level[i] == '+') {
690 current_info.player.row = r;
691 current_info.player.col = c;
694 current_info.board[r][c++] = level[i];
699 current_info.level.height = r;
701 #if LCD_DEPTH > 2
702 /* Fill in blank space outside level on color targets */
703 for (r = 0; r < ROWS; r++)
704 for (c = 0; current_info.board[r][c] == ' ' && c < COLS; c++)
705 current_info.board[r][c] = 'X';
707 for (c = 0; c < COLS; c++) {
708 for (r = 0; (current_info.board[r][c] == ' ' ||
709 current_info.board[r][c] == 'X') && r < ROWS; r++)
710 current_info.board[r][c] = 'X';
711 for (r = ROWS - 1; (current_info.board[r][c] == ' ' ||
712 current_info.board[r][c] == 'X') && r >= 0; r--)
713 current_info.board[r][c] = 'X';
715 #endif
718 static void update_screen(void)
720 int c, r;
721 int rows, cols;
723 #if LCD_DEPTH < 2 || ((LCD_HEIGHT < 96 || LCD_WIDTH < 152) && \
724 (LCD_HEIGHT < 121 || LCD_WIDTH < 120))
725 int i, j;
726 int max = MAGNIFY - 1;
727 int middle = max/2;
728 int ldelta = (middle + 1)/2;
729 #endif
731 #if LCD_WIDTH - (COLS*MAGNIFY) < 32
732 #define STAT_HEIGHT 25
733 #define STAT_X (LCD_WIDTH - 120)/2
734 #define STAT_Y (LCD_HEIGHT - STAT_HEIGHT)
735 #define BOARD_WIDTH LCD_WIDTH
736 #define BOARD_HEIGHT (LCD_HEIGHT - STAT_HEIGHT)
737 rb->lcd_putsxy(STAT_X + 4, STAT_Y + 4, "Level");
738 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.index + 1);
739 rb->lcd_putsxy(STAT_X + 7, STAT_Y + 14, buf);
740 rb->lcd_putsxy(STAT_X + 41, STAT_Y + 4, "Moves");
741 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.moves);
742 rb->lcd_putsxy(STAT_X + 44, STAT_Y + 14, buf);
743 rb->lcd_putsxy(STAT_X + 79, STAT_Y + 4, "Pushes");
744 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.pushes);
745 rb->lcd_putsxy(STAT_X + 82, STAT_Y + 14, buf);
747 rb->lcd_drawrect(STAT_X, STAT_Y, 38, STAT_HEIGHT);
748 rb->lcd_drawrect(STAT_X + 37, STAT_Y, 39, STAT_HEIGHT);
749 rb->lcd_drawrect(STAT_X + 75, STAT_Y, 45, STAT_HEIGHT);
750 #else
751 #if LCD_WIDTH - (COLS*MAGNIFY) > 40
752 #define STAT_X (LCD_WIDTH - 40)
753 #else
754 #define STAT_X COLS*MAGNIFY
755 #endif
756 #if LCD_HEIGHT >= 70
757 #define STAT_Y (LCD_HEIGHT - 70)/2
758 #else
759 #define STAT_Y (LCD_HEIGHT - 47)/2
760 #endif
761 #define STAT_WIDTH (LCD_WIDTH - STAT_X)
762 #define BOARD_WIDTH (LCD_WIDTH - STAT_WIDTH)
763 #define BOARD_HEIGHT LCD_HEIGHT
764 rb->lcd_putsxy(STAT_X + 1, STAT_Y + 3, "Level");
765 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.index + 1);
766 rb->lcd_putsxy(STAT_X + 4, STAT_Y + 13, buf);
767 rb->lcd_putsxy(STAT_X + 1, STAT_Y + 26, "Moves");
768 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.moves);
769 rb->lcd_putsxy(STAT_X + 4, STAT_Y + 36, buf);
771 rb->lcd_drawrect(STAT_X, STAT_Y + 0, STAT_WIDTH, 24);
772 rb->lcd_drawrect(STAT_X, STAT_Y + 23, STAT_WIDTH, 24);
774 #if LCD_HEIGHT >= 70
775 rb->lcd_putsxy(STAT_X + 1, STAT_Y + 49, "Pushes");
776 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.pushes);
777 rb->lcd_putsxy(STAT_X + 4, STAT_Y + 59, buf);
779 rb->lcd_drawrect(STAT_X, STAT_Y + 46, STAT_WIDTH, 24);
780 #endif
782 #endif
784 /* load the board to the screen */
785 for (rows = 0; rows < ROWS; rows++) {
786 for (cols = 0; cols < COLS; cols++) {
787 c = cols*MAGNIFY +
788 (BOARD_WIDTH - current_info.level.width*MAGNIFY)/2;
789 r = rows*MAGNIFY +
790 (BOARD_HEIGHT - current_info.level.height*MAGNIFY)/2;
792 switch(current_info.board[rows][cols]) {
793 case 'X': /* blank space outside of level */
794 break;
796 #if LCD_DEPTH >= 2 && ((LCD_HEIGHT >= 96 && LCD_WIDTH >= 152) || \
797 (LCD_HEIGHT >= 121 && LCD_WIDTH >= 120))
798 case ' ': /* floor */
799 rb->lcd_bitmap_part(sokoban_tiles, 0, 0*MAGNIFY, MAGNIFY,
800 c, r, MAGNIFY, MAGNIFY);
801 break;
803 case '#': /* wall */
804 rb->lcd_bitmap_part(sokoban_tiles, 0, 1*MAGNIFY, MAGNIFY,
805 c, r, MAGNIFY, MAGNIFY);
806 break;
808 case '$': /* box */
809 rb->lcd_bitmap_part(sokoban_tiles, 0, 2*MAGNIFY, MAGNIFY,
810 c, r, MAGNIFY, MAGNIFY);
811 break;
813 case '*': /* box on goal */
814 rb->lcd_bitmap_part(sokoban_tiles, 0, 3*MAGNIFY, MAGNIFY,
815 c, r, MAGNIFY, MAGNIFY);
816 break;
818 case '.': /* goal */
819 rb->lcd_bitmap_part(sokoban_tiles, 0, 4*MAGNIFY, MAGNIFY,
820 c, r, MAGNIFY, MAGNIFY);
821 break;
823 case '@': /* player */
824 rb->lcd_bitmap_part(sokoban_tiles, 0, 5*MAGNIFY, MAGNIFY,
825 c, r, MAGNIFY, MAGNIFY);
826 break;
828 case '+': /* player on goal */
829 rb->lcd_bitmap_part(sokoban_tiles, 0, 6*MAGNIFY, MAGNIFY,
830 c, r, MAGNIFY, MAGNIFY);
831 break;
832 #else
833 case '#': /* wall */
834 for (i = c; i < c + MAGNIFY; i++)
835 for (j = r; j < r + MAGNIFY; j++)
836 if ((i ^ j) & 1)
837 rb->lcd_drawpixel(i, j);
838 break;
840 case '$': /* box */
841 rb->lcd_drawrect(c, r, MAGNIFY, MAGNIFY);
842 break;
844 case '*': /* box on goal */
845 rb->lcd_drawrect(c, r, MAGNIFY, MAGNIFY);
846 rb->lcd_drawrect(c + MAGNIFY/2 - 1, r + MAGNIFY/2 - 1,
847 MAGNIFY/2, MAGNIFY/2);
848 break;
850 case '.': /* goal */
851 rb->lcd_drawrect(c + MAGNIFY/2 - 1, r + MAGNIFY/2 - 1,
852 MAGNIFY/2, MAGNIFY/2);
853 break;
855 case '@': /* player */
856 case '+': /* player on goal */
857 rb->lcd_drawline(c, r + middle, c + max, r + middle);
858 rb->lcd_drawline(c + middle, r, c + middle,
859 r + max - ldelta);
860 rb->lcd_drawline(c + max - middle, r, c + max - middle,
861 r + max - ldelta);
862 rb->lcd_drawline(c + middle, r + max - ldelta,
863 c + middle - ldelta, r + max);
864 rb->lcd_drawline(c + max - middle, r + max - ldelta,
865 c + max - middle + ldelta, r + max);
866 break;
867 #endif
872 /* print out the screen */
873 rb->lcd_update();
876 static void draw_level(void)
878 load_level();
879 rb->lcd_clear_display();
880 update_screen();
883 static bool save(char *filename, bool solution)
885 int fd;
886 char *loc;
887 DIR *dir;
888 char dirname[MAX_PATH];
890 rb->splash(0, "Saving...");
892 /* Create dir if it doesn't exist */
893 if ((loc = rb->strrchr(filename, '/')) != NULL) {
894 rb->strncpy(dirname, filename, loc - filename);
895 dirname[loc - filename] = '\0';
896 if(!(dir = rb->opendir(dirname)))
897 rb->mkdir(dirname);
898 else
899 rb->closedir(dir);
902 if (filename[0] == '\0' ||
903 (fd = rb->open(filename, O_WRONLY|O_CREAT|O_TRUNC)) < 0) {
904 rb->splash(HZ*2, "Unable to open %s", filename);
905 return false;
908 /* Sokoban: S/P for solution/progress : level number : current undo */
909 rb->snprintf(buf, sizeof(buf), "Sokoban:%c:%d:%d\n", (solution ? 'S' : 'P'),
910 current_info.level.index + 1, undo_info.current);
911 rb->write(fd, buf, rb->strlen(buf));
913 /* Filename of levelset */
914 rb->write(fd, buffered_boards.filename,
915 rb->strlen(buffered_boards.filename));
916 rb->write(fd, "\n", 1);
918 /* Full undo history */
919 rb->write(fd, undo_info.history, undo_info.max);
921 rb->close(fd);
923 return true;
926 static bool load(char *filename, bool silent)
928 int fd;
929 int i, n;
930 int len;
931 int button;
932 bool play_solution;
933 bool paused = false;
934 unsigned short speed = 2;
935 int delay[] = {HZ/2, HZ/3, HZ/4, HZ/6, HZ/8, HZ/12, HZ/16, HZ/25};
937 if (filename[0] == '\0' || (fd = rb->open(filename, O_RDONLY)) < 0) {
938 if (!silent)
939 rb->splash(HZ*2, "Unable to open %s", filename);
940 return false;
943 /* Read header, level number, & current undo */
944 rb->read_line(fd, buf, sizeof(buf));
946 /* If we're opening a level file, not a solution/progress file */
947 if (rb->strncmp(buf, "Sokoban", 7) != 0) {
948 rb->close(fd);
950 rb->strncpy(buffered_boards.filename, filename, MAX_PATH);
951 if (!read_levels(true))
952 return false;
954 current_info.level.index = 0;
955 load_level();
957 /* If there aren't any boxes to go or the player position wasn't set,
958 * the file probably wasn't a Sokoban level file */
959 if (current_info.level.boxes_to_go == 0 ||
960 current_info.player.row == 0 || current_info.player.col == 0) {
961 if (!silent)
962 rb->splash(HZ*2, "File is not a Sokoban level file");
963 return false;
966 } else {
968 /* Read filename of levelset */
969 rb->read_line(fd, buffered_boards.filename,
970 sizeof(buffered_boards.filename));
972 /* Read full undo history */
973 len = rb->read_line(fd, undo_info.history, MAX_UNDOS);
975 /* Correct len when trailing \r's or \n's are counted */
976 if (len > 2 && undo_info.history[len - 2] == '\0')
977 len -= 2;
978 else if (len > 1 && undo_info.history[len - 1] == '\0')
979 len--;
981 rb->close(fd);
983 /* Check to see if we're going to play a solution or resume progress */
984 play_solution = (buf[8] == 'S');
986 /* Get level number */
987 for (n = 0, i = 10; buf[i] >= '0' && buf[i] <= '9' && i < 15; i++)
988 n = n*10 + buf[i] - '0';
989 current_info.level.index = n - 1;
991 /* Get undo index */
992 for (n = 0, i++; buf[i] >= '0' && buf[i] <= '9' && i < 21; i++)
993 n = n*10 + buf[i] - '0';
994 if (n > len)
995 n = len;
996 undo_info.max = len;
998 if (current_info.level.index < 0) {
999 if (!silent)
1000 rb->splash(HZ*2, "Error loading level");
1001 return false;
1003 if (!read_levels(true))
1004 return false;
1005 if (current_info.level.index >= current_info.max_level) {
1006 if (!silent)
1007 rb->splash(HZ*2, "Error loading level");
1008 return false;
1011 load_level();
1013 if (play_solution) {
1014 rb->lcd_clear_display();
1015 update_screen();
1016 rb->sleep(2*delay[speed]);
1018 /* Replay solution until menu button is pressed */
1019 i = 0;
1020 while (true) {
1021 if (i < len) {
1022 if (!move(undo_info.history[i], true)) {
1023 n = i;
1024 break;
1026 rb->lcd_clear_display();
1027 update_screen();
1028 i++;
1029 } else
1030 paused = true;
1032 rb->sleep(delay[speed]);
1034 while ((button = rb->button_get(false)) || paused) {
1035 switch (button) {
1036 case SOKOBAN_MENU:
1037 /* Pretend the level is complete so we'll quit */
1038 current_info.level.boxes_to_go = 0;
1039 return true;
1041 case SOKOBAN_PAUSE:
1042 /* Toggle pause state */
1043 paused = !paused;
1044 break;
1046 case BUTTON_LEFT:
1047 case BUTTON_LEFT | BUTTON_REPEAT:
1048 /* Go back one move */
1049 if (paused) {
1050 if (undo())
1051 i--;
1052 rb->lcd_clear_display();
1053 update_screen();
1055 break;
1057 case BUTTON_RIGHT:
1058 case BUTTON_RIGHT | BUTTON_REPEAT:
1059 /* Go forward one move */
1060 if (paused) {
1061 if (redo())
1062 i++;
1063 rb->lcd_clear_display();
1064 update_screen();
1066 break;
1068 case SOKOBAN_UP:
1069 case SOKOBAN_UP | BUTTON_REPEAT:
1070 /* Speed up */
1071 if (speed < sizeof(delay)/sizeof(int) - 1)
1072 speed++;
1073 break;
1075 case SOKOBAN_DOWN:
1076 case SOKOBAN_DOWN | BUTTON_REPEAT:
1077 /* Slow down */
1078 if (speed > 0)
1079 speed--;
1082 if (paused)
1083 rb->sleep(HZ/33);
1087 /* If level is complete, wait for keypress before quitting */
1088 if (current_info.level.boxes_to_go == 0)
1089 rb->button_get(true);
1091 } else {
1092 /* Advance to current undo */
1093 for (i = 0; i < n; i++) {
1094 if (!move(undo_info.history[i], true)) {
1095 n = i;
1096 break;
1100 rb->button_clear_queue();
1101 rb->lcd_clear_display();
1104 undo_info.current = n;
1107 return true;
1110 static int sokoban_menu(void)
1112 int button;
1113 int selection = 0;
1114 int i;
1115 bool menu_quit;
1116 int start_selected = 0;
1117 int prev_level = current_info.level.index;
1119 MENUITEM_STRINGLIST(menu, "Sokoban Menu", NULL,
1120 "Resume", "Select Level", "Audio Playback", "Keys",
1121 "Load Default Level Set", "Quit Without Saving",
1122 "Save Progress & Quit");
1124 do {
1125 menu_quit = true;
1126 selection = rb->do_menu(&menu, &start_selected);
1128 switch (selection) {
1129 case 0: /* Resume */
1130 break;
1132 case 1: /* Select level */
1133 current_info.level.index++;
1134 rb->set_int("Select Level", "", UNIT_INT,
1135 &current_info.level.index, NULL, 1, 1,
1136 current_info.max_level, NULL);
1137 current_info.level.index--;
1138 if (prev_level != current_info.level.index) {
1139 init_undo();
1140 draw_level();
1141 } else
1142 menu_quit = false;
1143 break;
1145 case 2: /* Audio playback control */
1146 playback_control(rb);
1147 menu_quit = false;
1148 break;
1150 case 3: /* Keys */
1151 FOR_NB_SCREENS(i)
1152 rb->screens[i]->clear_display();
1153 rb->lcd_setfont(SOKOBAN_FONT);
1155 #if (CONFIG_KEYPAD == RECORDER_PAD) || \
1156 (CONFIG_KEYPAD == ARCHOS_AV300_PAD)
1157 rb->lcd_putsxy(3, 6, "[OFF] Menu");
1158 rb->lcd_putsxy(3, 16, "[ON] Undo");
1159 rb->lcd_putsxy(3, 26, "[PLAY] Redo");
1160 rb->lcd_putsxy(3, 36, "[F1] Down a Level");
1161 rb->lcd_putsxy(3, 46, "[F2] Restart Level");
1162 rb->lcd_putsxy(3, 56, "[F3] Up a Level");
1163 #elif CONFIG_KEYPAD == ONDIO_PAD
1164 rb->lcd_putsxy(3, 6, "[OFF] Menu");
1165 rb->lcd_putsxy(3, 16, "[MODE] Undo");
1166 rb->lcd_putsxy(3, 26, "[MODE+DOWN] Redo");
1167 rb->lcd_putsxy(3, 36, "[MODE+LEFT] Previous Level");
1168 rb->lcd_putsxy(3, 46, "[MODE+UP] Restart Level");
1169 rb->lcd_putsxy(3, 56, "[MODE+RIGHT] Up Level");
1170 #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
1171 (CONFIG_KEYPAD == IRIVER_H300_PAD)
1172 rb->lcd_putsxy(3, 6, "[STOP] Menu");
1173 rb->lcd_putsxy(3, 16, "[REC] Undo");
1174 rb->lcd_putsxy(3, 26, "[MODE] Redo");
1175 rb->lcd_putsxy(3, 36, "[PLAY+DOWN] Previous Level");
1176 rb->lcd_putsxy(3, 46, "[PLAY] Restart Level");
1177 rb->lcd_putsxy(3, 56, "[PLAY+UP] Next Level");
1178 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
1179 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
1180 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
1181 rb->lcd_putsxy(3, 6, "[SELECT+MENU] Menu");
1182 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
1183 rb->lcd_putsxy(3, 26, "[SELECT+PLAY] Redo");
1184 rb->lcd_putsxy(3, 36, "[SELECT+LEFT] Previous Level");
1185 rb->lcd_putsxy(3, 46, "[SELECT+RIGHT] Next Level");
1186 #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
1187 rb->lcd_putsxy(3, 6, "[POWER] Menu");
1188 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
1189 rb->lcd_putsxy(3, 26, "[PLAY] Redo");
1190 rb->lcd_putsxy(3, 36, "[REC] Restart Level");
1191 #elif CONFIG_KEYPAD == IRIVER_H10_PAD
1192 rb->lcd_putsxy(3, 6, "[POWER] Menu");
1193 rb->lcd_putsxy(3, 16, "[REW] Undo");
1194 rb->lcd_putsxy(3, 26, "[FF] Redo");
1195 rb->lcd_putsxy(3, 36, "[PLAY+DOWN] Previous Level");
1196 rb->lcd_putsxy(3, 46, "[PLAY+RIGHT] Restart Level");
1197 rb->lcd_putsxy(3, 56, "[PLAY+UP] Next Level");
1198 #elif CONFIG_KEYPAD == GIGABEAT_PAD
1199 rb->lcd_putsxy(3, 6, "[POWER] Menu");
1200 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
1201 rb->lcd_putsxy(3, 26, "[A] Redo");
1202 rb->lcd_putsxy(3, 36, "[VOL-] Previous Level");
1203 rb->lcd_putsxy(3, 46, "[MENU] Restart Level");
1204 rb->lcd_putsxy(3, 56, "[VOL+] Next Level");
1205 #elif CONFIG_KEYPAD == SANSA_E200_PAD
1206 rb->lcd_putsxy(3, 6, "[POWER] Menu");
1207 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
1208 rb->lcd_putsxy(3, 26, "[REC] Redo");
1209 rb->lcd_putsxy(3, 36, "[SELECT+DOWN] Previous Level");
1210 rb->lcd_putsxy(3, 46, "[SELECT+RIGHT] Restart Level");
1211 rb->lcd_putsxy(3, 56, "[SELECT+UP] Next Level");
1212 #endif
1214 FOR_NB_SCREENS(i)
1215 rb->screens[i]->update();
1217 /* Display until keypress */
1218 do {
1219 rb->sleep(HZ/20);
1220 button = rb->button_get(false);
1221 } while (!button || button & BUTTON_REL ||
1222 button & BUTTON_REPEAT);
1224 menu_quit = false;
1225 break;
1227 case 4: /* Load default levelset */
1228 init_boards();
1229 if (!read_levels(true))
1230 return 5; /* Quit */
1231 load_level();
1232 break;
1234 case 5: /* Quit */
1235 break;
1237 case 6: /* Save & quit */
1238 save(SOKOBAN_SAVE_FILE, false);
1239 rb->reload_directory();
1242 } while (!menu_quit);
1244 /* Restore font */
1245 rb->lcd_setfont(SOKOBAN_FONT);
1247 FOR_NB_SCREENS(i) {
1248 rb->screens[i]->clear_display();
1249 rb->screens[i]->update();
1252 return selection;
1255 static bool sokoban_loop(void)
1257 bool moved;
1258 int i = 0, button = 0, lastbutton = 0;
1259 short r = 0, c = 0;
1260 int w, h;
1261 char *loc;
1263 while (true) {
1264 moved = false;
1266 r = current_info.player.row;
1267 c = current_info.player.col;
1269 button = rb->button_get(true);
1271 switch(button)
1273 #ifdef SOKOBAN_RC_MENU
1274 case SOKOBAN_RC_MENU:
1275 #endif
1276 case SOKOBAN_MENU:
1277 switch (sokoban_menu()) {
1278 case 5: /* Quit */
1279 case 6: /* Save & quit */
1280 return PLUGIN_OK;
1282 update_screen();
1283 break;
1285 case SOKOBAN_UNDO:
1286 #ifdef SOKOBAN_UNDO_PRE
1287 if (lastbutton != SOKOBAN_UNDO_PRE)
1288 break;
1289 #else /* repeat can't work here for Ondio, iPod, et al */
1290 case SOKOBAN_UNDO | BUTTON_REPEAT:
1291 #endif
1292 undo();
1293 rb->lcd_clear_display();
1294 update_screen();
1295 break;
1297 #ifdef SOKOBAN_REDO
1298 case SOKOBAN_REDO:
1299 case SOKOBAN_REDO | BUTTON_REPEAT:
1300 moved = redo();
1301 rb->lcd_clear_display();
1302 update_screen();
1303 break;
1304 #endif
1306 #ifdef SOKOBAN_LEVEL_UP
1307 case SOKOBAN_LEVEL_UP:
1308 case SOKOBAN_LEVEL_UP | BUTTON_REPEAT:
1309 /* next level */
1310 init_undo();
1311 if (current_info.level.index + 1 < current_info.max_level)
1312 current_info.level.index++;
1314 draw_level();
1315 break;
1316 #endif
1318 #ifdef SOKOBAN_LEVEL_DOWN
1319 case SOKOBAN_LEVEL_DOWN:
1320 case SOKOBAN_LEVEL_DOWN | BUTTON_REPEAT:
1321 /* previous level */
1322 init_undo();
1323 if (current_info.level.index > 0)
1324 current_info.level.index--;
1326 draw_level();
1327 break;
1328 #endif
1330 #ifdef SOKOBAN_LEVEL_REPEAT
1331 case SOKOBAN_LEVEL_REPEAT:
1332 case SOKOBAN_LEVEL_REPEAT | BUTTON_REPEAT:
1333 /* same level */
1334 init_undo();
1335 draw_level();
1336 break;
1337 #endif
1339 case BUTTON_LEFT:
1340 case BUTTON_LEFT | BUTTON_REPEAT:
1341 moved = move(SOKOBAN_MOVE_LEFT, false);
1342 break;
1344 case BUTTON_RIGHT:
1345 case BUTTON_RIGHT | BUTTON_REPEAT:
1346 moved = move(SOKOBAN_MOVE_RIGHT, false);
1347 break;
1349 case SOKOBAN_UP:
1350 case SOKOBAN_UP | BUTTON_REPEAT:
1351 moved = move(SOKOBAN_MOVE_UP, false);
1352 break;
1354 case SOKOBAN_DOWN:
1355 case SOKOBAN_DOWN | BUTTON_REPEAT:
1356 moved = move(SOKOBAN_MOVE_DOWN, false);
1357 break;
1359 default:
1360 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
1361 return PLUGIN_USB_CONNECTED;
1362 break;
1365 lastbutton = button;
1367 if (moved) {
1368 rb->lcd_clear_display();
1369 update_screen();
1372 /* We have completed this level */
1373 if (current_info.level.boxes_to_go == 0) {
1375 if (moved) {
1376 rb->lcd_clear_display();
1378 /* Show level complete message & stats */
1379 rb->snprintf(buf, sizeof(buf), "Level %d Complete!",
1380 current_info.level.index + 1);
1381 rb->lcd_getstringsize(buf, &w, &h);
1382 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h*3, buf);
1384 rb->snprintf(buf, sizeof(buf), "%4d Moves ",
1385 current_info.level.moves);
1386 rb->lcd_getstringsize(buf, &w, &h);
1387 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h, buf);
1389 rb->snprintf(buf, sizeof(buf), "%4d Pushes",
1390 current_info.level.pushes);
1391 rb->lcd_getstringsize(buf, &w, &h);
1392 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2, buf);
1394 if (undo_info.count < MAX_UNDOS) {
1395 rb->snprintf(buf, sizeof(buf), "%s: Save solution",
1396 BUTTON_SAVE_NAME);
1397 rb->lcd_getstringsize(buf, &w, &h);
1398 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 + h*2, buf);
1401 rb->lcd_update();
1402 rb->sleep(HZ/4);
1403 rb->button_clear_queue();
1405 /* Display for 4 seconds or until new keypress */
1406 for (i = 0; i < 80; i++) {
1407 rb->sleep(HZ/20);
1408 button = rb->button_get(false);
1409 if (button && !(button & BUTTON_REL) &&
1410 !(button & BUTTON_REPEAT))
1411 break;
1414 if (button == BUTTON_SAVE) {
1415 if (undo_info.count < MAX_UNDOS) {
1416 /* Set filename to current levelset plus level number
1417 * and .sok extension. Use SAVE_FOLDER if using the
1418 * default levelset, since it's in a hidden folder. */
1419 if (rb->strcmp(buffered_boards.filename,
1420 SOKOBAN_LEVELS_FILE) == 0) {
1421 rb->snprintf(buf, sizeof(buf),
1422 "%s/sokoban.%d.sok",
1423 SOKOBAN_SAVE_FOLDER,
1424 current_info.level.index + 1);
1425 } else {
1426 if ((loc = rb->strrchr(buffered_boards.filename,
1427 '.')) != NULL)
1428 *loc = '\0';
1429 rb->snprintf(buf, sizeof(buf), "%s.%d.sok",
1430 buffered_boards.filename,
1431 current_info.level.index + 1);
1432 if (loc != NULL)
1433 *loc = '.';
1436 if (!rb->kbd_input(buf, MAX_PATH))
1437 save(buf, true);
1438 } else
1439 rb->splash(HZ*2, "Solution too long to save");
1441 rb->lcd_setfont(SOKOBAN_FONT); /* Restore font */
1445 FOR_NB_SCREENS(i) {
1446 rb->screens[i]->clear_display();
1447 rb->screens[i]->update();
1450 current_info.level.index++;
1452 /* clear undo stats */
1453 init_undo();
1455 if (current_info.level.index >= current_info.max_level) {
1456 /* Show levelset complete message */
1457 rb->snprintf(buf, sizeof(buf), "You WIN!!");
1458 rb->lcd_getstringsize(buf, &w, &h);
1459 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h/2, buf);
1461 rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
1462 /* Display for 4 seconds or until keypress */
1463 for (i = 0; i < 80; i++) {
1464 rb->lcd_fillrect(0, 0, LCD_WIDTH, LCD_HEIGHT);
1465 rb->lcd_update();
1466 rb->sleep(HZ/10);
1468 button = rb->button_get(false);
1469 if (button && !(button & BUTTON_REL))
1470 break;
1472 rb->lcd_set_drawmode(DRMODE_SOLID);
1474 /* Reset to first level & show quit menu */
1475 current_info.level.index = 0;
1477 switch (sokoban_menu()) {
1478 case 5: /* Quit */
1479 case 6: /* Save & quit */
1480 return PLUGIN_OK;
1484 load_level();
1485 update_screen();
1488 } /* end while */
1490 return PLUGIN_OK;
1494 enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
1496 int w, h;
1498 (void)(parameter);
1499 rb = api;
1501 rb->lcd_setfont(SOKOBAN_FONT);
1503 rb->lcd_clear_display();
1504 rb->lcd_getstringsize(SOKOBAN_TITLE, &w, &h);
1505 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h/2, SOKOBAN_TITLE);
1506 rb->lcd_update();
1507 rb->sleep(HZ); /* Show title for 1 second */
1509 init_boards();
1511 if (parameter == NULL) {
1512 /* Attempt to resume saved progress, otherwise start at beginning */
1513 if (!load(SOKOBAN_SAVE_FILE, true)) {
1514 init_boards();
1515 if (!read_levels(true))
1516 return PLUGIN_OK;
1517 load_level();
1520 } else {
1521 /* The plugin is being used to open a file */
1522 if (load((char*) parameter, false)) {
1523 /* If we loaded & played a solution, quit */
1524 if (current_info.level.boxes_to_go == 0)
1525 return PLUGIN_OK;
1526 } else
1527 return PLUGIN_OK;
1530 rb->lcd_clear_display();
1531 update_screen();
1533 return sokoban_loop();
1536 #endif