I’ve decided how buttons should work. It only remains to make it so.

I’ve managed to get buttons displayed on the screen, where I actually intend them to be. I spoke last time about various options, and about my feeling that I’d have moved to objects by now if not sooner. But maybe there’s no reason to do it yet, and I’ve come up with an idea for how I’d like them to work, and an idea for how to do that.

It seems to me that one way or another, we want to know things like *if the ‘left’ button is pressed”. Just what we’ll do isn’t entirely clear, but it seems like if the ‘left’ button is pressed, we’ll add a bit to the Ship’s current angle. And so on.

So I want to be able to code roughly like this:

  if Button.left then
    Ship.angle = Ship.angle + angleIncrement
  end

So the job of our button code will be to maintain an object called Button, with fields like fire and left, setting those fields to true if the button is pressed. We already have a table called Buttons that knows the button locations. We’ll extend that to be a table that knows the button’s name as well, and use that name to set the fields of the Button object.

Here’s the button setup now:

function createButtons()
    local dx=50
    local dy=200
    table.insert(Buttons, vec2(dx,dy))
    table.insert(Buttons, vec2(dy,dx))
    table.insert(Buttons, vec2(WIDTH-dx,dy))
    table.insert(Buttons, vec2(WIDTH-dy,dx))
end

We ask each button for its x and y when we draw it:

function drawButtons()
    pushStyle()
    ellipseMode(RADIUS)
    stroke(255)
    strokeWidth(1)
    for i,b in ipairs(Buttons) do
        pushMatrix()
        translate(b.x,b.y)
        ellipse(0,0, 50)
        popMatrix()
    end
    popStyle()
end

So let’s preserve that and make each of the buttons a table with x, y, and name. (It’s worth noting that vec2 is just a name for a kind of table that yas x and y, no more than that. Well, a bit more, because the secret tables for vec2 include pointers to handy vector functions. We’ll ignore that fact for now, because we’re not using anything really vectorish. Here’s the new create:

function createButtons()
    local dx=50
    local dy=200
    table.insert(Buttons, {x=dx, y=dy, name="left"})
    table.insert(Buttons, {x=dy, y=dx, name="right"})
    table.insert(Buttons, {x=WIDTH-dx, y=dy, name="fire"})
    table.insert(Buttons, {x=WIDTH-dy, y=dx, name = "go"})
end

And I improved the draw a bit:

function drawButtons()
    pushStyle()
    ellipseMode(RADIUS)
    textMode(CENTER)
    stroke(255)
    strokeWidth(1)
    for i,b in ipairs(Buttons) do
        pushMatrix()
        translate(b.x,b.y)
        ellipse(0,0, 50)
        pushStyle()
        fill(255)
        fontSize(30)
        text(b.name,0,0)
        popStyle()
        popMatrix()
    end
    popStyle()
end

That’s getting pretty long, by the way. We should extract its insides. And I am getting the feeling that the styles are out of control, but as long as we push and pop at the right times, we’ll be OK. The effect is pretty good:

labeled buttons

Commit: “labeled buttons”.

Let’s make the buttons work

Recall what we have for touches:

function touched(touch)
    if touch.state == ENDED or touch.state == CANCELLED then
        Touches[touch.id] = nil
    else
        Touches[touch.id] = touch
    end
end

This maintains a table of as many touches as are present, indexed by their id, which Codea maintains for continuity. (I imagine that in fact it’s iPadOS that takes care of that.) Each touch knows many things, the most important to us being the field pos, its position.

Our buttons are conveniently round, with a radius of 50. So if a touch’s position is within 50 of a button’s position, you’re pressing the button. I think the best thing will be to sort out the buttons at the beginning of each draw cycle. We’ll just loop over touches and buttons and see what’s what. Let me try to write that:

That went easily:

function draw()
    checkButtons()
    pushStyle()
    background(40, 40, 50)
    drawButtons()
    drawShip()
    moveShip()
    drawAsteroids()
    popStyle()
end

function checkButtons()
    Button.left = false
    Button.right = false
    Button.go = false
    Button.fire = false
    for id,touch in pairs(Touches) do
        for i,button in ipairs(Buttons) do
            if touch.pos:dist(vec2(button.x,button.y)) < 50 then
                Button[button.name]=true
            end
        end
    end
end

function drawButtons()
    pushStyle()
    ellipseMode(RADIUS)
    textMode(CENTER)
    stroke(255)
    strokeWidth(1)
    for i,b in ipairs(Buttons) do
        pushMatrix()
        pushStyle()
        translate(b.x,b.y)
        if Button[b.name] then
            fill(128,0,0)
        else
            fill(128,128,128,128)
        end
        ellipse(0,0, 50)
        fill(255)
        fontSize(30)
        text(b.name,0,0)
        popStyle()
        popMatrix()
    end
    popStyle()
end

We call checkButtons at the beginning of draw. In checkButtons we clear all the button flags, then we check each touch against each button, and if the touch is in range, we set that button flag to true.

I tweaked the drawButton to color the button red when it’s pressed, by way of a test. And it goes like this:

red buttons

Commit: “colored active buttons”.

Rotation

Just to complete the morning, let’s see if we can make the left and right buttons make the ship rotate. That’s about the right size for the time I have left.

Turning logic goes in moveShip, which is presently empty. I’ll try just adjusting the angle by 1 degree when the button is down:

function moveShip()
    if Button.left then Ship.ang = Ship.ang + 1 end
    if Button.right then Ship.ang = Ship.ang - 1 end
end

And it works just about as one might like:

ship turns

Commit: “ship turns”.

Summing Up

That’ll do for today. We have buttons implemented fairly cleanly, and we’ve pretty seriously munged up their drawing. So that could use a bit of cleaning.

Other than that, which is localized in any case, we might want to rename one or both of Button and Buttons, lest there be confusion.

I think we’re almost ready to fly the ship and then to shoot at the asteroids.

By the way …

I’ve found some nice information about the old version, including the original 6502 source code on computerarcheology.com. I’d not discovered that site before. It’s really quite interesting. Among the things that will be useful will be that it includes the original patterns for the shapes of the asteroids, plus the graphics chip’s code for them, for example:

; Rock Pattern 1
09E6: 08 F9          SVEC scale=03(/64)  bri=0     x=0       y=1       (0.0000, 0.0156)
09E8: 79 F9          SVEC scale=03(/64)  bri=7     x=1       y=1       (0.0156, 0.0156)
09EA: 79 FD          SVEC scale=03(/64)  bri=7     x=1       y=-1      (0.0156, -0.0469)
09EC: 7D F6          SVEC scale=02(/128) bri=7     x=-1      y=-2      (-0.0078, -0.0234)
09EE: 79 F6          SVEC scale=02(/128) bri=7     x=1       y=-2      (0.0078, -0.0234)
09F0: 8F F6          SVEC scale=02(/128) bri=8     x=-3      y=-2      (-0.0234, -0.0234)
09F2: 8F F0          SVEC scale=02(/128) bri=8     x=-3      y=0       (-0.0234, 0.0000)
09F4: 7D F9          SVEC scale=03(/64)  bri=7     x=-1      y=1       (-0.0156, 0.0156)
09F6: 78 FA          SVEC scale=03(/64)  bri=7     x=0       y=2       (0.0000, 0.0313)
09F8: 79 F9          SVEC scale=03(/64)  bri=7     x=1       y=1       (0.0156, 0.0156)
09FA: 79 FD          SVEC scale=03(/64)  bri=7     x=1       y=-1      (0.0156, -0.0469)
09FC: 00 D0          RTS
;

So that’s nice, isn’t it? We’ll just code that right up.

But that’s for another day. For today, we’re good to go. See you next time, I hope!


-- Asteroids
-- RJ 20200511

local Asteroids = {}
local Vel = 1.5
local Ship = {}
local Touches = {}
local Buttons = {}
local Button = {}

function setup()
    print("Hello Asteroids!")
    displayMode(FULLSCREEN_NO_BUTTONS)
    createButtons()
    createAsteroids()
    createShip()
end

function createAsteroids()
    for i = 1,10 do
        table.insert(Asteroids, createAsteroid())
    end
end

function createAsteroid()
    local a = {}
    a.pos = vec2(math.random(WIDTH), math.random(HEIGHT))
    a.angle = math.random()*2*math.pi
    return a
end

function createButtons()
    local dx=50
    local dy=200
    table.insert(Buttons, {x=dx, y=dy, name="left"})
    table.insert(Buttons, {x=dy, y=dx, name="right"})
    table.insert(Buttons, {x=WIDTH-dx, y=dy, name="fire"})
    table.insert(Buttons, {x=WIDTH-dy, y=dx, name = "go"})
end

function createShip()
    Ship.pos = vec2(WIDTH, HEIGHT)/2
    Ship.ang = 0
end

function draw()
    checkButtons()
    pushStyle()
    background(40, 40, 50)
    drawButtons()
    drawShip()
    moveShip()
    drawAsteroids()
    popStyle()
end

function checkButtons()
    Button.left = false
    Button.right = false
    Button.go = false
    Button.fire = false
    for id,touch in pairs(Touches) do
        for i,button in ipairs(Buttons) do
            if touch.pos:dist(vec2(button.x,button.y)) < 50 then
                Button[button.name]=true
            end
        end
    end
end

function drawButtons()
    pushStyle()
    ellipseMode(RADIUS)
    textMode(CENTER)
    stroke(255)
    strokeWidth(1)
    for i,b in ipairs(Buttons) do
        pushMatrix()
        pushStyle()
        translate(b.x,b.y)
        if Button[b.name] then
            fill(128,0,0)
        else
            fill(128,128,128,128)
        end
        ellipse(0,0, 50)
        fill(255)
        fontSize(30)
        text(b.name,0,0)
        popStyle()
        popMatrix()
    end
    popStyle()
end

function drawShip()
    local sx = 10
    local sy = 6
    pushStyle()
    pushMatrix()
    translate(Ship.pos.x, Ship.pos.y)
    rotate(Ship.ang)
    strokeWidth(2)
    stroke(255)
    line(sx,0, -sx,sy)
    line(-sx,sy, -sx,-sy)
    line(-sx,-sy, sx,0)
    popMatrix()
    popStyle()
end

function moveShip()
    if Button.left then Ship.ang = Ship.ang + 1 end
    if Button.right then Ship.ang = Ship.ang - 1 end
end

function drawAsteroids()
    pushStyle()
    stroke(255)
    fill(0,0,0, 0)
    strokeWidth(2)
    rectMode(CENTER)
    for i,asteroid in ipairs(Asteroids) do
        drawAsteroid(asteroid)
        moveAsteroid(asteroid)
    end
    popStyle()
end

function drawAsteroid(asteroid)
    rect(asteroid.pos.x, asteroid.pos.y, 120)
end

function moveAsteroid(asteroid)
    local step = vec2(Vel,0):rotate(asteroid.angle)
    local pos = asteroid.pos + step
    asteroid.pos = vec2(keepInBounds(pos.x, WIDTH), keepInBounds(pos.y, HEIGHT))
end

function keepInBounds(value, bound)
    return (value+bound)%bound
end

function touched(touch)
    if touch.state == ENDED or touch.state == CANCELLED then
        Touches[touch.id] = nil
    else
        Touches[touch.id] = touch
    end
end