Code Sketch


BHAve,sh art sindoooooor game
By: Mhalsakant School
Category: Art
// --- OPERATION SINDOOR WITH SLOW ENEMY MISSILES ---
cleari()
originBottomLeft()

val gravity = Vector2D(0, -0.08)
var waveAngle = 0.0
var chakraRotation = 0.0

// ?. ???????? ???????????? ??????????? ??? ???� ????
var missileX = cwidth / 2.0 - 150.0
var missileY = 150.0
var missileAngle = math.Pi / 2.0

class BrahMosMissile(var x: Double, var y: Double, val vx: Double, val vy: Double, val angle: Double) {
    var active = true
    def step() {
        x += vx
        y += vy
        if (x < -100 || x > cwidth + 100 || y < -100 || y > cheight + 100) active = false
    }
    def view(canvas: CanvasDraw) {
        canvas.pushMatrix()
        canvas.translate(x, y)
        canvas.rotate(angle)
        canvas.fill(200, 200, 200)
        canvas.stroke(80, 80, 80)
        canvas.strokeWeight(1)
        canvas.rect(-15, -6, 30, 12)
        canvas.fill(220, 20, 60)
        canvas.noStroke()
        canvas.ellipse(15, 0, 12, 12)
        canvas.fill(50, 50, 50)
        canvas.rect(-20, -10, 6, 20)
        canvas.fill(255, 140, 0)
        canvas.ellipse(-22, 0, 10, 8)
        canvas.popMatrix()
    }
}
val brahmosShells = ArrayBuffer.empty[BrahMosMissile]

// ?. ??? ?????? ??? ???� ???
var tankX = cwidth / 2.0 + 100.0
val tankY = 30.0
var cannonAngle = math.Pi / 2.0
var muzzleFlashTimer = 0

class TankShell(var x: Double, var y: Double, val vx: Double, val vy: Double) {
    var active = true
    def step() {
        x += vx
        y += vy
        if (x < -50 || x > cwidth + 50 || y < -50 || y > cheight + 50) active = false
    }
    def view(canvas: CanvasDraw) {
        canvas.fill(255, 140, 0)
        canvas.noStroke()
        canvas.ellipse(x, y, 9, 9)
        canvas.fill(255, 69, 0, 180)
        canvas.ellipse(x - vx * 1.5, y - vy * 1.5, 6, 6)
    }
}
val tankShells = ArrayBuffer.empty[TankShell]

// ?. ??????? ???????????? (?????? ??? ?????? - Slow Enemy Missile)
class EnemyMissile(var x: Double, var y: Double, var vy: Double) {
    var active = true
    var exploded = false
    var explosionTimer = 0

    def step() {
        if (!exploded) {
            y += vy // ???? ??? ???? ???? (Slow Speed)
            if (y < 0) active = false
        } else {
            explosionTimer += 1
            if (explosionTimer > 25) active = false
        }
    }

    def view(canvas: CanvasDraw) {
        if (!exploded) {
            canvas.pushMatrix()
            canvas.translate(x, y)
            canvas.rotate(-math.Pi / 2)
            
            canvas.fill(180, 30, 30)
            canvas.stroke(80, 10, 10)
            canvas.strokeWeight(1)
            canvas.rect(-15, -5, 30, 10)

            canvas.fill(30, 30, 30)
            canvas.noStroke()
            canvas.ellipse(15, 0, 10, 10)
            canvas.popMatrix()
        } else {
            canvas.noStroke()
            canvas.fill(255, 100, 0, 200)
            canvas.ellipse(x, y, 45, 45)
            canvas.fill(255, 255, 0, 220)
            canvas.ellipse(x, y, 25, 25)
        }
    }
}
val enemyMissiles = ArrayBuffer.empty[EnemyMissile]
var enemySpawnTimer = 0

// ?. ?????? ????????
val tricolorPalette = Array(
    cm.rgb(255, 153, 51),
    cm.rgb(255, 255, 255),
    cm.rgb(18, 136, 7)
)

class Particle(x0: Double, y0: Double, col: Color, seed: Boolean) {
    var location = Vector2D(x0, y0)
    private var velocity =
        if (seed) Vector2D(0, random(5, 11))
        else randomExplosionVector
    private var acceleration = Vector2D(0, 0)
    var lifespan = 255.0
    private var exploded = false

    def randomExplosionVector: Vector2D = {
        val rv1 = Vector2D(randomNormalDouble, randomNormalDouble)
        val r1 = randomDouble(2, 4)
        rv1 * r1
    }

    def applyForce(force: Vector2D) {
        acceleration += force
    }

    def shouldExplode: Boolean = !exploded && lifespan <= 0

    def checkExplode() {
        if (seed && velocity.y < 0) {
            lifespan = 0
        }
    }

    def isDead: Boolean = {
        if (seed) exploded else lifespan <= 0
    }

    def explode() {
        exploded = true
    }

    def step() {
        velocity += acceleration
        location += velocity
        if (!seed) {
            lifespan -= 5
            velocity *= 0.95
        }
        acceleration *= 0
        checkExplode()
    }

    def view(canvas: CanvasDraw) {
        canvas.stroke(col)
        if (seed) canvas.strokeWeight(4) else canvas.strokeWeight(2)
        canvas.point(location.x, location.y)
    }
}

class Firework() {
    private val fireworkColor = tricolorPalette(random(tricolorPalette.length))
    private val firework = new Particle(random(cwidth), 0, fireworkColor, true)
    private val particles = ArrayBuffer.empty[Particle]

    def done: Boolean = firework.isDead && particles.isEmpty

    def step() {
        particles.filterInPlace(p => !p.isDead)
        if (!firework.isDead) {
            firework.applyForce(gravity)
            firework.step()
            if (firework.shouldExplode) {
                repeat(80) {
                    particles.append(new Particle(firework.location.x, firework.location.y, fireworkColor, false))
                }
                firework.explode()
            }
        }
        repeatFor(particles) { p =>
            p.applyForce(gravity)
            p.step()
        }
    }

    def view(canvas: CanvasDraw) {
        firework.view(canvas)
        repeatFor(particles) { p => p.view(canvas) }
    }
}

val fireworks = ArrayBuffer.empty[Firework]

def updateState() {
    fireworks.filterInPlace(f => !f.done)
    if (randomDouble(1) < 0.08) {
        fireworks.append(new Firework())
    }
    repeatFor(fireworks) { f => f.step() }

    waveAngle += 0.08
    chakraRotation += 0.05

    // --- ??? ????????? ---
    if (mouseX >= 20 && mouseX <= 75 && mouseY >= 90 && mouseY <= 130) {
        tankX -= 2.5
    }
    else if (mouseX >= 80 && mouseX <= 135 && mouseY >= 90 && mouseY <= 130) {
        tankX += 2.5
    }
    else if (mouseX >= 140 && mouseX <= 195 && mouseY >= 90 && mouseY <= 130) {
        cannonAngle -= 0.05
    }
    else if (mouseX >= 200 && mouseX <= 255 && mouseY >= 90 && mouseY <= 130) {
        cannonAngle += 0.05
    }
    else if (mouseX >= 260 && mouseX <= 325 && mouseY >= 90 && mouseY <= 130) {
        val turretBaseX = tankX
        val turretBaseY = tankY + 36.0
        val barrelLength = 55.0
        val tipX = turretBaseX + math.cos(cannonAngle) * barrelLength
        val tipY = turretBaseY + math.sin(cannonAngle) * barrelLength
        val speed = 7.0
        tankShells.append(new TankShell(tipX, tipY, math.cos(cannonAngle) * speed, math.sin(cannonAngle) * speed))
        muzzleFlashTimer = 8
    }
    else if (mouseX >= 335 && mouseX <= 390 && mouseY >= 90 && mouseY <= 130) {
        missileAngle -= 0.05
    }
    else if (mouseX >= 395 && mouseX <= 450 && mouseY >= 90 && mouseY <= 130) {
        missileAngle += 0.05
    }
    else if (mouseX >= 455 && mouseX <= 525 && mouseY >= 90 && mouseY <= 130) {
        val mTipX = missileX + math.cos(missileAngle) * 45.0
        val mTipY = missileY + math.sin(missileAngle) * 45.0
        brahmosShells.append(new BrahMosMissile(mTipX, mTipY, math.cos(missileAngle) * 5.0, math.sin(missileAngle) * 5.0, missileAngle))
    }

    if (tankX < 50.0) tankX = 50.0
    if (tankX > cwidth - 50.0) tankX = cwidth - 50.0
    if (muzzleFlashTimer > 0) muzzleFlashTimer -= 1

    // ??????? ???????????? ??? ?????????? ??? -0.8 ???? ??? (Very Slow Speed)
    enemySpawnTimer += 1
    if (enemySpawnTimer >= 110) {
        enemySpawnTimer = 0
        val ex = random(100, cwidth - 100)
        val ey = cheight + 20.0
        enemyMissiles.append(new EnemyMissile(ex, ey, -0.8))
    }

    tankShells.filterInPlace(_.active)
    repeatFor(tankShells) { s => s.step() }

    brahmosShells.filterInPlace(_.active)
    repeatFor(brahmosShells) { bm => bm.step() }

    enemyMissiles.filterInPlace(_.active)
    repeatFor(enemyMissiles) { em => em.step() }

    // ????? ?????? (Collision Detection)
    repeatFor(enemyMissiles) { em =>
        if (!em.exploded) {
            repeatFor(tankShells) { ts =>
                if (ts.active && math.abs(ts.x - em.x) < 25 && math.abs(ts.y - em.y) < 25) {
                    em.exploded = true
                    ts.active = false
                }
            }
            repeatFor(brahmosShells) { bm =>
                if (bm.active && math.abs(bm.x - em.x) < 30 && math.abs(bm.y - em.y) < 30) {
                    em.exploded = true
                    bm.active = false
                }
            }
        }
    }
}

def drawBrahMosLauncher(canvas: CanvasDraw, mx: Double, my: Double) {
    canvas.fill(60, 60, 70)
    canvas.stroke(30, 30, 35)
    canvas.strokeWeight(2)
    canvas.ellipse(mx, my, 44, 44)
    canvas.pushMatrix()
    canvas.translate(mx, my)
    canvas.rotate(missileAngle)
    canvas.fill(80, 90, 80)
    canvas.stroke(40, 50, 40)
    canvas.strokeWeight(2)
    canvas.rect(-10, -12, 50, 24)
    canvas.fill(120, 30, 30)
    canvas.rect(10, -12, 6, 24)
    canvas.popMatrix()
}

def drawArmyTank(canvas: CanvasDraw, tx: Double, ty: Double) {
    canvas.fill(40, 40, 40)
    canvas.stroke(20, 20, 20)
    canvas.strokeWeight(1)
    canvas.rect(tx - 40, ty, 80, 18)
    canvas.fill(80, 80, 80)
    for (i <- -3 to 3) {
        canvas.ellipse(tx + (i * 11), ty + 9, 8, 8)
    }
    canvas.fill(55, 85, 45)
    canvas.rect(tx - 32, ty + 18, 64, 18)
    canvas.fill(40, 70, 35)
    canvas.ellipse(tx, ty + 36, 36, 20)

    val turretBaseX = tx
    val turretBaseY = ty + 36.0
    val barrelLength = 55.0
    canvas.stroke(30, 55, 25)
    canvas.strokeWeight(6)
    val tipX = turretBaseX + math.cos(cannonAngle) * barrelLength
    val tipY = turretBaseY + math.sin(cannonAngle) * barrelLength
    canvas.line(turretBaseX, turretBaseY, tipX, tipY)

    if (muzzleFlashTimer > 0) {
        canvas.noStroke()
        canvas.fill(255, 140, 0, 220)
        canvas.ellipse(tipX, tipY, 28, 28)
        canvas.fill(255, 235, 0, 250)
        canvas.ellipse(tipX, tipY, 16, 16)
        canvas.fill(255, 255, 255)
        canvas.ellipse(tipX, tipY, 8, 8)
    }
}

def drawControlButtons(canvas: CanvasDraw) {
    canvas.fill(50, 50, 80, 200)
    canvas.stroke(200, 200, 200)
    canvas.strokeWeight(2)
    canvas.rect(20, 90, 55, 40)
    canvas.fill(255, 255, 255)
    canvas.text("?", 38, 100)

    canvas.fill(50, 50, 80, 200)
    canvas.stroke(200, 200, 200)
    canvas.strokeWeight(2)
    canvas.rect(80, 90, 55, 40)
    canvas.fill(255, 255, 255)
    canvas.text("?", 98, 100)

    canvas.fill(80, 50, 50, 200)
    canvas.stroke(200, 200, 200)
    canvas.strokeWeight(2)
    canvas.rect(140, 90, 55, 40)
    canvas.fill(255, 255, 255)
    canvas.text("?T", 155, 100)

    canvas.fill(80, 50, 50, 200)
    canvas.stroke(200, 200, 200)
    canvas.strokeWeight(2)
    canvas.rect(200, 90, 55, 40)
    canvas.fill(255, 255, 255)
    canvas.text("?T", 215, 100)

    canvas.fill(200, 80, 0, 220)
    canvas.stroke(255, 255, 255)
    canvas.strokeWeight(2)
    canvas.rect(260, 90, 65, 40)
    canvas.fill(255, 255, 255)
    canvas.text("FIRE T", 270, 100)

    canvas.fill(100, 40, 120, 200)
    canvas.stroke(200, 200, 200)
    canvas.strokeWeight(2)
    canvas.rect(335, 90, 55, 40)
    canvas.fill(255, 255, 255)
    canvas.text("?M", 350, 100)

    canvas.fill(100, 40, 120, 200)
    canvas.stroke(200, 200, 200)
    canvas.strokeWeight(2)
    canvas.rect(395, 90, 55, 40)
    canvas.fill(255, 255, 255)
    canvas.text("?M", 410, 100)

    canvas.fill(150, 0, 200, 220)
    canvas.stroke(255, 255, 255)
    canvas.strokeWeight(2)
    canvas.rect(455, 90, 70, 40)
    canvas.fill(255, 255, 255)
    canvas.text("LAUNCH", 462, 100)
}

def drawWavingTricolorFlag(canvas: CanvasDraw) {
    val fw = 160.0
    val fh = 28.0
    val fx = (cwidth - fw) / 2.0 + 110.0
    val fy = cheight / 2.0 - 10.0

    canvas.stroke(200, 200, 200)
    canvas.strokeWeight(5)
    canvas.line(fx, fy + fh * 3 + 15, fx, fy - 160)

    val stepSize = 4.0
    var x = 0.0
    while (x < fw) {
        val yOffset = math.sin(waveAngle + (x * 0.03)) * 8.0
        canvas.stroke(255, 153, 51)
        canvas.strokeWeight(stepSize + 1)
        canvas.line(fx + x, fy + fh * 2 + yOffset, fx + x, fy + fh * 3 + yOffset)
        canvas.stroke(255, 255, 255)
        canvas.line(fx + x, fy + fh + yOffset, fx + x, fy + fh * 2 + yOffset)
        canvas.stroke(18, 136, 7)
        canvas.line(fx + x, fy + yOffset, fx + x, fy + fh + yOffset)
        x += stepSize
    }

    val chakraX = fx + fw / 2.0
    val chakraY = fy + fh + (fh / 2.0) + (math.sin(waveAngle + ((fw / 2.0) * 0.03)) * 8.0)
    val radius = 11.0
    canvas.stroke(0, 0, 128)
    canvas.strokeWeight(2)
    canvas.noFill()
    canvas.ellipse(chakraX, chakraY, radius * 2, radius * 2)
    canvas.strokeWeight(1)
    for (i <- 0 until 24) {
        val angle = chakraRotation + (i * (2 * math.Pi / 24))
        val xEnd = chakraX + math.cos(angle) * radius
        val yEnd = chakraY + math.sin(angle) * radius
        canvas.line(chakraX, chakraY, xEnd, yEnd)
    }
}

def drawBigOperationSindoorTitle(canvas: CanvasDraw) {
    val startX = 25.0
    val startY = cheight - 60.0
    canvas.fill(160, 160, 160)
    canvas.text("OPERATION", startX, startY)
    canvas.fill(220, 20, 20)
    canvas.text("SINDOOR", startX, startY - 35.0)
}

def viewState(canvas: CanvasDraw) {
    canvas.fill(10, 10, 25, 45)
    canvas.noStroke()
    canvas.rect(0, 0, cwidth, cheight)

    repeatFor(fireworks) { f => f.view(canvas) }

    drawBrahMosLauncher(canvas, missileX, missileY)
    drawArmyTank(canvas, tankX, tankY)
    drawControlButtons(canvas)

    repeatFor(tankShells) { s => s.view(canvas) }
    repeatFor(brahmosShells) { bm => bm.view(canvas) }
    repeatFor(enemyMissiles) { em => em.view(canvas) }

    drawBigOperationSindoorTitle(canvas)
    drawWavingTricolorFlag(canvas)
}

animateWithCanvasDraw { canvas =>
    updateState()
    viewState(canvas)
}