Fix yellow
[Rockbox.git] / apps / plugins / mazezam.c
blobdac73473ce4bc4a12652136b0c6f40672bcb5a9f
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * ### line of auto-generated stuff I don't understand ###
10 * Copyright (C) 2006 Malcolm Tyrrell
12 * MazezaM - a Rockbox version of my ZX Spectrum game from 2002
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
21 #include "plugin.h"
22 #include "configfile.h"
23 #include "helper.h"
25 /* Include standard plugin macro */
26 PLUGIN_HEADER
28 static struct plugin_api* rb;
30 MEM_FUNCTION_WRAPPERS(rb);
32 #if CONFIG_KEYPAD == RECORDER_PAD
33 #define MAZEZAM_UP BUTTON_UP
34 #define MAZEZAM_DOWN BUTTON_DOWN
35 #define MAZEZAM_LEFT BUTTON_LEFT
36 #define MAZEZAM_RIGHT BUTTON_RIGHT
37 #define MAZEZAM_SELECT BUTTON_PLAY
39 #define MAZEZAM_RETRY BUTTON_F1
40 #define MAZEZAM_RETRY_KEYNAME "[F1]"
41 #define MAZEZAM_QUIT BUTTON_OFF
42 #define MAZEZAM_QUIT_KEYNAME "[OFF]"
44 #elif CONFIG_KEYPAD == ARCHOS_AV300_PAD
45 #define MAZEZAM_UP BUTTON_UP
46 #define MAZEZAM_DOWN BUTTON_DOWN
47 #define MAZEZAM_LEFT BUTTON_LEFT
48 #define MAZEZAM_RIGHT BUTTON_RIGHT
49 #define MAZEZAM_SELECT BUTTON_SELECT
51 #define MAZEZAM_RETRY BUTTON_F1
52 #define MAZEZAM_RETRY_KEYNAME "[F1]"
53 #define MAZEZAM_QUIT BUTTON_OFF
54 #define MAZEZAM_QUIT_KEYNAME "[OFF]"
56 #elif CONFIG_KEYPAD == ONDIO_PAD
57 #define MAZEZAM_UP BUTTON_UP
58 #define MAZEZAM_DOWN BUTTON_DOWN
59 #define MAZEZAM_LEFT BUTTON_LEFT
60 #define MAZEZAM_RIGHT BUTTON_RIGHT
61 #define MAZEZAM_SELECT BUTTON_RIGHT
63 #define MAZEZAM_RETRY BUTTON_MENU
64 #define MAZEZAM_RETRY_KEYNAME "[MENU]"
65 #define MAZEZAM_QUIT BUTTON_OFF
66 #define MAZEZAM_QUIT_KEYNAME "[OFF]"
68 #elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD)
69 #define MAZEZAM_UP BUTTON_UP
70 #define MAZEZAM_DOWN BUTTON_DOWN
71 #define MAZEZAM_LEFT BUTTON_LEFT
72 #define MAZEZAM_RIGHT BUTTON_RIGHT
73 #define MAZEZAM_SELECT BUTTON_SELECT
75 #define MAZEZAM_RETRY BUTTON_REC
76 #define MAZEZAM_RETRY_KEYNAME "[REC]"
77 #define MAZEZAM_QUIT BUTTON_POWER
78 #define MAZEZAM_QUIT_KEYNAME "[POWER]"
80 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
81 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
82 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
83 #define MAZEZAM_UP BUTTON_MENU
84 #define MAZEZAM_DOWN BUTTON_PLAY
85 #define MAZEZAM_LEFT BUTTON_LEFT
86 #define MAZEZAM_RIGHT BUTTON_RIGHT
87 #define MAZEZAM_SELECT BUTTON_SELECT
89 #define MAZEZAM_RETRY BUTTON_SELECT
90 #define MAZEZAM_RETRY_KEYNAME "[SELECT]"
91 #define MAZEZAM_QUIT (BUTTON_SELECT | BUTTON_REPEAT)
92 #define MAZEZAM_QUIT_KEYNAME "[SELECT] (held)"
94 #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
95 (CONFIG_KEYPAD == IRIVER_H300_PAD)
96 #define MAZEZAM_UP BUTTON_UP
97 #define MAZEZAM_DOWN BUTTON_DOWN
98 #define MAZEZAM_LEFT BUTTON_LEFT
99 #define MAZEZAM_RIGHT BUTTON_RIGHT
100 #define MAZEZAM_SELECT BUTTON_SELECT
102 #define MAZEZAM_RETRY BUTTON_ON
103 #define MAZEZAM_RETRY_KEYNAME "[ON]"
104 #define MAZEZAM_QUIT BUTTON_OFF
105 #define MAZEZAM_QUIT_KEYNAME "[OFF]"
107 #elif (CONFIG_KEYPAD == GIGABEAT_PAD)
108 #define MAZEZAM_UP BUTTON_UP
109 #define MAZEZAM_DOWN BUTTON_DOWN
110 #define MAZEZAM_LEFT BUTTON_LEFT
111 #define MAZEZAM_RIGHT BUTTON_RIGHT
112 #define MAZEZAM_SELECT BUTTON_SELECT
114 #define MAZEZAM_RETRY BUTTON_A
115 #define MAZEZAM_RETRY_KEYNAME "[A]"
116 #define MAZEZAM_QUIT BUTTON_POWER
117 #define MAZEZAM_QUIT_KEYNAME "[POWER]"
119 #elif (CONFIG_KEYPAD == SANSA_E200_PAD) || \
120 (CONFIG_KEYPAD == SANSA_C200_PAD)
121 #define MAZEZAM_UP BUTTON_UP
122 #define MAZEZAM_DOWN BUTTON_DOWN
123 #define MAZEZAM_LEFT BUTTON_LEFT
124 #define MAZEZAM_RIGHT BUTTON_RIGHT
125 #define MAZEZAM_SELECT BUTTON_SELECT
127 #define MAZEZAM_RETRY BUTTON_REC
128 #define MAZEZAM_RETRY_KEYNAME "[REC]"
129 #define MAZEZAM_QUIT BUTTON_POWER
130 #define MAZEZAM_QUIT_KEYNAME "[POWER]"
132 #elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
133 #define MAZEZAM_UP BUTTON_SCROLL_UP
134 #define MAZEZAM_DOWN BUTTON_SCROLL_DOWN
135 #define MAZEZAM_LEFT BUTTON_LEFT
136 #define MAZEZAM_RIGHT BUTTON_RIGHT
137 #define MAZEZAM_SELECT BUTTON_PLAY
139 #define MAZEZAM_RETRY BUTTON_PLAY
140 #define MAZEZAM_RETRY_KEYNAME "[PLAY]"
141 #define MAZEZAM_QUIT BUTTON_POWER
142 #define MAZEZAM_QUIT_KEYNAME "[POWER]"
144 #elif (CONFIG_KEYPAD == GIGABEAT_S_PAD)
145 #define MAZEZAM_UP BUTTON_UP
146 #define MAZEZAM_DOWN BUTTON_DOWN
147 #define MAZEZAM_LEFT BUTTON_LEFT
148 #define MAZEZAM_RIGHT BUTTON_RIGHT
149 #define MAZEZAM_SELECT BUTTON_SELECT
151 #define MAZEZAM_RETRY BUTTON_PLAY
152 #define MAZEZAM_RETRY_KEYNAME "[PLAY]"
153 #define MAZEZAM_QUIT BUTTON_BACK
154 #define MAZEZAM_QUIT_KEYNAME "[BACK]"
156 #elif (CONFIG_KEYPAD == MROBE100_PAD)
157 #define MAZEZAM_UP BUTTON_UP
158 #define MAZEZAM_DOWN BUTTON_DOWN
159 #define MAZEZAM_LEFT BUTTON_LEFT
160 #define MAZEZAM_RIGHT BUTTON_RIGHT
161 #define MAZEZAM_SELECT BUTTON_SELECT
163 #define MAZEZAM_RETRY BUTTON_DISPLAY
164 #define MAZEZAM_RETRY_KEYNAME "[DISPLAY]"
165 #define MAZEZAM_QUIT BUTTON_POWER
166 #define MAZEZAM_QUIT_KEYNAME "[POWER]"
168 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
169 #define MAZEZAM_UP BUTTON_RC_VOL_UP
170 #define MAZEZAM_DOWN BUTTON_RC_VOL_DOWN
171 #define MAZEZAM_LEFT BUTTON_RC_REW
172 #define MAZEZAM_RIGHT BUTTON_RC_FF
173 #define MAZEZAM_SELECT BUTTON_RC_PLAY
175 #define MAZEZAM_RETRY BUTTON_RC_MODE
176 #define MAZEZAM_RETRY_KEYNAME "[MODE]"
177 #define MAZEZAM_QUIT BUTTON_RC_REC
178 #define MAZEZAM_QUIT_KEYNAME "[REC]"
180 #elif (CONFIG_KEYPAD == COWOND2_PAD)
181 #define MAZEZAM_UP BUTTON_UP
182 #define MAZEZAM_DOWN BUTTON_DOWN
183 #define MAZEZAM_LEFT BUTTON_LEFT
184 #define MAZEZAM_RIGHT BUTTON_RIGHT
185 #define MAZEZAM_SELECT BUTTON_SELECT
187 #define MAZEZAM_RETRY BUTTON_SELECT
188 #define MAZEZAM_RETRY_KEYNAME "[PLAY]"
189 #define MAZEZAM_QUIT BUTTON_POWER
190 #define MAZEZAM_QUIT_KEYNAME "[POWER]"
192 #else
193 #error No keymap defined!
194 #endif
196 /* The gap for the border around the heading in text pages. In fact, 2 is
197 * really the only acceptable value.
199 #define MAZEZAM_MENU_BORDER 2
200 #define MAZEZAM_EXTRA_LIFE 2 /* get an extra life every _ levels */
201 #define MAZEZAM_START_LIVES 3 /* how many lives at game start */
203 #ifdef HAVE_LCD_COLOR
204 #define MAZEZAM_HEADING_COLOR LCD_RGBPACK(255,255, 0) /* Yellow */
205 #define MAZEZAM_BORDER_COLOR LCD_RGBPACK( 0, 0,255) /* Blue */
206 #define MAZEZAM_TEXT_COLOR LCD_RGBPACK(255,255,255) /* White */
207 #define MAZEZAM_BG_COLOR LCD_RGBPACK( 0, 0, 0) /* Black */
208 #define MAZEZAM_WALL_COLOR LCD_RGBPACK(100,100,100) /* Dark gray */
209 #define MAZEZAM_PLAYER_COLOR LCD_RGBPACK(255,255,255) /* White */
210 #define MAZEZAM_GATE_COLOR LCD_RGBPACK(100,100,100) /* Dark gray */
212 /* the rows are coloured sequentially */
213 #define MAZEZAM_NUM_CHUNK_COLORS 8
214 static const unsigned chunk_colors[MAZEZAM_NUM_CHUNK_COLORS] = {
215 LCD_RGBPACK(255,192, 32), /* Orange */
216 LCD_RGBPACK(255, 0, 0), /* Red */
217 LCD_RGBPACK( 0,255, 0), /* Green */
218 LCD_RGBPACK( 0,255,255), /* Cyan */
219 LCD_RGBPACK(255,175,175), /* Pink */
220 LCD_RGBPACK(255,255, 0), /* Yellow */
221 LCD_RGBPACK( 0, 0,255), /* Blue */
222 LCD_RGBPACK(255, 0,255), /* Magenta */
225 #elif LCD_DEPTH > 1
227 #define MAZEZAM_HEADING_GRAY LCD_BLACK
228 #define MAZEZAM_BORDER_GRAY LCD_DARKGRAY
229 #define MAZEZAM_TEXT_GRAY LCD_BLACK
230 #define MAZEZAM_BG_GRAY LCD_WHITE
231 #define MAZEZAM_WALL_GRAY LCD_DARKGRAY
232 #define MAZEZAM_PLAYER_GRAY LCD_BLACK
233 #define MAZEZAM_GATE_GRAY LCD_BLACK
234 #define MAZEZAM_CHUNK_EDGE_GRAY LCD_BLACK
236 #define MAZEZAM_NUM_CHUNK_GRAYS 2
237 static const unsigned chunk_gray[MAZEZAM_NUM_CHUNK_GRAYS] = {
238 LCD_LIGHTGRAY,
239 LCD_DARKGRAY,
241 /* darker version of the above */
242 static const unsigned chunk_gray_shade[MAZEZAM_NUM_CHUNK_GRAYS] = {
243 LCD_DARKGRAY,
244 LCD_BLACK,
246 #endif
248 #define MAZEZAM_GAMEOVER_TEXT "Game Over"
249 #define MAZEZAM_GAMEOVER_DELAY (3 * HZ) / 2
250 #define MAZEZAM_LEVEL_LIVES_TEXT "Level %d, Lives %d"
251 #define MAZEZAM_LEVEL_LIVES_DELAY HZ
252 #define MAZEZAM_WELLDONE_DELAY 4 * HZ
254 /* The maximum number of lines that a text page can display.
255 * This must be 4 or less if the Archos recorder is to be
256 * supported.
258 #define MAZEZAM_TEXT_MAXLINES 4
260 /* A structure for holding text pages */
261 struct textpage {
262 /* Ensure 1 < num_lines <= MAZEZAM_TEXT_MAXLINES */
263 short num_lines;
264 char *line[MAZEZAM_TEXT_MAXLINES]; /* text of lines */
267 /* The text page for the welcome screen */
268 static const struct textpage title_page = {
270 {"MazezaM", "play game", "instructions", "quit"}
273 /* The number of help screens */
274 #define MAZEZAM_NUM_HELP_PAGES 4
276 /* The instruction screens */
277 static const struct textpage help_page[] = {
278 {4,{"Instructions","10 mazezams","bar your way","to freedom"}},
279 {4,{"Instructions","Push the rows","left and right","to escape"}},
280 {4,{"Instructions","Press " MAZEZAM_RETRY_KEYNAME " to","retry a level",
281 "(lose 1 life)"}},
282 {4,{"Instructions","Press " MAZEZAM_QUIT_KEYNAME,"to quit","the game"}}
285 /* the text of the screen that asks for a quit confirmation */
286 static const struct textpage confirm_page = {
288 {"Quit","Are you sure?","yes","no"}
291 /* the text of the screen at the end of the game */
292 static const struct textpage welldone_page = {
294 {"Well Done","You have","escaped",""}
297 /* the text of the screen asking if the user wants to
298 * resume or start a new game.
300 static const struct textpage resume_page = {
302 {"Checkpoint", "continue", "new game"}
305 /* maximum height of a level */
306 #define MAZEZAM_MAX_LINES 11
307 /* maximum number of chunks on a line */
308 #define MAZEZAM_MAX_CHUNKS 5
310 /* A structure for holding levels */
311 struct mazezam_level {
312 short height; /* the number of lines */
313 short width; /* the width */
314 short entrance; /* the line on which the entrance lies */
315 short exit; /* the line on which the exit lies */
316 char *line[MAZEZAM_MAX_LINES]; /* the chunk data in string form */
319 /* The number of levels. Note that the instruction screens reference this
320 * number
322 #define MAZEZAM_NUM_LEVELS 10
324 /* The levels. In theory, they could be stored in a file so this data
325 * structure should not be accessed outside parse_level()
327 * These levels are copyright (C) 2002 Malcolm Tyrrell. They're
328 * probably covered by the GPL as they constitute part of the source
329 * code of this plugin, but you may distibute them seperately with
330 * other Free Software if you want. You can download them from:
331 * http://webpages.dcu.ie/~tyrrelma/MazezaM.
333 static const struct mazezam_level level_data[MAZEZAM_NUM_LEVELS] = {
334 {2,7,0,0,{" $ $"," $ $$",NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
335 NULL}},
336 {3,8,2,1,{" $ $$$"," $ $ $"," $ $ $",NULL,NULL,NULL,NULL,NULL,NULL,
337 NULL,NULL}},
338 {4,14,1,3,{" $$$$$ $$ $$"," $$ $$ $$","$$ $ $$ $$$",
339 " $$$$$$$$ $",NULL,NULL,NULL,NULL,NULL,NULL,NULL}},
340 {6,7,4,2,{" $"," $$$$"," $$$ $$"," $ $ $"," $ $$","$ $$",
341 NULL,NULL,NULL,NULL,NULL}},
342 {6,13,0,0,{" $$$$$","$ $$$$$ $$$"," $ $$$ $$$$",
343 "$ $ $$$$$$$"," $$$ $ $$","$ $ $ $$ $",NULL,NULL,
344 NULL,NULL,NULL}},
345 {11,5,10,0,{" $"," $ $$"," $$","$ $"," $ $"," $$$","$ $",
346 " $ $"," $ $","$ $$"," $"}},
347 {7,16,0,6,{" $$$$$$$"," $$$$ $$$$ $ $","$$ $$ $$$$$$ $ $",
348 "$ $ $"," $$$$$$$$$$$$$$"," $ $$ $ $$$",
349 " $ $$$ $$",NULL,NULL,NULL,NULL}},
350 {4,15,2,0,{" $$$$ $$$$ $$"," $ $$ $$ $ $$"," $ $$ $$$$ $$",
351 " $ $$ $$$$ $",NULL,NULL,NULL,NULL,NULL,NULL,NULL}},
352 {7,9,6,2,{" $ $$$$"," $ $ $$"," $ $$$$ $","$ $$ $"," $ $$$",
353 " $$$$$$"," $",NULL,NULL,NULL,NULL}},
354 {10,14,8,0,{" $"," $$$$$$$$$$ $"," $$$ $$",
355 " $ $$$$$$$$ $"," $$$ $$$ $$$"," $$$ $ $$$",
356 " $ $$$$$$$ $$"," $ $ $ $$$"," $$$$$$$$$$$$",
357 "",NULL}}
360 /* This is the data structure the game uses for managing levels */
361 struct chunk_data {
362 /* the number of chunks on a line */
363 short l_num[MAZEZAM_MAX_LINES];
364 /* the width of a chunk */
365 short c_width[MAZEZAM_MAX_LINES][MAZEZAM_MAX_CHUNKS];
366 /* the inset of a chunk */
367 short c_inset[MAZEZAM_MAX_LINES][MAZEZAM_MAX_CHUNKS];
370 /* The state and exit code of the level loop */
371 enum level_state {
372 LEVEL_STATE_LOOPING,
373 LEVEL_STATE_COMPLETED,
374 LEVEL_STATE_FAILED,
375 LEVEL_STATE_QUIT,
376 LEVEL_STATE_PARSE_ERROR,
377 LEVEL_STATE_USB_CONNECTED,
380 /* The state and exit code of the text screens. I use the
381 * same enum for all of them, even though there are some
382 * differences.
384 enum text_state {
385 TEXT_STATE_LOOPING,
386 TEXT_STATE_QUIT,
387 TEXT_STATE_OKAY,
388 TEXT_STATE_USB_CONNECTED,
389 TEXT_STATE_PARSE_ERROR,
390 TEXT_STATE_BACK,
393 /* The state and exit code of the game loop */
394 enum game_state {
395 GAME_STATE_LOOPING,
396 GAME_STATE_QUIT,
397 GAME_STATE_OKAY,
398 GAME_STATE_USB_CONNECTED,
399 GAME_STATE_OVER,
400 GAME_STATE_COMPLETED,
401 GAME_STATE_PARSE_ERROR,
404 /* The various constants needed for configuration files.
405 * See apps/plugins/lib/configfile.*
407 #define MAZEZAM_CONFIG_FILENAME "mazezam.data"
408 #define MAZEZAM_CONFIG_NUM_ITEMS 1
409 #define MAZEZAM_CONFIG_VERSION 0
410 #define MAZEZAM_CONFIG_MINVERSION 0
411 #define MAZEZAM_CONFIG_LEVELS_NAME "restart_level"
413 /* A structure containing the data that is written to
414 * the configuration file
416 struct resume_data {
417 int level; /* level at which to restart the game */
420 /* Display a screen of text. line[0] is the heading.
421 * line[highlight] will be highlighted, unless highlight == 0
423 static void display_text_page(struct textpage text, int highlight)
425 int w[text.num_lines], h[text.num_lines];
426 int hsum,i,vgap,vnext;
428 rb->lcd_clear_display();
430 /* find out how big the text is so we can determine the positioning */
431 hsum = 0;
432 for(i = 0; i < text.num_lines; i++) {
433 rb->lcd_getstringsize(text.line[i], w+i, h+i);
434 hsum += h[i];
437 vgap = (LCD_HEIGHT-hsum)/(text.num_lines+1);
439 /* The Heading */
441 #ifdef HAVE_LCD_COLOR
442 rb->lcd_set_foreground(MAZEZAM_BORDER_COLOR);
443 #elif LCD_DEPTH > 1
444 rb->lcd_set_foreground(MAZEZAM_BORDER_GRAY);
445 #endif
446 rb->lcd_drawrect((LCD_WIDTH-w[0])/2-MAZEZAM_MENU_BORDER,
447 vgap-MAZEZAM_MENU_BORDER, w[0] + 2*MAZEZAM_MENU_BORDER,
448 h[0] + 2*MAZEZAM_MENU_BORDER);
449 rb->lcd_drawrect((LCD_WIDTH-w[0])/2-MAZEZAM_MENU_BORDER*2,
450 vgap-MAZEZAM_MENU_BORDER*2, w[0] + 4*MAZEZAM_MENU_BORDER,
451 h[0] + 4*MAZEZAM_MENU_BORDER);
452 rb->lcd_drawline(0,vgap + h[0]/2,(LCD_WIDTH-w[0])/2-MAZEZAM_MENU_BORDER*2,
453 vgap + h[0]/2);
454 rb->lcd_drawline((LCD_WIDTH-w[0])/2+w[0]+MAZEZAM_MENU_BORDER*2,
455 vgap + h[0]/2,LCD_WIDTH-1,vgap + h[0]/2);
456 #ifdef HAVE_LCD_COLOR
457 rb->lcd_set_foreground(MAZEZAM_HEADING_COLOR);
458 #elif LCD_DEPTH > 1
459 rb->lcd_set_foreground(MAZEZAM_HEADING_GRAY);
460 #endif
461 rb->lcd_putsxy((LCD_WIDTH-w[0])/2,vgap,text.line[0]);
463 vnext = vgap*2 + h[0];
465 /* The other lines */
467 #ifdef HAVE_LCD_COLOR
468 rb->lcd_set_foreground(MAZEZAM_TEXT_COLOR);
469 #elif LCD_DEPTH > 1
470 rb->lcd_set_foreground(MAZEZAM_TEXT_GRAY);
471 #endif
472 for (i = 1; i<text.num_lines; i++) {
473 rb->lcd_putsxy((LCD_WIDTH-w[i])/2,vnext,text.line[i]);
475 /* add underlining if i is the highlighted line */
476 if (i == highlight) {
477 rb->lcd_drawline((LCD_WIDTH-w[i])/2, vnext + h[i] + 1,
478 (LCD_WIDTH-w[i])/2 + w[i], vnext + h[i] + 1);
481 vnext += vgap + h[i];
484 rb->lcd_update();
488 /* Parse the level data from the level_data structure. This could be
489 * replaced by a file read. Returns true if the level parsed correctly.
491 static bool parse_level(short level, struct chunk_data *cd,
492 short *width, short *height, short *entrance, short *exit)
494 int i,j;
495 char c,clast;
497 *width = level_data[level].width;
498 *height = level_data[level].height;
499 *entrance = level_data[level].entrance;
500 *exit = level_data[level].exit;
502 /* for each line in the level */
503 for (i = 0; i<level_data[level].height; i++) {
504 if (level_data[level].line[i] == NULL)
505 return false;
506 else {
507 j = 0;
508 cd->l_num[i] = 0;
509 clast = ' '; /* the character we last considered */
510 while ((c = level_data[level].line[i][j]) != '\0') {
511 if (c != ' ') {
512 if (clast == ' ') {
513 cd->l_num[i] += 1;
514 if (cd->l_num[i] > MAZEZAM_MAX_CHUNKS)
515 return false;
516 cd->c_inset[i][cd->l_num[i] - 1] = j;
517 cd->c_width[i][cd->l_num[i] - 1] = 1;
519 else
520 cd->c_width[i][cd->l_num[i] - 1] += 1;
522 clast = c;
523 j++;
527 return true;
530 /* Draw the level */
531 static void draw_level(
532 struct chunk_data *cd, /* the data about the chunks */
533 short *shift, /* an array of the horizontal offset of the lines */
534 short width,
535 short height,
536 short entrance,
537 short exit,
538 short x, /* player's x and y coords */
539 short y)
541 /* The number of pixels the side of a square should be */
542 short size = (LCD_WIDTH/(width+2)) < (LCD_HEIGHT/height) ?
543 (LCD_WIDTH/(width+2)) : (LCD_HEIGHT/height);
544 /* The x and y position (in pixels) of the top left corner of the
545 * level
547 short xOff = (LCD_WIDTH - (size*width))/2;
548 short yOff = (LCD_HEIGHT - (size*height))/2;
549 /* For drawing the player, taken from the sokoban plugin */
550 short max = size - 1;
551 short middle = max / 2;
552 short ldelta = (middle + 1) / 2;
553 short i,j;
554 short third = size / 3;
555 short twothirds = (2 * size) / 3;
556 #ifndef HAVE_LCD_COLOR
557 /* We #def these out to supress a compiler warning */
558 short k;
559 #if LCD_DEPTH <= 1
560 short l;
561 #endif
562 #endif
564 rb->lcd_clear_display();
566 #ifdef HAVE_LCD_COLOR
567 rb->lcd_set_foreground(MAZEZAM_WALL_COLOR);
568 #elif LCD_DEPTH > 1
569 rb->lcd_set_foreground(MAZEZAM_WALL_GRAY);
570 #endif
571 /* draw the upper wall */
572 rb->lcd_fillrect(0,0,xOff,yOff+(size*entrance));
573 rb->lcd_fillrect(xOff,0,size*width,yOff);
574 rb->lcd_fillrect(xOff+(size*width),0,LCD_WIDTH-xOff-(size*width),
575 yOff+(size*exit));
577 /* draw the lower wall */
578 rb->lcd_fillrect(0,yOff+(size*entrance)+size,xOff,
579 LCD_HEIGHT-yOff-(size*entrance)-size);
580 rb->lcd_fillrect(xOff,yOff+(size*height),size*width,
581 LCD_HEIGHT-yOff-(size*height));
582 /* Note: the exit is made one pixel thinner than necessary as a visual
583 * clue that chunks cannot be pushed into it
585 rb->lcd_fillrect(xOff+(size*width),yOff+(size*exit)+size-1,
586 LCD_WIDTH-xOff+(size*width),
587 LCD_HEIGHT-yOff-(size*exit)-size+1);
589 /* draw the chunks */
590 for (i = 0; i<height; i++) {
591 #ifdef HAVE_LCD_COLOR
592 /* adding width to i should have a fixed, but randomising effect on
593 * the choice of the colours of the top line of chunks
595 rb->lcd_set_foreground(chunk_colors[(i+width) %
596 MAZEZAM_NUM_CHUNK_COLORS]);
597 #endif
598 for (j = 0; j<cd->l_num[i]; j++) {
599 #ifdef HAVE_LCD_COLOR
600 rb->lcd_fillrect(xOff+size*shift[i]+size*cd->c_inset[i][j],
601 yOff+size*i, cd->c_width[i][j]*size,size);
602 #elif LCD_DEPTH > 1
603 rb->lcd_set_foreground(MAZEZAM_CHUNK_EDGE_GRAY);
604 rb->lcd_drawrect(xOff+size*shift[i]+size*cd->c_inset[i][j],
605 yOff+size*i, cd->c_width[i][j]*size,size);
607 /* draw shade */
608 rb->lcd_set_foreground(chunk_gray_shade[(i+width) %
609 MAZEZAM_NUM_CHUNK_GRAYS]);
610 rb->lcd_drawline(xOff+size*shift[i]+size*cd->c_inset[i][j]+1,
611 yOff+size*i+size-2,
612 xOff+size*shift[i]+size*cd->c_inset[i][j]+
613 cd->c_width[i][j]*size-3,
614 yOff+size*i+size-2);
615 rb->lcd_drawline(xOff+size*shift[i]+size*cd->c_inset[i][j]+
616 cd->c_width[i][j]*size-2,
617 yOff+size*i,
618 xOff+size*shift[i]+size*cd->c_inset[i][j]+
619 cd->c_width[i][j]*size-2,
620 yOff+size*i+size-2);
622 /* draw fill */
623 rb->lcd_set_foreground(chunk_gray[(i+width) %
624 MAZEZAM_NUM_CHUNK_GRAYS]);
625 for (k = yOff+size*i+2; k < yOff+size*i+size-2; k += 2)
626 rb->lcd_drawline(xOff+size*shift[i]+size*cd->c_inset[i][j]+2,k,
627 xOff+size*shift[i]+size*cd->c_inset[i][j]+
628 cd->c_width[i][j]*size-3,k);
629 #else
630 rb->lcd_drawrect(xOff+size*shift[i]+size*cd->c_inset[i][j],
631 yOff+size*i, cd->c_width[i][j]*size,size);
632 for (k = xOff+size*shift[i]+size*cd->c_inset[i][j]+2;
633 k < xOff+size*shift[i]+size*cd->c_inset[i][j]+
634 cd->c_width[i][j]*size;
635 k += 2 + (i & 1))
636 for (l = yOff+size*i+2; l < yOff+size*i+size; l += 2 + (i & 1))
637 rb->lcd_drawpixel(k, l);
638 #endif
642 /* draw the player (mostly copied from the sokoban plugin) */
643 #ifdef HAVE_LCD_COLOR
644 rb->lcd_set_foreground(MAZEZAM_PLAYER_COLOR);
645 #elif LCD_DEPTH > 1
646 rb->lcd_set_foreground(MAZEZAM_PLAYER_GRAY);
647 #endif
648 rb->lcd_drawline(xOff+size*x, yOff+size*y+middle,
649 xOff+size*x+max, yOff+size*y+middle);
650 rb->lcd_drawline(xOff+size*x+middle, yOff+size*y,
651 xOff+size*x+middle, yOff+size*y+max-ldelta);
652 rb->lcd_drawline(xOff+size*x+middle, yOff+size*y+max-ldelta,
653 xOff+size*x+middle-ldelta, yOff+size*y+max);
654 rb->lcd_drawline(xOff+size*x+middle, yOff+size*y+max-ldelta,
655 xOff+size*x+middle+ldelta, yOff+size*y+max);
657 /* draw the gate, if the player has moved into the level */
658 if (x >= 0) {
659 #ifdef HAVE_LCD_COLOR
660 rb->lcd_set_foreground(MAZEZAM_GATE_COLOR);
661 #elif LCD_DEPTH > 1
662 rb->lcd_set_foreground(MAZEZAM_GATE_GRAY);
663 #endif
664 rb->lcd_drawline(xOff-size,yOff+entrance*size+third,
665 xOff-1,yOff+entrance*size+third);
666 rb->lcd_drawline(xOff-size,yOff+entrance*size+twothirds,
667 xOff-1,yOff+entrance*size+twothirds);
668 rb->lcd_drawline(xOff-size+third,yOff+entrance*size,
669 xOff-size+third,yOff+entrance*size+size-1);
670 rb->lcd_drawline(xOff-size+twothirds,yOff+entrance*size,
671 xOff-size+twothirds,yOff+entrance*size+size-1);
675 /* Manage the congratulations screen */
676 static enum text_state welldone_screen(void)
678 int button = BUTTON_NONE;
679 enum text_state state = TEXT_STATE_LOOPING;
681 display_text_page(welldone_page, 0);
683 while (state == TEXT_STATE_LOOPING) {
684 button = rb->button_get(true);
686 switch (button) {
687 case MAZEZAM_QUIT:
688 state = TEXT_STATE_QUIT;
689 break;
691 case MAZEZAM_SELECT:
692 #if CONFIG_KEYPAD != ONDIO_PAD
693 case MAZEZAM_RIGHT:
694 #endif
695 state = TEXT_STATE_OKAY;
696 break;
698 default:
699 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
700 state = TEXT_STATE_USB_CONNECTED;
701 break;
705 return state;
708 /* Manage the quit confimation screen */
709 static enum text_state quitconfirm_loop(void)
711 int button = BUTTON_NONE;
712 enum text_state state = TEXT_STATE_LOOPING;
713 short select = 2;
715 display_text_page(confirm_page, select + 1);
717 /* Wait for a button release. This is useful when a repeated button
718 * press is used for quit.
720 while ((rb->button_get(true) & BUTTON_REL) != BUTTON_REL);
722 while (state == TEXT_STATE_LOOPING) {
723 display_text_page(confirm_page, select + 1);
725 button = rb->button_get(true);
727 switch (button) {
728 case MAZEZAM_QUIT:
729 state = TEXT_STATE_QUIT;
730 break;
732 case MAZEZAM_UP:
733 case MAZEZAM_DOWN:
734 select = (2 - select) + 1;
735 break;
737 case MAZEZAM_SELECT:
738 #if CONFIG_KEYPAD != ONDIO_PAD
739 case MAZEZAM_RIGHT:
740 #endif
741 if (select == 1)
742 state = TEXT_STATE_QUIT;
743 else
744 state = TEXT_STATE_OKAY;
745 break;
747 default:
748 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
749 state = TEXT_STATE_USB_CONNECTED;
750 break;
754 return state;
757 /* Manage the playing of a level */
758 static enum level_state level_loop(short level, short lives)
760 struct chunk_data cd;
761 short shift[MAZEZAM_MAX_LINES]; /* amount each line has been shifted */
762 short width;
763 short height;
764 short entrance;
765 short exit;
766 short i;
767 short x,y;
768 int button;
769 enum level_state state = LEVEL_STATE_LOOPING;
770 bool blocked; /* is there a chunk in the way of the player? */
772 if (!(parse_level(level,&cd,&width,&height,&entrance,&exit)))
773 return LEVEL_STATE_PARSE_ERROR;
775 for (i = 0; i < height; i++)
776 shift[i] = 0;
778 x = -1;
779 y = entrance;
781 draw_level(&cd, shift, width, height, entrance, exit, x, y);
783 #ifdef HAVE_REMOTE_LCD
784 /* Splash text seems to use the remote display by
785 * default. I suppose I better keep it tidy!
787 rb->lcd_remote_clear_display();
788 #endif
789 rb->splash(MAZEZAM_LEVEL_LIVES_DELAY, MAZEZAM_LEVEL_LIVES_TEXT,
790 level+1, lives);
792 /* ensure keys pressed during the splash screen are ignored */
793 rb->button_clear_queue();
795 while (state == LEVEL_STATE_LOOPING) {
796 draw_level(&cd, shift, width, height, entrance, exit, x, y);
797 rb->lcd_update();
798 button = rb->button_get(true);
799 blocked = false;
801 switch (button) {
802 case MAZEZAM_UP:
803 case MAZEZAM_UP | BUTTON_REPEAT:
804 if ((y > 0) && (x >= 0) && (x < width)) {
805 for (i = 0; i < cd.l_num[y-1]; i++)
806 blocked = blocked ||
807 ((x>=shift[y-1]+cd.c_inset[y-1][i]) &&
808 (x<shift[y-1]+cd.c_inset[y-1][i]+
809 cd.c_width[y-1][i]));
810 if (!blocked) y -= 1;
812 break;
814 case MAZEZAM_DOWN:
815 case MAZEZAM_DOWN | BUTTON_REPEAT:
816 if ((y < height-1) && (x >= 0) && (x < width)) {
817 for (i = 0; i < cd.l_num[y+1]; i++)
818 blocked = blocked ||
819 ((x>=shift[y+1]+cd.c_inset[y+1][i]) &&
820 (x<shift[y+1]+cd.c_inset[y+1][i]+
821 cd.c_width[y+1][i]));
822 if (!blocked) y += 1;
824 break;
826 case MAZEZAM_LEFT:
827 case MAZEZAM_LEFT | BUTTON_REPEAT:
828 if (x > 0) {
829 for (i = 0; i < cd.l_num[y]; i++)
830 blocked = blocked ||
831 (x == shift[y]+cd.c_inset[y][i]+
832 cd.c_width[y][i]);
833 if (!blocked) x -= 1;
834 else if (shift[y] + cd.c_inset[y][0] > 0) {
835 x -= 1;
836 shift[y] -= 1;
839 break;
841 case MAZEZAM_RIGHT:
842 case MAZEZAM_RIGHT | BUTTON_REPEAT:
843 if (x < width-1) {
844 for (i = 0; i < cd.l_num[y]; i++)
845 blocked = blocked || (x+1 == shift[y]+cd.c_inset[y][i]);
846 if (!blocked) x += 1;
847 else if (shift[y] + cd.c_inset[y][cd.l_num[y]-1] +
848 cd.c_width[y][cd.l_num[y]-1] < width) {
849 x += 1;
850 shift[y] += 1;
853 else if (x == width) state = LEVEL_STATE_COMPLETED;
854 else if (y == exit) x += 1;
855 break;
857 case MAZEZAM_RETRY:
858 state = LEVEL_STATE_FAILED;
859 break;
861 case MAZEZAM_QUIT:
862 switch (quitconfirm_loop()) {
863 case TEXT_STATE_QUIT:
864 state = LEVEL_STATE_QUIT;
865 break;
867 case TEXT_STATE_USB_CONNECTED:
868 state = LEVEL_STATE_USB_CONNECTED;
869 break;
871 default:
872 break;
874 break;
876 default:
877 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
878 state = LEVEL_STATE_USB_CONNECTED;
879 break;
883 return state;
886 /* The loop which manages a full game of MazezaM */
887 static enum game_state game_loop(struct resume_data *r)
889 enum game_state state = GAME_STATE_LOOPING;
890 int level = r->level;
891 int lives = MAZEZAM_START_LIVES;
893 rb->lcd_clear_display();
895 while (state == GAME_STATE_LOOPING)
897 switch (level_loop(level,lives)) {
898 case LEVEL_STATE_COMPLETED:
899 level += 1;
900 if (!((level - r->level) % MAZEZAM_EXTRA_LIFE))
901 lives += 1;
902 break;
904 case LEVEL_STATE_QUIT:
905 state = GAME_STATE_QUIT;
906 break;
908 case LEVEL_STATE_FAILED:
909 lives -= 1;
910 break;
912 case LEVEL_STATE_PARSE_ERROR:
913 state = GAME_STATE_PARSE_ERROR;
914 break;
916 case LEVEL_STATE_USB_CONNECTED:
917 state = GAME_STATE_USB_CONNECTED;
918 break;
920 default:
921 break;
923 if (lives == 0)
924 state = GAME_STATE_OVER;
925 else if (level == MAZEZAM_NUM_LEVELS)
926 state = GAME_STATE_COMPLETED;
929 switch (state) {
930 case GAME_STATE_OVER:
931 #ifdef HAVE_REMOTE_LCD
932 /* Splash text seems to use the remote display by
933 * default. I suppose I better keep it tidy!
935 rb->lcd_remote_clear_display();
936 #endif
937 rb->splash(MAZEZAM_GAMEOVER_DELAY, MAZEZAM_GAMEOVER_TEXT);
938 break;
940 case GAME_STATE_COMPLETED:
941 switch (welldone_screen()) {
942 case TEXT_STATE_QUIT:
943 state = GAME_STATE_QUIT;
944 break;
946 case TEXT_STATE_USB_CONNECTED:
947 state = GAME_STATE_USB_CONNECTED;
948 break;
950 default:
951 state = GAME_STATE_OKAY;
952 break;
954 break;
956 default:
957 break;
960 /* This particular resume game logic is designed to make
961 * players prove they can solve a level more than once
963 if (level > r->level + 1)
964 r->level += 1;
966 return state;
969 /* Manage the instruction screen */
970 static enum text_state instruction_loop(void)
972 int button;
973 enum text_state state = TEXT_STATE_LOOPING;
974 int page = 0;
976 while (state == TEXT_STATE_LOOPING) {
977 display_text_page(help_page[page], 0);
978 button = rb->button_get(true);
980 switch (button) {
981 case MAZEZAM_LEFT:
982 page -= 1;
983 break;
985 case MAZEZAM_SELECT:
986 #if CONFIG_KEYPAD != ONDIO_PAD
987 case MAZEZAM_RIGHT:
988 #endif
989 page += 1;
990 break;
992 case MAZEZAM_QUIT:
993 state = TEXT_STATE_QUIT;
994 break;
996 default:
997 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
998 state = TEXT_STATE_USB_CONNECTED;
999 break;
1003 if ((page < 0) || (page >= MAZEZAM_NUM_HELP_PAGES))
1004 state = TEXT_STATE_OKAY;
1007 return state;
1010 /* Manage the text screen that offers the user the option of
1011 * resuming or starting a new game
1013 static enum text_state resume_game_loop (struct resume_data *r)
1015 int button = BUTTON_NONE;
1016 enum text_state state = TEXT_STATE_LOOPING;
1017 short select = 0;
1019 /* if the resume level is 0, don't bother asking */
1020 if (r->level == 0) return TEXT_STATE_OKAY;
1022 display_text_page(resume_page, select + 1);
1024 while (state == TEXT_STATE_LOOPING) {
1025 display_text_page(resume_page, select + 1);
1027 button = rb->button_get(true);
1029 switch (button) {
1030 case MAZEZAM_QUIT:
1031 state = TEXT_STATE_QUIT;
1032 break;
1034 case MAZEZAM_LEFT:
1035 state = TEXT_STATE_BACK;
1036 break;
1038 case MAZEZAM_UP:
1039 case MAZEZAM_DOWN:
1040 select = 1 - select;
1041 break;
1043 case MAZEZAM_SELECT:
1044 #if CONFIG_KEYPAD != ONDIO_PAD
1045 case MAZEZAM_RIGHT:
1046 #endif
1047 if (select == 1) {
1048 /* The player wants to play a new game. I could ask
1049 * for confirmation here, but the only penalty is
1050 * playing through some already completed levels,
1051 * so I don't think it's necessary
1053 r->level = 0;
1055 state = TEXT_STATE_OKAY;
1056 break;
1058 default:
1059 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
1060 state = TEXT_STATE_USB_CONNECTED;
1061 break;
1065 return state;
1068 /* Load the resume data from the config file. The data is
1069 * stored in both r and old.
1071 static void resume_load_data (struct resume_data *r, struct resume_data *old)
1073 struct configdata config[] = {
1074 {TYPE_INT,0,MAZEZAM_NUM_LEVELS-1,&(r->level),
1075 MAZEZAM_CONFIG_LEVELS_NAME,NULL,NULL}
1078 if (configfile_load(MAZEZAM_CONFIG_FILENAME,config,MAZEZAM_CONFIG_NUM_ITEMS,
1079 MAZEZAM_CONFIG_VERSION) < 0)
1080 r->level = 0;
1081 /* an extra precaution */
1082 else if ((r->level < 0) || (MAZEZAM_NUM_LEVELS <= r->level))
1083 r->level = 0;
1085 old->level = r->level;
1088 /* Save the resume data in the config file, but only if necessary */
1089 static void resume_save_data (struct resume_data *r, struct resume_data *old)
1091 struct configdata config[] = {
1092 {TYPE_INT,0,MAZEZAM_NUM_LEVELS-1,&(r->level),
1093 MAZEZAM_CONFIG_LEVELS_NAME,NULL,NULL}
1096 /* To reduce disk usage, only write the file if the resume data has
1097 * changed.
1099 if (old->level != r->level)
1100 configfile_save(MAZEZAM_CONFIG_FILENAME,config,MAZEZAM_CONFIG_NUM_ITEMS,
1101 MAZEZAM_CONFIG_MINVERSION);
1104 /* The loop which manages the welcome screen and menu */
1105 static enum text_state welcome_loop(void)
1107 int button;
1108 short select = 0;
1109 enum text_state state = TEXT_STATE_LOOPING;
1110 struct resume_data r_data, old_data;
1112 /* Load data */
1113 resume_load_data(&r_data, &old_data);
1115 while (state == TEXT_STATE_LOOPING) {
1116 display_text_page(title_page, select + 1);
1117 button = rb->button_get(true);
1119 switch (button) {
1120 case MAZEZAM_QUIT:
1121 state = TEXT_STATE_QUIT;
1122 break;
1124 case MAZEZAM_UP:
1125 select = (select + (title_page.num_lines - 2)) %
1126 (title_page.num_lines - 1);
1127 break;
1129 case MAZEZAM_DOWN:
1130 select = (select + 1) % (title_page.num_lines - 1);
1131 break;
1133 case MAZEZAM_SELECT:
1134 #if CONFIG_KEYPAD != ONDIO_PAD
1135 case MAZEZAM_RIGHT:
1136 #endif
1137 if (select == 0) { /* play game */
1138 switch (resume_game_loop(&r_data)) {
1139 case TEXT_STATE_QUIT:
1140 state = TEXT_STATE_QUIT;
1141 break;
1143 case TEXT_STATE_USB_CONNECTED:
1144 state = TEXT_STATE_USB_CONNECTED;
1145 break;
1147 case TEXT_STATE_BACK:
1148 break;
1150 default: { /* Ouch! This nesting is too deep! */
1151 switch (game_loop(&r_data)) {
1152 case GAME_STATE_QUIT:
1153 state = TEXT_STATE_QUIT;
1154 break;
1156 case GAME_STATE_USB_CONNECTED:
1157 state = TEXT_STATE_USB_CONNECTED;
1158 break;
1160 case GAME_STATE_PARSE_ERROR:
1161 state = TEXT_STATE_PARSE_ERROR;
1162 break;
1164 default:
1165 break;
1167 break;
1171 else if (select == 1) { /* Instructions */
1172 switch (instruction_loop()) {
1173 case TEXT_STATE_QUIT:
1174 state = TEXT_STATE_QUIT;
1175 break;
1177 case TEXT_STATE_USB_CONNECTED:
1178 state = TEXT_STATE_USB_CONNECTED;
1179 break;
1181 default:
1182 break;
1185 else /* Quit */
1186 state = TEXT_STATE_QUIT;
1188 break;
1190 default:
1191 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
1192 state = TEXT_STATE_USB_CONNECTED;
1193 break;
1197 /* I'm not sure if it's appropriate to write to disk on USB events.
1198 * Currently, I do so.
1200 resume_save_data(&r_data, &old_data);
1202 return state;
1205 /* Plugin entry point */
1206 enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
1208 enum plugin_status state;
1210 /* Usual plugin stuff */
1211 (void)parameter;
1212 rb = api;
1214 /* Turn off backlight timeout */
1215 backlight_force_on(rb); /* backlight control in lib/helper.c */
1217 #ifdef HAVE_LCD_COLOR
1218 rb->lcd_set_background(MAZEZAM_BG_COLOR);
1219 rb->lcd_set_backdrop(NULL);
1220 #elif LCD_DEPTH > 1
1221 rb->lcd_set_background(MAZEZAM_BG_GRAY);
1222 #endif
1223 rb->lcd_setfont(FONT_SYSFIXED);
1225 /* initialise the config file module */
1226 configfile_init(rb);
1228 switch (welcome_loop()) {
1229 case TEXT_STATE_USB_CONNECTED:
1230 state = PLUGIN_USB_CONNECTED;
1231 break;
1233 case TEXT_STATE_PARSE_ERROR:
1234 state = PLUGIN_ERROR;
1235 break;
1237 default:
1238 state = PLUGIN_OK;
1239 break;
1242 /* Turn on backlight timeout (revert to settings) */
1243 backlight_use_settings(rb); /* backlight control in lib/helper.c */
1245 return state;