| CSCI A201/A597 Lecture Notes 25
Spring 2000
|
Solution to assignment 9 (due last night).
We'll start the lecture with a demo (it will be Sangwook's assignment 10,
that he turned in on Friday). Once we clarify how the program should
run we will start designing it (in stages).
Stage 1 (one): The Basic Outline
Observations: there are a few things that need to be done only once,
such as creating the drawing window and drawing the bin (that we want
to pack with circles), and a few others that need to be done over and
over again (until we reach the end of the game) such as the generation
and placement of a new circle. Provided that we can identify the end
of game situation we need to generate a new circle and try to place
it until the game ends.
Assume a certain boolean variable contains the truth value
representing whether the game should go on or not. Whenever we look into
it we find out if we should go on or not. In the beginning this variable
(call it theGameMustGoOn) is set to true. If
nobody changes the value of this variable the game will never end, which
for this stage of design is just fine (we type control-C when we want to
stop the game).
So this first stage of design looks like this:
- get a drawing window (call it board)
- decide on a rectangular area where you place the bin (Rect bin)
- draw bin on board
- start the game (theGameMustGoOn = true)
- while (theGameMustGoOn) {
- generate new circle (call it circle)
- help the user place it, using the mouse
}
Step 5.2 above is by far the most complex. Let's refine it.
Stage 2 (two): Refining Step 5.2
The user knows that placing the circle is done using the mouse. So
the user needs to press the mouse before we can do anything about it.
Once the mouse is pressed dragging the mouse will signal the user's
intent to move the circle to a certain location on the screen. For as
long as the mouse is being dragged the circle should be following the
mouse. Our program will have to take care of that, to simulate that
the circle is being moved by the mouse.
Of course, the mouse and the circle don't interact directly. We work
out a contract with the user of our program as follows: if you want to
move the circle press the mouse button then drag the mouse; the circle
will follow the mouse pointer as if you'd be dragging the circle with
it. We'll take care of updating the situation to look that way. This
way (if the update is fast enough) the circle will follow the mouse
pointer (and you will see that on the screen) and will be at the mouse
location almost all the time during the mouse dragging movement. So you
can drop the circle at a particular location by just releasing the mouse
button, which means that you no longer want the circle to follow the
mouse pointer.
Here's this process in pseudo code:
- wait for user to press the mouse
- while (mouse is kept pressed) {
- get mouse position
- erase the circle (wherever it is)
- move the circle to the new position
- draw the circle (in this new position)
}
Here's the whole program (with the complex step refined):
- get a drawing window (call it board)
- decide on a rectangular area where you place the bin (Rect bin)
- draw bin on board
- start the game (theGameMustGoOn = true)
- while (theGameMustGoOn) {
- generate new circle (call it circle)
// help the user place it, using the mouse
- wait for user to press the mouse
- while (mouse is kept pressed) {
- get mouse position (this will be a Pt, call it m)
- erase the circle (wherever it is)
- move the circle to the new position, m
- draw the circle (in its new position)
}
// place the circle when you reach this place:
// probably fill the circle and update the score
}
How operational is this pseudo code? Here are some considerations:
- Steps 1, 2, 3, 4 are straightforward. Steps 5 and 5.3 are also
straightforward. 5.1 is similar to step 2. To implement step 5.2
we need to know that a drawing window can monitor the mouse: the
program can be made to await a mouse press.
- The condition that controls loop 5.3 can also be implemented
with the help of the drawing window. It can be asked whether
the mouse button is currently being kept pressed or not. The
window replies with
true or false
to such a question and we can use the answer in step 5.3.
- Step 5.3.1 can be implemented by asking the window to retrieve
the mouse position.
- Step 5.3.2 is a bit tricky. The easiest thing here would be to
use the special properties of invertMode (drawing the same thing
twice, at the exact same location, erases it). I will use this
approach here.
- Steps 5.3.3 and 5.3.4 are distinct. It's one thing to move the
circle and quite another to actually draw it. Moving the circle
changes the coordinates of its center. Drawing the circle simply
makes it visible to the user, but doesn't change anything as far
as the circle is concerned.
Please use c.moveTo(m) to move the circle to a certain
point. Appendix E is not extremely explicit about this, as the Circle
class extends Oval which extends Rect which has this moveTo method that
is inherited by all the classes that extend it without overriding it
(except we have not studied inheritance yet).
We now turn our attention to whether a circle can be placed or not.
Stage 3 (three): Rejecting Incorrect Locations
Let's say that if the circle is not placed inside the bin we return the
(same) circle to the start location. So we have two basic questions:
- How do we figure out if the circle is inside the bin or not, and
- how do we return it to the start location?
The first question is purely mathematical: we need to know if the circle's
center is within the bin and if the distance from the center to the walls
is in each of the four cases smaller than the radius of the circle. We could
even write a method that expects a circle and a rectangle and returns either
true or false depending on whether the circle is inside the rectangle or not.
So I will assume that this method will be available in a collection of methods
that we call Library and its name will be inside. The
method expects a circle and a rectangle (in this order) and returns a boolean.
This pretty much takes care of the first question. Now how do we return the
circle to the start location instead of generating a new circle and resuming
the loop?
This second question is hard, and this is where the design that we currently
have is breaking up. If we restart the loop a new circle will be generated. We
need to avoid doing that if the placement of the circle was not successful. So
we investigate ways of generating the new circle only at the end of the body of
the loop. Here's the basic outline:
while (theGameMustGoOn) {
- generate new circle
- let the user choose a placement location for it
- if (placement location not appropriate) {
- don't place the circle there
- // don't generate a new circle: keep working with this one
} else {
- go ahead and place the circle there
- // generate a new circle and work with that one from now on
}
}
What do we do?
We need a circle generated in the first place, or we wouldn't have one to ask
question about and move it with the mouse.
But we don't need it generated unconditionally at the beginning of the loop, instead
it appears that we have the information about whether we need to generate a new one or
not only after we try to place it on the screen.
We could generate the first circle outside
the loop, before we start it, and any new circle, as needed, inside it (as hinted above).
This is a powerful thought as it realizes an important distinction between the circles:
- the generation of the first circle is unconditional
- all the other circles will be generated conditionally (if the
circle that had been generated before them was placed correctly then
a new circle is needed otherwise we don't need a new circle as we
still have this one to place somewhere)
So here's how the pseudo code looks now:
- generate new circle (this is our first)
- while (theGameMustGoOn) {
- let the user choose a placement location for it
- if (placement location not appropriate) {
- don't place the circle there
- don't generate a new circle: keep working with this one
} else {
- go ahead and place the circle there
- generate a new circle and work with that one from now on
}
}
Stage 4 (four): Overlapping Circles
Here's what we have right now.
- get a drawing window, generate and draw a bin on it
- generate new circle (call it c) // this is our first circle
- start the game (theGameMustGoOn = true)
- while (theGameMustGoOn) {
- place c in the starting location and draw it there
// now let the user choose a placement location for it
- wait for user to press the mouse
- while (mouse is kept pressed) {
- get mouse position (this will be a Pt, call it m)
- erase the circle (wherever it is)
- move the circle to the new position, m
- draw the circle (in its new position)
}
- // at this point the circle has been dropped at a location
if (placement location not appropriate) {
- don't place the circle there
- don't generate a new circle: keep working with this one
} else {
- go ahead and place the circle there
- generate a new circle and work with that one from now on
}
}
If we look closely at this code we realize we only need one piece of
detail in step 3.4.2:
c = new Circle(...);
All the rest is already in place for the whole program to work well.
Now that the basic loop has been take care of, let's see how we can acknowledge
the circles that we are successfully placing in the bin.
- First of all, how do we remember them?
- Second, how do we check for overlap?
To answer the first question we could decide to use an array to store the circles.
Let's take care of this problem first.
- get a drawing window, generate and draw a bin on it
- declare and allocate an array to store the placed circles
// the array is empty in the beginning, and we store circles
// in it only when we know they can be placed in the bin
- generate new circle (call it c) // this is our first circle
- start the game (theGameMustGoOn = true)
- while (theGameMustGoOn) {
- place c in the starting location and draw it there
// now let the user choose a placement location for it
- wait for user to press the mouse
- while (mouse is kept pressed) {
- get mouse position (this will be a Pt, call it m)
- erase the circle (wherever it is)
- move the circle to the new position, m
- draw the circle (in its new position)
}
- // at this point the circle has been dropped at a location
if (placement location not appropriate) {
- don't place the circle there
- don't generate a new circle (keep working with this one)
// what do we do next? where do we go from here?
// (hint: what else is left to be done in this loop?)
} else {
- go ahead and place the circle there
- also store the circle in the array
// we need a number for that, who's counting the circles?
- generate a new circle c
}
}
So we need to count the circles.
Let's use an int variable (call it number) for that.
We start with number being 0 (zero).
Let's add these new things to our design:
- get a drawing window, generate and draw a bin on it
- declare and allocate an array (call it circles) to store the placed circles
- generate new circle (call it c) // this is our first circle
- int number = 0; // that's the number of the circle we're working on
- boolean theGameMustGoOn = true; // start the game
- while (theGameMustGoOn) {
- place c in the starting location and draw it there
// now let the user choose a placement location for it
- wait for user to press the mouse
- while (mouse is kept pressed) {
- get mouse position (this will be a Pt, call it m)
- erase the circle (wherever it is)
- move the circle to the new position, m
- draw the circle (in its new position)
}
- // at this point the circle has been dropped at a location
// we need to check here if the placement is appropriate.
- if (placement location not appropriate) {
- don't place the circle there
- don't generate a new circle (keep working with this one)
// what do we do next? where do we go from here?
// (hint: what else is left to be done in this loop?)
} else {
- go ahead and place the circle there
- also store the circle in the array
// we need a number for that, who's counting the circles?
circles[number] = c;
- generate a new circle c
- number = number + 1; // the number of the circle we're working with
}
}
Most of the steps above are fairly straightorward, except for step 6.4.
Here's a design for it.
Let's say the answer will be in a variable placementOK.
If there's nothing to check then the answer is true.
That is, if nobody complains we can take up that space.
So we initialize this variable to true.
Who could complain?
Well, the other circles, if any.
So we need to go and ask them, one by one.
If one or more complain then we can't take the space.
How do we get to them?
Well, the current circle has a number (number).
The previously placed circles also had numbers and they were placed
in the array according to their numbers at the time.
What are their numbers?
They are:
That's because the current circle is numbered number.
So let's go through all of them and ask them one by one:
boolean placementOK = true;
for (int k = 0; k < number; k++) {
if (Library.overlap(c[k], c[i])) {
placementOK = false;
}
}
After this the variable placementOK will have the answer.
We use this in the program and the design is pretty much finished.
- get a drawing window, generate and draw a bin on it
- declare and allocate an array (call it circles) to store the placed circles
- generate new circle (call it c) // this is our first circle
- int number = 0; // that's the number of the circle we're working on
- boolean theGameMustGoOn = true; // start the game
- while (theGameMustGoOn) {
- place c in the starting location and draw it there
// now let the user choose a placement location for it
- wait for user to press the mouse
- while (mouse is kept pressed) {
- get mouse position (this will be a Pt, call it m)
- erase the circle (wherever it is)
- move the circle to the new position, m
- draw the circle (in its new position)
}
- // at this point the circle has been dropped at a location
// we need to check here if the placement is appropriate.
- boolean placementOK = true; // perhaps there's nothing to check
- if (circle outside bin) {
// you could, if you want, stop the game here
} // otherwise it's still OK to place it here
- for each of the previously placed circles j (j from 0 to number - 1)
if (circles[j] overlaps c) then
- if (placement location not appropriate) {
- don't place the circle there
- don't generate a new circle (keep working with this one)
// what do we do next? where do we go from here?
// (hint: what else is left to be done in this loop?)
} else {
- go ahead and place the circle there
- also store the circle in the array
// we need a number for that, who's counting the circles?
circles[number] = c;
- generate a new circle c
- number = number + 1; // the number of the circle we're working with
}
}
Last updated: Apr 11, 2000 by Adrian German