Code Sketch


yoiiiii
By: Mhalsakant School
Category: Programming
//scala
import java.awt.BasicStroke
import java.awt.BorderLayout
import java.awt.Color
import java.awt.Cursor
import java.awt.Dimension
import java.awt.FlowLayout
import java.awt.Font
import java.awt.GradientPaint
import java.awt.Graphics
import java.awt.Graphics2D
import java.awt.Point
import java.awt.RenderingHints
import java.awt.event.ActionEvent
import java.awt.event.ActionListener
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.event.MouseMotionAdapter

import javax.swing.DefaultListModel
import javax.swing.JButton
import javax.swing.JColorChooser
import javax.swing.JFileChooser
import javax.swing.JFrame
import javax.swing.JLabel
import javax.swing.JList
import javax.swing.JOptionPane
import javax.swing.JPanel
import javax.swing.JScrollPane
import javax.swing.JSplitPane
import javax.swing.JTabbedPane
import javax.swing.JTextArea
import javax.swing.JTextField
import javax.swing.ListSelectionModel
import javax.swing.SwingUtilities
import javax.swing.Timer
import javax.swing.WindowConstants

import scala.collection.mutable.ArrayBuffer


// ============================================================
// ULTRA LEGEND DRAW + GAME STUDIO X8
// CLEAN / CORRECT SCALA VERSION
// ============================================================

val CANVAS_WIDTH: Int = 1000
val CANVAS_HEIGHT: Int = 650

val CENTER_X: Int = 500
val CENTER_Y: Int = 325


// ============================================================
// DRAW MODEL
// ============================================================

case class DrawItem(
  points: Vector[Point],
  strokeColor: Color,
  fillColor: Color,
  filled: Boolean,
  itemName: String,
  visible: Boolean,
  locked: Boolean
)


// ============================================================
// GLOBAL DRAW STATE
// ============================================================

var drawItems =
  ArrayBuffer[DrawItem]()

var undoStack =
  ArrayBuffer[ArrayBuffer[DrawItem]]()

var redoStack =
  ArrayBuffer[ArrayBuffer[DrawItem]]()

var currentTool: String =
  "PENCIL"

var currentStrokeColor: Color =
  new Color(20, 90, 220)

var currentFillColor: Color =
  new Color(255, 180, 40)

var brushWidth: Float =
  5.0f

var gridEnabled: Boolean =
  true

var snapEnabled: Boolean =
  false

var drawingActive: Boolean =
  false

var selectedItemIndex: Int =
  -1

var movingSelected: Boolean =
  false

var resizingSelected: Boolean =
  false

var resizeMode: String =
  ""

var previousMousePoint: Point =
  new Point(0, 0)

var originalSelectedPoints: Vector[Point] =
  Vector.empty

var temporaryPoints =
  ArrayBuffer[Point]()

var generatedDrawingCode: String =
  ""

var generatedGameCode: String =
  ""


// ============================================================
// UI REFERENCES
// ============================================================

var mainFrame: JFrame =
  null

var drawingCanvas: JPanel =
  null

var drawingCodeArea: JTextArea =
  null

var mainStatus: JLabel =
  null

var objectModel =
  new DefaultListModel[String]()

var shapeModel =
  new DefaultListModel[String]()

var objectList: JList[String] =
  null

var shapeList: JList[String] =
  null

var objectSearchField: JTextField =
  null


// ============================================================
// BUTTON HELPER
// ============================================================

def createButton(
  label: String
): JButton = {

  val button =
    new JButton(label)

  button.setFocusable(false)

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

  button
}


// ============================================================
// STATUS
// ============================================================

def setMainStatus(
  message: String
): Unit = {

  if (
    mainStatus != null
  ) {

    mainStatus.setText(
      message
    )
  }
}


def repaintDrawingCanvas(): Unit = {

  if (
    drawingCanvas != null
  ) {

    drawingCanvas.repaint()
  }
}


// ============================================================
// HISTORY COPY
// ============================================================

def copyDrawItems(
  source: ArrayBuffer[DrawItem]
): ArrayBuffer[DrawItem] = {

  val result =
    ArrayBuffer[DrawItem]()

  var i: Int =
    0

  while (
    i < source.length
  ) {

    val item =
      source(i)

    val copiedPoints =
      item.points.map(
        p =>
          new Point(
            p.x,
            p.y
          )
      )

    result +=
      item.copy(
        points = copiedPoints
      )

    i += 1
  }

  result
}


// ============================================================
// UNDO
// ============================================================

def saveHistory(): Unit = {

  undoStack +=
    copyDrawItems(
      drawItems
    )

  redoStack.clear()

  while (
    undoStack.length > 50
  ) {

    undoStack.remove(0)
  }
}


def performUndo(): Unit = {

  if (
    undoStack.nonEmpty
  ) {

    redoStack +=
      copyDrawItems(
        drawItems
      )

    val oldState =
      undoStack.last

    undoStack.remove(
      undoStack.length - 1
    )

    drawItems.clear()

    drawItems ++=
      copyDrawItems(
        oldState
      )

    selectedItemIndex =
      -1

    repaintDrawingCanvas()

    setMainStatus(
      "UNDO COMPLETE"
    )

  } else {

    setMainStatus(
      "NOTHING TO UNDO"
    )
  }
}


// ============================================================
// REDO
// ============================================================

def performRedo(): Unit = {

  if (
    redoStack.nonEmpty
  ) {

    undoStack +=
      copyDrawItems(
        drawItems
      )

    val nextState =
      redoStack.last

    redoStack.remove(
      redoStack.length - 1
    )

    drawItems.clear()

    drawItems ++=
      copyDrawItems(
        nextState
      )

    selectedItemIndex =
      -1

    repaintDrawingCanvas()

    setMainStatus(
      "REDO COMPLETE"
    )

  } else {

    setMainStatus(
      "NOTHING TO REDO"
    )
  }
}


// ============================================================
// MOUSE / GEOMETRY
// ============================================================

def snapCoordinate(
  value: Int
): Int = {

  if (
    snapEnabled
  ) {

    (value / 10) * 10

  } else {

    value
  }
}


def canvasPoint(
  e: MouseEvent
): Point = {

  val px =
    math.max(
      0,
      math.min(
        CANVAS_WIDTH - 1,
        snapCoordinate(
          e.getX
        )
      )
    )

  val py =
    math.max(
      0,
      math.min(
        CANVAS_HEIGHT - 1,
        snapCoordinate(
          e.getY
        )
      )
    )

  new Point(
    px,
    py
  )
}


def pointDistance(
  a: Point,
  b: Point
): Double = {

  math.hypot(
    b.x - a.x,
    b.y - a.y
  )
}


def isClosedShape(
  points: Iterable[Point]
): Boolean = {

  val seq =
    points.toSeq

  seq.length >= 3 &&
  pointDistance(
    seq.head,
    seq.last
  ) <= 50
}


// ============================================================
// ITEM BOUNDS
// ============================================================

def itemBounds(
  item: DrawItem
): java.awt.Rectangle = {

  if (
    item.points.isEmpty
  ) {

    new java.awt.Rectangle(
      0,
      0,
      1,
      1
    )

  } else {

    var minX =
      item.points.head.x

    var maxX =
      item.points.head.x

    var minY =
      item.points.head.y

    var maxY =
      item.points.head.y

    var i: Int =
      1

    while (
      i < item.points.length
    ) {

      val p =
        item.points(i)

      if (
        p.x < minX
      ) {
        minX = p.x
      }

      if (
        p.x > maxX
      ) {
        maxX = p.x
      }

      if (
        p.y < minY
      ) {
        minY = p.y
      }

      if (
        p.y > maxY
      ) {
        maxY = p.y
      }

      i += 1
    }

    new java.awt.Rectangle(
      minX,
      minY,
      math.max(
        1,
        maxX - minX
      ),
      math.max(
        1,
        maxY - minY
      )
    )
  }
}


// ============================================================
// SEGMENT DISTANCE
// ============================================================

def pointToSegmentDistance(
  p: Point,
  a: Point,
  b: Point
): Double = {

  val dx: Double =
    b.x - a.x

  val dy: Double =
    b.y - a.y

  if (
    dx == 0.0 &&
    dy == 0.0
  ) {

    pointDistance(
      p,
      a
    )

  } else {

    val numerator: Double =
      (p.x - a.x) * dx +
      (p.y - a.y) * dy

    val denominator: Double =
      dx * dx +
      dy * dy

    val t: Double =
      math.max(
        0.0,
        math.min(
          1.0,
          numerator /
          denominator
        )
      )

    val cx: Double =
      a.x + t * dx

    val cy: Double =
      a.y + t * dy

    math.hypot(
      p.x - cx,
      p.y - cy
    )
  }
}


// ============================================================
// POLYGON
// ============================================================

def pointInsidePolygon(
  p: Point,
  points: Seq[Point]
): Boolean = {

  if (
    points.length < 3
  ) {

    false

  } else {

    var inside: Boolean =
      false

    var j: Int =
      points.length - 1

    var i: Int =
      0

    while (
      i < points.length
    ) {

      val xi =
        points(i).x

      val yi =
        points(i).y

      val xj =
        points(j).x

      val yj =
        points(j).y

      val hit =
        ((yi > p.y) != (yj > p.y)) &&
        (
          p.x <
          (
            xj - xi
          ).toDouble *
          (
            p.y - yi
          ).toDouble /
          (
            yj - yi + 0.000001
          ) +
          xi
        )

      if (
        hit
      ) {

        inside =
          !inside
      }

      j =
        i

      i += 1
    }

    inside
  }
}


// ============================================================
// HIT TEST
// ============================================================

def findItemAt(
  p: Point
): Int = {

  var found: Int =
    -1

  var i: Int =
    drawItems.length - 1

  while (
    i >= 0 &&
    found < 0
  ) {

    val item =
      drawItems(i)

    if (
      item.visible
    ) {

      val b =
        itemBounds(item)

      val expanded =
        new java.awt.Rectangle(
          b.x - 15,
          b.y - 15,
          b.width + 30,
          b.height + 30
        )

      if (
        expanded.contains(p)
      ) {

        if (
          item.filled &&
          isClosedShape(item.points) &&
          pointInsidePolygon(
            p,
            item.points
          )
        ) {

          found =
            i

        } else {

          var j: Int =
            1

          while (
            j < item.points.length &&
            found < 0
          ) {

            if (
              pointToSegmentDistance(
                p,
                item.points(j - 1),
                item.points(j)
              ) < 18
            ) {

              found =
                i
            }

            j += 1
          }
        }
      }
    }

    i -= 1
  }

  found
}


// ============================================================
// HANDLE HELPERS
// ============================================================

def selectionHandles(
  r: java.awt.Rectangle
): Array[(String, Point)] = {

  val mx =
    r.x + r.width / 2

  val my =
    r.y + r.height / 2

  Array(
    ("TL", new Point(r.x, r.y)),
    ("TM", new Point(mx, r.y)),
    ("TR", new Point(r.x + r.width, r.y)),
    ("ML", new Point(r.x, my)),
    ("MR", new Point(r.x + r.width, my)),
    ("BL", new Point(r.x, r.y + r.height)),
    ("BM", new Point(mx, r.y + r.height)),
    ("BR", new Point(r.x + r.width, r.y + r.height))
  )
}


def findResizeHandle(
  item: DrawItem,
  p: Point
): String = {

  val b =
    itemBounds(item)

  val r =
    new java.awt.Rectangle(
      b.x - 8,
      b.y - 8,
      b.width + 16,
      b.height + 16
    )

  val handles =
    selectionHandles(r)

  var answer: String =
    ""

  var i: Int =
    0

  while (
    i < handles.length &&
    answer == ""
  ) {

    if (
      pointDistance(
        p,
        handles(i)._2
      ) <= 14
    ) {

      answer =
        handles(i)._1
    }

    i += 1
  }

  answer
}


// ============================================================
// MOVE
// ============================================================

def moveSelectedItem(
  dx: Int,
  dy: Int
): Unit = {

  if (
    selectedItemIndex >= 0 &&
    selectedItemIndex < drawItems.length
  ) {

    val item =
      drawItems(
        selectedItemIndex
      )

    if (
      !item.locked
    ) {

      val movedPoints =
        item.points.map(
          p =>
            new Point(
              p.x + dx,
              p.y + dy
            )
        )

      drawItems(
        selectedItemIndex
      ) =
        item.copy(
          points = movedPoints
        )
    }
  }
}


// ============================================================
// RESIZE
// ============================================================

def resizeSelectedItem(
  p: Point
): Unit = {

  if (
    selectedItemIndex >= 0 &&
    selectedItemIndex < drawItems.length &&
    originalSelectedPoints.nonEmpty
  ) {

    val item =
      drawItems(
        selectedItemIndex
      )

    var minX =
      originalSelectedPoints.head.x

    var maxX =
      originalSelectedPoints.head.x

    var minY =
      originalSelectedPoints.head.y

    var maxY =
      originalSelectedPoints.head.y

    var i: Int =
      1

    while (
      i < originalSelectedPoints.length
    ) {

      val q =
        originalSelectedPoints(i)

      if (
        q.x < minX
      ) {
        minX = q.x
      }

      if (
        q.x > maxX
      ) {
        maxX = q.x
      }

      if (
        q.y < minY
      ) {
        minY = q.y
      }

      if (
        q.y > maxY
      ) {
        maxY = q.y
      }

      i += 1
    }

    val oldWidth: Int =
      math.max(
        1,
        maxX - minX
      )

    val oldHeight: Int =
      math.max(
        1,
        maxY - minY
      )

    var newMinX =
      minX

    var newMaxX =
      maxX

    var newMinY =
      minY

    var newMaxY =
      maxY

    if (resizeMode == "TL") {

      newMinX = p.x
      newMinY = p.y

    } else if (resizeMode == "TR") {

      newMaxX = p.x
      newMinY = p.y

    } else if (resizeMode == "BL") {

      newMinX = p.x
      newMaxY = p.y

    } else if (resizeMode == "BR") {

      newMaxX = p.x
      newMaxY = p.y

    } else if (resizeMode == "TM") {

      newMinY = p.y

    } else if (resizeMode == "BM") {

      newMaxY = p.y

    } else if (resizeMode == "ML") {

      newMinX = p.x

    } else if (resizeMode == "MR") {

      newMaxX = p.x
    }

    val newWidth: Int =
      math.max(
        5,
        newMaxX - newMinX
      )

    val newHeight: Int =
      math.max(
        5,
        newMaxY - newMinY
      )

    val scaled =
      originalSelectedPoints.map(
        q => {

          val nx =
            newMinX +
            (
              (
                q.x - minX
              ).toDouble *
              newWidth /
              oldWidth
            ).toInt

          val ny =
            newMinY +
            (
              (
                q.y - minY
              ).toDouble *
              newHeight /
              oldHeight
            ).toInt

          new Point(
            nx,
            ny
          )
        }
      )

    drawItems(
      selectedItemIndex
    ) =
      item.copy(
        points = scaled
      )
  }
}


// ============================================================
// SHAPE HELPERS
// ============================================================

def polygonPoints(
  sideCount: Int,
  radius: Int,
  startAngle: Double
): Vector[Point] = {

  val result =
    ArrayBuffer[Point]()

  var i: Int =
    0

  while (
    i <= sideCount
  ) {

    val angle =
      startAngle +
      i *
      2.0 *
      math.Pi /
      sideCount.toDouble

    result +=
      new Point(
        CENTER_X +
        (
          math.cos(angle) *
          radius
        ).toInt,

        CENTER_Y +
        (
          math.sin(angle) *
          radius
        ).toInt
      )

    i += 1
  }

  result.toVector
}


def ellipsePoints(
  radiusX: Int,
  radiusY: Int
): Vector[Point] = {

  val result =
    ArrayBuffer[Point]()

  var degree: Int =
    0

  while (
    degree <= 360
  ) {

    val angle =
      degree *
      math.Pi /
      180.0

    result +=
      new Point(
        CENTER_X +
        (
          math.cos(angle) *
          radiusX
        ).toInt,

        CENTER_Y +
        (
          math.sin(angle) *
          radiusY
        ).toInt
      )

    degree += 3
  }

  result.toVector
}


def starPoints(
  pointCount: Int,
  outerRadius: Int
): Vector[Point] = {

  val result =
    ArrayBuffer[Point]()

  var i: Int =
    0

  while (
    i <= pointCount * 2
  ) {

    val angle =
      -math.Pi / 2.0 +
      i *
      math.Pi /
      pointCount

    val radius =
      if (
        i % 2 == 0
      ) {

        outerRadius

      } else {

        (
          outerRadius *
          0.45
        ).toInt
      }

    result +=
      new Point(
        CENTER_X +
        (
          math.cos(angle) *
          radius
        ).toInt,

        CENTER_Y +
        (
          math.sin(angle) *
          radius
        ).toInt
      )

    i += 1
  }

  result.toVector
}


def heartPoints():
  Vector[Point] = {

  val result =
    ArrayBuffer[Point]()

  var i: Int =
    0

  while (
    i <= 180
  ) {

    val t =
      i *
      math.Pi /
      180.0

    val x =
      16 *
      math.pow(
        math.sin(t),
        3
      )

    val y =
      13 *
      math.cos(t) -
      5 *
      math.cos(2 * t) -
      2 *
      math.cos(3 * t) -
      math.cos(4 * t)

    result +=
      new Point(
        CENTER_X +
        (x * 13).toInt,

        CENTER_Y -
        (y * 13).toInt
      )

    i += 1
  }

  result.toVector
}


// ============================================================
// SHAPE LIBRARY
// ============================================================

val allShapeNames =
  Array(
    "LINE",
    "SQUARE",
    "RECTANGLE",
    "CIRCLE",
    "ELLIPSE",
    "TRIANGLE",
    "DIAMOND",
    "PENTAGON",
    "HEXAGON",
    "HEPTAGON",
    "OCTAGON",
    "STAR 5",
    "STAR 6",
    "STAR 8",
    "HEART",
    "ARROW RIGHT",
    "ARROW LEFT",
    "ARROW UP",
    "ARROW DOWN",
    "PLUS",
    "CLOUD",
    "SUN",
    "MOON",
    "RING",
    "ARC",
    "WAVE",
    "SPIRAL",
    "BURST",
    "SNOWFLAKE"
  )


var shapeIndex: Int =
  0

while (
  shapeIndex < allShapeNames.length
) {

  shapeModel.addElement(
    allShapeNames(shapeIndex)
  )

  shapeIndex += 1
}


// ============================================================
// GET SHAPE GEOMETRY
// ============================================================

def getShapeGeometry(
  name: String
): Vector[Point] = {

  val upper =
    name.toUpperCase

  if (upper == "LINE") {

    Vector(
      new Point(100,150),
      new Point(900,500)
    )

  } else if (upper == "SQUARE") {

    Vector(
      new Point(300,125),
      new Point(700,125),
      new Point(700,525),
      new Point(300,525),
      new Point(300,125)
    )

  } else if (upper == "RECTANGLE") {

    Vector(
      new Point(150,200),
      new Point(850,200),
      new Point(850,450),
      new Point(150,450),
      new Point(150,200)
    )

  } else if (upper == "CIRCLE") {

    ellipsePoints(
      220,
      220
    )

  } else if (upper == "ELLIPSE") {

    ellipsePoints(
      300,
      160
    )

  } else if (upper == "TRIANGLE") {

    polygonPoints(
      3,
      250,
      -math.Pi / 2
    )

  } else if (upper == "DIAMOND") {

    Vector(
      new Point(500,75),
      new Point(840,325),
      new Point(500,575),
      new Point(160,325),
      new Point(500,75)
    )

  } else if (upper == "PENTAGON") {

    polygonPoints(
      5,
      250,
      -math.Pi / 2
    )

  } else if (upper == "HEXAGON") {

    polygonPoints(
      6,
      240,
      0
    )

  } else if (upper == "HEPTAGON") {

    polygonPoints(
      7,
      235,
      -math.Pi / 2
    )

  } else if (upper == "OCTAGON") {

    polygonPoints(
      8,
      235,
      math.Pi / 8
    )

  } else if (upper == "STAR 5") {

    starPoints(
      5,
      255
    )

  } else if (upper == "STAR 6") {

    starPoints(
      6,
      250
    )

  } else if (upper == "STAR 8") {

    starPoints(
      8,
      245
    )

  } else if (upper == "HEART") {

    heartPoints()

  } else if (upper == "ARROW RIGHT") {

    Vector(
      new Point(130,240),
      new Point(620,240),
      new Point(620,140),
      new Point(860,325),
      new Point(620,510),
      new Point(620,410),
      new Point(130,410),
      new Point(130,240)
    )

  } else if (upper == "ARROW LEFT") {

    Vector(
      new Point(870,240),
      new Point(380,240),
      new Point(380,140),
      new Point(140,325),
      new Point(380,510),
      new Point(380,410),
      new Point(870,410),
      new Point(870,240)
    )

  } else if (upper == "ARROW UP") {

    Vector(
      new Point(430,535),
      new Point(430,300),
      new Point(275,300),
      new Point(500,70),
      new Point(725,300),
      new Point(570,300),
      new Point(570,535),
      new Point(430,535)
    )

  } else if (upper == "ARROW DOWN") {

    Vector(
      new Point(430,115),
      new Point(430,350),
      new Point(275,350),
      new Point(500,580),
      new Point(725,350),
      new Point(570,350),
      new Point(570,115),
      new Point(430,115)
    )

  } else if (upper == "PLUS") {

    Vector(
      new Point(430,100),
      new Point(570,100),
      new Point(570,245),
      new Point(715,245),
      new Point(715,405),
      new Point(570,405),
      new Point(570,550),
      new Point(430,550),
      new Point(430,405),
      new Point(285,405),
      new Point(285,245),
      new Point(430,245),
      new Point(430,100)
    )

  } else if (upper == "CLOUD") {

    Vector(
      new Point(170,470),
      new Point(215,350),
      new Point(330,270),
      new Point(440,300),
      new Point(530,195),
      new Point(660,220),
      new Point(770,320),
      new Point(835,375),
      new Point(800,470),
      new Point(170,470)
    )

  } else if (upper == "SUN") {

    starPoints(
      16,
      250
    )

  } else if (upper == "MOON") {

    ellipsePoints(
      230,
      230
    )

  } else if (upper == "RING") {

    ellipsePoints(
      230,
      230
    )

  } else if (upper == "BURST") {

    starPoints(
      20,
      255
    )

  } else if (upper == "SNOWFLAKE") {

    Vector(
      new Point(500,75),
      new Point(500,575),
      new Point(245,160),
      new Point(755,490),
      new Point(245,490),
      new Point(755,160)
    )

  } else {

    ellipsePoints(
      230,
      160
    )
  }
}


// ============================================================
// OBJECT LIBRARY
// ============================================================

val allObjectNames =
  Array(
    "CAR",
    "SPORTS CAR",
    "RACING CAR",
    "TRUCK",
    "BUS",
    "TRAIN",
    "AIRPLANE",
    "JET",
    "HELICOPTER",
    "ROCKET",
    "BICYCLE",
    "MOTORBIKE",
    "SCOOTER",
    "BOAT",
    "SHIP",
    "SUBMARINE",
    "DRONE",
    "UFO",
    "HOUSE",
    "VILLA",
    "PALACE",
    "CASTLE",
    "TOWER",
    "SCHOOL",
    "HOSPITAL",
    "BRIDGE",
    "LIGHTHOUSE",
    "CITY",
    "SKYSCRAPER",
    "STADIUM",
    "TREE",
    "PALM TREE",
    "FLOWER",
    "CACTUS",
    "MOUNTAIN",
    "VOLCANO",
    "ISLAND",
    "CLOUD",
    "RAINBOW",
    "SUN",
    "MOON",
    "PLANET",
    "EARTH",
    "SATURN",
    "ROBOT",
    "AI ROBOT",
    "ASTRONAUT",
    "KING",
    "QUEEN",
    "CAT",
    "DOG",
    "BIRD",
    "FISH",
    "SHARK",
    "WHALE",
    "DOLPHIN",
    "LION",
    "TIGER",
    "ELEPHANT",
    "HORSE",
    "RABBIT",
    "BEAR",
    "PANDA",
    "MONKEY",
    "FOX",
    "DEER",
    "COW",
    "GOAT",
    "CHICKEN",
    "DUCK",
    "BUTTERFLY",
    "BEE",
    "SNAKE",
    "TURTLE",
    "APPLE",
    "BANANA",
    "ORANGE",
    "MANGO",
    "WATERMELON",
    "PIZZA",
    "BURGER",
    "CAKE",
    "ICE CREAM",
    "GUITAR",
    "PIANO",
    "DRUM",
    "CAMERA",
    "COMPUTER",
    "PHONE",
    "BOOK",
    "PENCIL",
    "CLOCK",
    "KEY",
    "LAMP",
    "CHAIR",
    "TABLE",
    "BACKPACK",
    "STAR",
    "HEART",
    "DIAMOND",
    "CROWN",
    "TROPHY",
    "GIFT",
    "BALLOON",
    "UMBRELLA",
    "FOOTBALL",
    "BASKETBALL",
    "TENNIS BALL",
    "CRICKET BAT",
    "CRICKET BALL",
    "FUTURE CAR",
    "CYBER CITY",
    "NEON TOWER",
    "HOLOGRAM",
    "TIME MACHINE"
  )


var objectIndex: Int =
  0

while (
  objectIndex < allObjectNames.length
) {

  objectModel.addElement(
    allObjectNames(objectIndex)
  )

  objectIndex += 1
}


// ============================================================
// OBJECT GEOMETRY
// ============================================================

def getObjectGeometry(
  name: String
): Vector[Point] = {

  val upper =
    name.toUpperCase

  if (
    upper.indexOf("CAR") >= 0
  ) {

    Vector(
      new Point(100,470),
      new Point(205,365),
      new Point(390,355),
      new Point(470,235),
      new Point(665,235),
      new Point(775,355),
      new Point(900,370),
      new Point(955,470),
      new Point(100,470)
    )

  } else if (
    upper.indexOf("HOUSE") >= 0 ||
    upper == "VILLA" ||
    upper == "PALACE" ||
    upper == "CASTLE"
  ) {

    Vector(
      new Point(220,555),
      new Point(220,300),
      new Point(500,65),
      new Point(780,300),
      new Point(780,555),
      new Point(220,555)
    )

  } else if (
    upper.indexOf("TREE") >= 0
  ) {

    Vector(
      new Point(455,580),
      new Point(545,580),
      new Point(545,350),
      new Point(680,270),
      new Point(600,150),
      new Point(500,215),
      new Point(400,150),
      new Point(320,270),
      new Point(455,350),
      new Point(455,580)
    )

  } else if (
    upper == "BIRD"
  ) {

    Vector(
      new Point(190,350),
      new Point(385,215),
      new Point(500,300),
      new Point(650,175),
      new Point(815,320),
      new Point(635,420),
      new Point(450,400),
      new Point(315,475),
      new Point(190,350)
    )

  } else if (
    upper == "FISH" ||
    upper == "SHARK" ||
    upper == "WHALE" ||
    upper == "DOLPHIN"
  ) {

    Vector(
      new Point(140,325),
      new Point(325,210),
      new Point(650,210),
      new Point(850,325),
      new Point(650,440),
      new Point(325,440),
      new Point(140,325)
    )

  } else if (
    upper.indexOf("ROCKET") >= 0 ||
    upper == "UFO"
  ) {

    Vector(
      new Point(425,520),
      new Point(450,220),
      new Point(500,55),
      new Point(550,220),
      new Point(575,520),
      new Point(500,595),
      new Point(425,520)
    )

  } else if (
    upper.indexOf("ROBOT") >= 0
  ) {

    Vector(
      new Point(355,555),
      new Point(355,295),
      new Point(335,295),
      new Point(335,135),
      new Point(665,135),
      new Point(665,295),
      new Point(645,295),
      new Point(645,555),
      new Point(355,555)
    )

  } else if (
    upper.indexOf("MOUNTAIN") >= 0
  ) {

    Vector(
      new Point(40,560),
      new Point(290,205),
      new Point(430,400),
      new Point(625,70),
      new Point(960,560),
      new Point(40,560)
    )

  } else if (
    upper == "FLOWER"
  ) {

    Vector(
      new Point(500,585),
      new Point(500,315),
      new Point(390,225),
      new Point(500,115),
      new Point(610,225),
      new Point(500,315),
      new Point(500,585)
    )

  } else if (
    upper == "STAR" ||
    upper == "HEART"
  ) {

    if (
      upper == "STAR"
    ) {

      starPoints(
        5,
        255
      )

    } else {

      heartPoints()
    }

  } else if (
    upper == "CROWN"
  ) {

    Vector(
      new Point(235,490),
      new Point(195,150),
      new Point(390,295),
      new Point(500,85),
      new Point(610,295),
      new Point(805,150),
      new Point(765,490),
      new Point(235,490)
    )

  } else {

    polygonPoints(
      6,
      235,
      0
    )
  }
}


// ============================================================
// ADD OBJECT
// ============================================================

def addObjectToCanvas(
  name: String
): Unit = {

  saveHistory()

  val geometry =
    getObjectGeometry(name)

  drawItems +=
    DrawItem(
      geometry,
      currentStrokeColor,
      currentFillColor,
      isClosedShape(geometry),
      name,
      true,
      false
    )

  selectedItemIndex =
    drawItems.length - 1

  currentTool =
    "SELECT"

  repaintDrawingCanvas()

  setMainStatus(
    "OBJECT ADDED: " + name
  )
}


// ============================================================
// ADD SHAPE
// ============================================================

def addShapeToCanvas(
  name: String
): Unit = {

  saveHistory()

  val geometry =
    getShapeGeometry(name)

  drawItems +=
    DrawItem(
      geometry,
      currentStrokeColor,
      currentFillColor,
      isClosedShape(geometry),
      name,
      true,
      false
    )

  selectedItemIndex =
    drawItems.length - 1

  currentTool =
    "SELECT"

  repaintDrawingCanvas()

  setMainStatus(
    "SHAPE ADDED: " + name
  )
}


// ============================================================
// CLEAR
// ============================================================

def clearDrawing(): Unit = {

  if (
    drawItems.nonEmpty
  ) {

    saveHistory()
  }

  drawItems.clear()

  temporaryPoints.clear()

  selectedItemIndex =
    -1

  if (
    drawingCodeArea != null
  ) {

    drawingCodeArea.setText(
      "// CANVAS CLEARED"
    )
  }

  repaintDrawingCanvas()

  setMainStatus(
    "CANVAS CLEARED"
  )
}


// ============================================================
// BUCKET
// ============================================================

def bucketFillSelected(): Unit = {

  if (
    selectedItemIndex >= 0 &&
    selectedItemIndex < drawItems.length
  ) {

    val item =
      drawItems(
        selectedItemIndex
      )

    if (
      isClosedShape(
        item.points
      )
    ) {

      saveHistory()

      drawItems(
        selectedItemIndex
      ) =
        item.copy(
          fillColor =
            currentFillColor,
          filled =
            true
        )

      repaintDrawingCanvas()

      setMainStatus(
        "BUCKET FILL COMPLETE"
      )

    } else {

      setMainStatus(
        "CLOSED OBJECT REQUIRED"
      )
    }

  } else {

    setMainStatus(
      "SELECT OBJECT FIRST"
    )
  }
}


// ============================================================
// COLOR NAME
// ============================================================

def kojoColorName(
  c: Color
): String = {

  val r =
    c.getRed

  val g =
    c.getGreen

  val b =
    c.getBlue

  if (
    r > 220 &&
    g < 100 &&
    b < 100
  ) {

    "red"

  } else if (
    r < 100 &&
    g > 170 &&
    b < 120
  ) {

    "green"

  } else if (
    r < 100 &&
    g < 150 &&
    b > 170
  ) {

    "blue"

  } else if (
    r > 220 &&
    g > 170 &&
    b < 130
  ) {

    "yellow"

  } else if (
    r > 220 &&
    g > 100 &&
    b < 110
  ) {

    "orange"

  } else if (
    r > 150 &&
    b > 150
  ) {

    "magenta"

  } else {

    "black"
  }
}


// ============================================================
// DRAW CODE
// ============================================================

def generateDrawingCode(): Unit = {

  val builder =
    new StringBuilder()

  builder.append(
    "// ULTRA LEGEND GENERATED CODE\n"
  )

  builder.append(
    "cleari()\n"
  )

  builder.append(
    "setAnimationDelay(5)\n\n"
  )

  if (
    drawItems.isEmpty
  ) {

    builder.append(
      "// NOTHING DRAWN\n"
    )

  } else {

    var i: Int =
      0

    while (
      i < drawItems.length
    ) {

      val item =
        drawItems(i)

      if (
        item.visible &&
        item.points.nonEmpty
      ) {

        builder.append(
          "// " +
          item.itemName +
          "\n"
        )

        builder.append(
          "setPenColor(" +
          kojoColorName(
            item.strokeColor
          ) +
          ")\n"
        )

        builder.append(
          "penUp()\n"
        )

        builder.append(
          "setPosition(" +
          (
            item.points.head.x -
            CENTER_X
          ) +
          ", " +
          (
            CENTER_Y -
            item.points.head.y
          ) +
          ")\n"
        )

        builder.append(
          "penDown()\n"
        )

        var j: Int =
          1

        while (
          j < item.points.length
        ) {

          builder.append(
            "lineTo(" +
            (
              item.points(j).x -
              CENTER_X
            ) +
            ", " +
            (
              CENTER_Y -
              item.points(j).y
            ) +
            ")\n"
          )

          j += 1
        }

        builder.append(
          "\n"
        )
      }

      i += 1
    }
  }

  generatedDrawingCode =
    builder.toString

  if (
    drawingCodeArea != null
  ) {

    drawingCodeArea.setText(
      generatedDrawingCode
    )
  }

  setMainStatus(
    "DRAW CODE GENERATED"
  )
}


// ============================================================
// COPY
// ============================================================

def copyGeneratedText(
  text: String
): Unit = {

  try {

    val clipboard =
      java.awt.Toolkit
        .getDefaultToolkit
        .getSystemClipboard

    clipboard.setContents(
      new java.awt.datatransfer.StringSelection(
        text
      ),
      null
    )

    setMainStatus(
      "CODE COPIED"
    )

  } catch {

    case _: Exception =>
      setMainStatus(
        "COPY FAILED"
      )
  }
}


// ============================================================
// SAVE
// ============================================================

def saveGeneratedText(
  text: String
): Unit = {

  val chooser =
    new JFileChooser()

  chooser.setDialogTitle(
    "SAVE SCALA CODE"
  )

  if (
    chooser.showSaveDialog(mainFrame) ==
    JFileChooser.APPROVE_OPTION
  ) {

    try {

      var file =
        chooser.getSelectedFile

      if (
        !file.getName
          .toLowerCase
          .endsWith(".scala")
      ) {

        file =
          new java.io.File(
            file.getAbsolutePath +
            ".scala"
          )
      }

      val writer =
        new java.io.PrintWriter(
          file
        )

      writer.write(text)

      writer.close()

      setMainStatus(
        "CODE SAVED"
      )

    } catch {

      case _: Exception =>
        setMainStatus(
          "SAVE ERROR"
        )
    }
  }
}


// ============================================================
// GAME LIST
// ============================================================

val gameNames =
  Array(
    "SLING BALL",
    "TIC TAC TOE",
    "PONG",
    "KEYBOARD SNAKE",
    "KEYBOARD RUNNER",
    "KEYBOARD SPACE",
    "KEYBOARD CAR",
    "KEYBOARD PLATFORM",
    "KEYBOARD MAZE",
    "KEYBOARD DODGE"
  )


val gameDescriptions =
  Array(
    "Mouse drag and launch ball.",
    "Mouse Tic Tac Toe.",
    "Mouse paddle Pong.",
    "Arrow / WASD Snake.",
    "Arrow / WASD runner with coins and enemies.",
    "Arrow movement and SPACE shooting.",
    "Arrow controlled car.",
    "Platform game with jump and coins.",
    "Keyboard maze.",
    "Keyboard enemy dodge game."
  )


// ============================================================
// GAME FRAME
// ============================================================

def createGameFrame(
  title: String
): JFrame = {

  val frame =
    new JFrame(title)

  frame.setDefaultCloseOperation(
    WindowConstants.DISPOSE_ON_CLOSE
  )

  frame.setSize(
    900,
    650
  )

  frame.setLocationRelativeTo(
    mainFrame
  )

  frame
}


// ============================================================
// TIMER CLEANUP
// ============================================================

def stopTimerWhenClosed(
  frame: JFrame,
  timerObject: Timer
): Unit = {

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

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

        timerObject.stop()
      }
    }
  )
}


// ============================================================
// MOUSE SLING BALL
// ============================================================

def launchSlingBall(): Unit = {

  val frame =
    createGameFrame(
      "SLING BALL - MOUSE"
    )

  var ballX: Double =
    120

  var ballY: Double =
    500

  var velocityX: Double =
    0

  var velocityY: Double =
    0

  var launched: Boolean =
    false

  var won: Boolean =
    false

  var aimX: Double =
    120

  var aimY: Double =
    500

  val target =
    new java.awt.Rectangle(
      760,
      100,
      70,
      70
    )

  val panel =
    new JPanel {

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

        super.paintComponent(
          graphics
        )

        val g =
          graphics.asInstanceOf[Graphics2D]

        g.setPaint(
          new GradientPaint(
            0,
            0,
            new Color(
              30,
              100,
              160
            ),
            0,
            getHeight,
            new Color(
              5,
              15,
              35
            )
          )
        )

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

        g.setColor(
          Color.RED
        )

        g.fillOval(
          target.x,
          target.y,
          target.width,
          target.height
        )

        g.setColor(
          Color.YELLOW
        )

        g.fillOval(
          ballX.toInt - 10,
          ballY.toInt - 10,
          20,
          20
        )

        if (
          !launched
        ) {

          g.setColor(
            Color.GREEN
          )

          g.drawLine(
            ballX.toInt,
            ballY.toInt,
            aimX.toInt,
            aimY.toInt
          )
        }

        g.setColor(
          Color.WHITE
        )

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

        g.drawString(
          if (won)
            "YOU WIN!"
          else
            "MOVE MOUSE / RELEASE TO LAUNCH",
          20,
          30
        )
      }
    }

  panel.addMouseMotionListener(
    new MouseMotionAdapter {

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

        if (!launched) {

          aimX =
            e.getX

          aimY =
            e.getY

          panel.repaint()
        }
      }
    }
  )

  panel.addMouseListener(
    new MouseAdapter {

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

        if (!launched) {

          velocityX =
            (ballX - e.getX) * 0.08

          velocityY =
            (ballY - e.getY) * 0.08

          launched =
            true
        }
      }
    }
  )

  val slingTimer: Timer =
    new Timer(
      25,
      new ActionListener {

        override def actionPerformed(
          e: ActionEvent
        ): Unit = {

          if (
            launched &&
            !won
          ) {

            velocityY += 0.25

            ballX += velocityX
            ballY += velocityY

            if (
              ballX < 10 ||
              ballX > panel.getWidth - 10
            ) {

              velocityX =
                -velocityX
            }

            if (
              ballY < 10
            ) {

              velocityY =
                -velocityY
            }

            if (
              ballY >
              panel.getHeight + 20
            ) {

              ballX = 120
              ballY = 500

              velocityX = 0
              velocityY = 0

              launched = false
            }

            if (
              target.contains(
                ballX.toInt,
                ballY.toInt
              )
            ) {

              won = true
            }

            panel.repaint()
          }
        }
      }
    )

  slingTimer.start()

  stopTimerWhenClosed(
    frame,
    slingTimer
  )

  frame.add(panel)

  frame.setVisible(true)
}


// ============================================================
// TIC TAC TOE
// ============================================================

def launchTicTacToe(): Unit = {

  val frame =
    createGameFrame(
      "TIC TAC TOE"
    )

  val board =
    Array.fill[Int](9)(0)

  var humanTurn =
    true

  var finished =
    false


  def winner(
    player: Int
  ): Boolean = {

    val lines =
      Array(
        Array(0,1,2),
        Array(3,4,5),
        Array(6,7,8),
        Array(0,3,6),
        Array(1,4,7),
        Array(2,5,8),
        Array(0,4,8),
        Array(2,4,6)
      )

    var found =
      false

    var i =
      0

    while (
      i < lines.length
    ) {

      if (
        board(lines(i)(0)) == player &&
        board(lines(i)(1)) == player &&
        board(lines(i)(2)) == player
      ) {

        found =
          true
      }

      i += 1
    }

    found
  }


  def fullBoard(): Boolean = {

    var full =
      true

    var i =
      0

    while (
      i < 9
    ) {

      if (
        board(i) == 0
      ) {

        full =
          false
      }

      i += 1
    }

    full
  }


  def computerMove(): Unit = {

    var chosen =
      -1

    var i =
      0

    while (
      i < 9 &&
      chosen < 0
    ) {

      if (
        board(i) == 0
      ) {

        chosen =
          i
      }

      i += 1
    }

    if (
      chosen >= 0
    ) {

      board(chosen) =
        2
    }
  }


  val panel =
    new JPanel {

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

        super.paintComponent(
          graphics
        )

        val g =
          graphics.asInstanceOf[Graphics2D]

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

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

        g.setColor(
          Color.BLACK
        )

        g.setStroke(
          new BasicStroke(
            5
          )
        )

        var i =
          1

        while (
          i < 3
        ) {

          g.drawLine(
            i * getWidth / 3,
            0,
            i * getWidth / 3,
            getHeight
          )

          g.drawLine(
            0,
            i * getHeight / 3,
            getWidth,
            i * getHeight / 3
          )

          i += 1
        }

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

        i = 0

        while (
          i < 9
        ) {

          if (
            board(i) != 0
          ) {

            val col =
              i % 3

            val row =
              i / 3

            g.setColor(
              if (
                board(i) == 1
              )
                Color.BLUE
              else
                Color.RED
            )

            g.drawString(
              if (
                board(i) == 1
              )
                "X"
              else
                "O",
              col * getWidth / 3 + 65,
              row * getHeight / 3 + 100
            )
          }

          i += 1
        }

        g.setColor(
          Color.DARK_GRAY
        )

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

        g.drawString(
          if (finished)
            "GAME OVER"
          else if (humanTurn)
            "YOUR TURN"
          else
            "COMPUTER TURN",
          20,
          28
        )
      }
    }


  panel.addMouseListener(
    new MouseAdapter {

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

        if (
          humanTurn &&
          !finished
        ) {

          val col =
            math.max(
              0,
              math.min(
                2,
                e.getX /
                math.max(
                  1,
                  panel.getWidth / 3
                )
              )
            )

          val row =
            math.max(
              0,
              math.min(
                2,
                e.getY /
                math.max(
                  1,
                  panel.getHeight / 3
                )
              )
            )

          val index =
            row * 3 + col

          if (
            board(index) == 0
          ) {

            board(index) =
              1

            if (
              winner(1) ||
              fullBoard()
            ) {

              finished =
                true

            } else {

              humanTurn =
                false

              computerMove()

              if (
                winner(2) ||
                fullBoard()
              ) {

                finished =
                  true

              } else {

                humanTurn =
                  true
              }
            }

            panel.repaint()
          }
        }
      }
    }
  )

  frame.add(panel)

  frame.setVisible(true)
}


// ============================================================
// MOUSE PONG
// ============================================================

def launchPong(): Unit = {

  val frame =
    createGameFrame(
      "PONG - MOUSE"
    )

  var paddleX: Double =
    350

  var ballX: Double =
    450

  var ballY: Double =
    250

  var vx: Double =
    5

  var vy: Double =
    4

  var score: Int =
    0


  val panel =
    new JPanel {

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

        super.paintComponent(
          graphics
        )

        val g =
          graphics.asInstanceOf[Graphics2D]

        g.setColor(
          Color.BLACK
        )

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

        g.setColor(
          Color.WHITE
        )

        g.fillOval(
          ballX.toInt - 10,
          ballY.toInt - 10,
          20,
          20
        )

        g.fillRoundRect(
          paddleX.toInt,
          getHeight - 60,
          150,
          20,
          10,
          10
        )

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

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


  panel.addMouseMotionListener(
    new MouseMotionAdapter {

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

        paddleX =
          math.max(
            0,
            math.min(
              panel.getWidth - 150,
              e.getX - 75
            )
          )
      }
    }
  )


  val pongTimer: Timer =
    new Timer(
      25,
      new ActionListener {

        override def actionPerformed(
          e: ActionEvent
        ): Unit = {

          ballX += vx
          ballY += vy

          if (
            ballX < 10 ||
            ballX > panel.getWidth - 10
          ) {

            vx =
              -vx
          }

          if (
            ballY < 10
          ) {

            vy =
              -vy
          }

          if (
            ballY >
            panel.getHeight - 85 &&
            ballX >= paddleX &&
            ballX <= paddleX + 150
          ) {

            vy =
              -math.abs(vy)

            score += 1
          }

          if (
            ballY >
            panel.getHeight + 10
          ) {

            ballX =
              panel.getWidth / 2.0

            ballY =
              panel.getHeight / 2.0

            vx = 5
            vy = -4

            score = 0
          }

          panel.repaint()
        }
      }
    )

  pongTimer.start()

  stopTimerWhenClosed(
    frame,
    pongTimer
  )

  frame.add(panel)

  frame.setVisible(true)
}


// ============================================================
// KEYBOARD SNAKE
// CELL SIZE FIX
// ============================================================

def launchKeyboardSnake(): Unit = {

  val frame =
    createGameFrame(
      "SNAKE - KEYBOARD"
    )

  val cellSize: Int =
    25

  val snakeBody =
    ArrayBuffer[Point](
      new Point(10,10),
      new Point(9,10),
      new Point(8,10)
    )

  var food =
    new Point(
      16,
      10
    )

  var directionX: Int =
    1

  var directionY: Int =
    0

  var score: Int =
    0

  var running: Boolean =
    true


  val panel =
    new JPanel {

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

        super.paintComponent(
          graphics
        )

        val g =
          graphics.asInstanceOf[Graphics2D]

        g.setColor(
          new Color(
            15,
            45,
            22
          )
        )

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


        // GRID

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

        var gx: Int =
          0

        while (
          gx <= getWidth
        ) {

          g.drawLine(
            gx,
            0,
            gx,
            getHeight
          )

          gx += cellSize
        }


        var gy: Int =
          0

        while (
          gy <= getHeight
        ) {

          g.drawLine(
            0,
            gy,
            getWidth,
            gy
          )

          gy += cellSize
        }


        // FOOD

        g.setColor(
          Color.RED
        )

        g.fillOval(
          food.x * cellSize + 3,
          food.y * cellSize + 3,
          cellSize - 6,
          cellSize - 6
        )


        // BODY

        var i: Int =
          0

        while (
          i < snakeBody.length
        ) {

          if (
            i == 0
          ) {

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

          } else {

            g.setColor(
              new Color(
                35,
                175,
                65
              )
            )
          }

          g.fillRoundRect(
            snakeBody(i).x * cellSize + 2,
            snakeBody(i).y * cellSize + 2,
            cellSize - 4,
            cellSize - 4,
            8,
            8
          )

          i += 1
        }


        // SCORE

        g.setColor(
          Color.WHITE
        )

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

        g.drawString(
          "ARROWS / WASD   SCORE: " + score,
          15,
          25
        )


        if (
          !running
        ) {

          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,
              42
            )
          )

          g.drawString(
            "GAME OVER",
            300,
            285
          )

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

          g.drawString(
            "FINAL SCORE: " + score,
            345,
            325
          )
        }
      }
    }


  panel.setFocusable(true)


  panel.addKeyListener(
    new java.awt.event.KeyAdapter {

      override def keyPressed(
        e: java.awt.event.KeyEvent
      ): Unit = {

        val key =
          e.getKeyCode

        if (
          key ==
          java.awt.event.KeyEvent.VK_LEFT ||
          key ==
          java.awt.event.KeyEvent.VK_A
        ) {

          if (
            directionX != 1
          ) {

            directionX =
              -1

            directionY =
              0
          }

        } else if (
          key ==
          java.awt.event.KeyEvent.VK_RIGHT ||
          key ==
          java.awt.event.KeyEvent.VK_D
        ) {

          if (
            directionX != -1
          ) {

            directionX =
              1

            directionY =
              0
          }

        } else if (
          key ==
          java.awt.event.KeyEvent.VK_UP ||
          key ==
          java.awt.event.KeyEvent.VK_W
        ) {

          if (
            directionY != 1
          ) {

            directionX =
              0

            directionY =
              -1
          }

        } else if (
          key ==
          java.awt.event.KeyEvent.VK_DOWN ||
          key ==
          java.awt.event.KeyEvent.VK_S
        ) {

          if (
            directionY != -1
          ) {

            directionX =
              0

            directionY =
              1
          }
        }
      }
    }
  )


  val snakeTimer: Timer =
    new Timer(
      110,
      new ActionListener {

        override def actionPerformed(
          e: ActionEvent
        ): Unit = {

          if (
            running &&
            snakeBody.nonEmpty
          ) {

            val head =
              snakeBody.head

            val nextX =
              head.x +
              directionX

            val nextY =
              head.y +
              directionY

            val columnCount: Int =
              math.max(
                1,
                panel.getWidth /
                cellSize
              )

            val rowCount: Int =
              math.max(
                1,
                panel.getHeight /
                cellSize
              )


            // WALL

            if (
              nextX < 0 ||
              nextY < 0 ||
              nextX >= columnCount ||
              nextY >= rowCount
            ) {

              running =
                false

            } else {

              val newHead =
                new Point(
                  nextX,
                  nextY
                )


              // SELF COLLISION

              var collision =
                false

              var i: Int =
                0

              while (
                i < snakeBody.length
              ) {

                if (
                  snakeBody(i).x ==
                  newHead.x &&
                  snakeBody(i).y ==
                  newHead.y
                ) {

                  collision =
                    true
                }

                i += 1
              }


              if (
                collision
              ) {

                running =
                  false

              } else {

                snakeBody.prepend(
                  newHead
                )


                // FOOD

                if (
                  newHead.x ==
                  food.x &&
                  newHead.y ==
                  food.y
                ) {

                  score +=
                    1

                  var foodFound =
                    false

                  var foodX: Int =
                    0

                  var foodY: Int =
                    0

                  while (
                    !foodFound
                  ) {

                    foodX =
                      (
                        math.random *
                        columnCount
                      ).toInt

                    foodY =
                      (
                        math.random *
                        rowCount
                      ).toInt

                    foodFound =
                      true

                    var check: Int =
                      0

                    while (
                      check < snakeBody.length
                    ) {

                      if (
                        snakeBody(check).x ==
                        foodX &&
                        snakeBody(check).y ==
                        foodY
                      ) {

                        foodFound =
                          false
                      }

                      check += 1
                    }
                  }

                  food =
                    new Point(
                      foodX,
                      foodY
                    )

                } else {

                  snakeBody.remove(
                    snakeBody.length - 1
                  )
                }
              }
            }

            panel.repaint()
          }
        }
      }
    )


  snakeTimer.start()

  stopTimerWhenClosed(
    frame,
    snakeTimer
  )

  frame.add(
    panel,
    BorderLayout.CENTER
  )

  frame.setVisible(true)

  panel.requestFocusInWindow()
}


// ============================================================
// KEYBOARD RUNNER
// ============================================================

def launchKeyboardRunner(): Unit = {

  val frame =
    createGameFrame(
      "KEYBOARD RUNNER"
    )

  var playerX: Double =
    100

  var playerY: Double =
    450

  var velocityY: Double =
    0

  var score: Int =
    0

  var jumping: Boolean =
    false

  var running: Boolean =
    true

  val coins =
    ArrayBuffer[Point](
      new Point(300,390),
      new Point(500,330),
      new Point(700,390)
    )

  val enemies =
    ArrayBuffer[Point](
      new Point(550,450),
      new Point(800,450)
    )


  val panel =
    new JPanel {

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

        super.paintComponent(graphics)

        val g =
          graphics.asInstanceOf[Graphics2D]

        g.setPaint(
          new GradientPaint(
            0,
            0,
            new Color(
              75,
              180,
              250
            ),
            0,
            getHeight,
            new Color(
              210,
              240,
              255
            )
          )
        )

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

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

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

        g.setColor(
          Color.BLUE
        )

        g.fillRoundRect(
          playerX.toInt,
          playerY.toInt,
          45,
          65,
          10,
          10
        )

        g.setColor(
          Color.YELLOW
        )

        var i =
          0

        while (
          i < coins.length
        ) {

          g.fillOval(
            coins(i).x,
            coins(i).y,
            25,
            25
          )

          i += 1
        }

        g.setColor(
          Color.RED
        )

        i = 0

        while (
          i < enemies.length
        ) {

          g.fillRect(
            enemies(i).x,
            enemies(i).y,
            45,
            45
          )

          i += 1
        }

        g.setColor(
          Color.WHITE
        )

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

        g.drawString(
          "A/D or ARROWS = MOVE   SPACE = JUMP   SCORE: " +
          score,
          15,
          28
        )

        if (
          !running
        ) {

          g.setColor(
            Color.BLACK
          )

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

          g.setColor(
            Color.WHITE
          )

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

          g.drawString(
            "GAME OVER",
            305,
            300
          )
        }
      }
    }


  panel.setFocusable(true)


  panel.addKeyListener(
    new java.awt.event.KeyAdapter {

      override def keyPressed(
        e: java.awt.event.KeyEvent
      ): Unit = {

        if (
          !running
        ) {
          return
        }

        val key =
          e.getKeyCode

        if (
          key ==
          java.awt.event.KeyEvent.VK_LEFT ||
          key ==
          java.awt.event.KeyEvent.VK_A
        ) {

          playerX -=
            12

        } else if (
          key ==
          java.awt.event.KeyEvent.VK_RIGHT ||
          key ==
          java.awt.event.KeyEvent.VK_D
        ) {

          playerX +=
            12

        } else if (
          key ==
          java.awt.event.KeyEvent.VK_SPACE ||
          key ==
          java.awt.event.KeyEvent.VK_UP
        ) {

          if (
            !jumping
          ) {

            velocityY =
              -11

            jumping =
              true
          }
        }

        playerX =
          math.max(
            0,
            math.min(
              panel.getWidth - 45,
              playerX
            )
          )
      }
    }
  )


  val runnerTimer: Timer =
    new Timer(
      30,
      new ActionListener {

        override def actionPerformed(
          e: ActionEvent
        ): Unit = {

          if (
            running
          ) {

            velocityY +=
              0.55

            playerY +=
              velocityY

            val groundY =
              panel.getHeight -
              165

            if (
              playerY >= groundY
            ) {

              playerY =
                groundY

              velocityY =
                0

              jumping =
                false
            }


            var i =
              coins.length - 1

            while (
              i >= 0
            ) {

              val coin =
                coins(i)

              val hit =
                playerX + 45 > coin.x &&
                playerX < coin.x + 25 &&
                playerY + 65 > coin.y &&
                playerY < coin.y + 25

              if (
                hit
              ) {

                coins.remove(i)

                score +=
                  10
              }

              i -= 1
            }


            i = 0

            while (
              i < enemies.length
            ) {

              enemies(i).x -=
                3

              if (
                enemies(i).x < -50
              ) {

                enemies(i).x =
                  panel.getWidth +
                  100 +
                  i * 200
              }

              val hit =
                playerX + 45 > enemies(i).x &&
                playerX < enemies(i).x + 45 &&
                playerY + 65 > enemies(i).y &&
                playerY < enemies(i).y + 45

              if (
                hit
              ) {

                running =
                  false
              }

              i += 1
            }

            panel.repaint()
          }
        }
      }
    )


  runnerTimer.start()

  stopTimerWhenClosed(
    frame,
    runnerTimer
  )

  frame.add(panel)

  frame.setVisible(true)

  panel.requestFocusInWindow()
}


// ============================================================
// KEYBOARD SPACE
// ============================================================

def launchKeyboardSpace(): Unit = {

  val frame =
    createGameFrame(
      "KEYBOARD SPACE"
    )

  var shipX: Double =
    400

  var laserX: Double =
    -100

  var laserY: Double =
    -100

  var enemyX: Double =
    400

  var enemyY: Double =
    80

  var score: Int =
    0


  val panel =
    new JPanel {

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

        super.paintComponent(graphics)

        val g =
          graphics.asInstanceOf[Graphics2D]

        g.setColor(
          new Color(
            5,
            10,
            30
          )
        )

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

        g.setColor(
          Color.WHITE
        )

        var star: Int =
          0

        while (
          star < 70
        ) {

          g.fillOval(
            (star * 83) %
            math.max(
              1,
              getWidth
            ),
            (star * 47) %
            math.max(
              1,
              getHeight
            ),
            2,
            2
          )

          star += 1
        }

        g.setColor(
          Color.CYAN
        )

        val ship =
          new java.awt.Polygon()

        ship.addPoint(
          shipX.toInt,
          getHeight - 70
        )

        ship.addPoint(
          shipX.toInt + 25,
          getHeight - 130
        )

        ship.addPoint(
          shipX.toInt + 50,
          getHeight - 70
        )

        g.fillPolygon(ship)

        g.setColor(
          Color.RED
        )

        g.fillOval(
          enemyX.toInt,
          enemyY.toInt,
          45,
          35
        )

        g.setColor(
          Color.YELLOW
        )

        if (
          laserY > -40
        ) {

          g.fillRect(
            laserX.toInt,
            laserY.toInt,
            6,
            20
          )
        }

        g.setColor(
          Color.WHITE
        )

        g.drawString(
          "LEFT / RIGHT = MOVE   SPACE = FIRE   SCORE: " +
          score,
          15,
          28
        )
      }
    }


  panel.setFocusable(true)


  panel.addKeyListener(
    new java.awt.event.KeyAdapter {

      override def keyPressed(
        e: java.awt.event.KeyEvent
      ): Unit = {

        val key =
          e.getKeyCode

        if (
          key ==
          java.awt.event.KeyEvent.VK_LEFT
        ) {

          shipX -=
            12

        } else if (
          key ==
          java.awt.event.KeyEvent.VK_RIGHT
        ) {

          shipX +=
            12

        } else if (
          key ==
          java.awt.event.KeyEvent.VK_SPACE
        ) {

          laserX =
            shipX + 22

          laserY =
            panel.getHeight - 150
        }

        shipX =
          math.max(
            0,
            math.min(
              panel.getWidth - 50,
              shipX
            )
          )
      }
    }
  )


  val spaceTimer: Timer =
    new Timer(
      25,
      new ActionListener {

        override def actionPerformed(
          e: ActionEvent
        ): Unit = {

          enemyY +=
            2

          laserY -=
            12

          if (
            laserX >= enemyX &&
            laserX <= enemyX + 45 &&
            laserY <= enemyY + 35 &&
            laserY >= enemyY - 20
          ) {

            score +=
              1

            enemyX =
              math.random *
              math.max(
                1,
                panel.getWidth - 45
              )

            enemyY =
              50
          }

          if (
            enemyY >
            panel.getHeight
          ) {

            enemyY =
              50
          }

          panel.repaint()
        }
      }
    )


  spaceTimer.start()

  stopTimerWhenClosed(
    frame,
    spaceTimer
  )

  frame.add(panel)

  frame.setVisible(true)

  panel.requestFocusInWindow()
}


// ============================================================
// KEYBOARD CAR
// ============================================================

def launchKeyboardCar(): Unit = {

  val frame =
    createGameFrame(
      "KEYBOARD CAR"
    )

  var playerX: Double =
    420

  var enemyX: Double =
    250

  var enemyY: Double =
    -100

  var score: Int =
    0

  var running: Boolean =
    true


  val panel =
    new JPanel {

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

        super.paintComponent(graphics)

        val g =
          graphics.asInstanceOf[Graphics2D]

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

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

        g.setColor(
          Color.WHITE
        )

        var roadY: Int =
          0

        while (
          roadY < getHeight
        ) {

          g.fillRect(
            getWidth / 2 - 5,
            roadY,
            10,
            60
          )

          roadY +=
            100
        }

        g.setColor(
          Color.BLUE
        )

        g.fillRoundRect(
          playerX.toInt,
          getHeight - 125,
          60,
          100,
          18,
          18
        )

        g.setColor(
          Color.RED
        )

        g.fillRoundRect(
          enemyX.toInt,
          enemyY.toInt,
          60,
          100,
          18,
          18
        )

        g.setColor(
          Color.WHITE
        )

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

        g.drawString(
          "LEFT / RIGHT   SCORE: " +
          score,
          15,
          28
        )

        if (
          !running
        ) {

          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,
              42
            )
          )

          g.drawString(
            "CRASH!",
            355,
            300
          )
        }
      }
    }


  panel.setFocusable(true)


  panel.addKeyListener(
    new java.awt.event.KeyAdapter {

      override def keyPressed(
        e: java.awt.event.KeyEvent
      ): Unit = {

        val key =
          e.getKeyCode

        if (
          key ==
          java.awt.event.KeyEvent.VK_LEFT
        ) {

          playerX -=
            15

        } else if (
          key ==
          java.awt.event.KeyEvent.VK_RIGHT
        ) {

          playerX +=
            15
        }

        playerX =
          math.max(
            0,
            math.min(
              panel.getWidth - 60,
              playerX
            )
          )
      }
    }
  )


  val carTimer: Timer =
    new Timer(
      25,
      new ActionListener {

        override def actionPerformed(
          e: ActionEvent
        ): Unit = {

          if (
            running
          ) {

            enemyY +=
              7

            if (
              enemyY >
              panel.getHeight
            ) {

              enemyY =
                -100

              enemyX =
                math.random *
                math.max(
                  1,
                  panel.getWidth - 60
                )

              score +=
                1
            }

            val hit =
              enemyX + 60 > playerX &&
              enemyX < playerX + 60 &&
              enemyY + 100 >
              panel.getHeight - 125 &&
              enemyY <
              panel.getHeight

            if (
              hit
            ) {

              running =
                false
            }

            panel.repaint()
          }
        }
      }
    )


  carTimer.start()

  stopTimerWhenClosed(
    frame,
    carTimer
  )

  frame.add(panel)

  frame.setVisible(true)

  panel.requestFocusInWindow()
}


// ============================================================
// KEYBOARD PLATFORM
// ============================================================

def launchKeyboardPlatform(): Unit = {

  val frame =
    createGameFrame(
      "KEYBOARD PLATFORM"
    )

  var playerX: Double =
    100

  var playerY: Double =
    450

  var velocityY: Double =
    0

  var score: Int =
    0

  var jumping: Boolean =
    false

  var won: Boolean =
    false

  val platforms =
    Array(
      new java.awt.Rectangle(
        0,
        550,
        900,
        70
      ),
      new java.awt.Rectangle(
        180,
        430,
        160,
        20
      ),
      new java.awt.Rectangle(
        420,
        350,
        160,
        20
      ),
      new java.awt.Rectangle(
        650,
        260,
        160,
        20
      )
    )

  val coins =
    ArrayBuffer[Point](
      new Point(230,395),
      new Point(470,315),
      new Point(705,225)
    )

  val goal =
    new java.awt.Rectangle(
      820,
      170,
      50,
      50
    )


  val panel =
    new JPanel {

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

        super.paintComponent(graphics)

        val g =
          graphics.asInstanceOf[Graphics2D]

        g.setPaint(
          new GradientPaint(
            0,
            0,
            new Color(
              60,
              160,
              245
            ),
            0,
            getHeight,
            new Color(
              210,
              240,
              255
            )
          )
        )

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

        g.setColor(
          new Color(
            90,
            180,
            80
          )
        )

        var p: Int =
          0

        while (
          p < platforms.length
        ) {

          g.fillRoundRect(
            platforms(p).x,
            platforms(p).y,
            platforms(p).width,
            platforms(p).height,
            10,
            10
          )

          p += 1
        }

        g.setColor(
          Color.YELLOW
        )

        var c: Int =
          0

        while (
          c < coins.length
        ) {

          g.fillOval(
            coins(c).x,
            coins(c).y,
            24,
            24
          )

          c += 1
        }

        g.setColor(
          Color.GREEN
        )

        g.fillOval(
          goal.x,
          goal.y,
          goal.width,
          goal.height
        )

        g.setColor(
          Color.BLUE
        )

        g.fillRoundRect(
          playerX.toInt,
          playerY.toInt,
          42,
          58,
          10,
          10
        )

        g.setColor(
          Color.WHITE
        )

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

        g.drawString(
          "A/D or ARROWS = MOVE   SPACE = JUMP   SCORE: " +
          score,
          15,
          28
        )

        if (
          won
        ) {

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

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

          g.setColor(
            Color.WHITE
          )

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

          g.drawString(
            "LEVEL COMPLETE!",
            270,
            300
          )
        }
      }
    }


  panel.setFocusable(true)


  panel.addKeyListener(
    new java.awt.event.KeyAdapter {

      override def keyPressed(
        e: java.awt.event.KeyEvent
      ): Unit = {

        val key =
          e.getKeyCode

        if (
          key ==
          java.awt.event.KeyEvent.VK_LEFT ||
          key ==
          java.awt.event.KeyEvent.VK_A
        ) {

          playerX -=
            12

        } else if (
          key ==
          java.awt.event.KeyEvent.VK_RIGHT ||
          key ==
          java.awt.event.KeyEvent.VK_D
        ) {

          playerX +=
            12

        } else if (
          key ==
          java.awt.event.KeyEvent.VK_SPACE ||
          key ==
          java.awt.event.KeyEvent.VK_UP
        ) {

          if (
            !jumping
          ) {

            velocityY =
              -12

            jumping =
              true
          }
        }

        playerX =
          math.max(
            0,
            math.min(
              panel.getWidth - 42,
              playerX
            )
          )
      }
    }
  )


  val platformTimer: Timer =
    new Timer(
      30,
      new ActionListener {

        override def actionPerformed(
          e: ActionEvent
        ): Unit = {

          if (
            !won
          ) {

            velocityY +=
              0.55

            playerY +=
              velocityY


            val playerRect =
              new java.awt.Rectangle(
                playerX.toInt,
                playerY.toInt,
                42,
                58
              )


            var onPlatform =
              false

            var i: Int =
              0

            while (
              i < platforms.length
            ) {

              val platform =
                platforms(i)

              if (
                playerRect.x + playerRect.width >
                platform.x &&
                playerRect.x <
                platform.x + platform.width &&
                playerRect.y + playerRect.height >
                platform.y &&
                playerRect.y + playerRect.height <
                platform.y + platform.height + 20 &&
                velocityY >= 0
              ) {

                playerY =
                  platform.y - 58

                velocityY =
                  0

                jumping =
                  false

                onPlatform =
                  true
              }

              i += 1
            }


            if (
              playerY >
              panel.getHeight + 50
            ) {

              playerX =
                100

              playerY =
                450

              velocityY =
                0

              jumping =
                false
            }


            i =
              coins.length - 1

            while (
              i >= 0
            ) {

              val coin =
                new java.awt.Rectangle(
                  coins(i).x,
                  coins(i).y,
                  24,
                  24
                )

              if (
                coin.intersects(
                  playerRect
                )
              ) {

                coins.remove(i)

                score +=
                  10
              }

              i -= 1
            }


            if (
              goal.intersects(
                playerRect
              )
            ) {

              won =
                true
            }

            panel.repaint()
          }
        }
      }
    )


  platformTimer.start()

  stopTimerWhenClosed(
    frame,
    platformTimer
  )

  frame.add(panel)

  frame.setVisible(true)

  panel.requestFocusInWindow()
}


// ============================================================
// KEYBOARD MAZE
// ============================================================

def launchKeyboardMaze(): Unit = {

  val frame =
    createGameFrame(
      "KEYBOARD MAZE"
    )

  var playerX: Int =
    50

  var playerY: Int =
    70

  var score: Int =
    0

  val playerSize: Int =
    30

  val target =
    new java.awt.Rectangle(
      800,
      520,
      45,
      45
    )

  val walls =
    Array(
      new java.awt.Rectangle(150,50,25,400),
      new java.awt.Rectangle(300,200,25,350),
      new java.awt.Rectangle(450,50,25,400),
      new java.awt.Rectangle(600,200,25,350),
      new java.awt.Rectangle(750,50,25,400)
    )


  val panel =
    new JPanel {

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

        super.paintComponent(graphics)

        val g =
          graphics.asInstanceOf[Graphics2D]

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

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

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

        var i =
          0

        while (
          i < walls.length
        ) {

          g.fillRect(
            walls(i).x,
            walls(i).y,
            walls(i).width,
            walls(i).height
          )

          i += 1
        }

        g.setColor(
          Color.GREEN
        )

        g.fillOval(
          target.x,
          target.y,
          target.width,
          target.height
        )

        g.setColor(
          Color.CYAN
        )

        g.fillRoundRect(
          playerX,
          playerY,
          playerSize,
          playerSize,
          8,
          8
        )

        g.setColor(
          Color.WHITE
        )

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

        g.drawString(
          "ARROWS / WASD = MOVE   SCORE: " +
          score,
          15,
          28
        )
      }
    }


  panel.setFocusable(true)


  panel.addKeyListener(
    new java.awt.event.KeyAdapter {

      override def keyPressed(
        e: java.awt.event.KeyEvent
      ): Unit = {

        val key =
          e.getKeyCode

        var dx =
          0

        var dy =
          0

        if (
          key ==
          java.awt.event.KeyEvent.VK_LEFT ||
          key ==
          java.awt.event.KeyEvent.VK_A
        ) {

          dx =
            -10

        } else if (
          key ==
          java.awt.event.KeyEvent.VK_RIGHT ||
          key ==
          java.awt.event.KeyEvent.VK_D
        ) {

          dx =
            10

        } else if (
          key ==
          java.awt.event.KeyEvent.VK_UP ||
          key ==
          java.awt.event.KeyEvent.VK_W
        ) {

          dy =
            -10

        } else if (
          key ==
          java.awt.event.KeyEvent.VK_DOWN ||
          key ==
          java.awt.event.KeyEvent.VK_S
        ) {

          dy =
            10
        }


        val candidate =
          new java.awt.Rectangle(
            playerX + dx,
            playerY + dy,
            playerSize,
            playerSize
          )

        var blocked =
          false

        var i =
          0

        while (
          i < walls.length
        ) {

          if (
            walls(i).intersects(
              candidate
            )
          ) {

            blocked =
              true
          }

          i += 1
        }


        if (
          !blocked
        ) {

          playerX =
            math.max(
              0,
              math.min(
                panel.getWidth - playerSize,
                playerX + dx
              )
            )

          playerY =
            math.max(
              45,
              math.min(
                panel.getHeight - playerSize,
                playerY + dy
              )
            )
        }


        val playerRect =
          new java.awt.Rectangle(
            playerX,
            playerY,
            playerSize,
            playerSize
          )

        if (
          target.intersects(
            playerRect
          )
        ) {

          score +=
            1

          playerX =
            50

          playerY =
            70
        }

        panel.repaint()
      }
    }
  )


  frame.add(panel)

  frame.setVisible(true)

  panel.requestFocusInWindow()
}


// ============================================================
// KEYBOARD DODGE
// ============================================================

def launchKeyboardDodge(): Unit = {

  val frame =
    createGameFrame(
      "KEYBOARD DODGE"
    )

  var playerX: Double =
    400

  var playerY: Double =
    500

  var score: Int =
    0

  var running: Boolean =
    true

  val enemies =
    ArrayBuffer[Point](
      new Point(100,-50),
      new Point(300,-180),
      new Point(550,-100),
      new Point(750,-230)
    )


  val panel =
    new JPanel {

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

        super.paintComponent(graphics)

        val g =
          graphics.asInstanceOf[Graphics2D]

        g.setColor(
          new Color(
            20,
            25,
            40
          )
        )

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

        g.setColor(
          Color.CYAN
        )

        g.fillOval(
          playerX.toInt,
          playerY.toInt,
          45,
          45
        )

        g.setColor(
          Color.RED
        )

        var i =
          0

        while (
          i < enemies.length
        ) {

          g.fillRoundRect(
            enemies(i).x,
            enemies(i).y,
            40,
            40,
            8,
            8
          )

          i += 1
        }

        g.setColor(
          Color.WHITE
        )

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

        g.drawString(
          "ARROWS / WASD   SCORE: " +
          score,
          15,
          28
        )

        if (
          !running
        ) {

          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,
              42
            )
          )

          g.drawString(
            "DODGED: " + score,
            305,
            300
          )
        }
      }
    }


  panel.setFocusable(true)


  panel.addKeyListener(
    new java.awt.event.KeyAdapter {

      override def keyPressed(
        e: java.awt.event.KeyEvent
      ): Unit = {

        val key =
          e.getKeyCode

        if (
          key ==
          java.awt.event.KeyEvent.VK_LEFT ||
          key ==
          java.awt.event.KeyEvent.VK_A
        ) {

          playerX -=
            14

        } else if (
          key ==
          java.awt.event.KeyEvent.VK_RIGHT ||
          key ==
          java.awt.event.KeyEvent.VK_D
        ) {

          playerX +=
            14

        } else if (
          key ==
          java.awt.event.KeyEvent.VK_UP ||
          key ==
          java.awt.event.KeyEvent.VK_W
        ) {

          playerY -=
            14

        } else if (
          key ==
          java.awt.event.KeyEvent.VK_DOWN ||
          key ==
          java.awt.event.KeyEvent.VK_S
        ) {

          playerY +=
            14
        }

        playerX =
          math.max(
            0,
            math.min(
              panel.getWidth - 45,
              playerX
            )
          )

        playerY =
          math.max(
            45,
            math.min(
              panel.getHeight - 45,
              playerY
            )
          )
      }
    }
  )


  val dodgeTimer: Timer =
    new Timer(
      30,
      new ActionListener {

        override def actionPerformed(
          e: ActionEvent
        ): Unit = {

          if (
            running
          ) {

            var i =
              0

            while (
              i < enemies.length
            ) {

              enemies(i).y +=
                5 + i

              if (
                enemies(i).y >
                panel.getHeight
              ) {

                enemies(i).y =
                  -60

                enemies(i).x =
                  (
                    math.random *
                    math.max(
                      1,
                      panel.getWidth - 40
                    )
                  ).toInt

                score +=
                  1
              }

              val hit =
                enemies(i).x + 40 >
                playerX &&
                enemies(i).x <
                playerX + 45 &&
                enemies(i).y + 40 >
                playerY &&
                enemies(i).y <
                playerY + 45

              if (
                hit
              ) {

                running =
                  false
              }

              i += 1
            }

            panel.repaint()
          }
        }
      }
    )

  dodgeTimer.start()

  stopTimerWhenClosed(
    frame,
    dodgeTimer
  )

  frame.add(panel)

  frame.setVisible(true)

  panel.requestFocusInWindow()
}


// ============================================================
// GAME ROUTER
// ============================================================

def startSelectedGame(
  name: String
): Unit = {

  if (
    name == "SLING BALL"
  ) {

    launchSlingBall()

  } else if (
    name == "TIC TAC TOE"
  ) {

    launchTicTacToe()

  } else if (
    name == "PONG"
  ) {

    launchPong()

  } else if (
    name == "KEYBOARD SNAKE"
  ) {

    launchKeyboardSnake()

  } else if (
    name == "KEYBOARD RUNNER"
  ) {

    launchKeyboardRunner()

  } else if (
    name == "KEYBOARD SPACE"
  ) {

    launchKeyboardSpace()

  } else if (
    name == "KEYBOARD CAR"
  ) {

    launchKeyboardCar()

  } else if (
    name == "KEYBOARD PLATFORM"
  ) {

    launchKeyboardPlatform()

  } else if (
    name == "KEYBOARD MAZE"
  ) {

    launchKeyboardMaze()

  } else {

    launchKeyboardDodge()
  }
}


// ============================================================
// GAME STUDIO WINDOW
// ============================================================

def openGameStudio(): Unit = {

  val frame =
    new JFrame(
      "ULTRA LEGEND GAME STUDIO X8"
    )

  frame.setDefaultCloseOperation(
    WindowConstants.DISPOSE_ON_CLOSE
  )

  frame.setSize(
    1200,
    750
  )

  frame.setLocationRelativeTo(
    mainFrame
  )


  val gameModel =
    new DefaultListModel[String]()


  var i =
    0

  while (
    i < gameNames.length
  ) {

    gameModel.addElement(
      gameNames(i)
    )

    i += 1
  }


  val gameList =
    new JList[String](
      gameModel
    )

  gameList.setSelectionMode(
    ListSelectionModel.SINGLE_SELECTION
  )

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


  val searchField =
    new JTextField(
      14
    )

  val searchButton =
    createButton(
      "SEARCH"
    )

  val playButton =
    createButton(
      "PLAY"
    )

  val codeButton =
    createButton(
      "GAME CODE"
    )

  val closeButton =
    createButton(
      "CLOSE"
    )


  val infoArea =
    new JTextArea()

  infoArea.setEditable(false)

  infoArea.setFont(
    new Font(
      "Monospaced",
      Font.PLAIN,
      13
    )
  )

  infoArea.setText(
    "ULTRA LEGEND GAME STUDIO X8\n\n" +
    "MOUSE + KEYBOARD GAMES\n\n" +
    "DOUBLE CLICK A GAME = PLAY\n\n" +
    "KEYBOARD GAMES:\n" +
    "SNAKE\n" +
    "RUNNER\n" +
    "SPACE\n" +
    "CAR\n" +
    "PLATFORM\n" +
    "MAZE\n" +
    "DODGE"
  )


  val searchPanel =
    new JPanel(
      new FlowLayout(
        FlowLayout.LEFT
      )
    )

  searchPanel.add(
    new JLabel(
      "SEARCH:"
    )
  )

  searchPanel.add(
    searchField
  )

  searchPanel.add(
    searchButton
  )


  val leftPanel =
    new JPanel(
      new BorderLayout()
    )

  leftPanel.add(
    new JLabel(
      "GAME LIBRARY"
    ),
    BorderLayout.NORTH
  )

  leftPanel.add(
    searchPanel,
    BorderLayout.CENTER
  )

  leftPanel.add(
    new JScrollPane(
      gameList
    ),
    BorderLayout.SOUTH
  )


  val rightPanel =
    new JPanel(
      new BorderLayout()
    )

  rightPanel.add(
    new JLabel(
      "GAME INFORMATION"
    ),
    BorderLayout.NORTH
  )

  rightPanel.add(
    new JScrollPane(
      infoArea
    ),
    BorderLayout.CENTER
  )


  val split =
    new JSplitPane(
      JSplitPane.HORIZONTAL_SPLIT,
      leftPanel,
      rightPanel
    )

  split.setDividerLocation(
    380
  )


  gameList.addListSelectionListener(
    new javax.swing.event.ListSelectionListener {

      override def valueChanged(
        e: javax.swing.event.ListSelectionEvent
      ): Unit = {

        if (
          !e.getValueIsAdjusting
        ) {

          val selected =
            gameList.getSelectedValue

          if (
            selected != null
          ) {

            var description =
              ""

            var j =
              0

            while (
              j < gameNames.length
            ) {

              if (
                gameNames(j) ==
                selected
              ) {

                description =
                  gameDescriptions(j)
              }

              j += 1
            }

            infoArea.setText(
              "GAME:\n\n" +
              selected +
              "\n\n" +
              description
            )
          }
        }
      }
    }
  )


  searchButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
        e: ActionEvent
      ): Unit = {

        val query =
          searchField
            .getText
            .trim
            .toLowerCase

        gameModel.clear()

        var j =
          0

        while (
          j < gameNames.length
        ) {

          if (
            query.isEmpty ||
            gameNames(j)
              .toLowerCase
              .indexOf(query) >= 0
          ) {

            gameModel.addElement(
              gameNames(j)
            )
          }

          j += 1
        }
      }
    }
  )


  playButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
        e: ActionEvent
      ): Unit = {

        val selected =
          gameList.getSelectedValue

        if (
          selected != null
        ) {

          startSelectedGame(
            selected
          )

        } else {

          JOptionPane.showMessageDialog(
            frame,
            "SELECT A GAME FIRST."
          )
        }
      }
    }
  )


  gameList.addMouseListener(
    new MouseAdapter {

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

        if (
          e.getClickCount == 2
        ) {

          val selected =
            gameList.getSelectedValue

          if (
            selected != null
          ) {

            startSelectedGame(
              selected
            )
          }
        }
      }
    }
  )


  codeButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
        e: ActionEvent
      ): Unit = {

        val selected =
          gameList.getSelectedValue

        if (
          selected != null
        ) {

          val codeFrame =
            new JFrame(
              "GAME CODE - " +
              selected
            )

          codeFrame.setDefaultCloseOperation(
            WindowConstants.DISPOSE_ON_CLOSE
          )

          codeFrame.setSize(
            800,
            600
          )

          codeFrame.setLocationRelativeTo(
            frame
          )

          val area =
            new JTextArea()

          area.setFont(
            new Font(
              "Monospaced",
              Font.PLAIN,
              13
            )
          )

          area.setText(
            "// ULTRA LEGEND GAME STUDIO X8\n" +
            "// GAME: " +
            selected +
            "\n\n" +
            "// Select PLAY in the Game Studio to launch this game."
          )

          codeFrame.add(
            new JScrollPane(
              area
            ),
            BorderLayout.CENTER
          )

          codeFrame.setVisible(true)

        } else {

          JOptionPane.showMessageDialog(
            frame,
            "SELECT A GAME FIRST."
          )
        }
      }
    }
  )


  closeButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
        e: ActionEvent
      ): Unit = {

        frame.dispose()
      }
    }
  )


  val bottomPanel =
    new JPanel(
      new FlowLayout(
        FlowLayout.CENTER
      )
    )

  bottomPanel.add(
    playButton
  )

  bottomPanel.add(
    codeButton
  )

  bottomPanel.add(
    closeButton
  )


  frame.setLayout(
    new BorderLayout()
  )

  frame.add(
    split,
    BorderLayout.CENTER
  )

  frame.add(
    bottomPanel,
    BorderLayout.SOUTH
  )

  frame.setVisible(true)
}


// ============================================================
// MAIN DRAWING CANVAS
// ============================================================

drawingCanvas =
  new JPanel {

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

      super.paintComponent(graphics)

      val g =
        graphics.asInstanceOf[Graphics2D]

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


      // WHITE BACKGROUND

      g.setColor(
        Color.WHITE
      )

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


      // GRID

      if (
        gridEnabled
      ) {

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

        var x: Int =
          0

        while (
          x <= CANVAS_WIDTH
        ) {

          g.drawLine(
            x,
            0,
            x,
            CANVAS_HEIGHT
          )

          x +=
            50
        }


        var y: Int =
          0

        while (
          y <= CANVAS_HEIGHT
        ) {

          g.drawLine(
            0,
            y,
            CANVAS_WIDTH,
            y
          )

          y +=
            50
        }
      }


      // OBJECTS

      var i: Int =
        0

      while (
        i < drawItems.length
      ) {

        val item =
          drawItems(i)

        if (
          item.visible &&
          item.points.length >= 2
        ) {

          if (
            item.filled &&
            item.points.length >= 3
          ) {

            val path =
              new java.awt.geom.Path2D.Double()

            path.moveTo(
              item.points.head.x,
              item.points.head.y
            )

            var j: Int =
              1

            while (
              j < item.points.length
            ) {

              path.lineTo(
                item.points(j).x,
                item.points(j).y
              )

              j += 1
            }

            path.closePath()

            g.setColor(
              item.fillColor
            )

            g.fill(path)
          }


          g.setColor(
            item.strokeColor
          )

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


          var j: Int =
            1

          while (
            j < item.points.length
          ) {

            g.drawLine(
              item.points(j - 1).x,
              item.points(j - 1).y,
              item.points(j).x,
              item.points(j).y
            )

            j += 1
          }
        }

        i += 1
      }


      // TEMPORARY DRAW

      if (
        temporaryPoints.length >= 2
      ) {

        g.setColor(
          currentStrokeColor
        )

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

        var j: Int =
          1

        while (
          j < temporaryPoints.length
        ) {

          g.drawLine(
            temporaryPoints(j - 1).x,
            temporaryPoints(j - 1).y,
            temporaryPoints(j).x,
            temporaryPoints(j).y
          )

          j += 1
        }
      }


      // SELECTION

      if (
        selectedItemIndex >= 0 &&
        selectedItemIndex < drawItems.length &&
        currentTool == "SELECT"
      ) {

        val bounds =
          itemBounds(
            drawItems(
              selectedItemIndex
            )
          )

        val rect =
          new java.awt.Rectangle(
            bounds.x - 8,
            bounds.y - 8,
            bounds.width + 16,
            bounds.height + 16
          )

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

        g.setStroke(
          new BasicStroke(2)
        )

        g.drawRect(
          rect.x,
          rect.y,
          rect.width,
          rect.height
        )

        val handles =
          selectionHandles(rect)

        var h: Int =
          0

        while (
          h < handles.length
        ) {

          val hp =
            handles(h)._2

          g.setColor(
            Color.WHITE
          )

          g.fillRect(
            hp.x - 5,
            hp.y - 5,
            10,
            10
          )

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

          g.drawRect(
            hp.x - 5,
            hp.y - 5,
            10,
            10
          )

          h += 1
        }
      }
    }
  }


// ============================================================
// CANVAS INPUT
// ============================================================

drawingCanvas.setFocusable(true)


drawingCanvas.addMouseListener(
  new MouseAdapter {

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

      val p =
        canvasPoint(e)


      if (
        currentTool == "SELECT"
      ) {

        val hit =
          findItemAt(p)

        if (
          selectedItemIndex >= 0 &&
          selectedItemIndex < drawItems.length
        ) {

          val handle =
            findResizeHandle(
              drawItems(
                selectedItemIndex
              ),
              p
            )

          if (
            handle != ""
          ) {

            if (
              !drawItems(
                selectedItemIndex
              ).locked
            ) {

              saveHistory()

              resizingSelected =
                true

              movingSelected =
                false

              resizeMode =
                handle

              originalSelectedPoints =
                drawItems(
                  selectedItemIndex
                ).points

              previousMousePoint =
                p
            }

          } else if (
            hit ==
            selectedItemIndex
          ) {

            if (
              !drawItems(
                selectedItemIndex
              ).locked
            ) {

              saveHistory()

              movingSelected =
                true

              resizingSelected =
                false

              previousMousePoint =
                p
            }

          } else {

            selectedItemIndex =
              hit
          }

        } else {

          selectedItemIndex =
            hit
        }

        repaintDrawingCanvas()


      } else if (
        currentTool == "BUCKET"
      ) {

        val hit =
          findItemAt(p)

        if (
          hit >= 0
        ) {

          selectedItemIndex =
            hit

          bucketFillSelected()

        } else {

          setMainStatus(
            "SELECT A CLOSED OBJECT"
          )
        }


      } else if (
        currentTool == "ERASER"
      ) {

        val hit =
          findItemAt(p)

        if (
          hit >= 0 &&
          !drawItems(hit).locked
        ) {

          saveHistory()

          drawItems.remove(hit)

          selectedItemIndex =
            -1

          repaintDrawingCanvas()

          setMainStatus(
            "OBJECT ERASED"
          )
        }


      } else {

        saveHistory()

        temporaryPoints.clear()

        temporaryPoints +=
          p

        drawingActive =
          true

        selectedItemIndex =
          -1

        setMainStatus(
          "DRAWING..."
        )
      }
    }


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

      if (
        currentTool == "SELECT"
      ) {

        movingSelected =
          false

        resizingSelected =
          false

      } else if (
        drawingActive
      ) {

        temporaryPoints +=
          canvasPoint(e)

        if (
          temporaryPoints.length >= 2
        ) {

          drawItems +=
            DrawItem(
              temporaryPoints.toVector,
              currentStrokeColor,
              currentFillColor,
              isClosedShape(
                temporaryPoints
              ),
              "FREEHAND",
              true,
              false
            )

          selectedItemIndex =
            drawItems.length - 1
        }

        temporaryPoints.clear()

        drawingActive =
          false

        repaintDrawingCanvas()

        setMainStatus(
          "DRAWING RECORDED"
        )
      }
    }
  }
)


// ============================================================
// CANVAS DRAG
// ============================================================

drawingCanvas.addMouseMotionListener(
  new MouseMotionAdapter {

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

      val p =
        canvasPoint(e)


      if (
        currentTool == "SELECT"
      ) {

        if (
          movingSelected &&
          selectedItemIndex >= 0
        ) {

          moveSelectedItem(
            p.x - previousMousePoint.x,
            p.y - previousMousePoint.y
          )

          previousMousePoint =
            p

          repaintDrawingCanvas()

        } else if (
          resizingSelected &&
          selectedItemIndex >= 0
        ) {

          resizeSelectedItem(p)

          repaintDrawingCanvas()
        }

      } else if (
        drawingActive
      ) {

        if (
          temporaryPoints.isEmpty ||
          pointDistance(
            temporaryPoints.last,
            p
          ) >= 2
        ) {

          temporaryPoints +=
            p

          repaintDrawingCanvas()
        }
      }
    }
  }
)


// ============================================================
// MAIN BUTTONS
// ============================================================

val pencilButton =
  createButton(
    "PENCIL"
  )

val selectButton =
  createButton(
    "SELECT"
  )

val eraserButton =
  createButton(
    "ERASER"
  )

val bucketButton =
  createButton(
    "BUCKET"
  )

val undoButton =
  createButton(
    "UNDO"
  )

val redoButton =
  createButton(
    "REDO"
  )

val clearButton =
  createButton(
    "CLEAR"
  )

val drawCodeButton =
  createButton(
    "DRAW CODE"
  )

val copyButton =
  createButton(
    "COPY"
  )

val saveButton =
  createButton(
    "SAVE"
  )

val gamesButton =
  createButton(
    "GAMES"
  )

val gridButton =
  createButton(
    "GRID"
  )

val snapButton =
  createButton(
    "SNAP"
  )

val strokeButton =
  createButton(
    "STROKE"
  )

val fillButton =
  createButton(
    "FILL"
  )


// ============================================================
// TOOLBAR
// ============================================================

val toolbar =
  new JPanel(
    new FlowLayout(
      FlowLayout.LEFT,
      3,
      3
    )
  )

toolbar.add(
  pencilButton
)

toolbar.add(
  selectButton
)

toolbar.add(
  eraserButton
)

toolbar.add(
  bucketButton
)

toolbar.add(
  undoButton
)

toolbar.add(
  redoButton
)

toolbar.add(
  clearButton
)

toolbar.add(
  drawCodeButton
)

toolbar.add(
  copyButton
)

toolbar.add(
  saveButton
)

toolbar.add(
  gamesButton
)

toolbar.add(
  gridButton
)

toolbar.add(
  snapButton
)

toolbar.add(
  strokeButton
)

toolbar.add(
  fillButton
)


// ============================================================
// LIBRARY UI
// ============================================================

objectList =
  new JList[String](
    objectModel
  )

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


shapeList =
  new JList[String](
    shapeModel
  )

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


objectSearchField =
  new JTextField(
    12
  )

val objectSearchButton =
  createButton(
    "SEARCH"
  )


val objectSearchPanel =
  new JPanel(
    new FlowLayout(
      FlowLayout.LEFT
    )
  )

objectSearchPanel.add(
  objectSearchField
)

objectSearchPanel.add(
  objectSearchButton
)


val objectPanel =
  new JPanel(
    new BorderLayout()
  )

objectPanel.add(
  new JLabel(
    "OBJECT LIBRARY"
  ),
  BorderLayout.NORTH
)

objectPanel.add(
  objectSearchPanel,
  BorderLayout.CENTER
)

objectPanel.add(
  new JScrollPane(
    objectList
  ),
  BorderLayout.SOUTH
)


val shapePanel =
  new JPanel(
    new BorderLayout()
  )

shapePanel.add(
  new JLabel(
    "SHAPE LIBRARY"
  ),
  BorderLayout.NORTH
)

shapePanel.add(
  new JScrollPane(
    shapeList
  ),
  BorderLayout.CENTER
)


val libraryTabs =
  new JTabbedPane()

libraryTabs.addTab(
  "OBJECTS",
  objectPanel
)

libraryTabs.addTab(
  "SHAPES",
  shapePanel
)


// ============================================================
// SEARCH
// ============================================================

objectSearchButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      val query =
        objectSearchField
          .getText
          .trim
          .toLowerCase

      objectModel.clear()

      var i: Int =
        0

      while (
        i < allObjectNames.length
      ) {

        if (
          query.isEmpty ||
          allObjectNames(i)
            .toLowerCase
            .indexOf(query) >= 0
        ) {

          objectModel.addElement(
            allObjectNames(i)
          )
        }

        i += 1
      }
    }
  }
)


// ============================================================
// OBJECT DOUBLE CLICK
// ============================================================

objectList.addMouseListener(
  new MouseAdapter {

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

      if (
        e.getClickCount == 2
      ) {

        val selected =
          objectList.getSelectedValue

        if (
          selected != null
        ) {

          addObjectToCanvas(
            selected
          )
        }
      }
    }
  }
)


// ============================================================
// SHAPE DOUBLE CLICK
// ============================================================

shapeList.addMouseListener(
  new MouseAdapter {

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

      if (
        e.getClickCount == 2
      ) {

        val selected =
          shapeList.getSelectedValue

        if (
          selected != null
        ) {

          addShapeToCanvas(
            selected
          )
        }
      }
    }
  }
)


// ============================================================
// CODE AREA
// ============================================================

drawingCodeArea =
  new JTextArea()

drawingCodeArea.setEditable(
  false
)

drawingCodeArea.setFont(
  new Font(
    "Monospaced",
    Font.PLAIN,
    13
  )
)

drawingCodeArea.setText(
  "// DRAW SOMETHING\n" +
  "// THEN PRESS DRAW CODE"
)


// ============================================================
// PANELS
// ============================================================

val canvasPanel =
  new JPanel(
    new BorderLayout()
  )

canvasPanel.add(
  new JLabel(
    "DRAWING CANVAS"
  ),
  BorderLayout.NORTH
)

canvasPanel.add(
  drawingCanvas,
  BorderLayout.CENTER
)


val codePanel =
  new JPanel(
    new BorderLayout()
  )

codePanel.add(
  new JLabel(
    "GENERATED KOJO / SCALA CODE"
  ),
  BorderLayout.NORTH
)

codePanel.add(
  new JScrollPane(
    drawingCodeArea
  ),
  BorderLayout.CENTER
)


val canvasCodeSplit =
  new JSplitPane(
    JSplitPane.HORIZONTAL_SPLIT,
    canvasPanel,
    codePanel
  )

canvasCodeSplit.setDividerLocation(
  650
)


val mainSplit =
  new JSplitPane(
    JSplitPane.HORIZONTAL_SPLIT,
    libraryTabs,
    canvasCodeSplit
  )

mainSplit.setDividerLocation(
  280
)


// ============================================================
// STATUS
// ============================================================

mainStatus =
  new JLabel(
    "ULTRA LEGEND X8 READY"
  )

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


// ============================================================
// MAIN FRAME
// ============================================================

mainFrame =
  new JFrame(
    "ULTRA LEGEND DRAW + GAME STUDIO X8"
  )

mainFrame.setDefaultCloseOperation(
  WindowConstants.EXIT_ON_CLOSE
)

mainFrame.setSize(
  1650,
  900
)

mainFrame.setMinimumSize(
  new Dimension(
    1100,
    650
  )
)

mainFrame.setLocationRelativeTo(
  null
)

mainFrame.setLayout(
  new BorderLayout(
    4,
    4
  )
)

mainFrame.add(
  toolbar,
  BorderLayout.NORTH
)

mainFrame.add(
  mainSplit,
  BorderLayout.CENTER
)

mainFrame.add(
  mainStatus,
  BorderLayout.SOUTH
)


// ============================================================
// TOOL EVENTS
// ============================================================

pencilButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      currentTool =
        "PENCIL"

      selectedItemIndex =
        -1

      setMainStatus(
        "PENCIL MODE"
      )

      drawingCanvas.requestFocusInWindow()
    }
  }
)


selectButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      currentTool =
        "SELECT"

      setMainStatus(
        "SELECT / MOVE / RESIZE"
      )

      repaintDrawingCanvas()

      drawingCanvas.requestFocusInWindow()
    }
  }
)


eraserButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      currentTool =
        "ERASER"

      selectedItemIndex =
        -1

      setMainStatus(
        "ERASER MODE"
      )

      drawingCanvas.requestFocusInWindow()
    }
  }
)


bucketButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      currentTool =
        "BUCKET"

      setMainStatus(
        "BUCKET MODE"
      )

      drawingCanvas.requestFocusInWindow()
    }
  }
)


undoButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      performUndo()
    }
  }
)


redoButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      performRedo()
    }
  }
)


clearButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      clearDrawing()
    }
  }
)


drawCodeButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      generateDrawingCode()
    }
  }
)


copyButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      copyGeneratedText(
        drawingCodeArea.getText
      )
    }
  }
)


saveButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      saveGeneratedText(
        drawingCodeArea.getText
      )
    }
  }
)


gamesButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      openGameStudio()
    }
  }
)


gridButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      gridEnabled =
        !gridEnabled

      repaintDrawingCanvas()

      setMainStatus(
        if (
          gridEnabled
        )
          "GRID ON"
        else
          "GRID OFF"
      )
    }
  }
)


snapButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      snapEnabled =
        !snapEnabled

      setMainStatus(
        if (
          snapEnabled
        )
          "SNAP ON"
        else
          "SNAP OFF"
      )
    }
  }
)


strokeButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      val chosen =
        JColorChooser.showDialog(
          mainFrame,
          "CHOOSE STROKE COLOR",
          currentStrokeColor
        )

      if (
        chosen != null
      ) {

        currentStrokeColor =
          chosen

        setMainStatus(
          "STROKE COLOR CHANGED"
        )
      }
    }
  }
)


fillButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      val chosen =
        JColorChooser.showDialog(
          mainFrame,
          "CHOOSE FILL COLOR",
          currentFillColor
        )

      if (
        chosen != null
      ) {

        currentFillColor =
          chosen

        if (
          selectedItemIndex >= 0
        ) {

          bucketFillSelected()

        } else {

          setMainStatus(
            "FILL COLOR CHANGED"
          )
        }
      }
    }
  }
)


// ============================================================
// SHORTCUTS
// ============================================================

drawingCanvas.addKeyListener(
  new java.awt.event.KeyAdapter {

    override def keyPressed(
      e: java.awt.event.KeyEvent
    ): Unit = {

      val key =
        e.getKeyCode


      if (
        e.isControlDown &&
        key ==
        java.awt.event.KeyEvent.VK_Z
      ) {

        performUndo()

      } else if (
        e.isControlDown &&
        key ==
        java.awt.event.KeyEvent.VK_Y
      ) {

        performRedo()

      } else if (
        e.isControlDown &&
        key ==
        java.awt.event.KeyEvent.VK_G
      ) {

        generateDrawingCode()

      } else if (
        e.isControlDown &&
        key ==
        java.awt.event.KeyEvent.VK_C
      ) {

        copyGeneratedText(
          drawingCodeArea.getText
        )

      } else if (
        e.isControlDown &&
        key ==
        java.awt.event.KeyEvent.VK_S
      ) {

        saveGeneratedText(
          drawingCodeArea.getText
        )

      } else if (
        e.isControlDown &&
        key ==
        java.awt.event.KeyEvent.VK_D
      ) {

        openGameStudio()

      } else if (
        key ==
        java.awt.event.KeyEvent.VK_DELETE
      ) {

        if (
          selectedItemIndex >= 0 &&
          selectedItemIndex < drawItems.length
        ) {

          saveHistory()

          drawItems.remove(
            selectedItemIndex
          )

          selectedItemIndex =
            -1

          repaintDrawingCanvas()

          setMainStatus(
            "OBJECT DELETED"
          )
        }

      } else if (
        key ==
        java.awt.event.KeyEvent.VK_P
      ) {

        currentTool =
          "PENCIL"

      } else if (
        key ==
        java.awt.event.KeyEvent.VK_V
      ) {

        currentTool =
          "SELECT"

      } else if (
        key ==
        java.awt.event.KeyEvent.VK_B
      ) {

        currentTool =
          "BUCKET"

      } else if (
        key ==
        java.awt.event.KeyEvent.VK_E
      ) {

        currentTool =
          "ERASER"

      } else if (
        key ==
        java.awt.event.KeyEvent.VK_F1
      ) {

        JOptionPane.showMessageDialog(
          mainFrame,
          "P = PENCIL\n" +
          "V = SELECT\n" +
          "B = BUCKET\n" +
          "E = ERASER\n" +
          "DELETE = DELETE\n\n" +
          "CTRL+Z = UNDO\n" +
          "CTRL+Y = REDO\n" +
          "CTRL+G = DRAW CODE\n" +
          "CTRL+C = COPY\n" +
          "CTRL+S = SAVE\n" +
          "CTRL+D = GAME STUDIO",
          "ULTRA SHORTCUTS",
          JOptionPane.INFORMATION_MESSAGE
        )
      }
    }
  }
)


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

SwingUtilities.invokeLater(
  new Runnable {

    override def run(): Unit = {

      mainFrame.setVisible(
        true
      )

      drawingCanvas.requestFocusInWindow()

      setMainStatus(
        "ULTRA LEGEND X8 READY"
      )
    }
  }
)
//