Robot 28
Well, I don’t know. A bit more on motion. Maybe a start on scan/look? Oh and a better look for the robot?
The robot can now turn left and right, in the code, and left on the screen, using the “r” key to mean either rotate or right, I’m not sure which. But we use “l” to mean look, so that w and s and be forward and back, because WASD is a common set of keystrokes in games for left, forward, back, right.
Presently, scanning works, but screen rotation does not. I had planned to rotate the screen so that the robot always moved “upward” no matter how it was aimed. Just now, it will move side to side, and the screen orientation does not change:
In the movie above, we see the robot wandering, and we see the effect of our current scan limit being 5, but there’s no way to know which way the robot is facing. We either need to rotate the screen so that you’re always facing the top of the screen, or to give some indication of which way you’ll move.
Let’s make an oriented icon represent the robot.
Robot Display Icon
I don’t even remember how drawing works. Let’s have a look. Ah. It all comes down to this, drawing at world coordinates x and y, 10 on either side of wherever the robot is.
function drawCell(x,y, size)
local fact = CurrentRobot:factAt(x,y)
if x == 0 and y == 0 then
fact = "R"
end
local gd = GridDisplay:display(fact)
if gd.color then
fill(gd.color)
else
noFill()
end
rect(x*size,y*size,size,size)
if gd.text then
fill(0,256,0)
text(gd.text, x*size,y*size)
end
end
We have this little gimmick as well:
function GridDisplay:display(content)
if content == "OBSTACLE" then
return {color=color(0,256,0,128)}
elseif content == "PIT" then
return { color=color(0)}
elseif content == "R" then
return {text="R", color = color(0,256,0,48)}
else
return {color=color(0,256,0,48)}
end
end
This just returns a little structure that tells the drawing whether to draw just a fill (for obstacles or pits) or a letter (for other facts, of which we have none really, or the robot.
Let’s try something. In Codea’s assets, there are a lot of sprites and such. Let’s enhance the R structure to include an asset.
elseif content == "R" then
return {asset=asset.builtin.UI.Yellow_Slider_Up, color = color(0,256,0,48)}
Now in the draw code, we’ll check for that option:
if gd.asset then
pushMatrix()
scale(0.8, 0.8)
sprite(gd.asset,x,y)
popMatrix()
end
The 0.8 was cut in by looking to see how big the arrow appears. It looks like this:
I think I like that. The change broke one of GridDisplay’s tests. And we want to make it rotate. Let’s do that first. I propose some grotesque hackery here for a bit. Please consider holding your nose.
if x == 0 and y == 0 then
dir = CurrentRobot._direction
fact = "R"..dir
end
I’m going to pass in “RN” for robot facing north, etc. This may not survive long term but I’m trying to get a rope cast across this ravine so that we can get to the other side.
Now in the display:
function GridDisplay:display(content)
if content == "OBSTACLE" then
return {color=color(0,256,0,128)}
elseif content == "PIT" then
return { color=color(0)}
elseif content and content:sub(1,1) == "R" then
local a = self:assetInDirection(content)
return {asset=a, color = color(0,256,0,48)}
else
return {color=color(0,256,0,48)}
end
end
So if we get any of RN, RE, RS, RW, we’ll do this:
local assets = {
RN=asset.builtin.UI.Yellow_Slider_Up,
RE=asset.builtin.UI.Yellow_Slider_Right,
RS=asset.builtin.UI.Yellow_Slider_Down,
RW=asset.builtin.UI.Yellow_Slider_Left,
}
function GridDisplay:assetInDirection(Rdir)
return assets[Rdir]
end
And the result is quite nice:
I rather like that. We have a broken test:
_:test("GridDisplay", function()
local gd = GridDisplay()
_:expect(gd:display(nil).color).is(color(0,256,0,48))
_:expect(gd:display("OBSTACLE").color).is(color(0,256,0,128))
_:expect(gd:display("PIT").color).is(color(0))
_:expect(gd:display("R").color).is(color(0,256,0,48))
_:expect(gd:display("R").text).is("R")
end)
Now I could have worked on this test first, and driven out the result, but I needed to see it on the screen. So we’ll update the test now:
_:test("GridDisplay", function()
local gd = GridDisplay()
_:expect(gd:display(nil).color).is(color(0,256,0,48))
_:expect(gd:display("OBSTACLE").color).is(color(0,256,0,128))
_:expect(gd:display("PIT").color).is(color(0))
_:expect(gd:display("RN").color).is(color(0,256,0,48))
_:expect(gd:display("RE").color).is(color(0,256,0,48))
_:expect(gd:display("RS").color).is(color(0,256,0,48))
_:expect(gd:display("RW").color).is(color(0,256,0,48))
_:expect(gd:display("RN").asset).is(
asset.builtin.UI.Yellow_Slider_Up)
_:expect(gd:display("RE").asset).is(
asset.builtin.UI.Yellow_Slider_Right)
_:expect(gd:display("RS").asset).is(
asset.builtin.UI.Yellow_Slider_Down)
_:expect(gd:display("RW").asset).is(
asset.builtin.UI.Yellow_Slider_Left)
end)
Kind of boring but it’s the right thing to have done.
Now as to the code itself … we have no checking of any kind. If the display code asks for a fact “RQ”, we’re not going to display anything, or we might even explode … though I’m rather certain that a nil asset just doesn’t display.
So this is not ready for prime time, but we have a lot of display work ahead of us, so I think it’ll do for now.
You may hate me for this, but I think the stakeholders will love it, so commit: Robot now displays as yellow arrow pointing in its direction of motion.
WASD
Now that we’re here, let’s change the motion to use WASD throughout. I’m not sure what we’ll do longer term, especially for moves beyond one square at a time. But for now:
_:test("robot wasd", function()
local world = WorldProxy()
local robot = Robot("Wilbur", world)
_:expect(robot:x()).is(0)
_:expect(robot:y()).is(0)
robot:keyboard("w")
_:expect(robot:y()).is(1)
robot:keyboard("s")
_:expect(robot:y()).is(0)
robot:keyboard("d")
_:expect(robot:direction()).is("E")
robot:keyboard("a")
robot:keyboard("a")
robot:keyboard("a")
_:expect(robot:direction()).is("S")
end)
The w and s should work but the d and a do not.
10: robot wasd --
Actual: N,
Expected: E
10: robot wasd --
Actual: N,
Expected: S
As expected. Change the keyboard code. I’m going to remove the old keys, “1” for forward and “r” for right. A couple of tests may break.
function Robot:keyboard(key)
if key == "l" then
self:scan() -- look
elseif key == "w" then
self:forward(1)
elseif key == "s" then
self:back(1)
elseif key == "d" then
self:turn("right")
elseif key == "a" then
self:turn("left")
end
end
Yes …
5: Robot moves forward on 1 key post --
Actual: 0,
Expected: 1
I decide that that test is worth keeping, since it is checking the facts, not just the coordinates, so I revise it to use “w”:
_:test("Robot moves forward on w key", function()
local world = WorldProxy()
world:addFactAt("O",3,1)
local robot = Robot("Louie", world)
_:expect(robot:y(),"initial").is(0)
robot:scan()
_:expect(robot:factAt(3,0)).is(nil)
robot:keyboard("w")
_:expect(robot:y(), "post").is(1)
robot:scan()
_:expect(robot:factAt(3,0)).is("O")
end)
Should be green now. We are. Commit: robot motion converted to WASD. A and D only rotate.
Nice progress. Let’s sum up.
Summary
It’s Sunday, so this is enough “work”. An opoportunity of the right size appeared, improving the display, and a random idea of looking for an oriented icon in the Codea assets found a very nice arrow icon. The GridDisplay, really just a method returning a struct, is an object waiting to be born. As we come up with more obstacles, and ways to display them, I expect that GridDisplay will grow up. For now, it’s good enough.
Then, another feature that has been waiting was to convert to WASD, and that, too was quite simple. We can imagine improving keyboard handling, if the game is going to be driven from the keyboard, but again, for now, it’s good enough to demo the game.
And, given the new icon and the ability to move forward, backward, and to turn in either direction, even our walking skeleton demo is much improved. I’m sure our new Robot Overlords, whom I for one welcome, will approve.
Me, I’m going to drink my chai latte, eat a banana, read some trasy novel, and wait for breakfast.
Hoping you are the same, I remain, yours etc etc …