Code Sketch


yoiiiiiiii
By: Mhalsakant School
Category: Programming
import javax.swing._
import java.awt._
import java.awt.event._
import java.io._
import java.net._
import java.util.concurrent._
import scala.collection.mutable
import scala.util.Random

val PORT = 5555

// ============================================================
// DATA
// ============================================================

case class PlayerData(
  var name: String,
  var x: Double,
  var distance: Double,
  var speed: Double,
  var gear: String,
  var racing: Boolean,
  var ready: Boolean
)

case class TrafficCar(
  var lane: Int,
  var z: Double,
  var speed: Double,
  var kind: Int,
  var body: Color
)

// ============================================================
// ONLINE SERVER
// ============================================================

class DriveServer {

  case class Client(
    id: Int,
    socket: Socket,
    in: BufferedReader,
    out: PrintWriter,
    var name: String = "Player",
    var x: Double = 0.0,
    var distance: Double = 0.0,
    var speed: Double = 0.0,
    var gear: String = "N",
    var racing: Boolean = false,
    var ready: Boolean = false
  )

  val clients =
    new ConcurrentHashMap[Int, Client]()

  val pool =
    Executors.newCachedThreadPool()

  var server: ServerSocket = null
  @volatile var running = false
  var nextId = 9000

  def start(): Unit = synchronized {

    if (running)
      return

    try {

      server =
        new ServerSocket(PORT)

      running = true

      val thread =
        new Thread(
          new Runnable {
            def run(): Unit = {

              while (running) {

                try {

                  val s =
                    server.accept()

                  s.setTcpNoDelay(true)

                  nextId += 1

                  val id =
                    nextId

                  val in =
                    new BufferedReader(
                      new InputStreamReader(
                        s.getInputStream
                      )
                    )

                  val out =
                    new PrintWriter(
                      new BufferedWriter(
                        new OutputStreamWriter(
                          s.getOutputStream
                        )
                      ),
                      true
                    )

                  val client =
                    Client(
                      id,
                      s,
                      in,
                      out
                    )

                  clients.put(
                    id,
                    client
                  )

                  out.println(
                    "WELCOME|" +
                      id
                  )

                  broadcastPlayers()

                  pool.submit(
                    new Runnable {
                      def run(): Unit =
                        readClient(client)
                    }
                  )

                } catch {
                  case _: Throwable =>
                }
              }
            }
          },
          "DRIVE_SERVER"
        )

      thread.setDaemon(true)
      thread.start()

    } catch {
      case _: Throwable =>
        running = false
    }
  }

  def safeName(
      s: String
  ): String = {

    val n =
      Option(s)
        .getOrElse("Player")
        .trim
        .replace("|", " ")

    if (n.isEmpty)
      "Player"
    else
      n.take(18)
  }

  def safeText(
      s: String
  ): String = {

    Option(s)
      .getOrElse("")
      .replace("\n", " ")
      .replace("\r", " ")
      .replace("|", "/")
      .take(120)
  }

  def readClient(
      c: Client
  ): Unit = {

    try {

      var line: String = null

      while ({
        line = c.in.readLine()
        line != null
      }) {

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

        if (p.length > 0) {

          if (
            p(0) == "HELLO" &&
            p.length >= 2
          ) {

            c.name =
              safeName(p(1))

            broadcastPlayers()

          } else if (
            p(0) == "STATE" &&
            p.length >= 7
          ) {

            try {

              c.x =
                p(1).toDouble

              c.distance =
                p(2).toDouble

              c.speed =
                p(3).toDouble

              c.gear =
                p(4)

              c.racing =
                p(5) == "1"

              c.ready =
                p(6) == "1"

              broadcastPlayer(c)

            } catch {
              case _: Throwable =>
            }

          } else if (
            p(0) == "CHAT" &&
            p.length >= 2
          ) {

            val txt =
              safeText(
                p.drop(1)
                  .mkString("|")
              )

            broadcast(
              "CHAT|" +
                c.id +
                "|" +
                c.name +
                "|" +
                txt
            )

          } else if (
            p(0) == "RACE"
          ) {

            c.racing = true
            c.ready = true

            broadcastPlayers()
          }

        }
      }

    } catch {
      case _: Throwable =>
    }

    clients.remove(
      c.id
    )

    broadcast(
      "LEAVE|" +
        c.id
    )

    broadcastPlayers()

    try {
      c.socket.close()
    } catch {
      case _: Throwable =>
    }
  }

  def broadcastPlayer(
      c: Client
  ): Unit = {

    broadcast(
      "PLAYER|" +
        c.id +
        "|" +
        c.name +
        "|" +
        c.x +
        "|" +
        c.distance +
        "|" +
        c.speed +
        "|" +
        c.gear +
        "|" +
        (if (c.racing) "1" else "0") +
        "|" +
        (if (c.ready) "1" else "0")
    )
  }

  def broadcastPlayers(): Unit = {

    val it =
      clients.values().iterator()

    while (it.hasNext) {

      broadcastPlayer(
        it.next()
      )
    }
  }

  def broadcast(
      msg: String
  ): Unit = {

    val it =
      clients.values().iterator()

    while (it.hasNext) {

      val c =
        it.next()

      try {
        c.out.println(
          msg
        )
      } catch {
        case _: Throwable =>
      }
    }
  }
}

// ============================================================
// GLOBAL SERVER
// ============================================================

val driveServer =
  new DriveServer

driveServer.start()

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

class DriveWindow
    extends JFrame {

  setTitle(
    "REALISTIC DRIVE ONLINE"
  )

  setSize(
    1280,
    820
  )

  setLocationRelativeTo(
    null
  )

  setResizable(false)

  setDefaultCloseOperation(
    WindowConstants.EXIT_ON_CLOSE
  )

  val cards =
    new CardLayout

  val root =
    new JPanel(cards)

  val menu =
    new StartScreen

  val lobby =
    new OnlineLobby

  val cockpit =
    new CockpitScreen

  root.add(
    menu,
    "MENU"
  )

  root.add(
    lobby,
    "LOBBY"
  )

  root.add(
    cockpit,
    "GAME"
  )

  add(root)

  menu.hostPressed =
    (name: String) => {

      cockpit.startPlayer(
        name,
        "127.0.0.1",
        true
      )

      lobby.configure(
        name,
        "127.0.0.1"
      )

      cards.show(
        root,
        "LOBBY"
      )

      lobby.requestFocusInWindow()
    }

  menu.joinPressed =
    (name: String, host: String) => {

      lobby.configure(
        name,
        host
      )

      cards.show(
        root,
        "LOBBY"
      )

      lobby.connect(
        name,
        host
      )

      lobby.requestFocusInWindow()
    }

  menu.offlinePressed =
    (name: String) => {

      cockpit.startPlayer(
        name,
        "",
        false
      )

      cards.show(
        root,
        "GAME"
      )

      cockpit.requestFocusInWindow()
    }

  lobby.playPressed =
    () => {

      cockpit.startPlayer(
        lobby.playerName,
        lobby.serverHost,
        lobby.serverHost.nonEmpty
      )

      cards.show(
        root,
        "GAME"
      )

      cockpit.requestFocusInWindow()
    }

  lobby.exitPressed =
    () => {

      lobby.disconnect()

      cards.show(
        root,
        "MENU"
      )

      menu.requestFocusInWindow()
    }

  setVisible(
    true
  )
}

// ============================================================
// START SCREEN
// ============================================================

class StartScreen
    extends JPanel {

  var hostPressed:
    String => Unit =
      _ => ()

  var joinPressed:
    (String, String) => Unit =
      (_, _) => ()

  var offlinePressed:
    String => Unit =
      _ => ()

  val nameField =
    new JTextField(
      "Player"
    )

  val ipField =
    new JTextField(
      "127.0.0.1"
    )

  setLayout(
    null
  )

  setBackground(
    new Color(
      5,
      10,
      18
    )
  )

  val title =
    new JLabel(
      "REALISTIC DRIVE",
      SwingConstants.CENTER
    )

  title.setForeground(
    new Color(
      0,
      235,
      255
    )
  )

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

  title.setBounds(
    250,
    70,
    780,
    70
  )

  add(title)

  val sub =
    new JLabel(
      "ONLINE MULTIPLAYER ? COCKPIT ? CHAT ? RACE",
      SwingConstants.CENTER
    )

  sub.setForeground(
    Color.WHITE
  )

  sub.setFont(
    new Font(
      "Arial",
      Font.PLAIN,
      17
    )
  )

  sub.setBounds(
    300,
    140,
    680,
    35
  )

  add(sub)

  addLabel(
    "YOUR NAME",
    390,
    225
  )

  nameField.setBounds(
    520,
    215,
    300,
    40
  )

  add(nameField)

  addLabel(
    "SERVER IP",
    390,
    280
  )

  ipField.setBounds(
    520,
    270,
    300,
    40
  )

  add(ipField)

  val host =
    createButton(
      "HOST + PLAY ONLINE",
      390,
      335
    )

  host.addActionListener(
    new ActionListener {
      def actionPerformed(
          e: ActionEvent
      ): Unit = {

        hostPressed(
          name()
        )
      }
    }
  )

  add(host)

  val join =
    createButton(
      "JOIN ONLINE SERVER",
      390,
      400
    )

  join.addActionListener(
    new ActionListener {
      def actionPerformed(
          e: ActionEvent
      ): Unit = {

        joinPressed(
          name(),
          ipField.getText.trim
        )
      }
    }
  )

  add(join)

  val offline =
    createButton(
      "OFFLINE DRIVE",
      390,
      465
    )

  offline.addActionListener(
    new ActionListener {
      def actionPerformed(
          e: ActionEvent
      ): Unit = {

        offlinePressed(
          name()
        )
      }
    }
  )

  add(offline)

  val info =
    new JLabel(
      "<html><center>" +
        "1. HOST PC ? HOST + PLAY ONLINE<br>" +
        "2. OTHER PC ? enter HOST PC IP ? JOIN ONLINE SERVER<br>" +
        "3. After joining, lobby shows player names and status<br>" +
        "4. CHAT = messages ? RACE = start race" +
        "</center></html>",
      SwingConstants.CENTER
    )

  info.setForeground(
    new Color(
      190,
      205,
      215
    )
  )

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

  info.setBounds(
    330,
    545,
    620,
    105
  )

  add(info)

  def name(): String = {

    val n =
      nameField
        .getText
        .trim
        .replace("|", " ")

    if (n.isEmpty)
      "Player"
    else
      n.take(18)
  }

  def addLabel(
      text: String,
      x: Int,
      y: Int
  ): Unit = {

    val l =
      new JLabel(
        text
      )

    l.setForeground(
      Color.WHITE
    )

    l.setFont(
      new Font(
        "Arial",
        Font.BOLD,
        14
      )
    )

    l.setBounds(
      x,
      y,
      120,
      30
    )

    add(l)
  }

  def createButton(
      text: String,
      x: Int,
      y: Int
  ): JButton = {

    val b =
      new JButton(
        text
      )

    b.setBounds(
      x,
      y,
      500,
      52
    )

    b.setFont(
      new Font(
        "Arial",
        Font.BOLD,
        17
      )
    )

    b.setForeground(
      Color.WHITE
    )

    b.setBackground(
      new Color(
        15,
        48,
        62
      )
    )

    b.setFocusPainted(
      false
    )

    b
  }
}

// ============================================================
// ONLINE LOBBY
// ============================================================

class OnlineLobby
    extends JPanel {

  var playerName =
    "Player"

  var serverHost =
    ""

  var playPressed:
    () => Unit =
      () => ()

  var exitPressed:
    () => Unit =
      () => ()

  val playerList =
    new JTextArea

  val chat =
    new JTextArea

  val chatInput =
    new JTextField

  var socket:
    Socket = null

  var in:
    BufferedReader = null

  var out:
    PrintWriter = null

  var connected =
    false

  val remote =
    mutable.Map[
      Int,
      PlayerData
    ]()

  setLayout(
    null
  )

  setBackground(
    new Color(
      7,
      12,
      19
    )
  )

  val title =
    new JLabel(
      "ONLINE LOBBY",
      SwingConstants.CENTER
    )

  title.setForeground(
    new Color(
      0,
      235,
      255
    )
  )

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

  title.setBounds(
    360,
    25,
    560,
    55
  )

  add(title)

  playerList.setEditable(
    false
  )

  playerList.setBackground(
    new Color(
      12,
      20,
      28
    )
  )

  playerList.setForeground(
    Color.WHITE
  )

  playerList.setFont(
    new Font(
      "Monospaced",
      Font.PLAIN,
      15
    )
  )

  val playerScroll =
    new JScrollPane(
      playerList
    )

  playerScroll.setBounds(
    70,
    120,
    500,
    450
  )

  add(
    playerScroll
  )

  chat.setEditable(
    false
  )

  chat.setBackground(
    new Color(
      12,
      20,
      28
    )
  )

  chat.setForeground(
    new Color(
      230,
      235,
      240
    )
  )

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

  val chatScroll =
    new JScrollPane(
      chat
    )

  chatScroll.setBounds(
    620,
    120,
    520,
    340
  )

  add(
    chatScroll
  )

  chatInput.setBounds(
    620,
    475,
    390,
    40
  )

  add(
    chatInput
  )

  val send =
    new JButton(
      "SEND"
    )

  send.setBounds(
    1020,
    475,
    120,
    40
  )

  add(
    send
  )

  send.addActionListener(
    new ActionListener {
      def actionPerformed(
          e: ActionEvent
      ): Unit =
        sendChat()
    }
  )

  val race =
    new JButton(
      "? START RACE"
    )

  race.setBounds(
    620,
    545,
    250,
    55
  )

  race.setFont(
    new Font(
      "Arial",
      Font.BOLD,
      18
    )
  )

  add(
    race
  )

  race.addActionListener(
    new ActionListener {
      def actionPerformed(
          e: ActionEvent
      ): Unit = {

        if (connected) {
          send(
            "RACE"
          )
        }

        playPressed()
      }
    }
  )

  val play =
    new JButton(
      "ENTER COCKPIT"
    )

  play.setBounds(
    890,
    545,
    250,
    55
  )

  play.setFont(
    new Font(
      "Arial",
      Font.BOLD,
      18
    )
  )

  add(
    play
  )

  play.addActionListener(
    new ActionListener {
      def actionPerformed(
          e: ActionEvent
      ): Unit =
        playPressed()
    }
  )

  val exit =
    new JButton(
      "EXIT"
    )

  exit.setBounds(
    70,
    600,
    250,
    45
  )

  add(
    exit
  )

  exit.addActionListener(
    new ActionListener {
      def actionPerformed(
          e: ActionEvent
      ): Unit =
        exitPressed()
    }
  )

  def configure(
      name: String,
      host: String
  ): Unit = {

    playerName =
      name

    serverHost =
      host

    updateList()
  }

  def connect(
      name: String,
      host: String
  ): Unit = {

    playerName =
      name

    serverHost =
      host

    disconnect()

    if (host.isEmpty) {
      updateList()
      return
    }

    try {

      socket =
        new Socket()

      socket.connect(
        new InetSocketAddress(
          host,
          PORT
        ),
        4000
      )

      socket.setTcpNoDelay(
        true
      )

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

      out =
        new PrintWriter(
          new BufferedWriter(
            new OutputStreamWriter(
              socket.getOutputStream
            )
          ),
          true
        )

      connected =
        true

      send(
        "HELLO|" +
          playerName
      )

      val t =
        new Thread(
          new Runnable {
            def run(): Unit =
              readServer()
          }
        )

      t.setDaemon(
        true
      )

      t.start()

    } catch {

      case _: Throwable =>

        connected =
          false

        SwingUtilities.invokeLater(
          new Runnable {
            def run(): Unit = {

              JOptionPane.showMessageDialog(
                OnlineLobby.this,
                "Connection failed.\n" +
                  "Use HOST + PLAY on the first PC.\n" +
                  "Other PCs must use the host PC's IP."
              )
            }
          }
        )
    }
  }

  def send(
      s: String
  ): Unit = {

    if (
      out != null
    ) {

      try {
        out.println(
          s
        )
      } catch {
        case _: Throwable =>
          connected =
            false
      }
    }
  }

  def readServer(): Unit = {

    try {

      var line:
        String = null

      while ({
        line =
          in.readLine()

        line != null
      }) {

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

        if (
          p.length >= 8 &&
          p(0) == "PLAYER"
        ) {

          try {

            val id =
              p(1).toInt

            remote.synchronized {

              remote.update(
                id,
                PlayerData(
                  p(2),
                  p(3).toDouble,
                  p(4).toDouble,
                  p(5).toDouble,
                  p(6),
                  p(7) == "1",
                  p.length >= 9 &&
                    p(8) == "1"
                )
              )
            }

            SwingUtilities.invokeLater(
              new Runnable {
                def run(): Unit =
                  updateList()
              }
            )

          } catch {
            case _: Throwable =>
          }

        } else if (
          p.length >= 4 &&
          p(0) == "CHAT"
        ) {

          val msg =
            p(2) +
              ": " +
              p.drop(3).mkString("|")

          SwingUtilities.invokeLater(
            new Runnable {
              def run(): Unit = {

                chat.append(
                  msg +
                    "\n"
                )

                chat.setCaretPosition(
                  chat.getDocument.getLength
                )
              }
            }
          )

        } else if (
          p.length >= 2 &&
          p(0) == "LEAVE"
        ) {

          try {

            remote.synchronized {
              remote.remove(
                p(1).toInt
              )
            }

            SwingUtilities.invokeLater(
              new Runnable {
                def run(): Unit =
                  updateList()
              }
            )

          } catch {
            case _: Throwable =>
          }
        }
      }

    } catch {
      case _: Throwable =>
    }

    connected =
      false
  }

  def sendChat(): Unit = {

    val text =
      chatInput
        .getText
        .trim

    if (
      text.nonEmpty
    ) {

      if (connected) {

        send(
          "CHAT|" +
            text.take(120)
        )

      } else {

        chat.append(
          playerName +
            ": " +
            text.take(120) +
            "\n"
        )
      }

      chatInput.setText(
        ""
      )
    }
  }

  def updateList(): Unit = {

    val sb =
      new StringBuilder

    sb.append(
      "ONLINE PLAYERS\n"
    )

    sb.append(
      "===============================\n"
    )

    if (
      remote.isEmpty
    ) {

      sb.append(
        playerName +
          "    ? READY\n"
      )

    } else {

      val arr =
        remote.synchronized {
          remote.toArray
        }

      for (
        e <- arr
      ) {

        val p =
          e._2

        sb.append(
          p.name +
            (
              if (p.racing)
                "    ? RACING"
              else if (p.ready)
                "    ? READY"
              else
                "    ? WAITING"
            ) +
            "\n"
        )
      }
    }

    sb.append(
      "\nYOU: " +
        playerName
    )

    sb.append(
      "\nSERVER: " +
        (
          if (serverHost.isEmpty)
            "OFFLINE"
          else
            serverHost
        )
    )

    playerList.setText(
      sb.toString
    )
  }

  def disconnect(): Unit = {

    connected =
      false

    if (
      socket != null
    ) {

      try {
        socket.close()
      } catch {
        case _: Throwable =>
      }
    }

    socket =
      null

    in =
      null

    out =
      null

    remote.clear()
  }
}

// ============================================================
// COCKPIT GAME
// ============================================================

class CockpitScreen
    extends JPanel {

  var playerName =
    "Player"

  var serverHost =
    ""

  var online =
    false

  var playerId =
    -1

  var socket:
    Socket = null

  var in:
    BufferedReader = null

  var out:
    PrintWriter = null

  var speed =
    0.0

  var rpm =
    900.0

  var steering =
    0.0

  var gear =
    "N"

  var fuel =
    100.0

  var damage =
    0.0

  var nitro =
    100.0

  var distance =
    0.0

  var score =
    0

  var racing =
    false

  var headlights =
    false

  var mapVisible =
    true

  var paused =
    false

  var roadOffset =
    0.0

  var smoke =
    0.0

  var flash =
    0.0

  val rnd =
    new Random

  val keys =
    mutable.Set[Int]()

  val players =
    mutable.Map[
      Int,
      PlayerData
    ]()

  val traffic =
    mutable.ArrayBuffer[
      TrafficCar
    ]()

  setFocusable(
    true
  )

  createTraffic()

  setupKeys()

  // ==========================================================
  // START
  // ==========================================================

  def startPlayer(
      name: String,
      host: String,
      onlineMode: Boolean
  ): Unit = {

    playerName =
      name

    serverHost =
      host

    online =
      onlineMode

    resetGame()

    if (
      onlineMode
    ) {
      connect()
    } else {
      disconnect()
    }
  }

  // ==========================================================
  // NETWORK
  // ==========================================================

  def connect(): Unit = {

    disconnect()

    try {

      socket =
        new Socket()

      socket.connect(
        new InetSocketAddress(
          serverHost,
          PORT
        ),
        4000
      )

      socket.setTcpNoDelay(
        true
      )

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

      out =
        new PrintWriter(
          new BufferedWriter(
            new OutputStreamWriter(
              socket.getOutputStream
            )
          ),
          true
        )

      online =
        true

      send(
        "HELLO|" +
          playerName
      )

      val t =
        new Thread(
          new Runnable {
            def run(): Unit =
              readNetwork()
          },
          "COCKPIT_NETWORK"
        )

      t.setDaemon(
        true
      )

      t.start()

    } catch {

      case _: Throwable =>
        online =
          false
    }
  }

  def send(
      s: String
  ): Unit = {

    if (
      out != null
    ) {

      try {
        out.println(
          s
        )
      } catch {
        case _: Throwable =>
          online =
            false
      }
    }
  }

  def readNetwork(): Unit = {

    try {

      var line:
        String = null

      while ({
        line =
          in.readLine()

        line != null
      }) {

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

        if (
          p.length >= 2 &&
          p(0) == "WELCOME"
        ) {

          try {
            playerId =
              p(1).toInt
          } catch {
            case _: Throwable =>
          }

        } else if (
          p.length >= 8 &&
          p(0) == "PLAYER"
        ) {

          try {

            val id =
              p(1).toInt

            if (
              id != playerId
            ) {

              players.synchronized {

                players.update(
                  id,
                  PlayerData(
                    p(2),
                    p(3).toDouble,
                    p(4).toDouble,
                    p(5).toDouble,
                    p(6),
                    p(7) == "1",
                    p.length >= 9 &&
                      p(8) == "1"
                  )
                )
              }
            }

          } catch {
            case _: Throwable =>
          }

        } else if (
          p.length >= 2 &&
          p(0) == "LEAVE"
        ) {

          try {

            players.synchronized {
              players.remove(
                p(1).toInt
              )
            }

          } catch {
            case _: Throwable =>
          }
        }
      }

    } catch {
      case _: Throwable =>
    }

    online =
      false
  }

  var lastNetwork =
    0L

  def sendState(): Unit = {

    if (
      !online ||
      out == null
    )
      return

    val now =
      System.currentTimeMillis()

    if (
      now -
        lastNetwork <
        70
    )
      return

    lastNetwork =
      now

    send(
      "STATE|" +
        playerX +
        "|" +
        distance +
        "|" +
        speed +
        "|" +
        gear +
        "|" +
        (
          if (racing)
            "1"
          else
            "0"
        ) +
        "|" +
        "1"
    )
  }

  def disconnect(): Unit = {

    online =
      false

    if (
      socket != null
    ) {

      try {
        socket.close()
      } catch {
        case _: Throwable =>
      }
    }

    socket =
      null

    in =
      null

    out =
      null

    players.clear()
  }

  // ==========================================================
  // GAME
  // ==========================================================

  var playerX =
    0.0

  def resetGame(): Unit = {

    playerX =
      0.0

    speed =
      0.0

    rpm =
      900.0

    steering =
      0.0

    gear =
      "N"

    fuel =
      100.0

    damage =
      0.0

    nitro =
      100.0

    distance =
      0.0

    score =
      0

    roadOffset =
      0.0

    smoke =
      0.0

    flash =
      0.0

    racing =
      false

    paused =
      false

    createTraffic()
  }

  def setupKeys(): Unit = {

    val codes =
      Array(
        KeyEvent.VK_LEFT,
        KeyEvent.VK_RIGHT,
        KeyEvent.VK_UP,
        KeyEvent.VK_DOWN,
        KeyEvent.VK_A,
        KeyEvent.VK_D,
        KeyEvent.VK_W,
        KeyEvent.VK_S,
        KeyEvent.VK_SPACE,
        KeyEvent.VK_1,
        KeyEvent.VK_2,
        KeyEvent.VK_3,
        KeyEvent.VK_4,
        KeyEvent.VK_5,
        KeyEvent.VK_R,
        KeyEvent.VK_N,
        KeyEvent.VK_E,
        KeyEvent.VK_H,
        KeyEvent.VK_M,
        KeyEvent.VK_ESCAPE,
        KeyEvent.VK_F
      )

    val im =
      getInputMap(
        JComponent.WHEN_IN_FOCUSED_WINDOW
      )

    val am =
      getActionMap()

    for (
      k <- codes
    ) {

      val down =
        "DOWN_" +
          k

      val up =
        "UP_" +
          k

      im.put(
        KeyStroke.getKeyStroke(
          k,
          0,
          false
        ),
        down
      )

      im.put(
        KeyStroke.getKeyStroke(
          k,
          0,
          true
        ),
        up
      )

      am.put(
        down,
        new AbstractAction {
          def actionPerformed(
              e: ActionEvent
          ): Unit =
            keyDown(k)
        }
      )

      am.put(
        up,
        new AbstractAction {
          def actionPerformed(
              e: ActionEvent
          ): Unit =
            keys -= k
        }
      )
    }
  }

  def keyDown(
      k: Int
  ): Unit = {

    if (
      k ==
        KeyEvent.VK_ESCAPE
    ) {

      paused =
        !paused

      return
    }

    if (
      k ==
        KeyEvent.VK_H
    ) {

      headlights =
        !headlights

      return
    }

    if (
      k ==
        KeyEvent.VK_M
    ) {

      mapVisible =
        !mapVisible

      return
    }

    if (
      k ==
        KeyEvent.VK_F
    ) {

      racing =
        true

      send(
        "RACE"
      )

      return
    }

    keys +=
      k

    if (
      k ==
        KeyEvent.VK_R
    )
      gear =
        "R"
    else if (
      k ==
        KeyEvent.VK_N
    )
      gear =
        "N"
    else if (
      k ==
        KeyEvent.VK_1
    )
      gear =
        "1"
    else if (
      k ==
        KeyEvent.VK_2
    )
      gear =
        "2"
    else if (
      k ==
        KeyEvent.VK_3
    )
      gear =
        "3"
    else if (
      k ==
        KeyEvent.VK_4
    )
      gear =
        "4"
    else if (
      k ==
        KeyEvent.VK_5
    )
      gear =
        "5"
  }

  // ==========================================================
  // TRAFFIC
  // ==========================================================

  def createTraffic(): Unit = {

    traffic.clear()

    for (
      i <- 0 until 18
    ) {

      traffic +=
        TrafficCar(
          rnd.nextInt(3) - 1,
          0.18 +
            i * 0.075 +
            rnd.nextDouble() *
            0.12,
          0.25 +
            rnd.nextDouble() *
            0.75,
          rnd.nextInt(3),
          randomColor()
        )
    }
  }

  def randomColor(): Color = {

    val c =
      Array(
        new Color(
          220,
          40,
          45
        ),
        new Color(
          35,
          125,
          225
        ),
        new Color(
          245,
          210,
          50
        ),
        new Color(
          235,
          235,
          235
        ),
        new Color(
          45,
          47,
          52
        ),
        new Color(
          40,
          175,
          100
        )
      )

    c(
      rnd.nextInt(
        c.length
      )
    )
  }

  // ==========================================================
  // GAME LOOP
  // ==========================================================

  val timer =
    new Timer(
      16,
      new ActionListener {

        def actionPerformed(
            e: ActionEvent
        ): Unit = {

          updateGame(
            0.016
          )

          repaint()
        }
      }
    )

  timer.start()

  def updateGame(
      dt: Double
  ): Unit = {

    if (
      paused
    )
      return

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

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

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

    val brake =
      keys.contains(
        KeyEvent.VK_DOWN
      ) ||
      keys.contains(
        KeyEvent.VK_S
      ) ||
      keys.contains(
        KeyEvent.VK_SPACE
      )

    val boost =
      keys.contains(
        KeyEvent.VK_E
      ) &&
      nitro > 0 &&
      speed > 20

    var targetSteer =
      0.0

    if (
      left &&
      !right
    )
      targetSteer =
        -1.0

    else if (
      right &&
      !left
    )
      targetSteer =
        1.0

    steering +=
      (
        targetSteer -
          steering
      ) *
      0.14

    playerX +=
      steering *
      (
        4.8 +
          Math.abs(speed) *
          0.025
      )

    if (
      playerX <
        -315
    )
      playerX =
        -315

    if (
      playerX >
        315
    )
      playerX =
        315

    var ratio =
      0.0

    if (
      gear ==
        "1"
    )
      ratio =
        2.70

    else if (
      gear ==
        "2"
    )
      ratio =
        1.95

    else if (
      gear ==
        "3"
    )
      ratio =
        1.40

    else if (
      gear ==
        "4"
    )
      ratio =
        1.00

    else if (
      gear ==
        "5"
    )
      ratio =
        0.78

    if (
      gear ==
        "R"
    ) {

      if (
        accel
      )
        speed -=
          90 *
          dt

    } else if (
      gear ==
        "N"
    ) {

      speed *=
        0.997

    } else {

      if (
        accel
      )
        speed +=
          68 *
          ratio *
          dt
    }

    if (
      boost
    ) {

      speed +=
        135 *
        dt

      nitro -=
        30 *
        dt

    } else {

      nitro +=
        6.5 *
        dt
    }

    if (
      nitro <
        0
    )
      nitro =
        0

    if (
      nitro >
        100
    )
      nitro =
        100

    if (
      brake
    ) {

      speed -=
        145 *
        dt

      speed *=
        0.972

      if (
        speed <
          0
      )
        speed =
          0
    }

    speed -=
      speed *
      0.012 *
      dt

    if (
      speed >
        250
    )
      speed =
        250

    if (
      speed <
        -70
    )
      speed =
        -70

    if (
      gear ==
        "N"
    ) {

      if (
        accel
      )
        rpm =
          3000

      else
        rpm =
          850

    } else {

      var rf =
        19.0

      if (
        gear ==
          "1"
      )
        rf =
          48.0

      else if (
        gear ==
          "2"
      )
        rf =
          34.0

      else if (
        gear ==
          "3"
      )
        rf =
          26.0

      rpm =
        900 +
        Math.abs(speed) *
        rf

      if (
        rpm >
          7000
      )
        rpm =
          7000
    }

    if (
      accel
    ) {

      fuel -=
        0.008 +
        Math.abs(speed) *
        0.000022
    }

    if (
      boost
    )
      fuel -=
        0.02

    if (
      fuel <
        0
    )
      fuel =
        0

    if (
      fuel ==
        0
    ) {

      gear =
        "N"

      speed *=
        0.99
    }

    roadOffset +=
      speed *
      dt *
      0.45

    if (
      speed >
        0
    ) {

      distance +=
        speed *
        dt *
        0.90

      score +=
        (
          speed *
          dt *
          0.08
        ).toInt
    }

    for (
      t <- traffic
    ) {

      val rel =
        Math.abs(speed) *
        0.0021 -
        t.speed *
        0.00115

      t.z -=
        rel *
        dt *
        15

      if (
        t.z <
          0.06 ||
        t.z >
          1.5
      ) {

        t.lane =
          rnd.nextInt(3) - 1

        t.z =
          1.10 +
          rnd.nextDouble() *
          0.38

        t.speed =
          0.25 +
          rnd.nextDouble() *
          0.75
      }

      if (
        t.z >
          0.13 &&
        t.z <
          0.29
      ) {

        val tx =
          t.lane *
          205.0

        if (
          Math.abs(
            playerX -
              tx
          ) <
            76 &&
          Math.abs(speed) >
            22
        ) {

          damage +=
            16 +
            Math.abs(speed) *
            0.08

          score =
            Math.max(
              0,
              score -
                70
            )

          speed *=
            0.28

          smoke =
            1.0

          flash =
            1.0

          t.z =
            1.35

          if (
            damage >=
              100
          ) {

            damage =
              100

            paused =
              true
          }
        }
      }
    }

    smoke *=
      0.965

    flash *=
      0.90

    sendState()
  }

  // ==========================================================
  // DRAW
  // ==========================================================

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

    super.paintComponent(
      g0
    )

    val g =
      g0.asInstanceOf[
        Graphics2D
      ]

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

    drawSky(g)
    drawWorld(g)
    drawRoad(g)

    for (
      t <- traffic.sortBy(
        _.z
      ).reverse
    ) {

      drawTraffic(
        g,
        t
      )
    }

    drawOnlinePlayers(
      g
    )

    if (
      headlights
    )
      drawHeadlights(g)

    drawCockpit(
      g
    )

    drawHUD(
      g
    )

    if (
      mapVisible
    )
      drawMap(
        g
      )

    if (
      smoke >
        0.08
    )
      drawSmoke(
        g
      )

    if (
      flash >
        0.02
    )
      drawFlash(
        g
      )

    if (
      paused
    )
      drawPause(
        g
      )

    if (
      damage >=
        100
    )
      drawDestroyed(
        g
      )
  }

  // ==========================================================
  // SKY
  // ==========================================================

  def drawSky(
      g: Graphics2D
  ): Unit = {

    val sky =
      new GradientPaint(
        0,
        0,
        new Color(
          13,
          25,
          46
        ),
        0,
        350,
        new Color(
          112,
          145,
          162
        )
      )

    g.setPaint(
      sky
    )

    g.fillRect(
      0,
      0,
      1280,
      350
    )

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

    g.fillOval(
      1040,
      60,
      70,
      70
    )

    drawCloud(
      g,
      90,
      95,
      1.0
    )

    drawCloud(
      g,
      535,
      115,
      0.8
    )

    drawCloud(
      g,
      810,
      90,
      1.05
    )
  }

  def drawCloud(
      g: Graphics2D,
      x: Int,
      y: Int,
      s: Double
  ): Unit = {

    g.setColor(
      new Color(
        210,
        220,
        225,
        110
      )
    )

    g.fillOval(
      x,
      y,
      (80 * s).toInt,
      (36 * s).toInt
    )

    g.fillOval(
      x +
        (30 * s).toInt,
      y -
        (20 * s).toInt,
      (65 * s).toInt,
      (45 * s).toInt
    )

    g.fillOval(
      x +
        (67 * s).toInt,
      y,
      (75 * s).toInt,
      (36 * s).toInt
    )
  }

  // ==========================================================
  // WORLD
  // ==========================================================

  def drawWorld(
      g: Graphics2D
  ): Unit = {

    g.setColor(
      new Color(
        28,
        80,
        38
      )
    )

    g.fillRect(
      0,
      340,
      1280,
      480
    )

    val hills =
      new Polygon()

    hills.addPoint(
      0,
      360
    )

    hills.addPoint(
      150,
      300
    )

    hills.addPoint(
      300,
      350
    )

    hills.addPoint(
      470,
      292
    )

    hills.addPoint(
      650,
      356
    )

    hills.addPoint(
      815,
      298
    )

    hills.addPoint(
      1010,
      350
    )

    hills.addPoint(
      1200,
      300
    )

    hills.addPoint(
      1280,
      375
    )

    hills.addPoint(
      1280,
      440
    )

    hills.addPoint(
      0,
      440
    )

    g.setColor(
      new Color(
        42,
        75,
        52
      )
    )

    g.fillPolygon(
      hills
    )

    for (
      i <- 0 until 12
    ) {

      drawTree(
        g,
        25 +
          i * 108,
        300 +
          (
            i % 3
          ) * 12,
        0.68
      )

      drawTree(
        g,
        1255 -
          i * 108,
        300 +
          (
            i % 2
          ) * 15,
        0.67
      )
    }
  }

  def drawTree(
      g: Graphics2D,
      x: Int,
      y: Int,
      s: Double
  ): Unit = {

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

    g.fillRect(
      x - 6,
      y,
      12,
      58
    )

    g.setColor(
      new Color(
        28,
        103,
        42
      )
    )

    g.fillOval(
      x -
        (50 * s).toInt,
      y -
        (68 * s).toInt,
      (100 * s).toInt,
      (88 * s).toInt
    )
  }

  // ==========================================================
  // ROAD
  // ==========================================================

  def drawRoad(
      g: Graphics2D
  ): Unit = {

    val h =
      340

    val road =
      new Polygon()

    road.addPoint(
      500,
      h
    )

    road.addPoint(
      780,
      h
    )

    road.addPoint(
      1240,
      760
    )

    road.addPoint(
      40,
      760
    )

    g.setColor(
      new Color(
        47,
        48,
        52
      )
    )

    g.fillPolygon(
      road
    )

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

    g.setStroke(
      new BasicStroke(
        5
      )
    )

    g.drawLine(
      500,
      h,
      40,
      760
    )

    g.drawLine(
      780,
      h,
      1240,
      760
    )

    var y =
      h +
      5 -
      (
        roadOffset.toInt %
          92
      )

    while (
      y <
        760
    ) {

      val p =
        Math.max(
          0.0,
          Math.min(
            1.0,
            (
              y -
                h
            ).toDouble /
              (
                760 -
                  h
              )
          )
        )

      val left =
        500 -
        460 *
        p

      val right =
        780 +
        460 *
        p

      val lane =
        (
          right -
            left
        ) /
        3.0

      g.setColor(
        new Color(
          250,
          220,
          75
        )
      )

      for (
        n <- 1 to 2
      ) {

        val x =
          (
            left +
              lane *
              n
          ).toInt

        g.drawLine(
          x,
          y,
          x,
          Math.min(
            760,
            y +
              20 +
              (
                p *
                  85
              ).toInt
          )
        )
      }

      y +=
        58 +
        (
          p *
            100
        ).toInt
    }
  }

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

  def drawTraffic(
      g: Graphics2D,
      t: TrafficCar
  ): Unit = {

    val z =
      Math.max(
        0.17,
        t.z
      )

    val scale =
      1.0 /
        z

    val cx =
      (
        640 +
          t.lane *
          205 *
          scale
      ).toInt

    val cy =
      (
        340 +
          (
            1.2 -
              z
          ) *
          330
      ).toInt

    val w =
      Math.max(
        20,
        (
          56 *
            scale
        ).toInt
      )

    val h =
      Math.max(
        30,
        (
          108 *
            scale
        ).toInt
      )

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

    g.fillOval(
      cx -
        w /
        2,
      cy +
        h /
        2,
      w +
        24,
      14
    )

    g.setColor(
      Color.BLACK
    )

    g.fillRoundRect(
      cx -
        w /
        2,
      cy -
        h /
        2,
      w,
      h,
      12,
      12
    )

    g.setColor(
      t.body
    )

    g.fillRoundRect(
      cx -
        w /
        2 +
        3,
      cy -
        h /
        2 +
        3,
      w -
        6,
      h -
        6,
      10,
      10
    )

    g.setColor(
      new Color(
        55,
        82,
        100
      )
    )

    g.fillRoundRect(
      cx -
        w /
        2 +
        7,
      cy -
        h /
        4,
      w -
        14,
      h /
        4,
      6,
      6
    )

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

    g.fillRect(
      cx -
        w /
        2 +
        6,
      cy +
        h /
        2 -
        15,
      9,
      7
    )

    g.fillRect(
      cx +
        w /
        2 -
        15,
      cy +
        h /
        2 -
        15,
      9,
      7
    )
  }

  // ==========================================================
  // ONLINE CARS
  // ==========================================================

  def drawOnlinePlayers(
      g: Graphics2D
  ): Unit = {

    val arr =
      players.synchronized {
        players.toArray
      }

    for (
      e <- arr
    ) {

      val p =
        e._2

      val delta =
        p.distance -
          distance

      if (
        Math.abs(
          delta
        ) <
          850
      ) {

        val relative =
          Math.max(
            0.15,
            Math.min(
              1.18,
              0.72 -
                delta /
                900.0
            )
          )

        val scale =
          1.0 /
            Math.max(
              0.18,
              relative
            )

        val cx =
          (
            640 +
              p.x *
              scale
          ).toInt

        val cy =
          (
            350 +
              (
                1.13 -
                  relative
              ) *
              340
          ).toInt

        val w =
          Math.max(
            22,
            (
              62 *
                scale
            ).toInt
          )

        val h =
          Math.max(
            35,
            (
              112 *
                scale
            ).toInt
          )

        g.setColor(
          new Color(
            0,
            235,
            255,
            42
          )
        )

        g.fillOval(
          cx -
            w,
          cy -
            h /
            2 -
            10,
          w *
            2,
          h +
            20
        )

        g.setColor(
          Color.BLACK
        )

        g.fillRoundRect(
          cx -
            w /
            2,
          cy -
            h /
            2,
          w,
          h,
          13,
          13
        )

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

        g.fillRoundRect(
          cx -
            w /
            2 +
            3,
          cy -
            h /
            2 +
            3,
          w -
            6,
          h -
            6,
          11,
          11
        )

        g.setColor(
          if (
            p.racing
          )
            new Color(
              255,
              225,
              50
            )
          else
            new Color(
              0,
              245,
              255
            )
        )

        g.setFont(
          new Font(
            "Arial",
            Font.BOLD,
            Math.max(
              8,
              (
                12 *
                  scale
              ).toInt
            )
          )
        )

        g.drawString(
          p.name +
            (
              if (
                p.racing
              )
                " ? RACING"
              else
                ""
            ),
          cx -
            w /
            2,
          cy -
            h /
            2 -
            7
        )
      }
    }
  }

  // ==========================================================
  // COCKPIT
  // ==========================================================

  def drawCockpit(
      g: Graphics2D
  ): Unit = {

    // windshield frame
    g.setColor(
      new Color(
        12,
        15,
        19
      )
    )

    g.fillRect(
      0,
      305,
      22,
      345
    )

    g.fillRect(
      1258,
      305,
      22,
      345
    )

    g.fillRect(
      0,
      295,
      1280,
      18
    )

    // dashboard
    g.setColor(
      new Color(
        11,
        14,
        18
      )
    )

    g.fillRect(
      0,
      545,
      1280,
      105
    )

    // driver head
    g.setColor(
      new Color(
        220,
        180,
        150
      )
    )

    g.fillOval(
      460,
      392,
      46,
      46
    )

    // driver shirt
    g.setColor(
      new Color(
        35,
        80,
        145
      )
    )

    g.fillRoundRect(
      450,
      435,
      80,
      98,
      18,
      18
    )

    // seat belt
    g.setColor(
      new Color(
        235,
        220,
        180
      )
    )

    g.setStroke(
      new BasicStroke(
        4
      )
    )

    g.drawLine(
      462,
      442,
      516,
      515
    )

    // steering wheel
    val sx =
      640

    val sy =
      620

    g.setColor(
      new Color(
        28,
        28,
        31
      )
    )

    g.setStroke(
      new BasicStroke(
        12
      )
    )

    g.drawOval(
      sx -
        76,
      sy -
        76,
      152,
      152
    )

    val a =
      steering *
        Math.toRadians(
          60
        )

    val wx =
      sx +
        (
          Math.sin(a) *
            60
        ).toInt

    val wy =
      sy -
        (
          Math.cos(a) *
            60
        ).toInt

    g.setStroke(
      new BasicStroke(
        8
      )
    )

    g.drawLine(
      sx,
      sy,
      wx,
      wy
    )

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

    g.fillOval(
      sx -
        13,
      sy -
        13,
      26,
      26
    )

    // display
    g.setColor(
      Color.BLACK
    )

    g.fillRoundRect(
      500,
      552,
      280,
      65,
      12,
      12
    )

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

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

    g.drawString(
      Math.abs(
        speed
      ).toInt +
        " KM/H",
      525,
      585
    )

    g.setColor(
      new Color(
        0,
        225,
        255
      )
    )

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

    g.drawString(
      "GEAR " +
        gear,
      675,
      585
    )

    // pedals
    val accel =
      keys.contains(
        KeyEvent.VK_UP
      ) ||
      keys.contains(
        KeyEvent.VK_W
      )

    val brake =
      keys.contains(
        KeyEvent.VK_DOWN
      ) ||
      keys.contains(
        KeyEvent.VK_S
      ) ||
      keys.contains(
        KeyEvent.VK_SPACE
      )

    g.setColor(
      if (accel)
        new Color(
          0,
          230,
          160
        )
      else
        new Color(
          60,
          65,
          70
        )
    )

    g.fillRoundRect(
      820,
      555,
      110,
      36,
      8,
      8
    )

    g.setColor(
      if (brake)
        new Color(
          255,
          55,
          55
        )
      else
        new Color(
          60,
          65,
          70
        )
    )

    g.fillRoundRect(
      945,
      555,
      110,
      36,
      8,
      8
    )

    g.setColor(
      Color.WHITE
    )

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

    g.drawString(
      "ACCELERATOR",
      840,
      578
    )

    g.drawString(
      "BRAKE",
      980,
      578
    )
  }

  // ==========================================================
  // HEADLIGHTS
  // ==========================================================

  def drawHeadlights(
      g: Graphics2D
  ): Unit = {

    val cx =
      (
        640 +
          playerX
      ).toInt

    val left =
      new Polygon()

    left.addPoint(
      cx -
        65,
      550
    )

    left.addPoint(
      cx -
        20,
      550
    )

    left.addPoint(
      cx -
        290,
      340
    )

    left.addPoint(
      cx -
        160,
      340
    )

    val right =
      new Polygon()

    right.addPoint(
      cx +
        20,
      550
    )

    right.addPoint(
      cx +
        65,
      550
    )

    right.addPoint(
      cx +
        160,
      340
    )

    right.addPoint(
      cx +
        290,
      340
    )

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

    g.fillPolygon(
      left
    )

    g.fillPolygon(
      right
    )
  }

  // ==========================================================
  // HUD
  // ==========================================================

  def drawHUD(
      g: Graphics2D
  ): Unit = {

    g.setColor(
      new Color(
        4,
        8,
        13,
        230
      )
    )

    g.fillRect(
      0,
      0,
      960,
      70
    )

    g.setColor(
      if (online)
        new Color(
          0,
          245,
          160
        )
      else
        new Color(
          255,
          80,
          70
        )
    )

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

    g.drawString(
      if (online)
        "? ONLINE"
      else
        "? OFFLINE",
      20,
      24
    )

    g.setColor(
      Color.WHITE
    )

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

    g.drawString(
      playerName +
        "  #" +
        playerId,
      110,
      24
    )

    g.drawString(
      "SPEED " +
        Math.abs(
          speed
        ).toInt +
        " KM/H",
      305,
      24
    )

    g.drawString(
      "DIST " +
        distance.toInt +
        " M",
      460,
      24
    )

    g.drawString(
      "SCORE " +
        score,
      585,
      24
    )

    g.drawString(
      "PLAYERS " +
        players.synchronized {
          players.size
        },
      680,
      24
    )

    g.setColor(
      if (racing)
        new Color(
          255,
          225,
          50
        )
      else
        Color.WHITE
    )

    g.drawString(
      if (racing)
        "? RACING"
      else
        "F = RACE",
      790,
      24
    )

    g.setColor(
      new Color(
        40,
        45,
        50
      )
    )

    g.fillRect(
      305,
      44,
      365,
      10
    )

    g.setColor(
      new Color(
        0,
        230,
        160
      )
    )

    g.fillRect(
      305,
      44,
      Math.min(
        365,
        (
          distance /
          5000.0 *
          365
        ).toInt
      ),
      10
    )

    g.setColor(
      Color.WHITE
    )

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

    g.drawString(
      "5000 M RACE",
      690,
      51
    )
  }

  // ==========================================================
  // MAP
  // ==========================================================

  def drawMap(
      g: Graphics2D
  ): Unit = {

    val x =
      990

    val y =
      90

    g.setColor(
      new Color(
        5,
        9,
        14,
        240
      )
    )

    g.fillRoundRect(
      x,
      y,
      250,
      210,
      18,
      18
    )

    g.setColor(
      new Color(
        0,
        230,
        255
      )
    )

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

    g.drawString(
      "LIVE GPS",
      x +
        18,
      y +
        25
    )

    g.setColor(
      new Color(
        70,
        75,
        80
      )
    )

    g.setStroke(
      new BasicStroke(
        25,
        BasicStroke.CAP_ROUND,
        BasicStroke.JOIN_ROUND
      )
    )

    g.drawLine(
      x +
        125,
      y +
        48,
      x +
        125,
      y +
        160
    )

    val progress =
      Math.min(
        1.0,
        distance /
          5000.0
      )

    g.setColor(
      new Color(
        0,
        240,
        160
      )
    )

    g.setStroke(
      new BasicStroke(
        4
      )
    )

    g.drawLine(
      x +
        125,
      y +
        48,
      x +
        125,
      y +
        48 +
        (
          progress *
          112
        ).toInt
    )

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

    g.fillOval(
      x +
        119,
      y +
        43 +
        (
          progress *
          112
        ).toInt,
      12,
      12
    )

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

    g.fillOval(
      x +
        118,
      y +
        153,
      14,
      14
    )

    g.setColor(
      Color.WHITE
    )

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

    g.drawString(
      "YOU",
      x +
        148,
      y +
        68
    )

    g.drawString(
      "ONLINE: " +
        players.synchronized {
          players.size
        },
      x +
        18,
      y +
        190
    )
  }

  // ==========================================================
  // SMOKE / FLASH
  // ==========================================================

  def drawSmoke(
      g: Graphics2D
  ): Unit = {

    val cx =
      (
        640 +
          playerX
      ).toInt

    for (
      i <- 0 until 7
    ) {

      g.setColor(
        new Color(
          210,
          210,
          210,
          Math.max(
            15,
            95 -
              i *
              11
          )
        )
      )

      val s =
        22 +
          i *
          7

      g.fillOval(
        cx -
          35 +
          rnd.nextInt(
            55
          ),
        640 -
          i *
          14,
        s,
        s
      )
    }
  }

  def drawFlash(
      g: Graphics2D
  ): Unit = {

    val a =
      Math.max(
        0,
        Math.min(
          120,
          (
            flash *
              120
          ).toInt
        )
      )

    g.setColor(
      new Color(
        255,
        40,
        30,
        a
      )
    )

    g.fillRect(
      0,
      0,
      1280,
      650
    )
  }

  // ==========================================================
  // PAUSE / DESTROYED
  // ==========================================================

  def drawPause(
      g: Graphics2D
  ): Unit = {

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

    g.fillRect(
      0,
      0,
      1280,
      650
    )

    g.setColor(
      Color.WHITE
    )

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

    g.drawString(
      "PAUSED",
      520,
      325
    )

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

    g.drawString(
      "PRESS ESC TO CONTINUE",
      480,
      368
    )
  }

  def drawDestroyed(
      g: Graphics2D
  ): Unit = {

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

    g.fillRect(
      0,
      0,
      1280,
      650
    )

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

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

    g.drawString(
      "VEHICLE DESTROYED",
      405,
      315
    )

    g.setColor(
      Color.WHITE
    )

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

    g.drawString(
      "PRESS ENTER TO RESTART",
      485,
      370
    )
  }
}

// ============================================================
// AUTO START
// ============================================================

SwingUtilities.invokeLater(
  new Runnable {
    def run(): Unit = {
      new DriveWindow
    }
  }
)