-- title: Space Invaders -- author: Bob Grant -- desc: Lesson 24 -- 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 = 5 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 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) == 1) 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 elseif (aliensAlive <= 4) then delay = 3 elseif (aliensAlive <= 8) then delay = 8 elseif (aliensAlive <= 20) then delay = 15 elseif (aliensAlive <= 30) then delay = 30 elseif (aliensAlive <= 40) then delay = 40 else delay = 50 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 = 1 } 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()