Code Sketch


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

/**
 * BIXUU HILL CLIMB RIDER
 * Original side-scrolling hill-driving game made with Scala + Java Swing.
 * No external images, audio, libraries or assets are required.
 *
 * Controls:
 *   RIGHT / D  = accelerate
 *   LEFT / A   = brake / reverse
 *   SPACE      = nitro boost
 *   SHIFT      = drift / smoke trail
 *   UP / W     = lean back in the air
 *   DOWN / S   = lean forward in the air
 *   R          = restart run
 *   ESC        = pause / return to menu
 */
object BixuuHillClimbRider {
  def main(args: Array[String]): Unit = {
    SwingUtilities.invokeLater(new Runnable {
      override def run(): Unit = {
        val frame = new JFrame("BIXUU HILL CLIMB RIDER | Drift ? Boost ? Stunts")
        val panel = new HillGamePanel
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
        frame.setContentPane(panel)
        frame.setMinimumSize(new Dimension(900, 600))
        frame.setSize(1100, 700)
        frame.setLocationRelativeTo(null)
        frame.setVisible(true)
        panel.requestFocusInWindow()
        panel.startGameLoop()
      }
    })
  }

  private class HillGamePanel extends JPanel with KeyListener with MouseListener with MouseMotionListener {
    private val random = new Random(17L)
    private val keys = scala.collection.mutable.Set[Int]()
    private val pickups = ArrayBuffer[Pickup]()
    private val particles = ArrayBuffer[Particle]()
    private val skidMarks = ArrayBuffer[SkidMark]()

    private case class Pickup(x: Double, kind: String, phase: Double)
    private case class Particle(x: Double, y: Double, vx: Double, vy: Double, life: Int, kind: String)
    private case class SkidMark(x: Double, y: Double, life: Int)

    private val screenCarX = 275.0
    private val gravity = 0.24
    private val carNames = Array("BIXUU JEEP", "TURBO BUGGY", "SKY BIKE")
    private val carColors = Array(new Color(255, 174, 65), new Color(70, 220, 190), new Color(165, 135, 255))
    private val carPower = Array(1.0, 1.18, 1.08)
    private val carGrip = Array(1.0, 0.88, 0.72)
    private val trackNames = Array("GREEN HILLS", "DESERT DUNES", "MOON VALLEY")
    private val trackSkyA = Array(new Color(90, 177, 239), new Color(242, 156, 91), new Color(20, 30, 82))
    private val trackSkyB = Array(new Color(208, 239, 255), new Color(255, 218, 151), new Color(91, 89, 157))

    private var page = "MENU"
    private var selectedCar = 0
    private var selectedTrack = 0
    private var worldX = 0.0
    private var speed = 0.0
    private var carY = 0.0
    private var verticalSpeed = 0.0
    private var carAngle = 0.0
    private var angularSpeed = 0.0
    private var grounded = true
    private var fuel = 100.0
    private var nitro = 100.0
    private var damage = 0.0
    private var coins = 0
    private var score = 0
    private var bestDistance = 0.0
    private var driftHeat = 0.0
    private var driftPoints = 0
    private var wheelSpin = 0.0
    private var worldClock = 0.0
    private var nextCoinX = 380.0
    private var nextFuelX = 1050.0
    private var jumpCount = 0
    private var stuntRotation = 0.0
    private var boosting = false
    private var drifting = false
    private var mouseGas = false
    private var mouseBrake = false
    private var mouseBoost = false
    private var mouseDrift = false
    private var message = "READY, RIDER!"
    private var messageTimer = 150
    private var shake = 0.0

    private val loop = new Timer(16, new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = updateGame()
    })

    setPreferredSize(new Dimension(1100, 700))
    setFocusable(true)
    setFocusTraversalKeysEnabled(false)
    setBackground(Color.BLACK)
    addKeyListener(this)
    addMouseListener(this)
    addMouseMotionListener(this)

    def startGameLoop(): Unit = loop.start()

    private def groundY(x: Double): Double = {
      val base = if (selectedTrack == 0) 430.0 else if (selectedTrack == 1) 440.0 else 425.0
      val a = if (selectedTrack == 0) 67.0 else if (selectedTrack == 1) 52.0 else 76.0
      val b = if (selectedTrack == 0) 29.0 else if (selectedTrack == 1) 21.0 else 34.0
      val c = if (selectedTrack == 0) 10.0 else if (selectedTrack == 1) 13.0 else 8.0
      base + a * math.sin(x * 0.0035) + b * math.sin(x * 0.009 + 1.1) + c * math.sin(x * 0.026 + 0.7)
    }

    private def terrainSlope(x: Double): Double = (groundY(x + 30.0) - groundY(x - 30.0)) / 60.0

    private def resetRun(): Unit = {
      worldX = 0.0
      speed = 0.0
      carY = groundY(worldX) - 34.0
      verticalSpeed = 0.0
      carAngle = 0.0
      angularSpeed = 0.0
      grounded = true
      fuel = 100.0
      nitro = 100.0
      damage = 0.0
      coins = 0
      score = 0
      driftHeat = 0.0
      driftPoints = 0
      wheelSpin = 0.0
      worldClock = 0.0
      nextCoinX = 360.0
      nextFuelX = 980.0
      jumpCount = 0
      stuntRotation = 0.0
      boosting = false
      drifting = false
      shake = 0.0
      message = "GO! FIND THE HILLS!"
      messageTimer = 130
      pickups.clear()
      particles.clear()
      skidMarks.clear()
      page = "PLAY"
      requestFocusInWindow()
    }

    private def updateGame(): Unit = {
      if (page == "PLAY") {
        worldClock += 0.016
        val accelerating = keys.contains(KeyEvent.VK_RIGHT) || keys.contains(KeyEvent.VK_D) || mouseGas
        val braking = keys.contains(KeyEvent.VK_LEFT) || keys.contains(KeyEvent.VK_A) || mouseBrake
        val boostHeld = keys.contains(KeyEvent.VK_SPACE) || mouseBoost
        val driftHeld = keys.contains(KeyEvent.VK_SHIFT) || mouseDrift
        val leanBack = keys.contains(KeyEvent.VK_UP) || keys.contains(KeyEvent.VK_W)
        val leanForward = keys.contains(KeyEvent.VK_DOWN) || keys.contains(KeyEvent.VK_S)

        if (accelerating && fuel > 0.0) {
          speed += 0.105 * carPower(selectedCar)
          fuel = math.max(0.0, fuel - 0.018)
        } else {
          speed -= 0.012
        }
        if (braking) speed -= (if (speed > 0.5) 0.19 else 0.075)
        if (boostHeld && nitro > 0.0 && fuel > 0.0) {
          boosting = true
          speed += 0.16 * carPower(selectedCar)
          nitro = math.max(0.0, nitro - 0.43)
          fuel = math.max(0.0, fuel - 0.025)
          if (random.nextInt(2) == 0) addParticle(worldX - 36.0, carY + 15.0, -1.5 - random.nextDouble() * 2.0, random.nextDouble() * 2.0 - 1.0, 24, "FLAME")
        } else {
          boosting = false
          nitro = math.min(100.0, nitro + 0.035)
        }

        drifting = driftHeld && grounded && speed > 4.5
        if (drifting) {
          speed -= 0.025
          driftHeat = math.min(100.0, driftHeat + 1.15)
          driftPoints += 1
          if (random.nextInt(2) == 0) addParticle(worldX - 32.0, carY + 23.0, -1.8 - random.nextDouble() * 2.0, -0.6 - random.nextDouble() * 1.3, 30, "SMOKE")
          if (random.nextInt(3) == 0) skidMarks += SkidMark(worldX - 37.0, groundY(worldX) - 2.0, 85)
        } else {
          driftHeat = math.max(0.0, driftHeat - 0.75)
        }

        speed = math.max(-4.0, math.min(17.5, speed))
        if (fuel <= 0.0) {
          speed *= 0.985
          if (math.abs(speed) < 0.18) {
            speed = 0.0
            page = "OVER"
            message = "OUT OF FUEL!"
          }
        }

        worldX = math.max(0.0, worldX + speed)
        wheelSpin += speed * 0.075
        val slope = terrainSlope(worldX)
        val targetY = groundY(worldX) - 34.0

        if (grounded) {
          carY = targetY
          carAngle = math.atan(slope) * 0.75
          if (speed > 7.2 && slope > 0.58) {
            grounded = false
            verticalSpeed = -4.6 - math.min(2.0, speed * 0.06)
            angularSpeed = 0.0
            jumpCount += 1
            message = "BIG AIR!"
            messageTimer = 70
          }
        } else {
          if (leanBack) angularSpeed -= 0.006
          if (leanForward) angularSpeed += 0.006
          angularSpeed = math.max(-0.085, math.min(0.085, angularSpeed))
          verticalSpeed += gravity
          carY += verticalSpeed
          carAngle += angularSpeed
          stuntRotation += angularSpeed
          if (math.abs(stuntRotation) >= math.Pi * 2.0) {
            coins += 5
            score += 250
            stuntRotation = 0.0
            message = "360 STUNT! +5 COINS"
            messageTimer = 100
          }
          if (carY >= targetY && verticalSpeed > 0.0) {
            val impact = math.abs(verticalSpeed)
            val landingError = math.abs(normalizeAngle(carAngle - math.atan(slope) * 0.75))
            if (impact > 7.3 || landingError > 1.15) {
              damage = math.min(100.0, damage + impact * 2.2 + landingError * 15.0)
              shake = 10.0
              message = "ROUGH LANDING!"
              messageTimer = 90
            } else {
              message = "SMOOTH LANDING!"
              messageTimer = 55
            }
            carY = targetY
            carAngle = math.atan(slope) * 0.75
            verticalSpeed = 0.0
            angularSpeed = 0.0
            grounded = true
            stuntRotation = 0.0
          }
        }

        if (drifting && driftHeat > 65.0 && random.nextInt(5) == 0) {
          coins += 1
          message = "DRIFT BONUS +1"
          messageTimer = 45
        }

        spawnPickups()
        collectPickups()
        updateParticles()
        updateSkidMarks()
        if (messageTimer > 0) messageTimer -= 1
        if (shake > 0.0) shake *= 0.78

        if (damage >= 100.0) {
          page = "OVER"
          message = "VEHICLE DAMAGED!"
        }
        val distance = worldX / 10.0
        if (distance > bestDistance) bestDistance = distance
        score = math.max(score, (distance * 2.0).toInt + coins * 10 + driftPoints / 10)
      } else if (page == "MENU" || page == "GARAGE" || page == "TRACK" || page == "PAUSE" || page == "OVER") {
        worldClock += 0.012
        updateParticles()
      }
      repaint()
    }

    private def normalizeAngle(a: Double): Double = {
      var v = a
      while (v > math.Pi) v -= math.Pi * 2.0
      while (v < -math.Pi) v += math.Pi * 2.0
      v
    }

    private def spawnPickups(): Unit = {
      while (nextCoinX < worldX + 1450.0) {
        val cluster = 3 + random.nextInt(4)
        var i = 0
        while (i < cluster) {
          pickups += Pickup(nextCoinX + i * 33.0, "COIN", random.nextDouble() * 6.0)
          i += 1
        }
        nextCoinX += 300.0 + random.nextDouble() * 320.0
      }
      while (nextFuelX < worldX + 1450.0) {
        pickups += Pickup(nextFuelX, "FUEL", random.nextDouble() * 6.0)
        nextFuelX += 950.0 + random.nextDouble() * 360.0
      }
    }

    private def collectPickups(): Unit = {
      var i = pickups.length - 1
      while (i >= 0) {
        val p = pickups(i)
        val dy = math.abs((groundY(p.x) - 74.0) - (carY - 5.0))
        if (math.abs(p.x - worldX) < 47.0 && dy < 95.0) {
          if (p.kind == "COIN") {
            coins += 1
            score += 25
            addParticle(p.x, groundY(p.x) - 74.0, 0.0, -1.5, 28, "GOLD")
          } else {
            fuel = math.min(100.0, fuel + 32.0)
            addParticle(p.x, groundY(p.x) - 74.0, 0.0, -1.5, 35, "GREEN")
            message = "FUEL +32%"
            messageTimer = 75
          }
          pickups.remove(i)
        }
        i -= 1
      }
    }

    private def addParticle(x: Double, y: Double, vx: Double, vy: Double, life: Int, kind: String): Unit = {
      particles += Particle(x, y, vx, vy, life, kind)
      if (particles.length > 150) particles.remove(0)
    }

    private def updateParticles(): Unit = {
      var i = particles.length - 1
      while (i >= 0) {
        val p = particles(i)
        val next = p.copy(x = p.x + p.vx, y = p.y + p.vy, vy = p.vy + 0.035, life = p.life - 1)
        if (next.life <= 0) particles.remove(i) else particles(i) = next
        i -= 1
      }
    }

    private def updateSkidMarks(): Unit = {
      var i = skidMarks.length - 1
      while (i >= 0) {
        val m = skidMarks(i)
        if (m.life <= 1) skidMarks.remove(i) else skidMarks(i) = m.copy(life = m.life - 1)
        i -= 1
      }
    }

    private def screenX(wx: Double): Int = (screenCarX + wx - worldX).toInt

    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)
      val w = getWidth
      val h = getHeight
      if (page == "MENU") drawMenu(g, w, h)
      else if (page == "GARAGE") drawGarage(g, w, h)
      else if (page == "TRACK") drawTrackSelect(g, w, h)
      else if (page == "PLAY") drawPlay(g, w, h)
      else if (page == "PAUSE") { drawPlay(g, w, h); drawPause(g, w, h) }
      else if (page == "OVER") drawGameOver(g, w, h)
      g.dispose()
    }

    private def drawBackground(g: Graphics2D, w: Int, h: Int): Unit = {
      g.setPaint(new GradientPaint(0, 0, trackSkyA(selectedTrack), 0, h, trackSkyB(selectedTrack)))
      g.fillRect(0, 0, w, h)
      if (selectedTrack == 2) {
        g.setColor(new Color(255, 250, 207))
        g.fillOval(w - 190, 58, 88, 88)
        g.setColor(new Color(238, 246, 255, 170))
        var i = 0
        while (i < 45) {
          val sx = ((i * 173 + 57) % math.max(1, w)).toInt
          val sy = ((i * 71 + 23) % 260).toInt
          g.fillOval(sx, sy, 2, 2)
          i += 1
        }
      } else {
        g.setColor(if (selectedTrack == 0) new Color(255, 242, 164) else new Color(255, 231, 185))
        g.fillOval(w - 185, 55, 92, 92)
        drawCloud(g, (w * 0.18 + math.sin(worldClock) * 18).toInt, 105, 1.0)
        drawCloud(g, (w * 0.58 - math.sin(worldClock * 0.7) * 22).toInt, 155, 0.75)
      }
      drawMountains(g, w, h)
    }

    private def drawCloud(g: Graphics2D, x: Int, y: Int, scale: Double): Unit = {
      val old = g.getColor
      g.setColor(new Color(255, 255, 255, 155))
      g.fillOval(x, y, (70 * scale).toInt, (34 * scale).toInt)
      g.fillOval(x + (22 * scale).toInt, y - (17 * scale).toInt, (45 * scale).toInt, (43 * scale).toInt)
      g.fillOval(x + (47 * scale).toInt, y, (58 * scale).toInt, (33 * scale).toInt)
      g.setColor(old)
    }

    private def drawMountains(g: Graphics2D, w: Int, h: Int): Unit = {
      val offset = (worldX * 0.12).toInt
      val p1 = new Polygon()
      p1.addPoint(0, 365)
      var x = -100
      while (x <= w + 100) {
        val y = 315 + (34 * math.sin((x + offset) * 0.006)).toInt + (24 * math.sin((x + offset) * 0.013 + 1.2)).toInt
        p1.addPoint(x, y)
        x += 55
      }
      p1.addPoint(w, h)
      p1.addPoint(0, h)
      g.setColor(if (selectedTrack == 2) new Color(55, 62, 116) else if (selectedTrack == 1) new Color(190, 126, 87) else new Color(81, 145, 138))
      g.fillPolygon(p1)
      val p2 = new Polygon()
      p2.addPoint(0, 405)
      x = -100
      while (x <= w + 100) {
        val y = 355 + (27 * math.sin((x + offset * 1.7) * 0.009 + 2.0)).toInt + (18 * math.sin((x + offset) * 0.021)).toInt
        p2.addPoint(x, y)
        x += 42
      }
      p2.addPoint(w, h)
      p2.addPoint(0, h)
      g.setColor(if (selectedTrack == 2) new Color(77, 81, 139) else if (selectedTrack == 1) new Color(205, 150, 97) else new Color(66, 125, 104))
      g.fillPolygon(p2)
    }

    private def drawTerrain(g: Graphics2D, w: Int, h: Int): Unit = {
      val poly = new Polygon()
      poly.addPoint(0, h)
      var sx = 0
      while (sx <= w + 8) {
        val wx = worldX + sx - screenCarX
        val yy = groundY(wx).toInt
        poly.addPoint(sx, yy)
        sx += 8
      }
      poly.addPoint(w + 8, h)
      g.setColor(if (selectedTrack == 2) new Color(96, 96, 131) else if (selectedTrack == 1) new Color(177, 111, 57) else new Color(63, 139, 71))
      g.fillPolygon(poly)
      g.setStroke(new BasicStroke(5.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND))
      g.setColor(if (selectedTrack == 2) new Color(203, 206, 224) else if (selectedTrack == 1) new Color(255, 205, 110) else new Color(116, 222, 111))
      val top = new Polygon()
      sx = 0
      while (sx <= w + 8) {
        val wx = worldX + sx - screenCarX
        top.addPoint(sx, groundY(wx).toInt)
        sx += 8
      }
      g.drawPolyline(top.xpoints, top.ypoints, top.npoints)
      g.setColor(new Color(0, 0, 0, 28))
      g.fillRect(0, h - 24, w, 24)
    }

    private def drawPickups(g: Graphics2D): Unit = {
      var i = 0
      while (i < pickups.length) {
        val p = pickups(i)
        val x = screenX(p.x)
        if (x > -50 && x < getWidth + 50) {
          val bob = (math.sin(worldClock * 4.0 + p.phase) * 5.0).toInt
          val y = (groundY(p.x) - (if (p.kind == "COIN") 84.0 else 92.0) + bob).toInt
          if (p.kind == "COIN") {
            g.setColor(new Color(255, 204, 57, 70))
            g.fillOval(x - 18, y - 18, 36, 36)
            g.setColor(new Color(255, 190, 32))
            g.fillOval(x - 11, y - 11, 22, 22)
            g.setColor(new Color(255, 244, 160))
            g.drawOval(x - 7, y - 7, 14, 14)
          } else {
            g.setColor(new Color(40, 55, 55))
            g.fillRoundRect(x - 13, y - 16, 26, 32, 7, 7)
            g.setColor(new Color(91, 234, 132))
            g.fillRoundRect(x - 10, y - 11, 20, 23, 5, 5)
            g.setColor(Color.WHITE)
            g.fillRect(x - 4, y - 7, 8, 14)
            g.fillRect(x - 7, y - 2, 14, 4)
          }
        }
        i += 1
      }
    }

    private def drawParticles(g: Graphics2D): Unit = {
      var i = 0
      while (i < particles.length) {
        val p = particles(i)
        val x = screenX(p.x)
        if (x > -20 && x < getWidth + 20) {
          val alpha = math.max(20, math.min(220, p.life * 7)).toInt
          if (p.kind == "FLAME") g.setColor(new Color(255, 112 + random.nextInt(80), 28, alpha))
          else if (p.kind == "SMOKE") g.setColor(new Color(210, 220, 226, alpha))
          else if (p.kind == "GOLD") g.setColor(new Color(255, 217, 73, alpha))
          else g.setColor(new Color(91, 239, 135, alpha))
          val size = if (p.kind == "FLAME") 8 else 6
          g.fillOval(x, p.y.toInt, size, size)
        }
        i += 1
      }
    }

    private def drawSkidMarks(g: Graphics2D): Unit = {
      g.setColor(new Color(28, 31, 36, 95))
      var i = 0
      while (i < skidMarks.length) {
        val m = skidMarks(i)
        val x = screenX(m.x)
        if (x > -30 && x < getWidth + 30) g.fillRoundRect(x, m.y.toInt, 18, 3, 3, 3)
        i += 1
      }
    }

    private def drawCar(g: Graphics2D): Unit = {
      val cg = g.create().asInstanceOf[Graphics2D]
      val cx = screenCarX.toInt
      val cy = carY.toInt
      cg.rotate(carAngle, cx, cy)
      val body = carColors(selectedCar)
      val wheelY = cy + 20
      cg.setColor(new Color(18, 23, 31))
      cg.fillOval(cx - 49, wheelY - 14, 29, 29)
      cg.fillOval(cx + 23, wheelY - 14, 29, 29)
      cg.setColor(new Color(107, 119, 132))
      cg.fillOval(cx - 42, wheelY - 7, 15, 15)
      cg.fillOval(cx + 30, wheelY - 7, 15, 15)
      cg.setColor(new Color(214, 224, 231))
      cg.fillOval(cx - 37, wheelY - 3, 5, 5)
      cg.fillOval(cx + 35, wheelY - 3, 5, 5)
      cg.setColor(new Color(30, 46, 67))
      cg.fillRoundRect(cx - 48, cy - 17, 101, 36, 15, 15)
      cg.setColor(body)
      cg.fillRoundRect(cx - 45, cy - 21, 96, 34, 13, 13)
      cg.setColor(body.brighter())
      cg.fillRoundRect(cx - 12, cy - 40, 43, 27, 10, 10)
      cg.setColor(new Color(157, 229, 255, 225))
      val window = new Polygon()
      window.addPoint(cx - 5, cy - 36)
      window.addPoint(cx + 24, cy - 36)
      window.addPoint(cx + 27, cy - 17)
      window.addPoint(cx - 10, cy - 17)
      cg.fillPolygon(window)
      cg.setColor(new Color(24, 38, 58))
      cg.fillOval(cx + 2, cy - 35, 15, 15)
      cg.setColor(new Color(255, 236, 204))
      cg.fillOval(cx + 5, cy - 33, 9, 9)
      cg.setColor(new Color(225, 62, 62))
      cg.fillRoundRect(cx - 47, cy - 7, 9, 10, 3, 3)
      cg.setColor(new Color(255, 249, 194))
      cg.fillRoundRect(cx + 43, cy - 8, 9, 10, 3, 3)
      if (boosting) {
        val flame = new Polygon()
        flame.addPoint(cx - 49, cy - 8)
        flame.addPoint(cx - 80 - random.nextInt(14), cy - 17)
        flame.addPoint(cx - 67, cy + 1)
        flame.addPoint(cx - 82 - random.nextInt(12), cy + 11)
        cg.setColor(new Color(255, 135, 31, 230))
        cg.fillPolygon(flame)
        cg.setColor(new Color(255, 237, 126, 230))
        cg.fillOval(cx - 68, cy - 8, 17, 13)
      }
      cg.setColor(new Color(255, 255, 255, 170))
      cg.setStroke(new BasicStroke(2.0f))
      cg.drawRoundRect(cx - 45, cy - 21, 96, 34, 13, 13)
      cg.dispose()
    }

    private def drawHud(g: Graphics2D, w: Int): Unit = {
      g.setColor(new Color(10, 20, 43, 205))
      g.fillRoundRect(18, 16, 355, 90, 18, 18)
      g.setColor(Color.WHITE)
      g.setFont(new Font("SansSerif", Font.BOLD, 17))
      g.drawString("BIXUU HILL RIDER", 34, 42)
      g.setFont(new Font("SansSerif", Font.BOLD, 13))
      g.setColor(new Color(255, 226, 115))
      g.drawString("DIST " + (worldX / 10.0).toInt + " m", 34, 67)
      g.setColor(new Color(255, 255, 255))
      g.drawString("COINS " + coins + "   SCORE " + score, 34, 91)
      drawBar(g, 394, 22, 170, 14, fuel, new Color(77, 225, 119), "FUEL")
      drawBar(g, 394, 51, 170, 14, nitro, new Color(65, 190, 255), "NITRO")
      drawBar(g, 394, 80, 170, 14, 100.0 - damage, new Color(255, 115, 100), "HEALTH")
      g.setColor(new Color(10, 20, 43, 205))
      g.fillRoundRect(w - 174, 16, 156, 48, 15, 15)
      g.setColor(Color.WHITE)
      g.setFont(new Font("SansSerif", Font.BOLD, 16))
      g.drawString("" + math.abs(speed * 9.0).toInt + " km/h", w - 158, 46)
      drawButton(g, w - 91, 76, 72, 34, "PAUSE", new Color(63, 78, 113))
      if (drifting) {
        g.setColor(new Color(255, 210, 99))
        g.setFont(new Font("SansSerif", Font.BOLD, 25))
        g.drawString("DRIFT!", w - 176, 139)
      }
      if (boosting) {
        g.setColor(new Color(112, 224, 255))
        g.setFont(new Font("SansSerif", Font.BOLD, 20))
        g.drawString("NITRO BOOST", w - 220, 166)
      }
      if (messageTimer > 0 && message.nonEmpty) {
        g.setFont(new Font("SansSerif", Font.BOLD, 23))
        val fm = g.getFontMetrics
        val tw = fm.stringWidth(message)
        g.setColor(new Color(9, 18, 39, 180))
        g.fillRoundRect((w - tw) / 2 - 18, 120, tw + 36, 42, 15, 15)
        g.setColor(new Color(255, 236, 151))
        g.drawString(message, (w - tw) / 2, 149)
      }
      drawControlButton(g, 22, getHeight - 91, 116, 65, "BRAKE", new Color(63, 102, 153))
      drawControlButton(g, 148, getHeight - 91, 116, 65, "GAS  ?", new Color(42, 147, 102))
      drawControlButton(g, w - 278, getHeight - 91, 116, 65, "NITRO", new Color(36, 127, 189))
      drawControlButton(g, w - 152, getHeight - 91, 130, 65, "DRIFT", new Color(176, 94, 43))
      g.setFont(new Font("SansSerif", Font.PLAIN, 11))
      g.setColor(new Color(255, 255, 255, 205))
      g.drawString("RIGHT/D GAS   LEFT/A BRAKE   SPACE BOOST   SHIFT DRIFT   W/S AIR STUNT", 290, getHeight - 22)
    }

    private def drawBar(g: Graphics2D, x: Int, y: Int, width: Int, height: Int, value: Double, color: Color, label: String): Unit = {
      g.setColor(new Color(12, 19, 34, 190))
      g.fillRoundRect(x, y, width, height, 8, 8)
      val fill = math.max(0, math.min(width, (width * value / 100.0).toInt))
      g.setColor(color)
      g.fillRoundRect(x, y, fill, height, 8, 8)
      g.setColor(Color.WHITE)
      g.setFont(new Font("SansSerif", Font.BOLD, 10))
      g.drawString(label, x + width + 8, y + 12)
    }

    private def drawControlButton(g: Graphics2D, x: Int, y: Int, w: Int, h: Int, text: String, color: Color): Unit = {
      g.setColor(new Color(0, 0, 0, 65))
      g.fillRoundRect(x + 3, y + 4, w, h, 17, 17)
      g.setColor(new Color(color.getRed, color.getGreen, color.getBlue, 220))
      g.fillRoundRect(x, y, w, h, 17, 17)
      g.setColor(new Color(255, 255, 255, 195))
      g.setStroke(new BasicStroke(2.0f))
      g.drawRoundRect(x, y, w, h, 17, 17)
      g.setFont(new Font("SansSerif", Font.BOLD, 16))
      val fm = g.getFontMetrics
      g.drawString(text, x + (w - fm.stringWidth(text)) / 2, y + h / 2 + 6)
    }

    private def drawButton(g: Graphics2D, x: Int, y: Int, w: Int, h: Int, text: String, color: Color): Unit = {
      g.setColor(new Color(0, 0, 0, 65))
      g.fillRoundRect(x + 4, y + 5, w, h, 18, 18)
      g.setPaint(new GradientPaint(x, y, color.brighter(), x, y + h, color.darker()))
      g.fillRoundRect(x, y, w, h, 18, 18)
      g.setColor(new Color(255, 255, 255, 175))
      g.setStroke(new BasicStroke(1.5f))
      g.drawRoundRect(x, y, w, h, 18, 18)
      g.setFont(new Font("SansSerif", Font.BOLD, 16))
      val tw = g.getFontMetrics.stringWidth(text)
      g.setColor(Color.WHITE)
      g.drawString(text, x + (w - tw) / 2, y + h / 2 + 6)
    }

    private def drawPlay(g: Graphics2D, w: Int, h: Int): Unit = {
      drawBackground(g, w, h)
      drawTerrain(g, w, h)
      drawSkidMarks(g)
      drawPickups(g)
      drawParticles(g)
      drawCar(g)
      drawHud(g, w)
    }

    private def drawMenu(g: Graphics2D, w: Int, h: Int): Unit = {
      drawBackground(g, w, h)
      drawTerrain(g, w, h)
      val titleY = math.max(140, h / 4)
      g.setFont(new Font("SansSerif", Font.BOLD, 54))
      g.setColor(new Color(13, 28, 52, 180))
      g.drawString("BIXUU HILL CLIMB", w / 2 - 298, titleY + 5)
      g.setColor(new Color(255, 246, 215))
      g.drawString("BIXUU HILL CLIMB", w / 2 - 302, titleY)
      g.setFont(new Font("SansSerif", Font.BOLD, 20))
      g.setColor(new Color(22, 44, 70))
      g.drawString("DRIFT  ?  NITRO BOOST  ?  AIR STUNTS  ?  COIN HUNT", w / 2 - 278, titleY + 40)
      val savedY = carY
      val savedAngle = carAngle
      carY = groundY(worldX) - 34.0
      carAngle = 0.0
      drawCar(g)
      carY = savedY
      carAngle = savedAngle
      val bx = w / 2 - 125
      drawButton(g, bx, titleY + 86, 250, 58, "?  PLAY GAME", new Color(33, 155, 103))
      drawButton(g, bx, titleY + 157, 250, 50, "GARAGE  /  CARS", new Color(63, 100, 161))
      drawButton(g, bx, titleY + 220, 250, 50, "SELECT TRACK", new Color(135, 89, 167))
      g.setFont(new Font("SansSerif", Font.BOLD, 14))
      g.setColor(Color.WHITE)
      g.drawString("BEST DISTANCE: " + bestDistance.toInt + " m", 25, h - 25)
      g.setFont(new Font("SansSerif", Font.PLAIN, 13))
      g.drawString("Original Bixuu arcade driving game ? No extra files needed", w - 390, h - 25)
    }

    private def drawGarage(g: Graphics2D, w: Int, h: Int): Unit = {
      drawBackground(g, w, h)
      drawTerrain(g, w, h)
      g.setFont(new Font("SansSerif", Font.BOLD, 43))
      g.setColor(Color.WHITE)
      g.drawString("BIXUU GARAGE", w / 2 - 170, 100)
      var i = 0
      while (i < carNames.length) {
        val x = w / 2 - 360 + i * 245
        val y = 190
        g.setColor(new Color(10, 23, 49, 205))
        g.fillRoundRect(x, y, 220, 255, 24, 24)
        g.setColor(carColors(i))
        g.setStroke(new BasicStroke(3.0f))
        g.drawRoundRect(x, y, 220, 255, 24, 24)
        val oldCar = selectedCar
        selectedCar = i
        val oldX = screenCarX
        val cg = g.create().asInstanceOf[Graphics2D]
        cg.translate(x - 100, 0)
        drawCarPreview(cg, w, h, i)
        cg.dispose()
        selectedCar = oldCar
        g.setFont(new Font("SansSerif", Font.BOLD, 18))
        g.setColor(Color.WHITE)
        g.drawString(carNames(i), x + 23, y + 183)
        g.setFont(new Font("SansSerif", Font.PLAIN, 13))
        g.setColor(new Color(203, 220, 239))
        val desc = if (i == 0) "BALANCED ? ALL ROUND" else if (i == 1) "FAST ? LIGHT WEIGHT" else "QUICK ? LOW GRIP"
        g.drawString(desc, x + 23, y + 208)
        drawButton(g, x + 28, y + 218, 164, 29, if (selectedCar == i) "SELECTED" else "SELECT CAR", carColors(i).darker())
        i += 1
      }
      drawButton(g, 25, h - 83, 150, 48, "? BACK", new Color(68, 86, 119))
      g.setFont(new Font("SansSerif", Font.PLAIN, 13))
      g.setColor(Color.WHITE)
      g.drawString("Choose your ride. Buggy is faster; bike is light and needs careful landings.", w / 2 - 245, h - 26)
    }

    private def drawCarPreview(g: Graphics2D, w: Int, h: Int, index: Int): Unit = {
      val oldWorld = worldX
      val oldY = carY
      val oldAngle = carAngle
      val oldGround = grounded
      worldX = 0.0
      carY = 320.0
      carAngle = -0.05
      grounded = true
      val tx = g.create().asInstanceOf[Graphics2D]
      tx.translate(-screenCarX + 210, 0)
      drawCar(tx)
      tx.dispose()
      worldX = oldWorld
      carY = oldY
      carAngle = oldAngle
      grounded = oldGround
    }

    private def drawTrackSelect(g: Graphics2D, w: Int, h: Int): Unit = {
      drawBackground(g, w, h)
      drawTerrain(g, w, h)
      g.setFont(new Font("SansSerif", Font.BOLD, 43))
      g.setColor(Color.WHITE)
      g.drawString("CHOOSE YOUR WORLD", w / 2 - 238, 115)
      val colors = Array(new Color(45, 150, 94), new Color(193, 122, 59), new Color(81, 91, 163))
      var i = 0
      while (i < 3) {
        val x = w / 2 - 360 + i * 245
        val y = 220
        g.setColor(new Color(8, 21, 43, 195))
        g.fillRoundRect(x, y, 220, 190, 22, 22)
        g.setColor(colors(i))
        g.setStroke(new BasicStroke(3.0f))
        g.drawRoundRect(x, y, 220, 190, 22, 22)
        g.setFont(new Font("SansSerif", Font.BOLD, 18))
        g.setColor(Color.WHITE)
        g.drawString(trackNames(i), x + 22, y + 52)
        g.setFont(new Font("SansSerif", Font.PLAIN, 13))
        g.setColor(new Color(211, 222, 238))
        val note = if (i == 0) "Rolling green mountains" else if (i == 1) "Warm desert and dunes" else "Low gravity moon route"
        g.drawString(note, x + 22, y + 83)
        drawButton(g, x + 26, y + 118, 168, 43, if (selectedTrack == i) "SELECTED" else "SELECT WORLD", colors(i))
        i += 1
      }
      drawButton(g, 25, h - 83, 150, 48, "? BACK", new Color(68, 86, 119))
      g.setFont(new Font("SansSerif", Font.PLAIN, 13))
      g.setColor(Color.WHITE)
      g.drawString("Moon Valley has a different sky and bumpier terrain.", w / 2 - 175, h - 26)
    }

    private def drawPause(g: Graphics2D, w: Int, h: Int): Unit = {
      g.setColor(new Color(4, 10, 25, 185))
      g.fillRect(0, 0, w, h)
      g.setFont(new Font("SansSerif", Font.BOLD, 48))
      g.setColor(Color.WHITE)
      g.drawString("PAUSED", w / 2 - 105, h / 2 - 95)
      drawButton(g, w / 2 - 125, h / 2 - 50, 250, 52, "RESUME", new Color(40, 151, 101))
      drawButton(g, w / 2 - 125, h / 2 + 17, 250, 48, "RESTART RUN", new Color(59, 111, 175))
      drawButton(g, w / 2 - 125, h / 2 + 79, 250, 48, "MAIN MENU", new Color(114, 83, 148))
    }

    private def drawGameOver(g: Graphics2D, w: Int, h: Int): Unit = {
      drawPlay(g, w, h)
      g.setColor(new Color(5, 11, 27, 205))
      g.fillRect(0, 0, w, h)
      g.setFont(new Font("SansSerif", Font.BOLD, 48))
      g.setColor(new Color(255, 221, 126))
      g.drawString("RUN FINISHED", w / 2 - 175, h / 2 - 115)
      g.setFont(new Font("SansSerif", Font.BOLD, 21))
      g.setColor(Color.WHITE)
      g.drawString(message, w / 2 - 105, h / 2 - 75)
      g.setFont(new Font("SansSerif", Font.PLAIN, 18))
      g.drawString("DISTANCE  " + (worldX / 10.0).toInt + " m     COINS  " + coins + "     SCORE  " + score, w / 2 - 225, h / 2 - 35)
      drawButton(g, w / 2 - 130, h / 2 + 8, 260, 54, "PLAY AGAIN", new Color(39, 151, 103))
      drawButton(g, w / 2 - 130, h / 2 + 77, 260, 48, "MAIN MENU", new Color(76, 104, 160))
    }

    private def startGame(): Unit = resetRun()

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

    private def handleMouseDown(e: MouseEvent): Unit = {
      val x = e.getX
      val y = e.getY
      val w = getWidth
      val h = getHeight
      requestFocusInWindow()
      if (page == "MENU") {
        val titleY = math.max(140, h / 4)
        val bx = w / 2 - 125
        if (pointIn(x, y, bx, titleY + 86, 250, 58)) startGame()
        else if (pointIn(x, y, bx, titleY + 157, 250, 50)) page = "GARAGE"
        else if (pointIn(x, y, bx, titleY + 220, 250, 50)) page = "TRACK"
      } else if (page == "GARAGE") {
        if (pointIn(x, y, 25, h - 83, 150, 48)) page = "MENU"
        else {
          var i = 0
          while (i < 3) {
            val bx = w / 2 - 360 + i * 245
            if (pointIn(x, y, bx, 190, 220, 255)) selectedCar = i
            i += 1
          }
        }
      } else if (page == "TRACK") {
        if (pointIn(x, y, 25, h - 83, 150, 48)) page = "MENU"
        else {
          var i = 0
          while (i < 3) {
            val bx = w / 2 - 360 + i * 245
            if (pointIn(x, y, bx, 220, 220, 190)) selectedTrack = i
            i += 1
          }
        }
      } else if (page == "PLAY") {
        if (pointIn(x, y, w - 91, 76, 72, 34)) page = "PAUSE"
        else if (pointIn(x, y, 22, h - 91, 116, 65)) mouseBrake = true
        else if (pointIn(x, y, 148, h - 91, 116, 65)) mouseGas = true
        else if (pointIn(x, y, w - 278, h - 91, 116, 65)) mouseBoost = true
        else if (pointIn(x, y, w - 152, h - 91, 130, 65)) mouseDrift = true
      } else if (page == "PAUSE") {
        if (pointIn(x, y, w / 2 - 125, h / 2 - 50, 250, 52)) page = "PLAY"
        else if (pointIn(x, y, w / 2 - 125, h / 2 + 17, 250, 48)) startGame()
        else if (pointIn(x, y, w / 2 - 125, h / 2 + 79, 250, 48)) page = "MENU"
      } else if (page == "OVER") {
        if (pointIn(x, y, w / 2 - 130, h / 2 + 8, 260, 54)) startGame()
        else if (pointIn(x, y, w / 2 - 130, h / 2 + 77, 260, 48)) page = "MENU"
      }
      repaint()
    }

    override def keyPressed(e: KeyEvent): Unit = {
      val k = e.getKeyCode
      keys += k
      if (k == KeyEvent.VK_ESCAPE) {
        if (page == "PLAY") page = "PAUSE"
        else if (page == "PAUSE") page = "PLAY"
        else page = "MENU"
      } else if (k == KeyEvent.VK_R && (page == "PLAY" || page == "OVER")) startGame()
      repaint()
    }
    override def keyReleased(e: KeyEvent): Unit = { keys -= e.getKeyCode }
    override def keyTyped(e: KeyEvent): Unit = {}
    override def mousePressed(e: MouseEvent): Unit = handleMouseDown(e)
    override def mouseReleased(e: MouseEvent): Unit = { mouseGas = false; mouseBrake = false; mouseBoost = false; mouseDrift = false }
    override def mouseClicked(e: MouseEvent): Unit = {}
    override def mouseEntered(e: MouseEvent): Unit = {}
    override def mouseExited(e: MouseEvent): Unit = { mouseGas = false; mouseBrake = false; mouseBoost = false; mouseDrift = false }
    override def mouseMoved(e: MouseEvent): Unit = {}
    override def mouseDragged(e: MouseEvent): Unit = {}
  }
}

// Kojo / Scala script entry point:
BixuuHillClimbRider.main(Array[String]())