Programming by Example

A BB4W Compendium

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

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

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.

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.