Rearange menu of mpegplayer. Add new menu with "settings" and "quit", and remove...
[kugel-rb.git] / apps / plugins / mazezam.c
blob1eab90e9dbd3bb806da38cc92e51241277e5cd1c
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
37 # define MAZEZAM_RIGHT_REPEAT (BUTTON_RIGHT|BUTTON_REPEAT)
38 # define MAZEZAM_LEFT_REPEAT (BUTTON_LEFT|BUTTON_REPEAT)
39 # define MAZEZAM_UP_REPEAT (BUTTON_MENU|BUTTON_REPEAT)
40 # define MAZEZAM_DOWN_REPEAT (BUTTON_PLAY|BUTTON_REPEAT)
42 #elif (CONFIG_KEYPAD == IPOD_3G_PAD)
43 # define MAZEZAM_MENU BUTTON_MENU
44 # define MAZEZAM_RIGHT BUTTON_RIGHT
45 # define MAZEZAM_LEFT BUTTON_LEFT
46 # define MAZEZAM_UP BUTTON_SCROLL_BACK
47 # define MAZEZAM_DOWN BUTTON_SCROLL_FWD
48 # define MAZEZAM_RIGHT_REPEAT (BUTTON_RIGHT|BUTTON_REPEAT)
49 # define MAZEZAM_LEFT_REPEAT (BUTTON_LEFT|BUTTON_REPEAT)
50 # define MAZEZAM_UP_REPEAT (BUTTON_SCROLL_BACK|BUTTON_REPEAT)
51 # define MAZEZAM_DOWN_REPEAT (BUTTON_SCROLL_FWD|BUTTON_REPEAT)
53 #elif (CONFIG_KEYPAD == SANSA_FUZE_PAD)
54 # define MAZEZAM_MENU (BUTTON_HOME | BUTTON_REPEAT)
55 # define MAZEZAM_RIGHT BUTTON_RIGHT
56 # define MAZEZAM_LEFT BUTTON_LEFT
57 # define MAZEZAM_UP BUTTON_UP
58 # define MAZEZAM_DOWN BUTTON_DOWN
59 # define MAZEZAM_RIGHT_REPEAT (BUTTON_RIGHT|BUTTON_REPEAT)
60 # define MAZEZAM_LEFT_REPEAT (BUTTON_LEFT|BUTTON_REPEAT)
61 # define MAZEZAM_UP_REPEAT (BUTTON_UP|BUTTON_REPEAT)
62 # define MAZEZAM_DOWN_REPEAT (BUTTON_DOWN|BUTTON_REPEAT)
64 #elif (CONFIG_KEYPAD == SANSA_E200_PAD)
65 # define MAZEZAM_MENU BUTTON_POWER
66 # define MAZEZAM_SOLVE BUTTON_SELECT
67 # define MAZEZAM_RIGHT BUTTON_RIGHT
68 # define MAZEZAM_LEFT BUTTON_LEFT
69 # define MAZEZAM_UP BUTTON_UP
70 # define MAZEZAM_DOWN BUTTON_DOWN
71 # define MAZEZAM_RIGHT_REPEAT (BUTTON_RIGHT|BUTTON_REPEAT)
72 # define MAZEZAM_LEFT_REPEAT (BUTTON_LEFT|BUTTON_REPEAT)
73 # define MAZEZAM_UP_REPEAT (BUTTON_UP|BUTTON_REPEAT)
74 # define MAZEZAM_DOWN_REPEAT (BUTTON_DOWN|BUTTON_REPEAT)
76 #else
77 # include "lib/pluginlib_actions.h"
78 # define MAZEZAM_MENU PLA_QUIT
79 # define MAZEZAM_RIGHT PLA_RIGHT
80 # define MAZEZAM_LEFT PLA_LEFT
81 # define MAZEZAM_UP PLA_UP
82 # define MAZEZAM_DOWN PLA_DOWN
83 # define MAZEZAM_RIGHT_REPEAT PLA_RIGHT_REPEAT
84 # define MAZEZAM_LEFT_REPEAT PLA_LEFT_REPEAT
85 # define MAZEZAM_UP_REPEAT PLA_UP_REPEAT
86 # define MAZEZAM_DOWN_REPEAT PLA_DOWN_REPEAT
87 const struct button_mapping *plugin_contexts[]
88 = {generic_directions, generic_actions};
89 #endif
91 /* All the text is here */
92 #define MAZEZAM_TEXT_GAME_OVER "Game Over"
93 #define MAZEZAM_TEXT_LIVES "Level %d, Lives %d"
94 #define MAZEZAM_TEXT_CHECKPOINT "Checkpoint reached"
95 #define MAZEZAM_TEXT_WELLDONE_TITLE "You have escaped!"
96 #define MAZEZAM_TEXT_WELLDONE_OPTION "Goodbye"
97 #define MAZEZAM_TEXT_MAZEZAM_MENU "MazezaM Menu"
98 #define MAZEZAM_TEXT_RETRY_LEVEL "Retry level"
99 #define MAZEZAM_TEXT_AUDIO_PLAYBACK "Playback Control"
100 #define MAZEZAM_TEXT_QUIT "Quit"
101 #define MAZEZAM_TEXT_BACK "Resume Game"
102 #define MAZEZAM_TEXT_MAIN_MENU "MazezaM"
103 #define MAZEZAM_TEXT_CONTINUE "Play from checkpoint"
104 #define MAZEZAM_TEXT_PLAY_NEW_GAME "Start New Game"
106 #define MAZEZAM_START_LIVES 3 /* how many lives at game start */
107 #define MAZEZAM_FIRST_CHECKPOINT 3 /* The level at the first checkpoint */
108 #define MAZEZAM_CHECKPOINT_INTERVAL 4 /* A checkpoint every _ levels */
110 #ifdef HAVE_LCD_COLOR
111 #define MAZEZAM_HEADING_COLOR LCD_RGBPACK(255,255, 0) /* Yellow */
112 #define MAZEZAM_BORDER_COLOR LCD_RGBPACK( 0, 0,255) /* Blue */
113 #define MAZEZAM_COLOR LCD_RGBPACK(255,255,255) /* White */
114 #define MAZEZAM_BG_COLOR LCD_RGBPACK( 0, 0, 0) /* Black */
115 #define MAZEZAM_WALL_COLOR LCD_RGBPACK(100,100,100) /* Dark gray */
116 #define MAZEZAM_PLAYER_COLOR LCD_RGBPACK(255,255,255) /* White */
117 #define MAZEZAM_GATE_COLOR LCD_RGBPACK(100,100,100) /* Dark gray */
119 /* the rows are coloured sequentially */
120 #define MAZEZAM_NUM_CHUNK_COLORS 8
121 static const unsigned chunk_colors[MAZEZAM_NUM_CHUNK_COLORS] = {
122 LCD_RGBPACK(255,192, 32), /* Orange */
123 LCD_RGBPACK(255, 0, 0), /* Red */
124 LCD_RGBPACK( 0,255, 0), /* Green */
125 LCD_RGBPACK( 0,255,255), /* Cyan */
126 LCD_RGBPACK(255,175,175), /* Pink */
127 LCD_RGBPACK(255,255, 0), /* Yellow */
128 LCD_RGBPACK( 0, 0,255), /* Blue */
129 LCD_RGBPACK(255, 0,255), /* Magenta */
132 #elif LCD_DEPTH > 1
134 #define MAZEZAM_HEADING_GRAY LCD_BLACK
135 #define MAZEZAM_BORDER_GRAY LCD_DARKGRAY
136 #define MAZEZAM_GRAY LCD_BLACK
137 #define MAZEZAM_BG_GRAY LCD_WHITE
138 #define MAZEZAM_WALL_GRAY LCD_DARKGRAY
139 #define MAZEZAM_PLAYER_GRAY LCD_BLACK
140 #define MAZEZAM_GATE_GRAY LCD_BLACK
141 #define MAZEZAM_CHUNK_EDGE_GRAY LCD_BLACK
143 #define MAZEZAM_NUM_CHUNK_GRAYS 2
144 static const unsigned chunk_gray[MAZEZAM_NUM_CHUNK_GRAYS] = {
145 LCD_LIGHTGRAY,
146 LCD_DARKGRAY,
148 /* darker version of the above */
149 static const unsigned chunk_gray_shade[MAZEZAM_NUM_CHUNK_GRAYS] = {
150 LCD_DARKGRAY,
151 LCD_BLACK,
153 #endif
155 #define MAZEZAM_DELAY_CHECKPOINT HZ
156 #define MAZEZAM_DELAY_LIVES HZ
157 #define MAZEZAM_DELAY_GAME_OVER (3 * HZ) / 2
159 /* maximum height of a level */
160 #define MAZEZAM_MAX_LINES 11
161 /* maximum number of chunks on a line */
162 #define MAZEZAM_MAX_CHUNKS 5
164 /* A structure for storing level data in unparsed form */
165 struct mazezam_level {
166 short height; /* the number of lines */
167 short width; /* the width */
168 short entrance; /* the line on which the entrance lies */
169 short exit; /* the line on which the exit lies */
170 char *line[MAZEZAM_MAX_LINES]; /* the chunk data in string form */
173 /* The number of levels. */
174 #define MAZEZAM_NUM_LEVELS 10
176 /* The levels. In theory, they could be stored in a file so this data
177 * structure should not be accessed outside parse_level()
179 * These levels are copyright (C) 2002 Malcolm Tyrrell. They're
180 * probably covered by the GPL as they constitute part of the source
181 * code of this plugin, but you may distibute them seperately with
182 * other Free Software if you want. You can download them from:
183 * http://webpages.dcu.ie/~tyrrelma/MazezaM.
185 static const struct mazezam_level level_data[MAZEZAM_NUM_LEVELS] = {
186 {2,7,0,0,{" $ $"," $ $$"}},
187 {3,8,2,1,{" $ $$$"," $ $ $"," $ $ $"}},
188 {4,14,1,3,{" $$$$$ $$ $$"," $$ $$ $$","$$ $ $$ $$$",
189 " $$$$$$$$ $"}},
190 {6,7,4,2,{" $"," $$$$"," $$$ $$"," $ $ $"," $ $$","$ $$"}},
191 {6,13,0,0,{" $$$$$","$ $$$$$ $$$"," $ $$$ $$$$",
192 "$ $ $$$$$$$"," $$$ $ $$","$ $ $ $$ $"}},
193 {11,5,10,0,{" $"," $ $$"," $$","$ $"," $ $"," $$$","$ $",
194 " $ $"," $ $","$ $$"," $"}},
195 {7,16,0,6,{" $$$$$$$"," $$$$ $$$$ $ $","$$ $$ $$$$$$ $ $",
196 "$ $ $"," $$$$$$$$$$$$$$"," $ $$ $ $$$",
197 " $ $$$ $$"}},
198 {4,15,2,0,{" $$$$ $$$$ $$"," $ $$ $$ $ $$"," $ $$ $$$$ $$",
199 " $ $$ $$$$ $"}},
200 {7,9,6,2,{" $ $$$$"," $ $ $$"," $ $$$$ $","$ $$ $"," $ $$$",
201 " $$$$$$"," $"}},
202 {10,14,8,0,{" $"," $$$$$$$$$$ $"," $$$ $$",
203 " $ $$$$$$$$ $"," $$$ $$$ $$$"," $$$ $ $$$",
204 " $ $$$$$$$ $$"," $ $ $ $$$"," $$$$$$$$$$$$",
205 ""}}
208 /* This data structure which holds information about the rows */
209 struct chunk_data {
210 /* the number of chunks on a line */
211 short l_num[MAZEZAM_MAX_LINES];
212 /* the width of a chunk */
213 short c_width[MAZEZAM_MAX_LINES][MAZEZAM_MAX_CHUNKS];
214 /* the inset of a chunk */
215 short c_inset[MAZEZAM_MAX_LINES][MAZEZAM_MAX_CHUNKS];
218 /* Parsed level data */
219 struct level_info {
220 short width;
221 short height;
222 short entrance;
223 short exit;
224 struct chunk_data cd;
227 /* The state variable used to hold the state of the plugin */
228 static enum {
229 STATE_QUIT, /* The player wants to quit */
230 STATE_USB_CONNECTED, /* A USB cable has been inserted */
231 STATE_PARSE_ERROR, /* There's a parse error in the levels */
232 STATE_WELLDONE, /* The player has finished the game */
234 STATE_IN_APPLICATION,
236 STATE_MAIN_MENU /* The player is at the main menu */
237 = STATE_IN_APPLICATION,
238 STATE_GAME_OVER, /* The player is out of lives */
240 STATE_IN_GAME,
242 STATE_COMPLETED /* A level has been completed */
243 = STATE_IN_GAME,
245 STATE_FAILED, /* The player wants to retry the level */
246 STATE_GAME_MENU, /* The player wan't to access the in-game menu */
248 STATE_IN_LEVEL,
249 } state;
251 /* The various constants needed for configuration files.
252 * See apps/plugins/lib/configfile.*
254 #define MAZEZAM_CONFIG_FILENAME "mazezam.data"
255 #define MAZEZAM_CONFIG_NUM_ITEMS 1
256 #define MAZEZAM_CONFIG_VERSION 0
257 #define MAZEZAM_CONFIG_MINVERSION 0
258 #define MAZEZAM_CONFIG_LEVELS_NAME "restart_level"
260 /* A structure containing the data that is written to
261 * the configuration file
263 struct resume_data {
264 int level; /* level at which to restart the game */
267 #if LCD_DEPTH > 1
268 /* Store the display settings so they are reintroduced during menus */
269 static struct {
270 fb_data* backdrop;
271 unsigned foreground;
272 unsigned background;
273 } lcd_settings;
274 #endif
276 /*****************************************************************************
277 * Store the LCD settings
278 ******************************************************************************/
279 static void store_lcd_settings(void)
281 /* Store the old settings */
282 #if LCD_DEPTH > 1
283 lcd_settings.backdrop = rb->lcd_get_backdrop();
284 lcd_settings.foreground = rb->lcd_get_foreground();
285 lcd_settings.background = rb->lcd_get_background();
286 #endif
289 /*****************************************************************************
290 * Restore the LCD settings to their defaults
291 ******************************************************************************/
292 static void restore_lcd_settings(void) {
293 /* Turn on backlight timeout (revert to settings) */
294 backlight_use_settings(); /* backlight control in lib/helper.c */
296 /* Restore the old settings */
297 #if LCD_DEPTH > 1
298 rb->lcd_set_foreground(lcd_settings.foreground);
299 rb->lcd_set_background(lcd_settings.background);
300 rb->lcd_set_backdrop(lcd_settings.backdrop);
301 #endif
304 /*****************************************************************************
305 * Adjust the LCD settings to suit MazezaM levels
306 ******************************************************************************/
307 static void plugin_lcd_settings(void) {
308 /* Turn off backlight timeout */
309 backlight_force_on(); /* backlight control in lib/helper.c */
311 /* Set the new settings */
312 #ifdef HAVE_LCD_COLOR
313 rb->lcd_set_background(MAZEZAM_BG_COLOR);
314 rb->lcd_set_backdrop(NULL);
315 #elif LCD_DEPTH > 1
316 rb->lcd_set_background(MAZEZAM_BG_GRAY);
317 rb->lcd_set_backdrop(NULL);
318 #endif
321 /*****************************************************************************
322 * Parse the level data from the level_data structure. This could be
323 * replaced by a file read. Returns true if the level parsed correctly.
324 ******************************************************************************/
325 static bool parse_level(short level, struct level_info* li)
327 int i,j;
328 char c,clast;
330 li->width = level_data[level].width;
331 li->height = level_data[level].height;
332 li->entrance = level_data[level].entrance;
333 li->exit = level_data[level].exit;
335 /* for each line in the level */
336 for (i = 0; i<level_data[level].height; i++) {
337 if (level_data[level].line[i] == NULL)
338 return false;
339 else {
340 j = 0;
341 li->cd.l_num[i] = 0;
342 clast = ' '; /* the character we last considered */
343 while ((c = level_data[level].line[i][j]) != '\0') {
344 if (c != ' ') {
345 if (clast == ' ') {
346 li->cd.l_num[i] += 1;
347 if (li->cd.l_num[i] > MAZEZAM_MAX_CHUNKS)
348 return false;
349 li->cd.c_inset[i][li->cd.l_num[i] - 1] = j;
350 li->cd.c_width[i][li->cd.l_num[i] - 1] = 1;
352 else
353 li->cd.c_width[i][li->cd.l_num[i] - 1] += 1;
355 clast = c;
356 j++;
360 return true;
363 /*****************************************************************************
364 * Draw the walls of a level
365 ******************************************************************************/
366 static void draw_walls(
367 short size,
368 short xOff,
369 short yOff,
370 short width,
371 short height,
372 short entrance,
373 short exit)
375 #ifdef HAVE_LCD_COLOR
376 rb->lcd_set_foreground(MAZEZAM_WALL_COLOR);
377 #elif LCD_DEPTH > 1
378 rb->lcd_set_foreground(MAZEZAM_WALL_GRAY);
379 #endif
380 /* draw the upper wall */
381 rb->lcd_fillrect(0,0,xOff,yOff+(size*entrance));
382 rb->lcd_fillrect(xOff,0,size*width,yOff);
383 rb->lcd_fillrect(xOff+(size*width),0,LCD_WIDTH-xOff-(size*width),
384 yOff+(size*exit));
386 /* draw the lower wall */
387 rb->lcd_fillrect(0,yOff+(size*entrance)+size,xOff,
388 LCD_HEIGHT-yOff-(size*entrance)-size);
389 rb->lcd_fillrect(xOff,yOff+(size*height),size*width,
390 LCD_HEIGHT-yOff-(size*height));
391 /* Note: the exit is made one pixel thinner than necessary as a visual
392 * clue that chunks cannot be pushed into it
394 rb->lcd_fillrect(xOff+(size*width),yOff+(size*exit)+size-1,
395 LCD_WIDTH-xOff+(size*width),
396 LCD_HEIGHT-yOff-(size*exit)-size+1);
399 /*****************************************************************************
400 * Draw chunk row i
401 ******************************************************************************/
402 static void draw_row(
403 short size,
404 short xOff,
405 short yOff,
406 short width,
407 short i, /* the row number */
408 struct chunk_data *cd, /* the data about the chunks */
409 short *shift /* an array of the horizontal offset of the lines */
412 /* The assignment below is just a hack to make supress a warning on
413 * non color targets */
414 short j = width;
415 #ifndef HAVE_LCD_COLOR
416 /* We #def these out to supress a compiler warning */
417 short k;
418 #if LCD_DEPTH <= 1
419 short l;
420 #endif
421 #endif
422 #ifdef HAVE_LCD_COLOR
423 /* adding width to i should have a fixed, but randomising effect on
424 * the choice of the colours of the top line of chunks
426 rb->lcd_set_foreground(chunk_colors[(i+width) %
427 MAZEZAM_NUM_CHUNK_COLORS]);
428 #endif
429 for (j = 0; j<cd->l_num[i]; j++) {
430 #ifdef HAVE_LCD_COLOR
431 rb->lcd_fillrect(xOff+size*shift[i]+size*cd->c_inset[i][j],
432 yOff+size*i, cd->c_width[i][j]*size,size);
433 #elif LCD_DEPTH > 1
434 rb->lcd_set_foreground(MAZEZAM_CHUNK_EDGE_GRAY);
435 rb->lcd_drawrect(xOff+size*shift[i]+size*cd->c_inset[i][j],
436 yOff+size*i, cd->c_width[i][j]*size,size);
438 /* draw shade */
439 rb->lcd_set_foreground(chunk_gray_shade[(i+width) %
440 MAZEZAM_NUM_CHUNK_GRAYS]);
441 rb->lcd_hline(xOff+size*shift[i]+size*cd->c_inset[i][j]+1,
442 xOff+size*shift[i]+size*cd->c_inset[i][j]+
443 cd->c_width[i][j]*size-3,
444 yOff+size*i+size-2);
445 rb->lcd_vline(xOff+size*shift[i]+size*cd->c_inset[i][j]+
446 cd->c_width[i][j]*size-2,
447 yOff+size*i,
448 yOff+size*i+size-2);
450 /* draw fill */
451 rb->lcd_set_foreground(chunk_gray[(i+width) %
452 MAZEZAM_NUM_CHUNK_GRAYS]);
453 for (k = yOff+size*i+2; k < yOff+size*i+size-2; k += 2)
454 rb->lcd_hline(xOff+size*shift[i]+size*cd->c_inset[i][j]+2,
455 xOff+size*shift[i]+size*cd->c_inset[i][j]+
456 cd->c_width[i][j]*size-3,k);
457 #else
458 rb->lcd_drawrect(xOff+size*shift[i]+size*cd->c_inset[i][j],
459 yOff+size*i, cd->c_width[i][j]*size,size);
460 for (k = xOff+size*shift[i]+size*cd->c_inset[i][j]+2;
461 k < xOff+size*shift[i]+size*cd->c_inset[i][j]+
462 cd->c_width[i][j]*size;
463 k += 2 + (i & 1))
464 for (l = yOff+size*i+2; l < yOff+size*i+size; l += 2 + (i & 1))
465 rb->lcd_drawpixel(k, l);
466 #endif
470 /*****************************************************************************
471 * Draw the player
472 ******************************************************************************/
473 static void draw_player(
474 short size,
475 short xOff,
476 short yOff,
477 short x,
478 short y)
480 /* For drawing the player, taken from the sokoban plugin */
481 short max = size - 1;
482 short middle = max / 2;
483 short ldelta = (middle + 1) / 2;
485 /* draw the player (mostly copied from the sokoban plugin) */
486 #ifdef HAVE_LCD_COLOR
487 rb->lcd_set_foreground(MAZEZAM_PLAYER_COLOR);
488 #elif LCD_DEPTH > 1
489 rb->lcd_set_foreground(MAZEZAM_PLAYER_GRAY);
490 #endif
491 rb->lcd_hline(xOff+size*x, xOff+size*x+max, yOff+size*y+middle);
492 rb->lcd_vline(xOff+size*x+middle, yOff+size*y, yOff+size*y+max-ldelta);
493 rb->lcd_drawline(xOff+size*x+middle, yOff+size*y+max-ldelta,
494 xOff+size*x+middle-ldelta, yOff+size*y+max);
495 rb->lcd_drawline(xOff+size*x+middle, yOff+size*y+max-ldelta,
496 xOff+size*x+middle+ldelta, yOff+size*y+max);
499 /*****************************************************************************
500 * Draw the gate
501 ******************************************************************************/
502 static void draw_gate(
503 short size,
504 short xOff,
505 short yOff,
506 short entrance)
508 short third = size / 3;
509 short twothirds = (2 * size) / 3;
510 #ifdef HAVE_LCD_COLOR
511 rb->lcd_set_foreground(MAZEZAM_GATE_COLOR);
512 #elif LCD_DEPTH > 1
513 rb->lcd_set_foreground(MAZEZAM_GATE_GRAY);
514 #endif
515 rb->lcd_hline(xOff-size,xOff-1,yOff+entrance*size+third);
516 rb->lcd_hline(xOff-size,xOff-1,yOff+entrance*size+twothirds);
517 rb->lcd_vline(xOff-size+third,yOff+entrance*size,
518 yOff+entrance*size+size-1);
519 rb->lcd_vline(xOff-size+twothirds,yOff+entrance*size,
520 yOff+entrance*size+size-1);
523 /*****************************************************************************
524 * Draw the level
525 ******************************************************************************/
526 static void draw_level(
527 struct level_info* li,
528 short *shift, /* an array of the horizontal offset of the lines */
529 short x, /* player's x and y coords */
530 short y)
532 /* First we calculate the draw info */
533 /* The number of pixels the side of a square should be */
534 short size = (LCD_WIDTH/(li->width+2)) < (LCD_HEIGHT/li->height) ?
535 (LCD_WIDTH/(li->width+2)) : (LCD_HEIGHT/li->height);
536 /* The x and y position (in pixels) of the top left corner of the
537 * level
539 short xOff = (LCD_WIDTH - (size*li->width))/2;
540 short yOff = (LCD_HEIGHT - (size*li->height))/2;
541 short i;
543 rb->lcd_clear_display();
545 draw_walls(size,xOff,yOff,li->width, li->height, li->entrance, li->exit);
547 /* draw the chunks */
548 for (i = 0; i<li->height; i++) {
549 draw_row(size,xOff,yOff,li->width,i,&(li->cd),shift);
552 draw_player(size,xOff,yOff,x,y);
554 /* if the player has moved into the level, draw the gate */
555 if (x >= 0)
556 draw_gate(size,xOff,yOff,li->entrance);
559 /*****************************************************************************
560 * Manage the congratulations screen
561 ******************************************************************************/
562 static void welldone_screen(void)
564 int start_selection = 0;
566 MENUITEM_STRINGLIST(menu,MAZEZAM_TEXT_WELLDONE_TITLE,NULL,
567 MAZEZAM_TEXT_WELLDONE_OPTION);
569 switch(rb->do_menu(&menu, &start_selection, NULL, true)){
570 case MENU_ATTACHED_USB:
571 state = STATE_USB_CONNECTED;
572 break;
576 /*****************************************************************************
577 * Manage the playing of a level
578 ******************************************************************************/
579 static void level_loop(struct level_info* li, short* shift, short *x, short *y)
581 int i;
582 int button;
583 bool blocked; /* is there a chunk in the way of the player? */
585 while (state >= STATE_IN_LEVEL) {
586 draw_level(li, shift, *x, *y);
587 rb->lcd_update();
588 #ifdef __PLUGINLIB_ACTIONS_H__
589 button = pluginlib_getaction(TIMEOUT_BLOCK, plugin_contexts, 2);
590 #else
591 button = rb->button_get(true);
592 #endif
593 blocked = false;
595 switch (button) {
596 case MAZEZAM_UP:
597 case MAZEZAM_UP_REPEAT:
598 if ((*y > 0) && (*x >= 0) && (*x < li->width)) {
599 for (i = 0; i < li->cd.l_num[*y-1]; i++)
600 blocked = blocked ||
601 ((*x>=shift[*y-1]+li->cd.c_inset[*y-1][i]) &&
602 (*x<shift[*y-1]+li->cd.c_inset[*y-1][i]+
603 li->cd.c_width[*y-1][i]));
604 if (!blocked) *y -= 1;
606 break;
608 case MAZEZAM_DOWN:
609 case MAZEZAM_DOWN_REPEAT:
610 if ((*y < li->height-1) && (*x >= 0) && (*x < li->width)) {
611 for (i = 0; i < li->cd.l_num[*y+1]; i++)
612 blocked = blocked ||
613 ((*x>=shift[*y+1]+li->cd.c_inset[*y+1][i]) &&
614 (*x<shift[*y+1]+li->cd.c_inset[*y+1][i]+
615 li->cd.c_width[*y+1][i]));
616 if (!blocked) *y += 1;
618 break;
620 case MAZEZAM_LEFT:
621 case MAZEZAM_LEFT_REPEAT:
622 if (*x > 0) {
623 for (i = 0; i < li->cd.l_num[*y]; i++)
624 blocked = blocked ||
625 (*x == shift[*y]+li->cd.c_inset[*y][i]+
626 li->cd.c_width[*y][i]);
627 if (!blocked) *x -= 1;
628 else if (shift[*y] + li->cd.c_inset[*y][0] > 0) {
629 *x -= 1;
630 shift[*y] -= 1;
633 break;
635 case MAZEZAM_RIGHT:
636 case MAZEZAM_RIGHT_REPEAT:
637 if (*x < li->width-1) {
638 for (i = 0; i < li->cd.l_num[*y]; i++)
639 blocked = blocked ||
640 (*x+1 == shift[*y]+li->cd.c_inset[*y][i]);
641 if (!blocked) *x += 1;
642 else if (shift[*y]
643 + li->cd.c_inset[*y][li->cd.l_num[*y]-1]
644 + li->cd.c_width[*y][li->cd.l_num[*y]-1]
645 < li->width) {
646 *x += 1;
647 shift[*y] += 1;
650 else if (*x == li->width) state = STATE_COMPLETED;
651 else if (*y == li->exit) *x += 1;
652 break;
654 case MAZEZAM_MENU:
655 state = STATE_GAME_MENU;
656 break;
658 default:
659 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
660 state = STATE_USB_CONNECTED;
661 break;
666 /*****************************************************************************
667 * Manage the in game menu
668 ******************************************************************************/
669 static void in_game_menu(void)
671 /* The initial option is retry level */
672 int start_selection = 1;
674 MENUITEM_STRINGLIST(menu,MAZEZAM_TEXT_MAZEZAM_MENU, NULL,
675 MAZEZAM_TEXT_BACK,
676 MAZEZAM_TEXT_RETRY_LEVEL,
677 MAZEZAM_TEXT_AUDIO_PLAYBACK,
678 MAZEZAM_TEXT_QUIT);
680 /* Don't show the status bar */
681 switch(rb->do_menu(&menu, &start_selection, NULL, false)){
682 case 1: /* retry */
683 state = STATE_FAILED;
684 break;
686 case 2: /* Audio playback */
687 playback_control(NULL);
688 state = STATE_IN_LEVEL;
689 break;
691 case 3: /* quit */
692 state = STATE_QUIT;
693 break;
695 case MENU_ATTACHED_USB:
696 state = STATE_USB_CONNECTED;
697 break;
699 default: /* Back */
700 state = STATE_IN_LEVEL;
701 break;
705 /*****************************************************************************
706 * Is the level a checkpoint
707 ******************************************************************************/
708 static bool at_checkpoint(int level)
710 if (level <= MAZEZAM_FIRST_CHECKPOINT)
711 return level == MAZEZAM_FIRST_CHECKPOINT;
712 else {
713 level = level - MAZEZAM_FIRST_CHECKPOINT;
714 return level % MAZEZAM_CHECKPOINT_INTERVAL == 0;
718 /*****************************************************************************
719 * Set up and play a level
720 * new_level should be true if this is the first time we've encountered
721 * this level
722 ******************************************************************************/
723 static void play_level(short level, short lives, bool new_level)
725 struct level_info li;
726 short shift[MAZEZAM_MAX_LINES]; /* amount each line has been shifted */
727 short x,y;
728 int i;
730 state = STATE_IN_LEVEL;
732 if (!(parse_level(level,&li)))
733 state = STATE_PARSE_ERROR;
735 for (i = 0; i < li.height; i++)
736 shift[i] = 0;
738 x = -1;
739 y = li.entrance;
741 plugin_lcd_settings();
742 rb->lcd_clear_display();
744 draw_level(&li, shift, x, y);
746 /* If we've just reached a checkpoint, then alert the player */
747 if (new_level && at_checkpoint(level)) {
748 rb->splash(MAZEZAM_DELAY_CHECKPOINT, MAZEZAM_TEXT_CHECKPOINT);
749 /* Clear the splash */
750 draw_level(&li, shift, x, y);
753 #ifdef HAVE_REMOTE_LCD
754 /* Splash text seems to use the remote display by
755 * default. I suppose I better keep it tidy!
757 rb->lcd_remote_clear_display();
758 #endif
759 rb->splashf(MAZEZAM_DELAY_LIVES, MAZEZAM_TEXT_LIVES,
760 level+1, lives);
762 /* ensure keys pressed during the splash screen are ignored */
763 rb->button_clear_queue();
765 /* this little loop just ensures we return to the game if the player
766 * doesn't perform an interesting action during the in game menu */
767 while (state >= STATE_IN_LEVEL) {
768 level_loop(&li, shift, &x, &y);
770 if (state == STATE_GAME_MENU) {
771 restore_lcd_settings();
772 in_game_menu();
773 plugin_lcd_settings();
776 restore_lcd_settings();
779 /*****************************************************************************
780 * Update the resume data based on the level reached
781 ******************************************************************************/
782 static void update_resume_data(struct resume_data *r, int level)
784 if (at_checkpoint(level))
785 r->level = level;
788 /*****************************************************************************
789 * The loop which manages a full game of MazezaM.
790 ******************************************************************************/
791 static void game_loop(struct resume_data *r)
793 int level = r->level;
794 int lives = MAZEZAM_START_LIVES;
795 /* We want to know when a player reaches a level for the first time,
796 * so we keep a second copy of the level. */
797 int old_level = level;
799 state = STATE_IN_GAME;
801 while (state >= STATE_IN_GAME)
803 play_level(level, lives, old_level < level);
804 old_level = level;
806 switch (state) {
807 case STATE_COMPLETED:
808 level += 1;
809 if (level == MAZEZAM_NUM_LEVELS)
810 state = STATE_WELLDONE;
811 break;
813 case STATE_FAILED:
814 lives -= 1;
815 if (lives == 0)
816 state = STATE_GAME_OVER;
817 break;
819 default:
820 break;
823 update_resume_data(r,level);
826 switch (state) {
827 case STATE_GAME_OVER:
828 #ifdef HAVE_REMOTE_LCD
829 /* Splash text seems to use the remote display by
830 * default. I suppose I better keep it tidy!
832 rb->lcd_remote_clear_display();
833 #endif
834 rb->splash(MAZEZAM_DELAY_GAME_OVER, MAZEZAM_TEXT_GAME_OVER);
835 break;
837 case STATE_WELLDONE:
838 r->level = 0;
839 welldone_screen();
840 break;
842 default:
843 break;
847 /*****************************************************************************
848 * Load the resume data from the config file. The data is
849 * stored in both r and old.
850 ******************************************************************************/
851 static void resume_load_data (struct resume_data *r, struct resume_data *old)
853 struct configdata config[] = {
854 {TYPE_INT,0,MAZEZAM_NUM_LEVELS-1, { .int_p = &(r->level) },
855 MAZEZAM_CONFIG_LEVELS_NAME,NULL}
858 if (configfile_load(MAZEZAM_CONFIG_FILENAME,config,
859 MAZEZAM_CONFIG_NUM_ITEMS, MAZEZAM_CONFIG_VERSION) < 0)
860 r->level = 0;
861 /* an extra precaution */
862 else if ((r->level < 0) || (MAZEZAM_NUM_LEVELS <= r->level))
863 r->level = 0;
865 old->level = r->level;
868 /*****************************************************************************
869 * Save the resume data in the config file, but only if necessary
870 ******************************************************************************/
871 static void resume_save_data (struct resume_data *r, struct resume_data *old)
873 struct configdata config[] = {
874 {TYPE_INT,0,MAZEZAM_NUM_LEVELS-1, {.int_p = &(r->level) },
875 MAZEZAM_CONFIG_LEVELS_NAME,NULL}
878 /* To reduce disk usage, only write the file if the resume data has
879 * changed.
881 if (old->level != r->level)
882 configfile_save(MAZEZAM_CONFIG_FILENAME,config,
883 MAZEZAM_CONFIG_NUM_ITEMS, MAZEZAM_CONFIG_MINVERSION);
886 /*****************************************************************************
887 * Manages the main menu
888 ******************************************************************************/
889 static bool have_continue;
890 static int main_menu_cb(int action, const struct menu_item_ex *this_item)
892 if(action == ACTION_REQUEST_MENUITEM
893 && !have_continue && ((intptr_t)this_item)==0)
894 return ACTION_EXIT_MENUITEM;
895 return action;
897 static void main_menu(void)
899 /* The initial option is "play game" */
900 int start_selection = 0;
901 int choice = 0;
902 struct resume_data r_data, old_data;
904 /* Load data */
905 resume_load_data(&r_data, &old_data);
907 MENUITEM_STRINGLIST(menu,MAZEZAM_TEXT_MAIN_MENU,main_menu_cb,
908 MAZEZAM_TEXT_CONTINUE,
909 MAZEZAM_TEXT_PLAY_NEW_GAME,
910 MAZEZAM_TEXT_AUDIO_PLAYBACK,
911 MAZEZAM_TEXT_QUIT);
913 while (state >= STATE_IN_APPLICATION) {
914 have_continue = (r_data.level != 0);
915 choice = rb->do_menu(&menu, &start_selection, NULL, false);
917 switch(choice) {
918 case 0: /* Continue */
919 state = STATE_IN_GAME;
920 game_loop(&r_data);
921 break;
923 case 1: /* Play new game */
924 r_data.level = 0;
925 state = STATE_IN_GAME;
926 game_loop(&r_data);
927 break;
929 case 2: /* Audio playback */
930 playback_control(NULL);
931 break;
933 case MENU_ATTACHED_USB:
934 state = STATE_USB_CONNECTED;
935 break;
937 default: /* Quit */
938 state = STATE_QUIT;
939 break;
943 /* I'm not sure if it's appropriate to write to disk on USB events.
944 * Currently, I do so.
946 resume_save_data(&r_data, &old_data);
949 /*****************************************************************************
950 * Plugin entry point
951 ******************************************************************************/
952 enum plugin_status plugin_start(const void* parameter)
954 enum plugin_status plugin_state;
956 /* Usual plugin stuff */
957 (void)parameter;
959 store_lcd_settings();
961 state = STATE_MAIN_MENU;
962 main_menu();
964 switch (state) {
965 case STATE_USB_CONNECTED:
966 plugin_state = PLUGIN_USB_CONNECTED;
967 break;
969 case STATE_PARSE_ERROR:
970 plugin_state = PLUGIN_ERROR;
971 break;
973 default:
974 plugin_state = PLUGIN_OK;
975 break;
978 return plugin_state;