Displaying a count down (non-blocking)
This lesson demonstrates how to create a non-blocking count down in LiveCode, this is done using the send command to manually send custom messages.
The example used here is the endless song 99 Bottles of Beer on the Wall. The example displays the song interactively, one verse at a time.
The mouseUp message
The mouseUp message is sent when the user clicks an object, in this case, a button. It's possible to include this code in a different handler; putting it in a button's mouseUp handler is just convenient for this example. When the mouseUp message is sent to the button, it triggers the mouseUp handler.
99 Bottles of Beer on the Wall
This handler displays the endless verses of the song "99 Bottles of Beer on the Wall". In case you're not familiar with the song, it goes like this:
99 bottles of beer on the wall, 99 bottles of beer
You take one down and pass it around, 98 bottles of beer on the wall.
98 bottles of beer on the wall, 98 bottles of beer
You take one down and pass it around, 97 bottles of beer on the wall.
...and so on. The song continues, decreasing the number of bottles by one with each verse, until there's just one bottle left:
One more bottle of beer on the wall, one more bottle of beer
You take one down and pass it around, no more bottles of beer on the wall.
No more bottles of beer on the wall, no more bottles of beer
You go to the store and buy some more, 99 bottles of beer on the wall.
99 bottles of beer on the wall...
...and the song starts over from the beginning.
The mouseUp handler
The example displays the song interactively, one verse at a time. When the user clicks, if the song is already going, the mouseUp handler stops it. Otherwise, it starts up the song from the beginning by sending a message called displayBottleVerse that shows the first verse of the song. The displayBottleVerse handler calls itself, after a one-second delay (to give the user time to read the verse). After the delay, the displayBottleVerse handler displays the next verse. The cycle continues for all the verses, then starts over again, until the user clicks the button and stops the song.
Setting up constants and script local variables
A constant declaration at the top of the script declares a constant value called cStartBottles which is equal to 99. This constant is declared for convenience. We could simply write "99" instead of cStartBottles in the two places where this constant is used, and the script would work the same way, but the use of a constant makes it easier to see how many bottles the song starts with when you're reading the script. It also makes it easier to change the starting verse, in case we ever decide to make this song "215 Bottles of Beer on the Wall" instead.
Next, the script declares a script local variable called sBottlesLeft. This is the current number of bottles, and starts off at 99. Because this variable is declared outside any handler, it retains its value for the entire session. If you declare a local variable inside a handler, or just use it without a local declaration, then the variable's value is lost when the handler finishes executing. This is important, because the displayCurrentVerse handler is executed once for each verse. The sBottlesLeft variable keeps track of which verse is next between executions.
The second local declaration creates a variable called sCurrentVerseMessage. Because it is a script local variable, it retains its value for the current session, and it's available to all handlers in this script. We'll see in a moment how this variable is used to stop the song.
constant cStartBottles = 99
local sBottlesLeft = 99
Starting the song
The mouseUp handler checks the sCurrentVerseMessage variable to see whether there's anything in it. We didn't specify a value when declaring this variable, so it starts out empty. If it's still empty, the handler sends the displayCurrentVerse message to show the first verse of the song.
on mouseUp if sCurrentVerseMessage is empty then ## song hasn't started ## start the song displayCurrentVerse else ## song is already going, so stop it cancel sCurrentVerseMessage put empty into sCurrentVerseMessage put empty into field "Song" end if end mouseUp
The displayCurrentVerse handler
The handler, displayCurrentVerse, uses the bottlePhrase function handler to construct the text of the first verse of the song, and puts it in the field named "Song". It subtracts 1 from the sBottlesLeft variable. Then it sends a displayCurrentVerse message again after a one-second delay. You can change the delay time, depending on how long you think it will take to read one verse and how long you want the song to take.
on displayCurrentVerse ## show a verse, using the bottlePhrase function, ## and set up the variable for the next verse switch sBottlesLeft case zero ## last verse, zero bottles left put bottlePhrase(sBottlesLeft) && "on the wall," \ && bottlePhrase(sBottlesLeft) & return \ & "You go to the store and buy some more," \ && bottlePhrase(cStartBottles) \ && "on the wall!" into field "Song" put cStartBottles into sBottlesLeft ## start again break default ## all other verses put bottlePhrase(sBottlesLeft) && "on the wall," \ && bottlePhrase(sBottlesLeft) & return \ & "You take one down and pass it around," \ && bottlePhrase(sBottlesLeft - 1) \ && "on the wall!" into field "Song" subtract 1 from sBottlesLeft ## for the next verse end switch send "displayCurrentVerse" to me in 1 second put the result into sCurrentVerseMessage end displayCurrentVerse
The send...in time form of the send command that's used here puts information about the message into a queue called the pendingMessages and assigns a unique number to the pending message. This number is returned in the result function, so our handler can retrieve it. A message waiting in the queue can be canceled later with the cancel command. Since we want to cancel the pending message if the user clicks the button, we need to store the message's number for later use. The next line of the handler stores the message's number in the script local variable sCurrentVerseMessage, which we declared at the top of the script.
One second later, that pending message executes, instructing LiveCode to execute the displayCurrentVerse handler again. The displayCurrentVerse handler shows the second verse, subtracts another bottle, and sends the message again after a one-second delay, and the process continues through all the verses of the song.
When the song reaches its end and sBottlesLeft is zero, the displayCurrentVerse handler sets the sBottlesLeft variable back to the value of the cStartBottles constant we declared at the top of the script. The next time the displayCurrentVerse handler is executed, the song's first verse is displayed again.
The bottlePhrase function code
function bottlePhrase pNumber ## generate the "base phrase" used to make a verse switch pNumber case zero return "No more bottles of beer" break case one return "One last bottle of beer" break default return pNumber && "bottles of beer" end switch end bottlePhrase
Stopping the loop
When the user clicks the button, the mouseUp handler again checks the sCurrentVerseMessage variable to see whether it's empty. If it's not, the song is currently playing and should be stopped. All we need to do to stop the verses is to cancel the pending displayCurrentVerse message that's waiting to be sent. To cancel a pending message, the mouseUp handler uses the cancel command:
cancel sCurrentVerseMessage put empty into sCurrentVerseMessage put empty into field "Song"
This example was inspired by a discussion on the LiveCode mailing list with David Vaughan. The topic was in turn inspired by the "99 Bottles of Beer" site at http://99-bottles-of-beer.ls-la.net, featuring 99 Bottles of Beer code in 454 programming languages (and counting).