1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
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 ****************************************************************************/
23 #include "lib/playback_control.h"
25 #ifdef HAVE_LCD_BITMAP
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
[];
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)
46 #define ROWS (LCD_HEIGHT/MAGNIFY)
47 #define COLS ((LCD_WIDTH-40)/MAGNIFY)
48 #elif (LCD_HEIGHT >= 249) && (LCD_WIDTH >= 280)
50 #define ROWS ((LCD_HEIGHT-25)/MAGNIFY)
51 #define COLS (LCD_WIDTH/MAGNIFY)
52 #elif (LCD_HEIGHT >= 144) && (LCD_WIDTH >= 220)
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 */
58 #define ROWS ((LCD_HEIGHT-25)/MAGNIFY)
59 #define COLS ((LCD_WIDTH+4)/MAGNIFY)
60 #elif (LCD_HEIGHT >= 96) && (LCD_WIDTH >= 160)
62 #define ROWS (LCD_HEIGHT/MAGNIFY)
63 #define COLS ((LCD_WIDTH-40)/MAGNIFY)
64 #elif (LCD_HEIGHT >= 121) && (LCD_WIDTH >= 120)
66 #define ROWS ((LCD_HEIGHT-25)/MAGNIFY)
67 #define COLS (LCD_WIDTH/MAGNIFY)
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)
79 #define MAX_LEVEL_DATA 0x20000
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
89 #define MAX_UNDOS (PLUGIN_BUFFER_SIZE - MAX_LEVEL_DATA - 0x3000)
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"
297 #error No keymap defined!
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 */
309 int index
; /* Level index (level number - 1) */
310 int moves
; /* Moves & pushes for the stats */
312 short boxes_to_go
; /* Number of unplaced boxes remaining in level */
313 short height
; /* Height & width for centering level display */
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
];
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 */
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 */
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)
357 undo_info
.current
= 0;
361 static void get_delta(char direction
, short *d_r
, short *d_c
)
364 case SOKOBAN_PUSH_LEFT
:
365 case SOKOBAN_MOVE_LEFT
:
369 case SOKOBAN_PUSH_RIGHT
:
370 case SOKOBAN_MOVE_RIGHT
:
374 case SOKOBAN_PUSH_UP
:
375 case SOKOBAN_MOVE_UP
:
379 case SOKOBAN_PUSH_DOWN
:
380 case SOKOBAN_MOVE_DOWN
:
386 static bool undo(void)
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
)
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;
406 undo
= undo_info
.history
[undo_info
.current
];
408 if (undo
< SOKOBAN_MOVE_MIN
)
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
= ¤t_info
.board
[r
][c
];
418 space_next
= ¤t_info
.board
[r
+ d_r
][c
+ d_c
];
419 space_prev
= ¤t_info
.board
[r
- d_r
][c
- d_c
];
421 /* Update board info */
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
--;
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
--;
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))
459 undo_info
.current
= 0;
461 if (undo_info
.count
< MAX_UNDOS
)
465 static bool move(char direction
, bool redo
)
468 short d_r
= 0, d_c
= 0; /* delta row & delta col */
469 char *space_cur
, *space_next
, *space_beyond
;
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
)
482 /* Give the 3 spaces we're going to use better names */
483 space_cur
= ¤t_info
.board
[r
][c
];
484 space_next
= ¤t_info
.board
[r
+ d_r
][c
+ d_c
];
485 space_beyond
= ¤t_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
;
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 */
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
!= ' ')
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
++;
516 /* Check for invalid move */
517 if (*space_next
!= '.' && *space_next
!= ' ')
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 */
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
)) {
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 */
551 static bool redo(void)
553 /* If no moves have been undone, quit */
554 if (undo_info
.current
== undo_info
.max
)
557 return move(undo_info
.history
[(undo_info
.current
< MAX_UNDOS
?
558 undo_info
.current
: 0)], true);
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;
578 static bool read_levels(bool initialize
)
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
&&
593 buffered_boards
.start
= current_info
.level
.index
-
594 buffered_boards
.prebuffered_boards
;
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
);
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')
609 else if (len
> 1 && buf
[len
- 1] == '\0')
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 */
618 level_count
- buffered_boards
.start
< MAX_LEVELS
) {
619 buffered_boards
.index
[level_count
- buffered_boards
.start
]
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;
632 /* If newline & level is tall enough or is RLE */
633 } else if (buf
[0] == '\0' && (row
> 2 || lastlen
> 22)) {
635 if (level_count
>= buffered_boards
.start
) {
637 if (i
< MAX_LEVEL_DATA
)
638 buffered_boards
.end
= level_count
;
639 else if (!initialize
)
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
;
656 current_info
.max_level
= level_count
;
657 buffered_boards
.prebuffered_boards
= buffered_boards
.end
/2;
665 static void load_level(void)
670 int index
= current_info
.level
.index
- buffered_boards
.start
;
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
) {
677 if (current_info
.level
.index
> buffered_boards
.prebuffered_boards
)
678 index
= buffered_boards
.prebuffered_boards
;
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;
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
] == '|') {
701 /* Update max width of level & go to next row */
702 if (c
> current_info
.level
.width
)
703 current_info
.level
.width
= c
;
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 */
720 else if (level
[i
] == '-' || level
[i
] == '_')
728 current_info
.level
.boxes_to_go
+= n
;
730 /* Put RLE character n times */
732 current_info
.board
[r
][c
++] = level
[i
];
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
;
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';
768 static void update_screen(void)
773 #if LCD_DEPTH < 2 || ((LCD_HEIGHT < 96 || LCD_WIDTH < 152) && \
774 (LCD_HEIGHT < 121 || LCD_WIDTH < 120))
776 int max
= MAGNIFY
- 1;
778 int ldelta
= (middle
+ 1)/2;
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
);
801 #if LCD_WIDTH - (COLS*MAGNIFY) > 40
802 #define STAT_X (LCD_WIDTH - 40)
804 #define STAT_X COLS*MAGNIFY
807 #define STAT_Y (LCD_HEIGHT - 70)/2
809 #define STAT_Y (LCD_HEIGHT - 47)/2
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);
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);
834 /* load the board to the screen */
835 for (rows
= 0; rows
< ROWS
; rows
++) {
836 for (cols
= 0; cols
< COLS
; cols
++) {
838 (BOARD_WIDTH
- current_info
.level
.width
*MAGNIFY
)/2;
840 (BOARD_HEIGHT
- current_info
.level
.height
*MAGNIFY
)/2;
842 switch(current_info
.board
[rows
][cols
]) {
843 case 'X': /* blank space outside of level */
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
);
854 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 1*MAGNIFY
, MAGNIFY
,
855 c
, r
, MAGNIFY
, MAGNIFY
);
859 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 2*MAGNIFY
, MAGNIFY
,
860 c
, r
, MAGNIFY
, MAGNIFY
);
863 case '*': /* box on goal */
864 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 3*MAGNIFY
, MAGNIFY
,
865 c
, r
, MAGNIFY
, MAGNIFY
);
869 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 4*MAGNIFY
, MAGNIFY
,
870 c
, r
, MAGNIFY
, MAGNIFY
);
873 case '@': /* player */
874 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 5*MAGNIFY
, MAGNIFY
,
875 c
, r
, MAGNIFY
, MAGNIFY
);
878 case '+': /* player on goal */
879 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 6*MAGNIFY
, MAGNIFY
,
880 c
, r
, MAGNIFY
, MAGNIFY
);
884 for (i
= c
; i
< c
+ MAGNIFY
; i
++)
885 for (j
= r
; j
< r
+ MAGNIFY
; j
++)
887 rb
->lcd_drawpixel(i
, j
);
891 rb
->lcd_drawrect(c
, r
, MAGNIFY
, MAGNIFY
);
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);
901 rb
->lcd_drawrect(c
+ MAGNIFY
/2 - 1, r
+ MAGNIFY
/2 - 1,
902 MAGNIFY
/2, MAGNIFY
/2);
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
,
910 rb
->lcd_drawline(c
+ max
- middle
, r
, c
+ max
- middle
,
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
);
922 /* print out the screen */
926 static void draw_level(void)
929 rb
->lcd_clear_display();
933 static bool save(char *filename
, bool solution
)
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
)))
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
);
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
);
976 static bool load(char *filename
, bool silent
)
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) {
989 rb
->splash(HZ
*2, "Unable to open %s", filename
);
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) {
1000 rb
->strncpy(buffered_boards
.filename
, filename
, MAX_PATH
);
1001 if (!read_levels(true))
1004 current_info
.level
.index
= 0;
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) {
1012 rb
->splash(HZ
*2, "File is not a Sokoban level file");
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')
1028 else if (len
> 1 && undo_info
.history
[len
- 1] == '\0')
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';
1046 undo_info
.max
= len
;
1048 if (current_info
.level
.index
< 0) {
1050 rb
->splash(HZ
*2, "Error loading level");
1053 if (!read_levels(true))
1055 if (current_info
.level
.index
>= current_info
.max_level
) {
1057 rb
->splash(HZ
*2, "Error loading level");
1063 if (play_solution
) {
1064 rb
->lcd_clear_display();
1066 rb
->sleep(2*delay
[speed
]);
1068 /* Replay solution until menu button is pressed */
1072 if (!move(undo_info
.history
[i
], true)) {
1076 rb
->lcd_clear_display();
1082 rb
->sleep(delay
[speed
]);
1084 while ((button
= rb
->button_get(false)) || paused
) {
1087 /* Pretend the level is complete so we'll quit */
1088 current_info
.level
.boxes_to_go
= 0;
1092 /* Toggle pause state */
1097 case SOKOBAN_LEFT
| BUTTON_REPEAT
:
1098 /* Go back one move */
1102 rb
->lcd_clear_display();
1108 case SOKOBAN_RIGHT
| BUTTON_REPEAT
:
1109 /* Go forward one move */
1113 rb
->lcd_clear_display();
1119 case SOKOBAN_UP
| BUTTON_REPEAT
:
1121 if (speed
< sizeof(delay
)/sizeof(int) - 1)
1126 case SOKOBAN_DOWN
| BUTTON_REPEAT
:
1137 /* If level is complete, wait for keypress before quitting */
1138 if (current_info
.level
.boxes_to_go
== 0)
1139 rb
->button_get(true);
1142 /* Advance to current undo */
1143 for (i
= 0; i
< n
; i
++) {
1144 if (!move(undo_info
.history
[i
], true)) {
1150 rb
->button_clear_queue();
1151 rb
->lcd_clear_display();
1154 undo_info
.current
= n
;
1160 static int sokoban_menu(void)
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");
1176 selection
= rb
->do_menu(&menu
, &start_selected
, NULL
, false);
1178 switch (selection
) {
1179 case 0: /* Resume */
1182 case 1: /* Select level */
1183 current_info
.level
.index
++;
1184 rb
->set_int("Select Level", "", UNIT_INT
,
1185 ¤t_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
) {
1195 case 2: /* Audio playback control */
1196 playback_control(rb
);
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");
1265 rb
->screens
[i
]->update();
1267 /* Display until keypress */
1270 button
= rb
->button_get(false);
1271 } while (!button
|| button
& BUTTON_REL
||
1272 button
& BUTTON_REPEAT
);
1277 case 4: /* Load default levelset */
1279 if (!read_levels(true))
1280 return 5; /* Quit */
1287 case 6: /* Save & quit */
1288 save(SOKOBAN_SAVE_FILE
, false);
1289 rb
->reload_directory();
1292 } while (!menu_quit
);
1295 rb
->lcd_setfont(SOKOBAN_FONT
);
1298 rb
->screens
[i
]->clear_display();
1299 rb
->screens
[i
]->update();
1305 static bool sokoban_loop(void)
1308 int i
= 0, button
= 0, lastbutton
= 0;
1316 r
= current_info
.player
.row
;
1317 c
= current_info
.player
.col
;
1319 button
= rb
->button_get(true);
1323 #ifdef SOKOBAN_RC_MENU
1324 case SOKOBAN_RC_MENU
:
1327 switch (sokoban_menu()) {
1329 case 6: /* Save & quit */
1336 #ifdef SOKOBAN_UNDO_PRE
1337 if (lastbutton
!= SOKOBAN_UNDO_PRE
)
1339 #else /* repeat can't work here for Ondio, iPod, et al */
1340 case SOKOBAN_UNDO
| BUTTON_REPEAT
:
1343 rb
->lcd_clear_display();
1349 case SOKOBAN_REDO
| BUTTON_REPEAT
:
1351 rb
->lcd_clear_display();
1356 #ifdef SOKOBAN_LEVEL_UP
1357 case SOKOBAN_LEVEL_UP
:
1358 case SOKOBAN_LEVEL_UP
| BUTTON_REPEAT
:
1361 if (current_info
.level
.index
+ 1 < current_info
.max_level
)
1362 current_info
.level
.index
++;
1368 #ifdef SOKOBAN_LEVEL_DOWN
1369 case SOKOBAN_LEVEL_DOWN
:
1370 case SOKOBAN_LEVEL_DOWN
| BUTTON_REPEAT
:
1371 /* previous level */
1373 if (current_info
.level
.index
> 0)
1374 current_info
.level
.index
--;
1380 #ifdef SOKOBAN_LEVEL_REPEAT
1381 case SOKOBAN_LEVEL_REPEAT
:
1382 case SOKOBAN_LEVEL_REPEAT
| BUTTON_REPEAT
:
1390 case SOKOBAN_LEFT
| BUTTON_REPEAT
:
1391 moved
= move(SOKOBAN_MOVE_LEFT
, false);
1395 case SOKOBAN_RIGHT
| BUTTON_REPEAT
:
1396 moved
= move(SOKOBAN_MOVE_RIGHT
, false);
1400 case SOKOBAN_UP
| BUTTON_REPEAT
:
1401 moved
= move(SOKOBAN_MOVE_UP
, false);
1405 case SOKOBAN_DOWN
| BUTTON_REPEAT
:
1406 moved
= move(SOKOBAN_MOVE_DOWN
, false);
1410 if (rb
->default_event_handler(button
) == SYS_USB_CONNECTED
)
1411 return PLUGIN_USB_CONNECTED
;
1415 lastbutton
= button
;
1418 rb
->lcd_clear_display();
1422 /* We have completed this level */
1423 if (current_info
.level
.boxes_to_go
== 0) {
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",
1447 rb
->lcd_getstringsize(buf
, &w
, &h
);
1448 rb
->lcd_putsxy(LCD_WIDTH
/2 - w
/2, LCD_HEIGHT
/2 + h
*2, buf
);
1453 rb
->button_clear_queue();
1455 /* Display for 4 seconds or until new keypress */
1456 for (i
= 0; i
< 80; i
++) {
1458 button
= rb
->button_get(false);
1459 if (button
&& !(button
& BUTTON_REL
) &&
1460 !(button
& BUTTON_REPEAT
))
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);
1476 if ((loc
= rb
->strrchr(buffered_boards
.filename
,
1479 rb
->snprintf(buf
, sizeof(buf
), "%s.%d.sok",
1480 buffered_boards
.filename
,
1481 current_info
.level
.index
+ 1);
1486 if (!rb
->kbd_input(buf
, MAX_PATH
))
1489 rb
->splash(HZ
*2, "Solution too long to save");
1491 rb
->lcd_setfont(SOKOBAN_FONT
); /* Restore font */
1496 rb
->screens
[i
]->clear_display();
1497 rb
->screens
[i
]->update();
1500 current_info
.level
.index
++;
1502 /* clear undo stats */
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
);
1518 button
= rb
->button_get(false);
1519 if (button
&& !(button
& BUTTON_REL
))
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()) {
1529 case 6: /* Save & quit */
1544 enum plugin_status
plugin_start(struct plugin_api
* api
, void* parameter
)
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
);
1557 rb
->sleep(HZ
); /* Show title for 1 second */
1561 if (parameter
== NULL
) {
1562 /* Attempt to resume saved progress, otherwise start at beginning */
1563 if (!load(SOKOBAN_SAVE_FILE
, true)) {
1565 if (!read_levels(true))
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)
1580 rb
->lcd_clear_display();
1583 return sokoban_loop();