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 * 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 ****************************************************************************/
25 #include "lib/playback_control.h"
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
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)
54 #define COLS ((LCD_WIDTH-32)/SOKOBAN_TILESIZE)
56 #else /* vertical layout*/
57 #define ROWS ((LCD_HEIGHT-25)/SOKOBAN_TILESIZE)
58 #define COLS (LCD_WIDTH/SOKOBAN_TILESIZE)
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)
66 #define MAX_LEVEL_DATA 0x20000
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
76 #define MAX_UNDOS (PLUGIN_BUFFER_SIZE - MAX_LEVEL_DATA - 0x3000)
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_FUZE_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_HOME|BUTTON_REPEAT)
229 #define SOKOBAN_UNDO_PRE BUTTON_SELECT
230 #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
231 #define SOKOBAN_REDO (BUTTON_SELECT | BUTTON_LEFT)
232 #define SOKOBAN_LEVEL_DOWN (BUTTON_SELECT | BUTTON_DOWN)
233 #define SOKOBAN_LEVEL_REPEAT (BUTTON_SELECT | BUTTON_RIGHT)
234 #define SOKOBAN_LEVEL_UP (BUTTON_SELECT | BUTTON_UP)
235 #define SOKOBAN_PAUSE BUTTON_SELECT
236 #define BUTTON_SAVE BUTTON_SELECT
237 #define BUTTON_SAVE_NAME "SELECT"
239 #elif CONFIG_KEYPAD == SANSA_C200_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_POWER
245 #define SOKOBAN_UNDO_PRE BUTTON_SELECT
246 #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
247 #define SOKOBAN_REDO BUTTON_REC
248 #define SOKOBAN_LEVEL_DOWN BUTTON_VOL_DOWN
249 #define SOKOBAN_LEVEL_REPEAT (BUTTON_SELECT | BUTTON_RIGHT)
250 #define SOKOBAN_LEVEL_UP BUTTON_VOL_UP
251 #define SOKOBAN_PAUSE BUTTON_SELECT
252 #define BUTTON_SAVE BUTTON_SELECT
253 #define BUTTON_SAVE_NAME "SELECT"
255 #elif CONFIG_KEYPAD == SANSA_CLIP_PAD
256 #define SOKOBAN_LEFT BUTTON_LEFT
257 #define SOKOBAN_RIGHT BUTTON_RIGHT
258 #define SOKOBAN_UP BUTTON_UP
259 #define SOKOBAN_DOWN BUTTON_DOWN
260 #define SOKOBAN_MENU BUTTON_POWER
261 #define SOKOBAN_UNDO_PRE BUTTON_SELECT
262 #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
263 #define SOKOBAN_REDO BUTTON_HOME
264 #define SOKOBAN_LEVEL_DOWN BUTTON_VOL_DOWN
265 #define SOKOBAN_LEVEL_REPEAT (BUTTON_SELECT | BUTTON_RIGHT)
266 #define SOKOBAN_LEVEL_UP BUTTON_VOL_UP
267 #define SOKOBAN_PAUSE BUTTON_SELECT
268 #define BUTTON_SAVE BUTTON_SELECT
269 #define BUTTON_SAVE_NAME "SELECT"
271 #elif CONFIG_KEYPAD == SANSA_M200_PAD
272 #define SOKOBAN_LEFT BUTTON_LEFT
273 #define SOKOBAN_RIGHT BUTTON_RIGHT
274 #define SOKOBAN_UP BUTTON_UP
275 #define SOKOBAN_DOWN BUTTON_DOWN
276 #define SOKOBAN_MENU BUTTON_POWER
277 #define SOKOBAN_UNDO_PRE BUTTON_SELECT
278 #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
279 #define SOKOBAN_REDO (BUTTON_SELECT | BUTTON_UP)
280 #define SOKOBAN_LEVEL_DOWN BUTTON_VOL_DOWN
281 #define SOKOBAN_LEVEL_REPEAT (BUTTON_SELECT | BUTTON_RIGHT)
282 #define SOKOBAN_LEVEL_UP BUTTON_VOL_UP
283 #define SOKOBAN_PAUSE BUTTON_SELECT
284 #define BUTTON_SAVE BUTTON_SELECT
285 #define BUTTON_SAVE_NAME "SELECT"
287 #elif CONFIG_KEYPAD == GIGABEAT_S_PAD
288 #define SOKOBAN_LEFT BUTTON_LEFT
289 #define SOKOBAN_RIGHT BUTTON_RIGHT
290 #define SOKOBAN_UP BUTTON_UP
291 #define SOKOBAN_DOWN BUTTON_DOWN
292 #define SOKOBAN_MENU BUTTON_MENU
293 #define SOKOBAN_UNDO BUTTON_VOL_UP
294 #define SOKOBAN_REDO BUTTON_VOL_DOWN
295 #define SOKOBAN_LEVEL_DOWN BUTTON_PREV
296 #define SOKOBAN_LEVEL_REPEAT BUTTON_PLAY
297 #define SOKOBAN_LEVEL_UP BUTTON_NEXT
298 #define SOKOBAN_PAUSE BUTTON_SELECT
299 #define BUTTON_SAVE BUTTON_SELECT
300 #define BUTTON_SAVE_NAME "SELECT"
302 #elif CONFIG_KEYPAD == MROBE100_PAD
303 #define SOKOBAN_LEFT BUTTON_LEFT
304 #define SOKOBAN_RIGHT BUTTON_RIGHT
305 #define SOKOBAN_UP BUTTON_UP
306 #define SOKOBAN_DOWN BUTTON_DOWN
307 #define SOKOBAN_MENU BUTTON_POWER
308 #define SOKOBAN_UNDO BUTTON_SELECT
309 #define SOKOBAN_REDO BUTTON_MENU
310 #define SOKOBAN_LEVEL_DOWN (BUTTON_DISPLAY | BUTTON_DOWN)
311 #define SOKOBAN_LEVEL_REPEAT (BUTTON_DISPLAY | BUTTON_RIGHT)
312 #define SOKOBAN_LEVEL_UP (BUTTON_DISPLAY | BUTTON_UP)
313 #define SOKOBAN_PAUSE BUTTON_SELECT
314 #define BUTTON_SAVE BUTTON_SELECT
315 #define BUTTON_SAVE_NAME "SELECT"
317 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
318 #define SOKOBAN_LEFT BUTTON_RC_REW
319 #define SOKOBAN_RIGHT BUTTON_RC_FF
320 #define SOKOBAN_UP BUTTON_RC_VOL_UP
321 #define SOKOBAN_DOWN BUTTON_RC_VOL_DOWN
322 #define SOKOBAN_MENU BUTTON_RC_REC
323 #define SOKOBAN_UNDO BUTTON_RC_MODE
324 #define SOKOBAN_REDO BUTTON_RC_MENU
325 #define SOKOBAN_PAUSE BUTTON_RC_PLAY
326 #define BUTTON_SAVE BUTTON_RC_PLAY
327 #define BUTTON_SAVE_NAME "PLAY"
329 #define SOKOBAN_RC_MENU BUTTON_REC
331 #elif CONFIG_KEYPAD == COWOND2_PAD
332 #define SOKOBAN_MENU BUTTON_MENU
333 #define SOKOBAN_MENU_NAME "[MENU]"
335 #elif CONFIG_KEYPAD == IAUDIO67_PAD
336 #define SOKOBAN_LEFT BUTTON_LEFT
337 #define SOKOBAN_RIGHT BUTTON_RIGHT
338 #define SOKOBAN_UP BUTTON_STOP
339 #define SOKOBAN_DOWN BUTTON_PLAY
340 #define SOKOBAN_MENU BUTTON_MENU
341 #define SOKOBAN_UNDO BUTTON_VOLDOWN
342 #define SOKOBAN_REDO BUTTON_VOLUP
343 #define SOKOBAN_PAUSE (BUTTON_MENU|BUTTON_LEFT)
344 #define BUTTON_SAVE (BUTTON_MENU|BUTTON_PLAY)
345 #define BUTTON_SAVE_NAME "MENU+PLAY"
347 #define SOKOBAN_RC_MENU (BUTTON_MENU|BUTTON_STOP)
349 #elif CONFIG_KEYPAD == CREATIVEZVM_PAD
350 #define SOKOBAN_LEFT BUTTON_LEFT
351 #define SOKOBAN_RIGHT BUTTON_RIGHT
352 #define SOKOBAN_UP BUTTON_UP
353 #define SOKOBAN_DOWN BUTTON_DOWN
354 #define SOKOBAN_MENU BUTTON_MENU
355 #define SOKOBAN_UNDO BUTTON_BACK
356 #define SOKOBAN_REDO (BUTTON_BACK | BUTTON_PLAY)
357 #define SOKOBAN_LEVEL_DOWN (BUTTON_SELECT | BUTTON_DOWN)
358 #define SOKOBAN_LEVEL_REPEAT (BUTTON_SELECT | BUTTON_RIGHT)
359 #define SOKOBAN_LEVEL_UP (BUTTON_SELECT | BUTTON_UP)
360 #define SOKOBAN_PAUSE BUTTON_PLAY
361 #define BUTTON_SAVE BUTTON_CUSTOM
362 #define BUTTON_SAVE_NAME "CUSTOM"
364 #elif CONFIG_KEYPAD == PHILIPS_HDD1630_PAD
365 #define SOKOBAN_LEFT BUTTON_LEFT
366 #define SOKOBAN_RIGHT BUTTON_RIGHT
367 #define SOKOBAN_UP BUTTON_UP
368 #define SOKOBAN_DOWN BUTTON_DOWN
369 #define SOKOBAN_MENU BUTTON_MENU
370 #define SOKOBAN_UNDO BUTTON_VIEW
371 #define SOKOBAN_REDO (BUTTON_SELECT | BUTTON_VIEW)
372 #define SOKOBAN_LEVEL_DOWN BUTTON_VOL_DOWN
373 #define SOKOBAN_LEVEL_REPEAT BUTTON_POWER
374 #define SOKOBAN_LEVEL_UP BUTTON_VOL_UP
375 #define SOKOBAN_PAUSE BUTTON_SELECT
376 #define BUTTON_SAVE BUTTON_PLAYLIST
377 #define BUTTON_SAVE_NAME "PLAYLIST"
379 #elif CONFIG_KEYPAD == ONDAVX747_PAD
380 #define SOKOBAN_MENU BUTTON_MENU
381 #define SOKOBAN_MENU_NAME "[MENU]"
383 #elif CONFIG_KEYPAD == ONDAVX777_PAD
384 #define SOKOBAN_MENU BUTTON_POWER
385 #define SOKOBAN_MENU_NAME "[POWER]"
387 #elif CONFIG_KEYPAD == MROBE500_PAD
389 #define SOKOBAN_MENU BUTTON_POWER
390 #define SOKOBAN_MENU_NAME "[POWER]"
392 #elif CONFIG_KEYPAD == SAMSUNG_YH_PAD
393 #define SOKOBAN_LEFT BUTTON_LEFT
394 #define SOKOBAN_RIGHT BUTTON_RIGHT
395 #define SOKOBAN_UP BUTTON_UP
396 #define SOKOBAN_DOWN BUTTON_DOWN
397 #define SOKOBAN_MENU BUTTON_REC
398 #define SOKOBAN_UNDO_PRE BUTTON_REW
399 #define SOKOBAN_UNDO (BUTTON_REW | BUTTON_LEFT)
400 #define SOKOBAN_REDO BUTTON_FFWD
401 #define SOKOBAN_LEVEL_DOWN (BUTTON_PLAY | BUTTON_DOWN)
402 #define SOKOBAN_LEVEL_REPEAT (BUTTON_PLAY | BUTTON_RIGHT)
403 #define SOKOBAN_LEVEL_UP (BUTTON_PLAY | BUTTON_UP)
404 #define SOKOBAN_PAUSE BUTTON_PLAY
405 #define BUTTON_SAVE BUTTON_PLAY
406 #define BUTTON_SAVE_NAME "PLAY"
409 #error No keymap defined!
412 #ifdef HAVE_TOUCHSCREEN
414 #define SOKOBAN_LEFT BUTTON_MIDLEFT
416 #ifndef SOKOBAN_RIGHT
417 #define SOKOBAN_RIGHT BUTTON_MIDRIGHT
420 #define SOKOBAN_UP BUTTON_TOPMIDDLE
423 #define SOKOBAN_DOWN BUTTON_BOTTOMMIDDLE
426 #define SOKOBAN_MENU BUTTON_TOPLEFT
427 #define SOKOBAN_MENU_NAME "[TOPLEFT]"
430 #define SOKOBAN_UNDO BUTTON_BOTTOMRIGHT
431 #define SOKOBAN_UNDO_NAME "[BOTTOMRIGHT]"
434 #define SOKOBAN_REDO BUTTON_BOTTOMLEFT
435 #define SOKOBAN_REDO_NAME "[BOTTOMLEFT]"
437 #ifndef SOKOBAN_PAUSE
438 #define SOKOBAN_PAUSE BUTTON_CENTER
439 #define SOKOBAN_PAUSE_NAME "[CENTER]"
441 #ifndef SOKOBAN_LEVEL_REPEAT
442 #define SOKOBAN_LEVEL_REPEAT BUTTON_TOPRIGHT
443 #define SOKOBAN_LEVEL_REPEAT_NAME "[TOPRIGHT]"
446 #define BUTTON_SAVE BUTTON_CENTER
447 #define BUTTON_SAVE_NAME "CENTER"
451 #define SOKOBAN_FONT FONT_SYSFIXED
454 /* The Location, Undo and LevelInfo structs are OO-flavored.
455 * (oooh!-flavored as Schnueff puts it.) It makes more you have to know,
456 * but the overall data layout becomes more manageable. */
458 /* Level data & stats */
460 int index
; /* Level index (level number - 1) */
461 int moves
; /* Moves & pushes for the stats */
463 short boxes_to_go
; /* Number of unplaced boxes remaining in level */
464 short height
; /* Height & width for centering level display */
473 /* Our full undo history */
474 static struct UndoInfo
{
475 int count
; /* How many undos have been done */
476 int current
; /* Which history is the current undo */
477 int max
; /* Which history is the max redoable */
478 char history
[MAX_UNDOS
];
481 /* Our playing board */
482 static struct BoardInfo
{
483 char board
[ROWS
][COLS
]; /* The current board data */
484 struct LevelInfo level
; /* Level data & stats */
485 struct Location player
; /* Where the player is */
486 int max_level
; /* The number of levels we have */
489 static struct BufferedBoards
{
490 char filename
[MAX_PATH
]; /* Filename of the levelset we're using */
491 char data
[MAX_LEVEL_DATA
]; /* Buffered level data */
492 int index
[MAX_LEVELS
+ 1]; /* Where each buffered board begins & ends */
493 int start
; /* Index of first buffered board */
494 int end
; /* Index of last buffered board */
495 short prebuffered_boards
; /* Number of boards before current to store */
499 static char buf
[ROWS
*(COLS
+ 1)]; /* Enough for a whole board or a filename */
502 static void init_undo(void)
505 undo_info
.current
= 0;
509 static void get_delta(char direction
, short *d_r
, short *d_c
)
512 case SOKOBAN_PUSH_LEFT
:
513 case SOKOBAN_MOVE_LEFT
:
517 case SOKOBAN_PUSH_RIGHT
:
518 case SOKOBAN_MOVE_RIGHT
:
522 case SOKOBAN_PUSH_UP
:
523 case SOKOBAN_MOVE_UP
:
527 case SOKOBAN_PUSH_DOWN
:
528 case SOKOBAN_MOVE_DOWN
:
534 static bool undo(void)
538 short d_r
= 0, d_c
= 0; /* delta row & delta col */
539 char *space_cur
, *space_next
, *space_prev
;
540 bool undo_push
= false;
542 /* If no more undos or we've wrapped all the way around, quit */
543 if (undo_info
.count
== 0 || undo_info
.current
- 1 == undo_info
.max
)
546 /* Move to previous undo in the list */
547 if (undo_info
.current
== 0 && undo_info
.count
> 1)
548 undo_info
.current
= MAX_UNDOS
- 1;
554 undo
= undo_info
.history
[undo_info
.current
];
556 if (undo
< SOKOBAN_MOVE_MIN
)
559 get_delta(undo
, &d_r
, &d_c
);
561 r
= current_info
.player
.row
;
562 c
= current_info
.player
.col
;
564 /* Give the 3 spaces we're going to use better names */
565 space_cur
= ¤t_info
.board
[r
][c
];
566 space_next
= ¤t_info
.board
[r
+ d_r
][c
+ d_c
];
567 space_prev
= ¤t_info
.board
[r
- d_r
][c
- d_c
];
569 /* Update board info */
571 /* Moving box from goal to floor */
572 if (*space_next
== '*' && *space_cur
== '@')
573 current_info
.level
.boxes_to_go
++;
574 /* Moving box from floor to goal */
575 else if (*space_next
== '$' && *space_cur
== '+')
576 current_info
.level
.boxes_to_go
--;
578 /* Move box off of next space... */
579 *space_next
= (*space_next
== '*' ? '.' : ' ');
580 /* ...and on to current space */
581 *space_cur
= (*space_cur
== '+' ? '*' : '$');
583 current_info
.level
.pushes
--;
585 /* Just move player off of current space */
586 *space_cur
= (*space_cur
== '+' ? '.' : ' ');
587 /* Move player back to previous space */
588 *space_prev
= (*space_prev
== '.' ? '+' : '@');
590 /* Update position */
591 current_info
.player
.row
-= d_r
;
592 current_info
.player
.col
-= d_c
;
594 current_info
.level
.moves
--;
599 static void add_undo(char undo
)
601 undo_info
.history
[undo_info
.current
] = undo
;
603 /* Wrap around if MAX_UNDOS exceeded */
604 if (undo_info
.current
< (MAX_UNDOS
- 1))
607 undo_info
.current
= 0;
609 if (undo_info
.count
< MAX_UNDOS
)
613 static bool move(char direction
, bool redo
)
616 short d_r
= 0, d_c
= 0; /* delta row & delta col */
617 char *space_cur
, *space_next
, *space_beyond
;
620 get_delta(direction
, &d_r
, &d_c
);
622 r
= current_info
.player
.row
;
623 c
= current_info
.player
.col
;
625 /* Check for out-of-bounds */
626 if (r
+ 2*d_r
< 0 || r
+ 2*d_r
>= ROWS
||
627 c
+ 2*d_c
< 0 || c
+ 2*d_c
>= COLS
)
630 /* Give the 3 spaces we're going to use better names */
631 space_cur
= ¤t_info
.board
[r
][c
];
632 space_next
= ¤t_info
.board
[r
+ d_r
][c
+ d_c
];
633 space_beyond
= ¤t_info
.board
[r
+ 2*d_r
][c
+ 2*d_c
];
635 if (*space_next
== '$' || *space_next
== '*') {
636 /* Change direction from move to push for undo */
637 if (direction
>= SOKOBAN_MOVE_MIN
)
638 direction
-= SOKOBAN_MOVE_DIFF
;
641 else if (direction
< SOKOBAN_MOVE_MIN
)
642 /* Change back to move if redo/solution playback push is invalid */
643 direction
+= SOKOBAN_MOVE_DIFF
;
645 /* Update board info */
647 /* Moving box from goal to floor */
648 if (*space_next
== '*' && *space_beyond
== ' ')
649 current_info
.level
.boxes_to_go
++;
650 /* Moving box from floor to goal */
651 else if (*space_next
== '$' && *space_beyond
== '.')
652 current_info
.level
.boxes_to_go
--;
653 /* Check for invalid move */
654 else if (*space_beyond
!= '.' && *space_beyond
!= ' ')
657 /* Move player onto next space */
658 *space_next
= (*space_next
== '*' ? '+' : '@');
659 /* Move box onto space beyond next */
660 *space_beyond
= (*space_beyond
== '.' ? '*' : '$');
662 current_info
.level
.pushes
++;
664 /* Check for invalid move */
665 if (*space_next
!= '.' && *space_next
!= ' ')
668 /* Move player onto next space */
669 *space_next
= (*space_next
== '.' ? '+' : '@');
671 /* Move player off of current space */
672 *space_cur
= (*space_cur
== '+' ? '.' : ' ');
674 /* Update position */
675 current_info
.player
.row
+= d_r
;
676 current_info
.player
.col
+= d_c
;
678 current_info
.level
.moves
++;
680 /* Update undo_info.max to current on every normal move,
681 * except if it's the same as a redo. */
682 /* normal move and either */
684 /* moves have been undone... */
685 ((undo_info
.max
!= undo_info
.current
&&
686 /* ...and the current move is NOT the same as the one in history */
687 undo_info
.history
[undo_info
.current
] != direction
) ||
688 /* or moves have not been undone */
689 undo_info
.max
== undo_info
.current
)) {
691 undo_info
.max
= undo_info
.current
;
692 } else /* redo move or move was same as redo */
693 add_undo(direction
); /* add_undo to update current */
699 static bool redo(void)
701 /* If no moves have been undone, quit */
702 if (undo_info
.current
== undo_info
.max
)
705 return move(undo_info
.history
[(undo_info
.current
< MAX_UNDOS
?
706 undo_info
.current
: 0)], true);
710 static void init_boards(void)
712 rb
->strlcpy(buffered_boards
.filename
, SOKOBAN_LEVELS_FILE
,
713 sizeof(buffered_boards
.filename
));
715 current_info
.level
.index
= 0;
716 current_info
.player
.row
= 0;
717 current_info
.player
.col
= 0;
718 current_info
.max_level
= 0;
720 buffered_boards
.start
= 0;
721 buffered_boards
.end
= 0;
722 buffered_boards
.prebuffered_boards
= 0;
727 static bool read_levels(bool initialize
)
737 bool index_set
= false;
739 /* Get the index of the first level to buffer */
740 if (current_info
.level
.index
> buffered_boards
.prebuffered_boards
&&
742 buffered_boards
.start
= current_info
.level
.index
-
743 buffered_boards
.prebuffered_boards
;
745 buffered_boards
.start
= 0;
747 if ((fd
= rb
->open(buffered_boards
.filename
, O_RDONLY
)) < 0) {
748 rb
->splashf(HZ
*2, "Unable to open %s", buffered_boards
.filename
);
753 len
= rb
->read_line(fd
, buf
, sizeof(buf
));
755 /* Correct len when trailing \r's or \n's are counted */
756 if (len
> 2 && buf
[len
- 2] == '\0')
758 else if (len
> 1 && buf
[len
- 1] == '\0')
761 /* Skip short lines & lines with non-level data */
762 if (len
>= 3 && ((buf
[0] >= '1' && buf
[0] <= '9') || buf
[0] == '#' ||
763 buf
[0] == ' ' || buf
[0] == '-' || buf
[0] == '_')) {
764 if (level_count
>= buffered_boards
.start
) {
765 /* Set the index of this level */
767 level_count
- buffered_boards
.start
< MAX_LEVELS
) {
768 buffered_boards
.index
[level_count
- buffered_boards
.start
]
772 /* Copy buffer to board data */
773 if (i
+ level_len
+ len
< MAX_LEVEL_DATA
) {
774 rb
->memcpy(&buffered_boards
.data
[i
+ level_len
], buf
, len
);
775 buffered_boards
.data
[i
+ level_len
+ len
] = '\n';
778 level_len
+= len
+ 1;
781 /* If newline & level is tall enough or is RLE */
782 } else if (buf
[0] == '\0' && (row
> 2 || lastlen
> 22)) {
784 if (level_count
>= buffered_boards
.start
) {
786 if (i
< MAX_LEVEL_DATA
)
787 buffered_boards
.end
= level_count
;
788 else if (!initialize
)
798 } while ((lastlen
= len
));
800 /* Set the index of the end of the last level */
801 if (level_count
- buffered_boards
.start
< MAX_LEVELS
)
802 buffered_boards
.index
[level_count
- buffered_boards
.start
] = i
;
805 current_info
.max_level
= level_count
;
806 buffered_boards
.prebuffered_boards
= buffered_boards
.end
/2;
814 static void load_level(void)
819 int index
= current_info
.level
.index
- buffered_boards
.start
;
822 /* Get the buffered board index of the current level */
823 if (current_info
.level
.index
< buffered_boards
.start
||
824 current_info
.level
.index
>= buffered_boards
.end
) {
826 if (current_info
.level
.index
> buffered_boards
.prebuffered_boards
)
827 index
= buffered_boards
.prebuffered_boards
;
829 index
= current_info
.level
.index
;
831 level
= &buffered_boards
.data
[buffered_boards
.index
[index
]];
833 /* Reset level info */
834 current_info
.level
.moves
= 0;
835 current_info
.level
.pushes
= 0;
836 current_info
.level
.boxes_to_go
= 0;
837 current_info
.level
.width
= 0;
840 for (r
= 0; r
< ROWS
; r
++)
841 for (c
= 0; c
< COLS
; c
++)
842 current_info
.board
[r
][c
] = 'X';
844 level_size
= buffered_boards
.index
[index
+ 1] -
845 buffered_boards
.index
[index
];
847 for (r
= 0, c
= 0, n
= 1, i
= 0; i
< level_size
; i
++) {
848 if (level
[i
] == '\n' || level
[i
] == '|') {
850 /* Update max width of level & go to next row */
851 if (c
> current_info
.level
.width
)
852 current_info
.level
.width
= c
;
858 } else if (c
< COLS
) {
859 /* Read RLE character's length into n */
860 if (level
[i
] >= '0' && level
[i
] <= '9') {
861 n
= level
[i
++] - '0';
862 if (level
[i
] >= '0' && level
[i
] <= '9')
863 n
= n
*10 + level
[i
++] - '0';
866 /* Cleanup & replace */
869 else if (level
[i
] == '-' || level
[i
] == '_')
877 current_info
.level
.boxes_to_go
+= n
;
879 /* Put RLE character n times */
881 current_info
.board
[r
][c
++] = level
[i
];
885 if (level
[i
] == '.' || level
[i
] == '+')
886 current_info
.level
.boxes_to_go
++;
888 if (level
[i
] == '@' ||level
[i
] == '+') {
889 current_info
.player
.row
= r
;
890 current_info
.player
.col
= c
;
893 current_info
.board
[r
][c
++] = level
[i
];
898 current_info
.level
.height
= r
;
901 /* Fill in blank space outside level on color targets */
902 for (r
= 0; r
< ROWS
; r
++)
903 for (c
= 0; current_info
.board
[r
][c
] == ' ' && c
< COLS
; c
++)
904 current_info
.board
[r
][c
] = 'X';
906 for (c
= 0; c
< COLS
; c
++) {
907 for (r
= 0; (current_info
.board
[r
][c
] == ' ' ||
908 current_info
.board
[r
][c
] == 'X') && r
< ROWS
; r
++)
909 current_info
.board
[r
][c
] = 'X';
910 for (r
= ROWS
- 1; (current_info
.board
[r
][c
] == ' ' ||
911 current_info
.board
[r
][c
] == 'X') && r
>= 0; r
--)
912 current_info
.board
[r
][c
] = 'X';
917 static void update_screen(void)
922 #if LCD_WIDTH - (COLS*SOKOBAN_TILESIZE) < 32
923 #define STAT_HEIGHT 25
924 #define STAT_X (LCD_WIDTH - 120)/2
925 #define STAT_Y (LCD_HEIGHT - STAT_HEIGHT)
926 #define BOARD_WIDTH LCD_WIDTH
927 #define BOARD_HEIGHT (LCD_HEIGHT - STAT_HEIGHT)
928 rb
->lcd_putsxy(STAT_X
+ 4, STAT_Y
+ 4, "Level");
929 rb
->snprintf(buf
, sizeof(buf
), "%d", current_info
.level
.index
+ 1);
930 rb
->lcd_putsxy(STAT_X
+ 7, STAT_Y
+ 14, buf
);
931 rb
->lcd_putsxy(STAT_X
+ 41, STAT_Y
+ 4, "Moves");
932 rb
->snprintf(buf
, sizeof(buf
), "%d", current_info
.level
.moves
);
933 rb
->lcd_putsxy(STAT_X
+ 44, STAT_Y
+ 14, buf
);
934 rb
->lcd_putsxy(STAT_X
+ 79, STAT_Y
+ 4, "Pushes");
935 rb
->snprintf(buf
, sizeof(buf
), "%d", current_info
.level
.pushes
);
936 rb
->lcd_putsxy(STAT_X
+ 82, STAT_Y
+ 14, buf
);
938 rb
->lcd_drawrect(STAT_X
, STAT_Y
, 38, STAT_HEIGHT
);
939 rb
->lcd_drawrect(STAT_X
+ 37, STAT_Y
, 39, STAT_HEIGHT
);
940 rb
->lcd_drawrect(STAT_X
+ 75, STAT_Y
, 45, STAT_HEIGHT
);
942 #if LCD_WIDTH - (COLS*SOKOBAN_TILESIZE) > 40
943 #define STAT_X (LCD_WIDTH - 40)
945 #define STAT_X COLS*SOKOBAN_TILESIZE
947 #define STAT_Y (LCD_HEIGHT - 64)/2
948 #define STAT_WIDTH (LCD_WIDTH - STAT_X)
949 #define BOARD_WIDTH (LCD_WIDTH - STAT_WIDTH)
950 #define BOARD_HEIGHT LCD_HEIGHT
951 rb
->lcd_putsxy(STAT_X
+ 1, STAT_Y
+ 2, "Level");
952 rb
->snprintf(buf
, sizeof(buf
), "%d", current_info
.level
.index
+ 1);
953 rb
->lcd_putsxy(STAT_X
+ 4, STAT_Y
+ 12, buf
);
954 rb
->lcd_putsxy(STAT_X
+ 1, STAT_Y
+ 23, "Moves");
955 rb
->snprintf(buf
, sizeof(buf
), "%d", current_info
.level
.moves
);
956 rb
->lcd_putsxy(STAT_X
+ 4, STAT_Y
+ 33, buf
);
958 rb
->lcd_putsxy(STAT_X
+ 1, STAT_Y
+ 44, "Push");
960 rb
->lcd_putsxy(STAT_X
+ 1, STAT_Y
+ 44, "Pushes");
962 rb
->snprintf(buf
, sizeof(buf
), "%d", current_info
.level
.pushes
);
963 rb
->lcd_putsxy(STAT_X
+ 4, STAT_Y
+ 54, buf
);
965 rb
->lcd_drawrect(STAT_X
, STAT_Y
+ 0, STAT_WIDTH
, 64);
966 rb
->lcd_hline(STAT_X
, LCD_WIDTH
- 1, STAT_Y
+ 21);
967 rb
->lcd_hline(STAT_X
, LCD_WIDTH
- 1, STAT_Y
+ 42);
971 /* load the board to the screen */
972 for (rows
= 0; rows
< ROWS
; rows
++) {
973 for (cols
= 0; cols
< COLS
; cols
++) {
974 c
= cols
*SOKOBAN_TILESIZE
+
975 (BOARD_WIDTH
- current_info
.level
.width
*SOKOBAN_TILESIZE
)/2;
976 r
= rows
*SOKOBAN_TILESIZE
+
977 (BOARD_HEIGHT
- current_info
.level
.height
*SOKOBAN_TILESIZE
)/2;
979 switch(current_info
.board
[rows
][cols
]) {
980 case 'X': /* blank space outside of level */
983 case ' ': /* floor */
984 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 0*SOKOBAN_TILESIZE
,
986 BMPWIDTH_sokoban_tiles
,
987 BMPHEIGHT_sokoban_tiles
),
988 c
, r
, SOKOBAN_TILESIZE
, SOKOBAN_TILESIZE
);
992 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 1*SOKOBAN_TILESIZE
,
994 BMPWIDTH_sokoban_tiles
,
995 BMPHEIGHT_sokoban_tiles
),
996 c
, r
, SOKOBAN_TILESIZE
, SOKOBAN_TILESIZE
);
1000 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 2*SOKOBAN_TILESIZE
,
1001 STRIDE( SCREEN_MAIN
,
1002 BMPWIDTH_sokoban_tiles
,
1003 BMPHEIGHT_sokoban_tiles
),
1004 c
, r
, SOKOBAN_TILESIZE
,SOKOBAN_TILESIZE
);
1007 case '*': /* box on goal */
1008 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 3*SOKOBAN_TILESIZE
,
1009 STRIDE( SCREEN_MAIN
,
1010 BMPWIDTH_sokoban_tiles
,
1011 BMPHEIGHT_sokoban_tiles
),
1012 c
, r
, SOKOBAN_TILESIZE
, SOKOBAN_TILESIZE
);
1015 case '.': /* goal */
1016 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 4*SOKOBAN_TILESIZE
,
1017 STRIDE( SCREEN_MAIN
,
1018 BMPWIDTH_sokoban_tiles
,
1019 BMPHEIGHT_sokoban_tiles
),
1020 c
, r
, SOKOBAN_TILESIZE
, SOKOBAN_TILESIZE
);
1023 case '@': /* player */
1024 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 5*SOKOBAN_TILESIZE
,
1025 STRIDE( SCREEN_MAIN
,
1026 BMPWIDTH_sokoban_tiles
,
1027 BMPHEIGHT_sokoban_tiles
),
1028 c
, r
, SOKOBAN_TILESIZE
, SOKOBAN_TILESIZE
);
1031 case '+': /* player on goal */
1032 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 6*SOKOBAN_TILESIZE
,
1033 STRIDE( SCREEN_MAIN
,
1034 BMPWIDTH_sokoban_tiles
,
1035 BMPHEIGHT_sokoban_tiles
),
1036 c
, r
, SOKOBAN_TILESIZE
, SOKOBAN_TILESIZE
);
1042 /* print out the screen */
1046 static void draw_level(void)
1049 rb
->lcd_clear_display();
1053 static bool save(char *filename
, bool solution
)
1058 char dirname
[MAX_PATH
];
1060 rb
->splash(0, "Saving...");
1062 /* Create dir if it doesn't exist */
1063 if ((loc
= rb
->strrchr(filename
, '/')) != NULL
) {
1064 rb
->strlcpy(dirname
, filename
, loc
- filename
+ 1);
1066 if(!(dir
= rb
->opendir(dirname
)))
1072 if (filename
[0] == '\0' ||
1073 (fd
= rb
->open(filename
, O_WRONLY
|O_CREAT
|O_TRUNC
)) < 0) {
1074 rb
->splashf(HZ
*2, "Unable to open %s", filename
);
1078 /* Sokoban: S/P for solution/progress : level number : current undo */
1079 rb
->snprintf(buf
, sizeof(buf
), "Sokoban:%c:%d:%d\n", (solution
? 'S' : 'P'),
1080 current_info
.level
.index
+ 1, undo_info
.current
);
1081 rb
->write(fd
, buf
, rb
->strlen(buf
));
1083 /* Filename of levelset */
1084 rb
->write(fd
, buffered_boards
.filename
,
1085 rb
->strlen(buffered_boards
.filename
));
1086 rb
->write(fd
, "\n", 1);
1088 /* Full undo history */
1089 rb
->write(fd
, undo_info
.history
, undo_info
.max
);
1096 static bool load(char *filename
, bool silent
)
1103 bool paused
= false;
1104 unsigned short speed
= 2;
1105 int delay
[] = {HZ
/2, HZ
/3, HZ
/4, HZ
/6, HZ
/8, HZ
/12, HZ
/16, HZ
/25};
1107 if (filename
[0] == '\0' || (fd
= rb
->open(filename
, O_RDONLY
)) < 0) {
1109 rb
->splashf(HZ
*2, "Unable to open %s", filename
);
1113 /* Read header, level number, & current undo */
1114 rb
->read_line(fd
, buf
, sizeof(buf
));
1116 /* If we're opening a level file, not a solution/progress file */
1117 if (rb
->strncmp(buf
, "Sokoban", 7) != 0) {
1120 rb
->strlcpy(buffered_boards
.filename
, filename
,
1121 sizeof(buffered_boards
.filename
));
1123 if (!read_levels(true))
1126 current_info
.level
.index
= 0;
1129 /* If there aren't any boxes to go or the player position wasn't set,
1130 * the file probably wasn't a Sokoban level file */
1131 if (current_info
.level
.boxes_to_go
== 0 ||
1132 current_info
.player
.row
== 0 || current_info
.player
.col
== 0) {
1134 rb
->splash(HZ
*2, "File is not a Sokoban level file");
1140 /* Read filename of levelset */
1141 rb
->read_line(fd
, buffered_boards
.filename
,
1142 sizeof(buffered_boards
.filename
));
1144 /* Read full undo history */
1145 len
= rb
->read_line(fd
, undo_info
.history
, MAX_UNDOS
);
1147 /* Correct len when trailing \r's or \n's are counted */
1148 if (len
> 2 && undo_info
.history
[len
- 2] == '\0')
1150 else if (len
> 1 && undo_info
.history
[len
- 1] == '\0')
1155 /* Check to see if we're going to play a solution or resume progress */
1156 play_solution
= (buf
[8] == 'S');
1158 /* Get level number */
1159 for (n
= 0, i
= 10; buf
[i
] >= '0' && buf
[i
] <= '9' && i
< 15; i
++)
1160 n
= n
*10 + buf
[i
] - '0';
1161 current_info
.level
.index
= n
- 1;
1163 /* Get undo index */
1164 for (n
= 0, i
++; buf
[i
] >= '0' && buf
[i
] <= '9' && i
< 21; i
++)
1165 n
= n
*10 + buf
[i
] - '0';
1168 undo_info
.max
= len
;
1170 if (current_info
.level
.index
< 0) {
1172 rb
->splash(HZ
*2, "Error loading level");
1175 if (!read_levels(true))
1177 if (current_info
.level
.index
>= current_info
.max_level
) {
1179 rb
->splash(HZ
*2, "Error loading level");
1185 if (play_solution
) {
1186 rb
->lcd_clear_display();
1188 rb
->sleep(2*delay
[speed
]);
1190 /* Replay solution until menu button is pressed */
1194 if (!move(undo_info
.history
[i
], true)) {
1198 rb
->lcd_clear_display();
1204 rb
->sleep(delay
[speed
]);
1206 while ((button
= rb
->button_get(false)) || paused
) {
1209 /* Pretend the level is complete so we'll quit */
1210 current_info
.level
.boxes_to_go
= 0;
1214 /* Toggle pause state */
1219 case SOKOBAN_LEFT
| BUTTON_REPEAT
:
1220 /* Go back one move */
1224 rb
->lcd_clear_display();
1230 case SOKOBAN_RIGHT
| BUTTON_REPEAT
:
1231 /* Go forward one move */
1235 rb
->lcd_clear_display();
1241 case SOKOBAN_UP
| BUTTON_REPEAT
:
1243 if (speed
< sizeof(delay
)/sizeof(int) - 1)
1248 case SOKOBAN_DOWN
| BUTTON_REPEAT
:
1259 /* If level is complete, wait for keypress before quitting */
1260 if (current_info
.level
.boxes_to_go
== 0)
1261 rb
->button_get(true);
1264 /* Advance to current undo */
1265 for (i
= 0; i
< n
; i
++) {
1266 if (!move(undo_info
.history
[i
], true)) {
1272 rb
->button_clear_queue();
1273 rb
->lcd_clear_display();
1276 undo_info
.current
= n
;
1282 static int sokoban_menu(void)
1288 int start_selected
= 0;
1289 int prev_level
= current_info
.level
.index
;
1291 MENUITEM_STRINGLIST(menu
, "Sokoban Menu", NULL
,
1292 "Resume", "Select Level", "Audio Playback", "Keys",
1293 "Load Default Level Set", "Quit Without Saving",
1294 "Save Progress & Quit");
1298 selection
= rb
->do_menu(&menu
, &start_selected
, NULL
, false);
1300 switch (selection
) {
1301 case 0: /* Resume */
1304 case 1: /* Select level */
1305 current_info
.level
.index
++;
1306 rb
->set_int("Select Level", "", UNIT_INT
,
1307 ¤t_info
.level
.index
, NULL
, 1, 1,
1308 current_info
.max_level
, NULL
);
1309 current_info
.level
.index
--;
1310 if (prev_level
!= current_info
.level
.index
) {
1317 case 2: /* Audio playback control */
1318 playback_control(NULL
);
1324 rb
->screens
[i
]->clear_display();
1325 rb
->lcd_setfont(SOKOBAN_FONT
);
1327 #if (CONFIG_KEYPAD == RECORDER_PAD) || \
1328 (CONFIG_KEYPAD == ARCHOS_AV300_PAD)
1329 rb
->lcd_putsxy(3, 6, "[OFF] Menu");
1330 rb
->lcd_putsxy(3, 16, "[ON] Undo");
1331 rb
->lcd_putsxy(3, 26, "[PLAY] Redo");
1332 rb
->lcd_putsxy(3, 36, "[F1] Down a Level");
1333 rb
->lcd_putsxy(3, 46, "[F2] Restart Level");
1334 rb
->lcd_putsxy(3, 56, "[F3] Up a Level");
1335 #elif CONFIG_KEYPAD == ONDIO_PAD
1336 rb
->lcd_putsxy(3, 6, "[OFF] Menu");
1337 rb
->lcd_putsxy(3, 16, "[MODE] Undo");
1338 rb
->lcd_putsxy(3, 26, "[MODE+DOWN] Redo");
1339 rb
->lcd_putsxy(3, 36, "[MODE+LEFT] Previous Level");
1340 rb
->lcd_putsxy(3, 46, "[MODE+UP] Restart Level");
1341 rb
->lcd_putsxy(3, 56, "[MODE+RIGHT] Up Level");
1342 #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
1343 (CONFIG_KEYPAD == IRIVER_H300_PAD)
1344 rb
->lcd_putsxy(3, 6, "[STOP] Menu");
1345 rb
->lcd_putsxy(3, 16, "[REC] Undo");
1346 rb
->lcd_putsxy(3, 26, "[MODE] Redo");
1347 rb
->lcd_putsxy(3, 36, "[PLAY+DOWN] Previous Level");
1348 rb
->lcd_putsxy(3, 46, "[PLAY] Restart Level");
1349 rb
->lcd_putsxy(3, 56, "[PLAY+UP] Next Level");
1350 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
1351 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
1352 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
1353 rb
->lcd_putsxy(3, 6, "[SELECT+MENU] Menu");
1354 rb
->lcd_putsxy(3, 16, "[SELECT] Undo");
1355 rb
->lcd_putsxy(3, 26, "[SELECT+PLAY] Redo");
1356 rb
->lcd_putsxy(3, 36, "[SELECT+LEFT] Previous Level");
1357 rb
->lcd_putsxy(3, 46, "[SELECT+RIGHT] Next Level");
1358 #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
1359 rb
->lcd_putsxy(3, 6, "[POWER] Menu");
1360 rb
->lcd_putsxy(3, 16, "[SELECT] Undo");
1361 rb
->lcd_putsxy(3, 26, "[PLAY] Redo");
1362 rb
->lcd_putsxy(3, 36, "[REC] Restart Level");
1363 #elif CONFIG_KEYPAD == IRIVER_H10_PAD
1364 rb
->lcd_putsxy(3, 6, "[POWER] Menu");
1365 rb
->lcd_putsxy(3, 16, "[REW] Undo");
1366 rb
->lcd_putsxy(3, 26, "[FF] Redo");
1367 rb
->lcd_putsxy(3, 36, "[PLAY+DOWN] Previous Level");
1368 rb
->lcd_putsxy(3, 46, "[PLAY+RIGHT] Restart Level");
1369 rb
->lcd_putsxy(3, 56, "[PLAY+UP] Next Level");
1370 #elif CONFIG_KEYPAD == GIGABEAT_PAD
1371 rb
->lcd_putsxy(3, 6, "[POWER] Menu");
1372 rb
->lcd_putsxy(3, 16, "[SELECT] Undo");
1373 rb
->lcd_putsxy(3, 26, "[A] Redo");
1374 rb
->lcd_putsxy(3, 36, "[VOL-] Previous Level");
1375 rb
->lcd_putsxy(3, 46, "[MENU] Restart Level");
1376 rb
->lcd_putsxy(3, 56, "[VOL+] Next Level");
1377 #elif CONFIG_KEYPAD == SANSA_E200_PAD
1378 rb
->lcd_putsxy(3, 6, "[POWER] Menu");
1379 rb
->lcd_putsxy(3, 16, "[SELECT] Undo");
1380 rb
->lcd_putsxy(3, 26, "[REC] Redo");
1381 rb
->lcd_putsxy(3, 36, "[SELECT+DOWN] Previous Level");
1382 rb
->lcd_putsxy(3, 46, "[SELECT+RIGHT] Restart Level");
1383 rb
->lcd_putsxy(3, 56, "[SELECT+UP] Next Level");
1384 #elif CONFIG_KEYPAD == GIGABEAT_S_PAD
1385 rb
->lcd_putsxy(3, 6, "[MENU] Menu");
1386 rb
->lcd_putsxy(3, 16, "[VOL+] Undo");
1387 rb
->lcd_putsxy(3, 26, "[VOL-] Redo");
1388 rb
->lcd_putsxy(3, 36, "[PREV] Previous Level");
1389 rb
->lcd_putsxy(3, 46, "[PLAY] Restart Level");
1390 rb
->lcd_putsxy(3, 56, "[NEXT] Next Level");
1393 #ifdef HAVE_TOUCHSCREEN
1394 rb
->lcd_putsxy(3, 6, SOKOBAN_MENU_NAME
" Menu");
1395 rb
->lcd_putsxy(3, 16, SOKOBAN_UNDO_NAME
" Undo");
1396 rb
->lcd_putsxy(3, 26, SOKOBAN_REDO_NAME
" Redo");
1397 rb
->lcd_putsxy(3, 36, SOKOBAN_PAUSE_NAME
" Pause");
1398 rb
->lcd_putsxy(3, 46, SOKOBAN_LEVEL_REPEAT_NAME
" Restart Level");
1402 rb
->screens
[i
]->update();
1404 /* Display until keypress */
1407 button
= rb
->button_get(false);
1408 } while (!button
|| button
& BUTTON_REL
||
1409 button
& BUTTON_REPEAT
);
1414 case 4: /* Load default levelset */
1416 if (!read_levels(true))
1417 return 5; /* Quit */
1424 case 6: /* Save & quit */
1425 save(SOKOBAN_SAVE_FILE
, false);
1426 rb
->reload_directory();
1429 } while (!menu_quit
);
1432 rb
->lcd_setfont(SOKOBAN_FONT
);
1435 rb
->screens
[i
]->clear_display();
1436 rb
->screens
[i
]->update();
1442 static bool sokoban_loop(void)
1445 int i
= 0, button
= 0, lastbutton
= 0;
1453 r
= current_info
.player
.row
;
1454 c
= current_info
.player
.col
;
1456 button
= rb
->button_get(true);
1460 #ifdef SOKOBAN_RC_MENU
1461 case SOKOBAN_RC_MENU
:
1464 switch (sokoban_menu()) {
1466 case 6: /* Save & quit */
1473 #ifdef SOKOBAN_UNDO_PRE
1474 if (lastbutton
!= SOKOBAN_UNDO_PRE
)
1476 #else /* repeat can't work here for Ondio, iPod, et al */
1477 case SOKOBAN_UNDO
| BUTTON_REPEAT
:
1480 rb
->lcd_clear_display();
1486 case SOKOBAN_REDO
| BUTTON_REPEAT
:
1488 rb
->lcd_clear_display();
1493 #ifdef SOKOBAN_LEVEL_UP
1494 case SOKOBAN_LEVEL_UP
:
1495 case SOKOBAN_LEVEL_UP
| BUTTON_REPEAT
:
1498 if (current_info
.level
.index
+ 1 < current_info
.max_level
)
1499 current_info
.level
.index
++;
1505 #ifdef SOKOBAN_LEVEL_DOWN
1506 case SOKOBAN_LEVEL_DOWN
:
1507 case SOKOBAN_LEVEL_DOWN
| BUTTON_REPEAT
:
1508 /* previous level */
1510 if (current_info
.level
.index
> 0)
1511 current_info
.level
.index
--;
1517 #ifdef SOKOBAN_LEVEL_REPEAT
1518 case SOKOBAN_LEVEL_REPEAT
:
1519 case SOKOBAN_LEVEL_REPEAT
| BUTTON_REPEAT
:
1527 case SOKOBAN_LEFT
| BUTTON_REPEAT
:
1528 moved
= move(SOKOBAN_MOVE_LEFT
, false);
1532 case SOKOBAN_RIGHT
| BUTTON_REPEAT
:
1533 moved
= move(SOKOBAN_MOVE_RIGHT
, false);
1537 case SOKOBAN_UP
| BUTTON_REPEAT
:
1538 moved
= move(SOKOBAN_MOVE_UP
, false);
1542 case SOKOBAN_DOWN
| BUTTON_REPEAT
:
1543 moved
= move(SOKOBAN_MOVE_DOWN
, false);
1547 if (rb
->default_event_handler(button
) == SYS_USB_CONNECTED
)
1548 return PLUGIN_USB_CONNECTED
;
1552 lastbutton
= button
;
1555 rb
->lcd_clear_display();
1559 /* We have completed this level */
1560 if (current_info
.level
.boxes_to_go
== 0) {
1563 rb
->lcd_clear_display();
1565 /* Show level complete message & stats */
1566 rb
->snprintf(buf
, sizeof(buf
), "Level %d Complete!",
1567 current_info
.level
.index
+ 1);
1568 rb
->lcd_getstringsize(buf
, &w
, &h
);
1569 rb
->lcd_putsxy(LCD_WIDTH
/2 - w
/2, LCD_HEIGHT
/2 - h
*3, buf
);
1571 rb
->snprintf(buf
, sizeof(buf
), "%4d Moves ",
1572 current_info
.level
.moves
);
1573 rb
->lcd_getstringsize(buf
, &w
, &h
);
1574 rb
->lcd_putsxy(LCD_WIDTH
/2 - w
/2, LCD_HEIGHT
/2 - h
, buf
);
1576 rb
->snprintf(buf
, sizeof(buf
), "%4d Pushes",
1577 current_info
.level
.pushes
);
1578 rb
->lcd_getstringsize(buf
, &w
, &h
);
1579 rb
->lcd_putsxy(LCD_WIDTH
/2 - w
/2, LCD_HEIGHT
/2, buf
);
1581 if (undo_info
.count
< MAX_UNDOS
) {
1582 rb
->snprintf(buf
, sizeof(buf
), "%s: Save solution",
1584 rb
->lcd_getstringsize(buf
, &w
, &h
);
1585 rb
->lcd_putsxy(LCD_WIDTH
/2 - w
/2, LCD_HEIGHT
/2 + h
*2, buf
);
1590 rb
->button_clear_queue();
1592 /* Display for 4 seconds or until new keypress */
1593 for (i
= 0; i
< 80; i
++) {
1595 button
= rb
->button_get(false);
1596 if (button
&& !(button
& BUTTON_REL
) &&
1597 !(button
& BUTTON_REPEAT
))
1601 if (button
== BUTTON_SAVE
) {
1602 if (undo_info
.count
< MAX_UNDOS
) {
1603 /* Set filename to current levelset plus level number
1604 * and .sok extension. Use SAVE_FOLDER if using the
1605 * default levelset, since it's in a hidden folder. */
1606 if (rb
->strcmp(buffered_boards
.filename
,
1607 SOKOBAN_LEVELS_FILE
) == 0) {
1608 rb
->snprintf(buf
, sizeof(buf
),
1609 "%s/sokoban.%d.sok",
1610 SOKOBAN_SAVE_FOLDER
,
1611 current_info
.level
.index
+ 1);
1613 if ((loc
= rb
->strrchr(buffered_boards
.filename
,
1616 rb
->snprintf(buf
, sizeof(buf
), "%s.%d.sok",
1617 buffered_boards
.filename
,
1618 current_info
.level
.index
+ 1);
1623 if (!rb
->kbd_input(buf
, MAX_PATH
))
1626 rb
->splash(HZ
*2, "Solution too long to save");
1628 rb
->lcd_setfont(SOKOBAN_FONT
); /* Restore font */
1633 rb
->screens
[i
]->clear_display();
1634 rb
->screens
[i
]->update();
1637 current_info
.level
.index
++;
1639 /* clear undo stats */
1642 if (current_info
.level
.index
>= current_info
.max_level
) {
1643 /* Show levelset complete message */
1644 rb
->snprintf(buf
, sizeof(buf
), "You WIN!!");
1645 rb
->lcd_getstringsize(buf
, &w
, &h
);
1646 rb
->lcd_putsxy(LCD_WIDTH
/2 - w
/2, LCD_HEIGHT
/2 - h
/2, buf
);
1648 rb
->lcd_set_drawmode(DRMODE_COMPLEMENT
);
1649 /* Display for 4 seconds or until keypress */
1650 for (i
= 0; i
< 80; i
++) {
1651 rb
->lcd_fillrect(0, 0, LCD_WIDTH
, LCD_HEIGHT
);
1655 button
= rb
->button_get(false);
1656 if (button
&& !(button
& BUTTON_REL
))
1659 rb
->lcd_set_drawmode(DRMODE_SOLID
);
1661 /* Reset to first level & show quit menu */
1662 current_info
.level
.index
= 0;
1664 switch (sokoban_menu()) {
1666 case 6: /* Save & quit */
1681 enum plugin_status
plugin_start(const void* parameter
)
1687 rb
->lcd_setfont(SOKOBAN_FONT
);
1689 rb
->lcd_clear_display();
1690 rb
->lcd_getstringsize(SOKOBAN_TITLE
, &w
, &h
);
1691 rb
->lcd_putsxy(LCD_WIDTH
/2 - w
/2, LCD_HEIGHT
/2 - h
/2, SOKOBAN_TITLE
);
1693 rb
->sleep(HZ
); /* Show title for 1 second */
1697 if (parameter
== NULL
) {
1698 /* Attempt to resume saved progress, otherwise start at beginning */
1699 if (!load(SOKOBAN_SAVE_FILE
, true)) {
1701 if (!read_levels(true))
1707 /* The plugin is being used to open a file */
1708 if (load((char*) parameter
, false)) {
1709 /* If we loaded & played a solution, quit */
1710 if (current_info
.level
.boxes_to_go
== 0)
1716 rb
->lcd_clear_display();
1719 return sokoban_loop();