Code Sketch
yoi
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]())