The basic
issue here is abstraction.
An instance of
"Robot" in your program is not a
physical
object. How could it possibly point North,
South, or
Nor-nor-west? It cannot.
Its location
and direction are abstract values
*metaphorically*
related to real world notions
like position
vectors and velocity vectors.
"North" in
this program is not a real thing,
it is an
*idea* which could be represented by
'North',
'north', #North, #north, $N, $n,
'Raki',
'raki', #Raki, #raki, $R, $r,
137, (0@ -1),
a picture of the star Polaris,
the colour red
(the conventional colour for
that end of a
compass needle which points north),
a sound
recording of a lecture by Alfred North
Whitehead, or
anything you please, as long as,
inside the
program, it *acts* the way *you* want
"north" to act
(which is not necessarily the way
the physical
direction North acts, and in fact in
this case it
most certainly is not).
Locations and
movements in a 2D space are, in Smalltalk,
commonly
represented by Points. "Represented by."
As for this
method:
test11_MovesTheRobotForward1SpaceInTheDirectionItIsPointingIncreasesTheYCoordinateOneWhenFacingNorth
| result |
result := robotSimulatorCalculator
moveDirection: 'north'
position:
(Dictionary new
add: 'x' -> 0;
add: 'y' -> 0;
yourself)
instructions: 'A'.
self
assert: result
equals:
(Dictionary new
add: 'direction' -> 'north';
add:
'position'
->
(Dictionary new
add: 'x' -> 0;
add: 'y' -> 1;
yourself);
yourself)
PLEASE tell me
that is not what they are actually using.
Let's start
with
(Dictionary
new)
add: k1
-> v1;
...
add: kn
-> vn;
yourself
Did you know
that sending #add: to a dictionary is not
portable?
Storing actual Association objects inside
Dictionaries
was originally an encapsulation error and
remains a
performance error, so there are Smalltalks
that do not
make that mistake. The *portable* way to
make a
Dictionary is
(Dictionary new)
at: k1
put: v1;
...
at: kn
put: vn;
yourself.
And why in the
name of sanity are the keys *strings*
instead of
*symbols*? This is not Smalltalk. It is
_javascript_ in
drag.
implementations.
For example, I completed the SML track,
and found that
the test code ONLY worked with Poly and
not with any
of the three SML implementations I already
had on my
machine. Since you are doing this in Pharo,
I take it that
exercism.io will insist
on the Smalltalk
track being
done in Pharo, and in that case it is
*nauseating*
to use a Dictionary when you could use a
Point.
Old-fashioned Smalltalk style would have been
to return
something like
#(<direction> <x> <y>)
e.g. #(north 1
0), and I still prefer that.
In fact *good*
Smalltalk style for something like this
would be
test11_MovesTheRobotForward1SpaceInTheDirectionItIsPointingIncreasesTheYCoordinateOneWhenFacingNorth
robotSimulatorCalculator
moveTo:
0@0;
head:
#north;
obey: 'A'.
self assert:
robotSimulatorCalculator heading equals: #north.
self assert:
robotSimulatorCalculator location equals: 0@1.
-- We're
starting to get the idea that identifiers like
robotSimulatorCalculator
are not a very good idea when
simulatedRobot
would do the job as well or better.
(This is also
pointing us towards Betrand Meyer's
Command/Query
Separation principle, but we shan't
go there
today.)
people. The
test code should use a SMALLTALK interface,
not a
warmed-over _javascript_ interface.
Now, how do we
map between direction *names* and
direction
*points*? Well, we have to start by
laying down
clearly what we *mean* by the directions.
To move North
one step is to add 1 to y and 0 to x.
(We know that
from the appalling test case above.)
To move South
one step is to add -1 to y and 0 to x.
(South is the
opposite of North.)
To move East
one step, oh we have a problem.
THIS NEEDS
SPELLING OUT. And one of the things the
exercism.io exercises are
HORRIBLY BAD AT is specifying
the problem.
Nearly every single exercise I have tried,
I have been
unable to tell what the problem is without
examining the
test cases, and that is not the way
exercises are
supposed to work. (Yeah, that's why I'm
screaming
about it. I've taught a class using exercises
like this that
were not of my writing and vague specifications
really upset
the students. People who had taken the class
under someone
else several years before were still angry
about it.)
The geometric
classes in Smalltalk were written to support
graphic user
interfaces. And in user interfaces, the y
coordinate
increases DOWN. So if we take the compass rose
and rotate it
so that North is DOWN, it follows that
West is right
and East is left. So
To move East
one step is to add -1 to x and 0 to y.
To move West
one step is to add 1 to x and 0 to y.
The chances
are excellent that the problem specification
is
inconsistent with this. Sigh. Let's proceed, though.
North 0@1
South 0@ -1
East -1@0
West 1@0
pointToName:
aPoint
^aPoint x
isZero
ifTrue:
[aPoint y > 0 ifTrue: [#north] ifFalse: [#south]]
ifFalse:
[aPoint x > 0 ifTrue: [#west ] ifFalse: [#east ]]
nameToPoint:
aSymbol
aSymbol =
#north ifTrue: [^0 @ 1].
aSymbol =
#south ifTrue: [^0 @ -1].
aSymbol =
#west ifTrue: [^1 @ 0].
aSymbol =
#east ifTrue: [^-1 @ 0].
aSymbol
error: 'not a compass direction in lower case'.
Another
problem I had with exercism was a "Space-Age"
exercise where
the README.md capitalised the planet names
but
test_Space-Age.<whatever> insisted on lower
case.
That might
well happen here.
Just for
grins,
Dictionary>>
asPoint
^(self at:
'x') @ (self at: 'y')
Point>>
asDictionary
^(Dictionary
new)
at: 'x'
put: self x;
at: 'y'
put: self y;
yourself