Code Sketch


bhavesh shahare
By: Mhalsakant School
// --- OPERATION SINDOOR: PART 2 - SMOOTH CONTROLS & SLOW ENEMIES ---
cleari()
originBottomLeft()

// --- GAME STATES ---
var gameState = 0
var letterTextTyped = 0
var letterTimer = 0.0

// Game Timer & Target
var gameTimeRemaining = 50.0 
var missilesDestroyed = 0
val targetMissiles = 20

// Hero Tank Entry variables
var heroX = cwidth / 2.0 - 150.0
var heroY = 30.0
var heroState = "WALKING"

// Finale Attack Variables
var finaleMissileX = cwidth / 2.0 + 200.0
var finaleMissileY = cheight + 100.0

val officialLetterText = 
  "TOP SECRET DIRECTIVE: PART 2 - THE RETURN\n" +
  "SUBJECT: ASTRONAUT SUMMONED FOR BORDER TACTICAL COMBAT\n\n" +
  "Welcome back, Commander.\n" +
  "Hostile air strikes & ballistic threats have breached the border.\n" +
  "Your Mission: Hold the defense line for 50 SECONDS and\n" +
  "neutralize at least 20 ENEMY MISSILES.\n\n" +
  "Click 'CONFIRM COMMAND' below to board your Heavy Battle Tank!"

val gravity = Vector2D(0, -0.06)
var waveAngle = 0.0
var chakraRotation = 0.0
var missileX = cwidth / 2.0 - 220.0
var missileY = 140.0
var missileAngle = math.Pi / 2.0
var selectedMissileType = 1

var shootCooldown = 0
var launchCooldown = 0

// --- PARTICLES & EXPLOSIONS SYSTEM ---
class ExplosionSpark(x0: Double, y0: Double, r: Int, g: Int, b: Int) {
    var loc = Vector2D(x0, y0)
    var vel = Vector2D(randomDouble(-8, 8), randomDouble(-8, 8))
    var life = 255.0

    def step() {
        loc = loc + vel
        vel = vel * 0.90
        life -= 15.0
    }

    def view(canvas: CanvasDraw) {
        if (life > 0) {
            canvas.noStroke()
            canvas.fill(r, g, b, life.toInt)
            canvas.ellipse(loc.x, loc.y, random(4, 12), random(4, 12))
        }
    }
}
val sparks = ArrayBuffer.empty[ExplosionSpark]

class Shockwave(val x: Double, val y: Double) {
    var radius = 5.0
    var alpha = 255.0
    var active = true

    def step() {
        radius += 6.0
        alpha -= 18.0
        if (alpha <= 0) active = false
    }

    def view(canvas: CanvasDraw) {
        if (active) {
            canvas.noFill()
            canvas.stroke(255, 100, 0, alpha.toInt)
            canvas.strokeWeight(4)
            canvas.ellipse(x, y, radius * 2, radius * 2)
        }
    }
}
val shockwaves = ArrayBuffer.empty[Shockwave]

// --- MISSILE & TANK SYSTEM ---
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.006
        else if (mType == 3) angle += math.sin(x * 0.05) * 0.03
        
        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) * 160).toInt
            canvas.fill(255, 140, 0, alpha)
            canvas.ellipse(trail(i).x, trail(i).y, 6, 6)
        }

        canvas.pushMatrix()
        canvas.translate(x, y); canvas.rotate(angle)
        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)
        canvas.popMatrix()
    }
}
val strategicMissiles = ArrayBuffer.empty[StrategicMissile]

var tankX = cwidth / 2.0 + 120.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, 160, 0); canvas.noStroke()
        canvas.ellipse(x, y, 10, 10)
    }
}
val tankShells = ArrayBuffer.empty[TankShell]

// ENEMY MISSILE (SLOWER SPEED APPLIED HERE)
class EnemyMissile(var x: Double, var y: Double, var vy: Double) {
    var active = true
    var exploded = false

    def step() {
        if (!exploded) {
            y += vy // Slower downward movement
            if (y < 0) 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(70, 10, 10); canvas.strokeWeight(1)
            canvas.rect(-16, -6, 32, 12)
            canvas.fill(50, 50, 50); canvas.noStroke(); canvas.ellipse(16, 0, 10, 10)
            canvas.popMatrix()
        }
    }
}
val enemyMissiles = ArrayBuffer.empty[EnemyMissile]
var enemySpawnTimer = 0

// --- TRICOLOR 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(7, 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 -= 5.0; velocity = velocity * 0.94 }
        acceleration = Vector2D(0, 0)
        checkExplode()
    }

    def view(canvas: CanvasDraw) {
        canvas.stroke(col)
        canvas.strokeWeight(if (seed) 4 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(60) { 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 createExplosionEffects(x: Double, y: Double) {
    shockwaves.append(new Shockwave(x, y))
    for (i <- 0 until 15) sparks.append(new ExplosionSpark(x, y, 255, random(100, 220), 0))
}

// CONTINUOUS HOLD & CONTROL PROCESSOR
def handleContinuousControls() {
    if (gameState == 2 && isMousePressed) {
        val mx = mouseX; val my = mouseY

        // Tank Controls (Hold to move/rotate)
        if (mx >= 20 && mx <= 70 && my >= 90 && my <= 125) tankX -= 4.0
        else if (mx >= 75 && mx <= 125 && my >= 90 && my <= 125) tankX += 4.0
        else if (mx >= 130 && mx <= 180 && my >= 90 && my <= 125) cannonAngle -= 0.04
        else if (mx >= 185 && mx <= 235 && my >= 90 && my <= 125) cannonAngle += 0.04
        else if (mx >= 240 && mx <= 295 && my >= 90 && my <= 125) {
            if (shootCooldown <= 0) {
                val tbx = tankX; val tby = tankY + 36.0; val bl = 65.0
                val tx = tbx + math.cos(cannonAngle) * bl
                val ty = tby + math.sin(cannonAngle) * bl
                tankShells.append(new TankShell(tx, ty, math.cos(cannonAngle) * 10.0, math.sin(cannonAngle) * 10.0))
                muzzleFlashTimer = 5
                shootCooldown = 12 // Rate of fire control
            }
        }
        // Missile Launcher Controls (Hold to rotate)
        else if (mx >= 300 && mx <= 345 && my >= 90 && my <= 125) missileAngle -= 0.04
        else if (mx >= 350 && mx <= 395 && my >= 90 && my <= 125) missileAngle += 0.04
        else if (mx >= 400 && mx <= 445 && my >= 90 && my <= 125) selectedMissileType = 1
        else if (mx >= 450 && mx <= 495 && my >= 90 && my <= 125) selectedMissileType = 2
        else if (mx >= 500 && mx <= 545 && my >= 90 && my <= 125) selectedMissileType = 3
        else if (mx >= 555 && mx <= 630 && my >= 90 && my <= 125) {
            if (launchCooldown <= 0) {
                val mTipX = missileX + math.cos(missileAngle) * 55.0
                val mTipY = missileY + math.sin(missileAngle) * 55.0
                strategicMissiles.append(new StrategicMissile(mTipX, mTipY, math.cos(missileAngle) * 7.5, math.sin(missileAngle) * 7.5, missileAngle, selectedMissileType))
                launchCooldown = 18
            }
        }
    }
}

def updateState() {
    if (shootCooldown > 0) shootCooldown -= 1
    if (launchCooldown > 0) launchCooldown -= 1

    handleContinuousControls()

    if (gameState == 0) {
        letterTimer += 0.04
        if (letterTimer >= 0.03 && letterTextTyped < officialLetterText.length) {
            letterTextTyped += 1; letterTimer = 0.0
        }
        return
    }

    if (gameState == 1) {
        if (heroX < tankX - 15) {
            heroX += 2.5
        } else {
            heroState = "BOARDED"
            gameState = 2
        }
        return
    }

    if (gameState == 2) {
        gameTimeRemaining -= 0.033
        if (gameTimeRemaining <= 0.0) {
            gameTimeRemaining = 0.0
            gameState = 3 // Trigger High-Speed Finale Attack
        }

        fireworks.filterInPlace(f => !f.done)
        if (randomDouble(1) < 0.06) fireworks.append(new Firework())
        repeatFor(fireworks)(_.step())

        sparks.filterInPlace(_.life > 0); repeatFor(sparks)(_.step())
        shockwaves.filterInPlace(_.active); repeatFor(shockwaves)(_.step())

        waveAngle += 0.08; chakraRotation += 0.05

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

        enemySpawnTimer += 1
        if (enemySpawnTimer >= 80) {
            enemySpawnTimer = 0
            val ex = random(100, cwidth - 100)
            enemyMissiles.append(new EnemyMissile(ex, cheight + 20.0, -0.45)) // VERY SLOW MISSILE SPEED (-0.45)
        }

        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) < 30 && math.abs(ts.y - em.y) < 30) {
                        em.exploded = true; ts.active = false
                        missilesDestroyed += 1
                        createExplosionEffects(em.x, em.y)
                    }
                }
                repeatFor(strategicMissiles) { sm =>
                    if (sm.active && math.abs(sm.x - em.x) < 40 && math.abs(sm.y - em.y) < 40) {
                        em.exploded = true; sm.active = false
                        missilesDestroyed += 1
                        createExplosionEffects(em.x, em.y)
                    }
                }
            }
        }
    }

    // STATE 3: FINALE MISSILE STRIKE & GAME END GUARANTEE
    if (gameState == 3) {
        val targetX = tankX
        val targetY = tankY + 20.0
        
        finaleMissileX += (targetX - finaleMissileX) * 0.2
        finaleMissileY -= 15.0

        if (finaleMissileY <= targetY + 15.0) {
            createExplosionEffects(tankX, tankY + 30.0)
            for (i <- 0 until 60) sparks.append(new ExplosionSpark(tankX, tankY + 30.0, 255, random(50, 255), 0))
            gameState = 4 // GUARANTEED END TO STATE 4
        }
    }

    if (gameState == 4) {
        sparks.filterInPlace(_.life > 0); repeatFor(sparks)(_.step())
        shockwaves.filterInPlace(_.active); repeatFor(shockwaves)(_.step())
    }
}

// --- DRAWING FUNCTIONS ---
def drawHeroAstronaut(canvas: CanvasDraw, x: Double, y: Double) {
    if (heroState == "BOARDED") return
    canvas.fill(240, 240, 255); canvas.ellipse(x, y + 22, 14, 14)
    canvas.fill(0, 150, 255); canvas.ellipse(x, y + 23, 8, 6)
    canvas.fill(40, 70, 40); canvas.rect(x - 5, y, 10, 16)
    canvas.stroke(20, 20, 20); canvas.strokeWeight(2)
    canvas.line(x - 3, y, x - 5, y - 8); canvas.line(x + 3, y, x + 5, y - 8)
}

def drawLauncherPad(canvas: CanvasDraw, mx: Double, my: Double) {
    canvas.fill(40, 45, 55); canvas.stroke(20, 25, 30); canvas.strokeWeight(2)
    canvas.ellipse(mx, my, 58, 58)
    
    canvas.pushMatrix()
    canvas.translate(mx, my); canvas.rotate(missileAngle)
    canvas.fill(60, 75, 70); canvas.stroke(30, 40, 35); canvas.strokeWeight(2)
    canvas.rect(-14, -16, 62, 32)
    canvas.fill(220, 30, 30); canvas.rect(16, -16, 8, 32)
    canvas.popMatrix()
}

def drawRealisticArmyTank(canvas: CanvasDraw, tx: Double, ty: Double) {
    if (gameState == 4) return // Hide tank after explosion

    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)

    val turretBaseX = tx; val turretBaseY = ty + 44.0; val barrelLength = 65.0
    canvas.stroke(25, 50, 20); canvas.strokeWeight(8)
    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, 160, 0, 230); canvas.ellipse(tipX, tipY, 40, 40)
    }
}

def drawControlButtons(canvas: CanvasDraw) {
    canvas.fill(15, 20, 35, 220); canvas.stroke(0, 255, 200); canvas.strokeWeight(1)
    canvas.rect(15, 85, 665, 50)

    canvas.fill(50, 50, 80); canvas.rect(20, 95, 50, 30); canvas.fill(cm.rgb(255, 255, 255)); canvas.text("<- T", 30, 105)
    canvas.fill(50, 50, 80); canvas.rect(75, 95, 50, 30); canvas.fill(cm.rgb(255, 255, 255)); canvas.text("T ->", 85, 105)
    canvas.fill(70, 40, 40); canvas.rect(130, 95, 50, 30); canvas.fill(cm.rgb(255, 255, 255)); canvas.text("<- C", 140, 105)
    canvas.fill(70, 40, 40); canvas.rect(185, 95, 50, 30); canvas.fill(cm.rgb(255, 255, 255)); canvas.text("C ->", 195, 105)
    canvas.fill(200, 70, 0); canvas.rect(240, 95, 55, 30); canvas.fill(cm.rgb(255, 255, 255)); canvas.text("FIRE", 252, 105)

    canvas.fill(40, 90, 90); canvas.rect(300, 95, 45, 30); canvas.fill(cm.rgb(255, 255, 255)); canvas.text("<- M", 308, 105)
    canvas.fill(40, 90, 90); canvas.rect(350, 95, 45, 30); canvas.fill(cm.rgb(255, 255, 255)); canvas.text("M ->", 358, 105)

    canvas.fill(if (selectedMissileType == 1) 180 else 80, 30, 120); canvas.rect(400, 95, 45, 30); canvas.fill(cm.rgb(255, 255, 255)); canvas.text("BRM", 408, 105)
    canvas.fill(if (selectedMissileType == 2) 180 else 80, 30, 120); canvas.rect(450, 95, 45, 30); canvas.fill(cm.rgb(255, 255, 255)); canvas.text("AGN", 458, 105)
    canvas.fill(if (selectedMissileType == 3) 180 else 80, 30, 120); canvas.rect(500, 95, 45, 30); canvas.fill(cm.rgb(255, 255, 255)); canvas.text("SUD", 508, 105)

    canvas.fill(140, 0, 180); canvas.rect(555, 95, 75, 30); canvas.fill(cm.rgb(255, 255, 255)); canvas.text("LAUNCH", 568, 105)
}

def drawHUD(canvas: CanvasDraw) {
    canvas.fill(10, 15, 30, 230); canvas.stroke(255, 215, 0); canvas.strokeWeight(1.5)
    canvas.rect(20, cheight - 50, 300, 38)
    canvas.fill(255, 215, 0)
    canvas.text(s"? TIME: ${gameTimeRemaining.toInt}s | ? KILLS: $missilesDestroyed / $targetMissiles", 32, cheight - 38)
}

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

    canvas.stroke(210, 210, 210); canvas.strokeWeight(6)
    canvas.line(fx, fy + fh * 3 + 15, fx, fy - 170)

    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, 130); canvas.strokeWeight(2); canvas.noFill()
    canvas.ellipse(chakraX, chakraY, radius * 2, radius * 2)
}

def drawIntroLetter(canvas: CanvasDraw) {
    canvas.fill(5, 8, 20); canvas.noStroke(); canvas.rect(0, 0, cwidth, cheight)
  
    val (lx, ly, lw, lh) = (cwidth / 2.0 - 270.0, cheight / 2.0 - 150.0, 540.0, 320.0)
    canvas.fill(15, 25, 45, 245); canvas.stroke(0, 255, 200); canvas.strokeWeight(2.5); canvas.rect(lx, ly, lw, lh)
  
    canvas.fill(255, 215, 0); canvas.text("? PART 2: THE ASTRONAUT RETURNS AS ARMY HERO ??", lx + 60, ly + 25)
  
    if (letterTextTyped > 0) {
        val printedText = officialLetterText.substring(0, letterTextTyped)
        val lines = printedText.split("\n")
        var currY = ly + 55.0
        for (line <- lines) {
            canvas.fill(220, 240, 255)
            canvas.text(line, lx + 20, currY)
            currY += 17.0
        }
    }

    if (letterTextTyped >= officialLetterText.length) {
        val btnX = lx + 170.0; val btnY = ly + 20.0
        canvas.fill(0, 180, 120); canvas.stroke(255, 255, 255); canvas.strokeWeight(2.0)
        canvas.rect(btnX, btnY, 200, 36)
        canvas.fill(255, 255, 255); canvas.text("CONFIRM COMMAND ?", btnX + 22, btnY + 23)

        if (isMousePressed && mouseX >= btnX && mouseX <= btnX + 200 && mouseY >= btnY && mouseY <= btnY + 36) {
            gameState = 1
        }
    }
}

// MARTYRDOM SCREEN
def drawMartyrTributeScreen(canvas: CanvasDraw) {
    canvas.fill(10, 5, 5, 240); canvas.noStroke(); canvas.rect(0, 0, cwidth, cheight)
    
    val (cx, cy) = (cwidth / 2.0, cheight / 2.0)
    canvas.fill(255, 50, 50); canvas.text("? ENEMY SUPER-MISSILE IMPACT DETECTED ?", cx - 180, cy + 90)
    
    canvas.fill(255, 215, 0)
    canvas.text("?? A BRAVE SOLDIER HAS BEEN MARTYRED ??", cx - 170, cy + 40)
    
    canvas.fill(240, 240, 240)
    canvas.text(s"FINAL COMBAT STATS: Neutralized $missilesDestroyed Enemy Missiles", cx - 160, cy)
    canvas.text("His sacrifice protected the nation till the final breath.", cx - 165, cy - 30)
    
    canvas.fill(0, 255, 200); canvas.text("SALUTE TO OUR BRAVE HERO! AMAR RAHE! ?", cx - 150, cy - 80)
}

def viewState(canvas: CanvasDraw) {
    if (gameState == 0) {
        drawIntroLetter(canvas); return
    }

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

    repeatFor(fireworks)(_.view(canvas))

    drawLauncherPad(canvas, missileX, missileY)
    drawRealisticArmyTank(canvas, tankX, tankY)
    drawHeroAstronaut(canvas, heroX, heroY)

    if (gameState == 2) {
        drawControlButtons(canvas)
        drawHUD(canvas)
    }

    repeatFor(sparks)(_.view(canvas))
    repeatFor(shockwaves)(_.view(canvas))

    repeatFor(tankShells)(_.view(canvas))
    repeatFor(strategicMissiles)(_.view(canvas))
    repeatFor(enemyMissiles)(_.view(canvas))

    if (gameState == 3) {
        canvas.pushMatrix()
        canvas.translate(finaleMissileX, finaleMissileY); canvas.rotate(-math.Pi / 2)
        canvas.fill(255, 0, 0); canvas.rect(-30, -12, 60, 24)
        canvas.fill(255, 255, 0); canvas.ellipse(30, 0, 24, 24)
        canvas.popMatrix()
    }

    if (gameState == 4) {
        drawMartyrTributeScreen(canvas)
    } else {
        drawWavingTricolorFlag(canvas)
    }
}

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