I'm going to end the week with our most elaborate world program yet.
What we saw earlier was that cats have a mind of their own.
When they get to the edge of the box, they just keep walking off the screen.
Cows on the other hand, cows are a docile creature.
When they get to the fence they turn around and they go back the other way.
So, we're going to do a cow animation now.
And you can kind of see the key way it's going to be different, right?
A cat only has a position, it always goes the same direction, but a cow has two
changing properties. Its position and its direction.
So, this is going to be a compound data problem.
It's going to be a how to design worlds problem with compound data.
This is a long video, you may not want to watch it all at one sitting.
But a world problem is a long problem and compound data makes it a bit longer, and
there is a couple of very interesting points that happen here along the way.
So, I would encourage you to be sure to watch this whole video through.
Again, a good thing to do might be to stop it every now and then, and try to
work a bit of it on your own, getting ahead of the video.
But don't do that when you get to the render cow function, because there's a
special thing going on there that we need to talk about.
What I have open here in Doctor Racket is the starter file.
We're also providing intermediate solutions at different points along the
way of developing the whole solution. And as I said, I'll be dropping into
different places in this as we go. So, here's the problem statement worked
out in a bit more detail. We're going to have a cow that walks back
and forth across the screen, and when it gets to the edge, it changes direction
and goes back the other way. And we're giving you a couple nice images
of cows, which we got from Brown. Of course, with any world program, the
first step is to do an analysis. I have the analysis already worked out.
And here it is, and mostly it's like the analysis of a cat program.
A lot of the constant information is the same: the width, the height, the center
y, the background. One difference is here the cow has two
images: the image where it's facing to the right and the image where it's facing
to the left. That's one difference.
The most important difference is is reflected in the center picture and also
in the changing information, which is the key here.
There are two changing information values associated with the cow.
The cow's x coordinates and the cow's direction and speed.
We're going to call the direction edx for the change in x.
So, that is how we're going to name label the velocity, which is, which is the
change in the position. Another difference shows up here in the
third image, where I've made a detailed note to myself describing what's going to
happen when the cow gets to the edge. So, what I'm saying here is that if the
cows center, cow x, tries to go past width or past 0, then we're going to
reset it. So, here the decision I'm making, is that
I'm going to let the cow get all the way up to the edge of the box but not
outside. The big-bang options are relatively
straight forward, there's on-tick, to-draw, and on-key.
So, now I'm going to jump to the v zero solution, and in all of these solution
files, you can see a list of kind of where each one is in it.
You might choose not to read that, so that you don't get too far ahead.
What you might want to do now is try to do some of this yourself and then use
these solution files to catch yourself up, or watch the video to catch yourself
up, or follow along with me either way. So, look here is the constants, and the
constants are relatively straightforward. There's the width, the height, the center
y. There's two cow images and the
background. Nothing really new there.
Now, I'm going to go on to the data definition for the changing world state.
On looking at the analysis, I see that there's two pieces of changing
information. Two changing information values that
naturally belong together. And so, that tells me that I want to use
a compound data definition. So, when you use a compound data
definition, the first thing we have to do is a structure definition for it.
So, let me make a structure, and I guess I'll just call it cow.
And it has two fields, and I'll call one x for the x coordinate and one dx for the
current rate of change in the x coordinate.
And I will say cow is make cow. Now, we know that this cow nicely stays
in the box. So, that means we know that the
x-coordinate is bounded a certain way in this world program.
So, what I'm going to do here is I'm going to say, Natural 0, width, as a way
of saying that the cow's x-coordinate stays within the boundaries of the box.
Now, we don't know of any bounds on dx, on the velocity of the cow, but let's
just decide that it's going to be at least be an an integer.
That'll make some things simpler. So, we'll say that this is an integer.
We'll say that the interpretation of a cow is that, if you have a cow make cow
with an x and a dx. Then that's a cow with x coordinate x and
velocity dx. We'll say that the x is the center of the
cow. We'll say that x is in screen coordinates
or pixels. And we'll say that dx is in pixels per
tick. So that if dx is three, for example, that
means that on each clock tick the cow moves three pixels, where its x value
gets bigger by three. Now, here we're to start to see with some
examples get a bit more interesting. I'm going to make a couple of them, let's
say C1. Let's say this is a cow that its current
position is 10, and that its current dx is 3.
And I'll just label this one to help me understand the interpretation at position
10 moving left to right. And here's another one.
And this one is at 20, and it's moving the other way.
So, this is a case where as the data definition becomes more complicated, the
examples become more useful to really understand how the data definition works,
and what the interpretation is. Now, of course, I have to do the template
and template rules used. But what I'm going to do to speed this
video up a little bit, is I'm kind of going to pull, pull the next stage of the
roast out of the oven ready done. I'll go to cowabunga v1.
And it already has the complete data definition here, everything that we just
did plus the template and template rules used.
Next step is going to be to set up the main function, and all the wish list
entries for the big bang option handlers. And since that's straightforward again,
I'm going to jump ahead to go to cowabunga V2 that has all that.
You might want to not do that, and go ahead and develop that yourself as a way
of checking your work. I'm going to jump ahead now though.
So, here I'm in cowabunga V2, and it's got the data definition we did before.
And it's got a main function that calls big bang with the three handlers:
on-tick, to-draw, and on-key. And I've given those functions the names:
next cow, render cow, and handle key. And here's the wish list entries.
The signatures are formulaic, of course. Once we know that the changing real state
is represented by type called cow, all these signatures come automatically.
But I did spend some time thinking about the purposes that I put in here.
So, I said for next cow that it increases the cow x by dx, and it bounces off the
edges just to remind me that when the cow gets to the edge it has to turn around.
In this one I said, place the appropriate cow image on MTS at its current position
and center-Y. And appropriate here is reminding me that
sometimes I use the cow that faces right, and sometimes I use the cow that faces
left. And for handle-key, I put reverse the
direction of cow travel when the Spacebar is pressed.
At this point we're set up to go, and all that has to happen is to complete the
design of these three functions. There are a couple of interesting things
that happen in the design of these functions though.
So, I'm going to go ahead and work through next cow and render cow in more
detail, so that you could see how that plays out.
So, now I need to work my wish list until it's done.
And the first WishList entry is for next cow, so let me start with that.
I've already got the signature, purpose, and stub, I need to work on the examples.
So, let's see, examples are going to look something like this.
Next-cow, they're always going to consume some sort of cow, and they're always
going to produce some sort of cow. So, that's kind of the skeleton of an
example. Let's try to fill them in some.
Let's see, a simple case is that the cow is somewhere in the middle of the screen.
Let's just assume that 20 is less than the full width.
And it's moving at a velocity of three, which means it's moving three pixels to
the right each time. And that means that on the next tick that
cow should be at 20 plus 3, and still moving at the same velocity.
So, that's a cow in the middle moving left to right.
Let's do a cow in the middle that's moving right to left.
Let's see that cow would just be at minus 20 and three, and still moving right to
left. So, that's those two cases, these are
middle cases. I said in the analysis that we would let
the cow go all the way up to the edge of the box but without changing direction.
So, let's do that case next. Let me make some copies of this x before
I added it. So, let's see, we might have a cow moving
from left to right that's just about to get all the way to to the edge of the
box. So, that might be a cow for example
that's at width minus three and moving three pixels to the right per tick.
So, that cow after this tick will be all the way to width, and still think it's
moving towards the right. It hasn't bounced yet, because we said
were going to let it get all the way to the edge.
It'll bounce the next time. And another cow might be, let's see, it
might be at three moving with speed minus three.
And that means that when this tick is done it will be all the way at zero, and
still think it's going to head towards the left even though its not.
You know, bounce on the next tick. So, these are somewhere, these were, how
we would describe these cases. These are cases where it's going to
reaches, the cow reaches the very edge. And now it seems like there's going to be
two more cases for when the cow would actually pass the edge.
Let's see, there's a case where the cow might be at, let's say width minus 2,
moving 3 to the right each time. And what are we going to do with that
cow? Well, let's put it all the way at the
edge but now headed back towards the center.
It needs to change its direction now. So, we'll change its direction to that.
Basically make it reverse its direction, because it was, it had, it would, it
actually tried to leave the box. So now, its going to be at the edge, but
its going to be headed towards the center of the box again.
And the corresponding case of course is that there's a cow at two that's headed
towards the left. And we'll say no, no, no, you can't leave
the box, you stay at the edge of the box, and now you're heading the other way.
So, these are cows that these two counts. They try these cases, try to pass the
edge. And let's see all those are well formed.
They are well formed. And you can see that it's a little bit
complicated. That's a feature of this design method,
which is that I'm working out all these complex boundary cases before I actually
get to coding the general purpose function.
I've got some concrete examples to help me understand the function.
And, I've got tests for when I get the function going.
So, now let's see, let's go get the template.
There's the template for a cow. And I've got these notes here in the
template. I'll make a note to myself it says I took
template from Cow. I'll rename this function to next cow.
And I've got note for myself that it says the template has two fields and it also
tells me the types of fields to remind me what's going on there.
Now, let's think about the examples for a second.
What do the examples tell us in terms of how many cases of examples are there are.
In all four of these cases, the cow really keeps doing whatever it was doing.
Here it goes from 20 to 23, and its direction doesn't change.
Here it goes from 20 to 17, and its direction doesn't change.
Here it goes from width minus three to width, its direction doesn't change.
Here it goes from 3 to 0, but its direction doesn't change.
These four are all really the same case, they're just the keep doing what you're
doing case. But these two are different, this is the
case where the cow tries to leave the right hand edge of the box.
And this is the case where the cow tries to leave the left hand edge of the box.
So, my next step in building the template for this function is I'm going to give
myself a three case cond. Okay.
I'm going to say cond, and then in each case, now that I know what these types
are, I'll just delete this note. And then in each case, I'll give myself
two copies of the template to work with, the question version and the answer
version. And I'll have three of these.
So now, let me see how do I think about these cases.
Well, this case here is the case where if the cow kept going, it would leave the
box by the right edge. It would go too far.
What does that mean it we go too far? Well, it means that if I took the cow's
current x position and I added the dx to it, this expression here is what the cows
position would be if it just kept going. In, in this case, what happens is that,
if the cow just kept going, then it's position would be greater than width.
The cow would leave the right edge of the box, and of course we don't want the cow
to do that. We want the cow to be a nice docile
creature that stays inside the box. And so, instead of letting it leave the
box, we're going to say, no, no, no, no, you're going to be a new cow that is
going to be right at the edge of the box in your new velocity.
We will basically do the opposite of your current velocity.
And in Racket, if you call the minus primitive with a single argument, it just
changes the sign of the number. So, if you call minus with three, it
gives you minus three. If you call minus with minus six, it
gives you six. So, this changes the direction of the
cow, and this puts it right at the edge of the box.
So, that's this case here. So now, let's look at this case here.
Well, this is the case where if you let the cow just keep going, this is the cow
that just keeps going, then it would end up outside the left edge of the box.
Its position would end up less than zero, and of course we don't want the cow to do
that. We want it to stay in the box.
So, we're going to say, no, instead we're going to have a cow that's at the very
left edge of the box. And once again, we're just going to flip
its direction around. So, that's this case.
And now, all these other cases are just the cow keeps doing whatever it was
doing. So, we don't need a question here, we'll
just put an else. And we'll say, well the new cow is a cow
that keeps doing whatever it was doing, that's plus cow of x and cow dx.
This is the same expression used here and here to see where will the cow end up if
we just let it keep going. Well, this cow is going to just keep
going. And its new dx is just going to be the
same as its old dx, because it's just going to keep going.
Let's try all our tests now. Whoops, make is wrong, this is supposed
to be make cow. All six test paths.
Now look, this is in some sense the most complicated function we've designed so
far. Now, it's complicated entirely due to
domain knowledge. It's complicated, because the geometry of
an object moving back and forth across a box, and handling the edge conditions
right is a bit complicated. But notice something important.
If you break it down into all of these six examples, and then you realize that
those six examples are really just three cases, then it isn't complicated.
The design method really worked for us here.
It told us to do the examples first, which broke the problem down, and helped
us then take the template and put structure onto it.
Here's an example of the design method making a moderately difficult problem a
lot easier. What I've done now is to jump to the v3
version of the cowabunga solution. That has the next-cow solution, which we
just developed a few minutes ago. And I've actually done a bit more than
that. I've started doing the design for the
render-cow function. Just to save time, I did the first part
of the design already. So I've got the signature, the purpose,
the stab, and I did the examples. And the examples for this function are
actually relatively straightforward. If I render a cow that's at position 99
and has a positive velocity. In other words, it's, it's facing towards
the right edge of the screen. Then, what this function needs to do is
to place an image of the right facing cow at x position 99 at y position center y
onto the background MTS. And I need two tests for this function,
because sometimes it places a cow facing right and sometimes it places a cow
facing left. And so, I've got a second test here where
the cow is going in the different direction.
And to be honest, this test would actually be a little bit better if I
changed the x coordinate just to be sure that I couldn't accidentally have put in
by accident 99 into the actual solution. So, let's just make this be you know, 33,
and we'll make that be 33. And now those are two pretty good
examples, they vary both the direction that the cow is facing and the x
coordinate. So, I've already got the template, let's
just start filling this in. Let's see, both examples tell me that I'm
going to place an image. And sometimes it's one image and
sometimes it's another image. Let me just put dots there for a second.
And I always place the image at the cow's x coordinate.
And I always place the image at center y. And I always place the image on the
background. That's all of it, except for this part
here where I have to decide exactly which image to place.
Now, there's two choices here. One choice is I could Start to put an if
expression here that would choose which cow image to use.
Well, I'm going to do something slightly different, and it's going to be something
that we'll talk a lot about in the course as we go forward.
Which is I'm going to say to myself you know, this function is actually doing two
things. This function is deciding which image to
use and putting it in the proper place. Two tasks.
And as a general rule, we don't want functions that do two tasks.
We want each function to do one task. So, what I'm going to do is I'm going to
wish for a helper. I'm going to say you know, it really
would be great here if there was a function called choose-image.
And I could call that function with a cow, and it would give me back the right
image to use. If I had that, then I would be done.
And so, I'm just going to wish for that to exist.
And I have to wish in a little bit more detail.
I'll wish in a little more detail by writing a WishList entry for it.
So I'll say cow to image, produce. Now, what does it need to do, well, it
needs to choose the right cow. Produce RCOW or LCOW depending on
direction cow is going. And the reason I write this signature on
purpose right now, and I write it in detail right now, is that right now after
I just finished wishing for this function, I know what I want it to do.
So, I'm going to take the trouble to write down what I want it to do.
And then I'm going to say, oh, but this doesn't exist yet.
I hope it will some day, and I'll make a stub.
Choose-image and sends a cow, and it's got to produce some image.
I could make it produce the blank image. In this case, I'll just make the stub
produce the right cow. And now I run this to see if it's
well-formed. And it is well-formed.
And what we're going to see paradoxically is that one of these two render cow tests
actually passed. The first one actually passed just
because it's choose image stub always produces the right facing cow.
So now, I've basically done render-cow. If somebody goes and fulfills this wish
that I made for choose image. And that somebody is going to be me a
little bit later when I say to myself, hey, what what work remains to be done.
Let me search for bang, bang, bang, oh, I need this function choose-image.
Okay, great. Let me do it.
Lets see what does it do, it consumes a cow, it produces an image.
It produces either the right cow or the left cow depending on the direction the
cow is going. So, I'm going to speed this up a bit.
I've got two examples. One example for when the cow is moving
left to right so, it's facing toward the right.
Another example for when the cow is moving the other way, and so, I need the
left cow image. I'm going to go and get the template from
cow. And now, let's see, there's two cases.
So I'll use an if, and the question is whether the dx of the cow is greater than
zero. If it is, I use the right-facing cow,
otherwise I use the left-facing cow. And there's a bit of an issue here I have
to deal with, which is what to do if cow dx is actually zero.
And in that case, I'm just going to go ahead and decide to use the left-facing
cow. Now when I run it, I get that all my
tests pass. And that includes the three tests for
choose-image, but it also includes the two tests for render-cow, because
choose-image is a helper for render-cow. So now, render-cow is working completely,
because choose-image is working completely.
Let me just recap what happened there. We were in the middle of coding the
function render-cow. And we knew it had to place an image, and
we knew where it was going to place the image, and we knew it had to decide what
image it was going to place. And that's when this idea about each
function should only do one task came up. What I did was I decided you know,
deciding where to put the image, and deciding what the image is, it's two
tasks. I'm going to delegate one of the tasks to
a new helper function, choose-image in this case.
We're going to talk a lot more about this idea of helper functions as we go along.
And in some cases, we're going to give you very clear cut and dried rules for
when to use helper function. This is not one of those cut and dried
cases. In this case, you have to kind of decide
that choose image is its own separate task.
And some programmers wouldn't make a separate function for choose-image,
because it's so small. But this is a reasonable place to use a
helper function, because there really is a separate task here.
And as we work through this part of this course, we want you to look out for
places where there is a separate task and it makes sense to use a separate
function. What is clear is that when I go back to
read this function, and I go to read render cow, it says okay, let's see.
I'm going to place an image and somebody else is going to choose the image, and
I'm going to put it at the cow's x coordinate, Center Y and MTS.
That's very clear to me. It says, I'm placing the image and
somebody else is choosing the image. And now, when I go down here and
re-choose the image, it says, well. If the dx is greater than zero, it's
right facing, otherwise it's left facing. This code is easy to read.
And at this point, I have all of my handlers done except for handle key.
So, I actually can run this program at this point.
And I could say, main, make cow. The cow that starts at the left edge and
is moving three ticks, three pixels per tick.
And there's our little cow. I'll leave it to you to finish the key
handler, as I said the solution is there in cowabunga v five.
And the problem description also talks about a fun little thing you can do,
where if you take the cow image and you take each image and rotate it a couple
degrees left and a couple degrees right. And then you take a speed that's for
example, an odd number. And you arrange to use different cow
images depending on whether the position is an odd or an even number.
You can make the cow appear to waddle as it crosses the screen, and that looks
kind of neat. And there's all sorts of other fun stuff
you can do here. Just go ahead and have some fun with this
cow program, that will be good practice for doing this week's homework
assignment.