rockbox/apps/plugins/amaze.c
Christian Soffke 5aa2b339d5 plugins & debug menu: apply consistent titles
Purge "Menu" or "Rockbox" from plugin menu titles
and match application names from manual.

Exception: "Main Menu" is left unchanged as the
title for the Main Menu Configuration plugin, since
it appears in Rockbox's Settings menu and therefore
should match the name of the setting.

E.g.:

"Rockbox Goban" => "Goban"
"Image Viewer Menu" => "Image Viewer"
"Viewer Menu" => "Text Viewer"
"Menu" => "Chess Clock"
"Do What?" => "Text Editor"
"Mpegplayer Menu" => "MPEG Player"
"Multiboot Settings" => "Multiboot"
"Disktidy" => "Disk Tidy"
...

Change-Id: Ie6d3be7557f31a36309489037ad8b2b27b06706e
2025-12-23 10:25:05 -05:00

1620 lines
41 KiB
C

/***************************************************************************
* __________ __ ___.
* 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 <hcs> 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, "Amaze", 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;
}