// A quick effort to recreate Spirograph in HP PPL. The main focus was for // me to learn how to use menus. As a result the code isn't quite as crisp // or general as it could be. Enhancements are welcome! // // Mike Markowski, mike.ab3ap@gmail.com // Sep 2023 EXPORT SPIRO() BEGIN LOCAL b,c:=0,m; // Display splash screen. RECT; // Clear screen. PRINT; // Clear terminal. WHILE 1 DO DRAWMENU("Clear","Inner","Outer","Maurer","Color","Exit"); // Soft menu. m:=mousePt; // Wait for choice. b:=softPick(m); CASE IF b==1 THEN RECT; END; // Clear drawing. IF b==2 THEN inner(c); END; // Inner. IF b==3 THEN outer(c); END; // Outer. IF b==4 THEN maurer(c); END; // Maurer. IF b==5 THEN c:=pen(); RECT; END; // Choose pen color. IF b==6 THEN PRINT;RECT;KILL; END; // Exit. END; END; END; inner(c) // Calculate a hypotrochoid where an inner wheel rolls within a fixed, outer // wheel. GUI inputs: // R: radius in pixels of fixed, larger circle. // r: radius in pixels of rolling, smaller circle inside fixed circle. // d: offset in pixels of pen from center of rolling circle. // See https://en.wikipedia.org/wiki/Hypotrochoid BEGIN LOCAL d,f:=1,R,r,t,tInc,tMax; LOCAL x0,y0,x1,y1; LOCAL s,x0p,y0p,x1p,y1p; INPUT({R,r,d}, "Hypotrochoid", {"R=","r=","d="}, {"Radius (pixels) of Fixed Circle", "Radius (pixels) of Inner Rolling Circle", "Pen Distance (pixels) from Rolling Center"}); if R==0 THEN RETURN; END; tMax:=CAS.lcm(R, r); // theta maximum. tInc:=0.1; // theta increment. RECT; // Clear screen. FOR t FROM 0 TO tMax+tInc STEP tInc DO x0:=x1; y0:=y1; // Save previous point. x1:=(R-r)*COS(t) + d*COS(((R-r)/r)*t); y1:=(R-r)*SIN(t) - d*SIN(((R-r)/r)*t); IF f==1 THEN // First time through loop. f:=0; // Never first time again. ELSE s:=(110-1)/R; // Scale to plotting area. x0p:=160+x0*s; x1p:=160+x1*s; // Center figure. y0p:=110+y0*s; y1p:=110+y1*s; LINE_P(x0p,y0p,x1p,y1p,c); END; END; FREEZE; END; maurer(c) // Calculate and draw a Maurer rose. // See https://en.wikipedia.org/wiki/Maurer_rose BEGIN LOCAL f:=1,r,t; LOCAL x0,y0,x1,y1; LOCAL a,p,s,x0p,y0p,x1p,y1p; INPUT({p,s}, "Maurer Rose", {"p=","s="}, {"Number of Petals", "Angular Step"}); RECT; // Clear screen. LOCAL pi=3.1415928, sInc=2*pi/360; FOR t FROM 0 TO 2*pi+sInc STEP sInc DO x0:=x1; y0:=y1; // Save previous point. r:=SIN(p*s*t); x1:=r*COS(s*t); y1:=r*SIN(s*t); IF f==1 THEN // First time through loop. f:=0; // Never first time again. ELSE a:=(110-10); // Scale to plotting area. x0p:=160+x0*a; x1p:=160+x1*a; // Center figure. y0p:=100+y0*a; y1p:=100+y1*a; LINE_P(x0p,y0p,x1p,y1p,c); END; END; FREEZE; END; mousePt() BEGIN LOCAL m,m1; WHILE MOUSE(1)≥0 DO END; REPEAT m := MOUSE; m1 := m(1); UNTIL SIZE(m1)>0; RETURN m1; // {x, y, xOrig, yOrig, type} END; outer(c) // Calculate an epitrochoid where a small wheel rolls outside a fixed // circle. GUI inputs: // R: radius in pixels of fixed circle. // r: radius in pixels of rolling circle outside fixed circle. // d: offset in pixels of pen from center of rolling circle. // See https://en.wikipedia.org/wiki/Epitrochoid BEGIN LOCAL d,f:=1,R,r,t,tInc,tMax; LOCAL x0,y0,x1,y1; LOCAL s,x0p,y0p,x1p,y1p; INPUT({R,r,d}, "Epitrochoid", {"R=","r=","d="}, {"Radius (pixels) of Fixed Circle", "Radius (pixels) of Outer Rolling Circle", "Pen Distance (pixels) from Rolling Center"}); if R==0 THEN RETURN; END; tMax:=CAS.lcm(R, r); // theta maximum. tInc:=0.1; // theta increment. RECT; // Clear screen. FOR t FROM 0 TO tMax+tInc STEP tInc DO x0:=x1; y0:=y1; // Save previous point. x1:=(R+r)*COS(t) - d*COS(((R+r)/r)*t); y1:=(R+r)*SIN(t) - d*SIN(((R+r)/r)*t); IF f==1 THEN // First time through loop. f:=0; // Never first time again. ELSE s:=(110-1)/(R+2*r); // Scale to plotting area. x0p:=160+x0*s; x1p:=160+x1*s; // Center figure. y0p:=110+y0*s; y1p:=110+y1*s; LINE_P(x0p,y0p,x1p,y1p,c); END; END; FREEZE; END; softPick(m) // Determine which soft button was clicked on by user, -1 if none. BEGIN LOCAL x,y; x:=m(1); // X coord of mouse click. y:=m(2); // Y coord. IF y<220 THEN RETURN -1; END; // Not in soft menu region. IF x≤52 THEN RETURN 1; END; IF x≤105 THEN RETURN 2; END; IF x≤158 THEN RETURN 3; END; IF x≤211 THEN RETURN 4; END; IF x≤264 THEN RETURN 5; END; RETURN 6; END; pen() BEGIN LOCAL c; CHOOSE(c, "Choose Color", "Black","Blue","Green","Orange","Red","Yellow"); IF c==1 THEN RETURN RGB(0,0,0); END; IF c==2 THEN RETURN RGB(0,0,255); END; IF c==3 THEN RETURN RGB(0,255,0); END; IF c==4 THEN RETURN RGB(255,127,0); END; IF c==5 THEN RETURN RGB(255,0,0); END; IF c==6 THEN RETURN RGB(255,255,0); END; END;