Add proper svn:mime-type
[maemo-rb.git] / apps / plugins / mazezam.c
blob759ca6477d5de74c237d42034c9730d29f183696
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/playback_control.h"
27 /* Include standard plugin macro */
30 #if (CONFIG_KEYPAD == IPOD_3G_PAD)
31 # define MAZEZAM_MENU BUTTON_MENU
32 # define MAZEZAM_RIGHT BUTTON_RIGHT
33 # define MAZEZAM_LEFT BUTTON_LEFT
34 # define MAZEZAM_UP BUTTON_SCROLL_BACK
35 # define MAZEZAM_DOWN BUTTON_SCROLL_FWD
36 # define MAZEZAM_RIGHT_REPEAT (BUTTON_RIGHT|BUTTON_REPEAT)
37 # define MAZEZAM_LEFT_REPEAT (BUTTON_LEFT|BUTTON_REPEAT)
38 # define MAZEZAM_UP_REPEAT (BUTTON_SCROLL_BACK|BUTTON_REPEAT)
39 # define MAZEZAM_DOWN_REPEAT (BUTTON_SCROLL_FWD|BUTTON_REPEAT)
41 #else
42 # include "lib/pluginlib_actions.h"
43 # define MAZEZAM_MENU PLA_CANCEL
44 # define MAZEZAM_RIGHT PLA_RIGHT
45 # define MAZEZAM_LEFT PLA_LEFT
46 # define MAZEZAM_UP PLA_UP
47 # define MAZEZAM_DOWN PLA_DOWN
48 # define MAZEZAM_RIGHT_REPEAT PLA_RIGHT_REPEAT
49 # define MAZEZAM_LEFT_REPEAT PLA_LEFT_REPEAT
50 # define MAZEZAM_UP_REPEAT PLA_UP_REPEAT
51 # define MAZEZAM_DOWN_REPEAT PLA_DOWN_REPEAT
52 const struct button_mapping *plugin_contexts[]
53 = { pla_main_ctx };
54 #endif
56 /* All the text is here */
57 #define MAZEZAM_TEXT_GAME_OVER "Game Over"
58 #define MAZEZAM_TEXT_LIVES "Level %d, Lives %d"
59 #define MAZEZAM_TEXT_CHECKPOINT "Checkpoint reached"
60 #define MAZEZAM_TEXT_WELLDONE_TITLE "You have escaped!"
61 #define MAZEZAM_TEXT_WELLDONE_OPTION "Goodbye"
62 #define MAZEZAM_TEXT_MAZEZAM_MENU "MazezaM Menu"
63 #define MAZEZAM_TEXT_RETRY_LEVEL "Retry level"
64 #define MAZEZAM_TEXT_AUDIO_PLAYBACK "Playback Control"
65 #define MAZEZAM_TEXT_QUIT "Quit"
66 #define MAZEZAM_TEXT_BACK "Resume Game"
67 #define MAZEZAM_TEXT_MAIN_MENU "MazezaM"
68 #define MAZEZAM_TEXT_CONTINUE "Play from checkpoint"
69 #define MAZEZAM_TEXT_PLAY_NEW_GAME "Start New Game"
71 #define MAZEZAM_START_LIVES 3 /* how many lives at game start */
72 #define MAZEZAM_FIRST_CHECKPOINT 3 /* The level at the first checkpoint */
73 #define MAZEZAM_CHECKPOINT_INTERVAL 4 /* A checkpoint every _ levels */
75 #ifdef HAVE_LCD_COLOR
76 #define MAZEZAM_HEADING_COLOR LCD_RGBPACK(255,255, 0) /* Yellow */
77 #define MAZEZAM_BORDER_COLOR LCD_RGBPACK( 0, 0,255) /* Blue */
78 #define MAZEZAM_COLOR LCD_RGBPACK(255,255,255) /* White */
79 #define MAZEZAM_BG_COLOR LCD_RGBPACK( 0, 0, 0) /* Black */
80 #define MAZEZAM_WALL_COLOR LCD_RGBPACK(100,100,100) /* Dark gray */
81 #define MAZEZAM_PLAYER_COLOR LCD_RGBPACK(255,255,255) /* White */
82 #define MAZEZAM_GATE_COLOR LCD_RGBPACK(100,100,100) /* Dark gray */
84 /* the rows are coloured sequentially */
85 #define MAZEZAM_NUM_CHUNK_COLORS 8
86 static const unsigned chunk_colors[MAZEZAM_NUM_CHUNK_COLORS] = {
87 LCD_RGBPACK(255,192, 32), /* Orange */
88 LCD_RGBPACK(255, 0, 0), /* Red */
89 LCD_RGBPACK( 0,255, 0), /* Green */
90 LCD_RGBPACK( 0,255,255), /* Cyan */
91 LCD_RGBPACK(255,175,175), /* Pink */
92 LCD_RGBPACK(255,255, 0), /* Yellow */
93 LCD_RGBPACK( 0, 0,255), /* Blue */
94 LCD_RGBPACK(255, 0,255), /* Magenta */
97 #elif LCD_DEPTH > 1
99 #define MAZEZAM_HEADING_GRAY LCD_BLACK
100 #define MAZEZAM_BORDER_GRAY LCD_DARKGRAY
101 #define MAZEZAM_GRAY LCD_BLACK
102 #define MAZEZAM_BG_GRAY LCD_WHITE
103 #define MAZEZAM_WALL_GRAY LCD_DARKGRAY
104 #define MAZEZAM_PLAYER_GRAY LCD_BLACK
105 #define MAZEZAM_GATE_GRAY LCD_BLACK
106 #define MAZEZAM_CHUNK_EDGE_GRAY LCD_BLACK
108 #define MAZEZAM_NUM_CHUNK_GRAYS 2
109 static const unsigned chunk_gray[MAZEZAM_NUM_CHUNK_GRAYS] = {
110 LCD_LIGHTGRAY,
111 LCD_DARKGRAY,
113 /* darker version of the above */
114 static const unsigned chunk_gray_shade[MAZEZAM_NUM_CHUNK_GRAYS] = {
115 LCD_DARKGRAY,
116 LCD_BLACK,
118 #endif
120 #define MAZEZAM_DELAY_CHECKPOINT HZ
121 #define MAZEZAM_DELAY_LIVES HZ
122 #define MAZEZAM_DELAY_GAME_OVER (3 * HZ) / 2
124 /* maximum height of a level */
125 #define MAZEZAM_MAX_LINES 11
126 /* maximum number of chunks on a line */
127 #define MAZEZAM_MAX_CHUNKS 5
129 /* A structure for storing level data in unparsed form */
130 struct mazezam_level {
131 short height; /* the number of lines */
132 short width; /* the width */
133 short entrance; /* the line on which the entrance lies */
134 short exit; /* the line on which the exit lies */
135 char *line[MAZEZAM_MAX_LINES]; /* the chunk data in string form */
138 /* The number of levels. */
139 #define MAZEZAM_NUM_LEVELS 10
141 /* The levels. In theory, they could be stored in a file so this data
142 * structure should not be accessed outside parse_level()
144 * These levels are copyright (C) 2002 Malcolm Tyrrell. They're
145 * probably covered by the GPL as they constitute part of the source
146 * code of this plugin, but you may distibute them seperately with
147 * other Free Software if you want. You can download them from:
148 * http://webpages.dcu.ie/~tyrrelma/MazezaM.
150 static const struct mazezam_level level_data[MAZEZAM_NUM_LEVELS] = {
151 {2,7,0,0,{" $ $"," $ $$"}},
152 {3,8,2,1,{" $ $$$"," $ $ $"," $ $ $"}},
153 {4,14,1,3,{" $$$$$ $$ $$"," $$ $$ $$","$$ $ $$ $$$",
154 " $$$$$$$$ $"}},
155 {6,7,4,2,{" $"," $$$$"," $$$ $$"," $ $ $"," $ $$","$ $$"}},
156 {6,13,0,0,{" $$$$$","$ $$$$$ $$$"," $ $$$ $$$$",
157 "$ $ $$$$$$$"," $$$ $ $$","$ $ $ $$ $"}},
158 {11,5,10,0,{" $"," $ $$"," $$","$ $"," $ $"," $$$","$ $",
159 " $ $"," $ $","$ $$"," $"}},
160 {7,16,0,6,{" $$$$$$$"," $$$$ $$$$ $ $","$$ $$ $$$$$$ $ $",
161 "$ $ $"," $$$$$$$$$$$$$$"," $ $$ $ $$$",
162 " $ $$$ $$"}},
163 {4,15,2,0,{" $$$$ $$$$ $$"," $ $$ $$ $ $$"," $ $$ $$$$ $$",
164 " $ $$ $$$$ $"}},
165 {7,9,6,2,{" $ $$$$"," $ $ $$"," $ $$$$ $","$ $$ $"," $ $$$",
166 " $$$$$$"," $"}},
167 {10,14,8,0,{" $"," $$$$$$$$$$ $"," $$$ $$",
168 " $ $$$$$$$$ $"," $$$ $$$ $$$"," $$$ $ $$$",
169 " $ $$$$$$$ $$"," $ $ $ $$$"," $$$$$$$$$$$$",
170 ""}}
173 /* This data structure which holds information about the rows */
174 struct chunk_data {
175 /* the number of chunks on a line */
176 short l_num[MAZEZAM_MAX_LINES];
177 /* the width of a chunk */
178 short c_width[MAZEZAM_MAX_LINES][MAZEZAM_MAX_CHUNKS];
179 /* the inset of a chunk */
180 short c_inset[MAZEZAM_MAX_LINES][MAZEZAM_MAX_CHUNKS];
183 /* Parsed level data */
184 struct level_info {
185 short width;
186 short height;
187 short entrance;
188 short exit;
189 struct chunk_data cd;
192 /* The state variable used to hold the state of the plugin */
193 static enum {
194 STATE_QUIT, /* The player wants to quit */
195 STATE_USB_CONNECTED, /* A USB cable has been inserted */
196 STATE_PARSE_ERROR, /* There's a parse error in the levels */
197 STATE_WELLDONE, /* The player has finished the game */
199 STATE_IN_APPLICATION,
201 STATE_MAIN_MENU /* The player is at the main menu */
202 = STATE_IN_APPLICATION,
203 STATE_GAME_OVER, /* The player is out of lives */
205 STATE_IN_GAME,
207 STATE_COMPLETED /* A level has been completed */
208 = STATE_IN_GAME,
210 STATE_FAILED, /* The player wants to retry the level */
211 STATE_GAME_MENU, /* The player wan't to access the in-game menu */
213 STATE_IN_LEVEL,
214 } state;
216 /* The various constants needed for configuration files.
217 * See apps/plugins/lib/configfile.*
219 #define MAZEZAM_CONFIG_FILENAME "mazezam.data"
220 #define MAZEZAM_CONFIG_NUM_ITEMS 1
221 #define MAZEZAM_CONFIG_VERSION 0
222 #define MAZEZAM_CONFIG_MINVERSION 0
223 #define MAZEZAM_CONFIG_LEVELS_NAME "restart_level"
225 /* A structure containing the data that is written to
226 * the configuration file
228 struct resume_data {
229 int level; /* level at which to restart the game */
232 #if LCD_DEPTH > 1
233 /* Store the display settings so they are reintroduced during menus */
234 static struct {
235 fb_data* backdrop;
236 unsigned foreground;
237 unsigned background;
238 } lcd_settings;
239 #endif
241 /*****************************************************************************
242 * Store the LCD settings
243 ******************************************************************************/
244 static void store_lcd_settings(void)
246 /* Store the old settings */
247 #if LCD_DEPTH > 1
248 lcd_settings.backdrop = rb->lcd_get_backdrop();
249 lcd_settings.foreground = rb->lcd_get_foreground();
250 lcd_settings.background = rb->lcd_get_background();
251 #endif
254 /*****************************************************************************
255 * Restore the LCD settings to their defaults
256 ******************************************************************************/
257 static void restore_lcd_settings(void) {
258 /* Turn on backlight timeout (revert to settings) */
259 backlight_use_settings();
261 /* Restore the old settings */
262 #if LCD_DEPTH > 1
263 rb->lcd_set_foreground(lcd_settings.foreground);
264 rb->lcd_set_background(lcd_settings.background);
265 rb->lcd_set_backdrop(lcd_settings.backdrop);
266 #endif
269 /*****************************************************************************
270 * Adjust the LCD settings to suit MazezaM levels
271 ******************************************************************************/
272 static void plugin_lcd_settings(void) {
273 /* Turn off backlight timeout */
274 backlight_ignore_timeout();
276 /* Set the new settings */
277 #ifdef HAVE_LCD_COLOR
278 rb->lcd_set_background(MAZEZAM_BG_COLOR);
279 rb->lcd_set_backdrop(NULL);
280 #elif LCD_DEPTH > 1
281 rb->lcd_set_background(MAZEZAM_BG_GRAY);
282 rb->lcd_set_backdrop(NULL);
283 #endif
286 /*****************************************************************************
287 * Parse the level data from the level_data structure. This could be
288 * replaced by a file read. Returns true if the level parsed correctly.
289 ******************************************************************************/
290 static bool parse_level(short level, struct level_info* li)
292 int i,j;
293 char c,clast;
295 li->width = level_data[level].width;
296 li->height = level_data[level].height;
297 li->entrance = level_data[level].entrance;
298 li->exit = level_data[level].exit;
300 /* for each line in the level */
301 for (i = 0; i<level_data[level].height; i++) {
302 if (level_data[level].line[i] == NULL)
303 return false;
304 else {
305 j = 0;
306 li->cd.l_num[i] = 0;
307 clast = ' '; /* the character we last considered */
308 while ((c = level_data[level].line[i][j]) != '\0') {
309 if (c != ' ') {
310 if (clast == ' ') {
311 li->cd.l_num[i] += 1;
312 if (li->cd.l_num[i] > MAZEZAM_MAX_CHUNKS)
313 return false;
314 li->cd.c_inset[i][li->cd.l_num[i] - 1] = j;
315 li->cd.c_width[i][li->cd.l_num[i] - 1] = 1;
317 else
318 li->cd.c_width[i][li->cd.l_num[i] - 1] += 1;
320 clast = c;
321 j++;
325 return true;
328 /*****************************************************************************
329 * Draw the walls of a level
330 ******************************************************************************/
331 static void draw_walls(
332 short size,
333 short xOff,
334 short yOff,
335 short width,
336 short height,
337 short entrance,
338 short exit)
340 #ifdef HAVE_LCD_COLOR
341 rb->lcd_set_foreground(MAZEZAM_WALL_COLOR);
342 #elif LCD_DEPTH > 1
343 rb->lcd_set_foreground(MAZEZAM_WALL_GRAY);
344 #endif
345 /* draw the upper wall */
346 rb->lcd_fillrect(0,0,xOff,yOff+(size*entrance));
347 rb->lcd_fillrect(xOff,0,size*width,yOff);
348 rb->lcd_fillrect(xOff+(size*width),0,LCD_WIDTH-xOff-(size*width),
349 yOff+(size*exit));
351 /* draw the lower wall */
352 rb->lcd_fillrect(0,yOff+(size*entrance)+size,xOff,
353 LCD_HEIGHT-yOff-(size*entrance)-size);
354 rb->lcd_fillrect(xOff,yOff+(size*height),size*width,
355 LCD_HEIGHT-yOff-(size*height));
356 /* Note: the exit is made one pixel thinner than necessary as a visual
357 * clue that chunks cannot be pushed into it
359 rb->lcd_fillrect(xOff+(size*width),yOff+(size*exit)+size-1,
360 LCD_WIDTH-xOff+(size*width),
361 LCD_HEIGHT-yOff-(size*exit)-size+1);
364 /*****************************************************************************
365 * Draw chunk row i
366 ******************************************************************************/
367 static void draw_row(
368 short size,
369 short xOff,
370 short yOff,
371 short width,
372 short i, /* the row number */
373 struct chunk_data *cd, /* the data about the chunks */
374 short *shift /* an array of the horizontal offset of the lines */
377 /* The assignment below is just a hack to make supress a warning on
378 * non color targets */
379 short j = width;
380 #ifndef HAVE_LCD_COLOR
381 /* We #def these out to supress a compiler warning */
382 short k;
383 #if LCD_DEPTH <= 1
384 short l;
385 #endif
386 #endif
387 #ifdef HAVE_LCD_COLOR
388 /* adding width to i should have a fixed, but randomising effect on
389 * the choice of the colours of the top line of chunks
391 rb->lcd_set_foreground(chunk_colors[(i+width) %
392 MAZEZAM_NUM_CHUNK_COLORS]);
393 #endif
394 for (j = 0; j<cd->l_num[i]; j++) {
395 #ifdef HAVE_LCD_COLOR
396 rb->lcd_fillrect(xOff+size*shift[i]+size*cd->c_inset[i][j],
397 yOff+size*i, cd->c_width[i][j]*size,size);
398 #elif LCD_DEPTH > 1
399 rb->lcd_set_foreground(MAZEZAM_CHUNK_EDGE_GRAY);
400 rb->lcd_drawrect(xOff+size*shift[i]+size*cd->c_inset[i][j],
401 yOff+size*i, cd->c_width[i][j]*size,size);
403 /* draw shade */
404 rb->lcd_set_foreground(chunk_gray_shade[(i+width) %
405 MAZEZAM_NUM_CHUNK_GRAYS]);
406 rb->lcd_hline(xOff+size*shift[i]+size*cd->c_inset[i][j]+1,
407 xOff+size*shift[i]+size*cd->c_inset[i][j]+
408 cd->c_width[i][j]*size-3,
409 yOff+size*i+size-2);
410 rb->lcd_vline(xOff+size*shift[i]+size*cd->c_inset[i][j]+
411 cd->c_width[i][j]*size-2,
412 yOff+size*i,
413 yOff+size*i+size-2);
415 /* draw fill */
416 rb->lcd_set_foreground(chunk_gray[(i+width) %
417 MAZEZAM_NUM_CHUNK_GRAYS]);
418 for (k = yOff+size*i+2; k < yOff+size*i+size-2; k += 2)
419 rb->lcd_hline(xOff+size*shift[i]+size*cd->c_inset[i][j]+2,
420 xOff+size*shift[i]+size*cd->c_inset[i][j]+
421 cd->c_width[i][j]*size-3,k);
422 #else
423 rb->lcd_drawrect(xOff+size*shift[i]+size*cd->c_inset[i][j],
424 yOff+size*i, cd->c_width[i][j]*size,size);
425 for (k = xOff+size*shift[i]+size*cd->c_inset[i][j]+2;
426 k < xOff+size*shift[i]+size*cd->c_inset[i][j]+
427 cd->c_width[i][j]*size;
428 k += 2 + (i & 1))
429 for (l = yOff+size*i+2; l < yOff+size*i+size; l += 2 + (i & 1))
430 rb->lcd_drawpixel(k, l);
431 #endif
435 /*****************************************************************************
436 * Draw the player
437 ******************************************************************************/
438 static void draw_player(
439 short size,
440 short xOff,
441 short yOff,
442 short x,
443 short y)
445 /* For drawing the player, taken from the sokoban plugin */
446 short max = size - 1;
447 short middle = max / 2;
448 short ldelta = (middle + 1) / 2;
450 /* draw the player (mostly copied from the sokoban plugin) */
451 #ifdef HAVE_LCD_COLOR
452 rb->lcd_set_foreground(MAZEZAM_PLAYER_COLOR);
453 #elif LCD_DEPTH > 1
454 rb->lcd_set_foreground(MAZEZAM_PLAYER_GRAY);
455 #endif
456 rb->lcd_hline(xOff+size*x, xOff+size*x+max, yOff+size*y+middle);
457 rb->lcd_vline(xOff+size*x+middle, yOff+size*y, yOff+size*y+max-ldelta);
458 rb->lcd_drawline(xOff+size*x+middle, yOff+size*y+max-ldelta,
459 xOff+size*x+middle-ldelta, yOff+size*y+max);
460 rb->lcd_drawline(xOff+size*x+middle, yOff+size*y+max-ldelta,
461 xOff+size*x+middle+ldelta, yOff+size*y+max);
464 /*****************************************************************************
465 * Draw the gate
466 ******************************************************************************/
467 static void draw_gate(
468 short size,
469 short xOff,
470 short yOff,
471 short entrance)
473 short third = size / 3;
474 short twothirds = (2 * size) / 3;
475 #ifdef HAVE_LCD_COLOR
476 rb->lcd_set_foreground(MAZEZAM_GATE_COLOR);
477 #elif LCD_DEPTH > 1
478 rb->lcd_set_foreground(MAZEZAM_GATE_GRAY);
479 #endif
480 rb->lcd_hline(xOff-size,xOff-1,yOff+entrance*size+third);
481 rb->lcd_hline(xOff-size,xOff-1,yOff+entrance*size+twothirds);
482 rb->lcd_vline(xOff-size+third,yOff+entrance*size,
483 yOff+entrance*size+size-1);
484 rb->lcd_vline(xOff-size+twothirds,yOff+entrance*size,
485 yOff+entrance*size+size-1);
488 /*****************************************************************************
489 * Draw the level
490 ******************************************************************************/
491 static void draw_level(
492 struct level_info* li,
493 short *shift, /* an array of the horizontal offset of the lines */
494 short x, /* player's x and y coords */
495 short y)
497 /* First we calculate the draw info */
498 /* The number of pixels the side of a square should be */
499 short size = (LCD_WIDTH/(li->width+2)) < (LCD_HEIGHT/li->height) ?
500 (LCD_WIDTH/(li->width+2)) : (LCD_HEIGHT/li->height);
501 /* The x and y position (in pixels) of the top left corner of the
502 * level
504 short xOff = (LCD_WIDTH - (size*li->width))/2;
505 short yOff = (LCD_HEIGHT - (size*li->height))/2;
506 short i;
508 rb->lcd_clear_display();
510 draw_walls(size,xOff,yOff,li->width, li->height, li->entrance, li->exit);
512 /* draw the chunks */
513 for (i = 0; i<li->height; i++) {
514 draw_row(size,xOff,yOff,li->width,i,&(li->cd),shift);
517 draw_player(size,xOff,yOff,x,y);
519 /* if the player has moved into the level, draw the gate */
520 if (x >= 0)
521 draw_gate(size,xOff,yOff,li->entrance);
524 /*****************************************************************************
525 * Manage the congratulations screen
526 ******************************************************************************/
527 static void welldone_screen(void)
529 int start_selection = 0;
531 MENUITEM_STRINGLIST(menu,MAZEZAM_TEXT_WELLDONE_TITLE,NULL,
532 MAZEZAM_TEXT_WELLDONE_OPTION);
534 switch(rb->do_menu(&menu, &start_selection, NULL, true)){
535 case MENU_ATTACHED_USB:
536 state = STATE_USB_CONNECTED;
537 break;
541 /*****************************************************************************
542 * Manage the playing of a level
543 ******************************************************************************/
544 static void level_loop(struct level_info* li, short* shift, short *x, short *y)
546 int i;
547 int button;
548 bool blocked; /* is there a chunk in the way of the player? */
550 while (state >= STATE_IN_LEVEL) {
551 draw_level(li, shift, *x, *y);
552 rb->lcd_update();
553 #ifdef __PLUGINLIB_ACTIONS_H__
554 button = pluginlib_getaction(TIMEOUT_BLOCK, plugin_contexts,
555 ARRAYLEN(plugin_contexts));
556 #else
557 button = rb->button_get(true);
558 #endif
559 blocked = false;
561 switch (button) {
562 case MAZEZAM_UP:
563 case MAZEZAM_UP_REPEAT:
564 if ((*y > 0) && (*x >= 0) && (*x < li->width)) {
565 for (i = 0; i < li->cd.l_num[*y-1]; i++)
566 blocked = blocked ||
567 ((*x>=shift[*y-1]+li->cd.c_inset[*y-1][i]) &&
568 (*x<shift[*y-1]+li->cd.c_inset[*y-1][i]+
569 li->cd.c_width[*y-1][i]));
570 if (!blocked) *y -= 1;
572 break;
574 case MAZEZAM_DOWN:
575 case MAZEZAM_DOWN_REPEAT:
576 if ((*y < li->height-1) && (*x >= 0) && (*x < li->width)) {
577 for (i = 0; i < li->cd.l_num[*y+1]; i++)
578 blocked = blocked ||
579 ((*x>=shift[*y+1]+li->cd.c_inset[*y+1][i]) &&
580 (*x<shift[*y+1]+li->cd.c_inset[*y+1][i]+
581 li->cd.c_width[*y+1][i]));
582 if (!blocked) *y += 1;
584 break;
586 case MAZEZAM_LEFT:
587 case MAZEZAM_LEFT_REPEAT:
588 if (*x > 0) {
589 for (i = 0; i < li->cd.l_num[*y]; i++)
590 blocked = blocked ||
591 (*x == shift[*y]+li->cd.c_inset[*y][i]+
592 li->cd.c_width[*y][i]);
593 if (!blocked) *x -= 1;
594 else if (shift[*y] + li->cd.c_inset[*y][0] > 0) {
595 *x -= 1;
596 shift[*y] -= 1;
599 break;
601 case MAZEZAM_RIGHT:
602 case MAZEZAM_RIGHT_REPEAT:
603 if (*x < li->width-1) {
604 for (i = 0; i < li->cd.l_num[*y]; i++)
605 blocked = blocked ||
606 (*x+1 == shift[*y]+li->cd.c_inset[*y][i]);
607 if (!blocked) *x += 1;
608 else if (shift[*y]
609 + li->cd.c_inset[*y][li->cd.l_num[*y]-1]
610 + li->cd.c_width[*y][li->cd.l_num[*y]-1]
611 < li->width) {
612 *x += 1;
613 shift[*y] += 1;
616 else if (*x == li->width) state = STATE_COMPLETED;
617 else if (*y == li->exit) *x += 1;
618 break;
620 case MAZEZAM_MENU:
621 state = STATE_GAME_MENU;
622 break;
624 default:
625 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
626 state = STATE_USB_CONNECTED;
627 break;
632 /*****************************************************************************
633 * Manage the in game menu
634 ******************************************************************************/
635 static void in_game_menu(void)
637 /* The initial option is retry level */
638 int start_selection = 1;
640 MENUITEM_STRINGLIST(menu,MAZEZAM_TEXT_MAZEZAM_MENU, NULL,
641 MAZEZAM_TEXT_BACK,
642 MAZEZAM_TEXT_RETRY_LEVEL,
643 MAZEZAM_TEXT_AUDIO_PLAYBACK,
644 MAZEZAM_TEXT_QUIT);
646 /* Don't show the status bar */
647 switch(rb->do_menu(&menu, &start_selection, NULL, false)){
648 case 1: /* retry */
649 state = STATE_FAILED;
650 break;
652 case 2: /* Audio playback */
653 playback_control(NULL);
654 state = STATE_IN_LEVEL;
655 break;
657 case 3: /* quit */
658 state = STATE_QUIT;
659 break;
661 case MENU_ATTACHED_USB:
662 state = STATE_USB_CONNECTED;
663 break;
665 default: /* Back */
666 state = STATE_IN_LEVEL;
667 break;
671 /*****************************************************************************
672 * Is the level a checkpoint
673 ******************************************************************************/
674 static bool at_checkpoint(int level)
676 if (level <= MAZEZAM_FIRST_CHECKPOINT)
677 return level == MAZEZAM_FIRST_CHECKPOINT;
678 else {
679 level = level - MAZEZAM_FIRST_CHECKPOINT;
680 return level % MAZEZAM_CHECKPOINT_INTERVAL == 0;
684 /*****************************************************************************
685 * Set up and play a level
686 * new_level should be true if this is the first time we've encountered
687 * this level
688 ******************************************************************************/
689 static void play_level(short level, short lives, bool new_level)
691 struct level_info li;
692 short shift[MAZEZAM_MAX_LINES]; /* amount each line has been shifted */
693 short x,y;
694 int i;
696 state = STATE_IN_LEVEL;
698 if (!(parse_level(level,&li)))
699 state = STATE_PARSE_ERROR;
701 for (i = 0; i < li.height; i++)
702 shift[i] = 0;
704 x = -1;
705 y = li.entrance;
707 plugin_lcd_settings();
708 rb->lcd_clear_display();
710 draw_level(&li, shift, x, y);
712 /* If we've just reached a checkpoint, then alert the player */
713 if (new_level && at_checkpoint(level)) {
714 rb->splash(MAZEZAM_DELAY_CHECKPOINT, MAZEZAM_TEXT_CHECKPOINT);
715 /* Clear the splash */
716 draw_level(&li, shift, x, y);
719 #ifdef HAVE_REMOTE_LCD
720 /* Splash text seems to use the remote display by
721 * default. I suppose I better keep it tidy!
723 rb->lcd_remote_clear_display();
724 #endif
725 rb->splashf(MAZEZAM_DELAY_LIVES, MAZEZAM_TEXT_LIVES,
726 level+1, lives);
728 /* ensure keys pressed during the splash screen are ignored */
729 rb->button_clear_queue();
731 /* this little loop just ensures we return to the game if the player
732 * doesn't perform an interesting action during the in game menu */
733 while (state >= STATE_IN_LEVEL) {
734 level_loop(&li, shift, &x, &y);
736 if (state == STATE_GAME_MENU) {
737 restore_lcd_settings();
738 in_game_menu();
739 plugin_lcd_settings();
742 restore_lcd_settings();
745 /*****************************************************************************
746 * Update the resume data based on the level reached
747 ******************************************************************************/
748 static void update_resume_data(struct resume_data *r, int level)
750 if (at_checkpoint(level))
751 r->level = level;
754 /*****************************************************************************
755 * The loop which manages a full game of MazezaM.
756 ******************************************************************************/
757 static void game_loop(struct resume_data *r)
759 int level = r->level;
760 int lives = MAZEZAM_START_LIVES;
761 /* We want to know when a player reaches a level for the first time,
762 * so we keep a second copy of the level. */
763 int old_level = level;
765 state = STATE_IN_GAME;
767 while (state >= STATE_IN_GAME)
769 play_level(level, lives, old_level < level);
770 old_level = level;
772 switch (state) {
773 case STATE_COMPLETED:
774 level += 1;
775 if (level == MAZEZAM_NUM_LEVELS)
776 state = STATE_WELLDONE;
777 break;
779 case STATE_FAILED:
780 lives -= 1;
781 if (lives == 0)
782 state = STATE_GAME_OVER;
783 break;
785 default:
786 break;
789 update_resume_data(r,level);
792 switch (state) {
793 case STATE_GAME_OVER:
794 #ifdef HAVE_REMOTE_LCD
795 /* Splash text seems to use the remote display by
796 * default. I suppose I better keep it tidy!
798 rb->lcd_remote_clear_display();
799 #endif
800 rb->splash(MAZEZAM_DELAY_GAME_OVER, MAZEZAM_TEXT_GAME_OVER);
801 break;
803 case STATE_WELLDONE:
804 r->level = 0;
805 welldone_screen();
806 break;
808 default:
809 break;
813 /*****************************************************************************
814 * Load the resume data from the config file. The data is
815 * stored in both r and old.
816 ******************************************************************************/
817 static void resume_load_data (struct resume_data *r, struct resume_data *old)
819 struct configdata config[] = {
820 {TYPE_INT,0,MAZEZAM_NUM_LEVELS-1, { .int_p = &(r->level) },
821 MAZEZAM_CONFIG_LEVELS_NAME,NULL}
824 if (configfile_load(MAZEZAM_CONFIG_FILENAME,config,
825 MAZEZAM_CONFIG_NUM_ITEMS, MAZEZAM_CONFIG_VERSION) < 0)
826 r->level = 0;
827 /* an extra precaution */
828 else if ((r->level < 0) || (MAZEZAM_NUM_LEVELS <= r->level))
829 r->level = 0;
831 old->level = r->level;
834 /*****************************************************************************
835 * Save the resume data in the config file, but only if necessary
836 ******************************************************************************/
837 static void resume_save_data (struct resume_data *r, struct resume_data *old)
839 struct configdata config[] = {
840 {TYPE_INT,0,MAZEZAM_NUM_LEVELS-1, {.int_p = &(r->level) },
841 MAZEZAM_CONFIG_LEVELS_NAME,NULL}
844 /* To reduce disk usage, only write the file if the resume data has
845 * changed.
847 if (old->level != r->level)
848 configfile_save(MAZEZAM_CONFIG_FILENAME,config,
849 MAZEZAM_CONFIG_NUM_ITEMS, MAZEZAM_CONFIG_MINVERSION);
852 /*****************************************************************************
853 * Manages the main menu
854 ******************************************************************************/
855 static bool have_continue;
856 static int main_menu_cb(int action, const struct menu_item_ex *this_item)
858 if(action == ACTION_REQUEST_MENUITEM
859 && !have_continue && ((intptr_t)this_item)==0)
860 return ACTION_EXIT_MENUITEM;
861 return action;
863 static void main_menu(void)
865 /* The initial option is "play game" */
866 int start_selection = 0;
867 int choice = 0;
868 struct resume_data r_data, old_data;
870 /* Load data */
871 resume_load_data(&r_data, &old_data);
873 MENUITEM_STRINGLIST(menu,MAZEZAM_TEXT_MAIN_MENU,main_menu_cb,
874 MAZEZAM_TEXT_CONTINUE,
875 MAZEZAM_TEXT_PLAY_NEW_GAME,
876 MAZEZAM_TEXT_AUDIO_PLAYBACK,
877 MAZEZAM_TEXT_QUIT);
879 while (state >= STATE_IN_APPLICATION) {
880 have_continue = (r_data.level != 0);
881 choice = rb->do_menu(&menu, &start_selection, NULL, false);
883 switch(choice) {
884 case 0: /* Continue */
885 state = STATE_IN_GAME;
886 game_loop(&r_data);
887 break;
889 case 1: /* Play new game */
890 r_data.level = 0;
891 state = STATE_IN_GAME;
892 game_loop(&r_data);
893 break;
895 case 2: /* Audio playback */
896 playback_control(NULL);
897 break;
899 case MENU_ATTACHED_USB:
900 state = STATE_USB_CONNECTED;
901 break;
903 default: /* Quit */
904 state = STATE_QUIT;
905 break;
909 /* I'm not sure if it's appropriate to write to disk on USB events.
910 * Currently, I do so.
912 resume_save_data(&r_data, &old_data);
915 /*****************************************************************************
916 * Plugin entry point
917 ******************************************************************************/
918 enum plugin_status plugin_start(const void* parameter)
920 enum plugin_status plugin_state;
922 /* Usual plugin stuff */
923 (void)parameter;
925 store_lcd_settings();
927 state = STATE_MAIN_MENU;
928 main_menu();
930 switch (state) {
931 case STATE_USB_CONNECTED:
932 plugin_state = PLUGIN_USB_CONNECTED;
933 break;
935 case STATE_PARSE_ERROR:
936 plugin_state = PLUGIN_ERROR;
937 break;
939 default:
940 plugin_state = PLUGIN_OK;
941 break;
944 return plugin_state;