logologologologo
  • home
  • learn to code
    • Space Invaders – Beginner’s Course
    • Asteroids – Beginner’s Level 2
    • Snake – a Beginner’s Challenge
  • learn to make
    • Arduino Tutorials
  • blog
    • Computer Science
    • Coding
      • Collision Detection
    • Retro Gaming
    • Computer Games for Vision Therapy
  • contact me
Learn to Code Space Invaders – Lesson 23 – Player Lives and Game States
16th November 2019
Learn to Code Space Invaders – Lesson 25 – Level Up!
17th November 2019
Published by Bob at 17th November 2019
Categories
  • Learn to Code Space Invaders
Tags

Space Invaders Lesson 24 – The Aliens Have Landed

Code for Lesson 24

-- 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()
Share
1
Bob
Bob

Related posts

17th November 2019

Learn to Code Space Invaders – Lesson 25 – Level Up!


Read more
16th November 2019

Learn to Code Space Invaders – Lesson 23 – Player Lives and Game States


Read more
15th November 2019

Learn to Code Space Invaders – Lesson 22 – The Aliens Fight Back


Read more

Comments are closed.

  • Privacy Policy
  • Contact Me
© 2020 Bytes N Bits
We use cookies to ensure that we give you the best experience on our website. If you continue to use this site we will assume that you are happy with it.Ok