-
This video is a Redo for the MOOC.
-
Redo means do it yourself,
-
watch the video and do what is
shown step by step.
-
It is also an invitation to
look at how we program.
-
I won't follow a precise script,
-
I will try to do it,
but if I encounter bugs or
-
things like that, I'm gonna
handle them as I go along.
-
In this video, the idea
is to program a little
-
language that you can find
in role-playing games like
-
"Dungeons and Dragons".
For example,
-
for the ones who have this
expression, what
-
does this expression mean?
-
It means: you must throw 2
20-sided dice and 1 4-sided.
-
You'll see it can be a Pharo
expression.
-
So in this video we're going
-
to implement a class representing
a die and a class
-
representing a handful of dice.
-
So let's begin.
-
We begin with defining a package.
-
We call this package "Dice".
-
I don't really want to see
-
other things. So in this
-
video I won't code in the
-
debugger, you've seen it in another
video, I will
-
do it on a case-by-case
basis, in an opportunistic way.
-
Here I define the "Die" class
which
-
has a certain number of "faces".
-
I compile. I add a class comment.
-
For the moment, not much thing
because it is very very simple.
-
We are starting to enable our
-
object to be initialized.
-
I will do it like this.
-
I call for an initialization of
the super-class,
-
by default I assign 6 faces
to my die because it is
-
the most common die.
-
Now I'm starting to
develop a
-
tests class, to be sure that what
we do
-
doesn't break what has already
been done.
-
Tests classes are sub-classes of the
TestCase class.
-
We call it "DieTest".
-
I have my tests class.
-
One of the first tests to do,
you
-
don't have always to do it
like this, but
-
in any case I want to begin with
a test that works well.
-
As for the moment we don't have many
things, I say that initialization is ok.
-
This is also a way to show you
-
how you can test that you can
catch exceptions
-
or that exceptions mustn't
occur.
-
Here I am saying:
-
"Die new should not raise error".
-
What does it mean?
-
It means that when I execute this
bit of code "Die
-
new", no error must occur.
-
I'm gonna classify my test
and execute it.
-
It's green. All right.
-
So now, I'd like to define the
-
method that makes a die roll.
-
I know that in Pharo there must
be a method
-
called "at Random".
-
At Random, what does it do?
-
It enables to have... Ok...
-
So now I look at the implementation
to be sure
-
it's ok. AtRandom, what does it
do?
-
It returns an integer at random
from 1 to self, so it's perfect.
-
So I'm gonna define a new
-
method in Operations.
-
What will it do?
-
Roll. I say: "you return faces
-
atRandom".
-
So I write a test for this.
-
TestRolling. What do we do now?
-
We create a die.
d:= die
-
new
-
And now I write
"1000 timesRepeat".
-
What? "d roll".
-
And I want this to be
between 1 and 6.
-
"Between: and:", it's
-
ok. "Between 1 and 6".
-
It is not very good
because here we created
-
a test only for 6-sided dice,
we could have said
-
it works depending on the
number of sides of the die.
-
We will do it later.
-
So I compile.
-
I get an error. Here it is...
-
It is ok, I have my test.
-
Now it's time to save.
Here I have my "Dice"
-
package, I save it locally,
"Save".
-
I had created others before
to train a little so I
-
create a new one
"New version with
-
rolling and test". All right.
-
Ok, it is saved.
-
Now I'd like to change
-
the creation interface.
First we rearrange
-
categories. If we want to change
a little the creation
-
interface. We say:
"to create a die
-
use die faces".
-
On this expression you must see
-
that faces is a message sent to
the die class and not
-
to an instance of die class,
as it is the case
-
in the roll method or in others
methods coded until now.
-
I will do this for you to understand
when you
-
have to use and go to the class
level or not.
-
Let's begin by writing a test.
-
"betterInterface".
-
If I go on with the same logic,
-
"TestbetterCreationInterface",
-
Here I'd like to do something like
this for instance,
-
and this to be faces.
-
I will do it slowly.
-
I go there and I type
"instance creation", faces: , anInteger.
-
I could write it in a short way
but here
-
I do it in a calm way.
-
I create a die.
-
I write "self new", as
self here is the die class itself.
-
I tell: "create an instance".
-
And now with this instance I
use
-
an accessor to assign it the value
passed as an argument.
-
Obviously, I return the die
that has just been created.
-
When the code will be executed,
it won't work because
-
faces doesn't exist, so don't
worry.
-
You see that the test isn't ok,
but
-
it's normal, if I
execute this
-
for example, if I do debug to see...
-
And I click on Over, here it
says: "I don't know
-
the faces message."
-
Here we will do it calmly, I won't
do it in the debugger.
-
I say: "that's true, I have
to add an accessor
-
here. So I write
faces: anInteger.
-
And there I write : faces := anInteger.
-
And while I'm at it, I create
the read accessor.
-
I return this one.
-
And here my test is green.
-
So we save, "save"
-
"better die creation
method with tests".
-
All right.
-
Now we can start to
-
define what we want for
-
diceHandle. Basically if we
look, diceHandle,
-
how would we like to write it?
-
We would like to write
diceHandle new addDie.
-
So now we are going to create
a new die, "die faces 6, addDie".
-
"Die faces 10".
-
We start to write a
-
test class, this time.
-
So a new class which
inherits from TestCase.
-
All right. I have my new
tests class.
-
And I define a test.
-
The idea is to create a
handful and to
-
check there are the right dice
in it.
-
I write "testAdding",
I want to reuse my
-
code, there is no reason otherwise.
-
So I have my
-
handle; yourself , because I
want to
-
get the message receiver,
it is to say the handle
-
and not the argument that is here.
-
Now what should I do?
-
I write "self
assert h diceNumber
-
equals 2".
-
I compile. Obviously the system
says: "I don't
-
know the DiceHandle variable. Do
you want it
-
to be a class?"
-
Yes. It must be a class.
-
Here it will define it.
-
As I know that I have
to stop the dice anyway
-
I take this opportunity to
-
put an instance variable.
-
I compile all this.
-
Now it's red because "Add die"
hasn't been defined.
-
So we will do it.
-
Before doing this, it will
be nice to initialise
-
the handle, so we do it like this,
it will prevent
-
to have a bug later.
-
dice : = OrderedCollection new.
-
Recategorize.
-
And now, I must be able to
run my
-
test, which will crash.
Ok, very well.
-
I create Add die.
-
Adding.
-
It says: "You should implement
this method."
-
Yes, it makes sense.
-
I write "Dice add aDie".
-
Ok, very good.
My test won't still
-
work because I still don't
have defined the diceNumber
-
method, let's do it.
Yes, diceNumber,
-
we will create it, in
accessing this time.
-
And diceNumber,
what will it do?
-
It must return
-
dice size. I compile again,
proceed.
-
And my test should be green
so, the tests
-
are green and I save.
-
"With addDie and test".
-
We could improve the test because
here
-
it checks that we add 2 numbers,
I'd like
-
to check that when
-
we add twice the same die
we don't lose it.
-
I write
"TestAddingTwiceTheSame DieisOK".
-
Here what do I do?
-
I add 6 and 6 and I want to
get 2
-
I do this I run my test,
it's green, super.
-
Now, it will be nice to be able
-
to define what it is to do
-
add 2 dice. But before this,
let's do something.
-
If you look, what I don't like,
-
when I inspect this for example,
-
if I do "Inspect"
here, I don't see
-
the dice values and it's not
practical to debug.
-
In the debugger, we don't see this.
-
So before going on, I want
to improve
-
this. I'm going to add
a method
-
in the Printing protocol.
The "PrintOn" method
-
is defined on all the objects of
the system and
-
it will convert an object to a
-
textual representation and
-
pass a stream. We will only precise
-
the representation we want
inside it.
-
If I do this, I've done nothing
in fact.
-
If I do super PrintOn, in fact I've
done nothing.
-
Now I will do
-
"aStreamnextPut", so I will
put characters in the
-
stream, but what will I
put first?
-
I will write a parenthesis with a
space, maybe it
-
will be nicer, a parenthesis.
-
Then I will consider faces
and convert them
-
in numbers, in strings, and
concatenate all this
-
with a closing parenthesis.
-
If I do this... I closed the debugger,
so I open it again.
-
I have the debugger. Now
I have a 6-sided die
-
and a 10-sided die. So it is
much
-
nicer, you will see, if we
encounter bugs, it will help.
-
So here I didn't do anything special,
my tests are running.
-
I save again, it doesn't cost
much, "With printing".
-
We write "with die printOn".
-
All right. Now we create the
test, we won't
-
do it, we will go
directly there.
-
We select "add
protocol", "roll", "operations".
-
So,
-
there are several ways to define
this.
-
I propose you one, this is not
the nicest but
-
at least it is probably the
clearest for you.
-
There is a compact way, I could
-
do it in one line, but using
iterators
-
like "Injected to", here I'm
gonna use a loop.
-
So what do I do?
-
I take a value that I
initialize to zero.
-
Then I do a loop on all the dice,
and
-
for each loop step I get
a die, and what am
-
I going to do with this die?
-
I ask it to get a die roll
and to add the result to
-
my variable.
-
Nothing very special but at least
it is very very explicit.
-
Now if I do "Inspect" and there
-
"Roll", 5, it doesn't prove
it is working.
-
Let's try once more.
-
11. Ok, it's working.
-
We are gonna try to write
a test, there is
-
no reason, so we do
"Test", I want to see this one
-
and I call it rolling.
-
So how do we have to do to test
this?
-
It has to be between one and
the maximum of the number of dice.
-
So we will do this.
-
We
-
could define a method
doing this.
-
Let's create a method
defining the maximum.
-
"Operation maxValue".
-
What is maxValue ?
-
It is very close to this.
-
Here instead of doing roll,
-
I will ask for the faces.
-
Let's check. If I do
"Inspect", there
-
I do "maxValue", "16".
-
Yes, it's right 10 and 6.
-
So we write a test for
-
"maxValue". So I have this,
I do "maxValue equal
-
16".
-
So here you see, I could have
coded something
-
very dirty in my test, but
finally, it is better
-
to create a method in the class
and to use it.
-
So now, we can test the
roll method
-
works well. Let's do
"roll",
-
and say it must be comprised
-
between... "Roll between 1 and
-
h maxValue.
-
If I do this, ok it works.
-
This is not very statistical,
so here we could do
-
something like
1 000 timesRepeat.
-
Ok. And there,
-
we have our 1000 tests.
-
Now we save. All right.
-
We save once more.
"save" added maxValue
-
and roll with tests.
-
We've almost finished,
-
what we want to express now,
is
-
instead of having "die faces 6",
-
I'd like to have "1 D6".
-
And
-
what you see at the end is that
it means "send
-
the message 6 to a small integer".
-
So we go and look at the
integer class.
-
What we are going to do is
-
to define a class extension.
-
What is a class extension?
-
I'm gonna package my methods
with the same name as my package.
-
So you will see, what do I do?
I add a
-
protocol, I put *, it must
start with *dice which is
-
my package's name, automatically
this is put in grey, and
-
it means the method will be
packaged
-
at the same time as this package.
So let's do it.
-
Let's imagine we do...
What is D6?
-
A D6... I have to think
a little about it...
-
We first create a handle because
it could
-
be 2 D6 finally.
-
So "handle", we do
"diceHandle
-
new",
-
ok, so I have created my thing.
-
Now for each receiver,
I will do
-
"self", this is my integer,
"timesRepeat".
-
We will have really used a lot
the timesRepeat, it's rare.
-
"TimesRepeat handle addDie", of what?
-
Of "die faces". And there,
-
we know it's 6.
-
And indeed, it would maybe be
good to return the handle.
-
So does it work?
-
We're going to test like this and
we write a test.
-
But if I do 2 D6, Inspect,
look,
-
I do have 2 D6. So that's cool.
-
Let's write the test.
-
We will categorize those tests
after all.
-
We write "testNewSyntax".
-
Here for the moment we only have
D6, we will generalize later.
-
We want to do exactly the
same thing
-
than this, so we will have
an handle, let's say 2 D6.
-
And there, we do
"selfAssert".
-
What could we test?
-
That diceNumber equals 2
-
for instance. So you've noticed
sometimes I use
-
diceHandle, I could have also
used = 2 here.
-
In general, it is nicer to use
assert equal
-
because like this, when there is
an error, the system
-
says: "I've received this and
got this value instead of..."
-
If I write =, it will say:
"I've got a wrong expression."
-
Here for the final user, who is
yourself,
-
as a developer, it is better
to use
-
assert equals because it will
say: "I've received 3 whereas
-
I was expecting 2", for instance.
-
So here, I do this.
-
Ok, it works.
-
Could we have a smarter
-
version of this test?
-
For the moment, it suits us.
-
You see that now in
-
the package, I have an extension
called D6.
-
We will generalize this
with
-
"aNumberOfFaces"
-
So "aNumberOfFaces",
we put it there.
-
And we rewrite D6 because it would
be better.
-
We write D6 like this. We do
-
returns self D6.
-
We do all the other ones.
-
We do 4,
-
2. It's more a coin than a die,
but 2,
-
10 and 20.
-
You've understood the principle.
-
So let's run the tests, as we've
changed the implementation.
-
2 D6... it means it works.
-
So let's save.
-
What is there still to do?
-
In fact, we have still to be able
to add the handles.
-
What tests do I want?
-
For instance, I want to be sure
if I do
-
"addingHandles",
-
(I can use the new syntax,
-
so it's nice)
-
I want to test that if
-
I write 2 D20 + 3 D5
-
or 3 D6 instead (don't start to complicate
-
things). How much should I get ?
-
diceNumber should be equal to 5.
-
So here you see that we have to define
the + operator.
-
In Pharo + isn't an operator,
it's just a message.
-
So we define a message on the
DiceHandle class.
-
We write +.
-
So "aDiceHandle".
-
Now we can wonder if
-
we modify the receiver or
either if we use
-
a functional approach.
-
I prefer to use a functional approach
-
in which we create
a new handle.
-
So I'm gonna create a
new handle, I write
-
"handle self class new".
-
Here I avoided to write
diceHandle and later there
-
will be a lesson explaining why.
-
I prefer, it's closer.
-
In general you don't hard-code
the classes' name.
-
You will see it in week 7 or
something like this,
-
there is a complete explanation.
-
If I do "self
dice do", I
-
iterate on my dice
and I add them in handle.
-
So I do
"handle addDie each",
-
and I do the same...
Here I don't
-
need self and in fact I don't
know
-
the message, that's what it was
telling me, and
-
it makes me notice that, indeed,
I haven't defined it
-
and it hasn't worked for
"diceHandle", but
-
no matter, let's compile first
and we'll fix it later.
-
So here, what does it mean?
-
It means it lacks an accessor,
dice.
-
So we add dice here,
dice returns
-
the collection of my dice.
-
Now I'm gonna test, see if
my test is ok.
-
My test is ok, it's super, it
means I have
-
almost finished, I save,
"with handles
-
additions". Ok, all right.
-
It means now we can write
-
2 D4 and we
-
can do "Roll", and it returns
a number.
-
Now you are ready to play
"Dungeons and Dragons".
-
What you have to know:
we defined
-
our methods, we defined
our tests, we run
-
them, we extended a system
class,
-
the integer class, with extensions
linked to our
-
package, which will only be visible
when our package will be loaded.
-
We also overloaded operators,
but
-
in fact we only defined a new
+ message, because
-
in Pharo the addition is
just another message,
-
this enabled us to
express quite easily a nice DSL.
-
So now it's your turn to code!