IDIC

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.:

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)

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

980         CASE pred OF

990           WHEN 1 : GCOL 0,1

1000           WHEN 2 : GCOL 0,7

1010           WHEN 3 : GCOL 0,4

1020         ENDCASE

1040

1050         FOR f=0 TO MAXBUGS

1060           IF bugs{(f)}.status THEN

1070             food=bugs{(f)}.type

1080             IF pred=food+1 THEN

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

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

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: 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)

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.

980         CASE pred OF

990           WHEN 1 : GCOL 0,1

1000           WHEN 2 : GCOL 0,7

1010           WHEN 3 : GCOL 0,4

1020         ENDCASE

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'.

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

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

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.