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