summaryrefslogtreecommitdiff
path: root/apps/plugins/sokoban.c
diff options
context:
space:
mode:
authorAntoine Cellerier <dionoea@videolan.org>2007-06-28 20:45:00 +0000
committerAntoine Cellerier <dionoea@videolan.org>2007-06-28 20:45:00 +0000
commitf91d06de7bf724e8e0aa580c18efa3eb345f88f9 (patch)
tree29c6446a4556cd074dbee3c3d97cdaae207ff228 /apps/plugins/sokoban.c
parent7a1108227b67fb62f3d3447d795447b5b631ed32 (diff)
downloadrockbox-f91d06de7bf724e8e0aa580c18efa3eb345f88f9.tar.gz
rockbox-f91d06de7bf724e8e0aa580c18efa3eb345f88f9.zip
Apply FS #6702: More Sokoban Improvements.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@13731 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps/plugins/sokoban.c')
-rw-r--r--apps/plugins/sokoban.c1355
1 files changed, 880 insertions, 475 deletions
diff --git a/apps/plugins/sokoban.c b/apps/plugins/sokoban.c
index 40a2f549ad..976f552dbd 100644
--- a/apps/plugins/sokoban.c
+++ b/apps/plugins/sokoban.c
@@ -10,6 +10,7 @@
10 * Copyright (C) 2002 Eric Linenberg 10 * Copyright (C) 2002 Eric Linenberg
11 * February 2003: Robert Hak performs a cleanup/rewrite/feature addition. 11 * February 2003: Robert Hak performs a cleanup/rewrite/feature addition.
12 * Eric smiles. Bjorn cries. Linus say 'huh?'. 12 * Eric smiles. Bjorn cries. Linus say 'huh?'.
13 * March 2007: Sean Morrisey performs a major rewrite/feature addition.
13 * 14 *
14 * All files in this archive are subject to the GNU General Public License. 15 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement. 16 * See the file COPYING in the source tree root for full license agreement.
@@ -19,95 +20,126 @@
19 * 20 *
20 ****************************************************************************/ 21 ****************************************************************************/
21#include "plugin.h" 22#include "plugin.h"
23#include "lib/playback_control.h"
22 24
23#ifdef HAVE_LCD_BITMAP 25#ifdef HAVE_LCD_BITMAP
24 26
25PLUGIN_HEADER 27PLUGIN_HEADER
26 28
27#if LCD_DEPTH >= 2 29#if LCD_DEPTH >= 2 && ((LCD_HEIGHT >= 96 && LCD_WIDTH >= 152) || \
30 (LCD_HEIGHT >= 121 && LCD_WIDTH >= 120))
28extern const fb_data sokoban_tiles[]; 31extern const fb_data sokoban_tiles[];
29#endif 32#endif
30 33
31#define SOKOBAN_TITLE "Sokoban" 34#define SOKOBAN_TITLE "Sokoban"
32 35
33#define LEVELS_FILE PLUGIN_DIR "/sokoban.levels" 36#define SOKOBAN_LEVELS_FILE PLUGIN_DIR "/sokobanlevels.sok"
37#define SOKOBAN_SAVE_FILE PLUGIN_DIR "/sokobansave.sok"
34 38
35#define ROWS 16 39/* Magnify is the number of pixels for each block.
36#define COLS 20 40 * Set dynamically so all targets can support levels
37#define SOKOBAN_LEVEL_SIZE (ROWS*COLS) 41 * that fill their entire screen, less the stat box.
38#define MAX_BUFFERED_BOARDS 500 42 * 16 rows & 20 cols minimum */
39/* Use either all but 12k of the plugin buffer for board data 43#if (LCD_HEIGHT >= 224) && (LCD_WIDTH >= 320)
40 or just enough for MAX_BUFFERED_BOARDS, which ever is less */ 44#define MAGNIFY 14
41#if (PLUGIN_BUFFER_SIZE - 0x3000)/SOKOBAN_LEVEL_SIZE < MAX_BUFFERED_BOARDS 45#define ROWS (LCD_HEIGHT/MAGNIFY)
42#define NUM_BUFFERED_BOARDS (PLUGIN_BUFFER_SIZE - 0x3000)/SOKOBAN_LEVEL_SIZE 46#define COLS ((LCD_WIDTH-40)/MAGNIFY)
47#elif (LCD_HEIGHT >= 249) && (LCD_WIDTH >= 280)
48#define MAGNIFY 14
49#define ROWS ((LCD_HEIGHT-25)/MAGNIFY)
50#define COLS (LCD_WIDTH/MAGNIFY)
51#elif (LCD_HEIGHT >= 144) && (LCD_WIDTH >= 220)
52#define MAGNIFY 9
53#define ROWS (LCD_HEIGHT/MAGNIFY)
54#define COLS ((LCD_WIDTH-40)/MAGNIFY)
55#elif (LCD_HEIGHT >= 169) && (LCD_WIDTH+4 >= 180) /* plus 4 for sansa */
56#define MAGNIFY 9
57#define ROWS ((LCD_HEIGHT-25)/MAGNIFY)
58#define COLS ((LCD_WIDTH+4)/MAGNIFY)
59#elif (LCD_HEIGHT >= 96) && (LCD_WIDTH >= 160)
60#define MAGNIFY 6
61#define ROWS (LCD_HEIGHT/MAGNIFY)
62#define COLS ((LCD_WIDTH-40)/MAGNIFY)
63#elif (LCD_HEIGHT >= 121) && (LCD_WIDTH >= 120)
64#define MAGNIFY 6
65#define ROWS ((LCD_HEIGHT-25)/MAGNIFY)
66#define COLS (LCD_WIDTH/MAGNIFY)
43#else 67#else
44#define NUM_BUFFERED_BOARDS MAX_BUFFERED_BOARDS 68#define MAGNIFY 4
69#define ROWS 16
70#define COLS 20
45#endif 71#endif
46/* Use 4k plus remaining plugin buffer (-8k for prog) for undo, up to 32k */ 72
47#if PLUGIN_BUFFER_SIZE - NUM_BUFFERED_BOARDS*SOKOBAN_LEVEL_SIZE - 0x2000 > \ 73/* Use either all but 16k of the plugin buffer for level data
48 0x7FFF 74 * or 128k, which ever is less */
49#define MAX_UNDOS 0x7FFF 75#if PLUGIN_BUFFER_SIZE - 0x4000 < 0x20000
76#define MAX_LEVEL_DATA (PLUGIN_BUFFER_SIZE - 0x4000)
77#else
78#define MAX_LEVEL_DATA 0x20000
79#endif
80
81/* Number of levels for which to allocate buffer indexes */
82#define MAX_LEVELS MAX_LEVEL_DATA/70
83
84/* Use 4k plus remaining plugin buffer (-12k for prog) for undo, up to 64k */
85#if PLUGIN_BUFFER_SIZE - MAX_LEVEL_DATA - 0x3000 > 0x10000
86#define MAX_UNDOS 0x10000
50#else 87#else
51#define MAX_UNDOS PLUGIN_BUFFER_SIZE - \ 88#define MAX_UNDOS (PLUGIN_BUFFER_SIZE - MAX_LEVEL_DATA - 0x3000)
52 NUM_BUFFERED_BOARDS*SOKOBAN_LEVEL_SIZE - 0x2000
53#endif 89#endif
54 90
55/* Move/push definitions for undo */ 91/* Move/push definitions for undo */
56enum { 92#define SOKOBAN_PUSH_LEFT 'L'
57 SOKOBAN_PUSH_LEFT, 93#define SOKOBAN_PUSH_RIGHT 'R'
58 SOKOBAN_PUSH_RIGHT, 94#define SOKOBAN_PUSH_UP 'U'
59 SOKOBAN_PUSH_UP, 95#define SOKOBAN_PUSH_DOWN 'D'
60 SOKOBAN_PUSH_DOWN, 96#define SOKOBAN_MOVE_LEFT 'l'
61 SOKOBAN_MOVE_LEFT, 97#define SOKOBAN_MOVE_RIGHT 'r'
62 SOKOBAN_MOVE_RIGHT, 98#define SOKOBAN_MOVE_UP 'u'
63 SOKOBAN_MOVE_UP, 99#define SOKOBAN_MOVE_DOWN 'd'
64 SOKOBAN_MOVE_DOWN 100
65}; 101#define SOKOBAN_MOVE_DIFF (SOKOBAN_MOVE_LEFT-SOKOBAN_PUSH_LEFT)
66#define SOKOBAN_MOVE_DIFF (SOKOBAN_MOVE_LEFT-SOKOBAN_PUSH_LEFT) 102#define SOKOBAN_MOVE_MIN SOKOBAN_MOVE_DOWN
67#define SOKOBAN_MOVE_MIN SOKOBAN_MOVE_LEFT
68 103
69/* variable button definitions */ 104/* variable button definitions */
70#if CONFIG_KEYPAD == RECORDER_PAD 105#if (CONFIG_KEYPAD == RECORDER_PAD) || \
106 (CONFIG_KEYPAD == ARCHOS_AV300_PAD)
71#define SOKOBAN_UP BUTTON_UP 107#define SOKOBAN_UP BUTTON_UP
72#define SOKOBAN_DOWN BUTTON_DOWN 108#define SOKOBAN_DOWN BUTTON_DOWN
73#define SOKOBAN_QUIT BUTTON_OFF 109#define SOKOBAN_MENU BUTTON_OFF
74#define SOKOBAN_UNDO BUTTON_ON 110#define SOKOBAN_UNDO BUTTON_ON
75#define SOKOBAN_REDO BUTTON_PLAY 111#define SOKOBAN_REDO BUTTON_PLAY
76#define SOKOBAN_LEVEL_UP BUTTON_F3
77#define SOKOBAN_LEVEL_DOWN BUTTON_F1 112#define SOKOBAN_LEVEL_DOWN BUTTON_F1
78#define SOKOBAN_LEVEL_REPEAT BUTTON_F2 113#define SOKOBAN_LEVEL_REPEAT BUTTON_F2
79
80#elif CONFIG_KEYPAD == ARCHOS_AV300_PAD
81#define SOKOBAN_UP BUTTON_UP
82#define SOKOBAN_DOWN BUTTON_DOWN
83#define SOKOBAN_QUIT BUTTON_OFF
84#define SOKOBAN_UNDO BUTTON_ON
85#define SOKOBAN_REDO BUTTON_PLAY
86#define SOKOBAN_LEVEL_UP BUTTON_F3 114#define SOKOBAN_LEVEL_UP BUTTON_F3
87#define SOKOBAN_LEVEL_DOWN BUTTON_F1 115#define BUTTON_SAVE BUTTON_ON
88#define SOKOBAN_LEVEL_REPEAT BUTTON_F2 116#define BUTTON_SAVE_NAME "ON"
89 117
90#elif CONFIG_KEYPAD == ONDIO_PAD 118#elif CONFIG_KEYPAD == ONDIO_PAD
91#define SOKOBAN_UP BUTTON_UP 119#define SOKOBAN_UP BUTTON_UP
92#define SOKOBAN_DOWN BUTTON_DOWN 120#define SOKOBAN_DOWN BUTTON_DOWN
93#define SOKOBAN_QUIT BUTTON_OFF 121#define SOKOBAN_MENU BUTTON_OFF
94#define SOKOBAN_UNDO_PRE BUTTON_MENU 122#define SOKOBAN_UNDO_PRE BUTTON_MENU
95#define SOKOBAN_UNDO (BUTTON_MENU | BUTTON_REL) 123#define SOKOBAN_UNDO (BUTTON_MENU | BUTTON_REL)
96#define SOKOBAN_REDO (BUTTON_MENU | BUTTON_DOWN) 124#define SOKOBAN_REDO (BUTTON_MENU | BUTTON_DOWN)
97#define SOKOBAN_LEVEL_UP (BUTTON_MENU | BUTTON_RIGHT)
98#define SOKOBAN_LEVEL_DOWN (BUTTON_MENU | BUTTON_LEFT) 125#define SOKOBAN_LEVEL_DOWN (BUTTON_MENU | BUTTON_LEFT)
99#define SOKOBAN_LEVEL_REPEAT (BUTTON_MENU | BUTTON_UP) 126#define SOKOBAN_LEVEL_REPEAT (BUTTON_MENU | BUTTON_UP)
127#define SOKOBAN_LEVEL_UP (BUTTON_MENU | BUTTON_RIGHT)
128#define BUTTON_SAVE BUTTON_MENU
129#define BUTTON_SAVE_NAME "MENU"
100 130
101#elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \ 131#elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
102 (CONFIG_KEYPAD == IRIVER_H300_PAD) 132 (CONFIG_KEYPAD == IRIVER_H300_PAD)
103#define SOKOBAN_UP BUTTON_UP 133#define SOKOBAN_UP BUTTON_UP
104#define SOKOBAN_DOWN BUTTON_DOWN 134#define SOKOBAN_DOWN BUTTON_DOWN
105#define SOKOBAN_QUIT BUTTON_OFF 135#define SOKOBAN_MENU BUTTON_OFF
106#define SOKOBAN_UNDO BUTTON_REC 136#define SOKOBAN_UNDO BUTTON_REC
107#define SOKOBAN_REDO BUTTON_MODE 137#define SOKOBAN_REDO BUTTON_MODE
108#define SOKOBAN_LEVEL_UP (BUTTON_ON | BUTTON_UP)
109#define SOKOBAN_LEVEL_DOWN (BUTTON_ON | BUTTON_DOWN) 138#define SOKOBAN_LEVEL_DOWN (BUTTON_ON | BUTTON_DOWN)
110#define SOKOBAN_LEVEL_REPEAT BUTTON_ON 139#define SOKOBAN_LEVEL_REPEAT BUTTON_ON
140#define SOKOBAN_LEVEL_UP (BUTTON_ON | BUTTON_UP)
141#define BUTTON_SAVE BUTTON_MODE
142#define BUTTON_SAVE_NAME "MODE"
111 143
112#define SOKOBAN_RC_QUIT BUTTON_RC_STOP 144#define SOKOBAN_RC_QUIT BUTTON_RC_STOP
113 145
@@ -115,65 +147,70 @@ enum {
115 (CONFIG_KEYPAD == IPOD_3G_PAD) 147 (CONFIG_KEYPAD == IPOD_3G_PAD)
116#define SOKOBAN_UP BUTTON_MENU 148#define SOKOBAN_UP BUTTON_MENU
117#define SOKOBAN_DOWN BUTTON_PLAY 149#define SOKOBAN_DOWN BUTTON_PLAY
118#define SOKOBAN_QUIT (BUTTON_SELECT | BUTTON_MENU) 150#define SOKOBAN_MENU (BUTTON_SELECT | BUTTON_MENU)
119#define SOKOBAN_UNDO_PRE BUTTON_SELECT 151#define SOKOBAN_UNDO_PRE BUTTON_SELECT
120#define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL) 152#define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
121#define SOKOBAN_REDO (BUTTON_SELECT | BUTTON_PLAY) 153#define SOKOBAN_REDO (BUTTON_SELECT | BUTTON_PLAY)
122#define SOKOBAN_LEVEL_UP (BUTTON_SELECT | BUTTON_RIGHT)
123#define SOKOBAN_LEVEL_DOWN (BUTTON_SELECT | BUTTON_LEFT) 154#define SOKOBAN_LEVEL_DOWN (BUTTON_SELECT | BUTTON_LEFT)
155#define SOKOBAN_LEVEL_UP (BUTTON_SELECT | BUTTON_RIGHT)
156#define BUTTON_SAVE BUTTON_SELECT
157#define BUTTON_SAVE_NAME "SELECT"
124 158
125/* fixme: if/when simultaneous button presses work for X5, 159/* FIXME: if/when simultaneous button presses work for X5,
126 add redo & level repeat */ 160 * add redo & level repeat */
127#elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD) 161#elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
128#define SOKOBAN_UP BUTTON_UP 162#define SOKOBAN_UP BUTTON_UP
129#define SOKOBAN_DOWN BUTTON_DOWN 163#define SOKOBAN_DOWN BUTTON_DOWN
130#define SOKOBAN_QUIT BUTTON_POWER 164#define SOKOBAN_MENU BUTTON_POWER
131#define SOKOBAN_UNDO_PRE BUTTON_SELECT 165#define SOKOBAN_UNDO_PRE BUTTON_SELECT
132#define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL) 166#define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
133#define SOKOBAN_LEVEL_UP BUTTON_PLAY
134#define SOKOBAN_LEVEL_DOWN BUTTON_REC 167#define SOKOBAN_LEVEL_DOWN BUTTON_REC
168#define SOKOBAN_LEVEL_UP BUTTON_PLAY
169#define BUTTON_SAVE BUTTON_SELECT
170#define BUTTON_SAVE_NAME "SELECT"
135 171
136#elif (CONFIG_KEYPAD == GIGABEAT_PAD) 172#elif CONFIG_KEYPAD == IRIVER_H10_PAD
173#define SOKOBAN_UP BUTTON_SCROLL_UP
174#define SOKOBAN_DOWN BUTTON_SCROLL_DOWN
175#define SOKOBAN_MENU BUTTON_POWER
176#define SOKOBAN_UNDO_PRE BUTTON_REW
177#define SOKOBAN_UNDO (BUTTON_REW | BUTTON_REL)
178#define SOKOBAN_REDO BUTTON_FF
179#define SOKOBAN_LEVEL_DOWN (BUTTON_PLAY | BUTTON_SCROLL_DOWN)
180#define SOKOBAN_LEVEL_REPEAT (BUTTON_PLAY | BUTTON_RIGHT)
181#define SOKOBAN_LEVEL_UP (BUTTON_PLAY | BUTTON_SCROLL_UP)
182#define BUTTON_SAVE BUTTON_PLAY
183#define BUTTON_SAVE_NAME "PLAY"
184
185#elif CONFIG_KEYPAD == GIGABEAT_PAD
137#define SOKOBAN_UP BUTTON_UP 186#define SOKOBAN_UP BUTTON_UP
138#define SOKOBAN_DOWN BUTTON_DOWN 187#define SOKOBAN_DOWN BUTTON_DOWN
139#define SOKOBAN_QUIT BUTTON_POWER 188#define SOKOBAN_MENU BUTTON_POWER
140#define SOKOBAN_UNDO BUTTON_SELECT 189#define SOKOBAN_UNDO BUTTON_SELECT
141#define SOKOBAN_REDO BUTTON_A 190#define SOKOBAN_REDO BUTTON_A
142#define SOKOBAN_LEVEL_UP BUTTON_VOL_UP
143#define SOKOBAN_LEVEL_DOWN BUTTON_VOL_DOWN 191#define SOKOBAN_LEVEL_DOWN BUTTON_VOL_DOWN
144#define SOKOBAN_LEVEL_REPEAT BUTTON_MENU 192#define SOKOBAN_LEVEL_REPEAT BUTTON_MENU
193#define SOKOBAN_LEVEL_UP BUTTON_VOL_UP
194#define BUTTON_SAVE BUTTON_SELECT
195#define BUTTON_SAVE_NAME "SELECT"
145 196
146#elif (CONFIG_KEYPAD == SANSA_E200_PAD) 197#elif CONFIG_KEYPAD == SANSA_E200_PAD
147#define SOKOBAN_UP BUTTON_UP 198#define SOKOBAN_UP BUTTON_UP
148#define SOKOBAN_DOWN BUTTON_DOWN 199#define SOKOBAN_DOWN BUTTON_DOWN
149#define SOKOBAN_QUIT BUTTON_POWER 200#define SOKOBAN_MENU BUTTON_POWER
150#define SOKOBAN_UNDO_PRE BUTTON_SELECT 201#define SOKOBAN_UNDO_PRE BUTTON_SELECT
151#define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL) 202#define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
152#define SOKOBAN_REDO BUTTON_REC 203#define SOKOBAN_REDO BUTTON_REC
153#define SOKOBAN_LEVEL_UP (BUTTON_SELECT | BUTTON_UP)
154#define SOKOBAN_LEVEL_DOWN (BUTTON_SELECT | BUTTON_DOWN) 204#define SOKOBAN_LEVEL_DOWN (BUTTON_SELECT | BUTTON_DOWN)
155#define SOKOBAN_LEVEL_REPEAT (BUTTON_SELECT | BUTTON_RIGHT) 205#define SOKOBAN_LEVEL_REPEAT (BUTTON_SELECT | BUTTON_RIGHT)
156 206#define SOKOBAN_LEVEL_UP (BUTTON_SELECT | BUTTON_UP)
157#elif (CONFIG_KEYPAD == IRIVER_H10_PAD) 207#define BUTTON_SAVE BUTTON_SELECT
158#define SOKOBAN_UP BUTTON_SCROLL_UP 208#define BUTTON_SAVE_NAME "SELECT"
159#define SOKOBAN_DOWN BUTTON_SCROLL_DOWN
160#define SOKOBAN_QUIT BUTTON_POWER
161#define SOKOBAN_UNDO_PRE BUTTON_REW
162#define SOKOBAN_UNDO (BUTTON_REW | BUTTON_REL)
163#define SOKOBAN_REDO BUTTON_FF
164#define SOKOBAN_LEVEL_UP (BUTTON_PLAY | BUTTON_SCROLL_UP)
165#define SOKOBAN_LEVEL_DOWN (BUTTON_PLAY | BUTTON_SCROLL_DOWN)
166#define SOKOBAN_LEVEL_REPEAT (BUTTON_PLAY | BUTTON_RIGHT)
167 209
168#endif 210#endif
169 211
170#ifdef HAVE_LCD_COLOR 212#define SOKOBAN_FONT FONT_SYSFIXED
171/* Background color. Default Rockbox light blue. */
172#define BG_COLOR LCD_RGBPACK(181, 199, 231)
173 213
174#elif LCD_DEPTH >= 2
175#define MEDIUM_GRAY LCD_BRIGHTNESS(127)
176#endif
177 214
178/* The Location, Undo and LevelInfo structs are OO-flavored. 215/* The Location, Undo and LevelInfo structs are OO-flavored.
179 * (oooh!-flavored as Schnueff puts it.) It makes more you have to know, 216 * (oooh!-flavored as Schnueff puts it.) It makes more you have to know,
@@ -181,10 +218,12 @@ enum {
181 218
182/* Level data & stats */ 219/* Level data & stats */
183struct LevelInfo { 220struct LevelInfo {
184 short level; 221 short index; /* Level index (level number - 1) */
185 short moves; 222 int moves; /* Moves & pushes for the stats */
186 short pushes; 223 int pushes;
187 short boxes_to_go; 224 short boxes_to_go; /* Number of unplaced boxes remaining in level */
225 short height; /* Height & width for centering level display */
226 short width;
188}; 227};
189 228
190struct Location { 229struct Location {
@@ -192,39 +231,42 @@ struct Location {
192 short col; 231 short col;
193}; 232};
194 233
195struct Board {
196 char spaces[ROWS][COLS];
197};
198
199/* Our full undo history */ 234/* Our full undo history */
200static struct UndoInfo { 235static struct UndoInfo {
201 short count; /* How many undos are left */ 236 int count; /* How many undos have been done */
202 short current; /* Which history is the current undo */ 237 int current; /* Which history is the current undo */
203 short max; /* Which history is the max redoable */ 238 int max; /* Which history is the max redoable */
204 char history[MAX_UNDOS]; 239 char history[MAX_UNDOS];
205} undo_info; 240} undo_info;
206 241
207/* Our playing board */ 242/* Our playing board */
208static struct BoardInfo { 243static struct BoardInfo {
209 char board[ROWS][COLS]; 244 char board[ROWS][COLS]; /* The current board data */
210 struct LevelInfo level; 245 struct LevelInfo level; /* Level data & stats */
211 struct Location player; 246 struct Location player; /* Where the player is */
212 int max_level; /* How many levels do we have? */ 247 int max_level; /* The number of levels we have */
213 int loaded_level; /* Which level is in memory */
214} current_info; 248} current_info;
215 249
216static struct BufferedBoards { 250static struct BufferedBoards {
217 struct Board levels[NUM_BUFFERED_BOARDS]; 251 char filename[MAX_PATH]; /* Filename of the levelset we're using */
218 int low; 252 char data[MAX_LEVEL_DATA]; /* Buffered level data */
253 int index[MAX_LEVELS + 1]; /* Where each buffered board begins & ends */
254 int start; /* Index of first buffered board */
255 int end; /* Index of last buffered board */
256 short prebuffered_boards; /* Number of boards before current to store */
219} buffered_boards; 257} buffered_boards;
220 258
259
221static struct plugin_api* rb; 260static struct plugin_api* rb;
222 261
262static char buf[ROWS*(COLS + 1)]; /* Enough for a whole board or a filename */
263
264
223static void init_undo(void) 265static void init_undo(void)
224{ 266{
225 undo_info.count = 0; 267 undo_info.count = 0;
226 undo_info.current = -1; 268 undo_info.current = 0;
227 undo_info.max = -1; 269 undo_info.max = 0;
228} 270}
229 271
230static void get_delta(char direction, short *d_r, short *d_c) 272static void get_delta(char direction, short *d_r, short *d_c)
@@ -261,9 +303,17 @@ static void undo(void)
261 bool undo_push = false; 303 bool undo_push = false;
262 304
263 /* If no more undos or we've wrapped all the way around, quit */ 305 /* If no more undos or we've wrapped all the way around, quit */
264 if (undo_info.count == 0 || undo_info.current-1 == undo_info.max) 306 if (undo_info.count == 0 || undo_info.current - 1 == undo_info.max)
265 return; 307 return;
266 308
309 /* Move to previous undo in the list */
310 if (undo_info.current == 0 && undo_info.count > 1)
311 undo_info.current = MAX_UNDOS - 1;
312 else
313 undo_info.current--;
314
315 undo_info.count--;
316
267 undo = undo_info.history[undo_info.current]; 317 undo = undo_info.history[undo_info.current];
268 318
269 if (undo < SOKOBAN_MOVE_MIN) 319 if (undo < SOKOBAN_MOVE_MIN)
@@ -281,17 +331,17 @@ static void undo(void)
281 331
282 /* Update board info */ 332 /* Update board info */
283 if (undo_push) { 333 if (undo_push) {
284 /* Moving box from goal to blank */ 334 /* Moving box from goal to floor */
285 if (*space_next == '%' && *space_cur == '@') 335 if (*space_next == '*' && *space_cur == '@')
286 current_info.level.boxes_to_go++; 336 current_info.level.boxes_to_go++;
287 /* Moving box from blank to goal */ 337 /* Moving box from floor to goal */
288 else if (*space_next == '$' && *space_cur == '+') 338 else if (*space_next == '$' && *space_cur == '+')
289 current_info.level.boxes_to_go--; 339 current_info.level.boxes_to_go--;
290 340
291 /* Move box off of next space... */ 341 /* Move box off of next space... */
292 *space_next = (*space_next == '%' ? '.' : ' '); 342 *space_next = (*space_next == '*' ? '.' : ' ');
293 /* ...and on to current space */ 343 /* ...and on to current space */
294 *space_cur = (*space_cur == '+' ? '%' : '$'); 344 *space_cur = (*space_cur == '+' ? '*' : '$');
295 345
296 current_info.level.pushes--; 346 current_info.level.pushes--;
297 } else 347 } else
@@ -306,27 +356,19 @@ static void undo(void)
306 356
307 current_info.level.moves--; 357 current_info.level.moves--;
308 358
309 /* Move to previous undo in the list */
310 if (undo_info.current == 0 && undo_info.count > 1)
311 undo_info.current = MAX_UNDOS - 1;
312 else
313 undo_info.current--;
314
315 undo_info.count--;
316
317 return; 359 return;
318} 360}
319 361
320static void add_undo(char undo) 362static void add_undo(char undo)
321{ 363{
364 undo_info.history[undo_info.current] = undo;
365
322 /* Wrap around if MAX_UNDOS exceeded */ 366 /* Wrap around if MAX_UNDOS exceeded */
323 if (undo_info.current < (MAX_UNDOS - 1)) 367 if (undo_info.current < (MAX_UNDOS - 1))
324 undo_info.current++; 368 undo_info.current++;
325 else 369 else
326 undo_info.current = 0; 370 undo_info.current = 0;
327 371
328 undo_info.history[undo_info.current] = undo;
329
330 if (undo_info.count < MAX_UNDOS) 372 if (undo_info.count < MAX_UNDOS)
331 undo_info.count++; 373 undo_info.count++;
332} 374}
@@ -353,34 +395,37 @@ static bool move(char direction, bool redo)
353 space_next = &current_info.board[r + d_r][c + d_c]; 395 space_next = &current_info.board[r + d_r][c + d_c];
354 space_beyond = &current_info.board[r + 2*d_r][c + 2*d_c]; 396 space_beyond = &current_info.board[r + 2*d_r][c + 2*d_c];
355 397
356 if (*space_next == '$' || *space_next == '%') { 398 if (*space_next == '$' || *space_next == '*') {
357 /* Change direction from move to push for undo */ 399 /* Change direction from move to push for undo */
358 if (direction >= SOKOBAN_MOVE_MIN) 400 if (direction >= SOKOBAN_MOVE_MIN)
359 direction -= SOKOBAN_MOVE_DIFF; 401 direction -= SOKOBAN_MOVE_DIFF;
360 push = true; 402 push = true;
361 } 403 }
404 else if (direction < SOKOBAN_MOVE_MIN)
405 /* Change back to move if redo/solution playback push is invalid */
406 direction += SOKOBAN_MOVE_DIFF;
362 407
363 /* Update board info */ 408 /* Update board info */
364 if (push) { 409 if (push) {
365 /* Moving box from goal to blank */ 410 /* Moving box from goal to floor */
366 if (*space_next == '%' && *space_beyond == ' ') 411 if (*space_next == '*' && *space_beyond == ' ')
367 current_info.level.boxes_to_go++; 412 current_info.level.boxes_to_go++;
368 /* Moving box from blank to goal */ 413 /* Moving box from floor to goal */
369 else if (*space_next == '$' && *space_beyond == '.') 414 else if (*space_next == '$' && *space_beyond == '.')
370 current_info.level.boxes_to_go--; 415 current_info.level.boxes_to_go--;
371 /* Check for illegal move */ 416 /* Check for invalid move */
372 else if (*space_beyond != '.' && *space_beyond != ' ') 417 else if (*space_beyond != '.' && *space_beyond != ' ')
373 return false; 418 return false;
374 419
375 /* Move player onto next space */ 420 /* Move player onto next space */
376 *space_next = (*space_next == '%' ? '+' : '@'); 421 *space_next = (*space_next == '*' ? '+' : '@');
377 /* Move box onto space beyond next */ 422 /* Move box onto space beyond next */
378 *space_beyond = (*space_beyond == '.' ? '%' : '$'); 423 *space_beyond = (*space_beyond == '.' ? '*' : '$');
379 424
380 current_info.level.pushes++; 425 current_info.level.pushes++;
381 } else { 426 } else {
382 /* Check for illegal move */ 427 /* Check for invalid move */
383 if (*space_next == '#' || *space_next == 'X') 428 if (*space_next != '.' && *space_next != ' ')
384 return false; 429 return false;
385 430
386 /* Move player onto next space */ 431 /* Move player onto next space */
@@ -396,19 +441,19 @@ static bool move(char direction, bool redo)
396 current_info.level.moves++; 441 current_info.level.moves++;
397 442
398 /* Update undo_info.max to current on every normal move, 443 /* Update undo_info.max to current on every normal move,
399 except if it's the same as a redo. */ 444 * except if it's the same as a redo. */
400 /* normal move */ 445 /* normal move and either */
401 if (!redo && 446 if (!redo &&
402 /* moves have been undone */ 447 /* moves have been undone... */
403 ((undo_info.max != undo_info.current && 448 ((undo_info.max != undo_info.current &&
404 /* and the current move is NOT the same as the one in history */ 449 /* ...and the current move is NOT the same as the one in history */
405 undo_info.history[undo_info.current+1] != direction) || 450 undo_info.history[undo_info.current] != direction) ||
406 /* or moves have not been undone */ 451 /* or moves have not been undone */
407 undo_info.max == undo_info.current)) { 452 undo_info.max == undo_info.current)) {
408 add_undo(direction); 453 add_undo(direction);
409 undo_info.max = undo_info.current; 454 undo_info.max = undo_info.current;
410 } else /* redo move or move was same as redo */ 455 } else /* redo move or move was same as redo */
411 add_undo(direction); /* (just to update current) */ 456 add_undo(direction); /* add_undo to update current */
412 457
413 return true; 458 return true;
414} 459}
@@ -420,290 +465,370 @@ static bool redo(void)
420 if (undo_info.current == undo_info.max) 465 if (undo_info.current == undo_info.max)
421 return false; 466 return false;
422 467
423 return move(undo_info.history[(undo_info.current+1 < MAX_UNDOS ? 468 return move(undo_info.history[(undo_info.current < MAX_UNDOS ?
424 undo_info.current+1 : 0)], true); 469 undo_info.current : 0)], true);
425} 470}
426#endif 471#endif
427 472
428static void init_boards(void) 473static void init_boards(void)
429{ 474{
430 current_info.level.level = 0; 475 rb->strncpy(buffered_boards.filename, SOKOBAN_LEVELS_FILE, MAX_PATH);
431 current_info.level.moves = 0; 476
432 current_info.level.pushes = 0; 477 current_info.level.index = 0;
433 current_info.level.boxes_to_go = 0;
434 current_info.player.row = 0; 478 current_info.player.row = 0;
435 current_info.player.col = 0; 479 current_info.player.col = 0;
436 current_info.max_level = 0; 480 current_info.max_level = 0;
437 current_info.loaded_level = 0;
438 481
439 buffered_boards.low = 0; 482 buffered_boards.start = 0;
483 buffered_boards.end = 0;
484 buffered_boards.prebuffered_boards = 0;
440 485
441 init_undo(); 486 init_undo();
442} 487}
443 488
444static int read_levels(int initialize_count) 489static bool read_levels(bool initialize)
445{ 490{
446 int fd = 0; 491 int fd = 0;
447 int len; 492 short len;
448 int lastlen = 0; 493 short lastlen = 0;
449 int row = 0; 494 short row = 0;
450 int level_count = 0; 495 int level_count = 0;
451 char buffer[COLS + 3]; /* COLS plus CR/LF and \0 */
452 int endpoint = current_info.level.level-1;
453
454 if (endpoint < buffered_boards.low)
455 endpoint = current_info.level.level - NUM_BUFFERED_BOARDS;
456 496
457 if (endpoint < 0) endpoint = 0; 497 int i = 0;
498 int level_len = 0;
499 bool index_set = false;
458 500
459 buffered_boards.low = endpoint; 501 /* Get the index of the first level to buffer */
460 endpoint += NUM_BUFFERED_BOARDS; 502 if (current_info.level.index > buffered_boards.prebuffered_boards &&
503 !initialize)
504 buffered_boards.start = current_info.level.index -
505 buffered_boards.prebuffered_boards;
506 else
507 buffered_boards.start = 0;
461 508
462 if ((fd = rb->open(LEVELS_FILE, O_RDONLY)) < 0) { 509 if ((fd = rb->open(buffered_boards.filename, O_RDONLY)) < 0) {
463 rb->splash(HZ*2, "Unable to open %s", LEVELS_FILE); 510 rb->splash(HZ*2, "Unable to open %s", buffered_boards.filename);
464 return -1; 511 return false;
465 } 512 }
466 513
467 do { 514 do {
468 len = rb->read_line(fd, buffer, sizeof(buffer)); 515 len = rb->read_line(fd, buf, sizeof(buf));
469 if (len >= 3) { 516
470 /* This finds lines that are more than 1 or 2 characters 517 /* Correct len when trailing \r's or \n's are counted */
471 * shorter than they should be. Due to the possibility of 518 if (len > 2 && buf[len - 2] == '\0')
472 * a mixed unix and dos CR/LF file format, I'm not going to 519 len -= 2;
473 * do a precise check */ 520 else if (len > 1 && buf[len - 1] == '\0')
474 if (len < COLS) { 521 len--;
475 rb->splash(HZ*2, "Error in levels file: short line"); 522
476 return -1; 523 /* Skip short lines & lines with non-level data */
477 } 524 if (len >= 3 && ((buf[0] >= '1' && buf[0] <= '9') || buf[0] == '#' ||
478 if (level_count >= buffered_boards.low && level_count < endpoint) { 525 buf[0] == ' ' || buf[0] == '-' || buf[0] == '_')) {
479 int index = level_count - buffered_boards.low; 526 if (level_count >= buffered_boards.start) {
480 rb->memcpy( 527 /* Set the index of this level */
481 buffered_boards.levels[index].spaces[row], buffer, COLS); 528 if (!index_set &&
529 level_count - buffered_boards.start < MAX_LEVELS) {
530 buffered_boards.index[level_count - buffered_boards.start]
531 = i;
532 index_set = true;
533 }
534 /* Copy buffer to board data */
535 if (i + level_len + len < MAX_LEVEL_DATA) {
536 rb->memcpy(&buffered_boards.data[i + level_len], buf, len);
537 buffered_boards.data[i + level_len + len] = '\n';
538 }
482 } 539 }
540 level_len += len + 1;
483 row++; 541 row++;
484 } else if (len) { 542
485 if (lastlen < 3) { 543 /* If newline & level is tall enough or is RLE */
486 /* Two short lines in a row means new level */ 544 } else if (buf[0] == '\0' && (row > 2 || lastlen > 22)) {
487 level_count++; 545 level_count++;
488 if (level_count >= endpoint && !initialize_count) break; 546 if (level_count >= buffered_boards.start) {
489 if (level_count && row != ROWS) { 547 i += level_len;
490 rb->splash(HZ*2, "Error in levels file: short board"); 548 if (i < MAX_LEVEL_DATA)
491 return -1; 549 buffered_boards.end = level_count;
492 } 550 else if (!initialize)
493 row = 0; 551 break;
494 } 552 }
495 } 553 row = 0;
496 } while ((lastlen=len)); 554 level_len = 0;
555 index_set = false;
497 556
498 rb->close(fd); 557 } else if (len > 22)
499 if (initialize_count) { 558 len = 1;
500 /* Plus one because there aren't trailing short lines in the file */ 559
501 current_info.max_level = level_count + 1; 560 } while ((lastlen = len));
561
562 /* Set the index of the end of the last level */
563 if (level_count - buffered_boards.start < MAX_LEVELS)
564 buffered_boards.index[level_count - buffered_boards.start] = i;
565
566 if (initialize) {
567 current_info.max_level = level_count;
568 buffered_boards.prebuffered_boards = buffered_boards.end/2;
502 } 569 }
503 return 0; 570
571 rb->close(fd);
572
573 return true;
504} 574}
505 575
506/* return non-zero on error */
507static void load_level(void) 576static void load_level(void)
508{ 577{
509 int c = 0; 578 int c, r;
510 int r = 0; 579 int i, n;
511 int index = current_info.level.level - buffered_boards.low - 1; 580 int level_size;
512 struct Board *level; 581 int index = current_info.level.index - buffered_boards.start;
513 582 char *level;
514 if (index < 0 || index >= NUM_BUFFERED_BOARDS) { 583
584 /* Get the buffered board index of the current level */
585 if (current_info.level.index < buffered_boards.start ||
586 current_info.level.index >= buffered_boards.end) {
515 read_levels(false); 587 read_levels(false);
516 index = index < 0 ? NUM_BUFFERED_BOARDS-1 : 0; 588 if (current_info.level.index > buffered_boards.prebuffered_boards)
589 index = buffered_boards.prebuffered_boards;
590 else
591 index = current_info.level.index;
517 } 592 }
518 level = &buffered_boards.levels[index]; 593 level = &buffered_boards.data[buffered_boards.index[index]];
519 594
520 current_info.level.boxes_to_go = 0; 595 /* Reset level info */
521 current_info.level.moves = 0; 596 current_info.level.moves = 0;
522 current_info.level.pushes = 0; 597 current_info.level.pushes = 0;
523 current_info.loaded_level = current_info.level.level; 598 current_info.level.boxes_to_go = 0;
599 current_info.level.width = 0;
600
601 /* Clear board */
602 for (r = 0; r < ROWS; r++)
603 for (c = 0; c < COLS; c++)
604 current_info.board[r][c] = 'X';
605
606 level_size = buffered_boards.index[index + 1] -
607 buffered_boards.index[index];
608
609 for (r = 0, c = 0, n = 1, i = 0; i < level_size; i++) {
610 if (level[i] == '\n' || level[i] == '|') {
611 if (c > 3) {
612 /* Update max width of level & go to next row */
613 if (c > current_info.level.width)
614 current_info.level.width = c;
615 c = 0;
616 r++;
617 if (r >= ROWS)
618 break;
619 }
620 } else if (c < COLS) {
621 /* Read RLE character's length into n */
622 if (level[i] >= '0' && level[i] <= '9') {
623 n = level[i++] - '0';
624 if (level[i] >= '0' && level[i] <= '9')
625 n = n*10 + level[i++] - '0';
626 }
627
628 /* Cleanup & replace */
629 if (level[i] == '%')
630 level[i] = '*';
631 else if (level[i] == '-' || level[i] == '_')
632 level[i] = ' ';
633
634 if (n > 1) {
635 if (c + n >= COLS)
636 n = COLS - c;
524 637
525 for (r = 0; r < ROWS; r++) { 638 if (level[i] == '.')
526 for (c = 0; c < COLS; c++) { 639 current_info.level.boxes_to_go += n;
527 current_info.board[r][c] = level->spaces[r][c];
528 640
529 if (current_info.board[r][c] == '.' || 641 /* Put RLE character n times */
530 current_info.board[r][c] == '+') 642 while (n--)
531 current_info.level.boxes_to_go++; 643 current_info.board[r][c++] = level[i];
644 n = 1;
532 645
533 if (current_info.board[r][c] == '@' || 646 } else {
534 current_info.board[r][c] == '+') { 647 if (level[i] == '.' || level[i] == '+')
535 current_info.player.row = r; 648 current_info.level.boxes_to_go++;
536 current_info.player.col = c; 649
650 if (level[i] == '@' ||level[i] == '+') {
651 current_info.player.row = r;
652 current_info.player.col = c;
653 }
654
655 current_info.board[r][c++] = level[i];
537 } 656 }
538 } 657 }
539 } 658 }
659
660 current_info.level.height = r;
661
662#if LCD_DEPTH > 2
663 /* Fill in blank space outside level on color targets */
664 for (r = 0; r < ROWS; r++)
665 for (c = 0; current_info.board[r][c] == ' ' && c < COLS; c++)
666 current_info.board[r][c] = 'X';
667
668 for (c = 0; c < COLS; c++) {
669 for (r = 0; (current_info.board[r][c] == ' ' ||
670 current_info.board[r][c] == 'X') && r < ROWS; r++)
671 current_info.board[r][c] = 'X';
672 for (r = ROWS - 1; (current_info.board[r][c] == ' ' ||
673 current_info.board[r][c] == 'X') && r >= 0; r--)
674 current_info.board[r][c] = 'X';
675 }
676#endif
540} 677}
541 678
542static void update_screen(void) 679static void update_screen(void)
543{ 680{
544 int b = 0, c = 0; 681 int c, r;
545 int rows = 0, cols = 0; 682 int rows, cols;
546 char s[25];
547
548/* magnify is the number of pixels for each block */
549#if (LCD_HEIGHT >= 224) && (LCD_WIDTH >= 312) || \
550 (LCD_HEIGHT >= 249) && (LCD_WIDTH >= 280) /* ipod 5g */
551#define MAGNIFY 14
552#elif (LCD_HEIGHT >= 144) && (LCD_WIDTH >= 212) || \
553 (LCD_HEIGHT >= 169) && (LCD_WIDTH >= 180-4) /* h3x0, ipod color/photo */
554#define MAGNIFY 9
555#elif (LCD_HEIGHT >= 96) && (LCD_WIDTH >= 152) || \
556 (LCD_HEIGHT >= 121) && (LCD_WIDTH >= 120) /* h1x0, ipod nano/mini */
557#define MAGNIFY 6
558#else /* other */
559#define MAGNIFY 4
560#endif
561 683
562#if LCD_DEPTH < 2 684#if LCD_DEPTH < 2 || ((LCD_HEIGHT < 96 || LCD_WIDTH < 152) && \
685 (LCD_HEIGHT < 121 || LCD_WIDTH < 120))
563 int i, j; 686 int i, j;
564 int max = MAGNIFY - 1; 687 int max = MAGNIFY - 1;
565 int middle = max / 2; 688 int middle = max/2;
566 int ldelta = (middle + 1) / 2; 689 int ldelta = (middle + 1)/2;
567#endif 690#endif
568 691
569 /* load the board to the screen */ 692#if LCD_WIDTH - (COLS*MAGNIFY) < 32
570 for (rows=0; rows < ROWS; rows++) { 693#define STAT_HEIGHT 25
571 for (cols = 0; cols < COLS; cols++) { 694#define STAT_X (LCD_WIDTH - 120)/2
572 c = cols * MAGNIFY; 695#define STAT_Y (LCD_HEIGHT - STAT_HEIGHT)
573 b = rows * MAGNIFY; 696#define BOARD_WIDTH LCD_WIDTH
574 697#define BOARD_HEIGHT (LCD_HEIGHT - STAT_HEIGHT)
575 switch(current_info.board[rows][cols]) { 698 rb->lcd_putsxy(STAT_X + 4, STAT_Y + 4, "Level");
576 case 'X': /* black space */ 699 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.index + 1);
577 break; 700 rb->lcd_putsxy(STAT_X + 7, STAT_Y + 14, buf);
578 701 rb->lcd_putsxy(STAT_X + 41, STAT_Y + 4, "Moves");
579 case '#': /* this is a wall */ 702 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.moves);
580#if LCD_DEPTH >= 2 703 rb->lcd_putsxy(STAT_X + 44, STAT_Y + 14, buf);
581 rb->lcd_bitmap_part(sokoban_tiles, 0, 1*MAGNIFY, MAGNIFY, 704 rb->lcd_putsxy(STAT_X + 79, STAT_Y + 4, "Pushes");
582 c, b, MAGNIFY, MAGNIFY); 705 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.pushes);
706 rb->lcd_putsxy(STAT_X + 82, STAT_Y + 14, buf);
707
708 rb->lcd_drawrect(STAT_X, STAT_Y, 38, STAT_HEIGHT);
709 rb->lcd_drawrect(STAT_X + 37, STAT_Y, 39, STAT_HEIGHT);
710 rb->lcd_drawrect(STAT_X + 75, STAT_Y, 45, STAT_HEIGHT);
583#else 711#else
584 for (i = c; i < c + MAGNIFY; i++) 712#if LCD_WIDTH - (COLS*MAGNIFY) > 40
585 for (j = b; j < b + MAGNIFY; j++) 713#define STAT_X (LCD_WIDTH - 40)
586 if ((i ^ j) & 1) 714#else
587 rb->lcd_drawpixel(i, j); 715#define STAT_X COLS*MAGNIFY
588#endif 716#endif
589 break; 717#if LCD_HEIGHT >= 70
590 718#define STAT_Y (LCD_HEIGHT - 70)/2
591 case '$': /* this is a box */
592#if LCD_DEPTH >= 2
593 rb->lcd_bitmap_part(sokoban_tiles, 0, 2*MAGNIFY, MAGNIFY,
594 c, b, MAGNIFY, MAGNIFY);
595#else 719#else
596 /* Free boxes are not filled in */ 720#define STAT_Y (LCD_HEIGHT - 47)/2
597 rb->lcd_drawrect(c, b, MAGNIFY, MAGNIFY);
598#endif 721#endif
599 break; 722#define STAT_WIDTH (LCD_WIDTH - STAT_X)
723#define BOARD_WIDTH (LCD_WIDTH - STAT_WIDTH)
724#define BOARD_HEIGHT LCD_HEIGHT
725 rb->lcd_putsxy(STAT_X + 1, STAT_Y + 3, "Level");
726 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.index + 1);
727 rb->lcd_putsxy(STAT_X + 4, STAT_Y + 13, buf);
728 rb->lcd_putsxy(STAT_X + 1, STAT_Y + 26, "Moves");
729 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.moves);
730 rb->lcd_putsxy(STAT_X + 4, STAT_Y + 36, buf);
731
732 rb->lcd_drawrect(STAT_X, STAT_Y + 0, STAT_WIDTH, 24);
733 rb->lcd_drawrect(STAT_X, STAT_Y + 23, STAT_WIDTH, 24);
600 734
601 case '*': 735#if LCD_HEIGHT >= 70
602 case '%': /* this is a box on a goal */ 736 rb->lcd_putsxy(STAT_X + 1, STAT_Y + 49, "Pushes");
737 rb->snprintf(buf, sizeof(buf), "%d", current_info.level.pushes);
738 rb->lcd_putsxy(STAT_X + 4, STAT_Y + 59, buf);
603 739
604#if LCD_DEPTH >= 2 740 rb->lcd_drawrect(STAT_X, STAT_Y + 46, STAT_WIDTH, 24);
605 rb->lcd_bitmap_part(sokoban_tiles, 0, 3*MAGNIFY, MAGNIFY,
606 c, b, MAGNIFY, MAGNIFY );
607#else
608 rb->lcd_drawrect(c, b, MAGNIFY, MAGNIFY);
609 rb->lcd_drawrect(c+(MAGNIFY/2)-1, b+(MAGNIFY/2)-1, MAGNIFY/2,
610 MAGNIFY/2);
611#endif 741#endif
612 break;
613 742
614 case '.': /* this is a goal */
615#if LCD_DEPTH >= 2
616 rb->lcd_bitmap_part(sokoban_tiles, 0, 4*MAGNIFY, MAGNIFY,
617 c, b, MAGNIFY, MAGNIFY);
618#else
619 rb->lcd_drawrect(c+(MAGNIFY/2)-1, b+(MAGNIFY/2)-1, MAGNIFY/2,
620 MAGNIFY/2);
621#endif 743#endif
622 break;
623 744
624 case '@': /* this is you */ 745 /* load the board to the screen */
625#if LCD_DEPTH >= 2 746 for (rows = 0; rows < ROWS; rows++) {
626 rb->lcd_bitmap_part(sokoban_tiles, 0, 5*MAGNIFY, MAGNIFY, 747 for (cols = 0; cols < COLS; cols++) {
627 c, b, MAGNIFY, MAGNIFY); 748 c = cols*MAGNIFY +
628#else 749 (BOARD_WIDTH - current_info.level.width*MAGNIFY)/2;
629 rb->lcd_drawline(c, b+middle, c+max, b+middle); 750 r = rows*MAGNIFY +
630 rb->lcd_drawline(c+middle, b, c+middle, b+max-ldelta); 751 (BOARD_HEIGHT - current_info.level.height*MAGNIFY)/2;
631 rb->lcd_drawline(c+max-middle, b,
632 c+max-middle, b+max-ldelta);
633 rb->lcd_drawline(c+middle, b+max-ldelta,
634 c+middle-ldelta, b+max);
635 rb->lcd_drawline(c+max-middle, b+max-ldelta,
636 c+max-middle+ldelta, b+max);
637#endif
638 break;
639 752
640 case '+': /* this is you on drugs, erm, on a goal */ 753 switch(current_info.board[rows][cols]) {
641#if LCD_DEPTH >= 2 754 case 'X': /* blank space outside of level */
642 rb->lcd_bitmap_part(sokoban_tiles, 0, 6*MAGNIFY, MAGNIFY, 755 break;
643 c, b, MAGNIFY, MAGNIFY );
644#else
645 rb->lcd_drawline(c, b+middle, c+max, b+middle);
646 rb->lcd_drawline(c+middle, b, c+middle, b+max-ldelta);
647 rb->lcd_drawline(c+max-middle, b, c+max-middle, b+max-ldelta);
648 rb->lcd_drawline(c+middle, b+max-ldelta, c+middle-ldelta,
649 b+max);
650 rb->lcd_drawline(c+max-middle, b+max-ldelta,
651 c+max-middle+ldelta, b+max);
652 rb->lcd_drawline(c+middle-1, b+middle+1, c+max-middle+1,
653 b+middle+1);
654#endif
655 break;
656 756
657#if LCD_DEPTH >= 2 757#if LCD_DEPTH >= 2 && ((LCD_HEIGHT >= 96 && LCD_WIDTH >= 152) || \
658 default: 758 (LCD_HEIGHT >= 121 && LCD_WIDTH >= 120))
659 rb->lcd_bitmap_part(sokoban_tiles, 0, 0*MAGNIFY, MAGNIFY, 759 case ' ': /* floor */
660 c, b, MAGNIFY, MAGNIFY ); 760 rb->lcd_bitmap_part(sokoban_tiles, 0, 0*MAGNIFY, MAGNIFY,
661#endif 761 c, r, MAGNIFY, MAGNIFY);
662 } 762 break;
663 }
664 }
665 763
666#if LCD_WIDTH-(COLS*MAGNIFY) < 32 764 case '#': /* wall */
667#define STAT_SIZE 25 765 rb->lcd_bitmap_part(sokoban_tiles, 0, 1*MAGNIFY, MAGNIFY,
668#define STAT_POS LCD_HEIGHT-STAT_SIZE 766 c, r, MAGNIFY, MAGNIFY);
669#define STAT_CENTER (LCD_WIDTH-120)/2 767 break;
670 768
671 rb->lcd_putsxy(4+STAT_CENTER, STAT_POS+4, "Level"); 769 case '$': /* box */
672 rb->snprintf(s, sizeof(s), "%d", current_info.level.level); 770 rb->lcd_bitmap_part(sokoban_tiles, 0, 2*MAGNIFY, MAGNIFY,
673 rb->lcd_putsxy(7+STAT_CENTER, STAT_POS+14, s); 771 c, r, MAGNIFY, MAGNIFY);
674 rb->lcd_putsxy(41+STAT_CENTER, STAT_POS+4, "Moves"); 772 break;
675 rb->snprintf(s, sizeof(s), "%d", current_info.level.moves);
676 rb->lcd_putsxy(44+STAT_CENTER, STAT_POS+14, s);
677 rb->lcd_putsxy(79+STAT_CENTER, STAT_POS+4, "Pushes");
678 rb->snprintf(s, sizeof(s), "%d", current_info.level.pushes);
679 rb->lcd_putsxy(82+STAT_CENTER, STAT_POS+14, s);
680
681 rb->lcd_drawrect(STAT_CENTER, STAT_POS, 38, STAT_SIZE);
682 rb->lcd_drawrect(37+STAT_CENTER, STAT_POS, 39, STAT_SIZE);
683 rb->lcd_drawrect(75+STAT_CENTER, STAT_POS, 45, STAT_SIZE);
684 773
774 case '*': /* box on goal */
775 rb->lcd_bitmap_part(sokoban_tiles, 0, 3*MAGNIFY, MAGNIFY,
776 c, r, MAGNIFY, MAGNIFY);
777 break;
778
779 case '.': /* goal */
780 rb->lcd_bitmap_part(sokoban_tiles, 0, 4*MAGNIFY, MAGNIFY,
781 c, r, MAGNIFY, MAGNIFY);
782 break;
783
784 case '@': /* player */
785 rb->lcd_bitmap_part(sokoban_tiles, 0, 5*MAGNIFY, MAGNIFY,
786 c, r, MAGNIFY, MAGNIFY);
787 break;
788
789 case '+': /* player on goal */
790 rb->lcd_bitmap_part(sokoban_tiles, 0, 6*MAGNIFY, MAGNIFY,
791 c, r, MAGNIFY, MAGNIFY);
792 break;
685#else 793#else
686#define STAT_POS COLS*MAGNIFY 794 case '#': /* wall */
687#define STAT_SIZE LCD_WIDTH-STAT_POS 795 for (i = c; i < c + MAGNIFY; i++)
796 for (j = r; j < r + MAGNIFY; j++)
797 if ((i ^ j) & 1)
798 rb->lcd_drawpixel(i, j);
799 break;
688 800
689 rb->lcd_putsxy(STAT_POS+1, 3, "Level"); 801 case '$': /* box */
690 rb->snprintf(s, sizeof(s), "%d", current_info.level.level); 802 rb->lcd_drawrect(c, r, MAGNIFY, MAGNIFY);
691 rb->lcd_putsxy(STAT_POS+4, 13, s); 803 break;
692 rb->lcd_putsxy(STAT_POS+1, 26, "Moves");
693 rb->snprintf(s, sizeof(s), "%d", current_info.level.moves);
694 rb->lcd_putsxy(STAT_POS+4, 36, s);
695 804
696 rb->lcd_drawrect(STAT_POS, 0, STAT_SIZE, 24); 805 case '*': /* box on goal */
697 rb->lcd_drawrect(STAT_POS, 23, STAT_SIZE, 24); 806 rb->lcd_drawrect(c, r, MAGNIFY, MAGNIFY);
807 rb->lcd_drawrect(c + MAGNIFY/2 - 1, r + MAGNIFY/2 - 1,
808 MAGNIFY/2, MAGNIFY/2);
809 break;
698 810
699#if LCD_HEIGHT >= 70 811 case '.': /* goal */
700 rb->lcd_putsxy(STAT_POS+1, 49, "Pushes"); 812 rb->lcd_drawrect(c + MAGNIFY/2 - 1, r + MAGNIFY/2 - 1,
701 rb->snprintf(s, sizeof(s), "%d", current_info.level.pushes); 813 MAGNIFY/2, MAGNIFY/2);
702 rb->lcd_putsxy(STAT_POS+4, 59, s); 814 break;
703 815
704 rb->lcd_drawrect(STAT_POS, 46, STAT_SIZE, 24); 816 case '@': /* player */
705#endif 817 case '+': /* player on goal */
818 rb->lcd_drawline(c, r + middle, c + max, r + middle);
819 rb->lcd_drawline(c + middle, r, c + middle,
820 r + max - ldelta);
821 rb->lcd_drawline(c + max - middle, r, c + max - middle,
822 r + max - ldelta);
823 rb->lcd_drawline(c + middle, r + max - ldelta,
824 c + middle - ldelta, r + max);
825 rb->lcd_drawline(c + max - middle, r + max - ldelta,
826 c + max - middle + ldelta, r + max);
827 break;
706#endif 828#endif
829 }
830 }
831 }
707 832
708 /* print out the screen */ 833 /* print out the screen */
709 rb->lcd_update(); 834 rb->lcd_update();
@@ -716,21 +841,320 @@ static void draw_level(void)
716 update_screen(); 841 update_screen();
717} 842}
718 843
844static bool save(char *filename, bool solution)
845{
846 int fd;
847
848 rb->splash(0, "Saving...");
849
850 if (filename[0] == '\0' ||
851 (fd = rb->open(filename, O_WRONLY|O_CREAT|O_TRUNC)) < 0) {
852 rb->splash(HZ*2, "Unable to open %s", filename);
853 return false;
854 }
855
856 /* Sokoban: S/P for solution/progress : level number : current undo */
857 rb->snprintf(buf, sizeof(buf), "Sokoban:%c:%d:%d\n", (solution ? 'S' : 'P'),
858 current_info.level.index + 1, undo_info.current);
859 rb->write(fd, buf, rb->strlen(buf));
860
861 /* Filename of levelset */
862 rb->write(fd, buffered_boards.filename,
863 rb->strlen(buffered_boards.filename));
864 rb->write(fd, "\n", 1);
865
866 /* Full undo history */
867 rb->write(fd, undo_info.history, undo_info.max);
868
869 rb->close(fd);
870
871 return true;
872}
873
874static bool load(char *filename, bool silent)
875{
876 int fd;
877 int i = 0, n;
878 int len;
879 bool play_solution;
880 int button;
881 int step_delay = HZ/4;
882
883 if (filename[0] == '\0' || (fd = rb->open(filename, O_RDONLY)) < 0) {
884 if (!silent)
885 rb->splash(HZ*2, "Unable to open %s", filename);
886 return false;
887 }
888
889 /* Read header, level number, & current undo */
890 rb->read_line(fd, buf, sizeof(buf));
891
892 /* If we're opening a level file, not a solution/progress file */
893 if (rb->strncmp(buf, "Sokoban", 7) != 0) {
894 rb->close(fd);
895
896 rb->strncpy(buffered_boards.filename, filename, MAX_PATH);
897 if (!read_levels(true))
898 return false;
899
900 current_info.level.index = 0;
901 load_level();
902
903 /* If there aren't any boxes to go or the player position wasn't set,
904 * the file probably wasn't a Sokoban level file */
905 if (current_info.level.boxes_to_go == 0 ||
906 current_info.player.row == 0 || current_info.player.col == 0) {
907 if (!silent)
908 rb->splash(HZ*2, "File is not a Sokoban level file");
909 return false;
910 }
911
912 } else {
913
914 /* Read filename of levelset */
915 rb->read_line(fd, buffered_boards.filename,
916 sizeof(buffered_boards.filename));
917
918 /* Read full undo history */
919 len = rb->read_line(fd, undo_info.history, MAX_UNDOS);
920
921 /* Correct len when trailing \r's or \n's are counted */
922 if (len > 2 && undo_info.history[len - 2] == '\0')
923 len -= 2;
924 else if (len > 1 && undo_info.history[len - 1] == '\0')
925 len--;
926
927 rb->close(fd);
928
929 /* Check to see if we're going to play a solution or resume progress */
930 play_solution = (buf[8] == 'S');
931
932 /* Get level number */
933 for (n = 0, i = 10; buf[i] >= '0' && buf[i] <= '9' && i < 15; i++)
934 n = n*10 + buf[i] - '0';
935 current_info.level.index = n - 1;
936
937 /* Get current undo index */
938 for (n = 0, i++; buf[i] >= '0' && buf[i] <= '9' && i < 21; i++)
939 n = n*10 + buf[i] - '0';
940 if (n > len)
941 n = len;
942
943 if (current_info.level.index < 0) {
944 if (!silent)
945 rb->splash(HZ*2, "Error loading level");
946 return false;
947 }
948 if (!read_levels(true))
949 return false;
950 if (current_info.level.index >= current_info.max_level) {
951 if (!silent)
952 rb->splash(HZ*2, "Error loading level");
953 return false;
954 }
955
956 load_level();
957
958 if (play_solution) {
959 rb->lcd_clear_display();
960 update_screen();
961 rb->sleep(2*step_delay);
962
963 /* Replay solution until the end or quit button is pressed */
964 for (i = 0; i < len; i++) {
965 if (!move(undo_info.history[i], true)) {
966 n = i;
967 break;
968 }
969
970 rb->lcd_clear_display();
971 update_screen();
972 rb->sleep(step_delay);
973
974 /* Ignore keypresses except for quit & changing speed */
975 while ((button = rb->button_get(false)) != BUTTON_NONE) {
976 switch (button) {
977 case SOKOBAN_MENU:
978 /* Pretend the level is complete so we'll quit */
979 current_info.level.boxes_to_go = 0;
980 return true;
981
982 case SOKOBAN_UP:
983 if (step_delay > HZ/12)
984 step_delay = 5*step_delay/6;
985 break;
986
987 case SOKOBAN_DOWN:
988 if (step_delay < 3*HZ/4)
989 step_delay = 6*step_delay/5;
990 }
991 }
992 }
993
994 /* If level complete, wait for keypress before quitting */
995 if (current_info.level.boxes_to_go == 0)
996 rb->button_get(true);
997
998 } else {
999 /* Advance to current undo */
1000 for (i = 0; i < n; i++) {
1001 if (!move(undo_info.history[i], true)) {
1002 n = i;
1003 break;
1004 }
1005 }
1006
1007 rb->button_clear_queue();
1008 rb->lcd_clear_display();
1009 }
1010
1011 undo_info.max = len;
1012 undo_info.current = n;
1013 }
1014
1015 return true;
1016}
1017
1018static int sokoban_menu(void)
1019{
1020 int button;
1021 int selection = 0;
1022 int i;
1023 bool menu_quit;
1024 int start_selected = 0;
1025
1026 MENUITEM_STRINGLIST(menu, "Sokoban Menu", NULL,
1027 "Resume", "Audio Playback", "Keys",
1028 "Load Default Level Set", "Quit Without Saving",
1029 "Save Progress & Quit");
1030
1031 do {
1032 menu_quit = true;
1033 selection = rb->do_menu(&menu, &start_selected);
1034
1035 switch (selection) {
1036 case 0: /* Resume */
1037 break;
1038
1039 case 1: /* Audio playback control */
1040 playback_control(rb);
1041 menu_quit = false;
1042 break;
1043
1044 case 2: /* Keys */
1045 FOR_NB_SCREENS(i)
1046 rb->screens[i]->clear_display();
1047 rb->lcd_setfont(SOKOBAN_FONT);
1048
1049#if (CONFIG_KEYPAD == RECORDER_PAD) || \
1050 (CONFIG_KEYPAD == ARCHOS_AV300_PAD)
1051 rb->lcd_putsxy(3, 6, "[OFF] Menu");
1052 rb->lcd_putsxy(3, 16, "[ON] Undo");
1053 rb->lcd_putsxy(3, 26, "[PLAY] Redo");
1054 rb->lcd_putsxy(3, 36, "[F1] Down a Level");
1055 rb->lcd_putsxy(3, 46, "[F2] Restart Level");
1056 rb->lcd_putsxy(3, 56, "[F3] Up a Level");
1057#elif CONFIG_KEYPAD == ONDIO_PAD
1058 rb->lcd_putsxy(3, 6, "[OFF] Menu");
1059 rb->lcd_putsxy(3, 16, "[MODE] Undo");
1060 rb->lcd_putsxy(3, 26, "[MODE+DOWN] Redo");
1061 rb->lcd_putsxy(3, 36, "[MODE+LEFT] Previous Level");
1062 rb->lcd_putsxy(3, 46, "[MODE+UP] Restart Level");
1063 rb->lcd_putsxy(3, 56, "[MODE+RIGHT] Up Level");
1064#elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
1065 (CONFIG_KEYPAD == IRIVER_H300_PAD)
1066 rb->lcd_putsxy(3, 6, "[STOP] Menu");
1067 rb->lcd_putsxy(3, 16, "[REC] Undo");
1068 rb->lcd_putsxy(3, 26, "[MODE] Redo");
1069 rb->lcd_putsxy(3, 36, "[PLAY+DOWN] Previous Level");
1070 rb->lcd_putsxy(3, 46, "[PLAY] Restart Level");
1071 rb->lcd_putsxy(3, 56, "[PLAY+UP] Next Level");
1072#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
1073 (CONFIG_KEYPAD == IPOD_3G_PAD)
1074 rb->lcd_putsxy(3, 6, "[SELECT+MENU] Menu");
1075 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
1076 rb->lcd_putsxy(3, 26, "[SELECT+PLAY] Redo");
1077 rb->lcd_putsxy(3, 36, "[SELECT+LEFT] Previous Level");
1078 rb->lcd_putsxy(3, 46, "[SELECT+RIGHT] Next Level");
1079#elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
1080 rb->lcd_putsxy(3, 6, "[POWER] Menu");
1081 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
1082 rb->lcd_putsxy(3, 26, "[REC] Previous Level");
1083 rb->lcd_putsxy(3, 36, "[PLAY] Next Level");
1084#elif CONFIG_KEYPAD == IRIVER_H10_PAD
1085 rb->lcd_putsxy(3, 6, "[POWER] Menu");
1086 rb->lcd_putsxy(3, 16, "[REW] Undo");
1087 rb->lcd_putsxy(3, 26, "[FF] Redo");
1088 rb->lcd_putsxy(3, 36, "[PLAY+DOWN] Previous Level");
1089 rb->lcd_putsxy(3, 46, "[PLAY+RIGHT] Restart Level");
1090 rb->lcd_putsxy(3, 56, "[PLAY+UP] Next Level");
1091#elif CONFIG_KEYPAD == GIGABEAT_PAD
1092 rb->lcd_putsxy(3, 6, "[POWER] Menu");
1093 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
1094 rb->lcd_putsxy(3, 26, "[A] Redo");
1095 rb->lcd_putsxy(3, 36, "[VOL-] Previous Level");
1096 rb->lcd_putsxy(3, 46, "[MENU] Restart Level");
1097 rb->lcd_putsxy(3, 56, "[VOL+] Next Level");
1098#elif CONFIG_KEYPAD == SANSA_E200_PAD
1099 rb->lcd_putsxy(3, 6, "[POWER] Menu");
1100 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
1101 rb->lcd_putsxy(3, 26, "[REC] Redo");
1102 rb->lcd_putsxy(3, 36, "[SELECT+DOWN] Previous Level");
1103 rb->lcd_putsxy(3, 46, "[SELECT+RIGHT] Restart Level");
1104 rb->lcd_putsxy(3, 56, "[SELECT+UP] Next Level");
1105#endif
1106
1107 FOR_NB_SCREENS(i)
1108 rb->screens[i]->update();
1109
1110 /* Display until keypress */
1111 do {
1112 rb->sleep(HZ/20);
1113 button = rb->button_get(false);
1114 } while (!button || button & BUTTON_REL ||
1115 button & BUTTON_REPEAT);
1116
1117 menu_quit = false;
1118 break;
1119
1120 case 3: /* Load default levelset */
1121 init_boards();
1122 if (!read_levels(true))
1123 return 4;
1124 load_level();
1125 break;
1126
1127 case 4: /* Quit */
1128 break;
1129
1130 case 5: /* Save & quit */
1131 save(SOKOBAN_SAVE_FILE, false);
1132 rb->reload_directory();
1133 }
1134
1135 } while (!menu_quit);
1136
1137 /* Restore font */
1138 rb->lcd_setfont(SOKOBAN_FONT);
1139
1140 FOR_NB_SCREENS(i) {
1141 rb->screens[i]->clear_display();
1142 rb->screens[i]->update();
1143 }
1144
1145 return selection;
1146}
1147
719static bool sokoban_loop(void) 1148static bool sokoban_loop(void)
720{ 1149{
721 bool moved = true; 1150 bool moved;
722 int i = 0, button = 0, lastbutton = 0; 1151 int i = 0, button = 0, lastbutton = 0;
723 short r = 0, c = 0; 1152 short r = 0, c = 0;
724 int w, h; 1153 int w, h;
725 char s[25]; 1154 char *loc;
726 1155
727 current_info.level.level = 1; 1156 while (true) {
728 1157 moved = false;
729 load_level();
730 update_screen();
731
732 while (1) {
733 moved = true;
734 1158
735 r = current_info.player.row; 1159 r = current_info.player.row;
736 c = current_info.player.col; 1160 c = current_info.player.col;
@@ -742,24 +1166,25 @@ static bool sokoban_loop(void)
742#ifdef SOKOBAN_RC_QUIT 1166#ifdef SOKOBAN_RC_QUIT
743 case SOKOBAN_RC_QUIT: 1167 case SOKOBAN_RC_QUIT:
744#endif 1168#endif
745 case SOKOBAN_QUIT: 1169 case SOKOBAN_MENU:
746 /* get out of here */ 1170 switch (sokoban_menu()) {
747#ifdef HAVE_LCD_COLOR /* reset background color */ 1171 case 4: /* Quit */
748 rb->lcd_set_background(rb->global_settings->bg_color); 1172 case 5: /* Save & quit */
749#endif 1173 return PLUGIN_OK;
750 return PLUGIN_OK; 1174 }
1175 update_screen();
1176 break;
751 1177
752 case SOKOBAN_UNDO: 1178 case SOKOBAN_UNDO:
753#ifdef SOKOBAN_UNDO_PRE 1179#ifdef SOKOBAN_UNDO_PRE
754 if (lastbutton != SOKOBAN_UNDO_PRE) 1180 if (lastbutton != SOKOBAN_UNDO_PRE)
755 break; 1181 break;
756#else /* repeat can't work here for Ondio et al */ 1182#else /* repeat can't work here for Ondio, iPod, et al */
757 case SOKOBAN_UNDO | BUTTON_REPEAT: 1183 case SOKOBAN_UNDO | BUTTON_REPEAT:
758#endif 1184#endif
759 undo(); 1185 undo();
760 rb->lcd_clear_display(); 1186 rb->lcd_clear_display();
761 update_screen(); 1187 update_screen();
762 moved = false;
763 break; 1188 break;
764 1189
765#ifdef SOKOBAN_REDO 1190#ifdef SOKOBAN_REDO
@@ -775,22 +1200,20 @@ static bool sokoban_loop(void)
775 case SOKOBAN_LEVEL_UP | BUTTON_REPEAT: 1200 case SOKOBAN_LEVEL_UP | BUTTON_REPEAT:
776 /* next level */ 1201 /* next level */
777 init_undo(); 1202 init_undo();
778 if (current_info.level.level < current_info.max_level) 1203 if (current_info.level.index + 1 < current_info.max_level)
779 current_info.level.level++; 1204 current_info.level.index++;
780 1205
781 draw_level(); 1206 draw_level();
782 moved = false;
783 break; 1207 break;
784 1208
785 case SOKOBAN_LEVEL_DOWN: 1209 case SOKOBAN_LEVEL_DOWN:
786 case SOKOBAN_LEVEL_DOWN | BUTTON_REPEAT: 1210 case SOKOBAN_LEVEL_DOWN | BUTTON_REPEAT:
787 /* previous level */ 1211 /* previous level */
788 init_undo(); 1212 init_undo();
789 if (current_info.level.level > 1) 1213 if (current_info.level.index > 0)
790 current_info.level.level--; 1214 current_info.level.index--;
791 1215
792 draw_level(); 1216 draw_level();
793 moved = false;
794 break; 1217 break;
795 1218
796#ifdef SOKOBAN_LEVEL_REPEAT 1219#ifdef SOKOBAN_LEVEL_REPEAT
@@ -799,7 +1222,6 @@ static bool sokoban_loop(void)
799 /* same level */ 1222 /* same level */
800 init_undo(); 1223 init_undo();
801 draw_level(); 1224 draw_level();
802 moved = false;
803 break; 1225 break;
804#endif 1226#endif
805 1227
@@ -826,13 +1248,10 @@ static bool sokoban_loop(void)
826 default: 1248 default:
827 if (rb->default_event_handler(button) == SYS_USB_CONNECTED) 1249 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
828 return PLUGIN_USB_CONNECTED; 1250 return PLUGIN_USB_CONNECTED;
829
830 moved = false;
831 break; 1251 break;
832 } 1252 }
833 1253
834 if (button != BUTTON_NONE) 1254 lastbutton = button;
835 lastbutton = button;
836 1255
837 if (moved) { 1256 if (moved) {
838 rb->lcd_clear_display(); 1257 rb->lcd_clear_display();
@@ -844,58 +1263,102 @@ static bool sokoban_loop(void)
844 1263
845 if (moved) { 1264 if (moved) {
846 rb->lcd_clear_display(); 1265 rb->lcd_clear_display();
847 /* Center level completed message */ 1266
848 rb->snprintf(s, sizeof(s), "Level %d Complete!", 1267 /* Show level complete message & stats */
849 current_info.level.level); 1268 rb->snprintf(buf, sizeof(buf), "Level %d Complete!",
850 rb->lcd_getstringsize(s, &w, &h); 1269 current_info.level.index + 1);
851 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - 16 , s); 1270 rb->lcd_getstringsize(buf, &w, &h);
852 rb->snprintf(s, sizeof(s), "%4d Moves ", 1271 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h*3, buf);
1272
1273 rb->snprintf(buf, sizeof(buf), "%4d Moves ",
853 current_info.level.moves); 1274 current_info.level.moves);
854 rb->lcd_getstringsize(s, &w, &h); 1275 rb->lcd_getstringsize(buf, &w, &h);
855 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 + 0 , s); 1276 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h, buf);
856 rb->snprintf(s, sizeof(s), "%4d Pushes", 1277
1278 rb->snprintf(buf, sizeof(buf), "%4d Pushes",
857 current_info.level.pushes); 1279 current_info.level.pushes);
858 rb->lcd_getstringsize(s, &w, &h); 1280 rb->lcd_getstringsize(buf, &w, &h);
859 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 + 8 , s); 1281 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2, buf);
1282
1283 if (undo_info.count < MAX_UNDOS) {
1284 rb->snprintf(buf, sizeof(buf), "%s: Save solution",
1285 BUTTON_SAVE_NAME);
1286 rb->lcd_getstringsize(buf, &w, &h);
1287 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 + h*2, buf);
1288 }
1289
860 rb->lcd_update(); 1290 rb->lcd_update();
861 rb->button_get(false); 1291 rb->sleep(HZ/4);
1292 rb->button_clear_queue();
862 1293
863 rb->sleep(HZ/2); 1294 /* Display for 4 seconds or until new keypress */
864 for (i = 0; i < 30; i++) { 1295 for (i = 0; i < 75; i++) {
865 rb->sleep(HZ/20); 1296 rb->sleep(HZ/20);
866 button = rb->button_get(false); 1297 button = rb->button_get(false);
867 if (button && ((button & BUTTON_REL) != BUTTON_REL)) 1298 if (button && !(button & BUTTON_REL) &&
1299 !(button & BUTTON_REPEAT))
868 break; 1300 break;
869 } 1301 }
1302
1303 if (button == BUTTON_SAVE) {
1304 if (undo_info.count < MAX_UNDOS) {
1305 /* Default filename to current levelset plus
1306 * level number and .sok extension */
1307 loc = rb->strrchr(buffered_boards.filename, '.');
1308 if (loc != NULL)
1309 *loc = '\0';
1310 rb->snprintf(buf, sizeof(buf), "%s.%d.sok",
1311 buffered_boards.filename,
1312 current_info.level.index + 1);
1313 if (loc != NULL)
1314 *loc = '.';
1315
1316 if (!rb->kbd_input(buf, MAX_PATH))
1317 save(buf, true);
1318 } else
1319 rb->splash(HZ*2, "Solution too long to save");
1320
1321 rb->lcd_setfont(SOKOBAN_FONT); /* Restore font */
1322 }
870 } 1323 }
871 1324
872 current_info.level.level++; 1325 FOR_NB_SCREENS(i) {
1326 rb->screens[i]->clear_display();
1327 rb->screens[i]->update();
1328 }
1329
1330 current_info.level.index++;
873 1331
874 /* clear undo stats */ 1332 /* clear undo stats */
875 init_undo(); 1333 init_undo();
876 1334
877 rb->lcd_clear_display(); 1335 if (current_info.level.index >= current_info.max_level) {
878 1336 /* Show levelset complete message */
879 if (current_info.level.level > current_info.max_level) { 1337 rb->snprintf(buf, sizeof(buf), "You WIN!!");
880 /* Center "You WIN!!" on all screen sizes */ 1338 rb->lcd_getstringsize(buf, &w, &h);
881 rb->snprintf(s, sizeof(s), "You WIN!!"); 1339 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h/2, buf);
882 rb->lcd_getstringsize(s, &w, &h);
883 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h/2, s);
884 1340
885 rb->lcd_set_drawmode(DRMODE_COMPLEMENT); 1341 rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
886 /* Display for 10 seconds or until keypress */ 1342 /* Display for 4 seconds or until keypress */
887 for (i = 0; i < 200; i++) { 1343 for (i = 0; i < 80; i++) {
888 rb->lcd_fillrect(0, 0, LCD_WIDTH, LCD_HEIGHT); 1344 rb->lcd_fillrect(0, 0, LCD_WIDTH, LCD_HEIGHT);
889 rb->lcd_update(); 1345 rb->lcd_update();
890 rb->sleep(HZ/20); 1346 rb->sleep(HZ/10);
891 1347
892 button = rb->button_get(false); 1348 button = rb->button_get(false);
893 if (button && ((button & BUTTON_REL) != BUTTON_REL)) 1349 if (button && !(button & BUTTON_REL))
894 break; 1350 break;
895 } 1351 }
896 rb->lcd_set_drawmode(DRMODE_SOLID); 1352 rb->lcd_set_drawmode(DRMODE_SOLID);
897 1353
898 return PLUGIN_OK; 1354 /* Reset to first level & show quit menu */
1355 current_info.level.index = 0;
1356
1357 switch (sokoban_menu()) {
1358 case 4: /* Quit */
1359 case 5: /* Save & quit */
1360 return PLUGIN_OK;
1361 }
899 } 1362 }
900 1363
901 load_level(); 1364 load_level();
@@ -911,99 +1374,41 @@ static bool sokoban_loop(void)
911enum plugin_status plugin_start(struct plugin_api* api, void* parameter) 1374enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
912{ 1375{
913 int w, h; 1376 int w, h;
914 int i;
915 int button = 0;
916 1377
917 (void)(parameter); 1378 (void)(parameter);
918 rb = api; 1379 rb = api;
919 1380
920 rb->lcd_setfont(FONT_SYSFIXED); 1381 rb->lcd_setfont(SOKOBAN_FONT);
921
922#ifdef HAVE_LCD_COLOR
923 rb->lcd_set_background(BG_COLOR);
924#endif
925 1382
926 rb->lcd_clear_display(); 1383 rb->lcd_clear_display();
927 rb->lcd_getstringsize(SOKOBAN_TITLE, &w, &h); 1384 rb->lcd_getstringsize(SOKOBAN_TITLE, &w, &h);
928 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h/2, SOKOBAN_TITLE); 1385 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h/2, SOKOBAN_TITLE);
929 rb->lcd_update(); 1386 rb->lcd_update();
930 rb->sleep(HZ); 1387 rb->sleep(HZ); /* Show title for 1 second */
931 1388
932 rb->lcd_clear_display(); 1389 init_boards();
933 1390
934#if (CONFIG_KEYPAD == RECORDER_PAD) || \ 1391 if (parameter == NULL) {
935 (CONFIG_KEYPAD == ARCHOS_AV300_PAD) 1392 /* Attempt to resume saved progress, otherwise start at beginning */
936 rb->lcd_putsxy(3, 6, "[OFF] Quit"); 1393 if (!load(SOKOBAN_SAVE_FILE, true)) {
937 rb->lcd_putsxy(3, 16, "[ON] Undo"); 1394 init_boards();
938 rb->lcd_putsxy(3, 26, "[PLAY] Redo"); 1395 if (!read_levels(true))
939 rb->lcd_putsxy(3, 36, "[F1] Down a Level"); 1396 return PLUGIN_OK;
940 rb->lcd_putsxy(3, 46, "[F2] Restart Level"); 1397 load_level();
941 rb->lcd_putsxy(3, 56, "[F3] Up a Level"); 1398 }
942#elif CONFIG_KEYPAD == ONDIO_PAD
943 rb->lcd_putsxy(3, 6, "[OFF] Quit");
944 rb->lcd_putsxy(3, 16, "[MODE] Undo");
945 rb->lcd_putsxy(3, 26, "[MODE+DOWN] Redo");
946 rb->lcd_putsxy(3, 36, "[MODE+LEFT] Down a Level");
947 rb->lcd_putsxy(3, 46, "[MODE+UP] Restart Level");
948 rb->lcd_putsxy(3, 56, "[MODE+RIGHT] Up Level");
949#elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
950 (CONFIG_KEYPAD == IRIVER_H300_PAD)
951 rb->lcd_putsxy(3, 6, "[STOP] Quit");
952 rb->lcd_putsxy(3, 16, "[REC] Undo");
953 rb->lcd_putsxy(3, 26, "[MODE] Redo");
954 rb->lcd_putsxy(3, 36, "[PLAY+DOWN] Down a Level");
955 rb->lcd_putsxy(3, 46, "[PLAY] Restart Level");
956 rb->lcd_putsxy(3, 56, "[PLAY+UP] Up a Level");
957#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
958 (CONFIG_KEYPAD == IPOD_3G_PAD)
959 rb->lcd_putsxy(3, 6, "[SELECT+MENU] Quit");
960 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
961 rb->lcd_putsxy(3, 26, "[SELECT+PLAY] Redo");
962 rb->lcd_putsxy(3, 36, "[SELECT+LEFT] Down a Level");
963 rb->lcd_putsxy(3, 46, "[SELECT+RIGHT] Up a Level");
964#elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
965 rb->lcd_putsxy(3, 6, "[POWER] Quit");
966 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
967 rb->lcd_putsxy(3, 26, "[REC] Down a Level");
968 rb->lcd_putsxy(3, 36, "[PLAY] Up Level");
969#elif CONFIG_KEYPAD == GIGABEAT_PAD
970 rb->lcd_putsxy(3, 6, "[POWER] Quit");
971 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
972 rb->lcd_putsxy(3, 26, "[A] Redo");
973 rb->lcd_putsxy(3, 36, "[VOL-] Down a Level");
974 rb->lcd_putsxy(3, 46, "[MENU] Restart Level");
975 rb->lcd_putsxy(3, 56, "[VOL+] Up Level");
976#elif CONFIG_KEYPAD == SANSA_E200_PAD
977 rb->lcd_putsxy(3, 6, "[POWER] Quit");
978 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
979 rb->lcd_putsxy(3, 26, "[REC] Redo");
980 rb->lcd_putsxy(3, 36, "[SELECT+DOWN] Down a Level");
981 rb->lcd_putsxy(3, 46, "[SELECT+RIGHT] Restart Level");
982 rb->lcd_putsxy(3, 56, "[SELECT+UP] Up Level");
983#elif CONFIG_KEYPAD == IRIVER_H10_PAD
984 rb->lcd_putsxy(3, 6, "[POWER] Quit");
985 rb->lcd_putsxy(3, 16, "[REW] Undo");
986 rb->lcd_putsxy(3, 26, "[FF] Redo");
987 rb->lcd_putsxy(3, 36, "[PLAY+DOWN] Down a Level");
988 rb->lcd_putsxy(3, 46, "[PLAY+RIGHT] Restart Level");
989 rb->lcd_putsxy(3, 56, "[PLAY+UP] Up Level");
990#endif
991 1399
992 rb->lcd_update(); 1400 } else {
993 rb->button_get(false); 1401 /* The plugin is being used to open a file */
994 /* Display for 3 seconds or until keypress */ 1402 if (load((char*) parameter, false)) {
995 for (i = 0; i < 60; i++) { 1403 /* If we loaded & played a solution, quit */
996 rb->sleep(HZ/20); 1404 if (current_info.level.boxes_to_go == 0)
997 button = rb->button_get(false); 1405 return PLUGIN_OK;
998 if (button && ((button & BUTTON_REL) != BUTTON_REL)) 1406 } else
999 break; 1407 return PLUGIN_OK;
1000 } 1408 }
1001 rb->lcd_clear_display();
1002
1003 init_boards();
1004 1409
1005 if (read_levels(1) != 0) 1410 rb->lcd_clear_display();
1006 return PLUGIN_OK; 1411 update_screen();
1007 1412
1008 return sokoban_loop(); 1413 return sokoban_loop();
1009} 1414}