Code Sketch


Bixuu INFINITY REALMS SEASON ETERNAL BLOOM AWAKENING
By: Mhalsakant School
Category: Programming
import java.awt.{BasicStroke, Color, Dimension, Font, FontMetrics, Graphics, Graphics2D, GradientPaint, RenderingHints, Polygon}
import java.awt.event.{ActionEvent, ActionListener, KeyEvent, KeyListener, MouseEvent, MouseListener, WindowAdapter, WindowEvent}
import java.awt.geom.AffineTransform
import javax.swing.{JFrame, JPanel, SwingUtilities, Timer, WindowConstants}
import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable
import scala.util.Random

object InfinityRealmsEternalBloom {
  private val VIEW_W = 1100
  private val VIEW_H = 720
  private val WORLD_W = 1850.0
  private val WORLD_H = 1320.0

  def main(args: Array[String]): Unit = {
    SwingUtilities.invokeLater(new Runnable {
      override def run(): Unit = {
        val frame = new JFrame("INFINITY REALMS SEASON - ETERNAL BLOOM AWAKENING")
        val panel = new GamePanel()
        frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE)
        frame.setContentPane(panel)
        frame.pack()
        frame.setResizable(false)
        frame.setLocationRelativeTo(null)
        frame.addWindowListener(new WindowAdapter {
          override def windowClosing(e: WindowEvent): Unit = panel.stopLoop()
          override def windowClosed(e: WindowEvent): Unit = panel.stopLoop()
        })
        frame.setVisible(true)
        panel.requestFocusInWindow()
      }
    })
  }

  private case class Crystal(var x: Double, var y: Double, var collected: Boolean, var kind: Int)
  private case class Wisp(var x: Double, var y: Double, var stunned: Double)
  private case class Tree(x: Double, y: Double, size: Double, kind: Int)

  private class GamePanel extends JPanel with ActionListener with KeyListener with MouseListener {
    private val MENU = 0
    private val LOGIN = 1
    private val AVATAR = 2
    private val PLAY = 3
    private val PAUSE = 4
    private val WIN = 5
    private val LOST = 6

    private var screen = MENU
    private var playerName = "Bloom Explorer"
    private var editingName = false
    private var avatar = 0
    private var realm = 0
    private var playerX = 145.0
    private var playerY = WORLD_H * 0.52
    private var camX = 0.0
    private var camY = 0.0
    private var targetX = -1.0
    private var targetY = -1.0
    private var health = 3
    private var coins = 0
    private var crystalsCollected = 0
    private var energy = 100.0
    private var dashTime = 0.0
    private var dashCooldown = 0.0
    private var shieldTime = 0.0
    private var damageCooldown = 0.0
    private var elapsed = 0.0
    private var message = "Collect Bloom Crystals and awaken the Ancient Tree!"
    private var messageTime = 5.0
    private var portalOpen = false
    private var worldAwakened = false

    private val keys = mutable.Set[Int]()
    private val crystals = ArrayBuffer[Crystal]()
    private val wisps = ArrayBuffer[Wisp]()
    private val trees = ArrayBuffer[Tree]()
    private val flowers = ArrayBuffer[(Double, Double, Int)]()
    private val rng = new Random(8065L)
    private val timer = new Timer(16, this)

    setPreferredSize(new Dimension(VIEW_W, VIEW_H))
    setFocusable(true)
    setDoubleBuffered(true)
    addKeyListener(this)
    addMouseListener(this)
    setToolTipText("WASD: move | SPACE: dash | Q: shield | E: interact | P: pause")
    timer.start()

    def stopLoop(): Unit = timer.stop()

    private def setMessage(s: String, seconds: Double): Unit = {
      message = s
      messageTime = seconds
    }

    private def beginGame(): Unit = {
      realm = 0
      health = 3
      coins = 0
      crystalsCollected = 0
      energy = 100.0
      playerX = 145.0
      playerY = WORLD_H * 0.52
      targetX = -1.0
      targetY = -1.0
      elapsed = 0.0
      worldAwakened = false
      createRealm()
      screen = PLAY
      setMessage("Welcome, " + playerName + "! Gather 7 crystals, then reach the Ancient Tree.", 6.0)
      requestFocusInWindow()
    }

    private def createRealm(): Unit = {
      crystals.clear()
      wisps.clear()
      trees.clear()
      flowers.clear()
      portalOpen = false
      val treeCount = 65
      var i = 0
      while (i < treeCount) {
        val tx = 80.0 + rng.nextDouble() * (WORLD_W - 160.0)
        val ty = 70.0 + rng.nextDouble() * (WORLD_H - 140.0)
        trees += Tree(tx, ty, 0.75 + rng.nextDouble() * 0.8, rng.nextInt(3))
        i += 1
      }
      i = 0
      while (i < 185) {
        val fx = 30.0 + rng.nextDouble() * (WORLD_W - 60.0)
        val fy = 30.0 + rng.nextDouble() * (WORLD_H - 60.0)
        flowers += ((fx, fy, rng.nextInt(5)))
        i += 1
      }
      val crystalCount = 14 + realm * 4
      i = 0
      while (i < crystalCount) {
        val cx = 300.0 + rng.nextDouble() * (WORLD_W - 610.0)
        val cy = 105.0 + rng.nextDouble() * (WORLD_H - 210.0)
        crystals += Crystal(cx, cy, false, rng.nextInt(4))
        i += 1
      }
      i = 0
      while (i < 3 + realm) {
        val ex = 430.0 + rng.nextDouble() * (WORLD_W - 570.0)
        val ey = 100.0 + rng.nextDouble() * (WORLD_H - 200.0)
        wisps += Wisp(ex, ey, 0.0)
        i += 1
      }
      playerX = 145.0
      playerY = WORLD_H * 0.52
      targetX = -1.0
      targetY = -1.0
      camX = 0.0
      camY = 0.0
    }

    private def goalForRealm: Int = 7 + realm * 2

    private def startDash(): Unit = {
      if (screen == PLAY && dashCooldown <= 0.0) {
        dashTime = 0.24
        dashCooldown = 1.25
        setMessage("PETAL DASH!", 0.8)
      }
    }

    private def useShield(): Unit = {
      if (screen == PLAY && energy >= 28.0 && shieldTime <= 0.0) {
        energy -= 28.0
        shieldTime = 3.2
        setMessage("Nature Shield activated!", 1.5)
      } else if (screen == PLAY) {
        setMessage("Need 28 Nature Energy for a shield.", 2.0)
      }
    }

    private def interact(): Unit = {
      if (screen != PLAY) return
      val portalX = WORLD_W - 190.0
      val portalY = WORLD_H - 190.0
      val d = distance(playerX, playerY, portalX, portalY)
      if (d < 125.0) {
        if (crystalsCollected >= goalForRealm) {
          if (realm < 2) {
            realm += 1
            crystalsCollected = 0
            health = math.min(3, health + 1)
            energy = math.min(100.0, energy + 35.0)
            createRealm()
            setMessage("Realm awakened! Welcome to " + realmName + ".", 4.0)
          } else {
            worldAwakened = true
            screen = WIN
          }
        } else {
          setMessage("The portal needs " + goalForRealm + " Bloom Crystals. You have " + crystalsCollected + ".", 3.0)
        }
      } else if (distance(playerX, playerY, 150.0, WORLD_H * 0.52) < 100.0) {
        energy = math.min(100.0, energy + 20.0)
        health = math.min(3, health + 1)
        setMessage("The Home Grove restores your nature energy.", 2.5)
      } else {
        setMessage("Find the glowing Ancient Tree portal at the far end of the realm.", 2.3)
      }
    }

    private def realmName: String = realm match {
      case 0 => "Verdant Grove"
      case 1 => "Crystal Falls"
      case _ => "Moonlit Canopy"
    }

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

    override def actionPerformed(e: ActionEvent): Unit = {
      if (screen == PLAY) updateGame(0.016)
      repaint()
    }

    private def updateGame(dt: Double): Unit = {
      elapsed += dt
      if (messageTime > 0.0) messageTime -= dt
      if (dashTime > 0.0) dashTime -= dt
      if (dashCooldown > 0.0) dashCooldown -= dt
      if (shieldTime > 0.0) shieldTime -= dt
      if (damageCooldown > 0.0) damageCooldown -= dt
      energy = math.min(100.0, energy + dt * 2.4)

      var mx = 0.0
      var my = 0.0
      if (keys.contains(KeyEvent.VK_W) || keys.contains(KeyEvent.VK_UP)) my -= 1.0
      if (keys.contains(KeyEvent.VK_S) || keys.contains(KeyEvent.VK_DOWN)) my += 1.0
      if (keys.contains(KeyEvent.VK_A) || keys.contains(KeyEvent.VK_LEFT)) mx -= 1.0
      if (keys.contains(KeyEvent.VK_D) || keys.contains(KeyEvent.VK_RIGHT)) mx += 1.0
      if (mx != 0.0 || my != 0.0) {
        val len = math.sqrt(mx * mx + my * my)
        mx /= len
        my /= len
        targetX = -1.0
        targetY = -1.0
      } else if (targetX >= 0.0 && targetY >= 0.0) {
        val dx = targetX - playerX
        val dy = targetY - playerY
        val len = math.sqrt(dx * dx + dy * dy)
        if (len > 13.0) {
          mx = dx / len
          my = dy / len
        } else {
          targetX = -1.0
          targetY = -1.0
        }
      }
      val speedBonus = math.min(95.0, crystalsCollected * 5.0)
      var moveSpeed = 205.0 + speedBonus
      if (dashTime > 0.0) moveSpeed *= 2.65
      playerX = math.max(35.0, math.min(WORLD_W - 35.0, playerX + mx * moveSpeed * dt))
      playerY = math.max(50.0, math.min(WORLD_H - 50.0, playerY + my * moveSpeed * dt))

      var i = 0
      while (i < crystals.length) {
        val c = crystals(i)
        if (!c.collected && distance(playerX, playerY, c.x, c.y) < 34.0) {
          c.collected = true
          crystalsCollected += 1
          coins += 15 + realm * 5
          energy = math.min(100.0, energy + 7.0)
          setMessage("Bloom Crystal collected! +" + (15 + realm * 5) + " coins.", 1.3)
        }
        i += 1
      }

      i = 0
      while (i < wisps.length) {
        val w = wisps(i)
        if (w.stunned > 0.0) {
          w.stunned -= dt
        } else {
          val d = distance(w.x, w.y, playerX, playerY)
          if (d < 345.0 && d > 1.0) {
            val sp = 62.0 + realm * 10.0
            w.x += ((playerX - w.x) / d) * sp * dt
            w.y += ((playerY - w.y) / d) * sp * dt
          }
          if (d < 31.0 && damageCooldown <= 0.0 && shieldTime <= 0.0) {
            health -= 1
            damageCooldown = 1.15
            w.stunned = 1.0
            setMessage("A Thorn Wisp touched you! Find a crystal or use your Nature Shield.", 2.5)
            if (health <= 0) screen = LOST
          } else if (d < 31.0 && shieldTime > 0.0) {
            w.stunned = 1.4
          }
        }
        i += 1
      }

      val desiredCamX = math.max(0.0, math.min(WORLD_W - VIEW_W, playerX - VIEW_W / 2.0))
      val desiredCamY = math.max(0.0, math.min(WORLD_H - VIEW_H, playerY - VIEW_H / 2.0))
      camX += (desiredCamX - camX) * 0.12
      camY += (desiredCamY - camY) * 0.12
    }

    override def paintComponent(gg: Graphics): Unit = {
      super.paintComponent(gg)
      val g = gg.asInstanceOf[Graphics2D]
      g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
      g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON)
      screen match {
        case MENU => drawMenu(g)
        case LOGIN => drawLogin(g)
        case AVATAR => drawAvatar(g)
        case PLAY => drawGame(g)
        case PAUSE => drawGame(g); drawPause(g)
        case WIN => drawEnd(g, true)
        case LOST => drawEnd(g, false)
        case _ => drawMenu(g)
      }
    }

    private def drawMenu(g: Graphics2D): Unit = {
      drawBackdrop(g)
      drawCentered(g, "INFINITY REALMS SEASON", VIEW_W / 2, 150, new Font("Serif", Font.BOLD, 34), new Color(222, 255, 220))
      drawCentered(g, "ETERNAL BLOOM AWAKENING", VIEW_W / 2, 215, new Font("Serif", Font.BOLD, 43), new Color(255, 239, 177))
      drawCentered(g, "A MAGICAL NATURE ADVENTURE", VIEW_W / 2, 260, new Font("SansSerif", Font.BOLD, 16), new Color(202, 245, 220))
      drawLargeTree(g, VIEW_W / 2, 390, 1.25)
      drawButton(g, 390, 555, 320, 66, "START YOUR ADVENTURE", true)
      drawCentered(g, "Explore three realms ? Collect crystals ? Awaken the Ancient Tree", VIEW_W / 2, 663, new Font("SansSerif", Font.PLAIN, 14), Color.WHITE)
    }

    private def drawLogin(g: Graphics2D): Unit = {
      drawBackdrop(g)
      drawCentered(g, "WELCOME, EXPLORER", VIEW_W / 2, 170, new Font("Serif", Font.BOLD, 38), new Color(255, 239, 177))
      drawCentered(g, "Enter your name to begin your journey", VIEW_W / 2, 220, new Font("SansSerif", Font.PLAIN, 18), Color.WHITE)
      g.setColor(new Color(8, 38, 37, 215)); g.fillRoundRect(300, 280, 500, 76, 22, 22)
      g.setColor(new Color(165, 239, 192)); g.setStroke(new BasicStroke(2f)); g.drawRoundRect(300, 280, 500, 76, 22, 22)
      g.setFont(new Font("SansSerif", Font.BOLD, 23)); g.setColor(Color.WHITE)
      val label = if (playerName.isEmpty) "Type your name..." else playerName
      g.drawString(label + (if (editingName) "|" else ""), 330, 327)
      drawButton(g, 390, 425, 320, 62, "CONTINUE", true)
      drawCentered(g, "Click the name box, type your name, then click Continue.", VIEW_W / 2, 525, new Font("SansSerif", Font.PLAIN, 15), new Color(226, 255, 231))
    }

    private def drawAvatar(g: Graphics2D): Unit = {
      drawBackdrop(g)
      drawCentered(g, "CHOOSE YOUR BLOOM GUARDIAN", VIEW_W / 2, 145, new Font("Serif", Font.BOLD, 32), new Color(255, 239, 177))
      val names = Array("MOSS RANGER", "PETAL MAGE", "CRYSTAL KNIGHT", "SUN GUARDIAN")
      var i = 0
      while (i < 4) {
        val x = 105 + i * 225
        val selected = avatar == i
        g.setColor(if (selected) new Color(45, 119, 79, 235) else new Color(7, 36, 35, 220))
        g.fillRoundRect(x, 245, 175, 220, 20, 20)
        g.setColor(if (selected) new Color(255, 225, 130) else new Color(133, 207, 164))
        g.setStroke(new BasicStroke(if (selected) 4f else 2f))
        g.drawRoundRect(x, 245, 175, 220, 20, 20)
        drawAvatarSprite(g, x + 87, 330, i, 1.25)
        drawCentered(g, names(i), x + 87, 420, new Font("SansSerif", Font.BOLD, 13), Color.WHITE)
        drawCentered(g, selectedLabel(i), x + 87, 447, new Font("SansSerif", Font.PLAIN, 11), new Color(203, 245, 214))
        i += 1
      }
      drawButton(g, 390, 535, 320, 62, "ENTER THE REALM", true)
      drawCentered(g, "Click a guardian to select your avatar.", VIEW_W / 2, 635, new Font("SansSerif", Font.PLAIN, 14), Color.WHITE)
    }

    private def selectedLabel(i: Int): String = i match {
      case 0 => "Quick explorer"
      case 1 => "Nature energy"
      case 2 => "Strong shield"
      case _ => "Balanced guardian"
    }

    private def drawBackdrop(g: Graphics2D): Unit = {
      g.setPaint(new GradientPaint(0, 0, new Color(7, 37, 42), 0, VIEW_H, new Color(19, 91, 62)))
      g.fillRect(0, 0, VIEW_W, VIEW_H)
      var i = 0
      while (i < 34) {
        val x = (i * 137 + 43) % VIEW_W
        val y = (i * 79 + 27) % 430
        val r = 2 + (i % 4)
        g.setColor(new Color(210, 255, 210, 95 + (i % 4) * 25))
        g.fillOval(x, y, r, r)
        i += 1
      }
      g.setColor(new Color(8, 56, 42))
      g.fillOval(-120, 430, 430, 420)
      g.fillOval(760, 420, 480, 460)
      g.setColor(new Color(13, 76, 51))
      g.fillOval(120, 490, 480, 310)
      g.fillOval(570, 480, 470, 340)
    }

    private def drawGame(g: Graphics2D): Unit = {
      val old = g.getTransform
      g.translate(-camX.toInt, -camY.toInt)
      val top = realm match {
        case 0 => new Color(40, 112, 71)
        case 1 => new Color(40, 105, 105)
        case _ => new Color(43, 66, 103)
      }
      val bottom = realm match {
        case 0 => new Color(13, 67, 48)
        case 1 => new Color(12, 66, 80)
        case _ => new Color(20, 38, 77)
      }
      g.setPaint(new GradientPaint(0, 0, top, 0, WORLD_H.toFloat, bottom))
      g.fillRect(0, 0, WORLD_W.toInt, WORLD_H.toInt)
      g.setColor(new Color(210, 246, 187, 22))
      var gx = 0
      while (gx < WORLD_W.toInt) { g.drawLine(gx, 0, gx, WORLD_H.toInt); gx += 100 }
      var gy = 0
      while (gy < WORLD_H.toInt) { g.drawLine(0, gy, WORLD_W.toInt, gy); gy += 100 }
      drawWindingPath(g)
      var i = 0
      while (i < flowers.length) {
        val f = flowers(i)
        drawFlower(g, f._1, f._2, f._3)
        i += 1
      }
      i = 0
      while (i < trees.length) { val t = trees(i); drawTree(g, t.x, t.y, t.size, t.kind); i += 1 }
      drawBase(g)
      drawPortal(g)
      i = 0
      while (i < crystals.length) { val c = crystals(i); if (!c.collected) drawCrystal(g, c.x, c.y, c.kind); i += 1 }
      i = 0
      while (i < wisps.length) { val w = wisps(i); drawWisp(g, w.x, w.y, w.stunned > 0.0); i += 1 }
      drawPlayer(g, playerX, playerY)
      if (shieldTime > 0.0) {
        g.setColor(new Color(160, 235, 255, 95))
        g.setStroke(new BasicStroke(4f))
        g.drawOval((playerX - 29).toInt, (playerY - 36).toInt, 58, 72)
      }
      g.setTransform(old)
      drawHud(g)
    }

    private def drawWindingPath(g: Graphics2D): Unit = {
      val p = new Polygon()
      p.addPoint(0, (WORLD_H * 0.52).toInt - 70)
      p.addPoint(430, (WORLD_H * 0.52).toInt - 110)
      p.addPoint(690, (WORLD_H * 0.35).toInt)
      p.addPoint(980, (WORLD_H * 0.42).toInt + 80)
      p.addPoint(1250, (WORLD_H * 0.63).toInt)
      p.addPoint(WORLD_W.toInt, WORLD_H.toInt - 120)
      g.setColor(new Color(192, 178, 117, 45)); g.setStroke(new BasicStroke(105f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); g.drawPolyline(Array(0, 430, 690, 980, 1250, WORLD_W.toInt), Array((WORLD_H * 0.52).toInt, (WORLD_H * 0.52).toInt - 110, (WORLD_H * 0.35).toInt, (WORLD_H * 0.42).toInt + 80, (WORLD_H * 0.63).toInt, WORLD_H.toInt - 120), 6)
      g.setColor(new Color(234, 220, 167, 25)); g.setStroke(new BasicStroke(3f)); g.drawPolyline(Array(0, 430, 690, 980, 1250, WORLD_W.toInt), Array((WORLD_H * 0.52).toInt, (WORLD_H * 0.52).toInt - 110, (WORLD_H * 0.35).toInt, (WORLD_H * 0.42).toInt + 80, (WORLD_H * 0.63).toInt, WORLD_H.toInt - 120), 6)
    }

    private def drawTree(g: Graphics2D, x: Double, y: Double, scale: Double, kind: Int): Unit = {
      val s = scale
      g.setColor(new Color(56, 43, 32)); g.fillRoundRect((x - 8 * s).toInt, (y - 12 * s).toInt, (16 * s).toInt, (43 * s).toInt, 8, 8)
      val c = kind match {
        case 0 => new Color(35, 105, 65)
        case 1 => new Color(45, 132, 92)
        case _ => new Color(64, 112, 97)
      }
      g.setColor(c); g.fillOval((x - 37 * s).toInt, (y - 61 * s).toInt, (74 * s).toInt, (68 * s).toInt)
      g.setColor(c.brighter()); g.fillOval((x - 25 * s).toInt, (y - 76 * s).toInt, (49 * s).toInt, (54 * s).toInt)
      g.setColor(new Color(194, 255, 181, 105)); g.fillOval((x - 11 * s).toInt, (y - 65 * s).toInt, (17 * s).toInt, (17 * s).toInt)
    }

    private def drawFlower(g: Graphics2D, x: Double, y: Double, kind: Int): Unit = {
      val col = kind match {
        case 0 => new Color(255, 189, 222)
        case 1 => new Color(248, 235, 164)
        case 2 => new Color(182, 202, 255)
        case 3 => new Color(255, 255, 235)
        case _ => new Color(210, 165, 255)
      }
      g.setColor(new Color(38, 110, 61)); g.drawLine(x.toInt, y.toInt, x.toInt, (y + 9).toInt)
      g.setColor(col)
      g.fillOval((x - 4).toInt, (y - 7).toInt, 7, 7)
      g.fillOval((x + 1).toInt, (y - 3).toInt, 7, 7)
      g.fillOval((x - 7).toInt, (y - 2).toInt, 7, 7)
      g.setColor(new Color(255, 224, 116)); g.fillOval((x - 2).toInt, (y - 2).toInt, 5, 5)
    }

    private def drawBase(g: Graphics2D): Unit = {
      val x = 150.0; val y = WORLD_H * 0.52
      g.setColor(new Color(255, 227, 128, 40)); g.fillOval((x - 65).toInt, (y - 57).toInt, 130, 114)
      g.setColor(new Color(186, 232, 153)); g.setStroke(new BasicStroke(4f)); g.drawOval((x - 49).toInt, (y - 42).toInt, 98, 84)
      drawLargeTree(g, x.toInt, y.toInt - 15, 0.45)
      g.setColor(Color.WHITE); g.setFont(new Font("SansSerif", Font.BOLD, 13)); g.drawString("HOME GROVE", (x - 47).toInt, (y + 68).toInt)
    }

    private def drawPortal(g: Graphics2D): Unit = {
      val x = WORLD_W - 190.0; val y = WORLD_H - 190.0
      val required = crystalsCollected >= goalForRealm
      val glow = if (required) new Color(247, 226, 129, 85) else new Color(110, 179, 190, 55)
      g.setColor(glow); g.fillOval((x - 78).toInt, (y - 91).toInt, 156, 182)
      g.setColor(if (required) new Color(255, 225, 136) else new Color(135, 206, 202))
      g.setStroke(new BasicStroke(8f)); g.drawOval((x - 53).toInt, (y - 70).toInt, 106, 140)
      g.setColor(new Color(22, 45, 62, 195)); g.fillOval((x - 43).toInt, (y - 60).toInt, 86, 120)
      g.setColor(new Color(175, 245, 213, 155)); g.drawArc((x - 35).toInt, (y - 50).toInt, 70, 100, 40, 260)
      g.setColor(Color.WHITE); g.setFont(new Font("SansSerif", Font.BOLD, 14)); g.drawString("ANCIENT TREE", (x - 55).toInt, (y + 103).toInt)
    }

    private def drawCrystal(g: Graphics2D, x: Double, y: Double, kind: Int): Unit = {
      val col = kind match {
        case 0 => new Color(140, 255, 186)
        case 1 => new Color(126, 226, 255)
        case 2 => new Color(233, 166, 255)
        case _ => new Color(255, 225, 133)
      }
      g.setColor(new Color(col.getRed, col.getGreen, col.getBlue, 55)); g.fillOval((x - 21).toInt, (y - 21).toInt, 42, 42)
      val p = new Polygon()
      p.addPoint(x.toInt, (y - 17).toInt); p.addPoint((x + 12).toInt, (y - 2).toInt); p.addPoint(x.toInt, (y + 17).toInt); p.addPoint((x - 12).toInt, (y - 2).toInt)
      g.setColor(col); g.fillPolygon(p)
      g.setColor(Color.WHITE); g.setStroke(new BasicStroke(2f)); g.drawLine(x.toInt, (y - 12).toInt, (x + 4).toInt, (y - 1).toInt)
    }

    private def drawWisp(g: Graphics2D, x: Double, y: Double, stunned: Boolean): Unit = {
      val col = if (stunned) new Color(170, 220, 255, 160) else new Color(177, 255, 210, 200)
      g.setColor(new Color(col.getRed, col.getGreen, col.getBlue, 55)); g.fillOval((x - 24).toInt, (y - 24).toInt, 48, 48)
      g.setColor(col); g.fillOval((x - 12).toInt, (y - 14).toInt, 24, 28)
      g.setColor(new Color(30, 66, 56)); g.fillOval((x - 6).toInt, (y - 5).toInt, 4, 5); g.fillOval((x + 3).toInt, (y - 5).toInt, 4, 5)
    }

    private def drawPlayer(g: Graphics2D, x: Double, y: Double): Unit = {
      g.setColor(new Color(0, 0, 0, 65)); g.fillOval((x - 18).toInt, (y + 15).toInt, 36, 13)
      drawAvatarSprite(g, x.toInt, y.toInt, avatar, 0.72)
    }

    private def drawAvatarSprite(g: Graphics2D, x: Int, y: Int, style: Int, scale: Double): Unit = {
      val coat = style match {
        case 0 => new Color(80, 184, 115)
        case 1 => new Color(175, 112, 218)
        case 2 => new Color(85, 149, 203)
        case _ => new Color(225, 166, 73)
      }
      val s = scale
      g.setColor(new Color(0, 0, 0, 70)); g.fillOval((x - 15 * s).toInt, (y + 17 * s).toInt, (30 * s).toInt, (9 * s).toInt)
      g.setColor(coat); g.fillRoundRect((x - 12 * s).toInt, (y - 1 * s).toInt, (24 * s).toInt, (27 * s).toInt, 10, 10)
      g.setColor(new Color(239, 202, 151)); g.fillOval((x - 9 * s).toInt, (y - 22 * s).toInt, (18 * s).toInt, (19 * s).toInt)
      g.setColor(new Color(47, 76, 54)); g.fillArc((x - 11 * s).toInt, (y - 26 * s).toInt, (22 * s).toInt, (16 * s).toInt, 0, 180)
      g.setColor(new Color(255, 243, 189)); g.fillOval((x + 4 * s).toInt, (y - 16 * s).toInt, (3 * s).toInt, (3 * s).toInt)
    }

    private def drawLargeTree(g: Graphics2D, x: Int, y: Int, scale: Double): Unit = {
      val s = scale
      g.setColor(new Color(82, 57, 35)); g.fillRoundRect((x - 12 * s).toInt, (y - 5 * s).toInt, (24 * s).toInt, (85 * s).toInt, 12, 12)
      g.setColor(new Color(39, 135, 82)); g.fillOval((x - 67 * s).toInt, (y - 91 * s).toInt, (134 * s).toInt, (112 * s).toInt)
      g.setColor(new Color(62, 164, 98)); g.fillOval((x - 47 * s).toInt, (y - 116 * s).toInt, (94 * s).toInt, (95 * s).toInt)
      g.setColor(new Color(189, 255, 174, 190)); g.fillOval((x - 10 * s).toInt, (y - 82 * s).toInt, (19 * s).toInt, (19 * s).toInt)
    }

    private def drawHud(g: Graphics2D): Unit = {
      g.setColor(new Color(4, 25, 31, 205)); g.fillRoundRect(18, 16, 420, 105, 18, 18)
      g.setColor(new Color(169, 231, 184)); g.setStroke(new BasicStroke(2f)); g.drawRoundRect(18, 16, 420, 105, 18, 18)
      g.setFont(new Font("SansSerif", Font.BOLD, 17)); g.setColor(Color.WHITE); g.drawString("" + playerName + "  ?  " + realmName, 34, 44)
      g.setFont(new Font("SansSerif", Font.PLAIN, 14)); g.drawString("Bloom Crystals: " + crystalsCollected + " / " + goalForRealm, 34, 70)
      g.drawString("Coins: " + coins + "     Health: " + health + "     Nature Energy: " + energy.toInt, 34, 96)
      g.setColor(new Color(4, 25, 31, 205)); g.fillRoundRect(VIEW_W - 193, 18, 172, 58, 15, 15)
      g.setColor(new Color(240, 222, 149)); g.drawRoundRect(VIEW_W - 193, 18, 172, 58, 15, 15)
      drawButton(g, VIEW_W - 178, 28, 142, 38, "PAUSE  (P)", false)
      if (messageTime > 0.0) {
        g.setColor(new Color(7, 34, 34, 205)); g.fillRoundRect(190, VIEW_H - 66, 720, 42, 18, 18)
        drawCentered(g, message, VIEW_W / 2, VIEW_H - 39, new Font("SansSerif", Font.BOLD, 14), new Color(255, 246, 205))
      }
      val barX = VIEW_W - 215; val barY = VIEW_H - 28
      g.setColor(new Color(0, 0, 0, 130)); g.fillRoundRect(barX, barY, 180, 12, 8, 8)
      g.setColor(new Color(100, 232, 155)); g.fillRoundRect(barX, barY, (180.0 * energy / 100.0).toInt, 12, 8, 8)
    }

    private def drawPause(g: Graphics2D): Unit = {
      g.setColor(new Color(0, 0, 0, 165)); g.fillRect(0, 0, VIEW_W, VIEW_H)
      drawCentered(g, "PAUSED", VIEW_W / 2, 265, new Font("Serif", Font.BOLD, 48), Color.WHITE)
      drawButton(g, 390, 330, 320, 60, "RESUME", true)
      drawButton(g, 390, 410, 320, 60, "RETURN TO MENU", false)
    }

    private def drawEnd(g: Graphics2D, success: Boolean): Unit = {
      drawBackdrop(g)
      if (success) {
        drawLargeTree(g, VIEW_W / 2, 385, 1.55)
        drawCentered(g, "ETERNAL BLOOM AWAKENED!", VIEW_W / 2, 145, new Font("Serif", Font.BOLD, 39), new Color(255, 239, 177))
        drawCentered(g, "You restored the three realms, " + playerName + "!", VIEW_W / 2, 205, new Font("SansSerif", Font.PLAIN, 18), Color.WHITE)
      } else {
        drawCentered(g, "THE WISPS OVERTOOK YOU", VIEW_W / 2, 190, new Font("Serif", Font.BOLD, 38), new Color(255, 205, 180))
        drawCentered(g, "Return to the Home Grove and try again.", VIEW_W / 2, 245, new Font("SansSerif", Font.PLAIN, 18), Color.WHITE)
      }
      drawButton(g, 390, 555, 320, 62, if (success) "PLAY AGAIN" else "RETRY", true)
    }

    private def drawButton(g: Graphics2D, x: Int, y: Int, w: Int, h: Int, label: String, bright: Boolean): Unit = {
      g.setColor(if (bright) new Color(54, 139, 88) else new Color(20, 76, 69))
      g.fillRoundRect(x, y, w, h, 22, 22)
      g.setColor(if (bright) new Color(255, 224, 143) else new Color(163, 229, 185))
      g.setStroke(new BasicStroke(2.2f)); g.drawRoundRect(x, y, w, h, 22, 22)
      drawCentered(g, label, x + w / 2, y + h / 2 + 6, new Font("SansSerif", Font.BOLD, 17), Color.WHITE)
    }

    private def drawCentered(g: Graphics2D, text: String, centerX: Int, baselineY: Int, font: Font, color: Color): Unit = {
      g.setFont(font); g.setColor(color)
      val fm = g.getFontMetrics(font)
      g.drawString(text, centerX - fm.stringWidth(text) / 2, baselineY)
    }

    private def inside(mx: Int, my: Int, x: Int, y: Int, w: Int, h: Int): Boolean = mx >= x && mx <= x + w && my >= y && my <= y + h

    override def mousePressed(e: MouseEvent): Unit = {
      requestFocusInWindow()
      val mx = e.getX
      val my = e.getY
      screen match {
        case MENU =>
          if (inside(mx, my, 390, 555, 320, 66)) screen = LOGIN
        case LOGIN =>
          if (inside(mx, my, 300, 280, 500, 76)) {
            if (!editingName && playerName == "Bloom Explorer") playerName = ""
            editingName = true
          }
          else if (inside(mx, my, 390, 425, 320, 62)) {
            if (playerName.trim.isEmpty) playerName = "Bloom Explorer"
            editingName = false
            screen = AVATAR
          } else editingName = false
        case AVATAR =>
          var i = 0
          while (i < 4) {
            if (inside(mx, my, 105 + i * 225, 245, 175, 220)) avatar = i
            i += 1
          }
          if (inside(mx, my, 390, 535, 320, 62)) beginGame()
        case PLAY =>
          if (inside(mx, my, VIEW_W - 193, 18, 172, 58)) screen = PAUSE
          else {
            targetX = mx + camX
            targetY = my + camY
          }
        case PAUSE =>
          if (inside(mx, my, 390, 330, 320, 60)) { screen = PLAY; requestFocusInWindow() }
          else if (inside(mx, my, 390, 410, 320, 60)) { screen = MENU; keys.clear() }
        case WIN => if (inside(mx, my, 390, 555, 320, 62)) beginGame()
        case LOST => if (inside(mx, my, 390, 555, 320, 62)) beginGame()
        case _ =>
      }
      repaint()
    }

    override def keyPressed(e: KeyEvent): Unit = {
      val code = e.getKeyCode
      if (screen == PLAY) {
        keys += code
        if (code == KeyEvent.VK_SPACE) startDash()
        else if (code == KeyEvent.VK_Q) useShield()
        else if (code == KeyEvent.VK_E) interact()
        else if (code == KeyEvent.VK_P || code == KeyEvent.VK_ESCAPE) screen = PAUSE
      } else if (screen == PAUSE && (code == KeyEvent.VK_P || code == KeyEvent.VK_ESCAPE)) {
        screen = PLAY
        requestFocusInWindow()
      } else if (screen == LOGIN && code == KeyEvent.VK_ENTER) {
        if (playerName.trim.isEmpty) playerName = "Bloom Explorer"
        editingName = false
        screen = AVATAR
      } else if (screen == AVATAR && code == KeyEvent.VK_ENTER) {
        beginGame()
      }
    }

    override def keyReleased(e: KeyEvent): Unit = keys -= e.getKeyCode

    override def keyTyped(e: KeyEvent): Unit = {
      if (screen == LOGIN && editingName) {
        val ch = e.getKeyChar
        if (ch == '\b') {
          if (playerName.length > 0) playerName = playerName.substring(0, playerName.length - 1)
        } else if (!Character.isISOControl(ch) && playerName.length < 18) {
          playerName += ch
        }
        repaint()
      }
    }

    override def mouseClicked(e: MouseEvent): Unit = {}
    override def mouseReleased(e: MouseEvent): Unit = {}
    override def mouseEntered(e: MouseEvent): Unit = {}
    override def mouseExited(e: MouseEvent): Unit = {}
  }
}

InfinityRealmsEternalBloom.main(Array[String]())