freeman69@gmx.com
Dragonfly is a vertically scrolling game designed to demonstrate the use of vector mathematics. The aim is to visit the flowers for extra points while remaining aloft by catching smaller insects. Avoid the camouflaged frogs!
Z = Left
X = Right
K = Forward
M = Backward
This game includes:
Evinrude the dragonfly
Rocks (collision detection using point-within-convex-polygon test)
Frogs (turn towards Evinrude)
Flowers that puff pollen particles
Flies
10 MODE 9:OFF
20 *REFRESH OFF
30 *FONT Arial,16
40 COLOUR 12,0,0,64:COLOUR 11,0,216,0:COLOUR 15,160,160,160
50
60 MAXNODES=32
70 DIM node(MAXNODES,3):FOR a=0 TO 26:READ node(a,0),node(a,1):NEXT
80 FOR a=0 TO 4
90 r=RND(20)/5+16:REM Randomly irregular pentagon rock
100 node(a+27,0)=SIN(RAD(a*72))*r
110 node(a+27,1)=COS(RAD(a*72))*r
120 NEXT
130 node(32,0)=node(27,0):node(32,1)=node(27,1)
140 MAXOBJS=200
150 DIM obj{(MAXOBJS) status,type,x,y,angle,sc,r,fc,frog,lash}
160 DIM stack(MAXOBJS):FOR a=0 TO MAXOBJS:stack(a)=a:NEXT
170 stackptr=0
180 DIM ix{(4) x,y}:REM Intersecting lines data block
190
200 REM Lily pads & rocks are created at regular intervals in horizontal bands
210 bandystart=1024+7*20:bandyvel=-4:bandy=bandystart:bandwidth=100
220 probofnew=11:REM controls probability of lily pad or rock appearing
230
240 flycount=0:flytarget=10
250 LICKSIZE=160:REM Frog tongue length
260 evinrude=-1:dead=FALSE:evinergy=100
270 hiscore=0:score=0:lives=0
280
290 COLOUR 128+12
300 REPEAT
310 TIME=0:CLS
320
330 PROCupstream:REM Create objects upstream
340 PROCthink:REM Control objects
350 REM Game Over or first game
360 IF lives=0 OR lives=1 AND dead THEN
370 IF score>hiscore hiscore=score
380 GCOL 0,7
390 MOVE 440,512:VDU 5:PRINT"Press SPACE to play":VDU4
400 IF INKEY(-99) THEN
410 PROCele:evinrude=FNnewobj(6,640,60,0,0,50):dead=FALSE
420 evinax=0:evinay=0:evinvx=0:evinvy=0:evinergy=100
430 level=0:probofnew=11:bandyvel=-4
440 score=0:lives=3
450 ENDIF
460 ENDIF
470 REM Respawn Evinrude
480 IF evinrude=-1 AND lives>0 THEN
490 PROCele:evinrude=FNnewobj(6,640,60,0,0,50):dead=FALSE
500 evinax=0:evinay=0:evinvx=0:evinvy=0:evinergy=100
510 ENDIF
520 REM Energy level display
530 IF evinergy>100 evinergy=100
540 VDU 23,23,3;0;0;0;
550 GCOL 0,0:RECTANGLEFILL 50,512,20,200
560 IF evinergy<40 GCOL 0,1 ELSE GCOL 0,2
570 RECTANGLEFILL 50,512,20,evinergy*2
580 GCOL 0,7:RECTANGLE 50,512,20,200
590
600 VDU5
610 FOR a=0 TO 1
620 IF a=0 GCOL 0,0 ELSE GCOL0,7
630 MOVE 100-a*4,1023-a*4:PRINT"Score: ";score
640 MOVE 500-a*4,1023-a*4:PRINT"Lives: ";lives
650 MOVE 900-a*4,1023-a*4:PRINT"Hi-Score: ";hiscore
660 NEXT
670 VDU4
680 *REFRESH
690 WHILE TIME<4:ENDWHILE
700 *FX21
710 UNTIL FALSE
720 END
730
740 REM Lily 0 offset,10 nodes
750 DATA 0,-18,0,10,0,0,10,10,-10,10
760 DATA 0,-12,14,0,-14,0,-4,21,4,21
770 REM Frog 10 offset,6 nodes
780 DATA -40,40,40,-40,-40,-40,40,40
790 DATA 0,-10,0,15
800 REM Evinrude 16 offset,6 nodes
810 DATA 0,-50,0,20
820 DATA -40,20,40,-20,-40,-20,40,20
830 REM Fly 22 offset,5 nodes
840 DATA 0,-22,0,8
850 DATA 0,0,-15,-15,15,-15
860 REM Rock 27 offset,6 nodes
870
880 DEF PROCprespin(tptr,n,x,y,angle,scale)
890 LOCAL c,d
900 FOR c=0 TO n-1
910 d=c+tptr
920 node(d,2)=x+FNrotx(node(d,0),node(d,1),angle)*scale
930 node(d,3)=y+FNroty(node(d,0),node(d,1),angle)*scale
940 NEXT
950 ENDPROC
960
970 DEF PROClily(x,y,angle,scale)
980 PROCprespin(0,10,x,y,angle,scale)
990 GCOL 0,2:VDU 23,23,3;0;0;0;
1000 CIRCLEFILL x,y,20*scale
1010 GCOL 0,3:CIRCLE x,y,20*scale
1020 GCOL 0,10
1030 LINE node(0,2),node(0,3),node(1,2),node(1,3)
1040 LINE node(2,2),node(2,3),node(3,2),node(3,3)
1050 LINE node(2,2),node(2,3),node(4,2),node(4,3)
1060 LINE node(5,2),node(5,3),node(6,2),node(6,3)
1070 LINE node(5,2),node(5,3),node(7,2),node(7,3)
1080 GCOL 0,12
1090 MOVE node(1,2),node(1,3)
1100 MOVE node(8,2),node(8,3)
1110 PLOT 85,node(9,2),node(9,3)
1120 ENDPROC
1130
1140 DEF PROCfrog(x,y,angle)
1150 PROCprespin(10,6,x,y,angle,1)
1160 VDU 23,23,11;0;0;0;
1170 LINE node(10,2),node(10,3),node(11,2),node(11,3)
1180 LINE node(12,2),node(12,3),node(13,2),node(13,3)
1190 CIRCLEFILL node(14,2),node(14,3),40
1200 CIRCLEFILL node(15,2),node(15,3),35
1210 ENDPROC
1220
1230 DEF PROCfrog2(x,y,angle)
1240 GCOL 0,10:PROCfrog(x+4,y+4,angle)
1250 GCOL 0,11:PROCfrog(x,y,angle)
1260 ENDPROC
1270
1280 DEF PROCevinrude(x,y,angle)
1290 PROCprespin(16,6,x,y,angle,1)
1300 GCOL 0,4:VDU 23,23,9;0;0;0;
1310 LINE node(16,2),node(16,3),node(17,2),node(17,3)
1320 GCOL 0,14
1330 LINE node(18,2),node(18,3),node(19,2),node(19,3)
1340 LINE node(20,2),node(20,3),node(21,2),node(21,3)
1350 GCOL 0,4:VDU 23,23,3;0;0;0;
1360 LINE node(16,2),node(16,3),node(17,2),node(17,3)
1370 ENDPROC
1380
1390 DEF PROCfly(x,y,angle)
1400 PROCprespin(22,5,x,y,angle,1)
1410 GCOL 0,5:VDU 23,23,7;0;0;0;
1420 LINE node(22,2),node(22,3),node(23,2),node(23,3)
1430 GCOL 0,6
1440 LINE node(24,2),node(24,3),node(25,2),node(25,3)
1450 LINE node(24,2),node(24,3),node(26,2),node(26,3)
1460 GCOL 0,5:VDU 23,23,3;0;0;0;
1470 LINE node(22,2),node(22,3),node(23,2),node(23,3)
1480 ENDPROC
1490
1500 DEF PROCrock(x,y,angle,scale)
1510 PROCprespin(27,6,x,y,angle,scale)
1520 GCOL 0,15
1530 MOVE node(27,2),node(27,3):MOVE node(28,2),node(28,3)
1540 PLOT 85,node(31,2),node(31,3)
1550 PLOT 85,node(29,2),node(29,3)
1560 PLOT 85,node(30,2),node(30,3)
1570 GCOL 0,7:VDU 23,23,3;0;0;0;
1580 FOR c=0 TO 4
1590 LINE node(27+c,2),node(27+c,3),node(27+c+1,2),node(27+c+1,3)
1600 NEXT
1610 ENDPROC
1620
1630 DEF PROCflower(x,y,a,col,scale)
1640 LOCAL d,f,g,r,k,c
1650 scale*=20:d=51.4:f=0.66
1660 GCOL 0,col:REM Pollen
1670 RECTANGLEFILL x-scale/2,y-scale/2,scale
1680 FOR g=1 TO 2
1690 GCOL 0,VAL(MID$("15",g,1)):REM Petal colours
1700 r=scale-scale/8*(g-1):k=g*d/2
1710 FOR c=0 TO 6
1720 MOVE x+SIN(RAD(a+c*d-d+k))*r*f,y+COS(RAD(a+c*d-d+k))*r*f
1730 MOVE x+SIN(RAD(a+c*d+d+k))*r*f,y+COS(RAD(a+c*d+d+k))*r*f
1740 PLOT 85,x+SIN(RAD(a+c*d+k))*r,y+COS(RAD(a+c*d+k))*r
1750 NEXT
1760 NEXT
1770 ENDPROC
1780
1790 DEF PROCpollen(x,y)
1800 GCOL 0,7:RECTANGLEFILL x-4,y-4,8
1810 ENDPROC
1820
1830 DEF FNrotx(x,y,angle)
1840 =COS(RAD(angle))*x-SIN(RAD(angle))*y
1850
1860 DEF FNroty(x,y,angle)
1870 =SIN(RAD(angle))*x+COS(RAD(angle))*y
1880
1890 DEF FNnewobj(type,x,y,angle,sc,r)
1900 LOCAL p
1910 IF stackptr>=MAXOBJS THEN
1920 =-1
1930 ELSE
1940 p=stack(stackptr):stackptr+=1
1950 obj{(p)}.status=TRUE
1960 obj{(p)}.type=type
1970 obj{(p)}.x=x
1980 obj{(p)}.y=y
1990 obj{(p)}.angle=angle
2000 obj{(p)}.sc=sc
2010 obj{(p)}.r=r
2020 =p
2030 ENDIF
2040
2050 DEF PROCkillobj(p)
2060 stackptr-=1:stack(stackptr)=p
2070 obj{(p)}.status=FALSE
2080 IF p=evinrude evinrude=-1:lives-=1
2090 IF obj{(p)}.type=5 flycount-=1
2100 ENDPROC
2110
2120 DEF PROCele
2130 LOCAL a
2140 FOR a=0 TO MAXOBJS:stack(a)=a:obj{(a)}.status=FALSE:NEXT
2150 stackptr=0:flycount=0
2160 ENDPROC
2170
2180 DEF PROCthink
2190 LOCAL t,a,x,y,angle,tx,ty,achg,d
2200 IF evinrude>-1 tx=obj{(evinrude)}.x:ty=obj{(evinrude)}.y+10
2210 FOR t=0 TO 3
2220 FOR a=0 TO MAXOBJS
2230 IF obj{(a)}.status THEN
2240 x=obj{(a)}.x:y=obj{(a)}.y:angle=obj{(a)}.angle
2250 CASE t OF
2260 WHEN 0
2270 CASE obj{(a)}.type OF
2280 WHEN 1
2290 REM Rocks (may collide with dragonfly)
2300 obj{(a)}.y+=bandyvel
2310 PROCrock(x,y,angle,obj{(a)}.sc)
2320 IF evinrude>-1 THEN
2330 IF FNinpolygon(tx,ty) dead=TRUE
2340 ENDIF
2350 WHEN 2
2360 REM Lilies
2370 obj{(a)}.y+=bandyvel
2380 PROClily(x,y,angle,obj{(a)}.sc)
2390 ENDCASE
2400 WHEN 1
2410 CASE obj{(a)}.type OF
2420 WHEN 3
2430 REM Flowers may create a puff of pollen when touched
2440 obj{(a)}.y+=bandyvel
2450 IF evinrude>-1 AND obj{(a)}.fc=7 THEN
2460 IF SQR((tx-x)^2+(ty-y)^2)<obj{(a)}.sc*8 THEN
2470 obj{(a)}.fc=4:PROCpuff(x,y,obj{(a)}.sc*8)
2480 ENDIF
2490 ENDIF
2500 PROCflower(x,y,angle,obj{(a)}.fc,obj{(a)}.sc)
2510 WHEN 4
2520 REM Frogs turn towards dragonfly and flick tongue when close
2530 obj{(a)}.y+=bandyvel
2540 IF evinrude>-1 AND NOT dead THEN
2550 achg=FNanglebetween(tx-x,ty-y,-SIN(RAD(angle)),COS(RAD(angle)))
2560 IF ABS(achg)>5 achg=5*SGN(achg)
2570 angle+=achg
2580 obj{(a)}.angle=angle
2590 IF obj{(a)}.sc=0 AND ABS(achg)<3 THEN
2600 d=SQR((tx-x)^2+(ty-y)^2)
2610 IF d<LICKSIZE+40 PROCtongue(a,x,y,angle)
2620 ENDIF
2630 ENDIF
2640 PROCfrog2(x,y,angle)
2650 WHEN 8
2660 REM Frog tongues are processed separately
2670 obj{(a)}.y+=bandyvel
2680 obj{(a)}.fc-=1:REM Tongue countdown
2690 IF obj{(a)}.fc<=0 THEN
2700 IF obj{(obj{(a)}.frog)}.status THEN
2710 obj{(obj{(a)}.frog)}.sc=0:REM Update frog about tongue (in)
2720 ENDIF
2730 IF obj{(a)}.lash AND evinrude>-1 THEN
2740 obj{(evinrude)}.x=x:REM Draw Evinrude into Frog's mouth
2750 obj{(evinrude)}.y=y
2760 tx=obj{(evinrude)}.x:ty=obj{(evinrude)}.y+10
2770 ENDIF
2780 PROCkillobj(a):REM Tongue withdraws
2790 ELSE
2800 VDU 23,23,3;0;0;0;:GCOL 0,1
2810 LINE x,y,x-SIN(RAD(angle))*LICKSIZE,y+COS(RAD(angle))*LICKSIZE
2820 ENDIF
2830 ENDCASE
2840 WHEN 2
2850 CASE obj{(a)}.type OF
2860 WHEN 5
2870 REM Flies (may collide with dragonfly)
2880 x+=SIN(RAD(obj{(a)}.angle))*bandyvel*2
2890 y-=COS(RAD(obj{(a)}.angle))*bandyvel*2
2900 obj{(a)}.x=x
2910 obj{(a)}.y=y
2920 PROCfly(x,y,angle)
2930 IF evinrude>-1 THEN
2940 IF SQR((tx-x)^2+(ty-y)^2)<30 THEN
2950 PROCkillobj(a):evinergy+=10:PROCincreasescore(5)
2960 ENDIF
2970 ENDIF
2980 WHEN 7
2990 REM Pollen
3000 obj{(a)}.y+=bandyvel:angle-=1
3010 IF angle=0 PROCkillobj(a) ELSE obj{(a)}.angle=angle
3020 PROCpollen(x,y)
3030 ENDCASE
3040 WHEN 3
3050 CASE obj{(a)}.type OF
3060 WHEN 6
3070 REM Dragonfly
3080 IF dead obj{(a)}.y+=bandyvel ELSE PROCsteer
3090 x=obj{(a)}.x:y=obj{(a)}.y:angle=obj{(a)}.angle
3100 PROCevinrude(x,y,angle)
3110 ENDCASE
3120 ENDCASE
3130 IF obj{(a)}.y<-obj{(a)}.r PROCkillobj(a)
3140 ENDIF
3150 NEXT
3160 NEXT
3170 ENDPROC
3180
3190 DEF PROCtongue(f,x,y,angle)
3200 LOCAL d,tx,ty
3210 x-=SIN(RAD(angle))*40
3220 y+=COS(RAD(angle))*40
3230 d=FNnewobj(8,x,y,angle,0,LICKSIZE)
3240 IF d>-1 THEN
3250 obj{(f)}.sc=1:REM Frog updated about tongue status (out)
3260 obj{(d)}.fc=5:REM Countdown
3270 obj{(d)}.frog=f:REM Retain link to frog
3280 REM Check for contact with Evinrude
3290 ix{(0)}.x=x
3300 ix{(0)}.y=y
3310 ix{(1)}.x=x-SIN(RAD(angle))*LICKSIZE
3320 ix{(1)}.y=y+COS(RAD(angle))*LICKSIZE
3330 ix{(2)}.x=obj{(evinrude)}.x
3340 ix{(2)}.y=obj{(evinrude)}.y+20
3350 ix{(3)}.x=obj{(evinrude)}.x
3360 ix{(3)}.y=obj{(evinrude)}.y-50
3370 IF FNintersect THEN
3380 obj{(d)}.lash=TRUE:dead=TRUE
3390 ELSE
3400 obj{(d)}.lash=FALSE
3410 ENDIF
3420 ENDIF
3430 ENDPROC
3440
3450 DEF PROCupstream
3460 LOCAL a,d,x,y,scale,r,angle,tx,ty
3470 bandy+=bandyvel
3480 IF bandy<bandystart-bandwidth THEN
3490 bandy+=bandwidth
3500 FOR a=1 TO 10:REM Max objs/attempts per band
3510 IF RND(probofnew)>10 THEN
3520 REM Scale of lily or rock
3530 scale=RND(4)+3:r=scale*20
3540 x=RND(1280-r*2)+r:y=RND(bandwidth)+bandy
3550 angle=RND(360)
3560 IF FNnooverlap(x,y,r) THEN
3570 REM 66:33 chance of lily or rock
3580 IF RND(3)=1 THEN
3590 d=FNnewobj(1,x,y,angle,scale,r)
3600 ELSE
3610 d=FNnewobj(2,x,y,angle,scale,r)
3620 IF d>-1 THEN
3630 IF RND(10)>5 THEN
3640 scale=RND(4)+1:REM Flower
3650 IF scale>obj{(d)}.sc scale=obj{(d)}.sc
3660 r=scale*20
3670 d=FNnewobj(3,x,y,RND(360),scale,r):IF d<>-1 obj{(d)}.fc=7
3680 ELSE
3690 IF RND(2)=1 d=FNnewobj(4,x,y,RND(360),0,60):REM Frog
3700 ENDIF
3710 ENDIF
3720 ENDIF
3730 ENDIF
3740 ENDIF
3750 NEXT
3760 ENDIF
3770 IF flycount<flytarget AND RND(30)=1 THEN
3780 x=RND(1280):y=1050
3790 tx=RND(1000)+140:ty=RND(400)
3800 a=FNanglebetween(tx-x,ty-y,0,1)
3810 d=FNnewobj(5,x,y,a,0,22)
3820 IF d<>-1 flycount+=1
3830 ENDIF
3840 ENDPROC
3850
3860 DEF FNnooverlap(x,y,r)
3870 LOCAL clr,a,xa,ya
3880 clr=TRUE:a=0
3890 REPEAT
3900 IF obj{(a)}.status AND obj{(a)}.type<3 THEN
3910 xa=obj{(a)}.x:ya=obj{(a)}.y
3920 IF SQR((x-xa)^2+(y-ya)^2)<=r+obj{(a)}.sc*20 clr=FALSE
3930 ENDIF
3940 a+=1
3950 UNTIL a>MAXOBJS OR NOT clr
3960 =clr
3970
3980 DEF PROCsteer
3990 LOCAL accel,x,y
4000 IF evinergy>0 evinergy-=0.1
4010 accel=0.5*evinergy/100
4020 IF INKEY(-98) AND evinax>-2 evinax-=accel
4030 IF INKEY(-67) AND evinax<2 evinax+=accel
4040 IF INKEY(-102) AND evinay>-2 evinay-=accel
4050 IF INKEY(-71) AND evinay<2 evinay+=accel
4060 evinvx+=evinax:evinvy+=evinay
4070 evinax*=0.9:evinay*=0.9
4080 evinvx*=0.9:evinvy*=0.9
4090 IF ABS(evinvx)>16 evinvx=16*SGN(evinvx)
4100 IF ABS(evinvy)>16 evinvy=16*SGN(evinvy)
4110 x=obj{(evinrude)}.x+evinvx
4120 y=obj{(evinrude)}.y+evinvy
4130 IF x<0 x=0:evinvx*=-1:evinax*=-1
4140 IF x>1279 x=1279:evinvx*=-1:evinax*=-1
4150 IF y<0 y=0:evinvy*=-1:evinay*=-1
4160 IF y>1023 y=1023:evinvy*=-1:evinay*=-1
4170 obj{(evinrude)}.x=x
4180 obj{(evinrude)}.y=y
4190 ENDPROC
4200
4210 DEF FNinpolygon(x,y)
4220 LOCAL a,inside,xe,ye,xp,yp
4230 a=27:inside=TRUE
4240 REPEAT
4250 xe=x-node(a,2)
4260 ye=y-node(a,3)
4270 xp=node(a,3)-node(a+1,3)
4280 yp=node(a+1,2)-node(a,2)
4290 IF (xe*xp+ye*yp)>0 inside=FALSE
4300 a+=1
4310 UNTIL a>31 OR NOT inside
4320 =inside
4330
4340 DEF FNanglebetween(xa,ya,xb,yb)
4350 LOCAL ma,mb,adotb,ndotb
4360 IF xa=xb AND ya=yb THEN
4370 =0
4380 ELSE
4390 ma=SQR(xa^2+ya^2)
4400 mb=SQR(xb^2+yb^2)
4410 adotb=xa*xb+ya*yb
4420 IF adotb>ma*mb THEN =0
4430 ndotb=xa*yb-ya*xb
4440 =DEG(ACS(adotb/(ma*mb)))*SGN(-ndotb)
4450 ENDIF
4460
4470 DEF PROCpuff(x,y,r)
4480 LOCAL p,a,d,angle,h,xo,yo
4490 p=3*r
4500 FOR a=1 TO p
4510 angle=RND(360):h=RND(r)*2
4520 xo=SIN(RAD(angle))*h
4530 yo=COS(RAD(angle))*h
4540 d=FNnewobj(7,x+xo,y+yo,5+RND(10),0,8)
4550 NEXT
4560 PROCincreasescore(p)
4570 ENDPROC
4580
4590 DEF FNintersect
4600 LOCAL xa,ya,xb,yb,xg,yg,xn,yn,ndotb,ndota,t,xd,yd
4610 REM Calculate vectors alpha, beta & gamma
4620 xa=ix{(1)}.x-ix{(0)}.x:REM alpha
4630 ya=ix{(1)}.y-ix{(0)}.y
4640 xb=ix{(2)}.x-ix{(0)}.x:REM beta
4650 yb=ix{(2)}.y-ix{(0)}.y
4660 xg=ix{(3)}.x-ix{(2)}.x:REM gamma
4670 yg=ix{(3)}.y-ix{(2)}.y
4680 xn=-yg:yn=xg:REM Normal of gamma
4690 ndotb=xn*xb+yn*yb
4700 REM Lines parallel (never intersect)?
4710 IF ndotb=0 THEN =FALSE
4720 ndota=xn*xa+yn*ya
4730 t=ndotb/ndota
4740 REM Intersect is within first line segment?
4750 IF t<0 OR t>1 THEN =FALSE
4760 REM Calculate intersect point
4770 ix{(4)}.x=ix{(0)}.x+t*xa
4780 ix{(4)}.y=ix{(0)}.y+t*ya
4790 REM Intersect is within second line segment?
4800 xd=ix{(4)}.x-ix{(2)}.x:REM delta
4810 yd=ix{(4)}.y-ix{(2)}.y
4820 IF xd*xg+yd*yg<0 OR (xd^2+yd^2)>(xg^2+yg^2) THEN =FALSE
4830 =TRUE
4840
4850 DEF PROCincreasescore(v)
4860 score+=v
4870 IF score>level*1000 THEN
4880 level+=1:probofnew+=1
4890 IF level<10 AND (level DIV 2)=0 THEN
4900 bandyyvel-=2
4910 ENDIF
4920 ENDIF
4930 ENDPROC
Dragonfly: Code explained...
60 MAXNODES=32
70 DIM node(MAXNODES,3):FOR a=0 TO 26:READ node(a,0),node(a,1):NEXT
In previous programs we've stored the details of line-drawn graphics in an array called 'shape'. This program differs slightly in two respects.
a) Specific routines draw specific items (e.g. frogs, lily pads), but refer to the same array.
b) A separate routine rotates each 'node' (corner/point) before drawing, and stores the result in the same array in columns 2 and 3.
80 FOR a=0 TO 4
90 r=RND(20)/5+16:REM Randomly irregular pentagon rock
100 node(a+27,0)=SIN(RAD(a*72))*r
110 node(a+27,1)=COS(RAD(a*72))*r
120 NEXT
130 node(32,0)=node(27,0):node(32,1)=node(27,1)
Pentagonal rocks are drawn using three triangles and an outline. They consist of an irregular pentagon, defined above. Although slightly irregular in shape (to look more natural), the pentagon must not have any concave sides because we're going to use point-within-polygon collision testing.
For convenience, this pentagon has 6 corners, the sixth being identical to the first. (Line 130)
140 MAXOBJS=200
150 DIM obj{(MAXOBJS) status,type,x,y,angle,sc,r,fc,frog,lash}
All objects have a 'type', 'x', 'y', 'angle', 'sc' scale and 'r' max radius. The max radius is used to check that new objects do not overlap existing objects. It's also used to test for when an object moves completely beyond the bottom border of the window and therefore needs to be destroyed.
'fc' is the flower colour/status.
Frogs have a fixed scale and 'sc' is used to indicate if the tongue is sticking out.
'frog' and 'lash' are specific to frog tongues, which are separate objects (see below).
180 DIM ix{(4) x,y}:REM Intersecting lines data block
The 'ix' array relates directly to FNintersect (the line segment intersect test).
210 bandystart=1024+7*20:bandyvel=-4:bandy=bandystart:bandwidth=100
220 probofnew=11:REM controls probability of lily pad or rock appearing
All objects begin life above the top border of the window. Objects are added in bands at regular intervals, dependent on the speed of the game.
240 flycount=0:flytarget=10
250 LICKSIZE=160:REM Frog tongue length
260 evinrude=-1:dead=FALSE:evinergy=100
'flycount' and 'flytarget' control the number of active flies.
'LICKSIZE' defines the length of a frog's tongue.
'evinrude' contains either -1 or the row number allocated to the player's dragonfly in the 'obj' array.
'dead' indicates if Evinrude is dead (TRUE or FALSE).
'evinergy' holds the energy level of the dragonfly. Low energy makes Evinrude sluggish.
420 evinax=0:evinay=0:evinvx=0:evinvy=0:evinergy=100
Evinrude's controls are based on acceleration. The player accelerates Evinrude in four different directions. Both acceleration and velocity have set limits, and velocity diminishes over time.
740 REM Lily 0 offset,10 nodes
750 DATA 0,-18,0,10,0,0,10,10,-10,10
760 DATA 0,-12,14,0,-14,0,-4,21,4,21
770 REM Frog 10 offset,6 nodes
780 DATA -40,40,40,-40,-40,-40,40,40
790 DATA 0,-10,0,15
800 REM Evinrude 16 offset,6 nodes
810 DATA 0,-50,0,20
820 DATA -40,20,40,-20,-40,-20,40,20
830 REM Fly 22 offset,5 nodes
840 DATA 0,-22,0,8
850 DATA 0,0,-15,-15,15,-15
860 REM Rock 27 offset,6 nodes
As mentioned above, each object is defined by a set of points (nodes) relative to the object's centre, but how each point is used varies with each object. E.g. a node may be the start or end of a line, the corner of a triangle or the centre of a circle.
880 DEF PROCprespin(tptr,n,x,y,angle,scale)
This routine is called by every routine that draws a complex object that can be rotated. It takes the template nodes and rotates them about the object centre, by a given angle, and stores the result in columns 2 and 3 of the template ('node') array.
970 DEF PROClily(x,y,angle,scale)
1140 DEF PROCfrog(x,y,angle)
1230 DEF PROCfrog2(x,y,angle)
1280 DEF PROCevinrude(x,y,angle)
1390 DEF PROCfly(x,y,angle)
1500 DEF PROCrock(x,y,angle,scale)
1630 DEF PROCflower(x,y,a,col,scale)
1790 DEF PROCpollen(x,y)
All of the above routines draw a specific graphic, with the exception of PROCfrog2 which calls PROCfrog twice to create a highlighted frog.
1890 DEF FNnewobj(type,x,y,angle,sc,r)
2050 DEF PROCkillobj(p)
2120 DEF PROCele
Versions of the above have appeared in other programs and control the addition and removal of objects to/from the 'obj' array.
2180 DEF PROCthink
2190 LOCAL t,a,x,y,angle,tx,ty,achg,d
2200 IF evinrude>-1 tx=obj{(evinrude)}.x:ty=obj{(evinrude)}.y+10
2210 FOR t=0 TO 3
2220 FOR a=0 TO MAXOBJS
2230 IF obj{(a)}.status THEN
2240 x=obj{(a)}.x:y=obj{(a)}.y:angle=obj{(a)}.angle
2250 CASE t OF
2260 WHEN 0
This routine controls the behaviour of all objects. Our list of objects is processed in 4 passes, each pass relates the the graphical layer e.g. lily pads need to be drawn before flowers and frogs. Evinrude is drawn last.
2270 CASE obj{(a)}.type OF
2280 WHEN 1
2290 REM Rocks (may collide with dragonfly)
2300 obj{(a)}.y+=bandyvel
2310 PROCrock(x,y,angle,obj{(a)}.sc)
2320 IF evinrude>-1 THEN
2330 IF FNinpolygon(tx,ty) dead=TRUE
2340 ENDIF
When processing rocks, we simply move them down the window and test for collision with Evinrude.
2350 WHEN 2
2360 REM Lilies
2370 obj{(a)}.y+=bandyvel
2380 PROClily(x,y,angle,obj{(a)}.sc)
Lilies are non-interactive and just need to be moved downstream.
2420 WHEN 3
2430 REM Flowers may create a puff of pollen when touched
2440 obj{(a)}.y+=bandyvel
2450 IF evinrude>-1 AND obj{(a)}.fc=7 THEN
2460 IF SQR((tx-x)^2+(ty-y)^2)<obj{(a)}.sc*8 THEN
2470 obj{(a)}.fc=4:PROCpuff(x,y,obj{(a)}.sc*8)
2480 ENDIF
2490 ENDIF
2500 PROCflower(x,y,angle,obj{(a)}.fc,obj{(a)}.sc)
Flowers create a puff of pollen when visited by Evinrude, changing the flower's colour scheme.
2510 WHEN 4
2520 REM Frogs turn towards dragonfly and flick tongue when close
2530 obj{(a)}.y+=bandyvel
2540 IF evinrude>-1 AND NOT dead THEN
2550 achg=FNanglebetween(tx-x,ty-y,-SIN(RAD(angle)),COS(RAD(angle)))
2560 IF ABS(achg)>5 achg=5*SGN(achg)
2570 angle+=achg
2580 obj{(a)}.angle=angle
2590 IF obj{(a)}.sc=0 AND ABS(achg)<3 THEN
2600 d=SQR((tx-x)^2+(ty-y)^2)
2610 IF d<LICKSIZE+40 PROCtongue(a,x,y,angle)
2620 ENDIF
2630 ENDIF
2640 PROCfrog2(x,y,angle)
Frogs always turn towards Evinrude. If Evinrude is within range and within a given angle (and the frog's tongue isn't already sticking out) then a fixed length tongue is created as a separate, temporary object.
2650 WHEN 8
2660 REM Frog tongues are processed separately
2670 obj{(a)}.y+=bandyvel
2680 obj{(a)}.fc-=1:REM Tongue countdown
2690 IF obj{(a)}.fc<=0 THEN
2700 IF obj{(obj{(a)}.frog)}.status THEN
2710 obj{(obj{(a)}.frog)}.sc=0:REM Update frog about tongue (in)
2720 ENDIF
2730 IF obj{(a)}.lash AND evinrude>-1 THEN
2740 obj{(evinrude)}.x=x:REM Draw Evinrude into Frog's mouth
2750 obj{(evinrude)}.y=y
2760 tx=obj{(evinrude)}.x:ty=obj{(evinrude)}.y+10
2770 ENDIF
2780 PROCkillobj(a):REM Tongue withdraws
2790 ELSE
2800 VDU 23,23,3;0;0;0;:GCOL 0,1
2810 LINE x,y,x-SIN(RAD(angle))*LICKSIZE,y+COS(RAD(angle))*LICKSIZE
2820 ENDIF
Frog's tongues are controlled independently of the 'parent' frog, but maintain a link to the frog. The tongue sticks out for a limited period of time. When it disappears, and if Evinrude has been struck, then (a dead) Evinrude is repositioned at the base of the tongue. The frog's tongue status 'sc' is changed so the frog can stick its tongue out again, if necessary.
2860 WHEN 5
2870 REM Flies (may collide with dragonfly)
2880 x+=SIN(RAD(obj{(a)}.angle))*bandyvel*2
2890 y-=COS(RAD(obj{(a)}.angle))*bandyvel*2
2900 obj{(a)}.x=x
2910 obj{(a)}.y=y
2920 PROCfly(x,y,angle)
2930 IF evinrude>-1 THEN
2940 IF SQR((tx-x)^2+(ty-y)^2)<30 THEN
2950 PROCkillobj(a):evinergy+=10:PROCincreasescore(5)
2960 ENDIF
2970 ENDIF
Flies are initially located above the top border of the window, but unlike other objects they have unique x & y velocity vectors that move them downstream at an angle. We test each for a collision with Evinrude using a point and radius system.
2980 WHEN 7
2990 REM Pollen
3000 obj{(a)}.y+=bandyvel:angle-=1
3010 IF angle=0 PROCkillobj(a) ELSE obj{(a)}.angle=angle
3020 PROCpollen(x,y)
Pollen must also move/scroll down the window. Particles exist for a limited, random period. The lifespan of a pollen particle is held in the 'obj' array variable 'angle'.
3060 WHEN 6
3070 REM Dragonfly
3080 IF dead obj{(a)}.y+=bandyvel ELSE PROCsteer
3090 x=obj{(a)}.x:y=obj{(a)}.y:angle=obj{(a)}.angle
3100 PROCevinrude(x,y,angle)
Evinrude is steered by the player, unless dead, in which case the dragonfly scrolls downstream with the other objects.
3130 IF obj{(a)}.y<-obj{(a)}.r PROCkillobj(a)
All objects are destroyed when they pass completely beyond the bottom border of the window.
3190 DEF PROCtongue(f,x,y,angle)
When a frog flicks its tongue, a separate object is created, but only if there is room in the 'obj' array.
The frog's tongue status is updated to indicate a sticking-out tongue.
The tongue object stores the 'obj' array row of the frog for future reference.
The life of the tongue (how long it remains sticking out) is held in 'fc'.
The routine then calls FNintersect to determine if the line segment of the tongue intersects the line segment of Evinrude's body. The result is stored in 'lash'.
3450 DEF PROCupstream
3460 LOCAL a,d,x,y,scale,r,angle,tx,ty
3470 bandy+=bandyvel
3480 IF bandy<bandystart-bandwidth THEN
3490 bandy+=bandwidth
At specific intervals, new objects are added to the 'obj' array.
3500 FOR a=1 TO 10:REM Max objs/attempts per band
3510 IF RND(probofnew)>10 THEN
'probofnew' controls the probability of an object appearing.
3520 REM Scale of lily or rock
3530 scale=RND(4)+3:r=scale*20
3540 x=RND(1280-r*2)+r:y=RND(bandwidth)+bandy
3550 angle=RND(360)
The new object (rock or lily) will be placed randomly within the current band, without overlapping the sides of the window.
3560 IF FNnooverlap(x,y,r) THEN
Nor will a new object be created if it overlaps an existing object.
3570 REM 66:33 chance of lily or rock
3580 IF RND(3)=1 THEN
3590 d=FNnewobj(1,x,y,angle,scale,r)
If a rock is created then no further action is required.
3600 ELSE
3610 d=FNnewobj(2,x,y,angle,scale,r)
3620 IF d>-1 THEN
3630 IF RND(10)>5 THEN
3640 scale=RND(4)+1:REM Flower
3650 IF scale>obj{(d)}.sc scale=obj{(d)}.sc
3660 r=scale*20
3670 d=FNnewobj(3,x,y,RND(360),scale,r):IF d<>-1 obj{(d)}.fc=7
3680 ELSE
3690 IF RND(2)=1 d=FNnewobj(4,x,y,RND(360),0,60):REM Frog
3700 ENDIF
3710 ENDIF
If a lily is created then we must decide if it's empty, contains a flower or harbours a frog.
3770 IF flycount<flytarget AND RND(30)=1 THEN
3780 x=RND(1280):y=1050
3790 tx=RND(1000)+140:ty=RND(400)
3800 a=FNanglebetween(tx-x,ty-y,0,1)
3810 d=FNnewobj(5,x,y,a,0,22)
3820 IF d<>-1 flycount+=1
Flies are also created by the same routine. The flight path of a fly takes it through the bottom half of the window.
3860 DEF FNnooverlap(x,y,r)
This routine returns FALSE if any other object exists that overlaps the circle centred on x,y with radius 'r'.
3980 DEF PROCsteer
As described above, Evinrude's controls are based on acceleration along the x-axis and the y-axis.
4210 DEF FNinpolygon(x,y)
4220 LOCAL a,inside,xe,ye,xp,yp
4230 a=27:inside=TRUE
4240 REPEAT
4250 xe=x-node(a,2)
4260 ye=y-node(a,3)
4270 xp=node(a,3)-node(a+1,3)
4280 yp=node(a+1,2)-node(a,2)
4290 IF (xe*xp+ye*yp)>0 inside=FALSE
4300 a+=1
4310 UNTIL a>31 OR NOT inside
4320 =inside
This routine is specific to the five-sided rock template held in the 'node' array. The Vector Demo shows how we can determine whether a point lies on the left or right hand side of a line (depending on the direction of the line/vector). The pentagon is defined (and drawn) clockwise using nodes a to b, b to c, c to d, d to e, and e to f (where f=a). If a point lies on the right hand side of all five lines then the point lies inside the pentagon and the routine returns TRUE.
4340 DEF FNanglebetween(xa,ya,xb,yb)
4350 LOCAL ma,mb,adotb,ndotb
4360 IF xa=xb AND ya=yb THEN
4370 =0
4380 ELSE
4390 ma=SQR(xa^2+ya^2)
4400 mb=SQR(xb^2+yb^2)
4410 adotb=xa*xb+ya*yb
4420 IF adotb>ma*mb THEN =0
4430 ndotb=xa*yb-ya*xb
4440 =DEG(ACS(adotb/(ma*mb)))*SGN(-ndotb)
4450 ENDIF
This routine makes use of the formula for calculating the angle between two vectors (including whether a point is on the left or right of a line) to return a value between -180 and +180 degrees.
4470 DEF PROCpuff(x,y,r)
Creates a number of short-lived pollen particles. The number of particles depends on flower size.
4590 DEF FNintersect
This function was lifted directly from the Intersecting Lines program and is used to determine whether a frog's tongue intersects the line of the dragonfly's body.
4850 DEF PROCincreasescore(v)
4860 score+=v
4870 IF score>level*1000 THEN
4880 level+=1:probofnew+=1
4890 IF level<10 AND (level DIV 2)=0 THEN
4900 bandyyvel-=2
4910 ENDIF
4920 ENDIF
4930 ENDPROC
As the score increases, the probability of lilies and rocks appearing increases (similar to the method used to control the appearance of traffic in HopSquash).