Programming by Example

 

A BB4W Compendium

freeman69@gmx.com

IDIC BBC_Owl2 M&P

Bugs: Food Chain Evolution

This program is a crude simulation of the evolution of a food chain. It begins slowly and simply and may suffer the odd false start, but the appearance of 'higher' life forms is inevitable. (Simulations tend to be non-interactive, unlike games).

 

Bugs are depicted as circles and one bug may bite another when they overlap. Overlapping circles (or points falling within a circle) provide a simple means of collision detection. When the distance between the centres of two bugs is less than the sum of their radii, then the circles overlap e.g.:

 

         IF sqr((x1-x2)^2+(y1-y2)^2)<radius1+radius2 THEN the circles overlap.

 

    The general rules are as follows:

 

         'Greens' always grow (mimicking photosynthesis), but are not mobile.

         'Reds' eat 'Greens'

         'Whites' eat 'Reds'

         'Blues' eat 'Whites'

         Bugs spawn when they have sufficient energy and the population of a particular species has not reached its limit. If the population limit has been reached then the bugs continue to grow.

         Spawning results in the creation of two new bugs sharing the parent's energy. Spawning can also result in mutation, either into a different species (one up or down), or a change in speed of mobility.

         Any bug has a 1/10000 chance of expiring at any given moment.

         Mobile bugs expend more energy.

         Bugs that lose too much energy, expire.

 

  10 MODE9:OFF

  20 *REFRESH OFF

  30 MAXBUGS=240:MINENERGY=400

  40 MAXTYPE=MAXBUGS DIV 4

  50 DIM bugs{(MAXBUGS) status,type,speed,x,y,xvel,yvel,energy,walk}

  60 DIM stack(MAXBUGS)

  70 DIM counts(3)

  80

  90 stackptr=0

 100 FOR a=0 TO MAXBUGS:stack(a)=a:bugs{(a)}.status=FALSE:NEXT

 110 REM Create initial food source

 120 FOR a=1 TO 10

 130   PROCnewbug(FNgetslot(0),0,RND(1280),RND(1024),0,0,1200,0)

 140 NEXT

 150

 160 REPEAT

 170   TIME=0

 180   CLS

 190  

 200   FOR a=0 TO MAXBUGS

 210     IF bugs{(a)}.status THEN

 220       REM Green stuff grows, while true bugs expend energy

 230       IF bugs{(a)}.type=0 THEN

 240         IF RND(3)=1 bugs{(a)}.energy+=20

 250       ELSE

 260         bugs{(a)}.energy-=(2+ABS(bugs{(a)}.speed))

 270       ENDIF

 280       IF bugs{(a)}.energy<MINENERGY OR RND(10000)=1 THEN

 290         PROCreturnslot(a):REM Kill any starving bug

 300       ELSE

 310         REM Move the bug, testing for edge of display

 320         x=bugs{(a)}.x+bugs{(a)}.xvel

 330         IF x<0 OR x>=1280 bugs{(a)}.xvel*=-1

 340         y=bugs{(a)}.y+bugs{(a)}.yvel

 350         IF y<0 OR y>=1024 bugs{(a)}.yvel*=-1

 360         bugs{(a)}.x+=bugs{(a)}.xvel

 370         bugs{(a)}.y+=bugs{(a)}.yvel

 380         REM Change direction?

 390         bugs{(a)}.walk-=1

 400         IF bugs{(a)}.walk<=0 THEN

 410           IF bugs{(a)}.type=0 THEN

 420             bugs{(a)}.xvel=0:bugs{(a)}.yvel=0

 430           ELSE

 440             angle=RND(360)

 450             bugs{(a)}.xvel=SIN(RAD(angle))*bugs{(a)}.speed

 460             bugs{(a)}.yvel=COS(RAD(angle))*bugs{(a)}.speed

 470             bugs{(a)}.walk=RND(20)+20

 480           ENDIF

 490         ENDIF

 500         REM Spawn/split into 2 bugs if sufficient energy and space

 510         IF bugs{(a)}.energy>=3000 AND counts(bugs{(a)}.type)<MAXTYPE-1 THEN

 520           FOR b=1 TO 2

 530             type=bugs{(a)}.type

 540             speed=bugs{(a)}.speed

 550             x=bugs{(a)}.x

 560             y=bugs{(a)}.y

 570             e=bugs{(a)}.energy

 580             xvel=0:yvel=0:walk=0

 590             IF RND(4)=1 THEN

 600               REM Mutate offspring

 610               CASE RND(2) OF

 620                 WHEN 1

 630                   d=RND(2):IF d=2 d=-1

 640                   IF type<2 AND d=-1 d=0

 650                   type+=d:IF type>3 type=3

 660                 WHEN 2

 670                   d=RND(2):IF d=2 d=-1

 680                   speed+=d

 690               ENDCASE

 700             ENDIF

 710             IF speed=0 THEN

 720               walk=RND(10)+10:xvel=RND(31)-16:yvel=RND(31)-16

 730             ENDIF

 740             PROCnewbug(FNgetslot(type),speed,x,y,xvel,yvel,e DIV 2,walk)

 750           NEXT

 760           PROCreturnslot(a):REM Kill parent bug

 770         ENDIF

 780       ENDIF

 790     ENDIF

 800   NEXT

 810  

 820   REM Draw green stuff first

 830   GCOL 0,2

 840   FOR a=0 TO MAXBUGS

 850     IF bugs{(a)}.status AND bugs{(a)}.type=0 THEN

 860       r=SQR(bugs{(a)}.energy/PI)

 870       CIRCLEFILL bugs{(a)}.x,bugs{(a)}.y,r

 880     ENDIF

 890   NEXT

 900  

 910   REM Draw bugs and check for food

 920   FOR b=1 TO 3

 930     FOR a=0 TO MAXBUGS

 940       pred=bugs{(a)}.type

 950       IF bugs{(a)}.status AND pred=b THEN

 960        

 970         predradius=SQR(bugs{(a)}.energy/PI)

 980         CASE pred OF

 990           WHEN 1 : GCOL 0,1

1000           WHEN 2 : GCOL 0,7

1010           WHEN 3 : GCOL 0,4

1020         ENDCASE

1030         CIRCLEFILL bugs{(a)}.x,bugs{(a)}.y,predradius

1040        

1050         FOR f=0 TO MAXBUGS

1060           IF bugs{(f)}.status THEN

1070             food=bugs{(f)}.type

1080             IF pred=food+1 THEN

1090               foodradius=SQR(bugs{(f)}.energy/PI)

1100               xd=bugs{(a)}.x-bugs{(f)}.x

1110               yd=bugs{(a)}.y-bugs{(f)}.y

1120               IF SQR(xd^2+yd^2)<predradius+foodradius THEN

1130                 bugs{(a)}.energy+=20:REM Eating

1140                 bugs{(f)}.energy-=20:REM Being eaten

1150                 IF bugs{(f)}.energy<=MINENERGY PROCreturnslot(f)

1160               ENDIF

1170             ENDIF

1180           ENDIF

1190         NEXT

1200        

1210       ENDIF

1220     NEXT

1230   NEXT

1240  

1250   *REFRESH

1260   WAIT 4-TIME

1270 UNTIL FALSE

1280 END

1290

1300 DEF PROCnewbug(i,speed,x,y,xvel,yvel,energy,walk)

1310 IF i<>-1 THEN

1320   bugs{(i)}.speed=speed

1330   bugs{(i)}.x=x

1340   bugs{(i)}.y=y

1350   bugs{(i)}.xvel=xvel

1360   bugs{(i)}.yvel=yvel

1370   bugs{(i)}.energy=energy

1380   bugs{(i)}.walk=walk

1390 ENDIF

1400 ENDPROC

1410

1420 DEF FNgetslot(type)

1430 LOCAL i

1440 IF stackptr=MAXBUGS OR counts(type)>=MAXTYPE THEN =-1

1450 i=stack(stackptr):stackptr+=1

1460 counts(type)+=1

1470 bugs{(i)}.status=TRUE

1480 bugs{(i)}.type=type

1490 =i

1500

1510 DEF PROCreturnslot(i)

1520 stackptr-=1:stack(stackptr)=i

1530 counts(bugs{(i)}.type)-=1

1540 bugs{(i)}.status=FALSE

1550 ENDPROC

Bugs

Bugs: Code explained...

 

  10 MODE9:OFF

  20 *REFRESH OFF

 

In the Rocket Rotation program we made the first use of CLS to clear the entire window before redrawing any animation frame. However, if the task of drawing a complete frame takes too long then our graphics will flicker.

*REFRESH OFF instructs BB4W that we don't want the window to show our graphics until we're ready (by issuing a *REFRESH command). In reality, we're drawing within a hidden window and the *REFRESH command copies the contents of this hidden window onto the visible window, very quickly.

 

  30 MAXBUGS=240:MINENERGY=400

  40 MAXTYPE=MAXBUGS DIV 4

 

There are 4 species of bugs. Only 240 may be alive at any time, limited to a maximum of 60 of each species (mutations may exceed this limit). These limits prevent any one species from becoming too dominant.

Any bug with less than MINENERGY will expire.

 

  50 DIM bugs{(MAXBUGS) status,type,speed,x,y,xvel,yvel,energy,walk}

 

'bugs' is a special type of array. It's an array of structures. Ordinary arrays are simple lists or tables, referenced by row and column. Arrays of structures allow us to create lists of various values using different types of variables. However, we're using an array of structures because it allows us to use variable names instead of numbers.

Imagine a spreadsheet with rows and columns. We can refer to a cell as 10 rows down and three columns across. We can also use column headings and refer to a cell as column 'speed' in row 10. The use of 'speed' provides additional information about the contents of column three to the casual observer.

The 'bugs' array will contain information about every bug. This includes status, type, speed, position, velocity and energy.

 

  60 DIM stack(MAXBUGS)

  70 DIM counts(3)

  80

  90 stackptr=0

 100 FOR a=0 TO MAXBUGS:stack(a)=a:bugs{(a)}.status=FALSE:NEXT

 

We're using a stack again to quickly find an empty row in the 'bugs' array.

'counts' maintains a count of the current number of each species.

 

 110 REM Create initial food source

 120 FOR a=1 TO 10

 130   PROCnewbug(FNgetslot(0),0,RND(1280),RND(1024),0,0,1200,0)

 140 NEXT

 

At the start of the simulation we create 10 green bugs (bacteria?), placed randomly.

 

 160 REPEAT

 170   TIME=0

 180   CLS

 190  

 200   FOR a=0 TO MAXBUGS

 210     IF bugs{(a)}.status THEN

 

For every animation frame drawn, we need to process every living bug separately.

 

 220       REM Green stuff grows, while true bugs expend energy

 230       IF bugs{(a)}.type=0 THEN

 240         IF RND(3)=1 bugs{(a)}.energy+=20

 250       ELSE

 260         bugs{(a)}.energy-=(2+ABS(bugs{(a)}.speed))

 270       ENDIF

 

Bug type zero is the immobile green stuff (bacteria). This grows at a relatively constant rate. Other species of bugs lose energy over time and due to increased movement.

The ABS function returns the magnitude of a variable i.e. negative values are returned as positives, and positive values are also returned as positives.

 

 280       IF bugs{(a)}.energy<MINENERGY OR RND(10000)=1 THEN

 290         PROCreturnslot(a):REM Kill any starving bug

 300       ELSE

 

Mobile bugs may lose too much energy and perish. Any bug may expire at any time with a 1 in 10,000 chance.

 

 310         REM Move the bug, testing for edge of display

 320         x=bugs{(a)}.x+bugs{(a)}.xvel

 330         IF x<0 OR x>=1280 bugs{(a)}.xvel*=-1

 340         y=bugs{(a)}.y+bugs{(a)}.yvel

 350         IF y<0 OR y>=1024 bugs{(a)}.yvel*=-1

 360         bugs{(a)}.x+=bugs{(a)}.xvel

 370         bugs{(a)}.y+=bugs{(a)}.yvel

 

If a bug collides with the edge of the window, it bounces off in the opposite direction. This includes spores of immobile bugs that are ejected some distance from the parent.

 

 380         REM Change direction?

 390         bugs{(a)}.walk-=1

 400         IF bugs{(a)}.walk<=0 THEN

 410           IF bugs{(a)}.type=0 THEN

 420             bugs{(a)}.xvel=0:bugs{(a)}.yvel=0

 430           ELSE

 440             angle=RND(360)

 450             bugs{(a)}.xvel=SIN(RAD(angle))*bugs{(a)}.speed

 460             bugs{(a)}.yvel=COS(RAD(angle))*bugs{(a)}.speed

 470             bugs{(a)}.walk=RND(20)+20

 480           ENDIF

 490         ENDIF

 

Mobile bugs 'walk' a predefined number of steps before choosing a new random direction. This section also halts the motion of ejected spores. Green bugs are not allowed to be mobile.

 

 500         REM Spawn/split into 2 bugs if sufficient energy and space

 510         IF bugs{(a)}.energy>=3000 AND counts(bugs{(a)}.type)<MAXTYPE-1 THEN

 520           FOR b=1 TO 2

 530             type=bugs{(a)}.type

 540             speed=bugs{(a)}.speed

 550             x=bugs{(a)}.x

 560             y=bugs{(a)}.y

 570             e=bugs{(a)}.energy

 580             xvel=0:yvel=0:walk=0

 

Two offspring are created. The use of the specific variables: 'type', 'speed', 'x', 'y' and 'e', are to keep the following code lines short and easily readable.

 

 590             IF RND(4)=1 THEN

 600               REM Mutate offspring

 610               CASE RND(2) OF

 620                 WHEN 1

 630                   d=RND(2):IF d=2 d=-1

 640                   IF type<2 AND d=-1 d=0

 650                   type+=d:IF type>3 type=3

 660                 WHEN 2

 670                   d=RND(2):IF d=2 d=-1

 680                   speed+=d

 690               ENDCASE

 700             ENDIF

 

A newly spawned bug has a one in four chance of mutating, either into a different species, or changing the speed of its mobility.

Reds cannot devolve into Greens.

 

 710             IF speed=0 THEN

 720               walk=RND(10)+10:xvel=RND(31)-16:yvel=RND(31)-16

 730             ENDIF

 

If the new bug is immobile then it is treated as a spore and thrown some distance from the parent's position.

 

 740             PROCnewbug(FNgetslot(type),speed,x,y,xvel,yvel,e DIV 2,walk)

 750           NEXT

 

PROCnewbug adds the details of the new bug to the 'bugs' array (as long as a row in the array is available).

 

 760           PROCreturnslot(a):REM Kill parent bug

 770         ENDIF

 780       ENDIF

 790     ENDIF

 800   NEXT

 

On spawning, the parent bug always expires.

 

 820   REM Draw green stuff first

 830   GCOL 0,2

 840   FOR a=0 TO MAXBUGS

 850     IF bugs{(a)}.status AND bugs{(a)}.type=0 THEN

 860       r=SQR(bugs{(a)}.energy/PI)

 870       CIRCLEFILL bugs{(a)}.x,bugs{(a)}.y,r

 880     ENDIF

 890   NEXT

 

Bugs are drawn in order of species. Greens are drawn first, appearing underneath other types.

The radius of a bug is calculated from its area. The area is equal to the energy of the bug.

 

 910   REM Draw bugs and check for food

 920   FOR b=1 TO 3

 930     FOR a=0 TO MAXBUGS

 940       pred=bugs{(a)}.type

 950       IF bugs{(a)}.status AND pred=b THEN

 

While the design of this loop (and its related processes) may not be efficient, it is convenient. We loop once for each remaining species, drawing our bugs in the correct order and testing for collisions with potential food.

 

 970         predradius=SQR(bugs{(a)}.energy/PI)

 980         CASE pred OF

 990           WHEN 1 : GCOL 0,1

1000           WHEN 2 : GCOL 0,7

1010           WHEN 3 : GCOL 0,4

1020         ENDCASE

1030         CIRCLEFILL bugs{(a)}.x,bugs{(a)}.y,predradius

 

As with Greens, the radius of a bug is equal to its energy. Colour depends on the species.

 

1050         FOR f=0 TO MAXBUGS

1060           IF bugs{(f)}.status THEN

1070             food=bugs{(f)}.type

1080             IF pred=food+1 THEN

 

Inner loop 'f' cycles through every bug, selecting the living and choosing only those bugs that are potential food for the bug from loop 'a'.

 

1090               foodradius=SQR(bugs{(f)}.energy/PI)

1100               xd=bugs{(a)}.x-bugs{(f)}.x

1110               yd=bugs{(a)}.y-bugs{(f)}.y

1120               IF SQR(xd^2+yd^2)<predradius+foodradius THEN

1130                 bugs{(a)}.energy+=20:REM Eating

1140                 bugs{(f)}.energy-=20:REM Being eaten

1150                 IF bugs{(f)}.energy<=MINENERGY PROCreturnslot(f)

1160               ENDIF

1170             ENDIF

1180           ENDIF

1190         NEXT

 

If the current 'predator' bug being processed overlaps a potential food source then the predator gains energy while its prey loses energy. If too much energy is lost then the prey expires.

 

1210       ENDIF

1220     NEXT

1230   NEXT

1240  

1250   *REFRESH

1260   WAIT 4-TIME

1270 UNTIL FALSE

1280 END

 

Line 1250 issues the *REFRESH command, instructing BB4W to display our latest animation frame. Note than any new printing or drawing will still be directed to the hidden window!

 

1300 DEF PROCnewbug(i,speed,x,y,xvel,yvel,energy,walk)

1310 IF i<>-1 THEN

1320   bugs{(i)}.speed=speed

1330   bugs{(i)}.x=x

1340   bugs{(i)}.y=y

1350   bugs{(i)}.xvel=xvel

1360   bugs{(i)}.yvel=yvel

1370   bugs{(i)}.energy=energy

1380   bugs{(i)}.walk=walk

1390 ENDIF

1400 ENDPROC

 

PROCnewbug populates the 'bugs' array with the parameters provided, unless 'i' is equal to minus one (indicating no room). The value 'i' is obtained from a call to FNgetslot.

 

1420 DEF FNgetslot(type)

1430 LOCAL i

1440 IF stackptr=MAXBUGS OR counts(type)>=MAXTYPE THEN =-1

1450 i=stack(stackptr):stackptr+=1

1460 counts(type)+=1

1470 bugs{(i)}.status=TRUE

1480 bugs{(i)}.type=type

1490 =i

 

FNgetslot checks for an unused row in the 'bugs' array and also checks the current count of the relevant species.

If there is space then the relevant species count is incremented, the status of the new bug is flagged as active and we store the type of the bug in the 'bugs' array.

 

1510 DEF PROCreturnslot(i)

1520 stackptr-=1:stack(stackptr)=i

1530 counts(bugs{(i)}.type)-=1

1540 bugs{(i)}.status=FALSE

1550 ENDPROC

 

PROCreturnslot is the mirror image of FNgetslot.

No checks are necessary. A pointer to the row is added to our stack, making the row available for the next bug to be created. The relevant species count is decremented and the status of the bug is flagged as inactive.

 

Similar to Rocket Rotation, this program provides an intermediate step to understanding the Asteroid Belt game.

Arrow black large Arrow black large