Programming by Example

 

A BB4W Compendium

freeman69@gmx.com

IDIC BBC_Owl2 M&P

Inky, Pinky, Stinky & Clyde

         Z = Left

         X = Right

         K = Up

         M = Down

 

    This game includes:

 

         Complex map/arena

         Animated pac-man

         4 patrolling ghosts with variable behaviours

 

  10 MODE 9:OFF

  20 *REFRESH OFF

  30 DIM maze$(20):FOR a=0 TO 20:READ maze$(a):NEXT

  40 wall$="v<L^|rt>u-InjT+":gatex=0:gatey=0:homex=0:homey=0

  50 DIM map(26,20),way(4,9,1):PROCmakemap

  60 DIM shapes(13,1):FOR a=0 TO 13:READ shapes(a,0),shapes(a,1):NEXT

  70 DIM td(1,1):FOR a=0 TO 0:READ td(a,0),td(a,1):NEXT

  80 DIM obj{(4) x,y,d,sts,pm,gc,tx,ty,cdn,ppcd,dcd}

  90 DIM list{(4) nxt,d,dist}:REM Path decision-making

 100 VDU 23,224,0,0,0,24,24,0,0,0,0:REM Dot

 110 VDU 23,225,0,24,52,106,118,60,24,0:REM Power pill

 120

 130 ORIGIN 640,512-24

 140 GCOL 0,4:VDU 23,23,11;0;0;0;:PROCdrawmaze

 150 GCOL 0,0:VDU 23,23,5;0;0;0;:PROCdrawmaze

 160 ORIGIN 640-13*48,512-24-10*48

 170

 180 hiscore=0

 190 REPEAT

 200   *FONT Arial 12,B

 210   GCOL 0,0:RECTANGLEFILL -16,976,1279,48

 220   PRINTTAB(30,0);"Press SPACE to play"

 230   PRINTTAB(58,0);"Hi-score: ";hiscore

 240   *REFRESH

 250   REPEATUNTILGET=32

 260   GCOL 0,0:RECTANGLEFILL -16,976,1279,48

 270   newscreen=TRUE:score=0:lives=3:level=1

 280   REPEAT

 290     IF level<5 cspeed=12 ELSE cspeed=16

 300     IF newscreen PROCmakemap:newscreen=FALSE

 310     dead=FALSE:PROCdrawdots:flipflop=0

 320     PROCstartingblocks:PROCdrawall:PROCscoreboard:PROCready

 330     REPEAT

 340       TIME=0:flipflop EOR=1

 350      

 360       PROCdeleteall

 370      

 380       REM Pac-man controls

 390       x=obj{(0)}.x:y=obj{(0)}.y:d=obj{(0)}.d:newd=-1

 400       IF  (x MOD 48)=0 xa=TRUE ELSE xa=FALSE

 410       IF  (y MOD 48)=0 ya=TRUE ELSE ya=FALSE

 420       IF xa AND ya THEN

 430         xb=x DIV 48:yb=y DIV 48

 440         IF map(xb,yb)=1 map(xb,yb)=0:score+=10:tdots-=1

 450         IF map(xb,yb)=2 map(xb,yb)=0:PROCscareghosts:tdots-=1

 460       ENDIF

 470       IF INKEY(-98) AND ya AND newd=-1 AND FNwayclear(x,y,3,12) newd=3

 480       IF INKEY(-67) AND ya AND newd=-1 AND FNwayclear(x,y,1,12) newd=1

 490       IF INKEY(-102) AND xa AND newd=-1 AND FNwayclear(x,y,2,12) newd=2

 500       IF INKEY(-71) AND xa AND newd=-1 AND FNwayclear(x,y,0,12) newd=0

 510       IF newd=-1 AND d<>-1 AND FNwayclear(x,y,d,cspeed) newd=d

 520       IF newd>-1 THEN

 530         PROCupdateobjpos(0,newd,cspeed)

 540         obj{(0)}.pm+=15:IF obj{(0)}.pm>30 obj{(0)}.pm=-30:REM Move mouth

 550       ENDIF

 560      

 570       PROCghost_AI

 580      

 590       REM Collision detection (dead or chomped ghost)

 600       tx=obj{(0)}.x:ty=obj{(0)}.y

 610       FOR a=1 TO 4

 620         IF obj{(a)}.sts=2 THEN

 630           dist=SQR((tx-obj{(a)}.x)^2+(ty-obj{(a)}.y)^2)

 640           IF dist<24 THEN

 650             IF obj{(a)}.ppcd=0 THEN

 660               dead=TRUE

 670             ELSE

 680               obj{(a)}.ppcd=0

 690               obj{(a)}.sts=3

 700               obj{(a)}.tx=gatex

 710               obj{(a)}.ty=gatey

 720               score+=400

 730             ENDIF

 740           ENDIF

 750         ENDIF

 760       NEXT

 770      

 780       REM Redraw background (dots & power pills)

 790       FOR a=0 TO 4:PROCredrawbackground(obj{(a)}.x,obj{(a)}.y):NEXT

 800       REM Draw Pac-man, ghosts, gate and scoreboard

 810       PROCdrawall:PROCscoreboard

 820      

 830       IF tdots<=0 newscreen=TRUE

 840       *REFRESH

 850       *FX21

 860       WHILE TIME<4:ENDWHILE

 870     UNTIL newscreen OR dead

 880     IF newscreen WAIT 100:level+=1:PROCdeleteall

 890     IF dead PROCfuneral:lives-=1

 900   UNTIL lives=0

 910   IF score>hiscore hiscore=score

 920 UNTIL FALSE

 930 END

 940

 950 DEF PROCready

 960 *FONT Arial,16

 970 VDU 5:GCOL 0,7:MOVE 560,652:PRINT"Ready?"

 980 *REFRESH

 990 WAIT 200:GCOL 0,0:RECTANGLEFILL 560,652,160,-64:VDU 4

1000 ENDPROC

1010

1020 DEF PROCscoreboard

1030 *FONT Arial,12,B

1040 PRINTTAB(0,0);"Score: ";score TAB(20,0);"Lives: ";lives

1050 PRINTTAB(30,0);"Level: ";level TAB(58,0);"Hi-score: ";hiscore

1060 ENDPROC

1070

1080 DEF PROCstartingblocks

1090 LOCAL a

1100 obj{(0)}.x=13*48

1110 obj{(0)}.y=5*48

1120 obj{(0)}.d=3

1130 obj{(0)}.sts=0

1140 obj{(0)}.pm=30

1150 FOR a=1 TO 4

1160   obj{(a)}.x=(a*2+7)*48

1170   obj{(a)}.y=9*48

1180   obj{(a)}.d=0:obj{(a)}.sts=0

1190   obj{(a)}.gc=VAL(MID$("1256",a,1))

1200   obj{(a)}.tx=obj{(a)}.x DIV 48

1210   obj{(a)}.ty=obj{(a)}.y DIV 48

1220   obj{(a)}.cdn=(a-1)*3:obj{(a)}.ppcd=0

1230   obj{(a)}.dcd=FALSE

1240 NEXT

1250 obj{(1)}.x=13*48:obj{(1)}.y=11*48

1260 ENDPROC

1270

1280 DEF PROCfuneral

1290 LOCAL a,c,d

1300 REPEAT

1310   GCOL 0,0:RECTANGLEFILL obj{(0)}.x-34,obj{(0)}.y-36,70

1320   obj{(0)}.pm+=15

1330   PROCdrawpacman(obj{(0)}.x,obj{(0)}.y,ABS(obj{(0)}.pm),obj{(0)}.d)

1340   *REFRESH

1350   WAIT 4

1360 UNTIL obj{(0)}.pm>=180

1370 GCOL 0,0:RECTANGLEFILL obj{(0)}.x-34,obj{(0)}.y-36,70:GCOL 0,3

1380 FOR a=0 TO 360 STEP 45

1390   c=SIN(RAD(a))*14:d=COS(RAD(a))*14

1400   MOVE obj{(0)}.x+c,obj{(0)}.y+d:PLOT 1,c,d

1410 NEXT

1420 *REFRESH

1430 WAIT 200:PROCdeleteall

1440 ENDPROC

1450

1460 DEF PROCdeleteall

1470 LOCAL a

1480 GCOL 0,0

1490 FOR a=0 TO 4

1500   RECTANGLEFILL obj{(a)}.x-34,obj{(a)}.y-36,70

1510 NEXT

1520 ENDPROC

1530

1540 DEF PROCscareghosts

1550 LOCAL a,b

1560 b=22:IF level<5 b=32-level*2

1570 FOR a=1 TO 4

1580   IF obj{(a)}.sts<3 obj{(a)}.ppcd=b

1590 NEXT

1600 ENDPROC

1610

1620 DEF PROCghost_AI

1630 LOCAL a,x,y,tx,ty,reachedtarget,ghostbhv,ghostspd

1640 FOR a=1 TO 4

1650   x=obj{(a)}.x:y=obj{(a)}.y

1660   tx=obj{(a)}.tx:ty=obj{(a)}.ty

1670   reachedtarget=FALSE:ghostspd=cspeed

1680   IF (x MOD 48)=0 AND (y MOD 48)=0 AND NOT obj{(a)}.dcd THEN

1690     x DIV=48:y DIV=48

1700     IF x=tx AND y=ty reachedtarget=TRUE

1710     CASE obj{(a)}.sts OF

1720       WHEN 0 : REM Wandering within home area

1730         IF reachedtarget PROCrandomhome(a,x,y)

1740         obj{(a)}.cdn-=1:REM Time to find exit?

1750         IF obj{(a)}.cdn<=0 THEN

1760           obj{(a)}.tx=gatex

1770           obj{(a)}.ty=gatey

1780           obj{(a)}.sts=1

1790         ENDIF

1800       WHEN 1 : REM Finding exit

1810         IF reachedtarget THEN

1820           obj{(a)}.tx=way(a,0,0):REM Initiate patrol

1830           obj{(a)}.ty=way(a,0,1)

1840           obj{(a)}.sts=2

1850           way(a,9,1)=0:REM First waypoint

1860         ENDIF

1870       WHEN 2 : REM On patrol

1880         IF reachedtarget THEN

1890           way(a,9,1)+=1:IF way(a,9,1)>=way(a,9,0) way(a,9,1)=0

1900           obj{(a)}.tx=way(a,way(a,9,1),0)

1910           obj{(a)}.ty=way(a,way(a,9,1),1)

1920         ENDIF

1930       WHEN 3 : REM Running for gate

1940         IF reachedtarget THEN

1950           obj{(a)}.tx=homex

1960           obj{(a)}.ty=homey

1970           obj{(a)}.sts=4

1980         ENDIF

1990       WHEN 4 : REM Going towards 'H'

2000         IF reachedtarget THEN

2010           obj{(a)}.cdn=RND(4)

2020           obj{(a)}.sts=0

2030         ENDIF

2040     ENDCASE

2050     REM Default target & behaviour

2060     tx=obj{(a)}.tx:ty=obj{(a)}.ty:ghostbhv=1

2070     REM Override patrolling with character choice?

2080     IF obj{(a)}.sts=2 THEN

2090       IF level<5 prob=110-level*20 ELSE prob=10

2100       IF RND(100)>prob THEN

2110         ghostbhv=VAL(MID$("111110112110",(a-1)*3+RND(3),1))

2120         tx=obj{(0)}.x DIV 48:ty=obj{(0)}.y DIV 48

2130       ENDIF

2140     ENDIF

2150     REM Override behaviour with fear if power pill active

2160     IF obj{(a)}.ppcd>0 THEN

2170       obj{(a)}.ppcd-=1

2180       IF obj{(a)}.ppcd>0 AND obj{(a)}.sts=2 THEN

2190         tx=obj{(0)}.x DIV 48:ty=obj{(0)}.y DIV 48:ghostbhv=2

2200       ENDIF

2210     ENDIF

2220     D$=FNpaths(x,y,tx,ty,obj{(a)}.d):obj{(a)}.dcd=TRUE

2230     IF LEN(D$)>1 THEN

2240       CASE ghostbhv OF

2250         WHEN 0 : obj{(a)}.d=VAL(MID$(D$,RND(LEN(D$)),1)):REM RND

2260         WHEN 1 : obj{(a)}.d=VAL(LEFT$(D$,1)):REM Go to

2270         WHEN 2 : obj{(a)}.d=VAL(RIGHT$(D$,1)):REM Run away!

2280       ENDCASE

2290     ELSE

2300       obj{(a)}.d=VAL(D$)

2310     ENDIF

2320   ELSE

2330     obj{(a)}.dcd=FALSE

2340   ENDIF

2350   IF flipflop=1 THEN

2360     IF obj{(a)}.ppcd>0 AND obj{(a)}.sts<3 ghostspd=0

2370     IF map(obj{(a)}.x DIV 48,obj{(a)}.y DIV 48)=8 ghostspd=0

2380   ENDIF

2390   PROCupdateobjpos(a,obj{(a)}.d,ghostspd)

2400 NEXT

2410 ENDPROC

2420

2430 DEF PROCrandomhome(a,x,y)

2440 LOCAL tx,ty:REM Find random destination for ghost within home area

2450 REPEAT:tx=RND(5)+10:ty=RND(3)+8:UNTIL tx<>x OR ty<>y

2460 obj{(a)}.tx=tx:obj{(a)}.ty=ty

2470 ENDPROC

2480

2490 DEF FNpaths(x,y,tx,ty,cd)

2500 LOCAL D$,d,a,b,okay,dist,free,p,lastp

2510 D$="":free=1:list{(0)}.nxt=-1:list{(0)}.dist=-1:REM Linked list anchor

2520 FOR d=0 TO 3

2530   okay=FALSE

2540   REM Ignore reverse as an option

2550   IF d<>(cd EOR 2) THEN

2560     CASE d OF

2570       WHEN 0 : a=x:b=y+1

2580       WHEN 1 : a=x+1:b=y

2590       WHEN 2 : a=x:b=y-1

2600       WHEN 3 : a=x-1:b=y

2610     ENDCASE

2620     REM 'Ghost gate' is a viable direction when a target

2630     IF a=tx AND b=ty AND tx=gatex AND ty=gatey THEN

2640       okay=TRUE

2650     ELSE

2660       IF a>=0 AND a<=26 THEN IF map(a,b)<>4 okay=TRUE

2670     ENDIF

2680     IF okay THEN

2690       REM Creating a linked list in the order of a move's distance to target

2700       dist=SQR((a-tx)^2+(b-ty)^2)

2710       p=list{(0)}.nxt:lastp=0

2720       WHILE okay AND p>-1

2730         IF dist<list{(p)}.dist okay=FALSE ELSE lastp=p:p=list{(p)}.nxt

2740       ENDWHILE

2750       list{(free)}.nxt=list{(lastp)}.nxt

2760       list{(free)}.d=d:list{(free)}.dist=dist

2770       list{(lastp)}.nxt=free

2780       free+=1

2790     ENDIF

2800   ENDIF

2810 NEXT

2820 REM Convert the linked list into a string of (valid) directions

2830 p=list{(0)}.nxt

2840 WHILE p>-1

2850   D$+=STR$(list{(p)}.d):p=list{(p)}.nxt

2860 ENDWHILE

2870 IF D$="" D$=STR$(cd EOR 2):REM Failsafe - reverse

2880 =D$

2890

2900 DEF PROCredrawbackground(x,y)

2910 LOCAL a,b,c,d,t

2920 VDU 5:GCOL 0,2

2930 FOR b=-1 TO 1:FOR a=-1 TO 1

2940     c=(x DIV 48)+a:d=(y DIV 48)+b:t=0

2950     IF c>=0 AND c<27 AND d>=0 AND d<21 t=(map(c,d) AND 3)

2960     IF t<>0 MOVE c*48-16,d*48+16:VDU 223+t

2970   NEXT:NEXT:VDU 4

2980 ENDPROC

2990

3000 DEF PROCupdateobjpos(a,newd,spd)

3010 CASE newd OF

3020   WHEN 0 : obj{(a)}.y+=spd

3030   WHEN 1 : obj{(a)}.x+=spd

3040   WHEN 2 : obj{(a)}.y-=spd

3050   WHEN 3 : obj{(a)}.x-=spd

3060 ENDCASE

3070 obj{(a)}.d=newd

3080 REM Corridor wrap-around

3090 IF obj{(a)}.x<=0 AND obj{(a)}.d=3 obj{(a)}.x+=26*48

3100 IF obj{(a)}.x>=26*48 AND obj{(a)}.d=1 obj{(a)}.x-=26*48

3110 ENDPROC

3120

3130 DEF FNwayclear(x,y,d,spd)

3140 LOCAL isclear

3150 isclear=TRUE

3160 IF d<>-1 THEN

3170   CASE d OF

3180     WHEN 0 : y=y+46+spd

3190     WHEN 1 : x=x+46+spd

3200     WHEN 2 : y=y-spd

3210     WHEN 3 : x=x-spd

3220   ENDCASE

3230   x DIV=48:y DIV=48

3240   IF x>=0 AND x<27 AND y>=0 AND y<21 THEN

3250     IF map(x,y)=4 isclear=FALSE

3260   ENDIF

3270 ENDIF

3280 =isclear

3290

3300 DEF PROCdrawall

3310 LOCAL a,col

3320 PROCdrawpacman(obj{(0)}.x,obj{(0)}.y,ABS(obj{(0)}.pm),obj{(0)}.d)

3330 FOR a=1 TO 4

3340   col=obj{(a)}.gc

3350   IF obj{(a)}.ppcd>0 THEN

3360     col=4:IF obj{(a)}.ppcd<8 AND flipflop=0 col=6

3370   ENDIF

3380   IF obj{(a)}.sts<3 PROCdrawobject(0,obj{(a)}.x,obj{(a)}.y,4,col)

3390   PROCdraweyes(obj{(a)}.x,obj{(a)}.y,obj{(a)}.d)

3400 NEXT

3410 GCOL0,7:RECTANGLEFILL (gatex*48)-34,(gatey*48)-10,72,18

3420 ENDPROC

3430

3440 DEF PROCdrawpacman(x,y,m,d)

3450 LOCAL a,cx,cy

3460 x+=2:y+=2:MOVE x,y:GCOL 0,3:VDU 23,23,3;0;0;0;

3470 FOR a=m TO 360-m STEP 6

3480   cx=x+SIN(RAD(a+d*90))*31.5

3490   cy=y+COS(RAD(a+d*90))*31.5

3500   DRAW cx,cy

3510 NEXT

3520 DRAW x,y

3530 ENDPROC

3540

3550 DEF PROCdrawobject(type,x,y,scale,col)

3560 LOCAL first,a,cx,cy

3570 first=TRUE:GCOL 0,col:VDU 23,23,3;0;0;0;

3580 FOR a=td(type,0) TO td(type,0)+td(type,1)-1

3590   cx=x+shapes(a,0)*scale

3600   cy=y+shapes(a,1)*scale

3610   IF first MOVE cx,cy:first=FALSE ELSE DRAW cx,cy

3620 NEXT

3630 ENDPROC

3640

3650 DEF PROCdraweyes(x,y,d)

3660 LOCAL xo,yo,y1,x1,x2

3670 CASE d OF

3680   WHEN 0 : xo=0:yo=8:y1=8:x1=0:x2=0

3690   WHEN 1 : xo=6:yo=0:y1=4:x1=8:x2=0

3700   WHEN 2 : xo=0:yo=0:y1=0:x1=0:x2=0

3710   WHEN 3 : xo=-6:yo=0:y1=4:x1=0:x2=-8

3720 ENDCASE

3730 GCOL 0,7

3740 RECTANGLEFILL x+xo-22,y+yo,16

3750 RECTANGLEFILL x+xo+8,y+yo,16

3760 GCOL 0,0

3770 RECTANGLEFILL x+xo+x2-14,y+yo+y1,8

3780 RECTANGLEFILL x+xo+x1+8,y+yo+y1,8

3790 ENDPROC

3800

3810 DEF PROCdrawmaze

3820 LOCAL x,y,i,a,b

3830 FOR y=0 TO 20:FOR x=0 TO 26

3840     a=(x-13)*48:b=(10-y)*48

3850     i=INSTR(wall$,MID$(maze$(y),x+1,1))

3860     IF (i AND 1)=1 LINE a,b,a,b+24

3870     IF (i AND 2)=2 LINE a,b,a+24,b

3880     IF (i AND 4)=4 LINE a,b,a,b-24

3890     IF (i AND 8)=8 LINE a,b,a-24,b

3900   NEXT:NEXT

3910 ENDPROC

3920

3930 DEF PROCdrawdots

3940 LOCAL x,y,t

3950 VDU 5:GCOL 0,2:tdots=0

3960 FOR y=0 TO 20:FOR x=0 TO 26

3970     MOVE x*48-16,y*48+16

3980     t=map(x,y) AND 3:IF t<>0 VDU 223+t:tdots+=1

3990   NEXT:NEXT:VDU 4

4000 ENDPROC

4010

4020 DEF PROCmakemap

4030 LOCAL x,y,c$,wp,i,g

4040 way()=0

4050 FOR y=0 TO 20:FOR x=0 TO 26

4060     c$=MID$(maze$(20-y),x+1,1):wp=FALSE

4070     IF c$="=" gatex=x:gatey=y

4080     IF c$="H" homex=x:homey=y

4090     IF INSTR("012345678",c$)>0 wp=TRUE:i=VAL(c$)

4100     IF c$=" " OR wp map(x,y)=1

4110     IF c$="*" map(x,y)=2

4120     IF INSTR(wall$,c$)>0 OR c$="=" map(x,y)=4

4130     IF c$="~" map(x,y)=8

4140     IF wp THEN

4150       IF x<13 AND y>10 g=1

4160       IF x>13 AND y>10 g=2

4170       IF x>13 AND y<10 g=3

4180       IF x<13 AND y<10 g=4

4190       way(g,i,0)=x

4200       way(g,i,1)=y

4210       way(g,9,0)+=1

4220     ENDIF

4230   NEXT:NEXT

4240 ENDPROC

4250

4260 DATA "r------------T------------n"

4270 DATA "|   2        |        2   |"

4280 DATA "| r-n r-T-->1v1<--T-n r-n |"

4290 DATA "|*|.| |.|         |.| |.|*|"

4300 DATA "| L-u L-u0<--T-->0L-u L-u |"

4310 DATA "|            |            |"

4320 DATA "|3<->4<-T-->.v.<--T->4<->3|"

4330 DATA "|       |.........|       |"

4340 DATA "L-----n |.r->=<-n.| r-----u"

4350 DATA "<-----u v.|.....|.v L----->"

4360 DATA "~~~~~~~ ..|..H..|.. ~~~~~~~"

4370 DATA "<-----n ^.|.....|.^ r----->"

4380 DATA "r-----u v.L--T--u.v L-----n"

4390 DATA "|   1   0    |    0   1   |"

4400 DATA "| <-n <----> v <----> r-> |"

4410 DATA "|*  |     4  .  4     |  *|"

4420 DATA "t-> L-> ^ <--T--> ^ <-u <-j"

4430 DATA "|       |    |    |       |"

4440 DATA "|2<-----I-->3v3<--I----->2|"

4450 DATA "|                         |"

4460 DATA "L-------------------------u"

4470 REM Ghost body

4480 DATA -8,-6,-8,4,-4,8,4,8,8,4,8,-6,6,-8

4490 DATA 4,-6,2,-8,0,-6,-2,-8,-4,-6,-6,-8,-8,-6

4500 DATA 0,14

IPS&C

I,S,P & C: Code explained...

 

  30 DIM maze$(20):FOR a=0 TO 20:READ maze$(a):NEXT

  40 wall$="v<L^|rt>u-InjT+":gatex=0:gatey=0:homex=0:homey=0

  50 DIM map(26,20),way(4,9,1):PROCmakemap

 

The maze for this game is defined in 21 lines of data, with each line containing 27 characters. Each line is held in 'maze$'. The method for drawing the graphics is described later.

'wall$' holds the characters that define each type of wall segment.

'gatex' & 'gatey' hold the co-ordinates of the ghost gate (the entry to the ghosts' home area).

'homex' & 'homey' hold the co-ordinates of the location where ghosts regain their form.

The 'map' array holds a simplified version of the maze:

    0 = empty square

    1 = dot (removed when eaten by pac-man)

    2 = power pill (removed when eaten by pac-man)

    4 = wall

    8 = treacle (slows ghosts)

'way' holds a list of waypoints, a unique patrol path for each ghost to follow.

 

  60 DIM shapes(13,1):FOR a=0 TO 13:READ shapes(a,0),shapes(a,1):NEXT

  70 DIM td(1,1):FOR a=0 TO 0:READ td(a,0),td(a,1):NEXT

  80 DIM obj{(4) x,y,d,sts,pm,gc,tx,ty,cdn,ppcd,dcd}

 

'shapes' holds the template for the outline of a ghost (excluding the eyes).

'td' holds the pointer and number of nodes for each shape (only one shape in this game).

The 'obj' array holds the details of pac-man and 4 ghosts.

'd' = direction (0=up, 1=right, 2=down, 3=left).

'sts' = status.

'pm' = pac-man's mouth, how open it is.

'gc' = ghost colour.

'tx' & 'ty' are the object's current destination (target).

'cdn' = countdown. Used to determine the length of time a ghost remains at home.

'ppcd' = power pill countdown.

'dcd' = decided, TRUE or FALSE. The method used to limit the speed of ghosts (moving every other animation frame), created a bug where a ghost could make two different decisions on which direction to take, while waiting. 'dcd' is used to prevent a slow ghost from thinking too much!

 

  90 DIM list{(4) nxt,d,dist}:REM Path decision-making

 

'list' is described later and is an example of a simple linked-list. We use it to prioritise the available directions open to a ghost.

 

 130 ORIGIN 640,512-24

 140 GCOL 0,4:VDU 23,23,11;0;0;0;:PROCdrawmaze

 150 GCOL 0,0:VDU 23,23,5;0;0;0;:PROCdrawmaze

 160 ORIGIN 640-13*48,512-24-10*48

 

The maze is drawn using the centre of the window as the origin, but once drawn, the graphics origin is changed to the centre of the bottom left grid square.

PROCdrawmaze is called twice, with different colours, to create the hollow wall effect.

 

 270   newscreen=TRUE:score=0:lives=3:level=1

 280   REPEAT

 290     IF level<5 cspeed=12 ELSE cspeed=16

 300     IF newscreen PROCmakemap:newscreen=FALSE

 310     dead=FALSE:PROCdrawdots:flipflop=0

 320     PROCstartingblocks:PROCdrawall:PROCscoreboard:PROCready

 330     REPEAT

 

Line 280: a single loop handles each life as well as each new level.

When a new level begins, the 'map' array is reset. While the walls of the maze don't need redrawing, everything else does.

'flipflop' holds the value zero or 1, alternating each animation frame. It is used to limit the speed of ghosts.

Line 320 reinitialises the ghosts and pac-man, displaying 'Ready?' before the game continues.

 

 340       TIME=0:flipflop EOR=1

 360       PROCdeleteall

 

Animation for this game is handled slightly differently to previous programs. The walls of the maze are left untouched. A black, filled square effectively deletes each of the four ghosts and the pac-man graphics. However, this method also deletes any dots and power pills in the immediate vicinity.

 

 380       REM Pac-man controls

 390       x=obj{(0)}.x:y=obj{(0)}.y:d=obj{(0)}.d:newd=-1

 400       IF  (x MOD 48)=0 xa=TRUE ELSE xa=FALSE

 410       IF  (y MOD 48)=0 ya=TRUE ELSE ya=FALSE

 420       IF xa AND ya THEN

 

The maze is based on a 27 by 21 grid, each grid square of 48 units length. Neither ghosts or pac-man are allowed to change direction unless aligned with the grid. This method reduces the average number of processes required per animation frame.

 

 430         xb=x DIV 48:yb=y DIV 48

 440         IF map(xb,yb)=1 map(xb,yb)=0:score+=10:tdots-=1

 450         IF map(xb,yb)=2 map(xb,yb)=0:PROCscareghosts:tdots-=1

 460       ENDIF

 

If perfectly centred on a grid square, we test for the presence of a dot or power pill.

 

 470       IF INKEY(-98) AND ya AND newd=-1 AND FNwayclear(x,y,3,12) newd=3

 480       IF INKEY(-67) AND ya AND newd=-1 AND FNwayclear(x,y,1,12) newd=1

 490       IF INKEY(-102) AND xa AND newd=-1 AND FNwayclear(x,y,2,12) newd=2

 500       IF INKEY(-71) AND xa AND newd=-1 AND FNwayclear(x,y,0,12) newd=0

 510       IF newd=-1 AND d<>-1 AND FNwayclear(x,y,d,cspeed) newd=d

 520       IF newd>-1 THEN

 530         PROCupdateobjpos(0,newd,cspeed)

 540         obj{(0)}.pm+=15:IF obj{(0)}.pm>30 obj{(0)}.pm=-30:REM Move mouth

 550       ENDIF

 

Pac-man's controls are defined in such a way that the user can hold down a direction key in anticipation of the next corner to turn.

Whatever direction pac-man wishes to move, a test is conducted to check for walls. Only if the way is clear do we update the player's position.

 

 570       PROCghost_AI

 

PROCghost_AI handles all decision-making for the ghosts.

 

 590       REM Collision detection (dead or chomped ghost)

 600       tx=obj{(0)}.x:ty=obj{(0)}.y

 610       FOR a=1 TO 4

 620         IF obj{(a)}.sts=2 THEN

 630           dist=SQR((tx-obj{(a)}.x)^2+(ty-obj{(a)}.y)^2)

 640           IF dist<24 THEN

 650             IF obj{(a)}.ppcd=0 THEN

 660               dead=TRUE

 670             ELSE

 680               obj{(a)}.ppcd=0

 690               obj{(a)}.sts=3

 700               obj{(a)}.tx=gatex

 710               obj{(a)}.ty=gatey

 720               score+=400

 730             ENDIF

 740           ENDIF

 750         ENDIF

 760       NEXT

 

Tests are conducted to determine if pac-man has collided with a ghost. The result depends on whether the effects of a power-pill are in play. If not, then pac-man loses a life and a 'funeral' animation is drawn. Otherwise the status of the ghost is changed to make it run for home, without a body (eyes only).

 

 780       REM Redraw background (dots & power pills)

 790       FOR a=0 TO 4:PROCredrawbackground(obj{(a)}.x,obj{(a)}.y):NEXT

 800       REM Draw Pac-man, ghosts, gate and scoreboard

 810       PROCdrawall:PROCscoreboard

 

As mentioned above, when the ghosts and pac-man are deleted, dots and power pills may also be erased. These must be redrawn, before redrawing the ghosts and pac-man.

 

 830       IF tdots<=0 newscreen=TRUE

 

When the last dot has been eaten then a new level begins.

 

 950 DEF PROCready

 

Displays 'Ready?' and waits 2 seconds before deleting the text and commencing the new level.

 

1020 DEF PROCscoreboard

 

Displays the top line of text.

 

1080 DEF PROCstartingblocks

 

Places pac-man and each ghost in their starting positions, resetting all statuses.

 

1280 DEF PROCfuneral

 

Creates the animation of pac-man's mouth folding back at the end of a life.

 

1460 DEF PROCdeleteall

 

Deletes all 4 objects by drawing black squares over their locations.

 

1540 DEF PROCscareghosts

 

Unless the ghost is without a body then its power-pill countdown is set (depending on the level). When this countdown is active then the behaviour and the colour of the ghost is overridden.

 

1620 DEF PROCghost_AI

 

Ghost behaviour is generally controlled using its status:

0 = Wandering within the home area

1 = Finding the exit (gate)

2 = Patrolling

3 = Running towards the safe area (gate)

4 = Moving towards the 'home' square

 

2060     tx=obj{(a)}.tx:ty=obj{(a)}.ty:ghostbhv=1

 

When patrolling, the target destination is the next waypoint.

 

2070     REM Override patrolling with character choice?

2080     IF obj{(a)}.sts=2 THEN

2090       IF level<5 prob=110-level*20 ELSE prob=10

2100       IF RND(100)>prob THEN

2110         ghostbhv=VAL(MID$("111110112110",(a-1)*3+RND(3),1))

2120         tx=obj{(0)}.x DIV 48:ty=obj{(0)}.y DIV 48

2130       ENDIF

2140     ENDIF

 

Depending on the current level, there is a variable probability that a ghost's 'personality' will override its duty to patrol, in which case pac-man becomes the target destination. The behaviour of the ghost may become:

0 = random

1 = aggressive (chasing)

2 = fearful (running away)

 

2150     REM Override behaviour with fear if power pill active

2160     IF obj{(a)}.ppcd>0 THEN

2170       obj{(a)}.ppcd-=1

2180       IF obj{(a)}.ppcd>0 AND obj{(a)}.sts=2 THEN

2190         tx=obj{(0)}.x DIV 48:ty=obj{(0)}.y DIV 48:ghostbhv=2

2200       ENDIF

2210     ENDIF

 

Ghost behaviour can also be overridden when a power-pill is in play, in which case the effected ghosts run away from pac-man.

 

2220     D$=FNpaths(x,y,tx,ty,obj{(a)}.d):obj{(a)}.dcd=TRUE

 

FNpaths is an important routine that prioritises the available routes.

 

2230     IF LEN(D$)>1 THEN

2240       CASE ghostbhv OF

2250         WHEN 0 : obj{(a)}.d=VAL(MID$(D$,RND(LEN(D$)),1)):REM RND

2260         WHEN 1 : obj{(a)}.d=VAL(LEFT$(D$,1)):REM Go to

2270         WHEN 2 : obj{(a)}.d=VAL(RIGHT$(D$,1)):REM Run away!

2280       ENDCASE

2290     ELSE

2300       obj{(a)}.d=VAL(D$)

2310     ENDIF

2320   ELSE

2330     obj{(a)}.dcd=FALSE

2340   ENDIF

2350   IF flipflop=1 THEN

2360     IF obj{(a)}.ppcd>0 AND obj{(a)}.sts<3 ghostspd=0

2370     IF map(obj{(a)}.x DIV 48,obj{(a)}.y DIV 48)=8 ghostspd=0

2380   ENDIF

2390   PROCupdateobjpos(a,obj{(a)}.d,ghostspd)

2400 NEXT

2410 ENDPROC

 

The co-ordinates of each ghost are updated depending on the direction chosen. Speed can be affected by power-pills or 'treacle' on the ground, as well as the current level.

 

2430 DEF PROCrandomhome(a,x,y)

 

Creates a random destination for a ghost within the home area.

 

2490 DEF FNpaths(x,y,tx,ty,cd)

2500 LOCAL D$,d,a,b,okay,dist,free,p,lastp

2510 D$="":free=1:list{(0)}.nxt=-1:list{(0)}.dist=-1:REM Linked list anchor

2520 FOR d=0 TO 3

2530   okay=FALSE

2540   REM Ignore reverse as an option

2550   IF d<>(cd EOR 2) THEN

2560     CASE d OF

2570       WHEN 0 : a=x:b=y+1

2580       WHEN 1 : a=x+1:b=y

2590       WHEN 2 : a=x:b=y-1

2600       WHEN 3 : a=x-1:b=y

2610     ENDCASE

2620     REM 'Ghost gate' is a viable direction when a target

2630     IF a=tx AND b=ty AND tx=gatex AND ty=gatey THEN

2640       okay=TRUE

2650     ELSE

2660       IF a>=0 AND a<=26 THEN IF map(a,b)<>4 okay=TRUE

2670     ENDIF

2680     IF okay THEN

2690       REM Creating a linked list in the order of a move's distance to target

2700       dist=SQR((a-tx)^2+(b-ty)^2)

2710       p=list{(0)}.nxt:lastp=0

2720       WHILE okay AND p>-1

2730         IF dist<list{(p)}.dist okay=FALSE ELSE lastp=p:p=list{(p)}.nxt

2740       ENDWHILE

2750       list{(free)}.nxt=list{(lastp)}.nxt

2760       list{(free)}.d=d:list{(free)}.dist=dist

2770       list{(lastp)}.nxt=free

2780       free+=1

2790     ENDIF

2800   ENDIF

2810 NEXT

2820 REM Convert the linked list into a string of (valid) directions

2830 p=list{(0)}.nxt

2840 WHILE p>-1

2850   D$+=STR$(list{(p)}.d):p=list{(p)}.nxt

2860 ENDWHILE

2870 IF D$="" D$=STR$(cd EOR 2):REM Failsafe - reverse

2880 =D$

 

A ghost can potentially move in one of four directions, but the code prevents a ghost from reversing (unless it hits a dead end, which is impossible with the default maze).

This routine creates a variable length string of the digits: zero to 3. Each digit represents a direction.  The first digit/direction in the string moves the ghost closer to its target destination and the last takes it further away. In order to sort potential directions, by distance, we make use of a linked-list:

 

The first row (row zero) acts as the 'anchor', or our starting place. It contains the variable 'nxt' which is initially set to -1. A value of -1 indicates the end of the list.

 

    row 0: dist = -1, nxt = -1

 

Imagine we test direction zero (up) and we find that it would bring us within 100 units of our target:

 

    row 1: d = 0, dist = 100

 

Now we read our list to see where this entry should go. Only row zero exists (with a distance of -1). So direction zero comes after the anchor and we amend the pointers accordingly:

 

    row 0 : nxt = 1

    row 1 : nxt = -1

 

Row 1 is now at the end of our list and row zero (the start/anchor) points to row 1.

 

Next we test direction 1 (right) and find it brings us to within 90 units of our target. We read through our list, starting at row zero which points to row 1. Row one contains a distance greater than 90, so we need to insert direction 1 before row 1:

 

    row 0 : nxt = 2

    row 1 : nxt = -1 (no change)

    row 2 : nxt = 1

 

Direction 2 happens to be reverse, so we ignore it.

 

Direction 3 would take is within 110 units. Once again we read through the list to find where it should be inserted. This time it goes on the end:

 

    row 0 : d = n/a, dist = -1, nxt = 2 (no change)

    row 1 : d = 0, dist = 100, nxt = 3

    row 2 : d = 1, dist = 90, nxt = 1 (no change)

    row 3 : d = 3, dist = 110, nxt = -1

 

The important point to note is that although each new entry was added to the next free row in the array (i.e. 1, 2, 3), the pointers ('nxt') allow us to read the list in a different sequence i.e. rows 0, 2, 1, 3.

Finally, we read through the list to create the string of directions in order of distance to target: D$=”103”.

 

2900 DEF PROCredrawbackground(x,y)

 

Ghosts and pac-man are relatively large compared to the grid squares, overlapping more than one at any given instant. This routine redraws any remaining dots and power pills in the immediate vicinity of an object's co-ordinates (whether or not the dots and power pills have actually been graphically erased).

 

3000 DEF PROCupdateobjpos(a,newd,spd)

 

Ghosts and pac-man call this routine to update their co-ordinates upon a successful move request. This procedure also processes the effect of the wrap-around horizontal corridor.

 

3130 DEF FNwayclear(x,y,d,spd)

 

Pac-man can only move in one of four directions at a time. This routine checks for any walls that would inhibit movement in a particular direction, returning TRUE if the way is clear.

 

3300 DEF PROCdrawall

 

This routine draws pac-man, 4 ghosts (body &/or eyes) and the gate barring general entry to the home area serving the ghosts.

 

3440 DEF PROCdrawpacman(x,y,m,d)

3450 LOCAL a,cx,cy

3460 x+=2:y+=2:MOVE x,y:GCOL 0,3:VDU 23,23,3;0;0;0;

3470 FOR a=m TO 360-m STEP 6

3480   cx=x+SIN(RAD(a+d*90))*31.5

3490   cy=y+COS(RAD(a+d*90))*31.5

3500   DRAW cx,cy

3510 NEXT

3520 DRAW x,y

3530 ENDPROC

 

This routine draws a pac-man shape, using lines, in any of 4 directions, with a variable sized open mouth.

 

3550 DEF PROCdrawobject(type,x,y,scale,col)

 

A routine very similar to other programs that reference 'shape' and 'td' arrays.

 

3650 DEF PROCdraweyes(x,y,d)

 

The components of the eyes of the ghost vary in position (relative to the centre of the ghost) depending on the current direction of movement.

 

3810 DEF PROCdrawmaze

3820 LOCAL x,y,i,a,b

3830 FOR y=0 TO 20:FOR x=0 TO 26

3840     a=(x-13)*48:b=(10-y)*48

3850     i=INSTR(wall$,MID$(maze$(y),x+1,1))

3860     IF (i AND 1)=1 LINE a,b,a,b+24

3870     IF (i AND 2)=2 LINE a,b,a+24,b

3880     IF (i AND 4)=4 LINE a,b,a,b-24

3890     IF (i AND 8)=8 LINE a,b,a-24,b

3900   NEXT:NEXT

3910 ENDPROC

 

Refer to the diagram below. Each grid square of the maze is represented by a text character. Fifteen of these characters define different wall shapes, but only four are required to draw all fifteen (1,2,4 & 8).

wall$ contains all 15 characters and the position of the character in the string represents the numeric value of a particular wall shape. E.g. “r” is the 6th character and represents an r-shaped wall (pattern 6, composed of pattern 2 and pattern 4). This method of defining the maze within DATA lines provides a rough visual guide of the actual shape and contents of the maze (when listed using a fixed width font).

 

3930 DEF PROCdrawdots

 

Graphically fills the maze with dots and power pills, and keeps a total in 'tdots'.

 

4020 DEF PROCmakemap

 

Refer to the diagram below and the notes relating to PROCdrawmaze, as well as the notes at the top of this section regarding the 'map' array.

This procedure also populates the 'way' (waypoint) array, reading numeric values from the 'maze$' string array. The quadrant determines which row/ghost a waypoint relates to i.e. top left quadrant relates to ghost 1 (Clyde).

This routine also populates 'gatex', 'gatey' and 'homex', homey'.

 

4260 DATA "r------------T------------n"

4270 DATA "|   2        |        2   |"

4280 DATA "| r-n r-T-->1v1<--T-n r-n |"

4290 DATA "|*|.| |.|         |.| |.|*|"

4300 DATA "| L-u L-u0<--T-->0L-u L-u |"

4310 DATA "|            |            |"

4320 DATA "|3<->4<-T-->.v.<--T->4<->3|"

4330 DATA "|       |.........|       |"

4340 DATA "L-----n |.r->=<-n.| r-----u"

4350 DATA "<-----u v.|.....|.v L----->"

4360 DATA "~~~~~~~ ..|..H..|.. ~~~~~~~"

4370 DATA "<-----n ^.|.....|.^ r----->"

4380 DATA "r-----u v.L--T--u.v L-----n"

4390 DATA "|   1   0    |    0   1   |"

4400 DATA "| <-n <----> v <----> r-> |"

4410 DATA "|*  |     4  .  4     |  *|"

4420 DATA "t-> L-> ^ <--T--> ^ <-u <-j"

4430 DATA "|       |    |    |       |"

4440 DATA "|2<-----I-->3v3<--I----->2|"

4450 DATA "|                         |"

4460 DATA "L-------------------------u"

4470 REM Ghost body

4480 DATA -8,-6,-8,4,-4,8,4,8,8,4,8,-6,6,-8

4490 DATA 4,-6,2,-8,0,-6,-2,-8,-4,-6,-6,-8,-8,-6

4500 DATA 0,14

MapRules Arrow black large Arrow black large