0,]']±>Sï}WÿÍu§ðp8·d%qy)6o;]ÉA¦ösï÷ôôô”°ÂVl†3<: :09@7P6P5`4`3`3`2`2`2P2P2@3 456à7Ð8Ð9À9À:À;°;°<°<°=°ŽG¸R&.@£ÞÛ õàÀÐàðJ™õàúÿÿÿÿÿøAe´€ ¯AV´°„õà …õà @Tï¿ бï¿8àÿ dLG  "`_ÎÛÿÿÿÿ00000000000@700`?Î;208060!30000000!Pð«¤ÛI’A ´€AAü´€4A±´€„?ÀÌ ÌÌÌÌÀÌÀÌÌÌÀ ÌÌ PPPÌ ÀÌ ÀÌÌÌÀÌÌÀÌÀ ÌÀ ÌÀ À ÌÀÀÌÌÀ ÀPUUPUUPUUÀÌÌ Ì Ì ÌÀ ÌÀÌÌ Ì À À ÌÌ ÌÀ Ì UUUUUUUUUUUUÌ Ì Ì ÌÌ ÌÌÌÌ Ì À À Ì ÌÀ ÀUUUUUUUUUUUUÌÌÀÌ ÀÌÌÀÌÌÀÌ ÌÌ ÀÌÀÌÀ À ÀÌÌÌ UUUUUUUUUUUU          """"              BDD$    """"""""""B""$"""""$B""$B""$B""$B""$B"BB$$"$B""$B"""""""""""""""""""""BB$$""""""""""""""""""B""$""""     BDD$  """"""""""""""""""                            """""""""""""""$B""$B""$B""$B""$B""$B""$B"""""""""""""""""""""""""""""""""""""""""""       """"""""""""""```````ffffffffffffff`ff`ff`ff`ff`ff`ff""`fff`ff`ff`ff`ff`ff`fB$f`fffffffffffffffffffffffffB$ffff``````""``````````````````````````````````fffffffbff ""`ff`ff`ff`fb`ff DDf`ff`ff`ff`ff`f DDffffffffffffffbfffff DD````` DD`````````` ""`````````` ªª  ªª  ªª  ªª  ªª  ¤J  ¤J  ¤J  ¤J  ¤J  ªª  ªª  ªª  ªª  ªª ªªªªªªªª¢ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª          ª  ªª  ª¢  ªª  ªª  ª ª¢ ªª  ªª  ¤B ¤J  ¤J  ª¢ ªª  ªª ªªªª¢ªªªªªªªªªªªªªªªªªªª      ª  ªª  ªª  ª@@@@@@UUUUUPUUUPUUUPUUU›M-- this line below is for the linter to stop complaining about my naming conventions ---@diagnostic disable: lowercase-global -- title: Space Invaders -- author: Pedro Torres -- description: This is a clone o the classid arcade game -- space invaders done following Bytes N Bit tutorial: -- https://www.youtube.com/playlist?list=PLvOT6zBnJyYF3FzmfXz2QXMA8xk7JHA_b -- version: 1.1a -- script: lua -- Screen Constants SCREEN_WIDTH = 232 SCREEN_HEIGHT = 160 -- Color Constants RED = 2 YELLOW = 4 GREEN =5 WHITE = 12 -- btn Constants LEFT = 2 RIGHT = 3 Z = 4 -- key constants F1 = 67 -- Player Variables playerShip = { pos = { x = 120, y = 128 }, height = 6, width = 8, spriteNum = 0, minX = 0, maxX = 232, speed = 1, bulletOffset = { x = 4, y = 4 } } -- playerShip object playerLives = 5 -- Alien Global Variables aliens = {} -- 2D array of aliens alienRows = 6 -- Number of rows of aliens alienColumns = 8 -- Number of aliens per row alienHSpacing = 12 -- Horizontal spacing between aliens alienVSpacing = 12 -- Vertical spacing between aliens alienTopOffset = 8 -- Offset from top of screen alienDirection = 1 -- 1 => moving left, -1 => moving right alienSpeed = 4 -- Horizontal speed of the alien alienVSpeed = 4 -- Vertical speed of the alien alienMaxX = 232 -- Max X position of the alien alienMinX = 0 -- Min X position of the alien alienMaxY = 120 -- Max Y position of the alien alienMinY = 0 -- Min Y position of the alien bonusAlienChance = 10 -- Chance that a bonus alien will appear baseAlienSprites = {16, 32, 48} -- Alien Movement alienMoveDelay = 40 -- Number of frames between alien moves alienMoveCounter = 0 -- Counter for alien move -- Alien prites alienRowTypes = {} alienRowTypes[1] = {baseSprite = 16, scoreValue = 200, HP = 3} alienRowTypes[2] = {baseSprite = 16, scoreValue = 200, HP = 3} alienRowTypes[3] = {baseSprite = 32, scoreValue = 100, HP = 2} alienRowTypes[4] = {baseSprite = 32, scoreValue = 100, HP = 2} alienRowTypes[5] = {baseSprite = 48, scoreValue = 50, HP = 1} alienRowTypes[6] = {baseSprite = 48, scoreValue = 50, HP = 1} -- Bullet Global Variables playerBullets = {} maxPlayerBullets = 4 bulletDamage = 1 -- Explostion Variabless explosions = {} baseExplosionSprites = {64} -- Score Variables score = 0 highScore = 0 -- Timekeeping Variables timeTick = 0 clockTick = 0 -- Sound Variables soundStepCounter = 0 soundStepNotes = 4 -- Alien Missiles alienMissiles = {} maxAlienMissiles = 30 -- Entity Variables liveAliens = {} activeBullets = {} activeMissiles = {} -- GameState Variables stateStartGame = 0 statePlayGame = 1 stateNewLife = 2 stateGameOver = 3 stateAllAliensDead = 4 gameState = 0 -- Game Initialization Flag gameNeedsToBeInitialized = true 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 -- if gameState end -- TIC function startGameTIC() cls() print("LEFT and RIGHT keys to move the cannon.", SCREEN_HEIGHT // 2 - 80, 40, YELLOW) print("Press Z to fire", SCREEN_HEIGHT // 2 - 80, 50, YELLOW) print("Press the F1 key to save your highscore.", SCREEN_HEIGHT // 2 - 80, 60, YELLOW) print("Press Z to start the game", SCREEN_HEIGHT // 2 - 80, 70, YELLOW) loadHighScore() -- Load high score if btnp(Z) then gameState = statePlayGame end end -- startGameTIC function newLifeTIC() timerTick = 0 timeTick = timeTick + 1 if timeTick >= 300 then if playerLives == 0 then gameState = stateGameOver else -- if playerLives > 0 playerShip.pos.x = 120 playerShip.pos.y = 128 for _, bullet in ipairs(playerBullets) do if bullet.active then bullet.active = false end end activeBullets = {} for _, missile in ipairs(activeMissiles) do if missile.active then missile.active = false end end activeMissiles = {} timeTick = 0 gameState = statePlayGame end -- if playerLives end -- while timeTick < 180 cls() drawAliens() drawExplosions() drawLabels() end -- newLifeTIC function gameOverTIC() cls() drawAliens() drawLabels() print("Game Over!", SCREEN_HEIGHT // 2 - 30, 50, YELLOW) if score >= highScore then highScore = score print("You got a highscore!", SCREEN_HEIGHT // 2 - 30, 60, YELLOW) print("Your score was: "..score, SCREEN_HEIGHT // 2 - 30, 70, YELLOW) else print("Your score was: "..score, SCREEN_HEIGHT // 2 - 30, 70, YELLOW) end print("Press Z to start the game", SCREEN_HEIGHT // 2 - 30, 80, YELLOW) if btnp(Z) then restartGame() end -- if btnp (Z) end -- gameOverTIC function allAliensDeadTIC() cls() timerTick = 0 timeTick = timeTick + 1 if timeTick < 200 then else initAliens() gameState = statePlayGame end clearMissilesAndBullets() moveMissile() checkMissileCollision() drawPlayerBullet() drawPlayerShip() drawExplosions() drawLabels() drawMissile() end -- allAliensDeadTIC function restartGame() playerLives = 3 score = 0 timeTick = 0 clockTick = 0 playerShip.pos.x = 120 playerShip.pos.y = 128 clearMissilesAndBullets() for alienRow = 1, alienRows do for alienColumn = 1, alienColumns do aliens[alienRow][alienColumn].alive = true end end gameState = statePlayGame gameNeedsToBeInitialized = true end -- restartGame function playGameTic() if gameNeedsToBeInitialized then initializeGame() gameNeedsToBeInitialized = false end if keyp(F1) then trace("Highscore saved!", YELLOW) saveHighScore() end -- Timekeeping timeTick = timeTick + 1 clockTick = clockTick + 1 -- Update cleanup() movePlayerShip() checkPlayerShipBounds() moveBullet() checkPlayerFire() checkBulletBounds() moveAliens() checkBulletCollision() shootMissile() moveMissile() checkMissileCollision() -- Draw cls() drawPlayerBullet() drawPlayerShip() drawAliens() drawExplosions() drawLabels() drawMissile() end function initializeGame() initPlayerBullletsArray() -- Initialize player bullets initAliens() -- Initialize aliens initAlienMissiles() -- Initialize alien missiles end -- initializeGame function initPlayerBullletsArray() for bullet = 1, maxPlayerBullets do playerBullets[bullet] = { pos = { x = playerShip.pos.x + playerShip.bulletOffset.x, y = playerShip.pos.y + playerShip.bulletOffset.y }, height = 5, width = 1, length = 5, colour = YELLOW, speed = 0.75, active = false, } end -- for loop end -- initPlayerBullletsArray function movePlayerShip() -- Check left arrow button press if (btn(LEFT)) then playerShip.pos.x = playerShip.pos.x - playerShip.speed end -- if left arrow pressed -- Check right arrow button press if (btn(RIGHT)) then playerShip.pos.x = playerShip.pos.x + playerShip.speed end -- if right button pressed end -- movePlayerShip function checkPlayerShipBounds() -- Check if the player ship is out of bounds playerShip.pos.x = checkLimits( playerShip.pos.x, playerShip.minX, playerShip.maxX ) end -- checkPlayerShipBounds function checkLimits(value, min, max) -- Check if the value is within the min and max limits return math.max(min, math.min(max, value)) end -- checkLimits function checkPlayerFire() local bulletFired = false local bulletIndex = 1 -- Check if the fire button (Z) is pressed if btnp(Z) then sfx(1, 9, 10, 1, 8) -- Find an inactive bullet while (bulletIndex <= maxPlayerBullets) and (not bulletFired) do local currentBullet = playerBullets[bulletIndex] if not currentBullet.active then -- Initialize bullet position currentBullet.pos = { x = playerShip.pos.x + playerShip.bulletOffset.x, y = playerShip.pos.y + playerShip.bulletOffset.y } -- Mark bullet as active currentBullet.active = true -- Set bulletFired flag to true to prevent other bullets from firing bulletFired = true end bulletIndex = bulletIndex + 1 end end -- if btn(Z) end -- checkPlayerFire function moveBullet() for bullet = 1, maxPlayerBullets do currentBullet = playerBullets[bullet] if (currentBullet.active == true) then -- move the bullet up the screen currentBullet.pos.y = currentBullet.pos.y - currentBullet.speed end -- If currentBullet is active end -- for loop end -- moveullet function function checkBulletBounds() for bullet = 1, maxPlayerBullets do currentBullet = playerBullets[bullet] if currentBullet.pos.y < 0 then currentBullet.active = false end -- if out of bounds end -- for loop end -- checkBulletBounds function function drawPlayerBullet() for bullet = 1, maxPlayerBullets do currentBullet = playerBullets[bullet] if (currentBullet.active) then -- line(startX, StartY, endX, endY, colour) line( currentBullet.pos.x, currentBullet.pos.y, currentBullet.pos.x, currentBullet.pos.y + currentBullet.length, currentBullet.colour ) end -- if bullet is active end -- for loop end -- drawPLayerBuller function function drawPlayerShip() -- spr(sprit reference, x coordinate, y coordinate, opacity) spr(playerShip.spriteNum, playerShip.pos.x, playerShip.pos.y, 0) end -- drawPlayerShip 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] = { pos = { x = (column - 1) * alienHSpacing, y = alienTopOffset + (row - 1) * alienVSpacing }, HP = alienRowTypes[row].HP, height = 8, width = 8, alive = true, baseSprite = alienRowTypes[row].baseSprite, animationFrames = 2, scoreValue = alienRowTypes[row].scoreValue } end -- end row end -- end alien grid end -- initAliens function drawAliens() for row = 1, alienRows do for column = 1, alienColumns do alien = aliens[row][column] if (alien.alive) then spr(alien.baseSprite, alien.pos.x, alien.pos.y) drawHPBar(alien) end -- if end -- columns end -- rows end -- drawAliens function drawHPBar(alien) local barX = alien.pos.x local barY = alien.pos.y rect(barX, barY, alien.HP * 3, 2, GREEN) end function moveAliens() aliensAlive = 0 alienMoveCounter = alienMoveCounter + 1 -- Move the aliens down and reverse the direction if they are at the edges if (alienMoveCounter >= alienMoveDelay) then playAlienSounds() if aliensAtEdge() then for row = 1, alienRows do for column = 1, alienColumns do alien = aliens[row][column] if (alien.alive) then -- move alien down alien.pos.y = alien.pos.y + (alienVSpeed) aliensAlive = aliensAlive + 1 updateSprite(alien) -- check if alien is at the bottom if (alien.pos.y >= alienMaxY) then gameState = stateGameOver -- game over end -- if alien is at the bottom end -- if alien si alive end -- get alien columns end -- get alien rows -- Reverse the horizontal direction of the aliens alienDirection = -alienDirection else for row = 1, alienRows do for column = 1, alienColumns do alien = aliens[row][column] if (alien.alive) then alien.pos.x = alien.pos.x + (alienSpeed * alienDirection) aliensAlive = aliensAlive + 1 updateSprite(alien) end -- if end -- columns end -- rows end -- if at edge alienMoveDelay = calculateAlienSpeed(aliensAlive) alienMoveCounter = 0 if aliensAlive == 0 then timeTick = 0 gameState = stateAllAliensDead end -- if all aliens are dead end -- if moveCounter is larger or equal to alienMoveDelay end -- moveAliens function calculateAlienSpeed(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 -- calculateAlienSpeed function playAlienSounds() soundStepCounter = soundStepCounter + 1 -- Step Sounds if (soundStepCounter == 1) then sfx(0, 12, 10, 0, 8) end if (soundStepCounter == 2) then sfx(0, 11, 10, 0, 8) end if (soundStepCounter == 3) then sfx(0, 10, 10, 0, 8) end if (soundStepCounter == 4) then sfx(0, 9, 10, 0, 8) end if (soundStepCounter == soundStepNotes) then soundStepCounter = 0 end end function updateSprite(sprite) -- Iterate over the base alien sprites for _, baseSprite in ipairs(baseAlienSprites) do -- Check if the current sprite falls within the range of the current base sprite if (sprite.baseSprite >= baseSprite and sprite.baseSprite < baseSprite + sprite.animationFrames) then -- Update the sprite to the next frame in the animation sprite.baseSprite = baseSprite + (sprite.baseSprite - baseSprite + 1) % sprite.animationFrames break end -- if end -- for end -- updateSprite function aliensAtEdge() for row = 1, alienRows do for column = 1, alienColumns do alien = aliens[row][column] if alien.alive then if alienDirection == 1 then -- alien is going right if alien.pos.x + alienSpeed > SCREEN_WIDTH then return true end -- if out of bounds else -- alien is going left if alien.pos.x - alienSpeed < 0 then return true end end -- if alienDirection end -- if alive end -- columns end -- rows -- No aliens are at the edge return false end -- aliensAtEdge function checkCollision(obj1, obj2) -- Calculate edges of objects local obj1Left, obj1Right = obj1.pos.x, obj1.pos.x + obj1.width - 1 local obj1Top, obj1Bottom = obj1.pos.y, obj1.pos.y + obj1.height - 1 local obj2Left, obj2Right = obj2.pos.x, obj2.pos.x + obj2.width - 1 local obj2Top, obj2Bottom = obj2.pos.y, obj2.pos.y + obj2.height - 1 -- Determine if edges overlap, indicating a collision if obj1Right >= obj2Left and obj1Left <= obj2Right and obj1Bottom >= obj2Top and obj1Top <= obj2Bottom then return true else return false end end function checkBulletCollision() -- Populate the list of live aliens for row = 1, alienRows do for column = 1, alienColumns do local alien = aliens[row][column] if alien.alive then table.insert(liveAliens, alien) end end end -- Populate the list of active bullets for bulletIndex = 1, maxPlayerBullets do local bullet = playerBullets[bulletIndex] if bullet.active then table.insert(activeBullets, bullet) end end -- Check for collisions between bullets and aliens for _, bullet in ipairs(activeBullets) do for _, alien in ipairs(liveAliens) do if checkCollision(bullet, alien) then -- If bullet and alien collide damageAlien(bullet, alien) break end end end end function damageAlien(bullet, alien) alien.HP = alien.HP - bulletDamage if alien.HP == 0 then killALien(bullet, alien) else bullet.active = false -- Kill bullet end end function killALien(bullet, alien) spr(64, alien.pos.x, alien.pos.y, 0) sfx(2, "C#3", 30, 2, 8) alien.alive = false -- Kill alien bullet.active = false -- Kill bullet score = score + alien.scoreValue spawnExplosion(alien.pos) end function spawnExplosion(explosionPos) local explosion = { pos = explosionPos, baseSprite = 64, animationFrames = 8, tickCounter = 0, totalTicks = 10 * 9, } table.insert(explosions, explosion) end function drawExplosions() for index, explosion in ipairs(explosions) do spr(explosion.baseSprite, explosion.pos.x, explosion.pos.y, 0) explosion.tickCounter = explosion.tickCounter + 1 if explosion.tickCounter % explosion.animationFrames == 0 then explosion.baseSprite = explosion.baseSprite + 1 elseif explosion.tickCounter >= explosion.totalTicks then table.remove(explosions, index) end end end function drawScore() print("Score: "..score, 0, 0, WHITE) end function drawTimer() local currentTime = clockTick // 60 print("Time: "..currentTime, 75, 0, WHITE) end function drawHighScore() print("High Score: "..highScore, 75, 0, WHITE) end function drawLives() -- print("Lives "..playerLives, 200, 0, WHITE) for counter=0, playerLives - 2 do spr(0, 232 - (counter * 12), -3) end end function drawLabels() drawScore() drawHighScore() drawLives() end function initAlienMissiles() for missile = 1, maxAlienMissiles do alienMissile = { pos = { x = 0, y = 0 }, height = 5, width = 1, length = 5, speed = randomFloat(0.3, 0.5), active = false } table.insert(alienMissiles, alienMissile) end -- for loop end -- initAlienMissiles function shootMissile() for row = 1, alienRows do for column = 1, alienColumns do local alien = aliens[row][column] if alien.alive then table.insert(liveAliens, alien) end end end if #liveAliens > 0 and timeTick % 10 == 0 then -- Select a random live alien to shoot a missile local randomAlienIndex = math.random(1, #liveAliens) local randomAlien = liveAliens[randomAlienIndex] -- Find an inactive missile to shoot for missileIndex = 1, maxAlienMissiles do local missile = alienMissiles[missileIndex] if not missile.active then missile.pos.x = randomAlien.pos.x + 4 missile.pos.y = randomAlien.pos.y + 8 missile.active = true break -- Stop searching after activating a missile end end end end function moveMissile() for missileIndex = 1, maxAlienMissiles do if #alienMissiles > 0 then local currentMissile = alienMissiles[missileIndex] -- Declare currentMissile as local if currentMissile.active == true then currentMissile.pos.y = currentMissile.pos.y + currentMissile.speed end end end end function drawMissile() for missileIndex= 1, maxAlienMissiles do local currentMissile = alienMissiles[missileIndex] if currentMissile.active then -- line(startX, StartY, endX, endY, colour) line( currentMissile.pos.x, currentMissile.pos.y, currentMissile.pos.x, currentMissile.pos.y + currentMissile.length, RED ) end -- If currentBullet is active end -- for loop end -- drawMissile function checkMissileCollision() for i = 1, #alienMissiles do local currentMissile = alienMissiles[i] if currentMissile.active then table.insert(activeMissiles, currentMissile) -- Add missile to activeMissiles table end -- if currentMissile is active end -- for loop for alienMissiles for _, missile in ipairs(activeMissiles) do if checkCollision(playerShip, missile) then missile.active = false local explosion = { pos = playerShip.pos, baseSprite = 64, animationFrames = 8, tickCounter = 0, totalTicks = 10 * 9, } table.insert(explosions, explosion) sfx(2, "C#3", 30, 2, 12) playerLives = playerLives - 1 jumpToNewLifeState() elseif missile.pos.y > SCREEN_HEIGHT then missile.active = false end -- if missile is active end -- for loop for activeMissiles end -- checkMissileCollision function jumpToNewLifeState() gameState = stateNewLife end -- jumpToNewLifeState -- Helper Functions -- This function clears the table of liveAliens, activeBullets and activeMissiles function cleanup() clearTable(liveAliens) clearTable(activeBullets) clearTable(activeMissiles) end -- This function clears a specified table function clearTable(table) for k in pairs(table) do table[k] = nil end end function clearMissilesAndBullets() for bullet = 1, maxPlayerBullets do playerBullets[bullet].active = false end -- for for missile= 1, maxAlienMissiles do alienMissiles[missile].active = false end end function randomFloat(lower, greater) return lower + math.random() * (greater - lower); end function saveHighScore() pmem(0, highScore) end function loadHighScore() highScore = pmem(0) end