Code Sketch


sare jaha se aacha
By: Mhalsakant School
// --- SARE JAHAN SE ACHHA WITH ROTATING ASHOKA CHAKRA & STARBURSTS ---
cleari()
originBottomLeft()

// ?. "???? ???? ?? ?????" ?? ????? ????? (Midi Tune)
setNoteInstrument(Instrument.FLUTE)
val sareJahanTune = Array(
    (60, 250), (62, 250), (64, 250), (65, 250), (64, 300), (62, 250), (60, 300), // ??-?? ?-??? ?? ?-????
    (60, 250), (64, 250), (67, 300), (65, 300), (64, 400),                      // ????-????-??? ?-??-??
    (65, 250), (67, 250), (69, 250), (69, 300), (67, 250), (65, 300),             // ?? ???-??-?? ??? ??-??
    (64, 250), (65, 250), (67, 300), (65, 250), (64, 250), (62, 250), (60, 400)  // ?? ??-?-??-??? ?-??-??
)
var tuneIndex = 0

// "???? ???? ?? ?????" ?? ??????? (Letter by Letter)
val lyricsText = 
    "Sare Jahan Se Achha\n" +
    "Hindostan Hamara!\n\n" +
    "Hum Bulbule Hain Iski,\n" +
    "Yeh Gulsitan Hamara!\n\n" +
    "Gurbat Mein Ho Agar Hum,\n" +
    "Rehta Hai Dil Watan Mein,\n\n" +
    "Samjho Wahi Humen Bhi,\n" +
    "Dil Ho Jahan Hamara!\n\n" +
    "SARE JAHAN SE ACHHA..."

var charIndex = 0

def playSareJahanStep() {
    if (tuneIndex < sareJahanTune.length) {
        val (note, duration) = sareJahanTune(tuneIndex)
        playNote(note, duration)
        tuneIndex = (tuneIndex + 1) % sareJahanTune.length
    }
}

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

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

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

    def randomStarburstVector: Vector2D = {
        val angle = randomDouble(0, 2 * math.Pi)
        val speed = randomDouble(1.5, 4.5)
        Vector2D(math.cos(angle) * speed, math.sin(angle) * speed)
    }

    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() {
        playSareJahanStep()
        exploded = true
        
        // ?????? ?-? ?????? ???? ???? ????
        if (charIndex < lyricsText.length) {
            charIndex = math.min(charIndex + 3, lyricsText.length)
        }
    }

    def step() {
        velocity += acceleration
        location += velocity
        if (!seed) {
            lifespan -= 6
            velocity *= 0.94
        }
        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 starColor = tricolorPalette(random(tricolorPalette.length))
    private val firework = new StarParticle(random(cwidth), 0, starColor, true)
    private val particles = ArrayBuffer.empty[StarParticle]

    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(70) {
                    particles.append(
                        new StarParticle(firework.location.x, firework.location.y, starColor, 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 // ???? ???? ????????? ?????
}

// ?. ??????? ???? + ??????? ?? ?????? ???? ????
def drawInteractiveFlag(canvas: CanvasDraw) {
    val fw = 170.0
    val fh = 30.0
    val fx = (cwidth - fw) / 2.0 + 110.0
    val fy = cheight / 2.0 - 20.0

    // ????????? (Flag Pole)
    canvas.stroke(200, 200, 200)
    canvas.strokeWeight(5)
    canvas.line(fx, fy + fh * 3 + 15, fx, fy - 140)

    // ??????? ?????
    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
    }

    // ??????? ???? ???? (Spinning Ashoka Chakra)
    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 = 12.0

    canvas.stroke(0, 0, 128)
    canvas.strokeWeight(2)
    canvas.noFill()
    canvas.ellipse(chakraX, chakraY, radius * 2, radius * 2)

    // ????????? ?? ??? (24 Rotating Spokes)
    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 drawSareJahanLyrics(canvas: CanvasDraw) {
    val visibleSubstring = lyricsText.substring(0, charIndex)
    val lines = visibleSubstring.split("\n")
    
    val startX = 25.0
    var startY = cheight - 70.0

    for (i <- lines.indices) {
        if (i == lines.length - 1) {
            canvas.fill(255, 153, 51) // ???? ?? ????? ?????
        } else {
            canvas.fill(230, 230, 230) // ??????? ??? ???????? ?????
        }
        canvas.text(lines(i), startX, startY)
        startY -= 20.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)
    }

    // ?. ???? ???? ?? ????? ???????
    drawSareJahanLyrics(canvas)

    // ?. ??????? ?????? ???? ??? ??????? ????
    drawInteractiveFlag(canvas)
}

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