Code Sketch


Bixuu02 diwali animtion game
By: Mhalsakant School
Category: Programming
import java.awt.{BasicStroke, Color, Dimension, Font, GradientPaint, Graphics, Graphics2D, KeyboardFocusManager, KeyEventDispatcher, Polygon, RenderingHints}
import java.awt.event.{ActionEvent, ActionListener, KeyEvent, MouseEvent, MouseListener, WindowAdapter, WindowEvent}
import java.awt.geom.RoundRectangle2D
import java.awt.image.BufferedImage
import javax.swing.{JComponent, JFrame, JOptionPane, JPanel, SwingUtilities, Timer, WindowConstants}
import scala.util.Random

/**
 * BIXUU DIWALI TRAIN ADVENTURE
 * Complete standalone Scala + Java Swing/AWT game for Kojo.
 * Controls: WASD / Arrow keys = move; E / ENTER / SPACE = interact;
 * 1 = Gokuldham Family; 2 = Normal Family; P = pause; ESC = menu.
 * No external libraries or image files required.
 */
object BixuuDiwaliGame {
  def main(args: Array[String]): Unit = {
    SwingUtilities.invokeLater(new Runnable {
      override def run(): Unit = createWindow()
    })
  }

  private def createWindow(): Unit = {
    val frame = new JFrame("BIXUU DIWALI TRAIN ADVENTURE | Scala + Swing")
    val game = new GamePanel()
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
    frame.setContentPane(game)
    frame.setSize(1100, 720)
    frame.setMinimumSize(new Dimension(900, 620))
    frame.setLocationRelativeTo(null)
    frame.setIconImage(makeAppIcon())
    frame.addWindowFocusListener(new WindowAdapter {
      override def windowLostFocus(e: WindowEvent): Unit = game.clearHeldKeys()
    })
    frame.setVisible(true)
    game.startLoop()
    game.requestFocusInWindow()
  }

  private def makeAppIcon(): BufferedImage = {
    val img = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB)
    val g = img.createGraphics()
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
    g.setPaint(new GradientPaint(0, 0, new Color(35, 15, 65), 64, 64, new Color(230, 88, 18)))
    g.fillRoundRect(0, 0, 64, 64, 16, 16)
    g.setColor(new Color(255, 205, 77)); g.fillOval(17, 33, 30, 13)
    g.setColor(new Color(255, 242, 170)); g.fillOval(27, 24, 10, 15)
    g.setColor(new Color(255, 255, 225)); g.fillOval(30, 27, 4, 10)
    g.dispose()
    img
  }

  private class DecoTarget(val x: Double, val y: Double, val label: String, var done: Boolean = false)

  private class GamePanel extends JPanel with MouseListener {
    private val rng = new Random(17)
    private var phase = "menu" // menu, playing, complete
    private var family = "Gokuldham Family"
    private var familyName = "My Family"
    private var playerName = "Bixuu"
    private var score = 0
    private var tick = 0
    private var walkFrame = 0
    private var pause = false

    private var playerX = 160.0
    private var playerY = 470.0
    private var moveUp = false
    private var moveDown = false
    private var moveLeft = false
    private var moveRight = false
    private val heldKeys = scala.collection.mutable.Set.empty[Int]

    private var scene = "letter" // letter, ticket, missed, wait, coach, goaStation, goaHome, diwali
    private var ticketChecked = false
    private var currentCoach = 1
    private var coachCoins = Array(false, false, false)
    private var trainArrivalTicks = 0
    private var travelTicks = 0
    private var ticketBooked = false

    private var stage = 0
    private var supplies = false
    private var pujaDone = false
    private var targets = Array.empty[DecoTarget]
    private var stairAnimating = false
    private var stairTick = 0
    private var stairStartX = 0.0
    private var stairStartY = 0.0

    private var toast = "Welcome! Choose your family to begin."
    private var toastTicks = 180
    private var menuGokul = new java.awt.Rectangle(0, 0, 0, 0)
    private var menuNormal = new java.awt.Rectangle(0, 0, 0, 0)

    setFocusable(true)
    setBackground(new Color(16, 12, 35))
    addMouseListener(this)

    private val movementKeys: Set[Int] = Set(
      KeyEvent.VK_W, KeyEvent.VK_A, KeyEvent.VK_S, KeyEvent.VK_D,
      KeyEvent.VK_UP, KeyEvent.VK_DOWN, KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT
    )
    private val supportedKeys: Set[Int] = movementKeys ++ Set(
      KeyEvent.VK_E, KeyEvent.VK_ENTER, KeyEvent.VK_SPACE,
      KeyEvent.VK_1, KeyEvent.VK_2, KeyEvent.VK_NUMPAD1, KeyEvent.VK_NUMPAD2,
      KeyEvent.VK_P, KeyEvent.VK_ESCAPE
    )

    // Global dispatcher fixes focus-related keyboard failures in embedded editors such as Kojo.
    // It responds only when this game's window is active.
    private val keyDispatcher: KeyEventDispatcher = new KeyEventDispatcher {
      override def dispatchKeyEvent(e: KeyEvent): Boolean = {
        val win = SwingUtilities.getWindowAncestor(GamePanel.this)
        if (win == null || !win.isActive || !supportedKeys.contains(e.getKeyCode)) {
          false
        } else if (e.getID == KeyEvent.KEY_PRESSED) {
          val code = e.getKeyCode
          if (!heldKeys.contains(code)) {
            heldKeys += code
            updateMovementFlags()
            if (!movementKeys.contains(code)) handleKeyPressed(code)
          }
          true
        } else if (e.getID == KeyEvent.KEY_RELEASED) {
          heldKeys -= e.getKeyCode
          updateMovementFlags()
          true
        } else false
      }
    }

    private def installGlobalKeyboardControls(): Unit = {
      KeyboardFocusManager.getCurrentKeyboardFocusManager.addKeyEventDispatcher(keyDispatcher)
    }

    // Register after keyDispatcher has been initialized.
    installGlobalKeyboardControls()

    def clearHeldKeys(): Unit = {
      heldKeys.clear()
      updateMovementFlags()
    }

    private def updateMovementFlags(): Unit = {
      moveUp = heldKeys.contains(KeyEvent.VK_W) || heldKeys.contains(KeyEvent.VK_UP)
      moveDown = heldKeys.contains(KeyEvent.VK_S) || heldKeys.contains(KeyEvent.VK_DOWN)
      moveLeft = heldKeys.contains(KeyEvent.VK_A) || heldKeys.contains(KeyEvent.VK_LEFT)
      moveRight = heldKeys.contains(KeyEvent.VK_D) || heldKeys.contains(KeyEvent.VK_RIGHT)
    }

    private def handleKeyPressed(code: Int): Unit = {
      if (code == KeyEvent.VK_E || code == KeyEvent.VK_ENTER || code == KeyEvent.VK_SPACE) {
        if (phase == "menu") startGame(family)
        else interact()
      } else if (code == KeyEvent.VK_1 || code == KeyEvent.VK_NUMPAD1) {
        if (phase == "menu") startGame("Gokuldham Family")
      } else if (code == KeyEvent.VK_2 || code == KeyEvent.VK_NUMPAD2) {
        if (phase == "menu") startGame("Normal Family")
      } else if (code == KeyEvent.VK_P) {
        if (phase == "playing") {
          pause = !pause
          if (!pause) showToast("Game resumed.", 80)
        }
      } else if (code == KeyEvent.VK_ESCAPE) {
        phase = "menu"
        pause = false
        stairAnimating = false
        clearHeldKeys()
        showToast("Choose a family and start a new Diwali adventure!", 150)
      }
      repaint()
    }

    def startLoop(): Unit = {
      val timer = new Timer(30, new ActionListener {
        override def actionPerformed(e: ActionEvent): Unit = {
          updateGame()
          repaint()
        }
      })
      timer.setCoalesce(true)
      timer.start()
    }

    private def askName(prompt: String, defaultValue: String): String = {
      val answer = JOptionPane.showInputDialog(this, prompt, defaultValue)
      if (answer == null || answer.trim.isEmpty) defaultValue else answer.trim.take(24)
    }

    private def startGame(which: String): Unit = {
      clearHeldKeys()
      family = which
      playerName = askName("Choose your character name:", "Bixuu")
      familyName = if (family == "Normal Family") askName("Enter your family / house name:", "My Family") else "Gokuldham Society"
      phase = "playing"
      pause = false
      score = 0
      stage = 0
      supplies = false
      pujaDone = false
      scene = "letter"
      ticketChecked = false
      ticketBooked = false
      currentCoach = 1
      coachCoins = Array(false, false, false)
      trainArrivalTicks = 0
      travelTicks = 0
      stairAnimating = false
      targets = Array.empty[DecoTarget]
      playerX = math.max(70.0, getWidth * 0.16)
      playerY = math.max(365.0, getHeight * 0.66)
      showToast("A letter has arrived from Goa! Find it and read your family message.", 220)
      requestFocusInWindow()
    }

    private def beginDiwaliMissions(): Unit = {
      scene = "diwali"
      stage = 0
      supplies = false
      pujaDone = false
      stairAnimating = false
      if (family == "Gokuldham Family") {
        playerX = 410.0; playerY = 325.0
        targets = Array(
          new DecoTarget(505, 480, "Diyas"),
          new DecoTarget(570, 500, "Flower Toran"),
          new DecoTarget(640, 475, "Fairy Lights"),
          new DecoTarget(685, 515, "Rangoli")
        )
        showToast("Welcome! Walk to the staircase and press E to go down.", 200)
      } else {
        playerX = 160.0; playerY = 480.0
        targets = Array(
          new DecoTarget(120, 475, "Diyas"),
          new DecoTarget(185, 510, "Flower Toran"),
          new DecoTarget(250, 470, "Fairy Lights"),
          new DecoTarget(310, 510, "Rangoli")
        )
        showToast("You reached Goa! Buy decoration supplies, decorate your home, and perform puja.", 220)
      }
    }

    private def showToast(message: String, ticks: Int): Unit = {
      toast = message
      toastTicks = ticks
    }

    private def distance(x1: Double, y1: Double, x2: Double, y2: Double): Double = {
      val dx = x1 - x2; val dy = y1 - y2
      math.sqrt(dx * dx + dy * dy)
    }

    private def updateGame(): Unit = {
      tick += 1
      if (toastTicks > 0) toastTicks -= 1
      if (phase != "playing" || pause) return

      if (scene == "wait" && trainArrivalTicks < 100) trainArrivalTicks += 1
      if (scene == "coach" && travelTicks < 360) travelTicks += 1

      if (stairAnimating) {
        stairTick += 1
        val progress = math.min(1.0, stairTick / 34.0)
        playerX = stairStartX + (420.0 - stairStartX) * progress
        playerY = stairStartY + (405.0 - stairStartY) * progress
        walkFrame += 1
        if (progress >= 1.0) {
          stairAnimating = false
          stage = 1
          score += 20
          showToast("You came down the stairs! Ask the society secretary for permission.", 190)
        }
        return
      }

      var dx = 0.0; var dy = 0.0
      if (moveLeft) dx -= 1.0
      if (moveRight) dx += 1.0
      if (moveUp) dy -= 1.0
      if (moveDown) dy += 1.0
      if (dx != 0.0 || dy != 0.0) {
        val len = math.sqrt(dx * dx + dy * dy)
        val speed = if (scene == "coach") 3.6 else 3.35
        playerX += dx / len * speed
        playerY += dy / len * speed
        playerX = math.max(30.0, math.min(getWidth - 30.0, playerX))
        playerY = math.max(290.0, math.min(getHeight - 95.0, playerY))
        walkFrame += 1
      }
    }

    private def interact(): Unit = {
      if (phase == "menu") { startGame(family); return }
      if (phase == "complete") {
        phase = "menu"
        showToast("Choose a family and start a new adventure!", 150)
        return
      }
      if (pause) { pause = false; showToast("Game resumed.", 80); return }
      if (stairAnimating) return
      if (scene != "diwali") interactJourney() else interactDiwali()
    }

    private def interactJourney(): Unit = {
      val w = getWidth.toDouble; val h = getHeight.toDouble
      scene match {
        case "letter" =>
          if (distance(playerX, playerY, w * 0.70, h * 0.63) < 112) {
            scene = "ticket"; playerX = w * 0.12; playerY = h * 0.66
            score += 10
            showToast("Letter: Come to Goa for Diwali! Your grandparents are waiting. Go to the station ticket counter.", 230)
          } else showToast("Move to the glowing letter on the table and press E / ENTER.", 120)
        case "ticket" =>
          if (distance(playerX, playerY, w * 0.78, h * 0.60) < 125) {
            ticketBooked = true; scene = "missed"; playerX = w * 0.12; playerY = h * 0.66
            score += 20
            showToast("Ticket booked! Oh no, the train has left. Find the TTE on the platform.", 210)
          } else showToast("Walk to the ticket counter and press E / ENTER to book your ticket.", 120)
        case "missed" =>
          if (distance(playerX, playerY, w * 0.70, h * 0.62) < 120) {
            scene = "wait"; trainArrivalTicks = 0; playerX = w * 0.14; playerY = h * 0.66
            score += 15
            showToast("TTE: The next train is coming soon. Wait near the marked platform door.", 200)
          } else showToast("Talk to the TTE (Train Ticket Examiner) to ask about the next train.", 125)
        case "wait" =>
          if (trainArrivalTicks < 100) showToast("The next train is on its way. Please wait a little.", 100)
          else if (distance(playerX, playerY, w * 0.53, h * 0.62) < 150) {
            scene = "coach"; currentCoach = 1; ticketChecked = false; travelTicks = 0
            playerX = w * 0.12; playerY = h * 0.64
            showToast("You boarded the next train! Show your ticket to the TTE in Coach 1.", 200)
          } else showToast("The train has arrived! Walk to the glowing open door and press E.", 130)
        case "coach" =>
          val coinX = w * 0.44; val coinY = h * 0.57
          if (!coachCoins(currentCoach - 1) && distance(playerX, playerY, coinX, coinY) < 70) {
            coachCoins(currentCoach - 1) = true; score += 25
            showToast("Coin collected! " + coachCoins.count(identity) + "/3 collected.", 130)
          } else if (currentCoach == 1 && !ticketChecked && distance(playerX, playerY, w * 0.70, h * 0.59) < 110) {
            ticketChecked = true; score += 35
            showToast("TTE: Ticket verified! Collect your coins and move through all three coaches.", 190)
          } else if (distance(playerX, playerY, w * 0.88, h * 0.64) < 105) {
            if (!ticketChecked) showToast("Show your ticket to the TTE in Coach 1 first.", 130)
            else if (currentCoach < 3) {
              currentCoach += 1; playerX = w * 0.12; playerY = h * 0.64
              showToast("You entered Coach " + currentCoach + ". Find the coin and move to the right-side door.", 170)
            } else if (travelTicks < 220) {
              showToast("The train is waiting at a signal. Journey progress: " + math.min(100, travelTicks * 100 / 220) + "%.", 115)
            } else {
              scene = "goaStation"; playerX = w * 0.12; playerY = h * 0.66; score += 50
              showToast("The train has arrived in Goa! Walk to the station exit.", 200)
            }
          } else {
            val hint = if (currentCoach == 1 && !ticketChecked) "Find the TTE in Coach 1 and show your ticket." else "Collect the glowing coin or walk to the door on the right."
            showToast(hint, 110)
          }
        case "goaStation" =>
          if (distance(playerX, playerY, w * 0.84, h * 0.64) < 130) {
            scene = "goaHome"; playerX = w * 0.13; playerY = h * 0.66; score += 20
            showToast("You are outside Goa station. Find your grandparents' house.", 200)
          } else showToast("Walk to the glowing station exit on the right and press E.", 120)
        case "goaHome" =>
          if (distance(playerX, playerY, w * 0.74, h * 0.60) < 130) {
            score += 50
            showToast("Aaji-Ajoba: Welcome home! Happy Diwali! Let us prepare for the celebration.", 170)
            beginDiwaliMissions()
          } else showToast("Walk to Aaji-Ajoba near the front door and press E.", 120)
        case _ => showToast("Follow the glowing marker, then press E / ENTER.", 100)
      }
    }

    private def interactDiwali(): Unit = {
      if (family == "Gokuldham Family") {
        stage match {
          case 0 =>
            if (distance(playerX, playerY, 420, 355) < 105) {
              stairAnimating = true; stairTick = 0; stairStartX = playerX; stairStartY = playerY
              showToast("Walking down the staircase...", 70)
            } else showToast("Walk close to the staircase in the middle building and press E.", 110)
          case 1 =>
            if (distance(playerX, playerY, 535, 430) < 100) {
              stage = 2; score += 30
              showToast("Secretary: Permission granted! Visit Abdul's shop for supplies.", 170)
            } else showToast("Go near the society secretary and press E.", 110)
          case 2 =>
            if (distance(playerX, playerY, getWidth - 175, 420) < 115) buySupplies()
            else showToast("Walk to Abdul's shop on the right and press E.", 110)
          case 3 => decorateAtNearestTarget()
          case 4 =>
            if (distance(playerX, playerY, 535, 535) < 105) finishPuja()
            else showToast("Go to the community puja area and press E.", 110)
          case _ => phase = "complete"
        }
      } else {
        stage match {
          case 0 =>
            if (distance(playerX, playerY, getWidth - 175, 420) < 115) buySupplies()
            else showToast("Go to Abdul's shop on the right to buy supplies.", 110)
          case 1 => decorateAtNearestTarget()
          case 2 =>
            if (distance(playerX, playerY, 225, 535) < 105) finishPuja()
            else showToast("Return to your home puja area and press E.", 110)
          case _ => phase = "complete"
        }
      }
    }

    private def buySupplies(): Unit = {
      if (!supplies) {
        supplies = true; score += 50
        stage = if (family == "Gokuldham Family") 3 else 1
        showToast("Abdul: Decoration supplies collected! Go decorate the glowing markers.", 180)
      } else showToast("You already have all the decoration supplies. Happy Diwali!", 120)
    }

    private def decorateAtNearestTarget(): Unit = {
      if (!supplies) { showToast("First collect decoration supplies from Abdul's shop!", 120); return }
      var nearest: DecoTarget = null; var nearestD = 99999.0
      targets.foreach { t =>
        if (!t.done) {
          val d = distance(playerX, playerY, t.x, t.y)
          if (d < nearestD) { nearestD = d; nearest = t }
        }
      }
      if (nearest != null && nearestD < 88) {
        nearest.done = true; score += 25
        val remaining = targets.count(t => !t.done)
        if (remaining == 0) {
          stage = if (family == "Gokuldham Family") 4 else 2
          showToast("Fantastic! All four decorations are done. Go to the puja area.", 180)
        } else showToast(nearest.label + " decorated! " + (4 - remaining) + "/4 complete.", 135)
      } else {
        val next = targets.find(t => !t.done)
        if (next.isDefined) showToast("Move near the glowing " + next.get.label + " marker and press E.", 110)
        else showToast("All decorations complete. Go to the puja area.", 100)
      }
    }

    private def finishPuja(): Unit = {
      if (!supplies) showToast("Collect the supplies before starting puja.", 110)
      else if (targets.exists(t => !t.done)) showToast("Finish all four decoration tasks first.", 110)
      else {
        pujaDone = true; score += 100; phase = "complete"
        showToast("Shubh Deepawali! Mission completed!", 250)
      }
    }

    private def missionText: String = {
      if (scene != "diwali") scene match {
        case "letter" => "MISSION 1/7: Find the letter from Goa."
        case "ticket" => "MISSION 2/7: Book your railway ticket."
        case "missed" => "MISSION 3/7: Talk to the TTE."
        case "wait" => "MISSION 4/7: Wait for and board the next train."
        case "coach" => "MISSION 5/7: Verify ticket, collect coins, cross coaches."
        case "goaStation" => "MISSION 6/7: Exit Goa station."
        case "goaHome" => "MISSION 7/7: Greet Aaji-Ajoba."
        case _ => "Continue your adventure."
      } else if (family == "Gokuldham Family") stage match {
        case 0 => "DIWALI 1/5: Go down the society staircase."
        case 1 => "DIWALI 2/5: Ask the society secretary."
        case 2 => "DIWALI 3/5: Collect supplies from Abdul."
        case 3 => "DIWALI 4/5: Decorate all markers (" + targets.count(_.done) + "/4)."
        case 4 => "DIWALI 5/5: Go to community puja."
        case _ => "All missions completed!"
      } else stage match {
        case 0 => "DIWALI 1/3: Buy supplies from Abdul."
        case 1 => "DIWALI 2/3: Decorate your home (" + targets.count(_.done) + "/4)."
        case 2 => "DIWALI 3/3: Go to home puja."
        case _ => "All missions completed!"
      }
    }

    override def paintComponent(raw: Graphics): Unit = {
      super.paintComponent(raw)
      val g = raw.asInstanceOf[Graphics2D]
      g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
      g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON)
      if (phase == "menu") drawMenu(g)
      else {
        if (scene == "diwali") drawDiwaliWorld(g) else drawJourneyScene(g)
        drawHUD(g)
        if (phase == "complete") drawComplete(g)
        if (pause) drawPause(g)
      }
    }

    private def font(size: Int, style: Int = Font.PLAIN): Font = new Font("SansSerif", style, size)
    private def centered(g: Graphics2D, text: String, x: Int, y: Int): Unit = g.drawString(text, x - g.getFontMetrics.stringWidth(text) / 2, y)

    private def drawMenu(g: Graphics2D): Unit = {
      val w = getWidth; val h = getHeight
      g.setPaint(new GradientPaint(0, 0, new Color(17, 10, 45), 0, h, new Color(84, 27, 62))); g.fillRect(0, 0, w, h)
      drawStars(g, 0, 0, w, h, 90)
      g.setColor(new Color(255, 224, 146, 35)); g.fillOval(w - 180, 35, 120, 120)
      g.setColor(new Color(255, 235, 176)); g.fillOval(w - 153, 62, 66, 66)
      g.setFont(font(40, Font.BOLD)); g.setColor(new Color(255, 200, 67)); centered(g, "BIXUU DIWALI TRAIN ADVENTURE", w / 2, 103)
      g.setFont(font(17, Font.BOLD)); g.setColor(new Color(255, 239, 206)); centered(g, "LETTER  >  TICKET  >  TRAIN  >  GOA  >  DIWALI", w / 2, 142)
      g.setFont(font(14, Font.PLAIN)); g.setColor(new Color(227, 218, 246)); centered(g, "Explore three train coaches, visit your grandparents, and celebrate Diwali", w / 2, 177)
      val cardW = math.min(360, (w - 90) / 2); val cardH = 235; val gap = 28
      val left = (w - (cardW * 2 + gap)) / 2; val top = 215
      menuGokul = new java.awt.Rectangle(left, top, cardW, cardH)
      menuNormal = new java.awt.Rectangle(left + cardW + gap, top, cardW, cardH)
      drawFamilyCard(g, menuGokul, "GOKULDHAM FAMILY", "Train + Society missions", "Goa trip > stairs > permission", "Shop > decorate > community puja", true)
      drawFamilyCard(g, menuNormal, "NORMAL FAMILY", "Train + Home missions", "Goa trip > grandparents' home", "Buy supplies > decorate > puja", false)
      g.setFont(font(14, Font.BOLD)); g.setColor(new Color(255, 220, 124)); centered(g, "CLICK A CARD  OR  PRESS 1 / 2", w / 2, top + cardH + 35)
      g.setFont(font(13, Font.PLAIN)); g.setColor(new Color(221, 211, 238)); centered(g, "WASD / ARROWS = MOVE     E / ENTER / SPACE = INTERACT", w / 2, h - 27)
    }

    private def drawFamilyCard(g: Graphics2D, r: java.awt.Rectangle, heading: String, sub: String, line1: String, line2: String, society: Boolean): Unit = {
      g.setPaint(new GradientPaint(r.x, r.y, new Color(57, 39, 84), r.x + r.width, r.y + r.height, new Color(29, 23, 59)))
      g.fill(new RoundRectangle2D.Double(r.x, r.y, r.width, r.height, 28, 28))
      g.setStroke(new BasicStroke(2f)); g.setColor(if (society) new Color(255, 185, 64) else new Color(71, 219, 193))
      g.draw(new RoundRectangle2D.Double(r.x, r.y, r.width, r.height, 28, 28))
      if (society) drawMiniBuilding(g, r.x + r.width / 2 - 48, r.y + 20, 96, 72) else drawMiniHouse(g, r.x + r.width / 2 - 46, r.y + 26, 92, 66)
      g.setFont(font(20, Font.BOLD)); g.setColor(new Color(255, 219, 120)); centered(g, heading, r.x + r.width / 2, r.y + 121)
      g.setFont(font(15, Font.BOLD)); g.setColor(Color.WHITE); centered(g, sub, r.x + r.width / 2, r.y + 150)
      g.setFont(font(12, Font.PLAIN)); g.setColor(new Color(226, 219, 242)); centered(g, line1, r.x + r.width / 2, r.y + 176); centered(g, line2, r.x + r.width / 2, r.y + 195)
      g.setFont(font(12, Font.BOLD)); g.setColor(if (society) new Color(255, 198, 86) else new Color(106, 237, 210))
      centered(g, if (society) "CLICK TO PLAY  (1)" else "CLICK TO PLAY  (2)", r.x + r.width / 2, r.y + 219)
    }

    private def drawJourneyScene(g: Graphics2D): Unit = {
      val w = getWidth; val h = getHeight
      g.setPaint(new GradientPaint(0, 0, new Color(16, 20, 59), 0, h, new Color(82, 37, 74))); g.fillRect(0, 0, w, h)
      drawStars(g, 0, 0, w, 255, 55)
      g.setColor(new Color(255, 221, 155, 35)); g.fillOval(w - 175, 28, 105, 105)
      g.setColor(new Color(255, 231, 171)); g.fillOval(w - 149, 54, 55, 55)
      g.setPaint(new GradientPaint(0, 260, new Color(100, 92, 125), 0, h, new Color(35, 38, 61))); g.fillRect(0, 260, w, h - 260)
      g.setColor(new Color(255, 255, 255, 15))
      var gx = -40; while (gx < w + 100) { g.drawLine(gx, 330, gx + 125, h); gx += 100 }
      var gy = 350; while (gy < h) { g.drawLine(0, gy, w, gy); gy += 46 }

      scene match {
        case "letter" =>
          drawMiniHouse(g, 70, 300, 280, 190)
          g.setColor(new Color(86, 48, 52)); g.fillRoundRect((w * 0.58).toInt, 400, 180, 95, 12, 12)
          g.setColor(new Color(188, 111, 72)); g.fillRect((w * 0.57).toInt, 390, 190, 16)
          g.setColor(new Color(255, 232, 185)); g.fillRoundRect((w * 0.66).toInt, 370, 66, 52, 6, 6)
          g.setColor(new Color(174, 48, 52)); g.fillRoundRect((w * 0.67).toInt, 379, 48, 32, 4, 4)
          drawMarker(g, (w * 0.70).toInt, (h * 0.63).toInt - 28, new Color(255, 214, 86))
          drawPerson(g, w * 0.43, h * 0.62, new Color(77, 159, 184), new Color(242, 190, 143), tick)
          g.setFont(font(16, Font.BOLD)); g.setColor(new Color(255, 231, 166)); centered(g, "LETTER FROM GOA", (w * 0.70).toInt, 355)
          g.setFont(font(13)); g.setColor(Color.WHITE); centered(g, "Your grandparents invited you for Diwali", (w * 0.52).toInt, 545)
        case "ticket" =>
          drawStation(g, w, h, "CITY RAILWAY STATION"); drawRailTracks(g, w, h)
          val cx = (w * 0.68).toInt; val cy = 345
          g.setColor(new Color(47, 42, 72)); g.fillRoundRect(cx - 15, cy, 265, 126, 14, 14)
          g.setColor(new Color(243, 167, 77)); g.fillRoundRect(cx - 15, cy - 18, 265, 35, 10, 10)
          g.setFont(font(18, Font.BOLD)); g.setColor(new Color(62, 36, 55)); centered(g, "TICKET COUNTER", cx + 117, cy + 5)
          drawPerson(g, w * 0.78, h * 0.60, new Color(64, 137, 190), new Color(245, 190, 140), tick)
        case "missed" =>
          drawStation(g, w, h, "PLATFORM 1 - DEPARTED"); drawRailTracks(g, w, h)
          val trainX = w - ((tick * 7) % (w + 600)) - 480; drawPassengerTrain(g, trainX.toInt, 365, 146, 88)
          drawPerson(g, w * 0.70, h * 0.62, new Color(43, 111, 170), new Color(244, 194, 145), tick)
          g.setFont(font(16, Font.BOLD)); g.setColor(new Color(255, 206, 108)); centered(g, "OH NO! THE TRAIN HAS LEFT", w / 2, 305)
        case "wait" =>
          drawStation(g, w, h, "PLATFORM 2 - NEXT TRAIN"); drawRailTracks(g, w, h)
          val p = math.min(1.0, trainArrivalTicks / 100.0); val tx = ((w + 80) + (w * 0.30 - (w + 80)) * p).toInt
          drawPassengerTrain(g, tx, 365, 146, 88)
          g.setFont(font(14, Font.BOLD)); g.setColor(new Color(255, 220, 130)); centered(g, if (trainArrivalTicks < 100) "NEXT TRAIN ARRIVING... " + trainArrivalTicks + "%" else "TRAIN ARRIVED - BOARD THE OPEN DOOR", w / 2, 300)
          if (trainArrivalTicks >= 100) drawMarker(g, (w * 0.53).toInt, (h * 0.62).toInt - 35, new Color(100, 248, 193))
        case "coach" =>
          g.setPaint(new GradientPaint(0, 260, new Color(36, 47, 82), 0, h, new Color(23, 29, 50))); g.fillRect(0, 260, w, h - 260)
          var i = 0
          while (i < 5) {
            val wx = 28 + i * ((w - 55) / 5)
            g.setColor(new Color(15, 30, 58)); g.fillRoundRect(wx, 300, 138, 82, 10, 10)
            g.setPaint(new GradientPaint(wx, 302, new Color(113, 197, 212), wx, 380, new Color(77, 145, 109))); g.fillRoundRect(wx + 5, 305, 128, 72, 8, 8)
            val slide = (tick * 2 + i * 41) % 150; g.setColor(new Color(41, 119, 75)); g.fillOval(wx + slide - 15, 335, 35, 45); g.fillRect(wx + slide, 325, 7, 54); i += 1
          }
          g.setColor(new Color(255, 212, 124)); g.fillRect(0, 393, w, 7)
          g.setPaint(new GradientPaint(0, 400, new Color(67, 77, 104), 0, h, new Color(36, 40, 62))); g.fillRect(0, 400, w, h - 400)
          var sx = 25
          while (sx < w - 80) { g.setColor(new Color(21, 29, 57)); g.fillRoundRect(sx, 420, 75, 92, 12, 12); g.setColor(new Color(178, 63, 79)); g.fillRoundRect(sx + 5, 425, 65, 42, 9, 9); sx += 145 }
          g.setColor(new Color(18, 24, 42)); g.fillRoundRect(w - 105, 390, 72, 180, 9, 9); g.setColor(new Color(255, 194, 87)); g.drawRoundRect(w - 105, 390, 72, 180, 9, 9)
          g.setFont(font(15, Font.BOLD)); g.setColor(new Color(255, 223, 143)); centered(g, "COACH " + currentCoach, w / 2, 285)
          if (currentCoach == 1 && !ticketChecked) { drawPerson(g, w * 0.70, h * 0.59, new Color(43, 111, 170), new Color(244, 194, 145), tick); drawMarker(g, (w * 0.70).toInt, (h * 0.59).toInt - 35, new Color(98, 242, 192)) }
          if (!coachCoins(currentCoach - 1)) { val cx = (w * 0.44).toInt; val cy = (h * 0.57).toInt; g.setColor(new Color(255, 205, 55, 60)); g.fillOval(cx - 20, cy - 20, 40, 40); g.setColor(new Color(255, 198, 42)); g.fillOval(cx - 10, cy - 10, 20, 20); g.setColor(new Color(255, 244, 163)); g.drawOval(cx - 6, cy - 6, 12, 12) }
          g.setColor(new Color(255, 218, 115)); g.setFont(font(12, Font.BOLD)); g.drawString("COACH " + currentCoach + " / 3", 25, h - 98); g.drawString("Journey: " + math.min(100, travelTicks * 100 / 220) + "%", 25, h - 78)
        case "goaStation" =>
          drawStation(g, w, h, "GOA - MADGAON STATION"); drawRailTracks(g, w, h); drawPassengerTrain(g, 40, 365, 146, 88)
          drawPalm(g, w - 210, 285); drawPalm(g, w - 105, 305); drawMarker(g, (w * 0.84).toInt, (h * 0.64).toInt - 30, new Color(100, 248, 193))
          g.setFont(font(14, Font.BOLD)); g.setColor(new Color(255, 225, 144)); centered(g, "EXIT TO GOA", (w * 0.84).toInt, 340)
        case "goaHome" =>
          g.setColor(new Color(30, 80, 71)); g.fillRect(0, 260, w, h - 260); g.setColor(new Color(63, 117, 82)); g.fillOval(35, 280, 200, 125); g.fillOval(w - 260, 275, 225, 140)
          drawPalm(g, 70, 310); drawPalm(g, w - 95, 315); drawMiniHouse(g, (w * 0.32).toInt, 318, 300, 210)
          drawDiyas(g, (w * 0.32).toInt + 60, 505, 0.65); drawRangoli(g, (w * 0.32).toInt + 250, 545)
          drawPerson(g, w * 0.70, h * 0.60, new Color(155, 88, 160), new Color(232, 179, 133), tick); drawPerson(g, w * 0.78, h * 0.60, new Color(65, 143, 104), new Color(236, 188, 142), tick + 4)
          g.setFont(font(12, Font.BOLD)); g.setColor(new Color(255, 241, 199)); centered(g, "AAJI", (w * 0.70).toInt, (h * 0.60).toInt + 35); centered(g, "AJOBA", (w * 0.78).toInt, (h * 0.60).toInt + 35)
          drawMarker(g, (w * 0.74).toInt, (h * 0.60).toInt - 38, new Color(255, 215, 84)); g.setFont(font(15, Font.BOLD)); centered(g, "AAJI-AJOBA'S HOUSE - GOA", w / 2, 285)
        case _ =>
      }
      drawPlayer(g)
      drawAmbient(g)
    }

    private def drawDiwaliWorld(g: Graphics2D): Unit = {
      val w = getWidth; val h = getHeight
      g.setPaint(new GradientPaint(0, 0, new Color(20, 17, 57), 0, 245, new Color(91, 40, 81))); g.fillRect(0, 0, w, h)
      drawStars(g, 0, 0, w, 210, 55)
      g.setColor(new Color(255, 223, 143, 32)); g.fillOval(w - 170, 25, 108, 108); g.setColor(new Color(255, 229, 164)); g.fillOval(w - 146, 49, 60, 60)
      drawSkyline(g, w)
      g.setPaint(new GradientPaint(0, 205, new Color(88, 76, 111), 0, h, new Color(39, 42, 64))); g.fillRect(0, 205, w, h - 205)
      g.setColor(new Color(255, 194, 68, 120)); g.fillRoundRect(0, h - 70, w, 70, 14, 14); g.setColor(new Color(58, 42, 66)); g.fillRect(0, h - 65, w, 65)
      drawRoadDashes(g, w, h)
      drawNormalHouse(g); drawSociety(g); drawShop(g); drawSecretary(g); drawPujaArea(g); drawDecorationTargets(g); drawPlayer(g); drawAmbient(g)
    }

    private def drawHUD(g: Graphics2D): Unit = {
      val w = getWidth; val h = getHeight
      g.setColor(new Color(12, 12, 31, 225)); g.fillRoundRect(14, 12, w - 28, 94, 18, 18)
      g.setColor(new Color(255, 199, 79)); g.setStroke(new BasicStroke(1.5f)); g.drawRoundRect(14, 12, w - 28, 94, 18, 18)
      g.setFont(font(18, Font.BOLD)); g.setColor(new Color(255, 212, 108)); g.drawString(if (scene == "diwali") "BIXUU DIWALI ADVENTURE" else "BIXUU TRAIN ADVENTURE", 30, 38)
      g.setFont(font(11, Font.BOLD)); g.setColor(Color.WHITE); g.drawString(playerName + "  |  " + (if (family == "Normal Family") familyName else family), 31, 57)
      g.setFont(font(13, Font.BOLD)); g.setColor(new Color(131, 245, 205)); g.drawString("SCORE " + score + "   COINS " + coachCoins.count(identity) + "/3", 31, 81)
      g.setFont(font(13, Font.BOLD)); g.setColor(new Color(255, 243, 219)); g.drawString(missionText, 285, 39)
      g.setFont(font(11, Font.PLAIN)); g.setColor(new Color(222, 218, 240)); g.drawString("WASD / ARROWS: MOVE    E / ENTER / SPACE: INTERACT    P: PAUSE    ESC: MENU", 285, 61)
      if (scene == "coach") { g.setColor(new Color(255, 211, 107)); g.drawString("Coach " + currentCoach + "/3  | Ticket " + (if (ticketChecked) "verified" else "not checked") + " | Journey " + math.min(100, travelTicks * 100 / 220) + "%", 285, 82) }
      else if (scene == "wait") { g.setColor(new Color(255, 211, 107)); g.drawString("Train arrival: " + trainArrivalTicks + "%", 285, 82) }
      else { g.setColor(new Color(255, 211, 107)); g.drawString(if (scene == "diwali") "Supplies: " + (if (supplies) "COLLECTED" else "NEEDED") + "   Decorations: " + targets.count(_.done) + "/4" else "Goa family trip - Diwali celebration", 285, 82) }
      if (toastTicks > 0 && toast != null && toast.nonEmpty) {
        g.setFont(font(13, Font.BOLD)); val tw = math.min(w - 38, g.getFontMetrics.stringWidth(toast) + 34)
        g.setColor(new Color(15, 12, 34, 232)); g.fillRoundRect((w - tw) / 2, h - 68, tw, 32, 12, 12)
        g.setColor(new Color(255, 207, 93)); g.drawRoundRect((w - tw) / 2, h - 68, tw, 32, 12, 12)
        g.setColor(new Color(255, 245, 222)); centered(g, toast, w / 2, h - 47)
      }
      g.setFont(font(11, Font.BOLD)); g.setColor(new Color(255, 230, 156)); centered(g, "Move close to the glowing marker, then press E / ENTER", w / 2, h - 25)
    }

    private def drawPlayer(g: Graphics2D): Unit = {
      val moving = moveLeft || moveRight || moveUp || moveDown
      val bob = if (moving) math.sin(walkFrame * 0.65) * 3.0 else math.sin(tick * 0.06) * 1.2
      val x = playerX.toInt; val y = (playerY + bob).toInt
      g.setColor(new Color(0, 0, 0, 75)); g.fillOval(x - 18, y + 27, 37, 13)
      g.setStroke(new BasicStroke(6f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND))
      val swing = if (moving) (math.sin(walkFrame * 0.65) * 4).toInt else 0
      g.setColor(new Color(34, 44, 86)); g.drawLine(x - 5, y + 12, x - 7 + swing, y + 28); g.drawLine(x + 5, y + 12, x + 7 - swing, y + 28)
      g.setColor(new Color(255, 198, 93)); g.fillRoundRect(x - 14, y - 2, 28, 24, 9, 9)
      g.setColor(new Color(51, 115, 208)); g.fillRoundRect(x - 13, y - 2, 26, 18, 8, 8)
      g.setColor(new Color(249, 191, 143)); g.fillOval(x - 10, y - 23, 20, 22)
      g.setColor(new Color(56, 34, 30)); g.fillArc(x - 11, y - 26, 22, 16, 0, 180)
      g.setColor(new Color(33, 25, 40)); g.fillOval(x - 5, y - 14, 2, 3); g.fillOval(x + 4, y - 14, 2, 3)
      g.setColor(new Color(126, 55, 43)); g.drawArc(x - 4, y - 10, 8, 5, 200, 140)
      g.setColor(new Color(247, 207, 105)); g.setFont(font(10, Font.BOLD)); centered(g, playerName.take(12).toUpperCase, x, y - 33)
    }

    private def drawDecorationTargets(g: Graphics2D): Unit = {
      targets.foreach { t =>
        if (t.done) {
          t.label match {
            case "Diyas" => drawDiyas(g, t.x.toInt - 21, t.y.toInt - 5, 0.52)
            case "Flower Toran" => drawToran(g, t.x.toInt - 40, t.y.toInt - 14, 80, 20, true)
            case "Fairy Lights" =>
              var i = 0; while (i < 6) { val lx = t.x.toInt - 28 + i * 11; g.setColor(new Color(255, 228, 111)); g.fillOval(lx, t.y.toInt - 9 + (i % 2) * 4, 7, 7); i += 1 }
            case "Rangoli" => drawRangoli(g, t.x.toInt, t.y.toInt)
            case _ =>
          }
          g.setColor(new Color(96, 245, 174)); g.setFont(font(10, Font.BOLD)); centered(g, "DONE", t.x.toInt, t.y.toInt + 24)
        } else if (phase == "playing" && ((family == "Gokuldham Family" && stage == 3) || (family == "Normal Family" && stage == 1))) {
          drawMarker(g, t.x.toInt, t.y.toInt - 20 + (math.sin(tick * 0.12) * 4).toInt, new Color(82, 237, 201))
          g.setColor(new Color(236, 255, 248)); g.setFont(font(10, Font.BOLD)); centered(g, t.label.toUpperCase, t.x.toInt, t.y.toInt + 20)
        }
      }
    }

    private def drawSecretary(g: Graphics2D): Unit = {
      drawPerson(g, 535, 430, new Color(68, 137, 196), new Color(247, 194, 147), tick)
      g.setFont(font(11, Font.BOLD)); g.setColor(new Color(250, 245, 220)); centered(g, "SECRETARY", 535, 459)
      if (family == "Gokuldham Family" && stage == 1) drawMarker(g, 535, 383, new Color(93, 233, 180))
    }

    private def drawPujaArea(g: Graphics2D): Unit = {
      val px = if (family == "Gokuldham Family") 535 else 225; val py = 535
      g.setColor(new Color(20, 14, 30, 80)); g.fillOval(px - 48, py + 1, 96, 18)
      g.setColor(new Color(145, 67, 83)); g.fillRoundRect(px - 38, py - 15, 76, 27, 5, 5)
      g.setColor(new Color(255, 188, 70)); g.fillRect(px - 41, py - 20, 82, 8)
      g.setColor(new Color(255, 223, 139)); g.fillOval(px - 22, py - 45, 44, 32)
      g.setColor(new Color(185, 72, 57)); g.fillOval(px - 13, py - 38, 26, 24); g.setColor(new Color(255, 221, 117)); g.fillOval(px - 5, py - 31, 10, 10)
      drawMiniDiyas(g, px - 27, py - 4); drawMiniDiyas(g, px + 7, py - 4)
      g.setFont(font(11, Font.BOLD)); g.setColor(new Color(255, 242, 201)); centered(g, if (family == "Gokuldham Family") "COMMUNITY PUJA" else "HOME PUJA", px, py + 31)
      if ((family == "Gokuldham Family" && stage == 4) || (family == "Normal Family" && stage == 2)) drawMarker(g, px, py - 65, new Color(255, 210, 77))
      if (pujaDone) centered(g, "SHUBH DEEPAWALI!", px, py - 76)
    }

    private def drawShop(g: Graphics2D): Unit = {
      val x = getWidth - 270; val y = 280; val w = 225; val h = 159
      g.setColor(new Color(10, 7, 23, 90)); g.fillOval(x - 8, y + h - 1, w + 22, 23)
      g.setPaint(new GradientPaint(x, y, new Color(94, 47, 85), x + w, y + h, new Color(49, 31, 65))); g.fillRect(x, y, w, h)
      val roof = new Polygon(Array(x - 10, x + w / 2, x + w + 10), Array(y + 8, y - 34, y + 8), 3)
      g.setColor(new Color(240, 137, 56)); g.fillPolygon(roof); g.setColor(new Color(255, 206, 112)); g.fillRect(x + 12, y + 13, w - 24, 38)
      g.setFont(font(20, Font.BOLD)); g.setColor(new Color(75, 33, 58)); centered(g, "ABDUL'S SHOP", x + w / 2, y + 39)
      g.setColor(new Color(41, 28, 53)); g.fillRoundRect(x + 18, y + 68, 91, 79, 6, 6); drawMiniDiyas(g, x + 29, y + 93)
      g.setColor(new Color(244, 80, 115)); g.fillOval(x + 64, y + 105, 13, 13); g.setColor(new Color(89, 224, 175)); g.fillOval(x + 79, y + 105, 13, 13)
      g.setColor(new Color(255, 226, 147)); g.fillRoundRect(x + 131, y + 91, 68, 52, 6, 6); g.setColor(new Color(64, 47, 76)); g.fillRect(x + 137, y + 97, 56, 39)
      g.setColor(new Color(255, 219, 121)); g.setFont(font(12, Font.BOLD)); centered(g, "Lights", x + 165, y + 116)
      if (supplies) { g.setColor(new Color(46, 220, 152)); g.fillOval(x + w - 31, y + 8, 20, 20); g.setColor(new Color(13, 65, 48)); centered(g, "?", x + w - 21, y + 23) }
    }

    private def drawSociety(g: Graphics2D): Unit = {
      val x = 347; val y = 205; val w = 365; val h = 249
      g.setColor(new Color(10, 7, 22, 100)); g.fillOval(x - 10, y + h - 2, w + 35, 32)
      g.setPaint(new GradientPaint(x, y, new Color(89, 95, 151), x + w, y + h, new Color(53, 48, 94))); g.fillRect(x, y, w, h)
      g.setColor(new Color(255, 198, 109)); g.fillRect(x, y, w, 10)
      var row = 0
      while (row < 3) { var col = 0; while (col < 4) { val wx = x + 22 + col * 83; val wy = y + 25 + row * 62
        g.setColor(new Color(28, 27, 61)); g.fillRoundRect(wx, wy, 58, 43, 5, 5); g.setColor(new Color(255, 221, 135, 235)); g.fillRect(wx + 7, wy + 7, 44, 26)
        g.setColor(new Color(48, 54, 91)); g.fillRect(wx + 7, wy + 7, 44, 5); g.setColor(new Color(30, 25, 53)); g.drawLine(wx + 29, wy + 7, wx + 29, wy + 33); g.drawLine(wx + 4, wy + 35, wx + 54, wy + 35); col += 1 }; row += 1 }
      g.setColor(new Color(35, 31, 60)); g.fillRoundRect(x + 150, y + 171, 66, 78, 8, 8); g.setColor(new Color(255, 197, 90)); g.fillOval(x + 202, y + 211, 7, 7)
      g.setColor(new Color(191, 190, 218)); var i = 0; while (i < 7) { g.fillRect(x + 43 + i * 7, y + 127 + i * 13, 66 - i * 7, 11); i += 1 }
      g.setColor(new Color(255, 232, 178)); g.setFont(font(15, Font.BOLD)); centered(g, "GOKULDHAM SOCIETY", x + w / 2, y - 8)
      drawToran(g, x + 14, y + 8, w - 28, 16, targets.exists(_.done))
    }

    private def drawNormalHouse(g: Graphics2D): Unit = {
      val x = 24; val y = 255; val w = 300; val h = 190
      g.setColor(new Color(15, 12, 27, 90)); g.fillOval(x - 2, y + h - 3, w + 35, 29)
      g.setPaint(new GradientPaint(x, y, new Color(255, 180, 109), x + w, y + h, new Color(142, 69, 89))); g.fillRect(x, y + 40, w, h - 40)
      g.setColor(new Color(95, 44, 73)); g.fillPolygon(new Polygon(Array(x - 18, x + w / 2, x + w + 18), Array(y + 48, y - 28, y + 48), 3))
      g.setColor(new Color(255, 218, 149)); g.fillRect(x + 15, y + 56, w - 30, 8)
      g.setColor(new Color(89, 45, 51)); g.fillRoundRect(x + 128, y + 95, 53, 95, 7, 7); g.setColor(new Color(255, 202, 95)); g.fillOval(x + 167, y + 142, 6, 6)
      drawWindow(g, x + 35, y + 87, 52, 47); drawWindow(g, x + 215, y + 87, 52, 47)
      g.setFont(font(14, Font.BOLD)); g.setColor(new Color(255, 246, 208)); centered(g, if (family == "Normal Family") familyName.take(16).toUpperCase else "NEIGHBOUR'S HOME", x + w / 2, y + 22)
      drawToran(g, x + 106, y + 84, 106, 20, targets.exists(_.done))
      if (family == "Normal Family" && targets.exists(t => t.label == "Diyas" && t.done)) { drawDiyas(g, x + 35, y + h - 6, 0.75); drawDiyas(g, x + 228, y + h - 6, 0.75) }
    }

    private def drawSkyline(g: Graphics2D, w: Int): Unit = {
      val buildings = Array((0, 145, 78, 120), (80, 125, 90, 140), (180, 150, 65, 112), (250, 130, 90, 132), (700, 135, 86, 128), (800, 155, 66, 108), (875, 120, 100, 143), (970, 148, 80, 116))
      buildings.foreach { b => val (x, y, bw, bh) = b; g.setColor(new Color(30, 25, 56)); g.fillRect(x, y, bw, bh); g.setColor(new Color(255, 195, 93, 90)); var wx = x + 10
        while (wx < x + bw - 7) { var wy = y + 12; while (wy < y + bh - 7) { if ((wx + wy) % 3 != 0) g.fillRect(wx, wy, 5, 7); wy += 19 }; wx += 17 } }
    }

    private def drawStation(g: Graphics2D, w: Int, h: Int, title: String): Unit = {
      g.setColor(new Color(38, 43, 76)); g.fillRect(0, 250, w, 130); g.setColor(new Color(255, 192, 91)); g.fillRect(0, 250, w, 10)
      var bx = 18; while (bx < w) { g.setColor(new Color(62, 66, 98)); g.fillRoundRect(bx, 272, 125, 79, 12, 12); g.setColor(new Color(255, 220, 143, 160)); g.fillRect(bx + 11, 285, 103, 8); g.setColor(new Color(28, 39, 66)); g.fillRect(bx + 20, 307, 84, 29); bx += 145 }
      g.setColor(new Color(24, 24, 46)); g.fillRoundRect(w / 2 - 230, 205, 460, 56, 15, 15); g.setColor(new Color(255, 206, 100)); g.setStroke(new BasicStroke(2f)); g.drawRoundRect(w / 2 - 230, 205, 460, 56, 15, 15)
      g.setFont(font(21, Font.BOLD)); g.setColor(new Color(255, 228, 158)); centered(g, title, w / 2, 240)
    }

    private def drawRailTracks(g: Graphics2D, w: Int, h: Int): Unit = {
      val y = h - 122; g.setColor(new Color(43, 45, 58)); g.fillRect(0, y - 7, w, 42)
      g.setColor(new Color(199, 202, 208)); g.setStroke(new BasicStroke(4f)); g.drawLine(0, y, w, y); g.drawLine(0, y + 24, w, y + 24)
      g.setColor(new Color(113, 94, 91)); var x = 0; while (x < w) { g.fillRect(x, y - 8, 11, 40); x += 27 }
    }

    private def drawPassengerTrain(g: Graphics2D, x: Int, y: Int, coachW: Int, coachH: Int): Unit = {
      val totalW = coachW * 3 + 84; g.setColor(new Color(7, 9, 20, 95)); g.fillOval(x - 8, y + coachH + 28, totalW + 25, 23)
      var i = 0
      while (i < 3) { val cx = x + i * coachW; g.setPaint(new GradientPaint(cx, y, new Color(211, 225, 230), cx, y + coachH, new Color(70, 119, 164))); g.fillRoundRect(cx, y, coachW - 8, coachH, 13, 13)
        g.setColor(new Color(207, 56, 67)); g.fillRect(cx + 3, y + coachH - 18, coachW - 14, 8); var wx = cx + 12
        while (wx < cx + coachW - 25) { g.setColor(new Color(27, 49, 76)); g.fillRoundRect(wx, y + 13, 22, 27, 4, 4); g.setColor(new Color(154, 217, 233)); g.fillRect(wx + 3, y + 16, 16, 5); wx += 31 }
        g.setColor(new Color(36, 46, 66)); g.fillOval(cx + 20, y + coachH - 4, 25, 25); g.fillOval(cx + coachW - 45, y + coachH - 4, 25, 25); g.setColor(new Color(176, 183, 196)); g.fillOval(cx + 26, y + coachH + 2, 13, 13); g.fillOval(cx + coachW - 39, y + coachH + 2, 13, 13); i += 1 }
      val ex = x + coachW * 3; g.setPaint(new GradientPaint(ex, y + 10, new Color(225, 222, 209), ex + 84, y + coachH, new Color(54, 101, 149))); g.fillRoundRect(ex, y + 9, 84, coachH - 1, 18, 18)
      g.setColor(new Color(255, 201, 74)); g.fillRoundRect(ex + 8, y + coachH - 30, 67, 12, 5, 5); g.setColor(new Color(28, 45, 70)); g.fillRoundRect(ex + 15, y + 18, 47, 25, 5, 5)
    }

    private def drawPalm(g: Graphics2D, x: Int, y: Int): Unit = {
      g.setColor(new Color(103, 71, 48)); g.fillPolygon(new Polygon(Array(x - 4, x + 5, x + 10, x + 3), Array(y + 72, y + 72, y, y), 4))
      g.setColor(new Color(37, 110, 69)); g.fillArc(x - 47, y - 20, 60, 38, 25, 135); g.fillArc(x - 20, y - 32, 62, 40, 15, 140); g.fillArc(x - 49, y - 4, 64, 37, 190, 130); g.fillArc(x - 22, y - 8, 62, 37, 210, 120)
    }

    private def drawPerson(g: Graphics2D, xx: Double, yy: Double, shirt: Color, skin: Color, frame: Int): Unit = {
      val x = xx.toInt; val y = yy.toInt; g.setColor(new Color(0, 0, 0, 65)); g.fillOval(x - 15, y + 19, 31, 10)
      g.setColor(new Color(35, 36, 66)); g.setStroke(new BasicStroke(5f)); g.drawLine(x - 4, y + 8, x - 7, y + 21); g.drawLine(x + 4, y + 8, x + 7, y + 21)
      g.setColor(shirt); g.fillRoundRect(x - 13, y - 7, 26, 19, 7, 7); g.setColor(skin); g.fillOval(x - 9, y - 25, 18, 19); g.setColor(new Color(49, 30, 28)); g.fillArc(x - 10, y - 28, 20, 14, 0, 180)
    }

    private def drawAmbient(g: Graphics2D): Unit = {
      var i = 0
      while (i < 13) { val xx = ((i * 97 + tick * (i % 3 + 1)) % math.max(1, getWidth)).toInt; val yy = 225 + ((i * 41 + tick / 3) % math.max(1, getHeight - 270)).toInt; val alpha = 70 + ((math.sin(tick * 0.08 + i) + 1) * 60).toInt; g.setColor(new Color(255, 208, 93, alpha)); g.fillOval(xx, yy, 3, 3); i += 1 }
    }

    private def drawMarker(g: Graphics2D, x: Int, y: Int, c: Color): Unit = {
      val pulse = (math.sin(tick * 0.13) * 3).toInt; g.setColor(new Color(c.getRed, c.getGreen, c.getBlue, 45)); g.fillOval(x - 15 - pulse, y - 15 - pulse, 30 + pulse * 2, 30 + pulse * 2); g.setColor(c); g.setStroke(new BasicStroke(2f)); g.drawOval(x - 8, y - 8, 16, 16); g.fillOval(x - 3, y - 3, 6, 6)
    }

    private def drawToran(g: Graphics2D, x: Int, y: Int, width: Int, height: Int, bright: Boolean): Unit = {
      g.setColor(new Color(40, 26, 44)); g.setStroke(new BasicStroke(2f)); g.drawArc(x, y - 2, width, height * 2, 180, 180); var i = 0
      while (i <= 9) { val xx = x + i * width / 9; val yy = y + height / 2 + (math.sin(i * math.Pi / 9) * height * 0.55).toInt; g.setColor(if (i % 2 == 0) new Color(255, 183, 65) else new Color(64, 205, 142)); g.fillOval(xx - 4, yy - 3, 8, 10); if (bright) { g.setColor(new Color(255, 222, 103, 70)); g.fillOval(xx - 9, yy - 8, 18, 18) }; i += 1 }
      if (bright) { g.setColor(new Color(255, 224, 143)); g.setStroke(new BasicStroke(3f)); g.drawArc(x, y - 2, width, height * 2, 180, 180) }
    }

    private def drawDiyas(g: Graphics2D, x: Int, y: Int, scale: Double): Unit = {
      var i = 0
      while (i < 3) { val xx = x + (i * 25 * scale).toInt; val yy = y + (if (i == 1) -4 else 0); g.setColor(new Color(255, 190, 58, 45)); g.fillOval(xx - 8, yy - 12, math.max(1, (32 * scale).toInt), math.max(1, (28 * scale).toInt)); g.setColor(new Color(204, 91, 40)); g.fillOval(xx, yy, math.max(1, (21 * scale).toInt), math.max(1, (10 * scale).toInt)); g.setColor(new Color(255, 207, 82)); g.fillOval(xx + (5 * scale).toInt, yy - (5 * scale).toInt, math.max(1, (11 * scale).toInt), math.max(1, (7 * scale).toInt)); g.setColor(new Color(255, 249, 191)); g.fillOval(xx + (9 * scale).toInt, yy - (11 * scale).toInt, math.max(1, (4 * scale).toInt), math.max(1, (8 * scale).toInt)); i += 1 }
    }

    private def drawMiniDiyas(g: Graphics2D, x: Int, y: Int): Unit = {
      var i = 0; while (i < 3) { val xx = x + i * 18; g.setColor(new Color(241, 128, 49)); g.fillOval(xx, y, 13, 7); g.setColor(new Color(255, 230, 125)); g.fillOval(xx + 4, y - 5, 5, 6); i += 1 }
    }

    private def drawRangoli(g: Graphics2D, x: Int, y: Int): Unit = {
      val colors = Array(new Color(244, 86, 115), new Color(255, 205, 72), new Color(77, 212, 172), new Color(127, 115, 255))
      g.setColor(new Color(255, 231, 151, 40)); g.fillOval(x - 27, y - 13, 54, 28); var i = 0
      while (i < 8) { val angle = i * math.Pi / 4.0; val cx = x + (math.cos(angle) * 17).toInt; val cy = y + (math.sin(angle) * 8).toInt; g.setColor(colors(i % colors.length)); g.fillOval(cx - 5, cy - 4, 10, 8); i += 1 }
      g.setColor(new Color(255, 244, 193)); g.fillOval(x - 4, y - 3, 8, 6)
    }

    private def drawMiniBuilding(g: Graphics2D, x: Int, y: Int, w: Int, h: Int): Unit = {
      g.setColor(new Color(76, 69, 132)); g.fillRect(x, y, w, h); g.setColor(new Color(255, 205, 103)); g.fillRect(x, y, w, 6); var row = 0
      while (row < 2) { var col = 0; while (col < 3) { g.setColor(new Color(255, 226, 153)); g.fillRect(x + 10 + col * 27, y + 13 + row * 23, 16, 14); col += 1 }; row += 1 }
    }

    private def drawMiniHouse(g: Graphics2D, x: Int, y: Int, w: Int, h: Int): Unit = {
      val roof = new Polygon(Array(x - 5, x + w / 2, x + w + 5), Array(y + 22, y - 5, y + 22), 3); g.setColor(new Color(220, 117, 72)); g.fillPolygon(roof); g.setColor(new Color(249, 191, 128)); g.fillRect(x + 4, y + 20, w - 8, h - 20); g.setColor(new Color(88, 48, 62)); g.fillRect(x + w / 2 - 10, y + 38, 20, h - 38); g.setColor(new Color(255, 225, 153)); g.fillRect(x + 13, y + 31, 17, 16); g.fillRect(x + w - 30, y + 31, 17, 16)
    }

    private def drawWindow(g: Graphics2D, x: Int, y: Int, w: Int, h: Int): Unit = {
      g.setColor(new Color(59, 46, 78)); g.fillRoundRect(x, y, w, h, 5, 5); g.setColor(new Color(255, 226, 153)); g.fillRect(x + 5, y + 5, w - 10, h - 10); g.setColor(new Color(72, 57, 85)); g.setStroke(new BasicStroke(3f)); g.drawLine(x + w / 2, y + 4, x + w / 2, y + h - 4); g.drawLine(x + 4, y + h / 2, x + w - 4, y + h / 2)
    }

    private def drawRoadDashes(g: Graphics2D, w: Int, h: Int): Unit = {
      g.setColor(new Color(255, 224, 133, 100)); var x = 15; while (x < w) { g.fillRoundRect(x, h - 42, 34, 3, 4, 4); x += 65 }
    }

    private def drawStars(g: Graphics2D, x: Int, y: Int, w: Int, h: Int, count: Int): Unit = {
      var i = 0; while (i < count) { val sx = x + ((i * 137 + 31) % math.max(1, w)); val sy = y + ((i * 67 + 13) % math.max(1, h)); val a = 70 + ((i * 29) % 160); g.setColor(new Color(255, 239, 199, a)); val s = if (i % 7 == 0) 3 else 2; g.fillOval(sx, sy, s, s); i += 1 }
    }

    private def drawComplete(g: Graphics2D): Unit = {
      val w = getWidth; val h = getHeight; g.setColor(new Color(11, 8, 28, 210)); g.fillRect(0, 0, w, h); drawStars(g, 0, 0, w, h, 55)
      g.setColor(new Color(255, 202, 72, 50)); g.fillOval(w / 2 - 200, h / 2 - 180, 400, 320)
      g.setPaint(new GradientPaint(w / 2 - 220, h / 2 - 130, new Color(75, 43, 91), w / 2 + 220, h / 2 + 150, new Color(33, 27, 61))); g.fillRoundRect(w / 2 - 265, h / 2 - 160, 530, 315, 34, 34)
      g.setStroke(new BasicStroke(3f)); g.setColor(new Color(255, 205, 82)); g.drawRoundRect(w / 2 - 265, h / 2 - 160, 530, 315, 34, 34)
      drawDiyas(g, w / 2 - 180, h / 2 + 36, 1.0); drawDiyas(g, w / 2 + 125, h / 2 + 36, 1.0)
      g.setFont(font(36, Font.BOLD)); g.setColor(new Color(255, 208, 80)); centered(g, "SHUBH DEEPAWALI!", w / 2, h / 2 - 89)
      g.setFont(font(21, Font.BOLD)); g.setColor(Color.WHITE); centered(g, "MISSION COMPLETED", w / 2, h / 2 - 48)
      g.setFont(font(18)); g.setColor(new Color(226, 222, 243)); centered(g, family + " - All tasks finished", w / 2, h / 2 - 12)
      g.setFont(font(24, Font.BOLD)); g.setColor(new Color(121, 248, 201)); centered(g, "FINAL SCORE: " + score, w / 2, h / 2 + 29)
      g.setFont(font(15, Font.BOLD)); g.setColor(new Color(255, 222, 139)); centered(g, "Press ENTER to play again  |  ESC for family selection", w / 2, h / 2 + 99)
    }

    private def drawPause(g: Graphics2D): Unit = {
      g.setColor(new Color(9, 7, 22, 195)); g.fillRect(0, 0, getWidth, getHeight); g.setFont(font(34, Font.BOLD)); g.setColor(new Color(255, 211, 99)); centered(g, "PAUSED", getWidth / 2, getHeight / 2 - 10); g.setFont(font(16, Font.BOLD)); g.setColor(Color.WHITE); centered(g, "Press P or E to continue", getWidth / 2, getHeight / 2 + 28)
    }

    override def mouseClicked(e: MouseEvent): Unit = {
      if (phase == "menu") {
        if (menuGokul.contains(e.getPoint)) startGame("Gokuldham Family")
        else if (menuNormal.contains(e.getPoint)) startGame("Normal Family")
      }
      requestFocusInWindow()
    }
    override def mousePressed(e: MouseEvent): Unit = {}
    override def mouseReleased(e: MouseEvent): Unit = {}
    override def mouseEntered(e: MouseEvent): Unit = {}
    override def mouseExited(e: MouseEvent): Unit = {}
  }
}

// Kojo AUTO-START: press Run in Kojo and the game window opens.
BixuuDiwaliGame.main(Array.empty[String])