Programming by Example

 

A BB4W Compendium

freeman69@gmx.com

IDIC BBC_Owl2 M&P

Moon Shot

The original Lander game modelled the basic physics of landing a lunar module on the Moon's surface, including acceleration due to gravity and limited fuel. Moon Shot is an alternative interactive simulation, crudely modelling the gravity of the Earth as well as the Moon. The objective is to take off from the circular surface of the Earth, fly to the Moon (using the radar display as a guide) and land. Return to the Earth and land safely once again to complete the journey.

 

On close proximity to either body, a guide-line appears to aid alignment and approach speed. Green indicates a good approach. Red indicates a bad approach.

 

The player is advised to use minimal acceleration. Pressing ESCAPE will reset the simulation.

 

         Z= Rotate anticlockwise

         X= Rotate clockwise

         M= Thrust

 

    Simulation includes:

 

         Player's interplanetary rocket ship

         2 Body simulation of Newtonian gravity acting on a craft (not each other)

         Reference stars, thrust and explosion particles

 

  10 MODE 9:OFF

  20 ORIGIN 640,512

  30 *ESCAPE OFF

  40 *REFRESH OFF

  50 VDU 23,23,2;0;0;0;:REM Line thickness=2

  60 VDU 23,224,192,192,0,0,0,0,0,0:REM Particle

  70 VDU 23,225,64,224,64,0,0,0,0,0:REM Star

  80

  90 DIM tiles(7,7)

 100 DIM obj{(2) status,type,x,y,xvel,yvel,angle,r,col}

 110 DIM dots{(99) life,type,x,y,xvel,yvel,col}

 120 DIM stack(99):FOR a=0 TO 99:stack(a)=a:NEXT

 130 stackptr=0

 140 REM First 10 particles are stars

 150 FOR a=1 TO 10

 160   dots{(a)}.life=0:dots{(a)}.type=1:stackptr+=1

 170 NEXT

 180

 190 PROCreaddata

 200 pscale=4:shiprad=32:thrust=0.15

 210 vel=0:nearest=-1:neardist=500

 220 obj{(0)}.status=1:REM Trigger player respawn

 230

 240 a=RND(360):REM Place Moon in random part of orbit

 250 obj{(2)}.x=SIN(RAD(a))*3200

 260 obj{(2)}.y=COS(RAD(a))*3200

 270

 280 REPEAT

 290   TIME=0

 300   CLS

 310   REM Centre of view

 320   vx=obj{(0)}.x:vy=obj{(0)}.y

 330   REM Draw particles (stars, thrust etc)

 340   PROCparticles

 350   REM Draw Planets

 360   FOR p=1 TO 2

 370     px=obj{(p)}.x-vx

 380     py=obj{(p)}.y-vy

 390     PROCplanet(px,py,obj{(p)}.r,pscale,0.75,obj{(p)}.col)

 400   NEXT

 410   REM Player controls

 420   IF obj{(0)}.status>0 THEN

 430     obj{(0)}.status-=1

 440     IF obj{(0)}.status=0 THEN

 450       obj{(0)}.x=obj{(1)}.x

 460       obj{(0)}.y=obj{(1)}.y+obj{(1)}.r*pscale+shiprad

 470       obj{(0)}.xvel=0:obj{(0)}.yvel=0

 480       obj{(0)}.angle=0

 490     ENDIF

 500   ELSE

 510     REM Gravity acting upon ship from 2 bodies

 520     impact=FALSE:nearest=-1:neardist=500

 530     FOR p=1 TO 2

 540       xd=obj{(p)}.x-obj{(0)}.x

 550       yd=obj{(p)}.y-obj{(0)}.y

 560       r=SQR(xd^2+yd^2)

 570       IF r<neardist nearest=p:neardist=r

 580       IF r<obj{(p)}.r*pscale+shiprad impact=TRUE

 590       obj{(0)}.xvel+=obj{(p)}.r/2*xd/r^2

 600       obj{(0)}.yvel+=obj{(p)}.r/2*yd/r^2

 610     NEXT

 620     REM Crashed?

 630     vel=SQR(obj{(0)}.xvel^2+obj{(0)}.yvel^2)

 640     IF impact THEN

 650       obj{(0)}.xvel=0:obj{(0)}.yvel=0

 660       IF vel>2 OR FNanglebetween(nearest)>12 PROCexplode:obj{(0)}.status=50

 670     ENDIF

 680     REM Keyboard input

 690     IF INKEY(-98) obj{(0)}.angle-=6

 700     IF INKEY(-67) obj{(0)}.angle+=6

 710     IF INKEY(-102) THEN

 720       xd=SIN(RAD(obj{(0)}.angle))

 730       yd=COS(RAD(obj{(0)}.angle))

 740       obj{(0)}.xvel+=xd*thrust

 750       obj{(0)}.yvel+=yd*thrust

 760       xd+=RND(1)/4-0.125:yd+=RND(1)/4-0.125

 770       PROCnewparticle(10+RND(5),vx,vy,obj{(0)}.xvel-xd*10,obj{(0)}.yvel-yd*10,1)

 780     ENDIF

 790     IF INKEY(-113) obj{(0)}.status=1

 800     REM Update velocity and check for edge of universe

 810     obj{(0)}.x+=obj{(0)}.xvel

 820     obj{(0)}.y+=obj{(0)}.yvel

 830     IF ABS(obj{(0)}.x)>10000 obj{(0)}.x-=SGN(obj{(0)}.x)*20000

 840     IF ABS(obj{(0)}.y)>10000 obj{(0)}.y-=SGN(obj{(0)}.y)*20000

 850     REM Draw line to show approach status

 860     IF nearest<>-1 THEN

 870       IF FNanglebetween(nearest)<=12 AND vel<=2 GCOL 0,2 ELSE GCOL 0,1

 880       PROCtangent(vx,vy,obj{(nearest)}.x,obj{(nearest)}.y,obj{(nearest)}.r,4)

 890     ENDIF

 900     REM Draw ship

 910     PROCdrawpattern(0,0,obj{(0)}.angle,8)

 920   ENDIF

 930  

 940   PROCradar(200,200,40)

 950   *REFRESH

 960   *FX21

 970   WAIT 4-TIME

 980 UNTIL FALSE

 990 END

1000

1010 DEF PROCreaddata

1020 LOCAL y

1030 FOR y=0 TO 2

1040   READ obj{(y)}.status,obj{(y)}.type

1050   READ obj{(y)}.x,obj{(y)}.y

1060   READ obj{(y)}.xvel,obj{(y)}.yvel

1070   READ obj{(y)}.angle,obj{(y)}.r,obj{(y)}.col

1080 NEXT

1090 FOR y=0 TO 7

1100   READ d$

1110   FOR x=1 TO 8

1120     tiles(x-1,7-y)=VAL(MID$(d$,x,1))

1130   NEXT

1140 NEXT

1150 ENDPROC

1160 REM Objects: status,type,x,y,xvel,yvel,angle,radius,colour

1170 DATA 0,0,0,0,0,0,0,0,6

1180 DATA 1,1,0,0,0,0,0,60,2

1190 DATA 1,1,0,0,0,0,0,40,7

1200 REM Ship

1210 DATA "00067000"

1220 DATA "00666700"

1230 DATA "06666770"

1240 DATA "06166170"

1250 DATA "06666670"

1260 DATA "00666600"

1270 DATA "06700670"

1280 DATA "67000067"

1290

1300 DEF PROCdrawpattern(x,y,angle,scale)

1310 LOCAL tx1,ty1,tx2,ty2,a,b,col

1320 tx1=SIN(RAD(angle))*scale:ty1=COS(RAD(angle))*scale:REM Vertical vector

1330 tx2=SIN(RAD(angle+90))*scale:ty2=COS(RAD(angle+90))*scale:REM Horizontal

1340 FOR b=0 TO 7

1350   FOR a=0 TO 7

1360     col=tiles(a,b)

1370     IF col>0 THEN

1380       GCOL 0,col

1390       MOVE x+(a-4)*tx2+(b-4)*tx1,y+(a-4)*ty2+(b-4)*ty1

1400       MOVE x+(a-4)*tx2+(b-3)*tx1,y+(a-4)*ty2+(b-3)*ty1

1410       PLOT 85,x+(a-3)*tx2+(b-4)*tx1,y+(a-3)*ty2+(b-4)*ty1

1420       PLOT 85,x+(a-3)*tx2+(b-3)*tx1,y+(a-3)*ty2+(b-3)*ty1

1430     ENDIF

1440   NEXT

1450 NEXT

1460 ENDPROC

1470

1480 DEF PROCplanet(x,y,r,scale,f,col)

1490 LOCAL a,b,d

1500 d=r*scale

1510 IF x+d<-640 OR x-d>640 OR y+d<-512 OR y-d>512 ENDPROC

1520 y-=scale/2

1530 GCOL 0,col

1540 RECTANGLEFILL x-r*scale,y,r*scale*2*f,scale

1550 GCOL 0,col+8

1560 RECTANGLEFILL x-r*scale+r*scale*2*f,y,r*scale*2*(1-f),scale

1570 FOR b=1 TO r-1

1580   a=INT(SQR(r^2-(b-1)^2))*scale

1590   GCOL 0,col

1600   RECTANGLEFILL x-a,y+b*scale,a*2*f+2,scale

1610   RECTANGLEFILL x-a,y-b*scale,a*2*f+2,scale

1620   GCOL 0,col+8

1630   RECTANGLEFILL x-a+a*2*f,y+b*scale,a*2*(1-f),scale

1640   RECTANGLEFILL x-a+a*2*f,y-b*scale,a*2*(1-f),scale

1650 NEXT

1660 ENDPROC

1670

1680 DEF PROCtangent(x,y,px,py,pr,scale)

1690 LOCAL xd,yd,d,tx,ty,rx,ry

1700 REM Vector from planet centre to ship

1710 xd=x-px:yd=y-py:d=SQR(xd^2+yd^2)

1720 REM Normal vector of above

1730 tx=-yd/d*12*scale:ty=xd/d*12*scale

1740 REM Vector from planet centre to surface, in direction of ship

1750 rx=xd/d*pr*scale:ry=yd/d*pr*scale

1760 LINE px-x+rx+tx,py-y+ry+ty,px-x+rx-tx,py-y+ry-ty

1770 ENDPROC

1780

1790 DEF PROCradar(w,h,scale)

1800 LOCAL tx,ty,x,y

1810 tx=1230:ty=974

1820 ORIGIN 0,0:VDU 24,tx-w;ty-h;tx-1;ty-1;

1830 GCOL 0,0:RECTANGLEFILL tx-w,ty-h,w,h

1840 GCOL 0,7:RECTANGLE tx-w,ty-h,w,h-2

1850 ORIGIN tx-w/2,ty-h/2:VDU 5

1860 FOR a=0 TO 2

1870   x=obj{(a)}.x-obj{(0)}.x

1880   y=obj{(a)}.y-obj{(0)}.y

1890   GCOL 0,obj{(a)}.col

1900   MOVE x/scale-4,y/scale+4:VDU 224

1910 NEXT

1920 VDU 4,26:ORIGIN 640,512

1930 ENDPROC

1940

1950 DEF PROCparticles

1960 LOCAL a

1970 FOR a=0 TO 99

1980   CASE dots{(a)}.type OF

1990     WHEN 1

2000       dots{(a)}.life-=1

2010       IF dots{(a)}.life<=0 THEN

2020         dots{(a)}.life=50+RND(50)

2030         angle=RND(360)

2040         dots{(a)}.x=obj{(0)}.x+SIN(RAD(angle))*RND(1000)

2050         dots{(a)}.y=obj{(0)}.y+COS(RAD(angle))*RND(1000)

2060         dots{(a)}.xvel=0:dots{(a)}.yvel=0:dots{(a)}.col=7

2070       ENDIF

2080       VDU 5:MOVE dots{(a)}.x-vx,dots{(a)}.y-vy:VDU 225,4

2090     WHEN 2

2100       IF dots{(a)}.life>0 THEN

2110         dots{(a)}.life-=1

2120         IF dots{(a)}.life<=0 stackptr-=1:stack(stackptr)=a

2130         dots{(a)}.x+=dots{(a)}.xvel

2140         dots{(a)}.y+=dots{(a)}.yvel

2150         GCOL 0,dots{(a)}.col

2160         VDU 5:MOVE dots{(a)}.x-vx,dots{(a)}.y-vy:VDU 224,4

2170       ENDIF

2180   ENDCASE

2190 NEXT

2200 ENDPROC

2210

2220 DEF PROCnewparticle(life,x,y,xvel,yvel,col)

2230 LOCAL a

2240 IF stackptr<99 THEN

2250   a=stack(stackptr):stackptr+=1

2260   dots{(a)}.life=life

2270   dots{(a)}.type=2

2280   dots{(a)}.x=x:dots{(a)}.y=y

2290   dots{(a)}.xvel=xvel:dots{(a)}.yvel=yvel

2300   dots{(a)}.col=col

2310 ENDIF

2320 ENDPROC

2330

2340 DEF PROCexplode

2350 LOCAL a

2360 FOR a=1 TO 45

2370   PROCnewparticle(30+RND(30),vx,vy,FNspd,FNspd,7)

2380 NEXT

2390 ENDPROC

2400

2410 DEF FNspd

2420 =2*(RND(32)-16)

2430

2440 DEF FNanglebetween(p)

2450 LOCAL ax,ay,am,cx,cy

2460 REM Vector from planet centre to ship

2470 ax=obj{(0)}.x-obj{(p)}.x

2480 ay=obj{(0)}.y-obj{(p)}.y

2490 am=SQR(ax^2+ay^2):REM Magnitude of vector

2500 REM Vector created from angle of ship (magnitude=1)

2510 cx=SIN(RAD(obj{(0)}.angle))

2520 cy=COS(RAD(obj{(0)}.angle))

2530 REM a.c = |a||c| cos(theta)

2540 =DEG(ACS((ax*cx+ay*cy)/am))

Moon Shot: Code explained...

 

  30 *ESCAPE OFF

 

The ESCAPE key usually aborts a BB4W program. By turning this function off we can use this key just like any other.

 

  90 DIM tiles(7,7)

 100 DIM obj{(2) status,type,x,y,xvel,yvel,angle,r,col}

 110 DIM dots{(99) life,type,x,y,xvel,yvel,col}

 120 DIM stack(99):FOR a=0 TO 99:stack(a)=a:NEXT

 130 stackptr=0

 

'tiles' contains the graphic pattern of the player's ship. A special routine will draw this pattern using 2 coloured triangles for each square tile.

'obj' holds the details of the player's ship, Earth and Moon only. Storing these details together simplifies the process of drawing the radar display.

'dots' holds the details of all particles. The first 10 rows will be reserved for drawing stars to provide reference points when the Earth and Moon are not visible in the main display. Rows in the 'dots' array will be assigned using a stack of pointers.

 

 200 pscale=4:shiprad=32:thrust=0.15

 210 vel=0:nearest=-1:neardist=500

 

'pscale' defines the scale multiplier for drawing planets. Planets are draw as circles with two shades of colour to provide the illusion of a spherical surface.

'shiprad' is used for collision detection with planets (assumes the ship pattern being drawn at scale=8).

'thrust' is set at a constant 0.15 units/frame. This is sufficient to break free of the gravity of the Earth.

'vel' is used to hold the ship velocity. If too high then the ship will explode on impact.

'nearest' & 'neardist' are used to determine which of the Earth and Moon are closest to the player's ship.

 

 630     vel=SQR(obj{(0)}.xvel^2+obj{(0)}.yvel^2)

 640     IF impact THEN

 650       obj{(0)}.xvel=0:obj{(0)}.yvel=0

 660       IF vel>2 OR FNanglebetween(nearest)>12 PROCexplode:obj{(0)}.status=50

 670     ENDIF

 

 850     REM Draw line to show approach status

 860     IF nearest<>-1 THEN

 870       IF FNanglebetween(nearest)<=12 AND vel<=2 GCOL 0,2 ELSE GCOL 0,1

 880       PROCtangent(vx,vy,obj{(nearest)}.x,obj{(nearest)}.y,obj{(nearest)}.r,4)

 890     ENDIF

 

As mentioned above, both the velocity and the orientation of the ship (relative to the surface of the planet/moon), determine whether the ship lands safely. The latter is calculated using the formula for the angle between two vectors: a.b=|a||b|cos(theta)

The safety guide-line is drawn at a tangent to the surface of the planet.

 

1300 DEF PROCdrawpattern(x,y,angle,scale)

1310 LOCAL tx1,ty1,tx2,ty2,a,b,col

1320 tx1=SIN(RAD(angle))*scale:ty1=COS(RAD(angle))*scale:REM Vertical vector

1330 tx2=SIN(RAD(angle+90))*scale:ty2=COS(RAD(angle+90))*scale:REM Horizontal

1340 FOR b=0 TO 7

1350   FOR a=0 TO 7

1360     col=tiles(a,b)

1370     IF col>0 THEN

1380       GCOL 0,col

1390       MOVE x+(a-4)*tx2+(b-4)*tx1,y+(a-4)*ty2+(b-4)*ty1

1400       MOVE x+(a-4)*tx2+(b-3)*tx1,y+(a-4)*ty2+(b-3)*ty1

1410       PLOT 85,x+(a-3)*tx2+(b-4)*tx1,y+(a-3)*ty2+(b-4)*ty1

1420       PLOT 85,x+(a-3)*tx2+(b-3)*tx1,y+(a-3)*ty2+(b-3)*ty1

1430     ENDIF

1440   NEXT

1450 NEXT

1460 ENDPROC  

 

The player's ship is drawn as a pattern of coloured tiles, rotated and scaled. Each tile is comprised of two triangles. BB4W draws triangles using the last 3 co-ordinates visited by the graphics cursor. This means that a four sided shape can be drawn with a minimum of 4 instructions:

 

         1     2

         3     4

 

The first two MOVE instructions are the first two corners of the first triangle. PLOT 85 is an instruction to draw a triangle in the current foreground colour. The second triangle uses the last two co-ordinates of the first triangle.

 

Similar to the LOGO program, we're drawing a pattern relative to the current direction of the ship i.e. the pattern of tiles is centred on, and aligned with, the ship's position and orientation.

The routine calculates where each corner of each tile needs to be drawn. We already know the relative position of each tile i.e. how many tiles it is to the left or right of the centre of the ship and how many tiles above or below. By multiplying these offsets with two direction vectors (forwards and sideways), we can calculate the real co-ordinates of each tile corner.

 

1480 DEF PROCplanet(x,y,r,scale,f,col)

 

The Earth and Moon are basically circles, each circle drawn one horizontal line at a time (although the line is split into two and drawn in two different shades to achieve a crescent effect). Each horizontal line is wide enough to be drawn very quickly as a rectangle.

'f' is a value between zero and 1 that controls the split between light and dark.

Because this routine is relatively slow, we first check that the circle overlaps the window before drawing.

 

1680 DEF PROCtangent(x,y,px,py,pr,scale)

 

The safety guide-line is drawn at 90 degrees to the point on the circumference that is closest to the ship.

 

1790 DEF PROCradar(w,h,scale)

 

The mini radar display is a smaller version of the main window, displaying the position of the Earth and the Moon relative to the player's ship.

Line 1820 defines a small graphics window within the main window (restoring the main window origin to zero,zero first). Defining a graphics window prevents dots beyond the edge of the radar border rom being plotted. Line 1920 resets the graphics window to the size of the main window.

 

1950 DEF PROCparticles

 

As mentioned above, the dots array hold details of temporary stars, thrust particles and explosion particles. Stars and particles are drawn with different user-defined characters.

Stars have a limited life span and are randomly respawned (twinkling) in the vicinity of the player's ship. Hopefully this presents the player with the illusion of movement by providing reference points to judge velocity.

 

2220 DEF PROCnewparticle(life,x,y,xvel,yvel,col)

 

This routine adds the details of any new particle to the 'dots' array, but only if there is room.

 

2340 DEF PROCexplode

 

If the ship crashes then it is destroyed, followed by an explosion of particles with random initial velocity vectors provided by FNspd.

 

2440 DEF FNanglebetween(p)

2450 LOCAL ax,ay,am,cx,cy

2460 REM Vector from planet centre to ship

2470 ax=obj{(0)}.x-obj{(p)}.x

2480 ay=obj{(0)}.y-obj{(p)}.y

2490 am=SQR(ax^2+ay^2):REM Magnitude of vector

2500 REM Vector created from angle of ship (magnitude=1)

2510 cx=SIN(RAD(obj{(0)}.angle))

2520 cy=COS(RAD(obj{(0)}.angle))

2530 REM a.c = |a||c| cos(theta)

2540 =DEG(ACS((ax*cx+ay*cy)/am))

 

Similar to the Vector Demo, this routine calculates the angle between two vectors. The first vector is calculated from the centre of the planet to the centre of the ship. The second vector is calculated using the angle of the ship. By calculating the angle between these two vectors we are able to determine if the ship is landing on both feet at the same time (on the circumference of a circle). Landing on one foot is deemed disastrous and results in an explosion.

Note that the magnitude of the angle of the ship is always equal to one and is therefore superfluous to the calculation.

Moon Shot Arrow black large Arrow black large