-- title: Space Invaders
-- author: Bob Grant
-- desc: Lesson 25
-- script: lua
playerShip = {
position = {
x = 120,
y = 128
},
spriteNum = 0,
minX = 0,
maxX = 232,
speed = 1,
width = 8,
height = 8,
bulletOffset = {
x = 4,
y = 4
}
}
playerBullets = {}
maxPlayerBullets = 2
aliens = {}
alienRows = 6
alienColumns = 8
alienHorzSpacing = 12
alienVertSpacing = 12
alienTopOffset = 8
alienDirection = 1
alienSpeed = 4
alienVertSpeed = 4
alienMaxX = 232
alienMinX = 0
alienMaxY = 120
alienMoveDelay = 30
alienMoveCounter = alienMoveDelay
alienMoveNumFrames = 2
alienMoveFrameCounter = 0
alienStepSoundNotes = 4
alienStepSoundCounter = 0
alienStepSoundBaseNote = 8
alienRowTypes = {}
alienRowTypes[1] = {baseSprite = 16, score=50}
alienRowTypes[2] = {baseSprite = 16, score=50}
alienRowTypes[3] = {baseSprite = 32, score=25}
alienRowTypes[4] = {baseSprite = 32, score=25}
alienRowTypes[5] = {baseSprite = 48, score=10}
alienRowTypes[6] = {baseSprite = 48, score=10}
alienMissiles = {}
maxAlienMissiles = 20
fireProbability = 1
explosions = {}
playerScore = 0
playerLives = 3
gameNeedsToBeInitialised = true
-- game state values
stateStartGame = 0
statePlayGame = 1
stateNewLife = 2
stateGameOver = 3
stateAllAliensDead = 4
gameState = stateStartGame
timer = 0
function TIC()
if (gameState == stateStartGame) then
startGameTIC()
elseif (gameState == statePlayGame) then
playGameTIC()
elseif (gameState == stateNewLife) then
newLifeTIC()
elseif (gameState == stateGameOver) then
gameOverTIC()
elseif (gameState == stateAllAliensDead) then
allAliensDeadTIC()
end
end -- TIC
function startGameTIC()
cls()
print("Press Z to start game", 60, 60)
if (btnp(4)) then
gameState = statePlayGame
end -- if
end -- startGameTIC()
function newLifeTIC()
timer = timer + 1
if (timer < 180) then
-- wait for explosion to finish
else
-- what to do next
if (playerLives == 0) then
-- game over
gameState = stateGameOver
else
-- continue game
playerShip.position.x = 120
playerShip.position.y = 128
for bullet = 1, maxPlayerBullets do
playerBullets[bullet].active = false
end -- for
for index, missile in ipairs(alienMissiles) do
table.remove(alienMissiles, index)
end -- for
gameState = statePlayGame
end
end -- if
cls()
drawAliens()
drawExplosions()
drawScoreBoard()
end -- newLifeTIC()
function gameOverTIC()
cls()
drawAliens()
drawScoreBoard()
print("GAME OVER", 90, 46)
print("Press Z to start game", 60, 60)
if (btnp(4)) then
restartGame()
end -- if
end -- gameOverTIC()
function restartGame()
gameNeedsToBeInitialised = true
gameState = statePlayGame
end -- restartGame
function allAliensDeadTIC()
timer = timer + 1
if (timer < 180) then
-- wait for missiles to finish
else
-- reset alien grid
initAliens()
gameState = statePlayGame
end -- if
movePlayerShip()
movePlayerBullet()
moveAlienMissiles()
checkMissileCollisions()
-- drawing / rendering
cls()
drawPlayerBullet()
drawAlienMissiles()
drawPlayerShip()
drawExplosions()
drawScoreBoard()
end -- allAliensDeadTIC()
function playGameTIC()
-- game initialisation
-- check if game needs to be initialised
if (gameNeedsToBeInitialised) then
initialiseGame()
gameNeedsToBeInitialised = false
end
-- updating
movePlayerShip()
checkPlayerFire()
movePlayerBullet()
moveAliens()
moveAlienMissiles()
checkBulletCollisions()
checkMissileCollisions()
-- drawing / rendering
cls()
drawPlayerBullet()
drawAlienMissiles()
drawPlayerShip()
drawAliens()
drawExplosions()
drawScoreBoard()
end -- end TIC
function movePlayerShip()
-- check move right button
if(btn(2)) then
playerShip.position.x =
playerShip.position.x - playerShip.speed
end
-- check move left button
if(btn(3)) then
playerShip.position.x =
playerShip.position.x + playerShip.speed
end
playerShip.position.x = checkLimits(
playerShip.position.x,
playerShip.minX,
playerShip.maxX )
end -- movePlayerShip
function checkLimits(value, min, max)
if (value > max) then
value = max
elseif (value < min) then
value = min
else
value = value
end
return value
end -- checkLimits
function checkPlayerFire()
local bulletFired = false
local bullet = 1
-- if fire button is pressed then
if (btnp(4)) then
-- find a bullet that's ready to fire
while (bullet <= maxPlayerBullets) and (not bulletFired) do
if (not playerBullets[bullet].active) then
-- initialise bullet
playerBullets[bullet].position = {
x = playerShip.position.x + playerShip.bulletOffset.x,
y = playerShip.position.y + playerShip.bulletOffset.y
}
-- mark bullet as active
playerBullets[bullet].active = true
-- stop other bullets from firing
bulletFired = true
sfx(1, 50, 20, 1, 15)
end -- if not active
bullet = bullet + 1
end -- while
end -- if button pressed
end -- checkPlayerFire
function movePlayerBullet()
for bullet = 1, maxPlayerBullets do
if (playerBullets[bullet].active) then
-- move the bullet up the screen
playerBullets[bullet].position.y =
playerBullets[bullet].position.y - playerBullets[bullet].speed
if (playerBullets[bullet].position.y < 0) then
playerBullets[bullet].active = false
end
end
end
end -- movePlayerBullet
function drawPlayerBullet()
for bullet = 1, maxPlayerBullets do
if (playerBullets[bullet].active) then
-- draw player bullet
-- line(startX, StartY, endX, endY, colour)
line(
playerBullets[bullet].position.x,
playerBullets[bullet].position.y,
playerBullets[bullet].position.x,
playerBullets[bullet].position.y + playerBullets[bullet].length,
playerBullets[bullet].colour
)
end -- if
end -- for
end -- drawPlayerBullet
function drawPlayerShip()
spr(playerShip.spriteNum,
playerShip.position.x,
playerShip.position.y, 0)
end -- drawPlayerShip
function initialiseGame()
playerShip.position.x = 120
playerShip.position.y = 128
playerLives = 3
playerScore = 0
alienMissiles = {}
initPlayerBulletsArray()
initAliens()
end -- initialiseGame
function initPlayerBulletsArray()
for bullet = 1, maxPlayerBullets do
playerBullets[bullet] = {
position = {
x = 0,
y = 0
},
height = 5,
width = 1,
length = 5,
colour = 14,
speed = 2,
active = false
}
end -- for
end -- initPlayerBulletsArray
function initAliens()
-- create alien grid
for row = 1, alienRows do
-- create row of aliens
aliens[row] = {}
for column = 1,alienColumns do
-- create alien
aliens[row][column] = {
position = {
x = (column - 1) * alienHorzSpacing,
y = alienTopOffset + (row - 1) * alienVertSpacing
},
height = 8,
width = 8,
alive = true,
alienBaseSprite =
alienRowTypes[row].baseSprite
}
-- end row
end
-- end alien grid
end
end -- initAliens
function drawAliens()
for row = 1, alienRows do
for column = 1, alienColumns do
if (aliens[row][column].alive) then
spr(aliens[row][column].alienBaseSprite
+ alienMoveFrameCounter,
aliens[row][column].position.x,
aliens[row][column].position.y)
-- work out if alien will fire
if (math.random(50) <= fireProbability) then
spawnAlienMissile(aliens[row][column].position)
end
end -- if
end -- columns
end -- rows
end -- drawAliens
function moveAliens()
local aliensAlive = 0
alienMoveCounter = alienMoveCounter - 1
if (alienMoveCounter <= 0) then
-- do stepping sound
alienStepSoundCounter =
alienStepSoundCounter + 1
alienStepSoundCounter =
alienStepSoundCounter % alienStepSoundNotes
sfx(0, alienStepSoundBaseNote - alienStepSoundCounter, 6, 0, 8)
-- alienMoveNumFrames = 2
-- alienMoveFrameCounter = 0
alienMoveFrameCounter =
alienMoveFrameCounter + 1
alienMoveFrameCounter =
alienMoveFrameCounter % alienMoveNumFrames
if aliensAtEdge() then
-- move down
for row = 1, alienRows do
for column = 1, alienColumns do
if (aliens[row][column].alive) then
aliens[row][column].position.y =
aliens[row][column].position.y +
(alienVertSpeed)
aliensAlive = aliensAlive + 1
if (aliens[row][column].position.y > alienMaxY) then
-- game over
gameState = stateGameOver
end -- if
end -- if
end -- columns
end -- rows
alienDirection = -alienDirection
else
for row = 1, alienRows do
for column = 1, alienColumns do
if (aliens[row][column].alive) then
aliens[row][column].position.x =
aliens[row][column].position.x +
(alienSpeed * alienDirection)
aliensAlive = aliensAlive + 1
end -- if
end -- columns
end -- rows
end -- if
alienMoveDelay = calcAlienSpeed(aliensAlive)
alienMoveCounter = alienMoveDelay
if (aliensAlive == 0) then
timer = 0
gameState = stateAllAliensDead
end -- if
end -- if alienMoveCounter
end -- moveAliens
function calcAlienSpeed(aliensAlive)
local delay
if (aliensAlive <= 1) then
delay = 1
fireProbability = 5
elseif (aliensAlive <= 3) then
delay = 3
fireProbability = 3
elseif (aliensAlive <= 4) then
delay = 3
fireProbability = 1
elseif (aliensAlive <= 8) then
delay = 8
fireProbability = 1
elseif (aliensAlive <= 20) then
delay = 15
fireProbability = 1
elseif (aliensAlive <= 30) then
delay = 30
fireProbability = 1
elseif (aliensAlive <= 40) then
delay = 40
fireProbability = 1
else
delay = 50
fireProbability = 1
end -- if
return delay
end -- calcAlienSpeed()
function aliensAtEdge()
-- for each alien
for row = 1, alienRows do
for column = 1, alienColumns do
-- if alien is alive then
if (aliens[row][column].alive) then
-- if alien going right +1
if (alienDirection == 1) then
-- if alien x + step > MaxX then
if (aliens[row][column].position.x + alienSpeed) >
alienMaxX then
-- return true
return true
-- end if
end
-- else going left -1
else
if (aliens[row][column].position.x - alienSpeed) <
alienMinX then
-- return true
return true
-- end if
end
-- end if - going left right
end
-- end if alive
end
end -- for columns
end -- for rows
-- end for
-- return false
return false
end -- aliensAtEdge
function checkCollision(object1, object2)
-- object.position position.x posiition.y
-- object.width
-- object.height
local object1Left = object1.position.x
local object1Right = object1.position.x
+ object1.width -1
local object1Top = object1.position.y
local object1Bottom = object1.position.y
+ object1.height - 1
local object2Left = object2.position.x
local object2Right = object2.position.x
+ object2.width -1
local object2Top = object2.position.y
local object2Bottom = object2.position.y
+ object2.height - 1
if (object1Left < object2Right) and
(object1Right > object2Left) and
(object1Top < object2Bottom) and
(object1Bottom > object2Top) then
return true
else
return false
end
end -- checkCollision
function checkBulletCollisions()
local bulletHasHitAlien = false
for bullet = 1, maxPlayerBullets do
if (playerBullets[bullet].active) then
for row = 1, alienRows do
for column = 1, alienColumns do
if (aliens[row][column].alive) then
bulletHasHitAlien = checkCollision(
playerBullets[bullet],
aliens[row][column]
)
if (bulletHasHitAlien) then
-- hit
aliens[row][column].alive = false
playerBullets[bullet].active = false
alienExplosion(aliens[row][column].position)
playerScore = playerScore + alienRowTypes[row].score
sfx(2, 5, 30, 2, 15)
end -- if
end -- if alien alive
end -- for column
end -- for row
end -- if bullet active
end -- for bullet
end -- checkBulletCollisions
function alienExplosion(exPosition)
local explosion = {
position = exPosition,
ticCounter = 0,
totalTics = 30,
baseSprite = 64,
numFrames = 4
}
table.insert(explosions, explosion) -- add explosion into the array
end -- function alienExplosion
function drawExplosions()
local spriteNumber
for index, explosion in ipairs(explosions) do
spriteNumber = (explosion.ticCounter % explosion.numFrames)
+ explosion.baseSprite
spr(spriteNumber, explosion.position.x, explosion.position.y)
explosion.ticCounter = explosion.ticCounter + 1
if (explosion.ticCounter > explosion.totalTics) then
table.remove(explosions, index)
end --if
end -- for
end -- drawExplosions
function drawScoreBoard()
print("SCORE : "..playerScore,0,0)
for counter=0, playerLives-2 do
spr(0, 232 - (counter * 12), -3)
end -- for
end -- drawScoreBoard
function spawnAlienMissile(alienPosition)
if (#alienMissiles < maxAlienMissiles) then
local missile = {
position = {
x = alienPosition.x + 4,
y = alienPosition.y + 8
},
height = 5,
width = 1,
colour = 6,
speed = math.random(5,15)/10
}
table.insert(alienMissiles, missile)
end --if
end -- spawnAlienMissile
function drawAlienMissiles()
for index, missile in ipairs(alienMissiles) do
-- line(startX, StartY, endX, endY, colour)
line(
missile.position.x,
missile.position.y,
missile.position.x,
missile.position.y + missile.height,
missile.colour
)
end -- for
end -- drawAlienMissiles
function moveAlienMissiles()
for index, missile in ipairs(alienMissiles) do
missile.position.y = missile.position.y +
missile.speed
if (missile.position.y >= 136) then
table.remove(alienMissiles, index)
end -- if
end -- for
end -- moveAlienMissiles
function checkMissileCollisions()
for index, missile in ipairs(alienMissiles) do
if (checkCollision(missile, playerShip)) then
-- player ship has been hit
local explosion = {
position = playerShip.position,
ticCounter = 0,
totalTics = 60,
baseSprite = 64,
numFrames = 4
}
table.insert(explosions, explosion) -- add explosion into the array
table.remove(alienMissiles, index)
sfx(2, 5, 30, 2, 15)
jumpToNewLifeState()
end -- if
end -- for
end -- checkMissileCollisions()
function jumpToNewLifeState()
gameState = stateNewLife
timer = 0
playerLives = playerLives - 1
end -- jumpToNewLifeState()