summaryrefslogtreecommitdiff
path: root/apps/plugins/sokoban.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/sokoban.c')
-rw-r--r--apps/plugins/sokoban.c868
1 files changed, 868 insertions, 0 deletions
diff --git a/apps/plugins/sokoban.c b/apps/plugins/sokoban.c
new file mode 100644
index 0000000000..2387fa9517
--- /dev/null
+++ b/apps/plugins/sokoban.c
@@ -0,0 +1,868 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2002 Eric Linenberg
11 * February 2003: Robert Hak performs a cleanup/rewrite/feature addition.
12 * Eric smiles. Bjorn cries. Linus say 'huh?'.
13 *
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.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "plugin.h"
22
23#ifdef HAVE_LCD_BITMAP
24
25#define SOKOBAN_TITLE "Sokoban"
26#define SOKOBAN_TITLE_FONT 2
27
28#define LEVELS_FILE "/.rockbox/sokoban/levels.txt"
29
30#define ROWS 16
31#define COLS 20
32#define MAX_UNDOS 5
33
34#define SOKOBAN_LEVEL_SIZE (ROWS*COLS)
35
36static void init_undo(void);
37static void undo(void);
38static void add_undo(int button);
39
40static int get_level(char *level, int level_size);
41static int get_level_count(void);
42static int load_level(void);
43static void draw_level(void);
44
45static void init_boards(void);
46static void update_screen(void);
47static bool sokoban_loop(void);
48
49/* The Location, Undo and LevelInfo structs are OO-flavored.
50 * (oooh!-flavored as Schnueff puts it.) It makes more you have to know,
51 * but the overall data layout becomes more manageable. */
52
53/* We use the same three values in 2 structs. Makeing them a struct
54 * hopefully ensures that if you change things in one, the other changes
55 * as well. */
56struct LevelInfo {
57 short level;
58 short moves;
59 short boxes_to_go;
60};
61
62/* What a given location on the board looks like at a given time */
63struct Location {
64 char spot;
65 short row;
66 short col;
67};
68
69/* A single level of undo. Each undo move can affect upto,
70 * but not more then, 3 spots on the board */
71struct Undo {
72 struct LevelInfo level;
73 struct Location location[3];
74};
75
76/* Our full undo history */
77static struct UndoInfo {
78 short count; /* How many undos are there in history */
79 short current; /* Which history is the current undo */
80 struct Undo history[MAX_UNDOS];
81} undo_info;
82
83/* Our playing board */
84static struct BoardInfo {
85 char board[ROWS][COLS];
86 struct LevelInfo level;
87 struct Location player;
88 int max_level; /* How many levels do we have? */
89 int level_offset; /* Where in the level file is this level */
90 int loaded_level; /* Which level is in memory */
91} current_info;
92
93static struct plugin_api* rb;
94
95static void init_undo(void)
96{
97 undo_info.count = 0;
98 undo_info.current = 0;
99}
100
101static void undo(void)
102{
103 struct Undo *undo;
104 int i = 0;
105 short row, col;
106
107 if (undo_info.count == 0)
108 return;
109
110 /* Update board info */
111 undo = &undo_info.history[undo_info.current];
112
113 rb->memcpy(&current_info.level, &undo->level, sizeof(undo->level));
114 rb->memcpy(&current_info.player, &undo->location[0], sizeof(undo->location[0]));
115
116 row = undo->location[0].row;
117 col = undo->location[0].col;
118 current_info.board[row][col] = '@';
119
120 /* Update the two other possible spots */
121 for (i = 1; i < 3; i++) {
122 if (undo->location[i].spot != '\0') {
123 row = undo->location[i].row;
124 col = undo->location[i].col;
125 current_info.board[row][col] = undo->location[i].spot;
126 undo->location[i].spot = '\0';
127 }
128 }
129
130 /* Remove this undo from the list */
131 if (undo_info.current == 0) {
132 if (undo_info.count > 1)
133 undo_info.current = MAX_UNDOS - 1;
134 } else {
135 undo_info.current--;
136 }
137
138 undo_info.count--;
139
140 return;
141}
142
143static void add_undo(int button)
144{
145 struct Undo *undo;
146 int row, col, i;
147 bool storable;
148
149 if ((button != BUTTON_LEFT) && (button != BUTTON_RIGHT) &&
150 (button != BUTTON_UP) && (button != BUTTON_DOWN))
151 return;
152
153 if (undo_info.count != 0) {
154 if (undo_info.current < (MAX_UNDOS - 1))
155 undo_info.current++;
156 else
157 undo_info.current = 0;
158 }
159
160 /* Make what follows more readable */
161 undo = &undo_info.history[undo_info.current];
162
163 /* Store our level info */
164 rb->memcpy(&undo->level, &current_info.level, sizeof(undo->level));
165
166 /* Store our player info */
167 rb->memcpy(&undo->location[0], &current_info.player, sizeof(undo->location[0]));
168
169 /* Now we need to store upto 2 blocks that may be affected.
170 * If player.spot is NULL, then there is no info stored
171 * for that block */
172
173 row = current_info.player.row;
174 col = current_info.player.col;
175
176 /* This must stay as _1_ because the first block (0) is the player */
177 for (i = 1; i < 3; i++) {
178 storable = true;
179
180 switch (button) {
181 case BUTTON_LEFT:
182 col--;
183 if (col < 0)
184 storable = false;
185 break;
186
187 case BUTTON_RIGHT:
188 col++;
189 if (col >= COLS)
190 storable = false;
191 break;
192
193 case BUTTON_UP:
194 row--;
195 if (row < 0)
196 storable = false;
197 break;
198
199 case BUTTON_DOWN:
200 row++;
201 if (row >= ROWS)
202 storable = false;
203 break;
204
205 default:
206 return;
207 }
208
209 if (storable) {
210 undo->location[i].col = col;
211 undo->location[i].row = row;
212 undo->location[i].spot = current_info.board[row][col];
213 } else {
214 undo->location[i].spot = '\0';
215 }
216 }
217
218 if (undo_info.count < MAX_UNDOS)
219 undo_info.count++;
220}
221
222static void init_boards(void)
223{
224 current_info.level.level = 0;
225 current_info.level.moves = 0;
226 current_info.level.boxes_to_go = 0;
227 current_info.player.row = 0;
228 current_info.player.col = 0;
229 current_info.player.spot = ' ';
230 current_info.max_level = 0;
231 current_info.level_offset = 0;
232 current_info.loaded_level = 0;
233
234 init_undo();
235}
236
237static int get_level_count(void)
238{
239 int fd = 0;
240 int lastlen = 0;
241 char buffer[COLS + 3]; /* COLS plus CR/LF and \0 */
242
243 if ((fd = rb->open(LEVELS_FILE, O_RDONLY)) < 0) {
244 rb->splash(0, 0, true, "Unable to open %s", LEVELS_FILE);
245 return -1;
246 }
247
248 while(1) {
249 int len = rb->read_line(fd, buffer, sizeof(buffer));
250 if(len <= 0)
251 break;
252
253 /* Two short lines in a row means new level */
254 if(len < 3 && lastlen < 3)
255 current_info.max_level++;
256
257 lastlen = len;
258 }
259
260 rb->close(fd);
261 return 0;
262}
263
264static int get_level(char *level, int level_size)
265{
266 int fd = 0, i = 0;
267 int nread = 0;
268 int count = 0;
269 int lastlen = 0;
270 int level_ct = 1;
271 unsigned char buffer[SOKOBAN_LEVEL_SIZE * 2];
272 bool level_found = false;
273
274 /* open file */
275 if ((fd = rb->open(LEVELS_FILE, O_RDONLY)) < 0)
276 return -1;
277
278 /* Lets not reparse the full file if we can avoid it */
279 if (current_info.loaded_level < current_info.level.level) {
280 rb->lseek(fd, current_info.level_offset, SEEK_SET);
281 level_ct = current_info.loaded_level;
282 }
283
284 if(current_info.level.level > 1) {
285 while(!level_found) {
286 int len = rb->read_line(fd, buffer, SOKOBAN_LEVEL_SIZE);
287 if(len <= 0) {
288 rb->close(fd);
289 return -1;
290 }
291
292 /* Two short lines in a row means new level */
293 if(len < 3 && lastlen < 3) {
294 level_ct++;
295 if(level_ct == current_info.level.level)
296 level_found = true;
297 }
298 lastlen = len;
299 }
300 }
301
302 /* Remember the current offset */
303 current_info.level_offset = rb->lseek(fd, 0, SEEK_CUR);
304
305 /* read a full buffer chunk from here */
306 nread = rb->read(fd, buffer, sizeof(buffer)-1);
307 if (nread < 0)
308 return -1;
309 buffer[nread] = 0;
310
311 rb->close(fd);
312
313 /* If we read less then a level, error */
314 if (nread < level_size)
315 return -1;
316
317 /* Load our new level */
318 for(i=0, count=0; (count < nread) && (i<level_size);) {
319 if (buffer[count] != '\n' && buffer[count] != '\r')
320 level[i++] = buffer[count];
321 count++;
322 }
323 level[i] = 0;
324
325 current_info.loaded_level = current_info.level.level;
326 return 0;
327}
328
329/* return non-zero on error */
330static int load_level(void)
331{
332 short c = 0;
333 short r = 0;
334 short i = 0;
335 char level[ROWS*COLS+1];
336 int x = 0;
337
338 current_info.player.spot=' ';
339 current_info.level.boxes_to_go = 0;
340 current_info.level.moves = 0;
341
342 if (get_level(level, sizeof(level)) != 0)
343 return -1;
344
345 i = 0;
346 for (r = 0; r < ROWS; r++) {
347 x++;
348 for (c = 0; c < COLS; c++, i++) {
349 current_info.board[r][c] = level[i];
350
351 if (current_info.board[r][c] == '.')
352 current_info.level.boxes_to_go++;
353
354 else if (current_info.board[r][c] == '@') {
355 current_info.player.row = r;
356 current_info.player.col = c;
357 }
358 }
359 }
360
361 return 0;
362}
363
364static void update_screen(void)
365{
366 short b = 0, c = 0;
367 short rows = 0, cols = 0;
368 char s[25];
369
370 short magnify = 4;
371
372 /* load the board to the screen */
373 for (rows=0 ; rows < ROWS ; rows++) {
374 for (cols = 0 ; cols < COLS ; cols++) {
375 c = cols * magnify;
376 b = rows * magnify;
377
378 switch(current_info.board[rows][cols]) {
379 case 'X': /* black space */
380 rb->lcd_drawrect(c, b, magnify, magnify);
381 rb->lcd_drawrect(c+1, b+1, 2, 2);
382 break;
383
384 case '#': /* this is a wall */
385 rb->lcd_drawpixel(c, b);
386 rb->lcd_drawpixel(c+2, b);
387 rb->lcd_drawpixel(c+1, b+1);
388 rb->lcd_drawpixel(c+3, b+1);
389 rb->lcd_drawpixel(c, b+2);
390 rb->lcd_drawpixel(c+2, b+2);
391 rb->lcd_drawpixel(c+1, b+3);
392 rb->lcd_drawpixel(c+3, b+3);
393 break;
394
395 case '.': /* this is a home location */
396 rb->lcd_drawrect(c+1, b+1, 2, 2);
397 break;
398
399 case '$': /* this is a box */
400 rb->lcd_drawrect(c, b, magnify, magnify);
401 break;
402
403 case '@': /* this is you */
404 rb->lcd_drawline(c+1, b, c+2, b);
405 rb->lcd_drawline(c, b+1, c+3, b+1);
406 rb->lcd_drawline(c+1, b+2, c+2, b+2);
407
408 rb->lcd_drawpixel(c, b+3);
409 rb->lcd_drawpixel(c+3, b+3);
410 break;
411
412 case '%': /* this is a box on a home spot */
413 rb->lcd_drawrect(c, b, magnify, magnify);
414 rb->lcd_drawrect(c+1, b+1, 2, 2);
415 break;
416 }
417 }
418 }
419
420
421 rb->snprintf(s, sizeof(s), "%d", current_info.level.level);
422 rb->lcd_putsxy(86, 22, s);
423 rb->snprintf(s, sizeof(s), "%d", current_info.level.moves);
424 rb->lcd_putsxy(86, 54, s);
425
426 rb->lcd_drawrect(80,0,32,32);
427 rb->lcd_drawrect(80,32,32,64);
428 rb->lcd_putsxy(81, 10, "Level");
429 rb->lcd_putsxy(81, 42, "Moves");
430
431 /* print out the screen */
432 rb->lcd_update();
433}
434
435static void draw_level(void)
436{
437 load_level();
438 rb->lcd_clear_display();
439 update_screen();
440}
441
442static bool sokoban_loop(void)
443{
444 char new_spot;
445 bool moved = true;
446 int i = 0, button = 0;
447 short r = 0, c = 0;
448
449 current_info.level.level = 1;
450
451 load_level();
452 update_screen();
453
454 while (1) {
455 moved = true;
456
457 r = current_info.player.row;
458 c = current_info.player.col;
459
460 button = rb->button_get(true);
461
462 add_undo(button);
463
464 switch(button)
465 {
466 case BUTTON_OFF:
467 /* get out of here */
468 return PLUGIN_OK;
469
470 case BUTTON_ON:
471 case BUTTON_ON | BUTTON_REPEAT:
472 /* this is UNDO */
473 undo();
474 rb->lcd_clear_display();
475 update_screen();
476 moved = false;
477 break;
478
479 case BUTTON_F3:
480 case BUTTON_F3 | BUTTON_REPEAT:
481 /* increase level */
482 init_undo();
483 current_info.level.boxes_to_go=0;
484 moved = true;
485 break;
486
487 case BUTTON_F1:
488 case BUTTON_F1 | BUTTON_REPEAT:
489 /* previous level */
490 init_undo();
491 if (current_info.level.level > 1)
492 current_info.level.level--;
493
494 draw_level();
495 moved = false;
496 break;
497
498 case BUTTON_F2:
499 case BUTTON_F2 | BUTTON_REPEAT:
500 /* same level */
501 init_undo();
502 draw_level();
503 moved = false;
504 break;
505
506 case BUTTON_LEFT:
507 switch(current_info.board[r][c-1])
508 {
509 case ' ': /* if it is a blank spot */
510 case '.': /* if it is a home spot */
511 new_spot = current_info.board[r][c-1];
512 current_info.board[r][c-1] = '@';
513 current_info.board[r][c] = current_info.player.spot;
514 current_info.player.spot = new_spot;
515 break;
516
517 case '$':
518 switch(current_info.board[r][c-2])
519 {
520 case ' ': /* going from blank to blank */
521 current_info.board[r][c-2] = current_info.board[r][c-1];
522 current_info.board[r][c-1] = current_info.board[r][c];
523 current_info.board[r][c] = current_info.player.spot;
524 current_info.player.spot = ' ';
525 break;
526
527 case '.': /* going from a blank to home */
528 current_info.board[r][c-2] = '%';
529 current_info.board[r][c-1] = current_info.board[r][c];
530 current_info.board[r][c] = current_info.player.spot;
531 current_info.player.spot = ' ';
532 current_info.level.boxes_to_go--;
533 break;
534
535 default:
536 moved = false;
537 break;
538 }
539 break;
540
541 case '%':
542 switch(current_info.board[r][c-2]) {
543 case ' ': /* we are going from a home to a blank */
544 current_info.board[r][c-2] = '$';
545 current_info.board[r][c-1] = current_info.board[r][c];
546 current_info.board[r][c] = current_info.player.spot;
547 current_info.player.spot = '.';
548 current_info.level.boxes_to_go++;
549 break;
550
551 case '.': /* if we are going from a home to home */
552 current_info.board[r][c-2] = '%';
553 current_info.board[r][c-1] = current_info.board[r][c];
554 current_info.board[r][c] = current_info.player.spot;
555 current_info.player.spot = '.';
556 break;
557
558 default:
559 moved = false;
560 break;
561 }
562 break;
563
564 default:
565 moved = false;
566 break;
567 }
568
569 if (moved)
570 current_info.player.col--;
571 break;
572
573 case BUTTON_RIGHT: /* if it is a blank spot */
574 switch(current_info.board[r][c+1]) {
575 case ' ':
576 case '.': /* if it is a home spot */
577 new_spot = current_info.board[r][c+1];
578 current_info.board[r][c+1] = '@';
579 current_info.board[r][c] = current_info.player.spot;
580 current_info.player.spot = new_spot;
581 break;
582
583 case '$':
584 switch(current_info.board[r][c+2]) {
585 case ' ': /* going from blank to blank */
586 current_info.board[r][c+2] = current_info.board[r][c+1];
587 current_info.board[r][c+1] = current_info.board[r][c];
588 current_info.board[r][c] = current_info.player.spot;
589 current_info.player.spot = ' ';
590 break;
591
592 case '.': /* going from a blank to home */
593 current_info.board[r][c+2] = '%';
594 current_info.board[r][c+1] = current_info.board[r][c];
595 current_info.board[r][c] = current_info.player.spot;
596 current_info.player.spot = ' ';
597 current_info.level.boxes_to_go--;
598 break;
599
600 default:
601 moved = false;
602 break;
603 }
604 break;
605
606 case '%':
607 switch(current_info.board[r][c+2]) {
608 case ' ': /* going from a home to a blank */
609 current_info.board[r][c+2] = '$';
610 current_info.board[r][c+1] = current_info.board[r][c];
611 current_info.board[r][c] = current_info.player.spot;
612 current_info.player.spot = '.';
613 current_info.level.boxes_to_go++;
614 break;
615
616 case '.':
617 current_info.board[r][c+2] = '%';
618 current_info.board[r][c+1] = current_info.board[r][c];
619 current_info.board[r][c] = current_info.player.spot;
620 current_info.player.spot = '.';
621 break;
622
623 default:
624 moved = false;
625 break;
626 }
627 break;
628
629 default:
630 moved = false;
631 break;
632 }
633
634 if (moved)
635 current_info.player.col++;
636 break;
637
638 case BUTTON_UP:
639 switch(current_info.board[r-1][c]) {
640 case ' ': /* if it is a blank spot */
641 case '.': /* if it is a home spot */
642 new_spot = current_info.board[r-1][c];
643 current_info.board[r-1][c] = '@';
644 current_info.board[r][c] = current_info.player.spot;
645 current_info.player.spot = new_spot;
646 break;
647
648 case '$':
649 switch(current_info.board[r-2][c]) {
650 case ' ': /* going from blank to blank */
651 current_info.board[r-2][c] = current_info.board[r-1][c];
652 current_info.board[r-1][c] = current_info.board[r][c];
653 current_info.board[r][c] = current_info.player.spot;
654 current_info.player.spot = ' ';
655 break;
656
657 case '.': /* going from a blank to home */
658 current_info.board[r-2][c] = '%';
659 current_info.board[r-1][c] = current_info.board[r][c];
660 current_info.board[r][c] = current_info.player.spot;
661 current_info.player.spot = ' ';
662 current_info.level.boxes_to_go--;
663 break;
664
665 default:
666 moved = false;
667 break;
668 }
669 break;
670
671 case '%':
672 switch(current_info.board[r-2][c]) {
673 case ' ': /* we are going from a home to a blank */
674 current_info.board[r-2][c] = '$';
675 current_info.board[r-1][c] = current_info.board[r][c];
676 current_info.board[r][c] = current_info.player.spot;
677 current_info.player.spot = '.';
678 current_info.level.boxes_to_go++;
679 break;
680
681 case '.': /* if we are going from a home to home */
682 current_info.board[r-2][c] = '%';
683 current_info.board[r-1][c] = current_info.board[r][c];
684 current_info.board[r][c] = current_info.player.spot;
685 current_info.player.spot = '.';
686 break;
687
688 default:
689 moved = false;
690 break;
691 }
692 break;
693
694 default:
695 moved = false;
696 break;
697 }
698
699 if (moved)
700 current_info.player.row--;
701 break;
702
703 case BUTTON_DOWN:
704 switch(current_info.board[r+1][c]) {
705 case ' ': /* if it is a blank spot */
706 case '.': /* if it is a home spot */
707 new_spot = current_info.board[r+1][c];
708 current_info.board[r+1][c] = '@';
709 current_info.board[r][c] = current_info.player.spot;
710 current_info.player.spot = new_spot;
711 break;
712
713 case '$':
714 switch(current_info.board[r+2][c]) {
715 case ' ': /* going from blank to blank */
716 current_info.board[r+2][c] = current_info.board[r+1][c];
717 current_info.board[r+1][c] = current_info.board[r][c];
718 current_info.board[r][c] = current_info.player.spot;
719 current_info.player.spot = ' ';
720 break;
721
722 case '.': /* going from a blank to home */
723 current_info.board[r+2][c] = '%';
724 current_info.board[r+1][c] = current_info.board[r][c];
725 current_info.board[r][c] = current_info.player.spot;
726 current_info.player.spot = ' ';
727 current_info.level.boxes_to_go--;
728 break;
729
730 default:
731 moved = false;
732 break;
733 }
734 break;
735
736 case '%':
737 switch(current_info.board[r+2][c]) {
738 case ' ': /* going from a home to a blank */
739 current_info.board[r+2][c] = '$';
740 current_info.board[r+1][c] = current_info.board[r][c];
741 current_info.board[r][c] = current_info.player.spot;
742 current_info.player.spot = '.';
743 current_info.level.boxes_to_go++;
744 break;
745
746 case '.': /* going from a home to home */
747 current_info.board[r+2][c] = '%';
748 current_info.board[r+1][c] = current_info.board[r][c];
749 current_info.board[r][c] = current_info.player.spot;
750 current_info.player.spot = '.';
751 break;
752
753 default:
754 moved = false;
755 break;
756 }
757 break;
758
759 default:
760 moved = false;
761 break;
762 }
763
764 if (moved)
765 current_info.player.row++;
766 break;
767
768 case SYS_USB_CONNECTED:
769 rb->usb_screen();
770 return PLUGIN_USB_CONNECTED;
771
772 default:
773 moved = false;
774 break;
775 }
776
777 if (moved) {
778 current_info.level.moves++;
779 rb->lcd_clear_display();
780 update_screen();
781 }
782
783 /* We have completed this level */
784 if (current_info.level.boxes_to_go == 0) {
785 current_info.level.level++;
786
787 /* clear undo stats */
788 init_undo();
789
790 rb->lcd_clear_display();
791
792 if (current_info.level.level > current_info.max_level) {
793 rb->lcd_putsxy(10, 20, "You WIN!!");
794
795 for (i = 0; i < 30000 ; i++) {
796 rb->lcd_invertrect(0, 0, 111, 63);
797 rb->lcd_update();
798
799 button = rb->button_get(false);
800 if (button && ((button & BUTTON_REL) != BUTTON_REL))
801 break;
802 }
803
804 return PLUGIN_OK;
805 }
806
807 load_level();
808 update_screen();
809 }
810
811 } /* end while */
812
813 return PLUGIN_OK;
814}
815
816
817enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
818{
819 int w, h;
820 int len;
821
822 TEST_PLUGIN_API(api);
823 (void)(parameter);
824 rb = api;
825
826 rb->lcd_setfont(FONT_SYSFIXED);
827 rb->lcd_getstringsize(SOKOBAN_TITLE, &w, &h);
828
829 /* Get horizontel centering for text */
830 len = w;
831 if (len%2 != 0)
832 len =((len+1)/2)+(w/2);
833 else
834 len /= 2;
835
836 if (h%2 != 0)
837 h = (h/2)+1;
838 else
839 h /= 2;
840
841 rb->lcd_clear_display();
842 rb->lcd_putsxy(LCD_WIDTH/2-len,(LCD_HEIGHT/2)-h, SOKOBAN_TITLE);
843 rb->lcd_update();
844 rb->sleep(HZ*2);
845
846 rb->lcd_clear_display();
847
848 rb->lcd_putsxy(3, 6, "[OFF] To Stop");
849 rb->lcd_putsxy(3, 16, "[ON] To Undo");
850 rb->lcd_putsxy(3, 26, "[F1] - Level");
851 rb->lcd_putsxy(3, 36, "[F2] Same Level");
852 rb->lcd_putsxy(3, 46, "[F3] + Level");
853
854 rb->lcd_update();
855 rb->sleep(HZ*2);
856 rb->lcd_clear_display();
857
858 init_boards();
859
860 if (get_level_count() != 0) {
861 rb->splash(HZ*2,0,true,"Failed loading levels!");
862 return PLUGIN_OK;
863 }
864
865 return sokoban_loop();
866}
867
868#endif