fix wrong score recording.
[kugel-rb.git] / apps / plugins / mazezam.c
bloba19c8a027e92e948eadf49a273f68ae5aef2c443
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 */
28 PLUGIN_HEADER
30 #if (CONFIG_KEYPAD == IPOD_4G_PAD) || \
31 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
32 # define MAZEZAM_MENU (BUTTON_SELECT | BUTTON_MENU)
33 # define MAZEZAM_RIGHT BUTTON_RIGHT
34 # define MAZEZAM_LEFT BUTTON_LEFT
35 # define MAZEZAM_UP BUTTON_MENU
36 # define MAZEZAM_DOWN BUTTON_PLAY
38 #elif (CONFIG_KEYPAD == IPOD_3G_PAD)
39 # define MAZEZAM_MENU BUTTON_MENU
40 # define MAZEZAM_RIGHT BUTTON_RIGHT
41 # define MAZEZAM_LEFT BUTTON_LEFT
42 # define MAZEZAM_UP BUTTON_SCROLL_BACK
43 # define MAZEZAM_DOWN BUTTON_SCROLL_FWD
45 #elif (CONFIG_KEYPAD == SANSA_FUZE_PAD)
46 # define MAZEZAM_MENU (BUTTON_HOME | BUTTON_REPEAT)
47 # define MAZEZAM_RIGHT BUTTON_RIGHT
48 # define MAZEZAM_LEFT BUTTON_LEFT
49 # define MAZEZAM_UP BUTTON_UP
50 # define MAZEZAM_DOWN BUTTON_DOWN
52 #elif (CONFIG_KEYPAD == SANSA_E200_PAD)
53 # define MAZEZAM_MENU BUTTON_POWER
54 # define MAZEZAM_SOLVE BUTTON_SELECT
55 # define MAZEZAM_RIGHT BUTTON_RIGHT
56 # define MAZEZAM_LEFT BUTTON_LEFT
57 # define MAZEZAM_UP BUTTON_UP
58 # define MAZEZAM_DOWN BUTTON_DOWN
60 #else
61 # include "lib/pluginlib_actions.h"
62 # define MAZEZAM_MENU PLA_QUIT
63 # define MAZEZAM_RIGHT PLA_RIGHT
64 # define MAZEZAM_LEFT PLA_LEFT
65 # define MAZEZAM_UP PLA_UP
66 # define MAZEZAM_DOWN PLA_DOWN
67 const struct button_mapping *plugin_contexts[]
68 = {generic_directions, generic_actions};
69 #endif
71 /* All the text is here */
72 #define MAZEZAM_TEXT_GAME_OVER "Game Over"
73 #define MAZEZAM_TEXT_LIVES "Level %d, Lives %d"
74 #define MAZEZAM_TEXT_CHECKPOINT "Checkpoint reached"
75 #define MAZEZAM_TEXT_WELLDONE_TITLE "You have escaped!"
76 #define MAZEZAM_TEXT_WELLDONE_OPTION "Goodbye"
77 #define MAZEZAM_TEXT_MAZEZAM_MENU "MazezaM Menu"
78 #define MAZEZAM_TEXT_RETRY_LEVEL "Retry level"
79 #define MAZEZAM_TEXT_AUDIO_PLAYBACK "Playback Control"
80 #define MAZEZAM_TEXT_QUIT "Quit"
81 #define MAZEZAM_TEXT_BACK "Resume Game"
82 #define MAZEZAM_TEXT_MAIN_MENU "MazezaM"
83 #define MAZEZAM_TEXT_CONTINUE "Play from checkpoint"
84 #define MAZEZAM_TEXT_PLAY_NEW_GAME "Start New Game"
86 #define MAZEZAM_START_LIVES 3 /* how many lives at game start */
87 #define MAZEZAM_FIRST_CHECKPOINT 3 /* The level at the first checkpoint */
88 #define MAZEZAM_CHECKPOINT_INTERVAL 4 /* A checkpoint every _ levels */
90 #ifdef HAVE_LCD_COLOR
91 #define MAZEZAM_HEADING_COLOR LCD_RGBPACK(255,255, 0) /* Yellow */
92 #define MAZEZAM_BORDER_COLOR LCD_RGBPACK( 0, 0,255) /* Blue */
93 #define MAZEZAM_COLOR LCD_RGBPACK(255,255,255) /* White */
94 #define MAZEZAM_BG_COLOR LCD_RGBPACK( 0, 0, 0) /* Black */
95 #define MAZEZAM_WALL_COLOR LCD_RGBPACK(100,100,100) /* Dark gray */
96 #define MAZEZAM_PLAYER_COLOR LCD_RGBPACK(255,255,255) /* White */
97 #define MAZEZAM_GATE_COLOR LCD_RGBPACK(100,100,100) /* Dark gray */
99 /* the rows are coloured sequentially */
100 #define MAZEZAM_NUM_CHUNK_COLORS 8
101 static const unsigned chunk_colors[MAZEZAM_NUM_CHUNK_COLORS] = {
102 LCD_RGBPACK(255,192, 32), /* Orange */
103 LCD_RGBPACK(255, 0, 0), /* Red */
104 LCD_RGBPACK( 0,255, 0), /* Green */
105 LCD_RGBPACK( 0,255,255), /* Cyan */
106 LCD_RGBPACK(255,175,175), /* Pink */
107 LCD_RGBPACK(255,255, 0), /* Yellow */
108 LCD_RGBPACK( 0, 0,255), /* Blue */
109 LCD_RGBPACK(255, 0,255), /* Magenta */
112 #elif LCD_DEPTH > 1
114 #define MAZEZAM_HEADING_GRAY LCD_BLACK
115 #define MAZEZAM_BORDER_GRAY LCD_DARKGRAY
116 #define MAZEZAM_GRAY LCD_BLACK
117 #define MAZEZAM_BG_GRAY LCD_WHITE
118 #define MAZEZAM_WALL_GRAY LCD_DARKGRAY
119 #define MAZEZAM_PLAYER_GRAY LCD_BLACK
120 #define MAZEZAM_GATE_GRAY LCD_BLACK
121 #define MAZEZAM_CHUNK_EDGE_GRAY LCD_BLACK
123 #define MAZEZAM_NUM_CHUNK_GRAYS 2
124 static const unsigned chunk_gray[MAZEZAM_NUM_CHUNK_GRAYS] = {
125 LCD_LIGHTGRAY,
126 LCD_DARKGRAY,
128 /* darker version of the above */
129 static const unsigned chunk_gray_shade[MAZEZAM_NUM_CHUNK_GRAYS] = {
130 LCD_DARKGRAY,
131 LCD_BLACK,
133 #endif
135 #define MAZEZAM_DELAY_CHECKPOINT HZ
136 #define MAZEZAM_DELAY_LIVES HZ
137 #define MAZEZAM_DELAY_GAME_OVER (3 * HZ) / 2
139 /* maximum height of a level */
140 #define MAZEZAM_MAX_LINES 11
141 /* maximum number of chunks on a line */
142 #define MAZEZAM_MAX_CHUNKS 5
144 /* A structure for storing level data in unparsed form */
145 struct mazezam_level {
146 short height; /* the number of lines */
147 short width; /* the width */
148 short entrance; /* the line on which the entrance lies */
149 short exit; /* the line on which the exit lies */
150 char *line[MAZEZAM_MAX_LINES]; /* the chunk data in string form */
153 /* The number of levels. */
154 #define MAZEZAM_NUM_LEVELS 10
156 /* The levels. In theory, they could be stored in a file so this data
157 * structure should not be accessed outside parse_level()
159 * These levels are copyright (C) 2002 Malcolm Tyrrell. They're
160 * probably covered by the GPL as they constitute part of the source
161 * code of this plugin, but you may distibute them seperately with
162 * other Free Software if you want. You can download them from:
163 * http://webpages.dcu.ie/~tyrrelma/MazezaM.
165 static const struct mazezam_level level_data[MAZEZAM_NUM_LEVELS] = {
166 {2,7,0,0,{" $ $"," $ $$"}},
167 {3,8,2,1,{" $ $$$"," $ $ $"," $ $ $"}},
168 {4,14,1,3,{" $$$$$ $$ $$"," $$ $$ $$","$$ $ $$ $$$",
169 " $$$$$$$$ $"}},
170 {6,7,4,2,{" $"," $$$$"," $$$ $$"," $ $ $"," $ $$","$ $$"}},
171 {6,13,0,0,{" $$$$$","$ $$$$$ $$$"," $ $$$ $$$$",
172 "$ $ $$$$$$$"," $$$ $ $$","$ $ $ $$ $"}},
173 {11,5,10,0,{" $"," $ $$"," $$","$ $"," $ $"," $$$","$ $",
174 " $ $"," $ $","$ $$"," $"}},
175 {7,16,0,6,{" $$$$$$$"," $$$$ $$$$ $ $","$$ $$ $$$$$$ $ $",
176 "$ $ $"," $$$$$$$$$$$$$$"," $ $$ $ $$$",
177 " $ $$$ $$"}},
178 {4,15,2,0,{" $$$$ $$$$ $$"," $ $$ $$ $ $$"," $ $$ $$$$ $$",
179 " $ $$ $$$$ $"}},
180 {7,9,6,2,{" $ $$$$"," $ $ $$"," $ $$$$ $","$ $$ $"," $ $$$",
181 " $$$$$$"," $"}},
182 {10,14,8,0,{" $"," $$$$$$$$$$ $"," $$$ $$",
183 " $ $$$$$$$$ $"," $$$ $$$ $$$"," $$$ $ $$$",
184 " $ $$$$$$$ $$"," $ $ $ $$$"," $$$$$$$$$$$$",
185 ""}}
188 /* This data structure which holds information about the rows */
189 struct chunk_data {
190 /* the number of chunks on a line */
191 short l_num[MAZEZAM_MAX_LINES];
192 /* the width of a chunk */
193 short c_width[MAZEZAM_MAX_LINES][MAZEZAM_MAX_CHUNKS];
194 /* the inset of a chunk */
195 short c_inset[MAZEZAM_MAX_LINES][MAZEZAM_MAX_CHUNKS];
198 /* Parsed level data */
199 struct level_info {
200 short width;
201 short height;
202 short entrance;
203 short exit;
204 struct chunk_data cd;
207 /* The state variable used to hold the state of the plugin */
208 static enum {
209 STATE_QUIT, /* The player wants to quit */
210 STATE_USB_CONNECTED, /* A USB cable has been inserted */
211 STATE_PARSE_ERROR, /* There's a parse error in the levels */
212 STATE_WELLDONE, /* The player has finished the game */
214 STATE_IN_APPLICATION,
216 STATE_MAIN_MENU /* The player is at the main menu */
217 = STATE_IN_APPLICATION,
218 STATE_GAME_OVER, /* The player is out of lives */
220 STATE_IN_GAME,
222 STATE_COMPLETED /* A level has been completed */
223 = STATE_IN_GAME,
225 STATE_FAILED, /* The player wants to retry the level */
226 STATE_GAME_MENU, /* The player wan't to access the in-game menu */
228 STATE_IN_LEVEL,
229 } state;
231 /* The various constants needed for configuration files.
232 * See apps/plugins/lib/configfile.*
234 #define MAZEZAM_CONFIG_FILENAME "mazezam.data"
235 #define MAZEZAM_CONFIG_NUM_ITEMS 1
236 #define MAZEZAM_CONFIG_VERSION 0
237 #define MAZEZAM_CONFIG_MINVERSION 0
238 #define MAZEZAM_CONFIG_LEVELS_NAME "restart_level"
240 /* A structure containing the data that is written to
241 * the configuration file
243 struct resume_data {
244 int level; /* level at which to restart the game */
247 #if LCD_DEPTH > 1
248 /* Store the display settings so they are reintroduced during menus */
249 static struct {
250 fb_data* backdrop;
251 unsigned foreground;
252 unsigned background;
253 } lcd_settings;
254 #endif
256 /*****************************************************************************
257 * Store the LCD settings
258 ******************************************************************************/
259 static void store_lcd_settings(void)
261 /* Store the old settings */
262 #if LCD_DEPTH > 1
263 lcd_settings.backdrop = rb->lcd_get_backdrop();
264 lcd_settings.foreground = rb->lcd_get_foreground();
265 lcd_settings.background = rb->lcd_get_background();
266 #endif
269 /*****************************************************************************
270 * Restore the LCD settings to their defaults
271 ******************************************************************************/
272 static void restore_lcd_settings(void) {
273 /* Turn on backlight timeout (revert to settings) */
274 backlight_use_settings(); /* backlight control in lib/helper.c */
276 /* Restore the old settings */
277 #if LCD_DEPTH > 1
278 rb->lcd_set_foreground(lcd_settings.foreground);
279 rb->lcd_set_background(lcd_settings.background);
280 rb->lcd_set_backdrop(lcd_settings.backdrop);
281 #endif
284 /*****************************************************************************
285 * Adjust the LCD settings to suit MazezaM levels
286 ******************************************************************************/
287 static void plugin_lcd_settings(void) {
288 /* Turn off backlight timeout */
289 backlight_force_on(); /* backlight control in lib/helper.c */
291 /* Set the new settings */
292 #ifdef HAVE_LCD_COLOR
293 rb->lcd_set_background(MAZEZAM_BG_COLOR);
294 rb->lcd_set_backdrop(NULL);
295 #elif LCD_DEPTH > 1
296 rb->lcd_set_background(MAZEZAM_BG_GRAY);
297 rb->lcd_set_backdrop(NULL);
298 #endif
301 /*****************************************************************************
302 * Parse the level data from the level_data structure. This could be
303 * replaced by a file read. Returns true if the level parsed correctly.
304 ******************************************************************************/
305 static bool parse_level(short level, struct level_info* li)
307 int i,j;
308 char c,clast;
310 li->width = level_data[level].width;
311 li->height = level_data[level].height;
312 li->entrance = level_data[level].entrance;
313 li->exit = level_data[level].exit;
315 /* for each line in the level */
316 for (i = 0; i<level_data[level].height; i++) {
317 if (level_data[level].line[i] == NULL)
318 return false;
319 else {
320 j = 0;
321 li->cd.l_num[i] = 0;
322 clast = ' '; /* the character we last considered */
323 while ((c = level_data[level].line[i][j]) != '\0') {
324 if (c != ' ') {
325 if (clast == ' ') {
326 li->cd.l_num[i] += 1;
327 if (li->cd.l_num[i] > MAZEZAM_MAX_CHUNKS)
328 return false;
329 li->cd.c_inset[i][li->cd.l_num[i] - 1] = j;
330 li->cd.c_width[i][li->cd.l_num[i] - 1] = 1;
332 else
333 li->cd.c_width[i][li->cd.l_num[i] - 1] += 1;
335 clast = c;
336 j++;
340 return true;
343 /*****************************************************************************
344 * Draw the walls of a level
345 ******************************************************************************/
346 static void draw_walls(
347 short size,
348 short xOff,
349 short yOff,
350 short width,
351 short height,
352 short entrance,
353 short exit)
355 #ifdef HAVE_LCD_COLOR
356 rb->lcd_set_foreground(MAZEZAM_WALL_COLOR);
357 #elif LCD_DEPTH > 1
358 rb->lcd_set_foreground(MAZEZAM_WALL_GRAY);
359 #endif
360 /* draw the upper wall */
361 rb->lcd_fillrect(0,0,xOff,yOff+(size*entrance));
362 rb->lcd_fillrect(xOff,0,size*width,yOff);
363 rb->lcd_fillrect(xOff+(size*width),0,LCD_WIDTH-xOff-(size*width),
364 yOff+(size*exit));
366 /* draw the lower wall */
367 rb->lcd_fillrect(0,yOff+(size*entrance)+size,xOff,
368 LCD_HEIGHT-yOff-(size*entrance)-size);
369 rb->lcd_fillrect(xOff,yOff+(size*height),size*width,
370 LCD_HEIGHT-yOff-(size*height));
371 /* Note: the exit is made one pixel thinner than necessary as a visual
372 * clue that chunks cannot be pushed into it
374 rb->lcd_fillrect(xOff+(size*width),yOff+(size*exit)+size-1,
375 LCD_WIDTH-xOff+(size*width),
376 LCD_HEIGHT-yOff-(size*exit)-size+1);
379 /*****************************************************************************
380 * Draw chunk row i
381 ******************************************************************************/
382 static void draw_row(
383 short size,
384 short xOff,
385 short yOff,
386 short width,
387 short i, /* the row number */
388 struct chunk_data *cd, /* the data about the chunks */
389 short *shift /* an array of the horizontal offset of the lines */
392 /* The assignment below is just a hack to make supress a warning on
393 * non color targets */
394 short j = width;
395 #ifndef HAVE_LCD_COLOR
396 /* We #def these out to supress a compiler warning */
397 short k;
398 #if LCD_DEPTH <= 1
399 short l;
400 #endif
401 #endif
402 #ifdef HAVE_LCD_COLOR
403 /* adding width to i should have a fixed, but randomising effect on
404 * the choice of the colours of the top line of chunks
406 rb->lcd_set_foreground(chunk_colors[(i+width) %
407 MAZEZAM_NUM_CHUNK_COLORS]);
408 #endif
409 for (j = 0; j<cd->l_num[i]; j++) {
410 #ifdef HAVE_LCD_COLOR
411 rb->lcd_fillrect(xOff+size*shift[i]+size*cd->c_inset[i][j],
412 yOff+size*i, cd->c_width[i][j]*size,size);
413 #elif LCD_DEPTH > 1
414 rb->lcd_set_foreground(MAZEZAM_CHUNK_EDGE_GRAY);
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);
418 /* draw shade */
419 rb->lcd_set_foreground(chunk_gray_shade[(i+width) %
420 MAZEZAM_NUM_CHUNK_GRAYS]);
421 rb->lcd_hline(xOff+size*shift[i]+size*cd->c_inset[i][j]+1,
422 xOff+size*shift[i]+size*cd->c_inset[i][j]+
423 cd->c_width[i][j]*size-3,
424 yOff+size*i+size-2);
425 rb->lcd_vline(xOff+size*shift[i]+size*cd->c_inset[i][j]+
426 cd->c_width[i][j]*size-2,
427 yOff+size*i,
428 yOff+size*i+size-2);
430 /* draw fill */
431 rb->lcd_set_foreground(chunk_gray[(i+width) %
432 MAZEZAM_NUM_CHUNK_GRAYS]);
433 for (k = yOff+size*i+2; k < yOff+size*i+size-2; k += 2)
434 rb->lcd_hline(xOff+size*shift[i]+size*cd->c_inset[i][j]+2,
435 xOff+size*shift[i]+size*cd->c_inset[i][j]+
436 cd->c_width[i][j]*size-3,k);
437 #else
438 rb->lcd_drawrect(xOff+size*shift[i]+size*cd->c_inset[i][j],
439 yOff+size*i, cd->c_width[i][j]*size,size);
440 for (k = xOff+size*shift[i]+size*cd->c_inset[i][j]+2;
441 k < xOff+size*shift[i]+size*cd->c_inset[i][j]+
442 cd->c_width[i][j]*size;
443 k += 2 + (i & 1))
444 for (l = yOff+size*i+2; l < yOff+size*i+size; l += 2 + (i & 1))
445 rb->lcd_drawpixel(k, l);
446 #endif
450 /*****************************************************************************
451 * Draw the player
452 ******************************************************************************/
453 static void draw_player(
454 short size,
455 short xOff,
456 short yOff,
457 short x,
458 short y)
460 /* For drawing the player, taken from the sokoban plugin */
461 short max = size - 1;
462 short middle = max / 2;
463 short ldelta = (middle + 1) / 2;
465 /* draw the player (mostly copied from the sokoban plugin) */
466 #ifdef HAVE_LCD_COLOR
467 rb->lcd_set_foreground(MAZEZAM_PLAYER_COLOR);
468 #elif LCD_DEPTH > 1
469 rb->lcd_set_foreground(MAZEZAM_PLAYER_GRAY);
470 #endif
471 rb->lcd_hline(xOff+size*x, xOff+size*x+max, yOff+size*y+middle);
472 rb->lcd_vline(xOff+size*x+middle, yOff+size*y, yOff+size*y+max-ldelta);
473 rb->lcd_drawline(xOff+size*x+middle, yOff+size*y+max-ldelta,
474 xOff+size*x+middle-ldelta, yOff+size*y+max);
475 rb->lcd_drawline(xOff+size*x+middle, yOff+size*y+max-ldelta,
476 xOff+size*x+middle+ldelta, yOff+size*y+max);
479 /*****************************************************************************
480 * Draw the gate
481 ******************************************************************************/
482 static void draw_gate(
483 short size,
484 short xOff,
485 short yOff,
486 short entrance)
488 short third = size / 3;
489 short twothirds = (2 * size) / 3;
490 #ifdef HAVE_LCD_COLOR
491 rb->lcd_set_foreground(MAZEZAM_GATE_COLOR);
492 #elif LCD_DEPTH > 1
493 rb->lcd_set_foreground(MAZEZAM_GATE_GRAY);
494 #endif
495 rb->lcd_hline(xOff-size,xOff-1,yOff+entrance*size+third);
496 rb->lcd_hline(xOff-size,xOff-1,yOff+entrance*size+twothirds);
497 rb->lcd_vline(xOff-size+third,yOff+entrance*size,
498 yOff+entrance*size+size-1);
499 rb->lcd_vline(xOff-size+twothirds,yOff+entrance*size,
500 yOff+entrance*size+size-1);
503 /*****************************************************************************
504 * Draw the level
505 ******************************************************************************/
506 static void draw_level(
507 struct level_info* li,
508 short *shift, /* an array of the horizontal offset of the lines */
509 short x, /* player's x and y coords */
510 short y)
512 /* First we calculate the draw info */
513 /* The number of pixels the side of a square should be */
514 short size = (LCD_WIDTH/(li->width+2)) < (LCD_HEIGHT/li->height) ?
515 (LCD_WIDTH/(li->width+2)) : (LCD_HEIGHT/li->height);
516 /* The x and y position (in pixels) of the top left corner of the
517 * level
519 short xOff = (LCD_WIDTH - (size*li->width))/2;
520 short yOff = (LCD_HEIGHT - (size*li->height))/2;
521 short i;
523 rb->lcd_clear_display();
525 draw_walls(size,xOff,yOff,li->width, li->height, li->entrance, li->exit);
527 /* draw the chunks */
528 for (i = 0; i<li->height; i++) {
529 draw_row(size,xOff,yOff,li->width,i,&(li->cd),shift);
532 draw_player(size,xOff,yOff,x,y);
534 /* if the player has moved into the level, draw the gate */
535 if (x >= 0)
536 draw_gate(size,xOff,yOff,li->entrance);
539 /*****************************************************************************
540 * Manage the congratulations screen
541 ******************************************************************************/
542 static void welldone_screen(void)
544 int start_selection = 0;
546 MENUITEM_STRINGLIST(menu,MAZEZAM_TEXT_WELLDONE_TITLE,NULL,
547 MAZEZAM_TEXT_WELLDONE_OPTION);
549 switch(rb->do_menu(&menu, &start_selection, NULL, true)){
550 case MENU_ATTACHED_USB:
551 state = STATE_USB_CONNECTED;
552 break;
556 /*****************************************************************************
557 * Manage the playing of a level
558 ******************************************************************************/
559 static void level_loop(struct level_info* li, short* shift, short *x, short *y)
561 int i;
562 int button;
563 bool blocked; /* is there a chunk in the way of the player? */
565 while (state >= STATE_IN_LEVEL) {
566 draw_level(li, shift, *x, *y);
567 rb->lcd_update();
568 #ifdef __PLUGINLIB_ACTIONS_H__
569 button = pluginlib_getaction(TIMEOUT_BLOCK, plugin_contexts, 2);
570 #else
571 button = rb->button_get(true);
572 #endif
573 blocked = false;
575 switch (button) {
576 case MAZEZAM_UP:
577 case (MAZEZAM_UP|BUTTON_REPEAT):
578 if ((*y > 0) && (*x >= 0) && (*x < li->width)) {
579 for (i = 0; i < li->cd.l_num[*y-1]; i++)
580 blocked = blocked ||
581 ((*x>=shift[*y-1]+li->cd.c_inset[*y-1][i]) &&
582 (*x<shift[*y-1]+li->cd.c_inset[*y-1][i]+
583 li->cd.c_width[*y-1][i]));
584 if (!blocked) *y -= 1;
586 break;
590 case MAZEZAM_DOWN:
591 case (MAZEZAM_DOWN|BUTTON_REPEAT):
592 if ((*y < li->height-1) && (*x >= 0) && (*x < li->width)) {
593 for (i = 0; i < li->cd.l_num[*y+1]; i++)
594 blocked = blocked ||
595 ((*x>=shift[*y+1]+li->cd.c_inset[*y+1][i]) &&
596 (*x<shift[*y+1]+li->cd.c_inset[*y+1][i]+
597 li->cd.c_width[*y+1][i]));
598 if (!blocked) *y += 1;
600 break;
602 case MAZEZAM_LEFT:
603 case (MAZEZAM_LEFT|BUTTON_REPEAT):
604 if (*x > 0) {
605 for (i = 0; i < li->cd.l_num[*y]; i++)
606 blocked = blocked ||
607 (*x == shift[*y]+li->cd.c_inset[*y][i]+
608 li->cd.c_width[*y][i]);
609 if (!blocked) *x -= 1;
610 else if (shift[*y] + li->cd.c_inset[*y][0] > 0) {
611 *x -= 1;
612 shift[*y] -= 1;
615 break;
617 case MAZEZAM_RIGHT:
618 case (MAZEZAM_RIGHT|BUTTON_REPEAT):
619 if (*x < li->width-1) {
620 for (i = 0; i < li->cd.l_num[*y]; i++)
621 blocked = blocked ||
622 (*x+1 == shift[*y]+li->cd.c_inset[*y][i]);
623 if (!blocked) *x += 1;
624 else if (shift[*y]
625 + li->cd.c_inset[*y][li->cd.l_num[*y]-1]
626 + li->cd.c_width[*y][li->cd.l_num[*y]-1]
627 < li->width) {
628 *x += 1;
629 shift[*y] += 1;
632 else if (*x == li->width) state = STATE_COMPLETED;
633 else if (*y == li->exit) *x += 1;
634 break;
636 case MAZEZAM_MENU:
637 state = STATE_GAME_MENU;
638 break;
640 default:
641 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
642 state = STATE_USB_CONNECTED;
643 break;
648 /*****************************************************************************
649 * Manage the in game menu
650 ******************************************************************************/
651 static void in_game_menu(void)
653 /* The initial option is retry level */
654 int start_selection = 1;
656 MENUITEM_STRINGLIST(menu,MAZEZAM_TEXT_MAZEZAM_MENU, NULL,
657 MAZEZAM_TEXT_BACK,
658 MAZEZAM_TEXT_RETRY_LEVEL,
659 MAZEZAM_TEXT_AUDIO_PLAYBACK,
660 MAZEZAM_TEXT_QUIT);
662 /* Don't show the status bar */
663 switch(rb->do_menu(&menu, &start_selection, NULL, false)){
664 case 1: /* retry */
665 state = STATE_FAILED;
666 break;
668 case 2: /* Audio playback */
669 playback_control(NULL);
670 state = STATE_IN_LEVEL;
671 break;
673 case 3: /* quit */
674 state = STATE_QUIT;
675 break;
677 case MENU_ATTACHED_USB:
678 state = STATE_USB_CONNECTED;
679 break;
681 default: /* Back */
682 state = STATE_IN_LEVEL;
683 break;
687 /*****************************************************************************
688 * Is the level a checkpoint
689 ******************************************************************************/
690 static bool at_checkpoint(int level)
692 if (level <= MAZEZAM_FIRST_CHECKPOINT)
693 return level == MAZEZAM_FIRST_CHECKPOINT;
694 else {
695 level = level - MAZEZAM_FIRST_CHECKPOINT;
696 return level % MAZEZAM_CHECKPOINT_INTERVAL == 0;
700 /*****************************************************************************
701 * Set up and play a level
702 * new_level should be true if this is the first time we've encountered
703 * this level
704 ******************************************************************************/
705 static void play_level(short level, short lives, bool new_level)
707 struct level_info li;
708 short shift[MAZEZAM_MAX_LINES]; /* amount each line has been shifted */
709 short x,y;
710 int i;
712 state = STATE_IN_LEVEL;
714 if (!(parse_level(level,&li)))
715 state = STATE_PARSE_ERROR;
717 for (i = 0; i < li.height; i++)
718 shift[i] = 0;
720 x = -1;
721 y = li.entrance;
723 plugin_lcd_settings();
724 rb->lcd_clear_display();
726 draw_level(&li, shift, x, y);
728 /* If we've just reached a checkpoint, then alert the player */
729 if (new_level && at_checkpoint(level)) {
730 rb->splash(MAZEZAM_DELAY_CHECKPOINT, MAZEZAM_TEXT_CHECKPOINT);
731 /* Clear the splash */
732 draw_level(&li, shift, x, y);
735 #ifdef HAVE_REMOTE_LCD
736 /* Splash text seems to use the remote display by
737 * default. I suppose I better keep it tidy!
739 rb->lcd_remote_clear_display();
740 #endif
741 rb->splashf(MAZEZAM_DELAY_LIVES, MAZEZAM_TEXT_LIVES,
742 level+1, lives);
744 /* ensure keys pressed during the splash screen are ignored */
745 rb->button_clear_queue();
747 /* this little loop just ensures we return to the game if the player
748 * doesn't perform an interesting action during the in game menu */
749 while (state >= STATE_IN_LEVEL) {
750 level_loop(&li, shift, &x, &y);
752 if (state == STATE_GAME_MENU) {
753 restore_lcd_settings();
754 in_game_menu();
755 plugin_lcd_settings();
758 restore_lcd_settings();
761 /*****************************************************************************
762 * Update the resume data based on the level reached
763 ******************************************************************************/
764 static void update_resume_data(struct resume_data *r, int level)
766 if (at_checkpoint(level))
767 r->level = level;
770 /*****************************************************************************
771 * The loop which manages a full game of MazezaM.
772 ******************************************************************************/
773 static void game_loop(struct resume_data *r)
775 int level = r->level;
776 int lives = MAZEZAM_START_LIVES;
777 /* We want to know when a player reaches a level for the first time,
778 * so we keep a second copy of the level. */
779 int old_level = level;
781 state = STATE_IN_GAME;
783 while (state >= STATE_IN_GAME)
785 play_level(level, lives, old_level < level);
786 old_level = level;
788 switch (state) {
789 case STATE_COMPLETED:
790 level += 1;
791 if (level == MAZEZAM_NUM_LEVELS)
792 state = STATE_WELLDONE;
793 break;
795 case STATE_FAILED:
796 lives -= 1;
797 if (lives == 0)
798 state = STATE_GAME_OVER;
799 break;
801 default:
802 break;
805 update_resume_data(r,level);
808 switch (state) {
809 case STATE_GAME_OVER:
810 #ifdef HAVE_REMOTE_LCD
811 /* Splash text seems to use the remote display by
812 * default. I suppose I better keep it tidy!
814 rb->lcd_remote_clear_display();
815 #endif
816 rb->splash(MAZEZAM_DELAY_GAME_OVER, MAZEZAM_TEXT_GAME_OVER);
817 break;
819 case STATE_WELLDONE:
820 welldone_screen();
821 break;
823 default:
824 break;
828 /*****************************************************************************
829 * Load the resume data from the config file. The data is
830 * stored in both r and old.
831 ******************************************************************************/
832 static void resume_load_data (struct resume_data *r, struct resume_data *old)
834 struct configdata config[] = {
835 {TYPE_INT,0,MAZEZAM_NUM_LEVELS-1, { .int_p = &(r->level) },
836 MAZEZAM_CONFIG_LEVELS_NAME,NULL}
839 if (configfile_load(MAZEZAM_CONFIG_FILENAME,config,
840 MAZEZAM_CONFIG_NUM_ITEMS, MAZEZAM_CONFIG_VERSION) < 0)
841 r->level = 0;
842 /* an extra precaution */
843 else if ((r->level < 0) || (MAZEZAM_NUM_LEVELS <= r->level))
844 r->level = 0;
846 old->level = r->level;
849 /*****************************************************************************
850 * Save the resume data in the config file, but only if necessary
851 ******************************************************************************/
852 static void resume_save_data (struct resume_data *r, struct resume_data *old)
854 struct configdata config[] = {
855 {TYPE_INT,0,MAZEZAM_NUM_LEVELS-1, {.int_p = &(r->level) },
856 MAZEZAM_CONFIG_LEVELS_NAME,NULL}
859 /* To reduce disk usage, only write the file if the resume data has
860 * changed.
862 if (old->level != r->level)
863 configfile_save(MAZEZAM_CONFIG_FILENAME,config,
864 MAZEZAM_CONFIG_NUM_ITEMS, MAZEZAM_CONFIG_MINVERSION);
867 /*****************************************************************************
868 * Manages the main menu
869 ******************************************************************************/
870 static bool have_continue;
871 static int main_menu_cb(int action, const struct menu_item_ex *this_item)
873 if(action == ACTION_REQUEST_MENUITEM
874 && !have_continue && ((intptr_t)this_item)==0)
875 return ACTION_EXIT_MENUITEM;
876 return action;
878 static void main_menu(void)
880 /* The initial option is "play game" */
881 int start_selection = 0;
882 int choice = 0;
883 struct resume_data r_data, old_data;
885 /* Load data */
886 resume_load_data(&r_data, &old_data);
888 MENUITEM_STRINGLIST(menu,MAZEZAM_TEXT_MAIN_MENU,main_menu_cb,
889 MAZEZAM_TEXT_CONTINUE,
890 MAZEZAM_TEXT_PLAY_NEW_GAME,
891 MAZEZAM_TEXT_QUIT);
893 while (state >= STATE_IN_APPLICATION) {
894 have_continue = (r_data.level != 0);
895 choice = rb->do_menu(&menu, &start_selection, NULL, false);
897 switch(choice) {
898 case 0: /* Continue */
899 state = STATE_IN_GAME;
900 game_loop(&r_data);
901 break;
903 case 1: /* Play new game */
904 r_data.level = 0;
905 state = STATE_IN_GAME;
906 game_loop(&r_data);
907 break;
909 case MENU_ATTACHED_USB:
910 state = STATE_USB_CONNECTED;
911 break;
913 default: /* Quit */
914 state = STATE_QUIT;
915 break;
919 /* I'm not sure if it's appropriate to write to disk on USB events.
920 * Currently, I do so.
922 resume_save_data(&r_data, &old_data);
925 /*****************************************************************************
926 * Plugin entry point
927 ******************************************************************************/
928 enum plugin_status plugin_start(const void* parameter)
930 enum plugin_status plugin_state;
932 /* Usual plugin stuff */
933 (void)parameter;
935 store_lcd_settings();
937 state = STATE_MAIN_MENU;
938 main_menu();
940 switch (state) {
941 case STATE_USB_CONNECTED:
942 plugin_state = PLUGIN_USB_CONNECTED;
943 break;
945 case STATE_PARSE_ERROR:
946 plugin_state = PLUGIN_ERROR;
947 break;
949 default:
950 plugin_state = PLUGIN_OK;
951 break;
954 return plugin_state;