No idea what I’m going to do this morning. Or too many ideas. Let’s find out.

Improve Buttons
Ah, here’s an idea. The buttons should highlight while pressed (and take no action until released, with finger inside the button). I think that would make it more obvious what’s happening in the movies. For the user, highlighting won’t help much, as the finger is in the way, but being able to drag off and then lift should be helpful.

Relatedly, I’d like to have a way to find out what an Inventory Item is without using it. Either lifting after dragging finger off, or double-tapping should do the job.

Messages over sender?
It might be nice if messages rose over the sender instead of over the Player. When the NPC is “speaking”, it looks a bit odd to have the text above the player. They’re only one square apart, so I’m not sure this is worth doing.
FSM wildcard in From
Some FSMs allow from="*" to signify a transition that will wor for any initial state. We have such a transition now, and have used the list capability for it. It’s kind of a YAGNI situation, though. We should probably save that idea until we need it rather than make the code more complicated for no real benefit. Here’s the only spot where we could use the idea:
            {event="received", from={"unsat","wait"}, to="thanks", arg="thanks"},
Integrity check for FSM?
It would be easy to mistype something and thus create a situation where you got stuck in a transition and couldn’t get out, or where a callback wouldn’t get triggered. It should be possible to create at least a rudimentary integrity check for an FSM when we create one.
Only one Inventory Item in a level
It would be nice if we had a bit more control over what items are strewn around the dungeon. We’d surely like to ensure, for example, that there’s only one Amulet of Feline Persuasion to be found. We almost certainly want different items for different levels.

This would be an excellent place for some kind of text-driven definition, so that dungeon creation becomes less of a programming thing and more of a specifying thing.

Ever so much more …

I now have a stack of what looks like at least 40 cards with ideas for this game. Like so many real products and projects, this thing could go on forever. With a game we’re copying, like Spacewar! or Asteroids, there’s a finite end to the effort, when we’ve made our copy do enough of what the original did. We might cut off early, not including fancy scoring or the little alien dragging the upside-down Y off, but we’re not faced with endless ideas.

In a game like Dungeon, there’s always more. And for my purposes here, the decision comes down to whether we’re bored (are either of you bored?) or whether we feel we’d sucked all the learning off the bones.

With things like FSM and NPC and Tale being brand new, it seems there are still plenty of interesting things to build if we choose to do so.

If I didn’t do this, what should I do? Feel free to tweet or email and tell me. For today …

Highlight Buttons

Let’s define our story this way:

  1. Buttons should highlight when pressed, and should take action when the press ends, not when it starts.
  2. If the press ends outside the button bounds, it should not trigger whatever the button does.
  3. If the press starts on an Inventory button and ends outside, display information as to what the item pressed was, but do not use it. Optionally, allow double-tap on Inventory to provide the info.

We have some issues here.

We need to review what our Button code does now, to get a sense of where we stand. We need to review how touch works in Codea. We may need to do an experiment to get a sense of how to code up what we want.

Let’s begin.

A Button is a rectangular area on the screen. It draws something, like an image or a bit of text. And it responds to touch like this:

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

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

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

We see right away that we’re accepting state BEGAN, which is exactly what we don’t want.

What about the Inventory?

GameRunner dispatches all the touches:

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

We know that no touch can be both on a button and on inventory, so we just pass things on to everyone and assume that the right thing will be done. One could imagine a more structured approach, but at this point I don’t see why we’d do more than this.

What is going on in Inventory:touched?

function Inventory:touched(aTouch)
    for i,entry in ipairs(self:entries()) do
        entry.item:touched(aTouch, self:drawingPos(i, self:count()))
    end
end

So that defers on down to the item:

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

It would appear that if we touch an inventory item and draw our finger away from it, the item will not deploy. Excuse me for a moment while I check that. Yes, just as expected.

I am inclined to try something here. If an item encounters touch state BEGAN or CHANGED, let’s save the item somewhere. Then if we get ENDED and we’re not inside the item, we could say what it is.

We’ll need to be careful with this, since all the touch events go to buttons as well as inventory.

I’m not sure about this.

Uh oh!!

Major Yak to Shave!!

I got a new iPad yesterday. My code repo for Dungeon is on the old iPad, and WorkingCopy can’t see it. I might be able to fudge it over somehow, or I can just start a new repo here.

I don’t go back often, so let’s take the easy way out and create a new repo.

276 files committed. Wow. Mostly graphics items, of course.

I should mention that if I had an external remote repo for the project, Working Copy would have automatically cloned it into the new iPad (after asking permission). But I don’t keep most of these projects up on GitHub or another remote. Perhaps I should.

Yak not so major after all. Carry on.

Inventory Button

OK, now we’re on a clean commit. Let’s see what we can do about saving the Inventory item and providing info about it.

How can we even do this? If the touch is ENDED and not inside an inventory item, no one will even see the touch.

I haven’t programmed touch logic for decades, literally. I think what we need to do is to capture the touch on BEGAN and pass further touches to the capturing object until it releases it.

I’m on a clean commit, so I’m going to just hammer a bit. If it works, we’ll figure out a way to make it clean, and if it doesn’t work, we’ll revert and start anew, having learned something.

We’ll start here:

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

Let’s tell Inventory when we want to capture the touch. I start with this:

function InventoryItem:touched(aTouch, pos)
    if aTouch.state == BEGAN and manhattan(aTouch.pos,pos) < ItemWidth//2 then
        Inventory:capture(self)
        return
    end
    if aTouch.state == ENDED and manhattan(aTouch.pos,pos) < ItemWidth//2 then
        Inventory:remove(self)
        self:informObjectRemoved()
        self:usedMessage()
        Bus:publish(self.attribute, self.value1, self.value2)
    end
end

But I think what I’d like is for Inventory to send me a different message if I’ve captured the touch, say, capturedTouched:

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

And then …

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

Now we can give InventoryItem that method to deal with touch ending.

function InventoryItem:capturedTouched(aTouch)
    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

function InventoryItem:informObjectTouched()
    self:informObject("You touched a "..self.description..".")
end

I think this should nearly work. I’ll try it to find out why I’m wrong.

Tests:575: attempt to index a nil value (local 'v2')
stack traceback:
	Tests:575: in function 'manhattan'
	Inventory:286: in method 'capturedTouched'
	Inventory:243: in method 'touched'
	GameRunner:567: in method 'touched'
	Main:97: in function 'touched'

I think I pasted a bit indiscriminately. Yes. The touch expects to get pos, the center of its area. Fix Inventory:

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

Ah, nasty. We don’t know the pos, which is computed by the index of the item. We’ll pass it in on the capture and save it and pass it back. Nasty.

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:capture(item,pos)
    self.captured = item
    self.capturedPos = pos
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

That really ought to work. Mysteriously, it does:

you have used ...

I’m committing this: touching an inventory item and lifting outside the item displays “You have touched …”

And it’s July 4, I have a lunch date with my son and his wife, and it’s nearly time for brekkers, so let’s break right here on a success.

Summary

The good news is that we were able to get a message to come out when you touch an inventory item and then drag off of it. And if we don’t look too carefully, it kind of makes sense: if the item receives a beginning touch, it asks the inventory to capture the touch and thereafter the item will get the touch messages, until the end event, at which point it decides whether to use the item or just say what it is.

What isn’t quite so nice is that the touch is not captured at the topmost level, so that a touch on a movement button might still do something. And don’t even come at me with multi-touch. And we found the unfortunate truth that, because the position of an inventory item in the Inventory row is dynamic, as items are added and removed, it doesn’t know its position, which had to be saved and passed to it.

Not bad, but a bit clunky.

There’s also the question of what happens–what even should happen–if you touch one item, then drag over to another and release. Right now, you’ll get the message for the first one, That might be OK, we’ll have to think about it.

We still haven’t done the highlighting thing, but we didn’t try, we took the target of opportunity with the inventory advice.

A small step, and a decent one. Enjoy the small things … there can be so many of them!

See you next time!


D2.zip