Code Sketch


yadnesh shahare ai
By: Mhalsakant School
Category: Programming
// ============================================================================
// --- ISRO MISSION: ADVANCED REALISTIC 3D SOLAR SYSTEM & MULTI-MINIGAME SYSTEM ---
// ============================================================================
cleari()
originBottomLeft()

// ==================== JANA GANA MANA TUNE SETUP ====================
setNoteInstrument(Instrument.PIANO)
val fullJanaGanaTune = Array(
    (53, 200), (55, 200), (57, 200), (57, 200), (57, 200), (57, 200), (57, 200), 
    (57, 200), (57, 200), (55, 200), (57, 200), (58, 200),
    (57, 200), (57, 200), (57, 200), (55, 200), (55, 200), (55, 200), (53, 200), (55, 200), (53, 300),
    (55, 200), (57, 200), (55, 200), (53, 300),
    (53, 200), (60, 200), (60, 200), (60, 200), (60, 200), (60, 200), (60, 200),
    (60, 200), (58, 200), (60, 200), (58, 200), (57, 200), (55, 200), (57, 200), (58, 300),
    (57, 200), (57, 200), (57, 200), (57, 200), (55, 200), (58, 200), (57, 200),
    (55, 200), (55, 200), (53, 200), (55, 200), (53, 300),
    (53, 200), (55, 200), (57, 200), (57, 200), (57, 200), (55, 200), (57, 200), (58, 300),
    (57, 200), (55, 200), (57, 200), (53, 200), (55, 200), (53, 300),
    (53, 200), (55, 200), (57, 200), (57, 200), (57, 200), (57, 200), (57, 200),
    (55, 200), (57, 200), (58, 200), (57, 200), (55, 200), (53, 300),
    (60, 250), (58, 250), (60, 300),
    (58, 250), (57, 250), (58, 300),
    (57, 250), (55, 250), (57, 300),
    (53, 200), (53, 200), (55, 200), (55, 200), (57, 200), (57, 200), (58, 400)
)
var tuneIndex = 0

val fullAnthemText = 
    "Jana Gana Mana Adhinayaka Jaya He\n" +
    "Bharata Bhagya Vidhata\n" +
    "Punjaba Sindhu Gujarata Maratha\n" +
    "Dravida Utkala Banga\n" +
    "Vindhya Himachala Yamuna Ganga\n" +
    "Uchhala Jaladhi Taranga\n" +
    "Tava Subha Name Jage\n" +
    "Tava Subha Asisa Mange\n" +
    "Gahe Tava Jaya Gatha\n" +
    "Jana Gana Mangala Dayaka Jaya He\n" +
    "Bharata Bhagya Vidhata\n" +
    "Jaya He, Jaya He, Jaya He\n" +
    "Jaya Jaya Jaya Jaya He!"
var charIndex = 0

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

// ==================== STARS & ANIMATION VARS ====================
val starCount = 220
val starX = Array.fill(starCount)(randomDouble(0, cwidth))
val starY = Array.fill(starCount)(randomDouble(0, cheight))
val starSize = Array.fill(starCount)(randomDouble(1.0, 2.5))

var waveAngle = 0.0
var chakraRotation = 0.0
var neonGlowPhase = 0.0
var planetSelfRotationAngle = 0.0 

def drawStars(canvas: CanvasDraw) {
    canvas.stroke(255, 255, 255)
    for (i <- 0 until starCount) {
        canvas.strokeWeight(starSize(i))
        if (randomDouble(1) > 0.04) {
            canvas.point(starX(i), starY(i))
        }
    }
}

// ==================== ALL PLANETS 3D CONFIGURATION ====================
case class PlanetConfig(name: String, radiusX: Double, radiusY: Double, speed: Double, baseSize: Double, col: Color)

val planetConfigs = Array(
    PlanetConfig("Mercury", 70.0, 22.0, 0.035, 7.0, cm.rgb(180, 180, 180)),
    PlanetConfig("Venus",   105.0, 32.0, 0.028, 11.0, cm.rgb(230, 190, 110)),
    PlanetConfig("Earth",   145.0, 44.0, 0.022, 13.0, cm.rgb(50, 180, 255)),
    PlanetConfig("Moon",    160.0, 48.0, 0.020, 5.0, cm.rgb(200, 200, 200)),
    PlanetConfig("Mars",    195.0, 58.0, 0.017, 10.0, cm.rgb(230, 80, 50)),
    PlanetConfig("Jupiter", 245.0, 72.0, 0.012, 22.0, cm.rgb(230, 160, 100)),
    PlanetConfig("Saturn",  295.0, 88.0, 0.009, 18.0, cm.rgb(210, 190, 120)),
    PlanetConfig("Uranus",  340.0, 102.0, 0.006, 15.0, cm.rgb(120, 220, 230)),
    PlanetConfig("Neptune", 380.0, 114.0, 0.004, 14.0, cm.rgb(60, 100, 240))
)

val planetList = Array("Sun", "Mercury", "Venus", "Earth", "Moon", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune")
val planetAngles = Array.fill(planetConfigs.length)(randomDouble(0, math.Pi * 2))

var selectedPlanet = "Mars"

// LAUNCH & LANDING STATES
var isTimerRunning = false
var countdownTimer = 10.0
var isLaunched = false
var isLanded = false
var astronautOut = false

// ASTRONAUT VERIFICATION QUESTION SYSTEM
var isVerificationActive = false
var verificationPassed = false
var currentQuestionIdx = 0
case class QuizQuestion(question: String, optA: String, optB: String, correctOpt: String)

val verificationQuestions = Array(
    QuizQuestion("What does ISRO stand for?", "A) Indian Space Research Org", "B) Int Space Rocket Office", "A"),
    QuizQuestion("Which ISRO mission landed near Moon South Pole?", "A) Chandrayaan-3", "B) Mangalyaan-1", "A"),
    QuizQuestion("What is India's first human spaceflight mission?", "A) Gaganyaan", "B) Aditya-L1", "A"),
    QuizQuestion("Which launch vehicle is known as ISRO's Bahubali?", "A) LVM3 / GSLV Mk III", "B) PSLV-C37", "A")
)

// BLAST STATE FOR SUN
var isBlasted = false
var blastTimer = 0.0

var targetX = (cwidth / 2.0) + 120.0
var targetY = (cheight / 2.0) + 20.0

var rocketX = 80.0
var rocketY = 60.0
var rocketAngle = 0.0
var launchProgress = 0.0 

// MULTI MINI-GAME & RETURN LAUNCH STATES
var currentMiniGameId = 0 // 0: Platformer Door, 1: Meteor Dodge, 2: O2 Rush, 3: Thruster Timing
var isMiniGameActive = false
var readyToReturnEarth = false
var isReturnTimerRunning = false
var returnCountdownTimer = 5.0
var isReturnLaunched = false
var isTeleportedToEarth = false
var startAnthemPhase = false

// MINI GAME 1: PLATFORMER STATE
var miniPlayerX = (cwidth / 2.0) - 180.0
var miniPlayerY = (cheight / 2.0) - 60.0
var miniPlayerVy = 0.0
var isGrounded = true
var isKeyPotionCollected = false
var isDoorUnlocked = false

// MINI GAME 2: METEOR DODGE STATE
var dodgePlayerX = cwidth / 2.0
var meteorTimer = 0.0
val meteorX = Array.fill(5)(randomDouble(cwidth / 2.0 - 180, cwidth / 2.0 + 180))
val meteorY = Array.fill(5)(randomDouble(cheight / 2.0 + 80, cheight / 2.0 + 200))
val meteorSpeed = Array.fill(5)(randomDouble(2.0, 4.5))
var meteorSurviveTimer = 8.0

// MINI GAME 3: OXYGEN RUSH STATE
var o2Score = 0
val o2X = Array.fill(5)(randomDouble(cwidth / 2.0 - 180, cwidth / 2.0 + 180))
val o2Y = Array.fill(5)(randomDouble(cheight / 2.0 - 60, cheight / 2.0 + 60))
val o2Active = Array.fill(5)(true)

// MINI GAME 4: THRUSTER TIMING STATE
var needlePos = 0.0
var needleDir = 3.5
var timingSuccessHits = 0

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

// RESET GAME FUNCTION
def resetGame() {
    isTimerRunning = false
    countdownTimer = 10.0
    isLaunched = false
    isLanded = false
    astronautOut = false
    isBlasted = false
    blastTimer = 0.0

    isVerificationActive = false
    verificationPassed = false
    currentQuestionIdx = random(verificationQuestions.length)

    targetX = (cwidth / 2.0) + 120.0
    targetY = (cheight / 2.0) + 20.0

    rocketX = 80.0
    rocketY = 60.0
    rocketAngle = 0.0
    launchProgress = 0.0

    isMiniGameActive = false
    readyToReturnEarth = false
    isReturnTimerRunning = false
    returnCountdownTimer = 5.0
    isReturnLaunched = false
    isTeleportedToEarth = false
    startAnthemPhase = false

    // Reset Mini Game 1
    miniPlayerX = (cwidth / 2.0) - 180.0
    miniPlayerY = (cheight / 2.0) - 60.0
    miniPlayerVy = 0.0
    isGrounded = true
    isKeyPotionCollected = false
    isDoorUnlocked = false

    // Reset Mini Game 2
    dodgePlayerX = cwidth / 2.0
    meteorSurviveTimer = 8.0
    for(i <- meteorX.indices) {
        meteorX(i) = randomDouble(cwidth / 2.0 - 180, cwidth / 2.0 + 180)
        meteorY(i) = randomDouble(cheight / 2.0 + 80, cheight / 2.0 + 200)
    }

    // Reset Mini Game 3
    o2Score = 0
    for(i <- o2Active.indices) {
        o2Active(i) = true
        o2X(i) = randomDouble(cwidth / 2.0 - 180, cwidth / 2.0 + 180)
        o2Y(i) = randomDouble(cheight / 2.0 - 60, cheight / 2.0 + 60)
    }

    // Reset Mini Game 4
    needlePos = 0.0
    timingSuccessHits = 0

    selectedPlanet = "Mars"
}

// ==================== FIREWORKS SYSTEM ====================
class Particle(x0: Double, y0: Double, col: Color, seed: Boolean) {
    var location = Vector2D(x0, y0)
    private var velocity = if (seed) Vector2D(0, randomDouble(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 isDead: Boolean = if (seed) exploded else lifespan <= 0

    def explode() {
        exploded = true
        if (startAnthemPhase) {
            playJanaGanaStep()
            if (charIndex < fullAnthemText.length) {
                charIndex = math.min(charIndex + 4, fullAnthemText.length)
            }
        }
    }

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

    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(randomDouble(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(Vector2D(0, -0.08))
            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(Vector2D(0, -0.08))
            p.step()
        }
    }

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

val fireworks = ArrayBuffer.empty[Firework]

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

    waveAngle += 0.08
    chakraRotation += 0.05
    neonGlowPhase += 0.06
    planetSelfRotationAngle += 0.035

    val sunX = cwidth / 2.0 + 40.0
    val sunY = cheight / 2.0 - 10.0

    // UPDATE ORBITING ANGLES FOR ALL 3D PLANETS
    for (i <- planetConfigs.indices) {
        planetAngles(i) += planetConfigs(i).speed
        if (planetAngles(i) > math.Pi * 2) planetAngles(i) -= math.Pi * 2

        if (selectedPlanet == planetConfigs(i).name) {
            targetX = sunX + math.cos(planetAngles(i)) * planetConfigs(i).radiusX
            targetY = sunY + math.sin(planetAngles(i)) * planetConfigs(i).radiusY
        }
    }

    if (selectedPlanet == "Sun") {
        targetX = sunX
        targetY = sunY
    }

    // LAUNCH TIMER
    if (isTimerRunning && countdownTimer > 0) {
        countdownTimer -= 0.04
        if (countdownTimer <= 0) {
            countdownTimer = 0.0
            isTimerRunning = false
            isVerificationActive = true // TRIGGER ASTRONAUT VERIFICATION QUESTION BEFORE FLIGHT
            currentQuestionIdx = random(verificationQuestions.length)
        }
    }

    // ROCKET LAUNCH TRAJECTORY & LANDING ROTATION
    if (isLaunched && !isLanded && !isBlasted) {
        launchProgress += 0.008 

        if (launchProgress >= 1.0) {
            launchProgress = 1.0
            if (selectedPlanet == "Sun") {
                isBlasted = true
                blastTimer = 100.0 
            } else {
                isLanded = true
                rocketAngle = 0.0
                rocketX = targetX
                rocketY = targetY + 15
                astronautOut = true
                
                // ROTATE TO NEXT MINI GAME ON EVERY JOURNEY
                currentMiniGameId = (currentMiniGameId + 1) % 4
                isMiniGameActive = true
            }
        } else {
            val startX = 80.0
            val startY = 60.0
            val controlX = cwidth / 2.0 + 80.0 
            val controlY = cheight / 2.0 + 180.0

            val u = launchProgress
            val tt = u * u
            val uu = (1.0 - u) * (1.0 - u)

            val nextX = uu * startX + 2 * (1.0 - u) * u * controlX + tt * targetX
            val nextY = uu * startY + 2 * (1.0 - u) * u * controlY + tt * (targetY + 15)

            val dx = nextX - rocketX
            val dy = nextY - rocketY
            rocketAngle = math.atan2(dy, dx) - (math.Pi / 2.0)

            rocketX = nextX
            rocketY = nextY
        }
    }

    // ROCKET ORBITS AND ROTATES TOGETHER WITH THE PLANET AFTER LANDING
    if (isLanded && !isReturnLaunched) {
        rocketX = targetX
        rocketY = targetY + 15.0
        // Angle points outward from the Sun center to give seamless rotational alignment
        rocketAngle = math.atan2(targetY - sunY, targetX - sunX) - (math.Pi / 2.0)
    }

    // BLAST TIMER AND AUTO-RESTART
    if (isBlasted) {
        blastTimer -= 1.0
        if (blastTimer <= 0) {
            resetGame() 
        }
    }

    // ==================== MULTI MINI-GAME LOGIC ====================
    if (isMiniGameActive && !readyToReturnEarth) {
        val gx = cwidth / 2.0 - 220.0
        val gy = cheight / 2.0 - 100.0

        currentMiniGameId match {
            case 0 => // MINI-GAME 1: PLATFORMER
                val speed = 3.5
                if (isKeyPressed(Kc.VK_RIGHT) && miniPlayerX < gx + 415) miniPlayerX += speed
                if (isKeyPressed(Kc.VK_LEFT) && miniPlayerX > gx + 15) miniPlayerX -= speed
                
                if (isKeyPressed(Kc.VK_UP) && isGrounded) {
                    miniPlayerVy = 8.5  
                    isGrounded = false
                }

                miniPlayerY += miniPlayerVy
                if (!isGrounded) miniPlayerVy -= 0.45 

                val groundLevel = gy + 35.0

                if (miniPlayerY <= groundLevel) {
                    miniPlayerY = groundLevel
                    miniPlayerVy = 0.0
                    isGrounded = true
                } else if (miniPlayerX >= gx + 160 && miniPlayerX <= gx + 280 && miniPlayerY >= gy + 80 && miniPlayerY <= gy + 95 && miniPlayerVy <= 0) {
                    miniPlayerY = gy + 85.0
                    miniPlayerVy = 0.0
                    isGrounded = true
                } else if (miniPlayerX >= gx + 320 && miniPlayerX <= gx + 420 && miniPlayerY >= gy + 130 && miniPlayerY <= gy + 145 && miniPlayerVy <= 0) {
                    miniPlayerY = gy + 135.0
                    miniPlayerVy = 0.0
                    isGrounded = true
                } else {
                    if (miniPlayerY > groundLevel) isGrounded = false
                }

                if (!isKeyPotionCollected && math.abs(miniPlayerX - (gx + 380)) < 25 && math.abs(miniPlayerY - (gy + 150)) < 25) {
                    isKeyPotionCollected = true
                    isDoorUnlocked = true
                }

                if (isDoorUnlocked && math.abs(miniPlayerX - (gx + 40)) < 35 && math.abs(miniPlayerY - (gy + 45)) < 35) {
                    isMiniGameActive = false
                    readyToReturnEarth = true
                }

            case 1 => // MINI-GAME 2: METEOR SHOWER DODGE
                if (isKeyPressed(Kc.VK_RIGHT) && dodgePlayerX < gx + 410) dodgePlayerX += 4.0
                if (isKeyPressed(Kc.VK_LEFT) && dodgePlayerX > gx + 20) dodgePlayerX -= 4.0

                meteorSurviveTimer -= 0.03
                for (i <- meteorX.indices) {
                    meteorY(i) -= meteorSpeed(i)
                    if (meteorY(i) < gy + 20) {
                        meteorY(i) = gy + 200
                        meteorX(i) = randomDouble(gx + 20, gx + 420)
                    }
                    if (math.abs(meteorX(i) - dodgePlayerX) < 22 && math.abs(meteorY(i) - (gy + 35)) < 22) {
                        meteorSurviveTimer = 8.0 // Hit! Reset survive timer
                    }
                }

                if (meteorSurviveTimer <= 0) {
                    isMiniGameActive = false
                    readyToReturnEarth = true
                }

            case 2 => // MINI-GAME 3: OXYGEN REFUEL RUSH
                val speed = 4.0
                if (isKeyPressed(Kc.VK_RIGHT) && miniPlayerX < gx + 415) miniPlayerX += speed
                if (isKeyPressed(Kc.VK_LEFT) && miniPlayerX > gx + 15) miniPlayerX -= speed
                if (isKeyPressed(Kc.VK_UP) && miniPlayerY < gy + 190) miniPlayerY += speed
                if (isKeyPressed(Kc.VK_DOWN) && miniPlayerY > gy + 30) miniPlayerY -= speed

                for (i <- o2Active.indices) {
                    if (o2Active(i) && math.abs(miniPlayerX - o2X(i)) < 25 && math.abs(miniPlayerY - o2Y(i)) < 25) {
                        o2Active(i) = false
                        o2Score += 1
                    }
                }

                if (o2Score >= 5) {
                    isMiniGameActive = false
                    readyToReturnEarth = true
                }

            case 3 => // MINI-GAME 4: THRUSTER TIMING CALIBRATION
                needlePos += needleDir
                if (needlePos > 200 || needlePos < -200) needleDir *= -1

                if (isKeyPressed(Kc.VK_SPACE)) {
                    if (math.abs(needlePos) < 30) {
                        timingSuccessHits += 1
                        needlePos = -180
                    }
                }

                if (timingSuccessHits >= 3) {
                    isMiniGameActive = false
                    readyToReturnEarth = true
                }
        }
    }

    // RETURN LAUNCH TIMER
    if (isReturnTimerRunning && returnCountdownTimer > 0) {
        returnCountdownTimer -= 0.04
        if (returnCountdownTimer <= 0) {
            returnCountdownTimer = 0.0
            isReturnTimerRunning = false
            isReturnLaunched = true
            launchProgress = 0.0
        }
    }

    // RETURN LAUNCH TRAJECTORY
    if (isReturnLaunched && !isTeleportedToEarth) {
        launchProgress += 0.008
        if (launchProgress >= 1.0) {
            launchProgress = 1.0
            isTeleportedToEarth = true
            startAnthemPhase = true
        } else {
            val startX = targetX
            val startY = targetY + 15
            val destX = 80.0
            val destY = 60.0
            val controlX = cwidth / 2.0 - 100.0
            val controlY = cheight / 2.0 + 200.0

            val u = launchProgress
            val tt = u * u
            val uu = (1.0 - u) * (1.0 - u)

            val nextX = uu * startX + 2 * (1.0 - u) * u * controlX + tt * destX
            val nextY = uu * startY + 2 * (1.0 - u) * u * controlY + tt * destY

            val dx = nextX - rocketX
            val dy = nextY - rocketY
            rocketAngle = math.atan2(dy, dx) - (math.Pi / 2.0)

            rocketX = nextX
            rocketY = nextY
        }
    }
}

// MOUSE CLICK INPUT
onMouseClick { (x, y) =>
    // ASTRONAUT VERIFICATION MODAL CLICK HANDLER
    if (isVerificationActive) {
        val mx = cwidth / 2.0 - 200
        val my = cheight / 2.0 - 100
        val q = verificationQuestions(currentQuestionIdx)

        if (x >= mx + 20 && x <= mx + 380 && y >= my + 60 && y <= my + 95) {
            if (q.correctOpt == "A") {
                isVerificationActive = false
                isLaunched = true
                launchProgress = 0.0
            }
        } else if (x >= mx + 20 && x <= mx + 380 && y >= my + 15 && y <= my + 50) {
            if (q.correctOpt == "B") {
                isVerificationActive = false
                isLaunched = true
                launchProgress = 0.0
            }
        }
    }
    else if (x >= 15 && x <= 125 && y >= 15 && y <= 55) {
        if (!isTimerRunning && !isLaunched) {
            isTimerRunning = true
            countdownTimer = 10.0
        }
    } 
    else if (readyToReturnEarth && !isReturnLaunched && x >= cwidth / 2.0 - 100 && x <= cwidth / 2.0 + 100 && y >= cheight - 80 && y <= cheight - 30) {
        if (!isReturnTimerRunning) {
            isReturnTimerRunning = true
            returnCountdownTimer = 5.0
        }
    }
    else if (x <= 130 && y >= cheight - 340 && y <= cheight - 20 && !isLaunched) {
        val index = ((cheight - 40 - y) / 28).toInt
        if (index >= 0 && index < planetList.length) {
            selectedPlanet = planetList(index)
        }
    }
}

// ==================== ? REALISTIC 3D PERSPECTIVE SOLAR SYSTEM ====================
def drawOrbitingSolarSystem(canvas: CanvasDraw) {
    val sunX = cwidth / 2.0 + 40.0
    val sunY = cheight / 2.0 - 10.0

    // 1. DRAW 3D ORBIT LINES
    for (i <- planetConfigs.indices) {
        val p = planetConfigs(i)
        canvas.stroke(0, 200, 255, 30)
        canvas.strokeWeight(1.5)
        canvas.noFill()
        canvas.ellipse(sunX, sunY, p.radiusX * 2, p.radiusY * 2)
    }

    // 2. DRAW SUN WITH GLOW
    val glowAlpha = (math.sin(neonGlowPhase * 2) * 40 + 200).toInt
    canvas.fill(255, 100, 0, glowAlpha)
    canvas.stroke(255, 230, 0)
    canvas.strokeWeight(5)
    canvas.ellipse(sunX, sunY, 65, 65)

    canvas.fill(255, 255, 150)
    canvas.noStroke()
    canvas.ellipse(sunX - 8, sunY + 8, 20, 20) 

    // 3. DRAW PLANETS WITH 3D DEPTH
    for (i <- planetConfigs.indices) {
        val p = planetConfigs(i)
        val angle = planetAngles(i)

        val px = sunX + math.cos(angle) * p.radiusX
        val py = sunY + math.sin(angle) * p.radiusY

        val sinVal = math.sin(angle) 
        val depthScale = 0.75 + (sinVal + 1.0) * 0.25 
        val dynamicSize = p.baseSize * depthScale
        val alphaVal = (160 + sinVal * 95).toInt

        if (selectedPlanet == p.name) {
            canvas.stroke(0, 255, 200, alphaVal)
            canvas.strokeWeight(2)
            canvas.noFill()
            canvas.ellipse(px, py, dynamicSize + 12, dynamicSize + 6)
        }

        canvas.fill(p.col)
        canvas.stroke(255, 255, 255, alphaVal)
        canvas.strokeWeight(1)
        canvas.ellipse(px, py, dynamicSize * 1.1, dynamicSize)

        canvas.fill(0, 0, 0, 100)
        canvas.noStroke()
        canvas.ellipse(px + (depthScale * 3), py - (depthScale * 3), dynamicSize * 0.8, dynamicSize * 0.8)

        if (p.name == "Saturn") {
            canvas.stroke(210, 190, 120, alphaVal)
            canvas.strokeWeight(3)
            canvas.noFill()
            canvas.ellipse(px, py, dynamicSize * 2.4, dynamicSize * 0.7)
        }
    }
}

// ==================== ? FULL ROTATING 3D PLANET VIEW ====================
def drawDetailedRotatingPlanet(canvas: CanvasDraw) {
    val px = cwidth - 120.0
    val py = cheight - 120.0
    val r = 50.0

    canvas.stroke(0, 255, 255, 120)
    canvas.strokeWeight(2)
    canvas.fill(5, 10, 20, 210)
    canvas.rect(cwidth - 195, cheight - 195, 180, 180)

    canvas.fill(0, 255, 255)
    canvas.text(s"3D VIEW: $selectedPlanet", cwidth - 180, cheight - 25)

    if (selectedPlanet == "Sun") {
        canvas.fill(255, 120, 0)
        canvas.stroke(255, 220, 0)
        canvas.strokeWeight(4)
        canvas.ellipse(px, py, r * 2, r * 2)

        canvas.stroke(255, 200, 50, 180)
        canvas.strokeWeight(2)
        for (i <- -3 to 3) {
            val yOff = i * 12
            val xW = math.sqrt(math.max(0.0, r * r - yOff * yOff))
            val waveX = math.sin(planetSelfRotationAngle + i) * 10.0
            canvas.line(px - xW + waveX, py + yOff, px + xW + waveX, py + yOff)
        }
    } else {
        var col = cm.rgb(180, 180, 180)
        repeatFor(planetConfigs) { p => if (p.name == selectedPlanet) col = p.col }

        canvas.fill(col)
        canvas.stroke(255, 255, 255, 150)
        canvas.strokeWeight(1.5)
        canvas.ellipse(px, py, r * 2, r * 2)

        canvas.stroke(0, 0, 0, 90)
        canvas.strokeWeight(2)

        for (i <- -4 to 4) {
            val yOff = i * 10.0
            if (math.abs(yOff) < r) {
                val xW = math.sqrt(r * r - yOff * yOff)
                val rotX = (math.sin(planetSelfRotationAngle + (i * 0.4)) * (xW * 0.8))
                
                canvas.line(px - xW, py + yOff, px + xW, py + yOff)
                canvas.fill(255, 255, 255, 110)
                canvas.ellipse(px + rotX, py + yOff, 6, 4)
            }
        }

        if (selectedPlanet == "Saturn") {
            canvas.stroke(210, 190, 120, 200)
            canvas.strokeWeight(4)
            canvas.noFill()
            canvas.ellipse(px, py, r * 2.8, r * 0.8)
        } else if (selectedPlanet == "Uranus") {
            canvas.stroke(120, 220, 230, 180)
            canvas.strokeWeight(3)
            canvas.noFill()
            canvas.ellipse(px, py, r * 0.8, r * 2.6)
        }

        canvas.stroke(0, 0, 0, 120)
        canvas.strokeWeight(6)
        canvas.noFill()
        canvas.ellipse(px + 10, py - 10, r * 1.8, r * 1.8)
    }
}

def drawPlanetList(canvas: CanvasDraw) {
    val glowWidth = (math.sin(neonGlowPhase) * 2 + 3).toFloat

    canvas.stroke(0, 255, 255, 180)
    canvas.strokeWeight(glowWidth)
    canvas.fill(10, 15, 30, 230)
    canvas.rect(10, cheight - 340, 120, 300)

    canvas.fill(0, 255, 255)
    canvas.text("SELECT TARGET", 15, cheight - 48)

    var yPos = cheight - 70.0
    repeatFor(planetList) { pName =>
        if (pName == selectedPlanet) {
            if (pName == "Sun") canvas.fill(255, 50, 0) else canvas.fill(18, 136, 7)
            canvas.stroke(0, 255, 200)
            canvas.strokeWeight(1)
            canvas.rect(12, yPos - 12, 116, 22)
            canvas.fill(255, 255, 255)
        } else {
            if (pName == "Sun") canvas.fill(255, 100, 100) else canvas.fill(180, 220, 255)
        }
        
        val label = if (pName == "Sun") "? Sun ?" else s"? $pName"
        canvas.text(label, 18, yPos)
        yPos -= 28.0
    }
}

def drawFullEarthScreen(canvas: CanvasDraw) {
    val cx = cwidth / 2.0
    val cy = cheight / 2.0

    canvas.fill(20, 100, 220)
    canvas.stroke(0, 255, 255)
    canvas.strokeWeight(4)
    canvas.ellipse(cx, cy - 20, 360, 360)

    canvas.fill(40, 160, 60)
    canvas.ellipse(cx - 40, cy + 30, 120, 90)
    canvas.ellipse(cx + 60, cy - 50, 140, 80)
}

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 drawSingleSoldier(canvas: CanvasDraw, sx: Double, sy: Double) {
    canvas.fill(45, 80, 45)
    canvas.stroke(20, 50, 20)
    canvas.strokeWeight(1)
    canvas.rect(sx - 12, sy, 24, 38)

    canvas.fill(240, 195, 150)
    canvas.rect(sx - 4, sy + 38, 8, 6)
    canvas.ellipse(sx, sy + 52, 18, 18)

    canvas.fill(30, 60, 30)
    canvas.ellipse(sx, sy + 57, 22, 14)

    canvas.stroke(45, 80, 45)
    canvas.strokeWeight(5)
    canvas.line(sx - 12, sy + 34, sx - 18, sy + 10)

    canvas.line(sx + 12, sy + 34, sx + 22, sy + 48)
    canvas.line(sx + 22, sy + 48, sx + 8, sy + 57)

    canvas.stroke(240, 195, 150)
    canvas.strokeWeight(3)
    canvas.line(sx + 8, sy + 57, sx + 4, sy + 58)

    canvas.stroke(30, 50, 30)
    canvas.strokeWeight(6)
    canvas.line(sx - 6, sy, sx - 6, sy - 25)
    canvas.line(sx + 6, sy, sx + 6, sy - 25)
}

def drawSalutingAstronaut(canvas: CanvasDraw, ax: Double, ay: Double) {
    canvas.pushMatrix()
    canvas.translate(ax, ay)

    canvas.fill(240, 240, 245)
    canvas.stroke(80, 80, 90)
    canvas.strokeWeight(1)
    canvas.rect(-12, -4, 24, 38)

    canvas.fill(255, 153, 51); canvas.rect(-5, 14, 10, 3)
    canvas.fill(18, 136, 7); canvas.rect(-5, 10, 10, 3)

    canvas.fill(80, 80, 90)
    canvas.rect(-9, -25, 7, 20); canvas.rect(2, -25, 7, 20)

    canvas.stroke(200, 200, 210)
    canvas.strokeWeight(5)
    canvas.line(-12, 28, -18, 10)

    canvas.line(12, 28, 22, 42)
    canvas.line(22, 42, 8, 50)

    canvas.fill(255, 255, 255)
    canvas.stroke(100, 100, 120)
    canvas.ellipse(0, 48, 22, 22)

    canvas.fill(20, 140, 220)
    canvas.noStroke()
    canvas.ellipse(1, 48, 14, 12)

    canvas.popMatrix()
}

def drawSoldiersSquad(canvas: CanvasDraw) {
    val baseX = (cwidth / 2.0) + 40.0
    val baseY = (cheight / 2.0) - 130.0

    drawSingleSoldier(canvas, baseX, baseY)        
    drawSingleSoldier(canvas, baseX + 35, baseY)   
    drawSingleSoldier(canvas, baseX + 70, baseY)

    drawSalutingAstronaut(canvas, baseX - 40, baseY)
}

def drawEarthAndLaunchUI(canvas: CanvasDraw) {
    if (!isTeleportedToEarth) {
        canvas.fill(30, 120, 220)
        canvas.stroke(0, 255, 255)
        canvas.strokeWeight(2)
        canvas.ellipse(50, -20, 180, 180)
    }

    if (!isLaunched && !isTimerRunning) {
        canvas.fill(255, 50, 0)
        canvas.stroke(255, 200, 0)
        canvas.strokeWeight((math.sin(neonGlowPhase * 3) * 2 + 3).toFloat)
        canvas.rect(15, 15, 110, 40)

        canvas.fill(255, 255, 255)
        canvas.text("? LAUNCH", 25, 30)
    }

    if (readyToReturnEarth && !isReturnLaunched) {
        canvas.fill(18, 136, 7)
        canvas.stroke(0, 255, 150)
        canvas.strokeWeight((math.sin(neonGlowPhase * 3) * 2 + 4).toFloat)
        canvas.rect(cwidth / 2.0 - 100, cheight - 75, 200, 45)

        canvas.fill(255, 255, 255)
        canvas.text("? RETURN TO EARTH", cwidth / 2.0 - 75, cheight - 48)
    }

    if (isTimerRunning) {
        val sec = countdownTimer.toInt
        canvas.fill(255, 215, 0)
        canvas.text(s"COUNTDOWN TO LAUNCH: $sec s", 135, 35)
    } else if (isReturnTimerRunning) {
        val sec = returnCountdownTimer.toInt
        canvas.fill(255, 215, 0)
        canvas.text(s"RETURN LAUNCH COUNTDOWN: $sec s", cwidth / 2.0 - 110, cheight - 90)
    }
}

// DRAW ASTRONAUT VERIFICATION QUESTION UI
def drawAstronautVerificationModal(canvas: CanvasDraw) {
    if (isVerificationActive) {
        val mx = cwidth / 2.0 - 200
        val my = cheight / 2.0 - 100
        val q = verificationQuestions(currentQuestionIdx)

        canvas.fill(15, 20, 40, 245)
        canvas.stroke(0, 255, 255)
        canvas.strokeWeight(3)
        canvas.rect(mx, my, 400, 200)

        canvas.fill(255, 215, 0)
        canvas.text("??? ISRO ASTRONAUT VERIFICATION", mx + 20, my + 175)

        canvas.fill(255, 255, 255)
        canvas.text(q.question, mx + 20, my + 130)

        // Option A
        canvas.fill(30, 80, 140)
        canvas.stroke(0, 255, 200)
        canvas.strokeWeight(1.5)
        canvas.rect(mx + 20, my + 60, 360, 35)
        canvas.fill(255, 255, 255)
        canvas.text(q.optA, mx + 30, my + 82)

        // Option B
        canvas.fill(30, 80, 140)
        canvas.stroke(0, 255, 200)
        canvas.strokeWeight(1.5)
        canvas.rect(mx + 20, my + 15, 360, 35)
        canvas.fill(255, 255, 255)
        canvas.text(q.optB, mx + 30, my + 37)
    }
}

// EXPLOSION & ROCKET DRAWING
def drawISRORocket(canvas: CanvasDraw) {
    val rx = rocketX
    val ry = rocketY

    if (isBlasted) {
        canvas.fill(255, randomDouble(50, 200).toInt, 0)
        canvas.stroke(255, 255, 255)
        canvas.strokeWeight(3)
        canvas.ellipse(rx, ry, randomDouble(70, 110), randomDouble(70, 110))
        
        canvas.fill(255, 0, 0)
        canvas.ellipse(rx + randomDouble(-20, 20), ry + randomDouble(-20, 20), 40, 40)

        canvas.fill(255, 255, 255)
        canvas.text("? ROCKET DESTROYED BY SUN HEAT! RESTARTING...", rx - 140, ry + 70)
    } else {
        canvas.pushMatrix()
        canvas.translate(rx, ry)
        canvas.rotate(rocketAngle)

        if ((isLaunched && !isLanded) || (isReturnLaunched && !isTeleportedToEarth)) {
            canvas.fill(0, 255, 255, 150)
            canvas.noStroke()
            canvas.ellipse(0, -30, randomDouble(20, 30), randomDouble(20, 30))

            canvas.fill(255, 100, 0)
            canvas.ellipse(0, -18, 14, 28)
            canvas.fill(255, 255, 200)
            canvas.ellipse(0, -14, 8, 16)
        }

        canvas.fill(240, 240, 240)
        canvas.stroke(0, 255, 255)
        canvas.strokeWeight(1.5)
        canvas.rect(-15, 0, 30, 45)

        if (isLanded && astronautOut) {
            canvas.fill(20, 20, 30)
            canvas.rect(-8, 5, 16, 25)
        } else {
            canvas.fill(180, 180, 190)
            canvas.rect(-8, 5, 16, 25)
        }

        canvas.fill(255, 153, 51)
        canvas.line(-15, 45, 0, 60)
        canvas.line(15, 45, 0, 60)

        canvas.fill(0, 51, 102)
        canvas.text("ISRO", -12, 35)

        canvas.popMatrix()
    }
}

def drawAstronautPlayer(canvas: CanvasDraw, ax: Double, ay: Double) {
    canvas.pushMatrix()
    canvas.translate(ax, ay)

    canvas.fill(240, 240, 245)
    canvas.stroke(50, 50, 60)
    canvas.strokeWeight(1)
    canvas.rect(-8, -10, 16, 22)

    canvas.fill(255, 153, 51); canvas.rect(-4, 2, 8, 2)
    canvas.fill(18, 136, 7); canvas.rect(-4, -2, 8, 2)

    canvas.fill(200, 200, 210)
    canvas.rect(-7, -22, 5, 12)
    canvas.rect(2, -22, 5, 12)

    canvas.fill(255, 255, 255)
    canvas.stroke(80, 80, 100)
    canvas.ellipse(0, 18, 18, 18)

    canvas.fill(0, 180, 240)
    canvas.noStroke()
    canvas.ellipse(1, 18, 12, 9)

    canvas.popMatrix()
}

// DRAW DYNAMIC MULTI MINI-GAME UI
def drawDynamicMiniGame(canvas: CanvasDraw) {
    if (isMiniGameActive) {
        val gx = cwidth / 2.0 - 220.0
        val gy = cheight / 2.0 - 100.0

        canvas.fill(10, 10, 25, 240)
        canvas.stroke(255, 0, 200)
        canvas.strokeWeight((math.sin(neonGlowPhase * 2) * 2 + 3).toFloat)
        canvas.rect(gx, gy, 440, 220)

        currentMiniGameId match {
            case 0 => // MINI-GAME 1: TRICOLOR PLATFORMER
                canvas.fill(255, 255, 255)
                canvas.text("? MINI-GAME 1: Arrow Keys Move/Jump! Get Potion & Enter Earth Door!", gx + 10, gy + 200)

                canvas.fill(255, 153, 51); canvas.rect(gx + 20, gy + 20, 400, 15)
                canvas.fill(255, 255, 255); canvas.rect(gx + 160, gy + 75, 120, 10)
                canvas.fill(18, 136, 7); canvas.rect(gx + 320, gy + 125, 100, 10)

                if (!isKeyPotionCollected) {
                    canvas.fill(255, 50, 50)
                    canvas.stroke(255, 255, 0)
                    canvas.strokeWeight(2)
                    canvas.ellipse(gx + 380, gy + 150, 18, 18)
                    canvas.fill(255, 255, 255)
                    canvas.text("Potion", gx + 360, gy + 168)
                }

                if (isDoorUnlocked) {
                    canvas.fill(0, 200, 255, 200)
                    canvas.stroke(255, 255, 255)
                    canvas.strokeWeight(3)
                    canvas.ellipse(gx + 40, gy + 55, 30, 50)
                    canvas.fill(255, 255, 255)
                    canvas.text("EARTH DOOR", gx + 5, gy + 90)
                }

                drawAstronautPlayer(canvas, miniPlayerX, miniPlayerY)

            case 1 => // MINI-GAME 2: METEOR SHOWER DODGE
                canvas.fill(255, 255, 255)
                canvas.text(s"? MINI-GAME 2: Dodge Space Rocks! Time Left: ${meteorSurviveTimer.toInt}s", gx + 10, gy + 200)

                canvas.fill(18, 136, 7); canvas.rect(gx + 20, gy + 20, 400, 15)
                drawAstronautPlayer(canvas, dodgePlayerX, gy + 35)

                for (i <- meteorX.indices) {
                    canvas.fill(200, 80, 40)
                    canvas.stroke(255, 200, 0)
                    canvas.strokeWeight(1.5)
                    canvas.ellipse(meteorX(i), meteorY(i), 18, 18)
                }

            case 2 => // MINI-GAME 3: OXYGEN REFUEL RUSH
                canvas.fill(255, 255, 255)
                canvas.text(s"? MINI-GAME 3: Collect 5 O2 Tanks! Score: $o2Score/5", gx + 10, gy + 200)

                drawAstronautPlayer(canvas, miniPlayerX, miniPlayerY)

                for (i <- o2X.indices) {
                    if (o2Active(i)) {
                        canvas.fill(0, 200, 255)
                        canvas.stroke(255, 255, 255)
                        canvas.strokeWeight(1)
                        canvas.rect(o2X(i) - 6, o2Y(i) - 10, 12, 20)
                    }
                }

            case 3 => // MINI-GAME 4: THRUSTER TIMING CALIBRATION
                canvas.fill(255, 255, 255)
                canvas.text(s"? MINI-GAME 4: Press SPACE when indicator is in GREEN zone! Hits: $timingSuccessHits/3", gx + 10, gy + 200)

                // Scale line
                val cx = gx + 220
                val cy = gy + 100
                canvas.stroke(100, 100, 100)
                canvas.strokeWeight(10)
                canvas.line(cx - 180, cy, cx + 180, cy)

                // Sweet green zone
                canvas.stroke(0, 255, 100)
                canvas.strokeWeight(12)
                canvas.line(cx - 30, cy, cx + 30, cy)

                // Moving needle
                canvas.fill(255, 50, 0)
                canvas.stroke(255, 255, 255)
                canvas.strokeWeight(2)
                canvas.ellipse(cx + needlePos, cy, 20, 20)
        }
    }
}

def drawLetterByLetterAnthem(canvas: CanvasDraw) {
    if (charIndex > 0) {
        val visibleSubstring = fullAnthemText.substring(0, charIndex)
        val lines = visibleSubstring.split("\n")
        
        val startX = 20.0
        var startY = cheight - 60.0

        for (i <- lines.indices) {
            canvas.fill(255, 255, 255) 
            canvas.text(lines(i), startX, startY)
            startY -= 22.0
        }
    }
}

def viewState(canvas: CanvasDraw) {
    canvas.fill(5, 5, 15, 55)
    canvas.noStroke()
    canvas.rect(0, 0, cwidth, cheight)

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

    if (isTeleportedToEarth) {
        drawFullEarthScreen(canvas)
        drawWavingTricolorFlag(canvas)
        drawSoldiersSquad(canvas)
        drawLetterByLetterAnthem(canvas)
    } else {
        drawOrbitingSolarSystem(canvas)
        drawPlanetList(canvas)       
        drawDetailedRotatingPlanet(canvas)
        drawEarthAndLaunchUI(canvas)
        drawISRORocket(canvas)
    }

    drawAstronautVerificationModal(canvas)
    drawDynamicMiniGame(canvas)     
}

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