/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2014 Franklin Wei, (c) 2018 Sebastian Leonhardt * * Original work (C) 2000 David Leonard, public domain according to * http://www.adaptive-enterprises.com.au/~d/software/amaze/ * * Original Rockbox port by Jerry Chapman, 2007 * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ***************************************************************************/ #include "plugin.h" #include "lib/pluginlib_actions.h" const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; static const struct opt_items noyes_text[] = { { STR(LANG_SET_BOOL_NO) }, { STR(LANG_SET_BOOL_YES) } }; static const struct opt_items mazesize_text[] = { { STR(LANG_DIFFICULTY_EASY) }, { STR(LANG_DIFFICULTY_MEDIUM) }, { STR(LANG_DIFFICULTY_HARD) }, { STR(LANG_DIFFICULTY_EXPERT) } }; bool show_map; bool remember_visited; bool use_large_tiles; int maze_size; #if LCD_DEPTH >= 16 #define COLOR_GROUND LCD_RGBPACK(51,51,51) #define COLOR_SKY LCD_RGBPACK(51,51,102) #define COLOR_VISITED LCD_RGBPACK(102,77,51) #define COLOR_MARK LCD_RGBPACK(255,100,61) #define COLOR_GOAL LCD_RGBPACK(128,0,0) /* red */ #define COLOR_COMPASS LCD_RGBPACK(255,255,0) /* yellow */ #define COLOR_PARA LCD_RGBPACK(153,153,102) /* color side wall */ /* #define COLOR_PERP LCD_RGBPACK(102,51,0) */ #define COLOR_PERP LCD_RGBPACK(204,153,102) /* color wall perp */ #elif LCD_DEPTH == 2 #define COLOR_GROUND LCD_BLACK #define COLOR_SKY LCD_WHITE #define COLOR_VISITED LCD_DARKGRAY #define COLOR_GOAL LCD_WHITE #define COLOR_COMPASS LCD_BLACK #define COLOR_MARK LCD_WHITE #define COLOR_PARA LCD_DARKGRAY /* color side wall */ #define COLOR_PERP LCD_LIGHTGRAY /* color wall perp */ #else /* mono */ #define COLOR_GROUND LCD_BLACK #define COLOR_SKY LCD_BLACK #define COLOR_VISITED LCD_BLACK #define COLOR_GOAL LCD_BLACK #define COLOR_COMPASS LCD_WHITE #define COLOR_PARA LCD_BLACK /* color side wall */ #define COLOR_PERP LCD_WHITE /* color wall perp */ #endif #define A_DOWN 'v' #define A_RIGHT '>' #define A_UP '^' #define A_LEFT '<' #define SPACE ' ' /* Space you can walk through */ #define BLOCK 'B' /* A block that you can't walk through */ #define OBSPACE '#' /* Obscured space */ #define VISITED '.' /* Visited space */ #define GOAL '%' /* Exit from the maze */ #define START '+' /* Starting point in the maze */ enum dir { DIR_DOWN=0, DIR_RIGHT=1, DIR_UP=2, DIR_LEFT=3 }; #define _TOD(d) (enum dir)((d) % 4) #define _TOI(d) (int)(d) #define LEFT_OF(d) _TOD(_TOI(d) + 1) #define REVERSE_OF(d) _TOD(_TOI(d) + 2) #define RIGHT_OF(d) _TOD(_TOI(d) + 3) static struct { int y, x; } dirtab[4] = { { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 } }; static char ptab[4] = { A_DOWN, A_RIGHT, A_UP, A_LEFT }; int px, py; /* Player position */ enum dir pdir; /* Player direction */ char punder; /* character under player, if any */ int sx, sy; /* Start position */ int gx, gy; /* Goal position */ int gdist; /* Distance from start to goal */ int won = 0; /* Reached goal */ int cheated = 0; /* Cheated somehow */ bool compass = false; /* show compass */ int button; /* Button data */ bool loaded=false; /* Loaded? */ #define FIELD_SIZE 80 #define MAP_CONST 20 static char map[FIELD_SIZE * FIELD_SIZE]; /* map storage */ static char umap[FIELD_SIZE * FIELD_SIZE]; /* visible depth */ #define MAX_DEPTH 20 int depth; /* CX,CY = center of screen */ #define CX LCD_WIDTH / 2 #define CY LCD_HEIGHT / 2 int crd_x[MAX_DEPTH], crd_y[MAX_DEPTH]; /* screen vertices */ #if LCD_WIDTH >= 220 || LCD_HEIGHT >= 220 #include "pluginbitmaps/amaze_tiles_9.h" #define amaze_tiles_large amaze_tiles_9 #define TILESIZE_LARGE BMPWIDTH_amaze_tiles_9 #include "pluginbitmaps/amaze_tiles_7.h" #define amaze_tiles_small amaze_tiles_7 #define TILESIZE_SMALL BMPWIDTH_amaze_tiles_7 #elif LCD_WIDTH >= 160 || LCD_HEIGHT >= 160 #include "pluginbitmaps/amaze_tiles_7.h" #define amaze_tiles_large amaze_tiles_7 #define TILESIZE_LARGE BMPWIDTH_amaze_tiles_7 #include "pluginbitmaps/amaze_tiles_5.h" #define amaze_tiles_small amaze_tiles_5 #define TILESIZE_SMALL BMPWIDTH_amaze_tiles_5 #else #include "pluginbitmaps/amaze_tiles_5.h" #define amaze_tiles_large amaze_tiles_5 #define TILESIZE_LARGE BMPWIDTH_amaze_tiles_5 #include "pluginbitmaps/amaze_tiles_3.h" #define amaze_tiles_small amaze_tiles_3 #define TILESIZE_SMALL BMPWIDTH_amaze_tiles_3 #endif /* save file names */ #define UMAP_FILE PLUGIN_GAMES_DIR "/amaze_umap.sav" #define MAP_FILE PLUGIN_GAMES_DIR "/amaze_map.sav" #define PREF_FILE PLUGIN_GAMES_DIR "/amaze_prefs.sav" void clearscreen (void) { #if LCD_DEPTH > 1 rb->lcd_set_background(LCD_BLACK); rb->lcd_set_foreground(LCD_WHITE); #endif rb->lcd_clear_display(); rb->lcd_update(); } void getmaxyx(int *y, int *x) { *y = (maze_size + 1) * MAP_CONST; *x = (maze_size + 1) * MAP_CONST; } void map_write (char *pane, int y, int x, char c) { int maxy, maxx; getmaxyx(&maxy, &maxx); if (x<0 || x>=maxx || y<0 || y>=maxy) return; pane[x * FIELD_SIZE + y] = c; } char map_read (char *pane, int y, int x) { int maxy, maxx; getmaxyx(&maxy, &maxx); if (x<0 || x>=maxx || y<0 || y>=maxy) { return SPACE; } return pane[x * FIELD_SIZE + y]; } /* redefine ncurses werase */ void werase (char *pane) { int y, x; int maxy, maxx; getmaxyx(&maxy, &maxx); for (y = 0; y < maxy; y++) for (x = 0; x < maxx; x++) map_write(pane, y, x, SPACE); } /* start of David Leonard's code */ /* Look at position (y,x) in the maze map */ char at(int y, int x) { int maxy, maxx; getmaxyx(&maxy, &maxx); if (y == py && x == px) return punder; if (y < 0 || y >= maxy || x < 0 || x >= maxx) return SPACE; else { return map_read(map, y, x); } } void copyumap(int y, int x, int fullvis) { char c; c = at(y, x); if (!fullvis && c == SPACE && map_read(umap, y, x) != SPACE) c = OBSPACE; map_write(umap, y, x, c); } struct path { int y, x; int ttl; /* Time until this path stops */ int spawns; /* Max number of forks this path can do */ int distance; /* Distance from start */ struct path *next; }; /* * A better maze-digging algorithm. * Simultaneously advance multiple digging paths through the map. * This is done by having a work queue of advancing paths. * Occasionally a path can fork; thus adding more to the work * queue and diversifying the maze. */ void eatmaze(int starty, int startx) { struct path { int y, x; int ttl; /* Time until this path stops */ int spawns; /* Max number of forks this path can do */ int distance; /* Distance from start */ struct path *next; }; struct path *path_free, *path_head, *path_tail; struct path *p, *s; /* static -- per in #rockbox -- was having stack issues */ static struct path path_storage[2000]; int try; unsigned i; int y, x, dy, dx; int sdir; /* Set up the free list of path cells */ for (i = 2; i < sizeof path_storage / sizeof path_storage[0]; i++) path_storage[i].next = &path_storage[i-1]; path_storage[1].next = NULL; path_free = &path_storage[sizeof path_storage / sizeof path_storage[0] - 1]; /* Set up the initial path cell */ path_storage[0].y = starty; path_storage[0].x = startx; map_write(map, starty, startx, SPACE); /* Set up the initial goal. It will move later. */ gy = starty; gx = startx; gdist = 0; /* Initial properties of the root path */ path_storage[0].ttl = 50; path_storage[0].spawns = 20; path_storage[0].distance = 0; /* Put the initial path into the queue */ path_storage[0].next = NULL; path_head = path_tail = &path_storage[0]; while (path_head != NULL) { /* Dequeue */ p = path_head; path_head = p->next; if (path_head == NULL) path_tail = NULL; /* There's a large chance that some paths miss a turn */ if (rb->rand() % 100 < 60) goto requeue; /* First thing we do is advance the path. */ y = p->y; x = p->x; sdir = rb->rand() % 4; for (try = 0; try < 4; try ++) { dx = dirtab[(sdir + try) % 4].x; dy = dirtab[(sdir + try) % 4].y; /* Going back on ourselves? */ if (at(y + dy, x + dx) != BLOCK) continue; /* Connecting to another path? */ if (at(y + dy * 2, x + dx * 2) != BLOCK) continue; if (at(y + dy + dx, x + dx - dy) != BLOCK) continue; if (at(y + dy - dx, x + dx + dy) != BLOCK) continue; break; } if (try == 4 || p->ttl <= 0) { /* Failed: the path is placed on the free list. */ p->next = path_free; path_free = p; continue; } /* Dig the path a bit */ p->y = y + dy; p->x = x + dx; map_write(map, p->y, p->x, SPACE); p->ttl--; p->distance++; if (p->distance > gdist) { gx = p->x; gy = p->y; gdist = p->distance; } /* Decide if we should spawn */ if (/* rb->rand() % (p->ttl + 1) < p->spawns && */ path_free) { /* Take a new path element off the free list */ s = path_free; path_free = s->next; /* Insert it at the tail of the queue */ s->next = NULL; if (path_tail) path_tail->next = s; else path_head = s; path_tail = s; /* Newly spawned path s will inherit most properties from p */ s->y = p->y; s->x = p->x; s->ttl = p->ttl + rb->rand() % 10; s->spawns = p->spawns; s->distance = p->distance; /* p->spawns--; */ } requeue: /* Put p onto the tail of the queue */ p->next = NULL; if (path_tail) path_tail->next = p; else path_head = p; path_tail = p; } } /* Move the player to a new position/direction in the maze map */ void mappmove(int newpy, int newpx, enum dir newpdir) { map_write(map, py, px, punder); copyumap(py, px, 1); punder = at(newpy, newpx); py = newpy; px = newpx; pdir = newpdir; copyumap(py, px, 1); map_write(umap, py, px, ptab[_TOI(pdir)]); rb->lcd_update(); } void clearmap (char *amap) { int maxy, maxx; int y, x; getmaxyx(&maxy, &maxx); for (y = 0; y < maxy; y++) for (x = 0; x < maxx; x++) map_write(amap, y, x, BLOCK); } /* Reveal the solution to the user */ void showmap(void) { int maxy, maxx, y, x; char ch, och; getmaxyx(&maxy, &maxx); for (y = 0; y < maxy; y++) for (x = 0; x < maxx; x++) { ch = at(y, x); if (ch == SPACE) { och = map_read(umap, y, x); if (och == BLOCK || och == OBSPACE) ch = OBSPACE; } map_write(umap, y, x, ch); rb->yield(); } map_write(umap, py, px, ptab[_TOI(pdir)]); rb->lcd_update(); } /* * Create a new maze map * The algorithm here is quite inferior to that presented in the * magazine. I could only remember the gist of it: recursively dig a * trail that doesn't touch any other part of the maze, keeping track * of all possible points where the path could fork. Later on try those * possible branches; put limits on the segment lengths etc. */ void makemaze(void) { int maxy, maxx; int i; /* Get the window dimensions */ getmaxyx(&maxy, &maxx); clearmap(map); py = rb->rand() % (maxy - 2) + 1; /* maxy/2 */ px = rb->rand() % (maxx - 2) + 1; /* maxx/2 */ eatmaze(py, px); sx = px; /* starting position */ sy = py; /* Face in an interesting direction: */ pdir = DIR_UP; for (i = 0; i < 4 && at(py + dirtab[pdir].y, px + dirtab[pdir].x) == BLOCK; i++) pdir = LEFT_OF(pdir); map_write(map, py, px, START); map_write(map, gy, gx, GOAL); punder = START; mappmove(py, px, pdir); } /* new drawing routines */ void draw_arrow(int dir, int sx, int sy, int pass) { if (pass > 2) return; rb->lcd_fillrect(sx, sy, 1, 1); switch(dir) { case 0: /* down */ rb->lcd_fillrect(sx + 2*pass, sy, 1, 1); draw_arrow(dir, sx - 1, sy - 1, pass + 1); break; case 2: /* up */ rb->lcd_fillrect(sx + 2*pass, sy, 1, 1); draw_arrow(dir, sx - 1, sy + 1, pass + 1); break; case 1: /* left */ rb->lcd_fillrect(sx, sy + 2*pass, 1, 1); draw_arrow(dir, sx + 1, sy - 1, pass + 1); break; case 3: /* right */ rb->lcd_fillrect(sx, sy + 2*pass, 1, 1); draw_arrow(dir, sx - 1, sy - 1, pass +1); break; } } /* Provide a compass pointing 'north' or draw arrow mark on the floor */ void draw_pointer(int dir, bool is_compass) { int offset; if(is_compass) offset = -crd_y[1]*2/3; /* draw compass at the top */ else offset = crd_y[1]/2; /* draw mark on the floor */ #if LCD_DEPTH > 1 if(is_compass) rb->lcd_set_foreground(COLOR_COMPASS); else rb->lcd_set_foreground(COLOR_MARK); #else rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID); #endif switch(dir) { case 0: /* point down */ draw_arrow(dir, CX - 1, CY + offset + 6, 0); rb->lcd_fillrect(CX - 1, CY + offset + 1, 1, 3); break; case 2: /* point up */ draw_arrow(dir, CX - 1, CY + offset, 0); rb->lcd_fillrect(CX - 1, CY + offset + 3, 1, 3); break; case 1: /* point left */ draw_arrow(dir, CX - 6, CY + offset + 6, 0); rb->lcd_fillrect(CX - 3, CY + offset + 6, 3, 1); break; case 3: /* point right */ draw_arrow(dir, CX + 1, CY + offset + 6, 0); rb->lcd_fillrect(CX - 4, CY + offset + 6, 3, 1); break; } #if LCD_DEPTH == 1 rb->lcd_set_drawmode(DRMODE_SOLID); #endif rb->lcd_update(); } void draw_end_wall(int bx, int by) { #if LCD_DEPTH > 1 rb->lcd_set_foreground(COLOR_PERP); rb->lcd_fillrect(CX - bx/2, CY - by/2, bx + 1, by); #else rb->lcd_drawrect(CX - bx/2, CY - by/2, bx + 1, by); #endif } void draw_side(int fx, int bx, int by, int tan_n, int tan_d, bool isleft) { int signx; if(isleft) signx = -1; else signx = 1; #if LCD_DEPTH > 1 for(int i = bx; i < fx + 1; i++) { /* add some stripes */ if(i % 3 == 0) rb->lcd_set_foreground(COLOR_PERP); else rb->lcd_set_foreground(COLOR_PARA); rb->lcd_vline(CX + signx * i/2, CY - tan_n * (i-bx)/2 / tan_d - by/2, CY + tan_n * (i-bx)/2 / tan_d + by/2); } #else rb->lcd_vline(CX + signx * bx/2, CY - by/2, CY + by/2); rb->lcd_vline(CX + signx * (fx + 1)/2, CY - tan_n * (fx - bx + 1)/2 / tan_d - by/2, CY + tan_n * (fx - bx + 1)/2 / tan_d + by/2); rb->lcd_drawline(CX + signx * bx/2, CY - by/2, CX + signx * (fx + 1)/2, CY - tan_n * (fx - bx + 1)/2 / tan_d - by/2); rb->lcd_drawline(CX + signx * bx/2, CY + by/2, CX + signx * (fx + 1)/2, CY + tan_n * (fx - bx + 1)/2 / tan_d + by/2); #endif } void draw_hall(int fx, int bx, int by, bool isleft) { #if LCD_DEPTH > 1 rb->lcd_set_foreground(COLOR_PERP); if(isleft) rb->lcd_fillrect(CX - fx/2, CY - by/2, (fx - bx)/2 + 1, by); else rb->lcd_fillrect(CX + bx/2, CY - by/2, (fx - bx)/2 + 1, by); #else if(isleft) rb->lcd_drawrect(CX - fx/2, CY - by/2, (fx - bx)/2 + 1, by); else rb->lcd_drawrect(CX + bx/2, CY - by/2, (fx - bx)/2 + 1, by); #endif } void draw_side_tri(int fx, int fy, int bx, int tan_n, int tan_d, bool isvisited, bool isgoal) { int i; int signx, signy; signy = 1; while(signy >= -1) { #if LCD_DEPTH > 1 if(signy == 1) if(isgoal) rb->lcd_set_foreground(COLOR_GOAL); else if(isvisited) rb->lcd_set_foreground(COLOR_VISITED); else rb->lcd_set_foreground(COLOR_GROUND); else rb->lcd_set_foreground(COLOR_SKY); #endif signx = 1; while(signx >= -1) { for(i = fx; i > bx; i--) { #if LCD_DEPTH == 1 /* if unvisited floor draw pattern, otherwise solid */ if (signy!=1 || isgoal || isvisited || (CX + signx * i/2) & 1) #endif rb->lcd_vline(CX + signx * i/2, CY + signy * fy/2, CY + signy * fy/2 + signy * tan_n * (i - fx)/2 / tan_d); } signx-=2; } signy-=2; } } void draw_hall_crnr(int fx, int fy, int bx, int by, bool isleft, bool isvisited, bool isgoal) { #if LCD_DEPTH > 1 rb->lcd_set_foreground(COLOR_SKY); #endif if(isleft) rb->lcd_fillrect(CX - fx/2, CY - fy/2, (fx - bx)/2 + 1, (fy - by)/2); else rb->lcd_fillrect(CX + bx/2, CY - fy/2, (fx - bx)/2 + 1, (fy - by)/2); #if LCD_DEPTH > 1 if(isgoal) rb->lcd_set_foreground(COLOR_GOAL); else if(isvisited) rb->lcd_set_foreground(COLOR_VISITED); else rb->lcd_set_foreground(COLOR_GROUND); if(isleft) rb->lcd_fillrect(CX - fx/2, CY + by/2, (fx - bx)/2 + 1, (fy - by)/2); else rb->lcd_fillrect(CX + bx/2, CY + by/2, (fx - bx)/2 + 1, (fy - by)/2); #else /* LCD_DEPTH == 1 */ /* if unvisited floor draw pattern, otherwise solid */ if(isleft) for (int x = CX-fx/2; x <= CX-bx/2; x++) { if (isgoal || isvisited || x & 1) rb->lcd_vline(x, CY + by/2, CY + fy/2); } else for (int x = CX+bx/2; x <= CX+fx/2; x++) { if (isgoal || isvisited || x & 1) rb->lcd_vline(x, CY + by/2, CY + fy/2); } #endif } void draw_center_sq(int fy, int bx, int by, bool isvisited, bool isgoal, bool isfront, int chr) { chr = chr - '0'; /* get the integer value */ #if LCD_DEPTH > 1 rb->lcd_set_foreground(COLOR_SKY); #endif rb->lcd_fillrect(CX - bx/2, CY - fy/2, bx, (fy - by)/2); #if LCD_DEPTH > 1 if(isgoal) rb->lcd_set_foreground(COLOR_GOAL); else if(isvisited) rb->lcd_set_foreground(COLOR_VISITED); else rb->lcd_set_foreground(COLOR_GROUND); rb->lcd_fillrect(CX - bx/2, CY + by/2, bx, (fy - by)/2 + 1); #else /* if unvisited floor draw pattern, otherwise solid */ for (int x = CX-bx/2; x <= CX+bx/2; x++) { if (isgoal || isvisited || x & 1) rb->lcd_vline(x, CY + by/2, CY + fy/2 + 1); } #endif if(isvisited && chr >= 0 && chr <= 3) { #if LCD_DEPTH > 1 rb->lcd_set_foreground(COLOR_MARK); #else rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID); #endif if(isfront) { /* cell is marked in front, draw arrow */ if (chr == ((int)(pdir) + 3) % 4) draw_pointer(DIR_LEFT, false); else if (chr == ((int)(pdir) + 1) % 4) draw_pointer(DIR_RIGHT, false); else if (chr == ((int)(pdir) + 2) % 4) draw_pointer(DIR_DOWN, false); else /* same direction */ draw_pointer(DIR_UP, false); } else /* cell is marked but is in distance */ rb->lcd_fillrect(CX, CY + (fy + by)/4, 2, 2); #if LCD_DEPTH == 1 rb->lcd_set_drawmode(DRMODE_SOLID); #endif } } bool is_visited(char cell) { if (cell - '0' >= 0 && cell - '0' <= 3) return true; else if (cell == VISITED) return true; else return false; } void graphic_view(void) { int dist; int x, y, dx, dy; int a, l, r; /* is block? ahead/left/right */ bool g, gl, gr; /* ground visted? under/left/right */ bool e, el, er; /* is goal? under/left/right */ int tan_n, tan_d; /* tangent numerator/denominator */ dx = dirtab[(int)pdir].x; dy = dirtab[(int)pdir].y; for (dist = 1; dist < depth; dist++) if (at(py + dy * dist, px + dx * dist) == BLOCK) break; if (!show_map) { clearmap(umap); copyumap(gy, gx, 1); } #if LCD_DEPTH == 1 clearscreen(); #endif while (--dist >= 0) { x = px + dx * dist; y = py + dy * dist; /* ground */ g = is_visited(at(y, x)); gl = is_visited(at(y - dx, x + dy)); gr = is_visited(at(y + dx, x - dy)); /* goal/end */ e = at(y, x) == GOAL; el = at(y - dx, x + dy) == GOAL; er = at(y + dx, x - dy) == GOAL; /* ahead */ a = at(y + dy, x + dx) == BLOCK; /* to the left */ l = at(y - dx, x + dy) == BLOCK; /* to the right */ r = at(y + dx, x - dy) == BLOCK; tan_n = crd_y[dist] - crd_y[dist+1]; tan_d = crd_x[dist] - crd_x[dist+1]; if (a) draw_end_wall(crd_x[dist+1], crd_y[dist+1]); if (l) { draw_side(crd_x[dist], crd_x[dist+1], crd_y[dist+1], tan_n, tan_d, true); } else { draw_hall(crd_x[dist], crd_x[dist+1], crd_y[dist+1], true); draw_hall_crnr(crd_x[dist], crd_y[dist], crd_x[dist+1], crd_y[dist+1], true, gl, el); } if (r) { draw_side(crd_x[dist], crd_x[dist+1], crd_y[dist+1], tan_n, tan_d, false); } else { draw_hall(crd_x[dist], crd_x[dist+1], crd_y[dist+1], false); draw_hall_crnr(crd_x[dist], crd_y[dist], crd_x[dist+1], crd_y[dist+1], false, gr, er); } draw_center_sq(crd_y[dist], crd_x[dist+1], crd_y[dist+1], g, e, dist==0, (int)(at(y, x))); draw_side_tri(crd_x[dist], crd_y[dist], crd_x[dist+1], tan_n, tan_d, g, e); copyumap(y + dy, x + dx, 0); /* ahead */ copyumap(y, x, 1); /* here */ copyumap(y - dx, x + dy, 0); /* left */ copyumap(y + dx, x - dy, 0); /* right */ if (!l) copyumap(y - dx + dy, x + dy + dx, 0); /* lft ahead */ if (!r) copyumap(y + dx + dy, x - dy + dx, 0); /* rt ahead */ } if (compass) draw_pointer(pdir, true); } void win(void) { /* int i; char amazed[8] = "amazing!"; char newton; for (i=0; i <= 8; i++) { newton = amazed[i]; map_write(msg, 0, i + 31, newton); } */ won++; showmap(); show_map = 1; } /* Try to move the player in the direction given */ void trymove(enum dir dir) { int nx, ny; ny = py + dirtab[(int)dir].y; nx = px + dirtab[(int)dir].x; if (at(ny, nx) == BLOCK) { graphic_view(); return; } if (at(ny, nx) == GOAL) win(); mappmove(ny, nx, pdir); if (remember_visited && punder == SPACE) punder = VISITED; graphic_view(); } void walkleft(void) { int a, l; int dx, dy; int owon = won; while (1) { int input = pluginlib_getaction(TIMEOUT_NOBLOCK, plugin_contexts, ARRAYLEN(plugin_contexts)); if(input==PLA_CANCEL || input==PLA_EXIT) { return; } rb->lcd_update(); if (won != owon) { break; } dx = dirtab[(int)pdir].x; dy = dirtab[(int)pdir].y; /* ahead */ a = at(py + dy, px + dx) == BLOCK; /* to the left */ l = at(py - dx, px + dy) == BLOCK; if (!l) { mappmove(py, px, LEFT_OF(pdir)); graphic_view(); rb->sleep(2); trymove(pdir); continue; } if (a) { mappmove(py, px, RIGHT_OF(pdir)); graphic_view(); continue; } trymove(pdir); rb->yield(); } } void draw_tile(int index, int x, int y) { if (use_large_tiles == 1) rb->lcd_bitmap_part (amaze_tiles_large, 0, index * TILESIZE_LARGE, TILESIZE_LARGE, x * TILESIZE_LARGE, y * TILESIZE_LARGE, TILESIZE_LARGE, TILESIZE_LARGE); else rb->lcd_bitmap_part (amaze_tiles_small, 0, index * TILESIZE_SMALL, TILESIZE_SMALL, x * TILESIZE_SMALL, y * TILESIZE_SMALL, TILESIZE_SMALL, TILESIZE_SMALL); } void draw_tile_map(int xmin, int xmax, int ymin, int ymax) { int x,y; char map_unit; int tdex = 7; /* tile index */ enum tile_index { t_down=0, t_right=1, t_up=2, t_left=3, t_visited=4, t_obspace=5, t_goal=6, t_block=7, t_space=8, t_start=9 }; for(y = ymin; y <= ymax; y++) for(x = xmin; x <= xmax; x++) { map_unit = map_read(umap, y, x); switch (map_unit) { case VISITED: case '0': case '1': case '2': case '3': tdex = t_visited; break; case OBSPACE: tdex = t_obspace; break; case START: tdex = t_start; break; case GOAL: tdex = t_goal; break; case SPACE: tdex = t_space; break; case BLOCK: tdex = t_block; break; } draw_tile(tdex, x - xmin, y - ymin); } if(sx>=xmin && sx<=xmax && sy>=ymin && sy<=ymax) { x = sx; y = sy; draw_tile(t_start, x - xmin, y - ymin); } if(px>=xmin && px<=xmax && py>=ymin && py<=ymax) { x = px; y = py; draw_tile(pdir, x - xmin, y - ymin); } rb->lcd_update(); } void check_map_bounds(int *xmin, int *xmax, int *ymin, int *ymax) { int maxx, maxy; getmaxyx(&maxy, &maxx); /* bounds check x */ if(*xmin < 0) { *xmax = *xmax - *xmin; *xmin = 0; } if(*xmax >= maxx) { *xmin = *xmin - *xmax + maxx - 1; *xmax = maxx - 1; } /* bounds check y */ if(*ymin < 0) { *ymax = *ymax - *ymin; *ymin = 0; } if(*ymax >= maxy) { *ymin = *ymin - *ymax + maxy - 1; *ymax = maxy - 1; } } void calc_map_size(int *xmin, int *xmax, int *ymin, int *ymax) { int tile_size; /* runtime option */ int vx, vy; /* maxx, maxy of view */ int midx, midy; /* midpoint x, y of view */ int maxx, maxy; /* maxx, maxy of map */ getmaxyx(&maxy, &maxx); if (use_large_tiles == 1) tile_size = TILESIZE_LARGE; else tile_size = TILESIZE_SMALL; vx = LCD_WIDTH / tile_size; if (vx > maxx) vx = maxx; vy = LCD_HEIGHT / tile_size; if (vy > maxy) vy = maxy; midx = vx / 2; midy = vy / 2; *xmin = px - midx; if(vx % 2 == 0) *xmax = px + midx - 1; else *xmax = px + midx; *ymin = py - midy; if(vy % 2 == 0) *ymax = py + midy - 1; else *ymax = py + midy; } void draw_portion_map(void) { int xmin, xmax, ymin, ymax; /* coords of map corners */ bool quit_map; int input; clearscreen(); calc_map_size(&xmin, &xmax, &ymin, &ymax); quit_map = false; while (!quit_map) { check_map_bounds(&xmin, &xmax, &ymin, &ymax); draw_tile_map(xmin, xmax, ymin, ymax); input = pluginlib_getaction(TIMEOUT_BLOCK, plugin_contexts, ARRAYLEN(plugin_contexts)); switch(input) { case PLA_CANCEL: case PLA_EXIT: quit_map = true; break; case PLA_UP: case PLA_UP_REPEAT: ymin--; ymax--; break; case PLA_DOWN: case PLA_DOWN_REPEAT: ymin++; ymax++; break; case PLA_LEFT: case PLA_LEFT_REPEAT: xmin--; xmax--; break; case PLA_RIGHT: case PLA_RIGHT_REPEAT: xmin++; xmax++; break; default: break; } } } bool load_map(char *filename, char *amap) { int fd; int x,y; int maxxy; size_t n; char newton = BLOCK; char map_size[2]; /* load a map */ fd = rb->open(filename, O_RDONLY); if (fd < 0) { LOGF("Invalid map file: %s\n", filename); return false; } n = rb->read(fd, map_size, sizeof(map_size)); if (n <= 0) { LOGF("Invalid map size."); return false; } maze_size = (int)map_size[0] - 48; maxxy = MAP_CONST * (maze_size + 1); char line[maxxy + 1]; for(y=0; y < maxxy ; y++) { n = rb->read(fd, line, sizeof(line)); if (n <= 0) { return false; } for(x=0; x < maxxy+1; x++) { switch(line[x]) { case '\n': break; case '0': case '1': case '2': case '3': newton = line[x]; break; case START: sy = y; sx = x; newton = START; break; case SPACE: case BLOCK: case OBSPACE: newton = line[x]; break; case GOAL: newton = GOAL; gy = y; gx = x; break; case A_DOWN: case A_LEFT: case A_UP: case A_RIGHT: py = y; px = x; switch(line[x]) { case A_DOWN: pdir = DIR_DOWN; break; case A_LEFT: pdir = DIR_LEFT; break; case A_UP: pdir = DIR_UP; break; case A_RIGHT: pdir = DIR_RIGHT; break; } /* FALLTHROUGH */ case VISITED: newton = VISITED; break; } if (line[x] != '\n') map_write(amap, y, x, newton); } } rb->close(fd); rb->remove(filename); return true; } bool load_game(void) { if (load_map(UMAP_FILE, umap) && load_map(MAP_FILE, map)) return true; else return false; } bool save_map(char *filename, char *amap) { int x,y; int maxy, maxx; char map_unit; int fd; int line_len = (maze_size + 1) * MAP_CONST + 1; char line[line_len]; char map_size[2] = {'0','\n'}; line[line_len - 1] = '\n'; /* last cell is a linefeed */ map_size[0] = (char)(maze_size + 48); if ((fd = rb->open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) return false; rb->write(fd, map_size, 2); getmaxyx(&maxy, &maxx); for(y=0; y < maxy; y++) { for (x=0; x < maxx; x++) { map_unit = map_read(amap, y, x); if(y == py && x == px) line[x] = ptab[_TOI(pdir)]; else if(y == sy && x == sx) line[x] = START; else line[x] = map_unit; } rb->write(fd, line, line_len); } rb->close(fd); return true; } bool save_game(void) { if (save_map(UMAP_FILE, umap) && save_map(MAP_FILE, map)) return true; else return false; } bool ingame; bool save_prefs(char *filename); static int menu_cb(int action, const struct menu_item_ex *this_item, struct gui_synclist *this_list) { (void)this_list; int idx=((intptr_t)this_item); if (action==ACTION_REQUEST_MENUITEM) { if (ingame) { if (idx==2) return ACTION_EXIT_MENUITEM; } else { /* !ingame */ if (idx==3 || idx==8) return ACTION_EXIT_MENUITEM; if (!loaded && (idx==0 || idx==9)) return ACTION_EXIT_MENUITEM; } } return action; } int menu(void) { bool exit_menu = false; int selection = 0, result = 0, status = 1; MENUITEM_STRINGLIST(menu, ID2P(LANG_AMAZE_MENU), menu_cb, ID2P(LANG_CHESSBOX_MENU_RESUME_GAME), ID2P(LANG_CHESSBOX_MENU_NEW_GAME), ID2P(LANG_SET_MAZE_SIZE), ID2P(LANG_VIEW_MAP), ID2P(LANG_SHOW_COMPASS), ID2P(LANG_SHOW_MAP), ID2P(LANG_REMEMBER_PATH), ID2P(LANG_USE_LARGE_TILES), ID2P(LANG_SHOW_SOLUTION), ID2P(LANG_QUIT_WITHOUT_SAVING), ID2P(LANG_MENU_QUIT) ); clearscreen(); while(!exit_menu) { result = rb->do_menu(&menu, &selection, NULL, false); switch(result) { case 0: /* resume */ exit_menu = true; if (!ingame) { save_prefs(PREF_FILE); } break; case 1: /* new game */ exit_menu = true; if (ingame) { rb->splash(0, ID2P(LANG_GENERATING_MAZE)); clearmap(umap); makemaze(); /* Show where the goal is */ copyumap(gy, gx, 1); rb->lcd_update(); mappmove(py, px, pdir); if (remember_visited) punder = VISITED; else punder = SPACE; } else { /* !ingame */ loaded=false; save_prefs(PREF_FILE); } break; case 2: /* Set maze size */ { int old_size = maze_size; rb->set_option(rb->str(LANG_SET_MAZE_SIZE), &maze_size, RB_INT, mazesize_text, 4, NULL); if (maze_size != old_size) loaded = false; } break; case 3: /* View map */ exit_menu = true; draw_portion_map(); break; case 4: /* Show compass option */ rb->set_option(rb->str(LANG_SHOW_COMPASS), &compass, RB_BOOL, noyes_text, 2, NULL); break; case 5: /* Show Map option */ rb->set_option(rb->str(LANG_SHOW_MAP), &show_map, RB_BOOL, noyes_text, 2, NULL); break; case 6: /* Remember Path option */ rb->set_option(rb->str(LANG_REMEMBER_PATH), &remember_visited, RB_BOOL, noyes_text, 2, NULL); break; case 7: /* Tilesize option */ rb->set_option(rb->str(LANG_USE_LARGE_TILES), &use_large_tiles, RB_BOOL, noyes_text, 2, NULL); break; case 8: /* solver */ exit_menu = true; cheated++; walkleft(); break; case 9: /* quit w/o saving */ exit_menu = true; status = 0; break; case 10: /* save+quit */ exit_menu = true; if (ingame) { if (save_game()) status = 0; else rb->splash(HZ*3, ID2P(LANG_ERROR_WRITING_CONFIG)); } else { if(loaded) save_game(); status = 0; } break; } } return status; } int amaze(void) { int quitting; int i; int input; clearscreen(); rb->lcd_setfont(FONT_SYSFIXED); if(!loaded) rb->splash(0, ID2P(LANG_GENERATING_MAZE)); crd_x[0] = LCD_WIDTH + 1; crd_y[0] = LCD_HEIGHT + 1; for (depth=1; depth < MAX_DEPTH + 1; depth++) { crd_x[depth] = crd_x[depth-1]*2/3; if(crd_x[depth] % 2 != 0) crd_x[depth]++; crd_y[depth] = crd_y[depth-1]*2/3; if(crd_y[depth] % 2 != 0) crd_y[depth]++; if (crd_x[depth]==crd_x[depth-1] || crd_y[depth]==crd_y[depth-1]) break; } --depth; if (!loaded) { clearmap(umap); makemaze(); } /* Show where the goal is */ copyumap(gy, gx, 1); rb->lcd_update(); quitting = 0; mappmove(py, px, pdir); if (remember_visited) punder = VISITED; else punder = SPACE; clearscreen(); graphic_view(); while (!quitting && !won) { rb->lcd_update(); input = pluginlib_getaction(TIMEOUT_BLOCK, plugin_contexts, ARRAYLEN(plugin_contexts)); switch (input) { case PLA_CANCEL: case PLA_EXIT: i = menu(); rb->lcd_setfont(FONT_SYSFIXED); clearscreen(); graphic_view(); switch (i) { case 0: quitting = 1; break; case 1: break; case 2: return 0; break; } break; case PLA_UP: case PLA_UP_REPEAT: trymove(pdir); break; case PLA_DOWN: case PLA_DOWN_REPEAT: trymove(REVERSE_OF(pdir)); break; case PLA_LEFT: mappmove(py, px, LEFT_OF(pdir)); graphic_view(); break; case PLA_RIGHT: mappmove(py, px, RIGHT_OF(pdir)); graphic_view(); break; case PLA_SELECT: if (punder==SPACE || punder==VISITED) { /* mark ground */ punder = pdir + '0'; } else { /* clear mark */ if (remember_visited) punder = VISITED; else punder = SPACE; } graphic_view(); break; } } rb->lcd_update(); //graphic_view(); if (won) { won = false; /* reset boolean */ if (cheated) { rb->splash(HZ*2, ID2P(LANG_YOU_CHEATED)); return 0; } rb->splash(HZ*2, ID2P(LANG_YOU_WIN)); return 1; } else { return 0; } } bool save_prefs(char *filename) { int fd; char ms[2] = { (char)(maze_size) + '0', '\n' }; char sm[2] = { (char)(show_map) + '0', '\n' }; char rv[2] = { (char)(remember_visited) + '0', '\n' }; char lt[2] = { (char)(use_large_tiles) + '0', '\n' }; fd = rb->open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666); if(fd >= 0) { rb->write(fd, ms, 2); rb->write(fd, sm, 2); rb->write(fd, rv, 2); rb->write(fd, lt, 2); } else { rb->splash(HZ, ID2P(LANG_ERROR_WRITING_CONFIG)); return false; } rb->close(fd); return true; } bool load_prefs(char *filename) { int fd; char instr[2]; fd = rb->open(filename, O_RDONLY); if (fd < 0) { LOGF("Invalid preferences file: %s\n", filename); return false; } rb->read(fd, instr, sizeof(instr)); maze_size = (int)(instr[0] - '0'); rb->read(fd, instr, sizeof(instr)); show_map = (bool)(instr[0] - '0'); rb->read(fd, instr, sizeof(instr)); remember_visited = (bool)(instr[0] - '0'); rb->read(fd, instr, sizeof(instr)); use_large_tiles = (bool)(instr[0] - '0'); rb->close(fd); return true; } enum plugin_status plugin_start(const void *parameter) { (void) parameter; int ret; rb->srand(*rb->current_tick); /* hard-code in program default options */ show_map=1; remember_visited=1; use_large_tiles=1; maze_size=1; loaded=load_game(); /* let's go, gentlemen, we have some work to do */ #if LCD_DEPTH > 1 rb->lcd_set_backdrop(NULL); #endif ingame = false; ret = menu(); if (ret) { ingame = true; amaze(); } rb->lcd_setfont(FONT_UI); return PLUGIN_OK; }