Code Sketch


NAVYA ; 09
By: Mhalsakant School
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)
    }
}