Don't force double-buffering for sd devices. They apparently are not faster with...
[kugel-rb.git] / apps / plugins / mazezam.c
blob2e79567740503c2a91144153b488f6810d214d70
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
9 * Copyright (C) 2006, 2008 Malcolm Tyrrell
11 * MazezaM - a Rockbox version of my ZX Spectrum game from 2002
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 ****************************************************************************/
22 #include "plugin.h"
23 #include "lib/configfile.h"
24 #include "lib/helper.h"
25 #include "lib/pluginlib_actions.h"
26 #include "lib/playback_control.h"
28 /* Include standard plugin macro */
29 PLUGIN_HEADER
31 /* The plugin actions of interest. */
32 const struct button_mapping *plugin_contexts[]
33 = {generic_directions, generic_actions};
35 /* Use the standard plugin buttons rather than a hard-to-maintain list of
36 * MazezaM specific buttons. */
37 #define MAZEZAM_UP PLA_UP
38 #define MAZEZAM_UP_REPEAT PLA_UP_REPEAT
39 #define MAZEZAM_DOWN PLA_DOWN
40 #define MAZEZAM_DOWN_REPEAT PLA_DOWN_REPEAT
41 #define MAZEZAM_LEFT PLA_LEFT
42 #define MAZEZAM_LEFT_REPEAT PLA_LEFT_REPEAT
43 #define MAZEZAM_RIGHT PLA_RIGHT
44 #define MAZEZAM_RIGHT_REPEAT PLA_RIGHT_REPEAT
45 #define MAZEZAM_MENU PLA_QUIT
47 /* All the text is here */
48 #define MAZEZAM_TEXT_GAME_OVER "Game Over"
49 #define MAZEZAM_TEXT_LIVES "Level %d, Lives %d"
50 #define MAZEZAM_TEXT_CHECKPOINT "Checkpoint reached"
51 #define MAZEZAM_TEXT_WELLDONE_TITLE "You have escaped!"
52 #define MAZEZAM_TEXT_WELLDONE_OPTION "Goodbye"
53 #define MAZEZAM_TEXT_MAZEZAM_MENU "MazezaM Menu"
54 #define MAZEZAM_TEXT_RETRY_LEVEL "Retry level"
55 #define MAZEZAM_TEXT_AUDIO_PLAYBACK "Audio playback"
56 #define MAZEZAM_TEXT_QUIT "Quit"
57 #define MAZEZAM_TEXT_BACK "Return"
58 #define MAZEZAM_TEXT_MAIN_MENU "MazezaM"
59 #define MAZEZAM_TEXT_CONTINUE "Play from checkpoint"
60 #define MAZEZAM_TEXT_PLAY_GAME "Play game"
61 #define MAZEZAM_TEXT_PLAY_NEW_GAME "Play new game"
63 #define MAZEZAM_START_LIVES 3 /* how many lives at game start */
64 #define MAZEZAM_FIRST_CHECKPOINT 3 /* The level at the first checkpoint */
65 #define MAZEZAM_CHECKPOINT_INTERVAL 4 /* A checkpoint every _ levels */
67 #ifdef HAVE_LCD_COLOR
68 #define MAZEZAM_HEADING_COLOR LCD_RGBPACK(255,255, 0) /* Yellow */
69 #define MAZEZAM_BORDER_COLOR LCD_RGBPACK( 0, 0,255) /* Blue */
70 #define MAZEZAM_COLOR LCD_RGBPACK(255,255,255) /* White */
71 #define MAZEZAM_BG_COLOR LCD_RGBPACK( 0, 0, 0) /* Black */
72 #define MAZEZAM_WALL_COLOR LCD_RGBPACK(100,100,100) /* Dark gray */
73 #define MAZEZAM_PLAYER_COLOR LCD_RGBPACK(255,255,255) /* White */
74 #define MAZEZAM_GATE_COLOR LCD_RGBPACK(100,100,100) /* Dark gray */
76 /* the rows are coloured sequentially */
77 #define MAZEZAM_NUM_CHUNK_COLORS 8
78 static const unsigned chunk_colors[MAZEZAM_NUM_CHUNK_COLORS] = {
79 LCD_RGBPACK(255,192, 32), /* Orange */
80 LCD_RGBPACK(255, 0, 0), /* Red */
81 LCD_RGBPACK( 0,255, 0), /* Green */
82 LCD_RGBPACK( 0,255,255), /* Cyan */
83 LCD_RGBPACK(255,175,175), /* Pink */
84 LCD_RGBPACK(255,255, 0), /* Yellow */
85 LCD_RGBPACK( 0, 0,255), /* Blue */
86 LCD_RGBPACK(255, 0,255), /* Magenta */
89 #elif LCD_DEPTH > 1
91 #define MAZEZAM_HEADING_GRAY LCD_BLACK
92 #define MAZEZAM_BORDER_GRAY LCD_DARKGRAY
93 #define MAZEZAM_GRAY LCD_BLACK
94 #define MAZEZAM_BG_GRAY LCD_WHITE
95 #define MAZEZAM_WALL_GRAY LCD_DARKGRAY
96 #define MAZEZAM_PLAYER_GRAY LCD_BLACK
97 #define MAZEZAM_GATE_GRAY LCD_BLACK
98 #define MAZEZAM_CHUNK_EDGE_GRAY LCD_BLACK
100 #define MAZEZAM_NUM_CHUNK_GRAYS 2
101 static const unsigned chunk_gray[MAZEZAM_NUM_CHUNK_GRAYS] = {
102 LCD_LIGHTGRAY,
103 LCD_DARKGRAY,
105 /* darker version of the above */
106 static const unsigned chunk_gray_shade[MAZEZAM_NUM_CHUNK_GRAYS] = {
107 LCD_DARKGRAY,
108 LCD_BLACK,
110 #endif
112 #define MAZEZAM_DELAY_CHECKPOINT HZ
113 #define MAZEZAM_DELAY_LIVES HZ
114 #define MAZEZAM_DELAY_GAME_OVER (3 * HZ) / 2
116 /* maximum height of a level */
117 #define MAZEZAM_MAX_LINES 11
118 /* maximum number of chunks on a line */
119 #define MAZEZAM_MAX_CHUNKS 5
121 /* A structure for storing level data in unparsed form */
122 struct mazezam_level {
123 short height; /* the number of lines */
124 short width; /* the width */
125 short entrance; /* the line on which the entrance lies */
126 short exit; /* the line on which the exit lies */
127 char *line[MAZEZAM_MAX_LINES]; /* the chunk data in string form */
130 /* The number of levels. */
131 #define MAZEZAM_NUM_LEVELS 10
133 /* The levels. In theory, they could be stored in a file so this data
134 * structure should not be accessed outside parse_level()
136 * These levels are copyright (C) 2002 Malcolm Tyrrell. They're
137 * probably covered by the GPL as they constitute part of the source
138 * code of this plugin, but you may distibute them seperately with
139 * other Free Software if you want. You can download them from:
140 * http://webpages.dcu.ie/~tyrrelma/MazezaM.
142 static const struct mazezam_level level_data[MAZEZAM_NUM_LEVELS] = {
143 {2,7,0,0,{" $ $"," $ $$"}},
144 {3,8,2,1,{" $ $$$"," $ $ $"," $ $ $"}},
145 {4,14,1,3,{" $$$$$ $$ $$"," $$ $$ $$","$$ $ $$ $$$",
146 " $$$$$$$$ $"}},
147 {6,7,4,2,{" $"," $$$$"," $$$ $$"," $ $ $"," $ $$","$ $$"}},
148 {6,13,0,0,{" $$$$$","$ $$$$$ $$$"," $ $$$ $$$$",
149 "$ $ $$$$$$$"," $$$ $ $$","$ $ $ $$ $"}},
150 {11,5,10,0,{" $"," $ $$"," $$","$ $"," $ $"," $$$","$ $",
151 " $ $"," $ $","$ $$"," $"}},
152 {7,16,0,6,{" $$$$$$$"," $$$$ $$$$ $ $","$$ $$ $$$$$$ $ $",
153 "$ $ $"," $$$$$$$$$$$$$$"," $ $$ $ $$$",
154 " $ $$$ $$"}},
155 {4,15,2,0,{" $$$$ $$$$ $$"," $ $$ $$ $ $$"," $ $$ $$$$ $$",
156 " $ $$ $$$$ $"}},
157 {7,9,6,2,{" $ $$$$"," $ $ $$"," $ $$$$ $","$ $$ $"," $ $$$",
158 " $$$$$$"," $"}},
159 {10,14,8,0,{" $"," $$$$$$$$$$ $"," $$$ $$",
160 " $ $$$$$$$$ $"," $$$ $$$ $$$"," $$$ $ $$$",
161 " $ $$$$$$$ $$"," $ $ $ $$$"," $$$$$$$$$$$$",
162 ""}}
165 /* This data structure which holds information about the rows */
166 struct chunk_data {
167 /* the number of chunks on a line */
168 short l_num[MAZEZAM_MAX_LINES];
169 /* the width of a chunk */
170 short c_width[MAZEZAM_MAX_LINES][MAZEZAM_MAX_CHUNKS];
171 /* the inset of a chunk */
172 short c_inset[MAZEZAM_MAX_LINES][MAZEZAM_MAX_CHUNKS];
175 /* Parsed level data */
176 struct level_info {
177 short width;
178 short height;
179 short entrance;
180 short exit;
181 struct chunk_data cd;
184 /* The state variable used to hold the state of the plugin */
185 static enum {
186 STATE_QUIT, /* The player wants to quit */
187 STATE_USB_CONNECTED, /* A USB cable has been inserted */
188 STATE_PARSE_ERROR, /* There's a parse error in the levels */
189 STATE_WELLDONE, /* The player has finished the game */
191 STATE_IN_APPLICATION,
193 STATE_MAIN_MENU /* The player is at the main menu */
194 = STATE_IN_APPLICATION,
195 STATE_GAME_OVER, /* The player is out of lives */
197 STATE_IN_GAME,
199 STATE_COMPLETED /* A level has been completed */
200 = STATE_IN_GAME,
202 STATE_FAILED, /* The player wants to retry the level */
203 STATE_GAME_MENU, /* The player wan't to access the in-game menu */
205 STATE_IN_LEVEL,
206 } state;
208 /* The various constants needed for configuration files.
209 * See apps/plugins/lib/configfile.*
211 #define MAZEZAM_CONFIG_FILENAME "mazezam.data"
212 #define MAZEZAM_CONFIG_NUM_ITEMS 1
213 #define MAZEZAM_CONFIG_VERSION 0
214 #define MAZEZAM_CONFIG_MINVERSION 0
215 #define MAZEZAM_CONFIG_LEVELS_NAME "restart_level"
217 /* A structure containing the data that is written to
218 * the configuration file
220 struct resume_data {
221 int level; /* level at which to restart the game */
224 #if LCD_DEPTH > 1
225 /* Store the display settings so they are reintroduced during menus */
226 static struct {
227 fb_data* backdrop;
228 unsigned foreground;
229 unsigned background;
230 } lcd_settings;
231 #endif
233 /*****************************************************************************
234 * Store the LCD settings
235 ******************************************************************************/
236 static void store_lcd_settings(void)
238 /* Store the old settings */
239 #if LCD_DEPTH > 1
240 lcd_settings.backdrop = rb->lcd_get_backdrop();
241 lcd_settings.foreground = rb->lcd_get_foreground();
242 lcd_settings.background = rb->lcd_get_background();
243 #endif
246 /*****************************************************************************
247 * Restore the LCD settings to their defaults
248 ******************************************************************************/
249 static void restore_lcd_settings(void) {
250 /* Turn on backlight timeout (revert to settings) */
251 backlight_use_settings(); /* backlight control in lib/helper.c */
253 /* Restore the old settings */
254 #if LCD_DEPTH > 1
255 rb->lcd_set_foreground(lcd_settings.foreground);
256 rb->lcd_set_background(lcd_settings.background);
257 rb->lcd_set_backdrop(lcd_settings.backdrop);
258 #endif
261 /*****************************************************************************
262 * Adjust the LCD settings to suit MazezaM levels
263 ******************************************************************************/
264 static void plugin_lcd_settings(void) {
265 /* Turn off backlight timeout */
266 backlight_force_on(); /* backlight control in lib/helper.c */
268 /* Set the new settings */
269 #ifdef HAVE_LCD_COLOR
270 rb->lcd_set_background(MAZEZAM_BG_COLOR);
271 rb->lcd_set_backdrop(NULL);
272 #elif LCD_DEPTH > 1
273 rb->lcd_set_background(MAZEZAM_BG_GRAY);
274 rb->lcd_set_backdrop(NULL);
275 #endif
278 /*****************************************************************************
279 * Parse the level data from the level_data structure. This could be
280 * replaced by a file read. Returns true if the level parsed correctly.
281 ******************************************************************************/
282 static bool parse_level(short level, struct level_info* li)
284 int i,j;
285 char c,clast;
287 li->width = level_data[level].width;
288 li->height = level_data[level].height;
289 li->entrance = level_data[level].entrance;
290 li->exit = level_data[level].exit;
292 /* for each line in the level */
293 for (i = 0; i<level_data[level].height; i++) {
294 if (level_data[level].line[i] == NULL)
295 return false;
296 else {
297 j = 0;
298 li->cd.l_num[i] = 0;
299 clast = ' '; /* the character we last considered */
300 while ((c = level_data[level].line[i][j]) != '\0') {
301 if (c != ' ') {
302 if (clast == ' ') {
303 li->cd.l_num[i] += 1;
304 if (li->cd.l_num[i] > MAZEZAM_MAX_CHUNKS)
305 return false;
306 li->cd.c_inset[i][li->cd.l_num[i] - 1] = j;
307 li->cd.c_width[i][li->cd.l_num[i] - 1] = 1;
309 else
310 li->cd.c_width[i][li->cd.l_num[i] - 1] += 1;
312 clast = c;
313 j++;
317 return true;
320 /*****************************************************************************
321 * Draw the walls of a level
322 ******************************************************************************/
323 static void draw_walls(
324 short size,
325 short xOff,
326 short yOff,
327 short width,
328 short height,
329 short entrance,
330 short exit)
332 #ifdef HAVE_LCD_COLOR
333 rb->lcd_set_foreground(MAZEZAM_WALL_COLOR);
334 #elif LCD_DEPTH > 1
335 rb->lcd_set_foreground(MAZEZAM_WALL_GRAY);
336 #endif
337 /* draw the upper wall */
338 rb->lcd_fillrect(0,0,xOff,yOff+(size*entrance));
339 rb->lcd_fillrect(xOff,0,size*width,yOff);
340 rb->lcd_fillrect(xOff+(size*width),0,LCD_WIDTH-xOff-(size*width),
341 yOff+(size*exit));
343 /* draw the lower wall */
344 rb->lcd_fillrect(0,yOff+(size*entrance)+size,xOff,
345 LCD_HEIGHT-yOff-(size*entrance)-size);
346 rb->lcd_fillrect(xOff,yOff+(size*height),size*width,
347 LCD_HEIGHT-yOff-(size*height));
348 /* Note: the exit is made one pixel thinner than necessary as a visual
349 * clue that chunks cannot be pushed into it
351 rb->lcd_fillrect(xOff+(size*width),yOff+(size*exit)+size-1,
352 LCD_WIDTH-xOff+(size*width),
353 LCD_HEIGHT-yOff-(size*exit)-size+1);
356 /*****************************************************************************
357 * Draw chunk row i
358 ******************************************************************************/
359 static void draw_row(
360 short size,
361 short xOff,
362 short yOff,
363 short width,
364 short i, /* the row number */
365 struct chunk_data *cd, /* the data about the chunks */
366 short *shift /* an array of the horizontal offset of the lines */
369 /* The assignment below is just a hack to make supress a warning on
370 * non color targets */
371 short j = width;
372 #ifndef HAVE_LCD_COLOR
373 /* We #def these out to supress a compiler warning */
374 short k;
375 #if LCD_DEPTH <= 1
376 short l;
377 #endif
378 #endif
379 #ifdef HAVE_LCD_COLOR
380 /* adding width to i should have a fixed, but randomising effect on
381 * the choice of the colours of the top line of chunks
383 rb->lcd_set_foreground(chunk_colors[(i+width) %
384 MAZEZAM_NUM_CHUNK_COLORS]);
385 #endif
386 for (j = 0; j<cd->l_num[i]; j++) {
387 #ifdef HAVE_LCD_COLOR
388 rb->lcd_fillrect(xOff+size*shift[i]+size*cd->c_inset[i][j],
389 yOff+size*i, cd->c_width[i][j]*size,size);
390 #elif LCD_DEPTH > 1
391 rb->lcd_set_foreground(MAZEZAM_CHUNK_EDGE_GRAY);
392 rb->lcd_drawrect(xOff+size*shift[i]+size*cd->c_inset[i][j],
393 yOff+size*i, cd->c_width[i][j]*size,size);
395 /* draw shade */
396 rb->lcd_set_foreground(chunk_gray_shade[(i+width) %
397 MAZEZAM_NUM_CHUNK_GRAYS]);
398 rb->lcd_hline(xOff+size*shift[i]+size*cd->c_inset[i][j]+1,
399 xOff+size*shift[i]+size*cd->c_inset[i][j]+
400 cd->c_width[i][j]*size-3,
401 yOff+size*i+size-2);
402 rb->lcd_vline(xOff+size*shift[i]+size*cd->c_inset[i][j]+
403 cd->c_width[i][j]*size-2,
404 yOff+size*i,
405 yOff+size*i+size-2);
407 /* draw fill */
408 rb->lcd_set_foreground(chunk_gray[(i+width) %
409 MAZEZAM_NUM_CHUNK_GRAYS]);
410 for (k = yOff+size*i+2; k < yOff+size*i+size-2; k += 2)
411 rb->lcd_hline(xOff+size*shift[i]+size*cd->c_inset[i][j]+2,
412 xOff+size*shift[i]+size*cd->c_inset[i][j]+
413 cd->c_width[i][j]*size-3,k);
414 #else
415 rb->lcd_drawrect(xOff+size*shift[i]+size*cd->c_inset[i][j],
416 yOff+size*i, cd->c_width[i][j]*size,size);
417 for (k = xOff+size*shift[i]+size*cd->c_inset[i][j]+2;
418 k < xOff+size*shift[i]+size*cd->c_inset[i][j]+
419 cd->c_width[i][j]*size;
420 k += 2 + (i & 1))
421 for (l = yOff+size*i+2; l < yOff+size*i+size; l += 2 + (i & 1))
422 rb->lcd_drawpixel(k, l);
423 #endif
427 /*****************************************************************************
428 * Draw the player
429 ******************************************************************************/
430 static void draw_player(
431 short size,
432 short xOff,
433 short yOff,
434 short x,
435 short y)
437 /* For drawing the player, taken from the sokoban plugin */
438 short max = size - 1;
439 short middle = max / 2;
440 short ldelta = (middle + 1) / 2;
442 /* draw the player (mostly copied from the sokoban plugin) */
443 #ifdef HAVE_LCD_COLOR
444 rb->lcd_set_foreground(MAZEZAM_PLAYER_COLOR);
445 #elif LCD_DEPTH > 1
446 rb->lcd_set_foreground(MAZEZAM_PLAYER_GRAY);
447 #endif
448 rb->lcd_hline(xOff+size*x, xOff+size*x+max, yOff+size*y+middle);
449 rb->lcd_vline(xOff+size*x+middle, yOff+size*y, yOff+size*y+max-ldelta);
450 rb->lcd_drawline(xOff+size*x+middle, yOff+size*y+max-ldelta,
451 xOff+size*x+middle-ldelta, yOff+size*y+max);
452 rb->lcd_drawline(xOff+size*x+middle, yOff+size*y+max-ldelta,
453 xOff+size*x+middle+ldelta, yOff+size*y+max);
456 /*****************************************************************************
457 * Draw the gate
458 ******************************************************************************/
459 static void draw_gate(
460 short size,
461 short xOff,
462 short yOff,
463 short entrance)
465 short third = size / 3;
466 short twothirds = (2 * size) / 3;
467 #ifdef HAVE_LCD_COLOR
468 rb->lcd_set_foreground(MAZEZAM_GATE_COLOR);
469 #elif LCD_DEPTH > 1
470 rb->lcd_set_foreground(MAZEZAM_GATE_GRAY);
471 #endif
472 rb->lcd_hline(xOff-size,xOff-1,yOff+entrance*size+third);
473 rb->lcd_hline(xOff-size,xOff-1,yOff+entrance*size+twothirds);
474 rb->lcd_vline(xOff-size+third,yOff+entrance*size,
475 yOff+entrance*size+size-1);
476 rb->lcd_vline(xOff-size+twothirds,yOff+entrance*size,
477 yOff+entrance*size+size-1);
480 /*****************************************************************************
481 * Draw the level
482 ******************************************************************************/
483 static void draw_level(
484 struct level_info* li,
485 short *shift, /* an array of the horizontal offset of the lines */
486 short x, /* player's x and y coords */
487 short y)
489 /* First we calculate the draw info */
490 /* The number of pixels the side of a square should be */
491 short size = (LCD_WIDTH/(li->width+2)) < (LCD_HEIGHT/li->height) ?
492 (LCD_WIDTH/(li->width+2)) : (LCD_HEIGHT/li->height);
493 /* The x and y position (in pixels) of the top left corner of the
494 * level
496 short xOff = (LCD_WIDTH - (size*li->width))/2;
497 short yOff = (LCD_HEIGHT - (size*li->height))/2;
498 short i;
500 rb->lcd_clear_display();
502 draw_walls(size,xOff,yOff,li->width, li->height, li->entrance, li->exit);
504 /* draw the chunks */
505 for (i = 0; i<li->height; i++) {
506 draw_row(size,xOff,yOff,li->width,i,&(li->cd),shift);
509 draw_player(size,xOff,yOff,x,y);
511 /* if the player has moved into the level, draw the gate */
512 if (x >= 0)
513 draw_gate(size,xOff,yOff,li->entrance);
516 /*****************************************************************************
517 * Manage the congratulations screen
518 ******************************************************************************/
519 static void welldone_screen(void)
521 int start_selection = 0;
523 MENUITEM_STRINGLIST(menu,MAZEZAM_TEXT_WELLDONE_TITLE,NULL,
524 MAZEZAM_TEXT_WELLDONE_OPTION);
526 switch(rb->do_menu(&menu, &start_selection, NULL, true)){
527 case MENU_ATTACHED_USB:
528 state = STATE_USB_CONNECTED;
529 break;
533 /*****************************************************************************
534 * Manage the playing of a level
535 ******************************************************************************/
536 static void level_loop(struct level_info* li, short* shift, short *x, short *y)
538 int i;
539 int button;
540 bool blocked; /* is there a chunk in the way of the player? */
542 while (state >= STATE_IN_LEVEL) {
543 draw_level(li, shift, *x, *y);
544 rb->lcd_update();
545 button = pluginlib_getaction(TIMEOUT_BLOCK, plugin_contexts, 2);
546 blocked = false;
548 switch (button) {
549 case MAZEZAM_UP:
550 case MAZEZAM_UP_REPEAT:
551 if ((*y > 0) && (*x >= 0) && (*x < li->width)) {
552 for (i = 0; i < li->cd.l_num[*y-1]; i++)
553 blocked = blocked ||
554 ((*x>=shift[*y-1]+li->cd.c_inset[*y-1][i]) &&
555 (*x<shift[*y-1]+li->cd.c_inset[*y-1][i]+
556 li->cd.c_width[*y-1][i]));
557 if (!blocked) *y -= 1;
559 break;
563 case MAZEZAM_DOWN:
564 case MAZEZAM_DOWN_REPEAT:
565 if ((*y < li->height-1) && (*x >= 0) && (*x < li->width)) {
566 for (i = 0; i < li->cd.l_num[*y+1]; i++)
567 blocked = blocked ||
568 ((*x>=shift[*y+1]+li->cd.c_inset[*y+1][i]) &&
569 (*x<shift[*y+1]+li->cd.c_inset[*y+1][i]+
570 li->cd.c_width[*y+1][i]));
571 if (!blocked) *y += 1;
573 break;
575 case MAZEZAM_LEFT:
576 case MAZEZAM_LEFT_REPEAT:
577 if (*x > 0) {
578 for (i = 0; i < li->cd.l_num[*y]; i++)
579 blocked = blocked ||
580 (*x == shift[*y]+li->cd.c_inset[*y][i]+
581 li->cd.c_width[*y][i]);
582 if (!blocked) *x -= 1;
583 else if (shift[*y] + li->cd.c_inset[*y][0] > 0) {
584 *x -= 1;
585 shift[*y] -= 1;
588 break;
590 case MAZEZAM_RIGHT:
591 case MAZEZAM_RIGHT_REPEAT:
592 if (*x < li->width-1) {
593 for (i = 0; i < li->cd.l_num[*y]; i++)
594 blocked = blocked ||
595 (*x+1 == shift[*y]+li->cd.c_inset[*y][i]);
596 if (!blocked) *x += 1;
597 else if (shift[*y]
598 + li->cd.c_inset[*y][li->cd.l_num[*y]-1]
599 + li->cd.c_width[*y][li->cd.l_num[*y]-1]
600 < li->width) {
601 *x += 1;
602 shift[*y] += 1;
605 else if (*x == li->width) state = STATE_COMPLETED;
606 else if (*y == li->exit) *x += 1;
607 break;
609 case MAZEZAM_MENU:
610 state = STATE_GAME_MENU;
611 break;
613 default:
614 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
615 state = STATE_USB_CONNECTED;
616 break;
621 /*****************************************************************************
622 * Manage the in game menu
623 ******************************************************************************/
624 static void in_game_menu(void)
626 /* The initial option is retry level */
627 int start_selection = 1;
629 MENUITEM_STRINGLIST(menu,MAZEZAM_TEXT_MAZEZAM_MENU, NULL,
630 MAZEZAM_TEXT_BACK,
631 MAZEZAM_TEXT_RETRY_LEVEL,
632 MAZEZAM_TEXT_AUDIO_PLAYBACK,
633 MAZEZAM_TEXT_QUIT);
635 /* Don't show the status bar */
636 switch(rb->do_menu(&menu, &start_selection, NULL, false)){
637 case 1: /* retry */
638 state = STATE_FAILED;
639 break;
641 case 2: /* Audio playback */
642 playback_control(NULL);
643 state = STATE_IN_LEVEL;
644 break;
646 case 3: /* quit */
647 state = STATE_QUIT;
648 break;
650 case MENU_ATTACHED_USB:
651 state = STATE_USB_CONNECTED;
652 break;
654 default: /* Back */
655 state = STATE_IN_LEVEL;
656 break;
660 /*****************************************************************************
661 * Is the level a checkpoint
662 ******************************************************************************/
663 static bool at_checkpoint(int level)
665 if (level <= MAZEZAM_FIRST_CHECKPOINT)
666 return level == MAZEZAM_FIRST_CHECKPOINT;
667 else {
668 level = level - MAZEZAM_FIRST_CHECKPOINT;
669 return level % MAZEZAM_CHECKPOINT_INTERVAL == 0;
673 /*****************************************************************************
674 * Set up and play a level
675 * new_level should be true if this is the first time we've encountered
676 * this level
677 ******************************************************************************/
678 static void play_level(short level, short lives, bool new_level)
680 struct level_info li;
681 short shift[MAZEZAM_MAX_LINES]; /* amount each line has been shifted */
682 short x,y;
683 int i;
685 state = STATE_IN_LEVEL;
687 if (!(parse_level(level,&li)))
688 state = STATE_PARSE_ERROR;
690 for (i = 0; i < li.height; i++)
691 shift[i] = 0;
693 x = -1;
694 y = li.entrance;
696 plugin_lcd_settings();
697 rb->lcd_clear_display();
699 draw_level(&li, shift, x, y);
701 /* If we've just reached a checkpoint, then alert the player */
702 if (new_level && at_checkpoint(level)) {
703 rb->splash(MAZEZAM_DELAY_CHECKPOINT, MAZEZAM_TEXT_CHECKPOINT);
704 /* Clear the splash */
705 draw_level(&li, shift, x, y);
708 #ifdef HAVE_REMOTE_LCD
709 /* Splash text seems to use the remote display by
710 * default. I suppose I better keep it tidy!
712 rb->lcd_remote_clear_display();
713 #endif
714 rb->splashf(MAZEZAM_DELAY_LIVES, MAZEZAM_TEXT_LIVES,
715 level+1, lives);
717 /* ensure keys pressed during the splash screen are ignored */
718 rb->button_clear_queue();
720 /* this little loop just ensures we return to the game if the player
721 * doesn't perform an interesting action during the in game menu */
722 while (state >= STATE_IN_LEVEL) {
723 level_loop(&li, shift, &x, &y);
725 if (state == STATE_GAME_MENU) {
726 restore_lcd_settings();
727 in_game_menu();
728 plugin_lcd_settings();
731 restore_lcd_settings();
734 /*****************************************************************************
735 * Update the resume data based on the level reached
736 ******************************************************************************/
737 static void update_resume_data(struct resume_data *r, int level)
739 if (at_checkpoint(level))
740 r->level = level;
743 /*****************************************************************************
744 * The loop which manages a full game of MazezaM.
745 ******************************************************************************/
746 static void game_loop(struct resume_data *r)
748 int level = r->level;
749 int lives = MAZEZAM_START_LIVES;
750 /* We want to know when a player reaches a level for the first time,
751 * so we keep a second copy of the level. */
752 int old_level = level;
754 state = STATE_IN_GAME;
756 while (state >= STATE_IN_GAME)
758 play_level(level, lives, old_level < level);
759 old_level = level;
761 switch (state) {
762 case STATE_COMPLETED:
763 level += 1;
764 if (level == MAZEZAM_NUM_LEVELS)
765 state = STATE_WELLDONE;
766 break;
768 case STATE_FAILED:
769 lives -= 1;
770 if (lives == 0)
771 state = STATE_GAME_OVER;
772 break;
774 default:
775 break;
778 update_resume_data(r,level);
781 switch (state) {
782 case STATE_GAME_OVER:
783 #ifdef HAVE_REMOTE_LCD
784 /* Splash text seems to use the remote display by
785 * default. I suppose I better keep it tidy!
787 rb->lcd_remote_clear_display();
788 #endif
789 rb->splash(MAZEZAM_DELAY_GAME_OVER, MAZEZAM_TEXT_GAME_OVER);
790 break;
792 case STATE_WELLDONE:
793 welldone_screen();
794 break;
796 default:
797 break;
801 /*****************************************************************************
802 * Load the resume data from the config file. The data is
803 * stored in both r and old.
804 ******************************************************************************/
805 static void resume_load_data (struct resume_data *r, struct resume_data *old)
807 struct configdata config[] = {
808 {TYPE_INT,0,MAZEZAM_NUM_LEVELS-1, { .int_p = &(r->level) },
809 MAZEZAM_CONFIG_LEVELS_NAME,NULL}
812 if (configfile_load(MAZEZAM_CONFIG_FILENAME,config,
813 MAZEZAM_CONFIG_NUM_ITEMS, MAZEZAM_CONFIG_VERSION) < 0)
814 r->level = 0;
815 /* an extra precaution */
816 else if ((r->level < 0) || (MAZEZAM_NUM_LEVELS <= r->level))
817 r->level = 0;
819 old->level = r->level;
822 /*****************************************************************************
823 * Save the resume data in the config file, but only if necessary
824 ******************************************************************************/
825 static void resume_save_data (struct resume_data *r, struct resume_data *old)
827 struct configdata config[] = {
828 {TYPE_INT,0,MAZEZAM_NUM_LEVELS-1, {.int_p = &(r->level) },
829 MAZEZAM_CONFIG_LEVELS_NAME,NULL}
832 /* To reduce disk usage, only write the file if the resume data has
833 * changed.
835 if (old->level != r->level)
836 configfile_save(MAZEZAM_CONFIG_FILENAME,config,
837 MAZEZAM_CONFIG_NUM_ITEMS, MAZEZAM_CONFIG_MINVERSION);
840 /*****************************************************************************
841 * Offer a main menu with no continue option
842 ******************************************************************************/
843 static int main_menu_without_continue(int* start_selection)
845 MENUITEM_STRINGLIST(menu,MAZEZAM_TEXT_MAIN_MENU,NULL,
846 MAZEZAM_TEXT_PLAY_GAME,
847 MAZEZAM_TEXT_QUIT);
848 return rb->do_menu(&menu, start_selection, NULL, false);
851 /*****************************************************************************
852 * Offer a main menu with a continue option
853 ******************************************************************************/
854 static int main_menu_with_continue(int* start_selection)
856 MENUITEM_STRINGLIST(menu,MAZEZAM_TEXT_MAIN_MENU,NULL,
857 MAZEZAM_TEXT_CONTINUE,
858 MAZEZAM_TEXT_PLAY_NEW_GAME,
859 MAZEZAM_TEXT_QUIT);
860 return rb->do_menu(&menu, start_selection, NULL, false);
863 /*****************************************************************************
864 * Manages the main menu
865 ******************************************************************************/
866 static void main_menu(void)
868 /* The initial option is "play game" */
869 int start_selection = 0;
870 int choice = 0;
871 struct resume_data r_data, old_data;
873 /* Load data */
874 resume_load_data(&r_data, &old_data);
876 while (state >= STATE_IN_APPLICATION) {
877 if (r_data.level == 0)
878 choice = main_menu_without_continue(&start_selection);
879 else
880 choice = main_menu_with_continue(&start_selection);
882 switch(choice) {
883 case 0: /* Continue */
884 state = STATE_IN_GAME;
885 game_loop(&r_data);
886 break;
888 case 1: /* Quit or Play new game */
889 if (r_data.level == 0)
890 state = STATE_QUIT;
891 else { /* Play new game */
892 r_data.level = 0;
893 state = STATE_IN_GAME;
894 game_loop(&r_data);
896 break;
898 case MENU_ATTACHED_USB:
899 state = STATE_USB_CONNECTED;
900 break;
902 default: /* Quit */
903 state = STATE_QUIT;
904 break;
908 /* I'm not sure if it's appropriate to write to disk on USB events.
909 * Currently, I do so.
911 resume_save_data(&r_data, &old_data);
914 /*****************************************************************************
915 * Plugin entry point
916 ******************************************************************************/
917 enum plugin_status plugin_start(const void* parameter)
919 enum plugin_status plugin_state;
921 /* Usual plugin stuff */
922 (void)parameter;
924 store_lcd_settings();
926 state = STATE_MAIN_MENU;
927 main_menu();
929 switch (state) {
930 case STATE_USB_CONNECTED:
931 plugin_state = PLUGIN_USB_CONNECTED;
932 break;
934 case STATE_PARSE_ERROR:
935 plugin_state = PLUGIN_ERROR;
936 break;
938 default:
939 plugin_state = PLUGIN_OK;
940 break;
943 return plugin_state;