HP Prime for All
English
Русский
Name | IFS Prime |
Description | Creates Iterated Function System (IFS) Fractals with touch zoom in/out. There are 16 built-in definitions, plus users can store custom definitions in L0 to generate their own images. |
Author | Wes Loewer |
Source code formatted by website engine
BEGIN
LOCAL c, k;
// Needs to be in Function App to make plot window (almost) full screen.
// Some APPS, such as Geometry, use a much smaller region of screen for plotting.
STARTAPP("Function");
STARTVIEW(-1, 0); // Home
//STARTVIEW(-7, 0); // Program Catalog
c := -1; // default IFS definition is c = 1. the negative skips the prompt.
WHILE (c := get_ifsdef(c)) > 0 DO
REPEAT
k := gen_image();
UNTIL k == 4; // ESC key was pressed
END;
// This should prevent mouse events from being passed back to the OS but it doesn't.
flush_input;
RETURN 0;
END; // main program
////////////////////////////////////////////////////////////
// get_ifsdef(c)
// c - the default CHOOSE item
// if c < 0 then don't prompt with CHOOSE but just use the |c| choice
get_ifsdef(c)
BEGIN
LOCAL n, d, ifsDef, entries, ifsSize, doAutoZoom := 0;
IF c < 0 THEN
c := -c; // make |c| the choice without prompting.
ELSE
CHOOSE(c, "Iterated Function Systems",
{"HP-PRIME",
"Barnsley's Fern", "Dragon", "Floor Plan", "Spiral", "Tree", "ZigZag",
"Triangle (Sierpinski)", "Inverted Triangle", "Pentagon", "Inverted Pentagon",
"Hexagon (Koch Snowflake)", "Butterfly Tie", "Star? or Cube?", "Necklace", "Electron Cloud",
"Use List L0"});
IF c == 0 THEN
RETURN 0; // done
END;
END;
// "HP-PRIME" IFS definition generated by Wes's IFSSPELL program. (see FracXtra v6.0 http://www.zblob.com/fracxtra.html)
// Fern-ZigZag are my favorites from Fractint, some (all?) of which came from yet other sources.
// The rest are from Wes's Fractal Polygon program. (see http://www.nahee.com/spanky/pub/fractals/params/ifs_examples.par)
// Each line in the definition uses the format used in Fractint and numerous other programs: a, b, c, d, e, f, p
// [x'] = [a b]*[x] + [e] with a probability p of being selected
// [y'] [c d] [y] [f]
d := 0; // d used below to avoid the pain of renumbering CASEs when I change the order of the CHOOSE command
CASE
// HP-PRIME
IF c == (d := d+1) THEN
ifsDef := {
0.00000, -0.20000, 0.06250, 0.00000, 0.30000, 0.00000, 0.02526,
0.00000, -0.20000, 0.06250, 0.00000, 0.30000, 0.60000, 0.02526,
0.09375, 0.00000, 0.00000, 0.20000, 0.10000, 0.40000, 0.03789,
0.00000, 0.20000, -0.06250, 0.00000, 0.50000, 1.00000, 0.02526,
0.00000, 0.20000, -0.06250, 0.00000, 0.50000, 0.40000, 0.02526,
0.00000, -0.20000, 0.13281, 0.00000, 1.10000, 0.00000, 0.05367,
0.07031, 0.00000, 0.00000, 0.15000, 0.90000, 0.85000, 0.02131,
0.00000, 0.15000, -0.05469, 0.00000, 1.35000, 1.00000, 0.01658,
0.06250, 0.00000, 0.00000, 0.15000, 1.10000, 0.50000, 0.01894,
0.09375, 0.00000, 0.00000, 0.20000, 1.70000, 0.40000, 0.03789,
0.00000, -0.20000, 0.13281, 0.00000, 2.70000, 0.00000, 0.05367,
0.07031, 0.00000, 0.00000, 0.15000, 2.50000, 0.85000, 0.02131,
0.00000, 0.15000, -0.05469, 0.00000, 2.95000, 1.00000, 0.01658,
0.06250, 0.00000, 0.00000, 0.15000, 2.70000, 0.50000, 0.01894,
0.00000, -0.20000, 0.13281, 0.00000, 3.50000, 0.00000, 0.05367,
0.07031, 0.00000, 0.00000, 0.15000, 3.30000, 0.85000, 0.02131,
0.00000, 0.15000, -0.05469, 0.00000, 3.75000, 1.00000, 0.01658,
0.06250, 0.00000, 0.00000, 0.15000, 3.50000, 0.50000, 0.01894,
0.03476, 0.21893, -0.07808, -0.00000, 3.50000, 0.50000, 0.03454,
0.00000, -0.20000, 0.15625, 0.00000, 4.30000, 0.00000, 0.06314,
0.00000, -0.20000, 0.15625, 0.00000, 4.70000, 0.00000, 0.06314,
0.05919, 0.21540, -0.14798, -0.00000, 4.50000, 1.00000, 0.06441,
0.05919, -0.21540, 0.14798, -0.00000, 5.10000, 0.00000, 0.06441,
0.00000, -0.20000, 0.15625, 0.00000, 5.50000, 0.00000, 0.06314,
0.09375, 0.00000, 0.00000, 0.20000, 5.70000, 0.80000, 0.03789,
0.09375, 0.00000, 0.00000, 0.20000, 5.70000, 0.00000, 0.03789,
0.06250, 0.00000, 0.00000, 0.20000, 5.90000, 0.40000, 0.02526,
0.00000, -0.20000, 0.09375, 0.00000, 5.90000, 0.20000, 0.03789
};
// goes from (0, 0) to (6.35, 1)
Xmin := -0.03;
Xmax := 6.35;
Ymin := -1.6;
Ymax := 2.5;
END;
// Barnsley's iconic Fern
IF c == (d := d+1) THEN
ifsDef := {
0, 0, 0, .16, 0, 0, .01,
.85, .04, -.04, .85, 0, 1.6, .85,
.2, -.26, .23, .22, 0, 1.6, .07,
-.15, .28, .26, .24, 0, .44, .07
};
Xmin := -8;
Xmax := 8;
Ymin := -1;
Ymax := 11;
END;
// Dragon
IF c == (d := d+1) THEN
ifsDef := {
.824074, .281482, -.212346, .864198, -1.882290, -0.110607, .787473,
.088272, .520988, -.463889, -.377778, 0.785360, 8.095795, .212527
};
Xmin := -7.14;
Xmax := 7.14;
Ymin := -0.35;
Ymax := 10.35;
END;
// Floor
IF c == (d := d+1) THEN
ifsDef := {
.0, -.5, .5, .0, -1.732366, 3.366182, .333333,
.5, .0, .0, .5, -0.027891, 5.014877, .333333,
.0, .5, -.5, .0, 1.620804, 3.310401, .333333
};
Xmin := -9.6;
Xmax := 9.6;
Ymin := -1;
Ymax := 11;
END;
// Spiral
IF c == (d := d+1) THEN
ifsDef := {
.787879, -.424242, .242424, .859848, 1.758647, 1.408065, .895652,
-.121212, .257576, .151515, .053030, -6.721654, 1.377236, .052174,
.181818, -.136364, .090909, .181818, 6.086107, 1.568035, .052174
};
Xmin := -7.16;
Xmax := 7.16;
Ymin := -0.37;
Ymax := 10.37;
END;
// Tree
IF c == (d := d+1) THEN
ifsDef := {
0, 0, 0, .5, 0, 0, .05,
.42, -.42, .42, .42, 0, .2, .4,
.42, .42, -.42, .42, 0, .2, .4,
.1, 0, 0, .1, 0, .2, .15
};
Xmin := -0.310;
Xmax := 0.310;
Ymin := -0.01;
Ymax := 0.46;
END;
// ZigZag
IF c == (d := d+1) THEN
ifsDef := {
-.632407, -.614815, -.545370, .659259, 3.840822, 1.282321, .888128,
-.036111, .444444, .210185, .037037, 2.071081, 8.330552, .111872
};
Xmin := -7.14;
Xmax := 7.14;
Ymin := -0.35;
Ymax := 10.35;
END;
// Triangle (Sierpinski Triangle)
// (This was the first fractal I ever saw.)
IF c == (d := d+1) THEN
ifsDef := {
0.5, 0.0, 0.0, 0.5, 0.0, 0.5, 0.333333,
0.5, 0.0, 0.0, 0.5, 0.433013, -0.25, 0.333333,
0.5, 0.0, 0.0, 0.5, -0.433013, -0.25, 0.333333
};
Xmin := -1.1;
Xmax := 1.1;
Ymin := -0.5; // -cos(60)
Ymax := 1;
END;
// Inverted Triangle
IF c == (d := d+1) THEN
ifsDef := {
-0.5, 0.0, 0.0, -0.5, 0.0, 1.5, 0.333333,
-0.5, 0.0, 0.0, -0.5, 1.299038, -0.75, 0.333333,
-0.5, 0.0, 0.0, -0.5, -1.299038, -0.75, 0.333333
};
Xmin := -3.4;
Xmax := 3.4;
Ymin := -2.16;
Ymax := 2.76;
END;
// Pentagon
IF c == (d := d+1) THEN
ifsDef := {
0.381966, 0.0, 0.0, 0.381966, 0.0, 0.618034, 0.2,
0.381966, 0.0, 0.0, 0.381966, 0.587785, 0.190983, 0.2,
0.381966, 0.0, 0.0, 0.381966, 0.363271, -0.500000, 0.2,
0.381966, 0.0, 0.0, 0.381966, -0.363271, -0.500000, 0.2,
0.381966, 0.0, 0.0, 0.381966, -0.587785, 0.190983, 0.2
};
Xmin := -1.3;
Xmax := 1.3;
Ymin := -.809; // -cos(36)
Ymax := 1;
END;
// Inverted Pentagon
IF c == (d := d+1) THEN
ifsDef := {
-0.381970, 0.0, 0.0, -0.381970, 0.0, 1.381970, 0.2,
-0.381970, 0.0, 0.0, -0.381970, 1.314332, 0.427052, 0.2,
-0.381970, 0.0, 0.0, -0.381970, 0.812302, -1.118037, 0.2,
-0.381970, 0.0, 0.0, -0.381970, -0.812302, -1.118037, 0.2,
-0.381970, 0.0, 0.0, -0.381970, -1.314332, 0.427052, 0.2
};
Xmin := -2.9;
Xmax := 2.9;
Ymin := -1.969;
Ymax := 2.184;
END;
// Koch Snowflake, (poly 6), from IFS Polygon
IF c == (d := d+1) THEN
ifsDef := {
0.333333, 0.0, 0.0, 0.333333, 0.0 , 0.666667, 0.166667,
0.333333, 0.0, 0.0, 0.333333, 0.577351, 0.333334, 0.166667,
0.333333, 0.0, 0.0, 0.333333, 0.577351, -0.333333, 0.166667,
0.333333, 0.0, 0.0, 0.333333, 0.000000, -0.666667, 0.166667,
0.333333, 0.0, 0.0, 0.333333, -0.577351, -0.333334, 0.166667,
0.333333, 0.0, 0.0, 0.333333, -0.577351, 0.333333, 0.166667
};
Xmin := -1.45;
Xmax := 1.45;
Ymin := -1;
Ymax := 1;
END;
// Butterfly Tie
IF c == (d := d+1) THEN
ifsDef := {
0.500000, 0.000000, 0.000000, 0.500000, -0.066536, 0.492693, 0.250000,
0.500000, 0.000000, 0.000000, 0.500000, -0.052838, -0.473904, 0.250000,
0.500000, 0.000000, 0.000000, 0.500000, -0.180039, -0.348643, 0.250000,
0.500000, 0.000000, 0.000000, 0.500000, 0.078278, -0.350731, 0.250000
};
Xmin := -1.45;
Xmax := 1.45;
Ymin := -1;
Ymax := 1;
END;
// Star Cube
IF c == (d := d+1) THEN
ifsDef := {
0.500000, 0.000000, 0.000000, 0.500000, 0.000000, 0.500000, 0.166667,
0.500000, 0.000000, 0.000000, 0.500000, 0.433013, 0.250000, 0.166667,
0.500000, 0.000000, 0.000000, 0.500000, 0.433013, -0.250000, 0.166667,
0.500000, 0.000000, 0.000000, 0.500000, 0.000000, -0.500000, 0.166667,
0.500000, 0.000000, 0.000000, 0.500000, -0.433013, -0.250000, 0.166667,
0.500000, 0.000000, 0.000000, 0.500000, -0.433013, 0.250000, 0.166667
};
Xmin := -1.45;
Xmax := 1.45;
Ymin := -1;
Ymax := 1;
END;
// Necklace
IF c == (d := d+1) THEN
ifsDef := {
0.116337, 0.000000, 0.000000, 0.116337, 0.000000, 0.883663, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, 0.228709, 0.853553, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, 0.441831, 0.765275, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, 0.624844, 0.624844, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, 0.765275, 0.441832, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, 0.853553, 0.228709, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, 0.883663, 0.000000, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, 0.853553, -0.228709, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, 0.765275, -0.441831, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, 0.624844, -0.624844, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, 0.441832, -0.765275, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, 0.228709, -0.853553, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, 0.000000, -0.883663, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, -0.228709, -0.853553, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, -0.441831, -0.765275, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, -0.624844, -0.624844, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, -0.765275, -0.441832, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, -0.853553, -0.228709, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, -0.883663, -0.000000, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, -0.853553, 0.228709, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, -0.765275, 0.441831, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, -0.624844, 0.624844, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, -0.441832, 0.765275, 0.041667,
0.116337, 0.000000, 0.000000, 0.116337, -0.228709, 0.853553, 0.041667
};
Xmin := -1.45;
Xmax := 1.45;
Ymin := -1;
Ymax := 1;
END;
// Electron Cloud
IF c == (d := d+1) THEN
ifsDef := {
0.500000, 0.000000, 0.000000, 0.500000, 0.000000, 0.500000, 0.055556,
0.500000, 0.000000, 0.000000, 0.500000, 0.171010, 0.469846, 0.055556,
0.500000, 0.000000, 0.000000, 0.500000, 0.321394, 0.383022, 0.055556,
0.500000, 0.000000, 0.000000, 0.500000, 0.433013, 0.250000, 0.055556,
0.500000, 0.000000, 0.000000, 0.500000, 0.492404, 0.086824, 0.055556,
0.500000, 0.000000, 0.000000, 0.500000, 0.492404, -0.086824, 0.055556,
0.500000, 0.000000, 0.000000, 0.500000, 0.433013, -0.250000, 0.055556,
0.500000, 0.000000, 0.000000, 0.500000, 0.321394, -0.383022, 0.055556,
0.500000, 0.000000, 0.000000, 0.500000, 0.171010, -0.469846, 0.055556,
0.500000, 0.000000, 0.000000, 0.500000, 0.000000, -0.500000, 0.055556,
0.500000, 0.000000, 0.000000, 0.500000, -0.171010, -0.469846, 0.055556,
0.500000, 0.000000, 0.000000, 0.500000, -0.321394, -0.383022, 0.055556,
0.500000, 0.000000, 0.000000, 0.500000, -0.433013, -0.250000, 0.055556,
0.500000, 0.000000, 0.000000, 0.500000, -0.492404, -0.086824, 0.055556,
0.500000, 0.000000, 0.000000, 0.500000, -0.492404, 0.086824, 0.055556,
0.500000, 0.000000, 0.000000, 0.500000, -0.433013, 0.250000, 0.055556,
0.500000, 0.000000, 0.000000, 0.500000, -0.321394, 0.383022, 0.055556,
0.500000, 0.000000, 0.000000, 0.500000, -0.171010, 0.469846, 0.055556
};
Xmin := -1.45;
Xmax := 1.45;
Ymin := -1;
Ymax := 1;
END;
// List L0
IF c == (d := d+1) THEN
IF SIZE(L0) > 0 AND SIZE(L0) MOD 7 == 0 THEN
ifsDef := L0;
// attempt an auto window setting later
doAutoZoom := 1;
ELSE
MSGBOX("Invalid L0 list.");
RETURN 0;
END;
END;
END; // CASE
ifsSize := SIZE(ifsDef);
entries := ifsSize/7;
// separate Fractint formatted data into separate lists of matrices
// (The next two lines really demonstrate the power of using MAKEMAT nested inside a MAKELIST.)
multLstMat := MAKELIST(MAKEMAT(ifsDef(N+2 * (I-1) + J), 2, 2), N, 0, ifsSize-1, 7);
addLstMat := MAKELIST(MAKEMAT(ifsDef(N+4+2 * (I-1) + J), 2), N, 0, ifsSize-1, 7);
probLst := MAKELIST(ifsDef(N+7), N, 0, ifsSize-1, 7);
// convert probability list to a cumulative probability list
FOR n FROM 2 TO entries-1 DO
probLst(n) := probLst(n) + probLst(n-1);
END;
// force last cumulative probability to be exactly 1 instead of just approximately 1 due to rounding
probLst(entries) := 1;
IF doAutoZoom THEN
autozoom();
END;
RETURN c;
END; // get_ifsdef
////////////////////////////////////////////////////////////
gen_image()
BEGIN
LOCAL xy, i, k;
init_screen();
// starting place really isn't really critical, but might as well make it random
xy := [0, 0];
xy(1) := RANDOM(Xmin, Xmax);
xy(2) := RANDOM(Ymin, Ymax);
// Toss out the first few iterations to eliminate obvious outliers
// (If you zoom in deep enough, everything is an outlier.)
FOR i FROM 1 TO 20 DO
xy := iteration(xy);
END;
// now do the actual plotting of points
REPEAT
// slight speed up by doing some iterations between checks for input
FOR i FROM 1 TO 40 DO
xy := iteration(xy);
IF Xmin ≤ xy(1) ≤ Xmax AND Ymin ≤ xy(2) ≤ Ymax THEN // notice a < x < b is allowed
set_pixel(xy);
END;
END;
k := check_input();
UNTIL k ;
RETURN k;
END; // gen_image()
////////////////////////////////////////////////////////////
init_screen()
BEGIN
flush_input(); // just in case the is a key or mouse event left over
RECT(RGB(0, 0, 128)); // dark blue looks nice and has good contrast
drawZoomMenu();
END;
////////////////////////////////////////////////////////////
drawZoomMenu()
BEGIN
IF zoomin THEN
DRAWMENU("Z In•", "Z Out");
ELSE
DRAWMENU("Z In", "Z Out•");
END;
END;
////////////////////////////////////////////////////////////
flush_input()
BEGIN
// clear out any left over key or mouse
WHILE GETKEY ≥ 0 DO
END;
WHILE MOUSE(1) ≥ 0 DO
END;
END;
////////////////////////////////////////////////////////////
// sets window based on a sample of iterations
autozoom()
BEGIN
LOCAL i, xy, hw, hh, cx, cy;
LOCAL xmin := 1ᴇ99, xmax := -1ᴇ99, ymin := 1ᴇ99, ymax := -1ᴇ99; // init to impossible values
xy := [0, 0];
// toss out the first few iterations
FOR i FROM 1 TO 20 DO
xy := iteration(xy);
END;
// do a bunch of iterations and find the min/max values
FOR i FROM 1 TO 500 DO
xy := iteration(xy);
xmin := MIN(xmin, xy(1));
xmax := MAX(xmax, xy(1));
ymin := MIN(ymin, xy(2));
ymax := MAX(ymax, xy(2));
END;
// zoom out square from center x, y to 319x219 aspect ratio
// center x, y
cx := (xmax + xmin) / 2;
cy := (ymax + ymin) / 2;
// half width, half height
hw := 1.2 * (xmax - xmin) / 2; // add 20% to be sure
hh := 1.2 * (ymax - ymin) / 2;
// zoom out horizontally or vertically to aspect ratio
IF hw/hh > 319/219 THEN
hh := hw * 219/319;
ELSE
hw := hh * 319/219;
END;
// set plot window
Xmin := cx - hw;
Xmax := cx + hw;
Ymin := cy - hh;
Ymax := cy + hh;
END;
////////////////////////////////////////////////////////////
iteration(xy)
BEGIN
LOCAL r, n;
// select a random rule weighted by rule probabilities
r := RANDOM;
n := 0;
REPEAT
n := n+1;
UNTIL probLst(n) ≥ r;
// apply the n'th rule
xy := multLstMat(n) * xy + addLstMat(n);
RETURN xy;
END; // iteration
////////////////////////////////////////////////////////////
set_pixel(xy)
BEGIN
LOCAL x, y, color, r;
x := xy(1);
y := xy(2);
color := GETPIX(x, y);
color := invRGB(color);
// red and green will be the same for yellow
r := color(1);
//g := color(2);
//b := color(3);
// Generate new color.
// This just increases the brightness of a repeated pixel,
// but if you wanted, here's where you could be creative.
r := MIN(MAX(r, 128) + 32, 255);
//g := MIN(MAX(g, 128) + 32, 255);
//b := 0;
PIXON(x, y, RGB(r, r, 0));
// for debugging, faster, but doesn't look as nice
//PIXON(xy(1), xy(2), RGB(255, 255, 0));
END;
////////////////////////////////////////////////////////////
// decomposes color into {r, g, b} components
// inverse of c := RGB(r, g, b)
invRGB(c)
BEGIN
//LOCAL r, g, b;
//r := BITSR(c, 16);
//r := BITAND(r, 255);
//g := BITSR(c, 8);
//g := BITAND(g, 255);
//b := BITAND(c, 255);
//RETURN {r, g, b};
// equivalent
RETURN { BITAND(BITSR(c, 16), 255), BITAND(BITSR(c, 8), 255), BITAND(c, 255) };
END;
////////////////////////////////////////////////////////////
check_input()
BEGIN
LOCAL k, m, m1, mx, my;
LOCAL halfwidth, halfheight;
LOCAL menu;
// check key first
k := GETKEY;
IF k == 4 THEN // ESC key
RETURN k;
END;
IF k == 30 THEN // Enter key
k := WAIT; // pause
IF k == 4 THEN
RETURN k;
ELSE
RETURN 0; // carry on
END;
END;
// no key, now check mouse
m := MOUSE;
m1 := m(1);
IF SIZE(m1) == 0 THEN
RETURN 0; // no mouse either. carry on
END;
// there was a mouse tap
// wait until mouse is no longer being pressed
WHILE MOUSE(1) ≥ 0 DO
END;
// check to see if a softmenu was selected
mx := m1(1);
my := m1(2);
menu := softmenu(mx, my, 2);
IF menu > 0 THEN
zoomin := (menu == 1);
drawZoomMenu();
RETURN 0; // set zoom, carry on
END;
IF menu == 0 THEN
RETURN 0; // invalid menu tapped, just ignore and carry on
END;
// no soft menu tapped
// convert mouse to Cartesian coordinates and zoom there
m1 := PX→C(mx, my);
mx := m1(1);
my := m1(2);
IF zoomin THEN
// zoom IN x2 and recenter at tap
halfwidth := (Xmax-Xmin) / 4; // half of the previous half width
halfheight := (Ymax-Ymin) / 4;
ELSE
// zoom OUT x2 and recenter at tap
halfwidth := (Xmax-Xmin); // twice the previous half width
halfheight := (Ymax-Ymin);
END;
Xmin := mx - halfwidth;
Xmax := mx + halfwidth;
Ymin := my - halfheight;
Ymax := my + halfheight;
RETURN 100; // to indicate mouse tap, just some value > 50 to distinguish from a keyboard input
END;
////////////////////////////////////////////////////////////
// converts pixel x, y into a menu choice
// returns menu number (1 through n)
// returns 0 if an invalid menu option was selected ( > n) or tap was exactly between two menu options
// returns -1 if y was not in softmenu region of the screen
// (This should definitely be a built-in function.)
softmenu(x, y, n)
BEGIN
LOCAL m;
m := -1;
IF y ≥ 220 THEN
CASE
IF 0 ≤ x ≤ 51 THEN m := 1; END;
IF 53 ≤ x ≤ 104 THEN m := 2; END;
IF 106 ≤ x ≤ 157 THEN m := 3; END;
IF 159 ≤ x ≤ 210 THEN m := 4; END;
IF 212 ≤ x ≤ 263 THEN m := 5; END;
IF 265 ≤ x ≤ 319 THEN m := 6; END;
DEFAULT m := 0; // if it's right in between menu options
END;
// check to see if valid menu option was selected
IF m > n THEN
m := 0;
END;
END;
RETURN m;
END;