Code Sketch


bhavesh shahare
By: Mhalsakant School
Category: Programming
// --- OPERATION SINDOOR PART 2: THE BORDER WAR ---
cleari()
originBottomLeft()

// --- GAME STATES ---
// 0: Intro Part 2, 1: Confidential Letter, 2: Cutscene (Hatch Enter), 3: Gameplay, 4: Victory, 5: Defeat
var gameState = 0
var letterTimer = 0 // ???????? ??? ????????????? ?????

// --- CUTSCENE VARIABLES ---
var cutsceneTankX = cwidth + 120.0
var heroX = -50.0
var cutscenePhase = 1 
var hatchOpen = false
var heroInside = false

// --- GAMEPLAY & TIMER VARIABLES ---
var timer = 50.0 // 50 Seconds Countdown
val targetKills = 12
var interceptedCount = 0
var baseHealth = 100

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

// Player Tank & Launcher
var playerTankX = cwidth / 2.0
val playerTankY = 30.0
var playerCannonAngle = math.Pi / 2.0
var muzzleFlashTimer = 0

var missileX = 120.0
var missileY = 150.0
var missileAngle = math.Pi / 2.0
var selectedMissileType = 1

// --- AUTO-BOT ALLY TANKS ---
class AllyTank(val x: Double, val y: Double) {
    var cannonAngle = math.Pi / 2.0
    var fireTimer = random(30, 80)

    def step(targets: ArrayBuffer[EnemyMissile], shells: ArrayBuffer[TankShell]) {
        fireTimer -= 1
        var nearest: EnemyMissile = null
        var minDist = 9999.0
        repeatFor(targets) { em =>
            val d = math.hypot(em.x - x, em.y - y)
            if (d < minDist && em.y > y) {
                minDist = d
                nearest = em
            }
        }

        if (nearest != null) {
            val dx = nearest.x - x
            val dy = nearest.y - y
            cannonAngle = math.atan2(dy, dx)
        }

        if (fireTimer <= 0 && nearest != null) {
            val tx = x + math.cos(cannonAngle) * 50.0
            val ty = y + math.sin(cannonAngle) * 50.0
            shells.append(new TankShell(tx, ty, math.cos(cannonAngle) * 9.0, math.sin(cannonAngle) * 9.0, true))
            fireTimer = random(40, 90)
        }
    }

    def view(canvas: CanvasDraw) {
        canvas.fill(40, 50, 40); canvas.stroke(10, 20, 10); canvas.strokeWeight(1)
        canvas.rect(x - 35, y, 70, 16)
        canvas.fill(60, 75, 55); canvas.rect(x - 28, y + 16, 56, 14)
        canvas.fill(45, 60, 40); canvas.ellipse(x, y + 30, 32, 18)

        canvas.stroke(30, 45, 25); canvas.strokeWeight(6)
        canvas.line(x, y + 30, x + math.cos(cannonAngle) * 45.0, y + math.sin(cannonAngle) * 45.0)
    }
}
val allyTanks = ArrayBuffer(new AllyTank(150, 30), new AllyTank(cwidth - 150, 30), new AllyTank(cwidth - 320, 30))

// --- BLAST PARTICLES ---
class BlastParticle(x0: Double, y0: Double, r: Int, g: Int, b: Int) {
    var loc = Vector2D(x0, y0)
    var vel = Vector2D(randomDouble(-5, 5), randomDouble(-5, 5))
    var life = 255.0

    def step() { loc = loc + vel; life -= 12.0 }

    def view(canvas: CanvasDraw) {
        if (life > 0) {
            canvas.noStroke()
            canvas.fill(r, g, b, life.toInt)
            canvas.ellipse(loc.x, loc.y, 8, 8)
        }
    }
}
val blastParticles = ArrayBuffer.empty[BlastParticle]

// --- INDIAN MISSILES ---
class StrategicMissile(var x: Double, var y: Double, val vx: Double, val vy: Double, var angle: Double, val mType: Int) {
    var active = true
    var trail = ArrayBuffer.empty[Vector2D]

    def step() {
        if (mType == 2) angle += 0.004
        else if (mType == 3) angle += math.sin(x * 0.05) * 0.02
        
        val speed = math.sqrt(vx*vx + vy*vy)
        x += math.cos(angle) * speed
        y += math.sin(angle) * speed

        trail.append(Vector2D(x, y))
        if (trail.length > 18) trail.remove(0)

        if (x < -100 || x > cwidth + 100 || y < -100 || y > cheight + 100) active = false
    }

    def view(canvas: CanvasDraw) {
        canvas.noStroke()
        for (i <- 0 until trail.length) {
            val alpha = ((i.toDouble / trail.length) * 150).toInt
            canvas.fill(255, 120, 0, alpha)
            canvas.ellipse(trail(i).x, trail(i).y, 8, 8)
        }

        canvas.pushMatrix()
        canvas.translate(x, y); canvas.rotate(angle)

        if (mType == 1) { // BRAHMOS
            canvas.fill(220, 220, 230); canvas.stroke(40, 40, 50); canvas.strokeWeight(1); canvas.rect(-20, -7, 40, 14)
            canvas.fill(200, 20, 20); canvas.noStroke(); canvas.ellipse(20, 0, 14, 14)
        } else if (mType == 2) { // AGNI
            canvas.fill(50, 55, 65); canvas.stroke(20, 20, 20); canvas.rect(-25, -10, 50, 20)
            canvas.fill(240, 150, 0); canvas.ellipse(25, 0, 16, 16)
        } else { // SUDARSHAN
            canvas.fill(20, 140, 200); canvas.stroke(10, 50, 90); canvas.rect(-18, -6, 36, 12)
            canvas.fill(0, 255, 255); canvas.noStroke(); canvas.ellipse(18, 0, 12, 12)
        }

        canvas.fill(255, 180, 0, 230); canvas.noStroke(); canvas.ellipse(-26, 0, 14, 10)
        canvas.popMatrix()
    }
}
val strategicMissiles = ArrayBuffer.empty[StrategicMissile]

// --- TANK SHELLS ---
class TankShell(var x: Double, var y: Double, val vx: Double, val vy: Double, val isAlly: Boolean) {
    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) {
        if (isAlly) canvas.fill(0, 230, 255) else canvas.fill(255, 140, 0)
        canvas.noStroke(); canvas.ellipse(x, y, 9, 9)
    }
}
val tankShells = ArrayBuffer.empty[TankShell]

// --- PAKISTAN MISSILES ---
class EnemyMissile(var x: Double, var y: Double, var vy: Double, val mName: String) {
    var active = true
    var exploded = false
    var explosionTimer = 0

    def step() {
        if (!exploded) {
            y += vy
            if (y < 15) { active = false; baseHealth -= 12 }
        } 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(20, 90, 35); canvas.stroke(10, 40, 15); canvas.strokeWeight(1)
            canvas.rect(-18, -7, 36, 14)
            canvas.fill(250, 250, 250); canvas.noStroke(); canvas.ellipse(18, 0, 12, 12)
            canvas.popMatrix()

            canvas.fill(255, 80, 80); canvas.text(mName, x - 20, y + 18)
        } else {
            canvas.noStroke(); canvas.fill(255, 100, 0, 200)
            canvas.ellipse(x, y, 50 + explosionTimer * 2, 50 + explosionTimer * 2)
        }
    }
}
val enemyMissiles = ArrayBuffer.empty[EnemyMissile]
var enemySpawnTimer = 0

// --- FIREWORKS ---
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(6, 12)) else Vector2D(randomNormalDouble, randomNormalDouble) * randomDouble(2, 5)
    private var acceleration = Vector2D(0, 0)
    var lifespan = 255.0
    private var exploded = false

    def applyForce(force: Vector2D) { acceleration = 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 = velocity + acceleration; location = location + velocity
        if (!seed) { lifespan -= 4; velocity = velocity * 0.94 }
        acceleration = Vector2D(0, 0); checkExplode()
    }

    def view(canvas: CanvasDraw) {
        canvas.stroke(col); canvas.strokeWeight(if (seed) 5 else 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]

// --- SOLDIER & TANK WITH HATCH ---
def drawArmyMan(canvas: CanvasDraw, x: Double, y: Double) {
    canvas.pushMatrix(); canvas.translate(x, y)
    canvas.fill(20, 20, 20); canvas.noStroke(); canvas.rect(-7, 0, 5, 8); canvas.rect(2, 0, 5, 8)
    canvas.fill(40, 60, 30); canvas.rect(-7, 8, 6, 20); canvas.rect(1, 8, 6, 20)
    canvas.fill(55, 80, 40); canvas.rect(-9, 28, 18, 22)
    canvas.fill(220, 170, 130); canvas.ellipse(0, 56, 12, 14)
    canvas.fill(30, 50, 20); canvas.ellipse(0, 60, 16, 10)
    canvas.popMatrix()
}

def drawCutsceneTank(canvas: CanvasDraw, tx: Double, ty: Double) {
    canvas.fill(30, 33, 38); canvas.stroke(10, 10, 12); canvas.strokeWeight(1)
    canvas.rect(tx - 50, ty, 100, 22)
    canvas.fill(70, 75, 70)
    for (i <- -4 to 4) canvas.ellipse(tx + (i * 11), ty + 11, 9, 9)
    
    canvas.fill(50, 80, 45); canvas.rect(tx - 40, ty + 22, 80, 20)
    canvas.fill(40, 70, 35); canvas.ellipse(tx, ty + 44, 44, 24)

    if (hatchOpen) {
        canvas.fill(15, 15, 15); canvas.ellipse(tx, ty + 53, 20, 8)
        canvas.fill(60, 60, 60); canvas.rect(tx + 8, ty + 50, 12, 12)
    } else {
        canvas.fill(25, 30, 25); canvas.ellipse(tx, ty + 53, 22, 8)
    }

    canvas.stroke(25, 50, 20); canvas.strokeWeight(8)
    canvas.line(tx, ty + 44, tx - 60, ty + 44)
}

// --- CUTSCENE ANIMATION ---
def updateCutscene() {
    val targetTankX = cwidth / 2.0 + 80.0
    val targetHeroX = targetTankX - 5.0

    if (cutscenePhase == 1) {
        if (cutsceneTankX > targetTankX) cutsceneTankX -= 4.0
        if (heroX < targetHeroX) heroX += 3.0
        if (cutsceneTankX <= targetTankX && heroX >= targetHeroX) cutscenePhase = 2
    } else if (cutscenePhase == 2) {
        hatchOpen = true
        cutscenePhase = 3
    } else if (cutscenePhase == 3) {
        heroInside = true
        cutscenePhase = 4
    } else if (cutscenePhase == 4) {
        hatchOpen = false
        cutscenePhase = 5
    } else if (cutscenePhase == 5) {
        cutsceneTankX -= 5.0
        if (cutsceneTankX < -150) gameState = 3
    }
}

// --- UPDATE GAME STATE ---
def updateState() {
    if (gameState == 0) {
        if (isMousePressed) {
            gameState = 1
            letterTimer = 60 // ? ???????? ??? ????
        }
        return
    }
    if (gameState == 1) {
        if (letterTimer > 0) letterTimer -= 1
        else if (isMousePressed) gameState = 2
        return
    }
    if (gameState == 2) {
        updateCutscene()
        return
    }
    if (gameState == 4 || gameState == 5) {
        fireworks.filterInPlace(f => !f.done)
        if (randomDouble(1) < 0.15) fireworks.append(new Firework())
        repeatFor(fireworks) { f => f.step() }
        return
    }

    // GAMEPLAY
    timer -= 0.03
    if (timer <= 0) {
        if (interceptedCount >= targetKills) gameState = 4 else gameState = 5
    }
    if (baseHealth <= 0) gameState = 5

    waveAngle += 0.08; chakraRotation += 0.05
    blastParticles.filterInPlace(_.life > 0); repeatFor(blastParticles)(_.step())

    val dx = mouseX - playerTankX
    val dy = mouseY - (playerTankY + 44.0)
    playerCannonAngle = math.atan2(dy, dx)

    if (isMousePressed && mouseY > 140) {
        val tx = playerTankX + math.cos(playerCannonAngle) * 60.0
        val ty = (playerTankY + 44.0) + math.sin(playerCannonAngle) * 60.0
        tankShells.append(new TankShell(tx, ty, math.cos(playerCannonAngle) * 10.0, math.sin(playerCannonAngle) * 10.0, false))
        muzzleFlashTimer = 6
    }
    if (muzzleFlashTimer > 0) muzzleFlashTimer -= 1

    if (mouseX >= 400 && mouseX <= 445 && mouseY >= 90 && mouseY <= 130 && isMousePressed) selectedMissileType = 1
    else if (mouseX >= 450 && mouseX <= 495 && mouseY >= 90 && mouseY <= 130 && isMousePressed) selectedMissileType = 2
    else if (mouseX >= 500 && mouseX <= 545 && mouseY >= 90 && mouseY <= 130 && isMousePressed) selectedMissileType = 3
    else if (mouseX >= 555 && mouseX <= 630 && mouseY >= 90 && mouseY <= 130 && isMousePressed) {
        val mTipX = missileX + math.cos(missileAngle) * 50.0
        val mTipY = missileY + math.sin(missileAngle) * 50.0
        strategicMissiles.append(new StrategicMissile(mTipX, mTipY, math.cos(missileAngle) * 6.5, math.sin(missileAngle) * 6.5, missileAngle, selectedMissileType))
    }

    repeatFor(allyTanks) { at => at.step(enemyMissiles, tankShells) }

    enemySpawnTimer += 1
    if (enemySpawnTimer >= 60) {
        enemySpawnTimer = 0
        val names = Array("PAKF-16", "BABUR", "SHAHEEN-3")
        enemyMissiles.append(new EnemyMissile(random(100, cwidth - 100), cheight + 20.0, -1.3, names(random(names.length))))
    }

    tankShells.filterInPlace(_.active); repeatFor(tankShells)(_.step())
    strategicMissiles.filterInPlace(_.active); repeatFor(strategicMissiles)(_.step())
    enemyMissiles.filterInPlace(_.active); repeatFor(enemyMissiles)(_.step())

    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
                    interceptedCount += 1
                    for (i <- 0 until 15) blastParticles.append(new BlastParticle(em.x, em.y, 255, 140, 0))
                }
            }
            repeatFor(strategicMissiles) { sm =>
                if (sm.active && math.abs(sm.x - em.x) < 35 && math.abs(sm.y - em.y) < 35) {
                    em.exploded = true; sm.active = false
                    interceptedCount += 1
                    for (i <- 0 until 25) blastParticles.append(new BlastParticle(em.x, em.y, 255, 255, 0))
                }
            }
        }
    }
}

// --- SCREENS ---
def drawIntroPart2(canvas: CanvasDraw) {
    canvas.fill(10, 10, 15); canvas.rect(0, 0, cwidth, cheight)
    canvas.fill(255, 60, 60); canvas.text("THIS IS A SECOND PART", cwidth / 2.0 - 120, cheight / 2.0 + 30)
    canvas.fill(255, 215, 0); canvas.text("OPERATION SINDOOR: BORDER WARRIORS", cwidth / 2.0 - 160, cheight / 2.0 - 10)
    canvas.fill(200, 200, 200); canvas.text("CLICK ANYWHERE TO READ MISSION BRIEFING", cwidth / 2.0 - 150, cheight / 2.0 - 60)
}

def drawLetterScreen(canvas: CanvasDraw) {
    canvas.fill(245, 240, 220); canvas.stroke(100, 80, 50); canvas.strokeWeight(3)
    val lx = cwidth / 2.0 - 270.0; val ly = cheight / 2.0 - 210.0
    canvas.rect(lx, ly, 540, 430)

    canvas.fill(180, 30, 30); canvas.noStroke(); canvas.rect(lx, ly + 380, 540, 50)
    canvas.fill(255, 255, 255); canvas.text("TOP SECRET // DEFENCE HQ INDIA - PART 2", lx + 15, ly + 398)

    canvas.fill(20, 20, 30)
    canvas.text("COMMANDER RECALL & EMERGENCY ORDER", lx + 20, ly + 345)
    canvas.text("Enemy forces have launched high-speed strategic missiles", lx + 20, ly + 310)
    canvas.text("(PAKF-16, BABUR, SHAHEEN-3) across western borders!", lx + 20, ly + 285)

    canvas.fill(180, 30, 30)
    canvas.text("YOUR OBJECTIVE:", lx + 20, ly + 245)
    canvas.fill(20, 20, 30)
    canvas.text("1. Board the lead Heavy Main Battle Tank.", lx + 20, ly + 220)
    canvas.text("2. Command 3 Auto Support Tanks & Air Defence Launcher.", lx + 20, ly + 195)
    canvas.text("3. Intercept & Destroy at least 12 PAK missiles in 50s!", lx + 20, ly + 170)

    if (letterTimer > 0) {
        canvas.fill(150, 150, 150); canvas.rect(lx + 120, ly + 20, 300, 35)
        canvas.fill(255, 255, 255); canvas.text("PLEASE READ THE LETTER...", lx + 165, ly + 32)
    } else {
        canvas.fill(20, 140, 40); canvas.rect(lx + 120, ly + 20, 300, 35)
        canvas.fill(255, 255, 255); canvas.text("CLICK TO ACCEPT & DEPLOY", lx + 155, ly + 32)
    }
}

def drawPlayerTank(canvas: CanvasDraw, tx: Double, ty: Double) {
    canvas.fill(30, 33, 38); canvas.stroke(10, 10, 12); canvas.strokeWeight(1)
    canvas.rect(tx - 45, ty, 90, 20)
    canvas.fill(70, 75, 70)
    for (i <- -3 to 3) canvas.ellipse(tx + (i * 12), ty + 10, 9, 9)
    
    canvas.fill(80, 110, 65); canvas.rect(tx - 35, ty + 20, 70, 18)
    canvas.fill(50, 85, 45); canvas.ellipse(tx, ty + 38, 40, 22)

    val turretBaseX = tx; val turretBaseY = ty + 38.0
    canvas.stroke(25, 50, 20); canvas.strokeWeight(8)
    val tipX = turretBaseX + math.cos(playerCannonAngle) * 55.0
    val tipY = turretBaseY + math.sin(playerCannonAngle) * 55.0
    canvas.line(turretBaseX, turretBaseY, tipX, tipY)

    if (muzzleFlashTimer > 0) {
        canvas.noStroke(); canvas.fill(255, 140, 0, 230); canvas.ellipse(tipX, tipY, 30, 30)
    }
}

def drawHUD(canvas: CanvasDraw) {
    canvas.fill(255, 215, 0)
    canvas.text(s"TIME: ${math.max(0, timer.toInt)}s", 20, cheight - 20)
    canvas.text(s"PAK MISSILES DESTROYED: $interceptedCount / $targetKills", 140, cheight - 20)
    canvas.text(s"DEFENCE: $baseHealth%", 420, cheight - 20)

    canvas.fill(20, 20, 35, 200); canvas.rect(390, 85, 250, 50)
    canvas.fill(if (selectedMissileType == 1) 200 else 80, 30, 30); canvas.rect(400, 95, 45, 30); canvas.fill(255, 255, 255); canvas.text("BRM", 408, 105)
    canvas.fill(if (selectedMissileType == 2) 200 else 80, 30, 30); canvas.rect(450, 95, 45, 30); canvas.fill(255, 255, 255); canvas.text("AGN", 458, 105)
    canvas.fill(if (selectedMissileType == 3) 200 else 80, 30, 30); canvas.rect(500, 95, 45, 30); canvas.fill(255, 255, 255); canvas.text("SUD", 508, 105)
    canvas.fill(140, 0, 180); canvas.rect(555, 95, 75, 30); canvas.fill(255, 255, 255); canvas.text("LAUNCH", 568, 105)
}

def viewState(canvas: CanvasDraw) {
    if (gameState == 0) { drawIntroPart2(canvas); return }
    if (gameState == 1) { drawLetterScreen(canvas); return }
    if (gameState == 2) {
        canvas.fill(30, 30, 40); canvas.rect(0, 0, cwidth, cheight)
        canvas.fill(50, 40, 30); canvas.rect(0, 0, cwidth, 35)
        drawCutsceneTank(canvas, cutsceneTankX, 30)
        if (!heroInside) drawArmyMan(canvas, heroX, 30)
        canvas.fill(255, 255, 255); canvas.text("SOLDIER ENTERING TANK HATCH...", cwidth / 2.0 - 110, cheight - 50)
        return
    }

    canvas.fill(10, 10, 25, 50); canvas.noStroke(); canvas.rect(0, 0, cwidth, cheight)

    if (gameState == 3) {
        repeatFor(blastParticles) { bp => bp.view(canvas) }
        repeatFor(allyTanks) { at => at.view(canvas) }
        drawPlayerTank(canvas, playerTankX, playerTankY)
        
        repeatFor(tankShells) { s => s.view(canvas) }
        repeatFor(strategicMissiles) { sm => sm.view(canvas) }
        repeatFor(enemyMissiles) { em => em.view(canvas) }
        drawHUD(canvas)
    } else if (gameState == 4) {
        repeatFor(fireworks) { f => f.view(canvas) }
        canvas.fill(0, 0, 0, 180); canvas.rect(0, 0, cwidth, cheight)
        canvas.fill(255, 153, 51); canvas.text("VICTORY! PAKISTAN MISSILE THREAT DESTROYED!", cwidth / 2.0 - 180, cheight / 2.0 + 30)
        canvas.fill(18, 136, 7); canvas.text("INDIA IS SAFE! JAI HIND!", cwidth / 2.0 - 90, cheight / 2.0 - 20)
    } else if (gameState == 5) {
        canvas.fill(0, 0, 0, 200); canvas.rect(0, 0, cwidth, cheight)
        canvas.fill(255, 50, 50); canvas.text("MISSION FAILED! BORDER BREACHED!", cwidth / 2.0 - 130, cheight / 2.0)
    }
}

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