Code Sketch
NAVYA ; 09
Category: Art
// ========================================
// SPACE SHOOTER - ULTIMATE EDITION
// Kojo Coding Competition
// ========================================
cleari()
originBottomLeft()
// GAME VARIABLES
var shipX = 400.0
val shipY = 90.0
var score = 0
var lives = 5
var gameOver = false
var tick = 0
var bossActive = false
var bossX = 400.0
var bossY = 800.0
var bossHealth = 10
var bossDirection = 1.0
// METEORS
val meteorX = Array(
100.0, 250.0, 400.0, 550.0, 700.0
)
val meteorY = Array(
500.0, 650.0, 800.0, 950.0, 1100.0
)
// ENEMY SHIPS
val enemyX = Array(
180.0, 350.0, 520.0
)
val enemyY = Array(
700.0, 1000.0, 1300.0
)
// STARS
val starX = Array(
180.0, 350.0, 520.0, 680.0
)
val starY = Array(
400.0, 600.0, 800.0, 1000.0
)
val starTaken =
Array.fill(starX.length)(false)
// BULLETS
val bulletX = Array.fill(30)(0.0)
val bulletY = Array.fill(30)(0.0)
val bulletActive = Array.fill(30)(false)
// RESET GAME
def resetGame() {
shipX = 400.0
score = 0
lives = 5
tick = 0
gameOver = false
bossActive = false
bossX = 400.0
bossY = 800.0
bossHealth = 10
bossDirection = 1.0
for (i <- meteorY.indices) {
meteorY(i) = 500.0 + i * 150.0
meteorX(i) = 100.0 + i * 150.0
}
for (i <- enemyY.indices) {
enemyY(i) = 700.0 + i * 300.0
}
for (i <- starY.indices) {
starY(i) = 400.0 + i * 200.0
starTaken(i) = false
}
for (i <- bulletActive.indices) {
bulletActive(i) = false
}
}
// KEYBOARD CONTROLS
onKeyPress { key =>
if (gameOver) {
if (key == Kc.VK_R) {
resetGame()
}
} else {
if (key == Kc.VK_LEFT) {
shipX -= 25
}
if (key == Kc.VK_RIGHT) {
shipX += 25
}
if (key == Kc.VK_SPACE) {
var i = 0
while (
i < bulletActive.length &&
bulletActive(i)
) {
i += 1
}
if (i < bulletActive.length) {
bulletX(i) = shipX
bulletY(i) = shipY + 25
bulletActive(i) = true
}
}
shipX = math.max(
30,
math.min(cwidth - 30, shipX)
)
}
}
// BACKGROUND
def drawBackground(c: CanvasDraw) {
c.fill(5, 5, 30)
c.noStroke()
c.rect(0, 0, cwidth, cheight)
c.fill(25, 10, 65)
c.ellipse(
cwidth * 0.25,
cheight * 0.70,
250,
300
)
c.fill(10, 25, 75)
c.ellipse(
cwidth * 0.75,
cheight * 0.45,
280,
350
)
c.fill(220, 230, 255)
for (i <- 0 until 70) {
val x = (i * 137) % cwidth.toInt
val y = (
i * 197 + tick * (i % 3 + 1)
) % cheight.toInt
val size =
if (i % 7 == 0) 4 else 2
c.ellipse(
x.toDouble,
y.toDouble,
size,
size
)
}
}
// SPACESHIP
def drawShip(c: CanvasDraw) {
c.stroke(0, 220, 255)
c.strokeWeight(4)
c.noFill()
// Left wing
c.line(
shipX - 18, shipY,
shipX - 35, shipY - 20
)
c.line(
shipX - 35, shipY - 20,
shipX - 18, shipY - 15
)
// Right wing
c.line(
shipX + 18, shipY,
shipX + 35, shipY - 20
)
c.line(
shipX + 35, shipY - 20,
shipX + 18, shipY - 15
)
// Main body
c.line(
shipX, shipY + 25,
shipX - 20, shipY - 15
)
c.line(
shipX - 20, shipY - 15,
shipX + 20, shipY - 15
)
c.line(
shipX + 20, shipY - 15,
shipX, shipY + 25
)
// Cockpit
c.fill(255, 255, 255)
c.noStroke()
c.ellipse(
shipX,
shipY + 5,
10,
16
)
// Engine flame
c.fill(255, 100, 0)
c.ellipse(
shipX,
shipY - 22,
12,
18
)
c.fill(255, 230, 0)
c.ellipse(
shipX,
shipY - 20,
6,
10
)
}
// METEORS
def drawMeteors(c: CanvasDraw) {
for (i <- meteorX.indices) {
c.fill(130, 45, 35)
c.stroke(255, 120, 60)
c.strokeWeight(2)
c.ellipse(
meteorX(i),
meteorY(i),
32,
32
)
c.fill(255, 150, 50)
c.noStroke()
c.ellipse(
meteorX(i) - 5,
meteorY(i) + 5,
8,
8
)
}
}
// ENEMY SHIPS
def drawEnemies(c: CanvasDraw) {
for (i <- enemyX.indices) {
c.stroke(255, 100, 255)
c.strokeWeight(3)
c.noFill()
c.line(
enemyX(i), enemyY(i) - 20,
enemyX(i) - 24, enemyY(i) + 15
)
c.line(
enemyX(i) - 24, enemyY(i) + 15,
enemyX(i) + 24, enemyY(i) + 15
)
c.line(
enemyX(i) + 24, enemyY(i) + 15,
enemyX(i), enemyY(i) - 20
)
c.fill(255, 40, 80)
c.noStroke()
c.ellipse(
enemyX(i),
enemyY(i),
12,
12
)
}
}
// STARS AND POWER-UPS
def drawStars(c: CanvasDraw) {
for (i <- starX.indices) {
if (!starTaken(i)) {
c.fill(255, 220, 0)
c.stroke(255, 255, 150)
c.strokeWeight(2)
c.ellipse(
starX(i),
starY(i),
22,
22
)
c.fill(255, 255, 255)
c.noStroke()
c.ellipse(
starX(i) - 3,
starY(i) + 4,
5,
5
)
}
}
}
// BULLETS
def drawBullets(c: CanvasDraw) {
for (i <- bulletActive.indices) {
if (bulletActive(i)) {
c.fill(0, 255, 255)
c.noStroke()
c.rect(
bulletX(i) - 3,
bulletY(i),
6,
18
)
c.fill(150, 255, 255)
c.ellipse(
bulletX(i),
bulletY(i) + 18,
8,
8
)
}
}
}
// BOSS
def drawBoss(c: CanvasDraw) {
if (bossActive) {
c.fill(130, 0, 30)
c.stroke(255, 50, 80)
c.strokeWeight(3)
c.rect(
bossX - 45,
bossY - 25,
90,
50
)
c.fill(255, 30, 60)
c.noStroke()
c.ellipse(
bossX - 25,
bossY,
18,
18
)
c.ellipse(
bossX + 25,
bossY,
18,
18
)
// Health bar
c.fill(70, 20, 20)
c.rect(
bossX - 40,
bossY + 35,
80,
8
)
c.fill(0, 255, 80)
c.rect(
bossX - 40,
bossY + 35,
bossHealth * 8,
8
)
}
}
// UPDATE GAME
def updateGame() {
if (!gameOver) {
tick += 1
// Move meteors
for (i <- meteorY.indices) {
meteorY(i) -= 5
if (meteorY(i) < -30) {
meteorY(i) = cheight + 50
meteorX(i) =
randomDouble(40, cwidth - 40)
}
if (
math.abs(meteorX(i) - shipX) < 30 &&
math.abs(meteorY(i) - shipY) < 30
) {
lives -= 1
meteorY(i) = -100
if (lives <= 0) {
gameOver = true
}
}
}
// Move enemy ships
for (i <- enemyY.indices) {
enemyY(i) -= 4
if (enemyY(i) < -40) {
enemyY(i) = cheight + 100
enemyX(i) =
randomDouble(40, cwidth - 40)
}
if (
math.abs(enemyX(i) - shipX) < 35 &&
math.abs(enemyY(i) - shipY) < 35
) {
lives -= 1
enemyY(i) = -100
if (lives <= 0) {
gameOver = true
}
}
}
// Move bullets
for (i <- bulletActive.indices) {
if (bulletActive(i)) {
bulletY(i) += 12
if (bulletY(i) > cheight) {
bulletActive(i) = false
}
// Hit meteors
for (j <- meteorY.indices) {
if (
bulletActive(i) &&
math.abs(
bulletX(i) - meteorX(j)
) < 25 &&
math.abs(
bulletY(i) - meteorY(j)
) < 25 &&
meteorY(j) >= 0
) {
score += 20
meteorY(j) = -100
bulletActive(i) = false
}
}
// Hit enemies
for (j <- enemyY.indices) {
if (
bulletActive(i) &&
math.abs(
bulletX(i) - enemyX(j)
) < 28 &&
math.abs(
bulletY(i) - enemyY(j)
) < 28 &&
enemyY(j) >= 0
) {
score += 30
enemyY(j) = -100
bulletActive(i) = false
}
}
// Hit boss
if (
bulletActive(i) &&
bossActive &&
math.abs(bulletX(i) - bossX) < 48 &&
math.abs(bulletY(i) - bossY) < 30
) {
bossHealth -= 1
bulletActive(i) = false
if (bossHealth <= 0) {
score += 200
bossActive = false
}
}
}
}
// Spawn boss
if (tick % 600 == 0 && !bossActive) {
bossActive = true
bossX = cwidth / 2
bossY = cheight - 100
bossHealth = 10
}
// Move boss
if (bossActive) {
bossX += bossDirection * 3
if (bossX > cwidth - 60) {
bossDirection = -1.0
}
if (bossX < 60) {
bossDirection = 1.0
}
if (
math.abs(bossX - shipX) < 55 &&
math.abs(bossY - shipY) < 45
) {
lives -= 1
bossActive = false
if (lives <= 0) {
gameOver = true
}
}
}
// Move and collect stars
for (i <- starY.indices) {
starY(i) -= 3
if (starY(i) < 0) {
starY(i) = cheight + 100
starX(i) =
randomDouble(40, cwidth - 40)
starTaken(i) = false
}
if (
!starTaken(i) &&
math.abs(starX(i) - shipX) < 25 &&
math.abs(starY(i) - shipY) < 25
) {
starTaken(i) = true
score += 10
if (score % 50 == 0) {
lives = math.min(5, lives + 1)
}
}
}
// Survival bonus
if (tick % 100 == 0) {
score += 5
}
if (lives <= 0) {
gameOver = true
}
}
}
// SCOREBOARD
def drawScore(c: CanvasDraw) {
c.fill(255, 255, 255)
c.noStroke()
c.text(
s"SCORE: $score",
25,
cheight - 35
)
c.text(
s"LIVES: $lives",
25,
cheight - 60
)
c.text(
"SPACE SHOOTER",
cwidth / 2 - 65,
cheight - 35
)
c.text(
"LEFT / RIGHT: MOVE",
25,
25
)
c.text(
"SPACE: SHOOT",
25,
45
)
if (bossActive) {
c.fill(255, 80, 80)
c.text(
"BOSS FIGHT!",
cwidth / 2 - 40,
cheight - 60
)
}
}
// GAME OVER
def drawGameOver(c: CanvasDraw) {
c.fill(5, 5, 30)
c.noStroke()
c.rect(0, 0, cwidth, cheight)
c.fill(255, 50, 70)
c.text(
"GAME OVER!",
cwidth / 2 - 55,
cheight / 2 + 50
)
c.fill(255, 255, 255)
c.text(
s"FINAL SCORE: $score",
cwidth / 2 - 65,
cheight / 2 + 10
)
c.fill(0, 255, 200)
c.text(
"PRESS R TO RESTART",
cwidth / 2 - 75,
cheight / 2 - 30
)
}
// MAIN GAME LOOP
animateWithCanvasDraw { c =>
updateGame()
if (!gameOver) {
drawBackground(c)
drawStars(c)
drawMeteors(c)
drawEnemies(c)
drawBullets(c)
drawBoss(c)
drawShip(c)
drawScore(c)
} else {
drawGameOver(c)
}
}