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 "sokoban_tiles.h"
36 #define SOKOBAN_TILESIZE BMPWIDTH_sokoban_tiles
37 /* SOKOBAN_TILESIZE is the number of pixels for each block.
38 * Set dynamically so all targets can support levels
39 * that fill their entire screen, less the stat box.
40 * 16 rows & 20 cols minimum */
41 #if LCD_WIDTH > LCD_HEIGHT /* horizontal layout*/
42 #define ROWS (LCD_HEIGHT/SOKOBAN_TILESIZE)
43 #if (LCD_WIDTH+4) >= (20*SOKOBAN_TILESIZE+40) /* wide or narrow stats box */
44 #define COLS ((LCD_WIDTH-40)/SOKOBAN_TILESIZE)
46 #define COLS ((LCD_WIDTH-32)/SOKOBAN_TILESIZE)
48 #else /* vertical layout*/
49 #define ROWS ((LCD_HEIGHT-25)/SOKOBAN_TILESIZE)
50 #define COLS (LCD_WIDTH/SOKOBAN_TILESIZE)
53 /* Use either all but 16k of the plugin buffer for level data
54 * or 128k, which ever is less */
55 #if PLUGIN_BUFFER_SIZE - 0x4000 < 0x20000
56 #define MAX_LEVEL_DATA (PLUGIN_BUFFER_SIZE - 0x4000)
58 #define MAX_LEVEL_DATA 0x20000
61 /* Number of levels for which to allocate buffer indexes */
62 #define MAX_LEVELS MAX_LEVEL_DATA/70
64 /* Use 4k plus remaining plugin buffer (-12k for prog) for undo, up to 64k */
65 #if PLUGIN_BUFFER_SIZE - MAX_LEVEL_DATA - 0x3000 > 0x10000
66 #define MAX_UNDOS 0x10000
68 #define MAX_UNDOS (PLUGIN_BUFFER_SIZE - MAX_LEVEL_DATA - 0x3000)
71 /* Move/push definitions for undo */
72 #define SOKOBAN_PUSH_LEFT 'L'
73 #define SOKOBAN_PUSH_RIGHT 'R'
74 #define SOKOBAN_PUSH_UP 'U'
75 #define SOKOBAN_PUSH_DOWN 'D'
76 #define SOKOBAN_MOVE_LEFT 'l'
77 #define SOKOBAN_MOVE_RIGHT 'r'
78 #define SOKOBAN_MOVE_UP 'u'
79 #define SOKOBAN_MOVE_DOWN 'd'
81 #define SOKOBAN_MOVE_DIFF (SOKOBAN_MOVE_LEFT-SOKOBAN_PUSH_LEFT)
82 #define SOKOBAN_MOVE_MIN SOKOBAN_MOVE_DOWN
84 /* variable button definitions */
85 #if (CONFIG_KEYPAD == RECORDER_PAD) || \
86 (CONFIG_KEYPAD == ARCHOS_AV300_PAD)
87 #define SOKOBAN_LEFT BUTTON_LEFT
88 #define SOKOBAN_RIGHT BUTTON_RIGHT
89 #define SOKOBAN_UP BUTTON_UP
90 #define SOKOBAN_DOWN BUTTON_DOWN
91 #define SOKOBAN_MENU BUTTON_OFF
92 #define SOKOBAN_UNDO BUTTON_ON
93 #define SOKOBAN_REDO BUTTON_PLAY
94 #define SOKOBAN_LEVEL_DOWN BUTTON_F1
95 #define SOKOBAN_LEVEL_REPEAT BUTTON_F2
96 #define SOKOBAN_LEVEL_UP BUTTON_F3
97 #define SOKOBAN_PAUSE BUTTON_PLAY
98 #define BUTTON_SAVE BUTTON_ON
99 #define BUTTON_SAVE_NAME "ON"
101 #elif CONFIG_KEYPAD == ONDIO_PAD
102 #define SOKOBAN_LEFT BUTTON_LEFT
103 #define SOKOBAN_RIGHT BUTTON_RIGHT
104 #define SOKOBAN_UP BUTTON_UP
105 #define SOKOBAN_DOWN BUTTON_DOWN
106 #define SOKOBAN_MENU BUTTON_OFF
107 #define SOKOBAN_UNDO_PRE BUTTON_MENU
108 #define SOKOBAN_UNDO (BUTTON_MENU | BUTTON_REL)
109 #define SOKOBAN_REDO (BUTTON_MENU | BUTTON_DOWN)
110 #define SOKOBAN_LEVEL_DOWN (BUTTON_MENU | BUTTON_LEFT)
111 #define SOKOBAN_LEVEL_REPEAT (BUTTON_MENU | BUTTON_UP)
112 #define SOKOBAN_LEVEL_UP (BUTTON_MENU | BUTTON_RIGHT)
113 #define SOKOBAN_PAUSE BUTTON_MENU
114 #define BUTTON_SAVE BUTTON_MENU
115 #define BUTTON_SAVE_NAME "MENU"
117 #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
118 (CONFIG_KEYPAD == IRIVER_H300_PAD)
119 #define SOKOBAN_LEFT BUTTON_LEFT
120 #define SOKOBAN_RIGHT BUTTON_RIGHT
121 #define SOKOBAN_UP BUTTON_UP
122 #define SOKOBAN_DOWN BUTTON_DOWN
123 #define SOKOBAN_MENU BUTTON_OFF
124 #define SOKOBAN_UNDO BUTTON_REC
125 #define SOKOBAN_REDO BUTTON_MODE
126 #define SOKOBAN_LEVEL_DOWN (BUTTON_ON | BUTTON_DOWN)
127 #define SOKOBAN_LEVEL_REPEAT BUTTON_ON
128 #define SOKOBAN_LEVEL_UP (BUTTON_ON | BUTTON_UP)
129 #define SOKOBAN_PAUSE BUTTON_ON
130 #define BUTTON_SAVE BUTTON_MODE
131 #define BUTTON_SAVE_NAME "MODE"
133 #define SOKOBAN_RC_MENU BUTTON_RC_STOP
135 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
136 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
137 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
138 #define SOKOBAN_LEFT BUTTON_LEFT
139 #define SOKOBAN_RIGHT BUTTON_RIGHT
140 #define SOKOBAN_UP BUTTON_MENU
141 #define SOKOBAN_DOWN BUTTON_PLAY
142 #define SOKOBAN_MENU (BUTTON_SELECT | BUTTON_MENU)
143 #define SOKOBAN_UNDO_PRE BUTTON_SELECT
144 #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
145 #define SOKOBAN_REDO (BUTTON_SELECT | BUTTON_PLAY)
146 #define SOKOBAN_LEVEL_DOWN (BUTTON_SELECT | BUTTON_LEFT)
147 #define SOKOBAN_LEVEL_UP (BUTTON_SELECT | BUTTON_RIGHT)
148 #define SOKOBAN_PAUSE BUTTON_SELECT
149 #define BUTTON_SAVE BUTTON_SELECT
150 #define BUTTON_SAVE_NAME "SELECT"
152 /* FIXME: if/when simultaneous button presses work for X5/M5,
153 * add level up/down */
154 #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
155 #define SOKOBAN_LEFT BUTTON_LEFT
156 #define SOKOBAN_RIGHT BUTTON_RIGHT
157 #define SOKOBAN_UP BUTTON_UP
158 #define SOKOBAN_DOWN BUTTON_DOWN
159 #define SOKOBAN_MENU BUTTON_POWER
160 #define SOKOBAN_UNDO_PRE BUTTON_SELECT
161 #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
162 #define SOKOBAN_LEVEL_REPEAT BUTTON_REC
163 #define SOKOBAN_REDO BUTTON_PLAY
164 #define SOKOBAN_PAUSE BUTTON_PLAY
165 #define BUTTON_SAVE BUTTON_SELECT
166 #define BUTTON_SAVE_NAME "SELECT"
168 #elif CONFIG_KEYPAD == IRIVER_H10_PAD
169 #define SOKOBAN_LEFT BUTTON_LEFT
170 #define SOKOBAN_RIGHT BUTTON_RIGHT
171 #define SOKOBAN_UP BUTTON_SCROLL_UP
172 #define SOKOBAN_DOWN BUTTON_SCROLL_DOWN
173 #define SOKOBAN_MENU BUTTON_POWER
174 #define SOKOBAN_UNDO_PRE BUTTON_REW
175 #define SOKOBAN_UNDO (BUTTON_REW | BUTTON_REL)
176 #define SOKOBAN_REDO BUTTON_FF
177 #define SOKOBAN_LEVEL_DOWN (BUTTON_PLAY | BUTTON_SCROLL_DOWN)
178 #define SOKOBAN_LEVEL_REPEAT (BUTTON_PLAY | BUTTON_RIGHT)
179 #define SOKOBAN_LEVEL_UP (BUTTON_PLAY | BUTTON_SCROLL_UP)
180 #define SOKOBAN_PAUSE BUTTON_PLAY
181 #define BUTTON_SAVE BUTTON_PLAY
182 #define BUTTON_SAVE_NAME "PLAY"
184 #elif CONFIG_KEYPAD == GIGABEAT_PAD
185 #define SOKOBAN_LEFT BUTTON_LEFT
186 #define SOKOBAN_RIGHT BUTTON_RIGHT
187 #define SOKOBAN_UP BUTTON_UP
188 #define SOKOBAN_DOWN BUTTON_DOWN
189 #define SOKOBAN_MENU BUTTON_POWER
190 #define SOKOBAN_UNDO BUTTON_SELECT
191 #define SOKOBAN_REDO BUTTON_A
192 #define SOKOBAN_LEVEL_DOWN BUTTON_VOL_DOWN
193 #define SOKOBAN_LEVEL_REPEAT BUTTON_MENU
194 #define SOKOBAN_LEVEL_UP BUTTON_VOL_UP
195 #define SOKOBAN_PAUSE BUTTON_SELECT
196 #define BUTTON_SAVE BUTTON_SELECT
197 #define BUTTON_SAVE_NAME "SELECT"
199 #elif CONFIG_KEYPAD == SANSA_E200_PAD
200 #define SOKOBAN_LEFT BUTTON_LEFT
201 #define SOKOBAN_RIGHT BUTTON_RIGHT
202 #define SOKOBAN_UP BUTTON_UP
203 #define SOKOBAN_DOWN BUTTON_DOWN
204 #define SOKOBAN_MENU BUTTON_POWER
205 #define SOKOBAN_UNDO_PRE BUTTON_SELECT
206 #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
207 #define SOKOBAN_REDO BUTTON_REC
208 #define SOKOBAN_LEVEL_DOWN (BUTTON_SELECT | BUTTON_DOWN)
209 #define SOKOBAN_LEVEL_REPEAT (BUTTON_SELECT | BUTTON_RIGHT)
210 #define SOKOBAN_LEVEL_UP (BUTTON_SELECT | BUTTON_UP)
211 #define SOKOBAN_PAUSE BUTTON_SELECT
212 #define BUTTON_SAVE BUTTON_SELECT
213 #define BUTTON_SAVE_NAME "SELECT"
215 #elif CONFIG_KEYPAD == SANSA_C200_PAD
216 #define SOKOBAN_LEFT BUTTON_LEFT
217 #define SOKOBAN_RIGHT BUTTON_RIGHT
218 #define SOKOBAN_UP BUTTON_UP
219 #define SOKOBAN_DOWN BUTTON_DOWN
220 #define SOKOBAN_MENU BUTTON_POWER
221 #define SOKOBAN_UNDO_PRE BUTTON_SELECT
222 #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
223 #define SOKOBAN_REDO BUTTON_REC
224 #define SOKOBAN_LEVEL_DOWN BUTTON_VOL_DOWN
225 #define SOKOBAN_LEVEL_REPEAT (BUTTON_SELECT | BUTTON_RIGHT)
226 #define SOKOBAN_LEVEL_UP BUTTON_VOL_UP
227 #define SOKOBAN_PAUSE BUTTON_SELECT
228 #define BUTTON_SAVE BUTTON_SELECT
229 #define BUTTON_SAVE_NAME "SELECT"
231 #elif CONFIG_KEYPAD == GIGABEAT_S_PAD
232 #define SOKOBAN_LEFT BUTTON_LEFT
233 #define SOKOBAN_RIGHT BUTTON_RIGHT
234 #define SOKOBAN_UP BUTTON_UP
235 #define SOKOBAN_DOWN BUTTON_DOWN
236 #define SOKOBAN_MENU BUTTON_MENU
237 #define SOKOBAN_UNDO BUTTON_VOL_UP
238 #define SOKOBAN_REDO BUTTON_VOL_DOWN
239 #define SOKOBAN_LEVEL_DOWN BUTTON_PREV
240 #define SOKOBAN_LEVEL_REPEAT BUTTON_PLAY
241 #define SOKOBAN_LEVEL_UP BUTTON_NEXT
242 #define SOKOBAN_PAUSE BUTTON_SELECT
243 #define BUTTON_SAVE BUTTON_SELECT
244 #define BUTTON_SAVE_NAME "SELECT"
246 #elif CONFIG_KEYPAD == MROBE100_PAD
247 #define SOKOBAN_LEFT BUTTON_LEFT
248 #define SOKOBAN_RIGHT BUTTON_RIGHT
249 #define SOKOBAN_UP BUTTON_UP
250 #define SOKOBAN_DOWN BUTTON_DOWN
251 #define SOKOBAN_MENU BUTTON_POWER
252 #define SOKOBAN_UNDO BUTTON_SELECT
253 #define SOKOBAN_REDO BUTTON_MENU
254 #define SOKOBAN_LEVEL_DOWN (BUTTON_DISPLAY | BUTTON_DOWN)
255 #define SOKOBAN_LEVEL_REPEAT (BUTTON_DISPLAY | BUTTON_RIGHT)
256 #define SOKOBAN_LEVEL_UP (BUTTON_DISPLAY | BUTTON_UP)
257 #define SOKOBAN_PAUSE BUTTON_SELECT
258 #define BUTTON_SAVE BUTTON_SELECT
259 #define BUTTON_SAVE_NAME "SELECT"
261 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
262 #define SOKOBAN_LEFT BUTTON_RC_REW
263 #define SOKOBAN_RIGHT BUTTON_RC_FF
264 #define SOKOBAN_UP BUTTON_RC_VOL_UP
265 #define SOKOBAN_DOWN BUTTON_RC_VOL_DOWN
266 #define SOKOBAN_MENU BUTTON_RC_REC
267 #define SOKOBAN_UNDO BUTTON_RC_MODE
268 #define SOKOBAN_REDO BUTTON_RC_MENU
269 #define SOKOBAN_PAUSE BUTTON_RC_PLAY
270 #define BUTTON_SAVE BUTTON_RC_PLAY
271 #define BUTTON_SAVE_NAME "PLAY"
273 #define SOKOBAN_RC_MENU BUTTON_REC
275 #elif CONFIG_KEYPAD == COWOND2_PAD
276 #define SOKOBAN_MENU BUTTON_MENU
277 #define SOKOBAN_MENU_NAME "[MENU]"
280 #error No keymap defined!
285 #define SOKOBAN_LEFT BUTTON_MIDLEFT
287 #ifndef SOKOBAN_RIGHT
288 #define SOKOBAN_RIGHT BUTTON_MIDRIGHT
291 #define SOKOBAN_UP BUTTON_TOPMIDDLE
294 #define SOKOBAN_DOWN BUTTON_BOTTOMMIDDLE
297 #define SOKOBAN_MENU BUTTON_TOPLEFT
298 #define SOKOBAN_MENU_NAME "[TOPLEFT]"
301 #define SOKOBAN_UNDO BUTTON_BOTTOMRIGHT
302 #define SOKOBAN_UNDO_NAME "[BOTTOMRIGHT]"
305 #define SOKOBAN_REDO BUTTON_BOTTOMLEFT
306 #define SOKOBAN_REDO_NAME "[BOTTOMLEFT]"
308 #ifndef SOKOBAN_PAUSE
309 #define SOKOBAN_PAUSE BUTTON_CENTER
310 #define SOKOBAN_PAUSE_NAME "[CENTER]"
312 #ifndef SOKOBAN_LEVEL_REPEAT
313 #define SOKOBAN_LEVEL_REPEAT BUTTON_TOPRIGHT
314 #define SOKOBAN_LEVEL_REPEAT_NAME "[TOPRIGHT]"
317 #define BUTTON_SAVE BUTTON_CENTER
318 #define BUTTON_SAVE_NAME "CENTER"
322 #define SOKOBAN_FONT FONT_SYSFIXED
325 /* The Location, Undo and LevelInfo structs are OO-flavored.
326 * (oooh!-flavored as Schnueff puts it.) It makes more you have to know,
327 * but the overall data layout becomes more manageable. */
329 /* Level data & stats */
331 int index
; /* Level index (level number - 1) */
332 int moves
; /* Moves & pushes for the stats */
334 short boxes_to_go
; /* Number of unplaced boxes remaining in level */
335 short height
; /* Height & width for centering level display */
344 /* Our full undo history */
345 static struct UndoInfo
{
346 int count
; /* How many undos have been done */
347 int current
; /* Which history is the current undo */
348 int max
; /* Which history is the max redoable */
349 char history
[MAX_UNDOS
];
352 /* Our playing board */
353 static struct BoardInfo
{
354 char board
[ROWS
][COLS
]; /* The current board data */
355 struct LevelInfo level
; /* Level data & stats */
356 struct Location player
; /* Where the player is */
357 int max_level
; /* The number of levels we have */
360 static struct BufferedBoards
{
361 char filename
[MAX_PATH
]; /* Filename of the levelset we're using */
362 char data
[MAX_LEVEL_DATA
]; /* Buffered level data */
363 int index
[MAX_LEVELS
+ 1]; /* Where each buffered board begins & ends */
364 int start
; /* Index of first buffered board */
365 int end
; /* Index of last buffered board */
366 short prebuffered_boards
; /* Number of boards before current to store */
370 static const struct plugin_api
* rb
;
371 MEM_FUNCTION_WRAPPERS(rb
);
373 static char buf
[ROWS
*(COLS
+ 1)]; /* Enough for a whole board or a filename */
376 static void init_undo(void)
379 undo_info
.current
= 0;
383 static void get_delta(char direction
, short *d_r
, short *d_c
)
386 case SOKOBAN_PUSH_LEFT
:
387 case SOKOBAN_MOVE_LEFT
:
391 case SOKOBAN_PUSH_RIGHT
:
392 case SOKOBAN_MOVE_RIGHT
:
396 case SOKOBAN_PUSH_UP
:
397 case SOKOBAN_MOVE_UP
:
401 case SOKOBAN_PUSH_DOWN
:
402 case SOKOBAN_MOVE_DOWN
:
408 static bool undo(void)
412 short d_r
= 0, d_c
= 0; /* delta row & delta col */
413 char *space_cur
, *space_next
, *space_prev
;
414 bool undo_push
= false;
416 /* If no more undos or we've wrapped all the way around, quit */
417 if (undo_info
.count
== 0 || undo_info
.current
- 1 == undo_info
.max
)
420 /* Move to previous undo in the list */
421 if (undo_info
.current
== 0 && undo_info
.count
> 1)
422 undo_info
.current
= MAX_UNDOS
- 1;
428 undo
= undo_info
.history
[undo_info
.current
];
430 if (undo
< SOKOBAN_MOVE_MIN
)
433 get_delta(undo
, &d_r
, &d_c
);
435 r
= current_info
.player
.row
;
436 c
= current_info
.player
.col
;
438 /* Give the 3 spaces we're going to use better names */
439 space_cur
= ¤t_info
.board
[r
][c
];
440 space_next
= ¤t_info
.board
[r
+ d_r
][c
+ d_c
];
441 space_prev
= ¤t_info
.board
[r
- d_r
][c
- d_c
];
443 /* Update board info */
445 /* Moving box from goal to floor */
446 if (*space_next
== '*' && *space_cur
== '@')
447 current_info
.level
.boxes_to_go
++;
448 /* Moving box from floor to goal */
449 else if (*space_next
== '$' && *space_cur
== '+')
450 current_info
.level
.boxes_to_go
--;
452 /* Move box off of next space... */
453 *space_next
= (*space_next
== '*' ? '.' : ' ');
454 /* ...and on to current space */
455 *space_cur
= (*space_cur
== '+' ? '*' : '$');
457 current_info
.level
.pushes
--;
459 /* Just move player off of current space */
460 *space_cur
= (*space_cur
== '+' ? '.' : ' ');
461 /* Move player back to previous space */
462 *space_prev
= (*space_prev
== '.' ? '+' : '@');
464 /* Update position */
465 current_info
.player
.row
-= d_r
;
466 current_info
.player
.col
-= d_c
;
468 current_info
.level
.moves
--;
473 static void add_undo(char undo
)
475 undo_info
.history
[undo_info
.current
] = undo
;
477 /* Wrap around if MAX_UNDOS exceeded */
478 if (undo_info
.current
< (MAX_UNDOS
- 1))
481 undo_info
.current
= 0;
483 if (undo_info
.count
< MAX_UNDOS
)
487 static bool move(char direction
, bool redo
)
490 short d_r
= 0, d_c
= 0; /* delta row & delta col */
491 char *space_cur
, *space_next
, *space_beyond
;
494 get_delta(direction
, &d_r
, &d_c
);
496 r
= current_info
.player
.row
;
497 c
= current_info
.player
.col
;
499 /* Check for out-of-bounds */
500 if (r
+ 2*d_r
< 0 || r
+ 2*d_r
>= ROWS
||
501 c
+ 2*d_c
< 0 || c
+ 2*d_c
>= COLS
)
504 /* Give the 3 spaces we're going to use better names */
505 space_cur
= ¤t_info
.board
[r
][c
];
506 space_next
= ¤t_info
.board
[r
+ d_r
][c
+ d_c
];
507 space_beyond
= ¤t_info
.board
[r
+ 2*d_r
][c
+ 2*d_c
];
509 if (*space_next
== '$' || *space_next
== '*') {
510 /* Change direction from move to push for undo */
511 if (direction
>= SOKOBAN_MOVE_MIN
)
512 direction
-= SOKOBAN_MOVE_DIFF
;
515 else if (direction
< SOKOBAN_MOVE_MIN
)
516 /* Change back to move if redo/solution playback push is invalid */
517 direction
+= SOKOBAN_MOVE_DIFF
;
519 /* Update board info */
521 /* Moving box from goal to floor */
522 if (*space_next
== '*' && *space_beyond
== ' ')
523 current_info
.level
.boxes_to_go
++;
524 /* Moving box from floor to goal */
525 else if (*space_next
== '$' && *space_beyond
== '.')
526 current_info
.level
.boxes_to_go
--;
527 /* Check for invalid move */
528 else if (*space_beyond
!= '.' && *space_beyond
!= ' ')
531 /* Move player onto next space */
532 *space_next
= (*space_next
== '*' ? '+' : '@');
533 /* Move box onto space beyond next */
534 *space_beyond
= (*space_beyond
== '.' ? '*' : '$');
536 current_info
.level
.pushes
++;
538 /* Check for invalid move */
539 if (*space_next
!= '.' && *space_next
!= ' ')
542 /* Move player onto next space */
543 *space_next
= (*space_next
== '.' ? '+' : '@');
545 /* Move player off of current space */
546 *space_cur
= (*space_cur
== '+' ? '.' : ' ');
548 /* Update position */
549 current_info
.player
.row
+= d_r
;
550 current_info
.player
.col
+= d_c
;
552 current_info
.level
.moves
++;
554 /* Update undo_info.max to current on every normal move,
555 * except if it's the same as a redo. */
556 /* normal move and either */
558 /* moves have been undone... */
559 ((undo_info
.max
!= undo_info
.current
&&
560 /* ...and the current move is NOT the same as the one in history */
561 undo_info
.history
[undo_info
.current
] != direction
) ||
562 /* or moves have not been undone */
563 undo_info
.max
== undo_info
.current
)) {
565 undo_info
.max
= undo_info
.current
;
566 } else /* redo move or move was same as redo */
567 add_undo(direction
); /* add_undo to update current */
573 static bool redo(void)
575 /* If no moves have been undone, quit */
576 if (undo_info
.current
== undo_info
.max
)
579 return move(undo_info
.history
[(undo_info
.current
< MAX_UNDOS
?
580 undo_info
.current
: 0)], true);
584 static void init_boards(void)
586 rb
->strncpy(buffered_boards
.filename
, SOKOBAN_LEVELS_FILE
, MAX_PATH
);
588 current_info
.level
.index
= 0;
589 current_info
.player
.row
= 0;
590 current_info
.player
.col
= 0;
591 current_info
.max_level
= 0;
593 buffered_boards
.start
= 0;
594 buffered_boards
.end
= 0;
595 buffered_boards
.prebuffered_boards
= 0;
600 static bool read_levels(bool initialize
)
610 bool index_set
= false;
612 /* Get the index of the first level to buffer */
613 if (current_info
.level
.index
> buffered_boards
.prebuffered_boards
&&
615 buffered_boards
.start
= current_info
.level
.index
-
616 buffered_boards
.prebuffered_boards
;
618 buffered_boards
.start
= 0;
620 if ((fd
= rb
->open(buffered_boards
.filename
, O_RDONLY
)) < 0) {
621 rb
->splashf(HZ
*2, "Unable to open %s", buffered_boards
.filename
);
626 len
= rb
->read_line(fd
, buf
, sizeof(buf
));
628 /* Correct len when trailing \r's or \n's are counted */
629 if (len
> 2 && buf
[len
- 2] == '\0')
631 else if (len
> 1 && buf
[len
- 1] == '\0')
634 /* Skip short lines & lines with non-level data */
635 if (len
>= 3 && ((buf
[0] >= '1' && buf
[0] <= '9') || buf
[0] == '#' ||
636 buf
[0] == ' ' || buf
[0] == '-' || buf
[0] == '_')) {
637 if (level_count
>= buffered_boards
.start
) {
638 /* Set the index of this level */
640 level_count
- buffered_boards
.start
< MAX_LEVELS
) {
641 buffered_boards
.index
[level_count
- buffered_boards
.start
]
645 /* Copy buffer to board data */
646 if (i
+ level_len
+ len
< MAX_LEVEL_DATA
) {
647 rb
->memcpy(&buffered_boards
.data
[i
+ level_len
], buf
, len
);
648 buffered_boards
.data
[i
+ level_len
+ len
] = '\n';
651 level_len
+= len
+ 1;
654 /* If newline & level is tall enough or is RLE */
655 } else if (buf
[0] == '\0' && (row
> 2 || lastlen
> 22)) {
657 if (level_count
>= buffered_boards
.start
) {
659 if (i
< MAX_LEVEL_DATA
)
660 buffered_boards
.end
= level_count
;
661 else if (!initialize
)
671 } while ((lastlen
= len
));
673 /* Set the index of the end of the last level */
674 if (level_count
- buffered_boards
.start
< MAX_LEVELS
)
675 buffered_boards
.index
[level_count
- buffered_boards
.start
] = i
;
678 current_info
.max_level
= level_count
;
679 buffered_boards
.prebuffered_boards
= buffered_boards
.end
/2;
687 static void load_level(void)
692 int index
= current_info
.level
.index
- buffered_boards
.start
;
695 /* Get the buffered board index of the current level */
696 if (current_info
.level
.index
< buffered_boards
.start
||
697 current_info
.level
.index
>= buffered_boards
.end
) {
699 if (current_info
.level
.index
> buffered_boards
.prebuffered_boards
)
700 index
= buffered_boards
.prebuffered_boards
;
702 index
= current_info
.level
.index
;
704 level
= &buffered_boards
.data
[buffered_boards
.index
[index
]];
706 /* Reset level info */
707 current_info
.level
.moves
= 0;
708 current_info
.level
.pushes
= 0;
709 current_info
.level
.boxes_to_go
= 0;
710 current_info
.level
.width
= 0;
713 for (r
= 0; r
< ROWS
; r
++)
714 for (c
= 0; c
< COLS
; c
++)
715 current_info
.board
[r
][c
] = 'X';
717 level_size
= buffered_boards
.index
[index
+ 1] -
718 buffered_boards
.index
[index
];
720 for (r
= 0, c
= 0, n
= 1, i
= 0; i
< level_size
; i
++) {
721 if (level
[i
] == '\n' || level
[i
] == '|') {
723 /* Update max width of level & go to next row */
724 if (c
> current_info
.level
.width
)
725 current_info
.level
.width
= c
;
731 } else if (c
< COLS
) {
732 /* Read RLE character's length into n */
733 if (level
[i
] >= '0' && level
[i
] <= '9') {
734 n
= level
[i
++] - '0';
735 if (level
[i
] >= '0' && level
[i
] <= '9')
736 n
= n
*10 + level
[i
++] - '0';
739 /* Cleanup & replace */
742 else if (level
[i
] == '-' || level
[i
] == '_')
750 current_info
.level
.boxes_to_go
+= n
;
752 /* Put RLE character n times */
754 current_info
.board
[r
][c
++] = level
[i
];
758 if (level
[i
] == '.' || level
[i
] == '+')
759 current_info
.level
.boxes_to_go
++;
761 if (level
[i
] == '@' ||level
[i
] == '+') {
762 current_info
.player
.row
= r
;
763 current_info
.player
.col
= c
;
766 current_info
.board
[r
][c
++] = level
[i
];
771 current_info
.level
.height
= r
;
774 /* Fill in blank space outside level on color targets */
775 for (r
= 0; r
< ROWS
; r
++)
776 for (c
= 0; current_info
.board
[r
][c
] == ' ' && c
< COLS
; c
++)
777 current_info
.board
[r
][c
] = 'X';
779 for (c
= 0; c
< COLS
; c
++) {
780 for (r
= 0; (current_info
.board
[r
][c
] == ' ' ||
781 current_info
.board
[r
][c
] == 'X') && r
< ROWS
; r
++)
782 current_info
.board
[r
][c
] = 'X';
783 for (r
= ROWS
- 1; (current_info
.board
[r
][c
] == ' ' ||
784 current_info
.board
[r
][c
] == 'X') && r
>= 0; r
--)
785 current_info
.board
[r
][c
] = 'X';
790 static void update_screen(void)
795 #if LCD_WIDTH - (COLS*SOKOBAN_TILESIZE) < 32
796 #define STAT_HEIGHT 25
797 #define STAT_X (LCD_WIDTH - 120)/2
798 #define STAT_Y (LCD_HEIGHT - STAT_HEIGHT)
799 #define BOARD_WIDTH LCD_WIDTH
800 #define BOARD_HEIGHT (LCD_HEIGHT - STAT_HEIGHT)
801 rb
->lcd_putsxy(STAT_X
+ 4, STAT_Y
+ 4, "Level");
802 rb
->snprintf(buf
, sizeof(buf
), "%d", current_info
.level
.index
+ 1);
803 rb
->lcd_putsxy(STAT_X
+ 7, STAT_Y
+ 14, buf
);
804 rb
->lcd_putsxy(STAT_X
+ 41, STAT_Y
+ 4, "Moves");
805 rb
->snprintf(buf
, sizeof(buf
), "%d", current_info
.level
.moves
);
806 rb
->lcd_putsxy(STAT_X
+ 44, STAT_Y
+ 14, buf
);
807 rb
->lcd_putsxy(STAT_X
+ 79, STAT_Y
+ 4, "Pushes");
808 rb
->snprintf(buf
, sizeof(buf
), "%d", current_info
.level
.pushes
);
809 rb
->lcd_putsxy(STAT_X
+ 82, STAT_Y
+ 14, buf
);
811 rb
->lcd_drawrect(STAT_X
, STAT_Y
, 38, STAT_HEIGHT
);
812 rb
->lcd_drawrect(STAT_X
+ 37, STAT_Y
, 39, STAT_HEIGHT
);
813 rb
->lcd_drawrect(STAT_X
+ 75, STAT_Y
, 45, STAT_HEIGHT
);
815 #if LCD_WIDTH - (COLS*SOKOBAN_TILESIZE) > 40
816 #define STAT_X (LCD_WIDTH - 40)
818 #define STAT_X COLS*SOKOBAN_TILESIZE
820 #define STAT_Y (LCD_HEIGHT - 64)/2
821 #define STAT_WIDTH (LCD_WIDTH - STAT_X)
822 #define BOARD_WIDTH (LCD_WIDTH - STAT_WIDTH)
823 #define BOARD_HEIGHT LCD_HEIGHT
824 rb
->lcd_putsxy(STAT_X
+ 1, STAT_Y
+ 2, "Level");
825 rb
->snprintf(buf
, sizeof(buf
), "%d", current_info
.level
.index
+ 1);
826 rb
->lcd_putsxy(STAT_X
+ 4, STAT_Y
+ 12, buf
);
827 rb
->lcd_putsxy(STAT_X
+ 1, STAT_Y
+ 23, "Moves");
828 rb
->snprintf(buf
, sizeof(buf
), "%d", current_info
.level
.moves
);
829 rb
->lcd_putsxy(STAT_X
+ 4, STAT_Y
+ 33, buf
);
831 rb
->lcd_putsxy(STAT_X
+ 1, STAT_Y
+ 44, "Push");
833 rb
->lcd_putsxy(STAT_X
+ 1, STAT_Y
+ 44, "Pushes");
835 rb
->snprintf(buf
, sizeof(buf
), "%d", current_info
.level
.pushes
);
836 rb
->lcd_putsxy(STAT_X
+ 4, STAT_Y
+ 54, buf
);
838 rb
->lcd_drawrect(STAT_X
, STAT_Y
+ 0, STAT_WIDTH
, 64);
839 rb
->lcd_hline(STAT_X
, LCD_WIDTH
- 1, STAT_Y
+ 21);
840 rb
->lcd_hline(STAT_X
, LCD_WIDTH
- 1, STAT_Y
+ 42);
844 /* load the board to the screen */
845 for (rows
= 0; rows
< ROWS
; rows
++) {
846 for (cols
= 0; cols
< COLS
; cols
++) {
847 c
= cols
*SOKOBAN_TILESIZE
+
848 (BOARD_WIDTH
- current_info
.level
.width
*SOKOBAN_TILESIZE
)/2;
849 r
= rows
*SOKOBAN_TILESIZE
+
850 (BOARD_HEIGHT
- current_info
.level
.height
*SOKOBAN_TILESIZE
)/2;
852 switch(current_info
.board
[rows
][cols
]) {
853 case 'X': /* blank space outside of level */
856 case ' ': /* floor */
857 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 0*SOKOBAN_TILESIZE
,
858 SOKOBAN_TILESIZE
, c
, r
, SOKOBAN_TILESIZE
,
863 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 1*SOKOBAN_TILESIZE
,
864 SOKOBAN_TILESIZE
, c
, r
, SOKOBAN_TILESIZE
,
869 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 2*SOKOBAN_TILESIZE
,
870 SOKOBAN_TILESIZE
, c
, r
, SOKOBAN_TILESIZE
,
874 case '*': /* box on goal */
875 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 3*SOKOBAN_TILESIZE
,
876 SOKOBAN_TILESIZE
, c
, r
, SOKOBAN_TILESIZE
,
881 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 4*SOKOBAN_TILESIZE
,
882 SOKOBAN_TILESIZE
, c
, r
, SOKOBAN_TILESIZE
,
886 case '@': /* player */
887 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 5*SOKOBAN_TILESIZE
,
888 SOKOBAN_TILESIZE
, c
, r
, SOKOBAN_TILESIZE
,
892 case '+': /* player on goal */
893 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 6*SOKOBAN_TILESIZE
,
894 SOKOBAN_TILESIZE
, c
, r
, SOKOBAN_TILESIZE
,
901 /* print out the screen */
905 static void draw_level(void)
908 rb
->lcd_clear_display();
912 static bool save(char *filename
, bool solution
)
917 char dirname
[MAX_PATH
];
919 rb
->splash(0, "Saving...");
921 /* Create dir if it doesn't exist */
922 if ((loc
= rb
->strrchr(filename
, '/')) != NULL
) {
923 rb
->strncpy(dirname
, filename
, loc
- filename
);
924 dirname
[loc
- filename
] = '\0';
925 if(!(dir
= rb
->opendir(dirname
)))
931 if (filename
[0] == '\0' ||
932 (fd
= rb
->open(filename
, O_WRONLY
|O_CREAT
|O_TRUNC
)) < 0) {
933 rb
->splashf(HZ
*2, "Unable to open %s", filename
);
937 /* Sokoban: S/P for solution/progress : level number : current undo */
938 rb
->snprintf(buf
, sizeof(buf
), "Sokoban:%c:%d:%d\n", (solution
? 'S' : 'P'),
939 current_info
.level
.index
+ 1, undo_info
.current
);
940 rb
->write(fd
, buf
, rb
->strlen(buf
));
942 /* Filename of levelset */
943 rb
->write(fd
, buffered_boards
.filename
,
944 rb
->strlen(buffered_boards
.filename
));
945 rb
->write(fd
, "\n", 1);
947 /* Full undo history */
948 rb
->write(fd
, undo_info
.history
, undo_info
.max
);
955 static bool load(char *filename
, bool silent
)
963 unsigned short speed
= 2;
964 int delay
[] = {HZ
/2, HZ
/3, HZ
/4, HZ
/6, HZ
/8, HZ
/12, HZ
/16, HZ
/25};
966 if (filename
[0] == '\0' || (fd
= rb
->open(filename
, O_RDONLY
)) < 0) {
968 rb
->splashf(HZ
*2, "Unable to open %s", filename
);
972 /* Read header, level number, & current undo */
973 rb
->read_line(fd
, buf
, sizeof(buf
));
975 /* If we're opening a level file, not a solution/progress file */
976 if (rb
->strncmp(buf
, "Sokoban", 7) != 0) {
979 rb
->strncpy(buffered_boards
.filename
, filename
, MAX_PATH
);
980 if (!read_levels(true))
983 current_info
.level
.index
= 0;
986 /* If there aren't any boxes to go or the player position wasn't set,
987 * the file probably wasn't a Sokoban level file */
988 if (current_info
.level
.boxes_to_go
== 0 ||
989 current_info
.player
.row
== 0 || current_info
.player
.col
== 0) {
991 rb
->splash(HZ
*2, "File is not a Sokoban level file");
997 /* Read filename of levelset */
998 rb
->read_line(fd
, buffered_boards
.filename
,
999 sizeof(buffered_boards
.filename
));
1001 /* Read full undo history */
1002 len
= rb
->read_line(fd
, undo_info
.history
, MAX_UNDOS
);
1004 /* Correct len when trailing \r's or \n's are counted */
1005 if (len
> 2 && undo_info
.history
[len
- 2] == '\0')
1007 else if (len
> 1 && undo_info
.history
[len
- 1] == '\0')
1012 /* Check to see if we're going to play a solution or resume progress */
1013 play_solution
= (buf
[8] == 'S');
1015 /* Get level number */
1016 for (n
= 0, i
= 10; buf
[i
] >= '0' && buf
[i
] <= '9' && i
< 15; i
++)
1017 n
= n
*10 + buf
[i
] - '0';
1018 current_info
.level
.index
= n
- 1;
1020 /* Get undo index */
1021 for (n
= 0, i
++; buf
[i
] >= '0' && buf
[i
] <= '9' && i
< 21; i
++)
1022 n
= n
*10 + buf
[i
] - '0';
1025 undo_info
.max
= len
;
1027 if (current_info
.level
.index
< 0) {
1029 rb
->splash(HZ
*2, "Error loading level");
1032 if (!read_levels(true))
1034 if (current_info
.level
.index
>= current_info
.max_level
) {
1036 rb
->splash(HZ
*2, "Error loading level");
1042 if (play_solution
) {
1043 rb
->lcd_clear_display();
1045 rb
->sleep(2*delay
[speed
]);
1047 /* Replay solution until menu button is pressed */
1051 if (!move(undo_info
.history
[i
], true)) {
1055 rb
->lcd_clear_display();
1061 rb
->sleep(delay
[speed
]);
1063 while ((button
= rb
->button_get(false)) || paused
) {
1066 /* Pretend the level is complete so we'll quit */
1067 current_info
.level
.boxes_to_go
= 0;
1071 /* Toggle pause state */
1076 case SOKOBAN_LEFT
| BUTTON_REPEAT
:
1077 /* Go back one move */
1081 rb
->lcd_clear_display();
1087 case SOKOBAN_RIGHT
| BUTTON_REPEAT
:
1088 /* Go forward one move */
1092 rb
->lcd_clear_display();
1098 case SOKOBAN_UP
| BUTTON_REPEAT
:
1100 if (speed
< sizeof(delay
)/sizeof(int) - 1)
1105 case SOKOBAN_DOWN
| BUTTON_REPEAT
:
1116 /* If level is complete, wait for keypress before quitting */
1117 if (current_info
.level
.boxes_to_go
== 0)
1118 rb
->button_get(true);
1121 /* Advance to current undo */
1122 for (i
= 0; i
< n
; i
++) {
1123 if (!move(undo_info
.history
[i
], true)) {
1129 rb
->button_clear_queue();
1130 rb
->lcd_clear_display();
1133 undo_info
.current
= n
;
1139 static int sokoban_menu(void)
1145 int start_selected
= 0;
1146 int prev_level
= current_info
.level
.index
;
1148 MENUITEM_STRINGLIST(menu
, "Sokoban Menu", NULL
,
1149 "Resume", "Select Level", "Audio Playback", "Keys",
1150 "Load Default Level Set", "Quit Without Saving",
1151 "Save Progress & Quit");
1155 selection
= rb
->do_menu(&menu
, &start_selected
, NULL
, false);
1157 switch (selection
) {
1158 case 0: /* Resume */
1161 case 1: /* Select level */
1162 current_info
.level
.index
++;
1163 rb
->set_int("Select Level", "", UNIT_INT
,
1164 ¤t_info
.level
.index
, NULL
, 1, 1,
1165 current_info
.max_level
, NULL
);
1166 current_info
.level
.index
--;
1167 if (prev_level
!= current_info
.level
.index
) {
1174 case 2: /* Audio playback control */
1175 playback_control(rb
, NULL
);
1181 rb
->screens
[i
]->clear_display();
1182 rb
->lcd_setfont(SOKOBAN_FONT
);
1184 #if (CONFIG_KEYPAD == RECORDER_PAD) || \
1185 (CONFIG_KEYPAD == ARCHOS_AV300_PAD)
1186 rb
->lcd_putsxy(3, 6, "[OFF] Menu");
1187 rb
->lcd_putsxy(3, 16, "[ON] Undo");
1188 rb
->lcd_putsxy(3, 26, "[PLAY] Redo");
1189 rb
->lcd_putsxy(3, 36, "[F1] Down a Level");
1190 rb
->lcd_putsxy(3, 46, "[F2] Restart Level");
1191 rb
->lcd_putsxy(3, 56, "[F3] Up a Level");
1192 #elif CONFIG_KEYPAD == ONDIO_PAD
1193 rb
->lcd_putsxy(3, 6, "[OFF] Menu");
1194 rb
->lcd_putsxy(3, 16, "[MODE] Undo");
1195 rb
->lcd_putsxy(3, 26, "[MODE+DOWN] Redo");
1196 rb
->lcd_putsxy(3, 36, "[MODE+LEFT] Previous Level");
1197 rb
->lcd_putsxy(3, 46, "[MODE+UP] Restart Level");
1198 rb
->lcd_putsxy(3, 56, "[MODE+RIGHT] Up Level");
1199 #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
1200 (CONFIG_KEYPAD == IRIVER_H300_PAD)
1201 rb
->lcd_putsxy(3, 6, "[STOP] Menu");
1202 rb
->lcd_putsxy(3, 16, "[REC] Undo");
1203 rb
->lcd_putsxy(3, 26, "[MODE] Redo");
1204 rb
->lcd_putsxy(3, 36, "[PLAY+DOWN] Previous Level");
1205 rb
->lcd_putsxy(3, 46, "[PLAY] Restart Level");
1206 rb
->lcd_putsxy(3, 56, "[PLAY+UP] Next Level");
1207 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
1208 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
1209 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
1210 rb
->lcd_putsxy(3, 6, "[SELECT+MENU] Menu");
1211 rb
->lcd_putsxy(3, 16, "[SELECT] Undo");
1212 rb
->lcd_putsxy(3, 26, "[SELECT+PLAY] Redo");
1213 rb
->lcd_putsxy(3, 36, "[SELECT+LEFT] Previous Level");
1214 rb
->lcd_putsxy(3, 46, "[SELECT+RIGHT] Next Level");
1215 #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
1216 rb
->lcd_putsxy(3, 6, "[POWER] Menu");
1217 rb
->lcd_putsxy(3, 16, "[SELECT] Undo");
1218 rb
->lcd_putsxy(3, 26, "[PLAY] Redo");
1219 rb
->lcd_putsxy(3, 36, "[REC] Restart Level");
1220 #elif CONFIG_KEYPAD == IRIVER_H10_PAD
1221 rb
->lcd_putsxy(3, 6, "[POWER] Menu");
1222 rb
->lcd_putsxy(3, 16, "[REW] Undo");
1223 rb
->lcd_putsxy(3, 26, "[FF] Redo");
1224 rb
->lcd_putsxy(3, 36, "[PLAY+DOWN] Previous Level");
1225 rb
->lcd_putsxy(3, 46, "[PLAY+RIGHT] Restart Level");
1226 rb
->lcd_putsxy(3, 56, "[PLAY+UP] Next Level");
1227 #elif CONFIG_KEYPAD == GIGABEAT_PAD
1228 rb
->lcd_putsxy(3, 6, "[POWER] Menu");
1229 rb
->lcd_putsxy(3, 16, "[SELECT] Undo");
1230 rb
->lcd_putsxy(3, 26, "[A] Redo");
1231 rb
->lcd_putsxy(3, 36, "[VOL-] Previous Level");
1232 rb
->lcd_putsxy(3, 46, "[MENU] Restart Level");
1233 rb
->lcd_putsxy(3, 56, "[VOL+] Next Level");
1234 #elif CONFIG_KEYPAD == SANSA_E200_PAD
1235 rb
->lcd_putsxy(3, 6, "[POWER] Menu");
1236 rb
->lcd_putsxy(3, 16, "[SELECT] Undo");
1237 rb
->lcd_putsxy(3, 26, "[REC] Redo");
1238 rb
->lcd_putsxy(3, 36, "[SELECT+DOWN] Previous Level");
1239 rb
->lcd_putsxy(3, 46, "[SELECT+RIGHT] Restart Level");
1240 rb
->lcd_putsxy(3, 56, "[SELECT+UP] Next Level");
1243 #ifdef HAVE_TOUCHPAD
1244 rb
->lcd_putsxy(3, 6, SOKOBAN_MENU_NAME
" Menu");
1245 rb
->lcd_putsxy(3, 16, SOKOBAN_UNDO_NAME
" Undo");
1246 rb
->lcd_putsxy(3, 26, SOKOBAN_REDO_NAME
" Redo");
1247 rb
->lcd_putsxy(3, 36, SOKOBAN_PAUSE_NAME
" Pause");
1248 rb
->lcd_putsxy(3, 46, SOKOBAN_LEVEL_REPEAT_NAME
" Restart Level");
1252 rb
->screens
[i
]->update();
1254 /* Display until keypress */
1257 button
= rb
->button_get(false);
1258 } while (!button
|| button
& BUTTON_REL
||
1259 button
& BUTTON_REPEAT
);
1264 case 4: /* Load default levelset */
1266 if (!read_levels(true))
1267 return 5; /* Quit */
1274 case 6: /* Save & quit */
1275 save(SOKOBAN_SAVE_FILE
, false);
1276 rb
->reload_directory();
1279 } while (!menu_quit
);
1282 rb
->lcd_setfont(SOKOBAN_FONT
);
1285 rb
->screens
[i
]->clear_display();
1286 rb
->screens
[i
]->update();
1292 static bool sokoban_loop(void)
1295 int i
= 0, button
= 0, lastbutton
= 0;
1303 r
= current_info
.player
.row
;
1304 c
= current_info
.player
.col
;
1306 button
= rb
->button_get(true);
1310 #ifdef SOKOBAN_RC_MENU
1311 case SOKOBAN_RC_MENU
:
1314 switch (sokoban_menu()) {
1316 case 6: /* Save & quit */
1323 #ifdef SOKOBAN_UNDO_PRE
1324 if (lastbutton
!= SOKOBAN_UNDO_PRE
)
1326 #else /* repeat can't work here for Ondio, iPod, et al */
1327 case SOKOBAN_UNDO
| BUTTON_REPEAT
:
1330 rb
->lcd_clear_display();
1336 case SOKOBAN_REDO
| BUTTON_REPEAT
:
1338 rb
->lcd_clear_display();
1343 #ifdef SOKOBAN_LEVEL_UP
1344 case SOKOBAN_LEVEL_UP
:
1345 case SOKOBAN_LEVEL_UP
| BUTTON_REPEAT
:
1348 if (current_info
.level
.index
+ 1 < current_info
.max_level
)
1349 current_info
.level
.index
++;
1355 #ifdef SOKOBAN_LEVEL_DOWN
1356 case SOKOBAN_LEVEL_DOWN
:
1357 case SOKOBAN_LEVEL_DOWN
| BUTTON_REPEAT
:
1358 /* previous level */
1360 if (current_info
.level
.index
> 0)
1361 current_info
.level
.index
--;
1367 #ifdef SOKOBAN_LEVEL_REPEAT
1368 case SOKOBAN_LEVEL_REPEAT
:
1369 case SOKOBAN_LEVEL_REPEAT
| BUTTON_REPEAT
:
1377 case SOKOBAN_LEFT
| BUTTON_REPEAT
:
1378 moved
= move(SOKOBAN_MOVE_LEFT
, false);
1382 case SOKOBAN_RIGHT
| BUTTON_REPEAT
:
1383 moved
= move(SOKOBAN_MOVE_RIGHT
, false);
1387 case SOKOBAN_UP
| BUTTON_REPEAT
:
1388 moved
= move(SOKOBAN_MOVE_UP
, false);
1392 case SOKOBAN_DOWN
| BUTTON_REPEAT
:
1393 moved
= move(SOKOBAN_MOVE_DOWN
, false);
1397 if (rb
->default_event_handler(button
) == SYS_USB_CONNECTED
)
1398 return PLUGIN_USB_CONNECTED
;
1402 lastbutton
= button
;
1405 rb
->lcd_clear_display();
1409 /* We have completed this level */
1410 if (current_info
.level
.boxes_to_go
== 0) {
1413 rb
->lcd_clear_display();
1415 /* Show level complete message & stats */
1416 rb
->snprintf(buf
, sizeof(buf
), "Level %d Complete!",
1417 current_info
.level
.index
+ 1);
1418 rb
->lcd_getstringsize(buf
, &w
, &h
);
1419 rb
->lcd_putsxy(LCD_WIDTH
/2 - w
/2, LCD_HEIGHT
/2 - h
*3, buf
);
1421 rb
->snprintf(buf
, sizeof(buf
), "%4d Moves ",
1422 current_info
.level
.moves
);
1423 rb
->lcd_getstringsize(buf
, &w
, &h
);
1424 rb
->lcd_putsxy(LCD_WIDTH
/2 - w
/2, LCD_HEIGHT
/2 - h
, buf
);
1426 rb
->snprintf(buf
, sizeof(buf
), "%4d Pushes",
1427 current_info
.level
.pushes
);
1428 rb
->lcd_getstringsize(buf
, &w
, &h
);
1429 rb
->lcd_putsxy(LCD_WIDTH
/2 - w
/2, LCD_HEIGHT
/2, buf
);
1431 if (undo_info
.count
< MAX_UNDOS
) {
1432 rb
->snprintf(buf
, sizeof(buf
), "%s: Save solution",
1434 rb
->lcd_getstringsize(buf
, &w
, &h
);
1435 rb
->lcd_putsxy(LCD_WIDTH
/2 - w
/2, LCD_HEIGHT
/2 + h
*2, buf
);
1440 rb
->button_clear_queue();
1442 /* Display for 4 seconds or until new keypress */
1443 for (i
= 0; i
< 80; i
++) {
1445 button
= rb
->button_get(false);
1446 if (button
&& !(button
& BUTTON_REL
) &&
1447 !(button
& BUTTON_REPEAT
))
1451 if (button
== BUTTON_SAVE
) {
1452 if (undo_info
.count
< MAX_UNDOS
) {
1453 /* Set filename to current levelset plus level number
1454 * and .sok extension. Use SAVE_FOLDER if using the
1455 * default levelset, since it's in a hidden folder. */
1456 if (rb
->strcmp(buffered_boards
.filename
,
1457 SOKOBAN_LEVELS_FILE
) == 0) {
1458 rb
->snprintf(buf
, sizeof(buf
),
1459 "%s/sokoban.%d.sok",
1460 SOKOBAN_SAVE_FOLDER
,
1461 current_info
.level
.index
+ 1);
1463 if ((loc
= rb
->strrchr(buffered_boards
.filename
,
1466 rb
->snprintf(buf
, sizeof(buf
), "%s.%d.sok",
1467 buffered_boards
.filename
,
1468 current_info
.level
.index
+ 1);
1473 if (!rb
->kbd_input(buf
, MAX_PATH
))
1476 rb
->splash(HZ
*2, "Solution too long to save");
1478 rb
->lcd_setfont(SOKOBAN_FONT
); /* Restore font */
1483 rb
->screens
[i
]->clear_display();
1484 rb
->screens
[i
]->update();
1487 current_info
.level
.index
++;
1489 /* clear undo stats */
1492 if (current_info
.level
.index
>= current_info
.max_level
) {
1493 /* Show levelset complete message */
1494 rb
->snprintf(buf
, sizeof(buf
), "You WIN!!");
1495 rb
->lcd_getstringsize(buf
, &w
, &h
);
1496 rb
->lcd_putsxy(LCD_WIDTH
/2 - w
/2, LCD_HEIGHT
/2 - h
/2, buf
);
1498 rb
->lcd_set_drawmode(DRMODE_COMPLEMENT
);
1499 /* Display for 4 seconds or until keypress */
1500 for (i
= 0; i
< 80; i
++) {
1501 rb
->lcd_fillrect(0, 0, LCD_WIDTH
, LCD_HEIGHT
);
1505 button
= rb
->button_get(false);
1506 if (button
&& !(button
& BUTTON_REL
))
1509 rb
->lcd_set_drawmode(DRMODE_SOLID
);
1511 /* Reset to first level & show quit menu */
1512 current_info
.level
.index
= 0;
1514 switch (sokoban_menu()) {
1516 case 6: /* Save & quit */
1531 enum plugin_status
plugin_start(const struct plugin_api
* api
, const void* parameter
)
1538 rb
->lcd_setfont(SOKOBAN_FONT
);
1540 rb
->lcd_clear_display();
1541 rb
->lcd_getstringsize(SOKOBAN_TITLE
, &w
, &h
);
1542 rb
->lcd_putsxy(LCD_WIDTH
/2 - w
/2, LCD_HEIGHT
/2 - h
/2, SOKOBAN_TITLE
);
1544 rb
->sleep(HZ
); /* Show title for 1 second */
1548 if (parameter
== NULL
) {
1549 /* Attempt to resume saved progress, otherwise start at beginning */
1550 if (!load(SOKOBAN_SAVE_FILE
, true)) {
1552 if (!read_levels(true))
1558 /* The plugin is being used to open a file */
1559 if (load((char*) parameter
, false)) {
1560 /* If we loaded & played a solution, quit */
1561 if (current_info
.level
.boxes_to_go
== 0)
1567 rb
->lcd_clear_display();
1570 return sokoban_loop();