Code Sketch
yoiiiiii
Category: Programming
import javax.swing._
import java.awt._
import java.awt.event._
import java.awt.geom._
import java.io._
import java.net._
import java.util.concurrent._
import scala.collection.mutable
import scala.util.Random
object RealisticDriveOnline_Cockpit {
val PORT = 5555
def main(args: Array[String]): Unit = {
SwingUtilities.invokeLater(new Runnable {
override def run(): Unit = new DriveFrame(args)
})
}
}
class DriveFrame(args: Array[String]) extends JFrame {
setTitle("REALISTIC DRIVE ONLINE - COCKPIT EDITION")
setSize(1280, 820)
setResizable(false)
setLocationRelativeTo(null)
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
val panel = new DriveGamePanel(args)
setContentPane(panel)
setVisible(true)
panel.requestFocusInWindow()
}
class DriveGamePanel(args: Array[String]) extends JPanel {
setFocusable(true)
// ==========================================================
// GAME STATES
// ==========================================================
val MENU = 0
val GAME = 1
val PAUSE = 2
val GAME_OVER = 3
val FINISH = 4
var state = MENU
// ==========================================================
// PLAYER PROFILE
// ==========================================================
var playerName = "Player"
var playerId = -1
// ==========================================================
// NETWORK CLIENT
// ==========================================================
var socket: Socket = null
var input: BufferedReader = null
var output: PrintWriter = null
var online = false
var serverHost = "127.0.0.1"
case class RemotePlayer(
var name: String,
var x: Double,
var distance: Double,
var speed: Double,
var gear: String,
var score: Int,
var race: Boolean,
var colorCode: Int
)
val onlinePlayers = mutable.Map[Int, RemotePlayer]()
// ==========================================================
// CHAT
// ==========================================================
val chatLines = mutable.ArrayBuffer[String]()
var showChat = true
def addChat(text: String): Unit = synchronized {
chatLines += text
while (chatLines.length > 8) chatLines.remove(0)
}
// ==========================================================
// BUILT-IN SERVER
// ==========================================================
case class ServerPlayer(
id: Int,
socket: Socket,
input: BufferedReader,
output: PrintWriter,
var name: String,
var x: Double,
var distance: Double,
var speed: Double,
var gear: String,
var score: Int,
var race: Boolean,
var colorCode: Int
)
val serverPlayers = new ConcurrentHashMap[Int, ServerPlayer]()
val serverPool = Executors.newCachedThreadPool()
var serverSocket: ServerSocket = null
var serverRunning = false
var nextServerId = 1000
// ==========================================================
// GAME VARIABLES
// ==========================================================
val rnd = new Random()
var carX = 0.0
var speed = 0.0
var rpm = 900.0
var steering = 0.0
var gear = "N"
var fuel = 100.0
var damage = 0.0
var nitro = 100.0
var distance = 0.0
var score = 0
val targetDistance = 5000.0
var roadScroll = 0.0
var headlights = false
var cockpit = true
var mapVisible = true
var smoke = 0.0
var crashFlash = 0.0
var hornFlash = 0.0
var raceMode = false
var raceCountdown = 0
var raceStarted = false
var message = ""
var messageTimer = 0.0
// ==========================================================
// LOCAL TRAFFIC
// ==========================================================
case class Traffic(
var lane: Int,
var z: Double,
var aiSpeed: Double,
var kind: Int,
var color: Color
)
val traffic = mutable.ArrayBuffer[Traffic]()
// ==========================================================
// INPUT
// ==========================================================
val keys = mutable.Set[Int]()
val controlKeys = Array(
KeyEvent.VK_LEFT,
KeyEvent.VK_RIGHT,
KeyEvent.VK_UP,
KeyEvent.VK_DOWN,
KeyEvent.VK_A,
KeyEvent.VK_D,
KeyEvent.VK_W,
KeyEvent.VK_S,
KeyEvent.VK_SPACE,
KeyEvent.VK_ESCAPE,
KeyEvent.VK_ENTER,
KeyEvent.VK_H,
KeyEvent.VK_M,
KeyEvent.VK_C,
KeyEvent.VK_F,
KeyEvent.VK_P,
KeyEvent.VK_R,
KeyEvent.VK_N,
KeyEvent.VK_1,
KeyEvent.VK_2,
KeyEvent.VK_3,
KeyEvent.VK_4,
KeyEvent.VK_5,
KeyEvent.VK_E,
KeyEvent.VK_Q
)
setupKeys()
def setupKeys(): Unit = {
val im = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
val am = getActionMap()
for (k <- controlKeys) {
val dn = "D" + k
val up = "U" + k
im.put(KeyStroke.getKeyStroke(k, 0, false), dn)
im.put(KeyStroke.getKeyStroke(k, 0, true), up)
am.put(dn, new AbstractAction {
override def actionPerformed(e: ActionEvent): Unit = pressKey(k)
})
am.put(up, new AbstractAction {
override def actionPerformed(e: ActionEvent): Unit = keys -= k
})
}
}
def pressKey(k: Int): Unit = {
if (state == MENU) {
if (k == KeyEvent.VK_ENTER) hostAndPlay()
else if (k == KeyEvent.VK_C) joinOnline()
else if (k == KeyEvent.VK_O) offlinePlay()
return
}
if (k == KeyEvent.VK_ESCAPE) {
if (state == GAME) state = PAUSE
else if (state == PAUSE) state = GAME
return
}
if ((state == GAME_OVER || state == FINISH) && k == KeyEvent.VK_ENTER) {
restartGame()
return
}
if (k == KeyEvent.VK_C && state == GAME) {
sendChatDialog()
return
}
if (k == KeyEvent.VK_F && state == GAME) {
toggleRace()
return
}
if (k == KeyEvent.VK_P && state == GAME) {
changeName()
return
}
if (k == KeyEvent.VK_H && state == GAME) {
headlights = !headlights
return
}
if (k == KeyEvent.VK_M && state == GAME) {
mapVisible = !mapVisible
return
}
keys += k
if (k == KeyEvent.VK_R) gear = "R"
else if (k == KeyEvent.VK_N) gear = "N"
else if (k == KeyEvent.VK_1) gear = "1"
else if (k == KeyEvent.VK_2) gear = "2"
else if (k == KeyEvent.VK_3) gear = "3"
else if (k == KeyEvent.VK_4) gear = "4"
else if (k == KeyEvent.VK_5) gear = "5"
}
// ==========================================================
// STARTUP
// ==========================================================
chooseNameOnStart()
def chooseNameOnStart(): Unit = {
val n = JOptionPane.showInputDialog(
this,
"ENTER YOUR DRIVER NAME",
"Player"
)
if (n != null && n.trim.nonEmpty) playerName = cleanName(n.trim)
}
def cleanName(n: String): String = {
n.replace("|", " ").replace("\n", " ").take(18)
}
initializeTraffic()
// ==========================================================
// BUILT-IN SERVER
// ==========================================================
def startServer(): Boolean = {
if (serverRunning) return true
try {
serverSocket = new ServerSocket(RealisticDriveOnline_Cockpit.PORT)
serverRunning = true
val t = new Thread(new Runnable {
override def run(): Unit = acceptConnections()
}, "DriveServerAccept")
t.setDaemon(true)
t.start()
true
} catch {
case _: Throwable =>
serverRunning = false
false
}
}
def acceptConnections(): Unit = {
while (serverRunning) {
try {
val s = serverSocket.accept()
s.setTcpNoDelay(true)
val id = synchronized {
nextServerId += 1
nextServerId
}
val in = new BufferedReader(new InputStreamReader(s.getInputStream))
val out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(s.getOutputStream)), true)
val p = ServerPlayer(
id,
s,
in,
out,
"Player" + id,
0.0,
0.0,
0.0,
"N",
0,
false,
id % 6
)
serverPlayers.put(id, p)
out.println("WELCOME|" + id)
sendAllExistingTo(p)
broadcastServer("JOIN|" + id + "|" + p.name, id)
serverPool.submit(new Runnable {
override def run(): Unit = readServerPlayer(p)
})
} catch {
case _: Throwable =>
}
}
}
def sendAllExistingTo(newPlayer: ServerPlayer): Unit = {
val it = serverPlayers.values().iterator()
while (it.hasNext) {
val p = it.next()
if (p.id != newPlayer.id) sendPlayerPacket(newPlayer.output, p)
}
}
def sendPlayerPacket(out: PrintWriter, p: ServerPlayer): Unit = {
out.println(
"P|" + p.id + "|" + p.name + "|" + p.x + "|" + p.distance + "|" +
p.speed + "|" + p.gear + "|" + p.score + "|" + p.race + "|" + p.colorCode
)
}
def readServerPlayer(p: ServerPlayer): Unit = {
try {
var line: String = null
while ({ line = p.input.readLine(); line != null }) {
val d = line.split("\\|", -1)
if (d.nonEmpty && d(0) == "HELLO" && d.length >= 2) {
p.name = cleanName(d(1))
broadcastServer("NAME|" + p.id + "|" + p.name)
}
else if (d.nonEmpty && d(0) == "STATE" && d.length >= 10) {
try {
p.x = d(1).toDouble
p.distance = d(2).toDouble
p.speed = d(3).toDouble
p.gear = d(4)
p.score = d(5).toInt
p.race = d(6).toBoolean
p.colorCode = d(7).toInt
broadcastServer(
"P|" + p.id + "|" + p.name + "|" + p.x + "|" + p.distance + "|" +
p.speed + "|" + p.gear + "|" + p.score + "|" + p.race + "|" + p.colorCode
)
} catch { case _: Throwable => }
}
else if (d.nonEmpty && d(0) == "CHAT" && d.length >= 2) {
broadcastServer("CHAT|" + p.id + "|" + p.name + "|" + d.drop(1).mkString("|"))
}
else if (d.nonEmpty && d(0) == "RACE") {
p.race = true
broadcastServer("RACE|" + p.id + "|" + p.name)
}
}
} catch { case _: Throwable => }
serverPlayers.remove(p.id)
broadcastServer("LEAVE|" + p.id)
try { p.socket.close() } catch { case _: Throwable => }
}
def broadcastServer(msg: String, exceptId: Int = -1): Unit = {
val it = serverPlayers.values().iterator()
while (it.hasNext) {
val p = it.next()
if (p.id != exceptId) {
try { p.output.println(msg) } catch { case _: Throwable => }
}
}
}
// ==========================================================
// CLIENT NETWORK
// ==========================================================
def connectToServer(host: String): Unit = {
disconnectClient()
try {
socket = new Socket()
socket.connect(new InetSocketAddress(host, RealisticDriveOnline_Cockpit.PORT), 4000)
socket.setTcpNoDelay(true)
input = new BufferedReader(new InputStreamReader(socket.getInputStream))
output = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream)), true)
online = true
serverHost = host
val thread = new Thread(new Runnable {
override def run(): Unit = readClient()
}, "DriveClientReader")
thread.setDaemon(true)
thread.start()
} catch {
case _: Throwable =>
online = false
}
}
def readClient(): Unit = {
try {
var line: String = null
while ({ line = input.readLine(); line != null }) {
val d = line.split("\\|", -1)
if (d.nonEmpty && d(0) == "WELCOME" && d.length >= 2) {
try {
playerId = d(1).toInt
if (output != null) output.println("HELLO|" + cleanName(playerName))
} catch { case _: Throwable => }
}
else if (d.nonEmpty && d(0) == "P" && d.length >= 10) {
try {
val id = d(1).toInt
if (id != playerId) {
val rp = RemotePlayer(
cleanName(d(2)),
d(3).toDouble,
d(4).toDouble,
d(5).toDouble,
d(6),
d(7).toInt,
d(8).toBoolean,
d(9).toInt
)
onlinePlayers.synchronized {
onlinePlayers.update(id, rp)
}
}
} catch { case _: Throwable => }
}
else if (d.nonEmpty && d(0) == "NAME" && d.length >= 3) {
try {
val id = d(1).toInt
onlinePlayers.get(id).foreach(p => p.name = cleanName(d(2)))
} catch { case _: Throwable => }
}
else if (d.nonEmpty && d(0) == "JOIN" && d.length >= 3) {
addChat("SERVER: " + cleanName(d(2)) + " joined")
}
else if (d.nonEmpty && d(0) == "LEAVE" && d.length >= 2) {
try {
val id = d(1).toInt
val who = onlinePlayers.get(id).map(_.name).getOrElse("Player")
onlinePlayers.synchronized { onlinePlayers.remove(id) }
addChat("SERVER: " + who + " left")
} catch { case _: Throwable => }
}
else if (d.nonEmpty && d(0) == "CHAT" && d.length >= 4) {
addChat(cleanName(d(2)) + ": " + d.drop(3).mkString("|"))
}
else if (d.nonEmpty && d(0) == "RACE" && d.length >= 3) {
addChat("RACE REQUEST: " + cleanName(d(2)))
raceCountdown = 3
raceStarted = false
}
}
} catch { case _: Throwable => }
online = false
}
def disconnectClient(): Unit = {
online = false
if (socket != null) {
try { socket.close() } catch { case _: Throwable => }
}
socket = null
input = null
output = null
}
def sendHello(): Unit = {
if (online && output != null) {
try { output.println("HELLO|" + cleanName(playerName)) } catch { case _: Throwable => }
}
}
var lastStateSend = 0L
def sendState(): Unit = {
if (!online || output == null) return
val now = System.currentTimeMillis()
if (now - lastStateSend < 60) return
lastStateSend = now
try {
output.println(
"STATE|" + carX + "|" + distance + "|" + speed + "|" + gear + "|" +
score + "|" + raceMode + "|" + (playerId % 6)
)
} catch { case _: Throwable => online = false }
}
// ==========================================================
// ONLINE ACTIONS
// ==========================================================
def sendChatDialog(): Unit = {
val text = JOptionPane.showInputDialog(
this,
"TYPE MESSAGE",
"ONLINE CHAT",
JOptionPane.PLAIN_MESSAGE
)
if (text != null && text.trim.nonEmpty) {
val safe = text.replace("|", " ").replace("\n", " ").take(90)
if (online && output != null) {
try { output.println("CHAT|" + safe) } catch { case _: Throwable => }
} else {
addChat("You: " + safe)
}
}
}
def changeName(): Unit = {
val n = JOptionPane.showInputDialog(
this,
"NEW DRIVER NAME",
playerName
)
if (n != null && n.trim.nonEmpty) {
playerName = cleanName(n.trim)
sendHello()
addChat("YOUR NAME: " + playerName)
}
}
def toggleRace(): Unit = {
raceMode = !raceMode
if (raceMode) {
addChat("RACE MODE: READY")
if (online && output != null) {
try { output.println("RACE") } catch { case _: Throwable => }
}
} else {
addChat("RACE MODE: OFF")
}
}
// ==========================================================
// START MODES
// ==========================================================
def hostAndPlay(): Unit = {
val started = startServer()
if (started) connectToServer("127.0.0.1")
resetGame()
state = GAME
}
def joinOnline(): Unit = {
val host = JOptionPane.showInputDialog(
this,
"ENTER HOST PC IP OR SERVER HOSTNAME",
serverHost
)
if (host != null && host.trim.nonEmpty) {
connectToServer(host.trim)
resetGame()
state = GAME
}
}
def offlinePlay(): Unit = {
disconnectClient()
resetGame()
state = GAME
}
def resetGame(): Unit = {
carX = 0.0
speed = 0.0
rpm = 900.0
steering = 0.0
gear = "N"
fuel = 100.0
damage = 0.0
nitro = 100.0
distance = 0.0
score = 0
roadScroll = 0.0
smoke = 0.0
crashFlash = 0.0
hornFlash = 0.0
raceMode = false
raceCountdown = 0
raceStarted = false
initializeTraffic()
sendHello()
}
def restartGame(): Unit = {
resetGame()
state = GAME
}
// ==========================================================
// TRAFFIC
// ==========================================================
def initializeTraffic(): Unit = {
traffic.clear()
for (i <- 0 until 20) {
traffic += Traffic(
rnd.nextInt(3) - 1,
0.22 + i * 0.075 + rnd.nextDouble() * 0.10,
0.25 + rnd.nextDouble() * 0.75,
rnd.nextInt(3),
randomColor()
)
}
}
def randomColor(): Color = {
val c = Array(
new Color(220, 45, 45),
new Color(40, 125, 230),
new Color(245, 205, 50),
new Color(235, 235, 235),
new Color(45, 48, 55),
new Color(40, 175, 100),
new Color(180, 70, 185),
new Color(235, 120, 35)
)
c(rnd.nextInt(c.length))
}
// ==========================================================
// GAME LOOP
// ==========================================================
val timer = new Timer(
16,
new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
updateGame(0.016)
repaint()
}
}
)
timer.start()
def updateGame(dt: Double): Unit = {
if (state != GAME) return
val left = keys.contains(KeyEvent.VK_LEFT) || keys.contains(KeyEvent.VK_A)
val right = keys.contains(KeyEvent.VK_RIGHT) || keys.contains(KeyEvent.VK_D)
val accel = keys.contains(KeyEvent.VK_UP) || keys.contains(KeyEvent.VK_W)
val brake = keys.contains(KeyEvent.VK_DOWN) || keys.contains(KeyEvent.VK_S) || keys.contains(KeyEvent.VK_SPACE)
val nitroPress = keys.contains(KeyEvent.VK_E)
val horn = keys.contains(KeyEvent.VK_Q)
// ------------------------------------------------------
// STEERING
// ------------------------------------------------------
var targetSteer = 0.0
if (left && !right) targetSteer = -1.0
else if (right && !left) targetSteer = 1.0
steering += (targetSteer - steering) * 0.15
carX += steering * (5.0 + Math.abs(speed) * 0.028)
if (carX < -315) carX = -315
if (carX > 315) carX = 315
// ------------------------------------------------------
// ENGINE / GEARS
// ------------------------------------------------------
var ratio = 0.0
if (gear == "1") ratio = 2.70
else if (gear == "2") ratio = 1.95
else if (gear == "3") ratio = 1.40
else if (gear == "4") ratio = 1.00
else if (gear == "5") ratio = 0.78
if (gear == "R") {
if (accel) speed -= 90.0 * dt
} else if (gear == "N") {
speed *= 0.997
} else if (accel) {
speed += 68.0 * ratio * dt
}
// ------------------------------------------------------
// NITRO
// ------------------------------------------------------
if (nitroPress && nitro > 0 && speed > 25) {
speed += 140.0 * dt
nitro -= 30.0 * dt
} else {
nitro += 6.0 * dt
}
if (nitro < 0) nitro = 0
if (nitro > 100) nitro = 100
// ------------------------------------------------------
// BRAKES
// ------------------------------------------------------
if (brake) {
speed -= 140.0 * dt
speed *= 0.973
if (speed < 0) speed = 0
}
speed -= speed * 0.012 * dt
if (speed > 250) speed = 250
if (speed < -70) speed = -70
// ------------------------------------------------------
// RPM
// ------------------------------------------------------
if (gear == "N") {
if (accel) rpm = 2800 else rpm = 850
} else {
var rMult = 19.0
if (gear == "1") rMult = 48.0
else if (gear == "2") rMult = 34.0
else if (gear == "3") rMult = 27.0
rpm = 900 + Math.abs(speed) * rMult
if (rpm > 7000) rpm = 7000
}
// ------------------------------------------------------
// FUEL
// ------------------------------------------------------
if (accel) fuel -= 0.008 + Math.abs(speed) * 0.000022
if (fuel < 0) fuel = 0
if (fuel == 0) {
gear = "N"
speed *= 0.99
}
// ------------------------------------------------------
// WORLD / MISSION
// ------------------------------------------------------
roadScroll += speed * dt * 0.45
if (speed > 0) {
distance += speed * dt * 0.90
score += (speed * dt * 0.085).toInt
}
// ------------------------------------------------------
// TRAFFIC
// ------------------------------------------------------
for (t <- traffic) {
val relative = Math.abs(speed) * 0.0021 - t.aiSpeed * 0.00115
t.z -= relative * dt * 15.0
if (t.z < 0.06 || t.z > 1.5) {
t.lane = rnd.nextInt(3) - 1
t.z = 1.10 + rnd.nextDouble() * 0.38
t.aiSpeed = 0.25 + rnd.nextDouble() * 0.75
t.kind = rnd.nextInt(3)
t.color = randomColor()
}
}
// ------------------------------------------------------
// COLLISION
// ------------------------------------------------------
for (t <- traffic) {
if (t.z > 0.13 && t.z < 0.29) {
val tx = t.lane * 205.0
if (Math.abs(carX - tx) < 76 && Math.abs(speed) > 22) {
damage += 17 + Math.abs(speed) * 0.08
score = Math.max(0, score - 70)
speed *= 0.28
smoke = 1.0
crashFlash = 1.0
t.z = 1.35
if (damage >= 100) {
damage = 100
state = GAME_OVER
}
}
}
}
// ------------------------------------------------------
// EFFECTS / RACE
// ------------------------------------------------------
smoke *= 0.965
crashFlash *= 0.90
hornFlash *= 0.90
messageTimer -= dt
if (horn) hornFlash = 1.0
if (raceCountdown > 0 && online) {
raceCountdown -= 1
if (raceCountdown <= 0) raceStarted = true
}
if (distance >= targetDistance) state = FINISH
sendState()
}
// ==========================================================
// PAINT
// ==========================================================
override def paintComponent(g0: Graphics): Unit = {
super.paintComponent(g0)
val g = g0.asInstanceOf[Graphics2D]
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED)
if (state == MENU) drawMenu(g)
else {
drawSky(g)
drawEnvironment(g)
drawRoad(g)
for (t <- traffic.sortBy(_.z).reverse) drawTraffic(g, t)
drawOnlinePlayers(g)
if (headlights) drawHeadlights(g)
if (cockpit) drawPlayerCockpit(g) else drawPlayerExterior(g)
drawTopHud(g)
drawDashboard(g)
if (mapVisible) drawMap(g)
if (showChat) drawChat(g)
if (smoke > 0.08) drawSmoke(g)
if (crashFlash > 0.02) drawCrashFlash(g)
if (hornFlash > 0.1) drawHorn(g)
if (state == PAUSE) drawPause(g)
if (state == GAME_OVER) drawGameOver(g)
if (state == FINISH) drawFinish(g)
}
}
// ==========================================================
// MENU
// ==========================================================
def drawMenu(g: Graphics2D): Unit = {
val bg = new GradientPaint(0, 0, new Color(5, 10, 18), 0, 820, new Color(18, 55, 67))
g.setPaint(bg)
g.fillRect(0, 0, 1280, 820)
g.setColor(new Color(225, 232, 242))
for (i <- 0 until 70) {
val sx = (i * 79) % 1280
val sy = 20 + (i * 41) % 300
g.fillOval(sx, sy, 2, 2)
}
g.setColor(new Color(240, 240, 225))
g.fillOval(1030, 50, 90, 90)
g.setColor(new Color(0, 235, 255))
g.setFont(new Font("Arial", Font.BOLD, 58))
g.drawString("REALISTIC", 385, 145)
g.setColor(new Color(255, 65, 50))
g.drawString("DRIVE", 625, 145)
g.setColor(Color.WHITE)
g.setFont(new Font("Arial", Font.PLAIN, 18))
g.drawString("ONLINE COCKPIT MULTIPLAYER", 468, 184)
drawButton(g, 390, 235, 500, 62, "ENTER / HOST + PLAY")
drawButton(g, 390, 315, 500, 62, "C / JOIN ONLINE SERVER")
drawButton(g, 390, 395, 500, 62, "O / OFFLINE DRIVE")
g.setColor(new Color(0, 240, 170))
g.setFont(new Font("Arial", Font.BOLD, 17))
g.drawString("DRIVER NAME: " + playerName, 500, 515)
g.setColor(new Color(190, 205, 215))
g.setFont(new Font("Arial", Font.PLAIN, 13))
g.drawString("Online players see your driver name above your car.", 455, 550)
g.drawString("C = chat F = race request P = change name", 468, 578)
g.drawString("A/D or arrows = steering W/UP = accelerator S/DOWN/SPACE = brake", 395, 610)
g.drawString("R = reverse N = neutral 1-5 = gears E = nitro H = lights", 435, 634)
}
def drawButton(g: Graphics2D, x: Int, y: Int, w: Int, h: Int, text: String): Unit = {
g.setColor(new Color(10, 28, 39, 235))
g.fillRoundRect(x, y, w, h, 18, 18)
g.setColor(new Color(0, 220, 240))
g.setStroke(new BasicStroke(2))
g.drawRoundRect(x, y, w, h, 18, 18)
g.setColor(Color.WHITE)
g.setFont(new Font("Arial", Font.BOLD, 18))
g.drawString(text, x + 120, y + 40)
}
// ==========================================================
// SKY
// ==========================================================
def drawSky(g: Graphics2D): Unit = {
val sky = new GradientPaint(0, 0, new Color(14, 28, 50), 0, 350, new Color(105, 140, 160))
g.setPaint(sky)
g.fillRect(0, 0, 1280, 350)
g.setColor(new Color(240, 240, 225))
g.fillOval(1045, 58, 68, 68)
drawCloud(g, 100, 90, 1.0)
drawCloud(g, 540, 120, 0.8)
drawCloud(g, 820, 90, 1.1)
}
def drawCloud(g: Graphics2D, x: Int, y: Int, s: Double): Unit = {
g.setColor(new Color(215, 225, 230, 110))
g.fillOval(x, y, (80 * s).toInt, (35 * s).toInt)
g.fillOval(x + (30 * s).toInt, y - (20 * s).toInt, (65 * s).toInt, (44 * s).toInt)
g.fillOval(x + (68 * s).toInt, y, (74 * s).toInt, (35 * s).toInt)
}
// ==========================================================
// ENVIRONMENT
// ==========================================================
def drawEnvironment(g: Graphics2D): Unit = {
g.setColor(new Color(29, 82, 40))
g.fillRect(0, 340, 1280, 480)
val hills = new Polygon()
hills.addPoint(0, 360)
hills.addPoint(150, 300)
hills.addPoint(300, 350)
hills.addPoint(470, 295)
hills.addPoint(650, 355)
hills.addPoint(810, 298)
hills.addPoint(1010, 350)
hills.addPoint(1200, 300)
hills.addPoint(1280, 375)
hills.addPoint(1280, 440)
hills.addPoint(0, 440)
g.setColor(new Color(42, 75, 52))
g.fillPolygon(hills)
for (i <- 0 until 13) {
drawTree(g, 25 + i * 105, 300 + (i % 3) * 12, 0.70)
drawTree(g, 1255 - i * 105, 300 + (i % 2) * 15, 0.68)
}
}
def drawTree(g: Graphics2D, x: Int, y: Int, s: Double): Unit = {
g.setColor(new Color(90, 60, 35))
g.fillRect(x - 6, y, 12, 58)
g.setColor(new Color(28, 103, 42))
g.fillOval(x - (50 * s).toInt, y - (68 * s).toInt, (100 * s).toInt, (88 * s).toInt)
g.setColor(new Color(38, 124, 50))
g.fillOval(x - (30 * s).toInt, y - (78 * s).toInt, (65 * s).toInt, (65 * s).toInt)
}
// ==========================================================
// ROAD
// ==========================================================
def drawRoad(g: Graphics2D): Unit = {
val horizon = 340
val road = new Polygon()
road.addPoint(500, horizon)
road.addPoint(780, horizon)
road.addPoint(1240, 760)
road.addPoint(40, 760)
g.setColor(new Color(48, 49, 53))
g.fillPolygon(road)
g.setColor(new Color(190, 190, 190))
val ls = new Polygon()
ls.addPoint(488, horizon)
ls.addPoint(500, horizon)
ls.addPoint(40, 760)
ls.addPoint(0, 760)
g.fillPolygon(ls)
val rs = new Polygon()
rs.addPoint(780, horizon)
rs.addPoint(792, horizon)
rs.addPoint(1280, 760)
rs.addPoint(1240, 760)
g.fillPolygon(rs)
g.setColor(Color.WHITE)
g.setStroke(new BasicStroke(5))
g.drawLine(500, horizon, 40, 760)
g.drawLine(780, horizon, 1240, 760)
var y = horizon + 5 - (roadScroll.toInt % 92)
while (y < 760) {
val p = Math.max(0.0, Math.min(1.0, (y - horizon).toDouble / (760 - horizon)))
val left = 500 - 460 * p
val right = 780 + 460 * p
val laneW = (right - left) / 3.0
g.setColor(new Color(250, 220, 75))
g.setStroke(new BasicStroke(Math.max(3.0f, (2.0 + p * 7.0).toFloat)))
for (lane <- 1 to 2) {
val lx = (left + laneW * lane).toInt
g.drawLine(lx, y, lx, Math.min(760, y + 20 + (p * 85).toInt))
}
y += 58 + (p * 100).toInt
}
}
// ==========================================================
// TRAFFIC DRAW
// ==========================================================
def drawTraffic(g: Graphics2D, t: Traffic): Unit = {
val z = Math.max(0.17, t.z)
val scale = 1.0 / z
val cx = (640 + t.lane * 205 * scale).toInt
val cy = (340 + (1.20 - z) * 330).toInt
val w = Math.max(20, (56 * scale).toInt)
val h = Math.max(30, (108 * scale).toInt)
g.setColor(new Color(0, 0, 0, 80))
g.fillOval(cx - w / 2, cy + h / 2, w + 24, 14)
g.setColor(Color.BLACK)
g.fillRoundRect(cx - w / 2, cy - h / 2, w, h, 12, 12)
g.setColor(t.color)
g.fillRoundRect(cx - w / 2 + 3, cy - h / 2 + 3, w - 6, h - 6, 10, 10)
g.setColor(new Color(55, 82, 100))
if (t.kind == 2) {
for (i <- 0 until 4) {
g.fillRect(cx - w / 2 + 7, cy - h / 3 + i * Math.max(8, h / 8), w - 14, Math.max(5, h / 12))
}
} else {
g.fillRoundRect(cx - w / 2 + 7, cy - h / 4, w - 14, h / 4, 6, 6)
}
g.setColor(new Color(255, 35, 35))
g.fillRect(cx - w / 2 + 6, cy + h / 2 - 15, 9, 7)
g.fillRect(cx + w / 2 - 15, cy + h / 2 - 15, 9, 7)
}
// ==========================================================
// ONLINE PLAYERS DRAW
// ==========================================================
def drawOnlinePlayers(g: Graphics2D): Unit = {
val players = onlinePlayers.synchronized { onlinePlayers.toArray }
for (entry <- players) {
val id = entry._1
val p = entry._2
val delta = p.distance - distance
if (Math.abs(delta) < 850) {
val relative = Math.max(0.13, Math.min(1.2, 0.72 - delta / 850.0))
val scale = 1.0 / Math.max(0.18, relative)
val cx = (640 + p.x * scale).toInt
val cy = (340 + (1.15 - relative) * 390).toInt
val w = Math.max(22, (64 * scale).toInt)
val h = Math.max(35, (118 * scale).toInt)
g.setColor(new Color(0, 240, 255, 40))
g.fillOval(cx - w, cy - h / 2 - 12, w * 2, h + 24)
g.setColor(Color.BLACK)
g.fillRoundRect(cx - w / 2, cy - h / 2, w, h, 14, 14)
g.setColor(playerColor(p.colorCode))
g.fillRoundRect(cx - w / 2 + 3, cy - h / 2 + 3, w - 6, h - 6, 11, 11)
g.setColor(new Color(55, 88, 110))
g.fillRoundRect(cx - w / 2 + 8, cy - h / 4, w - 16, h / 4, 7, 7)
g.setColor(new Color(0, 245, 255))
g.setFont(new Font("Arial", Font.BOLD, Math.max(8, (12 * scale).toInt)))
g.drawString(p.name, cx - w / 2, cy - h / 2 - 8)
if (p.race) {
g.setColor(new Color(255, 220, 50))
g.setFont(new Font("Arial", Font.BOLD, Math.max(8, (10 * scale).toInt)))
g.drawString("RACE", cx - w / 2, cy + h / 2 + 12)
}
}
}
}
def playerColor(code: Int): Color = {
val colors = Array(
new Color(35, 160, 235),
new Color(255, 90, 45),
new Color(70, 210, 120),
new Color(245, 210, 45),
new Color(185, 80, 220),
new Color(70, 180, 235)
)
colors(Math.abs(code) % colors.length)
}
// ==========================================================
// EXTERIOR CAR
// ==========================================================
def drawPlayerExterior(g: Graphics2D): Unit = {
val cx = (640 + carX).toInt
val cy = 585
g.setColor(new Color(0, 0, 0, 120))
g.fillOval(cx - 110, cy + 75, 220, 42)
g.setColor(new Color(15, 15, 17))
g.fillRoundRect(cx - 88, cy - 5, 30, 95, 12, 12)
g.fillRoundRect(cx + 58, cy - 5, 30, 95, 12, 12)
g.setColor(new Color(205, 26, 31))
g.fillRoundRect(cx - 82, cy - 84, 164, 190, 36, 36)
val roof = new Polygon()
roof.addPoint(cx - 67, cy - 72)
roof.addPoint(cx - 43, cy - 145)
roof.addPoint(cx + 43, cy - 145)
roof.addPoint(cx + 67, cy - 72)
g.setColor(new Color(27, 42, 56))
g.fillPolygon(roof)
val glass = new Polygon()
glass.addPoint(cx - 51, cy - 78)
glass.addPoint(cx - 33, cy - 132)
glass.addPoint(cx + 33, cy - 132)
glass.addPoint(cx + 51, cy - 78)
g.setColor(new Color(76, 115, 138))
g.fillPolygon(glass)
g.setColor(Color.WHITE)
g.fillRect(cx - 5, cy - 70, 10, 168)
g.setColor(new Color(160, 15, 20))
g.fillRoundRect(cx - 70, cy - 24, 140, 62, 19, 19)
g.setColor(new Color(255, 250, 210))
g.fillRoundRect(cx - 64, cy - 39, 35, 18, 7, 7)
g.fillRoundRect(cx + 29, cy - 39, 35, 18, 7, 7)
g.setColor(new Color(45, 45, 48))
g.fillRoundRect(cx - 36, cy + 39, 72, 15, 5, 5)
}
// ==========================================================
// COCKPIT VIEW
// ==========================================================
def drawPlayerCockpit(g: Graphics2D): Unit = {
// lower dashboard frame
g.setColor(new Color(12, 14, 17, 245))
g.fillRect(0, 520, 1280, 300)
// windshield frame
g.setColor(new Color(20, 23, 27))
g.fillRect(0, 0, 54, 530)
g.fillRect(1226, 0, 54, 530)
g.fillRect(0, 0, 1280, 28)
// A-pillars
g.setColor(new Color(30, 32, 35))
g.fillPolygon(Array(0, 0, 75, 0, 170, 535, 0, 535), Array(0, 0, 535, 535), 4)
g.fillPolygon(Array(1280, 0, 1205, 0, 1110, 535, 1280, 535), Array(0, 0, 535, 535), 4)
// windshield reflection
g.setColor(new Color(220, 240, 250, 18))
val reflection = new Polygon()
reflection.addPoint(120, 70)
reflection.addPoint(260, 70)
reflection.addPoint(500, 500)
reflection.addPoint(430, 500)
g.fillPolygon(reflection)
// steering wheel
val wheelX = 640
val wheelY = 670
g.setColor(new Color(25, 25, 27))
g.fillOval(wheelX - 105, wheelY - 105, 210, 210)
g.setColor(new Color(72, 72, 75))
g.setStroke(new BasicStroke(12))
g.drawOval(wheelX - 88, wheelY - 88, 176, 176)
val a = steering * Math.toRadians(65)
val spokeX = wheelX + (Math.sin(a) * 88).toInt
val spokeY = wheelY - (Math.cos(a) * 88).toInt
g.setColor(new Color(110, 110, 115))
g.setStroke(new BasicStroke(7))
g.drawLine(wheelX, wheelY, spokeX, spokeY)
g.drawLine(wheelX, wheelY, wheelX - 70, wheelY + 45)
g.drawLine(wheelX, wheelY, wheelX + 70, wheelY + 45)
g.setColor(new Color(18, 18, 20))
g.fillOval(wheelX - 18, wheelY - 18, 36, 36)
// digital center screen
g.setColor(new Color(5, 8, 11))
g.fillRoundRect(505, 555, 270, 108, 18, 18)
g.setColor(new Color(0, 235, 190))
g.setFont(new Font("Arial", Font.BOLD, 13))
g.drawString("DIGITAL COCKPIT", 585, 577)
g.setColor(Color.WHITE)
g.setFont(new Font("Arial", Font.BOLD, 28))
g.drawString(Math.abs(speed).toInt + "", 565, 615)
g.setFont(new Font("Arial", Font.PLAIN, 11))
g.drawString("KM/H", 630, 615)
g.setColor(if (gear == "R") new Color(255, 70, 70) else new Color(0, 245, 190))
g.setFont(new Font("Arial", Font.BOLD, 24))
g.drawString("GEAR " + gear, 650, 648)
// driver head + body
g.setColor(new Color(222, 180, 148))
g.fillOval(165, 560, 45, 45)
g.setColor(new Color(40, 75, 130))
g.fillRoundRect(155, 600, 65, 90, 18, 18)
// seat belt
g.setColor(new Color(225, 215, 170))
g.setStroke(new BasicStroke(5))
g.drawLine(180, 607, 216, 690)
g.setColor(Color.WHITE)
g.setFont(new Font("Arial", Font.BOLD, 11))
g.drawString(playerName, 145, 716)
g.setFont(new Font("Arial", Font.PLAIN, 9))
g.drawString("DRIVER / SEATBELT", 137, 732)
// accelerator and brake pedals
g.setColor(new Color(28, 30, 33))
g.fillRoundRect(860, 650, 76, 115, 15, 15)
g.fillRoundRect(950, 650, 76, 115, 15, 15)
val accel = keys.contains(KeyEvent.VK_UP) || keys.contains(KeyEvent.VK_W)
val brake = keys.contains(KeyEvent.VK_DOWN) || keys.contains(KeyEvent.VK_S) || keys.contains(KeyEvent.VK_SPACE)
g.setColor(if (accel) new Color(0, 230, 160) else new Color(75, 80, 85))
g.fillRoundRect(872, 664, 52, 88, 10, 10)
g.setColor(if (brake) new Color(255, 55, 55) else new Color(75, 80, 85))
g.fillRoundRect(962, 664, 52, 88, 10, 10)
g.setColor(Color.WHITE)
g.setFont(new Font("Arial", Font.BOLD, 10))
g.drawString("ACCEL", 878, 785)
g.drawString("BRAKE", 968, 785)
// hand / dashboard indicator strips
drawDashboardStrip(g, 95, 548, "FUEL", fuel, new Color(0, 230, 160))
drawDashboardStrip(g, 95, 620, "DAMAGE", damage, new Color(255, 60, 60))
drawDashboardStrip(g, 1060, 548, "NITRO", nitro, new Color(0, 220, 255))
drawDashboardStrip(g, 1060, 620, "RPM", rpm / 70.0, new Color(255, 170, 60))
// small mirror
g.setColor(new Color(18, 21, 24))
g.fillRoundRect(565, 38, 150, 52, 18, 18)
g.setColor(new Color(70, 95, 110))
g.fillRoundRect(575, 48, 130, 32, 12, 12)
g.setColor(new Color(200, 220, 230, 70))
g.fillRect(600, 50, 25, 28)
// cockpit controls line
g.setColor(new Color(200, 210, 220))
g.setFont(new Font("Arial", Font.PLAIN, 10))
g.drawString("P NAME C CHAT F RACE M MAP H LIGHT ESC PAUSE", 430, 806)
}
def drawDashboardStrip(g: Graphics2D, x: Int, y: Int, label: String, value: Double, c: Color): Unit = {
val safe = Math.max(0.0, Math.min(100.0, value))
g.setColor(new Color(45, 48, 52))
g.fillRoundRect(x, y, 150, 17, 7, 7)
g.setColor(c)
g.fillRoundRect(x, y, (150 * safe / 100.0).toInt, 17, 7, 7)
g.setColor(Color.WHITE)
g.setFont(new Font("Arial", Font.BOLD, 10))
g.drawString(label + " " + safe.toInt + "%", x, y - 6)
}
// ==========================================================
// HEADLIGHTS
// ==========================================================
def drawHeadlights(g: Graphics2D): Unit = {
val left = new Polygon()
left.addPoint(110, 500)
left.addPoint(310, 500)
left.addPoint(450, 340)
left.addPoint(250, 340)
val right = new Polygon()
right.addPoint(970, 500)
right.addPoint(1170, 500)
right.addPoint(1030, 340)
right.addPoint(830, 340)
g.setColor(new Color(255, 247, 175, 45))
g.fillPolygon(left)
g.fillPolygon(right)
}
// ==========================================================
// TOP HUD
// ==========================================================
def drawTopHud(g: Graphics2D): Unit = {
g.setColor(new Color(5, 8, 13, 230))
g.fillRect(0, 0, 440, 92)
g.setColor(new Color(0, 235, 255))
g.setFont(new Font("Arial", Font.BOLD, 18))
g.drawString("ONLINE DRIVE", 18, 25)
g.setColor(if (online) new Color(0, 245, 160) else new Color(255, 75, 70))
g.setFont(new Font("Arial", Font.BOLD, 11))
g.drawString(if (online) "? ONLINE" else "? OFFLINE", 18, 47)
g.setColor(Color.WHITE)
g.drawString("DRIVER: " + playerName, 95, 47)
g.drawString("ID: " + playerId, 95, 64)
g.setFont(new Font("Arial", Font.BOLD, 13))
g.drawString("SPEED " + Math.abs(speed).toInt + " KM/H", 225, 25)
g.drawString("SCORE " + score, 350, 25)
g.drawString("PLAYERS " + onlinePlayers.synchronized { onlinePlayers.size }, 225, 47)
g.drawString("DIST " + distance.toInt + " M", 350, 47)
val progress = Math.min(1.0, distance / targetDistance)
g.setColor(new Color(45, 50, 55))
g.fillRect(225, 67, 180, 8)
g.setColor(new Color(0, 230, 160))
g.fillRect(225, 67, (180 * progress).toInt, 8)
}
// ==========================================================
// MAP
// ==========================================================
def drawMap(g: Graphics2D): Unit = {
val x = 1000
val y = 95
val w = 235
val h = 185
g.setColor(new Color(5, 9, 14, 235))
g.fillRoundRect(x, y, w, h, 18, 18)
g.setColor(new Color(0, 230, 255))
g.setFont(new Font("Arial", Font.BOLD, 13))
g.drawString("LIVE GPS / RACE", x + 15, y + 23)
g.setColor(new Color(75, 80, 85))
g.setStroke(new BasicStroke(23, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND))
g.drawLine(x + 118, y + 45, x + 118, y + 145)
g.setColor(new Color(0, 240, 160))
g.setStroke(new BasicStroke(3))
g.drawLine(x + 118, y + 45, x + 118, y + 145)
g.setColor(new Color(255, 60, 60))
g.fillOval(x + 110, y + 138, 16, 16)
val progress = Math.min(1.0, distance / targetDistance)
val py = (y + 45 + progress * 100).toInt
g.setColor(new Color(255, 235, 50))
g.fillOval(x + 112, py - 5, 11, 11)
g.setColor(new Color(0, 235, 255))
g.fillOval(x + 110, y + 90, 10, 10)
g.setColor(Color.WHITE)
g.setFont(new Font("Arial", Font.PLAIN, 9))
g.drawString("YOU", x + 138, y + 67)
g.drawString("ONLINE: " + onlinePlayers.synchronized { onlinePlayers.size }, x + 15, y + 170)
}
// ==========================================================
// CHAT
// ==========================================================
def drawChat(g: Graphics2D): Unit = {
val x = 15
val y = 115
val w = 315
val h = 210
g.setColor(new Color(4, 8, 13, 190))
g.fillRoundRect(x, y, w, h, 15, 15)
g.setColor(new Color(0, 230, 255))
g.setFont(new Font("Arial", Font.BOLD, 12))
g.drawString("ONLINE CHAT [C]", x + 12, y + 22)
val copy = synchronized { chatLines.toArray }
g.setColor(Color.WHITE)
g.setFont(new Font("Arial", Font.PLAIN, 10))
var yy = y + 43
for (line <- copy) {
g.drawString(line.take(47), x + 12, yy)
yy += 19
if (yy > y + h - 10) yy = y + h - 10
}
}
// ==========================================================
// DASHBOARD
// ==========================================================
def drawDashboard(g: Graphics2D): Unit = {
// RPM/speed gauge left/right edges, plus status
drawGauge(g, 110, 410, 72, Math.abs(speed), 250, "SPEED")
drawGauge(g, 1170, 410, 72, rpm / 1000.0, 7, "RPM")
g.setColor(new Color(5, 8, 11, 240))
g.fillRoundRect(470, 704, 340, 68, 20, 20)
g.setColor(new Color(0, 230, 190))
g.setFont(new Font("Arial", Font.BOLD, 12))
g.drawString("TRANSMISSION", 580, 725)
g.setColor(if (gear == "R") new Color(255, 60, 60) else new Color(0, 245, 190))
g.setFont(new Font("Arial", Font.BOLD, 35))
g.drawString(gear, 625, 758)
g.setColor(Color.WHITE)
g.setFont(new Font("Arial", Font.BOLD, 10))
g.drawString("R REVERSE N NEUTRAL 1-5 DRIVE", 535, 788)
// race badge
if (raceMode) {
g.setColor(new Color(255, 210, 50))
g.fillRoundRect(510, 95, 260, 35, 12, 12)
g.setColor(Color.BLACK)
g.setFont(new Font("Arial", Font.BOLD, 14))
g.drawString(if (raceStarted) "RACE LIVE" else "RACE READY", 585, 118)
}
// name panel
g.setColor(new Color(5, 8, 11, 220))
g.fillRoundRect(1040, 305, 195, 60, 12, 12)
g.setColor(new Color(0, 235, 255))
g.setFont(new Font("Arial", Font.BOLD, 10))
g.drawString("DRIVER", 1055, 323)
g.setColor(Color.WHITE)
g.drawString(playerName, 1055, 344)
g.drawString("ONLINE " + onlinePlayers.synchronized { onlinePlayers.size }, 1055, 359)
}
def drawGauge(g: Graphics2D, cx: Int, cy: Int, r: Int, value: Double, max: Double, title: String): Unit = {
g.setColor(new Color(5, 7, 9, 235))
g.fillOval(cx - r, cy - r, 2 * r, 2 * r)
g.setColor(new Color(105, 110, 115))
g.setStroke(new BasicStroke(3))
g.drawOval(cx - r, cy - r, 2 * r, 2 * r)
for (i <- 0 to 10) {
val a = Math.toRadians(-135 + i * 27)
val x1 = cx + (Math.cos(a) * (r - 7)).toInt
val y1 = cy + (Math.sin(a) * (r - 7)).toInt
val x2 = cx + (Math.cos(a) * (r - 16)).toInt
val y2 = cy + (Math.sin(a) * (r - 16)).toInt
g.setColor(Color.WHITE)
g.drawLine(x1, y1, x2, y2)
}
val safe = Math.max(0.0, Math.min(max, value))
val a = Math.toRadians(-135 + safe / max * 270.0)
val px = cx + (Math.cos(a) * (r - 22)).toInt
val py = cy + (Math.sin(a) * (r - 22)).toInt
g.setColor(new Color(255, 80, 55))
g.setStroke(new BasicStroke(4))
g.drawLine(cx, cy, px, py)
g.setColor(Color.WHITE)
g.fillOval(cx - 4, cy - 4, 8, 8)
g.setFont(new Font("Arial", Font.BOLD, 10))
g.drawString(title, cx - 23, cy + 40)
}
// ==========================================================
// PAUSE / END
// ==========================================================
def drawPause(g: Graphics2D): Unit = {
g.setColor(new Color(0, 0, 0, 175))
g.fillRect(0, 0, 1280, 820)
g.setColor(Color.WHITE)
g.setFont(new Font("Arial", Font.BOLD, 56))
g.drawString("PAUSED", 520, 325)
g.setFont(new Font("Arial", Font.PLAIN, 20))
g.drawString("PRESS ESC TO CONTINUE", 480, 370)
}
def drawGameOver(g: Graphics2D): Unit = {
g.setColor(new Color(0, 0, 0, 185))
g.fillRect(0, 0, 1280, 820)
g.setColor(new Color(255, 45, 45))
g.setFont(new Font("Arial", Font.BOLD, 52))
g.drawString("VEHICLE DESTROYED", 395, 310)
g.setColor(Color.WHITE)
g.setFont(new Font("Arial", Font.BOLD, 21))
g.drawString("SCORE : " + score, 535, 365)
g.drawString("DISTANCE : " + distance.toInt + " M", 500, 400)
g.setColor(new Color(0, 240, 180))
g.drawString("PRESS ENTER TO RESTART", 465, 450)
}
def drawFinish(g: Graphics2D): Unit = {
g.setColor(new Color(0, 25, 17, 185))
g.fillRect(0, 0, 1280, 820)
g.setColor(new Color(0, 255, 175))
g.setFont(new Font("Arial", Font.BOLD, 50))
g.drawString("MISSION COMPLETE!", 425, 310)
g.setColor(Color.WHITE)
g.setFont(new Font("Arial", Font.BOLD, 22))
g.drawString("FINAL SCORE : " + score, 510, 365)
g.setColor(new Color(0, 240, 180))
g.drawString("PRESS ENTER FOR NEW DRIVE", 450, 440)
}
// ==========================================================
// EFFECTS
// ==========================================================
def drawSmoke(g: Graphics2D): Unit = {
val cx = (640 + carX).toInt
for (i <- 0 until 7) {
val alpha = Math.max(15, 95 - i * 11)
g.setColor(new Color(210, 210, 210, alpha))
val s = 20 + i * 7
g.fillOval(cx - 40 + rnd.nextInt(60), 620 - i * 13, s, s)
}
}
def drawCrashFlash(g: Graphics2D): Unit = {
g.setColor(new Color(255, 50, 30, (crashFlash * 120).toInt))
g.fillRect(0, 0, 1280, 650)
}
def drawHorn(g: Graphics2D): Unit = {
g.setColor(new Color(255, 230, 80, (hornFlash * 120).toInt))
g.setFont(new Font("Arial", Font.BOLD, 22))
g.drawString("HORN", 610, 505)
}
}