Code Sketch


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

object CrystalCoreRevolutionEvent3 {
  private val VIEW_W = 1100
  private val VIEW_H = 760

  def main(args: Array[String]): Unit = {
    SwingUtilities.invokeLater(new Runnable {
      override def run(): Unit = {
        val frame = new JFrame("INFINITY REALMS SEASON 3 - CRYSTAL CORE REVOLUTION")
        val panel = new CrystalGamePanel()
        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.stopGame()
          override def windowClosed(e: WindowEvent): Unit = panel.stopGame()
        })
        frame.setVisible(true)
        panel.requestFocusInWindow()
      }
    })
  }

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

  private class CrystalGamePanel extends JPanel with ActionListener with KeyListener with MouseListener {
    private val SCREEN_MENU = 0
    private val SCREEN_LOGIN = 1
    private val SCREEN_AVATAR = 2
    private val SCREEN_PLAY = 3
    private val SCREEN_POWERS = 4
    private val SCREEN_PAUSE = 5
    private val SCREEN_WIN = 6
    private val SCREEN_LOST = 7
    private val SCREEN_HELP = 8

    private val rng = new Random()
    private val keys = mutable.Set[Int]()
    private val crystals = ArrayBuffer[Crystal]()
    private val wisps = ArrayBuffer[Wisp]()
    private val decorations = ArrayBuffer[TreeDecoration]()
    private val timer = new Timer(16, this)

    private var screen = SCREEN_MENU
    private var playerName = "Crystal Explorer"
    private var editingName = false
    private var avatar = 0
    private var realm = 0
    private var playerX = 105.0
    private var playerY = 390.0
    private var targetX = -1.0
    private var targetY = -1.0
    private var health = 4
    private var coins = 0
    private var totalCrystals = 0
    private var realmCrystals = 0
    private var energy = 100.0
    private var dashTime = 0.0
    private var dashCooldown = 0.0
    private var shieldTime = 0.0
    private var pulseTime = 0.0
    private var damageCooldown = 0.0
    private var message = "Collect six crystals in each realm!"
    private var messageTime = 5.0
    private var elapsed = 0.0
    private var coreAwakened = false
    private var lastFrameNanos = System.nanoTime()
    private var pulseCooldown = 0.0
    private var healCooldown = 0.0

    setPreferredSize(new Dimension(VIEW_W, VIEW_H))
    setFocusable(true)
    setDoubleBuffered(true)
    addKeyListener(this)
    addMouseListener(this)
    setToolTipText("WASD: move | SPACE: dash | Q: shield | F: Nova Pulse | H: Heal | E: interact | C: powers")

    timer.start()

    def stopGame(): Unit = {
      if (timer.isRunning) timer.stop()
    }

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

    private def startNewGame(): Unit = {
      realm = 0
      playerX = 105.0
      playerY = 390.0
      targetX = -1.0
      targetY = -1.0
      health = 4
      coins = 0
      totalCrystals = 0
      realmCrystals = 0
      energy = 100.0
      dashTime = 0.0
      dashCooldown = 0.0
      shieldTime = 0.0
      pulseTime = 0.0
      damageCooldown = 0.0
      pulseCooldown = 0.0
      healCooldown = 0.0
      coreAwakened = false
      buildRealm()
      screen = SCREEN_PLAY
      showMessage("Realm 1: enter the Prism Grove and collect 6 crystals!", 5.0)
      requestFocusInWindow()
    }

    private def buildRealm(): Unit = {
      crystals.clear()
      wisps.clear()
      decorations.clear()
      var i = 0
      while (i < 6) {
        val x = 220.0 + rng.nextInt(790).toDouble
        val y = 175.0 + rng.nextInt(475).toDouble
        val kind = (i + realm) % 4
        crystals += Crystal(x, y, kind, false)
        i += 1
      }
      val wispCount = 2 + realm
      i = 0
      while (i < wispCount) {
        val x = 380.0 + rng.nextInt(620).toDouble
        val y = 170.0 + rng.nextInt(470).toDouble
        wisps += Wisp(x, y, 0.0, rng.nextDouble() * 6.0)
        i += 1
      }
      i = 0
      while (i < 34) {
        decorations += TreeDecoration(35 + rng.nextInt(1020), 145 + rng.nextInt(540), 22 + rng.nextInt(35), rng.nextInt(3))
        i += 1
      }
      realmCrystals = 0
    }

    private def playerSpeed: Double = {
      val bonus = math.min(1.2, totalCrystals.toDouble * 0.035)
      2.75 + bonus + (if (dashTime > 0.0) 4.0 else 0.0)
    }

    private def distance(ax: Double, ay: Double, bx: Double, by: Double): Double = {
      val dx = ax - bx
      val dy = ay - by
      math.sqrt(dx * dx + dy * dy)
    }

    private def collectNearbyCrystal(): Unit = {
      var found = false
      var i = 0
      while (i < crystals.length && !found) {
        val c = crystals(i)
        if (!c.collected && distance(playerX, playerY, c.x, c.y) < 38.0) {
          c.collected = true
          realmCrystals += 1
          totalCrystals += 1
          coins += 20 + realm * 10
          energy = math.min(100.0, energy + 14.0)
          showMessage("Crystal collected! +" + (20 + realm * 10) + " coins", 2.0)
          found = true
          if (realmCrystals == 6) showMessage("Realm Gate activated! Go to the glowing gate and press E.", 4.0)
        }
        i += 1
      }
    }

    private def interact(): Unit = {
      collectNearbyCrystal()
      if (realmCrystals >= 6 && distance(playerX, playerY, 1010.0, 390.0) < 100.0) {
        if (realm < 2) {
          realm += 1
          playerX = 95.0
          playerY = 390.0
          targetX = -1.0
          targetY = -1.0
          buildRealm()
          energy = math.min(100.0, energy + 25.0)
          showMessage("Welcome to " + realmName(realm) + "! Find six new crystals.", 4.0)
        } else {
          coreAwakened = true
          screen = SCREEN_WIN
          showMessage("The Crystal Core is awake!", 8.0)
        }
      } else if (realmCrystals < 6) {
        showMessage("You need " + (6 - realmCrystals) + " more crystals to open this gate.", 2.0)
      } else {
        showMessage("Move closer to the glowing Realm Gate and press E.", 2.0)
      }
    }

    private def startDash(): Unit = {
      if (screen == SCREEN_PLAY && dashCooldown <= 0.0 && energy >= 12.0) {
        dashTime = 0.22
        dashCooldown = 1.0
        energy -= 12.0
      }
    }

    private def useShield(): Unit = {
      if (totalCrystals >= 3 && energy >= 25.0 && shieldTime <= 0.0) {
        energy -= 25.0
        shieldTime = 3.5
        showMessage("CRYSTAL GUARD activated!", 1.5)
      } else if (totalCrystals < 3) {
        showMessage("Crystal Guard unlocks at 3 collected crystals.", 2.0)
      } else {
        showMessage("Not enough energy or shield is already active.", 1.5)
      }
    }

    private def useNovaPulse(): Unit = {
      if (totalCrystals >= 6 && energy >= 35.0 && pulseCooldown <= 0.0) {
        energy -= 35.0
        pulseCooldown = 5.0
        pulseTime = 0.55
        var i = 0
        while (i < wisps.length) {
          val w = wisps(i)
          if (distance(playerX, playerY, w.x, w.y) < 205.0) w.stunned = 3.5
          i += 1
        }
        showMessage("NOVA PULSE! Nearby Wisps are stunned.", 2.0)
      } else if (totalCrystals < 6) {
        showMessage("Nova Pulse unlocks at 6 collected crystals.", 2.0)
      } else {
        showMessage("Nova Pulse is recharging or energy is low.", 1.5)
      }
    }

    private def useHeal(): Unit = {
      if (totalCrystals >= 9 && health < 4 && energy >= 40.0 && healCooldown <= 0.0) {
        energy -= 40.0
        health += 1
        healCooldown = 7.0
        showMessage("Verdant Heal restored one heart.", 2.0)
      } else if (totalCrystals < 9) {
        showMessage("Verdant Heal unlocks at 9 collected crystals.", 2.0)
      } else if (health >= 4) {
        showMessage("Your health is already full.", 1.5)
      } else {
        showMessage("Verdant Heal is recharging or energy is low.", 1.5)
      }
    }

    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 (pulseTime > 0.0) pulseTime -= dt
      if (damageCooldown > 0.0) damageCooldown -= dt
      if (pulseCooldown > 0.0) pulseCooldown -= dt
      if (healCooldown > 0.0) healCooldown -= dt
      energy = math.min(100.0, energy + 2.0 * dt)

      var dx = 0.0
      var dy = 0.0
      if (keys.contains(KeyEvent.VK_A) || keys.contains(KeyEvent.VK_LEFT)) dx -= 1.0
      if (keys.contains(KeyEvent.VK_D) || keys.contains(KeyEvent.VK_RIGHT)) dx += 1.0
      if (keys.contains(KeyEvent.VK_W) || keys.contains(KeyEvent.VK_UP)) dy -= 1.0
      if (keys.contains(KeyEvent.VK_S) || keys.contains(KeyEvent.VK_DOWN)) dy += 1.0

      if (dx == 0.0 && dy == 0.0 && targetX >= 0.0) {
        val tx = targetX - playerX
        val ty = targetY - playerY
        val d = math.sqrt(tx * tx + ty * ty)
        if (d > 8.0) {
          dx = tx / d
          dy = ty / d
        } else {
          targetX = -1.0
          targetY = -1.0
        }
      }
      if (dx != 0.0 || dy != 0.0) {
        val length = math.sqrt(dx * dx + dy * dy)
        playerX += (dx / length) * playerSpeed
        playerY += (dy / length) * playerSpeed
        targetX = -1.0
        targetY = -1.0
      }
      playerX = math.max(42.0, math.min(1058.0, playerX))
      playerY = math.max(142.0, math.min(692.0, playerY))

      collectNearbyCrystal()

      var i = 0
      while (i < wisps.length) {
        val w = wisps(i)
        if (w.stunned > 0.0) {
          w.stunned -= dt
        } else {
          val d = distance(playerX, playerY, w.x, w.y)
          if (d < 320.0 && d > 1.0) {
            val speed = 0.72 + realm.toDouble * 0.16
            w.x += ((playerX - w.x) / d) * speed
            w.y += ((playerY - w.y) / d) * speed
          }
          w.phase += dt * 2.0
          if (d < 31.0 && damageCooldown <= 0.0 && shieldTime <= 0.0) {
            health -= 1
            damageCooldown = 1.3
            playerX = math.max(42.0, playerX - 28.0)
            showMessage("A Corrupted Wisp touched you! Use Dash or Crystal Guard.", 2.0)
            if (health <= 0) screen = SCREEN_LOST
          }
        }
        i += 1
      }
    }

    override def actionPerformed(e: ActionEvent): Unit = {
      val now = System.nanoTime()
      var dt = (now - lastFrameNanos).toDouble / 1000000000.0
      lastFrameNanos = now
      if (dt > 0.05) dt = 0.05
      if (screen == SCREEN_PLAY) updateGame(dt)
      repaint()
    }

    private def realmName(index: Int): String = {
      if (index == 0) "Prism Grove"
      else if (index == 1) "Crystal Tide Cavern"
      else "Aurora Shard Summit"
    }

    private def realmColor(index: Int): Color = {
      if (index == 0) new Color(31, 105, 74)
      else if (index == 1) new Color(31, 83, 132)
      else new Color(91, 57, 137)
    }

    private def drawCentered(g: Graphics2D, text: String, x: Int, y: Int): Unit = {
      val fm = g.getFontMetrics
      g.drawString(text, x - fm.stringWidth(text) / 2, y)
    }

    private def drawButton(g: Graphics2D, x: Int, y: Int, w: Int, h: Int, label: String, accent: Color): Unit = {
      g.setColor(new Color(7, 18, 31, 205))
      g.fillRoundRect(x + 3, y + 5, w, h, 18, 18)
      g.setPaint(new GradientPaint(x.toFloat, y.toFloat, accent, (x + w).toFloat, (y + h).toFloat, new Color(25, 46, 65)))
      g.fillRoundRect(x, y, w, h, 18, 18)
      g.setColor(new Color(220, 250, 255, 210))
      g.setStroke(new BasicStroke(2.0f))
      g.drawRoundRect(x, y, w, h, 18, 18)
      g.setFont(new Font("Dialog", Font.BOLD, 18))
      g.setColor(Color.WHITE)
      drawCentered(g, label, x + w / 2, y + h / 2 + 6)
    }

    private def drawBackground(g: Graphics2D): Unit = {
      val top = if (realm == 0) new Color(12, 39, 35) else if (realm == 1) new Color(10, 27, 58) else new Color(30, 18, 57)
      val bottom = if (realm == 0) new Color(28, 86, 60) else if (realm == 1) new Color(30, 74, 112) else new Color(79, 43, 112)
      g.setPaint(new GradientPaint(0, 105, top, 0, VIEW_H, bottom))
      g.fillRect(0, 100, VIEW_W, VIEW_H - 100)
      var i = 0
      while (i < 80) {
        val x = (i * 137 + 29) % VIEW_W
        val y = 115 + ((i * 83 + 17) % 610)
        val pulse = (math.sin(elapsed * 1.8 + i.toDouble) + 1.0) * 0.5
        val alpha = (65 + pulse * 150).toInt
        g.setColor(new Color(185, 245, 255, alpha))
        g.fillOval(x, y, 2 + (i % 3), 2 + (i % 3))
        i += 1
      }
      g.setColor(new Color(8, 20, 30, 90))
      g.fillOval(-90, 560, 430, 190)
      g.fillOval(310, 570, 470, 180)
      g.fillOval(760, 565, 430, 190)
      for (d <- decorations) {
        val trunk = if (realm == 0) new Color(46, 76, 57, 190) else if (realm == 1) new Color(63, 83, 114, 180) else new Color(73, 57, 102, 180)
        g.setColor(trunk)
        g.fillRoundRect(d.x, d.y, 8, d.size, 6, 6)
        val crown = if (realm == 0) new Color(44 + d.kind * 12, 125 + d.kind * 8, 89 + d.kind * 10, 155) else if (realm == 1) new Color(77, 160 + d.kind * 10, 190 + d.kind * 8, 150) else new Color(131 + d.kind * 12, 83, 173 + d.kind * 10, 155)
        g.setColor(crown)
        g.fillOval(d.x - d.size / 2, d.y - d.size, d.size + 8, d.size + 12)
      }
    }

    private def drawHud(g: Graphics2D): Unit = {
      g.setColor(new Color(5, 14, 28, 235))
      g.fillRect(0, 0, VIEW_W, 105)
      g.setColor(new Color(119, 235, 221))
      g.setFont(new Font("Dialog", Font.BOLD, 24))
      g.drawString("CRYSTAL CORE REVOLUTION", 24, 34)
      g.setFont(new Font("Dialog", Font.PLAIN, 14))
      g.setColor(new Color(213, 229, 245))
      g.drawString("Explorer: " + playerName + "   |   Realm: " + realmName(realm), 26, 59)
      g.drawString("Crystals: " + totalCrystals + "   Realm: " + realmCrystals + "/6   |   Coins: " + coins, 26, 82)
      g.setColor(new Color(25, 42, 60))
      g.fillRoundRect(705, 19, 190, 17, 9, 9)
      g.setColor(new Color(64, 218, 192))
      g.fillRoundRect(705, 19, (energy * 1.9).toInt, 17, 9, 9)
      g.setColor(Color.WHITE)
      g.setFont(new Font("Dialog", Font.BOLD, 12))
      g.drawString("ENERGY " + energy.toInt + "%", 710, 50)
      g.setColor(new Color(45, 28, 47))
      g.fillRoundRect(920, 19, 125, 17, 9, 9)
      g.setColor(new Color(244, 110, 151))
      g.fillRoundRect(920, 19, (health * 31).toInt, 17, 9, 9)
      g.setColor(Color.WHITE)
      g.drawString("HEARTS " + health + "/4", 925, 50)
      g.setColor(new Color(230, 238, 250))
      g.drawString("C: POWERS", 930, 80)
    }

    private def drawCrystal(g: Graphics2D, c: Crystal): Unit = {
      if (!c.collected) {
        val color = if (c.kind == 0) new Color(78, 255, 207) else if (c.kind == 1) new Color(112, 190, 255) else if (c.kind == 2) new Color(212, 137, 255) else new Color(255, 221, 116)
        val glow = (math.sin(elapsed * 4.0 + c.x * 0.01) + 1.0) * 0.5
        g.setColor(new Color(color.getRed, color.getGreen, color.getBlue, 45 + (glow * 55).toInt))
        g.fillOval(c.x.toInt - 22, c.y.toInt - 22, 44, 44)
        g.setColor(color)
        val px = c.x.toInt
        val py = c.y.toInt
        val poly = new java.awt.Polygon()
        poly.addPoint(px, py - 17)
        poly.addPoint(px + 12, py - 3)
        poly.addPoint(px + 7, py + 14)
        poly.addPoint(px - 7, py + 14)
        poly.addPoint(px - 12, py - 3)
        g.fillPolygon(poly)
        g.setColor(new Color(255, 255, 255, 190))
        g.drawLine(px - 3, py - 8, px + 1, py + 7)
      }
    }

    private def drawPlayer(g: Graphics2D): Unit = {
      if (shieldTime > 0.0) {
        g.setColor(new Color(113, 226, 255, 65))
        g.fillOval(playerX.toInt - 27, playerY.toInt - 27, 54, 54)
        g.setColor(new Color(142, 239, 255, 220))
        g.setStroke(new BasicStroke(2.0f))
        g.drawOval(playerX.toInt - 27, playerY.toInt - 27, 54, 54)
      }
      val colors = Array(new Color(255, 194, 87), new Color(105, 225, 196), new Color(173, 143, 255), new Color(247, 133, 183))
      g.setColor(new Color(0, 0, 0, 90))
      g.fillOval(playerX.toInt - 16, playerY.toInt + 11, 32, 9)
      g.setColor(colors(avatar))
      g.fillOval(playerX.toInt - 14, playerY.toInt - 19, 28, 30)
      g.setColor(new Color(248, 251, 255))
      g.fillOval(playerX.toInt - 7, playerY.toInt - 11, 5, 5)
      g.fillOval(playerX.toInt + 3, playerY.toInt - 11, 5, 5)
      g.setColor(new Color(17, 31, 45))
      g.fillOval(playerX.toInt - 5, playerY.toInt - 10, 2, 3)
      g.fillOval(playerX.toInt + 5, playerY.toInt - 10, 2, 3)
      g.setFont(new Font("Dialog", Font.BOLD, 11))
      g.setColor(Color.WHITE)
      drawCentered(g, playerName, playerX.toInt, playerY.toInt - 26)
    }

    private def drawWisp(g: Graphics2D, w: Wisp): Unit = {
      val x = w.x.toInt
      val y = w.y.toInt
      if (w.stunned <= 0.0) {
        g.setColor(new Color(197, 123, 255, 45))
        g.fillOval(x - 22, y - 25, 44, 44)
        g.setColor(new Color(189, 157, 255, 215))
      } else {
        g.setColor(new Color(125, 237, 255, 220))
      }
      g.fillOval(x - 13, y - 16, 26, 28)
      g.setColor(new Color(24, 22, 51))
      g.fillOval(x - 7, y - 7, 4, 5)
      g.fillOval(x + 3, y - 7, 4, 5)
      g.setColor(new Color(236, 225, 255, 170))
      g.drawArc(x - 8, y - 2, 16, 12, 190, 160)
    }

    private def drawGate(g: Graphics2D): Unit = {
      val gateColor = if (realmCrystals >= 6) new Color(104, 255, 205) else new Color(118, 137, 160)
      val glow = (math.sin(elapsed * 2.5) + 1.0) * 0.5
      g.setColor(new Color(gateColor.getRed, gateColor.getGreen, gateColor.getBlue, 35 + (glow * 55).toInt))
      g.fillOval(969, 338, 82, 105)
      g.setColor(gateColor)
      g.setStroke(new BasicStroke(5.0f))
      g.drawOval(982, 345, 55, 87)
      g.drawArc(990, 353, 39, 69, 0, 180)
      g.setFont(new Font("Dialog", Font.BOLD, 12))
      g.setColor(Color.WHITE)
      drawCentered(g, if (realm == 2) "CORE" else "GATE", 1010, 462)
    }

    private def drawMessage(g: Graphics2D): Unit = {
      if (messageTime > 0.0) {
        g.setColor(new Color(5, 12, 25, 205))
        g.fillRoundRect(230, 112, 640, 42, 15, 15)
        g.setColor(new Color(205, 250, 243))
        g.setFont(new Font("Dialog", Font.BOLD, 14))
        drawCentered(g, message, 550, 138)
      }
    }

    private def drawMenu(g: Graphics2D): Unit = {
      g.setPaint(new GradientPaint(0, 0, new Color(7, 23, 43), 0, VIEW_H, new Color(15, 73, 76)))
      g.fillRect(0, 0, VIEW_W, VIEW_H)
      var i = 0
      while (i < 85) {
        val x = (i * 113 + 31) % VIEW_W
        val y = (i * 71 + 19) % VIEW_H
        g.setColor(new Color(116, 247, 225, 90 + (i % 5) * 25))
        g.fillOval(x, y, 2 + (i % 4), 2 + (i % 4))
        i += 1
      }
      g.setColor(new Color(83, 238, 213, 50))
      g.fillOval(375, 100, 350, 350)
      g.setColor(new Color(12, 45, 60, 220))
      g.fillOval(410, 135, 280, 280)
      g.setFont(new Font("Dialog", Font.BOLD, 43))
      g.setColor(new Color(112, 255, 214))
      drawCentered(g, "CRYSTAL CORE", 550, 260)
      g.setColor(Color.WHITE)
      drawCentered(g, "REVOLUTION", 550, 310)
      g.setFont(new Font("Dialog", Font.PLAIN, 19))
      g.setColor(new Color(207, 237, 244))
      drawCentered(g, "INFINITY REALMS SEASON 3  |  CRYSTAL POWERS", 550, 365)
      drawButton(g, 405, 430, 290, 58, "START ADVENTURE", new Color(39, 173, 145))
      drawButton(g, 405, 505, 290, 48, "HOW TO PLAY", new Color(76, 109, 184))
      g.setFont(new Font("Dialog", Font.PLAIN, 13))
      g.setColor(new Color(177, 212, 220))
      drawCentered(g, "Explore three realms. Collect crystals. Awaken the Core.", 550, 600)
    }

    private def drawLogin(g: Graphics2D): Unit = {
      drawMenu(g)
      g.setColor(new Color(5, 16, 29, 235))
      g.fillRoundRect(330, 220, 440, 330, 28, 28)
      g.setColor(new Color(119, 246, 220))
      g.setFont(new Font("Dialog", Font.BOLD, 27))
      drawCentered(g, "EXPLORER LOGIN", 550, 275)
      g.setFont(new Font("Dialog", Font.PLAIN, 16))
      g.setColor(Color.WHITE)
      drawCentered(g, "Type your name (max 18 characters):", 550, 330)
      g.setColor(new Color(21, 47, 64))
      g.fillRoundRect(390, 355, 320, 48, 12, 12)
      g.setColor(new Color(132, 242, 219))
      g.drawRoundRect(390, 355, 320, 48, 12, 12)
      g.setFont(new Font("Dialog", Font.BOLD, 21))
      drawCentered(g, playerName + (if (editingName) "|" else ""), 550, 385)
      drawButton(g, 425, 435, 250, 52, "CONTINUE", new Color(39, 173, 145))
      g.setFont(new Font("Dialog", Font.PLAIN, 13))
      g.setColor(new Color(200, 221, 232))
      drawCentered(g, "Click the name box, type your name, then press ENTER.", 550, 515)
    }

    private def drawAvatar(g: Graphics2D): Unit = {
      drawBackground(g)
      g.setColor(new Color(5, 14, 28, 225))
      g.fillRoundRect(160, 150, 780, 470, 28, 28)
      g.setColor(new Color(125, 247, 220))
      g.setFont(new Font("Dialog", Font.BOLD, 30))
      drawCentered(g, "CHOOSE YOUR CRYSTAL GUARDIAN", 550, 205)
      val names = Array("PRISM RANGER", "TIDE MAGE", "AURORA KNIGHT", "NOVA KEEPER")
      val colors = Array(new Color(255, 194, 87), new Color(105, 225, 196), new Color(173, 143, 255), new Color(247, 133, 183))
      var i = 0
      while (i < 4) {
        val x = 205 + i * 176
        g.setColor(if (avatar == i) new Color(47, 109, 105) else new Color(24, 42, 62))
        g.fillRoundRect(x, 260, 150, 205, 18, 18)
        g.setColor(if (avatar == i) new Color(141, 255, 225) else new Color(109, 140, 166))
        g.drawRoundRect(x, 260, 150, 205, 18, 18)
        g.setColor(colors(i))
        g.fillOval(x + 47, 290, 56, 56)
        g.setColor(Color.WHITE)
        g.setFont(new Font("Dialog", Font.BOLD, 12))
        drawCentered(g, names(i), x + 75, 390)
        g.setFont(new Font("Dialog", Font.PLAIN, 11))
        drawCentered(g, "Click to select", x + 75, 420)
        if (avatar == i) drawCentered(g, "SELECTED", x + 75, 447)
        i += 1
      }
      drawButton(g, 400, 525, 300, 55, "ENTER THE CRYSTAL REALMS", new Color(45, 174, 148))
    }

    private def drawPlay(g: Graphics2D): Unit = {
      drawBackground(g)
      drawHud(g)
      drawGate(g)
      var i = 0
      while (i < crystals.length) {
        drawCrystal(g, crystals(i))
        i += 1
      }
      i = 0
      while (i < wisps.length) {
        drawWisp(g, wisps(i))
        i += 1
      }
      if (pulseTime > 0.0) {
        val size = (420.0 * (1.0 - pulseTime / 0.55)).toInt
        g.setColor(new Color(103, 237, 255, 95))
        g.setStroke(new BasicStroke(5.0f))
        g.drawOval(playerX.toInt - size / 2, playerY.toInt - size / 2, size, size)
      }
      drawPlayer(g)
      g.setColor(new Color(5, 14, 28, 205))
      g.fillRoundRect(18, 665, 1064, 48, 13, 13)
      g.setColor(new Color(215, 239, 246))
      g.setFont(new Font("Dialog", Font.PLAIN, 13))
      g.drawString("WASD / Arrows: Move   SPACE: Dash   Q: Guard (3 crystals)   F: Nova Pulse (6)   H: Heal (9)   E: Gate   C: Power Book   P: Pause", 32, 694)
      drawMessage(g)
    }

    private def drawPowers(g: Graphics2D): Unit = {
      drawBackground(g)
      g.setColor(new Color(5, 14, 28, 235))
      g.fillRoundRect(180, 125, 740, 500, 28, 28)
      g.setColor(new Color(132, 248, 224))
      g.setFont(new Font("Dialog", Font.BOLD, 31))
      drawCentered(g, "CRYSTAL POWER ARCHIVE", 550, 185)
      val labels = Array("PRISM DASH", "CRYSTAL GUARD", "NOVA PULSE", "VERDANT HEAL")
      val desc = Array("SPACE  |  Quick energy burst", "Q  |  Unlock at 3 crystals", "F  |  Unlock at 6 crystals", "H  |  Unlock at 9 crystals")
      val unlocked = Array(true, totalCrystals >= 3, totalCrystals >= 6, totalCrystals >= 9)
      var i = 0
      while (i < 4) {
        val x = 235 + (i % 2) * 315
        val y = 230 + (i / 2) * 150
        g.setColor(if (unlocked(i)) new Color(24, 78, 77) else new Color(35, 39, 55))
        g.fillRoundRect(x, y, 270, 112, 18, 18)
        g.setColor(if (unlocked(i)) new Color(112, 248, 218) else new Color(111, 122, 144))
        g.drawRoundRect(x, y, 270, 112, 18, 18)
        g.setFont(new Font("Dialog", Font.BOLD, 19))
        g.setColor(Color.WHITE)
        drawCentered(g, labels(i), x + 135, y + 38)
        g.setFont(new Font("Dialog", Font.PLAIN, 13))
        g.setColor(new Color(210, 226, 239))
        drawCentered(g, desc(i), x + 135, y + 68)
        drawCentered(g, if (unlocked(i)) "UNLOCKED" else "LOCKED", x + 135, y + 93)
        i += 1
      }
      drawButton(g, 425, 565, 250, 45, "BACK TO ADVENTURE", new Color(55, 125, 157))
    }

    private def drawPause(g: Graphics2D): Unit = {
      drawPlay(g)
      g.setColor(new Color(3, 10, 21, 205))
      g.fillRect(0, 100, VIEW_W, VIEW_H - 100)
      g.setColor(new Color(118, 246, 220))
      g.setFont(new Font("Dialog", Font.BOLD, 38))
      drawCentered(g, "ADVENTURE PAUSED", 550, 310)
      drawButton(g, 415, 365, 270, 52, "RESUME", new Color(38, 160, 133))
      drawButton(g, 415, 435, 270, 52, "RESTART REALM", new Color(80, 105, 168))
      drawButton(g, 415, 505, 270, 52, "MAIN MENU", new Color(125, 75, 123))
    }

    private def drawWin(g: Graphics2D): Unit = {
      drawMenu(g)
      g.setColor(new Color(4, 17, 29, 235))
      g.fillRoundRect(250, 190, 600, 390, 28, 28)
      g.setFont(new Font("Dialog", Font.BOLD, 36))
      g.setColor(new Color(135, 255, 220))
      drawCentered(g, "THE CORE IS AWAKE!", 550, 275)
      g.setFont(new Font("Dialog", Font.PLAIN, 19))
      g.setColor(Color.WHITE)
      drawCentered(g, "You restored balance to all three Crystal Realms.", 550, 335)
      drawCentered(g, "Crystals: " + totalCrystals + "     Coins: " + coins, 550, 375)
      drawButton(g, 400, 430, 300, 55, "PLAY AGAIN", new Color(42, 173, 142))
    }

    private def drawLost(g: Graphics2D): Unit = {
      drawMenu(g)
      g.setColor(new Color(5, 12, 26, 238))
      g.fillRoundRect(280, 215, 540, 330, 25, 25)
      g.setColor(new Color(255, 160, 175))
      g.setFont(new Font("Dialog", Font.BOLD, 36))
      drawCentered(g, "THE WISPS OVERPOWERED YOU", 550, 300)
      g.setColor(Color.WHITE)
      g.setFont(new Font("Dialog", Font.PLAIN, 17))
      drawCentered(g, "Collect crystals to unlock stronger powers and try again.", 550, 355)
      drawButton(g, 415, 420, 270, 55, "RETRY ADVENTURE", new Color(47, 151, 139))
    }

    private def drawHelp(g: Graphics2D): Unit = {
      drawMenu(g)
      g.setColor(new Color(4, 17, 31, 238))
      g.fillRoundRect(220, 155, 660, 460, 24, 24)
      g.setColor(new Color(125, 246, 220))
      g.setFont(new Font("Dialog", Font.BOLD, 30))
      drawCentered(g, "HOW TO PLAY", 550, 215)
      val lines = Array(
        "Explore three magical realms and collect six crystals in each.",
        "Walk with WASD or arrow keys. Click the map to move there.",
        "SPACE: Prism Dash   |   Q: Crystal Guard (3 crystals)",
        "F: Nova Pulse (6 crystals)   |   H: Verdant Heal (9 crystals)",
        "Wisps follow you. Use your powers and avoid touching them.",
        "When you have six crystals, reach the glowing gate and press E.",
        "Complete all three realms to awaken the Crystal Core."
      )
      g.setFont(new Font("Dialog", Font.PLAIN, 16))
      g.setColor(Color.WHITE)
      var i = 0
      while (i < lines.length) {
        drawCentered(g, lines(i), 550, 280 + i * 42)
        i += 1
      }
      drawButton(g, 425, 565, 250, 42, "BACK", new Color(58, 120, 150))
    }

    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)
      if (screen == SCREEN_MENU) drawMenu(g)
      else if (screen == SCREEN_LOGIN) drawLogin(g)
      else if (screen == SCREEN_AVATAR) drawAvatar(g)
      else if (screen == SCREEN_PLAY) drawPlay(g)
      else if (screen == SCREEN_POWERS) drawPowers(g)
      else if (screen == SCREEN_PAUSE) drawPause(g)
      else if (screen == SCREEN_WIN) drawWin(g)
      else if (screen == SCREEN_LOST) drawLost(g)
      else if (screen == SCREEN_HELP) drawHelp(g)
      g.dispose()
    }

    private def inside(x: Int, y: Int, bx: Int, by: Int, bw: Int, bh: Int): Boolean = {
      x >= bx && x <= bx + bw && y >= by && y <= by + bh
    }

    override def mousePressed(e: MouseEvent): Unit = {
      val x = e.getX
      val y = e.getY
      if (screen == SCREEN_MENU) {
        if (inside(x, y, 405, 430, 290, 58)) screen = SCREEN_LOGIN
        else if (inside(x, y, 405, 505, 290, 48)) screen = SCREEN_HELP
      } else if (screen == SCREEN_LOGIN) {
        if (inside(x, y, 390, 355, 320, 48)) editingName = true
        else if (inside(x, y, 425, 435, 250, 52)) {
          if (playerName.trim.length == 0) playerName = "Crystal Explorer"
          editingName = false
          screen = SCREEN_AVATAR
        }
      } else if (screen == SCREEN_AVATAR) {
        var i = 0
        while (i < 4) {
          val bx = 205 + i * 176
          if (inside(x, y, bx, 260, 150, 205)) avatar = i
          i += 1
        }
        if (inside(x, y, 400, 525, 300, 55)) startNewGame()
      } else if (screen == SCREEN_PLAY) {
        if (x > 900 && y < 95) screen = SCREEN_POWERS
        else if (y > 110 && y < 660) {
          targetX = x.toDouble
          targetY = y.toDouble
        }
      } else if (screen == SCREEN_POWERS) {
        if (inside(x, y, 425, 565, 250, 45)) screen = SCREEN_PLAY
      } else if (screen == SCREEN_PAUSE) {
        if (inside(x, y, 415, 365, 270, 52)) screen = SCREEN_PLAY
        else if (inside(x, y, 415, 435, 270, 52)) startNewGame()
        else if (inside(x, y, 415, 505, 270, 52)) screen = SCREEN_MENU
      } else if (screen == SCREEN_WIN) {
        if (inside(x, y, 400, 430, 300, 55)) startNewGame()
      } else if (screen == SCREEN_LOST) {
        if (inside(x, y, 415, 420, 270, 55)) startNewGame()
      } else if (screen == SCREEN_HELP) {
        if (inside(x, y, 425, 565, 250, 42)) screen = SCREEN_MENU
      }
      requestFocusInWindow()
      repaint()
    }

    override def keyPressed(e: KeyEvent): Unit = {
      val code = e.getKeyCode
      val alreadyDown = keys.contains(code)
      keys += code
      if (!alreadyDown) {
        if (screen == SCREEN_PLAY) {
          if (code == KeyEvent.VK_SPACE) startDash()
          else if (code == KeyEvent.VK_Q) useShield()
          else if (code == KeyEvent.VK_F) useNovaPulse()
          else if (code == KeyEvent.VK_H) useHeal()
          else if (code == KeyEvent.VK_E) interact()
          else if (code == KeyEvent.VK_C) screen = SCREEN_POWERS
          else if (code == KeyEvent.VK_P || code == KeyEvent.VK_ESCAPE) screen = SCREEN_PAUSE
        } else if (screen == SCREEN_PAUSE && (code == KeyEvent.VK_P || code == KeyEvent.VK_ESCAPE)) {
          screen = SCREEN_PLAY
        } else if (screen == SCREEN_POWERS && code == KeyEvent.VK_ESCAPE) {
          screen = SCREEN_PLAY
        } else if (screen == SCREEN_LOGIN && code == KeyEvent.VK_ENTER) {
          if (playerName.trim.length == 0) playerName = "Crystal Explorer"
          editingName = false
          screen = SCREEN_AVATAR
        } else if (screen == SCREEN_AVATAR && code == KeyEvent.VK_ENTER) {
          startNewGame()
        }
      }
      repaint()
    }

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

    override def keyTyped(e: KeyEvent): Unit = {
      if (screen == 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 = {}
  }
}

CrystalCoreRevolutionEvent3.main(Array[String]())