Welcome to Beginner Programming 
Lesson #5

Course content Enter chat room Send email
to mailing list
Check calendar

In this lesson we are going to learn about loops.   By now you have heard us say that computers are not very smart.   Back in lesson #2 we introduced the if ... then ... else  branching construct.   With that our programs could change to a different section of code depending on the value of a variable.    This could make our programs appear to be more intelligent.   Another thing computers are good at is repetition ... they never get tired or bored of repeating things over and over again.   This is what programmers use loops for ... to cause the program to "back up some steps" and repeat a section of code.   Loops are another "trick" programmers use to make computer programs appear to be intelligent.

We are going to learn about a very powerful Tcl command called "after".   This command will be used to produce our animations in future lessons.   For now we are going to be using it to simply put our program to sleep for a period of time.
 
 
We are going to reintroduce comments to our programs.    Comments are very important to make our programs more readable by us humans.    Think of using comments as reminders as to exactly what a particular section of the code is actually trying to do.    If other programmers are to understand what you did ... or even if you are to understand it after you have not been working directly on it for a few days ... comments are very essential.    Of course the "wish" program will simply ignore all comments so they are strictly there for us humans.

 

Step 1: the first part

As we have been doing for several lessons now we will start lesson 5 by opening up your text editor and creating a new file called "lesson5.tcl".

Start by adding the following main section to the code.

If you enjoy typing you can type this program section in by hand.   If you are starting to think more like a programmer you might be asking "Is there not an easier way to create this new program?".

Fortunately, we are dealing with a program that has been progressively becoming more complex over the last few lessons.

Think  "copy and paste".    If you were to open another editor window and inside that open up your lesson4.tcl you could copy out a section of that code which was close to what you need in lesson5 and then paste it into your new lesson5.tcl window.   Then you can simply modify the lines required.
 
 
HINT:
Remember the old saying:
"veteran programmers only write one truely new piece of code ... they simply make a copy and enhance that everytime they need something new"
In programming circles this is called "code reuse".

 
 
#========================================
#  main - entry point
#========================================
destroy  .myArea

#
# setup all the frames
#
set f  [ frame .myArea -borderwidth 5 -background blue ]
set f_a [ frame $f.animateFrame -background lightBlue ]
set f_b [ frame $f.buttons ]

puts stdout "lesson5 starting"

#
# initialize some variables
#
set  lastcolor  red
set  go  0
set  x_i t 0
set  maxtime  31

#
# set up our animation canvas
# including a timer window in top left corner
#
canvas  $f_a.c  -height  200  -width  300  -background  lightBlue
set f_m  [ frame $f_a.c.m ]           ;# timer window
$f_a.c  create  window  0  0  -anchor  nw  -window  $f_m
label  $f_m.count -text "0 sec"  ;# label in timer window
pack  $f_m.count
pack  $f_a.c  -side top

#
# set up our go/stop button as a go button
#
button $f_b.gostop -text "go" -background green \
	-command  { set go  [ run_halt $go ] }
pack $f_b.gostop -side left

#
# set up a quit button
#
button $f_b.quit -text "quit" \
	-command  { set  go  0;  set  x_it  1 }
pack $f_b.quit -side right

#
# arrange the animation frame and button frame
# in main frame
#
place $f_a -x 10 -y 10
place $f_b -x 10 -y 250

#
# make the whole thing visible
#
pack $f -side top -expand true -fill both

#
# add the stop light onto the canvas
#
createStoplight

#
# enter the main loop
#
while  {  $x_it = = 0 }  {
	vwait go ;# block until some sets the "go" variable

	for { set i 1 }  {$i < $maxtime}  { incr i }  {
		set x 0
		after 1000 { set x 1 }  ;# setup pause for 1 sec
		vwait x
		handleTime $i

		if  { $go = = 0 } 
			break
	}  ;# end for

	if  { $i  = =  $maxtime }  {
		set  x_it  1 
	}

}  ;# end while

#
# disable both of the buttons
#
$f_b.gostop config -state disabled
$f_b.quit config -state disabled

puts stdout "lesson5 done"

The first thing to notice about this code snip is the addition of several comment blocks such as:
 
#
# disable both of the buttons
#

Remember the comments should explain what a section of code is doing in plain English ... not in Tcl eez.

The first new section of code we encounter in this code block is:
 
set f_m  [ frame $f_a.c.m ]              ;# timer window
$f_a.c   create  window  0  0  -anchor  nw  -window  $f_m
label  $f_m.count  -text  "0 sec"   ;# label in timer window
pack  $f_m.count

While this would appear to be very complicated ... well it actually is.

The canvas widget is primarily designed for us to put drawing shapes on it.   However, occasionally it is desirable to superimpose some of the other widgets such as buttons, labels etc. right onto the canvas area.    For this purpose the Tcl/Tk designers added the canvas object called a "window".    I think they chose to call it a window just to get us all confusing it with the top level Tk window we have come to already know.   For now let's just accept that the first two lines above create a frame which is placed directly on the canvas in much the same manner as we did for rectangles and ovals.

The more interesting part of those lines above is the introduction of a new widget called a "label".     The label widget is pretty basic.   It is used to display something but cannot be interacted with very much.   In our case we are creating a label called "count" inside our new canvas window thingy and we are placing the text string "0 sec" inside our label.    The "sec" in our string stands for "seconds" and we will be using our label as a digital clock.

The next new and interesting part of this code snip begins with the line below
 
#
# enter the main loop
#
while  { $x_it  = =  0  }  {
	vwait go     ;# block until some sets the "go" variable

This introduces the first of our two types of looping constructs ... the while loop.

The first thing you may have noticed is that a "while" statement looks very much like an "if" statement.    If you made that observation you'd be correct.

The first section inside { } immediately following the "while" command is called the condition.    Just as it is in the "if" statement it can have a "true" or "false" result.   The while statement tells the computer to execute the block of code which follows (exactly as it does in an "if" statement) until the condition becomes false.

It is important to understand this last sentence ... because the "while" command is one of the most powerful programming techniques in your toolbox.

The first command inside the "while" code block is the vwait statement above.

To fully understand what is going on here is probably beyond the scope of an introductory course.

But that is the beauty of programming ... you don't necessarily have to fully understand the background behind every command in order to be able to use them successfully in a piece of code.

For now think of the vwait as a kind of "pause until" statement.    In this case we are saying pause here until someone changes the variable called go (ie. invokes the command "set go").     The legitimate first question you may ask is "If my program is paused ... how can a line of code run to "set go"? ... won't my code sit on the vwait statement forever?".

The answer lies in our "-command" arguments that we added to our button widgets.    It turns out that even though our code is paused at the vwait statement ... the Tcl/Tk wish program is still going to allow us to interact with our button widgets.   This is an example of what in computer programming is known as "threading".

Programs can have multiple threads or paths of execution seemingly at the same time.     This is yet another way that computer programs appear to be intelligent ...  when in fact all they are are fast.

If you scan back a couple of lines in the code snip we are calling lesson5.tcl you will find exactly such a command attached to a button called "quit".
 
button  $f_b.quit  -text "quit" \
	-command  { set  go  0;  set  x_it  1 }

This command may look a little strange to us at first.    It is the first time we have used a semi colon ";" to place multiple statements together on the same line.    It is also the first time that we have actually placed the contents of our "command" directly inside the { } rather than in a separate "proc" block.     This technique is what is referred to in programming world as placing the functionality inline or simply an inline function.      We could have just as well created a separate proc and placed the two statements into that:
 
proc  whatyamacallit  {  }     {
set go 0
set x_it 1
}    ;# end whatyamacallit

The two techniques produce exactly the same result.    It is just more convenient (and easier to read) if we place our really short sequences "inline".

The bottom line is we will kick the code out of the vwait statement's pause whenever we hit the quit button.

For those of you who are ahead of me ... you might be asking "That's a pretty stupid program ... it does nothing until you hit quit then it does some things ... I thought "quit" would mean "quit" ".

You'd be absolutely correct.     We need to look closely at our lesson5.tcl for evidence of another place where "set go" is invoked on a button command.   Sure enough there is the following bit as well:
 
button  $f_b.gostop -text "go" -background green \
	-command  { set go  [ run_halt  $go ] }

It appears we can kick the vwait statement out of its pause by hitting the "go" button also.     This certainly makes a whole lot more sense ... hitting "go" should start something in our program.

Once the vwait kicks out the next  statement in our program  is an example of  the other loop construct called a "for loop".     The for loop is shown below:
 
for  { set  i  1 }  { $i  <  $maxtime }  { incr i }   {
	set x 0
	after 1000  { set  x  1 }    ;# setup pause for 1 sec
	vwait x
	handleTime $i

	if  { $go  ==  0  }  break;
}  ;# end for

A for loop begins with a statement started by the "for" command.   Unlike the "if" and "while" statements we have already encountered this statement certainly looks very strange ... we have 3 argument blocks inside { } before we begin the "for" statements' own code block with a single open {.       The for statement is obviously a bit complicated.   Let's go through it piece by piece.

The first argument in { } is what is called the initialization part.
 
{ set  i  1 }

This will get run only the first time you encounter a given for statement  (ie. it will not be run each time through the loop).     This is very powerful indeed.    Think about all the extra "if else" logic you are saving by not having to code something special for the first time around the loop.     While it is not always the case, the "for" command's initialization section is often used (as we have done) to set the initial value of what will become a counter.

The second argument in { } is what is called the condition or test part.
 
{ $i  <  $maxtime }

This is checked each time around the loop ... just as it would be in a "while" loop statement.     We will continue executing the loop block until this condition becomes false.   In our case we are going to loop as long as our counter ($i) is less than some value ($maxtime).

The third argument in { } is what is called the final part.
 
{ incr i }

In our case we are chosing to use the built in Tcl/Tk command "incr" (which is short for increment) to increase our counter (i) by 1 each time around the loop.    The important thing to recognize about this part of the "for" statement is that it is executed at the end of the loop block on each iteration of the loop.  In other words the value of $i is NOT INCREMENTED BEFORE THE FIRST TIME THE LOOP GOES THROUGH ... but will be incremented at the end of the loop block before the second time around the loop.

The "for" loop is probably more used as a loop construct in software programs than the "while" loop.   In fact the same functionality as provided by the while loop can be made if the initialization and the final sections of a "for" statement are left blank.   The statement
 
for { }  { whatever condition }  { }   {

is exactly equivalent to
 
while  { whatever condition }  {

The bottom line is that our little "for" block will execute exactly one less that $maxtime times.

Why one less you say?

If we snoop around in the lesson5.tcl code snip we find a line
 
set  maxtime  31

Recall that our condition said we will loop as long as the value of our counter is less than 31.    Well the last time that condition will be true is when our counter is equal to 30.    We will be bounced out of our loop block on the 31st iteration.  In other words our little loop will go around 30 times.   But the value of $i when the loop is bounced out of will be 31 ... we just won't execute the loop block with $i equal to 31.
 
 

Don't worry this one less stuff ... it is confusing to everyone when they first encounter for loops.

That brings us to the next important point about loops.

Where do they bounce to when the conditions are false.

The answer is simple.

They bounce to the first executable statement immediately after the closing } in the loop block.   In the case of our "for" loop we have labeled that last } using an inline comment as:
 
}  ;# end for

It is a good practice to get into the habit of labeling the end of loop blocks in this manner ... otherwise you will be counting { } pairs to figure out exactly where the loop ends in anything but the simplest of code.

Now that we are beginning to understand loops themselves we will turn our attention to what we are going to be doing inside the "for" loop block itself.    The first 3 statements in the for block are:
 
set  x  0

after  1000  { set  x  1 }    ;# setup pause for 1 sec
vwait  x

Let's work though these because they are a little tricky.

First of all we have introduced (as promised) the first of our "animation" commands ... "after".    The "after" command tells the Tcl/Tk wish program that you want to set up a command (in { }) to be executed at some future time.     The future time is measured relative to now.    In English you would say "I want you to execute the statement    set x 1    after 1000 time units have elapsed from right now  ... but I want you to continue executing the statements which follow this one as soon as you have set this up for me".    In other words the "after" command does not pause the program in any way.
 
 

NOTE:
The after statement is kind of like an alarm clock.    Once you set the alarm time the clock continues to show the time.   When the alarm time is reached ... something new happens ... the radio comes on or a beeper sounds.

What are the time units we use you ask?    They are in units of 1/1000 th of a second or what is known as a millisecond.     Modern computers are very fast.   My computer here can do 200000 separate things inside that single millisecond.     So you can see that for us humans a millisec is less than a blink of the eye ... but to a computer is the equivalent of the entire school year.

Continuing with understanding our little 3 line construct above we see that the "after" command is preceded by a "set x 0" command and followed by our friend the "vwait" command.     Recall that vwait is in fact a program pause.   It will pause until the variable it is waiting on (in this case x) has been reset.

Who is going to reset the value of "x"?

It will be reset after 1000 millisecs by the "set x 1" that we told the wish program to run at that time.    In other words we have set up a clock that will "tick" every 1000 milliseconds (which means every second).

In Tcl/Tk eeze ... the "vwait x" command will be unpaused every 1000 milliseconds.

So in other words if we examine the statements which follow the "vwait x" we see that we have arranged for a command called:
 
handleTime   $i

to be run every second.   Since this whole thing is inside a "for" loop designed to run 30 times ... our little construct will run for 30 seconds and then exit.

We will come to the "handleTime" function shortly.

Meanwhile we need to discuss the line which follows the handleTime:
 
if  { $go  = =  0  }  break;

To understand what this line is doing we need to start with the question

"Supposing we change our mind after we have started our little loop running and don't wish to wait the 30 seconds before stopping it ... how do we break out of our loop?".

 This kind of "unplanned" change is known in everyday life as an interuption.

Well in computer programming it is known as an interuption statement.   What we are saying is "As long as the value of the variable called "go" is not equal to 0 we are going to allow the loop to continue.    As soon as the value of "go" gets set to 0 we want to "break" out of the loop before it normally would have.

That is the purpose of the "break" statement  ... allows us to code for interuptions.

The ability to respond to interuptions is yet another way computers appear to be intelligent.     In fact the computer is not intelligent at all ... the software programmer is.

The final loop statement that we need to discuss before moving on to the "fun" part of lesson5 is the last statement in the while loop ... which also happens to be the first statement executed after the "for" loop bounces out:
 
if { $i  = =  $maxtime }  { set  x_it  1 }

}  ;# end while

You will recall that the final section of a "for" loop statement gets executed  after each time through the loop.    Well it also gets executed the last time.   In our case our "for" loop executes with our counter equal to 1,2,3 ... 28,29, 30.   The last time round the value gets set to 31  at which point our condition statement which says "continue as long as $i is less than 31 suddenly becomes false and the "for" loop bounces out.     So at the point where the for loop bounces out the value of $i will be 31 (or maxtime).    What we are doing here is then {set x_it 1}  ... in other words insuring that our "while" loop and our "for" loop will exit together.

In other words we want our little "animation" to run for 30 seconds and then stop.
 

Step 2: the second part

It is now time to make our lesson5 program actually do something.   For those of you that tried to run the program as it now stands you will have discovered that it is missing code for the following commands we have used:

    hogwarts
    createStoplight
    switchStoplight
    handleTime
    run_halt

This is actually a good way to design your programs in the future.    Create a main section which sets up widgets and then ties all the bits together.    Then write all the bits.

Fortunately, we don't have to write stuff completely over from scratch.    You will recognize some of these functions from lesson4.    Using whatever method you are comfortable with you need to add the following code to the beginning of your lesson5.tcl file.    As you did in lesson4 this part must push the main section to the bottom of the file.
 
 
#=======================================
# hogwarts - entry point
#=======================================
proc hogwarts  { mycolor }   {
global f_a
global f_b

#puts stdout  [ format "lastcolor was %s" $mycolor ]

if  { $mycolor  = =  "red" }   {
	$f_a.c itemconfigure redback -fill black
	$f_a.c itemconfigure greenback -fill white
	set newcolor green
}  else  {

	if  { $mycolor  = =  "yellow"  }   {
		$f_a.c itemconfigure yellowback -fill black
		$f_a.c itemconfigure redback -fill white
		set newcolor red
	}  else  {

		$f_a.c itemconfigure greenback -fill black
		$f_a.c itemconfigure yellowback -fill white
		set newcolor yellow
	}
}

#puts stdout [format "newcolor is %s" $newcolor]
return $newcolor
}  ;# end of proc hogwarts


#========================================
# createStoplight - entry point
#========================================
proc createStoplight {} {
global f_a

$f_a.c create rectangle \
	100 25 150 75 -fill white -tag redback
$f_a.c create oval \  
	100 25 150 75 -fill red -tag redlight

$f_a.c create rectangle \
	100 75 150 125 -fill black -tag yellowback
$f_a.c create oval \
	100 75 150 125 -fill yellow -tag yellowlight

$f_a.c create rectangle \
	100 125 150 175 -fill black -tag greenback
$f_a.c create oval \
	100 125 150 175 -fill green -tag greenlight

} ;# end createStoplight


#========================================
#  switchStoplight - entry point
#========================================
proc switchStoplight { }   {
global lastcolor

set lastcolor [hogwarts $lastcolor]

}  ;# end switchStoplight


#========================================
#   handleTime - entry point
#========================================
proc handleTime  { count }   {
global f_m

$f_m.count config -text [format "%d sec" $count]

if  { ($count % 5) = = 0 }   {    ;# switch every 5 seconds
	switchStoplight
}

}  ;# end handleTime



#========================================
# run_halt - entry point
#========================================
proc run_halt { go }   {
global f_b

if  { $go  = =  0  }   {    ;# must have been "go" showing
	set go 1
	$f_b.gostop config -text "stop" -background red
}  else  {          ;# must have been "stop" showing
	set go 0
	$f_b.gostop config -text "go" -background green
}

return $go

}   ;# end run_halt

The proc hogwarts has been lifted essentially unchanged from lesson4.    If you are still unclear about things inside this code block please return to lesson4 for review.   The only real change we have made is to comment out some of the "puts" commands to make this routine "less chatty" on the wish console window.    We have done this in order to leave the "puts" commands inside the file just in case we need to reactivate them at some future point in order to understand a bug.

The proc createStoplight is also essentially unchanged from lesson4.    You will recall that this code block is responsible for drawing our stoplight onto the canvas.    If you are unclear about anything in this code block please review lesson4.

The first new procedure we have introduced in this lesson5 is "handleTime".    If you recall from the discussion above we had programmed such that this handleTime block would be executed exactly every second for 30 seconds.   The first two statements in this procedure concern that little window thingy that we created in the corner of our canvas screen into which we put a label.    At the time we mentioned that it was going to become a digital clock.    Well here is how we make it happen.
 
global f_m

Recall that this line simply tells the wish program that our variable "f_m" has been defined elsewhere and we want to use that variable here.   The second line is a somewhat familiar statement which uses the "config" argument to permit us to change a widget attribute on the canvas.   In the scott procedure we used a variation of this called itemconfigure to set the color of our lights.   In this case we are changing the text in our label to read the seconds as they count up to 30.
 
$f_m.count config -text [format "%d sec" $count]

The next statement introduces a brand new Tcl/Tk operator that many of you may not yet have encountered in math at school.
 
if  { ($count % 5)  = =  0 }   {    ;# switch every 5 seconds

Most of you are very familiar with addition (+), subtraction (-), multiplication (*) or division (/) operators already.    The operator in this statement (%) is what is known in programming as the modulus operator.     It is just a big fancy name for "give me the remainder that you get when you perform a division".     In other words if you have a number ... say 4 and you divide it by 2 ... it will go 2 times with no remainder  (4 % 2 would equal 0).    If instead that number were 5 you will still be able to go 2 times ... but you would end up with a remainder of 1 (5 % 2 would equal 1).

In our little program we have arranged to kick our handleTime procedure every second ... but we don't want to switch our stoplight every second ... unless we want drivers with a very bad case of road rage.     What we really want to do is switch our stoplights more slowly ... like every 5 seconds.     We could have coded a bunch of if statements to check for count = = 5 , count = = 10, count = = 15 etc.  ... but that is certainly a lot of work and is not very general.    What would happen if we suddenly went to our main section of code and decided to make our program run for 60 seconds?.   If we had placed the above if statements in our code then our light would stop working after 30 seconds.    But if we take advantage of the modulus operator we can get our light to switch whenever the result of a division between our second counter and 5 yields no remainder.   ie. 5, 10, 15, 20, 25 etc.    Now we have a more general solution that doesn't need to be changed if the timer is extended to 60 seconds.

As you gain more experience as a programmer you will begin to recognize constructs like this which are general and extendable.   In programming there is never a single "correct" answer to a problem.    In fact there are usually as many answers as there are programmers working on the problem.    All of the solutions will work correctly but only some of them will be easy to enhance and extend.   In the real world software costs lots of money to develop ... but it costs many times more than that to maintain and enhance over its useful life.    So programmers always strive to build programs which are easy to extend to try and lower this enhancement cost.

The action we are telling the computer to take every 5 seconds is contained in our little procedure called:
 
switchStoplight

and it does ... you guessed it ... switches the stoplight from green to yellow to red and back to green.   It is a good programming practice to try to select your procedure names and variable names to help tell what they actually do.
 
 
#========================================

#  switchStoplight - entry point
#========================================
proc switchStoplight  { }   {
global lastcolor

set lastcolor [hogwarts $lastcolor]

}   ;# end switchStoplight

We have kept the "hogwarts" procedure in our code only to emphasize that commands can be created with any name we please.

We have "borrowed" the line which actually switches the stoplight from lesson4:
 
set lastcolor  [ hogwarts  $lastcolor ]

The final procedure we need to complete lesson5 is called run_halt.    Here we have borrowed the technique used in lesson2 for toggling the attributes of our "go" button between "go" and "stop" each time it is pressed.
 
#========================================
# run_halt - entry point
#========================================
proc run_halt  { go }   {
global f_b

if  {  $go  = =  0  }   {    ;# must have been "go" showing
	set go 1
	$f_b.gostop  config  -text  "stop"  -background  red
}  else  {          ;# must have been "stop" showing
	set go 0
	$f_b.gostop  config  -text  "go"  -background  green
}

return $go

} ;# end run_halt

Notice that we are influencing our for and while loops by returning the value of the $go variable.    There is some subtle programming things happening here that are worth mentioning.

The variable name we have chosen is "go".    We have used that variable name elsewhere in the program.    However, we have not declared "go" to be global in the run_halt routine.     So in fact the variable "go" in the run_halt procedure is different from the variable "go" used elsewhere in the program.    This is what is known in programming world as "scope of a variable".    The "go" is said to have "local scope"  ie. its influence is restricted to the run_halt routine itself.
 

Step 3: the fun part

Go ahead and run your program now.   You should see a screen which is very similar to that below:


 

When you press the "go" button the stop lights should operate for 30 seconds and switch every 5 seconds.

This lesson marks the end of the introduction of the fundamental programming constructs available in Tcl/Tk.   For the balance of the course we are going to focus on applying what we already know for the purpose of creating a cartoon animation sequence.

The final listing for lesson5 is:
 
 
#========================================
#
#    iCanProgram Beginner Lesson#5
#
#========================================
#=======================================
# hogwarts - entry point
#=======================================
proc hogwarts  { mycolor }   {
global f_a
global f_b

#puts stdout  [ format "lastcolor was %s" $mycolor ]

if  { $mycolor  = =  "red" }   {
	$f_a.c itemconfigure redback -fill black
	$f_a.c itemconfigure greenback -fill white
	set newcolor green
}  else  {

	if  { $mycolor  = =  "yellow"  }   {
		$f_a.c itemconfigure yellowback -fill black
		$f_a.c itemconfigure redback -fill white
		set newcolor red
	}  else  {
		$f_a.c itemconfigure greenback -fill black
		$f_a.c itemconfigure yellowback -fill white
		set newcolor yellow
	}
}

#puts stdout [format "newcolor is %s" $newcolor]

return $newcolor

}  ;# end of proc hogwarts



#========================================
# createStoplight - entry point
#========================================
proc createStoplight { }   {
global f_a

$f_a.c  create  rectangle \
	100  25 150  75  -fill  white  -tag  redback
$f_a.c  create  oval \
	100  25 150  75  -fill  red  -tag  redlight

$f_a.c  create  rectangle \
	100  75  150  125  -fill  black  -tag  yellowback
$f_a.c  create  oval \
	100  75  150  125  -fill  yellow  -tag  yellowlight

$f_a.c  create  rectangle \
	100  125  150  175  -fill  black  -tag  greenback
$f_a.c create oval \
	100  125  150  175  -fill  green  -tag  greenlight

}   ;# end createStoplight


#========================================
#  switchStoplight - entry point
#========================================
proc  switchStoplight { }   {
global  lastcolor

set  lastcolor  [ hogwarts $lastcolor ]

}   ;# end switchStoplight

#========================================
#   handleTime - entry point
#========================================
proc  handleTime  { count }   {
global  f_m

$f_m.count  config  -text [ format "%d sec"  $count ]

if  { ($count % 5)  = =  0 }   {    ;# switch every 5 seconds
	switchStoplight
}  ;# end if

}  ;# end handleTime



#========================================
# run_halt - entry point
#========================================
proc  run_halt  { go }   {
global  f_b

if  { $go  = =  0  }   {     ;# must have been "go" showing
	set go 1
	$f_b.gostop config -text "stop" -background red
}  else  {     ;# must have been "stop" showing
	set go 0
	$f_b.gostop config -text "go" -background green
}  ;# end if

return  $go

}   ;# end run_halt



#========================================
#  main - entry point
#========================================
destroy  .myArea

#
# setup all the frames
#
set  f  [ frame .myArea  -borderwidth  5  -background  blue ]
set  f_a [ frame  $f.animateFrame  -background  lightBlue ]
set  f_b [ frame  $f.buttons ]

puts stdout "lesson5 starting"

#
# initialize some variables
#
set  lastcolor  red
set  go  0
set  x_it  0
set  maxtime  31

#
# set up our animation canvas
# including a timer window in top left corner
#
canvas $f_a.c  -height  200  -width  300  -background  lightBlue
set  f_m  [ frame $f_a.c.m ]           ;# timer window
$f_a.c  create  window  0  0  -anchor  nw  -window  $f_m
label  $f_m.count -text "0 sec"  ;# label in timer window
pack  $f_m.count
pack  $f_a.c  -side top

#
# set up our go/stop button as a go button
#
button  $f_b.gostop  -text  "go"  -background  green  \
	-command  { set go  [ run_halt $go ] }
pack  $f_b.gostop  -side  left

#
# set up a quit button
#
button  $f_b.quit  -text  "quit"  \
	-command  { set  go  0;  set  x_it  1 }
pack  $f_b.quit  -side  right

#
# arrange the animation frame and button frame
# in main frame
#
place  $f_a  -x  10  -y  10
place  $f_b  -x  10  -y  250

#
# make the whole thing visible
#
pack  $f  -side  top  -expand  true  -fill  both

#
# add the stop light onto the canvas
#
createStoplight

#
# enter the main loop
#
while  {  $x_it = = 0 }  {
	vwait go ;# block until some sets the "go" variable

	for { set i 1 }  {$i < $maxtime}  { incr i }  {
		set x 0
		after 1000 { set x 1 }  ;# setup pause for 1 sec
		vwait x
		handleTime $i

		if  { $go = = 0 } 
			break
	}  ;# end for

	if  { $i  = =  $maxtime }  {
		set  x_it  1 
	}

}  ;# end while

#
# disable both of the buttons
#
$f_b.gostop  config  -state  disabled
$f_b.quit  config  -state  disabled

puts stdout "lesson5 done"

 

Summary

You have been introduced to Tcl/Tk loops in this lesson.   Let's review:

Exercises

  1. Try extending your little program to add different length of pauses between the red and green and green and yellow than between the yellow and red transitions.    This is much like a real stoplight would work.    The red and green lights would be on longer than the yellow light would be.
As before we will leave this exercise for you to complete on your own or by soliciting help from fellow students on the mailing list.

End of Lesson 5.