Code Sketch


yooooiiiiiii
By: Mhalsakant School
Category: Programming
import javax.swing.JFrame
import javax.swing.JPanel
import javax.swing.JButton
import javax.swing.JLabel
import javax.swing.JTextField
import javax.swing.JTextArea
import javax.swing.JScrollPane
import javax.swing.JOptionPane
import javax.swing.WindowConstants
import javax.swing.SwingUtilities
import javax.swing.BoxLayout
import javax.swing.BorderFactory
import javax.swing.Timer

import java.awt.Graphics
import java.awt.Graphics2D
import java.awt.Color
import java.awt.Font
import java.awt.BasicStroke
import java.awt.GradientPaint
import java.awt.Dimension
import java.awt.RenderingHints

import java.awt.event.KeyAdapter
import java.awt.event.KeyEvent
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.event.ActionListener

import java.net.ServerSocket
import java.net.Socket
import java.net.InetAddress

import java.io.PrintWriter
import java.io.BufferedReader
import java.io.InputStreamReader

import javax.sound.sampled.AudioFormat
import javax.sound.sampled.AudioSystem

import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.Set
import scala.util.Try


/* =========================================================
   REAL DRIVE X
   FULL SINGLE FILE
   ========================================================= */


/* =========================================================
   MAIN WINDOW
   ========================================================= */

var frame: JFrame = null
var root: JPanel = null
var cards: java.awt.CardLayout = null

var menuPanel: JPanel = null
var lobbyPanel: JPanel = null
var gamePanel: DrivePanel = null


/* =========================================================
   LOBBY UI
   ========================================================= */

var lobbyNameField: JTextField = null
var lobbyHostField: JTextField = null
var lobbyPortField: JTextField = null
var lobbyChatField: JTextField = null

var lobbyStatusLabel: JLabel = null
var lobbyPlayersLabel: JLabel = null
var lobbyModeLabel: JLabel = null
var lobbyChatArea: JTextArea = null


/* =========================================================
   SCREEN STATE
   ========================================================= */

var currentScreen = "menu"

var online = false
var multiplayer = false
var soloOnline = false

var ready = false
var raceStarted = false
var matchmaking = false

var playerName = "Driver"

var lastHost = "127.0.0.1"
var lastPort = 5555


/* =========================================================
   SERVER
   ========================================================= */

var serverSocket: ServerSocket = null
var serverRunning = false
var actualServerPort = 5555


case class ServerClient(
  id: String,
  name: String,
  socket: Socket,
  out: PrintWriter,
  var x: Double,
  var distance: Double,
  var speed: Double,
  var gear: String,
  var mode: String,
  var ready: Boolean
)

val serverClients =
  ArrayBuffer[ServerClient]()


/* =========================================================
   CLIENT
   ========================================================= */

var clientSocket: Socket = null
var clientOut: PrintWriter = null
var clientIn: BufferedReader = null

var clientConnected = false
var clientId = "P0"


case class RemotePlayer(
  id: String,
  name: String,
  var x: Double,
  var distance: Double,
  var speed: Double,
  var gear: String,
  var mode: String,
  var ready: Boolean
)

val remotePlayers =
  ArrayBuffer[RemotePlayer]()


/* =========================================================
   TRAFFIC
   ========================================================= */

case class TrafficCar(
  var lane: Int,
  var z: Double,
  var speed: Double,
  var kind: String,
  var police: Boolean,
  var phase: Double
)

val traffic =
  ArrayBuffer[TrafficCar]()


/* =========================================================
   KEY INPUT
   ========================================================= */

val keys =
  Set[Int]()


/* =========================================================
   GAME VARIABLES
   ========================================================= */

var gameDistance = 0.0
var gameSpeed = 0.0

var fuel = 100.0
var damage = 0.0

var score = 0

var missionNumber = 1
var missionProgress = 0
var missionTarget = 1000.0

var carX = 0.0
var steering = 0.0

var throttle = false
var braking = false
var nitro = false
var horn = false

var lights = false
var highBeam = false

var leftIndicator = false
var rightIndicator = false
var hazards = false

var rain = false
var wipers = true

var autoGear = true
var gear = "N"

var cruiseControl = false
var cruiseSpeed = 80.0

var nitroHeat = 0.0

var paused = false
var gameRunning = false

var timePhase = 0

var flashMessage = ""
var flashTime = 0L

var lastTick =
  System.currentTimeMillis()

var lastNetworkSend = 0L
var lastTrafficSpawn = 0L
var lastSound = 0L

var repairCooldown = 0L


/* =========================================================
   ROAD HELPERS
   ========================================================= */

def laneToX(
  lane: Int
): Double = {

  if (lane == 0) {
    -0.66
  } else if (lane == 1) {
    0.0
  } else {
    0.66
  }
}


def roadHalfWidth(
  z: Double
): Double = {

  82.0 +
  245.0 * z
}


def trafficScreenX(
  lane: Int,
  z: Double,
  width: Int
): Int = {

  val half =
    roadHalfWidth(z)

  (
    width / 2.0 +
    laneToX(lane) *
    half
  ).toInt
}


def interpolateRoadX(
  tl: Int,
  tr: Int,
  bl: Int,
  br: Int,
  topY: Int,
  bottomY: Int,
  y: Int,
  fraction: Double
): Int = {

  val t =
    (
      y -
      topY
    ).toDouble /
    Math.max(
      1,
      bottomY -
      topY
    ).toDouble

  val left =
    tl +
    (
      (
        bl -
        tl
      ) *
      t
    ).toInt

  val right =
    tr +
    (
      (
        br -
        tr
      ) *
      t
    ).toInt

  left +
  (
    (
      right -
      left
    ) *
    fraction
  ).toInt
}


/* =========================================================
   GENERAL HELPERS
   ========================================================= */

def showCard(
  name: String
): Unit = {

  currentScreen =
    name

  cards.show(
    root,
    name
  )
}


def cleanText(
  text: String
): String = {

  if (text == null) {
    ""
  } else {
    text
      .replace("|", "/")
      .replace("~", "-")
      .replace("\n", " ")
      .replace("\r", " ")
  }
}


def modeText(): String = {

  if (!online) {
    "OFFLINE SOLO"
  } else if (multiplayer) {
    "MULTIPLAYER"
  } else {
    "ONLINE SOLO"
  }
}


def onOff(
  value: Boolean
): String = {

  if (value) {
    "ON"
  } else {
    "OFF"
  }
}


def gearNumber(
  g: String
): Int = {

  if (g == "R") {
    -1
  } else if (g == "N") {
    0
  } else {
    Try(
      g.toInt
    ).getOrElse(
      0
    )
  }
}


def localIP(): String = {

  try {

    InetAddress
      .getLocalHost
      .getHostAddress

  } catch {

    case _: Throwable =>
      "127.0.0.1"
  }
}


/* =========================================================
   SOUND
   ========================================================= */

def playTone(
  frequency: Int,
  duration: Int
): Unit = {

  val now =
    System.currentTimeMillis()

  if (
    now -
    lastSound <
    30L
  ) {
    return
  }

  lastSound =
    now

  val soundThread =
    new Thread(
      new Runnable {

        def run(): Unit = {

          try {

            val sampleRate =
              44100.0f

            val format =
              new AudioFormat(
                sampleRate,
                8,
                1,
                true,
                false
              )

            val line =
              AudioSystem
                .getSourceDataLine(
                  format
                )

            line.open(
              format
            )

            line.start()

            val samples =
              Math.max(
                1,
                (
                  sampleRate *
                  duration.toDouble /
                  1000.0
                ).toInt
              )

            val buffer =
              new Array[Byte](
                samples
              )

            var i = 0

            while (
              i <
              samples
            ) {

              val angle =
                2.0 *
                Math.PI *
                frequency.toDouble *
                i.toDouble /
                sampleRate.toDouble

              buffer(i) =
                (
                  Math.sin(
                    angle
                  ) *
                  105.0
                ).toByte

              i += 1
            }

            line.write(
              buffer,
              0,
              buffer.length
            )

            line.drain()
            line.stop()
            line.close()

          } catch {

            case _: Throwable =>

              try {

                java.awt.Toolkit
                  .getDefaultToolkit
                  .beep()

              } catch {

                case _: Throwable =>
              }
          }
        }
      }
    )

  soundThread.setDaemon(
    true
  )

  soundThread.start()
}


def flash(
  text: String
): Unit = {

  flashMessage =
    text

  flashTime =
    System.currentTimeMillis() +
    2200L

  playTone(
    720,
    60
  )
}


/* =========================================================
   LIGHTS / WEATHER
   ========================================================= */

def toggleLights(): Unit = {

  lights =
    !lights

  if (lights) {
    flash("HEADLIGHTS ON")
  } else {
    flash("HEADLIGHTS OFF")
  }
}


def toggleHighBeam(): Unit = {

  highBeam =
    !highBeam

  if (highBeam) {
    flash("HIGH BEAM ON")
  } else {
    flash("HIGH BEAM OFF")
  }
}


def toggleRain(): Unit = {

  rain =
    !rain

  if (rain) {

    flash(
      "RAIN STARTED"
    )

    playTone(
      240,
      100
    )

  } else {

    flash(
      "RAIN STOPPED"
    )
  }
}


def toggleAutoGear(): Unit = {

  autoGear =
    !autoGear

  if (autoGear) {
    flash("AUTO GEAR")
  } else {
    flash("MANUAL GEAR")
  }
}


/* =========================================================
   INDICATORS
   ========================================================= */

def toggleLeftIndicator(): Unit = {

  leftIndicator =
    !leftIndicator

  rightIndicator =
    false
}


def toggleRightIndicator(): Unit = {

  rightIndicator =
    !rightIndicator

  leftIndicator =
    false
}


/* =========================================================
   MANUAL GEAR
   ========================================================= */

def setManualGear(
  value: String
): Unit = {

  if (!autoGear) {

    gear =
      value

    playTone(
      430,
      55
    )
  }
}


def autoShift(): Unit = {

  if (
    gameSpeed <
    4.0
  ) {

    gear =
      "N"

  } else if (
    gameSpeed <
    28.0
  ) {

    gear =
      "1"

  } else if (
    gameSpeed <
    55.0
  ) {

    gear =
      "2"

  } else if (
    gameSpeed <
    82.0
  ) {

    gear =
      "3"

  } else if (
    gameSpeed <
    118.0
  ) {

    gear =
      "4"

  } else {

    gear =
      "5"
  }
}


/* =========================================================
   CRUISE CONTROL
   ========================================================= */

def toggleCruise(): Unit = {

  cruiseControl =
    !cruiseControl

  if (cruiseControl) {

    cruiseSpeed =
      Math.max(
        35.0,
        Math.min(
          150.0,
          Math.abs(
            gameSpeed
          )
        )
      )

    flash(
      "CRUISE " +
      cruiseSpeed.toInt +
      " KM/H"
    )

  } else {

    flash(
      "CRUISE OFF"
    )
  }
}


/* =========================================================
   SERVER
   ========================================================= */

def startServer(
  wantedPort: Int
): Boolean = {

  if (
    serverRunning
  ) {

    return true
  }

  var port =
    wantedPort

  var opened:
    ServerSocket =
    null

  while (
    port <
    wantedPort +
    25 &&
    opened ==
    null
  ) {

    try {

      val s =
        new ServerSocket()

      s.setReuseAddress(
        true
      )

      s.bind(
        new java.net.InetSocketAddress(
          "0.0.0.0",
          port
        )
      )

      opened =
        s

    } catch {

      case _: Throwable =>
        port += 1
    }
  }

  if (
    opened ==
    null
  ) {

    return false
  }

  serverSocket =
    opened

  actualServerPort =
    port

  serverRunning =
    true

  addLobbyChat(
    "[SERVER] LISTENING " +
    localIP() +
    ":" +
    actualServerPort
  )


  val acceptThread =
    new Thread(
      new Runnable {

        def run(): Unit = {

          while (
            serverRunning
          ) {

            try {

              val socket =
                serverSocket
                  .accept()

              val t =
                new Thread(
                  new Runnable {

                    def run(): Unit = {

                      handleServerClient(
                        socket
                      )
                    }
                  }
                )

              t.setDaemon(
                true
              )

              t.start()

            } catch {

              case _: Throwable =>
            }
          }
        }
      }
    )


  acceptThread.setDaemon(
    true
  )

  acceptThread.start()

  true
}


/* =========================================================
   SERVER BROADCAST
   ========================================================= */

def broadcastServer(
  message: String
): Unit = {

  serverClients.synchronized {

    var i = 0

    while (
      i <
      serverClients.length
    ) {

      try {

        serverClients(i)
          .out
          .println(
            message
          )

        serverClients(i)
          .out
          .flush()

      } catch {

        case _: Throwable =>
      }

      i += 1
    }
  }
}


/* =========================================================
   PLAYER LIST
   ========================================================= */

def broadcastPlayers(): Unit = {

  val builder =
    new StringBuilder(
      "PLAYERS"
    )


  serverClients.synchronized {

    var i = 0

    while (
      i <
      serverClients.length
    ) {

      val c =
        serverClients(i)

      builder.append(
        "|"
      )

      builder.append(
        cleanText(
          c.id
        )
      )

      builder.append(
        "~"
      )

      builder.append(
        cleanText(
          c.name
        )
      )

      builder.append(
        "~"
      )

      builder.append(
        c.x
      )

      builder.append(
        "~"
      )

      builder.append(
        c.distance
      )

      builder.append(
        "~"
      )

      builder.append(
        c.speed
      )

      builder.append(
        "~"
      )

      builder.append(
        cleanText(
          c.gear
        )
      )

      builder.append(
        "~"
      )

      builder.append(
        cleanText(
          c.mode
        )
      )

      builder.append(
        "~"
      )

      builder.append(
        c.ready
      )

      i += 1
    }
  }


  broadcastServer(
    builder.toString()
  )

  updateLobbyPlayers()
}


/* =========================================================
   SERVER CLIENT HANDLER
   ========================================================= */

def handleServerClient(
  socket: Socket
): Unit = {

  var input:
    BufferedReader =
    null

  var output:
    PrintWriter =
    null

  var client:
    ServerClient =
    null


  try {

    input =
      new BufferedReader(
        new InputStreamReader(
          socket.getInputStream
        )
      )

    output =
      new PrintWriter(
        socket.getOutputStream,
        true
      )


    val first =
      input.readLine()


    if (
      first ==
      null
    ) {

      socket.close()

      return
    }


    val hp =
      first.split(
        "\\|",
        -1
      )


    var name =
      "Driver"


    if (
      hp.length >=
      2
    ) {

      name =
        cleanText(
          hp(1)
        )
    }


    val id =
      "P" +
      Math.abs(
        System.nanoTime()
      )
      .toString
      .takeRight(
        7
      )


    client =
      ServerClient(
        id,
        name,
        socket,
        output,
        0.0,
        0.0,
        0.0,
        "N",
        "ONLINE SOLO",
        false
      )


    serverClients.synchronized {

      serverClients +=
        client
    }


    output.println(
      "WELCOME|" +
      id +
      "|" +
      actualServerPort
    )

    output.flush()


    broadcastServer(
      "SYSTEM|" +
      cleanText(
        name
      ) +
      " joined."
    )


    broadcastPlayers()


    var listening =
      true


    while (
      listening
    ) {

      val line =
        input.readLine()


      if (
        line ==
        null
      ) {

        listening =
          false

      } else {

        val p =
          line.split(
            "\\|",
            -1
          )


        if (
          p.length >
          0
        ) {


          if (
            p(0) ==
            "STATE"
          ) {

            if (
              p.length >=
              9
            ) {

              client.x =
                Try(
                  p(3).toDouble
                )
                .getOrElse(
                  0.0
                )


              client.distance =
                Try(
                  p(4).toDouble
                )
                .getOrElse(
                  0.0
                )


              client.speed =
                Try(
                  p(5).toDouble
                )
                .getOrElse(
                  0.0
                )


              client.gear =
                p(6)


              client.mode =
                p(7)


              client.ready =
                p(8) ==
                "true"
            }


            broadcastPlayers()
          }


          if (
            p(0) ==
            "CHAT"
          ) {

            if (
              p.length >=
              3
            ) {

              broadcastServer(
                "CHAT|" +
                cleanText(
                  client.name
                ) +
                "|" +
                cleanText(
                  p(2)
                )
              )
            }
          }


          if (
            p(0) ==
            "READY"
          ) {

            client.ready =
              true

            broadcastServer(
              "SYSTEM|" +
              cleanText(
                client.name
              ) +
              " is READY."
            )

            broadcastPlayers()
          }


          if (
            p(0) ==
            "NOTREADY"
          ) {

            client.ready =
              false

            broadcastPlayers()
          }


          if (
            p(0) ==
            "MODE"
          ) {

            if (
              p.length >=
              2
            ) {

              client.mode =
                p(1)
            }

            broadcastPlayers()
          }


          if (
            p(0) ==
            "START"
          ) {

            broadcastServer(
              "RACE_START"
            )
          }


          if (
            p(0) ==
            "LOBBY"
          ) {

            client.mode =
              "LOBBY"

            client.ready =
              false

            broadcastPlayers()
          }
        }
      }
    }

  } catch {

    case _: Throwable =>
  }


  if (
    client !=
    null
  ) {

    broadcastServer(
      "SYSTEM|" +
      cleanText(
        client.name
      ) +
      " left."
    )

    removeServerClient(
      client.id
    )

  } else {

    try {

      socket.close()

    } catch {

      case _: Throwable =>
    }
  }
}


/* =========================================================
   REMOVE CLIENT
   ========================================================= */

def removeServerClient(
  id: String
): Unit = {

  serverClients.synchronized {

    var i =
      serverClients.length -
      1


    while (
      i >=
      0
    ) {

      if (
        serverClients(i).id ==
        id
      ) {

        try {

          serverClients(i)
            .socket
            .close()

        } catch {

          case _: Throwable =>
        }


        serverClients.remove(
          i
        )
      }


      i -= 1
    }
  }


  broadcastPlayers()
}


/* =========================================================
   STOP SERVER
   ========================================================= */

def stopServer(): Unit = {

  serverRunning =
    false


  try {

    if (
      serverSocket !=
      null
    ) {

      serverSocket.close()
    }

  } catch {

    case _: Throwable =>
  }


  serverClients.synchronized {

    var i = 0

    while (
      i <
      serverClients.length
    ) {

      try {

        serverClients(i)
          .socket
          .close()

      } catch {

        case _: Throwable =>
      }

      i += 1
    }


    serverClients.clear()
  }
}


/* =========================================================
   CLIENT CONNECT
   ========================================================= */

def connectToServer(
  host: String,
  port: Int
): Boolean = {

  disconnectClient()


  try {

    val socket =
      new Socket()


    socket.connect(
      new java.net.InetSocketAddress(
        host,
        port
      ),
      4000
    )


    socket.setTcpNoDelay(
      true
    )


    clientSocket =
      socket


    clientOut =
      new PrintWriter(
        socket.getOutputStream,
        true
      )


    clientIn =
      new BufferedReader(
        new InputStreamReader(
          socket.getInputStream
        )
      )


    clientOut.println(
      "HELLO|" +
      cleanText(
        playerName
      )
    )


    clientOut.flush()


    clientConnected =
      true


    online =
      true


    lastHost =
      host


    lastPort =
      port


    val listener =
      new Thread(
        new Runnable {

          def run(): Unit = {

            listenClient()
          }
        }
      )


    listener.setDaemon(
      true
    )


    listener.start()


    true

  } catch {

    case _: Throwable =>

      clientConnected =
        false

      online =
        false

      false
  }
}


/* =========================================================
   CLIENT DISCONNECT
   ========================================================= */

def disconnectClient(): Unit = {

  clientConnected =
    false

  online =
    false

  matchmaking =
    false


  try {

    if (
      clientSocket !=
      null
    ) {

      clientSocket.close()
    }

  } catch {

    case _: Throwable =>
  }


  clientSocket =
    null

  clientOut =
    null

  clientIn =
    null


  remotePlayers.synchronized {

    remotePlayers.clear()
  }
}


/* =========================================================
   CLIENT SEND
   ========================================================= */

def sendClient(
  message: String
): Unit = {

  if (
    !clientConnected
  ) {

    return
  }


  try {

    clientOut.println(
      message
    )

    clientOut.flush()

  } catch {

    case _: Throwable =>

      clientConnected =
        false

      online =
        false
  }
}


/* =========================================================
   CLIENT LISTENER
   ========================================================= */

def listenClient(): Unit = {

  try {

    var running =
      true


    while (
      running &&
      clientConnected
    ) {

      val line =
        clientIn.readLine()


      if (
        line ==
        null
      ) {

        running =
          false

      } else {

        receiveMessage(
          line
        )
      }
    }

  } catch {

    case _: Throwable =>
  }


  SwingUtilities.invokeLater(
    new Runnable {

      def run(): Unit = {

        if (
          clientConnected
        ) {

          clientConnected =
            false

          online =
            false


          if (
            lobbyStatusLabel !=
            null
          ) {

            lobbyStatusLabel.setText(
              "CONNECTION CLOSED"
            )
          }
        }
      }
    }
  )
}


/* =========================================================
   RECEIVE MESSAGE
   ========================================================= */

def receiveMessage(
  message: String
): Unit = {

  val p =
    message.split(
      "\\|",
      -1
    )


  if (
    p.length ==
    0
  ) {

    return
  }


  if (
    p(0) ==
    "WELCOME"
  ) {

    if (
      p.length >=
      2
    ) {

      clientId =
        p(1)
    }


    SwingUtilities.invokeLater(
      new Runnable {

        def run(): Unit = {

          if (
            lobbyStatusLabel !=
            null
          ) {

            lobbyStatusLabel.setText(
              "CONNECTED ? " +
              clientId
            )
          }
        }
      }
    )
  }


  if (
    p(0) ==
    "SYSTEM"
  ) {

    if (
      p.length >=
      2
    ) {

      addLobbyChat(
        "[SYSTEM] " +
        p(1)
      )
    }
  }


  if (
    p(0) ==
    "CHAT"
  ) {

    if (
      p.length >=
      3
    ) {

      addLobbyChat(
        p(1) +
        ": " +
        p(2)
      )
    }
  }


  if (
    p(0) ==
    "PLAYERS"
  ) {

    parsePlayers(
      p
    )
  }


  if (
    p(0) ==
    "RACE_START"
  ) {

    raceStarted =
      true

    matchmaking =
      false

    startGame()
  }
}


/* =========================================================
   PARSE PLAYERS
   ========================================================= */

def parsePlayers(
  p: Array[String]
): Unit = {

  remotePlayers.synchronized {

    remotePlayers.clear()


    var i =
      1


    while (
      i <
      p.length
    ) {

      val q =
        p(i).split(
          "~",
          -1
        )


      if (
        q.length >=
        8
      ) {

        if (
          q(0) !=
          clientId
        ) {

          remotePlayers +=
            RemotePlayer(
              q(0),
              q(1),
              Try(
                q(2).toDouble
              )
              .getOrElse(
                0.0
              ),
              Try(
                q(3).toDouble
              )
              .getOrElse(
                0.0
              ),
              Try(
                q(4).toDouble
              )
              .getOrElse(
                0.0
              ),
              q(5),
              q(6),
              q(7) ==
              "true"
            )
        }
      }


      i += 1
    }
  }


  updateLobbyPlayers()
}


/* =========================================================
   SEND GAME STATE
   ========================================================= */

def sendGameState(): Unit = {

  if (
    !clientConnected ||
    !online
  ) {

    return
  }


  val now =
    System.currentTimeMillis()


  if (
    now -
    lastNetworkSend <
    65L
  ) {

    return
  }


  lastNetworkSend =
    now


  sendClient(
    "STATE|" +
    clientId +
    "|" +
    cleanText(
      playerName
    ) +
    "|" +
    carX +
    "|" +
    gameDistance +
    "|" +
    gameSpeed +
    "|" +
    gear +
    "|" +
    modeText() +
    "|" +
    ready
  )
}


/* =========================================================
   CHAT
   ========================================================= */

def addLobbyChat(
  text: String
): Unit = {

  if (
    lobbyChatArea ==
    null
  ) {

    return
  }


  SwingUtilities.invokeLater(
    new Runnable {

      def run(): Unit = {

        lobbyChatArea.append(
          text +
          "\n"
        )


        lobbyChatArea.setCaretPosition(
          lobbyChatArea
            .getDocument
            .getLength
        )
      }
    }
  )
}


def sendChat(
  text: String
): Unit = {

  if (
    text.trim.isEmpty
  ) {

    return
  }


  if (
    clientConnected
  ) {

    sendClient(
      "CHAT|" +
      clientId +
      "|" +
      cleanText(
        text
      )
    )

  } else {

    addLobbyChat(
      "LOCAL: " +
      text
    )
  }
}


/* =========================================================
   LOBBY COUNT
   ========================================================= */

def updateLobbyPlayers(): Unit = {

  if (
    lobbyPlayersLabel ==
    null
  ) {

    return
  }


  val remoteCount =
    remotePlayers.synchronized {

      remotePlayers.length
    }


  val hostCount =
    serverClients.synchronized {

      serverClients.length
    }


  SwingUtilities.invokeLater(
    new Runnable {

      def run(): Unit = {

        if (
          serverRunning
        ) {

          lobbyPlayersLabel.setText(
            "HOST " +
            localIP() +
            ":" +
            actualServerPort +
            " ? PLAYERS " +
            hostCount
          )

        } else {

          lobbyPlayersLabel.setText(
            "ONLINE PLAYERS ? " +
            remoteCount
          )
        }
      }
    }
  )
}


/* =========================================================
   GAME RESET
   ========================================================= */

def resetGame(): Unit = {

  gameDistance =
    0.0

  gameSpeed =
    0.0

  fuel =
    100.0

  damage =
    0.0

  score =
    0

  missionNumber =
    1

  missionProgress =
    0

  missionTarget =
    1000.0

  carX =
    0.0

  steering =
    0.0

  throttle =
    false

  braking =
    false

  nitro =
    false

  horn =
    false

  lights =
    false

  highBeam =
    false

  leftIndicator =
    false

  rightIndicator =
    false

  hazards =
    false

  rain =
    false

  cruiseControl =
    false

  cruiseSpeed =
    80.0

  nitroHeat =
    0.0

  gear =
    "N"

  paused =
    false

  gameRunning =
    true

  raceStarted =
    false

  repairCooldown =
    0L


  traffic.clear()


  var i =
    0


  while (
    i <
    16
  ) {

    val kind =
      trafficKind(
        i
      )


    traffic +=
      TrafficCar(
        i % 3,
        0.10 +
        i.toDouble *
        0.072,
        38.0 +
        (
          i % 6
        ) *
        9.0,
        kind,
        i % 10 ==
        0,
        i.toDouble
      )


    i += 1
  }


  playTone(
    820,
    120
  )
}


/* =========================================================
   TRAFFIC TYPE
   ========================================================= */

def trafficKind(
  index: Int
): String = {

  if (
    index %
    7 ==
    0
  ) {

    "TRUCK"

  } else if (
    index %
    4 ==
    0
  ) {

    "BUS"

  } else {

    "CAR"
  }
}


/* =========================================================
   START GAME
   ========================================================= */

def startGame(): Unit = {

  resetGame()

  showCard(
    "game"
  )


  if (
    gamePanel !=
    null
  ) {

    gamePanel.requestFocusInWindow()
  }
}


/* =========================================================
   ONLINE SOLO
   ========================================================= */

def startOnlineSolo(): Unit = {

  if (
    !clientConnected
  ) {

    flash(
      "NOT CONNECTED"
    )

    return
  }


  multiplayer =
    false

  soloOnline =
    true

  matchmaking =
    false

  ready =
    true


  sendClient(
    "MODE|ONLINE SOLO"
  )


  sendClient(
    "READY"
  )


  startGame()
}


/* =========================================================
   MULTIPLAYER
   ========================================================= */

def startMultiplayer(): Unit = {

  if (
    !clientConnected
  ) {

    flash(
      "NOT CONNECTED"
    )

    return
  }


  multiplayer =
    true

  soloOnline =
    false

  ready =
    true


  sendClient(
    "MODE|MULTIPLAYER"
  )


  sendClient(
    "READY"
  )


  sendClient(
    "START"
  )
}


/* =========================================================
   AUTO MATCH
   ========================================================= */

def startAutoMatch(): Unit = {

  if (
    lobbyNameField !=
    null
  ) {

    playerName =
      cleanText(
        lobbyNameField
          .getText
      )
  }


  if (
    playerName.isEmpty
  ) {

    playerName =
      "Driver"
  }


  matchmaking =
    true


  if (
    lobbyStatusLabel !=
    null
  ) {

    lobbyStatusLabel.setText(
      "AUTO MATCH SEARCHING..."
    )
  }


  /*
     Automatically starts a local/LAN
     matchmaking host.
  */

  if (
    !serverRunning
  ) {

    val started =
      startServer(
        5555
      )


    if (!started) {

      matchmaking =
        false


      lobbyStatusLabel.setText(
        "AUTO MATCH SERVER FAILED"
      )


      return
    }
  }


  if (
    !clientConnected
  ) {

    val connected =
      connectToServer(
        "127.0.0.1",
        actualServerPort
      )


    if (!connected) {

      matchmaking =
        false


      lobbyStatusLabel.setText(
        "AUTO MATCH CONNECTION FAILED"
      )


      return
    }
  }


  multiplayer =
    true

  soloOnline =
    false

  ready =
    true


  sendClient(
    "MODE|MULTIPLAYER"
  )


  sendClient(
    "READY"
  )


  lobbyStatusLabel.setText(
    "MATCH FOUND ? STARTING"
  )


  playTone(
    900,
    100
  )


  startGame()
}


/* =========================================================
   LEAVE GAME
   ========================================================= */

def leaveToLobby(): Unit = {

  gameRunning =
    false


  throttle =
    false

  braking =
    false

  nitro =
    false

  horn =
    false

  cruiseControl =
    false

  ready =
    false


  sendClient(
    "LOBBY"
  )


  showCard(
    "lobby"
  )


  updateLobbyPlayers()
}


/* =========================================================
   MENU
   ========================================================= */

def returnToMenu(): Unit = {

  gameRunning =
    false


  sendClient(
    "LOBBY"
  )


  disconnectClient()
  stopServer()


  multiplayer =
    false

  soloOnline =
    false

  ready =
    false

  matchmaking =
    false


  showCard(
    "menu"
  )
}


/* =========================================================
   EXIT
   ========================================================= */

def exitApplication(): Unit = {

  try {
    disconnectClient()
  } catch {
    case _: Throwable =>
  }


  try {
    stopServer()
  } catch {
    case _: Throwable =>
  }


  try {
    frame.dispose()
  } catch {
    case _: Throwable =>
  }
}


/* =========================================================
   GAME UPDATE
   ========================================================= */

def updateGame(): Unit = {

  if (
    !gameRunning ||
    paused
  ) {

    return
  }


  val now =
    System.currentTimeMillis()


  val raw =
    (
      now -
      lastTick
    ).toDouble /
    1000.0


  val dt =
    Math.min(
      0.06,
      Math.max(
        0.001,
        raw
      )
    )


  lastTick =
    now


  updateControls(
    dt
  )


  updateTraffic(
    dt
  )


  detectCollisions()


  updateMission()


  updateTime()


  updateNitroHeat()


  updateRepair()


  sendGameState()
}


/* =========================================================
   CONTROLS
   ========================================================= */

def updateControls(
  dt: Double
): Unit = {

  val leftPressed =
    keys.contains(
      KeyEvent.VK_LEFT
    ) ||
    keys.contains(
      KeyEvent.VK_A
    )


  val rightPressed =
    keys.contains(
      KeyEvent.VK_RIGHT
    ) ||
    keys.contains(
      KeyEvent.VK_D
    )


  val upPressed =
    keys.contains(
      KeyEvent.VK_UP
    ) ||
    keys.contains(
      KeyEvent.VK_W
    )


  val downPressed =
    keys.contains(
      KeyEvent.VK_DOWN
    ) ||
    keys.contains(
      KeyEvent.VK_S
    )


  var steeringInput =
    0.0


  if (
    leftPressed
  ) {

    steeringInput =
      -1.0
  }


  if (
    rightPressed
  ) {

    steeringInput =
      1.0
  }


  /*
     SMOOTH FULL STEERING
  */

  if (
    steeringInput !=
    0.0
  ) {

    steering +=
      (
        steeringInput -
        steering
      ) *
      Math.min(
        1.0,
        11.0 *
        dt
      )

  } else {

    steering *=
      Math.max(
        0.0,
        1.0 -
        7.0 *
        dt
      )


    if (
      Math.abs(
        steering
      ) <
      0.012
    ) {

      steering =
        0.0
    }
  }


  val gas =
    throttle ||
    upPressed


  val brake =
    braking ||
    downPressed


  val currentGear =
    gearNumber(
      gear
    )


  var acceleration =
    0.0


  if (
    cruiseControl &&
    !gas &&
    !brake
  ) {

    if (
      gameSpeed <
      cruiseSpeed
    ) {

      acceleration +=
        14.0
    }
  }


  if (
    gas
  ) {

    cruiseControl =
      false


    if (
      currentGear >
      0
    ) {

      acceleration =
        31.0 +
        currentGear.toDouble *
        7.5
    }


    if (
      gear ==
      "R"
    ) {

      acceleration =
        -26.0
    }
  }


  if (
    brake
  ) {

    cruiseControl =
      false

    acceleration -=
      75.0
  }


  if (
    nitro &&
    fuel >
    2.0 &&
    gameSpeed >
    8.0 &&
    nitroHeat <
    100.0
  ) {

    acceleration +=
      86.0


    fuel -=
      6.5 *
      dt


    nitroHeat +=
      36.0 *
      dt

  } else {

    nitroHeat -=
      18.0 *
      dt
  }


  if (
    nitroHeat <
    0.0
  ) {

    nitroHeat =
      0.0
  }


  if (
    nitroHeat >
    100.0
  ) {

    nitroHeat =
      100.0

    nitro =
      false

    flash(
      "NITRO OVERHEAT"
    )
  }


  gameSpeed +=
    acceleration *
    dt


  if (
    !gas &&
    !brake &&
    !cruiseControl &&
    acceleration ==
    0.0
  ) {

    if (
      gameSpeed >
      0.0
    ) {

      gameSpeed -=
        9.0 *
        dt
    }


    if (
      gameSpeed <
      0.0
    ) {

      gameSpeed +=
        9.0 *
        dt
    }
  }


  if (
    gameSpeed >
    188.0
  ) {

    gameSpeed =
      188.0
  }


  if (
    gameSpeed <
    -42.0
  ) {

    gameSpeed =
      -42.0
  }


  if (
    autoGear
  ) {

    autoShift()
  }


  /*
     FULL LATERAL MOVEMENT
  */

  val speedFactor =
    0.62 +
    Math.min(
      1.65,
      Math.abs(
        gameSpeed
      ) /
      75.0
    )


  val lateral =
    steering *
    speedFactor *
    dt *
    1.28


  carX +=
    lateral


  /*
     Road edges
  */

  if (
    carX <
    -1.08
  ) {

    carX =
      -1.08

    steering =
      0.0

    damage +=
      0.18

  }


  if (
    carX >
    1.08
  ) {

    carX =
      1.08

    steering =
      0.0

    damage +=
      0.18
  }


  /*
     Road distance
  */

  val forwardSpeed =
    Math.max(
      0.0,
      gameSpeed
    )


  gameDistance +=
    forwardSpeed *
    dt *
    0.12


  /*
     Fuel
  */

  fuel -=
    (
      0.45 +
      forwardSpeed *
      0.0035
    ) *
    dt


  if (
    fuel <
    0.0
  ) {

    fuel =
      0.0
  }


  if (
    fuel <=
    0.0
  ) {

    gameSpeed -=
      17.0 *
      dt
  }


  /*
     Score
  */

  score +=
    (
      forwardSpeed *
      dt *
      0.13
    ).toInt
}


/* =========================================================
   TRAFFIC UPDATE
   ========================================================= */

def updateTraffic(
  dt: Double
): Unit = {

  var i =
    0


  while (
    i <
    traffic.length
  ) {

    val car =
      traffic(i)


    car.z +=
      (
        car.speed -
        gameSpeed
      ) *
      dt *
      0.00165


    car.phase +=
      dt *
      2.0


    if (
      car.z >
      1.28
    ) {

      car.z =
        -0.18


      car.lane =
        Math.abs(
          (
            System.nanoTime() /
            1000L +
            i
          ).toInt
        ) % 3


      car.speed =
        40.0 +
        (
          Math.abs(
            System.nanoTime()
          ) %
          75L
        ).toDouble
    }


    if (
      car.z <
      -0.45
    ) {

      car.z =
        1.05
    }


    i += 1
  }


  val now =
    System.currentTimeMillis()


  if (
    now -
    lastTrafficSpawn >
    2100L
  ) {

    lastTrafficSpawn =
      now


    val lane =
      Math.abs(
        (
          System.nanoTime() /
          1000L
        ).toInt
      ) % 3


    val policeCar =
      (
        Math.abs(
          System.nanoTime()
        ) %
        13L
      ) ==
      0


    var newKind =
      "CAR"


    val selector =
      Math.abs(
        (
          System.nanoTime() /
          1000L
        ).toInt
      ) % 10


    if (
      selector <
      2
    ) {

      newKind =
        "BUS"
    }


    if (
      selector ==
      8
    ) {

      newKind =
        "TRUCK"
    }


    traffic +=
      TrafficCar(
        lane,
        -0.16,
        42.0 +
        (
          Math.abs(
            System.nanoTime()
          ) %
          75L
        ).toDouble,
        newKind,
        policeCar,
        0.0
      )


    if (
      traffic.length >
      23
    ) {

      traffic.remove(
        0
      )
    }
  }
}


/* =========================================================
   COLLISION
   ========================================================= */

def detectCollisions(): Unit = {

  var i =
    0


  while (
    i <
    traffic.length
  ) {

    val car =
      traffic(i)


    if (
      car.z >
      0.82 &&
      car.z <
      1.04
    ) {

      val trafficX =
        laneToX(
          car.lane
        )


      val difference =
        Math.abs(
          trafficX -
          carX
        )


      if (
        difference <
        0.23
      ) {

        val impact =
          Math.abs(
            gameSpeed -
            car.speed
          )


        damage +=
          4.0 +
          impact *
          0.075


        score -=
          30


        if (
          score <
          0
        ) {

          score =
            0
        }


        gameSpeed *=
          0.70


        car.z =
          -0.25


        if (
          car.police
        ) {

          score -=
            80


          if (
            score <
            0
          ) {

            score =
              0
          }


          flash(
            "POLICE FINE!"
          )

        } else {

          flash(
            "COLLISION!"
          )
        }


        playTone(
          110,
          180
        )
      }
    }


    i += 1
  }


  if (
    damage >
    100.0
  ) {

    damage =
      100.0


    gameSpeed *=
      0.50


    flash(
      "VEHICLE HEAVILY DAMAGED"
    )
  }
}


/* =========================================================
   REPAIR / FUEL CHECKPOINT
   ========================================================= */

def updateRepair(): Unit = {

  if (
    gameDistance >
    0.0 &&
    missionNumber %
    4 ==
    0
  ) {

    val now =
      System.currentTimeMillis()


    if (
      now -
      repairCooldown >
      12000L
    ) {

      if (
        gameDistance.toInt %
        3500 <
        2
      ) {

        damage -=
          8.0


        fuel +=
          8.0


        if (
          damage <
          0.0
        ) {

          damage =
            0.0
        }


        if (
          fuel >
          100.0
        ) {

          fuel =
            100.0
        }


        repairCooldown =
          now


        flash(
          "SERVICE CHECKPOINT"
        )


        playTone(
          950,
          130
        )
      }
    }
  }
}


/* =========================================================
   NITRO HEAT
   ========================================================= */

def updateNitroHeat(): Unit = {

  if (
    !nitro
  ) {

    nitroHeat -=
      12.0 /
      30.0
  }


  if (
    nitroHeat <
    0.0
  ) {

    nitroHeat =
      0.0
  }
}


/* =========================================================
   MISSION
   ========================================================= */

def updateMission(): Unit = {

  missionProgress =
    Math.min(
      missionTarget.toInt,
      gameDistance.toInt
    )


  if (
    gameDistance >=
    missionTarget
  ) {

    missionNumber +=
      1


    missionTarget +=
      1000.0 +
      missionNumber.toDouble *
      300.0


    score +=
      1000


    playTone(
      1000,
      170
    )


    flash(
      "CHECKPOINT " +
      missionNumber
    )
  }
}


/* =========================================================
   DAY / NIGHT
   ========================================================= */

def updateTime(): Unit = {

  timePhase =
    (
      gameDistance /
      2500.0
    ).toInt %
    4
}


/* =========================================================
   BUTTON FACTORY
   ========================================================= */

def makeButton(
  text: String,
  width: Int,
  height: Int
): JButton = {

  val button =
    new JButton(
      text
    )


  button.setPreferredSize(
    new Dimension(
      width,
      height
    )
  )


  button.setFocusPainted(
    false
  )


  button.setFont(
    new Font(
      "Arial",
      Font.BOLD,
      15
    )
  )


  button
}


/* =========================================================
   MENU
   ========================================================= */

def makeMenu(): JPanel = {

  val panel =
    new JPanel()


  panel.setLayout(
    new BoxLayout(
      panel,
      BoxLayout.Y_AXIS
    )
  )


  panel.setBorder(
    BorderFactory
      .createEmptyBorder(
        30,
        70,
        30,
        70
      )
  )


  val title =
    new JLabel(
      "REAL DRIVE X"
    )


  title.setFont(
    new Font(
      "Arial",
      Font.BOLD,
      44
    )
  )


  title.setAlignmentX(
    0.5f
  )


  panel.add(
    title
  )


  val subtitle =
    new JLabel(
      "3D STYLE ? ONLINE ? MULTIPLAYER ? TRAFFIC"
    )


  subtitle.setAlignmentX(
    0.5f
  )


  panel.add(
    subtitle
  )


  panel.add(
    javax.swing.Box
      .createVerticalStrut(
        28
      )
  )


  val hostButton =
    makeButton(
      "HOST ONLINE",
      350,
      54
    )


  hostButton.setAlignmentX(
    0.5f
  )


  hostButton.addActionListener(
    new ActionListener {

      def actionPerformed(
        e: java.awt.event.ActionEvent
      ): Unit = {

        val nameInput =
          JOptionPane.showInputDialog(
            frame,
            "Driver name:",
            "Driver"
          )


        if (
          nameInput !=
          null &&
          nameInput.trim.nonEmpty
        ) {

          playerName =
            cleanText(
              nameInput.trim
            )
        }


        val portInput =
          JOptionPane.showInputDialog(
            frame,
            "Server port:",
            "5555"
          )


        val port =
          Try(
            Option(
              portInput
            )
            .getOrElse(
              "5555"
            )
            .toInt
          )
          .getOrElse(
            5555
          )


        val started =
          startServer(
            port
          )


        if (
          started
        ) {

          val connected =
            connectToServer(
              "127.0.0.1",
              actualServerPort
            )


          if (
            connected
          ) {

            lobbyStatusLabel.setText(
              "HOST CONNECTED ? " +
              localIP() +
              ":" +
              actualServerPort
            )


            showCard(
              "lobby"
            )

          } else {

            JOptionPane.showMessageDialog(
              frame,
              "Local host connection failed."
            )
          }

        } else {

          JOptionPane.showMessageDialog(
            frame,
            "Could not open server port."
          )
        }
      }
    }
  )


  panel.add(
    hostButton
  )


  panel.add(
    javax.swing.Box
      .createVerticalStrut(
        10
      )
  )


  val joinButton =
    makeButton(
      "JOIN ONLINE",
      350,
      54
    )


  joinButton.setAlignmentX(
    0.5f
  )


  joinButton.addActionListener(
    new ActionListener {

      def actionPerformed(
        e: java.awt.event.ActionEvent
      ): Unit = {

        val nameInput =
          JOptionPane.showInputDialog(
            frame,
            "Driver name:",
            "Driver"
          )


        if (
          nameInput !=
          null &&
          nameInput.trim.nonEmpty
        ) {

          playerName =
            cleanText(
              nameInput.trim
            )
        }


        val hostInput =
          JOptionPane.showInputDialog(
            frame,
            "Host IP:",
            lastHost
          )


        if (
          hostInput ==
          null ||
          hostInput.trim.isEmpty
        ) {

          return
        }


        val portInput =
          JOptionPane.showInputDialog(
            frame,
            "Port:",
            lastPort.toString
          )


        val port =
          Try(
            Option(
              portInput
            )
            .getOrElse(
              lastPort.toString
            )
            .toInt
          )
          .getOrElse(
            lastPort
          )


        lobbyStatusLabel.setText(
          "CONNECTING..."
        )


        val connected =
          connectToServer(
            hostInput.trim,
            port
          )


        if (
          connected
        ) {

          lobbyHostField.setText(
            hostInput.trim
          )


          lobbyPortField.setText(
            port.toString
          )


          lobbyStatusLabel.setText(
            "CONNECTED"
          )


          showCard(
            "lobby"
          )

        } else {

          lobbyStatusLabel.setText(
            "CONNECTION FAILED"
          )


          JOptionPane.showMessageDialog(
            frame,
            "Connection failed.\nCheck IP, port and firewall."
          )
        }
      }
    }
  )


  panel.add(
    joinButton
  )


  panel.add(
    javax.swing.Box
      .createVerticalStrut(
        10
      )
  )


  val autoMatchButton =
    makeButton(
      "AUTO MATCH",
      350,
      54
    )


  autoMatchButton.setAlignmentX(
    0.5f
  )


  autoMatchButton.addActionListener(
    new ActionListener {

      def actionPerformed(
        e: java.awt.event.ActionEvent
      ): Unit = {

        val nameInput =
          JOptionPane.showInputDialog(
            frame,
            "Driver name:",
            "Driver"
          )


        if (
          nameInput !=
          null &&
          nameInput.trim.nonEmpty
        ) {

          playerName =
            cleanText(
              nameInput.trim
            )
        }


        showCard(
          "lobby"
        )


        startAutoMatch()
      }
    }
  )


  panel.add(
    autoMatchButton
  )


  panel.add(
    javax.swing.Box
      .createVerticalStrut(
        10
      )
  )


  val offlineButton =
    makeButton(
      "OFFLINE SOLO",
      350,
      54
    )


  offlineButton.setAlignmentX(
    0.5f
  )


  offlineButton.addActionListener(
    new ActionListener {

      def actionPerformed(
        e: java.awt.event.ActionEvent
      ): Unit = {

        online =
          false

        multiplayer =
          false

        soloOnline =
          false


        startGame()
      }
    }
  )


  panel.add(
    offlineButton
  )


  panel.add(
    javax.swing.Box
      .createVerticalStrut(
        10
      )
  )


  val exitButton =
    makeButton(
      "EXIT",
      350,
      50
    )


  exitButton.setAlignmentX(
    0.5f
  )


  exitButton.addActionListener(
    new ActionListener {

      def actionPerformed(
        e: java.awt.event.ActionEvent
      ): Unit = {

        exitApplication()
      }
    }
  )


  panel.add(
    exitButton
  )


  panel
}


/* =========================================================
   LOBBY
   ========================================================= */

def makeLobby(): JPanel = {

  val panel =
    new JPanel()


  panel.setLayout(
    new BoxLayout(
      panel,
      BoxLayout.Y_AXIS
    )
  )


  panel.setBorder(
    BorderFactory
      .createEmptyBorder(
        18,
        30,
        18,
        30
      )
  )


  val title =
    new JLabel(
      "ONLINE LOBBY"
    )


  title.setFont(
    new Font(
      "Arial",
      Font.BOLD,
      34
    )
  )


  title.setAlignmentX(
    0.5f
  )


  panel.add(
    title
  )


  lobbyStatusLabel =
    new JLabel(
      "NOT CONNECTED"
    )


  lobbyStatusLabel.setAlignmentX(
    0.5f
  )


  panel.add(
    lobbyStatusLabel
  )


  lobbyPlayersLabel =
    new JLabel(
      "PLAYERS: 0"
    )


  lobbyPlayersLabel.setAlignmentX(
    0.5f
  )


  panel.add(
    lobbyPlayersLabel
  )


  lobbyModeLabel =
    new JLabel(
      "ONLINE SOLO ? MULTIPLAYER ? AUTO MATCH"
    )


  lobbyModeLabel.setAlignmentX(
    0.5f
  )


  panel.add(
    lobbyModeLabel
  )


  panel.add(
    javax.swing.Box
      .createVerticalStrut(
        8
      )
  )


  val form =
    new JPanel()


  form.add(
    new JLabel(
      "NAME"
    )
  )


  lobbyNameField =
    new JTextField(
      "Driver",
      11
    )


  form.add(
    lobbyNameField
  )


  form.add(
    new JLabel(
      "HOST"
    )
  )


  lobbyHostField =
    new JTextField(
      "127.0.0.1",
      11
    )


  form.add(
    lobbyHostField
  )


  form.add(
    new JLabel(
      "PORT"
    )
  )


  lobbyPortField =
    new JTextField(
      "5555",
      7
    )


  form.add(
    lobbyPortField
  )


  panel.add(
    form
  )


  val modes =
    new JPanel()


  val soloButton =
    makeButton(
      "ONLINE SOLO",
      165,
      43
    )


  soloButton.addActionListener(
    new ActionListener {

      def actionPerformed(
        e: java.awt.event.ActionEvent
      ): Unit = {

        playerName =
          cleanText(
            lobbyNameField.getText
          )


        if (
          playerName.isEmpty
        ) {

          playerName =
            "Driver"
        }


        startOnlineSolo()
      }
    }
  )


  modes.add(
    soloButton
  )


  val multiplayerButton =
    makeButton(
      "MULTIPLAYER",
      165,
      43
    )


  multiplayerButton.addActionListener(
    new ActionListener {

      def actionPerformed(
        e: java.awt.event.ActionEvent
      ): Unit = {

        playerName =
          cleanText(
            lobbyNameField.getText
          )


        if (
          playerName.isEmpty
        ) {

          playerName =
            "Driver"
        }


        startMultiplayer()
      }
    }
  )


  modes.add(
    multiplayerButton
  )


  val autoLobbyButton =
    makeButton(
      "AUTO MATCH",
      145,
      43
    )


  autoLobbyButton.addActionListener(
    new ActionListener {

      def actionPerformed(
        e: java.awt.event.ActionEvent
      ): Unit = {

        startAutoMatch()
      }
    }
  )


  modes.add(
    autoLobbyButton
  )


  panel.add(
    modes
  )


  /*
     IMPORTANT FIX:
     local button is named readyButton,
     NOT ready.
  */

  val readyButton =
    makeButton(
      "READY",
      105,
      42
    )


  readyButton.addActionListener(
    new ActionListener {

      def actionPerformed(
        e: java.awt.event.ActionEvent
      ): Unit = {

        ready =
          true


        sendClient(
          "READY"
        )


        addLobbyChat(
          "[LOCAL] READY"
        )
      }
    }
  )


  val notReadyButton =
    makeButton(
      "NOT READY",
      115,
      42
    )


  notReadyButton.addActionListener(
    new ActionListener {

      def actionPerformed(
        e: java.awt.event.ActionEvent
      ): Unit = {

        ready =
          false


        sendClient(
          "NOTREADY"
        )
      }
    }
  )


  val readyRow =
    new JPanel()


  readyRow.add(
    readyButton
  )


  readyRow.add(
    notReadyButton
  )


  panel.add(
    readyRow
  )


  val chatTitle =
    new JLabel(
      "CHAT"
    )


  chatTitle.setAlignmentX(
    0.5f
  )


  panel.add(
    chatTitle
  )


  lobbyChatArea =
    new JTextArea(
      8,
      60
    )


  lobbyChatArea.setEditable(
    false
  )


  panel.add(
    new JScrollPane(
      lobbyChatArea
    )
  )


  val chatRow =
    new JPanel()


  lobbyChatField =
    new JTextField(
      36
    )


  val sendButton =
    makeButton(
      "SEND",
      95,
      40
    )


  def sendCurrentChat(): Unit = {

    val text =
      lobbyChatField
        .getText
        .trim


    if (
      text.nonEmpty
    ) {

      sendChat(
        text
      )


      lobbyChatField
        .setText(
          ""
        )
    }
  }


  sendButton.addActionListener(
    new ActionListener {

      def actionPerformed(
        e: java.awt.event.ActionEvent
      ): Unit = {

        sendCurrentChat()
      }
    }
  )


  lobbyChatField.addActionListener(
    new ActionListener {

      def actionPerformed(
        e: java.awt.event.ActionEvent
      ): Unit = {

        sendCurrentChat()
      }
    }
  )


  chatRow.add(
    lobbyChatField
  )


  chatRow.add(
    sendButton
  )


  panel.add(
    chatRow
  )


  val navigation =
    new JPanel()


  val retryButton =
    makeButton(
      "RETRY",
      105,
      42
    )


  retryButton.addActionListener(
    new ActionListener {

      def actionPerformed(
        e: java.awt.event.ActionEvent
      ): Unit = {

        val host =
          lobbyHostField
            .getText
            .trim


        val port =
          Try(
            lobbyPortField
              .getText
              .trim
              .toInt
          )
          .getOrElse(
            5555
          )


        lobbyStatusLabel.setText(
          "RECONNECTING..."
        )


        val connected =
          connectToServer(
            host,
            port
          )


        if (
          connected
        ) {

          lobbyStatusLabel.setText(
            "CONNECTED"
          )

        } else {

          lobbyStatusLabel.setText(
            "RECONNECT FAILED"
          )
        }
      }
    }
  )


  navigation.add(
    retryButton
  )


  val backButton =
    makeButton(
      "BACK TO MENU",
      155,
      42
    )


  backButton.addActionListener(
    new ActionListener {

      def actionPerformed(
        e: java.awt.event.ActionEvent
      ): Unit = {

        returnToMenu()
      }
    }
  )


  navigation.add(
    backButton
  )


  panel.add(
    navigation
  )


  panel
}


/* =========================================================
   GAME PANEL
   ========================================================= */

class DrivePanel
  extends JPanel {


  setFocusable(
    true
  )


  /* =======================================================
     KEYBOARD
     ======================================================= */

  addKeyListener(
    new KeyAdapter {

      override def keyPressed(
        e: KeyEvent
      ): Unit = {

        val k =
          e.getKeyCode


        keys +=
          k


        if (
          k ==
          KeyEvent.VK_ESCAPE
        ) {

          leaveToLobby()

          return
        }


        if (
          k ==
          KeyEvent.VK_SPACE
        ) {

          nitro =
            true
        }


        if (
          k ==
          KeyEvent.VK_H
        ) {

          horn =
            true

          playTone(
            560,
            120
          )
        }


        if (
          k ==
          KeyEvent.VK_L
        ) {

          toggleLights()
        }


        if (
          k ==
          KeyEvent.VK_B
        ) {

          toggleHighBeam()
        }


        if (
          k ==
          KeyEvent.VK_G
        ) {

          toggleAutoGear()
        }


        if (
          k ==
          KeyEvent.VK_P
        ) {

          paused =
            !paused
        }


        if (
          k ==
          KeyEvent.VK_T
        ) {

          toggleRain()
        }


        if (
          k ==
          KeyEvent.VK_Q
        ) {

          toggleLeftIndicator()
        }


        if (
          k ==
          KeyEvent.VK_E
        ) {

          toggleRightIndicator()
        }


        if (
          k ==
          KeyEvent.VK_C
        ) {

          toggleCruise()
        }


        if (
          k ==
          KeyEvent.VK_1
        ) {

          setManualGear(
            "1"
          )
        }


        if (
          k ==
          KeyEvent.VK_2
        ) {

          setManualGear(
            "2"
          )
        }


        if (
          k ==
          KeyEvent.VK_3
        ) {

          setManualGear(
            "3"
          )
        }


        if (
          k ==
          KeyEvent.VK_4
        ) {

          setManualGear(
            "4"
          )
        }


        if (
          k ==
          KeyEvent.VK_5
        ) {

          setManualGear(
            "5"
          )
        }


        if (
          k ==
          KeyEvent.VK_R
        ) {

          setManualGear(
            "R"
          )
        }


        if (
          k ==
          KeyEvent.VK_N
        ) {

          setManualGear(
            "N"
          )
        }


        if (
          k ==
          KeyEvent.VK_F
        ) {

          flash(
            "WIPERS " +
            onOff(wipers)
          )
        }
      }


      override def keyReleased(
        e: KeyEvent
      ): Unit = {

        val k =
          e.getKeyCode


        keys -=
          k


        if (
          k ==
          KeyEvent.VK_SPACE
        ) {

          nitro =
            false
        }


        if (
          k ==
          KeyEvent.VK_H
        ) {

          horn =
            false
        }
      }
    }
  )


  /* =======================================================
     MOUSE / TOUCH
     ======================================================= */

  addMouseListener(
    new MouseAdapter {

      override def mousePressed(
        e: MouseEvent
      ): Unit = {

        val x =
          e.getX

        val y =
          e.getY

        val w =
          getWidth

        val h =
          getHeight


        /*
           bottom controls
        */

        if (
          y >
          h -
          100
        ) {

          if (
            x <
            120
          ) {

            steering =
              -1.0

            return
          }


          if (
            x >=
            120 &&
            x <
            245
          ) {

            braking =
              true

            return
          }


          if (
            x >
            w -
            245 &&
            x <=
            w -
            120
          ) {

            throttle =
              true

            return
          }


          if (
            x >
            w -
            120
          ) {

            steering =
              1.0

            return
          }
        }


        /*
           top controls
        */

        if (
          y <
          78
        ) {

          if (
            x <
            105
          ) {

            toggleLights()

            return
          }


          if (
            x >=
            105 &&
            x <
            210
          ) {

            toggleHighBeam()

            return
          }


          if (
            x >=
            210 &&
            x <
            315
          ) {

            nitro =
              true

            return
          }


          if (
            x >=
            315 &&
            x <
            420
          ) {

            horn =
              true

            playTone(
              560,
              120
            )

            return
          }
        }
      }


      override def mouseReleased(
        e: MouseEvent
      ): Unit = {

        throttle =
          false

        braking =
          false

        nitro =
          false

        horn =
          false

        steering =
          0.0
      }
    }
  )


  /* =======================================================
     PAINT
     ======================================================= */

  override def paintComponent(
    graphics: Graphics
  ): Unit = {

    super.paintComponent(
      graphics
    )


    val g =
      graphics.asInstanceOf[
        Graphics2D
      ]


    g.setRenderingHint(
      RenderingHints
        .KEY_ANTIALIASING,
      RenderingHints
        .VALUE_ANTIALIAS_ON
    )


    drawSky(g)

    drawRoad(g)

    drawTraffic(g)

    drawRemotePlayers(g)

    drawRain(g)

    drawPlayerCar(g)

    drawCockpit(g)

    drawHUD(g)

    drawTouchControls(g)


    if (
      paused
    ) {

      drawPause(g)
    }


    if (
      flashTime >
      System.currentTimeMillis()
    ) {

      drawFlash(g)
    }
  }


  /* =======================================================
     SKY
     ======================================================= */

  def drawSky(
    g: Graphics2D
  ): Unit = {

    val w =
      getWidth


    val h =
      getHeight


    var top =
      new Color(
        70,
        155,
        235
      )


    var bottom =
      new Color(
        200,
        232,
        250
      )


    if (
      timePhase ==
      1
    ) {

      top =
        new Color(
          238,
          132,
          75
        )

      bottom =
        new Color(
          255,
          215,
          150
        )
    }


    if (
      timePhase ==
      2
    ) {

      top =
        new Color(
          8,
          15,
          40
        )

      bottom =
        new Color(
          48,
          58,
          100
        )
    }


    if (
      timePhase ==
      3
    ) {

      top =
        new Color(
          110,
          65,
          130
        )

      bottom =
        new Color(
          210,
          125,
          135
        )
    }


    g.setPaint(
      new GradientPaint(
        0,
        0,
        top,
        0,
        h,
        bottom
      )
    )


    g.fillRect(
      0,
      0,
      w,
      h
    )


    if (
      timePhase ==
      2
    ) {

      drawStars(
        g
      )
    }


    drawSunMoon(
      g
    )
  }


  def drawStars(
    g: Graphics2D
  ): Unit = {

    var i =
      0


    while (
      i <
      80
    ) {

      val x =
        (
          i *
          89
        ) %
        Math.max(
          1,
          getWidth
        )


      val y =
        15 +
        (
          i *
          43
        ) %
        Math.max(
          1,
          getHeight /
          2
        )


      g.setColor(
        new Color(
          255,
          255,
          255,
          190
        )
      )


      val s =
        2 +
        i %
        3


      g.fillOval(
        x,
        y,
        s,
        s
      )


      i += 1
    }
  }


  def drawSunMoon(
    g: Graphics2D
  ): Unit = {

    if (
      timePhase ==
      2
    ) {

      g.setColor(
        new Color(
          235,
          242,
          255
        )
      )


      g.fillOval(
        getWidth -
        145,
        50,
        75,
        75
      )

    } else {

      g.setColor(
        new Color(
          255,
          220,
          90
        )
      )


      g.fillOval(
        getWidth -
        160,
        45,
        95,
        95
      )
    }
  }


  /* =======================================================
     ROAD
     ======================================================= */

  def drawRoad(
    g: Graphics2D
  ): Unit = {

    val w =
      getWidth


    val h =
      getHeight


    val horizon =
      h /
      3


    g.setColor(
      new Color(
        54,
        108,
        55
      )
    )


    g.fillRect(
      0,
      horizon,
      w,
      h -
      horizon
    )


    val topY =
      horizon -
      20


    val bottomY =
      h


    val tl =
      w /
      2 -
      85


    val tr =
      w /
      2 +
      85


    val bl =
      w /
      2 -
      275


    val br =
      w /
      2 +
      275


    val road =
      new java.awt.Polygon()


    road.addPoint(
      tl,
      topY
    )


    road.addPoint(
      tr,
      topY
    )


    road.addPoint(
      br,
      bottomY
    )


    road.addPoint(
      bl,
      bottomY
    )


    g.setColor(
      new Color(
        44,
        45,
        48
      )
    )


    g.fillPolygon(
      road
    )


    /*
       subtle road strips
    */

    g.setColor(
      new Color(
        58,
        59,
        63
      )
    )


    g.drawLine(
      w / 2 -
      50,
      topY,
      w / 2 -
      170,
      bottomY
    )


    g.drawLine(
      w / 2 +
      50,
      topY,
      w / 2 +
      170,
      bottomY
    )


    /*
       edges
    */

    g.setColor(
      new Color(
        235,
        230,
        210
      )
    )


    g.setStroke(
      new BasicStroke(
        7.0f
      )
    )


    g.drawLine(
      tl,
      topY,
      bl,
      bottomY
    )


    g.drawLine(
      tr,
      topY,
      br,
      bottomY
    )


    drawLaneLine(
      g,
      tl,
      tr,
      bl,
      br,
      topY,
      bottomY,
      1.0 /
      3.0
    )


    drawLaneLine(
      g,
      tl,
      tr,
      bl,
      br,
      topY,
      bottomY,
      2.0 /
      3.0
    )


    drawRoadside(
      g,
      horizon
    )
  }


  def drawLaneLine(
    g: Graphics2D,
    tl: Int,
    tr: Int,
    bl: Int,
    br: Int,
    topY: Int,
    bottomY: Int,
    fraction: Double
  ): Unit = {

    g.setColor(
      new Color(
        245,
        245,
        245,
        215
      )
    )


    var i =
      0


    while (
      i <
      11
    ) {

      if (
        i %
        2 ==
        0
      ) {

        val y1 =
          topY +
          (
            (
              bottomY -
              topY
            ) *
            i.toDouble /
            10.0
          ).toInt


        val y2 =
          topY +
          (
            (
              bottomY -
              topY
            ) *
            (
              i +
              0.5
            ).toDouble /
            10.0
          ).toInt


        val x1 =
          interpolateRoadX(
            tl,
            tr,
            bl,
            br,
            topY,
            bottomY,
            y1,
            fraction
          )


        val x2 =
          interpolateRoadX(
            tl,
            tr,
            bl,
            br,
            topY,
            bottomY,
            y2,
            fraction
          )


        g.setStroke(
          new BasicStroke(
            (
              2.0 +
              i *
              0.78
            ).toFloat
          )
        )


        g.drawLine(
          x1,
          y1,
          x2,
          y2
        )
      }


      i += 1
    }
  }


  def drawRoadside(
    g: Graphics2D,
    horizon: Int
  ): Unit = {

    var i =
      0


    while (
      i <
      10
    ) {

      val y =
        horizon +
        24 +
        i *
        58


      val left =
        60 +
        i *
        13


      val right =
        getWidth -
        75 -
        i *
        13


      g.setColor(
        new Color(
          30,
          90,
          38
        )
      )


      g.fillRect(
        left,
        y,
        8,
        45
      )


      g.fillOval(
        left -
        18,
        y -
        25,
        46,
        38
      )


      g.fillRect(
        right,
        y,
        8,
        45
      )


      g.fillOval(
        right -
        18,
        y -
        25,
        46,
        38
      )


      i += 1
    }
  }


  /* =======================================================
     TRAFFIC DRAW
     ======================================================= */

  def drawTraffic(
    g: Graphics2D
  ): Unit = {

    var i =
      0


    while (
      i <
      traffic.length
    ) {

      drawTrafficVehicle(
        g,
        traffic(i)
      )


      i += 1
    }
  }


  def drawTrafficVehicle(
    g: Graphics2D,
    car: TrafficCar
  ): Unit = {

    val w =
      getWidth


    val h =
      getHeight


    val horizon =
      h /
      3


    val z =
      Math.max(
        0.02,
        Math.min(
          1.0,
          car.z
        )
      )


    val y =
      horizon +
      (
        h -
        horizon
      ) *
      z


    val cx =
      trafficScreenX(
        car.lane,
        z,
        w
      )


    val cw =
      (
        19.0 +
        64.0 *
        z
      ).toInt


    val ch =
      (
        16.0 +
        82.0 *
        z
      ).toInt


    val left =
      cx -
      cw /
      2


    val top =
      y.toInt -
      ch /
      2


    /*
       shadow
    */

    g.setColor(
      new Color(
        0,
        0,
        0,
        110
      )
    )


    g.fillOval(
      left -
      cw /
      5,
      (
        y +
        ch *
        0.34
      ).toInt,
      cw +
      cw /
      2,
      Math.max(
        5,
        ch /
        6
      )
    )


    /*
       body color
    */

    var body =
      new Color(
        215,
        42,
        45
      )


    if (
      car.kind ==
      "BUS"
    ) {

      body =
        new Color(
          45,
          105,
          215
        )
    }


    if (
      car.kind ==
      "TRUCK"
    ) {

      body =
        new Color(
          194,
          135,
          60
        )
    }


    if (
      car.police
    ) {

      body =
        new Color(
          232,
          235,
          240
        )
    }


    /*
       body
    */

    g.setColor(
      body
    )


    g.fillRoundRect(
      left,
      top,
      cw,
      ch,
      Math.max(
        6,
        cw /
        4
      ),
      Math.max(
        6,
        cw /
        4
      )
    )


    /*
       roof / glass
    */

    g.setColor(
      new Color(
        22,
        40,
        60
      )
    )


    g.fillRoundRect(
      left +
      cw /
      7,
      top +
      ch /
      6,
      (
        cw *
        5 /
        7
      ),
      (
        ch /
        3
      ),
      6,
      6
    )


    /*
       windshield reflection
    */

    g.setColor(
      new Color(
        150,
        210,
        230,
        100
      )
    )


    g.fillRect(
      left +
      cw /
      5,
      top +
      ch /
      5,
      (
        cw *
        3 /
        5
      ),
      Math.max(
        2,
        ch /
        10
      )
    )


    /*
       front lights
    */

    g.setColor(
      new Color(
        255,
        245,
        180
      )
    )


    val lightW =
      Math.max(
        2,
        cw /
        7
      )


    val lightH =
      Math.max(
        2,
        ch /
        9
      )


    g.fillOval(
      left +
      cw /
      9,
      top +
      ch -
      ch /
      4,
      lightW,
      lightH
    )


    g.fillOval(
      left +
      cw -
      cw /
      9 -
      lightW,
      top +
      ch -
      ch /
      4,
      lightW,
      lightH
    )


    /*
       wheels
    */

    g.setColor(
      Color.BLACK
    )


    g.fillOval(
      left -
      1,
      top +
      ch -
      ch /
      5,
      Math.max(
        3,
        cw /
        5
      ),
      Math.max(
        3,
        ch /
        4
      )
    )


    g.fillOval(
      left +
      cw -
      Math.max(
        3,
        cw /
        5
      ) +
      1,
      top +
      ch -
      ch /
      5,
      Math.max(
        3,
        cw /
        5
      ),
      Math.max(
        3,
        ch /
        4
      )
    )


    /*
       POLICE LIGHTBAR
    */

    if (
      car.police
    ) {

      g.setColor(
        Color.RED
      )


      g.fillRect(
        left +
        cw /
        4,
        top +
        ch /
        18,
        cw /
        5,
        Math.max(
          2,
          ch /
          11
        )
      )


      g.setColor(
        Color.BLUE
      )


      g.fillRect(
        left +
        cw /
        2,
        top +
        ch /
        18,
        cw /
        5,
        Math.max(
          2,
          ch /
          11
        )
      )
    }
  }


  /* =======================================================
     REMOTE PLAYER CARS
     ======================================================= */

  def drawRemotePlayers(
    g: Graphics2D
  ): Unit = {

    if (
      !multiplayer
    ) {

      return
    }


    remotePlayers.synchronized {

      var i =
        0


      while (
        i <
        remotePlayers.length
      ) {

        val p =
          remotePlayers(i)


        val relative =
          p.distance -
          gameDistance


        if (
          Math.abs(
            relative
          ) <
          850.0
        ) {

          val z =
            Math.max(
              0.18,
              Math.min(
                1.0,
                0.58 +
                relative /
                1500.0
              )
            )


          val half =
            roadHalfWidth(
              z
            )


          val cx =
            getWidth /
            2.0 +
            p.x *
            half


          val y =
            getHeight /
            3 +
            (
              getHeight -
              getHeight /
              3
            ) *
            z


          val cw =
            28.0 +
            40.0 *
            z


          val ch =
            36.0 +
            52.0 *
            z


          g.setColor(
            new Color(
              150,
              80,
              255
            )
          )


          g.fillRoundRect(
            (
              cx -
              cw /
              2.0
            ).toInt,
            (
              y -
              ch /
              2.0
            ).toInt,
            cw.toInt,
            ch.toInt,
            12,
            12
          )


          g.setColor(
            new Color(
              25,
              42,
              60
            )
          )


          g.fillRoundRect(
            (
              cx -
              cw *
              0.3
            ).toInt,
            (
              y -
              ch *
              0.28
            ).toInt,
            (
              cw *
              0.6
            ).toInt,
            (
              ch *
              0.23
            ).toInt,
            5,
            5
          )


          g.setColor(
            Color.WHITE
          )


          g.setFont(
            new Font(
              "Arial",
              Font.BOLD,
              11
            )
          )


          g.drawString(
            p.name,
            (
              cx -
              30
            ).toInt,
            (
              y -
              ch /
              2.0 -
              7
            ).toInt
          )
        }


        i += 1
      }
    }
  }


  /* =======================================================
     PLAYER CAR
     ======================================================= */

  def drawPlayerCar(
    g: Graphics2D
  ): Unit = {

    val w =
      getWidth


    val h =
      getHeight


    val cx =
      w /
      2.0 +
      carX *
      235.0


    val y =
      h -
      210


    val cw =
      150.0


    val ch =
      185.0


    /*
       big shadow
    */

    g.setColor(
      new Color(
        0,
        0,
        0,
        120
      )
    )


    g.fillOval(
      (
        cx -
        92
      ).toInt,
      (
        y +
        66
      ).toInt,
      184,
      50
    )


    /*
       body
    */

    g.setColor(
      new Color(
        205,
        25,
        36
      )
    )


    g.fillRoundRect(
      (
        cx -
        cw /
        2.0
      ).toInt,
      (
        y -
        ch /
        2.0
      ).toInt,
      cw.toInt,
      ch.toInt,
      35,
      35
    )


    /*
       side highlight
    */

    g.setColor(
      new Color(
        240,
        75,
        80,
        150
      )
    )


    g.fillRoundRect(
      (
        cx -
        cw /
        2.0 +
        12
      ).toInt,
      (
        y -
        60
      ).toInt,
      10,
      112,
      8,
      8
    )


    /*
       roof
    */

    val roof =
      new java.awt.Polygon()


    roof.addPoint(
      (
        cx -
        55
      ).toInt,
      (
        y -
        70
      ).toInt
    )


    roof.addPoint(
      (
        cx -
        35
      ).toInt,
      (
        y -
        105
      ).toInt
    )


    roof.addPoint(
      (
        cx +
        35
      ).toInt,
      (
        y -
        105
      ).toInt
    )


    roof.addPoint(
      (
        cx +
        55
      ).toInt,
      (
        y -
        70
      ).toInt
    )


    g.setColor(
      new Color(
        145,
        20,
        30
      )
    )


    g.fillPolygon(
      roof
    )


    /*
       glass
    */

    g.setColor(
      new Color(
        20,
        42,
        65
      )
    )


    g.fillRoundRect(
      (
        cx -
        42
      ).toInt,
      (
        y -
        82
      ).toInt,
      84,
      45,
      12,
      12
    )


    /*
       glass reflection
    */

    g.setColor(
      new Color(
        170,
        220,
        240,
        110
      )
    )


    g.fillRect(
      (
        cx -
        31
      ).toInt,
      (
        y -
        74
      ).toInt,
      24,
      5
    )


    /*
       headlights
    */

    g.setColor(
      new Color(
        255,
        250,
        205
      )
    )


    g.fillRoundRect(
      (
        cx -
        59
      ).toInt,
      (
        y +
        55
      ).toInt,
      35,
      18,
      10,
      10
    )


    g.fillRoundRect(
      (
        cx +
        24
      ).toInt,
      (
        y +
        55
      ).toInt,
      35,
      18,
      10,
      10
    )


    /*
       headlights beam
    */

    if (
      lights ||
      highBeam
    ) {

      val alpha =
        if (
          highBeam
        ) {
          58
        } else {
          30
        }


      g.setColor(
        new Color(
          255,
          250,
          210,
          alpha
        )
      )


      val beamLeft =
        new java.awt.Polygon()


      beamLeft.addPoint(
        (
          cx -
          48
        ).toInt,
        (
          y +
          67
        ).toInt
      )


      beamLeft.addPoint(
        (
          cx -
          12
        ).toInt,
        (
          y +
          67
        ).toInt
      )


      beamLeft.addPoint(
        (
          cx -
          110
        ).toInt,
        h
      )


      beamLeft.addPoint(
        (
          cx -
          135
        ).toInt,
        h
      )


      g.fillPolygon(
        beamLeft
      )


      val beamRight =
        new java.awt.Polygon()


      beamRight.addPoint(
        (
          cx +
          12
        ).toInt,
        (
          y +
          67
        ).toInt
      )


      beamRight.addPoint(
        (
          cx +
          48
        ).toInt,
        (
          y +
          67
        ).toInt
      )


      beamRight.addPoint(
        (
          cx +
          135
        ).toInt,
        h
      )


      beamRight.addPoint(
        (
          cx +
          110
        ).toInt,
        h
      )


      g.fillPolygon(
        beamRight
      )
    }


    /*
       wheels
    */

    g.setColor(
      Color.BLACK
    )


    g.fillOval(
      (
        cx -
        77
      ).toInt,
      (
        y -
        28
      ).toInt,
      29,
      66
    )


    g.fillOval(
      (
        cx +
        48
      ).toInt,
      (
        y -
        28
      ).toInt,
      29,
      66
    )


    /*
       wheel hubs
    */

    g.setColor(
      new Color(
        165,
        170,
        180
      )
    )


    g.fillOval(
      (
        cx -
        70
      ).toInt,
      (
        y -
        12
      ).toInt,
      14,
      28
    )


    g.fillOval(
      (
        cx +
        55
      ).toInt,
      (
        y -
        12
      ).toInt,
      14,
      28
    )


    /*
       front grille
    */

    g.setColor(
      new Color(
        38,
        40,
        43
      )
    )


    g.fillRoundRect(
      (
        cx -
        37
      ).toInt,
      (
        y +
        55
      ).toInt,
      74,
      28,
      8,
      8
    )


    /*
       grille bars
    */

    g.setColor(
      new Color(
        120,
        120,
        125
      )
    )


    var bar =
      0


    while (
      bar <
      5
    ) {

      g.drawLine(
        (
          cx -
          25 +
          bar *
          12
        ).toInt,
        (
          y +
          59
        ).toInt,
        (
          cx -
          25 +
          bar *
          12
        ).toInt,
        (
          y +
          79
        ).toInt
      )


      bar += 1
    }


    /*
       bumper
    */

    g.setColor(
      new Color(
        55,
        55,
        60
      )
    )


    g.fillRoundRect(
      (
        cx -
        58
      ).toInt,
      (
        y +
        79
      ).toInt,
      116,
      18,
      10,
      10
    )


    /*
       indicators
    */

    val blink =
      (
        System.currentTimeMillis() /
        300L
      ) % 2L


    if (
      (
        leftIndicator ||
        hazards
      ) &&
      blink ==
      0L
    ) {

      g.setColor(
        new Color(
          255,
          170,
          30
        )
      )


      g.fillOval(
        (
          cx -
          67
        ).toInt,
        (
          y +
          39
        ).toInt,
        18,
        12
      )
    }


    if (
      (
        rightIndicator ||
        hazards
      ) &&
      blink ==
      0L
    ) {

      g.setColor(
        new Color(
          255,
          170,
          30
        )
      )


      g.fillOval(
        (
          cx +
          49
        ).toInt,
        (
          y +
          39
        ).toInt,
        18,
        12
      )
    }


    /*
       steering angle visual
    */

    g.setColor(
      new Color(
        210,
        210,
        215
      )
    )


    g.setStroke(
      new BasicStroke(
        6.0f
      )
    )


    val wheelAngle =
      steering *
      0.55


    val steeringRadius =
      48.0


    val sx =
      w /
      2.0


    val sy =
      h -
      78.0


    val ex =
      sx +
      Math.sin(
        wheelAngle
      ) *
      steeringRadius


    val ey =
      sy -
      Math.cos(
        wheelAngle
      ) *
      steeringRadius


    g.drawLine(
      sx.toInt,
      sy.toInt,
      ex.toInt,
      ey.toInt
    )
  }


  /* =======================================================
     RAIN
     ======================================================= */

  def drawRain(
    g: Graphics2D
  ): Unit = {

    if (
      !rain
    ) {

      return
    }


    g.setColor(
      new Color(
        175,
        205,
        250,
        125
      )
    )


    var i =
      0


    while (
      i <
      150
    ) {

      val x =
        (
          i *
          97 +
          System.currentTimeMillis() /
          7L
        ).toInt %
        Math.max(
          1,
          getWidth
        )


      val y =
        (
          i *
          43 +
          System.currentTimeMillis() /
          5L
        ).toInt %
        Math.max(
          1,
          getHeight
        )


      g.drawLine(
        x,
        y,
        x -
        5,
        y +
        18
      )


      i += 1
    }


    /*
       windshield wiper
    */

    if (
      wipers
    ) {

      g.setColor(
        new Color(
          30,
          30,
          35,
          130
        )
      )


      g.setStroke(
        new BasicStroke(
          5.0f
        )
      )


      val angle =
        Math.sin(
          System.currentTimeMillis() /
          700.0
        )


      val cx =
        getWidth /
        2.0


      val cy =
        getHeight -
        220.0


      val len =
        180.0


      val ex =
        cx +
        angle *
        len


      val ey =
        cy -
        60.0


      g.drawLine(
        cx.toInt,
        cy.toInt,
        ex.toInt,
        ey.toInt
      )
    }
  }


  /* =======================================================
     COCKPIT
     ======================================================= */

  def drawCockpit(
    g: Graphics2D
  ): Unit = {

    val w =
      getWidth


    val h =
      getHeight


    /*
       dashboard
    */

    g.setColor(
      new Color(
        12,
        15,
        20,
        235
      )
    )


    g.fillRoundRect(
      w /
      2 -
      265,
      h -
      180,
      530,
      165,
      32,
      32
    )


    /*
       speedometer
    */

    g.setColor(
      new Color(
        3,
        6,
        9
      )
    )


    g.fillOval(
      w /
      2 -
      145,
      h -
      155,
      125,
      125
    )


    g.setColor(
      new Color(
        235,
        235,
        240
      )
    )


    g.setStroke(
      new BasicStroke(
        7.0f
      )
    )


    g.drawOval(
      w /
      2 -
      145,
      h -
      155,
      125,
      125
    )


    /*
       gear dial
    */

    g.setColor(
      new Color(
        3,
        6,
        9
      )
    )


    g.fillOval(
      w /
      2 +
      20,
      h -
      155,
      125,
      125
    )


    g.setColor(
      new Color(
        235,
        235,
        240
      )
    )


    g.drawOval(
      w /
      2 +
      20,
      h -
      155,
      125,
      125
    )


    /*
       speed text
    */

    g.setColor(
      Color.WHITE
    )


    g.setFont(
      new Font(
        "Arial",
        Font.BOLD,
        15
      )
    )


    g.drawString(
      "KM/H",
      w /
      2 -
      114,
      h -
      48
    )


    g.drawString(
      "GEAR",
      w /
      2 +
      58,
      h -
      118
    )


    g.setFont(
      new Font(
        "Arial",
        Font.BOLD,
        29
      )
    )


    g.drawString(
      Math.abs(
        gameSpeed
      ).toInt.toString,
      w /
      2 -
      115,
      h -
      73
    )


    g.drawString(
      gear,
      w /
      2 +
      71,
      h -
      73
    )


    /*
       speed needle
    */

    val speedAngle =
      -Math.PI *
      0.75 +
      Math.min(
        180.0,
        Math.abs(
          gameSpeed
        )
      ) /
      180.0 *
      Math.PI *
      1.5


    val sx =
      w /
      2.0 -
      82.0


    val sy =
      h -
      92.0


    val needleLen =
      45.0


    val nx =
      sx +
      Math.cos(
        speedAngle
      ) *
      needleLen


    val ny =
      sy +
      Math.sin(
        speedAngle
      ) *
      needleLen


    g.setColor(
      new Color(
        255,
        70,
        70
      )
    )


    g.setStroke(
      new BasicStroke(
        4.0f
      )
    )


    g.drawLine(
      sx.toInt,
      sy.toInt,
      nx.toInt,
      ny.toInt
    )


    /*
       nitro heat bar
    */

    val barX =
      w /
      2 -
      8


    val barY =
      h -
      55


    g.setColor(
      new Color(
        35,
        35,
        40
      )
    )


    g.fillRoundRect(
      barX,
      barY,
      16,
      38,
      7,
      7
    )


    g.setColor(
      new Color(
        60,
        160,
        255
      )
    )


    val heatHeight =
      (
        34.0 *
        nitroHeat /
        100.0
      ).toInt


    g.fillRoundRect(
      barX +
      2,
      barY +
      36 -
      heatHeight,
      12,
      heatHeight,
      5,
      5
    )
  }


  /* =======================================================
     HUD
     ======================================================= */

  def drawHUD(
    g: Graphics2D
  ): Unit = {

    g.setColor(
      new Color(
        7,
        12,
        20,
        225
      )
    )


    g.fillRoundRect(
      15,
      95,
      305,
      230,
      18,
      18
    )


    g.setColor(
      Color.WHITE
    )


    g.setFont(
      new Font(
        "Arial",
        Font.BOLD,
        16
      )
    )


    g.drawString(
      "REAL DRIVE X",
      30,
      120
    )


    g.setFont(
      new Font(
        "Arial",
        Font.PLAIN,
        13
      )
    )


    g.drawString(
      "MODE: " +
      modeText(),
      30,
      145
    )


    g.drawString(
      "SPEED: " +
      Math.abs(
        gameSpeed
      ).toInt +
      " km/h",
      30,
      167
    )


    g.drawString(
      "DISTANCE: " +
      gameDistance.toInt +
      " m",
      30,
      189
    )


    g.drawString(
      "SCORE: " +
      score,
      30,
      211
    )


    g.drawString(
      "FUEL: " +
      fuel.toInt +
      "%",
      30,
      233
    )


    g.drawString(
      "DAMAGE: " +
      damage.toInt +
      "%",
      30,
      255
    )


    g.drawString(
      "MISSION " +
      missionNumber +
      " ? " +
      missionProgress +
      "/" +
      missionTarget.toInt,
      30,
      277
    )


    g.drawString(
      "NITRO HEAT: " +
      nitroHeat.toInt +
      "%",
      30,
      299
    )


    g.drawString(
      "CRUISE: " +
      onOff(cruiseControl),
      30,
      319
    )


    drawGPS(
      g
    )
  }


  /* =======================================================
     GPS
     ======================================================= */

  def drawGPS(
    g: Graphics2D
  ): Unit = {

    val x =
      getWidth -
      190


    val y =
      95


    g.setColor(
      new Color(
        8,
        18,
        22,
        235
      )
    )


    g.fillRoundRect(
      x,
      y,
      175,
      205,
      18,
      18
    )


    g.setColor(
      new Color(
        60,
        155,
        80
      )
    )


    g.fillRoundRect(
      x +
      18,
      y +
      18,
      138,
      125,
      20,
      20
    )


    g.setColor(
      new Color(
        35,
        35,
        35
      )
    )


    g.fillRect(
      x +
      78,
      y +
      18,
      28,
      124
    )


    /*
       route
    */

    g.setColor(
      new Color(
        245,
        230,
        80
      )
    )


    g.setStroke(
      new BasicStroke(
        3.0f
      )
    )


    g.drawLine(
      x +
      92,
      y +
      28,
      x +
      78,
      y +
      76
    )


    g.drawLine(
      x +
      78,
      y +
      76,
      x +
      102,
      y +
      112
    )


    g.drawLine(
      x +
      102,
      y +
      112,
      x +
      90,
      y +
      138
    )


    /*
       player
    */

    g.setColor(
      Color.WHITE
    )


    g.fillOval(
      x +
      86,
      y +
      99,
      10,
      10
    )


    /*
       destination
    */

    g.setColor(
      Color.RED
    )


    g.fillOval(
      x +
      101,
      y +
      43,
      12,
      12
    )


    g.setColor(
      Color.WHITE
    )


    g.setFont(
      new Font(
        "Arial",
        Font.BOLD,
        13
      )
    )


    g.drawString(
      "GPS / MAP",
      x +
      48,
      y +
      164
    )


    g.setFont(
      new Font(
        "Arial",
        Font.PLAIN,
        11
      )
    )


    g.drawString(
      "DESTINATION",
      x +
      42,
      y +
      182
    )


    g.drawString(
      "CHECKPOINT",
      x +
      47,
      y +
      198
    )
  }


  /* =======================================================
     TOUCH BUTTONS
     ======================================================= */

  def drawTouchControls(
    g: Graphics2D
  ): Unit = {

    val w =
      getWidth


    val h =
      getHeight


    drawControl(
      g,
      18,
      h -
      90,
      96,
      58,
      "LEFT"
    )


    drawControl(
      g,
      123,
      h -
      90,
      96,
      58,
      "BRAKE"
    )


    drawControl(
      g,
      w -
      219,
      h -
      90,
      96,
      58,
      "GAS"
    )


    drawControl(
      g,
      w -
      114,
      h -
      90,
      96,
      58,
      "RIGHT"
    )


    drawControl(
      g,
      18,
      18,
      90,
      48,
      "LIGHT"
    )


    drawControl(
      g,
      118,
      18,
      90,
      48,
      "BEAM"
    )


    drawControl(
      g,
      218,
      18,
      90,
      48,
      "NITRO"
    )


    drawControl(
      g,
      318,
      18,
      90,
      48,
      "HORN"
    )
  }


  def drawControl(
    g: Graphics2D,
    x: Int,
    y: Int,
    w: Int,
    h: Int,
    text: String
  ): Unit = {

    g.setColor(
      new Color(
        15,
        20,
        30,
        205
      )
    )


    g.fillRoundRect(
      x,
      y,
      w,
      h,
      15,
      15
    )


    g.setColor(
      new Color(
        255,
        255,
        255,
        175
      )
    )


    g.setStroke(
      new BasicStroke(
        2.0f
      )
    )


    g.drawRoundRect(
      x,
      y,
      w,
      h,
      15,
      15
    )


    g.setColor(
      Color.WHITE
    )


    g.setFont(
      new Font(
        "Arial",
        Font.BOLD,
        12
      )
    )


    val tw =
      g.getFontMetrics
        .stringWidth(
          text
        )


    g.drawString(
      text,
      x +
      (
        w -
        tw
      ) /
      2,
      y +
      h /
      2 +
      5
    )
  }


  /* =======================================================
     PAUSE
     ======================================================= */

  def drawPause(
    g: Graphics2D
  ): Unit = {

    g.setColor(
      new Color(
        0,
        0,
        0,
        180
      )
    )


    g.fillRect(
      0,
      0,
      getWidth,
      getHeight
    )


    g.setColor(
      Color.WHITE
    )


    g.setFont(
      new Font(
        "Arial",
        Font.BOLD,
        55
      )
    )


    g.drawString(
      "PAUSED",
      getWidth /
      2 -
      110,
      getHeight /
      2
    )
  }


  /* =======================================================
     FLASH
     ======================================================= */

  def drawFlash(
    g: Graphics2D
  ): Unit = {

    g.setColor(
      new Color(
        0,
        0,
        0,
        180
      )
    )


    g.fillRoundRect(
      getWidth /
      2 -
      180,
      70,
      360,
      56,
      18,
      18
    )


    g.setColor(
      Color.WHITE
    )


    g.setFont(
      new Font(
        "Arial",
        Font.BOLD,
        20
      )
    )


    val tw =
      g.getFontMetrics
        .stringWidth(
          flashMessage
        )


    g.drawString(
      flashMessage,
      getWidth /
      2 -
      tw /
      2,
      106
    )
  }
}


/* =========================================================
   BUILD WINDOW
   ========================================================= */

frame =
  new JFrame(
    "REAL DRIVE X"
  )


frame.setDefaultCloseOperation(
  WindowConstants.EXIT_ON_CLOSE
)


frame.setSize(
  1280,
  820
)


frame.setLocationRelativeTo(
  null
)


cards =
  new java.awt.CardLayout()


root =
  new JPanel(
    cards
  )


menuPanel =
  makeMenu()


lobbyPanel =
  makeLobby()


gamePanel =
  new DrivePanel()


root.add(
  menuPanel,
  "menu"
)


root.add(
  lobbyPanel,
  "lobby"
)


root.add(
  gamePanel,
  "game"
)


frame.setContentPane(
  root
)


frame.setVisible(
  true
)


showCard(
  "menu"
)


/* =========================================================
   MAIN TIMER
   ========================================================= */

val gameTimer =
  new Timer(
    33,
    new ActionListener {

      def actionPerformed(
        e: java.awt.event.ActionEvent
      ): Unit = {

        updateGame()


        if (
          gamePanel !=
          null
        ) {

          gamePanel.repaint()
        }


        if (
          flashTime >
          0L &&
          flashTime <
          System.currentTimeMillis()
        ) {

          flashTime =
            0L
        }


        if (
          currentScreen ==
          "game" &&
          gamePanel !=
          null
        ) {

          gamePanel.requestFocusInWindow()
        }


        updateLobbyPlayers()
      }
    }
  )


gameTimer.start()


/* =========================================================
   WINDOW FOCUS
   ========================================================= */

frame.addWindowFocusListener(
  new java.awt.event.WindowAdapter {

    override def windowGainedFocus(
      e: java.awt.event.WindowEvent
    ): Unit = {

      if (
        gamePanel !=
        null &&
        currentScreen ==
        "game"
      ) {

        gamePanel
          .requestFocusInWindow()
      }
    }


    override def windowClosing(
      e: java.awt.event.WindowEvent
    ): Unit = {

      exitApplication()
    }
  }
)