An interesting problem. Is there a clean solution, or do I need a bigger hammer?

Yesterday, “off line”, I renamed some more of the tiles and adjusted the tiling logic to use them. Nothing to see here.

But now that I have these fine tiles, I have a couple of interesting problems. One of them will be whether to use the “Face” tiles that provide the 3/4 view of walls on the north side of things. But before that, there’s another um opportunity.

The regular “Wall” tiles come in various styles, depending on their orientation in the layout. Here are some screenshots from the tile set’s page on graphicriver.net

gr1

gr2

gr3

A bit of thought tells us that the choice of a wall tile depends on what’s beside it. If it’s a right hand wall, with floor on its left only, it should be the tile with a row of white stones on the left, and dirt on the right:

left

But if it’s at the northeast corner, with only a floor tile to its southwest, it should be the one with just one stone in the lower right corner (I think):

corner

And if it’s in an inside corner, with tiles to its right, southeast, and bottom, then it should (probably) be this one:

inside

So the problem, stated vaguely, because I only understand it vaguely, is to come up with a scheme to select wall tiles based on how many floor tiles are adjacent to the tile, and where they are. I’ll draw a picture of the situation to get my head clear about the cases, but offhand I think they are:

  1. Top of room
  2. Bottom of room
  3. Left of room
  4. Right of room
  5. NW outside corner
  6. NE outside corner
  7. SW outside corner
  8. SE outside corner
  9. NW inside corner
  10. NE inside corner
  11. SW inside corner
  12. SE inside corner

And I think there are quite likely a few others, when two hallways are parallel and separated by only one row, so that the cases include

  1. Top of one room bottom of another
  2. Inner hallway end right
  3. Inner hallway end left

And the same for vertical hallways.

  1. Left of one room right of another
  2. Inner hallway end up
  3. Inner hallway end down.

And I think it’s even possible to leave a single square open, surrounded by floor tiles on all sides.

  1. room on all sides

That last one would probably use this tile:

square

Now it seems to me that the number of cases can’t possibly be 19, so there must be some possibilities that I’ve missed or duplicated. We’ll find that out soon enough, I imagine.

Prior Art

We do have some code that puts wall tiles around carved hallways. It looks like this:

function GameRunner:convertEdgesToWalls()
    for i,row in ipairs(self.tiles) do
        for j,tile in ipairs(row) do
            tile:convertEdgeToWall()
        end
    end
end

function Tile:convertEdgeToWall()
    if self.kind == TileEdge and self:hasRoomNeighbor() then
        self.kind = TileWall
    end
end

function Tile:hasRoomNeighbor()
    local ck = { vec2(1,0),vec2(1,1),vec2(0,1),vec2(-1,1), vec2(-1,0),vec2(-1,-1),vec2(0,-1),vec2(1,-1)}
    for i,p in ipairs(ck) do
        local pos = self:pos() + p
        local tile = self.runner:getTile(pos)
        if tile:isRoom() then return true end
    end
    return false
end

This code relies on the fact that there is only one kind of wall tile, so we can use it anywhere.

There’s also wall building around rooms. After we find a place for a room, we paint it into the array of tiles:

function Room:paint()
    -- leave wall on all four sides
    for x = self.x1,self.x2 do
        for y = self.y1,self.y2 do
            self.runner:setTile(self:correctTile(x,y))
        end
    end
end

function Room:correctTile(x,y)
    if x == self.x1 or x == self.x2 or y == self.y1 or y == self.y2 then
        return Tile:wall(x,y, self.runner)
    else
        return Tile:room(x,y, self.runner)
    end
end

This code sets the outer perimeter of the space allocated to wall. We could almost use it to choose left/right/cop/bottom/corners, with a more extensive nest of ifs.

The bugaboo, however, is hallways. After rooms are allocated, we create a hallway between each consecutive pair of rooms, varying whether we move first horizontally toward it or first vertically. This process ensures that all our rooms are connected, and tends to make interesting maps. But some of the “interesting” things get in the way of placing smart wall tiles. In principle, a hallway can touch any tile of any existing room. It can cut the entire top or side off the room, leaving a room with a hallway at the top.

I was going to fiddle the code to show the full map all the time, and then generate some maps showing odd things. It turns out that the map is barely visible, since it shows the floor tiles, which are not just barely not black. This will need improving. Meanwhile here are a few interesting layouts:

hall1

hall2

Enough Complication?

I think I’ve described enough about what’s going on to confuse us all with the problem. It seems to me that there is a core problem whose solution would help us:

Given a tile in the game, not a floor tile, return the sprite that tile should use, as a function of the 8 booleans representing whether each of the 8 tiles around it is a room tile.

Hm. Put this way, it’s “obvious” that there are 256 possible arrangements of a tile and its eight neighbors. Are they all possible given our layout process? And are they all unique? Do some override others? Are there patterns we can usefully apply, such as reflections and rotations?

I think we can answer some of these. Reflections and rotations will not be useful to us, because the tiles we want to use are not the same rotated or reflected. So the left side tile is not generally a reflection of the right side tile, even if we could reflect a tile. (We can do that: recall that monsters turn to face the player. But I don’t think the tiles we’re given work that way:

wall80

wall81

I’ve been looking at a tic-tac-toe diagram, imagining an “edge” tile in the middle, and trying to see what kind of wall pattern might be impossible around an edge tile. I’m starting to think that all combinations are possible. Yikes. I really don’t want to fill out a 256-element table.

Any “yikes” aside, here’s what I want to know:

  1. What data structure will allow me to easily specify the tile to be used for each unique case?
  2. What data structure will be useful to the program in selecting the tile needed given the case?

These two may not be the same. It’s easy, for example, to produce an integer from 0-255 based on the surrounding tiles of a given tile. That integer could be used to select the desired tile from our table of (yikes) 256 elements. But that table would be a veritable #@$%^@! to fill in.

I’m pretty sure that if I can devise a good way to input the data, I can devise a way to process that data to select the right tile. And we’re not very concerned about speed here, since the tiles are just computed once, when the dungeon is laid out.

But at this moment, I’m not seeing a good way to do this.

Maybe I need to create an app? It would be “easy” to display all the available tiles, and a tic-tac-toe thing showing the state we’re trying to fill in. Then you tap a tile, it displays, and if you like it, you tap OK and that value is recorded. 256 cycles later, you’re done.

I like that idea. Let’s try it, at least a spike to see if it makes sense.

New App

I’ll set up an app with CodeaUnit in it, just in case there’s TDD to be done.

We have 83 total tiles to be concerned about, though I think I have them classified into groups with useful names like Face_xx, Floor_xx, Pit_Column_xx, Wall_xx, and Water_xx. We only want the Pit and Wall ones in our array.

There turn out to be 36 tiles with those names. A bit of hackery and I have this:

-- CUBase

function setup()
    if CodeaUnit then 
        codeaTestsVisible(true)
        runCodeaUnitTests() 
    end
    local all = asset.documents.Dropbox.all
    used = {}
    for i,a in ipairs(all) do
        local n = a.name
        local i = string.find(n,"^Pit")
        local j = string.find(n,"^Wall")
        if i or j then
            table.insert(used,a)
        end
    end
    print(#used)
end

function draw()
--    if CodeaUnit then showCodeaUnitTests() end
    --background(128,128,128)
    pushStyle()
    spriteMode(CORNER)
    rectMode(CORNER)
    fill(128,128,128)
    rect(590,190, 670,670)
    for i,a in ipairs(used) do
        local row = (i-1)%6
        local col = (i-1)//6
        pushMatrix()
        translate(600,200)
        translate(col*110, row*110)
        sprite(a,0,0,100,100)
        popMatrix()
    end
    popStyle()
end

With this result:

array

So that’s nice. I notice there are two tiles showing floor instead of the apparent mud on the others. That may turn out to be interesting: I suppose the creator of these tiles had a reason for those. (I’m also vaguely thinking about how I could use these tiles and Procreate to make more if I really must.)

Now we want to display a “tic-tac-toe showing floor tiles in some of the outer cells, for us to put wall tiles in the middle. If I go with the same size tiles, which makes sense, the rectangle will be 340 square (3*100 + 4 fenceposts).

No. We want no fenceposts here, so that we can see how the files fit together. Width and height will be 320.

function drawTicTacToe()
    pushStyle()
    spriteMode(CORNER)
    rectMode(CORNER)
    fill(192,192,192)
    rect(90,290,320,320)
    for row = 0,2 do
        for col = 0,2 do
            pushMatrix()
            translate(100,300)
            translate(col*100,row*100)
            sprite(asset.documents.Dropbox.Floor_14, 0,0, 100,100)
            popMatrix()
        end
    end
    popStyle()
end

That gives this:

small square

Now it starts getting tricky. We have 256 cases to consider, 0-255, where the bits in the number represent positions in the periphery. And there will need to be some kind of control over moving to the next setting, and selecting the tile to be displayed, and so on.

Inch by inch.

function setup()
    if CodeaUnit then 
        codeaTestsVisible(true)
        runCodeaUnitTests() 
    end
    local all = asset.documents.Dropbox.all
    used = {}
    for i,a in ipairs(all) do
        local n = a.name
        local i = string.find(n,"^Pit")
        local j = string.find(n,"^Wall")
        if i or j then
            table.insert(used,a)
        end
    end
    byte = 0xFF
    selected = used[17]
end

I’ve decided that byte will be our value that increments. I’ve set it to 255 to give a full display to start with. And selected will be the tile most recently touched.

Now to make this display. What’s a good way to display those bits? Let’s do a shift.

function drawTicTacToe(byte)
    pushStyle()
    spriteMode(CORNER)
    rectMode(CORNER)
    fill(192,192,192)
    rect(90,290,320,320)
    local bits = byte
    for row = 0,2 do
        for col = 0,2 do
            pushMatrix()
            translate(100,300)
            translate(col*100,row*100)
            if col ~= 1 or row ~= 1 then
                if bits&1 == 1 then
                    sprite(asset.documents.Dropbox.Floor_14, 0,0, 100,100)
                    bits = bits >> 1
                end
            end
            popMatrix()
        end
    end
    popStyle()
end

This is getting intricate but it works as intended:

empty

Now I want to watch it count. It turns out, that code above doesn’t remotely work. Oh, maybe the shift is in the wrong place. I’ll try that, and failing that, back to the drawing board.

Yes that was it:

function drawTicTacToe(byte)
    pushStyle()
    spriteMode(CORNER)
    rectMode(CORNER)
    fill(192,192,192)
    rect(90,290,320,320)
    local bits = byte
    for row = 0,2 do
        for col = 0,2 do
            pushMatrix()
            translate(100,300)
            translate(col*100,row*100)
            if col ~= 1 or row ~= 1 then
                if bits&1 == 1 then
                    sprite(asset.documents.Dropbox.Floor_14, 0,0, 100,100)
                end
                bits = bits >> 1
            end
            popMatrix()
        end
    end
    popStyle()
end

Here’s a movie of the thing counting. Looks just like you’d expect of a binary counter with a hole in the middle:

count

So that tells us we can display what we need to. Remove the hackery of the timer and see what’s next.

I think what we want to do with this thing is to iterate manually through all 256 possibilities (yikes), touch tiles in the big matrix, see how they look, then when we like them, save them in an array, indexed by byte, in a text form that we can retain. This will take a few steps.

First, let’s see about displaying the chosen file in the middle.

function drawTicTacToe(byte)
    pushStyle()
    spriteMode(CORNER)
    rectMode(CORNER)
    fill(192,192,192)
    rect(90,290,320,320)
    local bits = byte
    for row = 0,2 do
        for col = 0,2 do
            pushMatrix()
            translate(100,300)
            translate(col*100,row*100)
            if col ~= 1 or row ~= 1 then
                if bits&1 == 1 then
                    sprite(asset.documents.Dropbox.Floor_14, 0,0, 100,100)
                end
                bits = bits >> 1
            else
                sprite(selected,0,0,100,100)
            end
            popMatrix()
        end
    end
    popStyle()
end

You may be wondering …

Where is all that clean, expressive, small method code for which I am always the intrepid champion? Well, to tell the truth, in all this excitement I kind of lost track myself, but I do feel lucky. This is just a one-time tool that we (believe) we’ll never modify again. (Do we believe that? What if I get a different tile set, or there are more than 36 I want to keep track of?)

But this is a spike, and we allow them to get rough, until the roughness starts to hurt.

But I digress …

How are we going to decide which tile we’ve touched?

I guess we have to convert a touch in the big square back into row and column indices. And I think that the first element in the array is at the bottom left. I wonder if we should be displaying the names? Maybe not yet.

There are at least two ways to do this that come to mind. One is to detect the touch coordinates and just smash them with arithmetic until they confess which tile number they represent. The other is to build up a helper data structure, an array of screen rectangles, each telling us what we want to know when it’s touched.

The latter is better, not least because it can handle arrangements that change far more readily than the arithmetic hammering can.

Let’s do that. We have each rectangle in hand … if we can recognize it:

    for i,a in ipairs(used) do
        local row = (i-1)%6
        local col = (i-1)//6
        pushMatrix()
        translate(600,200)
        translate(col*110, row*110)
        sprite(a,0,0,100,100)
        popMatrix()
    end

The rectangle corner will be (600+col110, 200+row110) and it’ll be 100x100. Let’s do a tiny object. I’m not sure quite what I want here. This is a guess:

TouchPoint = class()

function TouchPoint:init(x,y, tile)
    self.x = x
    self.y = y
    self.tile = tile
end


function TouchPoint:touched(touch)
    local pos = touch.pos
    if pos.x > self.x and pos.x < self.x+100 and pos.y > self.y and pos.y < self.y+100 then
        return self.tile
    else
        return nil
    end
end

I observe that if I’ve got these guys I can make them display themselves, but that’s not the swamp I’m here to drain. Let’s create them and use them.

    for i,a in ipairs(used) do
        local row = (i-1)%6
        local col = (i-1)//6
        pushMatrix()
        translate(600,200)
        translate(col*110, row*110)
        table.insert(tiles, TouchPoint(600+col*110,200+row*110,i))
        sprite(a,0,0,100,100)
        popMatrix()
    end

This is getting weird. Let’s make it work tho, then we really must clean up the camp a bit.

function touched(touch)
    for i,tp in ipairs(tiles) do
        local t = tp:touched(touch)
        if t then
            selected = used[t]
            return
        end
    end
end

And we get this:

works

Now let’s clean this up a bit. I don’t have it under version control. I may regret that but it’s a pain to set up.

function setup()
    if CodeaUnit then 
        codeaTestsVisible(true)
        runCodeaUnitTests() 
    end
    used = allUsedTiles()
    byte = 0x00
    savedTime = ElapsedTime
    selected = used[17]
    tiles = {}
end

function allUsedTiles()
    local all = asset.documents.Dropbox.all
    local used = {}
    for i,a in ipairs(all) do
        local n = a.name
        local i = string.find(n,"^Pit")
        local j = string.find(n,"^Wall")
        if i or j then
            table.insert(used,a)
        end
    end
    return used
end

Close enough for that.

function draw()
--    if CodeaUnit then showCodeaUnitTests() end
    --background(128,128,128)
    pushStyle()
    spriteMode(CORNER)
    rectMode(CORNER)
    fill(128,128,128)
    rect(590,190, 670,670)
    drawTileArray()
    popStyle()
    drawTicTacToe(byte)
end

function drawTileArray()
    for i,a in ipairs(used) do
        local row = (i-1)%6
        local col = (i-1)//6
        pushMatrix()
        translate(600,200)
        translate(col*110, row*110)
        table.insert(tiles, TouchPoint(600+col*110,200+row*110,i))
        sprite(a,0,0,100,100)
        popMatrix()
    end
end

Now a bit more clarity in drawTileArray:

function drawTileArray()
    for i,a in ipairs(used) do
        local row = (i-1)%6
        local col = (i-1)//6
        pushMatrix()
        local tableOrigin = vec2(600,200)
        local elementOrigin = vec2(col*110, row*110)
        local pos = tableOrigin + elementOrigin
        translate(pos.x, pos.y)
        table.insert(tiles, TouchPoint(pos.x,pos.y,i))
        sprite(a,0,0,100,100)
        popMatrix()
    end
end

Now for this mess:

function drawTicTacToe(byte)
    pushStyle()
    spriteMode(CORNER)
    rectMode(CORNER)
    fill(192,192,192)
    rect(90,290,320,320)
    local bits = byte
    for row = 0,2 do
        for col = 0,2 do
            pushMatrix()
            translate(100,300)
            translate(col*100,row*100)
            if col ~= 1 or row ~= 1 then
                if bits&1 == 1 then
                    sprite(asset.documents.Dropbox.Floor_14, 0,0, 100,100)
                end
                bits = bits >> 1
            else
                sprite(selected,0,0,100,100)
            end
            popMatrix()
        end
    end
    popStyle()
end

That becomes this, which is somewhat better:

function drawTicTacToe(byte)
    pushStyle()
    spriteMode(CORNER)
    rectMode(CORNER)
    fill(32,32,32)
    rect(90,290,320,320)
    drawCells()
    popStyle()
end

function drawCells()
    local bits = byte
    for row = 0,2 do
        for col = 0,2 do
            bits = drawCell(row,col, bits)
        end
    end
end

function drawCell(row,col,bits)
    pushMatrix()
    translate(100,300)
    translate(col*100,row*100)
    if col ~= 1 or row ~= 1 then
        if bits&1 == 1 then
            sprite(asset.documents.Dropbox.Floor_14, 0,0, 100,100)
        end
        bits = bits >> 1
    else
        sprite(selected,0,0,100,100)
    end
    popMatrix()
    return bits
end

I’ve noticed something during testing. The answers are not obvious. Look at these examples, all with the same surrounding tiles:

el

This looks possible … but isn’t this more likely right, given what’s going to happen at the top?

eldot

I think it is, because there will be two vertical walls above, and a horizontal one at the top of the cell to our right, but not on the bottom. But we might want the corresponding upside down L shape with one cell in the corner.

This is going to be difficult to do correctly one at a time.

That tells me that either we’ll have to accept textual input as well as output, or we’ll be faced with a decision to do the task all over (error-prone and tedious) or to update manually after the first run-through.

Amusing.

Press on a bit. Let’s display status below the tic-tac-toe

function draw()
--    if CodeaUnit then showCodeaUnitTests() end
    background(0)
    pushStyle()
    spriteMode(CORNER)
    rectMode(CORNER)
    fill(128,128,128)
    rect(590,190, 670,670)
    drawTileArray()
    drawTicTacToe(byte)
    drawStatus()
    popStyle()
end

function drawStatus()
    pushMatrix()
    pushStyle()
    textMode(CORNER)
    fontSize(30)
    s = string.format("[%X] = \"%s\"", byte, selected.name)
    text(s,90,250)
    popStyle()
    popMatrix()
end

That gives us this behavior:

show1

show2

The text shows the floor combination currently in effect, and the tile chosen in the center.

It remains to do the ticking through, and saving of the information, both internally and in a text output form of some kind. I’m moderately sure we can make Codea create a text file of some kind. Moderately sure.

Anyway, I’m tired and hungry. This has been an interesting experiment. I’m still stuck with creating 256 entries, but now it might at least be possible.

See you next time!


D2.zip

Tiler.zip