Code Sketch


bix
By: Mhalsakant School
Category: Programming
import java.awt.{BasicStroke, Color, Dimension, Font, GradientPaint, Graphics, Graphics2D, RenderingHints, Polygon, RadialGradientPaint, MultipleGradientPaint}
import java.awt.event.{ActionEvent, ActionListener, KeyEvent, KeyListener, MouseEvent, MouseListener}
import java.awt.geom.{Ellipse2D, RoundRectangle2D}
import java.awt.image.BufferedImage
import javax.swing.{JFrame, JOptionPane, JPanel, SwingUtilities, Timer, WindowConstants}
import scala.util.Random

/**
 * BIXUU DIWALI TRAIN ADVENTURE
 * Standalone Scala + Java Swing/AWT game. No external libraries or image files needed.
 * Complete story: Goa letter -> ticket counter -> missed train -> TTE -> next train ->
 * three coaches and coins -> Goa station -> grandparents -> Diwali decoration and puja.
 * Controls: WASD / Arrow Keys = move, E or ENTER / SPACE = interact,
 *           1 = Gokuldham Family (menu), 2 = Normal Family (menu), P = pause, ESC = menu.
 */
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.setVisible(true)
    game.requestFocusInWindow()
    game.startLoop()
  }

  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 KeyListener with MouseListener {
    private val rng = new Random(17)
    private var phase = "menu" // menu, playing, complete
    private var family = "Gokuldham Family"
    private var normalFamilyName = "My Family"
    private var playerName = "Bixuu"
    private var stage = 0
    private var score = 0
    private var supplies = false
    private var pujaDone = false
    private var playerX = 405.0
    private var playerY = 300.0
    private var moveUp = false
    private var moveDown = false
    private var moveLeft = false
    private var moveRight = false
    private var walkFrame = 0
    private var tick = 0
    private var toast = "Welcome! Choose your family to begin."
    private var toastTicks = 150
    private var targets = Array.empty[DecoTarget]
    private var menuGokul = new java.awt.Rectangle(0, 0, 0, 0)
    private var menuNormal = new java.awt.Rectangle(0, 0, 0, 0)
    private var pause = false
    private var stairDescending = false
    private var stairTick = 0
    private var stairStartX = 410.0
    private var stairStartY = 305.0

    // Train-adventure story state. After Goa and the grandparents scene, the game
    // continues into the original family-specific Diwali missions below.
    private var journeyComplete = false
    private var journeyScene = "letter" // letter, ticket, missed, wait, coach, goaStation, goaHome
    private var ticketBooked = false
    private var ticketChecked = false
    private var currentCoach = 1
    private var coachCoins = Array(false, false, false)
    private var trainArrivalTicks = 0
    private var travelTicks = 0

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

    def startLoop(): Unit = {
      val timer = new Timer(30, new ActionListener {
        override def actionPerformed(e: ActionEvent): Unit = {
          updateGame()
          repaint()
        }
      })
      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 = {
      family = which
      playerName = askName("Choose your character name:", "Bixuu")
      normalFamilyName = if (family == "Normal Family") askName("Enter your family / house name:", "My Family") else "Gokuldham Society"
      phase = "playing"
      stage = 0
      score = 0
      supplies = false
      pujaDone = false
      pause = false
      stairDescending = false
      stairTick = 0
      journeyComplete = false
      journeyScene = "letter"
      ticketBooked = false
      ticketChecked = false
      currentCoach = 1
      coachCoins = Array(false, false, false)
      trainArrivalTicks = 0
      travelTicks = 0
      playerX = math.max(90.0, getWidth * 0.18)
      playerY = math.max(320.0, getHeight * 0.66)
      showToast("A letter has arrived from Goa! Find it and read your family message.", 220)
      requestFocusInWindow()
    }

    private def beginDiwaliMissions(): Unit = {
      journeyComplete = true
      stage = 0
      supplies = false
      pujaDone = false
      stairDescending = false
      stairTick = 0
      if (family == "Gokuldham Family") {
        playerX = 410.0
        playerY = 305.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("You greeted your grandparents! Next: go down the stairs and start the Diwali society missions.", 230)
      } 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 your grandparents in Goa! Buy supplies and decorate your family home for Diwali.", 230)
      }
      requestFocusInWindow()
    }

    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 (!journeyComplete) {
        if (journeyScene == "wait" && trainArrivalTicks < 100) trainArrivalTicks += 1
        if (journeyScene == "coach" && travelTicks < 360) travelTicks += 1
        var jdx = 0.0
        var jdy = 0.0
        if (moveLeft) jdx -= 1.0
        if (moveRight) jdx += 1.0
        if (moveUp) jdy -= 1.0
        if (moveDown) jdy += 1.0
        if (jdx != 0.0 || jdy != 0.0) {
          val jlen = math.sqrt(jdx * jdx + jdy * jdy)
          playerX += jdx / jlen * 3.3
          playerY += jdy / jlen * 3.3
          playerX = math.max(30.0, math.min(getWidth - 30.0, playerX))
          playerY = math.max(270.0, math.min(getHeight - 93.0, playerY))
          walkFrame += 1
        }
        return
      }

      if (stairDescending) {
        stairTick += 1
        val progress = math.min(1.0, stairTick / 34.0)
        playerX = stairStartX + (420.0 - stairStartX) * progress
        playerY = stairStartY + (400.0 - stairStartY) * progress
        walkFrame += 1
        if (progress >= 1.0) {
          stairDescending = false
          stage = 1
          score += 20
          showToast("You came down the stairs! Now 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)
        playerX += dx / len * 3.2
        playerY += dy / len * 3.2
        playerX = math.max(34.0, math.min(getWidth - 34.0, playerX))
        playerY = math.max(270.0, math.min(getHeight - 98.0, playerY))
        walkFrame += 1
      }
    }

    private def interact(): Unit = {
      if (stairDescending) return
      if (phase == "menu") {
        startGame(family)
        return
      }
      if (phase == "complete") {
        phase = "menu"
        showToast("Choose a family and start a new Diwali adventure!", 150)
        return
      }
      if (pause) {
        pause = false
        showToast("Game resumed.", 80)
        return
      }

      if (!journeyComplete) {
        interactJourney()
        return
      }

      if (family == "Gokuldham Family") {
        stage match {
          case 0 =>
            if (distance(playerX, playerY, 420, 355) < 100) {
              stairDescending = true
              stairTick = 0
              stairStartX = playerX
              stairStartY = playerY
              showToast("Walking down the staircase...", 75)
            } else showToast("Walk near the staircase (middle building) and press E.", 100)
          case 1 =>
            if (distance(playerX, playerY, 535, 430) < 90) {
              stage = 2
              score += 30
              showToast("Secretary: Permission granted! Visit Abdul's shop for supplies.", 190)
            } else showToast("Go near the society secretary beside the apartment and press E.", 110)
          case 2 =>
            if (distance(playerX, playerY, getWidth - 175, 420) < 105) 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) < 95) 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) < 105) buySupplies()
            else showToast("Go to Abdul's shop on the right to buy your supplies.", 110)
          case 1 => decorateAtNearestTarget()
          case 2 =>
            if (distance(playerX, playerY, 225, 540) < 100) finishPuja()
            else showToast("Return to your home puja area and press E.", 110)
          case _ => phase = "complete"
        }
      }
    }

    private def interactJourney(): Unit = {
      val w = getWidth.toDouble
      val h = getHeight.toDouble
      journeyScene match {
        case "letter" =>
          if (distance(playerX, playerY, w * 0.70, h * 0.63) < 112.0) {
            journeyScene = "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.", 240)
          } else showToast("Find the glowing letter on the table and press E to read it.", 120)

        case "ticket" =>
          if (distance(playerX, playerY, w * 0.78, h * 0.60) < 115.0) {
            ticketBooked = true
            journeyScene = "missed"
            playerX = w * 0.12
            playerY = h * 0.66
            score += 20
            showToast("Ticket booked! Oh no ? your train has already left. Find the TTE on the platform.", 220)
          } else showToast("Go to the ticket counter, then press E to book your Goa ticket.", 120)

        case "missed" =>
          if (distance(playerX, playerY, w * 0.70, h * 0.62) < 112.0) {
            journeyScene = "wait"
            trainArrivalTicks = 0
            playerX = w * 0.14
            playerY = h * 0.66
            score += 15
            showToast("TTE: This train has gone. Another train is coming soon ? wait near the marked platform door.", 220)
          } 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 while on the platform.", 100)
          } else if (distance(playerX, playerY, w * 0.53, h * 0.62) < 145.0) {
            journeyScene = "coach"
            currentCoach = 1
            ticketChecked = false
            travelTicks = 0
            playerX = w * 0.12
            playerY = h * 0.64
            showToast("Boarded the next train! You are in Coach 1. Keep your luggage close and show your ticket to the TTE.", 220)
          } else showToast("The train has arrived! Walk to its open door near the middle of the platform 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) < 72.0) {
            coachCoins(currentCoach - 1) = true
            score += 25
            showToast("Coin collected! " + coachCoins.count(identity) + "/3 train coins collected.", 130)
          } else if (currentCoach == 1 && !ticketChecked && distance(playerX, playerY, w * 0.70, h * 0.59) < 105.0) {
            ticketChecked = true
            score += 35
            showToast("TTE: Ticket verified. You are in Coach 1 ? now cross the coaches and collect the bonus coins.", 220)
          } else if (distance(playerX, playerY, w * 0.88, h * 0.64) < 100.0) {
            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 + ". Look around for the glowing coin, then move to the next coach.", 180)
            } else if (travelTicks < 220) {
              showToast("The train is delayed at a signal. Stay aboard ? journey progress: " + math.min(100, travelTicks * 100 / 220) + "%.", 130)
            } else {
              journeyScene = "goaStation"
              playerX = w * 0.12
              playerY = h * 0.66
              score += 50
              showToast("The train has arrived in Goa! Leave the train and find the station exit.", 210)
            }
          } else {
            val hint = if (currentCoach == 1 && !ticketChecked) "Find the TTE in Coach 1 and show your ticket." else "Walk through the coach to collect the coin or reach the door at the right."
            showToast(hint, 120)
          }

        case "goaStation" =>
          if (distance(playerX, playerY, w * 0.84, h * 0.64) < 125.0) {
            journeyScene = "goaHome"
            playerX = w * 0.13
            playerY = h * 0.66
            score += 20
            showToast("You are outside Goa station. Walk to your grandparents' house and greet Aaji-Ajoba.", 220)
          } else showToast("Walk to the glowing station exit on the right and press E.", 120)

        case "goaHome" =>
          if (distance(playerX, playerY, w * 0.70, h * 0.60) < 120.0) {
            score += 50
            showToast("Aaji-Ajoba: Welcome home! Happy Diwali! Let us prepare the house and celebrate together.", 180)
            beginDiwaliMissions()
          } else showToast("Walk up to your grandparents near the front door and press E to greet them.", 120)

        case _ => showToast("Follow the glowing marker to continue your adventure.", 100)
      }
    }

    private def buySupplies(): Unit = {
      if (!supplies) {
        supplies = true
        score += 50
        if (family == "Gokuldham Family") stage = 3 else stage = 1
        showToast("Abdul: Supplies collected! Demo contact only: 000-000-0000 (not real).", 190)
      } else {
        showToast("Abdul: 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 < 82.0) {
        nearest.done = true
        score += 25
        val remaining = targets.count(t => !t.done)
        if (remaining == 0) {
          if (family == "Gokuldham Family") stage = 4 else stage = 2
          showToast("Amazing! All 4 decorations are complete. Go to the puja area.", 190)
        } else {
          showToast(nearest.label + " decorated! " + (4 - remaining) + "/4 complete. Find the next marker.", 135)
        }
      } else {
        val next = targets.find(t => !t.done)
        if (next.isDefined) showToast("Go 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 the puja.", 110)
      } else if (targets.exists(t => !t.done)) {
        showToast("Finish all 4 decoration tasks before the puja.", 110)
      } else {
        pujaDone = true
        score += 100
        phase = "complete"
        showToast("Shubh Deepawali! Mission completed!", 250)
      }
    }

    private def currentTask: String = {
      if (family == "Gokuldham Family") {
        stage match {
          case 0 => "MISSION 1 / 5  ?  ??????? ??????? ?? ??? E ????"
          case 1 => "MISSION 2 / 5  ?  Society secretary ???? permission ????"
          case 2 => "MISSION 3 / 5  ?  Abdul ???? ????????? ????? ????"
          case 3 => "MISSION 4 / 5  ?  4 decoration markers ????? ??? (" + targets.count(_.done) + "/4)"
          case 4 => "MISSION 5 / 5  ?  Community puja ????? ???"
          case _ => "All missions complete!"
        }
      } else {
        stage match {
          case 0 => "MISSION 1 / 3  ?  Abdul ???? ????????? ????? ????? ???"
          case 1 => "MISSION 2 / 3  ?  ????? ?? ???? (" + targets.count(_.done) + "/4)"
          case 2 => "MISSION 3 / 3  ?  ??? ???? ????? ???"
          case _ => "All missions complete!"
        }
      }
    }

    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 (!journeyComplete) {
          drawJourneyScene(g)
          drawJourneyHUD(g)
        } else {
          drawWorld(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 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, 78)
      // Soft festive moon
      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)
      drawDiyas(g, 52, h - 102, 1.0)
      drawDiyas(g, w - 110, h - 102, 1.0)

      g.setFont(font(42, Font.BOLD))
      g.setColor(new Color(255, 200, 67))
      centered(g, "BIXUU DIWALI TRAIN ADVENTURE", w / 2, 105)
      g.setFont(font(18, Font.BOLD))
      g.setColor(new Color(255, 239, 206))
      centered(g, "LETTER ? TICKET ? TRAIN ? GOA ? GRANDPARENTS ? DIWALI", w / 2, 143)
      g.setFont(font(15, Font.PLAIN))
      g.setColor(new Color(227, 218, 246))
      centered(g, "Travel to Goa, explore three train coaches, then celebrate Diwali", w / 2, 180)

      val cardW = math.min(360, (w - 90) / 2)
      val cardH = 235
      val gap = 28
      val left = (w - (cardW * 2 + gap)) / 2
      val top = 220
      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", "Abdul's shop ? Decorate ? Puja", true)
      drawFamilyCard(g, menuNormal, "NORMAL FAMILY", "Train + Home missions", "Goa trip ? Grandparents' home", "Buy supplies ? Decorate ? Puja", false)

      g.setFont(font(15, Font.BOLD))
      g.setColor(new Color(255, 220, 124))
      centered(g, "CLICK A FAMILY CARD  ?  OR PRESS 1 / 2", w / 2, top + cardH + 38)
      g.setFont(font(13, Font.PLAIN))
      g.setColor(new Color(221, 211, 238))
      centered(g, "Controls: WASD / Arrow Keys = 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(2.0f))
      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(21, Font.BOLD))
      g.setColor(new Color(255, 219, 120))
      centered(g, heading, r.x + r.width / 2, r.y + 121)
      g.setFont(font(16, 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, 48)
      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)
      // A subtle perspective grid gives the hand-drawn world a dimensional feel.
      g.setColor(new Color(255, 255, 255, 16))
      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 }

      journeyScene 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)
          g.setColor(new Color(255, 224, 137)); g.drawLine((w * 0.69).toInt, 386, (w * 0.72).toInt, 386)
          g.drawLine((w * 0.69).toInt, 393, (w * 0.72).toInt, 393)
          drawMarker(g, (w * 0.70).toInt, (h * 0.63).toInt - 28, new Color(255, 214, 86))
          drawPerson(g, (w * 0.43).toDouble, (h * 0.62).toDouble, new Color(77, 159, 184), new Color(242, 190, 143), false, 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, Font.PLAIN)); g.setColor(Color.WHITE); centered(g, "Your grandparents invited you for Diwali", (w * 0.52).toInt, 545)

        case "ticket" =>
          drawStationFacade(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)
          g.setColor(new Color(255, 236, 196)); g.fillRoundRect(cx + 70, cy + 35, 90, 40, 7, 7)
          g.setColor(new Color(91, 64, 76)); g.drawRect(cx + 80, cy + 43, 70, 21)
          drawPerson(g, (w * 0.78).toDouble, (h * 0.60).toDouble, new Color(64, 137, 190), new Color(245, 190, 140), false, tick)
          drawPlayer(g)

        case "missed" =>
          drawStationFacade(g, w, h, "PLATFORM 1 ? DEPARTED")
          drawRailTracks(g, w, h)
          val trainX = w - ((tick * 7) % (w + 600)) - 480
          drawPassengerTrain(g, trainX, 365, 146, 88)
          drawPerson(g, (w * 0.70).toDouble, (h * 0.62).toDouble, new Color(43, 111, 170), new Color(244, 194, 145), false, tick)
          g.setFont(font(12, Font.BOLD)); g.setColor(new Color(255, 229, 165)); centered(g, "TTE", (w * 0.70).toInt, (h * 0.62).toInt + 38)
          g.setFont(font(16, Font.BOLD)); g.setColor(new Color(255, 206, 108)); centered(g, "OH NO! THE TRAIN HAS LEFT", w / 2, 305)
          drawPlayer(g)

        case "wait" =>
          drawStationFacade(g, w, h, "PLATFORM 2 ? NEXT TRAIN")
          drawRailTracks(g, w, h)
          val prog = math.min(1.0, trainArrivalTicks / 100.0)
          val tx = ((w + 80) + (w * 0.30 - (w + 80)) * prog).toInt
          drawPassengerTrain(g, tx, 365, 146, 88)
          drawPerson(g, (w * 0.26).toDouble, (h * 0.62).toDouble, new Color(43, 111, 170), new Color(244, 194, 145), false, tick)
          g.setFont(font(12, Font.BOLD)); g.setColor(new Color(255, 229, 165)); centered(g, "TTE: NEXT TRAIN TO GOA", (w * 0.26).toInt, (h * 0.62).toInt + 38)
          if (trainArrivalTicks >= 100) drawMarker(g, (w * 0.53).toInt, (h * 0.62).toInt - 35, new Color(100, 248, 193))
          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)
          drawPlayer(g)

        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)
          // Windows with a moving Goa countryside view.
          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.setColor(new Color(37, 105, 67)); g.fillRect(wx + slide, 325, 7, 54)
            g.setColor(new Color(22, 32, 58)); g.drawRoundRect(wx, 300, 138, 82, 10, 10)
            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)
          // seats on both sides and the coach passage.
          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)
            g.setColor(new Color(107, 42, 63)); g.fillRect(sx + 5, 468, 65, 28)
            g.setColor(new Color(205, 147, 91)); g.fillRoundRect(sx + 10, 492, 55, 8, 4, 4)
            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)
          g.setFont(font(11, Font.BOLD)); g.setColor(new Color(235, 232, 255)); centered(g, "KEEP YOUR LUGGAGE SAFE", w / 2, 530)
          if (currentCoach == 1 && !ticketChecked) {
            drawPerson(g, (w * 0.70).toDouble, (h * 0.59).toDouble, new Color(43, 111, 170), new Color(244, 194, 145), false, tick)
            g.setFont(font(11, Font.BOLD)); g.setColor(new Color(255, 229, 165)); centered(g, "TTE", (w * 0.70).toInt, (h * 0.59).toInt + 34)
            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.setFont(font(10, Font.BOLD)); g.setColor(new Color(255, 236, 163)); centered(g, "COIN", cx, cy + 28)
          }
          g.setColor(new Color(255, 218, 115)); g.setFont(font(12, Font.BOLD)); g.drawString("COACH " + currentCoach + " / 3", 25, h - 98)
          g.setColor(new Color(131, 245, 205)); g.drawString("Journey: " + math.min(100, travelTicks * 100 / 220) + "%", 25, h - 78)
          drawPlayer(g)

        case "goaStation" =>
          drawStationFacade(g, w, h, "GOA ? MADGAON STATION")
          drawRailTracks(g, w, h)
          drawPassengerTrain(g, 40, 365, 146, 88)
          // Palm silhouettes make the destination recognizable.
          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)
          drawPlayer(g)

        case "goaHome" =>
          // Tropical lane, a Goan family home, and the waiting grandparents.
          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)
          drawToran(g, (w * 0.32).toInt + 100, 385, 100, 19, true)
          drawDiyas(g, (w * 0.32).toInt + 60, 505, 0.65)
          drawRangoli(g, (w * 0.32).toInt + 250, 545)
          drawPerson(g, (w * 0.70).toDouble, (h * 0.60).toDouble, new Color(155, 88, 160), new Color(232, 179, 133), false, tick)
          drawPerson(g, (w * 0.78).toDouble, (h * 0.60).toDouble, new Color(65, 143, 104), new Color(236, 188, 142), false, 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.72).toInt, (h * 0.60).toInt - 38, new Color(255, 215, 84))
          g.setFont(font(15, Font.BOLD)); g.setColor(new Color(255, 230, 165)); centered(g, "AAJI-AJOBA'S HOUSE ? GOA", w / 2, 285)
          drawPlayer(g)

        case _ => drawWorld(g)
      }
      drawAmbientEffects(g)
    }

    private def drawJourneyHUD(g: Graphics2D): Unit = {
      val w = getWidth
      g.setColor(new Color(12, 12, 31, 225)); g.fillRoundRect(14, 12, w - 28, 88, 18, 18)
      g.setColor(new Color(255, 199, 79)); g.setStroke(new BasicStroke(1.5f)); g.drawRoundRect(14, 12, w - 28, 88, 18, 18)
      g.setFont(font(18, Font.BOLD)); g.setColor(new Color(255, 212, 108)); g.drawString("BIXUU TRAIN ADVENTURE", 30, 38)
      g.setFont(font(11, Font.BOLD)); g.setColor(Color.WHITE); g.drawString(playerName + " ? " + 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, 79)
      val task = journeyScene match {
        case "letter" => "MISSION 1/7: Find the letter from Goa and read it."
        case "ticket" => "MISSION 2/7: Go to the railway ticket counter."
        case "missed" => "MISSION 3/7: The train left ? ask the TTE about another train."
        case "wait" => "MISSION 4/7: Wait for the next train, then board it."
        case "coach" => "MISSION 5/7: Show your ticket, cross 3 coaches and collect coins."
        case "goaStation" => "MISSION 6/7: Exit Goa station and find the way home."
        case "goaHome" => "MISSION 7/7: Walk to Aaji-Ajoba and greet them."
        case _ => "Continue your adventure."
      }
      g.setFont(font(13, Font.BOLD)); g.setColor(new Color(255, 243, 219)); g.drawString(task, 285, 39)
      g.setFont(font(11, Font.PLAIN)); g.setColor(new Color(222, 218, 240)); g.drawString("WASD / Arrows: Move     E / ENTER: Interact     P: Pause     ESC: Menu", 285, 61)
      if (journeyScene == "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, 81)
      } else if (journeyScene == "wait") {
        g.setColor(new Color(255, 211, 107)); g.drawString("Train arrival: " + trainArrivalTicks + "%", 285, 81)
      } else {
        g.setColor(new Color(255, 211, 107)); g.drawString("Goa family trip ? Diwali celebration", 285, 81)
      }
      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, getHeight - 67, tw, 32, 12, 12)
        g.setColor(new Color(255, 207, 93)); g.drawRoundRect((w - tw) / 2, getHeight - 67, tw, 32, 12, 12)
        g.setColor(new Color(255, 245, 222)); centered(g, toast, w / 2, getHeight - 46)
      }
      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, getHeight - 25)
    }

    private def drawStationFacade(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 }
      g.setColor(new Color(221, 198, 160, 130)); g.fillRect(0, y - 22, w, 5)
    }

    private def drawPassengerTrain(g: Graphics2D, x: Int, y: Int, coachW: Int, coachH: Int): Unit = {
      // Animated stylized passenger train, built completely from AWT shapes.
      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)
      g.setColor(new Color(255, 244, 190)); g.fillOval(ex + 10, y + 44, 10, 10); g.fillOval(ex + 66, y + 44, 10, 10)
    }

    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)
      g.setColor(new Color(67, 143, 80)); g.drawLine(x + 2, y + 8, x - 35, y - 7); g.drawLine(x + 3, y + 8, x + 30, y - 15)
    }

    private def drawWorld(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)
      // Moon and distant skyline
      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)

      // Courtyard floor
      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)
      // Perspective paving
      g.setStroke(new BasicStroke(1f))
      var xx = -100
      while (xx < w + 150) {
        g.setColor(new Color(255, 255, 255, 15))
        g.drawLine(xx, 340, xx + 160, h)
        xx += 100
      }
      var yy = 370
      while (yy < h) {
        g.setColor(new Color(255, 255, 255, 14))
        g.drawLine(0, yy, w, yy)
        yy += 48
      }
      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)
      drawAmbientEffects(g)
    }

    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 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)
      val roof = new Polygon(Array(x - 18, x + w / 2, x + w + 18), Array(y + 48, y - 28, y + 48), 3)
      g.setColor(new Color(95, 44, 73)); g.fillPolygon(roof)
      g.setColor(new Color(255, 218, 149)); g.fillRect(x + 15, y + 56, w - 30, 8)
      // door and windows
      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(15, Font.BOLD)); g.setColor(new Color(255, 246, 208))
      centered(g, if (family == "Normal Family") normalFamilyName.take(16).toUpperCase else "NEIGHBOUR'S HOME", x + w / 2, y + 22)
      if (family == "Normal Family") {
        drawStringShadow(g, "HOME", x + 126, y + h + 16, font(12, Font.BOLD), new Color(255, 237, 168))
      }
      drawToran(g, x + 106, y + 84, 106, 20, family == "Normal Family" && 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 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)
      g.setColor(new Color(42, 37, 70)); g.fillRect(x + 10, y + h - 45, w - 20, 45)
      // Apartment floors / balconies
      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.setStroke(new BasicStroke(2f)); g.drawLine(wx + 29, wy + 7, wx + 29, wy + 33)
          g.setColor(new Color(26, 27, 48)); g.drawLine(wx + 4, wy + 35, wx + 54, wy + 35)
          col += 1
        }
        row += 1
      }
      // central door
      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)
      // staircase on the front-left
      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)
        g.setColor(new Color(100, 94, 143)); g.drawLine(x + 43 + i * 7, y + 138 + i * 13, x + 109, y + 138 + i * 13)
        g.setColor(new Color(191, 190, 218)); i += 1
      }
      g.setColor(new Color(255, 232, 178))
      g.setFont(font(15, Font.BOLD)); centered(g, "GOKULDHAM SOCIETY", x + w / 2, y - 8)
      // fairy lights along roof
      if (family == "Gokuldham Family" && targets.exists(_.done)) drawToran(g, x + 14, y + 8, w - 28, 16, true)
      else drawToran(g, x + 14, y + 8, w - 28, 16, false)
    }

    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(21, 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)
      g.setColor(new Color(255, 191, 70)); g.fillRect(x + 27, y + 77, 73, 9)
      // visible supplies
      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.fillRect(x + 126, y + 76, 80, 8)
      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)
      drawStringShadow(g, "Lights", x + 165, y + 116, font(12, Font.BOLD), new Color(255, 219, 121))
      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)); g.setFont(font(14, Font.BOLD)); centered(g, "?", x + w - 21, y + 23)
      }
    }

    private def drawSecretary(g: Graphics2D): Unit = {
      val x = 535; val y = 430
      // NPC only emphasized in Gokuldham mission 2
      drawPerson(g, x.toDouble, y.toDouble, new Color(68, 137, 196), new Color(247, 194, 147), false, 0)
      g.setFont(font(11, Font.BOLD)); g.setColor(new Color(250, 245, 220))
      centered(g, "SECRETARY", x, y + 29)
      if (family == "Gokuldham Family" && stage == 1) drawMarker(g, x, y - 47, new Color(93, 233, 180))
    }

    private def drawPujaArea(g: Graphics2D): Unit = {
      val px = if (family == "Gokuldham Family") 535 else 225
      val py = 535
      // small decorated shrine / puja table
      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)
      val shouldMark = if (family == "Gokuldham Family") stage == 4 else stage == 2
      if (shouldMark) drawMarker(g, px, py - 65, new Color(255, 210, 77))
      if (pujaDone) centered(g, "SHUBH DEEPAWALI!", px, py - 76)
    }

    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)
                g.setColor(new Color(255, 228, 111, 55)); g.fillOval(lx - 4, t.y.toInt - 13 + (i % 2) * 4, 15, 15)
                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" && isDecorationStage) {
          val pulse = (math.sin(tick * 0.12) * 4).toInt
          drawMarker(g, t.x.toInt, t.y.toInt - 20 + pulse, 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 isDecorationStage: Boolean = {
      if (family == "Gokuldham Family") stage == 3 else stage == 1
    }

    private def drawPlayer(g: Graphics2D): Unit = {
      val bob = if (moveLeft || moveRight || moveUp || moveDown) 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)
      // legs animate while moving
      g.setStroke(new BasicStroke(6f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND))
      val swing = if (moveLeft || moveRight || moveUp || moveDown) (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 drawAmbientEffects(g: Graphics2D): Unit = {
      // Gentle glowing firefly lights and distant decorative sparkles; purely visual.
      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
      }
      if (tick % 115 < 22) {
        val fx = 140 + (tick * 3 % math.max(1, getWidth - 280))
        drawSparkle(g, fx, 180 + (tick % 23) * 2, new Color(255, 200, 83))
      }
    }

    private def drawHUD(g: Graphics2D): Unit = {
      val w = getWidth
      // top translucent HUD
      g.setColor(new Color(15, 12, 35, 226)); g.fillRoundRect(14, 12, w - 28, 78, 19, 19)
      g.setColor(new Color(255, 194, 72)); g.setStroke(new BasicStroke(1.4f)); g.drawRoundRect(14, 12, w - 28, 78, 19, 19)
      g.setFont(font(20, Font.BOLD)); g.setColor(new Color(255, 212, 104)); g.drawString("BIXUU DIWALI", 31, 40)
      g.setFont(font(12, Font.BOLD)); g.setColor(Color.WHITE); g.drawString(if (family == "Normal Family") normalFamilyName.take(22) else family, 32, 61)
      g.setFont(font(14, Font.BOLD)); g.setColor(new Color(140, 250, 206)); g.drawString("SCORE: " + score, 32, 79)
      g.setFont(font(14, Font.BOLD)); g.setColor(new Color(255, 243, 213)); g.drawString("TASK: " + currentTask, 260, 42)
      g.setFont(font(12, Font.PLAIN)); g.setColor(new Color(224, 218, 241))
      g.drawString("Supplies: " + (if (supplies) "COLLECTED ?" else "NOT COLLECTED") + "     Decorations: " + targets.count(_.done) + "/4", 260, 66)
      g.setColor(new Color(255, 206, 105)); g.setFont(font(11, Font.BOLD)); g.drawString("[E] INTERACT    [ESC] MENU    [P] PAUSE", w - 290, 79)

      // Context interaction prompt
      val prompt = activePrompt
      if (prompt.nonEmpty && phase == "playing") {
        val p = prompt.get
        val tw = math.min(w - 40, 370)
        g.setColor(new Color(9, 12, 26, 220)); g.fillRoundRect((w - tw) / 2, getHeight - 112, tw, 35, 13, 13)
        g.setColor(new Color(255, 212, 101)); g.drawRoundRect((w - tw) / 2, getHeight - 112, tw, 35, 13, 13)
        g.setFont(font(14, Font.BOLD)); g.setColor(Color.WHITE); centered(g, "Press E / ENTER: " + p, w / 2, getHeight - 90)
      }
      // toast at bottom
      if (toastTicks > 0 && toast != null && toast.nonEmpty) {
        g.setFont(font(13, Font.BOLD))
        val fm = g.getFontMetrics
        val tw = math.min(w - 40, fm.stringWidth(toast) + 38)
        g.setColor(new Color(20, 15, 42, 225)); g.fillRoundRect((w - tw) / 2, getHeight - 65, tw, 31, 12, 12)
        g.setColor(new Color(255, 198, 78)); g.drawRoundRect((w - tw) / 2, getHeight - 65, tw, 31, 12, 12)
        g.setColor(new Color(255, 244, 220)); centered(g, toast, w / 2, getHeight - 45)
      }
      // minimap-like mission route at lower left
      g.setColor(new Color(17, 14, 34, 200)); g.fillRoundRect(17, getHeight - 127, 178, 47, 12, 12)
      g.setFont(font(10, Font.BOLD)); g.setColor(new Color(255, 214, 111)); g.drawString("DIWALI CHECKLIST", 28, getHeight - 109)
      g.setFont(font(10, Font.PLAIN)); g.setColor(new Color(228, 229, 245))
      g.drawString((if (supplies) "?" else "?") + " Supplies   " + (if (targets.forall(_.done)) "?" else "?") + " Decorate", 28, getHeight - 94)
      g.drawString((if (pujaDone) "?" else "?") + " Puja", 28, getHeight - 82)
    }

    private def activePrompt: Option[String] = {
      if (family == "Gokuldham Family") {
        stage match {
          case 0 if distance(playerX, playerY, 420, 355) < 95 => Some("Go down stairs")
          case 1 if distance(playerX, playerY, 535, 430) < 85 => Some("Ask permission")
          case 2 if distance(playerX, playerY, getWidth - 175, 420) < 100 => Some("Talk to Abdul")
          case 3 => targets.find(t => !t.done && distance(playerX, playerY, t.x, t.y) < 78).map(_.label)
          case 4 if distance(playerX, playerY, 535, 535) < 90 => Some("Start community puja")
          case _ => None
        }
      } else {
        stage match {
          case 0 if distance(playerX, playerY, getWidth - 175, 420) < 100 => Some("Buy supplies")
          case 1 => targets.find(t => !t.done && distance(playerX, playerY, t.x, t.y) < 78).map(_.label)
          case 2 if distance(playerX, playerY, 225, 535) < 95 => Some("Start home puja")
          case _ => None
        }
      }
    }

    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, Font.PLAIN)); 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)
      // Celebratory virtual sparkles
      var i = 0
      while (i < 14) {
        val fx = (i * 83 + tick * 2) % math.max(1, w)
        val fy = (i * 57 + tick) % math.max(1, h)
        drawSparkle(g, fx, fy, if (i % 2 == 0) new Color(255, 206, 91) else new Color(101, 244, 215))
        i += 1
      }
    }

    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)
    }

    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 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, (32 * scale).toInt, (28 * scale).toInt)
        g.setColor(new Color(204, 91, 40)); g.fillOval(xx, yy, (21 * scale).toInt, (10 * scale).toInt)
        g.setColor(new Color(255, 207, 82)); g.fillOval(xx + (5 * scale).toInt, yy - (5 * scale).toInt, (11 * scale).toInt, (7 * scale).toInt)
        g.setColor(new Color(255, 249, 191)); g.fillOval(xx + (9 * scale).toInt, yy - (11 * scale).toInt, (4 * scale).toInt, (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 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 drawPerson(g: Graphics2D, xx: Double, yy: Double, shirt: Color, skin: Color, moving: Boolean, 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 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 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 drawSparkle(g: Graphics2D, x: Int, y: Int, c: Color): Unit = {
      g.setColor(new Color(c.getRed, c.getGreen, c.getBlue, 70)); g.fillOval(x - 9, y - 9, 18, 18)
      g.setColor(c); g.setStroke(new BasicStroke(1.5f)); g.drawLine(x - 6, y, x + 6, y); g.drawLine(x, y - 6, x, y + 6)
      g.fillOval(x - 2, y - 2, 4, 4)
    }

    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 centered(g: Graphics2D, text: String, x: Int, y: Int): Unit = {
      val fm = g.getFontMetrics
      g.drawString(text, x - fm.stringWidth(text) / 2, y)
    }

    private def drawStringShadow(g: Graphics2D, text: String, x: Int, y: Int, f: Font, c: Color): Unit = {
      g.setFont(f); g.setColor(new Color(0, 0, 0, 125)); g.drawString(text, x + 1, y + 1)
      g.setColor(c); g.drawString(text, x, y)
    }

    override def keyPressed(e: KeyEvent): Unit = {
      val k = e.getKeyCode
      if (phase == "menu") {
        if (k == KeyEvent.VK_1) startGame("Gokuldham Family")
        else if (k == KeyEvent.VK_2) startGame("Normal Family")
        else if (k == KeyEvent.VK_ENTER) startGame(family)
      } else if (phase == "complete") {
        if (k == KeyEvent.VK_ENTER || k == KeyEvent.VK_SPACE) {
          phase = "menu"
          showToast("Choose a family to play again.", 110)
        } else if (k == KeyEvent.VK_ESCAPE) {
          phase = "menu"
          showToast("Choose a family to play again.", 110)
        }
      } else {
        k match {
          case KeyEvent.VK_W | KeyEvent.VK_UP => moveUp = true
          case KeyEvent.VK_S | KeyEvent.VK_DOWN => moveDown = true
          case KeyEvent.VK_A | KeyEvent.VK_LEFT => moveLeft = true
          case KeyEvent.VK_D | KeyEvent.VK_RIGHT => moveRight = true
          case KeyEvent.VK_E | KeyEvent.VK_ENTER | KeyEvent.VK_SPACE => interact()
          case KeyEvent.VK_ESCAPE => phase = "menu"; stairDescending = false; moveUp = false; moveDown = false; moveLeft = false; moveRight = false
          case KeyEvent.VK_P => pause = !pause
          case _ =>
        }
      }
    }

    override def keyReleased(e: KeyEvent): Unit = {
      e.getKeyCode match {
        case KeyEvent.VK_W | KeyEvent.VK_UP => moveUp = false
        case KeyEvent.VK_S | KeyEvent.VK_DOWN => moveDown = false
        case KeyEvent.VK_A | KeyEvent.VK_LEFT => moveLeft = false
        case KeyEvent.VK_D | KeyEvent.VK_RIGHT => moveRight = false
        case _ =>
      }
    }
    override def keyTyped(e: KeyEvent): Unit = {}

    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: makes the game window open when you press Run in the Kojo editor.
BixuuDiwaliGame.main(Array.empty[String])