1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
10 * Copyright (C) 2002 Eric Linenberg
11 * February 2003: Robert Hak performs a cleanup/rewrite/feature addition.
12 * Eric smiles. Bjorn cries. Linus say 'huh?'.
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 ****************************************************************************/
23 #ifdef HAVE_LCD_BITMAP
28 extern const fb_data sokoban_tiles
[];
31 #define SOKOBAN_TITLE "Sokoban"
33 #define LEVELS_FILE PLUGIN_DIR "/sokoban.levels"
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
44 #define NUM_BUFFERED_BOARDS MAX_BUFFERED_BOARDS
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 > \
49 #define MAX_UNDOS 0x7FFF
51 #define MAX_UNDOS PLUGIN_BUFFER_SIZE - \
52 NUM_BUFFERED_BOARDS*SOKOBAN_LEVEL_SIZE - 0x2000
55 /* Move/push definitions for undo */
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)
170 #ifdef HAVE_LCD_COLOR
171 /* Background color. Default Rockbox light blue. */
172 #define BG_COLOR LCD_RGBPACK(181, 199, 231)
175 #define MEDIUM_GRAY LCD_BRIGHTNESS(127)
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 */
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
];
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 */
216 static struct BufferedBoards
{
217 struct Board levels
[NUM_BUFFERED_BOARDS
];
221 static struct plugin_api
* rb
;
223 static void init_undo(void)
226 undo_info
.current
= -1;
230 static void get_delta(char direction
, short *d_r
, short *d_c
)
233 case SOKOBAN_PUSH_LEFT
:
234 case SOKOBAN_MOVE_LEFT
:
238 case SOKOBAN_PUSH_RIGHT
:
239 case SOKOBAN_MOVE_RIGHT
:
243 case SOKOBAN_PUSH_UP
:
244 case SOKOBAN_MOVE_UP
:
248 case SOKOBAN_PUSH_DOWN
:
249 case SOKOBAN_MOVE_DOWN
:
255 static void undo(void)
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
)
267 undo
= undo_info
.history
[undo_info
.current
];
269 if (undo
< SOKOBAN_MOVE_MIN
)
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
= ¤t_info
.board
[r
][c
];
279 space_next
= ¤t_info
.board
[r
+ d_r
][c
+ d_c
];
280 space_prev
= ¤t_info
.board
[r
- d_r
][c
- d_c
];
282 /* Update board info */
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
--;
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;
320 static void add_undo(char undo
)
322 /* Wrap around if MAX_UNDOS exceeded */
323 if (undo_info
.current
< (MAX_UNDOS
- 1))
326 undo_info
.current
= 0;
328 undo_info
.history
[undo_info
.current
] = undo
;
330 if (undo_info
.count
< MAX_UNDOS
)
334 static bool move(char direction
, bool redo
)
337 short d_r
= 0, d_c
= 0; /* delta row & delta col */
338 char *space_cur
, *space_next
, *space_beyond
;
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
)
351 /* Give the 3 spaces we're going to use better names */
352 space_cur
= ¤t_info
.board
[r
][c
];
353 space_next
= ¤t_info
.board
[r
+ d_r
][c
+ d_c
];
354 space_beyond
= ¤t_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
;
363 /* Update board info */
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
!= ' ')
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
++;
382 /* Check for illegal move */
383 if (*space_next
== '#' || *space_next
== 'X')
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. */
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
)) {
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) */
417 static bool redo(void)
419 /* If no moves have been undone, quit */
420 if (undo_info
.current
== undo_info
.max
)
423 return move(undo_info
.history
[(undo_info
.current
+1 < MAX_UNDOS
?
424 undo_info
.current
+1 : 0)], true);
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;
444 static int read_levels(int initialize_count
)
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
);
468 len
= rb
->read_line(fd
, buffer
, sizeof(buffer
));
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 */
475 rb
->splash(HZ
*2, "Error in levels file: short line");
478 if (level_count
>= buffered_boards
.low
&& level_count
< endpoint
) {
479 int index
= level_count
- buffered_boards
.low
;
481 buffered_boards
.levels
[index
].spaces
[row
], buffer
, COLS
);
486 /* Two short lines in a row means new level */
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");
496 } while ((lastlen
=len
));
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;
506 /* return non-zero on error */
507 static void load_level(void)
511 int index
= current_info
.level
.level
- buffered_boards
.low
- 1;
514 if (index
< 0 || index
>= NUM_BUFFERED_BOARDS
) {
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)
545 int rows
= 0, cols
= 0;
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 */
552 #elif (LCD_HEIGHT >= 144) && (LCD_WIDTH >= 212) || \
553 (LCD_HEIGHT >= 169) && (LCD_WIDTH >= 180-4) /* h3x0, ipod color/photo */
555 #elif (LCD_HEIGHT >= 96) && (LCD_WIDTH >= 152) || \
556 (LCD_HEIGHT >= 121) && (LCD_WIDTH >= 120) /* h1x0, ipod nano/mini */
564 int max
= MAGNIFY
- 1;
565 int middle
= max
/ 2;
566 int ldelta
= (middle
+ 1) / 2;
569 /* load the board to the screen */
570 for (rows
=0; rows
< ROWS
; rows
++) {
571 for (cols
= 0; cols
< COLS
; cols
++) {
575 switch(current_info
.board
[rows
][cols
]) {
576 case 'X': /* black space */
579 case '#': /* this is a wall */
581 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 1*MAGNIFY
, MAGNIFY
,
582 c
, b
, MAGNIFY
, MAGNIFY
);
584 for (i
= c
; i
< c
+ MAGNIFY
; i
++)
585 for (j
= b
; j
< b
+ MAGNIFY
; j
++)
587 rb
->lcd_drawpixel(i
, j
);
591 case '$': /* this is a box */
593 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 2*MAGNIFY
, MAGNIFY
,
594 c
, b
, MAGNIFY
, MAGNIFY
);
596 /* Free boxes are not filled in */
597 rb
->lcd_drawrect(c
, b
, MAGNIFY
, MAGNIFY
);
602 case '%': /* this is a box on a goal */
605 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 3*MAGNIFY
, MAGNIFY
,
606 c
, b
, MAGNIFY
, MAGNIFY
);
608 rb
->lcd_drawrect(c
, b
, MAGNIFY
, MAGNIFY
);
609 rb
->lcd_drawrect(c
+(MAGNIFY
/2)-1, b
+(MAGNIFY
/2)-1, MAGNIFY
/2,
614 case '.': /* this is a goal */
616 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 4*MAGNIFY
, MAGNIFY
,
617 c
, b
, MAGNIFY
, MAGNIFY
);
619 rb
->lcd_drawrect(c
+(MAGNIFY
/2)-1, b
+(MAGNIFY
/2)-1, MAGNIFY
/2,
624 case '@': /* this is you */
626 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 5*MAGNIFY
, MAGNIFY
,
627 c
, b
, MAGNIFY
, MAGNIFY
);
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
);
640 case '+': /* this is you on drugs, erm, on a goal */
642 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 6*MAGNIFY
, MAGNIFY
,
643 c
, b
, MAGNIFY
, MAGNIFY
);
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
,
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,
659 rb
->lcd_bitmap_part(sokoban_tiles
, 0, 0*MAGNIFY
, MAGNIFY
,
660 c
, b
, MAGNIFY
, MAGNIFY
);
666 #if LCD_WIDTH-(COLS*MAGNIFY) < 32
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
);
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);
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);
708 /* print out the screen */
712 static void draw_level(void)
715 rb
->lcd_clear_display();
719 static bool sokoban_loop(void)
722 int i
= 0, button
= 0, lastbutton
= 0;
727 current_info
.level
.level
= 1;
735 r
= current_info
.player
.row
;
736 c
= current_info
.player
.col
;
738 button
= rb
->button_get(true);
742 #ifdef SOKOBAN_RC_QUIT
743 case SOKOBAN_RC_QUIT
:
746 /* get out of here */
747 #ifdef HAVE_LCD_COLOR /* reset background color */
748 rb
->lcd_set_background(rb
->global_settings
->bg_color
);
753 #ifdef SOKOBAN_UNDO_PRE
754 if (lastbutton
!= SOKOBAN_UNDO_PRE
)
756 #else /* repeat can't work here for Ondio et al */
757 case SOKOBAN_UNDO
| BUTTON_REPEAT
:
760 rb
->lcd_clear_display();
767 case SOKOBAN_REDO
| BUTTON_REPEAT
:
769 rb
->lcd_clear_display();
774 case SOKOBAN_LEVEL_UP
:
775 case SOKOBAN_LEVEL_UP
| BUTTON_REPEAT
:
778 if (current_info
.level
.level
< current_info
.max_level
)
779 current_info
.level
.level
++;
785 case SOKOBAN_LEVEL_DOWN
:
786 case SOKOBAN_LEVEL_DOWN
| BUTTON_REPEAT
:
789 if (current_info
.level
.level
> 1)
790 current_info
.level
.level
--;
796 #ifdef SOKOBAN_LEVEL_REPEAT
797 case SOKOBAN_LEVEL_REPEAT
:
798 case SOKOBAN_LEVEL_REPEAT
| BUTTON_REPEAT
:
807 case BUTTON_LEFT
| BUTTON_REPEAT
:
808 moved
= move(SOKOBAN_MOVE_LEFT
, false);
812 case BUTTON_RIGHT
| BUTTON_REPEAT
:
813 moved
= move(SOKOBAN_MOVE_RIGHT
, false);
817 case SOKOBAN_UP
| BUTTON_REPEAT
:
818 moved
= move(SOKOBAN_MOVE_UP
, false);
822 case SOKOBAN_DOWN
| BUTTON_REPEAT
:
823 moved
= move(SOKOBAN_MOVE_DOWN
, false);
827 if (rb
->default_event_handler(button
) == SYS_USB_CONNECTED
)
828 return PLUGIN_USB_CONNECTED
;
834 if (button
!= BUTTON_NONE
)
838 rb
->lcd_clear_display();
842 /* We have completed this level */
843 if (current_info
.level
.boxes_to_go
== 0) {
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
);
861 rb
->button_get(false);
864 for (i
= 0; i
< 30; i
++) {
866 button
= rb
->button_get(false);
867 if (button
&& ((button
& BUTTON_REL
) != BUTTON_REL
))
872 current_info
.level
.level
++;
874 /* clear undo stats */
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
);
892 button
= rb
->button_get(false);
893 if (button
&& ((button
& BUTTON_REL
) != BUTTON_REL
))
896 rb
->lcd_set_drawmode(DRMODE_SOLID
);
911 enum plugin_status
plugin_start(struct plugin_api
* api
, void* parameter
)
920 rb
->lcd_setfont(FONT_SYSFIXED
);
922 #ifdef HAVE_LCD_COLOR
923 rb
->lcd_set_background(BG_COLOR
);
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
);
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");
993 rb
->button_get(false);
994 /* Display for 3 seconds or until keypress */
995 for (i
= 0; i
< 60; i
++) {
997 button
= rb
->button_get(false);
998 if (button
&& ((button
& BUTTON_REL
) != BUTTON_REL
))
1001 rb
->lcd_clear_display();
1005 if (read_levels(1) != 0)
1008 return sokoban_loop();