Get rid of the 'center' parameter for splashes. There were only 2 of almost 500 splas...
[Rockbox.git] / apps / plugins / sokoban.c
blob6892e83f3190eb5995dbf84ff97c2bce2c42f85e
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002 Eric Linenberg
11 * February 2003: Robert Hak performs a cleanup/rewrite/feature addition.
12 * Eric smiles. Bjorn cries. Linus say 'huh?'.
14 * 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.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
21 #include "plugin.h"
23 #ifdef HAVE_LCD_BITMAP
25 PLUGIN_HEADER
27 #if LCD_DEPTH >= 2
28 extern const fb_data sokoban_tiles[];
29 #endif
31 #define SOKOBAN_TITLE "Sokoban"
33 #define LEVELS_FILE PLUGIN_DIR "/sokoban.levels"
35 #define ROWS 16
36 #define COLS 20
37 #define SOKOBAN_LEVEL_SIZE (ROWS*COLS)
38 #define MAX_BUFFERED_BOARDS 500
39 /* Use either all but 12k of the plugin buffer for board data
40 or just enough for MAX_BUFFERED_BOARDS, which ever is less */
41 #if (PLUGIN_BUFFER_SIZE - 0x3000)/SOKOBAN_LEVEL_SIZE < MAX_BUFFERED_BOARDS
42 #define NUM_BUFFERED_BOARDS (PLUGIN_BUFFER_SIZE - 0x3000)/SOKOBAN_LEVEL_SIZE
43 #else
44 #define NUM_BUFFERED_BOARDS MAX_BUFFERED_BOARDS
45 #endif
46 /* Use 4k plus remaining plugin buffer (-8k for prog) for undo, up to 32k */
47 #if PLUGIN_BUFFER_SIZE - NUM_BUFFERED_BOARDS*SOKOBAN_LEVEL_SIZE - 0x2000 > \
48 0x7FFF
49 #define MAX_UNDOS 0x7FFF
50 #else
51 #define MAX_UNDOS PLUGIN_BUFFER_SIZE - \
52 NUM_BUFFERED_BOARDS*SOKOBAN_LEVEL_SIZE - 0x2000
53 #endif
55 /* Move/push definitions for undo */
56 enum {
57 SOKOBAN_PUSH_LEFT,
58 SOKOBAN_PUSH_RIGHT,
59 SOKOBAN_PUSH_UP,
60 SOKOBAN_PUSH_DOWN,
61 SOKOBAN_MOVE_LEFT,
62 SOKOBAN_MOVE_RIGHT,
63 SOKOBAN_MOVE_UP,
64 SOKOBAN_MOVE_DOWN
66 #define SOKOBAN_MOVE_DIFF (SOKOBAN_MOVE_LEFT-SOKOBAN_PUSH_LEFT)
67 #define SOKOBAN_MOVE_MIN SOKOBAN_MOVE_LEFT
69 /* variable button definitions */
70 #if CONFIG_KEYPAD == RECORDER_PAD
71 #define SOKOBAN_UP BUTTON_UP
72 #define SOKOBAN_DOWN BUTTON_DOWN
73 #define SOKOBAN_QUIT BUTTON_OFF
74 #define SOKOBAN_UNDO BUTTON_ON
75 #define SOKOBAN_REDO BUTTON_PLAY
76 #define SOKOBAN_LEVEL_UP BUTTON_F3
77 #define SOKOBAN_LEVEL_DOWN BUTTON_F1
78 #define SOKOBAN_LEVEL_REPEAT BUTTON_F2
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
87 #define SOKOBAN_LEVEL_DOWN BUTTON_F1
88 #define SOKOBAN_LEVEL_REPEAT BUTTON_F2
90 #elif CONFIG_KEYPAD == ONDIO_PAD
91 #define SOKOBAN_UP BUTTON_UP
92 #define SOKOBAN_DOWN BUTTON_DOWN
93 #define SOKOBAN_QUIT BUTTON_OFF
94 #define SOKOBAN_UNDO_PRE BUTTON_MENU
95 #define SOKOBAN_UNDO (BUTTON_MENU | BUTTON_REL)
96 #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)
99 #define SOKOBAN_LEVEL_REPEAT (BUTTON_MENU | BUTTON_UP)
101 #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
102 (CONFIG_KEYPAD == IRIVER_H300_PAD)
103 #define SOKOBAN_UP BUTTON_UP
104 #define SOKOBAN_DOWN BUTTON_DOWN
105 #define SOKOBAN_QUIT BUTTON_OFF
106 #define SOKOBAN_UNDO BUTTON_REC
107 #define SOKOBAN_REDO BUTTON_MODE
108 #define SOKOBAN_LEVEL_UP (BUTTON_ON | BUTTON_UP)
109 #define SOKOBAN_LEVEL_DOWN (BUTTON_ON | BUTTON_DOWN)
110 #define SOKOBAN_LEVEL_REPEAT BUTTON_ON
112 #define SOKOBAN_RC_QUIT BUTTON_RC_STOP
114 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
115 (CONFIG_KEYPAD == IPOD_3G_PAD)
116 #define SOKOBAN_UP BUTTON_MENU
117 #define SOKOBAN_DOWN BUTTON_PLAY
118 #define SOKOBAN_QUIT (BUTTON_SELECT | BUTTON_MENU)
119 #define SOKOBAN_UNDO_PRE BUTTON_SELECT
120 #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
121 #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)
125 /* fixme: if/when simultaneous button presses work for X5,
126 add redo & level repeat */
127 #elif (CONFIG_KEYPAD == IAUDIO_X5_PAD)
128 #define SOKOBAN_UP BUTTON_UP
129 #define SOKOBAN_DOWN BUTTON_DOWN
130 #define SOKOBAN_QUIT BUTTON_POWER
131 #define SOKOBAN_UNDO_PRE BUTTON_SELECT
132 #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
133 #define SOKOBAN_LEVEL_UP BUTTON_PLAY
134 #define SOKOBAN_LEVEL_DOWN BUTTON_REC
136 #elif (CONFIG_KEYPAD == GIGABEAT_PAD)
137 #define SOKOBAN_UP BUTTON_UP
138 #define SOKOBAN_DOWN BUTTON_DOWN
139 #define SOKOBAN_QUIT BUTTON_A
140 #define SOKOBAN_UNDO BUTTON_SELECT
141 #define SOKOBAN_REDO BUTTON_POWER
142 #define SOKOBAN_LEVEL_UP (BUTTON_MENU | BUTTON_UP)
143 #define SOKOBAN_LEVEL_DOWN (BUTTON_MENU | BUTTON_DOWN)
144 #define SOKOBAN_LEVEL_REPEAT BUTTON_MENU
146 #elif (CONFIG_KEYPAD == SANSA_E200_PAD)
147 #define SOKOBAN_UP BUTTON_UP
148 #define SOKOBAN_DOWN BUTTON_DOWN
149 #define SOKOBAN_QUIT BUTTON_POWER
150 #define SOKOBAN_UNDO_PRE BUTTON_SELECT
151 #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL)
152 #define SOKOBAN_REDO BUTTON_REC
153 #define SOKOBAN_LEVEL_UP (BUTTON_SELECT | BUTTON_UP)
154 #define SOKOBAN_LEVEL_DOWN (BUTTON_SELECT | BUTTON_DOWN)
155 #define SOKOBAN_LEVEL_REPEAT (BUTTON_SELECT | BUTTON_RIGHT)
157 #elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
158 #define SOKOBAN_UP BUTTON_SCROLL_UP
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)
168 #endif
170 #ifdef HAVE_LCD_COLOR
171 /* Background color. Default Rockbox light blue. */
172 #define BG_COLOR LCD_RGBPACK(181, 199, 231)
174 #elif LCD_DEPTH >= 2
175 #define MEDIUM_GRAY LCD_BRIGHTNESS(127)
176 #endif
178 /* The Location, Undo and LevelInfo structs are OO-flavored.
179 * (oooh!-flavored as Schnueff puts it.) It makes more you have to know,
180 * but the overall data layout becomes more manageable. */
182 /* Level data & stats */
183 struct LevelInfo {
184 short level;
185 short moves;
186 short pushes;
187 short boxes_to_go;
190 struct Location {
191 short row;
192 short col;
195 struct Board {
196 char spaces[ROWS][COLS];
199 /* Our full undo history */
200 static struct UndoInfo {
201 short count; /* How many undos are left */
202 short current; /* Which history is the current undo */
203 short max; /* Which history is the max redoable */
204 char history[MAX_UNDOS];
205 } undo_info;
207 /* Our playing board */
208 static struct BoardInfo {
209 char board[ROWS][COLS];
210 struct LevelInfo level;
211 struct Location player;
212 int max_level; /* How many levels do we have? */
213 int loaded_level; /* Which level is in memory */
214 } current_info;
216 static struct BufferedBoards {
217 struct Board levels[NUM_BUFFERED_BOARDS];
218 int low;
219 } buffered_boards;
221 static struct plugin_api* rb;
223 static void init_undo(void)
225 undo_info.count = 0;
226 undo_info.current = -1;
227 undo_info.max = -1;
230 static void get_delta(char direction, short *d_r, short *d_c)
232 switch (direction) {
233 case SOKOBAN_PUSH_LEFT:
234 case SOKOBAN_MOVE_LEFT:
235 *d_r = 0;
236 *d_c = -1;
237 break;
238 case SOKOBAN_PUSH_RIGHT:
239 case SOKOBAN_MOVE_RIGHT:
240 *d_r = 0;
241 *d_c = 1;
242 break;
243 case SOKOBAN_PUSH_UP:
244 case SOKOBAN_MOVE_UP:
245 *d_r = -1;
246 *d_c = 0;
247 break;
248 case SOKOBAN_PUSH_DOWN:
249 case SOKOBAN_MOVE_DOWN:
250 *d_r = 1;
251 *d_c = 0;
255 static void undo(void)
257 char undo;
258 short r, c;
259 short d_r = 0, d_c = 0; /* delta row & delta col */
260 char *space_cur, *space_next, *space_prev;
261 bool undo_push = false;
263 /* 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)
265 return;
267 undo = undo_info.history[undo_info.current];
269 if (undo < SOKOBAN_MOVE_MIN)
270 undo_push = true;
272 get_delta(undo, &d_r, &d_c);
274 r = current_info.player.row;
275 c = current_info.player.col;
277 /* Give the 3 spaces we're going to use better names */
278 space_cur = &current_info.board[r][c];
279 space_next = &current_info.board[r + d_r][c + d_c];
280 space_prev = &current_info.board[r - d_r][c - d_c];
282 /* Update board info */
283 if (undo_push) {
284 /* Moving box from goal to blank */
285 if (*space_next == '%' && *space_cur == '@')
286 current_info.level.boxes_to_go++;
287 /* Moving box from blank to goal */
288 else if (*space_next == '$' && *space_cur == '+')
289 current_info.level.boxes_to_go--;
291 /* Move box off of next space... */
292 *space_next = (*space_next == '%' ? '.' : ' ');
293 /* ...and on to current space */
294 *space_cur = (*space_cur == '+' ? '%' : '$');
296 current_info.level.pushes--;
297 } else
298 /* Just move player off of current space */
299 *space_cur = (*space_cur == '+' ? '.' : ' ');
300 /* Move player back to previous space */
301 *space_prev = (*space_prev == '.' ? '+' : '@');
303 /* Update position */
304 current_info.player.row -= d_r;
305 current_info.player.col -= d_c;
307 current_info.level.moves--;
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--;
315 undo_info.count--;
317 return;
320 static void add_undo(char undo)
322 /* Wrap around if MAX_UNDOS exceeded */
323 if (undo_info.current < (MAX_UNDOS - 1))
324 undo_info.current++;
325 else
326 undo_info.current = 0;
328 undo_info.history[undo_info.current] = undo;
330 if (undo_info.count < MAX_UNDOS)
331 undo_info.count++;
334 static bool move(char direction, bool redo)
336 short r, c;
337 short d_r = 0, d_c = 0; /* delta row & delta col */
338 char *space_cur, *space_next, *space_beyond;
339 bool push = false;
341 get_delta(direction, &d_r, &d_c);
343 r = current_info.player.row;
344 c = current_info.player.col;
346 /* Check for out-of-bounds */
347 if (r + 2*d_r < 0 || r + 2*d_r >= ROWS ||
348 c + 2*d_c < 0 || c + 2*d_c >= COLS)
349 return false;
351 /* Give the 3 spaces we're going to use better names */
352 space_cur = &current_info.board[r][c];
353 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];
356 if (*space_next == '$' || *space_next == '%') {
357 /* Change direction from move to push for undo */
358 if (direction >= SOKOBAN_MOVE_MIN)
359 direction -= SOKOBAN_MOVE_DIFF;
360 push = true;
363 /* Update board info */
364 if (push) {
365 /* Moving box from goal to blank */
366 if (*space_next == '%' && *space_beyond == ' ')
367 current_info.level.boxes_to_go++;
368 /* Moving box from blank to goal */
369 else if (*space_next == '$' && *space_beyond == '.')
370 current_info.level.boxes_to_go--;
371 /* Check for illegal move */
372 else if (*space_beyond != '.' && *space_beyond != ' ')
373 return false;
375 /* Move player onto next space */
376 *space_next = (*space_next == '%' ? '+' : '@');
377 /* Move box onto space beyond next */
378 *space_beyond = (*space_beyond == '.' ? '%' : '$');
380 current_info.level.pushes++;
381 } else {
382 /* Check for illegal move */
383 if (*space_next == '#' || *space_next == 'X')
384 return false;
386 /* Move player onto next space */
387 *space_next = (*space_next == '.' ? '+' : '@');
389 /* Move player off of current space */
390 *space_cur = (*space_cur == '+' ? '.' : ' ');
392 /* Update position */
393 current_info.player.row += d_r;
394 current_info.player.col += d_c;
396 current_info.level.moves++;
398 /* Update undo_info.max to current on every normal move,
399 except if it's the same as a redo. */
400 /* normal move */
401 if (!redo &&
402 /* moves have been undone */
403 ((undo_info.max != undo_info.current &&
404 /* and the current move is NOT the same as the one in history */
405 undo_info.history[undo_info.current+1] != direction) ||
406 /* or moves have not been undone */
407 undo_info.max == undo_info.current)) {
408 add_undo(direction);
409 undo_info.max = undo_info.current;
410 } else /* redo move or move was same as redo */
411 add_undo(direction); /* (just to update current) */
413 return true;
416 #ifdef SOKOBAN_REDO
417 static bool redo(void)
419 /* If no moves have been undone, quit */
420 if (undo_info.current == undo_info.max)
421 return false;
423 return move(undo_info.history[(undo_info.current+1 < MAX_UNDOS ?
424 undo_info.current+1 : 0)], true);
426 #endif
428 static void init_boards(void)
430 current_info.level.level = 0;
431 current_info.level.moves = 0;
432 current_info.level.pushes = 0;
433 current_info.level.boxes_to_go = 0;
434 current_info.player.row = 0;
435 current_info.player.col = 0;
436 current_info.max_level = 0;
437 current_info.loaded_level = 0;
439 buffered_boards.low = 0;
441 init_undo();
444 static int read_levels(int initialize_count)
446 int fd = 0;
447 int len;
448 int lastlen = 0;
449 int row = 0;
450 int level_count = 0;
451 char buffer[COLS + 3]; /* COLS plus CR/LF and \0 */
452 int endpoint = current_info.level.level-1;
454 if (endpoint < buffered_boards.low)
455 endpoint = current_info.level.level - NUM_BUFFERED_BOARDS;
457 if (endpoint < 0) endpoint = 0;
459 buffered_boards.low = endpoint;
460 endpoint += NUM_BUFFERED_BOARDS;
462 if ((fd = rb->open(LEVELS_FILE, O_RDONLY)) < 0) {
463 rb->splash(HZ*2, "Unable to open %s", LEVELS_FILE);
464 return -1;
467 do {
468 len = rb->read_line(fd, buffer, sizeof(buffer));
469 if (len >= 3) {
470 /* This finds lines that are more than 1 or 2 characters
471 * shorter than they should be. Due to the possibility of
472 * a mixed unix and dos CR/LF file format, I'm not going to
473 * do a precise check */
474 if (len < COLS) {
475 rb->splash(HZ*2, "Error in levels file: short line");
476 return -1;
478 if (level_count >= buffered_boards.low && level_count < endpoint) {
479 int index = level_count - buffered_boards.low;
480 rb->memcpy(
481 buffered_boards.levels[index].spaces[row], buffer, COLS);
483 row++;
484 } else if (len) {
485 if (lastlen < 3) {
486 /* Two short lines in a row means new level */
487 level_count++;
488 if (level_count >= endpoint && !initialize_count) break;
489 if (level_count && row != ROWS) {
490 rb->splash(HZ*2, "Error in levels file: short board");
491 return -1;
493 row = 0;
496 } while ((lastlen=len));
498 rb->close(fd);
499 if (initialize_count) {
500 /* Plus one because there aren't trailing short lines in the file */
501 current_info.max_level = level_count + 1;
503 return 0;
506 /* return non-zero on error */
507 static void load_level(void)
509 int c = 0;
510 int r = 0;
511 int index = current_info.level.level - buffered_boards.low - 1;
512 struct Board *level;
514 if (index < 0 || index >= NUM_BUFFERED_BOARDS) {
515 read_levels(false);
516 index = index < 0 ? NUM_BUFFERED_BOARDS-1 : 0;
518 level = &buffered_boards.levels[index];
520 current_info.level.boxes_to_go = 0;
521 current_info.level.moves = 0;
522 current_info.level.pushes = 0;
523 current_info.loaded_level = current_info.level.level;
525 for (r = 0; r < ROWS; r++) {
526 for (c = 0; c < COLS; c++) {
527 current_info.board[r][c] = level->spaces[r][c];
529 if (current_info.board[r][c] == '.' ||
530 current_info.board[r][c] == '+')
531 current_info.level.boxes_to_go++;
533 if (current_info.board[r][c] == '@' ||
534 current_info.board[r][c] == '+') {
535 current_info.player.row = r;
536 current_info.player.col = c;
542 static void update_screen(void)
544 int b = 0, c = 0;
545 int rows = 0, cols = 0;
546 char s[25];
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
562 #if LCD_DEPTH < 2
563 int i, j;
564 int max = MAGNIFY - 1;
565 int middle = max / 2;
566 int ldelta = (middle + 1) / 2;
567 #endif
569 /* load the board to the screen */
570 for (rows=0; rows < ROWS; rows++) {
571 for (cols = 0; cols < COLS; cols++) {
572 c = cols * MAGNIFY;
573 b = rows * MAGNIFY;
575 switch(current_info.board[rows][cols]) {
576 case 'X': /* black space */
577 break;
579 case '#': /* this is a wall */
580 #if LCD_DEPTH >= 2
581 rb->lcd_bitmap_part(sokoban_tiles, 0, 1*MAGNIFY, MAGNIFY,
582 c, b, MAGNIFY, MAGNIFY);
583 #else
584 for (i = c; i < c + MAGNIFY; i++)
585 for (j = b; j < b + MAGNIFY; j++)
586 if ((i ^ j) & 1)
587 rb->lcd_drawpixel(i, j);
588 #endif
589 break;
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
596 /* Free boxes are not filled in */
597 rb->lcd_drawrect(c, b, MAGNIFY, MAGNIFY);
598 #endif
599 break;
601 case '*':
602 case '%': /* this is a box on a goal */
604 #if LCD_DEPTH >= 2
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
612 break;
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
622 break;
624 case '@': /* this is you */
625 #if LCD_DEPTH >= 2
626 rb->lcd_bitmap_part(sokoban_tiles, 0, 5*MAGNIFY, MAGNIFY,
627 c, b, MAGNIFY, MAGNIFY);
628 #else
629 rb->lcd_drawline(c, b+middle, c+max, b+middle);
630 rb->lcd_drawline(c+middle, b, c+middle, b+max-ldelta);
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;
640 case '+': /* this is you on drugs, erm, on a goal */
641 #if LCD_DEPTH >= 2
642 rb->lcd_bitmap_part(sokoban_tiles, 0, 6*MAGNIFY, MAGNIFY,
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;
657 #if LCD_DEPTH >= 2
658 default:
659 rb->lcd_bitmap_part(sokoban_tiles, 0, 0*MAGNIFY, MAGNIFY,
660 c, b, MAGNIFY, MAGNIFY );
661 #endif
666 #if LCD_WIDTH-(COLS*MAGNIFY) < 32
667 #define STAT_SIZE 25
668 #define STAT_POS LCD_HEIGHT-STAT_SIZE
669 #define STAT_CENTER (LCD_WIDTH-120)/2
671 rb->lcd_putsxy(4+STAT_CENTER, STAT_POS+4, "Level");
672 rb->snprintf(s, sizeof(s), "%d", current_info.level.level);
673 rb->lcd_putsxy(7+STAT_CENTER, STAT_POS+14, s);
674 rb->lcd_putsxy(41+STAT_CENTER, STAT_POS+4, "Moves");
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);
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);
685 #else
686 #define STAT_POS COLS*MAGNIFY
687 #define STAT_SIZE LCD_WIDTH-STAT_POS
689 rb->lcd_putsxy(STAT_POS+1, 3, "Level");
690 rb->snprintf(s, sizeof(s), "%d", current_info.level.level);
691 rb->lcd_putsxy(STAT_POS+4, 13, s);
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);
696 rb->lcd_drawrect(STAT_POS, 0, STAT_SIZE, 24);
697 rb->lcd_drawrect(STAT_POS, 23, STAT_SIZE, 24);
699 #if LCD_HEIGHT >= 70
700 rb->lcd_putsxy(STAT_POS+1, 49, "Pushes");
701 rb->snprintf(s, sizeof(s), "%d", current_info.level.pushes);
702 rb->lcd_putsxy(STAT_POS+4, 59, s);
704 rb->lcd_drawrect(STAT_POS, 46, STAT_SIZE, 24);
705 #endif
706 #endif
708 /* print out the screen */
709 rb->lcd_update();
712 static void draw_level(void)
714 load_level();
715 rb->lcd_clear_display();
716 update_screen();
719 static bool sokoban_loop(void)
721 bool moved = true;
722 int i = 0, button = 0, lastbutton = 0;
723 short r = 0, c = 0;
724 int w, h;
725 char s[25];
727 current_info.level.level = 1;
729 load_level();
730 update_screen();
732 while (1) {
733 moved = true;
735 r = current_info.player.row;
736 c = current_info.player.col;
738 button = rb->button_get(true);
740 switch(button)
742 #ifdef SOKOBAN_RC_QUIT
743 case SOKOBAN_RC_QUIT:
744 #endif
745 case SOKOBAN_QUIT:
746 /* get out of here */
747 #ifdef HAVE_LCD_COLOR /* reset background color */
748 rb->lcd_set_background(rb->global_settings->bg_color);
749 #endif
750 return PLUGIN_OK;
752 case SOKOBAN_UNDO:
753 #ifdef SOKOBAN_UNDO_PRE
754 if (lastbutton != SOKOBAN_UNDO_PRE)
755 break;
756 #else /* repeat can't work here for Ondio et al */
757 case SOKOBAN_UNDO | BUTTON_REPEAT:
758 #endif
759 undo();
760 rb->lcd_clear_display();
761 update_screen();
762 moved = false;
763 break;
765 #ifdef SOKOBAN_REDO
766 case SOKOBAN_REDO:
767 case SOKOBAN_REDO | BUTTON_REPEAT:
768 moved = redo();
769 rb->lcd_clear_display();
770 update_screen();
771 break;
772 #endif
774 case SOKOBAN_LEVEL_UP:
775 case SOKOBAN_LEVEL_UP | BUTTON_REPEAT:
776 /* next level */
777 init_undo();
778 if (current_info.level.level < current_info.max_level)
779 current_info.level.level++;
781 draw_level();
782 moved = false;
783 break;
785 case SOKOBAN_LEVEL_DOWN:
786 case SOKOBAN_LEVEL_DOWN | BUTTON_REPEAT:
787 /* previous level */
788 init_undo();
789 if (current_info.level.level > 1)
790 current_info.level.level--;
792 draw_level();
793 moved = false;
794 break;
796 #ifdef SOKOBAN_LEVEL_REPEAT
797 case SOKOBAN_LEVEL_REPEAT:
798 case SOKOBAN_LEVEL_REPEAT | BUTTON_REPEAT:
799 /* same level */
800 init_undo();
801 draw_level();
802 moved = false;
803 break;
804 #endif
806 case BUTTON_LEFT:
807 case BUTTON_LEFT | BUTTON_REPEAT:
808 moved = move(SOKOBAN_MOVE_LEFT, false);
809 break;
811 case BUTTON_RIGHT:
812 case BUTTON_RIGHT | BUTTON_REPEAT:
813 moved = move(SOKOBAN_MOVE_RIGHT, false);
814 break;
816 case SOKOBAN_UP:
817 case SOKOBAN_UP | BUTTON_REPEAT:
818 moved = move(SOKOBAN_MOVE_UP, false);
819 break;
821 case SOKOBAN_DOWN:
822 case SOKOBAN_DOWN | BUTTON_REPEAT:
823 moved = move(SOKOBAN_MOVE_DOWN, false);
824 break;
826 default:
827 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
828 return PLUGIN_USB_CONNECTED;
830 moved = false;
831 break;
834 if (button != BUTTON_NONE)
835 lastbutton = button;
837 if (moved) {
838 rb->lcd_clear_display();
839 update_screen();
842 /* We have completed this level */
843 if (current_info.level.boxes_to_go == 0) {
845 if (moved) {
846 rb->lcd_clear_display();
847 /* Center level completed message */
848 rb->snprintf(s, sizeof(s), "Level %d Complete!",
849 current_info.level.level);
850 rb->lcd_getstringsize(s, &w, &h);
851 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - 16 , s);
852 rb->snprintf(s, sizeof(s), "%4d Moves ",
853 current_info.level.moves);
854 rb->lcd_getstringsize(s, &w, &h);
855 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 + 0 , s);
856 rb->snprintf(s, sizeof(s), "%4d Pushes",
857 current_info.level.pushes);
858 rb->lcd_getstringsize(s, &w, &h);
859 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 + 8 , s);
860 rb->lcd_update();
861 rb->button_get(false);
863 rb->sleep(HZ/2);
864 for (i = 0; i < 30; i++) {
865 rb->sleep(HZ/20);
866 button = rb->button_get(false);
867 if (button && ((button & BUTTON_REL) != BUTTON_REL))
868 break;
872 current_info.level.level++;
874 /* clear undo stats */
875 init_undo();
877 rb->lcd_clear_display();
879 if (current_info.level.level > current_info.max_level) {
880 /* Center "You WIN!!" on all screen sizes */
881 rb->snprintf(s, sizeof(s), "You WIN!!");
882 rb->lcd_getstringsize(s, &w, &h);
883 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h/2, s);
885 rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
886 /* Display for 10 seconds or until keypress */
887 for (i = 0; i < 200; i++) {
888 rb->lcd_fillrect(0, 0, LCD_WIDTH, LCD_HEIGHT);
889 rb->lcd_update();
890 rb->sleep(HZ/20);
892 button = rb->button_get(false);
893 if (button && ((button & BUTTON_REL) != BUTTON_REL))
894 break;
896 rb->lcd_set_drawmode(DRMODE_SOLID);
898 return PLUGIN_OK;
901 load_level();
902 update_screen();
905 } /* end while */
907 return PLUGIN_OK;
911 enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
913 int w, h;
914 int i;
915 int button = 0;
917 (void)(parameter);
918 rb = api;
920 rb->lcd_setfont(FONT_SYSFIXED);
922 #ifdef HAVE_LCD_COLOR
923 rb->lcd_set_background(BG_COLOR);
924 #endif
926 rb->lcd_clear_display();
927 rb->lcd_getstringsize(SOKOBAN_TITLE, &w, &h);
928 rb->lcd_putsxy(LCD_WIDTH/2 - w/2, LCD_HEIGHT/2 - h/2, SOKOBAN_TITLE);
929 rb->lcd_update();
930 rb->sleep(HZ);
932 rb->lcd_clear_display();
934 #if (CONFIG_KEYPAD == RECORDER_PAD) || \
935 (CONFIG_KEYPAD == ARCHOS_AV300_PAD)
936 rb->lcd_putsxy(3, 6, "[OFF] Quit");
937 rb->lcd_putsxy(3, 16, "[ON] Undo");
938 rb->lcd_putsxy(3, 26, "[PLAY] Redo");
939 rb->lcd_putsxy(3, 36, "[F1] Down a Level");
940 rb->lcd_putsxy(3, 46, "[F2] Restart Level");
941 rb->lcd_putsxy(3, 56, "[F3] Up a Level");
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_X5_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, "[A] Quit");
971 rb->lcd_putsxy(3, 16, "[SELECT] Undo");
972 rb->lcd_putsxy(3, 26, "[POWER] Redo");
973 rb->lcd_putsxy(3, 36, "[MENU+DOWN] Down a Level");
974 rb->lcd_putsxy(3, 46, "[MENU] Restart Level");
975 rb->lcd_putsxy(3, 56, "[MENU+UP] 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
992 rb->lcd_update();
993 rb->button_get(false);
994 /* Display for 3 seconds or until keypress */
995 for (i = 0; i < 60; i++) {
996 rb->sleep(HZ/20);
997 button = rb->button_get(false);
998 if (button && ((button & BUTTON_REL) != BUTTON_REL))
999 break;
1001 rb->lcd_clear_display();
1003 init_boards();
1005 if (read_levels(1) != 0)
1006 return PLUGIN_OK;
1008 return sokoban_loop();
1011 #endif