Late start today. Not sure what to do. Try the hex map again, I guess. Feeling meh. But it works!

Combination of daily ingredients has me down a bit. I wasn’t going to do an article today but the alternatives are worse. Let’s take a look at what almost worked for the hex map last time and see what we can do.

My basic plan is to use the “Learn” button to trigger a hex map experiment. In a real app I’d probably hide it, since the Learn button would have an actual purpose.

The learning level is created like this:

function GameRunner:createLearningLevel()
    self.dungeonLevel = 99
    
    self:dcPrepare()
    
    -- customize rooms and connections
    self:createLearningRooms()
    self:connectLearningRooms()
    
    -- paint dungeon correctly
    self.dungeon:convertEdgesToWalls()
    -- ready for monsters
    self.monsters = Monsters()
    
    self:placePlayerInRoom1()
    
    -- customize contents
    self:placeWayDown() -- needs to be fixed room
    --self:placeSpikes(5)
    --self:placeLever()
    --self:setupMonsters(6)
    --self.keys = self:createThings(Key,5)
    --self:createThings(Chest,5)
    --self:createLoots(10)
    --self:createDecor(30)
    --self:createButtons()
    
    -- prepare crawl
    self.cofloater:startCrawl()
    Announcer:sayAndSave(self:crawlMessages(self.dungeonLevel))
    self:startTimers()
    
    self:dcFinalize()
end

Lots of things are already turned off here. I’ll rename this method to XXXcreate … and duplicate it for today’s effort.

My plan, such as it is, is to create a single large room and put the player in the center of it. We may not get that far.

Wait. Let’s rig this up so that we don’t have to revert it out. I’ll add a parameter switch and change what Learn does.

    parameter.boolean("HexMode", false)

Whee. Now to hack the Learn button.

The Buttons send messages to the Player, consisting of their name. Let’s leave that in place and see what’s in Player:

function Player:learn()
    self.runner:createLearningLevel()
end

We could put the check for HexMode in Player, GameRunner, or possibly even Button. Let’s put it here. There’s no perfect spot as far as I can see, but we’re just trying to toggle in a test feature.

function Player:learn()
    if HexMode then
        self.runner:createHexLevel()
    else
        self.runner:createLearningLevel()
    end
end

I put an assert in the new method to be sure that I get there when the toggle is set and the button is pressed. Works.

Now what goes inside the method? I start by ripping out all the useless commented-out stuff from the learning level method. I’m not sure why that’s there, probably to remind me of things.

function GameRunner:createHexLevel()
    assert(false, "got here")
    self.dungeonLevel = 99
    
    self:dcPrepare()
    
    -- customize rooms and connections
    self:createLearningRooms()
    
    -- ready for monsters
    self.monsters = Monsters()
    
    self:placePlayerInRoom1()
    -- prepare crawl
    self.cofloater:startCrawl()
    Announcer:sayAndSave(self:crawlMessages(self.dungeonLevel))
    self:startTimers()
    self:dcFinalize()
end

OK. Let’s start with dcPrepare:

function GameRunner:dcPrepare()
    TileLock = false
    DungeonContents = DungeonContentsCollection()
    self:createNewDungeon()
    local dungeon = self.dungeon
    dungeon:createTiles(self.tileCountX, self.tileCountY)
    dungeon:clearLevel()
    Announcer:clearCache()
end

Last time I checked for a flag down in createTiles. Let’s review that:

function Dungeon:createTiles(tileCountX, tileCountY)
    Maps:cartesian()
    self.tileCountX = tileCountX
    self.tileCountY = tileCountY
    self.map = Maps:map(self.tileCountX+1,self.tileCountY+1, Tile,TileEdge, self. runner)
end

That’s much more like something we want now. I think we’ll have create a new method createHexTiles. I plan to have the tiles created in a ring in future. For now we’ll keep the slanted rectangle.

We could use this code directly if we set Maps:hex elsewhere, but in fact I think I want to create room tiles rather than edge. So:

function Dungeon:createHexTiles(tileCountX, tileCountY)
    Maps:hex()
    self.tileCountX = tileCountX
    self.tileCountY = tileCountY
    self.map = Maps:map(self.tileCountX+1,self.tileCountY+1, Tile,TileRoom, self. runner)
end

Now let’s be clear: this is not the proper factoring for this stuff. I’m creating a lot of duplication. But it’s on purpose: I don’t know yet what will even work, so I want this code to be separate, at the cost of duplication, until I can see where the edges and solid parts are.

For this to be called, we could check HexMode in dcPrepare, I think.

function GameRunner:dcPrepare()
    TileLock = false
    DungeonContents = DungeonContentsCollection()
    self:createNewDungeon()
    local dungeon = self.dungeon
    dungeon:createTiles(self.tileCountX, self.tileCountY)
    dungeon:clearLevel()
    Announcer:clearCache()
end

I’ll do this:

function GameRunner:dcPrepare()
    TileLock = false
    DungeonContents = DungeonContentsCollection()
    self:createNewDungeon()
    local dungeon = self.dungeon
    if HexMode then
        dungeon:createHexTiles(self.tileCountX, self.tileCountY)
    else
        dungeon:createTiles(self.tileCountX, self.tileCountY)
    end
    dungeon:clearLevel()
    Announcer:clearCache()
end

Let’s run in HexMode. I think I’ll set that to the default, since it’s just us friends here.

Oh! The bad thing about that, or maybe it’s a good thing, is that with that flag set the game immediately tries to set up a hex level and we get this error:

MapPoint:19: bad argument #1 to 'floor' (number expected, got nil)
stack traceback:
	[C]: in function 'math.floor'
	MapPoint:19: in field 'init'
	... false
    end

    setmetatable(c, mt)
    return c
end:24: in global 'MapPoint'
	MapPoint:7: in function <MapPoint:6>
	(...tail calls...)
	Tile:47: in function <Tile:45>
	(...tail calls...)
	Room:101: in method 'paint'
	Dungeon:213: in method 'createRandomRooms'
	GameRunner:166: in method 'createLevel'
	Main:60: in function 'setup'

Yeah, we can’t default that flag to true. It’s trying to set up a real game in hex mode. Nix on that.

Now we get a similar but different error:

MapPoint:19: bad argument #1 to 'floor' (number expected, got nil)
stack traceback:
	[C]: in function 'math.floor'
	MapPoint:19: in field 'init'
	... false
    end

    setmetatable(c, mt)
    return c
end:24: in global 'MapPoint'
	MapPoint:7: in function <MapPoint:6>
	(...tail calls...)
	Tile:47: in function <Tile:45>
	(...tail calls...)
	Room:101: in method 'paint'
	Room:24: in field 'init'
	... false
    end

    setmetatable(c, mt)
    return c
end:24: in global 'Room'
	GameRunner:77: in method 'makeRoomsFromXYWH'
	GameRunner:66: in method 'createLearningRooms'
	GameRunner:143: in method 'createHexLevel'
	Player:166: in field '?'
	Button:79: in method 'performCommand'
	Button:64: in method 'touchEnded'
	Main:123: in function 'touched'

Yes, we don’t want to create learning rooms in createHexLevel.

Hm, an issue that I hadn’t thought of. Rooms are implicitly rectangular:

function Room:init(x,y,w,h, runner, paint)
    if paint == nil then paint = true end
    self.x1 = x
    self.y1 = y
    self.x2 = x + w - 1
    self.y2 = y + h - 1
    self.runner = runner
    if paint then
        self:paint()
    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

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

function Room:correctTile(x,y)
    return Tile:room(x,y, self.runner)
end

I think that since I’ve set up all the tiles to be of type room, we can avoid the painting. I’d like to do that because I’m not fond of replacing all the tiles in the world, which would happen if we define one big room. Let’s code that up, however.

Here’s what I want to have happen:

function GameRunner:createHexLevel()
    self.dungeonLevel = 99
    
    self:dcPrepare()
    
    self:defineAllRoom1()
    ...

A look at the room creation tells me that Dungeon has a member variable rooms that is a collection of Room objects. We just want one. Let’s just try it. I decide to call it createAllRoom1 to match the other room creation methods.

I finally decide that I want Dungeon to do it, as it does for regular levels.

function GameRunner:createHexLevel()
    self.dungeonLevel = 99
    
    self:dcPrepare()
    
    self.rooms = self.dungeon:createAllRoom1()
function Dungeon:createAllRoom1()
    self.rooms = {}
    local room = Room()
    table.insert(self.rooms, room)
    return self.rooms
end

I’m not sure what the parameters to Room creation should be. So, refresh memory:

function Room:init(x,y,w,h, runner, paint)
    if paint == nil then paint = true end
    self.x1 = x
    self.y1 = y
    self.x2 = x + w - 1
    self.y2 = y + h - 1
    self.runner = runner
    if paint then
        self:paint()
    end
end

Right. Just to be safe, I think I’ll use 2,2 as the origin and one less than tileCountX and Y for the w and h.

function Dungeon:createAllRoom1()
    self.rooms = {}
    local room = Room(2,2, self.tileCountX-1, self.tileCountY-1, self.runner, false)
    table.insert(self.rooms, room)
    return self.rooms
end

Time to test. And I get a princess inside a diagonal roundish thing:

roundish

We’re committing this. Commit: new HexMode creates a map without crashing. And now I’m going to back away slowly and sum up for the day.

Backing Away Slowly

I’m not exactly deeply surprised that it worked that well, but I am surface-level surprised. The game should automatically place the player at the center of Room 1. The createHexLevel method does create an empty Monsters collection, which I seem to recall caused trouble if we didn’t do that. Otherwise, not much is set up. Because of the way the tiles draw, I can’t tell whether I can move the princess or not.

But we have a hex map. That odd lozenge shape is surely what happens when our unmodified illuminate function runs. But the edges are rectangular, which tells me we’re not getting down to a hex style of drawing. I don’t even remember how I patched that last time. I suspect that right now we are drawing plain tiles, arranged on hexagonal points, but drawing them as squares. We’ll find out.

Comparing this to the first experiment, which also drew a map, we have a good result. We had to make no terrible hacks to prevent crashes. The code we have is simple and reasonable. It remains to push it a bit further, such as trying to figure out whether we can move or not.

But the fact that it went this smoothly and got this far with only additions tells me that the preparation over the past few days has been a good investment. Let’s hope that things continue this smoothly.

Until tomorrow, I’m definitely backing away slowly.

See you then?