Reverting parts of r19760 that was mistakenly committed.
[kugel-rb.git] / apps / plugins / mazezam.c
blobfac12def4fc6db5e6a7f69d49b05a3079c8fd12a
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 static const struct plugin_api* rb;
33 /* The plugin actions of interest. */
34 const struct button_mapping *plugin_contexts[]
35 = {generic_directions, generic_actions};
37 MEM_FUNCTION_WRAPPERS(rb);
39 /* Use the standard plugin buttons rather than a hard-to-maintain list of
40 * MazezaM specific buttons. */
41 #define MAZEZAM_UP PLA_UP
42 #define MAZEZAM_UP_REPEAT PLA_UP_REPEAT
43 #define MAZEZAM_DOWN PLA_DOWN
44 #define MAZEZAM_DOWN_REPEAT PLA_DOWN_REPEAT
45 #define MAZEZAM_LEFT PLA_LEFT
46 #define MAZEZAM_LEFT_REPEAT PLA_LEFT_REPEAT
47 #define MAZEZAM_RIGHT PLA_RIGHT
48 #define MAZEZAM_RIGHT_REPEAT PLA_RIGHT_REPEAT
49 #define MAZEZAM_MENU PLA_QUIT
51 /* All the text is here */
52 #define MAZEZAM_TEXT_GAME_OVER "Game Over"
53 #define MAZEZAM_TEXT_LIVES "Level %d, Lives %d"
54 #define MAZEZAM_TEXT_CHECKPOINT "Checkpoint reached"
55 #define MAZEZAM_TEXT_WELLDONE_TITLE "You have escaped!"
56 #define MAZEZAM_TEXT_WELLDONE_OPTION "Goodbye"
57 #define MAZEZAM_TEXT_MAZEZAM_MENU "MazezaM Menu"
58 #define MAZEZAM_TEXT_RETRY_LEVEL "Retry level"
59 #define MAZEZAM_TEXT_AUDIO_PLAYBACK "Audio playback"
60 #define MAZEZAM_TEXT_QUIT "Quit"
61 #define MAZEZAM_TEXT_BACK "Return"
62 #define MAZEZAM_TEXT_MAIN_MENU "MazezaM"
63 #define MAZEZAM_TEXT_CONTINUE "Play from checkpoint"
64 #define MAZEZAM_TEXT_PLAY_GAME "Play game"
65 #define MAZEZAM_TEXT_PLAY_NEW_GAME "Play new game"
67 #define MAZEZAM_START_LIVES 3 /* how many lives at game start */
68 #define MAZEZAM_FIRST_CHECKPOINT 3 /* The level at the first checkpoint */
69 #define MAZEZAM_CHECKPOINT_INTERVAL 4 /* A checkpoint every _ levels */
71 #ifdef HAVE_LCD_COLOR
72 #define MAZEZAM_HEADING_COLOR LCD_RGBPACK(255,255, 0) /* Yellow */
73 #define MAZEZAM_BORDER_COLOR LCD_RGBPACK( 0, 0,255) /* Blue */
74 #define MAZEZAM_COLOR LCD_RGBPACK(255,255,255) /* White */
75 #define MAZEZAM_BG_COLOR LCD_RGBPACK( 0, 0, 0) /* Black */
76 #define MAZEZAM_WALL_COLOR LCD_RGBPACK(100,100,100) /* Dark gray */
77 #define MAZEZAM_PLAYER_COLOR LCD_RGBPACK(255,255,255) /* White */
78 #define MAZEZAM_GATE_COLOR LCD_RGBPACK(100,100,100) /* Dark gray */
80 /* the rows are coloured sequentially */
81 #define MAZEZAM_NUM_CHUNK_COLORS 8
82 static const unsigned chunk_colors[MAZEZAM_NUM_CHUNK_COLORS] = {
83 LCD_RGBPACK(255,192, 32), /* Orange */
84 LCD_RGBPACK(255, 0, 0), /* Red */
85 LCD_RGBPACK( 0,255, 0), /* Green */
86 LCD_RGBPACK( 0,255,255), /* Cyan */
87 LCD_RGBPACK(255,175,175), /* Pink */
88 LCD_RGBPACK(255,255, 0), /* Yellow */
89 LCD_RGBPACK( 0, 0,255), /* Blue */
90 LCD_RGBPACK(255, 0,255), /* Magenta */
93 #elif LCD_DEPTH > 1
95 #define MAZEZAM_HEADING_GRAY LCD_BLACK
96 #define MAZEZAM_BORDER_GRAY LCD_DARKGRAY
97 #define MAZEZAM_GRAY LCD_BLACK
98 #define MAZEZAM_BG_GRAY LCD_WHITE
99 #define MAZEZAM_WALL_GRAY LCD_DARKGRAY
100 #define MAZEZAM_PLAYER_GRAY LCD_BLACK
101 #define MAZEZAM_GATE_GRAY LCD_BLACK
102 #define MAZEZAM_CHUNK_EDGE_GRAY LCD_BLACK
104 #define MAZEZAM_NUM_CHUNK_GRAYS 2
105 static const unsigned chunk_gray[MAZEZAM_NUM_CHUNK_GRAYS] = {
106 LCD_LIGHTGRAY,
107 LCD_DARKGRAY,
109 /* darker version of the above */
110 static const unsigned chunk_gray_shade[MAZEZAM_NUM_CHUNK_GRAYS] = {
111 LCD_DARKGRAY,
112 LCD_BLACK,
114 #endif
116 #define MAZEZAM_DELAY_CHECKPOINT HZ
117 #define MAZEZAM_DELAY_LIVES HZ
118 #define MAZEZAM_DELAY_GAME_OVER (3 * HZ) / 2
120 /* maximum height of a level */
121 #define MAZEZAM_MAX_LINES 11
122 /* maximum number of chunks on a line */
123 #define MAZEZAM_MAX_CHUNKS 5
125 /* A structure for storing level data in unparsed form */
126 struct mazezam_level {
127 short height; /* the number of lines */
128 short width; /* the width */
129 short entrance; /* the line on which the entrance lies */
130 short exit; /* the line on which the exit lies */
131 char *line[MAZEZAM_MAX_LINES]; /* the chunk data in string form */
134 /* The number of levels. */
135 #define MAZEZAM_NUM_LEVELS 10
137 /* The levels. In theory, they could be stored in a file so this data
138 * structure should not be accessed outside parse_level()
140 * These levels are copyright (C) 2002 Malcolm Tyrrell. They're
141 * probably covered by the GPL as they constitute part of the source
142 * code of this plugin, but you may distibute them seperately with
143 * other Free Software if you want. You can download them from:
144 * http://webpages.dcu.ie/~tyrrelma/MazezaM.
146 static const struct mazezam_level level_data[MAZEZAM_NUM_LEVELS] = {
147 {2,7,0,0,{" $ $"," $ $$"}},
148 {3,8,2,1,{" $ $$$"," $ $ $"," $ $ $"}},
149 {4,14,1,3,{" $$$$$ $$ $$"," $$ $$ $$","$$ $ $$ $$$",
150 " $$$$$$$$ $"}},
151 {6,7,4,2,{" $"," $$$$"," $$$ $$"," $ $ $"," $ $$","$ $$"}},
152 {6,13,0,0,{" $$$$$","$ $$$$$ $$$"," $ $$$ $$$$",
153 "$ $ $$$$$$$"," $$$ $ $$","$ $ $ $$ $"}},
154 {11,5,10,0,{" $"," $ $$"," $$","$ $"," $ $"," $$$","$ $",
155 " $ $"," $ $","$ $$"," $"}},
156 {7,16,0,6,{" $$$$$$$"," $$$$ $$$$ $ $","$$ $$ $$$$$$ $ $",
157 "$ $ $"," $$$$$$$$$$$$$$"," $ $$ $ $$$",
158 " $ $$$ $$"}},
159 {4,15,2,0,{" $$$$ $$$$ $$"," $ $$ $$ $ $$"," $ $$ $$$$ $$",
160 " $ $$ $$$$ $"}},
161 {7,9,6,2,{" $ $$$$"," $ $ $$"," $ $$$$ $","$ $$ $"," $ $$$",
162 " $$$$$$"," $"}},
163 {10,14,8,0,{" $"," $$$$$$$$$$ $"," $$$ $$",
164 " $ $$$$$$$$ $"," $$$ $$$ $$$"," $$$ $ $$$",
165 " $ $$$$$$$ $$"," $ $ $ $$$"," $$$$$$$$$$$$",
166 ""}}
169 /* This data structure which holds information about the rows */
170 struct chunk_data {
171 /* the number of chunks on a line */
172 short l_num[MAZEZAM_MAX_LINES];
173 /* the width of a chunk */
174 short c_width[MAZEZAM_MAX_LINES][MAZEZAM_MAX_CHUNKS];
175 /* the inset of a chunk */
176 short c_inset[MAZEZAM_MAX_LINES][MAZEZAM_MAX_CHUNKS];
179 /* Parsed level data */
180 struct level_info {
181 short width;
182 short height;
183 short entrance;
184 short exit;
185 struct chunk_data cd;
188 /* The state variable used to hold the state of the plugin */
189 static enum {
190 STATE_QUIT, /* The player wants to quit */
191 STATE_USB_CONNECTED, /* A USB cable has been inserted */
192 STATE_PARSE_ERROR, /* There's a parse error in the levels */
193 STATE_WELLDONE, /* The player has finished the game */
195 STATE_IN_APPLICATION,
197 STATE_MAIN_MENU /* The player is at the main menu */
198 = STATE_IN_APPLICATION,
199 STATE_GAME_OVER, /* The player is out of lives */
201 STATE_IN_GAME,
203 STATE_COMPLETED /* A level has been completed */
204 = STATE_IN_GAME,
206 STATE_FAILED, /* The player wants to retry the level */
207 STATE_GAME_MENU, /* The player wan't to access the in-game menu */
209 STATE_IN_LEVEL,
210 } state;
212 /* The various constants needed for configuration files.
213 * See apps/plugins/lib/configfile.*
215 #define MAZEZAM_CONFIG_FILENAME "mazezam.data"
216 #define MAZEZAM_CONFIG_NUM_ITEMS 1
217 #define MAZEZAM_CONFIG_VERSION 0
218 #define MAZEZAM_CONFIG_MINVERSION 0
219 #define MAZEZAM_CONFIG_LEVELS_NAME "restart_level"
221 /* A structure containing the data that is written to
222 * the configuration file
224 struct resume_data {
225 int level; /* level at which to restart the game */
228 #if LCD_DEPTH > 1
229 /* Store the display settings so they are reintroduced during menus */
230 static struct {
231 fb_data* backdrop;
232 unsigned foreground;
233 unsigned background;
234 } lcd_settings;
235 #endif
237 /*****************************************************************************
238 * Store the LCD settings
239 ******************************************************************************/
240 static void store_lcd_settings(void)
242 /* Store the old settings */
243 #if LCD_DEPTH > 1
244 lcd_settings.backdrop = rb->lcd_get_backdrop();
245 lcd_settings.foreground = rb->lcd_get_foreground();
246 lcd_settings.background = rb->lcd_get_background();
247 #endif
250 /*****************************************************************************
251 * Restore the LCD settings to their defaults
252 ******************************************************************************/
253 static void restore_lcd_settings(void) {
254 /* Turn on backlight timeout (revert to settings) */
255 backlight_use_settings(rb); /* backlight control in lib/helper.c */
257 /* Restore the old settings */
258 #if LCD_DEPTH > 1
259 rb->lcd_set_foreground(lcd_settings.foreground);
260 rb->lcd_set_background(lcd_settings.background);
261 rb->lcd_set_backdrop(lcd_settings.backdrop);
262 #endif
265 /*****************************************************************************
266 * Adjust the LCD settings to suit MazezaM levels
267 ******************************************************************************/
268 static void plugin_lcd_settings(void) {
269 /* Turn off backlight timeout */
270 backlight_force_on(rb); /* backlight control in lib/helper.c */
272 /* Set the new settings */
273 #ifdef HAVE_LCD_COLOR
274 rb->lcd_set_background(MAZEZAM_BG_COLOR);
275 rb->lcd_set_backdrop(NULL);
276 #elif LCD_DEPTH > 1
277 rb->lcd_set_background(MAZEZAM_BG_GRAY);
278 rb->lcd_set_backdrop(NULL);
279 #endif
282 /*****************************************************************************
283 * Parse the level data from the level_data structure. This could be
284 * replaced by a file read. Returns true if the level parsed correctly.
285 ******************************************************************************/
286 static bool parse_level(short level, struct level_info* li)
288 int i,j;
289 char c,clast;
291 li->width = level_data[level].width;
292 li->height = level_data[level].height;
293 li->entrance = level_data[level].entrance;
294 li->exit = level_data[level].exit;
296 /* for each line in the level */
297 for (i = 0; i<level_data[level].height; i++) {
298 if (level_data[level].line[i] == NULL)
299 return false;
300 else {
301 j = 0;
302 li->cd.l_num[i] = 0;
303 clast = ' '; /* the character we last considered */
304 while ((c = level_data[level].line[i][j]) != '\0') {
305 if (c != ' ') {
306 if (clast == ' ') {
307 li->cd.l_num[i] += 1;
308 if (li->cd.l_num[i] > MAZEZAM_MAX_CHUNKS)
309 return false;
310 li->cd.c_inset[i][li->cd.l_num[i] - 1] = j;
311 li->cd.c_width[i][li->cd.l_num[i] - 1] = 1;
313 else
314 li->cd.c_width[i][li->cd.l_num[i] - 1] += 1;
316 clast = c;
317 j++;
321 return true;
324 /*****************************************************************************
325 * Draw the walls of a level
326 ******************************************************************************/
327 static void draw_walls(
328 short size,
329 short xOff,
330 short yOff,
331 short width,
332 short height,
333 short entrance,
334 short exit)
336 #ifdef HAVE_LCD_COLOR
337 rb->lcd_set_foreground(MAZEZAM_WALL_COLOR);
338 #elif LCD_DEPTH > 1
339 rb->lcd_set_foreground(MAZEZAM_WALL_GRAY);
340 #endif
341 /* draw the upper wall */
342 rb->lcd_fillrect(0,0,xOff,yOff+(size*entrance));
343 rb->lcd_fillrect(xOff,0,size*width,yOff);
344 rb->lcd_fillrect(xOff+(size*width),0,LCD_WIDTH-xOff-(size*width),
345 yOff+(size*exit));
347 /* draw the lower wall */
348 rb->lcd_fillrect(0,yOff+(size*entrance)+size,xOff,
349 LCD_HEIGHT-yOff-(size*entrance)-size);
350 rb->lcd_fillrect(xOff,yOff+(size*height),size*width,
351 LCD_HEIGHT-yOff-(size*height));
352 /* Note: the exit is made one pixel thinner than necessary as a visual
353 * clue that chunks cannot be pushed into it
355 rb->lcd_fillrect(xOff+(size*width),yOff+(size*exit)+size-1,
356 LCD_WIDTH-xOff+(size*width),
357 LCD_HEIGHT-yOff-(size*exit)-size+1);
360 /*****************************************************************************
361 * Draw chunk row i
362 ******************************************************************************/
363 static void draw_row(
364 short size,
365 short xOff,
366 short yOff,
367 short width,
368 short i, /* the row number */
369 struct chunk_data *cd, /* the data about the chunks */
370 short *shift /* an array of the horizontal offset of the lines */
373 /* The assignment below is just a hack to make supress a warning on
374 * non color targets */
375 short j = width;
376 #ifndef HAVE_LCD_COLOR
377 /* We #def these out to supress a compiler warning */
378 short k;
379 #if LCD_DEPTH <= 1
380 short l;
381 #endif
382 #endif
383 #ifdef HAVE_LCD_COLOR
384 /* adding width to i should have a fixed, but randomising effect on
385 * the choice of the colours of the top line of chunks
387 rb->lcd_set_foreground(chunk_colors[(i+width) %
388 MAZEZAM_NUM_CHUNK_COLORS]);
389 #endif
390 for (j = 0; j<cd->l_num[i]; j++) {
391 #ifdef HAVE_LCD_COLOR
392 rb->lcd_fillrect(xOff+size*shift[i]+size*cd->c_inset[i][j],
393 yOff+size*i, cd->c_width[i][j]*size,size);
394 #elif LCD_DEPTH > 1
395 rb->lcd_set_foreground(MAZEZAM_CHUNK_EDGE_GRAY);
396 rb->lcd_drawrect(xOff+size*shift[i]+size*cd->c_inset[i][j],
397 yOff+size*i, cd->c_width[i][j]*size,size);
399 /* draw shade */
400 rb->lcd_set_foreground(chunk_gray_shade[(i+width) %
401 MAZEZAM_NUM_CHUNK_GRAYS]);
402 rb->lcd_hline(xOff+size*shift[i]+size*cd->c_inset[i][j]+1,
403 xOff+size*shift[i]+size*cd->c_inset[i][j]+
404 cd->c_width[i][j]*size-3,
405 yOff+size*i+size-2);
406 rb->lcd_vline(xOff+size*shift[i]+size*cd->c_inset[i][j]+
407 cd->c_width[i][j]*size-2,
408 yOff+size*i,
409 yOff+size*i+size-2);
411 /* draw fill */
412 rb->lcd_set_foreground(chunk_gray[(i+width) %
413 MAZEZAM_NUM_CHUNK_GRAYS]);
414 for (k = yOff+size*i+2; k < yOff+size*i+size-2; k += 2)
415 rb->lcd_hline(xOff+size*shift[i]+size*cd->c_inset[i][j]+2,
416 xOff+size*shift[i]+size*cd->c_inset[i][j]+
417 cd->c_width[i][j]*size-3,k);
418 #else
419 rb->lcd_drawrect(xOff+size*shift[i]+size*cd->c_inset[i][j],
420 yOff+size*i, cd->c_width[i][j]*size,size);
421 for (k = xOff+size*shift[i]+size*cd->c_inset[i][j]+2;
422 k < xOff+size*shift[i]+size*cd->c_inset[i][j]+
423 cd->c_width[i][j]*size;
424 k += 2 + (i & 1))
425 for (l = yOff+size*i+2; l < yOff+size*i+size; l += 2 + (i & 1))
426 rb->lcd_drawpixel(k, l);
427 #endif
431 /*****************************************************************************
432 * Draw the player
433 ******************************************************************************/
434 static void draw_player(
435 short size,
436 short xOff,
437 short yOff,
438 short x,
439 short y)
441 /* For drawing the player, taken from the sokoban plugin */
442 short max = size - 1;
443 short middle = max / 2;
444 short ldelta = (middle + 1) / 2;
446 /* draw the player (mostly copied from the sokoban plugin) */
447 #ifdef HAVE_LCD_COLOR
448 rb->lcd_set_foreground(MAZEZAM_PLAYER_COLOR);
449 #elif LCD_DEPTH > 1
450 rb->lcd_set_foreground(MAZEZAM_PLAYER_GRAY);
451 #endif
452 rb->lcd_hline(xOff+size*x, xOff+size*x+max, yOff+size*y+middle);
453 rb->lcd_vline(xOff+size*x+middle, yOff+size*y, yOff+size*y+max-ldelta);
454 rb->lcd_drawline(xOff+size*x+middle, yOff+size*y+max-ldelta,
455 xOff+size*x+middle-ldelta, yOff+size*y+max);
456 rb->lcd_drawline(xOff+size*x+middle, yOff+size*y+max-ldelta,
457 xOff+size*x+middle+ldelta, yOff+size*y+max);
460 /*****************************************************************************
461 * Draw the gate
462 ******************************************************************************/
463 static void draw_gate(
464 short size,
465 short xOff,
466 short yOff,
467 short entrance)
469 short third = size / 3;
470 short twothirds = (2 * size) / 3;
471 #ifdef HAVE_LCD_COLOR
472 rb->lcd_set_foreground(MAZEZAM_GATE_COLOR);
473 #elif LCD_DEPTH > 1
474 rb->lcd_set_foreground(MAZEZAM_GATE_GRAY);
475 #endif
476 rb->lcd_hline(xOff-size,xOff-1,yOff+entrance*size+third);
477 rb->lcd_hline(xOff-size,xOff-1,yOff+entrance*size+twothirds);
478 rb->lcd_vline(xOff-size+third,yOff+entrance*size,
479 yOff+entrance*size+size-1);
480 rb->lcd_vline(xOff-size+twothirds,yOff+entrance*size,
481 yOff+entrance*size+size-1);
484 /*****************************************************************************
485 * Draw the level
486 ******************************************************************************/
487 static void draw_level(
488 struct level_info* li,
489 short *shift, /* an array of the horizontal offset of the lines */
490 short x, /* player's x and y coords */
491 short y)
493 /* First we calculate the draw info */
494 /* The number of pixels the side of a square should be */
495 short size = (LCD_WIDTH/(li->width+2)) < (LCD_HEIGHT/li->height) ?
496 (LCD_WIDTH/(li->width+2)) : (LCD_HEIGHT/li->height);
497 /* The x and y position (in pixels) of the top left corner of the
498 * level
500 short xOff = (LCD_WIDTH - (size*li->width))/2;
501 short yOff = (LCD_HEIGHT - (size*li->height))/2;
502 short i;
504 rb->lcd_clear_display();
506 draw_walls(size,xOff,yOff,li->width, li->height, li->entrance, li->exit);
508 /* draw the chunks */
509 for (i = 0; i<li->height; i++) {
510 draw_row(size,xOff,yOff,li->width,i,&(li->cd),shift);
513 draw_player(size,xOff,yOff,x,y);
515 /* if the player has moved into the level, draw the gate */
516 if (x >= 0)
517 draw_gate(size,xOff,yOff,li->entrance);
520 /*****************************************************************************
521 * Manage the congratulations screen
522 ******************************************************************************/
523 static void welldone_screen(void)
525 int start_selection = 0;
527 MENUITEM_STRINGLIST(menu,MAZEZAM_TEXT_WELLDONE_TITLE,NULL,
528 MAZEZAM_TEXT_WELLDONE_OPTION);
530 switch(rb->do_menu(&menu, &start_selection, NULL, true)){
531 case MENU_ATTACHED_USB:
532 state = STATE_USB_CONNECTED;
533 break;
537 /*****************************************************************************
538 * Manage the playing of a level
539 ******************************************************************************/
540 static void level_loop(struct level_info* li, short* shift, short *x, short *y)
542 int i;
543 int button;
544 bool blocked; /* is there a chunk in the way of the player? */
546 while (state >= STATE_IN_LEVEL) {
547 draw_level(li, shift, *x, *y);
548 rb->lcd_update();
549 button = pluginlib_getaction(rb, TIMEOUT_BLOCK, plugin_contexts, 2);
550 blocked = false;
552 switch (button) {
553 case MAZEZAM_UP:
554 case MAZEZAM_UP_REPEAT:
555 if ((*y > 0) && (*x >= 0) && (*x < li->width)) {
556 for (i = 0; i < li->cd.l_num[*y-1]; i++)
557 blocked = blocked ||
558 ((*x>=shift[*y-1]+li->cd.c_inset[*y-1][i]) &&
559 (*x<shift[*y-1]+li->cd.c_inset[*y-1][i]+
560 li->cd.c_width[*y-1][i]));
561 if (!blocked) *y -= 1;
563 break;
567 case MAZEZAM_DOWN:
568 case MAZEZAM_DOWN_REPEAT:
569 if ((*y < li->height-1) && (*x >= 0) && (*x < li->width)) {
570 for (i = 0; i < li->cd.l_num[*y+1]; i++)
571 blocked = blocked ||
572 ((*x>=shift[*y+1]+li->cd.c_inset[*y+1][i]) &&
573 (*x<shift[*y+1]+li->cd.c_inset[*y+1][i]+
574 li->cd.c_width[*y+1][i]));
575 if (!blocked) *y += 1;
577 break;
579 case MAZEZAM_LEFT:
580 case MAZEZAM_LEFT_REPEAT:
581 if (*x > 0) {
582 for (i = 0; i < li->cd.l_num[*y]; i++)
583 blocked = blocked ||
584 (*x == shift[*y]+li->cd.c_inset[*y][i]+
585 li->cd.c_width[*y][i]);
586 if (!blocked) *x -= 1;
587 else if (shift[*y] + li->cd.c_inset[*y][0] > 0) {
588 *x -= 1;
589 shift[*y] -= 1;
592 break;
594 case MAZEZAM_RIGHT:
595 case MAZEZAM_RIGHT_REPEAT:
596 if (*x < li->width-1) {
597 for (i = 0; i < li->cd.l_num[*y]; i++)
598 blocked = blocked ||
599 (*x+1 == shift[*y]+li->cd.c_inset[*y][i]);
600 if (!blocked) *x += 1;
601 else if (shift[*y]
602 + li->cd.c_inset[*y][li->cd.l_num[*y]-1]
603 + li->cd.c_width[*y][li->cd.l_num[*y]-1]
604 < li->width) {
605 *x += 1;
606 shift[*y] += 1;
609 else if (*x == li->width) state = STATE_COMPLETED;
610 else if (*y == li->exit) *x += 1;
611 break;
613 case MAZEZAM_MENU:
614 state = STATE_GAME_MENU;
615 break;
617 default:
618 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
619 state = STATE_USB_CONNECTED;
620 break;
625 /*****************************************************************************
626 * Manage the in game menu
627 ******************************************************************************/
628 static void in_game_menu(void)
630 /* The initial option is retry level */
631 int start_selection = 1;
633 MENUITEM_STRINGLIST(menu,MAZEZAM_TEXT_MAZEZAM_MENU, NULL,
634 MAZEZAM_TEXT_BACK,
635 MAZEZAM_TEXT_RETRY_LEVEL,
636 MAZEZAM_TEXT_AUDIO_PLAYBACK,
637 MAZEZAM_TEXT_QUIT);
639 /* Don't show the status bar */
640 switch(rb->do_menu(&menu, &start_selection, NULL, false)){
641 case 1: /* retry */
642 state = STATE_FAILED;
643 break;
645 case 2: /* Audio playback */
646 playback_control(rb, NULL);
647 state = STATE_IN_LEVEL;
648 break;
650 case 3: /* quit */
651 state = STATE_QUIT;
652 break;
654 case MENU_ATTACHED_USB:
655 state = STATE_USB_CONNECTED;
656 break;
658 default: /* Back */
659 state = STATE_IN_LEVEL;
660 break;
664 /*****************************************************************************
665 * Is the level a checkpoint
666 ******************************************************************************/
667 static bool at_checkpoint(int level)
669 if (level <= MAZEZAM_FIRST_CHECKPOINT)
670 return level == MAZEZAM_FIRST_CHECKPOINT;
671 else {
672 level = level - MAZEZAM_FIRST_CHECKPOINT;
673 return level % MAZEZAM_CHECKPOINT_INTERVAL == 0;
677 /*****************************************************************************
678 * Set up and play a level
679 * new_level should be true if this is the first time we've encountered
680 * this level
681 ******************************************************************************/
682 static void play_level(short level, short lives, bool new_level)
684 struct level_info li;
685 short shift[MAZEZAM_MAX_LINES]; /* amount each line has been shifted */
686 short x,y;
687 int i;
689 state = STATE_IN_LEVEL;
691 if (!(parse_level(level,&li)))
692 state = STATE_PARSE_ERROR;
694 for (i = 0; i < li.height; i++)
695 shift[i] = 0;
697 x = -1;
698 y = li.entrance;
700 plugin_lcd_settings();
701 rb->lcd_clear_display();
703 draw_level(&li, shift, x, y);
705 /* If we've just reached a checkpoint, then alert the player */
706 if (new_level && at_checkpoint(level)) {
707 rb->splash(MAZEZAM_DELAY_CHECKPOINT, MAZEZAM_TEXT_CHECKPOINT);
708 /* Clear the splash */
709 draw_level(&li, shift, x, y);
712 #ifdef HAVE_REMOTE_LCD
713 /* Splash text seems to use the remote display by
714 * default. I suppose I better keep it tidy!
716 rb->lcd_remote_clear_display();
717 #endif
718 rb->splashf(MAZEZAM_DELAY_LIVES, MAZEZAM_TEXT_LIVES,
719 level+1, lives);
721 /* ensure keys pressed during the splash screen are ignored */
722 rb->button_clear_queue();
724 /* this little loop just ensures we return to the game if the player
725 * doesn't perform an interesting action during the in game menu */
726 while (state >= STATE_IN_LEVEL) {
727 level_loop(&li, shift, &x, &y);
729 if (state == STATE_GAME_MENU) {
730 restore_lcd_settings();
731 in_game_menu();
732 plugin_lcd_settings();
735 restore_lcd_settings();
738 /*****************************************************************************
739 * Update the resume data based on the level reached
740 ******************************************************************************/
741 static void update_resume_data(struct resume_data *r, int level)
743 if (at_checkpoint(level))
744 r->level = level;
747 /*****************************************************************************
748 * The loop which manages a full game of MazezaM.
749 ******************************************************************************/
750 static void game_loop(struct resume_data *r)
752 int level = r->level;
753 int lives = MAZEZAM_START_LIVES;
754 /* We want to know when a player reaches a level for the first time,
755 * so we keep a second copy of the level. */
756 int old_level = level;
758 state = STATE_IN_GAME;
760 while (state >= STATE_IN_GAME)
762 play_level(level, lives, old_level < level);
763 old_level = level;
765 switch (state) {
766 case STATE_COMPLETED:
767 level += 1;
768 if (level == MAZEZAM_NUM_LEVELS)
769 state = STATE_WELLDONE;
770 break;
772 case STATE_FAILED:
773 lives -= 1;
774 if (lives == 0)
775 state = STATE_GAME_OVER;
776 break;
778 default:
779 break;
782 update_resume_data(r,level);
785 switch (state) {
786 case STATE_GAME_OVER:
787 #ifdef HAVE_REMOTE_LCD
788 /* Splash text seems to use the remote display by
789 * default. I suppose I better keep it tidy!
791 rb->lcd_remote_clear_display();
792 #endif
793 rb->splash(MAZEZAM_DELAY_GAME_OVER, MAZEZAM_TEXT_GAME_OVER);
794 break;
796 case STATE_WELLDONE:
797 welldone_screen();
798 break;
800 default:
801 break;
805 /*****************************************************************************
806 * Load the resume data from the config file. The data is
807 * stored in both r and old.
808 ******************************************************************************/
809 static void resume_load_data (struct resume_data *r, struct resume_data *old)
811 struct configdata config[] = {
812 {TYPE_INT,0,MAZEZAM_NUM_LEVELS-1,&(r->level),
813 MAZEZAM_CONFIG_LEVELS_NAME,NULL,NULL}
816 if (configfile_load(MAZEZAM_CONFIG_FILENAME,config,
817 MAZEZAM_CONFIG_NUM_ITEMS, MAZEZAM_CONFIG_VERSION) < 0)
818 r->level = 0;
819 /* an extra precaution */
820 else if ((r->level < 0) || (MAZEZAM_NUM_LEVELS <= r->level))
821 r->level = 0;
823 old->level = r->level;
826 /*****************************************************************************
827 * Save the resume data in the config file, but only if necessary
828 ******************************************************************************/
829 static void resume_save_data (struct resume_data *r, struct resume_data *old)
831 struct configdata config[] = {
832 {TYPE_INT,0,MAZEZAM_NUM_LEVELS-1,&(r->level),
833 MAZEZAM_CONFIG_LEVELS_NAME,NULL,NULL}
836 /* To reduce disk usage, only write the file if the resume data has
837 * changed.
839 if (old->level != r->level)
840 configfile_save(MAZEZAM_CONFIG_FILENAME,config,
841 MAZEZAM_CONFIG_NUM_ITEMS, MAZEZAM_CONFIG_MINVERSION);
844 /*****************************************************************************
845 * Offer a main menu with no continue option
846 ******************************************************************************/
847 static int main_menu_without_continue(int* start_selection)
849 MENUITEM_STRINGLIST(menu,MAZEZAM_TEXT_MAIN_MENU,NULL,
850 MAZEZAM_TEXT_PLAY_GAME,
851 MAZEZAM_TEXT_QUIT);
852 return rb->do_menu(&menu, start_selection, NULL, false);
855 /*****************************************************************************
856 * Offer a main menu with a continue option
857 ******************************************************************************/
858 static int main_menu_with_continue(int* start_selection)
860 MENUITEM_STRINGLIST(menu,MAZEZAM_TEXT_MAIN_MENU,NULL,
861 MAZEZAM_TEXT_CONTINUE,
862 MAZEZAM_TEXT_PLAY_NEW_GAME,
863 MAZEZAM_TEXT_QUIT);
864 return rb->do_menu(&menu, start_selection, NULL, false);
867 /*****************************************************************************
868 * Manages the main menu
869 ******************************************************************************/
870 static void main_menu(void)
872 /* The initial option is "play game" */
873 int start_selection = 0;
874 int choice = 0;
875 struct resume_data r_data, old_data;
877 /* Load data */
878 resume_load_data(&r_data, &old_data);
880 while (state >= STATE_IN_APPLICATION) {
881 if (r_data.level == 0)
882 choice = main_menu_without_continue(&start_selection);
883 else
884 choice = main_menu_with_continue(&start_selection);
886 switch(choice) {
887 case 0: /* Continue */
888 state = STATE_IN_GAME;
889 game_loop(&r_data);
890 break;
892 case 1: /* Quit or Play new game */
893 if (r_data.level == 0)
894 state = STATE_QUIT;
895 else { /* Play new game */
896 r_data.level = 0;
897 state = STATE_IN_GAME;
898 game_loop(&r_data);
900 break;
902 case MENU_ATTACHED_USB:
903 state = STATE_USB_CONNECTED;
904 break;
906 default: /* Quit */
907 state = STATE_QUIT;
908 break;
912 /* I'm not sure if it's appropriate to write to disk on USB events.
913 * Currently, I do so.
915 resume_save_data(&r_data, &old_data);
918 /*****************************************************************************
919 * Plugin entry point
920 ******************************************************************************/
921 enum plugin_status plugin_start(const struct plugin_api* api, const void* parameter)
923 enum plugin_status plugin_state;
925 /* Usual plugin stuff */
926 (void)parameter;
927 rb = api;
930 /* initialise the config file module */
931 configfile_init(rb);
933 store_lcd_settings();
935 state = STATE_MAIN_MENU;
936 main_menu();
938 switch (state) {
939 case STATE_USB_CONNECTED:
940 plugin_state = PLUGIN_USB_CONNECTED;
941 break;
943 case STATE_PARSE_ERROR:
944 plugin_state = PLUGIN_ERROR;
945 break;
947 default:
948 plugin_state = PLUGIN_OK;
949 break;
952 return plugin_state;