Since the Procedure Inputs lesson, you have been using one kind of variable. Inputs allow you to write procedures which do the same sort-of thing, but any single execution of the procedure results in action that depends upon values you provide when you invoke the procedure. As examples, you’ve written a procedure that drew a house with a size specified by height and width inputs and it was drawn at a location specified via inputs. You’ve used inputs along with recursion as an advanced form of iteration to draw trees, a Koch snowflake, and to graph equations.
But there are two characteristics of inputs which limit what we can do with them:
- for each input declared, a corresponding value (an actual argument must be provided when the procedure is invoked, and
- inputs can only be accessed within the body of the procedure in which they were declared (in the procedure’s title line).
In this lesson, you will learn about a new kind of variable that can be declared inside your procedures and a kind of variable which can exist outside of any specific procedure. In computer science jargon, the accessibility of a variable (where it can be accessed) is called the scope of the variable.
I’m going to start off with an example where a local variable is used to save the day – to fix a bug in a simple drawing program. This is followed by walking through the process of writing a program which is a bits-vs-atoms copy of some art stuff I did as a child – stringart. In it, a local variable is used similarly to the way one was used in the first example. Then, I’ll get you started on an exercise where you can extend the tree program you wrote in the last lesson. With a local variable, you can draw trees that are much more real looking.
In the second half of the lesson, I introduce the concept of a global variable. We use global variables in the final project in this lesson – a simple four-function calculator.
Two Kinds of Variables (Globals vs Locals)
Inputs are variables that are created and filled with values when a procedure is invoked. The names of the variables, are given on the procedure’s title line. These identifiers can be used in the instructions which make up the body of the procedure. Inputs are also called local variables – they are local to the procedure in which they are defined.
Just to refresh your memory, given:
to box :size repeat 4 [ forward :size right 90 ] end box 100
The first three lines are the definition of the procedure box which includes the input identifier :size. A value is placed in the variable named “size” when the procedure box is invoked. In this example, it is filled with the value 100by the invocation, the last line.
Question: what if you need a variable that holds it’s value across procedure boundaries and/or requires a lifetime that’s longer than the execution of one procedure?
Think about how a calculator works. You are going to be creating one later in this lesson. You will need a variable that holds the current number as it is entered – one mouse-click at a time. Since the mouseClicked procedure will be executed multiple times, the variable needs to be declared independent of this one procedure. If you are confused or this is just too hard to imagine, checkout the calculator applet later in this lesson. Then, reread this paragraph.
Answer: you use the global command to declare a variable that exists outside the text boundaries of all procedures.
global commands are included in your source code like to commands (procedure title lines) that introduce procedures, at what’s called the outermost level. It is best to put all of a program’s global commands first.
Here is a summary of the important properties of variables:
- Variables have a scope; think of this as where in the source code of a program you can access a particular variable.
- Global variables can be referenced anywhere.
- Local variables can only be referenced within the body of the procedure in which they are declared.
- Variables have a lifetime. They are created at some point and go away at some other point.
- Containers for global variables are created once – when the global command that declares it is interpreted.
- Containers for local variables are created every time a procedure is invoked.
- Variables have an identifier and a value.
- A global variable’s identifier is supplied as an input to the global command which declares it. It is initially empty. Values are put into a global variable with the make command.
- Identifiers for local variables are either supplied on a procedure’s title line (input identifiers), or supplied as an input to the local command which declares it. Inputs get their initial values from actual arguments provided on procedure invocations. Local variables declared with a local command are initially empty. Values are put into local variables with the make command.
Ok, lets work through an example that shows a use of the local command.
Using Local Variables to Save State
Let’s write a very simple drawing program. Here is what we want it to do in its initial implementation.
|alt=”Your browser understands the <APPLET> tag but isn’t running the applet, for some reason.” Your browser is completely ignoring the <APPLET> tag!|
So, what you do is click the mouse in the window where you want the turtle to move to. In the process, if the turtle’s pen is down, a line is drawn from where it was to where the mouse is clicked. There are two buttons, one to clear the window (start over) and another that lets the user lift or lower the pen – its action flips back and forth based on the current state of the pen. Play with the applet a bit to get a feel for it…
You know how to do almost everything needed to write this program. So, click here to get most of the code needed. You can copy/paste it into TG.
Check it out. It has a bug in it. You can move the turtle around by clicking the mouse at various spots in the window, but when you click on the PenDown button, the turtle is moved to the button. This is not what we want… Read through the code, what’s wrong? How can we fix the problem?
Debugging a program is done in steps that match the Scientific Method.
- Make predictions, and
Starting with observation, if you play with the program, you can get a few clues as to what the bug is. The Clear button works fine. But, when you click on the PenUp/Down button, the turtle moves to the button instead of remaining where you’ve chosen to start drawing a line. If you click mouse outside of the PenUp/Down after clicking on it to lower the pen, you’ll see that the line starts at the base of the letter “P” of the “PenUp” text. So, our hypothesis is that the program is moving the turtle to redraw the button with new text (changing “PenDown” to “PenUp”) and leaves it there – our bug!
You have multiple ways of going through the last to steps: predicting and then testing your prediction. What I would do is read the program carefully to try to find what needs to be changed. But another way is just to search the program for the “PenUp” text. There are a few penup commands in the program, but only one “PenUp word, it has the matching uppercase “P” and “U” characters in it. It is in the lowerPen procedure, which is simple to read. sure enough… it redraws the PenUp/Down button to change its label to “PenUp. When this is done, the turtle needs to be moved to do this. And, there is no instructions in lowerPen to get the turtle back to where it was!
So, our prediction is that the turtle is being moved down to draw the button and not being put back. To test this, you can add some debug code. What you need is a way to print out where the turtle is. If you read through the jLogo Primitives Appendix (Graphics Procedures) you can find a few operators which do the job. Checkout the pos, xcor, and ycor procedures. They are operators that output coordinates for the current position of the turtle. You haven’t learned about sentences yet but this is debug code so who cares, let’s use the pos operator to print where the turtle was coming into the lowerPen and where it is at the end of the procedure. Modify lowerPen to look like this:
to lowerPen println pos drawButtonAt pudButtonX buttonsY "PenUp pendown setpencolor black println pos end
Then invoke init in the CommandCenter to restart the program. Click the mouse on the PenDown button. two lines of numbers are printed in the CommandCenter, the position of the turtle when lowerPen started and where it ended up at. The lines of number sentences don’t match! Our test step has proved our prediction and so our hypothesis is probably correct.
If you want to prove to yourself that the turtle has been left in the button you can check to see what the coordinates of the button are. If you look at the drawButtonAt procedure, you’ll see that its first two inputs are the X and Y coordinates of the lower left corner of the button drawn. And reading more code, you’ll see that the text in the button is four turtlesteps right and eight turtlesteps above this point. Go back to the CommandCenter and type in println commands to see what pudButtonX and buttonsY output. This should help prove you’re at the lower left corner of the PenUp text.
Figure 12.1 shows TG after performing the test.
So, to fix the bug you need to know about two new commands: local and make.
The local command declares a new variable for use inside a procedure. Like an input variable, it only can be referenced inside the procedure containing it. The syntax of the local command is:
- the command name: “local” and
- a <name> (a word, the variable’s identifier
Examples of local instructions are:
local "identifier local "savePos
Notice that the input identifiers are wordsin these examples instead of having dots as their prefix (:identifier or :savePos).
You are probably thinking… “Why is there a quotation mark prefixing the variable’s name instead of a colon (dots)?” Input variable identifiers on a procedure title line are always prefixed with dots, e.g.,
to box :size
Well, this title line could also be written
to box "size
although you rarely see it entered like this. A procedure title line is special. It does not follow the rules that the rest of Logo’s commands go by. But, since you will probably never see a Logo procedure title line with quoted names, I’ll stick to dots-prefixed identifiers in all of my examples.
The make command puts a value into a variable. The syntax of the make command is:
- the command name: “make”
- a <name> (a word, the variable’s identifier, and
- a <value>
We can use these two commands to save the turtle’s current position at the start of lowerPen. These commands will replace the first debug instruction.
But, how do we use the value produced by pos to move the turtle back after drawButtonAt has completed? Again, looking in the jLogo Primitives Appendix (Graphics Procedures) reveals a command called setpos which takes an input that is a sentence like produced by pos. After the drawButtonAt instruction, we will make sure the pen is up and use setpos to move the turtle back to the saved position. So our modified lowerPen now is:
to lowerPen local "savePos make "savePos pos drawButtonAt pudButtonX buttonsY "PenUp penup setpos :savePos pendown setpencolor black end
Make the changes in the Editor and try it out. It works… Well, almost… The first time you click on the PenUp button, the turtle stays on it!!! Ahhhh!!!!
Well, to make a long story short, if you read more of the program you will see that you need to make the same changes to the procedure that is the counterpart to lowerPen. Its name is liftPen.
OR… what we can do is move saving and restoring the turtle’s position into the drawButtonAt procedure. This is the best way to solve our bug. Why? Because we will shortly want to add more buttons to our program. How about a button to change the color of the turtle’s pen? How about a button to change the size of the turtle’s pen? How about a Fill button?
So, why add the same code to all of these procedures? Instead we should solve it once, in drawButtonAt.
Go ahead and put lowerPen back to its original text and improve drawButtonAt by changing it such that it saves the current location of the turtle as the first thing it does and then ends by restoring it.
I was introduced to StringArt as a child, a long time before we had computers. We would paint a thin piece of plywood white, draw a polygon on it, mark off equal intervals on the sides of the polygon, hammer in little nails at the marks, and then connect the little nails with colored string. It was called art and crafts.
So, now that we have computers and TurtleGraphics, we’ll draw some StringArt. In the process, you might learn something about geometric design. If you are already a math wizard, you could use your understanding of trigonometry to write a StringArt program. But, don’t worry if you haven’t been introduced to trig yet! I’m going to show you how to use global variables, to produce StringArt. All you need is the depth of expertice in mathematics that you’ve used to get this far. You’ll first draw something like Figure 12.2a and then your only limitation will be your own imagination.
In the following paragraphs I use common naming/terms from a junior high school math textbook (Gateways to Algebra and Geometry), e.g., angle, ray, vertex, etc… If you need background in this area, there is a ton of material out on the web, just Google for it. Here is a good introduction to angles that I found: http://www.mathleague.com/help/geometry/angles.htm
The basic technique in StringArt is to identify corresponding points on the sides of angles in some geometric object and then connect them with colored string. On the sides of angle QPR in Figure 12.2b, three points are marked. On one ray, line segment PQ, the numbers start at 1 for the closest point to the vertex and increase as you move away from it. On the other ray, line segment PR, the numbering is reversed. When identically numbered points are connected by straight lines, a hyperbolic curve is formed. These curves are pleasing to the eye and structurally sound; Google “Felix Candela” to see how he used hyperbolic curves in his beautiful architectural works in Mexico.
Our exercise is to write a procedure which is given
- the X and Y coordinates of point P, the vertex of an angle,
- the heading and length of line segment PQ of ray 1,
- the heading and length of line segment PR of ray 2, and
- the number of strings to draw.
Figure 12.2a was drawn with its vertex, point P, at -100,-50. A line segment (PA) of ray 1 has a heading of 30 degrees in TurtleSpace and is 200 turtlesteps long. A line segment (PC) of ray 2 has a heading of 110 degrees and is 200 turtlesteps long. It has 11 strings.
I promised above that you would not need to know trigonometry. Here’s how we’ll get around this: we’ll get the turtle to walk along the path of Ray-1 to a point. The current position of the turtle, its X and Y coordinates, can be obtained using the pos operator. We will remember them in a local variable. Then we’ll return to the starting point and walk along Ray-2 until we get to the corresponding point on it. Once there, we instruct the turtle to lower its pen and go to the remembered point with the setpos command.
Here’s a start; it’s incomplete but should give you a feel for the structure of the solution:
to black output 0 end to blue output 1 end ; draw string :n of :numPts strings ; a string connects corresponding points on line segments PQ and PR ; point P, the vertex of angle QPR, is at :x,:y ; PQ has a heading of :pqHed degrees and is :pqLen steps long ; PR has a heading of :prHed degrees and is :prLen steps long to drawString :n :numPts :x :y :pqHed :pqLen :prHed :prLen penup setxy :x :y setheading :pqHed ; walk to current point on ray-1 ; remember point where the turtle is setxy :x :y setheading :prHed ; walk to current point on ray-2 ; draw to the remembered point end ; iterate through drawing :numPts strings to angleStrings :n :numPts :x :y :pqAng :pqLen :prAng :prLen ; stop-rule drawString :n :numPts :x :y :pqAng :pqLen :prAng :prLen ; recursive invocation (do next point) end to stringart :x :y :pqAng :pqLen :prAng :prLen :numStrings :color setpencolor black setpensize 4 penup setxy :x :y pendown setheading :pqAng forward :pqLen back :pqLen setheading :prAng forward :prLen back :prLen setpencolor :color setpensize 1 angleStrings 1 :numStrings :x :y :pqAng :pqLen :prAng :prLen end to main hideturtle home clean stringart -100 -50 30 200 110 200 11 blue end main
You’ve learned everything you need to extend this to arrive at a working program. Go off and give it a try… At least play around a bit.
Did you get it working? If so, great job! Skip ahead to More Realistic Looking Trees. If not, don’t fret, you’ll get it; let me give you some tips.
My RULE #1 is: “get something working, then improve it.”
So, let’s get a single line drawn. Let’s write two procedures: walkToPointFromA and walkToPointFromB where AB is a line segment. The procedures have the inputs: :pointNum, :abLen, and :numPts. The difference between the procedures is the ordering of the points, see Figure 12.3.
; Walk to a point on a line segment AB. ; The points are numbered from A towards B. ; The closest point to A is point 1, the next closest is point 2, ; the next, next closest is point 3, etc... to walkToPointFromA :pointNum :abLen :numPts ;... end ; Walk to a point on a line segment AB. ; The points are numbered from B towards A. ; The point furthest from A, closest to B is point 1; ; the next furthest is point from A is point 2, etc... ; the next, next furthest is point from A is point 3, etc... ; The point closest to A is point number :numPts to walkToPointFromB :pointNum :abLen :numPts ;... end
With these completed, you can plug invocations to them into drawString, i.e.,
to drawString :n :numPts :x :y :pqHed :pqLen :prHed :prLen penup setxy :x :y setheading :pqHed walkToPointFromA :n :pqLen :numPts ; remember point where the turtle is setxy :x :y setheading :prHed walkToPointFromB :n :prLen :numPts ; draw to the remembered point end
Off you go; fill in the code for the walkTo…procedures.
A good next step is for you to fill in the missing code that saves the turtle’s position and then goes to that position once you’ve moved to the corresponding point. You’ve been introduced to the local, make, pos, and setpos commands. For remember point, we can use local and make to put the turtle’s position into a local variable. This variable will then be accessed when we instruct the turtle to go to the remembered point.
You could also use the setxy command together with a couple of other operators which you haven’t used yet (xcor and ycor) instead of pos and setpos. In this case you would declare two local variables, one to hold the X coordinate and the other to hold the Y coordinate. It’s up to you – either way works.
to drawString :n :numPts :x :y :pqHed :pqLen :prHed :prLen penup setxy :x :y setheading :pqHed walkToPointFromA :n :pqLen :numPts local "rememberedPoint make "rememberedPoint pos setxy :x :y setheading :prHed walkToPointFromB :n :prLen :numPts pendown setpos :rememberedPoint end
If you have everything right, your program should now draw the angle with the first string. If not, it’s debug time. Use trace to see if your procedures are getting inputs that match what you think they should. Add println instructions to see what is happening when your program is being executed. Treat debugging a program as a puzzle, a game – so solve it!
After you get this working, then finally tackle the problem of iterating through all of the points. Review the last lesson where you learned how to use recursion to do this if you need to. All you need is to get your point number input which to go from 1 to the last point number.
More Realistic Looking Trees
So, we had fun taking advantage of the power of recursion in the last lesson drawing trees. With just eight instructions, we could draw trees that could have any number of branches we wished. But, they were so, well… computer generated. The turtle is is a digital turtle, not alive, composed of atoms, and so it draws very straight lines. Let’s teach it to not be so perfect.
First, let’s write a procedure that draws a rough line, a sketched line, and use it instead of forward and backward.
We are going to write a procedure named sketchLine that moves the turtle forward some amount of turtle steps. When it’s done it will end up at the same spot that a forward command would, given the same number of turtle steps as its input. But, the turtle will wander around a bit on its way to the spot.
If you think you know how to do this, go ahead and write a procedure on your own. If you don’t know how to start, stay tuned…
All right, let’s move on to global variables. As a refresher, here is a list of their attributes.
- are declared with the global command,
- are containers whose contents can be accessed anywhere in a program,
- get assigned a value when a make command is interpreted, and
- maintain a value until changed with another make command.
For your programs, it is best to put your global variable declarations at the top/beginning. This way, you can see, in one place, the names of variables that will be referenced across procedure boundaries. Since a declaration creates an empty container, you need to use a make command to put an initial value into it. Since all programs should have an init procedure that does initialization, this is the best place to put these make instructions.
Example: Use of Global Variables as Named Constants
Let’s look at an example of how you could use global variables…
In the lesson Defining Your Own Operators, the first thing I showed you how to do was write procedures that did nothing but output a number. This was a form of abstraction, where I gave names (that mean something to me) to numbers used inside the computer to represent colors.
In past examples, I have defined procedures black, blue, etc… that simply output the colors’ numbers. Then, using these procedures as the input to a setpencolor command made reading the code easier to understand. As an example:
to red output 4 end setpencolor red
Global variables can be used to achieve the same results. Here is part of a program that shows the use of a bunch of global variables..
global "black global "green global "red global "signalX global "signalY global "signalRadius to drawSignal :action pu setxy :signalX :signalY setc :black repeat 360 [fd :signalRadius pd fd 2 bk 2 pu bk :signalRadius rt 1] if equal? :action "go [setpencolor :green] if equal? :action "stop [setpencolor :red] fill end to init hideturtle home clean make "black 0 make "green 2 make "red 4 make "signalX -50 make "signalY 0 make "signalRadius 25 end to main init drawSignal "stop wait 2000 drawSignal "go end main
So, what’s going on here? Well, as soon as a global instruction is interpreted, an empty variable is created;nbsp; The name of this variable is available anywhere in the program. This is the reason that the first thing that you should do in a program is to declare all of it’s global variables.
The syntax of the global command is:
- the command name: “global” and
- a <name> (a word, the variable’s identifier
Here is the TurtleGraphics applet. Play around a bit. Declare a few global variables, try accessing them before you use a make command to give them their initial values, use make to set their initial values, and then access them. Write a procedure which has inputs (e.g. boxAt 😡 :y :size) and notice that once you type in the end word which terminates your definition of boxAt, you can no longer reference its inputs. Type in the example I gave above to see what it does, make sure you understand it.
|alt=”Your browser understands the <APPLET> tag but isn’t running the applet, for some reason.” Your browser is completely ignoring the <APPLET> tag!|
The use of names in place of numbers in your programs is always a good thing to do. I use names for the coordinates of objects that I draw in TurtleSpace all the time, like buttons in the user interface part of a program. This allows me to change them in one spot instead of searching for where the object is drawn, where I test to see if the mouse-click was within its bounds, etc…
Many computer languages have explicit support for global variables that, once assigned a value, can not be changed.
A Four-Function Calculator
Here is a Java applet that mimics the program you are going to write. alt=”Your browser understands the <APPLET> tag but isn’t running the applet, for some reason.” Your browser is completely ignoring the <APPLET> tag!
Play around with it a bit, to see what it does when you click on its components. Try adding two numbers, subtracting one number from another, etc…
A Four-Function Calculator – How It Works
Figure 12.5 shows the calculator we’re going to build. It has space for 16 keys and a display. There are ten digit keys used to enter numbers and five operation keys which determine what the calculator does.
As the mouse is clicked on number keys, the current value being accumulated and displayed is multiplied by 10 and the key’s value is added in. As an example, when the program starts, the current value is zero and when a number key is clicked on, the current value becomes the value of the key. Let’s say the “1” key is clicked on and the current value becomes 1. If the mouse is then clicked on another number key, say “2,” the new current value becomes 12 ( 1 * 10 + 2 ). If the mouse is then clicked on the “3,” the current value becomes 123 ( 12 * 10 + 3 ).
Figure 12.6 shows our calculator after a user has clicked on the “1” key, the “2” key, the “3” key, the “4” key, the “5” key, and the “6” key.
Next we have the action keys. The action keys: “/,” “X,” “-” and “+” do two things.
- They signal that entry of the first number is complete. The next number key that is clicked on becomes the new current number.
- The mathematical operation must be saved. After a second number is entered and the mouse is clicked on the “=” action key, the remembered operation is performed and the result is displayed.
Continuing the example, Figure 12.7 shows our calculator after the user has clicked on the subtraction key (“-“) and then on the “4” key, the “5” key, the “6” key, the “7” key, the “8” key, and the “9” key.
And, finally, Figure 12.8 shows our calculator after the user has clicked on the compute-answer key (“=”).
The Calculator Program – Structure
To reduce the time it will take you to write the calculator program, I’m going to provide you with part of my version of the program and the details of its structure.
I’m doing this because I want you to concentrate on learning about how/why global variables are used in a program – not how to design a calculator. Since you are still learning the basics of programming, I want to offer you one way of structuring your program that I know will work. I’ll cover how to design the structure of a program in a future lesson.
Figure 12.9 shows the most important procedures that make up my initial design of the calculator program. The arrows indicate invocations of procedures. The program is started by invoking the init procedure. After this, all execution is driven by mouseClicked events (invocations of the procedure mouseClicked), which occurs when the user clicks the left button on the mouse on the graphics canvas where the calculator has been drawn.
Looking at Figure 12.9, you see that my calculator program consists of some procedures you are already familiar with. You’ve written procedures similar to frameAt, coloredRectAt, and mouseInRect?. The procedures digitAt, keyAt, displayMinusSign, and drawKeypad provide higher-level functionality – they draw the parts of the calculator using the primitive procedures. You are familiar with generic stuff that mouseClicked and init need to do. It was covered back when we put together our first User-Interface if you need to refresh your memory.
I’m going to give you most of my code for the calculator so that we can spend our time on the interesting procedures. I’m including stubs (empty procedures) for these procedures so that you can copy-paste the code into the TG application.
The second headstart I’m giving you is enough of the layout of the graphics part of the program. By now you have drawn so many boxes that it’s got to be getting pretty boring. And, figuring out the size of things like the keys and the labels takes a lot of time.
Figure 12.10 shows a partial layout of the calculator in TurtleSpace.
Read the Code You’ve Been Given
The very first thing you need to do is read the code you have. in the real world, in most software development positions, modifying existing programs is what’s done most often. Scanning over the existing code is important for at least two reasons:
- You need to see what you already have. The last thing you want to do is go to the trouble of writing your own version of some procedure that already exists and is working.
- The best way to get good at programming is to read a lot of code – to see different ways to do stuff.
displayNum – Display a Number
Okay, let’s get going. The first thing we will do is to get our calculator to display a current number. Now we need a global variable. The concept of a current numberis a real need for one. It’s value will change as the user clicks on digit keys and the equals key.
Like the named constants and stringart examples, we need to use the global command to create our variable. I named my variable curNum and placed the following line at the top of my source code.
make "curNum 0
Look back at Figure 12.9 (the structure of my program) and the code for the calculator. init invokes displayNum with the number it wants displayed as an input. So, in our case, we want to display the contents of the global variable curNum. So, we replace the comment line “;display the current…” with an invocation of displayNum.
And what does displayNumdo? I’m going to help you with the math stuff and the locations for the things you’re going to draw. Figure 12.11 gives you many of the coordinates for the minus sign that indicates a negative number and the six digits that are displayed. With the labelheight of 36, the characters look good when spaced 25 turtle steps apart on the X axis.
As for the math stuff, you are going to need to use three new operations: minus, remainder, and round.
To reduce the complexity of you first version of the calculator, it does not support a fractional part. So, you need to know how to get rid of it before you attempt to display a number. Even though the user can not enter a number with a fractional part, one can be generated with division, e.g., divide 100 by 3. The round operator takes one input and outputs the closest integer to it. The first thing that displayNum needs to do is make sure that curNum is an integer.
When you read the base code I gave you, hopefully you noticed the procedures displayMinusSign and digitAt. If not, check them out. They are key to how you will get a number displayed.
Determining whether or not to display the minus sign is easy. If the input’s value is less than zero, you need to display the left-justified minus sign, the dash character. An operator you need to know about is the minus operator. It takes one input and outputs the negative of its input, essentially changing the sign of its input.
The challenging piece of displayNum is how to display a right-justified number. As with most programming problems, there is more than one way to do it. One way would be to determine how many turtle steps long a single-digit number is, a two-digit number is, etc… Then with a bunch of if commands, you can position the turtle at the right spot in the display area, and output :num with a label command. I didn’t want to go to this much trouble.
I decided to figure out the width of a single digit and then display each digit separatedly – with the digitAt procedure. So, my problem became one of computing the value of each digit. My approach was to first determine whether or not each digit needs to be displayed for the given :num. As an example, unless :num is greater than 9, all I need to display is the rightmost digit. But, if :num is greater than 9 then I know I need to display a value in the tens column. If :num is greater than 99 then I need to display a value in the hundreths column. Etc… Here is an example of one of my if instructions.
if greater? :num 9 [ digitAt 40 115 isolated-tens-digit ]
But how do I get the isolated-tens-digit. Well, you need an operator I haven’t covered yet – the remainderoperator.
|REMAINDER||number1 number2||Outputs the remainder left after dividing number1 by number2||REMAINDER :num 10|
The remainder operator with :num as its first input and 10 as its second input will produce an output equal to the least significant digit of :num. The code for displaying the one’s column of :num is:
digitAt 65 115 remainder 10
Combining the remainder operator with the quotient operator isolates any digit of a number.
Example: Display the isolated digits of the number 4321
println remainder quotient 4321 1000 10 println remainder quotient 4321 100 10 println remainder quotient 4321 10 10 println remainder 4321 10
Go back up to the TG Appletand type in the above example. Play around a bit until you see/understand what is going on…
You can now complete the if instruction given above by exchanging the isolated-tens-digit piece with code that does just that…
OK… I’ve given you enough hints. It’s up to you to complete your displayNum procedure. When you complete it, test it out. Invoke init and see if your calculator now looks like Figure 12.6.
digit – mouse-click on a Number Key
Time to change the current number. Look at the code in the mouseClicked procedure. The digit procedure is invoked in the mouseClicked procedure when the user clicks on one of the digit keys. The number value of the digit key clicked on is provided as the input to digit.: So, what does digitneed to do?
The digit just clicked on becomes the least significant digit of the current number – simply multiply the current number by 10 and add in the new digit. You use the make command to give the current number a new value. Here you go.
to digit :num make "curNum sum :num product :curNum 10 end
I’ve given you this makecommand so that I can point out and explain three things.
- The first occurance of the word curNum is quoted (a quote mark is prefixed to it).
The reason for this is that
- ‘s first input is the name of a variable. If you left off the quote, the interpreter would think that you want a procedure named “curNum” invoked. The quote is a signal to the interpreter that this is simply a word.
- The second occurance of the word curNum is prefixed with a colon – the dots mark. This is an abbreviation meaning the contents of a variable named curNum.
- There is an order that’s important to understand regarding how this statement gets executed. Although we normally read left-to-right, the operations here get executed right-to-left. product is performed 1st, sum 2nd, and finally make is performed.
Oh… there’s one more thing that digit needs to do. I’ll let you figure it out.
The Math Function Keys
Procedures for the math function keys (divideKey, minusKey, plusKey, and timesKey) are invoked in the mouseClickedprocedure. So, what do they need to do?
The Math Function Keys signal two things.
- the current number is complete. Our calculator program now has the first of two numbers that it will use in a calculation. We need to save it somewhere and prepare for a new current number.
- which operation will be performed when the Equals Key is clicked on.
At this point when I was writing my version of the calculator, I added two more global variables. I created a prevNum variable which holds the previous number and a mathOp variable which holds the chosen mathematical operation.
As an example, my plusKey procedure sets prevNum to the value of the current number, then sets the current number to zero. Finally, it puts the word sum into mathOp.
The other math function keys are the same except for the value stored into mathOp.
equalsKey – Compute/Display the Answer
The Equals Key signals two things.
- the current number is complete. The calculator now has the second number that is required for generating an answer.
- it’s time to perform the requested math operation and display the answer
I’ll get you started by giving you one line in my equalsKey procedure.
to equalsKey if equal? :mathOp "sum [ make "curNum sum :curNum :prevNum ] end
And the rest of the procedure is left for you… enjoy!
Hierarchical Structure and Abstraction II
Back in the lesson Defining Operators and HierarchyI wrote a bit about why hierarchy and abstraction are important when writing programs. It’s time to revisit this topic because by now you’ve learned a lot – so much that it’s getting easier and easier to write programs that are very hard to get working right.
- A lot of primitive procedures (commands and operators) that do everything from move the turtle to add numbers, to get the X and Y coordinates of a mouse-click, and on and on…
- How to define procedures of your own, both commands and operators. You extend the language adding your own abstract concepts.
- Iteration – both simple and powerful ways to do things over and over a number of times, or until some condition is true.
- Conditional execution – only execute a list of procedures when a specified condition is true.
- And now state – global variables that you put stuff into. Unlike inputs to procedures (local variables) the stuff stays there until you put something else in them.
This is a lot to learn, and requires practice to get a good grasp on it. With each of these concepts, you added complexity to your programs. And, with each of these concepts you get additional ways to make mistakes and end up with programs that don’t do what you think you wrote them to do.
For conditional execution you need to make sure you have the predicate right. If not, when you think the list of instructions should be executed, they may not be.
For iteration, when you write recursive procedures, you need to make sure your stop-rule is right, and that your recursive-invocation changes an input variable one step closer to what the stop-rule is looking for.
- Add a “CLEAR” key to your calculator. It should zero out the current and previous numbers, and the display.
- Add little icons somewhere on the display of your calculator to show what mathematical operation is about to be performed.
- By using the count and minus operators, you can display all the digits with a single label command. counttakes an input, in our case a number, and outputs the number of characters in it. Think about it. The number of characters times the width of a character…Oh, you’ll also need a way to remove the leading minus sign from the number. Since it’s displayed by displayMinusSign, you need to convert the negative number into a positve one. There is a primitive operator, minus that outputs the negative of its input. Applying the minus operator on a negative number gets you its positive value…
Modify your displayNum procedure to use count, minus and a single label command to display the digits.
Global variables are containers that are created with the global command and can be accessed anywhere in your program. One gotcha is that the global command that introduces the variable (called its declaration) must be interpreted before its identifier can be entered in an instruction.
Most introductory programming classes/texts introduce global variables much sooner than I have. I’ve worked on lots and lots of code in my days as a programmer and I’ve seen too many inappropriate uses of global variables. Their usage (I guess I should say their misuse) is a common source of bugs. Global variables are a very important, powerful concept in programming. But, use them responsibly.
To read more about trouble you can get into with global variables, Google “global variables considered harmful” and check out the pages that come up. William Wulf and Mary Shaw wrote a paper a long time ago that is still referenced/debated today.
|New jLogo Procedures Used In This Lesson|
|GLOBAL||name||Declares a global variable named name. The variable can be referenced anywhere in the program’s text following this command. The variable has no initial contents.||GLOBAL “var|
|LOCAL||name||Declares a local variable named name. The variable is local to the procedure it is in. The variable has no initial contents.||LOCAL “var|
|Creates a variable named name if it doesn’t already exist. The contents of the variable is set to value.||MAKE “WHITE 7|
|MINUS||number||Outputs the negative of number||MINUS 122|
|POS||Outputs the current coordinates of the turtle as a sentence. The X coordinate is the FIRST of the sentence; the Y coordinate is the LAST of the sentence.||MAKE “CURLOC POS|
|REMAINDER||number1 number2||Outputs the remainder left after dividing number1 by number2||REMAINDER 17 3|
|ROUND||number||Outputs the closest integer to number||ROUND 22.45|
|SETPOS||sentence||Moves the turtle to an absolute position, specified by a sentence containing two numbers. The two numbers are (FIRST) a new horizontal (X) coordinate and (LAST) a new vertical (Y) coordinate.||SETPOS [100 100]|