Adapt most single-file plugins to the M3 keypad and screen. It's still preliminary...
[kugel-rb.git] / apps / plugins / mazezam.c
blob69997af9c35e669371ac99987f7ecc78fcab3bfc
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 #else
181 #error No keymap defined!
182 #endif
184 /* The gap for the border around the heading in text pages. In fact, 2 is
185 * really the only acceptable value.
187 #define MAZEZAM_MENU_BORDER 2
188 #define MAZEZAM_EXTRA_LIFE 2 /* get an extra life every _ levels */
189 #define MAZEZAM_START_LIVES 3 /* how many lives at game start */
191 #ifdef HAVE_LCD_COLOR
192 #define MAZEZAM_HEADING_COLOR LCD_RGBPACK(255,255, 0) /* Yellow */
193 #define MAZEZAM_BORDER_COLOR LCD_RGBPACK( 0, 0,255) /* Blue */
194 #define MAZEZAM_TEXT_COLOR LCD_RGBPACK(255,255,255) /* White */
195 #define MAZEZAM_BG_COLOR LCD_RGBPACK( 0, 0, 0) /* Black */
196 #define MAZEZAM_WALL_COLOR LCD_RGBPACK(100,100,100) /* Dark gray */
197 #define MAZEZAM_PLAYER_COLOR LCD_RGBPACK(255,255,255) /* White */
198 #define MAZEZAM_GATE_COLOR LCD_RGBPACK(100,100,100) /* Dark gray */
200 /* the rows are coloured sequentially */
201 #define MAZEZAM_NUM_CHUNK_COLORS 8
202 static const unsigned chunk_colors[MAZEZAM_NUM_CHUNK_COLORS] = {
203 LCD_RGBPACK(255,192, 32), /* Orange */
204 LCD_RGBPACK(255, 0, 0), /* Red */
205 LCD_RGBPACK( 0,255, 0), /* Green */
206 LCD_RGBPACK( 0,255,255), /* Cyan */
207 LCD_RGBPACK(255,175,175), /* Pink */
208 LCD_RGBPACK(255,255, 0), /* Yellow */
209 LCD_RGBPACK( 0, 0,255), /* Blue */
210 LCD_RGBPACK(255, 0,255), /* Magenta */
213 #elif LCD_DEPTH > 1
215 #define MAZEZAM_HEADING_GRAY LCD_BLACK
216 #define MAZEZAM_BORDER_GRAY LCD_DARKGRAY
217 #define MAZEZAM_TEXT_GRAY LCD_BLACK
218 #define MAZEZAM_BG_GRAY LCD_WHITE
219 #define MAZEZAM_WALL_GRAY LCD_DARKGRAY
220 #define MAZEZAM_PLAYER_GRAY LCD_BLACK
221 #define MAZEZAM_GATE_GRAY LCD_BLACK
222 #define MAZEZAM_CHUNK_EDGE_GRAY LCD_BLACK
224 #define MAZEZAM_NUM_CHUNK_GRAYS 2
225 static const unsigned chunk_gray[MAZEZAM_NUM_CHUNK_GRAYS] = {
226 LCD_LIGHTGRAY,
227 LCD_DARKGRAY,
229 /* darker version of the above */
230 static const unsigned chunk_gray_shade[MAZEZAM_NUM_CHUNK_GRAYS] = {
231 LCD_DARKGRAY,
232 LCD_BLACK,
234 #endif
236 #define MAZEZAM_GAMEOVER_TEXT "Game Over"
237 #define MAZEZAM_GAMEOVER_DELAY (3 * HZ) / 2
238 #define MAZEZAM_LEVEL_LIVES_TEXT "Level %d, Lives %d"
239 #define MAZEZAM_LEVEL_LIVES_DELAY HZ
240 #define MAZEZAM_WELLDONE_DELAY 4 * HZ
242 /* The maximum number of lines that a text page can display.
243 * This must be 4 or less if the Archos recorder is to be
244 * supported.
246 #define MAZEZAM_TEXT_MAXLINES 4
248 /* A structure for holding text pages */
249 struct textpage {
250 /* Ensure 1 < num_lines <= MAZEZAM_TEXT_MAXLINES */
251 short num_lines;
252 char *line[MAZEZAM_TEXT_MAXLINES]; /* text of lines */
255 /* The text page for the welcome screen */
256 static const struct textpage title_page = {
258 {"MazezaM", "play game", "instructions", "quit"}
261 /* The number of help screens */
262 #define MAZEZAM_NUM_HELP_PAGES 4
264 /* The instruction screens */
265 static const struct textpage help_page[] = {
266 {4,{"Instructions","10 mazezams","bar your way","to freedom"}},
267 {4,{"Instructions","Push the rows","left and right","to escape"}},
268 {4,{"Instructions","Press " MAZEZAM_RETRY_KEYNAME " to","retry a level",
269 "(lose 1 life)"}},
270 {4,{"Instructions","Press " MAZEZAM_QUIT_KEYNAME,"to quit","the game"}}
273 /* the text of the screen that asks for a quit confirmation */
274 static const struct textpage confirm_page = {
276 {"Quit","Are you sure?","yes","no"}
279 /* the text of the screen at the end of the game */
280 static const struct textpage welldone_page = {
282 {"Well Done","You have","escaped",""}
285 /* the text of the screen asking if the user wants to
286 * resume or start a new game.
288 static const struct textpage resume_page = {
290 {"Checkpoint", "continue", "new game"}
293 /* maximum height of a level */
294 #define MAZEZAM_MAX_LINES 11
295 /* maximum number of chunks on a line */
296 #define MAZEZAM_MAX_CHUNKS 5
298 /* A structure for holding levels */
299 struct mazezam_level {
300 short height; /* the number of lines */
301 short width; /* the width */
302 short entrance; /* the line on which the entrance lies */
303 short exit; /* the line on which the exit lies */
304 char *line[MAZEZAM_MAX_LINES]; /* the chunk data in string form */
307 /* The number of levels. Note that the instruction screens reference this
308 * number
310 #define MAZEZAM_NUM_LEVELS 10
312 /* The levels. In theory, they could be stored in a file so this data
313 * structure should not be accessed outside parse_level()
315 * These levels are copyright (C) 2002 Malcolm Tyrrell. They're
316 * probably covered by the GPL as they constitute part of the source
317 * code of this plugin, but you may distibute them seperately with
318 * other Free Software if you want. You can download them from:
319 * http://webpages.dcu.ie/~tyrrelma/MazezaM.
321 static const struct mazezam_level level_data[MAZEZAM_NUM_LEVELS] = {
322 {2,7,0,0,{" $ $"," $ $$",NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
323 NULL}},
324 {3,8,2,1,{" $ $$$"," $ $ $"," $ $ $",NULL,NULL,NULL,NULL,NULL,NULL,
325 NULL,NULL}},
326 {4,14,1,3,{" $$$$$ $$ $$"," $$ $$ $$","$$ $ $$ $$$",
327 " $$$$$$$$ $",NULL,NULL,NULL,NULL,NULL,NULL,NULL}},
328 {6,7,4,2,{" $"," $$$$"," $$$ $$"," $ $ $"," $ $$","$ $$",
329 NULL,NULL,NULL,NULL,NULL}},
330 {6,13,0,0,{" $$$$$","$ $$$$$ $$$"," $ $$$ $$$$",
331 "$ $ $$$$$$$"," $$$ $ $$","$ $ $ $$ $",NULL,NULL,
332 NULL,NULL,NULL}},
333 {11,5,10,0,{" $"," $ $$"," $$","$ $"," $ $"," $$$","$ $",
334 " $ $"," $ $","$ $$"," $"}},
335 {7,16,0,6,{" $$$$$$$"," $$$$ $$$$ $ $","$$ $$ $$$$$$ $ $",
336 "$ $ $"," $$$$$$$$$$$$$$"," $ $$ $ $$$",
337 " $ $$$ $$",NULL,NULL,NULL,NULL}},
338 {4,15,2,0,{" $$$$ $$$$ $$"," $ $$ $$ $ $$"," $ $$ $$$$ $$",
339 " $ $$ $$$$ $",NULL,NULL,NULL,NULL,NULL,NULL,NULL}},
340 {7,9,6,2,{" $ $$$$"," $ $ $$"," $ $$$$ $","$ $$ $"," $ $$$",
341 " $$$$$$"," $",NULL,NULL,NULL,NULL}},
342 {10,14,8,0,{" $"," $$$$$$$$$$ $"," $$$ $$",
343 " $ $$$$$$$$ $"," $$$ $$$ $$$"," $$$ $ $$$",
344 " $ $$$$$$$ $$"," $ $ $ $$$"," $$$$$$$$$$$$",
345 "",NULL}}
348 /* This is the data structure the game uses for managing levels */
349 struct chunk_data {
350 /* the number of chunks on a line */
351 short l_num[MAZEZAM_MAX_LINES];
352 /* the width of a chunk */
353 short c_width[MAZEZAM_MAX_LINES][MAZEZAM_MAX_CHUNKS];
354 /* the inset of a chunk */
355 short c_inset[MAZEZAM_MAX_LINES][MAZEZAM_MAX_CHUNKS];
358 /* The state and exit code of the level loop */
359 enum level_state {
360 LEVEL_STATE_LOOPING,
361 LEVEL_STATE_COMPLETED,
362 LEVEL_STATE_FAILED,
363 LEVEL_STATE_QUIT,
364 LEVEL_STATE_PARSE_ERROR,
365 LEVEL_STATE_USB_CONNECTED,
368 /* The state and exit code of the text screens. I use the
369 * same enum for all of them, even though there are some
370 * differences.
372 enum text_state {
373 TEXT_STATE_LOOPING,
374 TEXT_STATE_QUIT,
375 TEXT_STATE_OKAY,
376 TEXT_STATE_USB_CONNECTED,
377 TEXT_STATE_PARSE_ERROR,
378 TEXT_STATE_BACK,
381 /* The state and exit code of the game loop */
382 enum game_state {
383 GAME_STATE_LOOPING,
384 GAME_STATE_QUIT,
385 GAME_STATE_OKAY,
386 GAME_STATE_USB_CONNECTED,
387 GAME_STATE_OVER,
388 GAME_STATE_COMPLETED,
389 GAME_STATE_PARSE_ERROR,
392 /* The various constants needed for configuration files.
393 * See apps/plugins/lib/configfile.*
395 #define MAZEZAM_CONFIG_FILENAME "mazezam.data"
396 #define MAZEZAM_CONFIG_NUM_ITEMS 1
397 #define MAZEZAM_CONFIG_VERSION 0
398 #define MAZEZAM_CONFIG_MINVERSION 0
399 #define MAZEZAM_CONFIG_LEVELS_NAME "restart_level"
401 /* A structure containing the data that is written to
402 * the configuration file
404 struct resume_data {
405 int level; /* level at which to restart the game */
408 /* Display a screen of text. line[0] is the heading.
409 * line[highlight] will be highlighted, unless highlight == 0
411 static void display_text_page(struct textpage text, int highlight)
413 int w[text.num_lines], h[text.num_lines];
414 int hsum,i,vgap,vnext;
416 rb->lcd_clear_display();
418 /* find out how big the text is so we can determine the positioning */
419 hsum = 0;
420 for(i = 0; i < text.num_lines; i++) {
421 rb->lcd_getstringsize(text.line[i], w+i, h+i);
422 hsum += h[i];
425 vgap = (LCD_HEIGHT-hsum)/(text.num_lines+1);
427 /* The Heading */
429 #ifdef HAVE_LCD_COLOR
430 rb->lcd_set_foreground(MAZEZAM_BORDER_COLOR);
431 #elif LCD_DEPTH > 1
432 rb->lcd_set_foreground(MAZEZAM_BORDER_GRAY);
433 #endif
434 rb->lcd_drawrect((LCD_WIDTH-w[0])/2-MAZEZAM_MENU_BORDER,
435 vgap-MAZEZAM_MENU_BORDER, w[0] + 2*MAZEZAM_MENU_BORDER,
436 h[0] + 2*MAZEZAM_MENU_BORDER);
437 rb->lcd_drawrect((LCD_WIDTH-w[0])/2-MAZEZAM_MENU_BORDER*2,
438 vgap-MAZEZAM_MENU_BORDER*2, w[0] + 4*MAZEZAM_MENU_BORDER,
439 h[0] + 4*MAZEZAM_MENU_BORDER);
440 rb->lcd_drawline(0,vgap + h[0]/2,(LCD_WIDTH-w[0])/2-MAZEZAM_MENU_BORDER*2,
441 vgap + h[0]/2);
442 rb->lcd_drawline((LCD_WIDTH-w[0])/2+w[0]+MAZEZAM_MENU_BORDER*2,
443 vgap + h[0]/2,LCD_WIDTH-1,vgap + h[0]/2);
444 #ifdef HAVE_LCD_COLOR
445 rb->lcd_set_foreground(MAZEZAM_HEADING_COLOR);
446 #elif LCD_DEPTH > 1
447 rb->lcd_set_foreground(MAZEZAM_HEADING_GRAY);
448 #endif
449 rb->lcd_putsxy((LCD_WIDTH-w[0])/2,vgap,text.line[0]);
451 vnext = vgap*2 + h[0];
453 /* The other lines */
455 #ifdef HAVE_LCD_COLOR
456 rb->lcd_set_foreground(MAZEZAM_TEXT_COLOR);
457 #elif LCD_DEPTH > 1
458 rb->lcd_set_foreground(MAZEZAM_TEXT_GRAY);
459 #endif
460 for (i = 1; i<text.num_lines; i++) {
461 rb->lcd_putsxy((LCD_WIDTH-w[i])/2,vnext,text.line[i]);
463 /* add underlining if i is the highlighted line */
464 if (i == highlight) {
465 rb->lcd_drawline((LCD_WIDTH-w[i])/2, vnext + h[i] + 1,
466 (LCD_WIDTH-w[i])/2 + w[i], vnext + h[i] + 1);
469 vnext += vgap + h[i];
472 rb->lcd_update();
476 /* Parse the level data from the level_data structure. This could be
477 * replaced by a file read. Returns true if the level parsed correctly.
479 static bool parse_level(short level, struct chunk_data *cd,
480 short *width, short *height, short *entrance, short *exit)
482 int i,j;
483 char c,clast;
485 *width = level_data[level].width;
486 *height = level_data[level].height;
487 *entrance = level_data[level].entrance;
488 *exit = level_data[level].exit;
490 /* for each line in the level */
491 for (i = 0; i<level_data[level].height; i++) {
492 if (level_data[level].line[i] == NULL)
493 return false;
494 else {
495 j = 0;
496 cd->l_num[i] = 0;
497 clast = ' '; /* the character we last considered */
498 while ((c = level_data[level].line[i][j]) != '\0') {
499 if (c != ' ') {
500 if (clast == ' ') {
501 cd->l_num[i] += 1;
502 if (cd->l_num[i] > MAZEZAM_MAX_CHUNKS)
503 return false;
504 cd->c_inset[i][cd->l_num[i] - 1] = j;
505 cd->c_width[i][cd->l_num[i] - 1] = 1;
507 else
508 cd->c_width[i][cd->l_num[i] - 1] += 1;
510 clast = c;
511 j++;
515 return true;
518 /* Draw the level */
519 static void draw_level(
520 struct chunk_data *cd, /* the data about the chunks */
521 short *shift, /* an array of the horizontal offset of the lines */
522 short width,
523 short height,
524 short entrance,
525 short exit,
526 short x, /* player's x and y coords */
527 short y)
529 /* The number of pixels the side of a square should be */
530 short size = (LCD_WIDTH/(width+2)) < (LCD_HEIGHT/height) ?
531 (LCD_WIDTH/(width+2)) : (LCD_HEIGHT/height);
532 /* The x and y position (in pixels) of the top left corner of the
533 * level
535 short xOff = (LCD_WIDTH - (size*width))/2;
536 short yOff = (LCD_HEIGHT - (size*height))/2;
537 /* For drawing the player, taken from the sokoban plugin */
538 short max = size - 1;
539 short middle = max / 2;
540 short ldelta = (middle + 1) / 2;
541 short i,j;
542 short third = size / 3;
543 short twothirds = (2 * size) / 3;
544 #ifndef HAVE_LCD_COLOR
545 /* We #def these out to supress a compiler warning */
546 short k;
547 #if LCD_DEPTH <= 1
548 short l;
549 #endif
550 #endif
552 rb->lcd_clear_display();
554 #ifdef HAVE_LCD_COLOR
555 rb->lcd_set_foreground(MAZEZAM_WALL_COLOR);
556 #elif LCD_DEPTH > 1
557 rb->lcd_set_foreground(MAZEZAM_WALL_GRAY);
558 #endif
559 /* draw the upper wall */
560 rb->lcd_fillrect(0,0,xOff,yOff+(size*entrance));
561 rb->lcd_fillrect(xOff,0,size*width,yOff);
562 rb->lcd_fillrect(xOff+(size*width),0,LCD_WIDTH-xOff-(size*width),
563 yOff+(size*exit));
565 /* draw the lower wall */
566 rb->lcd_fillrect(0,yOff+(size*entrance)+size,xOff,
567 LCD_HEIGHT-yOff-(size*entrance)-size);
568 rb->lcd_fillrect(xOff,yOff+(size*height),size*width,
569 LCD_HEIGHT-yOff-(size*height));
570 /* Note: the exit is made one pixel thinner than necessary as a visual
571 * clue that chunks cannot be pushed into it
573 rb->lcd_fillrect(xOff+(size*width),yOff+(size*exit)+size-1,
574 LCD_WIDTH-xOff+(size*width),
575 LCD_HEIGHT-yOff-(size*exit)-size+1);
577 /* draw the chunks */
578 for (i = 0; i<height; i++) {
579 #ifdef HAVE_LCD_COLOR
580 /* adding width to i should have a fixed, but randomising effect on
581 * the choice of the colours of the top line of chunks
583 rb->lcd_set_foreground(chunk_colors[(i+width) %
584 MAZEZAM_NUM_CHUNK_COLORS]);
585 #endif
586 for (j = 0; j<cd->l_num[i]; j++) {
587 #ifdef HAVE_LCD_COLOR
588 rb->lcd_fillrect(xOff+size*shift[i]+size*cd->c_inset[i][j],
589 yOff+size*i, cd->c_width[i][j]*size,size);
590 #elif LCD_DEPTH > 1
591 rb->lcd_set_foreground(MAZEZAM_CHUNK_EDGE_GRAY);
592 rb->lcd_drawrect(xOff+size*shift[i]+size*cd->c_inset[i][j],
593 yOff+size*i, cd->c_width[i][j]*size,size);
595 /* draw shade */
596 rb->lcd_set_foreground(chunk_gray_shade[(i+width) %
597 MAZEZAM_NUM_CHUNK_GRAYS]);
598 rb->lcd_drawline(xOff+size*shift[i]+size*cd->c_inset[i][j]+1,
599 yOff+size*i+size-2,
600 xOff+size*shift[i]+size*cd->c_inset[i][j]+
601 cd->c_width[i][j]*size-3,
602 yOff+size*i+size-2);
603 rb->lcd_drawline(xOff+size*shift[i]+size*cd->c_inset[i][j]+
604 cd->c_width[i][j]*size-2,
605 yOff+size*i,
606 xOff+size*shift[i]+size*cd->c_inset[i][j]+
607 cd->c_width[i][j]*size-2,
608 yOff+size*i+size-2);
610 /* draw fill */
611 rb->lcd_set_foreground(chunk_gray[(i+width) %
612 MAZEZAM_NUM_CHUNK_GRAYS]);
613 for (k = yOff+size*i+2; k < yOff+size*i+size-2; k += 2)
614 rb->lcd_drawline(xOff+size*shift[i]+size*cd->c_inset[i][j]+2,k,
615 xOff+size*shift[i]+size*cd->c_inset[i][j]+
616 cd->c_width[i][j]*size-3,k);
617 #else
618 rb->lcd_drawrect(xOff+size*shift[i]+size*cd->c_inset[i][j],
619 yOff+size*i, cd->c_width[i][j]*size,size);
620 for (k = xOff+size*shift[i]+size*cd->c_inset[i][j]+2;
621 k < xOff+size*shift[i]+size*cd->c_inset[i][j]+
622 cd->c_width[i][j]*size;
623 k += 2 + (i & 1))
624 for (l = yOff+size*i+2; l < yOff+size*i+size; l += 2 + (i & 1))
625 rb->lcd_drawpixel(k, l);
626 #endif
630 /* draw the player (mostly copied from the sokoban plugin) */
631 #ifdef HAVE_LCD_COLOR
632 rb->lcd_set_foreground(MAZEZAM_PLAYER_COLOR);
633 #elif LCD_DEPTH > 1
634 rb->lcd_set_foreground(MAZEZAM_PLAYER_GRAY);
635 #endif
636 rb->lcd_drawline(xOff+size*x, yOff+size*y+middle,
637 xOff+size*x+max, yOff+size*y+middle);
638 rb->lcd_drawline(xOff+size*x+middle, yOff+size*y,
639 xOff+size*x+middle, yOff+size*y+max-ldelta);
640 rb->lcd_drawline(xOff+size*x+middle, yOff+size*y+max-ldelta,
641 xOff+size*x+middle-ldelta, yOff+size*y+max);
642 rb->lcd_drawline(xOff+size*x+middle, yOff+size*y+max-ldelta,
643 xOff+size*x+middle+ldelta, yOff+size*y+max);
645 /* draw the gate, if the player has moved into the level */
646 if (x >= 0) {
647 #ifdef HAVE_LCD_COLOR
648 rb->lcd_set_foreground(MAZEZAM_GATE_COLOR);
649 #elif LCD_DEPTH > 1
650 rb->lcd_set_foreground(MAZEZAM_GATE_GRAY);
651 #endif
652 rb->lcd_drawline(xOff-size,yOff+entrance*size+third,
653 xOff-1,yOff+entrance*size+third);
654 rb->lcd_drawline(xOff-size,yOff+entrance*size+twothirds,
655 xOff-1,yOff+entrance*size+twothirds);
656 rb->lcd_drawline(xOff-size+third,yOff+entrance*size,
657 xOff-size+third,yOff+entrance*size+size-1);
658 rb->lcd_drawline(xOff-size+twothirds,yOff+entrance*size,
659 xOff-size+twothirds,yOff+entrance*size+size-1);
663 /* Manage the congratulations screen */
664 static enum text_state welldone_screen(void)
666 int button = BUTTON_NONE;
667 enum text_state state = TEXT_STATE_LOOPING;
669 display_text_page(welldone_page, 0);
671 while (state == TEXT_STATE_LOOPING) {
672 button = rb->button_get(true);
674 switch (button) {
675 case MAZEZAM_QUIT:
676 state = TEXT_STATE_QUIT;
677 break;
679 case MAZEZAM_SELECT:
680 #if CONFIG_KEYPAD != ONDIO_PAD
681 case MAZEZAM_RIGHT:
682 #endif
683 state = TEXT_STATE_OKAY;
684 break;
686 default:
687 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
688 state = TEXT_STATE_USB_CONNECTED;
689 break;
693 return state;
696 /* Manage the quit confimation screen */
697 static enum text_state quitconfirm_loop(void)
699 int button = BUTTON_NONE;
700 enum text_state state = TEXT_STATE_LOOPING;
701 short select = 2;
703 display_text_page(confirm_page, select + 1);
705 /* Wait for a button release. This is useful when a repeated button
706 * press is used for quit.
708 while ((rb->button_get(true) & BUTTON_REL) != BUTTON_REL);
710 while (state == TEXT_STATE_LOOPING) {
711 display_text_page(confirm_page, select + 1);
713 button = rb->button_get(true);
715 switch (button) {
716 case MAZEZAM_QUIT:
717 state = TEXT_STATE_QUIT;
718 break;
720 case MAZEZAM_UP:
721 case MAZEZAM_DOWN:
722 select = (2 - select) + 1;
723 break;
725 case MAZEZAM_SELECT:
726 #if CONFIG_KEYPAD != ONDIO_PAD
727 case MAZEZAM_RIGHT:
728 #endif
729 if (select == 1)
730 state = TEXT_STATE_QUIT;
731 else
732 state = TEXT_STATE_OKAY;
733 break;
735 default:
736 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
737 state = TEXT_STATE_USB_CONNECTED;
738 break;
742 return state;
745 /* Manage the playing of a level */
746 static enum level_state level_loop(short level, short lives)
748 struct chunk_data cd;
749 short shift[MAZEZAM_MAX_LINES]; /* amount each line has been shifted */
750 short width;
751 short height;
752 short entrance;
753 short exit;
754 short i;
755 short x,y;
756 int button;
757 enum level_state state = LEVEL_STATE_LOOPING;
758 bool blocked; /* is there a chunk in the way of the player? */
760 if (!(parse_level(level,&cd,&width,&height,&entrance,&exit)))
761 return LEVEL_STATE_PARSE_ERROR;
763 for (i = 0; i < height; i++)
764 shift[i] = 0;
766 x = -1;
767 y = entrance;
769 draw_level(&cd, shift, width, height, entrance, exit, x, y);
771 #ifdef HAVE_REMOTE_LCD
772 /* Splash text seems to use the remote display by
773 * default. I suppose I better keep it tidy!
775 rb->lcd_remote_clear_display();
776 #endif
777 rb->splash(MAZEZAM_LEVEL_LIVES_DELAY, MAZEZAM_LEVEL_LIVES_TEXT,
778 level+1, lives);
780 /* ensure keys pressed during the splash screen are ignored */
781 rb->button_clear_queue();
783 while (state == LEVEL_STATE_LOOPING) {
784 draw_level(&cd, shift, width, height, entrance, exit, x, y);
785 rb->lcd_update();
786 button = rb->button_get(true);
787 blocked = false;
789 switch (button) {
790 case MAZEZAM_UP:
791 case MAZEZAM_UP | BUTTON_REPEAT:
792 if ((y > 0) && (x >= 0) && (x < width)) {
793 for (i = 0; i < cd.l_num[y-1]; i++)
794 blocked = blocked ||
795 ((x>=shift[y-1]+cd.c_inset[y-1][i]) &&
796 (x<shift[y-1]+cd.c_inset[y-1][i]+
797 cd.c_width[y-1][i]));
798 if (!blocked) y -= 1;
800 break;
802 case MAZEZAM_DOWN:
803 case MAZEZAM_DOWN | BUTTON_REPEAT:
804 if ((y < height-1) && (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_LEFT:
815 case MAZEZAM_LEFT | BUTTON_REPEAT:
816 if (x > 0) {
817 for (i = 0; i < cd.l_num[y]; i++)
818 blocked = blocked ||
819 (x == shift[y]+cd.c_inset[y][i]+
820 cd.c_width[y][i]);
821 if (!blocked) x -= 1;
822 else if (shift[y] + cd.c_inset[y][0] > 0) {
823 x -= 1;
824 shift[y] -= 1;
827 break;
829 case MAZEZAM_RIGHT:
830 case MAZEZAM_RIGHT | BUTTON_REPEAT:
831 if (x < width-1) {
832 for (i = 0; i < cd.l_num[y]; i++)
833 blocked = blocked || (x+1 == shift[y]+cd.c_inset[y][i]);
834 if (!blocked) x += 1;
835 else if (shift[y] + cd.c_inset[y][cd.l_num[y]-1] +
836 cd.c_width[y][cd.l_num[y]-1] < width) {
837 x += 1;
838 shift[y] += 1;
841 else if (x == width) state = LEVEL_STATE_COMPLETED;
842 else if (y == exit) x += 1;
843 break;
845 case MAZEZAM_RETRY:
846 state = LEVEL_STATE_FAILED;
847 break;
849 case MAZEZAM_QUIT:
850 switch (quitconfirm_loop()) {
851 case TEXT_STATE_QUIT:
852 state = LEVEL_STATE_QUIT;
853 break;
855 case TEXT_STATE_USB_CONNECTED:
856 state = LEVEL_STATE_USB_CONNECTED;
857 break;
859 default:
860 break;
862 break;
864 default:
865 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
866 state = LEVEL_STATE_USB_CONNECTED;
867 break;
871 return state;
874 /* The loop which manages a full game of MazezaM */
875 static enum game_state game_loop(struct resume_data *r)
877 enum game_state state = GAME_STATE_LOOPING;
878 int level = r->level;
879 int lives = MAZEZAM_START_LIVES;
881 rb->lcd_clear_display();
883 while (state == GAME_STATE_LOOPING)
885 switch (level_loop(level,lives)) {
886 case LEVEL_STATE_COMPLETED:
887 level += 1;
888 if (!((level - r->level) % MAZEZAM_EXTRA_LIFE))
889 lives += 1;
890 break;
892 case LEVEL_STATE_QUIT:
893 state = GAME_STATE_QUIT;
894 break;
896 case LEVEL_STATE_FAILED:
897 lives -= 1;
898 break;
900 case LEVEL_STATE_PARSE_ERROR:
901 state = GAME_STATE_PARSE_ERROR;
902 break;
904 case LEVEL_STATE_USB_CONNECTED:
905 state = GAME_STATE_USB_CONNECTED;
906 break;
908 default:
909 break;
911 if (lives == 0)
912 state = GAME_STATE_OVER;
913 else if (level == MAZEZAM_NUM_LEVELS)
914 state = GAME_STATE_COMPLETED;
917 switch (state) {
918 case GAME_STATE_OVER:
919 #ifdef HAVE_REMOTE_LCD
920 /* Splash text seems to use the remote display by
921 * default. I suppose I better keep it tidy!
923 rb->lcd_remote_clear_display();
924 #endif
925 rb->splash(MAZEZAM_GAMEOVER_DELAY, MAZEZAM_GAMEOVER_TEXT);
926 break;
928 case GAME_STATE_COMPLETED:
929 switch (welldone_screen()) {
930 case TEXT_STATE_QUIT:
931 state = GAME_STATE_QUIT;
932 break;
934 case TEXT_STATE_USB_CONNECTED:
935 state = GAME_STATE_USB_CONNECTED;
936 break;
938 default:
939 state = GAME_STATE_OKAY;
940 break;
942 break;
944 default:
945 break;
948 /* This particular resume game logic is designed to make
949 * players prove they can solve a level more than once
951 if (level > r->level + 1)
952 r->level += 1;
954 return state;
957 /* Manage the instruction screen */
958 static enum text_state instruction_loop(void)
960 int button;
961 enum text_state state = TEXT_STATE_LOOPING;
962 int page = 0;
964 while (state == TEXT_STATE_LOOPING) {
965 display_text_page(help_page[page], 0);
966 button = rb->button_get(true);
968 switch (button) {
969 case MAZEZAM_LEFT:
970 page -= 1;
971 break;
973 case MAZEZAM_SELECT:
974 #if CONFIG_KEYPAD != ONDIO_PAD
975 case MAZEZAM_RIGHT:
976 #endif
977 page += 1;
978 break;
980 case MAZEZAM_QUIT:
981 state = TEXT_STATE_QUIT;
982 break;
984 default:
985 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
986 state = TEXT_STATE_USB_CONNECTED;
987 break;
991 if ((page < 0) || (page >= MAZEZAM_NUM_HELP_PAGES))
992 state = TEXT_STATE_OKAY;
995 return state;
998 /* Manage the text screen that offers the user the option of
999 * resuming or starting a new game
1001 static enum text_state resume_game_loop (struct resume_data *r)
1003 int button = BUTTON_NONE;
1004 enum text_state state = TEXT_STATE_LOOPING;
1005 short select = 0;
1007 /* if the resume level is 0, don't bother asking */
1008 if (r->level == 0) return TEXT_STATE_OKAY;
1010 display_text_page(resume_page, select + 1);
1012 while (state == TEXT_STATE_LOOPING) {
1013 display_text_page(resume_page, select + 1);
1015 button = rb->button_get(true);
1017 switch (button) {
1018 case MAZEZAM_QUIT:
1019 state = TEXT_STATE_QUIT;
1020 break;
1022 case MAZEZAM_LEFT:
1023 state = TEXT_STATE_BACK;
1024 break;
1026 case MAZEZAM_UP:
1027 case MAZEZAM_DOWN:
1028 select = 1 - select;
1029 break;
1031 case MAZEZAM_SELECT:
1032 #if CONFIG_KEYPAD != ONDIO_PAD
1033 case MAZEZAM_RIGHT:
1034 #endif
1035 if (select == 1) {
1036 /* The player wants to play a new game. I could ask
1037 * for confirmation here, but the only penalty is
1038 * playing through some already completed levels,
1039 * so I don't think it's necessary
1041 r->level = 0;
1043 state = TEXT_STATE_OKAY;
1044 break;
1046 default:
1047 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
1048 state = TEXT_STATE_USB_CONNECTED;
1049 break;
1053 return state;
1056 /* Load the resume data from the config file. The data is
1057 * stored in both r and old.
1059 static void resume_load_data (struct resume_data *r, struct resume_data *old)
1061 struct configdata config[] = {
1062 {TYPE_INT,0,MAZEZAM_NUM_LEVELS-1,&(r->level),
1063 MAZEZAM_CONFIG_LEVELS_NAME,NULL,NULL}
1066 if (configfile_load(MAZEZAM_CONFIG_FILENAME,config,MAZEZAM_CONFIG_NUM_ITEMS,
1067 MAZEZAM_CONFIG_VERSION) < 0)
1068 r->level = 0;
1069 /* an extra precaution */
1070 else if ((r->level < 0) || (MAZEZAM_NUM_LEVELS <= r->level))
1071 r->level = 0;
1073 old->level = r->level;
1076 /* Save the resume data in the config file, but only if necessary */
1077 static void resume_save_data (struct resume_data *r, struct resume_data *old)
1079 struct configdata config[] = {
1080 {TYPE_INT,0,MAZEZAM_NUM_LEVELS-1,&(r->level),
1081 MAZEZAM_CONFIG_LEVELS_NAME,NULL,NULL}
1084 /* To reduce disk usage, only write the file if the resume data has
1085 * changed.
1087 if (old->level != r->level)
1088 configfile_save(MAZEZAM_CONFIG_FILENAME,config,MAZEZAM_CONFIG_NUM_ITEMS,
1089 MAZEZAM_CONFIG_MINVERSION);
1092 /* The loop which manages the welcome screen and menu */
1093 static enum text_state welcome_loop(void)
1095 int button;
1096 short select = 0;
1097 enum text_state state = TEXT_STATE_LOOPING;
1098 struct resume_data r_data, old_data;
1100 /* Load data */
1101 resume_load_data(&r_data, &old_data);
1103 while (state == TEXT_STATE_LOOPING) {
1104 display_text_page(title_page, select + 1);
1105 button = rb->button_get(true);
1107 switch (button) {
1108 case MAZEZAM_QUIT:
1109 state = TEXT_STATE_QUIT;
1110 break;
1112 case MAZEZAM_UP:
1113 select = (select + (title_page.num_lines - 2)) %
1114 (title_page.num_lines - 1);
1115 break;
1117 case MAZEZAM_DOWN:
1118 select = (select + 1) % (title_page.num_lines - 1);
1119 break;
1121 case MAZEZAM_SELECT:
1122 #if CONFIG_KEYPAD != ONDIO_PAD
1123 case MAZEZAM_RIGHT:
1124 #endif
1125 if (select == 0) { /* play game */
1126 switch (resume_game_loop(&r_data)) {
1127 case TEXT_STATE_QUIT:
1128 state = TEXT_STATE_QUIT;
1129 break;
1131 case TEXT_STATE_USB_CONNECTED:
1132 state = TEXT_STATE_USB_CONNECTED;
1133 break;
1135 case TEXT_STATE_BACK:
1136 break;
1138 default: { /* Ouch! This nesting is too deep! */
1139 switch (game_loop(&r_data)) {
1140 case GAME_STATE_QUIT:
1141 state = TEXT_STATE_QUIT;
1142 break;
1144 case GAME_STATE_USB_CONNECTED:
1145 state = TEXT_STATE_USB_CONNECTED;
1146 break;
1148 case GAME_STATE_PARSE_ERROR:
1149 state = TEXT_STATE_PARSE_ERROR;
1150 break;
1152 default:
1153 break;
1155 break;
1159 else if (select == 1) { /* Instructions */
1160 switch (instruction_loop()) {
1161 case TEXT_STATE_QUIT:
1162 state = TEXT_STATE_QUIT;
1163 break;
1165 case TEXT_STATE_USB_CONNECTED:
1166 state = TEXT_STATE_USB_CONNECTED;
1167 break;
1169 default:
1170 break;
1173 else /* Quit */
1174 state = TEXT_STATE_QUIT;
1176 break;
1178 default:
1179 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
1180 state = TEXT_STATE_USB_CONNECTED;
1181 break;
1185 /* I'm not sure if it's appropriate to write to disk on USB events.
1186 * Currently, I do so.
1188 resume_save_data(&r_data, &old_data);
1190 return state;
1193 /* Plugin entry point */
1194 enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
1196 enum plugin_status state;
1198 /* Usual plugin stuff */
1199 (void)parameter;
1200 rb = api;
1202 /* Turn off backlight timeout */
1203 backlight_force_on(rb); /* backlight control in lib/helper.c */
1205 #ifdef HAVE_LCD_COLOR
1206 rb->lcd_set_background(MAZEZAM_BG_COLOR);
1207 rb->lcd_set_backdrop(NULL);
1208 #elif LCD_DEPTH > 1
1209 rb->lcd_set_background(MAZEZAM_BG_GRAY);
1210 #endif
1211 rb->lcd_setfont(FONT_SYSFIXED);
1213 /* initialise the config file module */
1214 configfile_init(rb);
1216 switch (welcome_loop()) {
1217 case TEXT_STATE_USB_CONNECTED:
1218 state = PLUGIN_USB_CONNECTED;
1219 break;
1221 case TEXT_STATE_PARSE_ERROR:
1222 state = PLUGIN_ERROR;
1223 break;
1225 default:
1226 state = PLUGIN_OK;
1227 break;
1230 /* Turn on backlight timeout (revert to settings) */
1231 backlight_use_settings(rb); /* backlight control in lib/helper.c */
1233 return state;