Let’s check our stories along the way to better control over the trash, er, treasures in the Dungeon.

In the real world, I’d keep my stories on 3x5 cards. I have plenty of those, but not plenty of space, so I write my stories on small yellow sticky-notes, about 1.5x2 inches. There’s still more than enough room for a standard user story, which emphatically does not say “As the owner, I want enhanced messages for the Chest and Mimic, so that the game is more interesting. The story says: “Enhance Chest messages in Decor. Mimic messages?” And my writing isn’t notably small. Illegible, yes. Small, no.

That story’s done, by the way, as reported in article 292. Let’s look at the rest of them:

story stickies on desk

Not readable? OK. I just wanted to show you my office. Here are some of the current stories.

  • Smaller rock icon?
  • TileArbiter needs more tests for entry
  • General review of Loot Decor Chest …
  • Allow entry to open Decor & Chest
  • Does Chest need [more] tests?
  • zLevel messages / values? (done)
  • actionWith should be actionWithPlayer
  • Start Dung Over. Spike?
  • Loot able to create all items. by name?
  • Deal with missing InventoryItem kind in Loot
  • amulet didn’t display in Chest. (done)
  • Lever can still block door
  • Loot should create item on init, use it to display. (done, I think)
  • Loot needs a nothing item?
  • Fix ignored Decor test
  • nothing Loot should not exist -> nil

And of course, the ruling story that is driving all this:

Better control over placing DungeonObjects, counts, etc.

Hm. My Internet connection just went away. I am not as fond of that as I might be. Should I restart the modem right now, or wait to see what happens? I restarted it, will check later.

I guess I’ll carry on. Let’s do “Allow entry to open Decor”. What I’d like to have happen is for Decor to refuse entry to the tile if the decor is closed, to continue to refuse while the Loot, if any, is visible, and then to allow entry.

It seems to me that we can do this by combining Loot’s behavior and the Decor. We’ll have Decor refuse entry if ot open, and we’ll have Loot refuse entry always. This will mean that when a Loot is visible anywhere, you bump it once to receive it, it goes to inventory and vanishes from the floor.

So first, let’s look at how loots work. That’s in TileAribter’s table:

    t[Loot] = {}
    t[Loot][Player] = {moveTo=TileArbiter.refuseMove, action=Player.startActionWithLoot}

So it should already be that we bump a loot to receive it and then it vanishes from the floor and we can move onto that tile in the next move. I’ll test that. Yes. Works that way. So now, if we can make the Decor refuse entry only when not open …

    t[Decor] = {}
    t[Decor][Player] = {moveTo=TileArbiter.refuseMove, action=Player.startActionWithDecor}

We do have a case of something other than outright acceptance or rejection, here:

    t[Monster]={}
    t[Monster][Monster] = {moveTo=TileArbiter.refuseMoveIfResidentAlive, action=Monster.startActionWithMonster}

I’ll look at that to see how it works:

function TileArbiter:refuseMoveIfResidentAlive()
    if self.resident:isAlive() then
        return self:refuseMove()
    else
        return self:acceptMove()
    end
end

We’ll do a new one, acceptMoveIfResidentOpen.

function TileArbiter:acceptMoveIfResidentOpen()
    if self.resident:isOpen() then
        return self:acceptMove()
    else
        return self:refuseMove()
    end
end

(I should be writing a test for this. We need to talk about this subject. I’m on a roll, don’t bug me.)

Change the TA table:

    t[Decor] = {}
    t[Decor][Player] = {moveTo=TileArbiter.acceptMoveIfResidentOpen, action=Player.startActionWithDecor}

OK, I’m going to test this in the game. We will talk about tests, I promise.

TileArbiter:23: attempt to call a nil value (method 'isOpen')
stack traceback:
	TileArbiter:23: in field 'moveTo'
	TileArbiter:38: in method 'moveTo'
	Tile:129: in method 'attemptedEntranceBy'
	Tile:442: in function <Tile:440>
	(...tail calls...)
	Player:216: in method 'moveBy'
	Player:152: in method 'executeKey'
	Player:206: in method 'keyPress'
	GameRunner:243: in method 'keyPress'
	Main:107: in function 'keyboard'

I was sure we had that method. I guess it was in Chest.

function Decor:isOpen()
    return self.open
end

Test. And it doesn’t work. I am allowed to enter the Tile. The Loot appears underneath me. If I exit the tile and return, I am allowed to enter and I get the Loot.

I am confused. I propose to find this and fix it. I know I should do this via testing. I’m irritated and upset because my Internet has just gone down again. I am going to put in a couple of prints.

The prints tell me that the Decor responded that it was open. How did it get that way?

Here’s how TA starts:

function Tile:attemptedEntranceBy(enteringEntity, enteringTile)
    local ta
    local acceptedTile
    local accepted = false
    local residents = 0
    for k,residentEntity in pairs(self:getContents()) do
        residents = residents + 1
        ta = TileArbiter(residentEntity,self, enteringEntity, enteringTile)
        acceptedTile = ta:moveTo(self)
        accepted = accepted or acceptedTile==self
    end
    if residents == 0 or accepted then
        return self
    else
        return enteringTile
    end
end

Now this code will allow you into a Tile if any resident OKs it. But that should not affect us here, because when all this starts, the Loot is not present.

OK, I see the issue. The TileArbiter protocol does the “action” first, and after that tries the move. So our open logic has to be more clever. We want to know whether the Decor “was open” before all this started.

In Decor, we’re doing to see actionWith before we’re asked whether we’re open. That’s here:

function Decor:actionWith(aPlayer)
    print("action with")
    if not self.open then
        self.open = true
        self.sprite = self:openSprite(self.kind)
        self:currentTile():moveObject(self.loot)
        local round = CombatRound(self,aPlayer)
        self.danger(self,round)
    end
end

I think we want a two-stage opening here and I accept that this calls for a test. But the test will be difficult to set up, and it’s so easy to do this and test it in the game. This is the slippery slope, isn’t it?

I’m sorry. I’m rattled by the Internet issue. I’m just going to code this:

function Decor:init(tile, loot, kind)
    self.kind = kind or Decor:randomKind()
    self.sprite = DecorSprites[self.kind]
    if not self.sprite then
        self.kind = "Skeleton2"
        self.sprite = DecorSprites[self.kind]
    end
    self.loot = loot
    tile:moveObject(self)
    self.scaleX = ScaleX[math.random(1,2)]
    local dt = {self.doNothing, self.doNothing, self.castLethargy, self.castWeakness}
    self.danger = dt[math.random(1,#dt)]
    self.actionCount = 0
end

I’ve removed the open flag and replaced with actionCount. Then:

function Decor:actionWith(aPlayer)
    self.actionCount = self.actionCount + 1
    if self.actionCount == 1 then
        self.sprite = self:openSprite(self.kind)
        self:currentTile():moveObject(self.loot)
        local round = CombatRound(self,aPlayer)
        self.danger(self,round)
    end
end

When actionCount is 1, it’s first time through and we do our damage and materialize the loot if any. Then:

function Decor:isOpen()
    return self.actionCount > 2
end

This does the job and please forgive me if I commit this: Decor now permits entry on third attempt to enter, i.e. only when loot is taken.

Looking Back

I am very rattled by the Internet going up and down, far more than the problem deserves. No doubt it will sort out sooner or later, but it has made me very edgy. Accordingly, I’m not doing what I know I should, but instead going back to my ancient comfort zone of just coding until it works.

I really should write a test for this interaction between Decor, Loot, and Player but I’m not at all sure how.

I do have some TileArbiter tests. But perhaps that’s not what I really need. How about a direct test against a Decor?

        _:test("Decor open state", function()
            local tile = FakeTile()
            local decor = Decor(tile,item)
            decor.currentTile = function()
                return tile
            end
            decor.danger = decor.doNothing
            decor:actionWith(nil)
            _:expect(decor:isOpen(),"1").is(false)
            decor:actionWith(nil)
            _:expect(decor:isOpen(),"2").is(false)
            decor:actionWith(nil)
            _:expect(decor:isOpen(),"3").is(true)
        end)

This took a small amount of hackery. The actionWith method asks for currentTile(), which would normally look the item up in DungeonContents, but we don’t want to have that. So I monkey patched the currentTile method to return my current FakeTile. And Decor currently assign danger randomly, so I overrode that in this object to be doNothing.

The test runs. I add this explanatory comment:

        -- Decor requires three tries to enter,
        -- once to materialize the Loot
        -- once to fetch the loot without entering
        -- once to be allowed to enter thereafter.

Commit: Added test for three-phase entry to Decor to allow for Loot.

This is all made a bit more weird because the way TileArbiter decides you can enter a Tile is if any resident allows it. So if we just allowed entry on Decor open, and there was a Loot, the Decor would allow us to step on the Loot even though the Loot disagreed.

Perhaps we should change the rule so that all residents have to allow entry. I’ll make a story for that.

OK, I’ve justified my existence a bit. Let’s sum up. I’m emotionally whipped without my Internet.

Summary

Wow, terrible morning. But we did get a story done: Allow entry to open Decor. And I did write a test for the thing, though it is a weak one.

What we see here is that our emotional state interferes with our work in discernible ways. In my case, I was seriously reluctant to write a test for what I was doing, and I’ve still not completely tested the combined Decor/Loot behavior. Now, frequent readers will know that my tests are generally inadequate in this program, so my reluctance isn’t entirely due to a weird morning, but the weird morning didn’t help.

Or maybe it did help, by making me more aware that I was doing the wrong thing, because I felt generally nervous. Who knows for sure.

What I do know is that I “just coded” this up, and it took two tries, and it works, and I have at least a small testing improvement.

But I don’t feel good about the morning. Let’s look at this again tomorrow and see what we can see.