Today, Tozier is with me, and I believe we’ll continue working on the controls. Remember that right now, the Ship creates the buttons, because it wants to ask them if they are pressed, thusly:

function Ship:init(shipNumber)
    self.shipNumber = shipNumber
    self.ThrustAmount = vec2(0, 0.2)
    self.TurnAmount = 0.5
    self.pos = self:initialPosition()
    self.heading = self:initialHeading()
    self.vel = vec2(0,0)
    self.controls = { 
        left   = self:controlButton(200),
        right  = { pressed = false },
        thrust = { pressed = false }
    }
end

function Ship:controlButton(offset)
    if self.shipNumber == 1 then
        return Button(WIDTH-85, offset)
    else
        return Button(85, HEIGHT-offset)
    end
end

Tests

No test for width: where was T?

        _:test("right ship buttons", function()
            local rs = Ship(1)
            _:expect(rs.controls.left.pos.y).is(200)
            _:expect(rs.controls.left.pos.x).is(WIDTH - 85)
        end)
        
        _:test("left ship buttons", function()
            local ls = Ship(2)
            _:expect(ls.controls.left.pos.y).is(HEIGHT-200)
            _:expect(ls.controls.left.pos.x).is(85)
        end)

We decide just to add the rest of the buttons:

    self.controls = { 
        left   = self:controlButton(200),
        right  = self:controlButton(400),
        thrust = self:controlButton(600),
        fire   = self:controlButton(800)
    }

And they look right:

!()[controls-red.png]

Well, for values of “right”. The red color is blinding us and causing us to seize. The fact that the buttons are not bilaterally symmetric (thanks Tozier) across the short axis. We need to adjust the colors to be far less obtrusive. But I was quite surprised when I pressed the buttons and the ships actually turn and accelerate. I had thought they were not wired up. You, faithful reader, probably were not surprised, because you remember this code:

function Ship:move()
    local turn
    local thrust
    
    if     self.controls.left.pressed  and not self.controls.right.pressed then turn =  self.TurnAmount
    elseif self.controls.right.pressed and not self.controls.left.pressed  then turn = -self.TurnAmount
    else turn = 0 end
    self.heading = self.heading + turn
    
    if self.controls.thrust.pressed then thrust = self.ThrustAmount else thrust = vec2(0,0) end
    self.vel = self:adjustedVelocity(self.vel, self.heading, thrust)
    self.pos = clip_to_screen(self.pos + self.vel)
end

I believe I wrote this code by intention some days ago, to get a sense of how the buttons should work. Honestly, I’ll have to read back in the chapters to find out. (Yes, I pretty much go through life discovering in the morning where I put my glasses the night before.) What we learned by flying the ships was that velocity appears to be unbounded, and rotation is way too slow.

Tozier asked me to fly into a button and we discovered that at least one ship can hide under at least one button. He acknowledges that the strategy of hiding under the other guy’s finger and firing at him might be interesting but suggests that it is not what we intended. We have learned, it seems, that we need some kind of ordering to the Drawn list. Or, maybe, things like buttons should not be quite so opaque.

Tozier suggests priorities: Symmetry, button colors, and firing. He points out that collision appears in there. I add rotation speed and maximizing velocity. Also the ships are too small and don’t look like anything I remember from the original game.

(We digress to talk about Codea’s ability to draw “sprites”. I am inclined to draw our ships with drawing primitives, because history, but we can certainly look at drawing things with sprites as well. Tozier tells me that Codea can draw a PDF as a sprite. I’m not sure that dueling memoranda is the idea here but it’s interesting.)

We decide on this order: Symmetry, button colors, max velocity, rotation speed. Even this may be too much. If it isn’t, we might start firing some harmless bullets around.

###Symmetry

We did a quick change for symmetry:

    self.controls = { 
        left   = self:controlButton(HEIGHT/5),
        right  = self:controlButton(2*HEIGHT/5),
        thrust = self:controlButton(3*HEIGHT/5),
        fire   = self:controlButton(4*HEIGHT/5)
    }

In testing this to see if we liked the finger positions (we do not), we noticed that if you are holding down, say, the “left” button, your ship turns left. However, if another finger touches the screen anywhere, your touch is stolen, your button turns back to red, and you’re not turning until you at least wiggle your finger. Not good. Remember that button touching is the most tested part of the system, and it’s still wrong. That said, we didn’t do much multi-touch testing, though we did do some.

Our plan is to defer this fix. However, the button positions are terrible. Fix that or let it ride? Tozier votes to defer as it is independent of anything else. I agree because it’s easy and boring.

Now color:

We changed the opacity of the button in its idle state, to see if we liked the effect:

function Button:fillColor()
    if (self.pressed) then
        return color(0, 255, 0, 255)
    else
        return color(255,0,0,26)
    end
end

We’re not fond of it, and of course the pressed color is still a vivid green. We noticed in testing, however, that the ships sometimes fly over a button and sometimes under.

We had previously noticed that the layering is odd. In fact one ship is on top of everything and the other is sandwiched between. We realized that the layering is an artifact of the drawing order, which is an artifact of the creation order. We modified things to allow addDrawnAtTop and addDrawnAtBottom, and moved the ship’s call to be drawn into its creation, to align with how the button works:

-- Main
function setup()
    Ship(1)
    Ship(2)
end

function addDrawnAtTop(anObject)
    table.insert(Drawn, anObject)
end

function addDrawnAtBottom(anObject)
    table.insert(Drawn, 1, anObject)
end

-- Ship

function Ship:init(shipNumber)
    self.shipNumber = shipNumber
    self.ThrustAmount = vec2(0, 0.2)
    self.TurnAmount = 0.5
    self.pos = self:initialPosition()
    self.heading = self:initialHeading()
    self.vel = vec2(0,0)
    self.controls = { 
        left   = self:controlButton(HEIGHT/5),
        right  = self:controlButton(2*HEIGHT/5),
        thrust = self:controlButton(3*HEIGHT/5),
        fire   = self:controlButton(4*HEIGHT/5)
    }
    addDrawnAtTop(self)
end

With this change our ships draw on top of the buttons, which looks better. However, we are spinning out of control here and need to get back on track. We have improved it a bit, as Tozier points out, but the changes seem to me to be superficial and not of much value to the customer, whoever she is. So, things to do:

  • Button placement
  • Max velocity
  • Turn rate
  • Fire missiles
  • Collisions
  • Bigger better ships
  • and plenty more …

Of these, max velocity and turn rate seem easy enough and they’ll help with our manual testing. Bigger ships will include some learning about drawing. We have about an hour left, and we’ll try to do the rate things and the bigger better ships.

For the rate things, we adjusted the ship constants by eye, and added the MaxSpeed ship property:

function Ship:init(shipNumber)
    self.shipNumber = shipNumber
    self.ThrustAmount = vec2(0, 0.02)
    self.TurnAmount = 1
    self.MaxSpeed = 3
    self.pos = self:initialPosition()
    self.heading = self:initialHeading()
    self.vel = vec2(0,0)
    self.controls = { 
        left   = self:controlButton(HEIGHT/5),
        right  = self:controlButton(2*HEIGHT/5),
        thrust = self:controlButton(3*HEIGHT/5),
        fire   = self:controlButton(4*HEIGHT/5)
    }
    addDrawnAtTop(self)
end

function Ship:move()
    local turn
    local thrust
    
    if     self.controls.left.pressed  and not self.controls.right.pressed then turn =  self.TurnAmount
    elseif self.controls.right.pressed and not self.controls.left.pressed  then turn = -self.TurnAmount
    else turn = 0 end
    self.heading = self.heading + turn
    
    if self.controls.thrust.pressed then thrust = self.ThrustAmount else thrust = vec2(0,0) end
    self.vel = self:adjustedVelocity(self.vel, self.heading, thrust)
    self.pos = clip_to_screen(self.pos + self.vel)
end

function Ship:adjustedVelocity(vel, heading, accel)
    local proposed = vel + accel:rotate(math.rad(heading))
    local speed = proposed:len()
    if speed > self.MaxSpeed then
        proposed = proposed*self.MaxSpeed/speed
    end
    return proposed
end

We’ve tuned enough for now. Let’s draw the ships. I have in mind that they are shaped like triangles. The original Spacewar had a triangular ship and a round one but I think we’ll make ours the same shape and perhaps different colors. With a little sketching and hooraw, we arrive at this:

function Ship:draw()
    if self.thrustButton then self.thrustButton:draw() end
    pushStyle()
    strokeWidth(2)
    pushMatrix()
    translate(self.pos:unpack())
    rotate(self.heading)
    line(0, 15, 5, -15)
    line(5, -15, -5, -15)
    line(-5, -15, 0, 15)
    popMatrix()
    popStyle()
end

The picture at idle looks like this:

We’ll fly both ships now and show you a little video:

VIDEO HERE (ship-triangles-video)

See all the button flashing? That’s because of our button bug with the buttons not remembering pressed when another touch is processed. We also note that we can’t tell our ships apart, and that we can’t tell when they are accelerating and when not. The original game had a little flickering line coming out the aft end of the ship when accelerating. Sometimes the ships had different shapes. In our case, I’m still thinking different colors.

A good day’s work, much like what we imagined might happen. We’ll look at the bug, then stop. No commitment to fixing it.

Yes, well. We started by looking at the Button code, which looked like this:

function Button:touched(touch) 
    self.capturedID = self:getCapture(touch)
    self.pressed = self:getPressed(touch)
end

function Button:getCapture(touch) 
    if touch.state == BEGAN and self:insideCircle(touch.x, touch.y) and not self.capturedID then
        return touch.id
    elseif touch.state == ENDED and self.capturedID == touch.id then
        return nil
    else
        return self.capturedID
    end
end

function Button:getPressed(touch)
    if not self.capturedID then
        return false
    else 
        return self.capturedID == touch.id and self:insideCircle(touch.x,touch.y) 
    end
end

The getPressed returns false whenever it sees any touch that is not its captured one, whenever it has a captured one. But the Buttons see all touches and the way they’re implemented now, they always recompute whether they are pressed. This was obviously wrong.

So we wrote a new test:

        _:test("Two Fingers", function()
            button:touched(insideBeganTouch)
            _:expect(button.capturedID).is(3)
            _:expect(button.pressed).is(true)
            button:touched(outsideBeganTouch)
            _:expect(button.capturedID).is(3)
            _:expect(button.pressed).is(true)
        end)

The test failed, as planned. We reimplemented the getPressed to:

function Button:getPressed(touch)
    if not self.capturedID then
        return false
    elseif self.capturedID ~= touch.id then
        return self.pressed
    else 
        return self:insideCircle(touch)
    end
end

And the test passed. We then noticed this test, which we already had:

        _:test("Initial Button Capture", function()
            button:touched(insideBeganTouch)
            _:expect(button.capturedID).is(3)
            button:touched(outsideBeganTouch)
            _:expect(button.capturedID).is(3)
            _:expect(button.pressed).is(false)
        end)

This test is wrong! The final expect should be asking for true: the pressed property should not change when a different touch is processed: that is exactly the bug we saw on the screen. We don’t remember when that check got put in, but it was put in wrong. We fixed that as well. Now the buttons are better tested and work as we intend.

Are there some lessons to be had here? Likely so.

I think Michael Bolton (the tester, not the “singer”) would point out that automated checks cannot substitute for human testing, or what he likes to call “sapient” testing. We would make no claim to sapience, but it certainly is the case that it was our human testing that found this defect. Score one for humans.

We’ve already noted that the touch logic is tricky, and we’ve had trouble with it more than once. This leads to advice like “be more careful”, which is nearly as useful as Chet’s example of useless advice: “be taller”. I think we’re always as careful as we think we need to be, and sometimes the universe informs us that we weren’t careful enough to avoid some difficulty or other. Should we feel badly about this? Well, that’s up to you. I just chuckle ruefully and do my best to shore up weak areas.

It’s possible that our understanding of touch and its use in buttons is incomplete or that our model is too complex, not because of some inherent thing with Codea Lua, but because we haven’t yet seen a simple approach. Well, we haven’t. So we’ll see if we can get a better idea. I’m not inclined to wait for that to happen, so we’ll press on.

That said, it does seem that there is some simple rule for buttons, like “if I have something captured, totally ignore anything that isn’t the one I have captured”. That would be helpful. Maybe the current structure is there because of leftovers from when we wanted the button to send us messages. We’ll put that idea on our list. Which brings us to the next chapter …