New makefile solution: A single invocation of 'make' to build the entire tree. Fully...
[kugel-rb.git] / apps / plugins / sokoban.c
blob0a032f6bd1c02de0df725dbf08d7955d6465f87b
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 * This program is free software; you can redistribute it and/or
16 * modify it under the terms of the GNU General Public License
17 * as published by the Free Software Foundation; either version 2
18 * of the License, or (at your option) any later version.
20 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
21 * KIND, either express or implied.
23 ****************************************************************************/
24 #include "plugin.h"
25 #include "lib/playback_control.h"
27 PLUGIN_HEADER
29 #define SOKOBAN_TITLE "Sokoban"
31 #define SOKOBAN_LEVELS_FILE PLUGIN_GAMES_DIR "/sokoban.levels"
32 #define SOKOBAN_SAVE_FILE PLUGIN_GAMES_DIR "/sokoban.save"
33 #define SOKOBAN_SAVE_FOLDER "/games"
35 #include "pluginbitmaps/sokoban_tiles.h"
36 #define SOKOBAN_TILESIZE BMPWIDTH_sokoban_tiles
38 /* If tilesize is 0 (which it is during dependency generation) gcc will abort
39 (div by 0) and this plugin won't get any dependencies
41 #if SOKOBAN_TILESIZE < 1
42 #define SOKOBAN_TILESIZE 10
43 #endif
45 /* SOKOBAN_TILESIZE is the number of pixels for each block.
46 * Set dynamically so all targets can support levels
47 * that fill their entire screen, less the stat box.
48 * 16 rows & 20 cols minimum */
49 #if LCD_WIDTH > LCD_HEIGHT /* horizontal layout*/
50 #define ROWS (LCD_HEIGHT/SOKOBAN_TILESIZE)
51 #if (LCD_WIDTH+4) >= (20*SOKOBAN_TILESIZE+40) /* wide or narrow stats box */
52 #define COLS ((LCD_WIDTH-40)/SOKOBAN_TILESIZE)
53 #else
54 #define COLS ((LCD_WIDTH-32)/SOKOBAN_TILESIZE)
55 #endif
56 #else /* vertical layout*/
57 #define ROWS ((LCD_HEIGHT-25)/SOKOBAN_TILESIZE)
58 #define COLS (LCD_WIDTH/SOKOBAN_TILESIZE)
59 #endif
61 /* Use either all but 16k of the plugin buffer for level data
62 * or 128k, which ever is less */
63 #if PLUGIN_BUFFER_SIZE - 0x4000 < 0x20000
64 #define MAX_LEVEL_DATA (PLUGIN_BUFFER_SIZE - 0x4000)
65 #else
66 #define MAX_LEVEL_DATA 0x20000
67 #endif
69 /* Number of levels for which to allocate buffer indexes */
70 #define MAX_LEVELS MAX_LEVEL_DATA/70
72 /* Use 4k plus remaining plugin buffer (-12k for prog) for undo, up to 64k */
73 #if PLUGIN_BUFFER_SIZE - MAX_LEVEL_DATA - 0x3000 > 0x10000
74 #define MAX_UNDOS 0x10000
75 #else
76 #define MAX_UNDOS (PLUGIN_BUFFER_SIZE - MAX_LEVEL_DATA - 0x3000)
77 #endif
79 /* Move/push definitions for undo */
80 #define SOKOBAN_PUSH_LEFT 'L'
81 #define SOKOBAN_PUSH_RIGHT 'R'
82 #define SOKOBAN_PUSH_UP 'U'
83 #define SOKOBAN_PUSH_DOWN 'D'
84 #define SOKOBAN_MOVE_LEFT 'l'
85 #define SOKOBAN_MOVE_RIGHT 'r'
86 #define SOKOBAN_MOVE_UP 'u'
87 #define SOKOBAN_MOVE_DOWN 'd'
89 #define SOKOBAN_MOVE_DIFF (SOKOBAN_MOVE_LEFT-SOKOBAN_PUSH_LEFT)
90 #define SOKOBAN_MOVE_MIN SOKOBAN_MOVE_DOWN
92 /* variable button definitions */
93 #if (CONFIG_KEYPAD == RECORDER_PAD) || \
94 (CONFIG_KEYPAD == ARCHOS_AV300_PAD)
95 #define SOKOBAN_LEFT BUTTON_LEFT
96 #define SOKOBAN_RIGHT BUTTON_RIGHT
97 #define SOKOBAN_UP BUTTON_UP
98 #define SOKOBAN_DOWN BUTTON_DOWN
99 #define SOKOBAN_MENU BUTTON_OFF
100 #define SOKOBAN_UNDO BUTTON_ON
101 #define SOKOBAN_REDO BUTTON_PLAY
102 #define SOKOBAN_LEVEL_DOWN BUTTON_F1
103 #define SOKOBAN_LEVEL_REPEAT BUTTON_F2
104 #define SOKOBAN_LEVEL_UP BUTTON_F3
105 #define SOKOBAN_PAUSE BUTTON_PLAY
106 #define BUTTON_SAVE BUTTON_ON
107 #define BUTTON_SAVE_NAME "ON"
109 #elif CONFIG_KEYPAD == ONDIO_PAD
110 #define SOKOBAN_LEFT BUTTON_LEFT
111 #define SOKOBAN_RIGHT BUTTON_RIGHT
112 #define SOKOBAN_UP BUTTON_UP
113 #define SOKOBAN_DOWN BUTTON_DOWN
114 #define SOKOBAN_MENU BUTTON_OFF
115 #define SOKOBAN_UNDO_PRE BUTTON_MENU
116 #define SOKOBAN_UNDO (BUTTON_MENU | BUTTON_REL)
117 #define SOKOBAN_REDO (BUTTON_MENU | BUTTON_DOWN)
118 #define SOKOBAN_LEVEL_DOWN (BUTTON_MENU | BUTTON_LEFT)
119 #define SOKOBAN_LEVEL_REPEAT (BUTTON_MENU | BUTTON_UP)
120 #define SOKOBAN_LEVEL_UP (BUTTON_MENU | BUTTON_RIGHT)
121 #define SOKOBAN_PAUSE BUTTON_MENU
122 #define BUTTON_SAVE BUTTON_MENU
123 #define BUTTON_SAVE_NAME "MENU"
125 #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
126 (CONFIG_KEYPAD == IRIVER_H300_PAD)
127 #define SOKOBAN_LEFT BUTTON_LEFT
128 #define SOKOBAN_RIGHT BUTTON_RIGHT
129 #define SOKOBAN_UP BUTTON_UP
130 #define SOKOBAN_DOWN BUTTON_DOWN
131 #define SOKOBAN_MENU BUTTON_OFF
132 #define SOKOBAN_UNDO BUTTON_REC
133 #define SOKOBAN_REDO BUTTON_MODE
134 #define SOKOBAN_LEVEL_DOWN (BUTTON_ON | BUTTON_DOWN)
135 #define SOKOBAN_LEVEL_REPEAT BUTTON_ON
136 #define SOKOBAN_LEVEL_UP (BUTTON_ON | BUTTON_UP)
137 #define SOKOBAN_PAUSE BUTTON_ON
138 #define BUTTON_SAVE BUTTON_MODE
139 #define BUTTON_SAVE_NAME "MODE"
141 #define SOKOBAN_RC_MENU BUTTON_RC_STOP
143 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
144 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
145 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
146 #define SOKOBAN_LEFT BUTTON_LEFT
147 #define SOKOBAN_RIGHT BUTTON_RIGHT
148 #define SOKOBAN_UP BUTTON_MENU
149 #define SOKOBAN_DOWN BUTTON_PLAY
150 #define SOKOBAN_MENU (BUTTON_SELECT | BUTTON_MENU)
151 #define SOKOBAN_UNDO_PRE BUTTON_SELECT
152 #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
153 #define SOKOBAN_REDO (BUTTON_SELECT | BUTTON_PLAY)
154 #define SOKOBAN_LEVEL_DOWN (BUTTON_SELECT | BUTTON_LEFT)
155 #define SOKOBAN_LEVEL_UP (BUTTON_SELECT | BUTTON_RIGHT)
156 #define SOKOBAN_PAUSE BUTTON_SELECT
157 #define BUTTON_SAVE BUTTON_SELECT
158 #define BUTTON_SAVE_NAME "SELECT"
160 /* FIXME: if/when simultaneous button presses work for X5/M5,
161 * add level up/down */
162 #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
163 #define SOKOBAN_LEFT BUTTON_LEFT
164 #define SOKOBAN_RIGHT BUTTON_RIGHT
165 #define SOKOBAN_UP BUTTON_UP
166 #define SOKOBAN_DOWN BUTTON_DOWN
167 #define SOKOBAN_MENU BUTTON_POWER
168 #define SOKOBAN_UNDO_PRE BUTTON_SELECT
169 #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
170 #define SOKOBAN_LEVEL_REPEAT BUTTON_REC
171 #define SOKOBAN_REDO BUTTON_PLAY
172 #define SOKOBAN_PAUSE BUTTON_PLAY
173 #define BUTTON_SAVE BUTTON_SELECT
174 #define BUTTON_SAVE_NAME "SELECT"
176 #elif CONFIG_KEYPAD == IRIVER_H10_PAD
177 #define SOKOBAN_LEFT BUTTON_LEFT
178 #define SOKOBAN_RIGHT BUTTON_RIGHT
179 #define SOKOBAN_UP BUTTON_SCROLL_UP
180 #define SOKOBAN_DOWN BUTTON_SCROLL_DOWN
181 #define SOKOBAN_MENU BUTTON_POWER
182 #define SOKOBAN_UNDO_PRE BUTTON_REW
183 #define SOKOBAN_UNDO (BUTTON_REW | BUTTON_REL)
184 #define SOKOBAN_REDO BUTTON_FF
185 #define SOKOBAN_LEVEL_DOWN (BUTTON_PLAY | BUTTON_SCROLL_DOWN)
186 #define SOKOBAN_LEVEL_REPEAT (BUTTON_PLAY | BUTTON_RIGHT)
187 #define SOKOBAN_LEVEL_UP (BUTTON_PLAY | BUTTON_SCROLL_UP)
188 #define SOKOBAN_PAUSE BUTTON_PLAY
189 #define BUTTON_SAVE BUTTON_PLAY
190 #define BUTTON_SAVE_NAME "PLAY"
192 #elif CONFIG_KEYPAD == GIGABEAT_PAD
193 #define SOKOBAN_LEFT BUTTON_LEFT
194 #define SOKOBAN_RIGHT BUTTON_RIGHT
195 #define SOKOBAN_UP BUTTON_UP
196 #define SOKOBAN_DOWN BUTTON_DOWN
197 #define SOKOBAN_MENU BUTTON_POWER
198 #define SOKOBAN_UNDO BUTTON_SELECT
199 #define SOKOBAN_REDO BUTTON_A
200 #define SOKOBAN_LEVEL_DOWN BUTTON_VOL_DOWN
201 #define SOKOBAN_LEVEL_REPEAT BUTTON_MENU
202 #define SOKOBAN_LEVEL_UP BUTTON_VOL_UP
203 #define SOKOBAN_PAUSE BUTTON_SELECT
204 #define BUTTON_SAVE BUTTON_SELECT
205 #define BUTTON_SAVE_NAME "SELECT"
207 #elif CONFIG_KEYPAD == SANSA_E200_PAD
208 #define SOKOBAN_LEFT BUTTON_LEFT
209 #define SOKOBAN_RIGHT BUTTON_RIGHT
210 #define SOKOBAN_UP BUTTON_UP
211 #define SOKOBAN_DOWN BUTTON_DOWN
212 #define SOKOBAN_MENU BUTTON_POWER
213 #define SOKOBAN_UNDO_PRE BUTTON_SELECT
214 #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
215 #define SOKOBAN_REDO BUTTON_REC
216 #define SOKOBAN_LEVEL_DOWN (BUTTON_SELECT | BUTTON_DOWN)
217 #define SOKOBAN_LEVEL_REPEAT (BUTTON_SELECT | BUTTON_RIGHT)
218 #define SOKOBAN_LEVEL_UP (BUTTON_SELECT | BUTTON_UP)
219 #define SOKOBAN_PAUSE BUTTON_SELECT
220 #define BUTTON_SAVE BUTTON_SELECT
221 #define BUTTON_SAVE_NAME "SELECT"
223 #elif CONFIG_KEYPAD == SANSA_C200_PAD
224 #define SOKOBAN_LEFT BUTTON_LEFT
225 #define SOKOBAN_RIGHT BUTTON_RIGHT
226 #define SOKOBAN_UP BUTTON_UP
227 #define SOKOBAN_DOWN BUTTON_DOWN
228 #define SOKOBAN_MENU BUTTON_POWER
229 #define SOKOBAN_UNDO_PRE BUTTON_SELECT
230 #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
231 #define SOKOBAN_REDO BUTTON_REC
232 #define SOKOBAN_LEVEL_DOWN BUTTON_VOL_DOWN
233 #define SOKOBAN_LEVEL_REPEAT (BUTTON_SELECT | BUTTON_RIGHT)
234 #define SOKOBAN_LEVEL_UP BUTTON_VOL_UP
235 #define SOKOBAN_PAUSE BUTTON_SELECT
236 #define BUTTON_SAVE BUTTON_SELECT
237 #define BUTTON_SAVE_NAME "SELECT"
239 #elif CONFIG_KEYPAD == GIGABEAT_S_PAD
240 #define SOKOBAN_LEFT BUTTON_LEFT
241 #define SOKOBAN_RIGHT BUTTON_RIGHT
242 #define SOKOBAN_UP BUTTON_UP
243 #define SOKOBAN_DOWN BUTTON_DOWN
244 #define SOKOBAN_MENU BUTTON_MENU
245 #define SOKOBAN_UNDO BUTTON_VOL_UP
246 #define SOKOBAN_REDO BUTTON_VOL_DOWN
247 #define SOKOBAN_LEVEL_DOWN BUTTON_PREV
248 #define SOKOBAN_LEVEL_REPEAT BUTTON_PLAY
249 #define SOKOBAN_LEVEL_UP BUTTON_NEXT
250 #define SOKOBAN_PAUSE BUTTON_SELECT
251 #define BUTTON_SAVE BUTTON_SELECT
252 #define BUTTON_SAVE_NAME "SELECT"
254 #elif CONFIG_KEYPAD == MROBE100_PAD
255 #define SOKOBAN_LEFT BUTTON_LEFT
256 #define SOKOBAN_RIGHT BUTTON_RIGHT
257 #define SOKOBAN_UP BUTTON_UP
258 #define SOKOBAN_DOWN BUTTON_DOWN
259 #define SOKOBAN_MENU BUTTON_POWER
260 #define SOKOBAN_UNDO BUTTON_SELECT
261 #define SOKOBAN_REDO BUTTON_MENU
262 #define SOKOBAN_LEVEL_DOWN (BUTTON_DISPLAY | BUTTON_DOWN)
263 #define SOKOBAN_LEVEL_REPEAT (BUTTON_DISPLAY | BUTTON_RIGHT)
264 #define SOKOBAN_LEVEL_UP (BUTTON_DISPLAY | BUTTON_UP)
265 #define SOKOBAN_PAUSE BUTTON_SELECT
266 #define BUTTON_SAVE BUTTON_SELECT
267 #define BUTTON_SAVE_NAME "SELECT"
269 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
270 #define SOKOBAN_LEFT BUTTON_RC_REW
271 #define SOKOBAN_RIGHT BUTTON_RC_FF
272 #define SOKOBAN_UP BUTTON_RC_VOL_UP
273 #define SOKOBAN_DOWN BUTTON_RC_VOL_DOWN
274 #define SOKOBAN_MENU BUTTON_RC_REC
275 #define SOKOBAN_UNDO BUTTON_RC_MODE
276 #define SOKOBAN_REDO BUTTON_RC_MENU
277 #define SOKOBAN_PAUSE BUTTON_RC_PLAY
278 #define BUTTON_SAVE BUTTON_RC_PLAY
279 #define BUTTON_SAVE_NAME "PLAY"
281 #define SOKOBAN_RC_MENU BUTTON_REC
283 #elif CONFIG_KEYPAD == COWOND2_PAD
284 #define SOKOBAN_MENU BUTTON_MENU
285 #define SOKOBAN_MENU_NAME "[MENU]"
287 #elif CONFIG_KEYPAD == IAUDIO67_PAD
288 #define SOKOBAN_LEFT BUTTON_LEFT
289 #define SOKOBAN_RIGHT BUTTON_RIGHT
290 #define SOKOBAN_UP BUTTON_STOP
291 #define SOKOBAN_DOWN BUTTON_PLAY
292 #define SOKOBAN_MENU BUTTON_MENU
293 #define SOKOBAN_UNDO BUTTON_VOLDOWN
294 #define SOKOBAN_REDO BUTTON_VOLUP
295 #define SOKOBAN_PAUSE (BUTTON_MENU|BUTTON_LEFT)
296 #define BUTTON_SAVE (BUTTON_MENU|BUTTON_PLAY)
297 #define BUTTON_SAVE_NAME "MENU+PLAY"
299 #define SOKOBAN_RC_MENU (BUTTON_MENU|BUTTON_STOP)
301 #else
302 #error No keymap defined!
303 #endif
305 #ifdef HAVE_TOUCHSCREEN
306 #ifndef SOKOBAN_LEFT
307 #define SOKOBAN_LEFT BUTTON_MIDLEFT
308 #endif
309 #ifndef SOKOBAN_RIGHT
310 #define SOKOBAN_RIGHT BUTTON_MIDRIGHT
311 #endif
312 #ifndef SOKOBAN_UP
313 #define SOKOBAN_UP BUTTON_TOPMIDDLE
314 #endif
315 #ifndef SOKOBAN_DOWN
316 #define SOKOBAN_DOWN BUTTON_BOTTOMMIDDLE
317 #endif
318 #ifndef SOKOBAN_MENU
319 #define SOKOBAN_MENU BUTTON_TOPLEFT
320 #define SOKOBAN_MENU_NAME "[TOPLEFT]"
321 #endif
322 #ifndef SOKOBAN_UNDO
323 #define SOKOBAN_UNDO BUTTON_BOTTOMRIGHT
324 #define SOKOBAN_UNDO_NAME "[BOTTOMRIGHT]"
325 #endif
326 #ifndef SOKOBAN_REDO
327 #define SOKOBAN_REDO BUTTON_BOTTOMLEFT
328 #define SOKOBAN_REDO_NAME "[BOTTOMLEFT]"
329 #endif
330 #ifndef SOKOBAN_PAUSE
331 #define SOKOBAN_PAUSE BUTTON_CENTER
332 #define SOKOBAN_PAUSE_NAME "[CENTER]"
333 #endif
334 #ifndef SOKOBAN_LEVEL_REPEAT
335 #define SOKOBAN_LEVEL_REPEAT BUTTON_TOPRIGHT
336 #define SOKOBAN_LEVEL_REPEAT_NAME "[TOPRIGHT]"
337 #endif
338 #ifndef BUTTON_SAVE
339 #define BUTTON_SAVE BUTTON_CENTER
340 #define BUTTON_SAVE_NAME "CENTER"
341 #endif
342 #endif
344 #define SOKOBAN_FONT FONT_SYSFIXED
347 /* The Location, Undo and LevelInfo structs are OO-flavored.
348 * (oooh!-flavored as Schnueff puts it.) It makes more you have to know,
349 * but the overall data layout becomes more manageable. */
351 /* Level data & stats */
352 struct LevelInfo {
353 int index; /* Level index (level number - 1) */
354 int moves; /* Moves & pushes for the stats */
355 int pushes;
356 short boxes_to_go; /* Number of unplaced boxes remaining in level */
357 short height; /* Height & width for centering level display */
358 short width;
361 struct Location {
362 short row;
363 short col;
366 /* Our full undo history */
367 static struct UndoInfo {
368 int count; /* How many undos have been done */
369 int current; /* Which history is the current undo */
370 int max; /* Which history is the max redoable */
371 char history[MAX_UNDOS];
372 } undo_info;
374 /* Our playing board */
375 static struct BoardInfo {
376 char board[ROWS][COLS]; /* The current board data */
377 struct LevelInfo level; /* Level data & stats */
378 struct Location player; /* Where the player is */
379 int max_level; /* The number of levels we have */
380 } current_info;
382 static struct BufferedBoards {
383 char filename[MAX_PATH]; /* Filename of the levelset we're using */
384 char data[MAX_LEVEL_DATA]; /* Buffered level data */
385 int index[MAX_LEVELS + 1]; /* Where each buffered board begins & ends */
386 int start; /* Index of first buffered board */
387 int end; /* Index of last buffered board */
388 short prebuffered_boards; /* Number of boards before current to store */
389 } buffered_boards;
392 static const struct plugin_api* rb;
393 MEM_FUNCTION_WRAPPERS(rb);
395 static char buf[ROWS*(COLS + 1)]; /* Enough for a whole board or a filename */
398 static void init_undo(void)
400 undo_info.count = 0;
401 undo_info.current = 0;
402 undo_info.max = 0;
405 static void get_delta(char direction, short *d_r, short *d_c)
407 switch (direction) {
408 case SOKOBAN_PUSH_LEFT:
409 case SOKOBAN_MOVE_LEFT:
410 *d_r = 0;
411 *d_c = -1;
412 break;
413 case SOKOBAN_PUSH_RIGHT:
414 case SOKOBAN_MOVE_RIGHT:
415 *d_r = 0;
416 *d_c = 1;
417 break;
418 case SOKOBAN_PUSH_UP:
419 case SOKOBAN_MOVE_UP:
420 *d_r = -1;
421 *d_c = 0;
422 break;
423 case SOKOBAN_PUSH_DOWN:
424 case SOKOBAN_MOVE_DOWN:
425 *d_r = 1;
426 *d_c = 0;
430 static bool undo(void)
432 char undo;
433 short r, c;
434 short d_r = 0, d_c = 0; /* delta row & delta col */
435 char *space_cur, *space_next, *space_prev;
436 bool undo_push = false;
438 /* If no more undos or we've wrapped all the way around, quit */
439 if (undo_info.count == 0 || undo_info.current - 1 == undo_info.max)
440 return false;
442 /* Move to previous undo in the list */
443 if (undo_info.current == 0 && undo_info.count > 1)
444 undo_info.current = MAX_UNDOS - 1;
445 else
446 undo_info.current--;
448 undo_info.count--;
450 undo = undo_info.history[undo_info.current];
452 if (undo < SOKOBAN_MOVE_MIN)
453 undo_push = true;
455 get_delta(undo, &d_r, &d_c);
457 r = current_info.player.row;
458 c = current_info.player.col;
460 /* Give the 3 spaces we're going to use better names */
461 space_cur = &current_info.board[r][c];
462 space_next = &current_info.board[r + d_r][c + d_c];
463 space_prev = &current_info.board[r - d_r][c - d_c];
465 /* Update board info */
466 if (undo_push) {
467 /* Moving box from goal to floor */
468 if (*space_next == '*' && *space_cur == '@')
469 current_info.level.boxes_to_go++;
470 /* Moving box from floor to goal */
471 else if (*space_next == '$' && *space_cur == '+')
472 current_info.level.boxes_to_go--;
474 /* Move box off of next space... */
475 *space_next = (*space_next == '*' ? '.' : ' ');
476 /* ...and on to current space */
477 *space_cur = (*space_cur == '+' ? '*' : '$');
479 current_info.level.pushes--;
480 } else
481 /* Just move player off of current space */
482 *space_cur = (*space_cur == '+' ? '.' : ' ');
483 /* Move player back to previous space */
484 *space_prev = (*space_prev == '.' ? '+' : '@');
486 /* Update position */
487 current_info.player.row -= d_r;
488 current_info.player.col -= d_c;
490 current_info.level.moves--;
492 return true;
495 static void add_undo(char undo)
497 undo_info.history[undo_info.current] = undo;
499 /* Wrap around if MAX_UNDOS exceeded */
500 if (undo_info.current < (MAX_UNDOS - 1))
501 undo_info.current++;
502 else
503 undo_info.current = 0;
505 if (undo_info.count < MAX_UNDOS)
506 undo_info.count++;
509 static bool move(char direction, bool redo)
511 short r, c;
512 short d_r = 0, d_c = 0; /* delta row & delta col */
513 char *space_cur, *space_next, *space_beyond;
514 bool push = false;
516 get_delta(direction, &d_r, &d_c);
518 r = current_info.player.row;
519 c = current_info.player.col;
521 /* Check for out-of-bounds */
522 if (r + 2*d_r < 0 || r + 2*d_r >= ROWS ||
523 c + 2*d_c < 0 || c + 2*d_c >= COLS)
524 return false;
526 /* Give the 3 spaces we're going to use better names */
527 space_cur = &current_info.board[r][c];
528 space_next = &current_info.board[r + d_r][c + d_c];
529 space_beyond = &current_info.board[r + 2*d_r][c + 2*d_c];
531 if (*space_next == '$' || *space_next == '*') {
532 /* Change direction from move to push for undo */
533 if (direction >= SOKOBAN_MOVE_MIN)
534 direction -= SOKOBAN_MOVE_DIFF;
535 push = true;
537 else if (direction < SOKOBAN_MOVE_MIN)
538 /* Change back to move if redo/solution playback push is invalid */
539 direction += SOKOBAN_MOVE_DIFF;
541 /* Update board info */
542 if (push) {
543 /* Moving box from goal to floor */
544 if (*space_next == '*' && *space_beyond == ' ')
545 current_info.level.boxes_to_go++;
546 /* Moving box from floor to goal */
547 else if (*space_next == '$' && *space_beyond == '.')
548 current_info.level.boxes_to_go--;
549 /* Check for invalid move */
550 else if (*space_beyond != '.' && *space_beyond != ' ')
551 return false;
553 /* Move player onto next space */
554 *space_next = (*space_next == '*' ? '+' : '@');
555 /* Move box onto space beyond next */
556 *space_beyond = (*space_beyond == '.' ? '*' : '$');
558 current_info.level.pushes++;
559 } else {
560 /* Check for invalid move */
561 if (*space_next != '.' && *space_next != ' ')
562 return false;
564 /* Move player onto next space */
565 *space_next = (*space_next == '.' ? '+' : '@');
567 /* Move player off of current space */
568 *space_cur = (*space_cur == '+' ? '.' : ' ');
570 /* Update position */
571 current_info.player.row += d_r;
572 current_info.player.col += d_c;
574 current_info.level.moves++;
576 /* Update undo_info.max to current on every normal move,
577 * except if it's the same as a redo. */
578 /* normal move and either */
579 if (!redo &&
580 /* moves have been undone... */
581 ((undo_info.max != undo_info.current &&
582 /* ...and the current move is NOT the same as the one in history */
583 undo_info.history[undo_info.current] != direction) ||
584 /* or moves have not been undone */
585 undo_info.max == undo_info.current)) {
586 add_undo(direction);
587 undo_info.max = undo_info.current;
588 } else /* redo move or move was same as redo */
589 add_undo(direction); /* add_undo to update current */
591 return true;
594 #ifdef SOKOBAN_REDO
595 static bool redo(void)
597 /* If no moves have been undone, quit */
598 if (undo_info.current == undo_info.max)
599 return false;
601 return move(undo_info.history[(undo_info.current < MAX_UNDOS ?
602 undo_info.current : 0)], true);
604 #endif
606 static void init_boards(void)
608 rb->strncpy(buffered_boards.filename, SOKOBAN_LEVELS_FILE, MAX_PATH);
610 current_info.level.index = 0;
611 current_info.player.row = 0;
612 current_info.player.col = 0;
613 current_info.max_level = 0;
615 buffered_boards.start = 0;
616 buffered_boards.end = 0;
617 buffered_boards.prebuffered_boards = 0;
619 init_undo();
622 static bool read_levels(bool initialize)
624 int fd = 0;
625 short len;
626 short lastlen = 0;
627 short row = 0;
628 int level_count = 0;
630 int i = 0;
631 int level_len = 0;
632 bool index_set = false;
634 /* Get the index of the first level to buffer */
635 if (current_info.level.index > buffered_boards.prebuffered_boards &&
636 !initialize)
637 buffered_boards.start = current_info.level.index -
638 buffered_boards.prebuffered_boards;
639 else
640 buffered_boards.start = 0;
642 if ((fd = rb->open(buffered_boards.filename, O_RDONLY)) < 0) {
643 rb->splashf(HZ*2, "Unable to open %s", buffered_boards.filename);
644 return false;
647 do {
648 len = rb->read_line(fd, buf, sizeof(buf));
650 /* Correct len when trailing \r's or \n's are counted */
651 if (len > 2 && buf[len - 2] == '\0')
652 len -= 2;
653 else if (len > 1 && buf[len - 1] == '\0')
654 len--;
656 /* Skip short lines & lines with non-level data */
657 if (len >= 3 && ((buf[0] >= '1' && buf[0] <= '9') || buf[0] == '#' ||
658 buf[0] == ' ' || buf[0] == '-' || buf[0] == '_')) {
659 if (level_count >= buffered_boards.start) {
660 /* Set the index of this level */
661 if (!index_set &&
662 level_count - buffered_boards.start < MAX_LEVELS) {
663 buffered_boards.index[level_count - buffered_boards.start]
664 = i;
665 index_set = true;
667 /* Copy buffer to board data */
668 if (i + level_len + len < MAX_LEVEL_DATA) {
669 rb->memcpy(&buffered_boards.data[i + level_len], buf, len);
670 buffered_boards.data[i + level_len + len] = '\n';
673 level_len += len + 1;
674 row++;
676 /* If newline & level is tall enough or is RLE */
677 } else if (buf[0] == '\0' && (row > 2 || lastlen > 22)) {
678 level_count++;
679 if (level_count >= buffered_boards.start) {
680 i += level_len;
681 if (i < MAX_LEVEL_DATA)
682 buffered_boards.end = level_count;
683 else if (!initialize)
684 break;
686 row = 0;
687 level_len = 0;
688 index_set = false;
690 } else if (len > 22)
691 len = 1;
693 } while ((lastlen = len));
695 /* Set the index of the end of the last level */
696 if (level_count - buffered_boards.start < MAX_LEVELS)
697 buffered_boards.index[level_count - buffered_boards.start] = i;
699 if (initialize) {
700 current_info.max_level = level_count;
701 buffered_boards.prebuffered_boards = buffered_boards.end/2;
704 rb->close(fd);
706 return true;
709 static void load_level(void)
711 int c, r;
712 int i, n;
713 int level_size;
714 int index = current_info.level.index - buffered_boards.start;
715 char *level;
717 /* Get the buffered board index of the current level */
718 if (current_info.level.index < buffered_boards.start ||
719 current_info.level.index >= buffered_boards.end) {
720 read_levels(false);
721 if (current_info.level.index > buffered_boards.prebuffered_boards)
722 index = buffered_boards.prebuffered_boards;
723 else
724 index = current_info.level.index;
726 level = &buffered_boards.data[buffered_boards.index[index]];
728 /* Reset level info */
729 current_info.level.moves = 0;
730 current_info.level.pushes = 0;
731 current_info.level.boxes_to_go = 0;
732 current_info.level.width = 0;
734 /* Clear board */
735 for (r = 0; r < ROWS; r++)
736 for (c = 0; c < COLS; c++)
737 current_info.board[r][c] = 'X';
739 level_size = buffered_boards.index[index + 1] -
740 buffered_boards.index[index];
742 for (r = 0, c = 0, n = 1, i = 0; i < level_size; i++) {
743 if (level[i] == '\n' || level[i] == '|') {
744 if (c > 3) {
745 /* Update max width of level & go to next row */
746 if (c > current_info.level.width)
747 current_info.level.width = c;
748 c = 0;
749 r++;
750 if (r >= ROWS)
751 break;
753 } else if (c < COLS) {
754 /* Read RLE character's length into n */
755 if (level[i] >= '0' && level[i] <= '9') {
756 n = level[i++] - '0';
757 if (level[i] >= '0' && level[i] <= '9')
758 n = n*10 + level[i++] - '0';
761 /* Cleanup & replace */
762 if (level[i] == '%')
763 level[i] = '*';
764 else if (level[i] == '-' || level[i] == '_')
765 level[i] = ' ';
767 if (n > 1) {
768 if (c + n >= COLS)
769 n = COLS - c;
771 if (level[i] == '.')
772 current_info.level.boxes_to_go += n;
774 /* Put RLE character n times */
775 while (n--)
776 current_info.board[r][c++] = level[i];
777 n = 1;
779 } else {
780 if (level[i] == '.' || level[i] == '+')
781 current_info.level.boxes_to_go++;
783 if (level[i] == '@' ||level[i] == '+') {
784 current_info.player.row = r;
785 current_info.player.col = c;
788 current_info.board[r][c++] = level[i];
793 current_info.level.height = r;
795 #if LCD_DEPTH > 2
796 /* Fill in blank space outside level on color targets */
797 for (r = 0; r < ROWS; r++)
798 for (c = 0; current_info.board[r][c] == ' ' && c < COLS; c++)
799 current_info.board[r][c] = 'X';
801 for (c = 0; c < COLS; c++) {
802 for (r = 0; (current_info.board[r][c] == ' ' ||
803 current_info.board[r][c] == 'X') && r < ROWS; r++)
804 current_info.board[r][c] = 'X';
805 for (r = ROWS - 1; (current_info.board[r][c] == ' ' ||
806 current_info.board[r][c] == 'X') && r >= 0; r--)
807 current_info.board[r][c] = 'X';
809 #endif
812 static void update_screen(void)
814 int c, r;
815 int rows, cols;
817 #if LCD_WIDTH - (COLS*SOKOBAN_TILESIZE) < 32
818 #define STAT_HEIGHT 25
819 #define STAT_X (LCD_WIDTH - 120)/2
820 #define STAT_Y (LCD_HEIGHT - STAT_HEIGHT)
821 #define BOARD_WIDTH LCD_WIDTH
822 #define BOARD_HEIGHT (LCD_HEIGHT - STAT_HEIGHT)
823 rb->lcd_putsxy(STAT_X + 4, STAT_Y + 4, "Level");
824 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.index + 1);
825 rb->lcd_putsxy(STAT_X + 7, STAT_Y + 14, buf);
826 rb->lcd_putsxy(STAT_X + 41, STAT_Y + 4, "Moves");
827 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.moves);
828 rb->lcd_putsxy(STAT_X + 44, STAT_Y + 14, buf);
829 rb->lcd_putsxy(STAT_X + 79, STAT_Y + 4, "Pushes");
830 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.pushes);
831 rb->lcd_putsxy(STAT_X + 82, STAT_Y + 14, buf);
833 rb->lcd_drawrect(STAT_X, STAT_Y, 38, STAT_HEIGHT);
834 rb->lcd_drawrect(STAT_X + 37, STAT_Y, 39, STAT_HEIGHT);
835 rb->lcd_drawrect(STAT_X + 75, STAT_Y, 45, STAT_HEIGHT);
836 #else
837 #if LCD_WIDTH - (COLS*SOKOBAN_TILESIZE) > 40
838 #define STAT_X (LCD_WIDTH - 40)
839 #else
840 #define STAT_X COLS*SOKOBAN_TILESIZE
841 #endif
842 #define STAT_Y (LCD_HEIGHT - 64)/2
843 #define STAT_WIDTH (LCD_WIDTH - STAT_X)
844 #define BOARD_WIDTH (LCD_WIDTH - STAT_WIDTH)
845 #define BOARD_HEIGHT LCD_HEIGHT
846 rb->lcd_putsxy(STAT_X + 1, STAT_Y + 2, "Level");
847 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.index + 1);
848 rb->lcd_putsxy(STAT_X + 4, STAT_Y + 12, buf);
849 rb->lcd_putsxy(STAT_X + 1, STAT_Y + 23, "Moves");
850 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.moves);
851 rb->lcd_putsxy(STAT_X + 4, STAT_Y + 33, buf);
852 #if STAT_WIDTH < 38
853 rb->lcd_putsxy(STAT_X + 1, STAT_Y + 44, "Push");
854 #else
855 rb->lcd_putsxy(STAT_X + 1, STAT_Y + 44, "Pushes");
856 #endif
857 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.pushes);
858 rb->lcd_putsxy(STAT_X + 4, STAT_Y + 54, buf);
860 rb->lcd_drawrect(STAT_X, STAT_Y + 0, STAT_WIDTH, 64);
861 rb->lcd_hline(STAT_X, LCD_WIDTH - 1, STAT_Y + 21);
862 rb->lcd_hline(STAT_X, LCD_WIDTH - 1, STAT_Y + 42);
864 #endif
866 /* load the board to the screen */
867 for (rows = 0; rows < ROWS; rows++) {
868 for (cols = 0; cols < COLS; cols++) {
869 c = cols*SOKOBAN_TILESIZE +
870 (BOARD_WIDTH - current_info.level.width*SOKOBAN_TILESIZE)/2;
871 r = rows*SOKOBAN_TILESIZE +
872 (BOARD_HEIGHT - current_info.level.height*SOKOBAN_TILESIZE)/2;
874 switch(current_info.board[rows][cols]) {
875 case 'X': /* blank space outside of level */
876 break;
878 case ' ': /* floor */
879 rb->lcd_bitmap_part(sokoban_tiles, 0, 0*SOKOBAN_TILESIZE,
880 SOKOBAN_TILESIZE, c, r, SOKOBAN_TILESIZE,
881 SOKOBAN_TILESIZE);
882 break;
884 case '#': /* wall */
885 rb->lcd_bitmap_part(sokoban_tiles, 0, 1*SOKOBAN_TILESIZE,
886 SOKOBAN_TILESIZE, c, r, SOKOBAN_TILESIZE,
887 SOKOBAN_TILESIZE);
888 break;
890 case '$': /* box */
891 rb->lcd_bitmap_part(sokoban_tiles, 0, 2*SOKOBAN_TILESIZE,
892 SOKOBAN_TILESIZE, c, r, SOKOBAN_TILESIZE,
893 SOKOBAN_TILESIZE);
894 break;
896 case '*': /* box on goal */
897 rb->lcd_bitmap_part(sokoban_tiles, 0, 3*SOKOBAN_TILESIZE,
898 SOKOBAN_TILESIZE, c, r, SOKOBAN_TILESIZE,
899 SOKOBAN_TILESIZE);
900 break;
902 case '.': /* goal */
903 rb->lcd_bitmap_part(sokoban_tiles, 0, 4*SOKOBAN_TILESIZE,
904 SOKOBAN_TILESIZE, c, r, SOKOBAN_TILESIZE,
905 SOKOBAN_TILESIZE);
906 break;
908 case '@': /* player */
909 rb->lcd_bitmap_part(sokoban_tiles, 0, 5*SOKOBAN_TILESIZE,
910 SOKOBAN_TILESIZE, c, r, SOKOBAN_TILESIZE,
911 SOKOBAN_TILESIZE);
912 break;
914 case '+': /* player on goal */
915 rb->lcd_bitmap_part(sokoban_tiles, 0, 6*SOKOBAN_TILESIZE,
916 SOKOBAN_TILESIZE, c, r, SOKOBAN_TILESIZE,
917 SOKOBAN_TILESIZE);
918 break;
923 /* print out the screen */
924 rb->lcd_update();
927 static void draw_level(void)
929 load_level();
930 rb->lcd_clear_display();
931 update_screen();
934 static bool save(char *filename, bool solution)
936 int fd;
937 char *loc;
938 DIR *dir;
939 char dirname[MAX_PATH];
941 rb->splash(0, "Saving...");
943 /* Create dir if it doesn't exist */
944 if ((loc = rb->strrchr(filename, '/')) != NULL) {
945 rb->strncpy(dirname, filename, loc - filename);
946 dirname[loc - filename] = '\0';
947 if(!(dir = rb->opendir(dirname)))
948 rb->mkdir(dirname);
949 else
950 rb->closedir(dir);
953 if (filename[0] == '\0' ||
954 (fd = rb->open(filename, O_WRONLY|O_CREAT|O_TRUNC)) < 0) {
955 rb->splashf(HZ*2, "Unable to open %s", filename);
956 return false;
959 /* Sokoban: S/P for solution/progress : level number : current undo */
960 rb->snprintf(buf, sizeof(buf), "Sokoban:%c:%d:%d\n", (solution ? 'S' : 'P'),
961 current_info.level.index + 1, undo_info.current);
962 rb->write(fd, buf, rb->strlen(buf));
964 /* Filename of levelset */
965 rb->write(fd, buffered_boards.filename,
966 rb->strlen(buffered_boards.filename));
967 rb->write(fd, "\n", 1);
969 /* Full undo history */
970 rb->write(fd, undo_info.history, undo_info.max);
972 rb->close(fd);
974 return true;
977 static bool load(char *filename, bool silent)
979 int fd;
980 int i, n;
981 int len;
982 int button;
983 bool play_solution;
984 bool paused = false;
985 unsigned short speed = 2;
986 int delay[] = {HZ/2, HZ/3, HZ/4, HZ/6, HZ/8, HZ/12, HZ/16, HZ/25};
988 if (filename[0] == '\0' || (fd = rb->open(filename, O_RDONLY)) < 0) {
989 if (!silent)
990 rb->splashf(HZ*2, "Unable to open %s", filename);
991 return false;
994 /* Read header, level number, & current undo */
995 rb->read_line(fd, buf, sizeof(buf));
997 /* If we're opening a level file, not a solution/progress file */
998 if (rb->strncmp(buf, "Sokoban", 7) != 0) {
999 rb->close(fd);
1001 rb->strncpy(buffered_boards.filename, filename, MAX_PATH);
1002 if (!read_levels(true))
1003 return false;
1005 current_info.level.index = 0;
1006 load_level();
1008 /* If there aren't any boxes to go or the player position wasn't set,
1009 * the file probably wasn't a Sokoban level file */
1010 if (current_info.level.boxes_to_go == 0 ||
1011 current_info.player.row == 0 || current_info.player.col == 0) {
1012 if (!silent)
1013 rb->splash(HZ*2, "File is not a Sokoban level file");
1014 return false;
1017 } else {
1019 /* Read filename of levelset */
1020 rb->read_line(fd, buffered_boards.filename,
1021 sizeof(buffered_boards.filename));
1023 /* Read full undo history */
1024 len = rb->read_line(fd, undo_info.history, MAX_UNDOS);
1026 /* Correct len when trailing \r's or \n's are counted */
1027 if (len > 2 && undo_info.history[len - 2] == '\0')
1028 len -= 2;
1029 else if (len > 1 && undo_info.history[len - 1] == '\0')
1030 len--;
1032 rb->close(fd);
1034 /* Check to see if we're going to play a solution or resume progress */
1035 play_solution = (buf[8] == 'S');
1037 /* Get level number */
1038 for (n = 0, i = 10; buf[i] >= '0' && buf[i] <= '9' && i < 15; i++)
1039 n = n*10 + buf[i] - '0';
1040 current_info.level.index = n - 1;
1042 /* Get undo index */
1043 for (n = 0, i++; buf[i] >= '0' && buf[i] <= '9' && i < 21; i++)
1044 n = n*10 + buf[i] - '0';
1045 if (n > len)
1046 n = len;
1047 undo_info.max = len;
1049 if (current_info.level.index < 0) {
1050 if (!silent)
1051 rb->splash(HZ*2, "Error loading level");
1052 return false;
1054 if (!read_levels(true))
1055 return false;
1056 if (current_info.level.index >= current_info.max_level) {
1057 if (!silent)
1058 rb->splash(HZ*2, "Error loading level");
1059 return false;
1062 load_level();
1064 if (play_solution) {
1065 rb->lcd_clear_display();
1066 update_screen();
1067 rb->sleep(2*delay[speed]);
1069 /* Replay solution until menu button is pressed */
1070 i = 0;
1071 while (true) {
1072 if (i < len) {
1073 if (!move(undo_info.history[i], true)) {
1074 n = i;
1075 break;
1077 rb->lcd_clear_display();
1078 update_screen();
1079 i++;
1080 } else
1081 paused = true;
1083 rb->sleep(delay[speed]);
1085 while ((button = rb->button_get(false)) || paused) {
1086 switch (button) {
1087 case SOKOBAN_MENU:
1088 /* Pretend the level is complete so we'll quit */
1089 current_info.level.boxes_to_go = 0;
1090 return true;
1092 case SOKOBAN_PAUSE:
1093 /* Toggle pause state */
1094 paused = !paused;
1095 break;
1097 case SOKOBAN_LEFT:
1098 case SOKOBAN_LEFT | BUTTON_REPEAT:
1099 /* Go back one move */
1100 if (paused) {
1101 if (undo())
1102 i--;
1103 rb->lcd_clear_display();
1104 update_screen();
1106 break;
1108 case SOKOBAN_RIGHT:
1109 case SOKOBAN_RIGHT | BUTTON_REPEAT:
1110 /* Go forward one move */
1111 if (paused) {
1112 if (redo())
1113 i++;
1114 rb->lcd_clear_display();
1115 update_screen();
1117 break;
1119 case SOKOBAN_UP:
1120 case SOKOBAN_UP | BUTTON_REPEAT:
1121 /* Speed up */
1122 if (speed < sizeof(delay)/sizeof(int) - 1)
1123 speed++;
1124 break;
1126 case SOKOBAN_DOWN:
1127 case SOKOBAN_DOWN | BUTTON_REPEAT:
1128 /* Slow down */
1129 if (speed > 0)
1130 speed--;
1133 if (paused)
1134 rb->sleep(HZ/33);
1138 /* If level is complete, wait for keypress before quitting */
1139 if (current_info.level.boxes_to_go == 0)
1140 rb->button_get(true);
1142 } else {
1143 /* Advance to current undo */
1144 for (i = 0; i < n; i++) {
1145 if (!move(undo_info.history[i], true)) {
1146 n = i;
1147 break;
1151 rb->button_clear_queue();
1152 rb->lcd_clear_display();
1155 undo_info.current = n;
1158 return true;
1161 static int sokoban_menu(void)
1163 int button;
1164 int selection = 0;
1165 int i;
1166 bool menu_quit;
1167 int start_selected = 0;
1168 int prev_level = current_info.level.index;
1170 MENUITEM_STRINGLIST(menu, "Sokoban Menu", NULL,
1171 "Resume", "Select Level", "Audio Playback", "Keys",
1172 "Load Default Level Set", "Quit Without Saving",
1173 "Save Progress & Quit");
1175 do {
1176 menu_quit = true;
1177 selection = rb->do_menu(&menu, &start_selected, NULL, false);
1179 switch (selection) {
1180 case 0: /* Resume */
1181 break;
1183 case 1: /* Select level */
1184 current_info.level.index++;
1185 rb->set_int("Select Level", "", UNIT_INT,
1186 &current_info.level.index, NULL, 1, 1,
1187 current_info.max_level, NULL);
1188 current_info.level.index--;
1189 if (prev_level != current_info.level.index) {
1190 init_undo();
1191 draw_level();
1192 } else
1193 menu_quit = false;
1194 break;
1196 case 2: /* Audio playback control */
1197 playback_control(rb, NULL);
1198 menu_quit = false;
1199 break;
1201 case 3: /* Keys */
1202 FOR_NB_SCREENS(i)
1203 rb->screens[i]->clear_display();
1204 rb->lcd_setfont(SOKOBAN_FONT);
1206 #if (CONFIG_KEYPAD == RECORDER_PAD) || \
1207 (CONFIG_KEYPAD == ARCHOS_AV300_PAD)
1208 rb->lcd_putsxy(3, 6, "[OFF] Menu");
1209 rb->lcd_putsxy(3, 16, "[ON] Undo");
1210 rb->lcd_putsxy(3, 26, "[PLAY] Redo");
1211 rb->lcd_putsxy(3, 36, "[F1] Down a Level");
1212 rb->lcd_putsxy(3, 46, "[F2] Restart Level");
1213 rb->lcd_putsxy(3, 56, "[F3] Up a Level");
1214 #elif CONFIG_KEYPAD == ONDIO_PAD
1215 rb->lcd_putsxy(3, 6, "[OFF] Menu");
1216 rb->lcd_putsxy(3, 16, "[MODE] Undo");
1217 rb->lcd_putsxy(3, 26, "[MODE+DOWN] Redo");
1218 rb->lcd_putsxy(3, 36, "[MODE+LEFT] Previous Level");
1219 rb->lcd_putsxy(3, 46, "[MODE+UP] Restart Level");
1220 rb->lcd_putsxy(3, 56, "[MODE+RIGHT] Up Level");
1221 #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
1222 (CONFIG_KEYPAD == IRIVER_H300_PAD)
1223 rb->lcd_putsxy(3, 6, "[STOP] Menu");
1224 rb->lcd_putsxy(3, 16, "[REC] Undo");
1225 rb->lcd_putsxy(3, 26, "[MODE] Redo");
1226 rb->lcd_putsxy(3, 36, "[PLAY+DOWN] Previous Level");
1227 rb->lcd_putsxy(3, 46, "[PLAY] Restart Level");
1228 rb->lcd_putsxy(3, 56, "[PLAY+UP] Next Level");
1229 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
1230 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
1231 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
1232 rb->lcd_putsxy(3, 6, "[SELECT+MENU] Menu");
1233 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
1234 rb->lcd_putsxy(3, 26, "[SELECT+PLAY] Redo");
1235 rb->lcd_putsxy(3, 36, "[SELECT+LEFT] Previous Level");
1236 rb->lcd_putsxy(3, 46, "[SELECT+RIGHT] Next Level");
1237 #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
1238 rb->lcd_putsxy(3, 6, "[POWER] Menu");
1239 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
1240 rb->lcd_putsxy(3, 26, "[PLAY] Redo");
1241 rb->lcd_putsxy(3, 36, "[REC] Restart Level");
1242 #elif CONFIG_KEYPAD == IRIVER_H10_PAD
1243 rb->lcd_putsxy(3, 6, "[POWER] Menu");
1244 rb->lcd_putsxy(3, 16, "[REW] Undo");
1245 rb->lcd_putsxy(3, 26, "[FF] Redo");
1246 rb->lcd_putsxy(3, 36, "[PLAY+DOWN] Previous Level");
1247 rb->lcd_putsxy(3, 46, "[PLAY+RIGHT] Restart Level");
1248 rb->lcd_putsxy(3, 56, "[PLAY+UP] Next Level");
1249 #elif CONFIG_KEYPAD == GIGABEAT_PAD
1250 rb->lcd_putsxy(3, 6, "[POWER] Menu");
1251 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
1252 rb->lcd_putsxy(3, 26, "[A] Redo");
1253 rb->lcd_putsxy(3, 36, "[VOL-] Previous Level");
1254 rb->lcd_putsxy(3, 46, "[MENU] Restart Level");
1255 rb->lcd_putsxy(3, 56, "[VOL+] Next Level");
1256 #elif CONFIG_KEYPAD == SANSA_E200_PAD
1257 rb->lcd_putsxy(3, 6, "[POWER] Menu");
1258 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
1259 rb->lcd_putsxy(3, 26, "[REC] Redo");
1260 rb->lcd_putsxy(3, 36, "[SELECT+DOWN] Previous Level");
1261 rb->lcd_putsxy(3, 46, "[SELECT+RIGHT] Restart Level");
1262 rb->lcd_putsxy(3, 56, "[SELECT+UP] Next Level");
1263 #endif
1265 #ifdef HAVE_TOUCHSCREEN
1266 rb->lcd_putsxy(3, 6, SOKOBAN_MENU_NAME " Menu");
1267 rb->lcd_putsxy(3, 16, SOKOBAN_UNDO_NAME " Undo");
1268 rb->lcd_putsxy(3, 26, SOKOBAN_REDO_NAME " Redo");
1269 rb->lcd_putsxy(3, 36, SOKOBAN_PAUSE_NAME " Pause");
1270 rb->lcd_putsxy(3, 46, SOKOBAN_LEVEL_REPEAT_NAME " Restart Level");
1271 #endif
1273 FOR_NB_SCREENS(i)
1274 rb->screens[i]->update();
1276 /* Display until keypress */
1277 do {
1278 rb->sleep(HZ/20);
1279 button = rb->button_get(false);
1280 } while (!button || button & BUTTON_REL ||
1281 button & BUTTON_REPEAT);
1283 menu_quit = false;
1284 break;
1286 case 4: /* Load default levelset */
1287 init_boards();
1288 if (!read_levels(true))
1289 return 5; /* Quit */
1290 load_level();
1291 break;
1293 case 5: /* Quit */
1294 break;
1296 case 6: /* Save & quit */
1297 save(SOKOBAN_SAVE_FILE, false);
1298 rb->reload_directory();
1301 } while (!menu_quit);
1303 /* Restore font */
1304 rb->lcd_setfont(SOKOBAN_FONT);
1306 FOR_NB_SCREENS(i) {
1307 rb->screens[i]->clear_display();
1308 rb->screens[i]->update();
1311 return selection;
1314 static bool sokoban_loop(void)
1316 bool moved;
1317 int i = 0, button = 0, lastbutton = 0;
1318 short r = 0, c = 0;
1319 int w, h;
1320 char *loc;
1322 while (true) {
1323 moved = false;
1325 r = current_info.player.row;
1326 c = current_info.player.col;
1328 button = rb->button_get(true);
1330 switch(button)
1332 #ifdef SOKOBAN_RC_MENU
1333 case SOKOBAN_RC_MENU:
1334 #endif
1335 case SOKOBAN_MENU:
1336 switch (sokoban_menu()) {
1337 case 5: /* Quit */
1338 case 6: /* Save & quit */
1339 return PLUGIN_OK;
1341 update_screen();
1342 break;
1344 case SOKOBAN_UNDO:
1345 #ifdef SOKOBAN_UNDO_PRE
1346 if (lastbutton != SOKOBAN_UNDO_PRE)
1347 break;
1348 #else /* repeat can't work here for Ondio, iPod, et al */
1349 case SOKOBAN_UNDO | BUTTON_REPEAT:
1350 #endif
1351 undo();
1352 rb->lcd_clear_display();
1353 update_screen();
1354 break;
1356 #ifdef SOKOBAN_REDO
1357 case SOKOBAN_REDO:
1358 case SOKOBAN_REDO | BUTTON_REPEAT:
1359 moved = redo();
1360 rb->lcd_clear_display();
1361 update_screen();
1362 break;
1363 #endif
1365 #ifdef SOKOBAN_LEVEL_UP
1366 case SOKOBAN_LEVEL_UP:
1367 case SOKOBAN_LEVEL_UP | BUTTON_REPEAT:
1368 /* next level */
1369 init_undo();
1370 if (current_info.level.index + 1 < current_info.max_level)
1371 current_info.level.index++;
1373 draw_level();
1374 break;
1375 #endif
1377 #ifdef SOKOBAN_LEVEL_DOWN
1378 case SOKOBAN_LEVEL_DOWN:
1379 case SOKOBAN_LEVEL_DOWN | BUTTON_REPEAT:
1380 /* previous level */
1381 init_undo();
1382 if (current_info.level.index > 0)
1383 current_info.level.index--;
1385 draw_level();
1386 break;
1387 #endif
1389 #ifdef SOKOBAN_LEVEL_REPEAT
1390 case SOKOBAN_LEVEL_REPEAT:
1391 case SOKOBAN_LEVEL_REPEAT | BUTTON_REPEAT:
1392 /* same level */
1393 init_undo();
1394 draw_level();
1395 break;
1396 #endif
1398 case SOKOBAN_LEFT:
1399 case SOKOBAN_LEFT | BUTTON_REPEAT:
1400 moved = move(SOKOBAN_MOVE_LEFT, false);
1401 break;
1403 case SOKOBAN_RIGHT:
1404 case SOKOBAN_RIGHT | BUTTON_REPEAT:
1405 moved = move(SOKOBAN_MOVE_RIGHT, false);
1406 break;
1408 case SOKOBAN_UP:
1409 case SOKOBAN_UP | BUTTON_REPEAT:
1410 moved = move(SOKOBAN_MOVE_UP, false);
1411 break;
1413 case SOKOBAN_DOWN:
1414 case SOKOBAN_DOWN | BUTTON_REPEAT:
1415 moved = move(SOKOBAN_MOVE_DOWN, false);
1416 break;
1418 default:
1419 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
1420 return PLUGIN_USB_CONNECTED;
1421 break;
1424 lastbutton = button;
1426 if (moved) {
1427 rb->lcd_clear_display();
1428 update_screen();
1431 /* We have completed this level */
1432 if (current_info.level.boxes_to_go == 0) {
1434 if (moved) {
1435 rb->lcd_clear_display();
1437 /* Show level complete message & stats */
1438 rb->snprintf(buf, sizeof(buf), "Level %d Complete!",
1439 current_info.level.index + 1);
1440 rb->lcd_getstringsize(buf, &w, &h);
1441 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h*3, buf);
1443 rb->snprintf(buf, sizeof(buf), "%4d Moves ",
1444 current_info.level.moves);
1445 rb->lcd_getstringsize(buf, &w, &h);
1446 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h, buf);
1448 rb->snprintf(buf, sizeof(buf), "%4d Pushes",
1449 current_info.level.pushes);
1450 rb->lcd_getstringsize(buf, &w, &h);
1451 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2, buf);
1453 if (undo_info.count < MAX_UNDOS) {
1454 rb->snprintf(buf, sizeof(buf), "%s: Save solution",
1455 BUTTON_SAVE_NAME);
1456 rb->lcd_getstringsize(buf, &w, &h);
1457 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 + h*2, buf);
1460 rb->lcd_update();
1461 rb->sleep(HZ/4);
1462 rb->button_clear_queue();
1464 /* Display for 4 seconds or until new keypress */
1465 for (i = 0; i < 80; i++) {
1466 rb->sleep(HZ/20);
1467 button = rb->button_get(false);
1468 if (button && !(button & BUTTON_REL) &&
1469 !(button & BUTTON_REPEAT))
1470 break;
1473 if (button == BUTTON_SAVE) {
1474 if (undo_info.count < MAX_UNDOS) {
1475 /* Set filename to current levelset plus level number
1476 * and .sok extension. Use SAVE_FOLDER if using the
1477 * default levelset, since it's in a hidden folder. */
1478 if (rb->strcmp(buffered_boards.filename,
1479 SOKOBAN_LEVELS_FILE) == 0) {
1480 rb->snprintf(buf, sizeof(buf),
1481 "%s/sokoban.%d.sok",
1482 SOKOBAN_SAVE_FOLDER,
1483 current_info.level.index + 1);
1484 } else {
1485 if ((loc = rb->strrchr(buffered_boards.filename,
1486 '.')) != NULL)
1487 *loc = '\0';
1488 rb->snprintf(buf, sizeof(buf), "%s.%d.sok",
1489 buffered_boards.filename,
1490 current_info.level.index + 1);
1491 if (loc != NULL)
1492 *loc = '.';
1495 if (!rb->kbd_input(buf, MAX_PATH))
1496 save(buf, true);
1497 } else
1498 rb->splash(HZ*2, "Solution too long to save");
1500 rb->lcd_setfont(SOKOBAN_FONT); /* Restore font */
1504 FOR_NB_SCREENS(i) {
1505 rb->screens[i]->clear_display();
1506 rb->screens[i]->update();
1509 current_info.level.index++;
1511 /* clear undo stats */
1512 init_undo();
1514 if (current_info.level.index >= current_info.max_level) {
1515 /* Show levelset complete message */
1516 rb->snprintf(buf, sizeof(buf), "You WIN!!");
1517 rb->lcd_getstringsize(buf, &w, &h);
1518 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h/2, buf);
1520 rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
1521 /* Display for 4 seconds or until keypress */
1522 for (i = 0; i < 80; i++) {
1523 rb->lcd_fillrect(0, 0, LCD_WIDTH, LCD_HEIGHT);
1524 rb->lcd_update();
1525 rb->sleep(HZ/10);
1527 button = rb->button_get(false);
1528 if (button && !(button & BUTTON_REL))
1529 break;
1531 rb->lcd_set_drawmode(DRMODE_SOLID);
1533 /* Reset to first level & show quit menu */
1534 current_info.level.index = 0;
1536 switch (sokoban_menu()) {
1537 case 5: /* Quit */
1538 case 6: /* Save & quit */
1539 return PLUGIN_OK;
1543 load_level();
1544 update_screen();
1547 } /* end while */
1549 return PLUGIN_OK;
1553 enum plugin_status plugin_start(const struct plugin_api* api, const void* parameter)
1555 int w, h;
1557 (void)(parameter);
1558 rb = api;
1560 rb->lcd_setfont(SOKOBAN_FONT);
1562 rb->lcd_clear_display();
1563 rb->lcd_getstringsize(SOKOBAN_TITLE, &w, &h);
1564 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h/2, SOKOBAN_TITLE);
1565 rb->lcd_update();
1566 rb->sleep(HZ); /* Show title for 1 second */
1568 init_boards();
1570 if (parameter == NULL) {
1571 /* Attempt to resume saved progress, otherwise start at beginning */
1572 if (!load(SOKOBAN_SAVE_FILE, true)) {
1573 init_boards();
1574 if (!read_levels(true))
1575 return PLUGIN_OK;
1576 load_level();
1579 } else {
1580 /* The plugin is being used to open a file */
1581 if (load((char*) parameter, false)) {
1582 /* If we loaded & played a solution, quit */
1583 if (current_info.level.boxes_to_go == 0)
1584 return PLUGIN_OK;
1585 } else
1586 return PLUGIN_OK;
1589 rb->lcd_clear_display();
1590 update_screen();
1592 return sokoban_loop();