I believe that I have fallen into my own trap, Could this be the end of our intrepid hero? Click on in and see.

Faithful readers will recall that we’re in the process of arranging our buttons and inventory items so that two things happen:

  • You can touch and then drag your finger away, resulting in no action on a button, and a display of information for the inventory;
  • The button or item highlights when touched, so that our little movies make it more clear what the operator is doing.

Last time, we implemented the ability for a button or item to “capture” the touch, directing all further touch info to a function provided, which allowed us to do the drag-away behavior:

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

In GameRunner, we just save away that function and when a touch event occurs, we execute the function. One of the possible functions is the one that handles a touch when nothing is as yet captured:

function GameRunner:capture(aFunction)
    self.touchHandler = aFunction or function(aTouch)
        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

Now we want to make the buttons and items highlight. That needs to do something like this for buttons:

function Button:draw()
    pushMatrix()
    pushStyle()
    spriteMode(CENTER)
    translate(self.x,self.y)
    if this button is captured then
        tint(255,255,255, 255)
    else
        tint(255,255,255, 128)
    end
    sprite(Sprites:sprite(self.img), 0,0, self.w, self.h)
    popStyle()
    popMatrix()
end

When I apply that different tint to one button, the effect is just about right:

tint

The question facing me is how I can possibly know that the button we’re currently drawing is the captured one. The capture information is just a function that we call on touch.

In yesterday’s article, when considering how to do the capture, I mentioned the function approach that I finally used, or an alternative of putting a small object into the capture variable. I chose the function mostly to see how it would go. It’s a fairly “Lua” thing to do, but not as object-oriented as I usually choose to do things.

It looks now that that wasn’t the best idea. No problem, we’ll do something different. But what?

At present, there is no real record of what button or item is captured. The function saved by the capture knows self, and it does what has to be done. We need to do better.

Imagine a very small “capture” object, containing a button or inventory item, and whatever other information we need in order to work, with two methods: touched and highlight (or maybe shouldHighlight). We’d have three versions of that object just as we now have three versions of the capture functions, one for normal, one for button, one for inventory item.

The main touched method would call touched on the object and … hmm … how would the draw know how to access the information? We could certainly reach back up to Runner, but that’s not as cool as it might be.

Let’s review both draw methods:

function Button:draw()
    pushMatrix()
    pushStyle()
    spriteMode(CENTER)
    translate(self.x,self.y)
    if self.name == "learn" then
        tint(255,255,255,255)
    else
        tint(255,255,255, 128)
    end
    sprite(Sprites:sprite(self.img), 0,0, self.w, self.h)
    popStyle()
    popMatrix()
end

(I’ve left my color-testing if in there.)

function InventoryItem:draw(pos)
    fill(136,129,107)
    rect(pos.x,pos.y,64,64)
    sprite(Sprites:sprite(self.icon), pos.x,pos.y, 40)
end

Very similar and whatever works for one should work for both. But what do we want to say or do?

The caller of these methods is GameRunner for buttons, and Inventory for inventory items:

function GameRunner:drawButtons()
    for i,b in ipairs(self.buttons) do
        b:draw()
    end
end

function Inventory:draw()
    pushMatrix()
    pushStyle()
    rectMode(CENTER)
    spriteMode(CENTER)
    textMode(CENTER)
    for i,entry in ipairs(self:entries()) do
        local pos = self:drawingPos(i, self:count())
        entry.item:draw(pos)
        if entry.count > 1 then
            fill(255)
            fontSize(36)
            text(entry.count, pos.x+20,pos.y-15)
        end
    end
    popStyle()
    popMatrix()
end

At this moment, I have no good idea, but I do have an idea that would probably work.

If–and hear me out–if, at the same time we call capture, we were to store a flag in the captured object, we could use the flag to do the highlight. But we’d have to remember to clear the flag or pretty soon everything would be highlighted.

We’d have coupling, between the capture function and the object whose flag was set.

There’s no question that we could make this work, and we wouldn’t have to change the capture functions. But coupling should be represented, not in two lines of code, in two different places, that have to be inverses of each other, but by one thing, ideally in one place. (We are forced to have two, one to set and one to clear, by the nature of touch events. And in each capturing object as well. Definitely a tiny bit of duplication there that we’d do better to avoid. Not now: we’re on highlight.

I say we try it and see how bad it is.

For Button:

function Button:draw()
    pushMatrix()
    pushStyle()
    spriteMode(CENTER)
    translate(self.x,self.y)
    if self.highlight then
        tint(255,255,255,255)
    else
        tint(255,255,255, 128)
    end
    sprite(Sprites:sprite(self.img), 0,0, self.w, self.h)
    popStyle()
    popMatrix()
end

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)
        self.highlight = false
    end
end

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

I think this probably works. And it does.

buttons highlight

Easy to replicate that in InventoryItem:

function InventoryItem:draw(pos)
    fill(136,129,107)
    rect(pos.x,pos.y,64,64)
    if self.highlight then
        tint(255,255,255,255)
    else
        tint(255,255,255, 128)
    end
    sprite(Sprites:sprite(self.icon), pos.x,pos.y, 40)
end

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)
        self.highlight = true
    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
            Runner:capture(nil)
            self.highlight = false
            self:informObjectTouched()
        end
    end
end

I don’t like the effect of the tint on the more beige inventory items. I’ll fiddle with the values a bit.

I can’t find a color that works well. Let’s try something different.

OK, I found a setup that looks good:

function InventoryItem:draw(pos)
    if self.highlight then
        fill(255,255,255,64)
    else
        fill(255,255,255, 192)
    end
    rect(pos.x,pos.y,64,64)
    if self.highlight then
        tint(255,255,255,64)
    else
        tint(255,255,255, 255)
    end
    sprite(Sprites:sprite(self.icon), pos.x,pos.y, 40)
end

I’ll look at reducing the two if statements in a moment but I’ve found a bug.

color check

In the movie above you can see that i touch and drag on the Rock of Dullness. What you can’t see is that then I tried to use the pathfinder, and got a spurious message about touching the rock of dullness.

That tells me that the item hasn’t cleared the capture in the case of touch-drag. Can that be true?

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.highlight = false
            self:informObjectTouched()
        end
    end
end

Sure is. We need to undo the capture whenever the state is ENDED. I think that whole else clause needs to be moved after the manhattan check and made unconditional.

I kind of hope this breaks a test but I suspect it won’t.

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)
        end
        Runner:capture(nil)
        self.highlight = false
        self:informObjectTouched()
    end
end

I’ll try it again. The touched message came out on use and shouldn’t have. Moved too much.

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
            self:informObjectTouched()
        end
        Runner:capture(nil)
        self.highlight = false
    end
end

right

OK, that did the trick. I’m going to commit this: button and inventory highlight work.

Now I’ll try combining the two ifs. It should be OK graphically.

function InventoryItem:draw(pos)
    if self.highlight then
        fill(255,255,255,64)
        tint(255,255,255,64)
    else
        fill(255,255,255, 192)
        tint(255,255,255, 255)
    end
    rect(pos.x,pos.y,64,64)
    sprite(Sprites:sprite(self.icon), pos.x,pos.y, 40)
end

Works fine. Commit: refactor for just one if-else in inventory item draw.

I need to finish up. Errands and stuff this morning. Let’s sum up:

Summary

Well, it works, and we didn’t have to wreck our capture function code (or trick, if you want to call it that).

I’d be happier with something like this:

function InventoryItem:draw(pos)
    pushStyle()
    self:setColoration()
    rect(pos.x,pos.y,64,64)
    sprite(Sprites:sprite(self.icon), pos.x,pos.y, 40)
    popStyle()
end

function InventoryItem:setColoration()
    if self.highlight then
        fill(255,255,255,64)
        tint(255,255,255,64)
    else
        fill(255,255,255, 192)
        tint(255,255,255, 255)
    end
end

Im still troubled because we have to do two things on capture, one being to capture the touch behavior up at the very top, and the other being to highlight the individual button or item. And then we have to undo both. And I’ve demonstrated that it’s possible to get it wrong.

It seems intuitively that these things are the same. They’re certainly temporally coupled. But the one is so high level, and the other so low, that it makes some sense to allow them to be separate though coupled.

At the moment, I’m not satisfied, but everything is working as intended, so we can ship it. Maybe I’ll get an idea. Maybe you, or someone, will send me one.

See you next time!


D2.zip