Worbles Around the Water-Cooler

by Welopez

Whether you're writing games, graphic displays, or other timed events for your application, once in awhile you're going to say, "Dang! I wish I could use multiple timers in JB."

Suppose you're writing an action game, "Joe Worble: Fighter Pilot." Every 10 seconds, Joe will be attacked by an enemy missile. Every 30 seconds, Joe will be attacked by an enemy fighter. Finally, after 120 seconds, Joe will run low on fuel and have to land. He has to evade multiple missiles, score hits on the enemy fighters, then land within a given amount of time. It would sure be wonderful if we could use a TIMER for each event.

You can add multiple timer events via an API call to calldll #kernel32, "Sleep" when using LB; but when using JB it's necessary to create a "work-around" by writing code to "pause" for a specified period of time.

What do we want to use a timer for? Usually, to specify a brief suspension in program execution and then do something else. We can write code to create a pause and momentarily suspend execution, what's more, we can insert the code in as many places as we like, for as many different timer values as we like. A typical pause routine might look like this:

timeNow=TIME$("ms")
WHILE TIME$("ms") < timeNow + 10000
WEND
timeEnd=TIME$("ms")
PRINT "Execution complete in "; timeEnd-timeNow; " milliseconds."
END

The code above will check the system clock with each pass through the loop until TIME$("ms") equals timeNow + 10000, then exit the loop to execute the next line of code. Open your Task Manager to Performance, then run that short snippet of code.

Wow! Those Worbles keep running back to the system clock to see if it's quitting time, and CPU usage maxes out at 100%! I don't know about you, but I don't like to see my resources maxed out. What we need to do is encourage the Worbles to make a stop at the office water-cooler and exchange gossip briefly, to minimize CPU usage. Let's add a few more lines of code to slow down those Worbles.

timeNow=TIME$("ms")
WHILE TIME$("ms") < timeNow + 10000
GOSUB [breakTime]
WEND
timeEnd=TIME$("ms")
PRINT "Execution complete in "; timeEnd-timeNow; " milliseconds."
END

[breakTime]
TIMER 25, [continue]
WAIT
[continue]
TIMER 0
RETURN

Insert the GOSUB into your WHILE/WEND loop, and add the [breakTime] routine to the end of your code. Now run the code while monitoring system performance.

Success! Those Worbles stopped off at the water-cooler long enough to reduce CPU usage to practically nothing. Now, I'm a happy camper!

Program execution took slightly longer than the 10000 millisecond pause we wanted, but that's because our TIMER inserted a 25 millisecond delay. Execution may have returned from [breakTime] while timeNow=timeNow + 9985 milliseconds, and the Worbles had to make another trip. We can't be precisely accurate, but an error of .025 seconds is pretty much within the ballpark.

In the above code, we've used the only TIMER allowed in JB, but we can write as many WHILE/WEND loops or GOSUBs as we like, and send program execution to [breakTime] as often as needed.

'Multiple Timed Events
NOMAINWIN

begin=TIME$("ms")
WindowWidth=600
WindowHeight=400
UpperLeftX=INT((DisplayWidth-WindowWidth)/2)
UpperLeftY=INT((DisplayHeight-WindowHeight)/2)
offset1=5 : y1=300  'Initialize values for "down" coordinate
offset2=5 : y2=300
offset3=5 : y3=300

OPEN "See the Bouncing Worbles!" FOR GRAPHICS_NSB AS #grph
PRINT #grph, "trapclose [quit]"
PRINT #grph, "down"
[reDo]
WHILE TIME$("ms") < begin+15000  'Limit to 15 seconds then exit
    PRINT #grph, "discard"
    PRINT #grph, "FILL 204 204 255"
    GOSUB [red]
    GOSUB [blue]
    GOSUB [yellow]
    GOSUB [breakTime]
WEND

PRINT #grph, "backcolor 204 204 255"
PRINT #grph, "color BLUE"
PRINT #grph, "PLACE 25 80"
PRINT #grph, "\All Worbles have died.  Program terminated."
PRINT #grph, "PLACE 25 100"
PRINT #grph, "\We'll magically resurrect them in 2.5 seconds."
FOR i=1 TO 100
    GOSUB [breakTime]  'Take a break before restarting program
NEXT i

flag=1  'The cycle of animation is complete
IF flag=1 THEN
    begin=TIME$("ms")  'Reset beginning time
    flag=0  'Reset flag to permit running again
    GOTO [reDo]  'Run it again
END IF

WAIT
[quit]
    CLOSE #grph
    END

[breakTime]  'A brief pause (.025 seconds) to minimize CPU usage
    TIMER 25, [cont]
    WAIT
    [cont]
    TIMER 0
RETURN

[red]
IF TIME$("ms")>begin+9550 THEN  'Check to see if RED event is complete
    PRINT #grph, "PLACE 100 360"
    PRINT #grph, "backcolor RED"
    PRINT #grph, "ellipsefilled 50 10"  'RED goes flat
    PRINT #grph, "backcolor 204 204 255"
    PRINT #grph, "color BLUE"
    PRINT #grph, "PLACE 25 40"
    PRINT #grph, "\The RED Worble pooped out!"
ELSE
    y1=y1+offset1  'If not complete, move the RED Worble
    IF y1<100 OR y1> 340 THEN offset1=(offset1*-1)
    PRINT #grph, "place 100 "; y1
    PRINT #grph, "backcolor RED"
    PRINT #grph, "circlefilled 25"
    PRINT #grph, "flush"
END IF
RETURN

[blue]
IF TIME$("ms")>begin+13600 THEN  'Check to see if BLUE event is done
    PRINT #grph, "PLACE 300 360"
    PRINT #grph, "backcolor BLUE"
    PRINT #grph, "ellipsefilled 100 10"  'BLUE goes flat
    PRINT #grph, "backcolor 204 204 255"
    PRINT #grph, "color BLUE"
    PRINT #grph, "PLACE 25 60"
    PRINT #grph, "\The BLUE Worble lasted longest."
ELSE
    y2=y2+offset2  'If not, move the BLUE Worble
    IF y2<150 OR y2>320 THEN offset2=(offset2*-1)
    PRINT #grph, "place 300 "; y2
    PRINT #grph, "backcolor BLUE"
    PRINT #grph, "circlefilled 50"
    PRINT #grph, "flush"
END IF
RETURN

[yellow]
IF TIME$("ms")>begin+4000 THEN  'Check to see if YELLOW event is done
    PRINT #grph, "PLACE 500 360"
    PRINT #grph, "backcolor YELLOW"
    PRINT #grph, "ellipsefilled 75 10"  'YELLOW goes flat
    PRINT #grph, "PLACE 25 20"
    PRINT #grph, "color BLUE"
    PRINT #grph, "backcolor 204 204 255"
    PRINT #grph, "\The YELLOW Worble jumped too high, too fast!"
ELSE
    y3=y3+offset3  'If not, move the yellow Worble
    IF y3<50 OR y3>340 THEN offset3=(offset3*-1)
    PRINT #grph, "place 500 "; y3
    PRINT #grph, "backcolor YELLOW"
    PRINT #grph, "circlefilled 35"
    PRINT #grph, "flush"
END IF
RETURN

In this code, we've used a WINDOW FOR GRAPHICS to display three bounching Worbles, set the values to move each Worble and the place to begin displaying each Worble. The WHILE/WEND loop will execute for 15 seconds and then expire, at which time we'll reset it with [reDo]. We have three y values because some Worbles will be ascending while others are descending.

After a full cycle of WHILE/WEND, we print a message that we're done, then pause for 100 iterations of the TIMER. A flag is used to indicate we've completed a cycle, then begin is set to a new system time, the flag is reset to zero and execution branches to [reDo] for another cycle.

Throughout the WHILE/WEND loop, GOSUBs branch to actions for each Worble. IF the time alloted for that Worble has expired, THEN he dies, ELSE he is printed at a new location. When all Worbles have been printed, we branch to [breakTime] for a brief pause each time through the loop. By setting different values for each Worble (IF TIME$("ms") < (begin + aValue), each Worble has a different period before action is complete. The number of milliseconds used for aValue was derived by observation since I wanted the Worbles to go flat at or near the bottom of the display.

We still have only one TIMER which for the program, but we've set times for three events using TIME$("ms") and minimized CPU usage by interrupting the WHILE/WEND with a small delay, then restarting the WHILE/WEND loop using a flag.

It's not a requirement that event actions be compared to TIME$. We can also specify a given number of iterations by incrementing variables for multiple flags inside the TIMER loop.

[breakTime]
    TIMER 50, [cont]
    WAIT
    [cont]
    redFlag=redFlag+1
    blueFlag=blueFlag+1
    yellowFlag=yellowFlag+1
    winFlag=winFlag+1
    TIMER 0
RETURN

Changing the value for the timer will determine how quickly (or slowly) the flags are incremented. Using multiple flags, we can perform a comparison for each flag:

	IF redFlag>100 THEN/ELSE...
	IF blueFlag>200 THEN/ELSE...
	IF yellowFlag>300 THEN/ELSE...  

We can exit any event using different values, including the WHILE/WEND loop, using winFlag, which limits the actions for all graphics in the window. We can also reset individual flags to zero (or any intermediate value) to begin the actions for that event again.

Now you have several more tools your toolbox for JB. You can time multiple events for Joe Worble: Fighter Pilot, or perhaps it's Brenda Worble: Girl Reporter, who has to get her story completed before a deadline, perhaps spend a night in jail for refusing to name her sources, all the while avoiding bullets and car chases by the Meanie Worble Gang.

Hey! How cool is this stuff?