summaryrefslogtreecommitdiff
path: root/apps/plugins/mazezam.c
diff options
context:
space:
mode:
authorZakk Roberts <midk@rockbox.org>2007-01-02 02:00:15 +0000
committerZakk Roberts <midk@rockbox.org>2007-01-02 02:00:15 +0000
commite367ec3a3870a63a12725a625000449c663dd19b (patch)
tree8af2d1c0899615629f1e01ef502e691d4cd327cf /apps/plugins/mazezam.c
parentf8fcbafd129a6f56730f59e6ef2a63a521929e41 (diff)
downloadrockbox-e367ec3a3870a63a12725a625000449c663dd19b.tar.gz
rockbox-e367ec3a3870a63a12725a625000449c663dd19b.zip
Patch #5416 by Malcolm Tyrrell - Mazezam, a puzzle game. Move sliding blocks horizontally to reach the exit of each level. Should work on all bitmap-LCD targets.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@11882 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps/plugins/mazezam.c')
-rw-r--r--apps/plugins/mazezam.c1127
1 files changed, 1127 insertions, 0 deletions
diff --git a/apps/plugins/mazezam.c b/apps/plugins/mazezam.c
new file mode 100644
index 0000000000..2a8afc37ab
--- /dev/null
+++ b/apps/plugins/mazezam.c
@@ -0,0 +1,1127 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * ### line of auto-generated stuff I don't understand ###
9 *
10 * Copyright (C) 2006 Malcolm Tyrrell
11 *
12 * MazezaM - a Rockbox version of my ZX Spectrum game from 2002
13 *
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.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "plugin.h"
22#include "configfile.h"
23
24/* Include standard plugin macro */
25PLUGIN_HEADER
26
27static struct plugin_api* rb;
28
29#if CONFIG_KEYPAD == RECORDER_PAD
30#define MAZEZAM_UP BUTTON_UP
31#define MAZEZAM_DOWN BUTTON_DOWN
32#define MAZEZAM_LEFT BUTTON_LEFT
33#define MAZEZAM_RIGHT BUTTON_RIGHT
34#define MAZEZAM_SELECT BUTTON_PLAY
35
36#define MAZEZAM_RETRY BUTTON_F1
37#define MAZEZAM_RETRY_KEYNAME "[F1]"
38#define MAZEZAM_QUIT BUTTON_OFF
39#define MAZEZAM_QUIT_KEYNAME "[OFF]"
40
41#elif CONFIG_KEYPAD == ONDIO_PAD
42#define MAZEZAM_UP BUTTON_UP
43#define MAZEZAM_DOWN BUTTON_DOWN
44#define MAZEZAM_LEFT BUTTON_LEFT
45#define MAZEZAM_RIGHT BUTTON_RIGHT
46#define MAZEZAM_SELECT BUTTON_RIGHT
47
48#define MAZEZAM_RETRY BUTTON_MENU
49#define MAZEZAM_RETRY_KEYNAME "[MENU]"
50#define MAZEZAM_QUIT BUTTON_OFF
51#define MAZEZAM_QUIT_KEYNAME "[OFF]"
52
53#elif (CONFIG_KEYPAD == IAUDIO_X5_PAD)
54#define MAZEZAM_UP BUTTON_UP
55#define MAZEZAM_DOWN BUTTON_DOWN
56#define MAZEZAM_LEFT BUTTON_LEFT
57#define MAZEZAM_RIGHT BUTTON_RIGHT
58#define MAZEZAM_SELECT BUTTON_SELECT
59
60#define MAZEZAM_RETRY BUTTON_REC
61#define MAZEZAM_RETRY_KEYNAME "[REC]"
62#define MAZEZAM_QUIT BUTTON_POWER
63#define MAZEZAM_QUIT_KEYNAME "[POWER]"
64
65#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
66 (CONFIG_KEYPAD == IPOD_3G_PAD)
67#define MAZEZAM_UP BUTTON_MENU
68#define MAZEZAM_DOWN BUTTON_PLAY
69#define MAZEZAM_LEFT BUTTON_LEFT
70#define MAZEZAM_RIGHT BUTTON_RIGHT
71#define MAZEZAM_SELECT BUTTON_SELECT
72
73#define MAZEZAM_RETRY BUTTON_SELECT
74#define MAZEZAM_RETRY_KEYNAME "[SELECT]"
75#define MAZEZAM_QUIT (BUTTON_SELECT | BUTTON_REPEAT)
76#define MAZEZAM_QUIT_KEYNAME "[SELECT] (held)"
77
78#elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
79 (CONFIG_KEYPAD == IRIVER_H300_PAD)
80#define MAZEZAM_UP BUTTON_UP
81#define MAZEZAM_DOWN BUTTON_DOWN
82#define MAZEZAM_LEFT BUTTON_LEFT
83#define MAZEZAM_RIGHT BUTTON_RIGHT
84#define MAZEZAM_SELECT BUTTON_SELECT
85
86#define MAZEZAM_RETRY BUTTON_ON
87#define MAZEZAM_RETRY_KEYNAME "[ON]"
88#define MAZEZAM_QUIT BUTTON_OFF
89#define MAZEZAM_QUIT_KEYNAME "[OFF]"
90
91#elif (CONFIG_KEYPAD == GIGABEAT_PAD)
92#define MAZEZAM_UP BUTTON_UP
93#define MAZEZAM_DOWN BUTTON_DOWN
94#define MAZEZAM_LEFT BUTTON_LEFT
95#define MAZEZAM_RIGHT BUTTON_RIGHT
96#define MAZEZAM_SELECT BUTTON_SELECT
97
98#define MAZEZAM_RETRY BUTTON_MENU
99#define MAZEZAM_RETRY_KEYNAME "[MENU]"
100#define MAZEZAM_QUIT BUTTON_A
101#define MAZEZAM_QUIT_KEYNAME "[A]"
102
103#elif (CONFIG_KEYPAD == SANSA_E200_PAD)
104#define MAZEZAM_UP BUTTON_UP
105#define MAZEZAM_DOWN BUTTON_DOWN
106#define MAZEZAM_LEFT BUTTON_LEFT
107#define MAZEZAM_RIGHT BUTTON_RIGHT
108#define MAZEZAM_SELECT BUTTON_SELECT
109
110#define MAZEZAM_RETRY BUTTON_REC
111#define MAZEZAM_RETRY_KEYNAME "[REC]"
112#define MAZEZAM_QUIT BUTTON_POWER
113#define MAZEZAM_QUIT_KEYNAME "[POWER]"
114
115#elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
116#define MAZEZAM_UP BUTTON_SCROLL_UP
117#define MAZEZAM_DOWN BUTTON_SCROLL_DOWN
118#define MAZEZAM_LEFT BUTTON_LEFT
119#define MAZEZAM_RIGHT BUTTON_RIGHT
120#define MAZEZAM_SELECT BUTTON_PLAY
121
122#define MAZEZAM_RETRY BUTTON_PLAY
123#define MAZEZAM_RETRY_KEYNAME "[PLAY]"
124#define MAZEZAM_QUIT BUTTON_POWER
125#define MAZEZAM_QUIT_KEYNAME "[POWER]"
126
127#endif
128
129/* The gap for the border around the heading in text pages. In fact, 2 is
130 * really the only acceptable value.
131 */
132#define MAZEZAM_MENU_BORDER 2
133#define MAZEZAM_EXTRA_LIFE 2 /* get an extra life every _ levels */
134#define MAZEZAM_START_LIVES 3 /* how many lives at game start */
135
136#ifdef HAVE_LCD_COLOR
137#define MAZEZAM_HEADING_COLOR LCD_RGBPACK(255,255, 0) /* Yellow */
138#define MAZEZAM_BORDER_COLOR LCD_RGBPACK( 0, 0,255) /* Blue */
139#define MAZEZAM_TEXT_COLOR LCD_RGBPACK(255,255,255) /* White */
140#define MAZEZAM_BG_COLOR LCD_RGBPACK( 0, 0, 0) /* Black */
141#define MAZEZAM_WALL_COLOR LCD_RGBPACK(100,100,100) /* Dark gray */
142#define MAZEZAM_PLAYER_COLOR LCD_RGBPACK(255,255,255) /* White */
143#define MAZEZAM_GATE_COLOR LCD_RGBPACK(100,100,100) /* Dark gray */
144
145/* the rows are coloured sequentially */
146#define MAZEZAM_NUM_CHUNK_COLORS 8
147static const unsigned chunk_colors[MAZEZAM_NUM_CHUNK_COLORS] = {
148 LCD_RGBPACK(255,192, 32), /* Orange */
149 LCD_RGBPACK(255, 0, 0), /* Red */
150 LCD_RGBPACK( 0,255, 0), /* Green */
151 LCD_RGBPACK( 0,255,255), /* Cyan */
152 LCD_RGBPACK(255,175,175), /* Pink */
153 LCD_RGBPACK(255,255, 0), /* Yellow */
154 LCD_RGBPACK( 0, 0,255), /* Blue */
155 LCD_RGBPACK(255, 0,255), /* Magenta */
156};
157
158#elif LCD_DEPTH > 1
159
160#define MAZEZAM_HEADING_GRAY LCD_BLACK
161#define MAZEZAM_BORDER_GRAY LCD_DARKGRAY
162#define MAZEZAM_TEXT_GRAY LCD_BLACK
163#define MAZEZAM_BG_GRAY LCD_WHITE
164#define MAZEZAM_WALL_GRAY LCD_DARKGRAY
165#define MAZEZAM_PLAYER_GRAY LCD_BLACK
166#define MAZEZAM_GATE_GRAY LCD_BLACK
167#define MAZEZAM_CHUNK_EDGE_GRAY LCD_BLACK
168
169#define MAZEZAM_NUM_CHUNK_GRAYS 2
170static const unsigned chunk_gray[MAZEZAM_NUM_CHUNK_GRAYS] = {
171 LCD_LIGHTGRAY,
172 LCD_DARKGRAY,
173};
174/* darker version of the above */
175static const unsigned chunk_gray_shade[MAZEZAM_NUM_CHUNK_GRAYS] = {
176 LCD_DARKGRAY,
177 LCD_BLACK,
178};
179#endif
180
181#define MAZEZAM_GAMEOVER_TEXT "Game Over"
182#define MAZEZAM_GAMEOVER_DELAY (3 * HZ) / 2
183#define MAZEZAM_LEVEL_LIVES_TEXT "Level %d, Lives %d"
184#define MAZEZAM_LEVEL_LIVES_DELAY HZ
185#define MAZEZAM_WELLDONE_DELAY 4 * HZ
186
187/* The maximum number of lines that a text page can display.
188 * This must be 4 or less if the Archos recorder is to be
189 * supported.
190 */
191#define MAZEZAM_TEXT_MAXLINES 4
192
193/* A structure for holding text pages */
194struct textpage {
195 /* Ensure 1 < num_lines <= MAZEZAM_TEXT_MAXLINES */
196 short num_lines;
197 char *line[MAZEZAM_TEXT_MAXLINES]; /* text of lines */
198};
199
200/* The text page for the welcome screen */
201static const struct textpage title_page = {
202 4,
203 {"MazezaM", "play game", "instructions", "quit"}
204};
205
206/* The number of help screens */
207#define MAZEZAM_NUM_HELP_PAGES 4
208
209/* The instruction screens */
210static const struct textpage help_page[] = {
211 {4,{"Instructions","10 mazezams","bar your way","to freedom"}},
212 {4,{"Instructions","Push the rows","left and right","to escape"}},
213 {4,{"Instructions","Press " MAZEZAM_RETRY_KEYNAME " to","retry a level","(lose 1 life)"}},
214 {4,{"Instructions","Press " MAZEZAM_QUIT_KEYNAME,"to quit","the game"}}
215};
216
217/* the text of the screen that asks for a quit confirmation */
218static const struct textpage confirm_page = {
219 4,
220 {"Quit","Are you sure?","yes","no"}
221};
222
223/* the text of the screen at the end of the game */
224static const struct textpage welldone_page = {
225 3,
226 {"Well Done","You have","escaped",""}
227};
228
229/* the text of the screen asking if the user wants to
230 * resume or start a new game.
231 */
232static const struct textpage resume_page = {
233 3,
234 {"Checkpoint", "continue", "new game"}
235};
236
237/* maximum height of a level */
238#define MAZEZAM_MAX_LINES 11
239/* maximum number of chunks on a line */
240#define MAZEZAM_MAX_CHUNKS 5
241
242/* A structure for holding levels */
243struct mazezam_level {
244 short height; /* the number of lines */
245 short width; /* the width */
246 short entrance; /* the line on which the entrance lies */
247 short exit; /* the line on which the exit lies */
248 char *line[MAZEZAM_MAX_LINES]; /* the chunk data in string form */
249};
250
251/* The number of levels. Note that the instruction screens reference this
252 * number
253 */
254#define MAZEZAM_NUM_LEVELS 10
255
256/* The levels. In theory, they could be stored in a file so this data
257 * structure should not be accessed outside parse_level()
258 *
259 * These levels are copyright (C) 2002 Malcolm Tyrrell. They're
260 * probably covered by the GPL as they constitute part of the source
261 * code of this plugin, but you may distibute them seperately with
262 * other Free Software if you want. You can download them from:
263 * http://webpages.dcu.ie/~tyrrelma/MazezaM.
264 */
265static const struct mazezam_level level_data[MAZEZAM_NUM_LEVELS] = {
266 {2,7,0,0,{" $ $"," $ $$",NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
267 NULL}},
268 {3,8,2,1,{" $ $$$"," $ $ $"," $ $ $",NULL,NULL,NULL,NULL,NULL,NULL,
269 NULL,NULL}},
270 {4,14,1,3,{" $$$$$ $$ $$"," $$ $$ $$","$$ $ $$ $$$",
271 " $$$$$$$$ $",NULL,NULL,NULL,NULL,NULL,NULL,NULL}},
272 {6,7,4,2,{" $"," $$$$"," $$$ $$"," $ $ $"," $ $$","$ $$",
273 NULL,NULL,NULL,NULL,NULL}},
274 {6,13,0,0,{" $$$$$","$ $$$$$ $$$"," $ $$$ $$$$",
275 "$ $ $$$$$$$"," $$$ $ $$","$ $ $ $$ $",NULL,NULL,
276 NULL,NULL,NULL}},
277 {11,5,10,0,{" $"," $ $$"," $$","$ $"," $ $"," $$$","$ $",
278 " $ $"," $ $","$ $$"," $"}},
279 {7,16,0,6,{" $$$$$$$"," $$$$ $$$$ $ $","$$ $$ $$$$$$ $ $",
280 "$ $ $"," $$$$$$$$$$$$$$"," $ $$ $ $$$",
281 " $ $$$ $$",NULL,NULL,NULL,NULL}},
282 {4,15,2,0,{" $$$$ $$$$ $$"," $ $$ $$ $ $$"," $ $$ $$$$ $$",
283 " $ $$ $$$$ $",NULL,NULL,NULL,NULL,NULL,NULL,NULL}},
284 {7,9,6,2,{" $ $$$$"," $ $ $$"," $ $$$$ $","$ $$ $"," $ $$$",
285 " $$$$$$"," $",NULL,NULL,NULL,NULL}},
286 {10,14,8,0,{" $"," $$$$$$$$$$ $"," $$$ $$",
287 " $ $$$$$$$$ $"," $$$ $$$ $$$"," $$$ $ $$$",
288 " $ $$$$$$$ $$"," $ $ $ $$$"," $$$$$$$$$$$$",
289 "",NULL}}
290};
291
292/* This is the data structure the game uses for managing levels */
293struct chunk_data {
294 /* the number of chunks on a line */
295 short l_num[MAZEZAM_MAX_LINES];
296 /* the width of a chunk */
297 short c_width[MAZEZAM_MAX_LINES][MAZEZAM_MAX_CHUNKS];
298 /* the inset of a chunk */
299 short c_inset[MAZEZAM_MAX_LINES][MAZEZAM_MAX_CHUNKS];
300};
301
302/* The state and exit code of the level loop */
303enum level_state {
304 LEVEL_STATE_LOOPING,
305 LEVEL_STATE_COMPLETED,
306 LEVEL_STATE_FAILED,
307 LEVEL_STATE_QUIT,
308 LEVEL_STATE_PARSE_ERROR,
309 LEVEL_STATE_USB_CONNECTED,
310};
311
312/* The state and exit code of the text screens. I use the
313 * same enum for all of them, even though there are some
314 * differences.
315 */
316enum text_state {
317 TEXT_STATE_LOOPING,
318 TEXT_STATE_QUIT,
319 TEXT_STATE_OKAY,
320 TEXT_STATE_USB_CONNECTED,
321 TEXT_STATE_PARSE_ERROR,
322 TEXT_STATE_BACK,
323};
324
325/* The state and exit code of the game loop */
326enum game_state {
327 GAME_STATE_LOOPING,
328 GAME_STATE_QUIT,
329 GAME_STATE_OKAY,
330 GAME_STATE_USB_CONNECTED,
331 GAME_STATE_OVER,
332 GAME_STATE_COMPLETED,
333 GAME_STATE_PARSE_ERROR,
334};
335
336/* The various constants needed for configuration files.
337 * See apps/plugins/lib/configfile.*
338 */
339#define MAZEZAM_CONFIG_FILENAME "mazezam.data"
340#define MAZEZAM_CONFIG_NUM_ITEMS 1
341#define MAZEZAM_CONFIG_VERSION 0
342#define MAZEZAM_CONFIG_MINVERSION 0
343#define MAZEZAM_CONFIG_LEVELS_NAME "restart_level"
344
345/* A structure containing the data that is written to
346 * the configuration file
347 */
348struct resume_data {
349 int level; /* level at which to restart the game */
350};
351
352/* Display a screen of text. line[0] is the heading.
353 * line[highlight] will be highlighted, unless highlight == 0
354 */
355static void display_text_page(struct textpage text, int highlight) {
356 int w[text.num_lines], h[text.num_lines];
357 int hsum,i,vgap,vnext;
358
359 rb->lcd_clear_display();
360
361 /* find out how big the text is so we can determine the positioning */
362 hsum = 0;
363 for(i = 0; i < text.num_lines; i++) {
364 rb->lcd_getstringsize(text.line[i], w+i, h+i);
365 hsum += h[i];
366 }
367
368 vgap = (LCD_HEIGHT-hsum)/(text.num_lines+1);
369
370 /* The Heading */
371
372#ifdef HAVE_LCD_COLOR
373 rb->lcd_set_foreground(MAZEZAM_BORDER_COLOR);
374#elif LCD_DEPTH > 1
375 rb->lcd_set_foreground(MAZEZAM_BORDER_GRAY);
376#endif
377 rb->lcd_drawrect((LCD_WIDTH-w[0])/2-MAZEZAM_MENU_BORDER,
378 vgap-MAZEZAM_MENU_BORDER, w[0] + 2*MAZEZAM_MENU_BORDER,
379 h[0] + 2*MAZEZAM_MENU_BORDER);
380 rb->lcd_drawrect((LCD_WIDTH-w[0])/2-MAZEZAM_MENU_BORDER*2,
381 vgap-MAZEZAM_MENU_BORDER*2, w[0] + 4*MAZEZAM_MENU_BORDER,
382 h[0] + 4*MAZEZAM_MENU_BORDER);
383 rb->lcd_drawline(0,vgap + h[0]/2,(LCD_WIDTH-w[0])/2-MAZEZAM_MENU_BORDER*2,vgap + h[0]/2);
384 rb->lcd_drawline((LCD_WIDTH-w[0])/2+w[0]+MAZEZAM_MENU_BORDER*2,vgap + h[0]/2,LCD_WIDTH-1,vgap + h[0]/2);
385#ifdef HAVE_LCD_COLOR
386 rb->lcd_set_foreground(MAZEZAM_HEADING_COLOR);
387#elif LCD_DEPTH > 1
388 rb->lcd_set_foreground(MAZEZAM_HEADING_GRAY);
389#endif
390 rb->lcd_putsxy((LCD_WIDTH-w[0])/2,vgap,text.line[0]);
391
392 vnext = vgap*2 + h[0];
393
394 /* The other lines */
395
396#ifdef HAVE_LCD_COLOR
397 rb->lcd_set_foreground(MAZEZAM_TEXT_COLOR);
398#elif LCD_DEPTH > 1
399 rb->lcd_set_foreground(MAZEZAM_TEXT_GRAY);
400#endif
401 for (i = 1; i<text.num_lines; i++) {
402 rb->lcd_putsxy((LCD_WIDTH-w[i])/2,vnext,text.line[i]);
403
404 /* add underlining if i is the highlighted line */
405 if (i == highlight) {
406 rb->lcd_drawline((LCD_WIDTH-w[i])/2, vnext + h[i] + 1,
407 (LCD_WIDTH-w[i])/2 + w[i], vnext + h[i] + 1);
408 }
409
410 vnext += vgap + h[i];
411 }
412
413 rb->lcd_update();
414}
415
416
417/* Parse the level data from the level_data structure. This could be
418 * replaced by a file read. Returns true if the level parsed correctly.
419 */
420static bool parse_level(short level, struct chunk_data *cd,
421 short *width, short *height, short *entrance, short *exit) {
422 int i,j;
423 char c,clast;
424
425 *width = level_data[level].width;
426 *height = level_data[level].height;
427 *entrance = level_data[level].entrance;
428 *exit = level_data[level].exit;
429
430 /* for each line in the level */
431 for (i = 0; i<level_data[level].height; i++) {
432 if (level_data[level].line[i] == NULL)
433 return false;
434 else {
435 j = 0;
436 cd->l_num[i] = 0;
437 clast = ' '; /* the character we last considered */
438 while ((c = level_data[level].line[i][j]) != '\0') {
439 if (c != ' ') {
440 if (clast == ' ') {
441 cd->l_num[i] += 1;
442 if (cd->l_num[i] > MAZEZAM_MAX_CHUNKS)
443 return false;
444 cd->c_inset[i][cd->l_num[i] - 1] = j;
445 cd->c_width[i][cd->l_num[i] - 1] = 1;
446 }
447 else
448 cd->c_width[i][cd->l_num[i] - 1] += 1;
449 }
450 clast = c;
451 j++;
452 }
453 }
454 }
455 return true;
456}
457
458/* Draw the level */
459static void draw_level(
460 struct chunk_data *cd, /* the data about the chunks */
461 short *shift, /* an array of the horizontal offset of the lines */
462 short width,
463 short height,
464 short entrance,
465 short exit,
466 short x, /* player's x and y coords */
467 short y) {
468 /* The number of pixels the side of a square should be */
469 short size = (LCD_WIDTH/(width+2)) < (LCD_HEIGHT/height) ?
470 (LCD_WIDTH/(width+2)) : (LCD_HEIGHT/height);
471 /* The x and y position (in pixels) of the top left corner of the
472 * level
473 */
474 short xOff = (LCD_WIDTH - (size*width))/2;
475 short yOff = (LCD_HEIGHT - (size*height))/2;
476 /* For drawing the player, taken from the sokoban plugin */
477 short max = size - 1;
478 short middle = max / 2;
479 short ldelta = (middle + 1) / 2;
480 short i,j;
481 short third = size / 3;
482 short twothirds = (2 * size) / 3;
483#ifndef HAVE_LCD_COLOR
484 /* We #def these out to supress a compiler warning */
485 short k;
486#if LCD_DEPTH <= 1
487 short l;
488#endif
489#endif
490
491 rb->lcd_clear_display();
492
493#ifdef HAVE_LCD_COLOR
494 rb->lcd_set_foreground(MAZEZAM_WALL_COLOR);
495#elif LCD_DEPTH > 1
496 rb->lcd_set_foreground(MAZEZAM_WALL_GRAY);
497#endif
498 /* draw the upper wall */
499 rb->lcd_fillrect(0,0,xOff,yOff+(size*entrance));
500 rb->lcd_fillrect(xOff,0,size*width,yOff);
501 rb->lcd_fillrect(xOff+(size*width),0,LCD_WIDTH-xOff-(size*width),yOff+(size*exit));
502
503 /* draw the lower wall */
504 rb->lcd_fillrect(0,yOff+(size*entrance)+size,xOff,LCD_HEIGHT-yOff-(size*entrance)-size);
505 rb->lcd_fillrect(xOff,yOff+(size*height),size*width,LCD_HEIGHT-yOff-(size*height));
506 /* Note: the exit is made one pixel thinner than necessary as a visual
507 * clue that chunks cannot be pushed into it
508 */
509 rb->lcd_fillrect(xOff+(size*width),yOff+(size*exit)+size-1,LCD_WIDTH-xOff+(size*width),LCD_HEIGHT-yOff-(size*exit)-size+1);
510
511 /* draw the chunks */
512 for (i = 0; i<height; i++) {
513#ifdef HAVE_LCD_COLOR
514 /* adding width to i should have a fixed, but randomising effect on
515 * the choice of the colours of the top line of chunks
516 */
517 rb->lcd_set_foreground(chunk_colors[(i+width) % MAZEZAM_NUM_CHUNK_COLORS]);
518#endif
519 for (j = 0; j<cd->l_num[i]; j++) {
520#ifdef HAVE_LCD_COLOR
521 rb->lcd_fillrect(xOff+size*shift[i]+size*cd->c_inset[i][j],yOff+size*i,
522 cd->c_width[i][j]*size,size);
523#elif LCD_DEPTH > 1
524 rb->lcd_set_foreground(MAZEZAM_CHUNK_EDGE_GRAY);
525 rb->lcd_drawrect(xOff+size*shift[i]+size*cd->c_inset[i][j],yOff+size*i,
526 cd->c_width[i][j]*size,size);
527
528 /* draw shade */
529 rb->lcd_set_foreground(chunk_gray_shade[(i+width) % MAZEZAM_NUM_CHUNK_GRAYS]);
530 rb->lcd_drawline(xOff+size*shift[i]+size*cd->c_inset[i][j]+1,yOff+size*i+size-2,
531 xOff+size*shift[i]+size*cd->c_inset[i][j]+cd->c_width[i][j]*size-3,yOff+size*i+size-2);
532 rb->lcd_drawline(xOff+size*shift[i]+size*cd->c_inset[i][j]+cd->c_width[i][j]*size-2,yOff+size*i,
533 xOff+size*shift[i]+size*cd->c_inset[i][j]+cd->c_width[i][j]*size-2,yOff+size*i+size-2);
534
535 /* draw fill */
536 rb->lcd_set_foreground(chunk_gray[(i+width) % MAZEZAM_NUM_CHUNK_GRAYS]);
537 for (k = yOff+size*i+2; k < yOff+size*i+size-2; k += 2)
538 rb->lcd_drawline(xOff+size*shift[i]+size*cd->c_inset[i][j]+2,k,
539 xOff+size*shift[i]+size*cd->c_inset[i][j]+cd->c_width[i][j]*size-3,k);
540#else
541 rb->lcd_drawrect(xOff+size*shift[i]+size*cd->c_inset[i][j],yOff+size*i,
542 cd->c_width[i][j]*size,size);
543 for (k = xOff+size*shift[i]+size*cd->c_inset[i][j]+2;
544 k < xOff+size*shift[i]+size*cd->c_inset[i][j]+cd->c_width[i][j]*size;
545 k += 2 + (i & 1))
546 for (l = yOff+size*i+2; l < yOff+size*i+size; l += 2 + (i & 1))
547 rb->lcd_drawpixel(k, l);
548#endif
549 }
550 }
551
552 /* draw the player (mostly copied from the sokoban plugin) */
553#ifdef HAVE_LCD_COLOR
554 rb->lcd_set_foreground(MAZEZAM_PLAYER_COLOR);
555#elif LCD_DEPTH > 1
556 rb->lcd_set_foreground(MAZEZAM_PLAYER_GRAY);
557#endif
558 rb->lcd_drawline(xOff+size*x, yOff+size*y+middle,
559 xOff+size*x+max, yOff+size*y+middle);
560 rb->lcd_drawline(xOff+size*x+middle, yOff+size*y,
561 xOff+size*x+middle, yOff+size*y+max-ldelta);
562 rb->lcd_drawline(xOff+size*x+middle, yOff+size*y+max-ldelta,
563 xOff+size*x+middle-ldelta, yOff+size*y+max);
564 rb->lcd_drawline(xOff+size*x+middle, yOff+size*y+max-ldelta,
565 xOff+size*x+middle+ldelta, yOff+size*y+max);
566
567 /* draw the gate, if the player has moved into the level */
568 if (x >= 0) {
569#ifdef HAVE_LCD_COLOR
570 rb->lcd_set_foreground(MAZEZAM_GATE_COLOR);
571#elif LCD_DEPTH > 1
572 rb->lcd_set_foreground(MAZEZAM_GATE_GRAY);
573#endif
574 rb->lcd_drawline(xOff-size,yOff+entrance*size+third,
575 xOff-1,yOff+entrance*size+third);
576 rb->lcd_drawline(xOff-size,yOff+entrance*size+twothirds,
577 xOff-1,yOff+entrance*size+twothirds);
578 rb->lcd_drawline(xOff-size+third,yOff+entrance*size,
579 xOff-size+third,yOff+entrance*size+size-1);
580 rb->lcd_drawline(xOff-size+twothirds,yOff+entrance*size,
581 xOff-size+twothirds,yOff+entrance*size+size-1);
582 }
583}
584
585/* Manage the congratulations screen */
586static enum text_state welldone_screen(void) {
587 int button = BUTTON_NONE;
588 enum text_state state = TEXT_STATE_LOOPING;
589
590 display_text_page(welldone_page, 0);
591
592 while (state == TEXT_STATE_LOOPING) {
593 button = rb->button_get(true);
594
595 switch (button) {
596 case MAZEZAM_QUIT:
597 state = TEXT_STATE_QUIT;
598 break;
599
600 case MAZEZAM_SELECT:
601#if CONFIG_KEYPAD != ONDIO_PAD
602 case MAZEZAM_RIGHT:
603#endif
604 state = TEXT_STATE_OKAY;
605 break;
606
607 default:
608 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
609 state = TEXT_STATE_USB_CONNECTED;
610 break;
611 }
612 }
613
614 return state;
615}
616
617/* Manage the quit confimation screen */
618static enum text_state quitconfirm_loop(void) {
619 int button = BUTTON_NONE;
620 enum text_state state = TEXT_STATE_LOOPING;
621 short select = 2;
622
623 display_text_page(confirm_page, select + 1);
624
625 /* Wait for a button release. This is useful when a repeated button
626 * press is used for quit.
627 */
628 while ((rb->button_get(true) & BUTTON_REL) != BUTTON_REL);
629
630 while (state == TEXT_STATE_LOOPING) {
631 display_text_page(confirm_page, select + 1);
632
633 button = rb->button_get(true);
634
635 switch (button) {
636 case MAZEZAM_QUIT:
637 state = TEXT_STATE_QUIT;
638 break;
639
640 case MAZEZAM_UP:
641 case MAZEZAM_DOWN:
642 select = (2 - select) + 1;
643 break;
644
645 case MAZEZAM_SELECT:
646#if CONFIG_KEYPAD != ONDIO_PAD
647 case MAZEZAM_RIGHT:
648#endif
649 if (select == 1)
650 state = TEXT_STATE_QUIT;
651 else
652 state = TEXT_STATE_OKAY;
653 break;
654
655 default:
656 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
657 state = TEXT_STATE_USB_CONNECTED;
658 break;
659 }
660 }
661
662 return state;
663}
664
665/* Manage the playing of a level */
666static enum level_state level_loop(short level, short lives) {
667 struct chunk_data cd;
668 short shift[MAZEZAM_MAX_LINES]; /* amount each line has been shifted */
669 short width;
670 short height;
671 short entrance;
672 short exit;
673 short i;
674 short x,y;
675 int button;
676 enum level_state state = LEVEL_STATE_LOOPING;
677 bool blocked; /* is there a chunk in the way of the player? */
678
679 if (!(parse_level(level,&cd,&width,&height,&entrance,&exit)))
680 return LEVEL_STATE_PARSE_ERROR;
681
682 for (i = 0; i < height; i++)
683 shift[i] = 0;
684
685 x = -1;
686 y = entrance;
687
688 draw_level(&cd, shift, width, height, entrance, exit, x, y);
689
690#ifdef HAVE_REMOTE_LCD
691 /* Splash text seems to use the remote display by
692 * default. I suppose I better keep it tidy!
693 */
694 rb->lcd_remote_clear_display();
695#endif
696 rb->splash(MAZEZAM_LEVEL_LIVES_DELAY, true, MAZEZAM_LEVEL_LIVES_TEXT, level+1, lives);
697
698 /* ensure keys pressed during the splash screen are ignored */
699 rb->button_clear_queue();
700
701 while (state == LEVEL_STATE_LOOPING) {
702 draw_level(&cd, shift, width, height, entrance, exit, x, y);
703 rb->lcd_update();
704 button = rb->button_get(true);
705 blocked = false;
706
707 switch (button) {
708 case MAZEZAM_UP:
709 case MAZEZAM_UP | BUTTON_REPEAT:
710 if ((y > 0) && (x >= 0) && (x < width)) {
711 for (i = 0; i < cd.l_num[y-1]; i++)
712 blocked = blocked || ((x>=shift[y-1]+cd.c_inset[y-1][i])
713 && (x<shift[y-1]+cd.c_inset[y-1][i]+cd.c_width[y-1][i]));
714 if (!blocked) y -= 1;
715 }
716 break;
717
718 case MAZEZAM_DOWN:
719 case MAZEZAM_DOWN | BUTTON_REPEAT:
720 if ((y < height-1) && (x >= 0) && (x < width)) {
721 for (i = 0; i < cd.l_num[y+1]; i++)
722 blocked = blocked || ((x>=shift[y+1]+cd.c_inset[y+1][i])
723 && (x<shift[y+1]+cd.c_inset[y+1][i]+cd.c_width[y+1][i]));
724 if (!blocked) y += 1;
725 }
726 break;
727
728 case MAZEZAM_LEFT:
729 case MAZEZAM_LEFT | BUTTON_REPEAT:
730 if (x > 0) {
731 for (i = 0; i < cd.l_num[y]; i++)
732 blocked = blocked || (x == shift[y]+cd.c_inset[y][i]+cd.c_width[y][i]);
733 if (!blocked) x -= 1;
734 else if (shift[y] + cd.c_inset[y][0] > 0) {
735 x -= 1;
736 shift[y] -= 1;
737 }
738 }
739 break;
740
741 case MAZEZAM_RIGHT:
742 case MAZEZAM_RIGHT | BUTTON_REPEAT:
743 if (x < width-1) {
744 for (i = 0; i < cd.l_num[y]; i++)
745 blocked = blocked || (x+1 == shift[y]+cd.c_inset[y][i]);
746 if (!blocked) x += 1;
747 else if (shift[y] + cd.c_inset[y][cd.l_num[y]-1] + cd.c_width[y][cd.l_num[y]-1] < width) {
748 x += 1;
749 shift[y] += 1;
750 }
751 }
752 else if (x == width) state = LEVEL_STATE_COMPLETED;
753 else if (y == exit) x += 1;
754 break;
755
756 case MAZEZAM_RETRY:
757 state = LEVEL_STATE_FAILED;
758 break;
759
760 case MAZEZAM_QUIT:
761 switch (quitconfirm_loop()) {
762 case TEXT_STATE_QUIT:
763 state = LEVEL_STATE_QUIT;
764 break;
765
766 case TEXT_STATE_USB_CONNECTED:
767 state = LEVEL_STATE_USB_CONNECTED;
768 break;
769
770 default:
771 break;
772 }
773 break;
774
775 default:
776 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
777 state = LEVEL_STATE_USB_CONNECTED;
778 break;
779 }
780 }
781
782 return state;
783}
784
785/* The loop which manages a full game of MazezaM */
786static enum game_state game_loop(struct resume_data *r) {
787 enum game_state state = GAME_STATE_LOOPING;
788 int level = r->level;
789 int lives = MAZEZAM_START_LIVES;
790
791 rb->lcd_clear_display();
792
793 while (state == GAME_STATE_LOOPING)
794 {
795 switch (level_loop(level,lives)) {
796 case LEVEL_STATE_COMPLETED:
797 level += 1;
798 if (!((level - r->level) % MAZEZAM_EXTRA_LIFE))
799 lives += 1;
800 break;
801
802 case LEVEL_STATE_QUIT:
803 state = GAME_STATE_QUIT;
804 break;
805
806 case LEVEL_STATE_FAILED:
807 lives -= 1;
808 break;
809
810 case LEVEL_STATE_PARSE_ERROR:
811 state = GAME_STATE_PARSE_ERROR;
812 break;
813
814 case LEVEL_STATE_USB_CONNECTED:
815 state = GAME_STATE_USB_CONNECTED;
816 break;
817
818 default:
819 break;
820 }
821 if (lives == 0)
822 state = GAME_STATE_OVER;
823 else if (level == MAZEZAM_NUM_LEVELS)
824 state = GAME_STATE_COMPLETED;
825 }
826
827 switch (state) {
828 case GAME_STATE_OVER:
829#ifdef HAVE_REMOTE_LCD
830 /* Splash text seems to use the remote display by
831 * default. I suppose I better keep it tidy!
832 */
833 rb->lcd_remote_clear_display();
834#endif
835 rb->splash(MAZEZAM_GAMEOVER_DELAY, true, MAZEZAM_GAMEOVER_TEXT);
836 break;
837
838 case GAME_STATE_COMPLETED:
839 switch (welldone_screen()) {
840 case TEXT_STATE_QUIT:
841 state = GAME_STATE_QUIT;
842 break;
843
844 case TEXT_STATE_USB_CONNECTED:
845 state = GAME_STATE_USB_CONNECTED;
846 break;
847
848 default:
849 state = GAME_STATE_OKAY;
850 break;
851 }
852 break;
853
854 default:
855 break;
856 }
857
858 /* This particular resume game logic is designed to make
859 * players prove they can solve a level more than once
860 */
861 if (level > r->level + 1)
862 r->level += 1;
863
864 return state;
865}
866
867/* Manage the instruction screen */
868static enum text_state instruction_loop(void) {
869 int button;
870 enum text_state state = TEXT_STATE_LOOPING;
871 int page = 0;
872
873 while (state == TEXT_STATE_LOOPING) {
874 display_text_page(help_page[page], 0);
875 button = rb->button_get(true);
876
877 switch (button) {
878 case MAZEZAM_LEFT:
879 page -= 1;
880 break;
881
882 case MAZEZAM_SELECT:
883#if CONFIG_KEYPAD != ONDIO_PAD
884 case MAZEZAM_RIGHT:
885#endif
886 page += 1;
887 break;
888
889 case MAZEZAM_QUIT:
890 state = TEXT_STATE_QUIT;
891 break;
892
893 default:
894 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
895 state = TEXT_STATE_USB_CONNECTED;
896 break;
897
898 }
899
900 if ((page < 0) || (page >= MAZEZAM_NUM_HELP_PAGES))
901 state = TEXT_STATE_OKAY;
902 }
903
904 return state;
905}
906
907/* Manage the text screen that offers the user the option of
908 * resuming or starting a new game
909 */
910static enum text_state resume_game_loop (struct resume_data *r) {
911 int button = BUTTON_NONE;
912 enum text_state state = TEXT_STATE_LOOPING;
913 short select = 0;
914
915 /* if the resume level is 0, don't bother asking */
916 if (r->level == 0) return TEXT_STATE_OKAY;
917
918 display_text_page(resume_page, select + 1);
919
920 while (state == TEXT_STATE_LOOPING) {
921 display_text_page(resume_page, select + 1);
922
923 button = rb->button_get(true);
924
925 switch (button) {
926 case MAZEZAM_QUIT:
927 state = TEXT_STATE_QUIT;
928 break;
929
930 case MAZEZAM_LEFT:
931 state = TEXT_STATE_BACK;
932 break;
933
934 case MAZEZAM_UP:
935 case MAZEZAM_DOWN:
936 select = 1 - select;
937 break;
938
939 case MAZEZAM_SELECT:
940#if CONFIG_KEYPAD != ONDIO_PAD
941 case MAZEZAM_RIGHT:
942#endif
943 if (select == 1) {
944 /* The player wants to play a new game. I could ask
945 * for confirmation here, but the only penalty is
946 * playing through some already completed levels,
947 * so I don't think it's necessary
948 */
949 r->level = 0;
950 }
951 state = TEXT_STATE_OKAY;
952 break;
953
954 default:
955 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
956 state = TEXT_STATE_USB_CONNECTED;
957 break;
958 }
959 }
960
961 return state;
962}
963
964/* Load the resume data from the config file. The data is
965 * stored in both r and old.
966 */
967static void resume_load_data (struct resume_data *r, struct resume_data *old) {
968 struct configdata config[] = {
969 {TYPE_INT,0,MAZEZAM_NUM_LEVELS-1,&(r->level),MAZEZAM_CONFIG_LEVELS_NAME,NULL,NULL}
970 };
971
972 if (configfile_load(MAZEZAM_CONFIG_FILENAME,config,MAZEZAM_CONFIG_NUM_ITEMS,MAZEZAM_CONFIG_VERSION) < 0)
973 r->level = 0;
974 /* an extra precaution */
975 else if ((r->level < 0) || (MAZEZAM_NUM_LEVELS <= r->level))
976 r->level = 0;
977
978 old->level = r->level;
979}
980
981/* Save the resume data in the config file, but only if necessary */
982static void resume_save_data (struct resume_data *r, struct resume_data *old) {
983 struct configdata config[] = {
984 {TYPE_INT,0,MAZEZAM_NUM_LEVELS-1,&(r->level),MAZEZAM_CONFIG_LEVELS_NAME,NULL,NULL}
985 };
986
987 /* To reduce disk usage, only write the file if the resume data has
988 * changed.
989 */
990 if (old->level != r->level)
991 configfile_save(MAZEZAM_CONFIG_FILENAME,config,MAZEZAM_CONFIG_NUM_ITEMS,MAZEZAM_CONFIG_MINVERSION);
992}
993
994/* The loop which manages the welcome screen and menu */
995static enum text_state welcome_loop(void) {
996 int button;
997 short select = 0;
998 enum text_state state = TEXT_STATE_LOOPING;
999 struct resume_data r_data, old_data;
1000
1001 /* Load data */
1002 resume_load_data(&r_data, &old_data);
1003
1004 while (state == TEXT_STATE_LOOPING) {
1005 display_text_page(title_page, select + 1);
1006 button = rb->button_get(true);
1007
1008 switch (button) {
1009 case MAZEZAM_QUIT:
1010 state = TEXT_STATE_QUIT;
1011 break;
1012
1013 case MAZEZAM_UP:
1014 select = (select + (title_page.num_lines - 2)) % (title_page.num_lines - 1);
1015 break;
1016
1017 case MAZEZAM_DOWN:
1018 select = (select + 1) % (title_page.num_lines - 1);
1019 break;
1020
1021 case MAZEZAM_SELECT:
1022#if CONFIG_KEYPAD != ONDIO_PAD
1023 case MAZEZAM_RIGHT:
1024#endif
1025 if (select == 0) { /* play game */
1026 switch (resume_game_loop(&r_data)) {
1027 case TEXT_STATE_QUIT:
1028 state = TEXT_STATE_QUIT;
1029 break;
1030
1031 case TEXT_STATE_USB_CONNECTED:
1032 state = TEXT_STATE_USB_CONNECTED;
1033 break;
1034
1035 case TEXT_STATE_BACK:
1036 break;
1037
1038 default: { /* Ouch! This nesting is too deep! */
1039 switch (game_loop(&r_data)) {
1040 case GAME_STATE_QUIT:
1041 state = TEXT_STATE_QUIT;
1042 break;
1043
1044 case GAME_STATE_USB_CONNECTED:
1045 state = TEXT_STATE_USB_CONNECTED;
1046 break;
1047
1048 case GAME_STATE_PARSE_ERROR:
1049 state = TEXT_STATE_PARSE_ERROR;
1050 break;
1051
1052 default:
1053 break;
1054 }
1055 break;
1056 }
1057 }
1058 }
1059 else if (select == 1) { /* Instructions */
1060 switch (instruction_loop()) {
1061 case TEXT_STATE_QUIT:
1062 state = TEXT_STATE_QUIT;
1063 break;
1064
1065 case TEXT_STATE_USB_CONNECTED:
1066 state = TEXT_STATE_USB_CONNECTED;
1067 break;
1068
1069 default:
1070 break;
1071 }
1072 }
1073 else /* Quit */
1074 state = TEXT_STATE_QUIT;
1075
1076 break;
1077
1078 default:
1079 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
1080 state = TEXT_STATE_USB_CONNECTED;
1081 break;
1082 }
1083 }
1084
1085 /* I'm not sure if it's appropriate to write to disk on USB events.
1086 * Currently, I do so.
1087 */
1088 resume_save_data(&r_data, &old_data);
1089
1090 return state;
1091}
1092
1093/* Plugin entry point */
1094enum plugin_status plugin_start(struct plugin_api* api, void* parameter) {
1095 enum plugin_status state;
1096
1097 /* Usual plugin stuff */
1098 (void)parameter;
1099 rb = api;
1100
1101#ifdef HAVE_LCD_COLOR
1102 rb->lcd_set_background(MAZEZAM_BG_COLOR);
1103 rb->lcd_set_backdrop(NULL);
1104#elif LCD_DEPTH > 1
1105 rb->lcd_set_background(MAZEZAM_BG_GRAY);
1106#endif
1107 rb->lcd_setfont(FONT_SYSFIXED);
1108
1109 /* initialise the config file module */
1110 configfile_init(rb);
1111
1112 switch (welcome_loop()) {
1113 case TEXT_STATE_USB_CONNECTED:
1114 state = PLUGIN_USB_CONNECTED;
1115 break;
1116
1117 case TEXT_STATE_PARSE_ERROR:
1118 state = PLUGIN_ERROR;
1119 break;
1120
1121 default:
1122 state = PLUGIN_OK;
1123 break;
1124 }
1125
1126 return state;
1127}