What Does the Robot Say?
The Robot World Repo on GitHub
The Forth Repo on GitHub
I’ll begin the morning with a bit of ‘design thinking’ about how we might interface our Forth to a bunch of robots. During that we find and fix a bug! Er, defect.
I am pretty sure that the robot does not say “wa pa pa pa pow”, but what does it say, and what can we say to it?1 Some of the things it knows can pretty readily be represented as simple numbers. The robot will have an X and Y coordinate. It will be facing in some direction, which we can represent by zero through three.
It can be holding something. If it can only hold one thing at a time, we could represent that by zero (holding nothing) through N (however many different things one might be holding).
We have the notion of “scent”, or “aroma”, which if it hasn’t been elaborated yet, might be. I think that scent would have various types, scent 0-N and for a given scent, a level 0-N.
There is also, currently, a concept of “vision”. The vision data inside the robot game is a list of triples, the object indicator (block, robot, …), plus its x and y coordinates. Our current bot detects patterns in its vision, only considering what is in the three cells it is facing. It will pick up a block if the pattern has a block in front of it and a blank space on at least one side of the block. It will drop a bot if there is an empty space in front of it and a block on at least one side of the empty space. (It also considers scent.)
Our Forth has no notion of strings or lists or triples. Well, almost none.
We do have the ability to define a variable that contains more than one cell. I’m not certain whether we have it right or not, but you can say
VARIABLE VISION 9 ALLOT
And thereafter, VISION
can be treated as an array of 9 elements. If we said:
VISION 1 + @
The stack will contain the index of whatever is in front of the bot, if it is facing north. Probably. Something like that.
Thinking a bit more deeply … I think it would be best if VISION took the bot’s direction into account, so that cell 1 would always be the cell in front of the bot. So what would be the Forth code for CAN_TAKE? Let’s suppose that 1 is the code for a block and 0 means nothing present.
So we can take if VISION[1] is 1, and either VISION[0] is 0 or VISION[2] is zero.
- Oops!
- I was just experimenting with some small Forth definitions that might be useful for vision and have discovered a bug in the Forth code, in the REPL. If you attempt a definition with a word that is undefined, the error message does not come out and the REPL prompts … forever after. Let’s divert and fix that.
Bug Defect Fix2
I think the issue will be that when we get an error while compiling, we do not reset the compilation state. Let’s see if we can write a test to show that, and then fix it.
def test_clears_compilation_state(self):
f = Forth()
msg = f.compile(': foo bar ;')
assert msg == 'Syntax error: "BAR" unrecognized ?'
assert f.compilation_state == False
That fails on the check for False. And:
class Forth:
def abend(self):
self.stack.clear()
self.compile_stack.clear()
self.compilation_state = False # <=== added
self.return_stack.clear()
self.active_words = []
Green. Commit: fix failure to clear compilation_state in abend.
All better.
Now, as I was saying …
I was experimenting with some words for dealing with vision. Here’s a sketch:
Forth> variable vision 3 allot
ok
Forth> : v_l vision @ ;
ok
Forth> : v_f vision 1 + @ ;
ok
Forth> : v_r vision 2 + @ ;
ok
Forth> : left_blank v_l 0 = ;
ok
Forth> : right_blank v_r 0 = ;
ok
Forth> : front_block v_f 1 = ;
ok
Forth> 1 vision 1 + ! (put block in front)
ok
front_block .
1 ok
Forth> right_blank .
1 ok
In those statements, we defined an array vision
with 3 elements, and then defined three words v_l
, v_f
and v_r
to return elements 0, 1 and 2 of that array.
Then left_blank
is a word that returns 1 if v_l
contains zero, front_block
returns 1 if v_f
is one, and right_blank
returns 1 if v_r
is zero.
If we had and
and or
, we could write this:
: can_take front_block left_blank right_blank or and ;
I’m chuckling, because that definition is arguably more clear than the one in Python:
class Bot:
@property
def can_take(self):
is_scent_ok = self._scent <= self.take_threshold
return is_scent_ok and self.vision.match_forward_and_one_side('B', '_')
class Vision:
def match_forward_and_one_side(self, center, side):
return self.forward_name() == center and (self.forward_left_name() == side or self.forward_right_name() == side)
We could have written out the whole thing longhand in Forth, but I think one generally wouldn’t:
: can_take vision 1 + @ 1 = vision @ 0 = vision 2 + @ 0 = or and ;
That might be right but the other version is more obviously right, in my view.
I think this has been productive thinking and doing. Let’s sum up.
Summary
Rather than just speculate about the Forth code, and rather than just typing it into the article, I started typing real Forth statements to see how we might really express things. In doing that, I discovered a defect, which was quickly repaired. Good news!
And those little experiments begin to give me a sense of how we might connect our Forth to our robots. We know that our robots have those few values, location, direction, vision, scent, aroma. And we know there can be many robots and blocks and whatever in the world, at various locations, facing in various directions.
So I’m imagining that it’ll work something like this:
The world will display the world map, updated all the time. The player provides a Forth file which must define some keyword, perhaps EXECUTE
. For each of the players bots, the world will initialize some key variables with the current values of that bot’s status. We might have B.X, B.Y, B.DIRECTION, B.SCENT, and so on.
We’ll provide some kind of array variable for VISION, and will always arrange it so that, no matter which direction you’re facing, the cells in front of you are numbers 0, 1, and 2. Either we’ll provide words like v_l
, or we’ll leave it to the player. We’ll lean toward providing as many convenient words as is reasonable.
There will need to be some player values in the world’s bot, so that the player’s code can store state information. We might arrange that VARIABLE
defines a variable local to the particular bot, or perhaps provide another word for that purpose. Since you can ALLOT
more than one cell to a variable, and can define names within the array, as in my example above, that should provide most of what is needed.
Then there will be the primitive operations corresponding to what you can ask the robot to do in the world. Those include step, direction, take, drop, whatever. Those will update the bot’s Forth values, of course.
I don’t quite see how we’ll manage switching from one bot to the next, and I’m sure there will be plenty of other details to be worked out.
Before we’re ready for that, we have work to do within the Forth app, including reading Forth from a file, and additional words like and
and or
, or whatever they are called in Forth. At a random guess, we might integrate Forth and the robot code within February, but more likely not until March.
We’ll see. There’s no rush. We’re here to explore, not to finish. This gives us liberty to explore ideas that we can choose to apply when we’re programming to build a product or the like. Well, you can choose. I choose not to do that sort of thing any more. Although, you never know … but no.
For today, we have a better understanding of how we might run robots with this thing. See you next time!
-
That may or may not be what the fox says. If you do not understand this reference, you are a good person. ↩
-
I try to avoid the word “bug”, which seems to imply that defects just kind of creep into the code all on their own. Fact is, we put each defect in, so I use the word “defect” as a reminder to myself that when something isn’t right, it’s on me. ↩