Code Sketch


Bixuu spring season event 5 triel
By: Mhalsakant School
Category: Programming
import java.awt.{BasicStroke, Color, Font, GradientPaint, Graphics, Graphics2D, Polygon, RenderingHints}
import java.awt.event.{ActionEvent, ActionListener}
import java.awt.geom.{Arc2D, Ellipse2D}
import javax.swing.{AbstractAction, JFrame, JComponent, JPanel, JOptionPane, KeyStroke, SwingUtilities, Timer, WindowConstants}
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer

/**
 * SPRING SEASON EVENT 5 - NATUREVERSE: THE FUTURE BLOOM
 * A standalone Scala + Java Swing adventure/fighting game.
 *
 * Controls: W/A/S/D or arrow keys = move, SPACE = nature-pulse attack,
 *           P = pause/resume, R = restart.
 *
 * No external libraries are required. The 3D look is drawn with a
 * perspective projection using Swing Graphics2D.
 */
object BixuuSpringSeasonEvent5 {

  private case class Seed(var x: Double, var z: Double, var collected: Boolean = false)
  private class Guardian(var x: Double, var z: Double, var hp: Int = 3,
                         var attackCooldown: Double = 0.0, var counted: Boolean = false)
  private case class ScreenPoint(x: Int, y: Int, scale: Double, depth: Double)

  private val keys = mutable.Set.empty[String]
  private val enemyStarts = Array((-210.0, 520.0), (205.0, 890.0), (-160.0, 1270.0), (180.0, 1630.0))
  private val seedStarts = Array((-80.0, 310.0), (130.0, 650.0), (-125.0, 1010.0), (110.0, 1390.0), (0.0, 1810.0))
  private val trees: Array[(Double, Double)] = Array.tabulate(48) { i =>
    val side = if (i % 2 == 0) -1.0 else 1.0
    val row = i / 2
    val x = side * (390.0 + (row % 6) * 145.0)
    val z = 170.0 + row * 112.0
    (x, z)
  }

  private val guardians = ArrayBuffer.empty[Guardian]
  private val seeds = ArrayBuffer.empty[Seed]

  private var playerX = 0.0
  private var playerZ = 0.0
  private var facingX = 0.0
  private var facingZ = 1.0
  private var health = 100
  private var kills = 0
  private var collectedSeeds = 0
  private var attackCooldown = 0.0
  private var hurtCooldown = 0.0
  private var attackFlash = 0.0
  private var attackRequested = false
  private var paused = false
  private var won = false
  private var lost = false
  private var message = "Find the five Bloom Seeds and defeat the four corrupted guardians!"
  private var messageTimer = 5.0
  private var elapsed = 0.0
  private var panelRef: JPanel = _
  private var frameRef: JFrame = null
  private var gameTimerRef: Timer = null

  private val skyTop = new Color(8, 20, 48)
  private val skyBottom = new Color(78, 178, 171)
  private val leafGreen = new Color(42, 175, 112)
  private val darkGreen = new Color(12, 75, 59)
  private val gold = new Color(255, 218, 100)
  private val cyan = new Color(92, 242, 226)

  private def clamp(value: Double, low: Double, high: Double): Double =
    math.max(low, math.min(high, value))

  private def distance(ax: Double, az: Double, bx: Double, bz: Double): Double = {
    val dx = ax - bx
    val dz = az - bz
    math.sqrt(dx * dx + dz * dz)
  }

  private def showMessage(text: String, seconds: Double = 3.0): Unit = {
    message = text
    messageTimer = seconds
  }

  private def resetGame(): Unit = {
    playerX = 0.0
    playerZ = 0.0
    facingX = 0.0
    facingZ = 1.0
    health = 100
    kills = 0
    collectedSeeds = 0
    attackCooldown = 0.0
    hurtCooldown = 0.0
    attackFlash = 0.0
    attackRequested = false
    paused = false
    won = false
    lost = false
    elapsed = 0.0
    keys.clear()
    guardians.clear()
    enemyStarts.foreach { p => guardians += new Guardian(p._1, p._2) }
    seeds.clear()
    seedStarts.foreach { p => seeds += Seed(p._1, p._2) }
    showMessage("New adventure! Collect 5 Bloom Seeds and defeat 4 guardians.", 5.0)
  }

  private def bindKey(panel: JPanel, key: String)(onDown: => Unit): Unit = {
    val input = panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
    val actions = panel.getActionMap
    input.put(KeyStroke.getKeyStroke("pressed " + key), "down_" + key)
    input.put(KeyStroke.getKeyStroke("released " + key), "up_" + key)

    actions.put("down_" + key, new AbstractAction {
      override def actionPerformed(e: ActionEvent): Unit = {
        if (!keys.contains(key)) {
          keys += key
          onDown
        }
      }
    })
    actions.put("up_" + key, new AbstractAction {
      override def actionPerformed(e: ActionEvent): Unit = { keys -= key }
    })
  }

  private def project(worldX: Double, worldZ: Double, width: Int, height: Int): Option[ScreenPoint] = {
    val relativeZ = worldZ - playerZ
    val depth = relativeZ + 245.0
    if (depth < 85.0 || relativeZ > 3000.0) None
    else {
      val scale = 235.0 / depth
      val sx = width * 0.5 + (worldX - playerX) * scale * 1.55
      val sy = height * 0.395 + 65000.0 / depth
      Some(ScreenPoint(sx.toInt, sy.toInt, scale, depth))
    }
  }

  private def createAndShow(): Unit = {
    if (gameTimerRef != null) gameTimerRef.stop()
    if (frameRef != null) frameRef.dispose()
    resetGame()

    val panel = new JPanel {
      override def paintComponent(graphics: Graphics): Unit = {
        super.paintComponent(graphics)
        val g = graphics.create().asInstanceOf[Graphics2D]
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
        g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)
        renderGame(g, getWidth, getHeight)
        g.dispose()
      }
    }
    panelRef = panel
    panel.setFocusable(true)
    panel.setBackground(Color.BLACK)

    Seq("W", "A", "S", "D", "UP", "DOWN", "LEFT", "RIGHT").foreach { key => bindKey(panel, key) {} }
    bindKey(panel, "SPACE") { attackRequested = true }
    bindKey(panel, "P") { if (!won && !lost) paused = !paused }
    bindKey(panel, "R") { resetGame() }

    val frame = new JFrame("SPRING SEASON EVENT 5 - NATUREVERSE: THE FUTURE BLOOM")
    frameRef = frame
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
    frame.setContentPane(panel)
    frame.setSize(1180, 780)
    frame.setMinimumSize(new java.awt.Dimension(820, 560))
    frame.setLocationRelativeTo(null)
    frame.setVisible(true)
    panel.requestFocusInWindow()

    var previousTime = System.nanoTime()
    val gameTimer = new Timer(16, new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = {
        val now = System.nanoTime()
        val dt = math.min(0.05, (now - previousTime) / 1000000000.0)
        previousTime = now
        if (!paused && !won && !lost) updateGame(dt)
        panel.repaint()
      }
    })
    gameTimerRef = gameTimer
    gameTimer.start()
  }

  private def updateGame(dt: Double): Unit = {
    elapsed += dt
    if (messageTimer > 0.0) messageTimer -= dt
    if (attackCooldown > 0.0) attackCooldown -= dt
    if (hurtCooldown > 0.0) hurtCooldown -= dt
    if (attackFlash > 0.0) attackFlash -= dt

    val speed = 255.0 * dt
    var moveX = 0.0
    var moveZ = 0.0
    if (keys.contains("W") || keys.contains("UP")) moveZ += 1.0
    if (keys.contains("S") || keys.contains("DOWN")) moveZ -= 1.0
    if (keys.contains("A") || keys.contains("LEFT")) moveX -= 1.0
    if (keys.contains("D") || keys.contains("RIGHT")) moveX += 1.0

    val length = math.sqrt(moveX * moveX + moveZ * moveZ)
    if (length > 0.0) {
      moveX /= length
      moveZ /= length
      playerX = clamp(playerX + moveX * speed, -520.0, 520.0)
      playerZ = clamp(playerZ + moveZ * speed, -80.0, 2050.0)
      facingX = moveX
      facingZ = moveZ
    }

    seeds.foreach { seed =>
      if (!seed.collected && distance(playerX, playerZ, seed.x, seed.z) < 62.0) {
        seed.collected = true
        collectedSeeds += 1
        health = math.min(100, health + 8)
        showMessage("Bloom Seed collected! +8 health. (" + collectedSeeds + "/5)", 2.2)
      }
    }

    if (attackRequested) {
      attackRequested = false
      if (attackCooldown <= 0.0) {
        attackCooldown = 0.38
        attackFlash = 0.25
        val target = guardians.filter(_.hp > 0).map { enemy =>
          (enemy, distance(playerX, playerZ, enemy.x, enemy.z))
        }.filter { pair =>
          val enemy = pair._1
          val dx = enemy.x - playerX
          val dz = enemy.z - playerZ
          val d = math.max(1.0, pair._2)
          val forward = (dx * facingX + dz * facingZ) / d
          pair._2 < 205.0 && forward > -0.35
        }.sortBy(_._2).headOption

        target match {
          case Some((enemy, _)) =>
            enemy.hp -= 1
            if (enemy.hp <= 0 && !enemy.counted) {
              enemy.counted = true
              kills += 1
              showMessage("Guardian defeated! (" + kills + "/4)", 2.8)
            } else {
              showMessage("Nature Pulse hit! Keep going!", 0.9)
            }
          case None => showMessage("No guardian in range. Move closer for a Nature Pulse!", 1.0)
        }
      }
    }

    guardians.foreach { enemy =>
      if (enemy.hp > 0) {
        val dx = playerX - enemy.x
        val dz = playerZ - enemy.z
        val d = math.sqrt(dx * dx + dz * dz)
        if (d < 560.0 && d > 58.0) {
          val enemySpeed = 72.0 * dt
          enemy.x += (dx / d) * enemySpeed
          enemy.z += (dz / d) * enemySpeed
        } else if (d <= 58.0 && enemy.attackCooldown <= 0.0) {
          if (hurtCooldown <= 0.0) {
            health -= 12
            hurtCooldown = 0.75
            showMessage("A guardian reached you! Keep moving and use Nature Pulse.", 1.5)
          }
          enemy.attackCooldown = 1.05
        }
        if (enemy.attackCooldown > 0.0) enemy.attackCooldown -= dt
      }
    }

    if (health <= 0) {
      health = 0
      lost = true
      showMessage("Your journey has ended. Press R to try again.", 100.0)
    }

    val portalUnlocked = collectedSeeds >= 5 && kills >= 4
    if (portalUnlocked && distance(playerX, playerZ, 0.0, 2050.0) < 95.0) {
      won = true
      showMessage("THE FUTURE BLOOM IS RESTORED! You saved Natureverse! Press R to play again.", 100.0)
    }
  }

  private def renderGame(g: Graphics2D, w: Int, h: Int): Unit = {
    drawLandscape(g, w, h)

    // Far scenery is drawn first, then closer scenery and characters.
    trees.sortBy(t => -t._2).foreach { tree =>
      project(tree._1, tree._2, w, h).foreach(p => drawTree(g, p))
    }

    project(0.0, 2050.0, w, h).foreach(p => drawPortal(g, p, collectedSeeds >= 5 && kills >= 4))

    seeds.filterNot(_.collected).sortBy(s => -s.z).foreach { seed =>
      project(seed.x, seed.z, w, h).foreach(p => drawSeed(g, p))
    }

    guardians.filter(_.hp > 0).sortBy(e => -e.z).foreach { enemy =>
      project(enemy.x, enemy.z, w, h).foreach(p => drawGuardian(g, p, enemy))
    }

    drawPlayer(g, w, h)
    drawHud(g, w, h)

    if (paused || won || lost) drawOverlay(g, w, h)
  }

  private def drawLandscape(g: Graphics2D, w: Int, h: Int): Unit = {
    g.setPaint(new GradientPaint(0f, 0f, skyTop, 0f, (h * 0.58).toFloat, skyBottom))
    g.fillRect(0, 0, w, h)

    // Distant glowing sun and layered mountains.
    g.setColor(new Color(255, 222, 140, 205))
    g.fillOval((w * 0.73).toInt, (h * 0.16).toInt, (h * 0.16).toInt, (h * 0.16).toInt)
    val m1 = new Polygon(Array(0, (w * 0.12).toInt, (w * 0.25).toInt, (w * 0.39).toInt, (w * 0.53).toInt, (w * 0.7).toInt, w),
      Array((h * 0.47).toInt, (h * 0.30).toInt, (h * 0.46).toInt, (h * 0.28).toInt, (h * 0.46).toInt, (h * 0.32).toInt, (h * 0.46).toInt), 7)
    g.setColor(new Color(31, 89, 103))
    g.fillPolygon(m1)
    val m2 = new Polygon(Array(0, (w * 0.18).toInt, (w * 0.34).toInt, (w * 0.49).toInt, (w * 0.68).toInt, (w * 0.84).toInt, w),
      Array((h * 0.52).toInt, (h * 0.38).toInt, (h * 0.50).toInt, (h * 0.36).toInt, (h * 0.51).toInt, (h * 0.37).toInt, (h * 0.50).toInt), 7)
    g.setColor(new Color(23, 112, 91))
    g.fillPolygon(m2)

    val horizon = (h * 0.395).toInt
    g.setPaint(new GradientPaint(0f, horizon.toFloat, new Color(31, 134, 91), 0f, h.toFloat, new Color(6, 47, 42)))
    g.fillRect(0, horizon, w, h - horizon)

    // The central trail narrows toward the horizon to create perspective.
    val path = new Polygon(Array((w * 0.47).toInt, (w * 0.53).toInt, (w * 0.88).toInt, (w * 0.12).toInt),
      Array(horizon, horizon, h, h), 4)
    g.setColor(new Color(104, 105, 69))
    g.fillPolygon(path)

    // Trail edges and perspective floor markings.
    g.setColor(new Color(213, 184, 111, 100))
    g.setStroke(new BasicStroke(3f))
    g.drawLine((w * 0.47).toInt, horizon, (w * 0.12).toInt, h)
    g.drawLine((w * 0.53).toInt, horizon, (w * 0.88).toInt, h)
    g.setColor(new Color(230, 221, 164, 45))
    var i = 0
    while (i < 9) {
      val z = 180.0 + i * 245.0
      val left = project(-95.0, z, w, h)
      val right = project(95.0, z, w, h)
      (left, right) match {
        case (Some(a), Some(b)) => g.drawLine(a.x, a.y, b.x, b.y)
        case _ =>
      }
      i += 1
    }

    // Tiny fireflies in the air.
    var j = 0
    while (j < 22) {
      val x = ((j * 173 + 71) % math.max(1, w))
      val y = (h * 0.22 + ((j * 47) % math.max(1, (h * 0.42).toInt))).toInt
      val alpha = 90 + (j * 7 % 120)
      g.setColor(new Color(220, 255, 184, alpha))
      g.fillOval(x, y, 3, 3)
      j += 1
    }
  }

  private def drawTree(g: Graphics2D, p: ScreenPoint): Unit = {
    if (p.x < -100 || p.x > panelRef.getWidth + 100) return
    val s = clamp(p.scale, 0.12, 1.15)
    val trunkH = math.max(5, (76.0 * s).toInt)
    val trunkW = math.max(2, (13.0 * s).toInt)
    val crown = math.max(8, (57.0 * s).toInt)
    g.setColor(new Color(42, 48, 34))
    g.fillRect(p.x - trunkW / 2, p.y - trunkH, trunkW, trunkH)
    g.setColor(new Color(17, 94, 69))
    g.fillOval(p.x - crown / 2, p.y - trunkH - crown / 2, crown, crown)
    g.setColor(new Color(46, 172, 103))
    g.fillOval(p.x - crown / 2, p.y - trunkH - crown, crown, crown)
    g.setColor(new Color(122, 238, 151, 180))
    g.fillOval(p.x - crown / 5, p.y - trunkH - crown + crown / 5, math.max(3, crown / 3), math.max(3, crown / 3))
  }

  private def drawSeed(g: Graphics2D, p: ScreenPoint): Unit = {
    val r = math.max(5, (15.0 * clamp(p.scale, 0.2, 1.0)).toInt)
    g.setColor(new Color(255, 214, 74, 65))
    g.fillOval(p.x - r * 2, p.y - r * 2, r * 4, r * 4)
    g.setColor(gold)
    g.fillOval(p.x - r, p.y - r, r * 2, r * 2)
    g.setColor(Color.WHITE)
    g.fillOval(p.x - r / 3, p.y - r / 2, math.max(2, r / 2), math.max(2, r / 2))
    g.setColor(new Color(255, 247, 174))
    g.drawLine(p.x, p.y - r * 2, p.x, p.y + r * 2)
    g.drawLine(p.x - r * 2, p.y, p.x + r * 2, p.y)
  }

  private def drawGuardian(g: Graphics2D, p: ScreenPoint, enemy: Guardian): Unit = {
    val s = clamp(p.scale, 0.18, 1.05)
    val bodyW = math.max(13, (40.0 * s).toInt)
    val bodyH = math.max(18, (62.0 * s).toInt)
    val head = math.max(10, (24.0 * s).toInt)
    g.setColor(new Color(0, 0, 0, 75))
    g.fillOval(p.x - bodyW, p.y - 5, bodyW * 2, math.max(5, bodyW / 2))
    g.setColor(new Color(33, 43, 49))
    g.fillRoundRect(p.x - bodyW / 2, p.y - bodyH, bodyW, bodyH, bodyW / 3, bodyW / 3)
    g.setColor(new Color(104, 38, 77))
    g.fillOval(p.x - head / 2, p.y - bodyH - head + 5, head, head)
    g.setColor(new Color(255, 70, 97))
    g.fillOval(p.x - head / 3, p.y - bodyH - head / 2, math.max(3, head / 5), math.max(3, head / 5))
    g.fillOval(p.x + head / 8, p.y - bodyH - head / 2, math.max(3, head / 5), math.max(3, head / 5))
    g.setColor(new Color(159, 82, 188))
    g.setStroke(new BasicStroke(math.max(1f, (4.0 * s).toFloat)))
    g.drawLine(p.x + bodyW / 2, p.y - bodyH / 2, p.x + bodyW, p.y - bodyH / 2 - (22.0 * s).toInt)

    val barW = math.max(20, (48.0 * s).toInt)
    val barY = p.y - bodyH - head - 10
    g.setColor(new Color(25, 20, 30, 210))
    g.fillRoundRect(p.x - barW / 2, barY, barW, 5, 5, 5)
    g.setColor(new Color(255, 73, 105))
    g.fillRoundRect(p.x - barW / 2, barY, (barW * enemy.hp / 3.0).toInt, 5, 5, 5)
  }

  private def drawPortal(g: Graphics2D, p: ScreenPoint, unlocked: Boolean): Unit = {
    val s = clamp(p.scale, 0.22, 1.2)
    val rw = math.max(25, (92.0 * s).toInt)
    val rh = math.max(38, (138.0 * s).toInt)
    g.setColor(new Color(0, 0, 0, 65))
    g.fillOval(p.x - rw, p.y - 7, rw * 2, 18)
    g.setColor(if (unlocked) new Color(65, 248, 216, 85) else new Color(110, 108, 143, 65))
    g.fillOval(p.x - rw, p.y - rh, rw * 2, rh * 2)
    g.setStroke(new BasicStroke(math.max(3f, (8.0 * s).toFloat)))
    g.setColor(if (unlocked) cyan else new Color(123, 128, 151))
    g.drawOval(p.x - rw, p.y - rh, rw * 2, rh * 2)
    g.setStroke(new BasicStroke(math.max(1f, (2.0 * s).toFloat)))
    g.setColor(new Color(222, 255, 245, 200))
    g.drawOval(p.x - (rw * 0.72).toInt, p.y - (rh * 0.83).toInt, (rw * 1.44).toInt, (rh * 1.66).toInt)
    g.setFont(new Font("SansSerif", Font.BOLD, math.max(9, (13 * s).toInt)))
    g.setColor(Color.WHITE)
    g.drawString(if (unlocked) "RESTORE" else "LOCKED", p.x - rw, p.y - rh - 8)
  }

  private def drawPlayer(g: Graphics2D, w: Int, h: Int): Unit = {
    val x = w / 2
    val y = (h * 0.805).toInt
    val flicker = hurtCooldown > 0.0 && (elapsed * 18.0).toInt % 2 == 0
    if (!flicker) {
      g.setColor(new Color(0, 0, 0, 95))
      g.fillOval(x - 38, y - 5, 76, 20)
      g.setColor(new Color(24, 67, 91))
      g.fillRoundRect(x - 22, y - 76, 44, 65, 16, 16)
      g.setColor(new Color(58, 207, 166))
      g.fillRoundRect(x - 17, y - 69, 34, 38, 12, 12)
      g.setColor(new Color(237, 181, 134))
      g.fillOval(x - 16, y - 111, 32, 34)
      g.setColor(new Color(28, 36, 43))
      g.fillArc(x - 17, y - 117, 34, 29, 0, 180)
      g.setColor(new Color(21, 43, 53))
      g.fillRect(x - 18, y - 17, 14, 18)
      g.fillRect(x + 4, y - 17, 14, 18)
      // Outstretched hand with a small glowing nature orb (no realistic weapon).
      g.setColor(new Color(178, 233, 226))
      g.setStroke(new BasicStroke(5f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND))
      g.drawLine(x + 20, y - 60, x + 35, y - 76)
      g.setColor(cyan)
      g.fillOval(x + 28, y - 91, 18, 18)
      g.setColor(new Color(230, 255, 245))
      g.drawOval(x + 28, y - 91, 18, 18)
      g.setColor(gold)
      g.drawLine(x + 20, y - 61, x + 8, y - 48)
    }

    if (attackFlash > 0.0) {
      g.setColor(new Color(135, 255, 240, 145))
      g.setStroke(new BasicStroke(8f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND))
      g.drawArc(x - 18, y - 132, 112, 100, 205, 115)
      g.setColor(new Color(255, 255, 220, 190))
      g.setStroke(new BasicStroke(3f))
      g.drawArc(x - 18, y - 132, 112, 100, 205, 115)
    }
  }

  private def drawHud(g: Graphics2D, w: Int, h: Int): Unit = {
    // Header panel.
    g.setColor(new Color(5, 19, 31, 215))
    g.fillRoundRect(22, 18, 340, 134, 22, 22)
    g.setColor(new Color(92, 242, 226, 180))
    g.setStroke(new BasicStroke(2f))
    g.drawRoundRect(22, 18, 340, 134, 22, 22)
    g.setFont(new Font("SansSerif", Font.BOLD, 22))
    g.setColor(new Color(173, 255, 231))
    g.drawString("NATUREVERSE", 42, 49)
    g.setFont(new Font("SansSerif", Font.PLAIN, 12))
    g.setColor(new Color(215, 238, 232))
    g.drawString("THE FUTURE BLOOM  |  ADVENTURE QUEST", 43, 69)

    // Health bar.
    g.setColor(new Color(26, 37, 43))
    g.fillRoundRect(43, 86, 230, 15, 8, 8)
    g.setColor(if (health > 35) new Color(63, 220, 139) else new Color(255, 83, 96))
    g.fillRoundRect(43, 86, (230.0 * health / 100.0).toInt, 15, 8, 8)
    g.setFont(new Font("SansSerif", Font.BOLD, 12))
    g.setColor(Color.WHITE)
    g.drawString("HEALTH " + health + "%", 282, 98)
    g.setFont(new Font("SansSerif", Font.BOLD, 13))
    g.setColor(gold)
    g.drawString("BLOOM SEEDS  " + collectedSeeds + "/5", 43, 126)
    g.setColor(new Color(255, 147, 164))
    g.drawString("GUARDIANS  " + kills + "/4", 202, 126)

    // Event title banner.
    g.setFont(new Font("SansSerif", Font.BOLD, 17))
    val eventTitle = "SPRING SEASON EVENT 5"
    g.setColor(new Color(3, 23, 31, 190))
    g.drawString(eventTitle, w / 2 - g.getFontMetrics.stringWidth(eventTitle) / 2 + 1, 171)
    g.setFont(new Font("SansSerif", Font.BOLD, 14))
    val gameTitle = "NATUREVERSE: THE FUTURE BLOOM"
    g.setColor(new Color(226, 255, 239))
    g.drawString(gameTitle, w / 2 - g.getFontMetrics.stringWidth(gameTitle) / 2, 192)

    // Mission panel.
    val mx = w - 330
    g.setColor(new Color(5, 19, 31, 205))
    g.fillRoundRect(mx, 18, 308, 112, 20, 20)
    g.setColor(new Color(255, 218, 100, 140))
    g.drawRoundRect(mx, 18, 308, 112, 20, 20)
    g.setFont(new Font("SansSerif", Font.BOLD, 16))
    g.setColor(gold)
    g.drawString("MAIN QUEST", mx + 18, 45)
    g.setFont(new Font("SansSerif", Font.PLAIN, 13))
    g.setColor(Color.WHITE)
    g.drawString((if (collectedSeeds >= 5) "[DONE]" else "[ ]") + " Collect 5 Bloom Seeds", mx + 18, 72)
    g.drawString((if (kills >= 4) "[DONE]" else "[ ]") + " Defeat 4 corrupted guardians", mx + 18, 94)
    g.drawString((if (collectedSeeds >= 5 && kills >= 4) "[OPEN]" else "[LOCKED]") + " Reach the Future Portal", mx + 18, 116)

    // Bottom controls strip.
    val bottomY = h - 47
    g.setColor(new Color(4, 14, 23, 195))
    g.fillRoundRect(w / 2 - 280, bottomY, 560, 31, 16, 16)
    g.setFont(new Font("SansSerif", Font.BOLD, 12))
    g.setColor(new Color(230, 246, 240))
    g.drawString("WASD / ARROWS  MOVE     SPACE  NATURE PULSE     P  PAUSE     R  RESTART", w / 2 - 255, bottomY + 20)

    if (messageTimer > 0.0 && !paused && !won && !lost) {
      val boxW = math.min(w - 40, math.max(380, message.length * 8 + 50))
      g.setColor(new Color(4, 22, 31, 205))
      g.fillRoundRect(w / 2 - boxW / 2, h - 100, boxW, 38, 18, 18)
      g.setColor(new Color(185, 255, 233, 180))
      g.drawRoundRect(w / 2 - boxW / 2, h - 100, boxW, 38, 18, 18)
      g.setFont(new Font("SansSerif", Font.BOLD, 14))
      g.setColor(Color.WHITE)
      g.drawString(message, w / 2 - boxW / 2 + 18, h - 76)
    }
  }

  private def drawOverlay(g: Graphics2D, w: Int, h: Int): Unit = {
    g.setColor(new Color(0, 8, 18, 190))
    g.fillRect(0, 0, w, h)
    val boxW = math.min(650, w - 60)
    val boxH = 260
    val x = (w - boxW) / 2
    val y = (h - boxH) / 2
    g.setColor(new Color(6, 34, 45, 245))
    g.fillRoundRect(x, y, boxW, boxH, 30, 30)
    g.setColor(new Color(92, 242, 226))
    g.setStroke(new BasicStroke(3f))
    g.drawRoundRect(x, y, boxW, boxH, 30, 30)

    g.setFont(new Font("SansSerif", Font.BOLD, 32))
    g.setColor(if (won) gold else if (lost) new Color(255, 112, 125) else cyan)
    val heading = if (won) "THE FUTURE BLOOM IS RESTORED!" else if (lost) "ADVENTURE OVER" else "GAME PAUSED"
    val fm = g.getFontMetrics
    g.drawString(heading, w / 2 - fm.stringWidth(heading) / 2, y + 66)
    g.setFont(new Font("SansSerif", Font.PLAIN, 17))
    g.setColor(Color.WHITE)
    val line1 = if (won) "You gathered the seeds and saved the Natureverse." else if (lost) "The guardians were too strong this time." else "Take a break, hero. The forest is waiting."
    g.drawString(line1, w / 2 - g.getFontMetrics.stringWidth(line1) / 2, y + 115)
    g.setFont(new Font("SansSerif", Font.BOLD, 17))
    g.setColor(gold)
    val line2 = "Bloom Seeds: " + collectedSeeds + "/5     Guardians: " + kills + "/4"
    g.drawString(line2, w / 2 - g.getFontMetrics.stringWidth(line2) / 2, y + 153)
    g.setFont(new Font("SansSerif", Font.BOLD, 15))
    g.setColor(new Color(190, 255, 240))
    val line3 = if (paused) "Press P to resume  |  Press R to restart" else "Press R to start a new adventure"
    g.drawString(line3, w / 2 - g.getFontMetrics.stringWidth(line3) / 2, y + 205)
  }

  // Kojo Code Exchange-style launcher: RUN the whole script and this opens the JFrame.
  def run(): Unit = {
    SwingUtilities.invokeLater(new Runnable {
      override def run(): Unit = {
        try {
          createAndShow()
        } catch {
          case error: Throwable =>
            error.printStackTrace()
            try {
              JOptionPane.showMessageDialog(
                null,
                "Natureverse could not open the game window.\n" + error.toString,
                "Natureverse startup error",
                JOptionPane.ERROR_MESSAGE
              )
            } catch {
              case _: Throwable => println("Natureverse window startup failed: " + error.toString)
            }
        }
      }
    })
  }

  // Keep a main method for ordinary Scala launchers too.
  def main(args: Array[String]): Unit = run()
}

/** Backward-compatible launcher for the earlier filename/object name. */
object NatureverseAdventure {
  def main(args: Array[String]): Unit = BixuuSpringSeasonEvent5.run()
}

// Kojo launcher: run the complete script, not this line by itself.
BixuuSpringSeasonEvent5.main(Array[String]())