Code Sketch


bhavesh
By: Mhalsakant School
Category: Programming
import scala.collection.mutable.ArrayBuffer

cleari()
originBottomLeft()

// ============================================================
// OPERATION SINDOOR
// ULTRA SAINIK COMMAND EDITION
// KOJO 2.9.x CANVASDRAW
// ============================================================

// ============================================================
// GAME STATES
// ============================================================
//
// -1 = PASSWORD
// -2 = AVATAR SELECT
//  0 = BRIEFING
//  1 = SAINIK DEPLOYMENT
//  2 = ACTIVE DEFENSE
//  3 = BOSS BATTLE
//  4 = FINAL STRIKE
//  5 = VICTORY
// ============================================================

var currentGameState = -1

// ============================================================
// ENGINE SETTINGS
// ============================================================

val ENGINE_VERSION = "V13.0-SA INIK COMMAND"
val MAX_STARS = 420
val MAX_PARTICLES = 1000
val MAX_ENEMIES = 70
val MAX_SHOCKWAVES = 60
val MAX_SPARKS = 450

// ============================================================
// PASSWORD
// ============================================================

val missionPassword = "bhavesh"

var passwordUnlocked = false
var passwordInput = ""
var passwordErrorTimer = 0.0

var keyBLast = false
var keyHLast = false
var keyALast = false
var keyVLast = false
var keyELast = false
var keySLast = false

// ============================================================
// AVATAR SELECTION
// ============================================================

var selectedAvatar = 1
var avatarHover = 0

val avatarNames = Array(
  "NEON COMMANDER",
  "IRON DEFENDER",
  "SHADOW SCOUT",
  "QUANTUM SPECIALIST"
)

val avatarRoles = Array(
  "TACTICAL COMMAND",
  "HEAVY DEFENSE",
  "RAPID RESPONSE",
  "ENERGY SYSTEMS"
)

var avatarAnimClock = 0.0
var avatarButtonClock = 0.0

// ============================================================
// GLOBAL CLOCKS
// ============================================================

var universalClock = 0.0
var globalElapsedTime = 0.0

var briefingIndex = 0
var briefingTimer = 0.0

var combatTimer = 100.0
var scoreDestroyedCount = 0

val quotaRequirement = 35

// ============================================================
// COMMANDER
// ============================================================

var commanderHP = 100.0

var commanderX = cwidth / 2.0
var commanderY = 145.0

var commanderMarch = 0.0
var commanderPulse = 0.0

// ============================================================
// TANK
// ============================================================

var tankX = cwidth / 2.0 + 90.0
val tankY = 32.0

var turretAngle = math.Pi / 2.0

var tankRecoil = 0.0
var tankHeat = 0.0
var tankEngine = 0.0

var cannonFlash = 0
var cannonCooldown = 0

// ============================================================
// SILO
// ============================================================

var siloX = cwidth / 2.0 - 260.0
var siloY = 145.0

var siloAngle = math.Pi / 2.0

var missileType = 1
var missileCooldown = 0

// ============================================================
// AIR DEFENSE
// ============================================================

var akashX = cwidth / 2.0 - 90.0
var akashY = 110.0

var pinakaX = cwidth / 2.0 + 200.0
var pinakaY = 120.0

var akashAngle = 0.0
var pinakaPulse = 0.0
var pinakaCooldown = 0

// ============================================================
// BOSS
// ============================================================

var bossActive = false

var bossX = cwidth / 2.0
var bossY = cheight - 145.0

var bossHP = 800.0
val bossMaxHP = 800.0

var bossShield = 100.0

var bossPhase = 1
var bossMovement = 0.0
var bossWarning = 0.0
var bossCore = 0.0

// ============================================================
// FINAL STRIKE
// ============================================================

var strikeX = 0.0
var strikeY = 0.0

var strikeAngle = math.Pi / 2.0
var strikeSpeed = 9.0

// ============================================================
// CAMERA FX
// ============================================================

var screenShake = 0.0
var colorFlash = 0.0
var redAlert = 0.0
var cinematicBars = 0.0

// ============================================================
// SAINIK SQUAD
// ============================================================

val squadCount = 4

var squadDeploy = 0.0
var squadMarch = 0.0
var squadBreath = 0.0
var squadSalute = 0.0
var squadScan = 0.0
var squadDrone = 0.0

// ============================================================
// BRIEFING
// ============================================================

val briefingText =
  "TOP SECRET DIRECTIVE: OPERATION SINDOOR\n" +
  "DEFENSE STRATEGIC COMMAND NETWORK\n\n" +
  "MULTIPLE HOSTILE SIGNALS DETECTED.\n" +
  "PERIMETER STATUS: CRITICAL.\n\n" +
  "SAINIK COMMAND SQUAD: READY\n" +
  "MAIN BATTLE TANK: READY\n" +
  "AKASH AIR DEFENSE: ONLINE\n" +
  "PINAKA ROCKET GRID: ONLINE\n" +
  "COMMAND DRONE: ONLINE\n" +
  "QUANTUM LINK: STABLE\n\n" +
  "OBJECTIVES:\n" +
  "DEFEND THE SECTOR.\n" +
  "NEUTRALIZE HOSTILE WAVES.\n" +
  "DEFEAT THE FLAGSHIP.\n\n" +
  "INITIALIZE HYPER DEFENSE."

// ============================================================
// AUDIO
// ============================================================

var audioEnabled = false

try {
  setNoteInstrument(
    Instrument.SYNTH_STRINGS
  )
  audioEnabled = true
} catch {
  case _: Exception =>
    audioEnabled = false
}

def playFX(
  note: Int,
  duration: Int
): Unit = {

  if (audioEnabled) {
    try {
      playNote(
        note,
        duration
      )
    } catch {
      case _: Exception =>
    }
  }
}

// ============================================================
// STARS
// ============================================================

val starX =
  Array.fill(
    MAX_STARS
  )(
    randomDouble(
      0.0,
      cwidth
    )
  )

val starY =
  Array.fill(
    MAX_STARS
  )(
    randomDouble(
      60.0,
      cheight
    )
  )

val starSize =
  Array.fill(
    MAX_STARS
  )(
    randomDouble(
      0.5,
      3.5
    )
  )

val starSpeed =
  Array.fill(
    MAX_STARS
  )(
    randomDouble(
      0.5,
      4.0
    )
  )

// ============================================================
// PARTICLES
// ============================================================

case class Particle(
  var x: Double,
  var y: Double,
  var vx: Double,
  var vy: Double,
  var life: Double,
  val r: Int,
  val g: Int,
  val b: Int,
  var size: Double
)

val particles =
  ArrayBuffer.empty[Particle]

def spawnParticles(
  x: Double,
  y: Double,
  r: Int,
  g: Int,
  b: Int,
  count: Int
): Unit = {

  var i = 0

  while (
    i < count &&
    particles.length <
      MAX_PARTICLES
  ) {

    particles.append(
      Particle(
        x,
        y,
        randomDouble(
          -7.0,
          7.0
        ),
        randomDouble(
          -7.0,
          7.0
        ),
        1.0,
        r,
        g,
        b,
        randomDouble(
          2.0,
          8.0
        )
      )
    )

    i += 1
  }
}

def updateParticles(): Unit = {

  particles.filterInPlace(
    _.life > 0.0
  )

  particles.foreach { p =>

    p.x += p.vx
    p.y += p.vy

    p.vx *= 0.96
    p.vy *= 0.96

    p.life -= 0.025
    p.size *= 0.985
  }
}

def drawParticles(
  c: CanvasDraw
): Unit = {

  particles.foreach { p =>

    val alpha =
      math.max(
        0,
        math.min(
          255,
          (
            p.life *
            255.0
          ).toInt
        )
      )

    c.fill(
      p.r,
      p.g,
      p.b,
      alpha
    )

    c.noStroke()

    c.ellipse(
      p.x,
      p.y,
      p.size,
      p.size
    )
  }
}

// ============================================================
// SHOCKWAVES
// ============================================================

class Shockwave(
  val x: Double,
  val y: Double,
  val kind: Int
) {

  var radius = 5.0
  var life = 255.0

  def update(): Unit = {
    radius += 11.0
    life -= 14.0
  }

  def draw(
    c: CanvasDraw
  ): Unit = {

    if (life > 0.0) {

      c.noFill()

      if (kind == 0) {
        c.stroke(
          0,
          255,
          255,
          life.toInt
        )
      } else if (kind == 1) {
        c.stroke(
          255,
          180,
          50,
          life.toInt
        )
      } else {
        c.stroke(
          255,
          50,
          50,
          life.toInt
        )
      }

      c.strokeWeight(
        3.0
      )

      c.ellipse(
        x,
        y,
        radius * 2.0,
        radius * 2.0
      )
    }
  }
}

val shockwaves =
  ArrayBuffer.empty[Shockwave]

// ============================================================
// SHELL
// ============================================================

class Shell(
  var x: Double,
  var y: Double,
  val vx: Double,
  val vy: Double
) {

  var active = true

  val trail =
    ArrayBuffer.empty[
      (Double, Double)
    ]

  def update(): Unit = {

    x += vx
    y += vy

    trail.append(
      (x, y)
    )

    if (
      trail.length > 12
    ) {
      trail.remove(0)
    }

    if (
      x < -100.0 ||
      x > cwidth + 100.0 ||
      y < -100.0 ||
      y > cheight + 100.0
    ) {
      active = false
    }
  }

  def draw(
    c: CanvasDraw
  ): Unit = {

    var i = 0

    while (
      i < trail.length
    ) {

      val p = trail(i)

      c.fill(
        255,
        150,
        30,
        (
          i.toDouble /
          trail.length *
          180.0
        ).toInt
      )

      c.noStroke()

      c.ellipse(
        p._1,
        p._2,
        4.0,
        4.0
      )

      i += 1
    }

    c.fill(
      255,
      245,
      170
    )

    c.ellipse(
      x,
      y,
      12.0,
      12.0
    )
  }
}

val shells =
  ArrayBuffer.empty[Shell]

// ============================================================
// ENEMY
// ============================================================

class Enemy(
  var x: Double,
  var y: Double,
  val speed: Double,
  val pattern: Int
) {

  var active = true
  var destroyed = false
  var angle = 0.0

  val trail =
    ArrayBuffer.empty[
      (Double, Double)
    ]

  def update(): Unit = {

    if (!destroyed) {

      y += speed
      angle += 0.07

      if (pattern == 1) {
        x +=
          math.sin(
            universalClock * 4.0 +
            y * 0.02
          ) * 3.0
      }

      if (pattern == 2) {
        x +=
          math.sin(
            universalClock * 6.0 +
            y * 0.02
          ) * 5.0
      }

      if (pattern == 3) {
        x +=
          math.cos(
            universalClock * 7.0 +
            y * 0.025
          ) * 7.0
      }

      if (pattern == 4) {
        x +=
          math.sin(
            universalClock * 10.0 +
            y * 0.04
          ) * 9.0
      }

      trail.append(
        (x, y)
      )

      if (
        trail.length >
        22
      ) {
        trail.remove(0)
      }

      if (
        y < -100.0
      ) {
        active = false
      }
    }
  }

  def draw(
    c: CanvasDraw
  ): Unit = {

    if (!destroyed) {

      var i = 0

      while (
        i < trail.length
      ) {

        val p = trail(i)

        c.fill(
          255,
          40,
          60,
          (
            i.toDouble /
            trail.length *
            170.0
          ).toInt
        )

        c.noStroke()

        c.ellipse(
          p._1,
          p._2,
          4.0,
          4.0
        )

        i += 1
      }

      c.pushMatrix()

      c.translate(
        x,
        y
      )

      c.rotate(
        math.sin(angle) *
        0.18
      )

      c.fill(
        70,
        15,
        20
      )

      c.stroke(
        170,
        25,
        35
      )

      c.strokeWeight(
        2.0
      )

      c.rect(
        -28.0,
        -8.0,
        56.0,
        16.0
      )

      c.fill(
        210,
        40,
        45
      )

      c.rect(
        -17.0,
        -14.0,
        34.0,
        28.0
      )

      c.fill(
        255,
        170,
        40
      )

      c.noStroke()

      c.ellipse(
        22.0,
        0.0,
        14.0,
        14.0
      )

      c.popMatrix()
    }
  }
}

val enemies =
  ArrayBuffer.empty[Enemy]

// ============================================================
// MISSILE
// ============================================================

class Missile(
  var x: Double,
  var y: Double,
  val speed: Double,
  var angle: Double,
  val kind: Int
) {

  var active = true

  val trail =
    ArrayBuffer.empty[
      (Double, Double)
    ]

  def update(): Unit = {

    if (kind == 2) {
      angle += 0.025
    }

    if (kind == 3) {
      angle +=
        math.sin(
          universalClock * 8.0
        ) * 0.05
    }

    x +=
      math.cos(angle) *
      speed

    y +=
      math.sin(angle) *
      speed

    trail.append(
      (x, y)
    )

    if (
      trail.length > 24
    ) {
      trail.remove(0)
    }

    if (
      x < -150.0 ||
      x > cwidth + 150.0 ||
      y < -150.0 ||
      y > cheight + 150.0
    ) {
      active = false
    }

    spawnParticles(
      x -
        math.cos(angle) *
        18.0,
      y -
        math.sin(angle) *
        18.0,
      50,
      210,
      255,
      1
    )
  }

  def draw(
    c: CanvasDraw
  ): Unit = {

    var i = 0

    while (
      i < trail.length
    ) {

      val p = trail(i)

      if (kind == 1) {
        c.fill(
          0,
          255,
          230,
          (
            i.toDouble /
            trail.length *
            210.0
          ).toInt
        )
      } else if (kind == 2) {
        c.fill(
          255,
          170,
          40,
          (
            i.toDouble /
            trail.length *
            210.0
          ).toInt
        )
      } else {
        c.fill(
          255,
          70,
          40,
          (
            i.toDouble /
            trail.length *
            210.0
          ).toInt
        )
      }

      c.noStroke()

      c.ellipse(
        p._1,
        p._2,
        5.0,
        5.0
      )

      i += 1
    }

    c.pushMatrix()

    c.translate(
      x,
      y
    )

    c.rotate(
      angle
    )

    c.fill(
      235,
      250,
      255
    )

    c.stroke(
      0,
      110,
      150
    )

    c.strokeWeight(
      1.2
    )

    c.rect(
      -25.0,
      -7.0,
      50.0,
      14.0
    )

    c.fill(
      0,
      245,
      255
    )

    c.noStroke()

    c.ellipse(
      25.0,
      0.0,
      15.0,
      15.0
    )

    c.popMatrix()
  }
}

val missiles =
  ArrayBuffer.empty[Missile]

// ============================================================
// EXPLOSION
// ============================================================

def explosion(
  x: Double,
  y: Double,
  power: Int
): Unit = {

  shockwaves.append(
    new Shockwave(
      x,
      y,
      1
    )
  )

  if (
    shockwaves.length >
    MAX_SHOCKWAVES
  ) {
    shockwaves.remove(0)
  }

  spawnParticles(
    x,
    y,
    255,
    190,
    50,
    power
  )

  spawnParticles(
    x,
    y,
    255,
    60,
    30,
    power / 2
  )

  screenShake =
    math.max(
      screenShake,
      7.0 +
      power *
      0.15
    )

  colorFlash =
    math.max(
      colorFlash,
      0.25
    )
}

def megaExplosion(
  x: Double,
  y: Double
): Unit = {

  explosion(
    x,
    y,
    40
  )

  shockwaves.append(
    new Shockwave(
      x,
      y,
      0
    )
  )

  shockwaves.append(
    new Shockwave(
      x,
      y,
      2
    )
  )

  spawnParticles(
    x,
    y,
    255,
    255,
    220,
    30
  )

  screenShake =
    18.0

  colorFlash =
    0.65

  playFX(
    42,
    100
  )
}

// ============================================================
// FIRE GUN
// ============================================================

def fireGun(): Unit = {

  if (
    cannonCooldown <=
    0
  ) {

    val mx =
      tankX +
      math.cos(
        turretAngle
      ) *
      94.0

    val my =
      tankY +
      59.0 +
      math.sin(
        turretAngle
      ) *
      94.0

    shells.append(
      new Shell(
        mx,
        my,
        math.cos(
          turretAngle
        ) * 14.0,
        math.sin(
          turretAngle
        ) * 14.0
      )
    )

    cannonCooldown = 8
    cannonFlash = 6
    tankRecoil = 3.5
    tankHeat += 10.0

    spawnParticles(
      mx,
      my,
      255,
      180,
      60,
      8
    )

    screenShake = 4.0

    playFX(
      72,
      40
    )
  }
}

// ============================================================
// MISSILE LAUNCH
// ============================================================

def fireMissile(): Unit = {

  if (
    missileCooldown <=
    0
  ) {

    var speed =
      9.0

    if (
      missileType ==
      2
    ) {
      speed =
        8.0
    }

    if (
      missileType ==
      3
    ) {
      speed =
        11.0
    }

    val mx =
      siloX +
      math.cos(
        siloAngle
      ) *
      65.0

    val my =
      siloY +
      math.sin(
        siloAngle
      ) *
      65.0

    missiles.append(
      new Missile(
        mx,
        my,
        speed,
        siloAngle,
        missileType
      )
    )

    missileCooldown = 12

    spawnParticles(
      mx,
      my,
      255,
      140,
      40,
      12
    )

    screenShake = 3.0

    playFX(
      68,
      50
    )
  }
}

// ============================================================
// PASSWORD INPUT
// ============================================================

def passwordChar(
  ch: Char
): Unit = {

  if (
    passwordUnlocked
  ) {
    return
  }

  if (
    passwordInput.length <
    missionPassword.length
  ) {

    passwordInput +=
      ch
  }

  if (
    passwordInput ==
    missionPassword
  ) {

    passwordUnlocked =
      true

    passwordInput =
      ""

    currentGameState =
      -2

    playFX(
      84,
      180
    )
  }

  if (
    passwordInput.length >=
    missionPassword.length &&
    passwordInput !=
    missionPassword
  ) {

    passwordInput =
      ""

    passwordErrorTimer =
      1.8

    playFX(
      40,
      120
    )
  }
}

def updatePassword(): Unit = {

  if (
    passwordUnlocked
  ) {
    return
  }

  if (
    passwordErrorTimer >
    0.0
  ) {

    passwordErrorTimer -=
      0.04
  }

  val b =
    isKeyPressed(
      Kc.VK_B
    )

  val h =
    isKeyPressed(
      Kc.VK_H
    )

  val a =
    isKeyPressed(
      Kc.VK_A
    )

  val v =
    isKeyPressed(
      Kc.VK_V
    )

  val e =
    isKeyPressed(
      Kc.VK_E
    )

  val s =
    isKeyPressed(
      Kc.VK_S
    )

  if (
    b &&
    !keyBLast
  ) {
    passwordChar('b')
  }

  if (
    h &&
    !keyHLast
  ) {
    passwordChar('h')
  }

  if (
    a &&
    !keyALast
  ) {
    passwordChar('a')
  }

  if (
    v &&
    !keyVLast
  ) {
    passwordChar('v')
  }

  if (
    e &&
    !keyELast
  ) {
    passwordChar('e')
  }

  if (
    s &&
    !keySLast
  ) {
    passwordChar('s')
  }

  keyBLast = b
  keyHLast = h
  keyALast = a
  keyVLast = v
  keyELast = e
  keySLast = s
}

// ============================================================
// MOUSE CONTROLS
// ============================================================

def handleMouseControls(): Unit = {

  if (
    currentGameState !=
      2 &&
    currentGameState !=
      3
  ) {
    return
  }

  if (
    !isMousePressed
  ) {
    return
  }

  val mx =
    mouseX

  val my =
    mouseY

  if (
    mx >= 20.0 &&
    mx <= 70.0 &&
    my >= 90.0 &&
    my <= 125.0
  ) {

    tankX -=
      6.0

  } else if (
    mx >= 75.0 &&
    mx <= 125.0 &&
    my >= 90.0 &&
    my <= 125.0
  ) {

    tankX +=
      6.0

  } else if (
    mx >= 130.0 &&
    mx <= 180.0 &&
    my >= 90.0 &&
    my <= 125.0
  ) {

    turretAngle -=
      0.06

  } else if (
    mx >= 185.0 &&
    mx <= 235.0 &&
    my >= 90.0 &&
    my <= 125.0
  ) {

    turretAngle +=
      0.06

  } else if (
    mx >= 240.0 &&
    mx <= 315.0 &&
    my >= 90.0 &&
    my <= 125.0
  ) {

    fireGun()

  } else if (
    mx >= 320.0 &&
    mx <= 365.0 &&
    my >= 90.0 &&
    my <= 125.0
  ) {

    siloAngle -=
      0.06

  } else if (
    mx >= 370.0 &&
    mx <= 415.0 &&
    my >= 90.0 &&
    my <= 125.0
  ) {

    siloAngle +=
      0.06

  } else if (
    mx >= 420.0 &&
    mx <= 465.0 &&
    my >= 90.0 &&
    my <= 125.0
  ) {

    missileType = 1

  } else if (
    mx >= 470.0 &&
    mx <= 515.0 &&
    my >= 90.0 &&
    my <= 125.0
  ) {

    missileType = 2

  } else if (
    mx >= 520.0 &&
    mx <= 565.0 &&
    my >= 90.0 &&
    my <= 125.0
  ) {

    missileType = 3

  } else if (
    mx >= 570.0 &&
    mx <= 700.0 &&
    my >= 90.0 &&
    my <= 125.0
  ) {

    fireMissile()
  }
}

// ============================================================
// KEYBOARD CONTROLS
// ============================================================

def handleKeyboard(): Unit = {

  if (
    currentGameState !=
      2 &&
    currentGameState !=
      3
  ) {
    return
  }

  if (
    isKeyPressed(
      Kc.VK_LEFT
    )
  ) {

    tankX -=
      3.5
  }

  if (
    isKeyPressed(
      Kc.VK_RIGHT
    )
  ) {

    tankX +=
      3.5
  }

  if (
    isKeyPressed(
      Kc.VK_UP
    )
  ) {

    turretAngle +=
      0.045
  }

  if (
    isKeyPressed(
      Kc.VK_DOWN
    )
  ) {

    turretAngle -=
      0.045
  }

  if (
    isKeyPressed(
      Kc.VK_SPACE
    )
  ) {

    fireGun()
  }

  if (
    isKeyPressed(
      Kc.VK_1
    )
  ) {

    missileType = 1
  }

  if (
    isKeyPressed(
      Kc.VK_2
    )
  ) {

    missileType = 2
  }

  if (
    isKeyPressed(
      Kc.VK_3
    )
  ) {

    missileType = 3
  }

  if (
    isKeyPressed(
      Kc.VK_ENTER
    )
  ) {

    fireMissile()
  }
}

// ============================================================
// COLLISIONS
// ============================================================

def checkCollisions(): Unit = {

  repeatFor(
    enemies
  ) { enemy =>

    if (
      !enemy.destroyed
    ) {

      repeatFor(
        shells
      ) { shell =>

        if (
          shell.active &&
          math.abs(
            shell.x -
            enemy.x
          ) < 40.0 &&
          math.abs(
            shell.y -
            enemy.y
          ) < 40.0
        ) {

          shell.active =
            false

          enemy.destroyed =
            true

          scoreDestroyedCount +=
            1

          explosion(
            enemy.x,
            enemy.y,
            25
          )
        }
      }

      repeatFor(
        missiles
      ) { missile =>

        if (
          missile.active &&
          math.abs(
            missile.x -
            enemy.x
          ) < 46.0 &&
          math.abs(
            missile.y -
            enemy.y
          ) < 46.0
        ) {

          missile.active =
            false

          enemy.destroyed =
            true

          scoreDestroyedCount +=
            1

          explosion(
            enemy.x,
            enemy.y,
            30
          )
        }
      }
    }
  }

  if (
    bossActive
  ) {

    repeatFor(
      shells
    ) { shell =>

      if (
        shell.active &&
        math.abs(
          shell.x -
          bossX
        ) < 75.0 &&
        math.abs(
          shell.y -
          bossY
        ) < 65.0
      ) {

        shell.active =
          false

        if (
          bossShield >
          0.0
        ) {

          bossShield -=
            8.0

        } else {

          bossHP -=
            18.0
        }

        explosion(
          shell.x,
          shell.y,
          28
        )
      }
    }

    repeatFor(
      missiles
    ) { missile =>

      if (
        missile.active &&
        math.abs(
          missile.x -
          bossX
        ) < 78.0 &&
        math.abs(
          missile.y -
          bossY
        ) < 68.0
      ) {

        missile.active =
          false

        if (
          bossShield >
          0.0
        ) {

          bossShield -=
            18.0

        } else {

          bossHP -=
            38.0
        }

        explosion(
          missile.x,
          missile.y,
          32
        )
      }
    }

    if (
      bossShield <
      0.0
    ) {
      bossShield =
        0.0
    }
  }
}

// ============================================================
// UPDATE OBJECTS
// ============================================================

def updateObjects(): Unit = {

  shells.filterInPlace(
    _.active
  )

  repeatFor(
    shells
  )(
    _.update()
  )

  missiles.filterInPlace(
    _.active
  )

  repeatFor(
    missiles
  )(
    _.update()
  )

  enemies.filterInPlace(
    _.active
  )

  repeatFor(
    enemies
  )(
    _.update()
  )

  shockwaves.filterInPlace(
    _.life > 0.0
  )

  repeatFor(
    shockwaves
  )(
    _.update()
  )
}

// ============================================================
// SPAWN ENEMY
// ============================================================

def spawnEnemy(): Unit = {

  if (
    enemies.length >=
    MAX_ENEMIES
  ) {
    return
  }

  val x =
    randomDouble(
      80.0,
      cwidth - 80.0
    )

  val pattern =
    random(
      1,
      5
    )

  val speed =
    -(
      0.7 +
      randomDouble(
        0.0,
        0.65
      )
    )

  enemies.append(
    new Enemy(
      x,
      cheight + 40.0,
      speed,
      pattern
    )
  )

  spawnParticles(
    x,
    cheight -
      10.0,
    255,
    50,
    50,
    7
  )
}

// ============================================================
// START BOSS
// ============================================================

def startBoss(): Unit = {

  bossActive =
    true

  bossHP =
    bossMaxHP

  bossShield =
    100.0

  bossPhase =
    1

  bossWarning =
    4.0

  bossMovement =
    0.0

  currentGameState =
    3

  redAlert =
    1.0

  screenShake =
    10.0

  cinematicBars =
    8.0

  playFX(
    42,
    220
  )
}

// ============================================================
// SAINIK AVATAR
// ============================================================

def drawAvatar(
  c: CanvasDraw,
  x: Double,
  y: Double,
  avatarType: Int,
  scale: Double,
  selected: Boolean,
  salute: Boolean
): Unit = {

  c.pushMatrix()

  c.translate(
    x,
    y
  )

  c.scale(
    scale,
    scale
  )

  val breath =
    math.sin(
      avatarAnimClock *
      2.0
    ) * 1.5

  val walk =
    math.sin(
      squadMarch
    ) * 4.0

  if (
    selected
  ) {

    c.noFill()

    c.stroke(
      0,
      255,
      255,
      160
    )

    c.strokeWeight(
      2.5
    )

    c.ellipse(
      0.0,
      30.0,
      105.0 +
      math.sin(
        avatarAnimClock *
        3.0
      ) * 8.0,
      145.0
    )
  }

  c.fill(
    0,
    0,
    0,
    110
  )

  c.noStroke()

  c.ellipse(
    0.0,
    -35.0,
    60.0,
    12.0
  )

  // Legs
  c.stroke(
    35,
    45,
    52
  )

  c.strokeWeight(
    7.0
  )

  c.line(
    -9.0,
    0.0,
    -14.0 + walk,
    -31.0
  )

  c.line(
    9.0,
    0.0,
    14.0 - walk,
    -31.0
  )

  // Boots
  c.stroke(
    15,
    20,
    25
  )

  c.strokeWeight(
    8.0
  )

  c.line(
    -14.0 + walk,
    -31.0,
    -22.0 + walk,
    -36.0
  )

  c.line(
    14.0 - walk,
    -31.0,
    22.0 - walk,
    -36.0
  )

  // Body
  if (
    avatarType ==
    1
  ) {

    c.fill(
      45,
      75,
      85
    )

    c.stroke(
      0,
      220,
      255
    )

  } else if (
    avatarType ==
    2
  ) {

    c.fill(
      72,
      67,
      58
    )

    c.stroke(
      255,
      165,
      50
    )

  } else if (
    avatarType ==
    3
  ) {

    c.fill(
      35,
      40,
      60
    )

    c.stroke(
      175,
      80,
      255
    )

  } else {

    c.fill(
      35,
      68,
      58
    )

    c.stroke(
      0,
      255,
      180
    )
  }

  c.strokeWeight(
    2.0
  )

  c.rect(
    -20.0,
    0.0 + breath,
    40.0,
    43.0
  )

  // Chest core
  if (
    avatarType ==
    1
  ) {

    c.fill(
      0,
      220,
      255
    )

  } else if (
    avatarType ==
    2
  ) {

    c.fill(
      255,
      100,
      30
    )

  } else if (
    avatarType ==
    3
  ) {

    c.fill(
      185,
      75,
      255
    )

  } else {

    c.fill(
      0,
      255,
      180
    )
  }

  c.noStroke()

  c.ellipse(
    0.0,
    21.0 + breath,
    10.0,
    10.0
  )

  // Arms
  c.stroke(
    55,
    75,
    82
  )

  c.strokeWeight(
    6.0
  )

  if (
    salute
  ) {

    c.line(
      -15.0,
      28.0,
      -22.0,
      46.0
    )

    c.line(
      15.0,
      28.0,
      21.0,
      46.0
    )

  } else {

    c.line(
      -15.0,
      28.0,
      -26.0,
      7.0
    )

    c.line(
      15.0,
      28.0,
      26.0,
      7.0
    )
  }

  // Neck
  c.fill(
    170,
    125,
    85
  )

  c.noStroke()

  c.rect(
    -7.0,
    38.0,
    14.0,
    10.0
  )

  // Helmet
  c.fill(
    52,
    70,
    78
  )

  c.stroke(
    10,
    22,
    28
  )

  c.strokeWeight(
    2.0
  )

  c.ellipse(
    0.0,
    68.0 + breath,
    50.0,
    45.0
  )

  // Visor
  if (
    avatarType ==
    1
  ) {

    c.fill(
      5,
      35,
      48
    )

    c.stroke(
      0,
      235,
      255
    )

  } else if (
    avatarType ==
    2
  ) {

    c.fill(
      75,
      35,
      25
    )

    c.stroke(
      255,
      170,
      50
    )

  } else if (
    avatarType ==
    3
  ) {

    c.fill(
      30,
      15,
      60
    )

    c.stroke(
      190,
      90,
      255
    )

  } else {

    c.fill(
      5,
      45,
      35
    )

    c.stroke(
      0,
      255,
      180
    )
  }

  c.strokeWeight(
    2.5
  )

  c.ellipse(
    0.0,
    66.0 + breath,
    34.0,
    20.0
  )

  // Helmet light
  val lightPulse =
    120 +
    (
      math.abs(
        math.sin(
          avatarAnimClock *
          3.0
        )
      ) *
      120.0
    ).toInt

  if (
    avatarType ==
    3
  ) {

    c.fill(
      190,
      80,
      lightPulse
    )

  } else if (
    avatarType ==
    4
  ) {

    c.fill(
      0,
      lightPulse,
      180
    )

  } else {

    c.fill(
      0,
      lightPulse,
      255
    )
  }

  c.noStroke()

  c.ellipse(
    0.0,
    91.0 + breath,
    6.0,
    6.0
  )

  // Tricolor badge
  c.fill(
    255,
    153,
    51
  )

  c.rect(
    -27.0,
    12.0,
    6.0,
    5.0
  )

  c.fill(
    255,
    255,
    255
  )

  c.rect(
    -27.0,
    17.0,
    6.0,
    5.0
  )

  c.fill(
    18,
    136,
    7
  )

  c.rect(
    -27.0,
    22.0,
    6.0,
    5.0
  )

  c.popMatrix()
}

// ============================================================
// AVATAR SELECTION SCREEN
// ============================================================

def drawAvatarSelectionScreen(
  c: CanvasDraw
): Unit = {

  avatarAnimClock +=
    0.04

  avatarButtonClock +=
    0.05

  c.background(
    2,
    6,
    20
  )

  drawStarfield(
    c
  )

  // Grid
  c.stroke(
    0,
    170,
    220,
    28
  )

  c.strokeWeight(
    1.0
  )

  var gx =
    0.0

  while (
    gx <
    cwidth
  ) {

    c.line(
      gx,
      0.0,
      gx,
      cheight
    )

    gx +=
      45.0
  }

  var gy =
    0.0

  while (
    gy <
    cheight
  ) {

    c.line(
      0.0,
      gy,
      cwidth,
      gy
    )

    gy +=
      45.0
  }

  // Header
  c.fill(
    0,
    255,
    230
  )

  c.text(
    "DEFENSE COMMAND // AVATAR SELECTION",
    cwidth / 2.0 -
    175.0,
    cheight -
    30.0
  )

  c.fill(
    255,
    215,
    60
  )

  c.text(
    "SELECT YOUR SAINIK AVATAR",
    cwidth / 2.0 -
    125.0,
    cheight -
    58.0
  )

  c.fill(
    140,
    190,
    210
  )

  c.text(
    "Choose your specialist before starting the mission.",
    cwidth / 2.0 -
    170.0,
    cheight -
    84.0
  )

  val cardW =
    170.0

  val cardH =
    275.0

  val firstX =
    cwidth / 2.0 -
    365.0

  val cardY =
    cheight / 2.0 -
    145.0

  var i =
    0

  while (
    i < 4
  ) {

    val x =
      firstX +
      i *
      185.0

    val hovered =
      mouseX >= x &&
      mouseX <=
      x + cardW &&
      mouseY >= cardY &&
      mouseY <=
      cardY + cardH

    if (
      hovered
    ) {
      avatarHover =
        i + 1
    }

    val selected =
      selectedAvatar ==
      i + 1

    if (
      selected
    ) {

      c.fill(
        0,
        65,
        85,
        240
      )

      c.stroke(
        0,
        255,
        255
      )

      c.strokeWeight(
        3.5
      )

    } else if (
      hovered
    ) {

      c.fill(
        20,
        45,
        70,
        240
      )

      c.stroke(
        255,
        215,
        70
      )

      c.strokeWeight(
        2.5
      )

    } else {

      c.fill(
        7,
        20,
        40,
        235
      )

      c.stroke(
        55,
        90,
        120
      )

      c.strokeWeight(
        1.5
      )
    }

    c.rect(
      x,
      cardY,
      cardW,
      cardH
    )

    drawAvatar(
      c,
      x + cardW / 2.0,
      cardY + 105.0,
      i + 1,
      0.9,
      selected,
      false
    )

    c.fill(
      255,
      255,
      255
    )

    c.text(
      avatarNames(i),
      x + 19.0,
      cardY + 178.0
    )

    c.fill(
      0,
      245,
      220
    )

    c.text(
      avatarRoles(i),
      x + 26.0,
      cardY + 200.0
    )

    c.fill(
      160,
      190,
      210
    )

    if (
      i == 0
    ) {

      c.text(
        "Balanced Combat",
        x + 24.0,
        cardY + 228.0
      )

      c.text(
        "Tactical AI",
        x + 43.0,
        cardY + 248.0
      )

    } else if (
      i == 1
    ) {

      c.text(
        "Heavy Armour",
        x + 31.0,
        cardY + 228.0
      )

      c.text(
        "High Defense",
        x + 42.0,
        cardY + 248.0
      )

    } else if (
      i == 2
    ) {

      c.text(
        "Rapid Movement",
        x + 28.0,
        cardY + 228.0
      )

      c.text(
        "Recon Expert",
        x + 38.0,
        cardY + 248.0
      )

    } else {

      c.text(
        "Energy Control",
        x + 28.0,
        cardY + 228.0
      )

      c.text(
        "Quantum Systems",
        x + 20.0,
        cardY + 248.0
      )
    }

    if (
      isMousePressed &&
      hovered
    ) {

      selectedAvatar =
        i + 1

      playFX(
        72 +
        i * 4,
        90
      )
    }

    i +=
      1
  }

  // Selected
  c.fill(
    100,
    255,
    225
  )

  c.text(
    "SELECTED: " +
    avatarNames(
      selectedAvatar - 1
    ),
    cwidth / 2.0 -
    112.0,
    104.0
  )

  // Start Mission
  val bx =
    cwidth / 2.0 -
    140.0

  val by =
    42.0

  val pulse =
    3.0 +
    math.abs(
      math.sin(
        avatarButtonClock
      )
    ) *
    3.0

  c.fill(
    0,
    150,
    95
  )

  c.stroke(
    0,
    255,
    180
  )

  c.strokeWeight(
    2.0 + pulse * 0.2
  )

  c.rect(
    bx,
    by,
    280.0,
    48.0
  )

  c.fill(
    255,
    255,
    255
  )

  c.text(
    "START MISSION",
    bx + 83.0,
    by + 30.0
  )

  if (
    isMousePressed &&
    mouseX >= bx &&
    mouseX <= bx + 280.0 &&
    mouseY >= by &&
    mouseY <= by + 48.0
  ) {

    currentGameState =
      0

    briefingIndex =
      0

    briefingTimer =
      0.0

    squadDeploy =
      0.0

    playFX(
      84,
      180
    )
  }
}

// ============================================================
// STAR DRAW
// ============================================================

def drawStarfield(
  c: CanvasDraw
): Unit = {

  var i =
    0

  while (
    i < MAX_STARS
  ) {

    val alpha =
      math.max(
        60,
        math.min(
          255,
          (
            80.0 +
            math.abs(
              math.sin(
                universalClock *
                starSpeed(i) +
                i
              )
            ) *
            175.0
          ).toInt
        )
      )

    var yy =
      starY(i) -
      universalClock *
      starSpeed(i) *
      5.0

    while (
      yy < 50.0
    ) {

      yy +=
        cheight -
        50.0
    }

    c.fill(
      225,
      240,
      255,
      alpha
    )

    c.noStroke()

    c.ellipse(
      starX(i),
      yy,
      starSize(i),
      starSize(i)
    )

    i +=
      1
  }
}

// ============================================================
// BRIEFING
// ============================================================

def drawBriefing(
  c: CanvasDraw
): Unit = {

  c.background(
    1,
    4,
    16
  )

  drawStarfield(
    c
  )

  c.fill(
    8,
    16,
    40,
    250
  )

  c.stroke(
    0,
    255,
    255
  )

  c.strokeWeight(
    3.0
  )

  val bx =
    cwidth / 2.0 -
    315.0

  val by =
    cheight / 2.0 -
    190.0

  c.rect(
    bx,
    by,
    630.0,
    400.0
  )

  c.fill(
    255,
    215,
    60
  )

  c.text(
    "OPERATION SINDOOR // COMMAND BRIEFING",
    bx + 30.0,
    by + 32.0
  )

  c.fill(
    80,
    255,
    225
  )

  c.text(
    ENGINE_VERSION,
    bx + 460.0,
    by + 32.0
  )

  val shown =
    briefingText.substring(
      0,
      briefingIndex
    )

  val lines =
    shown.split(
      "\n"
    )

  var ty =
    by + 68.0

  for (
    line <- lines
  ) {

    c.fill(
      230,
      240,
      255
    )

    c.text(
      line,
      bx + 25.0,
      ty
    )

    ty +=
      19.0
  }

  if (
    briefingIndex >=
    briefingText.length
  ) {

    val sx =
      bx + 170.0

    val sy =
      by - 58.0

    c.fill(
      0,
      220,
      150
    )

    c.stroke(
      255,
      255,
      255
    )

    c.strokeWeight(
      2.0
    )

    c.rect(
      sx,
      sy,
      290.0,
      44.0
    )

    c.fill(
      255,
      255,
      255
    )

    c.text(
      "INITIALIZE MISSION",
      sx + 63.0,
      sy + 28.0
    )

    if (
      isMousePressed &&
      mouseX >= sx &&
      mouseX <=
      sx + 290.0 &&
      mouseY >= sy &&
      mouseY <=
      sy + 44.0
    ) {

      currentGameState =
        1

      squadDeploy =
        0.0

      playFX(
        80,
        150
      )
    }
  }
}

// ============================================================
// PASSWORD SCREEN
// ============================================================

def drawPasswordScreen(
  c: CanvasDraw
): Unit = {

  c.background(
    1,
    3,
    14
  )

  drawStarfield(
    c
  )

  val cx =
    cwidth / 2.0

  val cy =
    cheight / 2.0

  c.fill(
    6,
    18,
    42,
    250
  )

  c.stroke(
    0,
    255,
    255
  )

  c.strokeWeight(
    3.0
  )

  c.rect(
    cx - 275.0,
    cy - 165.0,
    550.0,
    330.0
  )

  c.fill(
    0,
    255,
    230
  )

  c.text(
    "DEFENSE COMMAND SECURITY",
    cx - 132.0,
    cy + 120.0
  )

  c.fill(
    255,
    215,
    60
  )

  c.text(
    "OPERATION SINDOOR",
    cx - 82.0,
    cy + 88.0
  )

  // Lock body
  c.fill(
    25,
    35,
    60
  )

  c.stroke(
    0,
    220,
    255
  )

  c.strokeWeight(
    3.0
  )

  c.rect(
    cx - 42.0,
    cy - 28.0,
    84.0,
    65.0
  )

  c.noFill()

  c.stroke(
    0,
    220,
    255
  )

  c.strokeWeight(
    5.0
  )

  c.line(
    cx - 28.0,
    cy - 28.0,
    cx - 28.0,
    cy - 52.0
  )

  c.line(
    cx - 28.0,
    cy - 52.0,
    cx,
    cy - 68.0
  )

  c.line(
    cx,
    cy - 68.0,
    cx + 28.0,
    cy - 52.0
  )

  c.line(
    cx + 28.0,
    cy - 52.0,
    cx + 28.0,
    cy - 28.0
  )

  c.fill(
    255,
    215,
    60
  )

  c.noStroke()

  c.ellipse(
    cx,
    cy,
    10.0,
    10.0
  )

  c.fill(
    170,
    210,
    230
  )

  c.text(
    "ENTER ACCESS PASSWORD",
    cx - 105.0,
    cy - 88.0
  )

  c.fill(
    4,
    22,
    40
  )

  c.stroke(
    0,
    210,
    255
  )

  c.strokeWeight(
    2.0
  )

  c.rect(
    cx - 135.0,
    cy - 130.0,
    270.0,
    38.0
  )

  c.fill(
    0,
    255,
    180
  )

  c.noStroke()

  val masked =
    passwordInput.map(
      _ =>
        "*"
    ).mkString

  c.text(
    masked,
    cx - 110.0,
    cy - 105.0
  )

  if (
    passwordErrorTimer >
    0.0
  ) {

    c.fill(
      255,
      70,
      70
    )

    c.text(
      "ACCESS DENIED",
      cx - 60.0,
      cy - 145.0
    )

  } else {

    c.fill(
      0,
      220,
      180
    )

    c.text(
      "WAITING FOR AUTHORIZATION",
      cx - 115.0,
      cy - 145.0
    )
  }

  c.fill(
    100,
    150,
    190
  )

  c.text(
    "TYPE PASSWORD ON KEYBOARD",
    cx - 120.0,
    cy + 145.0
  )
}

// ============================================================
// DEPLOYMENT
// ============================================================

def drawDeployment(
  c: CanvasDraw
): Unit = {

  drawCombatBackground(
    c
  )

  val p =
    math.max(
      0.2,
      math.min(
        1.0,
        squadDeploy
      )
    )

  var i =
    0

  while (
    i < squadCount
  ) {

    val offset =
      (
        i -
        1.5
      ) *
      75.0

    val sx =
      cwidth / 2.0 +
      offset

    val sy =
      140.0 +
      math.sin(
        squadMarch +
        i *
        0.8
      ) *
      3.0

    drawAvatar(
      c,
      sx,
      sy,
      selectedAvatar,
      p,
      false,
      true
    )

    i +=
      1
  }

  drawTank(
    c
  )

  drawSilo(
    c
  )

  drawAirDefense(
    c
  )

  c.fill(
    255,
    215,
    60
  )

  c.text(
    "SAINIK SQUAD DEPLOYMENT",
    cwidth / 2.0 -
    115.0,
    cheight - 35.0
  )

  c.fill(
    100,
    255,
    225
  )

  c.text(
    "FORMATION ALPHA // ALL UNITS READY",
    cwidth / 2.0 -
    135.0,
    cheight - 60.0
  )
}

// ============================================================
// COMBAT BACKGROUND
// ============================================================

def drawCombatBackground(
  c: CanvasDraw
): Unit = {

  c.background(
    2,
    5,
    20
  )

  drawStarfield(
    c
  )

  c.fill(
    15,
    50,
    150,
    25
  )

  c.noStroke()

  c.ellipse(
    cwidth / 2.0,
    100.0,
    cwidth + 100.0,
    160.0
  )

  c.stroke(
    0,
    100,
    140,
    35
  )

  c.strokeWeight(
    1.0
  )

  var gy =
    80.0

  while (
    gy <
    260.0
  ) {

    c.line(
      0.0,
      gy,
      cwidth,
      gy
    )

    gy +=
      25.0
  }

  var gx =
    0.0

  while (
    gx <
    cwidth
  ) {

    c.line(
      gx,
      120.0,
      cwidth / 2.0,
      80.0
    )

    gx +=
      50.0
  }
}

// ============================================================
// TANK DRAW
// ============================================================

def drawTank(
  c: CanvasDraw
): Unit = {

  c.fill(
    0,
    0,
    0,
    120
  )

  c.noStroke()

  c.ellipse(
    tankX,
    tankY -
    4.0,
    175.0,
    24.0
  )

  c.fill(
    34,
    39,
    44
  )

  c.stroke(
    12,
    17,
    22
  )

  c.strokeWeight(
    1.5
  )

  c.rect(
    tankX -
    70.0,
    tankY,
    140.0,
    32.0
  )

  c.fill(
    100,
    105,
    101
  )

  for (
    i <- -5 to 5
  ) {

    c.ellipse(
      tankX +
      i *
      13.5,
      tankY +
      16.0,
      11.0,
      11.0
    )
  }

  c.fill(
    68,
    110,
    60
  )

  c.rect(
    tankX -
    55.0,
    tankY +
    30.0,
    110.0,
    30.0
  )

  c.fill(
    58,
    96,
    52
  )

  c.ellipse(
    tankX,
    tankY +
    59.0,
    60.0,
    34.0
  )

  val barrel =
    90.0 +
    tankRecoil *
    4.0

  val tx =
    tankX +
    math.cos(
      turretAngle
    ) *
    barrel

  val ty =
    tankY +
    59.0 +
    math.sin(
      turretAngle
    ) *
    barrel

  c.stroke(
    42,
    72,
    37
  )

  c.strokeWeight(
    10.0 +
    tankRecoil *
    2.0
  )

  c.line(
    tankX,
    tankY +
    59.0,
    tx,
    ty
  )

  c.fill(
    255,
    80,
    20,
    150
  )

  c.noStroke()

  val enginePulse =
    70 +
    (
      math.abs(
        math.sin(
          tankEngine
        )
      ) *
      150.0
    ).toInt

  c.fill(
    255,
    80,
    20,
    enginePulse
  )

  c.ellipse(
    tankX -
    50.0,
    tankY +
    4.0,
    8.0,
    5.0
  )

  c.ellipse(
    tankX +
    50.0,
    tankY +
    4.0,
    8.0,
    5.0
  )

  if (
    cannonFlash >
    0
  ) {

    c.fill(
      255,
      150,
      40,
      220
    )

    c.ellipse(
      tx,
      ty,
      50.0,
      50.0
    )

    c.fill(
      255,
      250,
      180,
      240
    )

    c.ellipse(
      tx,
      ty,
      22.0,
      22.0
    )
  }
}

// ============================================================
// SILO DRAW
// ============================================================

def drawSilo(
  c: CanvasDraw
): Unit = {

  c.fill(
    58,
    63,
    73
  )

  c.stroke(
    25,
    30,
    38
  )

  c.strokeWeight(
    2.0
  )

  c.ellipse(
    siloX,
    siloY,
    76.0,
    76.0
  )

  c.pushMatrix()

  c.translate(
    siloX,
    siloY
  )

  c.rotate(
    siloAngle
  )

  c.fill(
    82,
    108,
    94
  )

  c.rect(
    -18.0,
    -21.0,
    80.0,
    42.0
  )

  c.popMatrix()
}

// ============================================================
// AIR DEFENSE DRAW
// ============================================================

def drawAirDefense(
  c: CanvasDraw
): Unit = {

  c.fill(
    45,
    85,
    60
  )

  c.stroke(
    20,
    45,
    30
  )

  c.strokeWeight(
    2.0
  )

  c.rect(
    akashX -
    36.0,
    akashY -
    16.0,
    72.0,
    32.0
  )

  c.pushMatrix()

  c.translate(
    akashX,
    akashY
  )

  c.rotate(
    akashAngle
  )

  c.stroke(
    0,
    255,
    180,
    170
  )

  c.strokeWeight(
    2.0
  )

  c.line(
    0.0,
    0.0,
    25.0,
    0.0
  )

  c.popMatrix()

  c.fill(
    0,
    255,
    180
  )

  c.noStroke()

  c.ellipse(
    akashX,
    akashY,
    15.0,
    15.0
  )

  c.fill(
    255,
    255,
    255
  )

  c.text(
    "AKASH",
    akashX -
    22.0,
    akashY -
    24.0
  )

  c.fill(
    85,
    65,
    43
  )

  c.stroke(
    45,
    30,
    18
  )

  c.strokeWeight(
    2.0
  )

  c.rect(
    pinakaX -
    42.0,
    pinakaY -
    20.0,
    84.0,
    40.0
  )

  c.fill(
    255,
    153,
    51,
    (
      70 +
      (
        math.abs(
          math.sin(
            pinakaPulse
          )
        ) *
        160.0
      )
    ).toInt
  )

  c.noStroke()

  c.ellipse(
    pinakaX -
    17.0,
    pinakaY,
    9.0,
    9.0
  )

  c.ellipse(
    pinakaX,
    pinakaY,
    9.0,
    9.0
  )

  c.ellipse(
    pinakaX +
    17.0,
    pinakaY,
    9.0,
    9.0
  )

  c.fill(
    255,
    255,
    255
  )

  c.text(
    "PINAKA",
    pinakaX -
    26.0,
    pinakaY -
    28.0
  )
}

// ============================================================
// SQUAD DRAW
// ============================================================

def drawSquad(
  c: CanvasDraw
): Unit = {

  var i =
    0

  while (
    i < squadCount
  ) {

    val offset =
      (
        i -
        1.5
      ) *
      75.0

    val x =
      cwidth / 2.0 +
      offset

    val y =
      140.0 +
      math.sin(
        squadMarch +
        i.toDouble
      ) *
      2.5

    drawAvatar(
      c,
      x,
      y,
      selectedAvatar,
      0.9 *
      math.max(
        0.2,
        squadDeploy
      ),
      false,
      squadSalute > 0.5
    )

    i +=
      1
  }
}

// ============================================================
// RADAR
// ============================================================

def drawRadar(
  c: CanvasDraw
): Unit = {

  val x =
    80.0

  val y =
    cheight -
    120.0

  c.fill(
    5,
    20,
    30,
    230
  )

  c.stroke(
    0,
    255,
    160,
    200
  )

  c.strokeWeight(
    2.0
  )

  c.ellipse(
    x,
    y,
    120.0,
    120.0
  )

  c.ellipse(
    x,
    y,
    82.0,
    82.0
  )

  c.ellipse(
    x,
    y,
    42.0,
    42.0
  )

  c.line(
    x - 60.0,
    y,
    x + 60.0,
    y
  )

  c.line(
    x,
    y - 60.0,
    x,
    y + 60.0
  )

  c.stroke(
    0,
    255,
    180
  )

  c.strokeWeight(
    2.5
  )

  c.line(
    x,
    y,
    x +
    math.cos(
      universalClock
    ) *
    56.0,
    y +
    math.sin(
      universalClock
    ) *
    56.0
  )

  repeatFor(
    enemies
  ) { e =>

    if (
      !e.destroyed
    ) {

      val dx =
        math.max(
          -50.0,
          math.min(
            50.0,
            (
              e.x -
              cwidth / 2.0
            ) /
            5.0
          )
        )

      val dy =
        math.max(
          -50.0,
          math.min(
            50.0,
            (
              e.y -
              cheight / 2.0
            ) /
            5.0
          )
        )

      c.fill(
        255,
        50,
        60
      )

      c.noStroke()

      c.ellipse(
        x + dx,
        y + dy,
        5.0,
        5.0
      )
    }
  }
}

// ============================================================
// COMMAND HUD
// ============================================================

def drawHUD(
  c: CanvasDraw
): Unit = {

  c.fill(
    5,
    10,
    30,
    245
  )

  c.stroke(
    0,
    255,
    255,
    190
  )

  c.strokeWeight(
    2.0
  )

  c.rect(
    15.0,
    cheight - 66.0,
    cwidth - 30.0,
    50.0
  )

  c.fill(
    255,
    220,
    70
  )

  c.text(
    "OPERATION SINDOOR",
    28.0,
    cheight - 38.0
  )

  c.fill(
    100,
    255,
    225
  )

  c.text(
    s"TARGETS: $scoreDestroyedCount / $quotaRequirement",
    210.0,
    cheight - 38.0
  )

  c.fill(
    255,
    120,
    110
  )

  c.text(
    s"TIME: ${combatTimer.toInt}s",
    390.0,
    cheight - 38.0
  )

  c.fill(
    30,
    35,
    48
  )

  c.rect(
    500.0,
    cheight - 53.0,
    170.0,
    10.0
  )

  c.fill(
    40,
    220,
    110
  )

  c.rect(
    500.0,
    cheight - 53.0,
    170.0 *
    math.max(
      0.0,
      math.min(
        1.0,
        commanderHP /
        100.0
      )
    ),
    10.0
  )

  c.fill(
    255,
    255,
    255
  )

  c.text(
    "COMMANDER HP",
    545.0,
    cheight - 25.0
  )

  drawRadar(
    c
  )
}

// ============================================================
// CONTROL PANEL
// ============================================================

def drawControls(
  c: CanvasDraw
): Unit = {

  c.fill(
    14,
    20,
    45,
    255
  )

  c.stroke(
    0,
    255,
    255
  )

  c.strokeWeight(
    1.8
  )

  c.rect(
    15.0,
    88.0,
    710.0,
    52.0
  )

  c.fill(
    70,
    75,
    110
  )

  c.rect(
    20.0,
    96.0,
    52.0,
    32.0
  )

  c.rect(
    78.0,
    96.0,
    52.0,
    32.0
  )

  c.fill(
    255,
    255,
    255
  )

  c.text(
    "T-",
    38.0,
    116.0
  )

  c.text(
    "T+",
    96.0,
    116.0
  )

  c.fill(
    100,
    50,
    50
  )

  c.rect(
    136.0,
    96.0,
    52.0,
    32.0
  )

  c.rect(
    194.0,
    96.0,
    52.0,
    32.0
  )

  c.fill(
    255,
    255,
    255
  )

  c.text(
    "C-",
    154.0,
    116.0
  )

  c.text(
    "C+",
    212.0,
    116.0
  )

  c.fill(
    240,
    100,
    0
  )

  c.rect(
    252.0,
    96.0,
    62.0,
    32.0
  )

  c.fill(
    255,
    255,
    255
  )

  c.text(
    "FIRE",
    267.0,
    116.0
  )

  c.fill(
    50,
    120,
    120
  )

  c.rect(
    320.0,
    96.0,
    48.0,
    32.0
  )

  c.rect(
    374.0,
    96.0,
    48.0,
    32.0
  )

  c.fill(
    255,
    255,
    255
  )

  c.text(
    "M-",
    334.0,
    116.0
  )

  c.text(
    "M+",
    388.0,
    116.0
  )

  c.fill(
    if (
      missileType ==
      1
    )
      240
    else
      80,
    35,
    150
  )

  c.rect(
    426.0,
    96.0,
    48.0,
    32.0
  )

  c.fill(
    if (
      missileType ==
      2
    )
      240
    else
      80,
    35,
    150
  )

  c.rect(
    480.0,
    96.0,
    48.0,
    32.0
  )

  c.fill(
    if (
      missileType ==
      3
    )
      240
    else
      80,
    35,
    150
  )

  c.rect(
    534.0,
    96.0,
    48.0,
    32.0
  )

  c.fill(
    255,
    255,
    255
  )

  c.text(
    "A",
    444.0,
    116.0
  )

  c.text(
    "B",
    498.0,
    116.0
  )

  c.text(
    "C",
    552.0,
    116.0
  )

  c.fill(
    200,
    0,
    240
  )

  c.rect(
    592.0,
    96.0,
    120.0,
    32.0
  )

  c.fill(
    255,
    255,
    255
  )

  c.text(
    "LAUNCH",
    625.0,
    116.0
  )
}

// ============================================================
// BOSS DRAW
// ============================================================

def drawBoss(
  c: CanvasDraw
): Unit = {

  if (
    !bossActive
  ) {
    return
  }

  if (
    bossShield >
    0.0
  ) {

    c.noFill()

    c.stroke(
      0,
      220,
      255,
      140
    )

    c.strokeWeight(
      5.0
    )

    c.ellipse(
      bossX,
      bossY,
      165.0 +
      math.sin(
        bossCore
      ) * 10.0,
      100.0 +
      math.sin(
        bossCore
      ) * 8.0
    )
  }

  c.fill(
    125,
    20,
    30
  )

  c.stroke(
    255,
    190,
    50
  )

  c.strokeWeight(
    3.0
  )

  c.ellipse(
    bossX,
    bossY,
    125.0,
    64.0
  )

  c.fill(
    60,
    65,
    80
  )

  c.rect(
    bossX - 42.0,
    bossY - 40.0,
    84.0,
    24.0
  )

  val pulse =
    20.0 +
    math.abs(
      math.sin(
        bossCore
      )
    ) * 7.0

  c.fill(
    255,
    50,
    40,
    150
  )

  c.noStroke()

  c.ellipse(
    bossX,
    bossY,
    pulse + 12.0,
    pulse + 12.0
  )

  c.fill(
    255,
    50,
    40
  )

  c.ellipse(
    bossX,
    bossY,
    pulse,
    pulse
  )

  // HP
  c.fill(
    20,
    20,
    28
  )

  c.rect(
    bossX - 85.0,
    bossY + 58.0,
    170.0,
    10.0
  )

  val hpRatio =
    math.max(
      0.0,
      math.min(
        1.0,
        bossHP /
        bossMaxHP
      )
    )

  c.fill(
    255,
    45,
    45
  )

  c.rect(
    bossX - 85.0,
    bossY + 58.0,
    170.0 *
    hpRatio,
    10.0
  )

  // Shield
  if (
    bossShield >
    0.0
  ) {

    val shieldRatio =
      math.max(
        0.0,
        math.min(
          1.0,
          bossShield /
          100.0
        )
      )

    c.fill(
      0,
      210,
      255
    )

    c.rect(
      bossX - 85.0,
      bossY + 72.0,
      170.0 *
      shieldRatio,
      5.0
    )
  }

  c.fill(
    255,
    220,
    80
  )

  c.text(
    s"PHASE $bossPhase",
    bossX - 32.0,
    bossY + 95.0
  )
}

// ============================================================
// TARGET LOCK
// ============================================================

def drawTargetLock(
  c: CanvasDraw
): Unit = {

  if (
    enemies.nonEmpty
  ) {

    val target =
      enemies.head

    if (
      !target.destroyed
    ) {

      val px =
        target.x

      val py =
        target.y

      c.noFill()

      c.stroke(
        0,
        255,
        220,
        200
      )

      c.strokeWeight(
        2.0
      )

      c.line(
        px - 32.0,
        py - 24.0,
        px - 10.0,
        py - 24.0
      )

      c.line(
        px + 10.0,
        py - 24.0,
        px + 32.0,
        py - 24.0
      )

      c.line(
        px - 32.0,
        py + 24.0,
        px - 10.0,
        py + 24.0
      )

      c.line(
        px + 10.0,
        py + 24.0,
        px + 32.0,
        py + 24.0
      )

      c.fill(
        0,
        255,
        220
      )

      c.noStroke()

      c.text(
        "TARGET",
        px - 22.0,
        py + 42.0
      )
    }
  }
}

// ============================================================
// COMMAND DRONE
// ============================================================

def drawCommandDrone(
  c: CanvasDraw
): Unit = {

  val x =
    cwidth / 2.0 +
    math.cos(
      squadDrone
    ) *
    220.0

  val y =
    250.0 +
    math.sin(
      squadDrone * 1.7
    ) *
    30.0

  c.fill(
    40,
    50,
    60
  )

  c.stroke(
    0,
    220,
    255
  )

  c.strokeWeight(
    2.0
  )

  c.ellipse(
    x,
    y,
    38.0,
    18.0
  )

  c.fill(
    0,
    230,
    255
  )

  c.noStroke()

  c.ellipse(
    x,
    y,
    10.0,
    10.0
  )

  c.stroke(
    180,
    240,
    255,
    150
  )

  c.strokeWeight(
    2.0
  )

  c.line(
    x - 20.0,
    y,
    x - 42.0,
    y
  )

  c.line(
    x + 20.0,
    y,
    x + 42.0,
    y
  )
}

// ============================================================
// SAINIK SCAN
// ============================================================

def drawSainikScan(
  c: CanvasDraw
): Unit = {

  val cx =
    cwidth / 2.0

  val cy =
    185.0

  c.noFill()

  c.stroke(
    0,
    255,
    220,
    70
  )

  c.strokeWeight(
    2.0
  )

  c.ellipse(
    cx,
    cy,
    350.0 +
    math.sin(
      squadScan
    ) * 20.0,
    170.0
  )

  c.stroke(
    0,
    230,
    255,
    100
  )

  c.line(
    cx,
    cy,
    cx +
    math.cos(
      squadScan
    ) *
    290.0,
    cy +
    math.sin(
      squadScan
    ) *
    110.0
  )
}

// ============================================================
// FINAL STRIKE
// ============================================================

def drawFinalStrike(
  c: CanvasDraw
): Unit = {

  if (
    currentGameState !=
    4
  ) {
    return
  }

  c.pushMatrix()

  c.translate(
    strikeX,
    strikeY
  )

  c.rotate(
    strikeAngle
  )

  c.fill(
    255,
    80,
    20,
    60
  )

  c.noStroke()

  c.ellipse(
    -25.0,
    0.0,
    80.0,
    40.0
  )

  c.fill(
    245,
    245,
    245
  )

  c.stroke(
    150,
    30,
    30
  )

  c.strokeWeight(
    2.0
  )

  c.rect(
    -38.0,
    -12.0,
    76.0,
    24.0
  )

  c.fill(
    255,
    235,
    100
  )

  c.noStroke()

  c.ellipse(
    40.0,
    0.0,
    28.0,
    28.0
  )

  c.popMatrix()
}

// ============================================================
// VICTORY
// ============================================================

def drawVictory(
  c: CanvasDraw
): Unit = {

  c.background(
    0,
    0,
    0
  )

  drawStarfield(
    c
  )

  val pulse =
    1.0 +
    math.abs(
      math.sin(
        universalClock
      )
    ) *
    0.15

  c.fill(
    255,
    153,
    51
  )

  c.rect(
    cwidth / 2.0 -
    220.0,
    70.0,
    440.0,
    5.0
  )

  c.fill(
    255,
    255,
    255
  )

  c.rect(
    cwidth / 2.0 -
    220.0,
    61.0,
    440.0,
    5.0
  )

  c.fill(
    18,
    136,
    7
  )

  c.rect(
    cwidth / 2.0 -
    220.0,
    52.0,
    440.0,
    5.0
  )

  c.fill(
    255,
    220,
    100,
    (
      230.0 *
      pulse
    ).toInt
  )

  c.text(
    "JAI HIND",
    cwidth / 2.0 -
    40.0,
    cheight / 2.0 +
    110.0
  )

  c.fill(
    255,
    255,
    255
  )

  c.text(
    "MISSION ACCOMPLISHED",
    cwidth / 2.0 -
    135.0,
    cheight / 2.0 +
    55.0
  )

  c.fill(
    255,
    235,
    190
  )

  c.text(
    "SAINIK COMMAND SUCCEEDED",
    cwidth / 2.0 -
    125.0,
    cheight / 2.0 +
    20.0
  )

  c.fill(
    100,
    240,
    255
  )

  c.text(
    "QUANTUM DEFENSE STATUS: STABLE",
    cwidth / 2.0 -
    135.0,
    cheight / 2.0 -
    15.0
  )

  c.fill(
    255,
    255,
    255
  )

  c.text(
    "SELECTED AVATAR:",
    cwidth / 2.0 -
    110.0,
    cheight / 2.0 -
    55.0
  )

  c.fill(
    0,
    255,
    190
  )

  c.text(
    avatarNames(
      selectedAvatar - 1
    ),
    cwidth / 2.0 -
    95.0,
    cheight / 2.0 -
    78.0
  )

  c.fill(
    255,
    220,
    80
  )

  c.text(
    s"DEFENSE SCORE: $scoreDestroyedCount",
    cwidth / 2.0 -
    85.0,
    cheight / 2.0 -
    110.0
  )

  drawAvatar(
    c,
    cwidth / 2.0,
    cheight / 2.0 -
    180.0,
    selectedAvatar,
    1.35,
    false,
    true
  )
}

// ============================================================
// MAIN COMBAT DRAW
// ============================================================

def drawCombat(
  c: CanvasDraw
): Unit = {

  drawCombatBackground(
    c
  )

  drawSainikScan(
    c
  )

  drawSquad(
    c
  )

  drawCommandDrone(
    c
  )

  drawTank(
    c
  )

  drawSilo(
    c
  )

  drawAirDefense(
    c
  )

  drawParticles(
    c
  )

  repeatFor(
    shells
  )(
    _.draw(c)
  )

  repeatFor(
    missiles
  )(
    _.draw(c)
  )

  repeatFor(
    enemies
  )(
    _.draw(c)
  )

  repeatFor(
    shockwaves
  )(
    _.draw(c)
  )

  drawTargetLock(
    c
  )

  if (
    bossActive
  ) {

    drawBoss(
      c
    )
  }

  drawControls(
    c
  )

  drawHUD(
    c
  )

  // Help
  c.fill(
    150,
    195,
    220
  )

  c.text(
    "ARROWS: MOVE / AIM",
    270.0,
    cheight - 90.0
  )

  c.text(
    "SPACE: FIRE",
    270.0,
    cheight - 108.0
  )

  c.text(
    "ENTER: LAUNCH MISSILE",
    270.0,
    cheight - 126.0
  )

  if (
    bossActive
  ) {

    c.fill(
      255,
      60,
      60
    )

    c.text(
      "FLAGSHIP BATTLE ACTIVE",
      cwidth / 2.0 -
      95.0,
      cheight -
      35.0
    )
  }
}

// ============================================================
// WORLD UPDATE
// ============================================================

def updateWorld(): Unit = {

  universalClock +=
    0.025

  globalElapsedTime +=
    0.025

  avatarAnimClock +=
    0.05

  avatarButtonClock +=
    0.05

  commanderMarch +=
    0.14

  commanderPulse +=
    0.06

  tankEngine +=
    0.08

  squadMarch +=
    0.14

  squadBreath +=
    0.05

  squadScan +=
    0.04

  squadDrone +=
    0.025

  akashAngle +=
    0.035

  pinakaPulse +=
    0.07

  bossCore +=
    0.09

  screenShake *=
    0.86

  colorFlash *=
    0.84

  redAlert *=
    0.95

  cinematicBars *=
    0.97

  tankRecoil *=
    0.85

  tankHeat *=
    0.99

  if (
    cannonCooldown >
    0
  ) {

    cannonCooldown -=
      1
  }

  if (
    missileCooldown >
    0
  ) {

    missileCooldown -=
      1
  }

  updateParticles()

  if (
    !passwordUnlocked
  ) {

    updatePassword()

    return
  }

  if (
    currentGameState ==
    0
  ) {

    briefingTimer +=
      0.04

    if (
      briefingTimer >=
      0.025 &&
      briefingIndex <
      briefingText.length
    ) {

      briefingIndex +=
        1

      briefingTimer =
        0.0
    }

    return
  }

  if (
    currentGameState ==
    1
  ) {

    squadDeploy =
      math.min(
        1.0,
        squadDeploy +
        0.018
      )

    squadSalute =
      0.5 +
      math.sin(
        universalClock
      ) *
      0.5

    if (
      squadDeploy >=
      1.0
    ) {

      currentGameState =
        2

      playFX(
        82,
        140
      )
    }

    return
  }

  if (
    currentGameState ==
    2
  ) {

    handleMouseControls()
    handleKeyboard()

    combatTimer -=
      0.033

    updateObjects()
    checkCollisions()

    if (
      tankX <
      60.0
    ) {
      tankX =
        60.0
    }

    if (
      tankX >
      cwidth - 60.0
    ) {
      tankX =
        cwidth - 60.0
    }

    if (
      randomDouble(
        0.0,
        1.0
      ) <
      0.055
    ) {

      spawnEnemy()
    }

    if (
      pinakaCooldown <=
      0 &&
      enemies.nonEmpty
    ) {

      val target =
        enemies.head

      val dx =
        target.x -
        pinakaX

      val dy =
        target.y -
        pinakaY

      val angle =
        math.atan2(
          dy,
          dx
        )

      missiles.append(
        new Missile(
          pinakaX,
          pinakaY,
          10.5,
          angle,
          3
        )
      )

      pinakaCooldown =
        38

      playFX(
        68,
        30
      )

    } else {

      pinakaCooldown -=
        1
    }

    if (
      combatTimer <=
      45.0 &&
      !bossActive
    ) {

      startBoss()

      return
    }

    return
  }

  if (
    currentGameState ==
    3
  ) {

    handleMouseControls()
    handleKeyboard()

    updateObjects()
    checkCollisions()

    bossWarning -=
      0.033

    bossMovement +=
      0.035

    bossX =
      cwidth / 2.0 +
      math.sin(
        bossMovement *
        1.8
      ) *
      225.0

    if (
      bossPhase ==
      1 &&
      bossHP <
      500.0
    ) {

      bossPhase =
        2

      redAlert =
        1.0

      screenShake =
        8.0

      playFX(
        48,
        140
      )
    }

    if (
      bossPhase ==
      2 &&
      bossHP <
      230.0
    ) {

      bossPhase =
        3

      redAlert =
        1.0

      screenShake =
        11.0

      playFX(
        38,
        160
      )
    }

    if (
      randomDouble(
        0.0,
        1.0
      ) <
      0.20
    ) {

      spawnParticles(
        bossX +
        randomDouble(
          -50.0,
          50.0
        ),
        bossY +
        randomDouble(
          -30.0,
          30.0
        ),
        255,
        50,
        40,
        2
      )
    }

    if (
      bossHP <=
      0.0
    ) {

      bossActive =
        false

      currentGameState =
        4

      strikeX =
        tankX +
        260.0

      strikeY =
        cheight +
        160.0

      strikeSpeed =
        8.5

      cinematicBars =
        12.0

      megaExplosion(
        bossX,
        bossY
      )

      playFX(
        36,
        250
      )
    }

    return
  }

  if (
    currentGameState ==
    4
  ) {

    val dx =
      tankX -
      strikeX

    val dy =
      (
        tankY +
        50.0
      ) -
      strikeY

    val targetAngle =
      math.atan2(
        dy,
        dx
      )

    strikeAngle +=
      (
        targetAngle -
        strikeAngle
      ) *
      0.08

    strikeX +=
      math.cos(
        strikeAngle
      ) *
      strikeSpeed

    strikeY +=
      math.sin(
        strikeAngle
      ) *
      strikeSpeed

    strikeSpeed +=
      0.06

    if (
      math.abs(
        strikeX -
        tankX
      ) <
      30.0 &&
      math.abs(
        strikeY -
        (
          tankY +
          50.0
        )
      ) <
      30.0
    ) {

      megaExplosion(
        tankX,
        tankY +
        50.0
      )

      currentGameState =
        5

      playFX(
        36,
        300
      )
    }

    updateObjects()

    return
  }

  if (
    currentGameState ==
    5
  ) {

    updateObjects()

    return
  }
}

// ============================================================
// COMPLETE PIPELINE
// ============================================================

def renderGame(
  c: CanvasDraw
): Unit = {

  if (
    currentGameState ==
    -1
  ) {

    drawPasswordScreen(
      c
    )

    return
  }

  if (
    currentGameState ==
    -2
  ) {

    drawAvatarSelectionScreen(
      c
    )

    return
  }

  if (
    currentGameState ==
    0
  ) {

    drawBriefing(
      c
    )

    return
  }

  if (
    currentGameState ==
    1
  ) {

    drawDeployment(
      c
    )

    return
  }

  if (
    currentGameState ==
    5
  ) {

    drawVictory(
      c
    )

    return
  }

  if (
    screenShake >
    0.5
  ) {

    c.pushMatrix()

    c.translate(
      randomDouble(
        -screenShake,
        screenShake
      ),
      randomDouble(
        -screenShake,
        screenShake
      )
    )
  }

  drawCombat(
    c
  )

  if (
    currentGameState ==
    4
  ) {

    drawFinalStrike(
      c
    )
  }

  if (
    screenShake >
    0.5
  ) {

    c.popMatrix()
  }

  if (
    colorFlash >
    0.01
  ) {

    c.fill(
      255,
      245,
      210,
      math.max(
        0,
        math.min(
          255,
          (
            colorFlash *
            255.0
          ).toInt
        )
      )
    )

    c.noStroke()

    c.rect(
      0.0,
      0.0,
      cwidth,
      cheight
    )
  }

  if (
    redAlert >
    0.02
  ) {

    c.noFill()

    c.stroke(
      255,
      20,
      20,
      math.min(
        190,
        (
          redAlert *
          190.0
        ).toInt
      )
    )

    c.strokeWeight(
      6.0
    )

    c.rect(
      4.0,
      4.0,
      cwidth - 8.0,
      cheight - 8.0
    )
  }

  val bars =
    math.max(
      0,
      math.min(
        55,
        cinematicBars.toInt
      )
    )

  if (
    bars >
    0
  ) {

    c.fill(
      0,
      0,
      0
    )

    c.noStroke()

    c.rect(
      0.0,
      0.0,
      cwidth,
      bars
    )

    c.rect(
      0.0,
      cheight -
      bars,
      cwidth,
      bars
    )
  }
}

// ============================================================
// MAIN LOOP
// ============================================================

animateWithCanvasDraw { c =>

  updateWorld()

  renderGame(
    c
  )
}