Space Invaders 13 - Do you feel lucky?
Let’s see about extending our square invaders game-like thing to be a bit more like a game.
OK, that’s our plan. Maybe we need a slightly better plan. How about …
- First, make the gunner square move side to side, so he can fire at different invaders.
- Second, make the invaders move side to side and maybe even downward, in that square menacing fashion that they have.
That should be more than enough for the morning, as I’m feeling a bit punk for some reason. Unlucky, I guess.
Moving the Gun
We fire the gun by tapping on the right of the screen. The definition of “right” is pretty random at present.
function touched(touch)
if touch.state == ENDED then
if touch.pos.x > 1000 then
fireMissile()
end
end
end
My iPad screen is 1366 across by actual test:
_:test("Screen is 1366 wide", function()
_:expect(WIDTH).is(1366)
end)
(That’s our only test so far. I saved it because I wanted at least one.)
Let’s do some arithmetic. The original screen was 224 by 256 pixels. We’re emulating that by drawing at scale 4. If we center the screen on the iPad (landscape mode), we’ll consume 244*4 or 976 pixels, or 488 on each side of center. Since there are 1366 pixels across, there are 683 on each side. … Oh heck, this is too hard, and I can’t make Paper™ work for some reason.
_:test("Screen facts", function()
local gameConsumes = 244*4
_:expect(gameConsumes).is(976)
local leftMargin = 1366/2 - 244*2
_:expect(leftMargin).is(195)
local fireTouch = 1366-195
_:expect(fireTouch).is(1171)
local moveLeft = 195//2
_:expect(moveLeft).is(97)
end)
That may be the first test I’ve ever written out of sheer laziness. Anyway, those are some useful numbers that we have need of.
Where was I? Oh, yes, move the gun back and forth: Here, by intention, is what I’d like to do:
function touched(touch)
local fireTouch = 1171
local moveLeft = 97
local moveRight = 195
if touch.state == ENDED then
local x = touch.pos.x
if x > fireTouch then
fireMissile()
elseif x < moveLeft then
moveLeft()
elseif x > moveLeft and x < moveRight then
moveRight()
end
end
end
Digression: Programming by Intention
I don’t think I’ve described “programming by intention” lately. I learned this idea, and the phrase, from Kent Beck, a couple of decades ago. Mistakes in the following are mine. It goes like this:
First, think what you want to do. In the present case, I was thinking something like:
- If the touch is to the right of the fireTouch position, fire a missile;
- If it’s to the left of the moveLeft position, move left;
- If it’s to the right of the moveLeft position, and to the right of the moveRight position, moveRight.
Second, write that down in code, almost as if you were writing what we used to call “pseudocode”. So I got what’s above, namely this bit:
local x = touch.pos.x
if x > fireTouch then
fireMissile()
elseif x < moveLeft then
moveLeft()
elseif x > moveLeft and x < moveRight then
moveRight()
end
Now that we’ve said exactly what the code has to do, we just fill in the blanks, by implementing moveLeft
and moveRight
. If that’s straightforward, just do it. If it’s not, program that smaller bit by intention also.
End Digression
I think we’ll need to create a little table for the Gunner, who is presently represented by a fixed square. Then we’ll have to move him. It goes like this:
function setup()
runTests()
setupInvaders()
setupGunner() -- <-- by intention
end
Then …
function setupGunner()
Gunner = {pos=vec2(112,10)} -- <-- copied from old draw code
end
And then this:
function drawGunner()
pushMatrix()
pushStyle()
stroke(255)
fill(255)
rectMode(CENTER)
rect(Gunner.pos.x, Gunner.pos.y, 8,8)
popStyle()
popMatrix()
end
At this point, we should draw the Gunner but he still can’t move (and the program will crash if we touch on the move side of the screen).
That seems to work.
I am experiencing power failures here. The generator should take over. It’s election day. My wife is working the election and I know she’s not going to like it if the computers all go to battery.
While checking the generator I realized that while firing on ENDED is good, requiring a separate tap per missile, for moving we want the touch state to be STARTED or CHANGED … or perhaps we want the move to continue until ENDED. I think the latter. Let’s do that before we do the moving. I think for now I’ll just create another global GunnerMove.
I wind up with this, which seems nearly good:
function setupGunner()
Gunner = {pos=vec2(112,10)}
GunMove = vec2(0,0) -- <---
end
function drawGunner()
pushMatrix()
pushStyle()
stroke(255)
fill(255)
rectMode(CENTER)
rect(Gunner.pos.x, Gunner.pos.y, 8,8) -- <---
popStyle()
popMatrix()
Gunner.pos = Gunner.pos + GunMove -- <---
end
function touched(touch)
local fireTouch = 1171
local moveLeft = 97
local moveRight = 195
local x = touch.pos.x
if touch.state == ENDED then
GunMove = vec2(0,0)
if x > fireTouch then
fireMissile()
end
end
if touch.state == BEGAN or touch.state == CHANGED then
if x < moveLeft then
GunMove = vec2(-1,0)
elseif x > moveLeft and x < moveRight then
GunMove = vec2(1,0)
end
end
end
That touch code is particularly messy but the program is doing as I intended, except that it’s moving the gunner very rapidly. I also note that the available screen space is pretty narrow. Recall the picture from the other spike:
But that’s not our problem for today. The real fix for the speed issue is to clock the change to occur every x seconds. I think we have some code to do that in the spike. We’ll look and scavenge, or do it over, in due time. For now, I’ll just adjust those vectors to be smaller:
function touched(touch)
local fireTouch = 1171
local moveLeft = 97
local moveRight = 195
local moveStep = 0.25 -- <---
local x = touch.pos.x
if touch.state == ENDED then
GunMove = vec2(0,0)
if x > fireTouch then
fireMissile()
end
end
if touch.state == BEGAN or touch.state == CHANGED then
if x < moveLeft then
GunMove = vec2(-moveStep,0) -- <---
elseif x > moveLeft and x < moveRight then
GunMove = vec2(moveStep,0) -- <---
end
end
end
That’s a bit on the slow side, but it’ll do for now. It should be possible for me to shoot any desired invader now.
That turns out not to be the case, because this:
function fireMissile()
Missile.pos = vec2(112,15)
Missile.v = 1
end
That causes all missiles to start in the center. Not so good.
function fireMissile()
Missile.pos = Gunner.pos + vec2(0,5)
Missile.v = 1
end
Now we can slide sideways and shoot at any column we want to. We can also slew completely off the page if we choose to, but I’m going to commit this as “gunner can move”.
Summing Up
Summing up already? I thought we were going to move the invaders.
Well, yes. That was before the power went out and the cat barfed and a few other interruptions, plus this bit took a bit longer than I had expected.
This is why selecting what to do next is a really good practice, and guessing how long things will take that you’ve not done is a not so very good practice. You never know when the power in the house, or in your head, may go out.
We’ve got a sort of p-baked game here, for p around 0.25 I’d say. We can move and shoot at stationary aliens. Motion is a bit slow, just because of values we’ve set, and there’s no scoring, and the invaders don’t screech or anything when you knock them out. But there’s a rudimentary fraction of a game here and we’ve only taken another hour or so to make it work a bit better, plus to write this article.
I may return to this today, tomorrow, or in the future. I will definitely not return to it in the past.
See you next time!