Space Invaders 12
Twelve articles and no product. What’s up with that?
Constant readers know that a central tenet of my advice about software development is:
Always have a running shippable version of the product.
I believe that if we have a product version always ready to go, it change the conversation with our colleagues (read: masters) on the business side of the organization, from “when are you #$%$%@! going to be done” to “you know, if we just added this one thing …”. That change in the conversation makes all the difference.
Furthermore, in the first article in this series, I linked to Mary Rose Cook’s marvelous “Space Invaders in 30 Minutes” video.
I had intended to copy Mary Rose Cook’s style, and build a simple version of the game and then enhance it. Then, immediately, I went down the rabbit hole of working the “interesting” part of the problem, figuring out how to make a Codea program that looked like a program running on a 1978 bit-mapped display.
That’s not a product and if someone were paying me for a playable game, I’d be doing some fast talking and a bit of dancing right now to keep them happy.
Don’t even think about the delay between this article and the previous one. That would tick off whoever was paying me as well, but in fact I’ve been doing other useful and non-useful things. So don’t even think about the dozen days since we last checked in.
Dammit, stop thinking about that.
It’s time to reassess and decide how to move forward, and let me tell you, Buster, it had better start looking like a product Real Soon Now.
Damn, that Jeffries is a slave driver
OK, let’s calmly look at what we have and what we don’t have. We have all the bitmaps imported from the old game, and we know how to display them with the sprite
operation.
We know how to make the aliens move and animate like the ones in the original game.
We have a general facility with the tools we’ll need to do the game and some decent ideas on how to do things.
We even have, in our hip pocket, a neat demo from Dave1707 of the Codea forum, showing one way to slowly blow holes in a shield, which is part of what the game does. We’ll write about that soon, but not today, because the boss is all over us about game play and having something that looks like the product.
I can see two ways to proceed from here. I will try to think of a third, because someone said that you need three.
First, we could start with a clean sheet and do a simple game along the lines of Mary Rose Cook’s example, some simple rectangles marching along and firing. She solved some interesting problems in that simple game, so we can be pretty sure we’d encounter some learning as well.
Second, we could start with our existing bitmaps and do the same thing. That could amount to popping the player gun thing down at the bottom of the screen and teaching it, and the invaders, to fire simple missiles.
Third … honestly, I don’t have a third decent idea. If you were here, you’d have one but I’m on my own. The cat is out on the deck, so I can’t even ask her. I know what she’d say though: “Now”.
So I choose door number one.
Clean Sheet
Clean sheet of paper, start fresh. Why? Because the things we’ve done so far have been experiments, “spikes” as we like to call them, and as such, they are messy. With the clean sheet, we’ll surely encounter problems, but because we’re in a relatively clean environment, we can, with luck (and incredible skill) come up with some clean solutions.
So I’ll start a new project, and this time I’ll call it “Invaders”, with intention to evolve forward from here. If that doesn’t work out, well, Codea allows us to rename projects.
I’ll start with a project that includes CodeaUnit, in the probably vain hope that I’ll think of some tests to write.
And some good news: Working Copy has been updated to do Codea sync correctly, so this new app is under version control. Life is food. Er, good. Life is good.
First a tiny refactoring to improve the look of things. And remind me that I’d like to do this to the CodeaUnit base as well.
-- Invaders
-- RJ 20200803
function setup()
runTests()
end
function draw()
background(40, 40, 50)
showTests()
end
function runTests()
local det = CodeaUnit.detailed
CodeaUnit.detailed = false
Console = _.execute()
CodeaUnit.detailed = det
end
function showTests()
pushMatrix()
pushStyle()
fontSize(50)
textAlign(CENTER)
text(Console, WIDTH/2, HEIGHT-200)
popStyle()
popMatrix()
end
I pulled out the showTests
function, which was formerly inlined in draw
. I think this is better.
OK, I’ll just program a bit “by intention” here. I’ll do a set up invaders thing in setup
and a play invaders in draw:
Oops, commit the refactoring first. “Break out showTests”.
Arrgh
Don’t you just hate it when you’re just starting to roll and something goes wrong? Somehow Working Copy didn’t see my showTests change, and I thought it wasn’t working. Some exiting and rebooting later, it seems that Codea didn’t have the change either. Maybe I control z’d it or something? No idea. Anyway after a short panic, let’s go again.
By intention:
function setup()
runTests()
setupInvaders()
end
function draw()
background(40, 40, 50)
showTests()
drawInvaders()
end
Now we just have to draw the rest of the #$%#$@! owl.
What’s the simplest thing I could do here that would still make sense. I know my invaders are 24 by 16, and that scale 4 is what I’ll need to get the screens lined up. There are five rows of eleven invaders, I think. Let’s just draw some rectangles on … um … 24 pixel centers, and see what happens:
function setupInvaders()
end
function drawInvaders()
pushMatrix()
pushStyle()
popStyle()
popMatrix()
end
This will draw … nothing, but it sets the framework in place. Commit.
function drawInvaders()
pushMatrix()
pushStyle()
for row = 1,5 do
for col = 1,11 do
drawInvader(row,col)
end
end
popStyle()
popMatrix()
end
Again, programming by intention. For 5 rows and 11 columns, draw an invader in that row and column. (It’s irritating that row, column seems natural to me, and x,y, seems natural to me, and row is y and column is x. That will surely confuse me soon. I’m already ticked off about it.)
Anyway …
function drawInvader(row,col)
pushMatrix()
pushStyle()
rectangle(col*24, row*24, 16,16)
popStyle()
popMatrix()
end
This should do something. Mostly explodes, because the command is rect, not rectangle. Try again, fail better:
function drawInvader(row,col)
pushMatrix()
pushStyle()
rect(col*24, row*24, 16,16)
popStyle()
popMatrix()
end
This gives me a bitty little array of squares in the lower left corner, as one might expect:
Let’s move them toward the middle and make them larger. We’ll do this with a transform at the group level, but we’ll change their Y coordinates to count down instead of up in the detailed drawing.
Doing that tells me that my squares are way too big, so that makes me go back and check my assumptions. The invaders aren’t 24 by 16, they are 16 by 8. That leads me to this:
function drawInvaders()
pushMatrix()
pushStyle()
rectMode(CENTER)
stroke(255)
fill(255)
scale(4) -- makes the screen 1366/4 x 1024/4
translate((1366-1024)/8,0)
for row = 1,5 do
for col = 1,11 do
drawInvader(row,col)
end
end
popStyle()
popMatrix()
end
function drawInvader(row,col)
pushMatrix()
pushStyle()
rect(col*16, 256 - row*16, 8,8)
popStyle()
popMatrix()
end
This draws my invader squares at the left of the playable area of the screen:
We could make those march, but honestly I want to get a gunner down there and start shooting at them. First, we’ll commit: invaders to scale.
Gunner
OK, the gunner will be a square as well, down near the bottom of the screen. I’ll start him in the middle, just because it’s a place.
I needed to move my scaling and translation up to make this work:
function draw()
pushMatrix()
pushStyle()
background(40, 40, 50)
showTests()
stroke(255)
fill(255)
scale(4) -- makes the screen 1366/4 x 1024/4
translate((1366-1024)/8,0)
drawInvaders()
drawGunner()
popStyle()
popMatrix()
end
function setupInvaders()
end
function drawInvaders()
pushMatrix()
pushStyle()
rectMode(CENTER)
for row = 1,5 do
for col = 1,11 do
drawInvader(row,col)
end
end
popStyle()
popMatrix()
end
But now I have a snapshot that looks a bit like the game:
Now next I want to fire a missile. But how? We really don’t have decent keyboard access in Codea yet, so it’ll have to be a screen tap. Let’s say that a tap in the right hand area of the screen fires a missile. For now, I”ll just have one missile and we’ll deal with the weirdness if the player taps too often.
function setupInvaders()
Missile = {v=0, p=vec2(0,0)}
end
I had to put it somewhere, so I put it here. Missile is a global, and it has a velocity, starting at 0, and a position, 0,0. The convention that I just made up is that if v is zero we don’t draw the missile.
Now the tap and the drawing.
function touched(touch)
if touch.ENDED then
if touch.pos.x > 1000 then
fireMissile()
end
end
end
Just a wild guess that x > 1000 will work. And I’ll fire the missile when you lift your finger, which will ensure you can’t fire repeatedly.
function fireMissile()
Missile.pos = vec2(112,20)
Missile.v = 1
end
That position is just above the (fixed) gunner position.
Now to move the missile:
function drawMissile()
if Missile.v == 0 then return end
rect(Missile.pos.x, Missile.pos.y, 2,4)
Missile.pos = Missile.pos + vec2(0,1)
end
This nearly works, although not quite as I expected. The missile is pretty small, and doesn’t emit from the center of the rectangle. Maybe my rectMode
isn’t set at a high enough level. But it does fly, and it looks like this in a snapshot:
Time to commit: missile flies.
I hope you’ve noticed that this is getting messy, and that’s because we’re succumbing to the pressure of time to get a demo done that satisfied our nasty Product Owner, Jeffries, that we’ll have a playable game. We’ll talk about that in the summing up.
Let’s see what to do about detecting a collision. We can’t really kill any of these invaders, because they’re just drawn explicitly. We could put them in a table at this point, and maybe we should. Yes, I think that may be best.
We’ll make an invader a little table with a position and an alive flag:
function setupInvaders()
Invaders = {}
for row = 1,5 do
for col = 1,11 do
local p = vec2(col*16, 256-row*16)
table.insert(Invaders, {pos=p, alive=true})
end
end
Missile = {v=0, p=vec2(0,0)}
end
Now we’ll draw the Invader table:
function drawInvaders()
pushMatrix()
pushStyle()
rectMode(CENTER)
for i,invader in ipairs(Invaders) do
if invader.alive then rect(invader.pos.x, invader.pos.y, 8,8) end
end
popStyle()
popMatrix()
end
That gives us our original picture back. Now on each draw cycle, let’s see if we have hit an invader:
function drawMissile()
if Missile.v == 0 then return end
rect(Missile.pos.x, Missile.pos.y, 2,4)
Missile.pos = Missile.pos + vec2(0,0.5)
checkForKill(Missile)
end
Then, by intention …
function checkForKill(missile)
for i, invader in ipairs(Invaders) do
if isHit(missile,invader) then
invader.alive = false
missile.v = 0
end
end
end
Now just the tiny detail of deciding if the missile has hit the invader. This will be the case if the missile’s top is greater than the invaders bottom. There’s surely a better check but this should do the job for right now.
function isHit(missile, invader)
local missileTop = missile.pos.y + 4
local invaderBottom = invader.pos.y - 4
return missileTop > invaderBottom
end
Programmer laughs. You gotta see this:
The first missile kills the entire row. Why? Because it is above the bottom of every invader in that row. The second missile kills nothing. Why? Because we’re checking against all invaders, so it rekills the bottom row to no visible effect. We’d better beef up the isHit
a bit.
function isHit(missile, invader)
if not invader.alive then return false end
local missileTop = missile.pos.y + 4
local invaderBottom = invader.pos.y - 4
if missileTop < invaderBottom then return false end
local missileLeft = missile.pos.x-1
local invaderRight = invader.pos.x + 4
if missileLeft > invaderRight then return false end
local missileRight = missile.pos.x + 1
local invaderLeft = invader.pos.x - 4
return missileRight > invaderLeft
end
OK, well that’s messy but it does work:
Commit: Missile kills invaders one by one.
I’m calling that done for the morning, time to go pick up a scrip and something to eat.
Summing Up
OK, what have we here? We have an exceedingly ad-hoc not very entertaining game, that includes individually-positioned invaders, and a very simple and quite possibly not very accurate collision algorithm.
On the other hand, the structure isn’t terrible if you fuzz your eyes a bit. We have init broken out into a separate function, we have drawing broken out into invaders, gunner and a single missile.
We even have the individual invaders knowing whether they are alive or dead, although I suspect that in our real version we’ll be removing them from the collection entirely. We’ll find out.
Overall, it’s not bad, and I’m rather comfortable that we can fold our spike learnings and our bitmaps in with little difficulty.
We’ll see. Anyway, a couple of hours and we have something that looks like a game. We’ll evolve from here.
See you next time!