Let’s continue to prepare the Dungeon class to work on hexes as well as square tiles. I expect no real trouble. I look forward to learning why my expectations will not be met.

I’ve been thinking about our notion of “distance”. Generally we have methods about “closer” and “further”, but we have some that mention just “distance”, and then, drilling down, some that mention “manhattanDistance”. We also have at least one method that is using vector distance, which isn’t the same as manhattan at all.

I think we should stick to manhattan at the game level, if we possibly can, but I am OK calling it distance, because it is the distance that the princess would have to walk to get somewhere. (Monsters can move diagonally sometimes. Clever, these monsters.) So I might take one more look at where the “distance” calls come down to “manhattanDistance”. That distinction is one between game, and geometry, so if it’s in about the right place, it should be OK.

And I want those vector distances right out, but we’ll have to take a careful look at them, because decisions made on geometric distance will be made differently on manhattan distance. With any luck, we won’t care. Let’s do that first.

``````function Tile:distance(aTile)
return self:pos():dist(aTile:pos())
end

function Tile:illuminateLine(dx,dy)
local max = IlluminationLimit
local pts = Bresenham:drawLine(0,0,dx,dy)
for i,offset in ipairs(pts) do
local pos = self:pos() + offset
local d = self:pos():dist(pos)
if d > max then break end
local tile = self.runner:getTile(pos)
tile:setVisible(d)
if tile.kind == TileWall then break end
end
end
``````

The second one is doing geometry, and I think we’ll allow it. Conveniently, it isn’t going through the distance method. Does anyone use that?

I find these:

``````function Entity:distance(aTile)
return aTile:distance(self:getTile())
end

function Dungeon:furthestFrom(target, r1, r2)
local d1 = r1:distance(target)
local d2 = r2:distance(target)
if d2 > d1 then return r2 else return r1 end
end
``````

What even are r1 and r2 here?

``````function Dungeon:farawayOpenRoomTile(room, target)
local candidate
local result = self:randomRoomTile(room)
for i = 1,20 do
candidate = self:randomRoomTile(room)
result = self:furthestFrom(target, result, candidate)
end
return result
end
``````

This method is used to find places, well, far away from other places. Manhattan distance will be just fine for this.

I can see no references to `Entity:distance`. I’m also finding that “manhattan” is used fairly high up in the hierarchy. I’ll probably change my mind and allow that, but one way or the other, I’d prefer either just one term throughout or one at game level and one down in the geometry.

Let’s see about removing `Entity:distance`. All goes well. We’ll commit: removed unused Entity:distance method.

## A Nice Codea Feature

While Codea isn’t anywhere near as powerful as a real IDE, it does have some nice features. One that I’m particularly fond of is that if you select a method name, one of the popup menu items is “Find” and touching that will show you all the lines containing that word. If you want a bit more precision, it’s easy enough to add a “:” on the front and “(“ on the back.

It’s not much, but it makes untangling senders of methods a lot easier. We are grateful for small things.

The only sender of `Tile:distance` seems to be this one:

``````function Dungeon:furthestFrom(target, r1, r2)
local d1 = r1:distance(target)
local d2 = r2:distance(target)
if d2 > d1 then return r2 else return r1 end
end
``````

First, please, let’s rename those parameters to something connoting what they are, Tiles.

``````function Dungeon:furthestFrom(targetTile, tile1, tile2)
local d1 = tile1:distance(targetTile)
local d2 =tile2:distance(targetTile)
if d2 > d1 then return tile2 else return tile1 end
end
``````

Yesterday, we started our multi-dispatch far better quite fine way of getting the distance between tiles with this method:

``````function Tile:distanceFrom(aTile)
return self.mapPoint:distanceFromTile(aTile)
end
``````

We’ll apply this here and remove the old `Tile:distance`. Should have no discernible impact.

``````function Dungeon:furthestFrom(targetTile, tile1, tile2)
local d1 = tile1:distanceFrom(targetTile)
local d2 =tile2:distanceFrom(targetTile)
if d2 > d1 then return tile2 else return tile1 end
end
``````

Test. We’re all good. I ran into a couple of mimics and had a tough battle, but I had stocked up on health potions, which saved me. Commit: remove old `Tile:distance` method, using new manhattan distance.

Now let’s see who’s using `Tile:pos()`, which is returning that vec2 that we’d like to be rid of.

``````function Dungeon:defineTile(aTile)
assert(not TileLock, "attempt to set tile while locked")
local pos = aTile:pos()
local pt = Maps:point(pos.x,pos.y)
self.map:atPointPut(pt,aTile)
end
``````

This one is particularly strange, since the Tile actually has a MapPoint that we could just fetch. However, I wonder whether we need this at all. Generally once a Tile is created it never changes. If I recall, however, there was an exception. Find.

``````function GameRunner:defineTile(aTile)
self:getDungeon():defineTile(aTile)
end

function Dungeon:setHallAndDoors(prevTile,currTile, dir)
local pos = currTile:pos()
if not currTile:isRoom() then
currTile = Tile:hall(pos.x,pos.y,currTile.runner)
self:defineTile(currTile)
if prevTile:isRoom() then
currTile:setCanBeDoor(dir)
end
else -- curr is room, may need to set prev
if prevTile:isHall() then
prevTile:setCanBeDoor(dir)
end
end
return currTile
end

function Dungeon:setHallwayTile(x,y)
local t = self:privateGetTileXY(x,y)
if not t:isRoom() then
self:defineTile(Tile:hall(x,y,t.runner))
end
end

function Room:paint()
for x = self.x1,self.x2 do
for y = self.y1,self.y2 do
self.runner:defineTile(self:correctTile(x,y))
end
end
end
``````

Ah, I see what we’re up to here, we’re trying to let the Tiles be immutable. That’s probably a good idea. So let’s just convert the top guy to do it right.

We do not have an accessor for the Tile’s `mapPoint`. We do have one object fetching it directly (bad Ron no biscuit) but no one else. For `defineTile` we’d prefer not to have to rip its guts out again. What can we do?

``````function Dungeon:defineTile(aTile)
assert(not TileLock, "attempt to set tile while locked")
local pos = aTile:pos()
local pt = Maps:point(pos.x,pos.y)
self.map:atPointPut(pt,aTile)
end
``````

We can tell the Tile to do something. Like this:

``````function Dungeon:defineTile(aTile)
assert(not TileLock, "attempt to set tile while locked")
aTile:defineInMap(self.map)
end
``````

That’s better, but I bet we change it, or add to it, before we’re done here.

``````function Tile:defineInMap(aMap)
aMap:atPointPut(self.mapPoint, self)
end
``````

I expect this to work. Test. Things are good. Commit: Dungeon:defineTile defers to Tile:defineInMap.

OK, good so far but what about those people who call that method? They’re a bit naff as well.

``````function Dungeon:setHallAndDoors(prevTile,currTile, dir)
local pos = currTile:pos()
if not currTile:isRoom() then
currTile = Tile:hall(pos.x,pos.y,currTile.runner)
self:defineTile(currTile)
if prevTile:isRoom() then
currTile:setCanBeDoor(dir)
end
else -- curr is room, may need to set prev
if prevTile:isHall() then
prevTile:setCanBeDoor(dir)
end
end
return currTile
end
``````

OK let’s see what we are doing here. If the tile is a room tile, we create a hall tile at the same position (and store it into the currTile parameter, which really that stuff is really tacky). Then we put the new hall tile back in the same spot as the former room tile.

What does the `Tile:hall` method do?

``````function Tile:hall(x,y, runner)
assert(not TileLock, "Attempt to create room tile when locked")
return Tile(Maps:point(x,y),TileHall, runner)
end

function Tile:init(mapPoint,kind, runner)
assert(mapPoint:is_a(MapPoint), "Tile requires MapPoint")
if runner ~= Runner then
print(runner, " should be ", Runner)
error("Invariant Failed: Tile must receive Runner")
end
self.mapPoint = mapPoint
self.kind = kind
self.runner = runner
self:initDetails()
end
``````

Basically, it just sets kind to the TileHall value. All this to avoid modifying the Tile. And right below there we do modify the Tile:

``````        if prevTile:isHall() then
prevTile:setCanBeDoor(dir)
end
``````

Meh. We’ll change the existing one:

``````function Dungeon:setHallAndDoors(prevTile,currTile, dir)
local pos = currTile:pos()
if not currTile:isRoom() then
currTile:setToHall()
if prevTile:isRoom() then
currTile:setCanBeDoor(dir)
end
else -- curr is room, may need to set prev
if prevTile:isHall() then
prevTile:setCanBeDoor(dir)
end
end
return currTile
end
``````

Now we need that method. We have a method `convertToEdge`, so let’s name this one similarly.

``````function Dungeon:setHallAndDoors(prevTile,currTile, dir)
local pos = currTile:pos()
if not currTile:isRoom() then
currTile:convertToHall()
if prevTile:isRoom() then
currTile:setCanBeDoor(dir)
end
else -- curr is room, may need to set prev
if prevTile:isHall() then
prevTile:setCanBeDoor(dir)
end
end
return currTile
end
``````
``````function Tile:convertToEdge()
self.kind = TileEdge
end
``````

But the more I look at this method, the more I realize that it has nothing to do with Dungeon and everything to do with Tiles. Feature envy again.

What this method is doing is deciding where to mark a possible door. We mark a door on a tile if the tile is a hallway tile and it is adjacent to a room tile. Because we carve hallways from one end to the other, this can occur either when we carve out of a room or into a room. That’s why the code above checks both prev and curr the way it does.

Let’s revert and look at this again.

``````function Dungeon:setHallAndDoors(prevTile,currTile, dir)
local pos = currTile:pos()
if not currTile:isRoom() then
currTile = Tile:hall(pos.x,pos.y,currTile.runner)
self:defineTile(currTile)
if prevTile:isRoom() then
currTile:setCanBeDoor(dir)
end
else -- curr is room, may need to set prev
if prevTile:isHall() then
prevTile:setCanBeDoor(dir)
end
end
return currTile
end
``````

The `dir` value tells us which way to draw the door picture.

This is tricky code. I wonder why I didn’t use `elseif`. What was I trying to accommodate or say? Maybe we should stay on mission and pick away at this bit by bit. Back to plan A, remove that one define:

``````function Dungeon:setHallAndDoors(prevTile,currTile, dir)
local pos = currTile:pos()
if not currTile:isRoom() then
currTile:convertToHall()
if prevTile:isRoom() then
currTile:setCanBeDoor(dir)
end
else -- curr is room, may need to set prev
if prevTile:isHall() then
prevTile:setCanBeDoor(dir)
end
end
return currTile
end

function Tile:convertToHall()
self.kind = TileHall
end
``````

Now I don’t need `pos` any more. Now there’s this:

``````function Dungeon:setHallwayTile(x,y)
local t = self:privateGetTileXY(x,y)
if not t:isRoom() then
self:defineTile(Tile:hall(x,y,t.runner))
end
end
``````

This can use `convertToHall`:

``````function Dungeon:setHallwayTile(x,y)
local t = self:privateGetTileXY(x,y)
if not t:isRoom() then
t:convertToHall()
end
end
``````

Commit: another use of convertToHall.

That use of `privateGetTileXY` is a clue that we are doing something weird, but it’s in the hallway carving, and I don’t want to go there yet.

Who else uses `defineTile:`?

``````function Room:paint()
for x = self.x1,self.x2 do
for y = self.y1,self.y2 do
self.runner:defineTile(self:correctTile(x,y))
end
end
end
``````

This, like the hallway stuff, is pretty cartesian in nature. I think we can leave it alone for present purposes. Back to people sending `pos()`.

``````function Dungeon:neighbors(tile)
local tPos = tile:pos()
local offsets = self:neighborOffsets()
return map(offsets, function(offset) return self:getTile(offset + tPos) end)
end
``````

Didn’t we give the Map the ability to return the neighbors?

``````function BaseMap:surroundingObjects(aCoordinate)
assert(aCoordinate, "nil coordinate")
local points = Maps:surroundingPoints(aCoordinate)
return self:pointsToObjects(points)
end
``````

We should ask the Tile for its neighbors. As I dig into that, however, I realize that it’s a bit more tangled that I feel up to dealing with right now. The wise thing to do will be to stop while we’re ahead rather than dig into what is a bit messier than we’re up to.

And, oddly enough, I’m going to do the wise thing, for once. Let’s sum up.

## Summary

We’ve done three useful commits this morning, discovering and removing a method that we had made obsolete, replaced an old distance with new manhattan distance, and changed defineTile to defer the work down to the Map where it belongs.

The smartest thing I did today was to stop before undertaking something I don’t feel up to. There’s no value to pushing through, it just results in fragile code. Better to wait, or do simple easy things. Like errands.

We’re pretty close to where we need to be to run on the Hex map. It’s probably time to build a simple Hex map and see what happens.

It’s worth noting that my estimate of the best possible time to get the Hex map working in the game was the end of last week. I think I said maybe it could take two weeks. I’m not ready to raise my hand and say it’ll take longer, but in order to know, I think we need to try it.

So, tomorrow, let’s try again to make a minimal playable hex map. It could happen.

See you then!