More like a game, step by step. There’s arithmetic involved. I hate that.

My overall objective for the week, aside from survival, is to make this little program seem more like a game. The phrase seems to me to include:

  • Showing how to make the expanded room look more like a room;
  • Allowing our player to move around the room;
  • Allowing our player to move through doors into other rooms;
  • Populating rooms with “stuff”;
  • Allowing some form of interaction with stuff.

Those are roughly in priority order, as I see it now. As always, things may change.

Presently the game can only display room 1, and it looks like this:

random room

I want to make a couple of changes. First, I’d like to be able to get the same map over and over. This will help with working on the visual aspects, and on motion. To accomplish this, I’ve added this to setup:

    local seed = math.random(1234567)
    -- seed = 
    math.randomseed(seed)
    print(seed)

To cause the game to repeat a map, all I need to do is copy the seed printed on the console into the seed = comment, and uncomment it. Then all our random numbers will be the same as last time. So that’s nice.

Second, based on an experiment last night, I think I’d like all the rooms to have dimensions that are a multiple of eight. Eight seems to be a good size for floor and wall tiles. So here’s where we initialize those:

function Room:init(x,y,w,h)
    self.number = 666
    self.color = color(0,255,0,25)
    self.x = x or math.random(WIDTH//4,3*WIDTH//4)
    self.y = y or math.random(HEIGHT//4,3*HEIGHT//4)
    self.w = w or math.random(WIDTH//40,WIDTH//20)*2
    self.h = h or math.random(HEIGHT//40,HEIGHT//20)*2
    self.doors = {}
end

Now I have to admit that I’m wondering why I’m multiplying the w and h by 2. You’d think you could multiply that through, wouldn’t you?

My screen size, according to Codea, is 1366x1024. Rooms appear to be between 1/20th and 1/10th of that, or between 68 and 136 in x and between 51 and 102 in y. Other devices would get different values. I want to force those to a multiple of 8, for reasons we’ll come to (at which point we may discover that 8 isn’t the right answer). So:

function Room:init(x,y,w,h)
    self.number = 666
    self.color = color(0,255,0,25)
    self.x = x or math.random(WIDTH//4,3*WIDTH//4)
    self.y = y or math.random(HEIGHT//4,3*HEIGHT//4)
    self.w = w or math.random(WIDTH//40,WIDTH//20)*2
    self.h = h or math.random(HEIGHT//40,HEIGHT//20)*2
    self.w = (self.w//8)*8
    self.h = (self.h//8)*8
    self.doors = {}
end

The parens aren’t necessary, but you never really know what a compiler might do, so let’s go with this. the rooms look good:

room81

I’ve found one I like. The console says:

684592

So I’ll save that:

    local seed = math.random(1234567)
    seed = 684592
    math.randomseed(seed)
    print(seed)

And we have a nice fixed game with rooms divisible by 8. Commit: constant map, rooms divisible by 8.

Now let’s see about tiling the floors. To do this, we’ll have to draw the tile at each suitable location in the room: Codea doesn’t have a way to draw a tiled rectangle all by itself.

Our room is drawn centered on the screen, at scale 8. Scale shouldn’t matter at all. Let’s review the drawing and see if it needs a little tweaking before we go ahead.

function ExpandedRoom:draw()
    background(40, 40, 50)
    self:drawGeometry()
    self:drawDescription()
end

function ExpandedRoom:drawPopulation()
    pushMatrix()
    pushStyle()
    translate(WIDTH/2,HEIGHT/2)
    scale(0.5)
    sprite(asset.builtin.Planet_Cute.Character_Princess_Girl,0,-200)
    sprite(asset.builtin.Planet_Cute.Chest_Closed,100,-300)
    popStyle()
    popMatrix()
end

function ExpandedRoom:drawGeometry()
    pushMatrix()
    pushStyle()
    translate(WIDTH/2, HEIGHT/2)
    scale(8)
    rectMode(CENTER)
    fill(229, 143, 37)
    stroke(108, 108, 108)
    strokeWidth(4)
    rect(0,0,self.room.w, self.room.h)
    self:drawDoors()
    popStyle()
    popMatrix()
    self:drawPopulation()
end

function ExpandedRoom:drawDescription()
    pushStyle()
    fill(255,0,0)
    fontSize(50)
    textAlign(CENTER)
    local t = string.format("ROOM %d\n%d x %d", self.room.number, self.room.w,self.room.h)
    text(t, WIDTH/2, HEIGHT/2)
    popStyle()
end

function ExpandedRoom:drawDoors()
    for _x,door in pairs(self.room.doors) do
        door:drawExpandedFor(self.room)
    end
end

We left that fairly nicely factored. We can just change the drawGeometry to our liking.

I expect to need to fiddle with the offsets a bit, to get the tiles lined up with the edges of the room. So I’m going to include a final drawing of the room rectangle, to tell us when we’ve got it right.

I’ll try to pick some tile from Codea’s assets that looks like a floor. Let’s take a swing at it:

function ExpandedRoom:drawGeometry()
    pushMatrix()
    pushStyle()
    translate(WIDTH/2, HEIGHT/2)
    scale(8)
    self:drawDebug()
    self:drawDoors()
    popStyle()
    popMatrix()
    self:drawPopulation()
end

function ExpandedRoom:drawDebug()
    pushStyle()
    rectMode(CENTER)
    noFill()
    stroke(255)
    strokeWidth(1)
    rect(0,0,self.room.w, self.room.h)
    popStyle()
end

debugroom

So that looks as intended.

Now for some tiles. I’m kind of thinking that if I draw in CORNER mode it might be easier. My first cut looks like this.

q1

I don’t see my white rectangle at all, although if I look closely it is there, and I’m clearly low on the left and bottom values. The code is:

function ExpandedRoom:drawGeometry()
    pushMatrix()
    pushStyle()
    translate(WIDTH/2, HEIGHT/2)
    scale(8)
    rectMode(CORNER)
    local w = self.room.w//2
    local h = self.room.h//2
    for x = -w,w,8 do
        for y = -h,h,8 do
            sprite(asset.builtin.Blocks.Missing,x,y, 8,8)
        end
    end
    self:drawDebug()
    self:drawDoors()
    popStyle()
    popMatrix()
    self:drawPopulation()
end

function ExpandedRoom:drawDebug()
    pushStyle()
    rectMode(CENTER)
    noFill()
    stroke(255)
    strokeWidth(1)
    rect(0,0,self.room.w, self.room.h)
    popStyle()
end

Expanding the debug rectangle’s stroke width to 2, and putting the doors back into CENTER mode gives me this:

qmarks too many

OK, we’re drawing 12x10 tiles, and we should be drawing 11x9 for this room. And setting them to CORNER didn’t really help. I guess we’re going to have to do some messy arithmetic.

OK, for one things, sprites have their own spriteMode, they don’t follow rectMode. So I’ll change that. After only a bit of bashing, I get this:

qmarks correct

Here’s the code:

function ExpandedRoom:drawGeometry()
    pushMatrix()
    pushStyle()
    translate(WIDTH/2, HEIGHT/2)
    scale(8)
    spriteMode(CORNER)
    local w = self.room.w//8
    local h = self.room.h//8
    for xi = 0,w-1 do
        for yi = 0,h-1 do
            local x = xi*8 - self.room.w//2
            local y = yi*8 - self.room.h//2
            sprite(asset.builtin.Blocks.Missing,x,y, 8,8)
        end
    end
    self:drawDebug()
    self:drawDoors()
    popStyle()
    popMatrix()
    self:drawPopulation()
end

This isn’t very communicative but I think it works. I’ll release my random seed and try a few more room sizes.

room2

room3

These look good. I should mention the debug rectangle. You can see that it is inside the room’s border. That’s because the Codea rectangle drawing code tries never to draw outside the limits of a rectangle: the border is always just short of the edge, never over it. Or almost never, anyway. So as odd as it looks, it’s as it should be.

Let’s pick a nicer flooring.

greyfloor

This is “Greystone” from Blocks. I’m tempted to draw a grid here. What if we drew little rectangles?

room grid

I like the look of that. Here’s the code. The trick was o draw a 9x9 rectangle, to adjust for the “inside drawing” that rectangles do:

function ExpandedRoom:drawGeometry()
    pushMatrix()
    pushStyle()
    translate(WIDTH/2, HEIGHT/2)
    scale(8)
    spriteMode(CORNER)
    rectMode(CORNER)
    noFill()
    stroke(0)
    strokeWidth(1)
    local w = self.room.w//8
    local h = self.room.h//8
    for xi = 0,w-1 do
        for yi = 0,h-1 do
            local x = xi*8 - self.room.w//2
            local y = yi*8 - self.room.h//2
            sprite(asset.builtin.Blocks.Greystone,x,y,8,8)
            rect(x,y,9,9)
        end
    end
    self:drawDebug()
    self:drawDoors()
    popStyle()
    popMatrix()
    self:drawPopulation()
end

That looks good for now, I think. Commit: gridded greystone floors.

What about the walls around the room? We need walls. My plan a moment ago was to draw them separately, but what if we just expanded the room on both sides, like this:

wider taller

With this code:

    for xi = -1,w do
        for yi = -1,h do
            local x = xi*8 - self.room.w//2
            local y = yi*8 - self.room.h//2
            sprite(asset.builtin.Blocks.Greystone,x,y,8,8)
            rect(x,y,9,9)
        end
    end

And then when we’re on an edge, draw a different sprite. After a bit of experimentation, I like this code:

    for xi = -1,w do
        for yi = -1,h do
            local x = xi*8 - self.room.w//2
            local y = yi*8 - self.room.h//2
            if xi==-1 or xi==w or yi==-1 or yi==h then
                sprite(asset.builtin.Blocks.Greystone,x,y,8,8)
            else
                
                sprite(asset.builtin.Blocks.Stone,x,y,8,8)
            end
            rect(x,y,9,9)
        end
    end

With this result:

room good walls

Commit: rooms have walls.

I can see that we’re going to have an issue with door positioning, and I’m concerned about making the transition from one room to another seem reasonable. We could just redraw and rez the player near the door or in the center, but it’d be nice if it looked better than that. I’m not sure if we can figure a good way to draw a subset of the map without tiling the whole thing and letting it draw offscreen. That would likely work, but it might be terribly slow if we have 50 tiled rooms.

For anything fancy, we’ll need a more clever drawing approach than the current ExpandedRoom, but we have some fairly sensible code drawing a fairly sensible room.

What if instead of drawing wall, we drew flooring in the door positions? Let’s try that. First we’ll extract that if statement into shouldDrawWall:

function ExpandedRoom:shouldDrawWall(xi,yi, w,h)
    return xi==-1 or xi==w or yi==-1 or yi==h
end

That works as before “of course”. Now about the doors:

A long pause, then:

OK, that didn’t work. I got buried and have become confused. Time for a break, or for my pair to take over.

In fairness, let’s revert.

I do have a zillion unit tests failing. I didn’t notice, because I was focusing on the big room. I hope it’s something simple, but it says -27 passed and 45 failed. That sounds bad. And I have no idea where things went wrong. Remind me to reconsider the process. It should be possible to halt the program entirely if a unit test fails. That would be harsh but we should consider it.

For now, let’s attack some of these failures. It starts here:

2: rooms intersect xLo -- Actual: 92.0, Expected: 90

The test is:

        _:test("rooms intersect", function()
            local r1,r2,r3,r4, r5
            r1 = Room(100,100, 20, 20)
            checkCorners(r1, 90,90,110,110)
            r2 = Room(120,100, 20,20)
            checkCorners(r2, 110, 90, 130, 110)
            _:expect(r1:intersects(r2)).is(true)
            r3 = Room(120.01,110, 20,20)
            _:expect(r1:intersects(r3), "room3").is(false)
            r4 = Room(120,100,19.99,19.99)
            _:expect(r1:intersects(r4), "room4").is(false)
        end)

So that’ll be the first checkCorners and apparently I don’t give back the same corners as before. Ah. Here’s init:

function Room:init(x,y,w,h)
    self.number = 666
    self.color = color(0,255,0,25)
    self.x = x or math.random(WIDTH//4,3*WIDTH//4)
    self.y = y or math.random(HEIGHT//4,3*HEIGHT//4)
    self.w = w or math.random(WIDTH//40,WIDTH//20)*2
    self.h = h or math.random(HEIGHT//40,HEIGHT//20)*2
    self.w = (self.w//8)*8
    self.h = (self.h//8)*8
    self.doors = {}
end

When h and w are provided, probably only in tests we should use them. Let’s move the 8 stuff up into the random area:

function Room:init(x,y,w,h)
    self.number = 666
    self.color = color(0,255,0,25)
    self.x = x or math.random(WIDTH//4,3*WIDTH//4)
    self.y = y or math.random(HEIGHT//4,3*HEIGHT//4)
    self.w = w or ((math.random(WIDTH//40,WIDTH//20)*2)//8)*8
    self.h = h or ((math.random(HEIGHT//40,HEIGHT//20)*2)//8)*8
    self.doors = {}
end

And all the tests pass. I am grateful. Now commit: explicit w and h on room creation works again.

Now for a break. I guess I’ll push this article and start a new one when I return, later today, or tomorrow, or whenever.

See you then!