Just button highlighting, I guess, unless we see something interesting. Ooo, joy! Some tests!

There’s a reason why little tasks like this are more interesting than the tasks themselves: each time we find that it’s pretty easy to extend our system to do the new things we ask of it, we have another tick on the side of incremental design and development.

By keeping different ideas isolated, even without making the code all general and reusable, we tend always to have a place to make the changes we need. Often we don’t put things in quite the right place. Sometimes there isn’t even a “right place” yet. We sort of nudge things in the right direction, bit by bit … incrementally, if you will, making the program more and more capable, keeping the code alive, on and on. Two hundred and twenty articles so far, in this case. Wow.

Last time, we modified the inventory touch logic so that if you touch and hold on an inventory item, then drag your finger off before releasing, we give a message about what you touched, but we don’t “use” it. So you can get a reminder of what a given item up there is, in case you didn’t notice the message, or forgot.

We did it like this:

function InventoryItem:touched(aTouch, pos)
    if aTouch.state == BEGAN and manhattan(aTouch.pos,pos) < ItemWidth//2 then
        Inventory:capture(self, pos)
        return
    end
end

function Inventory:touched(aTouch)
    if self.captured then
        self.captured:capturedTouched(aTouch, self.capturedPos)
    else
        for i,entry in ipairs(self:entries()) do
            entry.item:touched(aTouch, self:drawingPos(i, self:count()))
        end
    end
end

function InventoryItem:capturedTouched(aTouch, pos)
    if aTouch.state == ENDED then 
        if manhattan(aTouch.pos, pos) < ItemWidth//2 then
            Inventory:remove(self)
            self:informObjectRemoved()
            self:usedMessage()
            Bus:publish(self.attribute, self.value1, self.value2)
        else
            Inventory:capture(nil)
            self:informObjectTouched()
        end
    end
end

The trip around this is somewhat odd and a bit interesting. The object Inventory holds all the InventoryItem instances that you presently have. The list varies, which means that the display at the top of the screen varies, which means that the position of the “button” representing a given item changes.

The process starts with nothing “captured”. We’ll come around to capture in a moment. Inventory loops over its items, offering each one the chance to process the touch. If the touch position is close enough to the item’s position, as told to it by Inventory, and it’s not an ENDED state (finger up), the item tells the inventory that it wants to capture the touch.

Thereafter, when the touch continues, the item sees all the events, ignoring all but ENDED. When the touch is ENDED, the item checks to see if the final touch is in range or not. If it is, it uses itself and removes itself from inventory. If the final touch is not inside it, it just displays its information message “You have touched a whatever”.

What’s not to like?

There are some issues with this setup. One is that the inventory items don’t know their position, because they more around in the inventory bar, so that when we capture the item, we also capture its position, in two separate variables:

function Inventory:capture(item,pos)
    self.captured = item
    self.capturedPos = pos
end

That is coupling, and it’s a code smell. We could make it a bit better like this, perhaps:

function Inventory:capture(item,pos)
    self.captured = {item=item, pos=pos}
end

Now we bind the two objects together. Of course we now have to unbind them:

function Inventory:touched(aTouch)
    if self.captured then
        self.captured.item:capturedTouch(aTouch, self.captured.pos)
    else
        for i,entry in ipairs(self:entries()) do
            entry.item:touched(aTouch, self:drawingPos(i, self:count()))
        end
    end
end

This is a bit awkward in the usage, but at least they are bound together. There should be no reference to capturedPos now. There isn’t. That’s good.

This is still rather stinky, because of the way we have to unpack the thing to use it. We could make that more clear with some Explaining Local Variables. I think we’ll save that, however, because we have some larger concerns that are going to change this.

In fact, I’m tempted to revert this change back out, but no, I think we’ll keep it and see where it leads. Commit: save captured inventory item and position in a small table.

Bigger Fish

Our mission now is to get buttons and inventory items to highlight when touched. We think this will make it easier for viewers of our little test movies to see what is happening. We don’t expect that the actual user will appreciate the feature so much, because their finger will hide the highlighting.

There are a few possible ways to proceed. I think they have a common element which is that the notion of something capturing the touch will be lifted up to a higher level, so that either a button or an inventory item can do the capture.

We could proceed in at least these ways:

  • Put highlighting into InventoryItem. This would perhaps be a good way to see how to do it.
  • Start working on capture in Button, so that when you drag your finger off a button, it doesn’t count as a touch, same as the Inventory.
  • Figure out some way to do highlighting in Button without capture.

The last idea feels to me like something I might have done in the past. They asked us for highlighting, so push it in somewhere. But today, it feels wrong to me. It seems to me today that there is a tight linkage between capture and highlighting: if a thing is captured, it should be highlighted.

When you put it that way, you can almost see how to do it.

So let’s do this: we’ll figure out how to do Button capture, probably along the lines of the Inventory one. Then we will use that to do highlighting for Button. And, along there somewhere, we’ll convert the Inventory to use the save capture mechanism as Button, since we really only want one thing captured at a time. (We’re not doing multi-touch, and probably won’t. If we do, we’ll deal with it then.)

Here’s how touch works with Buttons:

function touched(aTouch)
    CodeaTestsVisible = false
    Runner:touched(aTouch)
end

function GameRunner:touched(aTouch)
    for i,b in ipairs(self.buttons) do
        b:touched(aTouch, self.player)
    end
    Inventory:touched(aTouch)
end

function Button:touched(aTouch, player)
    if self:shouldAccept(aTouch,player) then
        self:performCommand(player)
    end
end

function Button:shouldAccept(aTouch, player)
    return aTouch.state == BEGAN and player:itsOurTurn() and self:mine(aTouch)
end

We see that we trigger Buttons on BEGAN. That’s part of what we want to change.

We currently have the variable self.captured in Inventory. I’m toying with moving that to the new place, if I knew where the new place was.

And I think we can see where it should go: GameRunner.

I’m gonna move it up. It just feels like a good start.

The new rule will be that if your regular touched method is called, nothing is captured, and you can proceed normally.

function InventoryItem:touched(aTouch, pos)
    if aTouch.state == BEGAN and manhattan(aTouch.pos,pos) < ItemWidth//2 then
        Inventory:capture(self, pos)
        return
    end
end

We’ll change this to tell Runner:

function InventoryItem:touched(aTouch, pos)
    if aTouch.state == BEGAN and manhattan(aTouch.pos,pos) < ItemWidth//2 then
        Runner:capture(self, pos)
        return
    end
end

function GameRunner:capture(item,pos)
    self.captured = {item=item,pos=pos}
end

We’ll change Inventory to use the new captured:

function Inventory:touched(aTouch)
    if Runner.captured then
        Runner.captured.item:capturedTouch(aTouch, Runner.captured.pos)
    else
        for i,entry in ipairs(self:entries()) do
            entry.item:touched(aTouch, self:drawingPos(i, self:count()))
        end
    end
end

This is nasty. We’re ripping apart an object we had no part in creating. But we’ll let it stand for a bit and sort it soon.

This should work as before. I’l test. Didn’t work:

Inventory:239: attempt to call a nil value (method 'capturedTouch')
stack traceback:
	Inventory:239: in method 'touched'
	GameRunner:571: in method 'touched'
	Main:97: in function 'touched'

I don’t know why it did that. Oh. The method is capturedTouched. Now I get this. I should revert.

Inventory:292: attempt to call a nil value (method 'capture')
stack traceback:
	Inventory:292: in method 'capturedTouched'
	Inventory:239: in method 'touched'
	GameRunner:571: in method 'touched'
	Main:97: in function 'touched'

No, that was here:

function InventoryItem:capturedTouched(aTouch, pos)
    if aTouch.state == ENDED then 
        if manhattan(aTouch.pos, pos) < ItemWidth//2 then
            Inventory:remove(self)
            self:informObjectRemoved()
            self:usedMessage()
            Bus:publish(self.attribute, self.value1, self.value2)
        else
            Runner:capture(nil)
            self:informObjectTouched()
        end
    end
end

Used to say Inventory, not Runner. Bad refactoring. Test. Works.

I wonder if there’s a way to automate tests for the touch stuff. We’ll work on that. Commit: promote touch-capture object up to GameRunner.

Now let’s move the logic for checking for capture out of Inventory and up to GameRunner:

function GameRunner:touched(aTouch)
    if self.captured then
        self.captured.item:capturedTouched(aTouch, self.captured.pos)
    else
        for i,b in ipairs(self.buttons) do
            b:touched(aTouch, self.player)
        end
        Inventory:touched(aTouch)
    end
end

Should still work. Does. Commit: captured touch handled in GameRunner.

Now what? Well, we’ve got a sort of odd arrangement, where the Runner knows what method to call on the captured item. We’ll let that ride, but I’m not happy about that, nor about the odd little item/pos table.

We can use it however, in our buttons, and it’s time to make them work similarly to InventoryItems.

function InventoryItem:touched(aTouch, pos)
    if aTouch.state == BEGAN and manhattan(aTouch.pos,pos) < ItemWidth//2 then
        Runner:capture(self, pos)
        return
    end
end

(By the way, I think that manhattan check is too conservative, but again, that’s not for just now. I’ve made a card.)

So Button should be like that:

function Button:touched(aTouch, player)
    if self:shouldAccept(aTouch,player) then
        self:performCommand(player)
    end
end

function Button:shouldAccept(aTouch, player)
    return aTouch.state == BEGAN and player:itsOurTurn() and self:mine(aTouch)
end

function Button:mine(aTouch)
    local x = aTouch.pos.x
    local y = aTouch.pos.y
    local ww = self.w/2
    local hh = self.h/2
    return x > self.x - ww and x < self.x + ww and
    y > self.y - hh and y < self.y + hh
end

I think we’ll have to break this before fixing it. I rename the touched method capturedTouched, because it contains at least part of what I’ll need. Then

function Button:touched(aTouch, player)
    if aTouch.state == BEGAN and self:mine(aTouch) then
        Runner:capture(self,player)
    end
end

Now this is weird because Runner expects an object and a position, but we’ll let that ride, too, and rename when this works.

Now we should get a call to capturedTouch when you lift off after touching a button. I’ll plug in an assert to check that. Yes, we get there. Therefore:

function Button:capturedTouched(aTouch, player)
    if aTouch.state == ENDED and player:itsOurTurn() then
        if self:mine(aTouch) then
            self:performCommand(player)
        else
            -- lifted outside
        end
    end
end

We still check the itsOurTurn, which is probably odd and unnecessary, but again, not our job at present. Now I expect the button to work if I lift while on it, and not otherwise.

One little problem, we need to clear the capture!

function Button:capturedTouched(aTouch, player)
    if aTouch.state == ENDED and player:itsOurTurn() then
        if self:mine(aTouch) then
            self:performCommand(player)
        else
            -- lifted outside
        end
        Runner:capture(nil)
    end
end

That doesn’t work, and probably didn’t work in some previous commits. Why?

function GameRunner:capture(item,pos)
    self.captured = {item=item,pos=pos}
end

function GameRunner:touched(aTouch)
    if self.captured then
        self.captured.item:capturedTouched(aTouch, self.captured.pos)
    else
        for i,b in ipairs(self.buttons) do
            b:touched(aTouch, self.player)
        end
        Inventory:touched(aTouch)
    end
end

captured will never be nil after it’s once set, it’ll be a table with nothing in it.

Quick fix:

function GameRunner:touched(aTouch)
    if self.captured.item then
        self.captured.item:capturedTouched(aTouch, self.captured.pos)
    else
        for i,b in ipairs(self.buttons) do
            b:touched(aTouch, self.player)
        end
        Inventory:touched(aTouch)
    end
end

This is legacy code in action here but we need to get the bridge to bear weight unless we want to back out, and we don’t.

Hack wasn’t good enough. capture can be nil. Bigger hammer:

function GameRunner:touched(aTouch)
    if self.captured and self.captured.item then
        self.captured.item:capturedTouched(aTouch, self.captured.pos)
    else
        for i,b in ipairs(self.buttons) do
            b:touched(aTouch, self.player)
        end
        Inventory:touched(aTouch)
    end
end

Whew! All good. Commit: Buttons and Inventory use Runner:captured to accept ENDED touches that complete inside the button, and ignore or inform on those that do not.

But not very pretty.

What if captured was a function? That would be a very Lua thing to do. A more OO thing to do might be to make it an object.

If we make an object, i’ll be a silly little thing, with a couple of parameters that it doesn’t understand, and a method to call on the object.

We have other things not to like here. There’s an if in touched, to decide whether there’s a capture. And right now, it’s an ugly if.

OK, here’s an idea. I’m not sure it would work, and not sure it would be as nice as I think it would, but suppose we have a variable in Runner, not captured but touchFunction, and it always contains a function that we can call.

Normally it contains a function that does the main touch-sensing work:

        for i,b in ipairs(self.buttons) do
            b:touched(aTouch, self.player)
        end
        Inventory:touched(aTouch)

But it can be a function that either calls the capturedTouched on InventoryItem or Button, as desired.

Let’s try it. I think it might be good.

First extract the needed method:

function GameRunner:touched(aTouch)
    if self.captured and self.captured.item then
        self.captured.item:capturedTouched(aTouch, self.captured.pos)
    else
        self:basicTouched(aTouch)
    end
end

function GameRunner:basicTouched(aTouch)
    for i,b in ipairs(self.buttons) do
        b:touched(aTouch, self.player)
    end
    Inventory:touched(aTouch)
end

All should be well. Seems to be. Commit: refactor out basicTouched method in GameRunner.

Now we want to change the primary touched method to call the function unconditionally, and then make everyone do the right thing.

function GameRunner:touched(aTouch)
    self.touchHandler()
end

I plan to do this in capture:

function GameRunner:capture(aFunction)
    self.touchHandler = aFunction or function(aTouch)
        self:basicTouched(aTouch)
    end
end

Obviously I have to pass the touch, so:

function GameRunner:touched(aTouch)
    self.touchHandler(aTouch)
end

Now the calls to capture:

function Button:touched(aTouch, player)
    if aTouch.state == BEGAN and self:mine(aTouch) then
        Runner:capture(function(aTouch)
            self:capturedTouch(aTouch, player)
        end)
    end
end

I think that’s going to work, but I’m prepared to revert. Now the InventoryItem.

function InventoryItem:touched(aTouch, pos)
    if aTouch.state == BEGAN and manhattan(aTouch.pos,pos) < ItemWidth//2 then
        Runner:capture(function(aTouch)
            self:capturedTouched(aTouch,pos)
        end)
    end
end

Oops, didn’t say capturedTouched in the Button. Fix:

function Button:touched(aTouch, player)
    if aTouch.state == BEGAN and self:mine(aTouch) then
        Runner:capture(function(aTouch)
            self:capturedTouched(aTouch, player)
        end)
    end
end

This should almost work except that the touchHander isn’t initialized. Let me hack that:

function GameRunner:touched(aTouch)
    if not self.touchHandler then
        self:capture(nil)
    end
    self.touchHandler(aTouch)
end

Does this all work? Let’s find out. It does, all of it.

Commit: GameRunner:touch now calls current touchHander to dispatch to the right touch function, generic in GameRunner, or captured in InventoryItem or Button.

We could avoid that if with an unconditional capture in our init. Let’s do.

function GameRunner:init(testX, testY)
    self.tileSize = 64
    self.tileCountX = testX or 85 -- if these change, zoomed-out scale 
    self.tileCountY = testY or 64 -- may also need to be changed.
    self:createNewDungeon()
    self.cofloater = Floater(50,25,4)
    self.musicPlayer = MonsterPlayer(self)
    self.dungeonLevel = 0
    self.requestNewLevel = false
    self.playerRoom = 1
    self:capture(nil)
    Bus:subscribe(self, self.createNewLevel, "createNewLevel")
    Bus:subscribe(self, self.darkness, "darkness")
end

I’m going to test this manually one more time and then we gotta figure out some decent automated tests for all this.

Still good: commit: init capture properly, remove if from GameRunner:touched

Testing the Touch

Let’s see if we can get some testing working, to check at least some of this.

The touch properties we use are pos and state, and I think not much else.

We could test all this using a GameRunner, but even the test version of that is fairly heavy. It’s probably relatively easy, so let’s see where we can steal ideas on this particular testing problem. Here’s the current sole use:

        _:before(function()
            _bus = Bus
            Bus = EventBus()
            _dc = DungeonContents
            DungeonContents = DungeonContentsCollection()
            _runner = Runner
            local gm = GameRunner(25,15)
            Runner = gm
            gm:createTestLevel(0)
            dungeon = gm:getDungeon()
            _TileLock = TileLock
            TileLock = false
        end)

That’s a lot. Let’s go another way. We’ll create a new test function, in the Button tab, to test that the Button does the right thing. We’ll follow our nose as the test fails, and try to build up minimal scaffolding.

function testButton()
    CodeaUnit.detailed = false
    
    _:describe("Button", function()
        
        _:before(function()
        end)
        
        _:after(function()
        end)
        
        _:test("First Test", function()
            _:expect(2).is(1) -- change this to fail
        end)
        
    end)
end

So far so good, it fails.

        _:test("First Test", function()
            local b = Button("foo", 100,100, 20,20)
            local t = {pos=vec2(105,105)}
            _:expect(b:mine(t)).is(true)
        end)

I just created a fake touch, with a pos and nothing else. The pos is inside the button, and the test passes. It fails when the touch is outside.

Now we’d like to make sure that the button calls capture when its touched function is called. If we just send it touched, it will surely explode trying to capture if we set the state to BEGAN:

I tried this:

        _:test("First Test", function()
            local b = Button("foo", 100,100, 20,20)
            local t = {pos=vec2(105,105), state=BEGAN}
            _:expect(b:mine(t)).is(true)
            _:expect(b:touched(t)).throws("peep")
        end)

Clever but the error is:

1: First Test -- Button:65: attempt to index a nil value (global 'Runner')

Sure. Let’s init Runner. We should also save and restore in the before/after.

1: First Test -- Button:65: attempt to call a nil value (method 'capture')

I may not be using the throws correctly. We can do better, though.

        _:before(function()
            _runner = Runner
            Runner={capture=function(_self, aFunction)
                   _:expect(type(aFunction)).is("function") 
                end   
                }
        end)

We don’t know, and can’t know, whether the function works but we know one was passed to our runner table. The throws doesn’t fail, and we don’t need that check.

        _:test("First Test", function()
            local b = Button("foo", 100,100, 20,20)
            local t = {pos=vec2(105,105), state=BEGAN}
            _:expect(b:mine(t)).is(true)
            b:touched(t)
        end)

Now how about testing the capturedTouch? Let’s extend our test table:

        _:before(function()
            _runner = Runner
            Runner={capture=function(_self, aFunction)
                    _handler = aFunction
                   _:expect(type(_handler)).is("function")
                end   
                }
        end)

Now we’re saving the function, so in for a penny, let’s call it and see what happens.

        _:before(function()
            _runner = Runner
            Runner={capture=function(_self, aFunction)
                    _handler = aFunction
                   _:expect(type(_handler)).is("function")
                end   
                }
        end)

        _:test("First Test", function()
            local b = Button("foo", 100,100, 20,20)
            local t = {pos=vec2(105,105), state=BEGAN}
            local tEnd = {pos=vec2(200,200), state=ENDED}
            _:expect(b:mine(t)).is(true)
            b:touched(t)
            _handler(tEnd)
        end)

This explodes of course.

1: First Test -- Button:53: attempt to index a nil value (local 'player')

Now we realize, we need to pass a player to our Button:touched. Let’s do that, move a step or so forward, and then I’d like to review why what we’re doing here is actually sensible.

        _:test("First Test", function()
            local b = Button("foo", 100,100, 20,20)
            local p = {}
            local t = {pos=vec2(105,105), state=BEGAN}
            local tEnd = {pos=vec2(200,200), state=ENDED}
            _:expect(b:mine(t)).is(true)
            b:touched(t, p)
            _handler(tEnd, p)
        end)

I’ve made an empty player p, and passed it as expected to the Button. Our mission is to process whatever fails, so long as it’s right.

1: First Test -- Button:53: attempt to call a nil value (method 'itsOurTurn')
            local p = {itsOurTurn=function(_self)
                    return true
                end
                }
1: First Test  -- Actual: nil, Expected: function

We sent capture(nil) and that’s correct. Simplest fix is to allow nil as well as a function in our little fake object. But that would allow the sequence to be down out of order. We really want a function first time we call capture, and a nil the second time.

This is a bit skewed but let’s try it:

        _:before(function()
            _runner = Runner
            Runner={capture=function(_self, aFunction)
                    if not _handler then
                        _handler = aFunction
                        _:expect(type(_handler), "first time").is("function")
                    else
                        _:expect(aFunction, "second time").is(nil)
                    end
                end   
                }
        end)

That works. Still not great, though.

What we really “need”, it appears, is a fake runner object that can manage a sequence of calls to capture. That’s getting further into Fake and Mock objects than I want to go.

Let’s see. Our current test looks like this:

        _:test("First Test", function()
            local b = Button("foo", 100,100, 20,20)
            local p = {itsOurTurn=function(_self)
                    return true
                end
                }
            local t = {pos=vec2(105,105), state=BEGAN}
            local tEnd = {pos=vec2(200,200), state=ENDED}
            _:expect(b:mine(t)).is(true)
            b:touched(t, p)
            _handler(tEnd, p)
        end)

Why aren’t we getting another error on calling _handler? Oh, I know. Buttons don’t do anything on a lift outside, other than clear their capture. So let’s do a test with the touch still inside the button.

        _:test("Touch Test", function()
            local b = Button("foo", 100,100, 20,20)
            local p = {itsOurTurn=function(_self)
                    return true
                end
            }
            local t = {pos=vec2(105,105), state=BEGAN}
            local tEnd = {pos=vec2(105,105), state=ENDED}
            _:expect(b:mine(t)).is(true)
            b:touched(t, p)
            _handler(tEnd, p)
        end)

This one should explode wanting something from our take player. Errors surprise me:

2: Touch Test second time -- Actual: function: 0x2826faca0, Expected: nil
2: Touch Test -- Button:72: attempt to call a nil value (field '?')

The second one may be what I expected.

function Button:performCommand(player)
    player[self.name](player) -- 72
    player:turnComplete()
end

Yes, our fake player has no method foo to call. Let’s fi that. A couple of runs and I have this:

            local p = {itsOurTurn=function(_self)
                    return true
                end,
                foo=function() end,
                turnComplete=function() end
            }

But I still have this error:

2: Touch Test second time -- Actual: function: 0x2827be760, Expected: nil

This is almost too tricky to follow. Let’s look at the test carefully:

        _:test("Touch Test", function()
            local b = Button("foo", 100,100, 20,20)
            local p = {itsOurTurn=function(_self)
                    return true
                end
            }
            local t = {pos=vec2(105,105), state=BEGAN}
            local tEnd = {pos=vec2(105,105), state=ENDED}
            _:expect(b:mine(t)).is(true)
            b:touched(t, p)
            _handler(tEnd, p)
        end)

It sure seems like _handler will be pointing at capturedTouched in Button, and, well, what does that do?

function Button:capturedTouched(aTouch, player)
    if aTouch.state == ENDED and player:itsOurTurn() then
        if self:mine(aTouch) then
            self:performCommand(player)
        else
            -- lifted outside
        end
        Runner:capture(nil)
    end
end

Ah, I think we need to init _handler in the before.

Yes. I’ve gone for overkill here:

        _:before(function()
            _hander = nil
            _runner = Runner
            Runner={capture=function(_self, aFunction)
                    if not _handler then
                        _handler = aFunction
                        _:expect(type(_handler), "first time").is("function")
                    else
                        handler = nil
                        _:expect(aFunction, "second time").is(nil)
                    end
                end   
                }
        end)

Still no joy, same error. Wait, I don’t really know if this is “second time” or not.

What I do know is that it should switch between function and nil and back.

I have to get this test working or back out, so I’m going to bash it just a bit more.

Bashing isn’t working.

Back out the second test (wish I had committed the working one), and get the first one running again.

I think I know what the problem was. I now have this new checker for capture:

            Runner={capture=function(_self, aFunction)
                    if _captureCount%2 ==0 then
                        _handler = aFunction
                        _:expect(type(_handler), "first time").is("function")
                    else
                        handler = nil
                        _:expect(aFunction, "second time").is(nil)
                    end
                    _captureCount = _captureCount + 1
                end   
                }

It expects alternation between function and nil, but does it right. Now maybe the second test will do the right thing. I’ll paste it back.

Now I get an error I can almost believe:

2: Touch Test -- Button:72: attempt to call a nil value (field '?')

That’s because I have a new smarter player:

            _player = {itsOurTurn=function(_self)
                    return true
                end,
                foo=function() end,
                turnComplete=function() end
            }

However … the second test does run, but I don’t think it should:

        _:test("Touch Test", function()
            local b = Button("foo", 100,100, 20,20)
            local t = {pos=vec2(105,105), state=BEGAN}
            local tEnd = {pos=vec2(105,105), state=ENDED}
            _:expect(b:mine(t)).is(true)
            b:touched(t, _player)
            _handler(tEnd, _player)
        end)

Ah, no, it’s OK, there is a foo method. However, let’s check to see that it’s called:

        _:before(function()
            _captureCount = 0
            _hander = nil
            _fooCalled = false
            _runner = Runner
            _player = {itsOurTurn=function(_self)
                    return true
                end,
                foo=function() _fooCalled = true end,
                turnComplete=function() end
            }

And check that:

        _:test("Touch Test", function()
            local b = Button("foo", 100,100, 20,20)
            local t = {pos=vec2(105,105), state=BEGAN}
            local tEnd = {pos=vec2(105,105), state=ENDED}
            _:expect(b:mine(t)).is(true)
            b:touched(t, _player)
            _:expect(_fooCalled).is(false)
            _handler(tEnd, _player)
            _:expect(_fooCalled).is(true)
        end)

OK, this is nearly good. Commit: two unit tests for Button touched behavior.

Now let’s assess and sum up. Time is fleeting and we’ll do the other test tomorrow.

Assesment / Summary

Looking at the big picture, this pair of tests isn’t entirely terrible:

function testButton()
    CodeaUnit.detailed = false
    
    local _runner
    local _handler
    local _captureCount
    local _player
    
    _:describe("Button", function()
        
        _:before(function()
            _captureCount = 0
            _hander = nil
            _fooCalled = false
            _runner = Runner
            _player = {itsOurTurn=function(_self)
                    return true
                end,
                foo=function() _fooCalled = true end,
                turnComplete=function() end
            }
            Runner={capture=function(_self, aFunction)
                    if _captureCount%2 ==0 then
                        _handler = aFunction
                        _:expect(type(_handler), "first time").is("function")
                    else
                        handler = nil
                        _:expect(aFunction, "second time").is(nil)
                    end
                    _captureCount = _captureCount + 1
                end   
                }
        end)
        
        _:after(function()
            Runner = _runner
        end)
        
        _:test("Lift Test", function()
            local b = Button("foo", 100,100, 20,20)
            local t = {pos=vec2(105,105), state=BEGAN}
            local tEnd = {pos=vec2(200,200), state=ENDED}
            _:expect(b:mine(t)).is(true)
            b:touched(t, _player)
            _handler(tEnd, _player)
        end)
        
        _:test("Touch Test", function()
            local b = Button("foo", 100,100, 20,20)
            local t = {pos=vec2(105,105), state=BEGAN}
            local tEnd = {pos=vec2(105,105), state=ENDED}
            _:expect(b:mine(t)).is(true)
            b:touched(t, _player)
            _:expect(_fooCalled).is(false)
            _handler(tEnd, _player)
            _:expect(_fooCalled).is(true)
        end)
        
    end)
end

It’s about 60 lines, with four direct expect and two more duplicated inside our capture.

So that’s about 7 or 8 lines per expect, which isn’t great. I think I’d usually like to see two or three. The actual tests are about right, it’s the foofaraw up in before that got weird.

Part of the issue is that we hand-rolled a fake GameRunner and a fake Player. The fake Player had to accept three of the real Player’s calls, namely itsOutturn, turnComplete and foo. (The latter was because the Button we created was named “foo”. We could have named it “left” and had a function left in our fake player.

I think I’d have to admit that hand-rolled tables containing functions is a bit deep in the bag of tricks for most of us programmers, but I’d argue, in my defense, that it’s not that deep for a Lua programmer. After all, a class is just a table containing functions.

But yes, it is a bit obscure.

And the capture checking is a bit odd. We had wound up with a capture function that always expects to be called with a function first, and then with nil. So I wrote it to keep a count and check the one or other depending on whether count is odd or even.

Now my colleagues who are into Mocks and such would be telling me that I could use Mockito or Tarantula or Kraken or something and it would have been easy. And they’re right. It’s not just how I roll, especially not in Lua, but mostly because I’m Detroit school and that’s how we do it.

Be that as it may, we’re testing something that’s usually hidden, namely that the sequence of things that the Button does will be to capture using a function, and then clear the capture by passing a nil, which is the way we want it to be. In addition, we actually called the function and verified that it did the right things.

However, that verification is now a bit weak, because we patched functions into our fake Player but didn’t go so far as to verify that they are called at the right times. We know it was right when we got errors and put in those new fake functions … but we don’t really know now that they work.

We could comment out this line:

function Button:performCommand(player)
    player[self.name](player)
    --player:turnComplete()
end

And the tests would still run. There are a couple of weak spots in these tests.

But they are tests!! Not long ago, my only testing for buttons was to run the game. Now I have confidence without running the game. Not total confidence, not yet.

But a foothold, one that can be made better.

We have a bit of new capability, in that you can slide your finger off a button and not trigger it, and we have some tests to cover much of our work the past few days.

The program is more capable, and the code is better.

That’s a win. Take a little shot of joy. And see me next time!


D2.zip