allow the plugin playback control menu to be put in a viewport.
[Rockbox.git] / apps / plugins / sokoban.c
blobc8a6ba7c92bbd3d36efd072adb8760de2116e79c
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_LEFT BUTTON_LEFT
109 #define SOKOBAN_RIGHT BUTTON_RIGHT
110 #define SOKOBAN_UP BUTTON_UP
111 #define SOKOBAN_DOWN BUTTON_DOWN
112 #define SOKOBAN_MENU BUTTON_OFF
113 #define SOKOBAN_UNDO BUTTON_ON
114 #define SOKOBAN_REDO BUTTON_PLAY
115 #define SOKOBAN_LEVEL_DOWN BUTTON_F1
116 #define SOKOBAN_LEVEL_REPEAT BUTTON_F2
117 #define SOKOBAN_LEVEL_UP BUTTON_F3
118 #define SOKOBAN_PAUSE BUTTON_PLAY
119 #define BUTTON_SAVE BUTTON_ON
120 #define BUTTON_SAVE_NAME "ON"
122 #elif CONFIG_KEYPAD == ONDIO_PAD
123 #define SOKOBAN_LEFT BUTTON_LEFT
124 #define SOKOBAN_RIGHT BUTTON_RIGHT
125 #define SOKOBAN_UP BUTTON_UP
126 #define SOKOBAN_DOWN BUTTON_DOWN
127 #define SOKOBAN_MENU BUTTON_OFF
128 #define SOKOBAN_UNDO_PRE BUTTON_MENU
129 #define SOKOBAN_UNDO (BUTTON_MENU | BUTTON_REL)
130 #define SOKOBAN_REDO (BUTTON_MENU | BUTTON_DOWN)
131 #define SOKOBAN_LEVEL_DOWN (BUTTON_MENU | BUTTON_LEFT)
132 #define SOKOBAN_LEVEL_REPEAT (BUTTON_MENU | BUTTON_UP)
133 #define SOKOBAN_LEVEL_UP (BUTTON_MENU | BUTTON_RIGHT)
134 #define SOKOBAN_PAUSE BUTTON_MENU
135 #define BUTTON_SAVE BUTTON_MENU
136 #define BUTTON_SAVE_NAME "MENU"
138 #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
139 (CONFIG_KEYPAD == IRIVER_H300_PAD)
140 #define SOKOBAN_LEFT BUTTON_LEFT
141 #define SOKOBAN_RIGHT BUTTON_RIGHT
142 #define SOKOBAN_UP BUTTON_UP
143 #define SOKOBAN_DOWN BUTTON_DOWN
144 #define SOKOBAN_MENU BUTTON_OFF
145 #define SOKOBAN_UNDO BUTTON_REC
146 #define SOKOBAN_REDO BUTTON_MODE
147 #define SOKOBAN_LEVEL_DOWN (BUTTON_ON | BUTTON_DOWN)
148 #define SOKOBAN_LEVEL_REPEAT BUTTON_ON
149 #define SOKOBAN_LEVEL_UP (BUTTON_ON | BUTTON_UP)
150 #define SOKOBAN_PAUSE BUTTON_ON
151 #define BUTTON_SAVE BUTTON_MODE
152 #define BUTTON_SAVE_NAME "MODE"
154 #define SOKOBAN_RC_MENU BUTTON_RC_STOP
156 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
157 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
158 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
159 #define SOKOBAN_LEFT BUTTON_LEFT
160 #define SOKOBAN_RIGHT BUTTON_RIGHT
161 #define SOKOBAN_UP BUTTON_MENU
162 #define SOKOBAN_DOWN BUTTON_PLAY
163 #define SOKOBAN_MENU (BUTTON_SELECT | BUTTON_MENU)
164 #define SOKOBAN_UNDO_PRE BUTTON_SELECT
165 #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
166 #define SOKOBAN_REDO (BUTTON_SELECT | BUTTON_PLAY)
167 #define SOKOBAN_LEVEL_DOWN (BUTTON_SELECT | BUTTON_LEFT)
168 #define SOKOBAN_LEVEL_UP (BUTTON_SELECT | BUTTON_RIGHT)
169 #define SOKOBAN_PAUSE BUTTON_SELECT
170 #define BUTTON_SAVE BUTTON_SELECT
171 #define BUTTON_SAVE_NAME "SELECT"
173 /* FIXME: if/when simultaneous button presses work for X5/M5,
174 * add level up/down */
175 #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
176 #define SOKOBAN_LEFT BUTTON_LEFT
177 #define SOKOBAN_RIGHT BUTTON_RIGHT
178 #define SOKOBAN_UP BUTTON_UP
179 #define SOKOBAN_DOWN BUTTON_DOWN
180 #define SOKOBAN_MENU BUTTON_POWER
181 #define SOKOBAN_UNDO_PRE BUTTON_SELECT
182 #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
183 #define SOKOBAN_LEVEL_REPEAT BUTTON_REC
184 #define SOKOBAN_REDO BUTTON_PLAY
185 #define SOKOBAN_PAUSE BUTTON_PLAY
186 #define BUTTON_SAVE BUTTON_SELECT
187 #define BUTTON_SAVE_NAME "SELECT"
189 #elif CONFIG_KEYPAD == IRIVER_H10_PAD
190 #define SOKOBAN_LEFT BUTTON_LEFT
191 #define SOKOBAN_RIGHT BUTTON_RIGHT
192 #define SOKOBAN_UP BUTTON_SCROLL_UP
193 #define SOKOBAN_DOWN BUTTON_SCROLL_DOWN
194 #define SOKOBAN_MENU BUTTON_POWER
195 #define SOKOBAN_UNDO_PRE BUTTON_REW
196 #define SOKOBAN_UNDO (BUTTON_REW | BUTTON_REL)
197 #define SOKOBAN_REDO BUTTON_FF
198 #define SOKOBAN_LEVEL_DOWN (BUTTON_PLAY | BUTTON_SCROLL_DOWN)
199 #define SOKOBAN_LEVEL_REPEAT (BUTTON_PLAY | BUTTON_RIGHT)
200 #define SOKOBAN_LEVEL_UP (BUTTON_PLAY | BUTTON_SCROLL_UP)
201 #define SOKOBAN_PAUSE BUTTON_PLAY
202 #define BUTTON_SAVE BUTTON_PLAY
203 #define BUTTON_SAVE_NAME "PLAY"
205 #elif CONFIG_KEYPAD == GIGABEAT_PAD
206 #define SOKOBAN_LEFT BUTTON_LEFT
207 #define SOKOBAN_RIGHT BUTTON_RIGHT
208 #define SOKOBAN_UP BUTTON_UP
209 #define SOKOBAN_DOWN BUTTON_DOWN
210 #define SOKOBAN_MENU BUTTON_POWER
211 #define SOKOBAN_UNDO BUTTON_SELECT
212 #define SOKOBAN_REDO BUTTON_A
213 #define SOKOBAN_LEVEL_DOWN BUTTON_VOL_DOWN
214 #define SOKOBAN_LEVEL_REPEAT BUTTON_MENU
215 #define SOKOBAN_LEVEL_UP BUTTON_VOL_UP
216 #define SOKOBAN_PAUSE BUTTON_SELECT
217 #define BUTTON_SAVE BUTTON_SELECT
218 #define BUTTON_SAVE_NAME "SELECT"
220 #elif CONFIG_KEYPAD == SANSA_E200_PAD
221 #define SOKOBAN_LEFT BUTTON_LEFT
222 #define SOKOBAN_RIGHT BUTTON_RIGHT
223 #define SOKOBAN_UP BUTTON_UP
224 #define SOKOBAN_DOWN BUTTON_DOWN
225 #define SOKOBAN_MENU BUTTON_POWER
226 #define SOKOBAN_UNDO_PRE BUTTON_SELECT
227 #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
228 #define SOKOBAN_REDO BUTTON_REC
229 #define SOKOBAN_LEVEL_DOWN (BUTTON_SELECT | BUTTON_DOWN)
230 #define SOKOBAN_LEVEL_REPEAT (BUTTON_SELECT | BUTTON_RIGHT)
231 #define SOKOBAN_LEVEL_UP (BUTTON_SELECT | BUTTON_UP)
232 #define SOKOBAN_PAUSE BUTTON_SELECT
233 #define BUTTON_SAVE BUTTON_SELECT
234 #define BUTTON_SAVE_NAME "SELECT"
236 #elif CONFIG_KEYPAD == GIGABEAT_S_PAD
237 #define SOKOBAN_LEFT BUTTON_LEFT
238 #define SOKOBAN_RIGHT BUTTON_RIGHT
239 #define SOKOBAN_UP BUTTON_UP
240 #define SOKOBAN_DOWN BUTTON_DOWN
241 #define SOKOBAN_MENU BUTTON_MENU
242 #define SOKOBAN_UNDO BUTTON_VOL_UP
243 #define SOKOBAN_REDO BUTTON_VOL_DOWN
244 #define SOKOBAN_LEVEL_DOWN BUTTON_PREV
245 #define SOKOBAN_LEVEL_REPEAT BUTTON_PLAY
246 #define SOKOBAN_LEVEL_UP BUTTON_NEXT
247 #define SOKOBAN_PAUSE BUTTON_SELECT
248 #define BUTTON_SAVE BUTTON_SELECT
249 #define BUTTON_SAVE_NAME "SELECT"
251 #elif CONFIG_KEYPAD == MROBE100_PAD
252 #define SOKOBAN_LEFT BUTTON_LEFT
253 #define SOKOBAN_RIGHT BUTTON_RIGHT
254 #define SOKOBAN_UP BUTTON_UP
255 #define SOKOBAN_DOWN BUTTON_DOWN
256 #define SOKOBAN_MENU BUTTON_POWER
257 #define SOKOBAN_UNDO BUTTON_SELECT
258 #define SOKOBAN_REDO BUTTON_MENU
259 #define SOKOBAN_LEVEL_DOWN (BUTTON_DISPLAY | BUTTON_DOWN)
260 #define SOKOBAN_LEVEL_REPEAT (BUTTON_DISPLAY | BUTTON_RIGHT)
261 #define SOKOBAN_LEVEL_UP (BUTTON_DISPLAY | BUTTON_UP)
262 #define SOKOBAN_PAUSE BUTTON_SELECT
263 #define BUTTON_SAVE BUTTON_SELECT
264 #define BUTTON_SAVE_NAME "SELECT"
266 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
267 #define SOKOBAN_LEFT BUTTON_RC_REW
268 #define SOKOBAN_RIGHT BUTTON_RC_FF
269 #define SOKOBAN_UP BUTTON_RC_VOL_UP
270 #define SOKOBAN_DOWN BUTTON_RC_VOL_DOWN
271 #define SOKOBAN_MENU BUTTON_RC_REC
272 #define SOKOBAN_UNDO BUTTON_RC_MODE
273 #define SOKOBAN_REDO BUTTON_RC_MENU
274 #define SOKOBAN_PAUSE BUTTON_RC_PLAY
275 #define BUTTON_SAVE BUTTON_RC_PLAY
276 #define BUTTON_SAVE_NAME "PLAY"
278 #define SOKOBAN_RC_MENU BUTTON_REC
280 #elif CONFIG_KEYPAD == COWOND2_PAD
281 #define SOKOBAN_LEFT BUTTON_LEFT
282 #define SOKOBAN_RIGHT BUTTON_RIGHT
283 #define SOKOBAN_UP BUTTON_UP
284 #define SOKOBAN_DOWN BUTTON_DOWN
285 #define SOKOBAN_MENU BUTTON_MENU
286 #define SOKOBAN_UNDO_PRE BUTTON_PLUS
287 #define SOKOBAN_UNDO (BUTTON_LEFT|BUTTON_MENU)
288 #define SOKOBAN_REDO (BUTTON_RIGHT | BUTTON_MENU)
289 #define SOKOBAN_LEVEL_DOWN (BUTTON_MENU | BUTTON_DOWN)
290 #define SOKOBAN_LEVEL_UP (BUTTON_MENU | BUTTON_UP)
291 #define SOKOBAN_LEVEL_REPEAT (BUTTON_SELECT|BUTTON_MENU)
292 #define SOKOBAN_PAUSE BUTTON_SELECT
293 #define BUTTON_SAVE BUTTON_SELECT
294 #define BUTTON_SAVE_NAME "SELECT"
296 #else
297 #error No keymap defined!
298 #endif
300 #define SOKOBAN_FONT FONT_SYSFIXED
303 /* The Location, Undo and LevelInfo structs are OO-flavored.
304 * (oooh!-flavored as Schnueff puts it.) It makes more you have to know,
305 * but the overall data layout becomes more manageable. */
307 /* Level data & stats */
308 struct LevelInfo {
309 int index; /* Level index (level number - 1) */
310 int moves; /* Moves & pushes for the stats */
311 int pushes;
312 short boxes_to_go; /* Number of unplaced boxes remaining in level */
313 short height; /* Height & width for centering level display */
314 short width;
317 struct Location {
318 short row;
319 short col;
322 /* Our full undo history */
323 static struct UndoInfo {
324 int count; /* How many undos have been done */
325 int current; /* Which history is the current undo */
326 int max; /* Which history is the max redoable */
327 char history[MAX_UNDOS];
328 } undo_info;
330 /* Our playing board */
331 static struct BoardInfo {
332 char board[ROWS][COLS]; /* The current board data */
333 struct LevelInfo level; /* Level data & stats */
334 struct Location player; /* Where the player is */
335 int max_level; /* The number of levels we have */
336 } current_info;
338 static struct BufferedBoards {
339 char filename[MAX_PATH]; /* Filename of the levelset we're using */
340 char data[MAX_LEVEL_DATA]; /* Buffered level data */
341 int index[MAX_LEVELS + 1]; /* Where each buffered board begins & ends */
342 int start; /* Index of first buffered board */
343 int end; /* Index of last buffered board */
344 short prebuffered_boards; /* Number of boards before current to store */
345 } buffered_boards;
348 static struct plugin_api* rb;
349 MEM_FUNCTION_WRAPPERS(rb);
351 static char buf[ROWS*(COLS + 1)]; /* Enough for a whole board or a filename */
354 static void init_undo(void)
356 undo_info.count = 0;
357 undo_info.current = 0;
358 undo_info.max = 0;
361 static void get_delta(char direction, short *d_r, short *d_c)
363 switch (direction) {
364 case SOKOBAN_PUSH_LEFT:
365 case SOKOBAN_MOVE_LEFT:
366 *d_r = 0;
367 *d_c = -1;
368 break;
369 case SOKOBAN_PUSH_RIGHT:
370 case SOKOBAN_MOVE_RIGHT:
371 *d_r = 0;
372 *d_c = 1;
373 break;
374 case SOKOBAN_PUSH_UP:
375 case SOKOBAN_MOVE_UP:
376 *d_r = -1;
377 *d_c = 0;
378 break;
379 case SOKOBAN_PUSH_DOWN:
380 case SOKOBAN_MOVE_DOWN:
381 *d_r = 1;
382 *d_c = 0;
386 static bool undo(void)
388 char undo;
389 short r, c;
390 short d_r = 0, d_c = 0; /* delta row & delta col */
391 char *space_cur, *space_next, *space_prev;
392 bool undo_push = false;
394 /* If no more undos or we've wrapped all the way around, quit */
395 if (undo_info.count == 0 || undo_info.current - 1 == undo_info.max)
396 return false;
398 /* Move to previous undo in the list */
399 if (undo_info.current == 0 && undo_info.count > 1)
400 undo_info.current = MAX_UNDOS - 1;
401 else
402 undo_info.current--;
404 undo_info.count--;
406 undo = undo_info.history[undo_info.current];
408 if (undo < SOKOBAN_MOVE_MIN)
409 undo_push = true;
411 get_delta(undo, &d_r, &d_c);
413 r = current_info.player.row;
414 c = current_info.player.col;
416 /* Give the 3 spaces we're going to use better names */
417 space_cur = &current_info.board[r][c];
418 space_next = &current_info.board[r + d_r][c + d_c];
419 space_prev = &current_info.board[r - d_r][c - d_c];
421 /* Update board info */
422 if (undo_push) {
423 /* Moving box from goal to floor */
424 if (*space_next == '*' && *space_cur == '@')
425 current_info.level.boxes_to_go++;
426 /* Moving box from floor to goal */
427 else if (*space_next == '$' && *space_cur == '+')
428 current_info.level.boxes_to_go--;
430 /* Move box off of next space... */
431 *space_next = (*space_next == '*' ? '.' : ' ');
432 /* ...and on to current space */
433 *space_cur = (*space_cur == '+' ? '*' : '$');
435 current_info.level.pushes--;
436 } else
437 /* Just move player off of current space */
438 *space_cur = (*space_cur == '+' ? '.' : ' ');
439 /* Move player back to previous space */
440 *space_prev = (*space_prev == '.' ? '+' : '@');
442 /* Update position */
443 current_info.player.row -= d_r;
444 current_info.player.col -= d_c;
446 current_info.level.moves--;
448 return true;
451 static void add_undo(char undo)
453 undo_info.history[undo_info.current] = undo;
455 /* Wrap around if MAX_UNDOS exceeded */
456 if (undo_info.current < (MAX_UNDOS - 1))
457 undo_info.current++;
458 else
459 undo_info.current = 0;
461 if (undo_info.count < MAX_UNDOS)
462 undo_info.count++;
465 static bool move(char direction, bool redo)
467 short r, c;
468 short d_r = 0, d_c = 0; /* delta row & delta col */
469 char *space_cur, *space_next, *space_beyond;
470 bool push = false;
472 get_delta(direction, &d_r, &d_c);
474 r = current_info.player.row;
475 c = current_info.player.col;
477 /* Check for out-of-bounds */
478 if (r + 2*d_r < 0 || r + 2*d_r >= ROWS ||
479 c + 2*d_c < 0 || c + 2*d_c >= COLS)
480 return false;
482 /* Give the 3 spaces we're going to use better names */
483 space_cur = &current_info.board[r][c];
484 space_next = &current_info.board[r + d_r][c + d_c];
485 space_beyond = &current_info.board[r + 2*d_r][c + 2*d_c];
487 if (*space_next == '$' || *space_next == '*') {
488 /* Change direction from move to push for undo */
489 if (direction >= SOKOBAN_MOVE_MIN)
490 direction -= SOKOBAN_MOVE_DIFF;
491 push = true;
493 else if (direction < SOKOBAN_MOVE_MIN)
494 /* Change back to move if redo/solution playback push is invalid */
495 direction += SOKOBAN_MOVE_DIFF;
497 /* Update board info */
498 if (push) {
499 /* Moving box from goal to floor */
500 if (*space_next == '*' && *space_beyond == ' ')
501 current_info.level.boxes_to_go++;
502 /* Moving box from floor to goal */
503 else if (*space_next == '$' && *space_beyond == '.')
504 current_info.level.boxes_to_go--;
505 /* Check for invalid move */
506 else if (*space_beyond != '.' && *space_beyond != ' ')
507 return false;
509 /* Move player onto next space */
510 *space_next = (*space_next == '*' ? '+' : '@');
511 /* Move box onto space beyond next */
512 *space_beyond = (*space_beyond == '.' ? '*' : '$');
514 current_info.level.pushes++;
515 } else {
516 /* Check for invalid move */
517 if (*space_next != '.' && *space_next != ' ')
518 return false;
520 /* Move player onto next space */
521 *space_next = (*space_next == '.' ? '+' : '@');
523 /* Move player off of current space */
524 *space_cur = (*space_cur == '+' ? '.' : ' ');
526 /* Update position */
527 current_info.player.row += d_r;
528 current_info.player.col += d_c;
530 current_info.level.moves++;
532 /* Update undo_info.max to current on every normal move,
533 * except if it's the same as a redo. */
534 /* normal move and either */
535 if (!redo &&
536 /* moves have been undone... */
537 ((undo_info.max != undo_info.current &&
538 /* ...and the current move is NOT the same as the one in history */
539 undo_info.history[undo_info.current] != direction) ||
540 /* or moves have not been undone */
541 undo_info.max == undo_info.current)) {
542 add_undo(direction);
543 undo_info.max = undo_info.current;
544 } else /* redo move or move was same as redo */
545 add_undo(direction); /* add_undo to update current */
547 return true;
550 #ifdef SOKOBAN_REDO
551 static bool redo(void)
553 /* If no moves have been undone, quit */
554 if (undo_info.current == undo_info.max)
555 return false;
557 return move(undo_info.history[(undo_info.current < MAX_UNDOS ?
558 undo_info.current : 0)], true);
560 #endif
562 static void init_boards(void)
564 rb->strncpy(buffered_boards.filename, SOKOBAN_LEVELS_FILE, MAX_PATH);
566 current_info.level.index = 0;
567 current_info.player.row = 0;
568 current_info.player.col = 0;
569 current_info.max_level = 0;
571 buffered_boards.start = 0;
572 buffered_boards.end = 0;
573 buffered_boards.prebuffered_boards = 0;
575 init_undo();
578 static bool read_levels(bool initialize)
580 int fd = 0;
581 short len;
582 short lastlen = 0;
583 short row = 0;
584 int level_count = 0;
586 int i = 0;
587 int level_len = 0;
588 bool index_set = false;
590 /* Get the index of the first level to buffer */
591 if (current_info.level.index > buffered_boards.prebuffered_boards &&
592 !initialize)
593 buffered_boards.start = current_info.level.index -
594 buffered_boards.prebuffered_boards;
595 else
596 buffered_boards.start = 0;
598 if ((fd = rb->open(buffered_boards.filename, O_RDONLY)) < 0) {
599 rb->splash(HZ*2, "Unable to open %s", buffered_boards.filename);
600 return false;
603 do {
604 len = rb->read_line(fd, buf, sizeof(buf));
606 /* Correct len when trailing \r's or \n's are counted */
607 if (len > 2 && buf[len - 2] == '\0')
608 len -= 2;
609 else if (len > 1 && buf[len - 1] == '\0')
610 len--;
612 /* Skip short lines & lines with non-level data */
613 if (len >= 3 && ((buf[0] >= '1' && buf[0] <= '9') || buf[0] == '#' ||
614 buf[0] == ' ' || buf[0] == '-' || buf[0] == '_')) {
615 if (level_count >= buffered_boards.start) {
616 /* Set the index of this level */
617 if (!index_set &&
618 level_count - buffered_boards.start < MAX_LEVELS) {
619 buffered_boards.index[level_count - buffered_boards.start]
620 = i;
621 index_set = true;
623 /* Copy buffer to board data */
624 if (i + level_len + len < MAX_LEVEL_DATA) {
625 rb->memcpy(&buffered_boards.data[i + level_len], buf, len);
626 buffered_boards.data[i + level_len + len] = '\n';
629 level_len += len + 1;
630 row++;
632 /* If newline & level is tall enough or is RLE */
633 } else if (buf[0] == '\0' && (row > 2 || lastlen > 22)) {
634 level_count++;
635 if (level_count >= buffered_boards.start) {
636 i += level_len;
637 if (i < MAX_LEVEL_DATA)
638 buffered_boards.end = level_count;
639 else if (!initialize)
640 break;
642 row = 0;
643 level_len = 0;
644 index_set = false;
646 } else if (len > 22)
647 len = 1;
649 } while ((lastlen = len));
651 /* Set the index of the end of the last level */
652 if (level_count - buffered_boards.start < MAX_LEVELS)
653 buffered_boards.index[level_count - buffered_boards.start] = i;
655 if (initialize) {
656 current_info.max_level = level_count;
657 buffered_boards.prebuffered_boards = buffered_boards.end/2;
660 rb->close(fd);
662 return true;
665 static void load_level(void)
667 int c, r;
668 int i, n;
669 int level_size;
670 int index = current_info.level.index - buffered_boards.start;
671 char *level;
673 /* Get the buffered board index of the current level */
674 if (current_info.level.index < buffered_boards.start ||
675 current_info.level.index >= buffered_boards.end) {
676 read_levels(false);
677 if (current_info.level.index > buffered_boards.prebuffered_boards)
678 index = buffered_boards.prebuffered_boards;
679 else
680 index = current_info.level.index;
682 level = &buffered_boards.data[buffered_boards.index[index]];
684 /* Reset level info */
685 current_info.level.moves = 0;
686 current_info.level.pushes = 0;
687 current_info.level.boxes_to_go = 0;
688 current_info.level.width = 0;
690 /* Clear board */
691 for (r = 0; r < ROWS; r++)
692 for (c = 0; c < COLS; c++)
693 current_info.board[r][c] = 'X';
695 level_size = buffered_boards.index[index + 1] -
696 buffered_boards.index[index];
698 for (r = 0, c = 0, n = 1, i = 0; i < level_size; i++) {
699 if (level[i] == '\n' || level[i] == '|') {
700 if (c > 3) {
701 /* Update max width of level & go to next row */
702 if (c > current_info.level.width)
703 current_info.level.width = c;
704 c = 0;
705 r++;
706 if (r >= ROWS)
707 break;
709 } else if (c < COLS) {
710 /* Read RLE character's length into n */
711 if (level[i] >= '0' && level[i] <= '9') {
712 n = level[i++] - '0';
713 if (level[i] >= '0' && level[i] <= '9')
714 n = n*10 + level[i++] - '0';
717 /* Cleanup & replace */
718 if (level[i] == '%')
719 level[i] = '*';
720 else if (level[i] == '-' || level[i] == '_')
721 level[i] = ' ';
723 if (n > 1) {
724 if (c + n >= COLS)
725 n = COLS - c;
727 if (level[i] == '.')
728 current_info.level.boxes_to_go += n;
730 /* Put RLE character n times */
731 while (n--)
732 current_info.board[r][c++] = level[i];
733 n = 1;
735 } else {
736 if (level[i] == '.' || level[i] == '+')
737 current_info.level.boxes_to_go++;
739 if (level[i] == '@' ||level[i] == '+') {
740 current_info.player.row = r;
741 current_info.player.col = c;
744 current_info.board[r][c++] = level[i];
749 current_info.level.height = r;
751 #if LCD_DEPTH > 2
752 /* Fill in blank space outside level on color targets */
753 for (r = 0; r < ROWS; r++)
754 for (c = 0; current_info.board[r][c] == ' ' && c < COLS; c++)
755 current_info.board[r][c] = 'X';
757 for (c = 0; c < COLS; c++) {
758 for (r = 0; (current_info.board[r][c] == ' ' ||
759 current_info.board[r][c] == 'X') && r < ROWS; r++)
760 current_info.board[r][c] = 'X';
761 for (r = ROWS - 1; (current_info.board[r][c] == ' ' ||
762 current_info.board[r][c] == 'X') && r >= 0; r--)
763 current_info.board[r][c] = 'X';
765 #endif
768 static void update_screen(void)
770 int c, r;
771 int rows, cols;
773 #if LCD_DEPTH < 2 || ((LCD_HEIGHT < 96 || LCD_WIDTH < 152) && \
774 (LCD_HEIGHT < 121 || LCD_WIDTH < 120))
775 int i, j;
776 int max = MAGNIFY - 1;
777 int middle = max/2;
778 int ldelta = (middle + 1)/2;
779 #endif
781 #if LCD_WIDTH - (COLS*MAGNIFY) < 32
782 #define STAT_HEIGHT 25
783 #define STAT_X (LCD_WIDTH - 120)/2
784 #define STAT_Y (LCD_HEIGHT - STAT_HEIGHT)
785 #define BOARD_WIDTH LCD_WIDTH
786 #define BOARD_HEIGHT (LCD_HEIGHT - STAT_HEIGHT)
787 rb->lcd_putsxy(STAT_X + 4, STAT_Y + 4, "Level");
788 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.index + 1);
789 rb->lcd_putsxy(STAT_X + 7, STAT_Y + 14, buf);
790 rb->lcd_putsxy(STAT_X + 41, STAT_Y + 4, "Moves");
791 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.moves);
792 rb->lcd_putsxy(STAT_X + 44, STAT_Y + 14, buf);
793 rb->lcd_putsxy(STAT_X + 79, STAT_Y + 4, "Pushes");
794 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.pushes);
795 rb->lcd_putsxy(STAT_X + 82, STAT_Y + 14, buf);
797 rb->lcd_drawrect(STAT_X, STAT_Y, 38, STAT_HEIGHT);
798 rb->lcd_drawrect(STAT_X + 37, STAT_Y, 39, STAT_HEIGHT);
799 rb->lcd_drawrect(STAT_X + 75, STAT_Y, 45, STAT_HEIGHT);
800 #else
801 #if LCD_WIDTH - (COLS*MAGNIFY) > 40
802 #define STAT_X (LCD_WIDTH - 40)
803 #else
804 #define STAT_X COLS*MAGNIFY
805 #endif
806 #if LCD_HEIGHT >= 70
807 #define STAT_Y (LCD_HEIGHT - 70)/2
808 #else
809 #define STAT_Y (LCD_HEIGHT - 47)/2
810 #endif
811 #define STAT_WIDTH (LCD_WIDTH - STAT_X)
812 #define BOARD_WIDTH (LCD_WIDTH - STAT_WIDTH)
813 #define BOARD_HEIGHT LCD_HEIGHT
814 rb->lcd_putsxy(STAT_X + 1, STAT_Y + 3, "Level");
815 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.index + 1);
816 rb->lcd_putsxy(STAT_X + 4, STAT_Y + 13, buf);
817 rb->lcd_putsxy(STAT_X + 1, STAT_Y + 26, "Moves");
818 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.moves);
819 rb->lcd_putsxy(STAT_X + 4, STAT_Y + 36, buf);
821 rb->lcd_drawrect(STAT_X, STAT_Y + 0, STAT_WIDTH, 24);
822 rb->lcd_drawrect(STAT_X, STAT_Y + 23, STAT_WIDTH, 24);
824 #if LCD_HEIGHT >= 70
825 rb->lcd_putsxy(STAT_X + 1, STAT_Y + 49, "Pushes");
826 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.pushes);
827 rb->lcd_putsxy(STAT_X + 4, STAT_Y + 59, buf);
829 rb->lcd_drawrect(STAT_X, STAT_Y + 46, STAT_WIDTH, 24);
830 #endif
832 #endif
834 /* load the board to the screen */
835 for (rows = 0; rows < ROWS; rows++) {
836 for (cols = 0; cols < COLS; cols++) {
837 c = cols*MAGNIFY +
838 (BOARD_WIDTH - current_info.level.width*MAGNIFY)/2;
839 r = rows*MAGNIFY +
840 (BOARD_HEIGHT - current_info.level.height*MAGNIFY)/2;
842 switch(current_info.board[rows][cols]) {
843 case 'X': /* blank space outside of level */
844 break;
846 #if LCD_DEPTH >= 2 && ((LCD_HEIGHT >= 96 && LCD_WIDTH >= 152) || \
847 (LCD_HEIGHT >= 121 && LCD_WIDTH >= 120))
848 case ' ': /* floor */
849 rb->lcd_bitmap_part(sokoban_tiles, 0, 0*MAGNIFY, MAGNIFY,
850 c, r, MAGNIFY, MAGNIFY);
851 break;
853 case '#': /* wall */
854 rb->lcd_bitmap_part(sokoban_tiles, 0, 1*MAGNIFY, MAGNIFY,
855 c, r, MAGNIFY, MAGNIFY);
856 break;
858 case '$': /* box */
859 rb->lcd_bitmap_part(sokoban_tiles, 0, 2*MAGNIFY, MAGNIFY,
860 c, r, MAGNIFY, MAGNIFY);
861 break;
863 case '*': /* box on goal */
864 rb->lcd_bitmap_part(sokoban_tiles, 0, 3*MAGNIFY, MAGNIFY,
865 c, r, MAGNIFY, MAGNIFY);
866 break;
868 case '.': /* goal */
869 rb->lcd_bitmap_part(sokoban_tiles, 0, 4*MAGNIFY, MAGNIFY,
870 c, r, MAGNIFY, MAGNIFY);
871 break;
873 case '@': /* player */
874 rb->lcd_bitmap_part(sokoban_tiles, 0, 5*MAGNIFY, MAGNIFY,
875 c, r, MAGNIFY, MAGNIFY);
876 break;
878 case '+': /* player on goal */
879 rb->lcd_bitmap_part(sokoban_tiles, 0, 6*MAGNIFY, MAGNIFY,
880 c, r, MAGNIFY, MAGNIFY);
881 break;
882 #else
883 case '#': /* wall */
884 for (i = c; i < c + MAGNIFY; i++)
885 for (j = r; j < r + MAGNIFY; j++)
886 if ((i ^ j) & 1)
887 rb->lcd_drawpixel(i, j);
888 break;
890 case '$': /* box */
891 rb->lcd_drawrect(c, r, MAGNIFY, MAGNIFY);
892 break;
894 case '*': /* box on goal */
895 rb->lcd_drawrect(c, r, MAGNIFY, MAGNIFY);
896 rb->lcd_drawrect(c + MAGNIFY/2 - 1, r + MAGNIFY/2 - 1,
897 MAGNIFY/2, MAGNIFY/2);
898 break;
900 case '.': /* goal */
901 rb->lcd_drawrect(c + MAGNIFY/2 - 1, r + MAGNIFY/2 - 1,
902 MAGNIFY/2, MAGNIFY/2);
903 break;
905 case '@': /* player */
906 case '+': /* player on goal */
907 rb->lcd_drawline(c, r + middle, c + max, r + middle);
908 rb->lcd_drawline(c + middle, r, c + middle,
909 r + max - ldelta);
910 rb->lcd_drawline(c + max - middle, r, c + max - middle,
911 r + max - ldelta);
912 rb->lcd_drawline(c + middle, r + max - ldelta,
913 c + middle - ldelta, r + max);
914 rb->lcd_drawline(c + max - middle, r + max - ldelta,
915 c + max - middle + ldelta, r + max);
916 break;
917 #endif
922 /* print out the screen */
923 rb->lcd_update();
926 static void draw_level(void)
928 load_level();
929 rb->lcd_clear_display();
930 update_screen();
933 static bool save(char *filename, bool solution)
935 int fd;
936 char *loc;
937 DIR *dir;
938 char dirname[MAX_PATH];
940 rb->splash(0, "Saving...");
942 /* Create dir if it doesn't exist */
943 if ((loc = rb->strrchr(filename, '/')) != NULL) {
944 rb->strncpy(dirname, filename, loc - filename);
945 dirname[loc - filename] = '\0';
946 if(!(dir = rb->opendir(dirname)))
947 rb->mkdir(dirname);
948 else
949 rb->closedir(dir);
952 if (filename[0] == '\0' ||
953 (fd = rb->open(filename, O_WRONLY|O_CREAT|O_TRUNC)) < 0) {
954 rb->splash(HZ*2, "Unable to open %s", filename);
955 return false;
958 /* Sokoban: S/P for solution/progress : level number : current undo */
959 rb->snprintf(buf, sizeof(buf), "Sokoban:%c:%d:%d\n", (solution ? 'S' : 'P'),
960 current_info.level.index + 1, undo_info.current);
961 rb->write(fd, buf, rb->strlen(buf));
963 /* Filename of levelset */
964 rb->write(fd, buffered_boards.filename,
965 rb->strlen(buffered_boards.filename));
966 rb->write(fd, "\n", 1);
968 /* Full undo history */
969 rb->write(fd, undo_info.history, undo_info.max);
971 rb->close(fd);
973 return true;
976 static bool load(char *filename, bool silent)
978 int fd;
979 int i, n;
980 int len;
981 int button;
982 bool play_solution;
983 bool paused = false;
984 unsigned short speed = 2;
985 int delay[] = {HZ/2, HZ/3, HZ/4, HZ/6, HZ/8, HZ/12, HZ/16, HZ/25};
987 if (filename[0] == '\0' || (fd = rb->open(filename, O_RDONLY)) < 0) {
988 if (!silent)
989 rb->splash(HZ*2, "Unable to open %s", filename);
990 return false;
993 /* Read header, level number, & current undo */
994 rb->read_line(fd, buf, sizeof(buf));
996 /* If we're opening a level file, not a solution/progress file */
997 if (rb->strncmp(buf, "Sokoban", 7) != 0) {
998 rb->close(fd);
1000 rb->strncpy(buffered_boards.filename, filename, MAX_PATH);
1001 if (!read_levels(true))
1002 return false;
1004 current_info.level.index = 0;
1005 load_level();
1007 /* If there aren't any boxes to go or the player position wasn't set,
1008 * the file probably wasn't a Sokoban level file */
1009 if (current_info.level.boxes_to_go == 0 ||
1010 current_info.player.row == 0 || current_info.player.col == 0) {
1011 if (!silent)
1012 rb->splash(HZ*2, "File is not a Sokoban level file");
1013 return false;
1016 } else {
1018 /* Read filename of levelset */
1019 rb->read_line(fd, buffered_boards.filename,
1020 sizeof(buffered_boards.filename));
1022 /* Read full undo history */
1023 len = rb->read_line(fd, undo_info.history, MAX_UNDOS);
1025 /* Correct len when trailing \r's or \n's are counted */
1026 if (len > 2 && undo_info.history[len - 2] == '\0')
1027 len -= 2;
1028 else if (len > 1 && undo_info.history[len - 1] == '\0')
1029 len--;
1031 rb->close(fd);
1033 /* Check to see if we're going to play a solution or resume progress */
1034 play_solution = (buf[8] == 'S');
1036 /* Get level number */
1037 for (n = 0, i = 10; buf[i] >= '0' && buf[i] <= '9' && i < 15; i++)
1038 n = n*10 + buf[i] - '0';
1039 current_info.level.index = n - 1;
1041 /* Get undo index */
1042 for (n = 0, i++; buf[i] >= '0' && buf[i] <= '9' && i < 21; i++)
1043 n = n*10 + buf[i] - '0';
1044 if (n > len)
1045 n = len;
1046 undo_info.max = len;
1048 if (current_info.level.index < 0) {
1049 if (!silent)
1050 rb->splash(HZ*2, "Error loading level");
1051 return false;
1053 if (!read_levels(true))
1054 return false;
1055 if (current_info.level.index >= current_info.max_level) {
1056 if (!silent)
1057 rb->splash(HZ*2, "Error loading level");
1058 return false;
1061 load_level();
1063 if (play_solution) {
1064 rb->lcd_clear_display();
1065 update_screen();
1066 rb->sleep(2*delay[speed]);
1068 /* Replay solution until menu button is pressed */
1069 i = 0;
1070 while (true) {
1071 if (i < len) {
1072 if (!move(undo_info.history[i], true)) {
1073 n = i;
1074 break;
1076 rb->lcd_clear_display();
1077 update_screen();
1078 i++;
1079 } else
1080 paused = true;
1082 rb->sleep(delay[speed]);
1084 while ((button = rb->button_get(false)) || paused) {
1085 switch (button) {
1086 case SOKOBAN_MENU:
1087 /* Pretend the level is complete so we'll quit */
1088 current_info.level.boxes_to_go = 0;
1089 return true;
1091 case SOKOBAN_PAUSE:
1092 /* Toggle pause state */
1093 paused = !paused;
1094 break;
1096 case SOKOBAN_LEFT:
1097 case SOKOBAN_LEFT | BUTTON_REPEAT:
1098 /* Go back one move */
1099 if (paused) {
1100 if (undo())
1101 i--;
1102 rb->lcd_clear_display();
1103 update_screen();
1105 break;
1107 case SOKOBAN_RIGHT:
1108 case SOKOBAN_RIGHT | BUTTON_REPEAT:
1109 /* Go forward one move */
1110 if (paused) {
1111 if (redo())
1112 i++;
1113 rb->lcd_clear_display();
1114 update_screen();
1116 break;
1118 case SOKOBAN_UP:
1119 case SOKOBAN_UP | BUTTON_REPEAT:
1120 /* Speed up */
1121 if (speed < sizeof(delay)/sizeof(int) - 1)
1122 speed++;
1123 break;
1125 case SOKOBAN_DOWN:
1126 case SOKOBAN_DOWN | BUTTON_REPEAT:
1127 /* Slow down */
1128 if (speed > 0)
1129 speed--;
1132 if (paused)
1133 rb->sleep(HZ/33);
1137 /* If level is complete, wait for keypress before quitting */
1138 if (current_info.level.boxes_to_go == 0)
1139 rb->button_get(true);
1141 } else {
1142 /* Advance to current undo */
1143 for (i = 0; i < n; i++) {
1144 if (!move(undo_info.history[i], true)) {
1145 n = i;
1146 break;
1150 rb->button_clear_queue();
1151 rb->lcd_clear_display();
1154 undo_info.current = n;
1157 return true;
1160 static int sokoban_menu(void)
1162 int button;
1163 int selection = 0;
1164 int i;
1165 bool menu_quit;
1166 int start_selected = 0;
1167 int prev_level = current_info.level.index;
1169 MENUITEM_STRINGLIST(menu, "Sokoban Menu", NULL,
1170 "Resume", "Select Level", "Audio Playback", "Keys",
1171 "Load Default Level Set", "Quit Without Saving",
1172 "Save Progress & Quit");
1174 do {
1175 menu_quit = true;
1176 selection = rb->do_menu(&menu, &start_selected, NULL, false);
1178 switch (selection) {
1179 case 0: /* Resume */
1180 break;
1182 case 1: /* Select level */
1183 current_info.level.index++;
1184 rb->set_int("Select Level", "", UNIT_INT,
1185 &current_info.level.index, NULL, 1, 1,
1186 current_info.max_level, NULL);
1187 current_info.level.index--;
1188 if (prev_level != current_info.level.index) {
1189 init_undo();
1190 draw_level();
1191 } else
1192 menu_quit = false;
1193 break;
1195 case 2: /* Audio playback control */
1196 playback_control(rb, NULL);
1197 menu_quit = false;
1198 break;
1200 case 3: /* Keys */
1201 FOR_NB_SCREENS(i)
1202 rb->screens[i]->clear_display();
1203 rb->lcd_setfont(SOKOBAN_FONT);
1205 #if (CONFIG_KEYPAD == RECORDER_PAD) || \
1206 (CONFIG_KEYPAD == ARCHOS_AV300_PAD)
1207 rb->lcd_putsxy(3, 6, "[OFF] Menu");
1208 rb->lcd_putsxy(3, 16, "[ON] Undo");
1209 rb->lcd_putsxy(3, 26, "[PLAY] Redo");
1210 rb->lcd_putsxy(3, 36, "[F1] Down a Level");
1211 rb->lcd_putsxy(3, 46, "[F2] Restart Level");
1212 rb->lcd_putsxy(3, 56, "[F3] Up a Level");
1213 #elif CONFIG_KEYPAD == ONDIO_PAD
1214 rb->lcd_putsxy(3, 6, "[OFF] Menu");
1215 rb->lcd_putsxy(3, 16, "[MODE] Undo");
1216 rb->lcd_putsxy(3, 26, "[MODE+DOWN] Redo");
1217 rb->lcd_putsxy(3, 36, "[MODE+LEFT] Previous Level");
1218 rb->lcd_putsxy(3, 46, "[MODE+UP] Restart Level");
1219 rb->lcd_putsxy(3, 56, "[MODE+RIGHT] Up Level");
1220 #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
1221 (CONFIG_KEYPAD == IRIVER_H300_PAD)
1222 rb->lcd_putsxy(3, 6, "[STOP] Menu");
1223 rb->lcd_putsxy(3, 16, "[REC] Undo");
1224 rb->lcd_putsxy(3, 26, "[MODE] Redo");
1225 rb->lcd_putsxy(3, 36, "[PLAY+DOWN] Previous Level");
1226 rb->lcd_putsxy(3, 46, "[PLAY] Restart Level");
1227 rb->lcd_putsxy(3, 56, "[PLAY+UP] Next Level");
1228 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
1229 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
1230 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
1231 rb->lcd_putsxy(3, 6, "[SELECT+MENU] Menu");
1232 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
1233 rb->lcd_putsxy(3, 26, "[SELECT+PLAY] Redo");
1234 rb->lcd_putsxy(3, 36, "[SELECT+LEFT] Previous Level");
1235 rb->lcd_putsxy(3, 46, "[SELECT+RIGHT] Next Level");
1236 #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
1237 rb->lcd_putsxy(3, 6, "[POWER] Menu");
1238 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
1239 rb->lcd_putsxy(3, 26, "[PLAY] Redo");
1240 rb->lcd_putsxy(3, 36, "[REC] Restart Level");
1241 #elif CONFIG_KEYPAD == IRIVER_H10_PAD
1242 rb->lcd_putsxy(3, 6, "[POWER] Menu");
1243 rb->lcd_putsxy(3, 16, "[REW] Undo");
1244 rb->lcd_putsxy(3, 26, "[FF] Redo");
1245 rb->lcd_putsxy(3, 36, "[PLAY+DOWN] Previous Level");
1246 rb->lcd_putsxy(3, 46, "[PLAY+RIGHT] Restart Level");
1247 rb->lcd_putsxy(3, 56, "[PLAY+UP] Next Level");
1248 #elif CONFIG_KEYPAD == GIGABEAT_PAD
1249 rb->lcd_putsxy(3, 6, "[POWER] Menu");
1250 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
1251 rb->lcd_putsxy(3, 26, "[A] Redo");
1252 rb->lcd_putsxy(3, 36, "[VOL-] Previous Level");
1253 rb->lcd_putsxy(3, 46, "[MENU] Restart Level");
1254 rb->lcd_putsxy(3, 56, "[VOL+] Next Level");
1255 #elif CONFIG_KEYPAD == SANSA_E200_PAD
1256 rb->lcd_putsxy(3, 6, "[POWER] Menu");
1257 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
1258 rb->lcd_putsxy(3, 26, "[REC] Redo");
1259 rb->lcd_putsxy(3, 36, "[SELECT+DOWN] Previous Level");
1260 rb->lcd_putsxy(3, 46, "[SELECT+RIGHT] Restart Level");
1261 rb->lcd_putsxy(3, 56, "[SELECT+UP] Next Level");
1262 #endif
1264 FOR_NB_SCREENS(i)
1265 rb->screens[i]->update();
1267 /* Display until keypress */
1268 do {
1269 rb->sleep(HZ/20);
1270 button = rb->button_get(false);
1271 } while (!button || button & BUTTON_REL ||
1272 button & BUTTON_REPEAT);
1274 menu_quit = false;
1275 break;
1277 case 4: /* Load default levelset */
1278 init_boards();
1279 if (!read_levels(true))
1280 return 5; /* Quit */
1281 load_level();
1282 break;
1284 case 5: /* Quit */
1285 break;
1287 case 6: /* Save & quit */
1288 save(SOKOBAN_SAVE_FILE, false);
1289 rb->reload_directory();
1292 } while (!menu_quit);
1294 /* Restore font */
1295 rb->lcd_setfont(SOKOBAN_FONT);
1297 FOR_NB_SCREENS(i) {
1298 rb->screens[i]->clear_display();
1299 rb->screens[i]->update();
1302 return selection;
1305 static bool sokoban_loop(void)
1307 bool moved;
1308 int i = 0, button = 0, lastbutton = 0;
1309 short r = 0, c = 0;
1310 int w, h;
1311 char *loc;
1313 while (true) {
1314 moved = false;
1316 r = current_info.player.row;
1317 c = current_info.player.col;
1319 button = rb->button_get(true);
1321 switch(button)
1323 #ifdef SOKOBAN_RC_MENU
1324 case SOKOBAN_RC_MENU:
1325 #endif
1326 case SOKOBAN_MENU:
1327 switch (sokoban_menu()) {
1328 case 5: /* Quit */
1329 case 6: /* Save & quit */
1330 return PLUGIN_OK;
1332 update_screen();
1333 break;
1335 case SOKOBAN_UNDO:
1336 #ifdef SOKOBAN_UNDO_PRE
1337 if (lastbutton != SOKOBAN_UNDO_PRE)
1338 break;
1339 #else /* repeat can't work here for Ondio, iPod, et al */
1340 case SOKOBAN_UNDO | BUTTON_REPEAT:
1341 #endif
1342 undo();
1343 rb->lcd_clear_display();
1344 update_screen();
1345 break;
1347 #ifdef SOKOBAN_REDO
1348 case SOKOBAN_REDO:
1349 case SOKOBAN_REDO | BUTTON_REPEAT:
1350 moved = redo();
1351 rb->lcd_clear_display();
1352 update_screen();
1353 break;
1354 #endif
1356 #ifdef SOKOBAN_LEVEL_UP
1357 case SOKOBAN_LEVEL_UP:
1358 case SOKOBAN_LEVEL_UP | BUTTON_REPEAT:
1359 /* next level */
1360 init_undo();
1361 if (current_info.level.index + 1 < current_info.max_level)
1362 current_info.level.index++;
1364 draw_level();
1365 break;
1366 #endif
1368 #ifdef SOKOBAN_LEVEL_DOWN
1369 case SOKOBAN_LEVEL_DOWN:
1370 case SOKOBAN_LEVEL_DOWN | BUTTON_REPEAT:
1371 /* previous level */
1372 init_undo();
1373 if (current_info.level.index > 0)
1374 current_info.level.index--;
1376 draw_level();
1377 break;
1378 #endif
1380 #ifdef SOKOBAN_LEVEL_REPEAT
1381 case SOKOBAN_LEVEL_REPEAT:
1382 case SOKOBAN_LEVEL_REPEAT | BUTTON_REPEAT:
1383 /* same level */
1384 init_undo();
1385 draw_level();
1386 break;
1387 #endif
1389 case SOKOBAN_LEFT:
1390 case SOKOBAN_LEFT | BUTTON_REPEAT:
1391 moved = move(SOKOBAN_MOVE_LEFT, false);
1392 break;
1394 case SOKOBAN_RIGHT:
1395 case SOKOBAN_RIGHT | BUTTON_REPEAT:
1396 moved = move(SOKOBAN_MOVE_RIGHT, false);
1397 break;
1399 case SOKOBAN_UP:
1400 case SOKOBAN_UP | BUTTON_REPEAT:
1401 moved = move(SOKOBAN_MOVE_UP, false);
1402 break;
1404 case SOKOBAN_DOWN:
1405 case SOKOBAN_DOWN | BUTTON_REPEAT:
1406 moved = move(SOKOBAN_MOVE_DOWN, false);
1407 break;
1409 default:
1410 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
1411 return PLUGIN_USB_CONNECTED;
1412 break;
1415 lastbutton = button;
1417 if (moved) {
1418 rb->lcd_clear_display();
1419 update_screen();
1422 /* We have completed this level */
1423 if (current_info.level.boxes_to_go == 0) {
1425 if (moved) {
1426 rb->lcd_clear_display();
1428 /* Show level complete message & stats */
1429 rb->snprintf(buf, sizeof(buf), "Level %d Complete!",
1430 current_info.level.index + 1);
1431 rb->lcd_getstringsize(buf, &w, &h);
1432 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h*3, buf);
1434 rb->snprintf(buf, sizeof(buf), "%4d Moves ",
1435 current_info.level.moves);
1436 rb->lcd_getstringsize(buf, &w, &h);
1437 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h, buf);
1439 rb->snprintf(buf, sizeof(buf), "%4d Pushes",
1440 current_info.level.pushes);
1441 rb->lcd_getstringsize(buf, &w, &h);
1442 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2, buf);
1444 if (undo_info.count < MAX_UNDOS) {
1445 rb->snprintf(buf, sizeof(buf), "%s: Save solution",
1446 BUTTON_SAVE_NAME);
1447 rb->lcd_getstringsize(buf, &w, &h);
1448 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 + h*2, buf);
1451 rb->lcd_update();
1452 rb->sleep(HZ/4);
1453 rb->button_clear_queue();
1455 /* Display for 4 seconds or until new keypress */
1456 for (i = 0; i < 80; i++) {
1457 rb->sleep(HZ/20);
1458 button = rb->button_get(false);
1459 if (button && !(button & BUTTON_REL) &&
1460 !(button & BUTTON_REPEAT))
1461 break;
1464 if (button == BUTTON_SAVE) {
1465 if (undo_info.count < MAX_UNDOS) {
1466 /* Set filename to current levelset plus level number
1467 * and .sok extension. Use SAVE_FOLDER if using the
1468 * default levelset, since it's in a hidden folder. */
1469 if (rb->strcmp(buffered_boards.filename,
1470 SOKOBAN_LEVELS_FILE) == 0) {
1471 rb->snprintf(buf, sizeof(buf),
1472 "%s/sokoban.%d.sok",
1473 SOKOBAN_SAVE_FOLDER,
1474 current_info.level.index + 1);
1475 } else {
1476 if ((loc = rb->strrchr(buffered_boards.filename,
1477 '.')) != NULL)
1478 *loc = '\0';
1479 rb->snprintf(buf, sizeof(buf), "%s.%d.sok",
1480 buffered_boards.filename,
1481 current_info.level.index + 1);
1482 if (loc != NULL)
1483 *loc = '.';
1486 if (!rb->kbd_input(buf, MAX_PATH))
1487 save(buf, true);
1488 } else
1489 rb->splash(HZ*2, "Solution too long to save");
1491 rb->lcd_setfont(SOKOBAN_FONT); /* Restore font */
1495 FOR_NB_SCREENS(i) {
1496 rb->screens[i]->clear_display();
1497 rb->screens[i]->update();
1500 current_info.level.index++;
1502 /* clear undo stats */
1503 init_undo();
1505 if (current_info.level.index >= current_info.max_level) {
1506 /* Show levelset complete message */
1507 rb->snprintf(buf, sizeof(buf), "You WIN!!");
1508 rb->lcd_getstringsize(buf, &w, &h);
1509 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h/2, buf);
1511 rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
1512 /* Display for 4 seconds or until keypress */
1513 for (i = 0; i < 80; i++) {
1514 rb->lcd_fillrect(0, 0, LCD_WIDTH, LCD_HEIGHT);
1515 rb->lcd_update();
1516 rb->sleep(HZ/10);
1518 button = rb->button_get(false);
1519 if (button && !(button & BUTTON_REL))
1520 break;
1522 rb->lcd_set_drawmode(DRMODE_SOLID);
1524 /* Reset to first level & show quit menu */
1525 current_info.level.index = 0;
1527 switch (sokoban_menu()) {
1528 case 5: /* Quit */
1529 case 6: /* Save & quit */
1530 return PLUGIN_OK;
1534 load_level();
1535 update_screen();
1538 } /* end while */
1540 return PLUGIN_OK;
1544 enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
1546 int w, h;
1548 (void)(parameter);
1549 rb = api;
1551 rb->lcd_setfont(SOKOBAN_FONT);
1553 rb->lcd_clear_display();
1554 rb->lcd_getstringsize(SOKOBAN_TITLE, &w, &h);
1555 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h/2, SOKOBAN_TITLE);
1556 rb->lcd_update();
1557 rb->sleep(HZ); /* Show title for 1 second */
1559 init_boards();
1561 if (parameter == NULL) {
1562 /* Attempt to resume saved progress, otherwise start at beginning */
1563 if (!load(SOKOBAN_SAVE_FILE, true)) {
1564 init_boards();
1565 if (!read_levels(true))
1566 return PLUGIN_OK;
1567 load_level();
1570 } else {
1571 /* The plugin is being used to open a file */
1572 if (load((char*) parameter, false)) {
1573 /* If we loaded & played a solution, quit */
1574 if (current_info.level.boxes_to_go == 0)
1575 return PLUGIN_OK;
1576 } else
1577 return PLUGIN_OK;
1580 rb->lcd_clear_display();
1581 update_screen();
1583 return sokoban_loop();
1586 #endif