|
BFOIT
Introduction
Data Abstraction
Mastermind |
|
A long time ago, Lesson 3 (Defining Your Own Procedures) to be exact, you learned about procedural abstraction. It is the ability to assign a meaningful identifier to a bunch of instructions. Procedures with names are for your benefit - they allow you to control complexity, to break a problem into meaningful pieces. This capability, along with hierarchical structuring, became more and more important as the size of your programs grew.
Now it's time to learn a about data abstraction. If you think of procedures as verbs, the nouns in a computer program are data.
You've worked a lot with numbers as data. You've been working with boolean values as data for the last few lessons. In the last lesson you learned how you can manipulate the characters in a word and the words in a sentence. In all of your work with these forms of data, the values of your data had the meaning expected. You used numbers where you needed to; you used the boolean values true and false where a predicate is needed.; And in your hangman game, you manipulated words.
You have also written programs with graphical user interfaces. You're getting very familiar with recursion. You've used global variables to hold information accessed by multiple procedures. It's time you got to work.
In this lesson you are going to do just that. You are going to write a game that I'm sure you will enjoy playing when you're done - Mastermind. The challenge to you is to stop reading the lesson as soon as you can. Try to write the program on your own. When you need help, return to the lesson. When you complete your version of Mastermind, come back to finish reading the lesson. Compare and think about how your program evolved, versus the one in this lesson.
In this lesson, you will learn
To bring you up to speed, the programmer that wrote Mastermind in Java for the previous phone, Jayne, has given you some of her notes, and access to a copy of the game. Here is her Java applet that implements her version of Mastermind.
| Mastermind Applet |
The object of Mastermind is to guess a secret code that the program has chosen. The computer randomly picks four colors from a pallet of six. In Jayne's version of Mastermind, you get to choose whether all four colors are unique or if duplicate colors are allowed in the secret code. It has been decided that you do not have to provide this functionality; only the unique-color secret codes will be provided.
To play the game, you select colors from the column of six choices, filling in the row of empty frames which makes up your current guess. Your guess may contain duplicate color choices if this helps you solve the puzzle.
Clicking the left mouse button on a color choice box, fills the current empty guess box. If you want to change the choices in your guess, clicking on the [Clear Color] button, clears your previous choice (the guess box to the left of the current one). When your guess is complete, clicking on the [Guess] button provides feedback, guess hints.
If the guess is correct, "You Win" is displayed. Otherwise, two hints are given as feedback:
If you didn't win, and there is still room on the display, another set of empty boxes are drawn for your next guess.
When you run out of room for another guess, "Sorry! Answer was:" is displayed along with the the secret code.
Try out the applet. See what it does when you interact with it. The program you write should perform as similarly as possible with this one.
Jayne has a drawing of the methods in her program, including which methods invoke which others. Methods in a Java program are analogous to the procedures you have been writing.
Here is her drawing.
|
| Figure 13.1 |
After Jayne completed the program she wrote the following description of the highest-level of the program's execution. It goes well with Figure 13.1, giving a bit more insight to what the program does.
|
| Figure 13.2 |
One of the most time-consuming things she did was to layout all the pieces of the program on the cellphone's display. To save you time, she has provided a drawing that contains display coordinates for everything.
|
| Figure 13.3 |
What follows are sections that you should treat as suggestions from your mentor. You are now an apprentice programmer. What you still need are tips of the trade. As a programmer, you have joined the ranks of the learners for life.
My suggestion to you at this point is for you to try to write the program on your own.
If you complete it - congratulations! Now read (or at least scan) the remaining sections and compare how you approached the problem versus what is suggested. Send me an e-mail (guyhaas@pacbell.net) with your code in it and I'll reply with my code so that you can compare them and learn even more.
If you get stuck, read one or more of the following sections. The Mastermind program has a couple of tricky parts to it, but you can write it if you've come this far in the lessons.
Once this was working, she
Before reading any more of this lesson, take a crack at writing all of this initial code.
The next two sections present programming tricks of the trade, tips for the apprentice in the Art of Computer Programming. These tips are useful for writing all of the code described above.
Well, in programming, there is an advantage to start your counting with zero in many iterative processes. Drawing the guess boxes is just such a case. Read on to see why.
First, I'll define a few symbolic constants so that I can use them in the explanation.
to guessBoxSize
output 20
end
to guessBoxGap
output 5
end
to guessBoxesLeftX
output -15
end
to guessesTopY
output 130
end
Figure 13.4 shows what the values depict.
|
| Figure 13.4 |
Given these symbolic constants and two variables (boxNumToFill
and guessNumber), the lower-left corner of every guess box can
easily be computed.
The variable boxNumToFill will start out at zero when
the current box is the left-most one. It will be incremented as the
boxes get filled in. When it contains three, the current box is the
last (right-most) box.
The variable guessNumber will start out at zero for the
top row of guess boxes. As guesses are made, and a new row of guess
boxes is needed, it is incremented.
This gets us to the equations:
Let: a = the size of a guess box (guessBoxSize)
b = the size of a gap between any two guess boxes (guessBoxGap)
c = the left edge of the left-most box (guessBoxesLeftX)
d = the bottom edge of the top row of boxes (guessesTopY)
n = current box number in range 0...3 (boxNumToFill)
m = current guess number 0...9 (guessNumber)
x = the current guess box's left x value
y = the current guess box's bottom y value
then, x = c + n(a+b) and
y = d - m(a+b)
Make sure you understand why our variables (boxNumToFill and
guessNumber) must start with values of zero.
To see how this simplifies things, rewrite the equations assuming the
variables start with values of one, e.g., boxNumToFill
iterates through values: 1, 2, 3, and 4.
Finally, operators can be written for each of the equations. Here is the code for the first.
to curGuessBoxX
output sum guessBoxesLeftX product :boxNumToFill sum guessBoxSize guessBoxGap
end
and Figure 13.5 shows it graphically.
|
| Figure 13.5 |
By testing these situations, you are exercising edge conditions.
A few things need to happen when the [Guess] button is clicked on.
guessNumber global variable,
reseting the boxNumToFill global variable, and
drawing four new boxes (but you should be able to use the
same procedure that drew the first boxes).
guessNumber exceeds the
maximum allowable guess count, the program should display
something like "Sorry, you lose."
The obvious way to represent the secret code and the guess is with a bunch of global variables, one for each color in the secret code and one for each box of the guess. You could do this. Try this if you want, but I'll warn you - you will be writing a lot of if commands.
The alternative is to use a sentence or word for the secret code and guess. I suggest using words; you have a lot of experience manipulating them now that you've written the Hangman game.
The words will be composed of the first characters of each of the colors that are in the game.
r for red
o for orange
y for yellow
g for green
b for blue
v for violet
The easiest way for me to demonstrate how using a word will work is to
show you how my version of the program generates the secret code.
There are quite a few different ways in which to select four unique letters
from our set of six. I'm going to show you the one I like the best.
The basic steps in the approach I'm going to take are listed in Table 13.1.
|
|
| Table 13.1 |
The algorithm I'm using comes from Brian Harvey's book "Computer Science Logo Style." It's quite clever and definitely a technique worth learning. Writing code is an art and so it's best to learn from the masters when you have the chance.
In the last lesson you learned the primitives that are available to
assemble/disassemble words: word, first,
butfirst, last, and butlast.
And, the first exercise I asked you to do was to write an operator
(rotate) which takes the
leading character off of a word and appends it onto the end. You
should now enhance rotate so that it takes two inputs: a number
of times to rotate, and a word.
to rotate :num :wd
if equal? :num 0 [output :wd]
output rotate (difference :num 1) (word butfirst :wd first :wd)
end
This new version can be used to get a random letter. Test it...
? println rotate random 6 "roygbv
bvroyg
? println rotate random 6 "roygbv
oygbvr
? println rotate random 6 "roygbv
gbvroy
Got this working? Ok, then add the declaration of a global variable
which will hold the secret code to the top of your Mastermind code.
Then add your the procedure(s) to fill it with four random characters from
the six choices. Here is most of my code. I've put comments
in place of code for all of the steps from Table 13.1.
;global variable containing the program assembled secret code
make "secretCode "
to makeSecretHelper :choices
; stop-rule: secretCode has all four characters?
; rotate choices a random amount
; append first character of choices on to secretCode
; recursive invocation - removes chosen character from choices
end
to makeSecretCode
make "secretCode "
makeSecretHelper "roygbv
end
So - finish the code...
Make sure to take advantage of trace to watch how the global variable (secretCode) changes as your code executes.
Once you have some code, working or not (at least try), compare it with my code for makeSecretCode.