Code Sketch


yoiii111
By: Mhalsakant School
Category: Programming
import java.awt.BasicStroke
import java.awt.BorderLayout
import java.awt.Color
import java.awt.GradientPaint
import java.awt.Graphics
import java.awt.Graphics2D
import java.awt.RenderingHints
import java.awt.Polygon
import java.awt.Point

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

import javax.swing.JButton
import javax.swing.JFrame
import javax.swing.JLabel
import javax.swing.JPanel
import javax.swing.SwingConstants
import javax.swing.Timer
import javax.swing.WindowConstants

import scala.collection.mutable.ArrayBuffer


// ============================================================
// GOKUL ASHTAMI ULTRA FESTIVAL
// CLEAN SCALA / SWING VERSION
// ============================================================
//
// LEFT / A       = MOVE LEFT
// RIGHT / D     = MOVE RIGHT
// UP / W / SPACE= JUMP
// P              = PAUSE
// R              = RESET
// MOUSE CLICK    = JUMP
//
// FEATURES
// - Janmashtami opening screen
// - Palna Utsav
// - Gokul adventure
// - Coins
// - Makhan
// - Enemies
// - Moving platforms
// - Dahi Handi
// - Levels
// - Score
// - Combo
// - Lives
// - Countdown
// - Checkpoint
// - Particles
// - Final celebration
// ============================================================


// ============================================================
// CONSTANTS
// ============================================================

val GAME_WIDTH: Int = 1200
val GAME_HEIGHT: Int = 760

val GROUND_HEIGHT: Int = 100

val PLAYER_WIDTH: Int = 46
val PLAYER_HEIGHT: Int = 64

val GRAVITY_VALUE: Double = 0.58
val JUMP_VALUE: Double = -12.5

val TOTAL_LEVELS: Int = 5
val LEVEL_TIME: Int = 70


// ============================================================
// DATA TYPES
// ============================================================

case class CoinData(
  var x: Double,
  var y: Double,
  var collected: Boolean
)

case class MakhanData(
  var x: Double,
  var y: Double,
  var collected: Boolean
)

case class EnemyData(
  var x: Double,
  var y: Double,
  var vx: Double,
  var minX: Double,
  var maxX: Double,
  var active: Boolean
)

case class PlatformData(
  var x: Double,
  var y: Double,
  val width: Int,
  val height: Int,
  var vx: Double,
  val minX: Double,
  val maxX: Double
)

case class ParticleData(
  var x: Double,
  var y: Double,
  var vx: Double,
  var vy: Double,
  var life: Int,
  val kind: Int
)

case class StarData(
  x: Double,
  y: Double
)

case class FlowerData(
  x: Int,
  y: Int,
  size: Int,
  kind: Int
)


// ============================================================
// GAME STATE
// ============================================================

var stage: Int = 0
var level: Int = 1

var score: Int = 0
var lives: Int = 3
var timeLeft: Int = LEVEL_TIME

var combo: Int = 0
var bestCombo: Int = 0

var coinCount: Int = 0
var makhanCount: Int = 0

var running: Boolean = true
var paused: Boolean = false
var victory: Boolean = false

var playerX: Double = 120.0
var playerY: Double = 500.0

var velocityY: Double = 0.0
var jumping: Boolean = false

var leftPressed: Boolean = false
var rightPressed: Boolean = false

var invincibleTicks: Int = 0

var messageText: String = ""
var messageTicks: Int = 0

var festivalTicks: Int = 0
var glowTicks: Int = 0

var checkpointX: Double = 120.0
var checkpointActive: Boolean = false

var handiBroken: Boolean = false
var handiHits: Int = 0
var handiRequired: Int = 1


// ============================================================
// COLLECTIONS
// ============================================================

val coins =
  ArrayBuffer[CoinData]()

val makhan =
  ArrayBuffer[MakhanData]()

val enemies =
  ArrayBuffer[EnemyData]()

val platforms =
  ArrayBuffer[PlatformData]()

val particles =
  ArrayBuffer[ParticleData]()

val stars =
  ArrayBuffer[StarData]()

val flowers =
  ArrayBuffer[FlowerData]()


// ============================================================
// FRAME / PANEL
// ============================================================

var gameFrame: JFrame = null
var festivalPanel: JPanel = null

var mainTimer: Timer = null
var clockTimer: Timer = null


// ============================================================
// HANDI
// ============================================================

val handiX: Double = 540.0
val handiY: Double = 95.0


// ============================================================
// BASIC HELPERS
// ============================================================

def clampValue(
  value: Double,
  minValue: Double,
  maxValue: Double
): Double = {

  math.max(
    minValue,
    math.min(
      maxValue,
      value
    )
  )
}


def rectanglesOverlap(
  x1: Double,
  y1: Double,
  w1: Double,
  h1: Double,
  x2: Double,
  y2: Double,
  w2: Double,
  h2: Double
): Boolean = {

  x1 < x2 + w2 &&
  x1 + w1 > x2 &&
  y1 < y2 + h2 &&
  y1 + h1 > y2
}


def playerTouches(
  x: Double,
  y: Double,
  w: Double,
  h: Double
): Boolean = {

  rectanglesOverlap(
    playerX,
    playerY,
    PLAYER_WIDTH,
    PLAYER_HEIGHT,
    x,
    y,
    w,
    h
  )
}


def addScore(
  amount: Int
): Unit = {

  score += amount

  if (score < 0) {
    score = 0
  }
}


def addCombo(): Unit = {

  combo += 1

  if (combo > bestCombo) {
    bestCombo = combo
  }
}


def showMessage(
  text: String,
  ticks: Int
): Unit = {

  messageText = text
  messageTicks = ticks
}


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

def createBurst(
  px: Double,
  py: Double,
  amount: Int
): Unit = {

  var i: Int = 0

  while (i < amount) {

    val angle: Double =
      math.random * math.Pi * 2.0

    val speed: Double =
      1.0 + math.random * 6.0

    particles +=
      ParticleData(
        px,
        py,
        math.cos(angle) * speed,
        math.sin(angle) * speed - 2.0,
        30 + (math.random * 45).toInt,
        i % 4
      )

    i += 1
  }
}


def createStars(): Unit = {

  stars.clear()

  var i: Int = 0

  while (i < 100) {

    stars +=
      StarData(
        math.random * GAME_WIDTH,
        math.random * 420
      )

    i += 1
  }
}


// ============================================================
// CLEAR WORLD
// ============================================================

def clearWorld(): Unit = {

  coins.clear()
  makhan.clear()
  enemies.clear()
  platforms.clear()
  particles.clear()
  flowers.clear()
}


// ============================================================
// SETUP LEVEL
// ============================================================

def setupLevel(
  requestedLevel: Int
): Unit = {

  clearWorld()

  level = requestedLevel
  stage = 2

  running = true
  paused = false
  victory = false

  playerX = 120.0

  playerY =
    GAME_HEIGHT -
    GROUND_HEIGHT -
    PLAYER_HEIGHT

  velocityY = 0.0
  jumping = false

  checkpointX = 120.0
  checkpointActive = false

  invincibleTicks = 0

  handiBroken = false
  handiHits = 0

  if (level <= 2) {

    handiRequired = 1

  } else if (level <= 4) {

    handiRequired = 2

  } else {

    handiRequired = 3
  }


  // ----------------------------------------------------------
  // COINS
  // ----------------------------------------------------------

  val coinTotal: Int =
    7 + level * 2

  var i: Int = 0

  while (i < coinTotal) {

    val cx: Int =
      150 + (i * 125) % 980

    val cy: Int =
      360 - (i % 3) * 65

    coins +=
      CoinData(
        cx.toDouble,
        cy.toDouble,
        false
      )

    i += 1
  }


  // ----------------------------------------------------------
  // MAKHAN
  // ----------------------------------------------------------

  val makhanTotal: Int =
    2 + level

  i = 0

  while (i < makhanTotal) {

    val mx: Int =
      220 + (i * 170) % 850

    val my: Int =
      440 - (i % 2) * 100

    makhan +=
      MakhanData(
        mx.toDouble,
        my.toDouble,
        false
      )

    i += 1
  }


  // ----------------------------------------------------------
  // ENEMIES
  // ----------------------------------------------------------

  val enemyTotal: Int =
    math.min(
      2 + level,
      6
    )

  i = 0

  while (i < enemyTotal) {

    val ex: Int =
      300 + (i * 155) % 760

    val ey: Double =
      GAME_HEIGHT -
      GROUND_HEIGHT -
      46

    val speed: Double =
      if ((i % 2) == 0)
        1.2 + level * 0.22
      else
        -1.1 - level * 0.18

    enemies +=
      EnemyData(
        ex.toDouble,
        ey,
        speed,
        math.max(80, ex - 100).toDouble,
        math.min(1090, ex + 190).toDouble,
        true
      )

    i += 1
  }


  // ----------------------------------------------------------
  // MOVING PLATFORMS
  // ----------------------------------------------------------

  val platformTotal: Int =
    2 + level

  i = 0

  while (i < platformTotal) {

    val px: Int =
      180 + i * 175

    val py: Int =
      450 - (i % 3) * 90

    val direction: Double =
      if ((i % 2) == 0)
        1.0
      else
        -1.0

    platforms +=
      PlatformData(
        px.toDouble,
        py.toDouble,
        125,
        18,
        direction * 1.1,
        math.max(60, px - 90).toDouble,
        math.min(1020, px + 160).toDouble
      )

    i += 1
  }


  // ----------------------------------------------------------
  // FLOWERS
  // ----------------------------------------------------------

  i = 0

  while (i < 40) {

    flowers +=
      FlowerData(
        10 + (i * 37) % 1170,
        600 + (i % 3) * 25,
        8 + (i % 3) * 3,
        i % 4
      )

    i += 1
  }


  timeLeft = LEVEL_TIME

  if (level == 1) {

    showMessage(
      "LEVEL 1 - MAKHAN CHOR ADVENTURE",
      160
    )

  } else if (level == 2) {

    showMessage(
      "LEVEL 2 - GOKUL FUN RUN",
      160
    )

  } else if (level == 3) {

    showMessage(
      "LEVEL 3 - GOVINDA CHALLENGE",
      160
    )

  } else if (level == 4) {

    showMessage(
      "LEVEL 4 - DAHI HANDI MASTER",
      160
    )

  } else {

    showMessage(
      "LEVEL 5 - FINAL JANMASHTAMI",
      180
    )
  }
}


// ============================================================
// START FESTIVAL
// ============================================================

def startFestival(): Unit = {

  clearWorld()

  stage = 0
  level = 1

  score = 0
  lives = 3
  timeLeft = LEVEL_TIME

  combo = 0
  bestCombo = 0

  coinCount = 0
  makhanCount = 0

  running = true
  paused = false
  victory = false

  playerX = 120.0
  playerY = 500.0

  velocityY = 0.0
  jumping = false

  checkpointX = 120.0
  checkpointActive = false

  handiBroken = false
  handiHits = 0
  handiRequired = 1

  festivalTicks = 320
  glowTicks = 0

  createStars()

  showMessage(
    "SHREE KRISHNA JANMA MAHOTSAV",
    240
  )
}


// ============================================================
// NEXT STAGE
// ============================================================

def nextStage(): Unit = {

  if (stage == 0) {

    stage = 1

    showMessage(
      "BAL GOPAL PALNA UTSAV",
      200
    )

  } else if (stage == 1) {

    setupLevel(1)

  } else if (stage == 2) {

    if (handiBroken) {

      if (level < TOTAL_LEVELS) {

        setupLevel(
          level + 1
        )

      } else {

        stage = 3
        running = true
        victory = false

        festivalTicks = 320

        addScore(1000)

        createBurst(
          handiX + 55,
          handiY + 40,
          140
        )

        showMessage(
          "GRAND DAHI HANDI MAHOTSAV!",
          220
        )
      }

    } else {

      showMessage(
        "FIRST BREAK THE DAHI HANDI!",
        100
      )
    }

  } else if (stage == 3) {

    stage = 5
    victory = true
    running = false
  }
}


// ============================================================
// RESET PLAYER
// ============================================================

def resetPlayer(): Unit = {

  playerX = checkpointX

  playerY =
    GAME_HEIGHT -
    GROUND_HEIGHT -
    PLAYER_HEIGHT

  velocityY = 0.0
  jumping = false

  invincibleTicks = 120
}


// ============================================================
// LOSE LIFE
// ============================================================

def loseLife(): Unit = {

  lives -= 1
  combo = 0

  createBurst(
    playerX + 22,
    playerY + 30,
    25
  )

  if (lives <= 0) {

    running = false
    victory = false

    showMessage(
      "FESTIVAL CHALLENGE OVER",
      180
    )

  } else {

    resetPlayer()
    timeLeft = LEVEL_TIME

    showMessage(
      "TRY AGAIN GOVINDA!",
      100
    )
  }
}


// ============================================================
// CHECKPOINT
// ============================================================

def updateCheckpoint(): Unit = {

  if (
    !checkpointActive &&
    playerX > 500
  ) {

    checkpointActive = true
    checkpointX = 500

    addScore(50)

    createBurst(
      500,
      520,
      20
    )

    showMessage(
      "CHECKPOINT ACTIVATED!",
      90
    )
  }
}


// ============================================================
// COINS
// ============================================================

def updateCoins(): Unit = {

  var i: Int = 0

  while (i < coins.length) {

    val coin =
      coins(i)

    if (
      !coin.collected &&
      playerTouches(
        coin.x,
        coin.y,
        26,
        26
      )
    ) {

      coin.collected = true

      coinCount += 1

      addCombo()

      addScore(
        10 + combo * 2
      )

      createBurst(
        coin.x + 13,
        coin.y + 13,
        8
      )
    }

    i += 1
  }
}


// ============================================================
// MAKHAN
// ============================================================

def updateMakhan(): Unit = {

  var i: Int = 0

  while (i < makhan.length) {

    val item =
      makhan(i)

    if (
      !item.collected &&
      playerTouches(
        item.x,
        item.y,
        38,
        35
      )
    ) {

      item.collected = true

      makhanCount += 1

      addCombo()

      addScore(
        45 + level * 10
      )

      glowTicks = 70

      createBurst(
        item.x + 19,
        item.y + 18,
        12
      )

      showMessage(
        "MAKHAN MILA!",
        55
      )
    }

    i += 1
  }
}


// ============================================================
// ENEMIES
// ============================================================

def updateEnemies(): Unit = {

  var i: Int = 0

  while (i < enemies.length) {

    val enemy =
      enemies(i)

    if (enemy.active) {

      enemy.x += enemy.vx

      if (
        enemy.x < enemy.minX ||
        enemy.x > enemy.maxX
      ) {

        enemy.vx =
          -enemy.vx
      }

      if (
        invincibleTicks <= 0 &&
        playerTouches(
          enemy.x,
          enemy.y,
          46,
          46
        )
      ) {

        enemy.active = false
        loseLife()
      }
    }

    i += 1
  }
}


// ============================================================
// PLATFORMS
// ============================================================

def updatePlatforms(): Unit = {

  var i: Int = 0

  while (i < platforms.length) {

    val platform =
      platforms(i)

    platform.x += platform.vx

    if (
      platform.x < platform.minX ||
      platform.x > platform.maxX
    ) {

      platform.vx =
        -platform.vx
    }

    i += 1
  }
}


// ============================================================
// PLATFORM LANDING
// ============================================================

def checkPlatformLanding(): Unit = {

  if (velocityY >= 0.0) {

    var i: Int = 0

    while (i < platforms.length) {

      val platform =
        platforms(i)

      val oldBottom =
        playerY +
        PLAYER_HEIGHT -
        velocityY

      val newBottom =
        playerY +
        PLAYER_HEIGHT

      val horizontal =
        playerX + PLAYER_WIDTH >
        platform.x &&
        playerX <
        platform.x + platform.width

      val vertical =
        oldBottom <=
        platform.y + 8 &&
        newBottom >=
        platform.y

      if (
        horizontal &&
        vertical
      ) {

        playerY =
          platform.y -
          PLAYER_HEIGHT

        velocityY = 0.0
        jumping = false
      }

      i += 1
    }
  }
}


// ============================================================
// DAHI HANDI HIT
// ============================================================

def hitHandi(): Unit = {

  if (
    stage == 2 &&
    !handiBroken &&
    playerTouches(
      handiX,
      handiY,
      110,
      82
    ) &&
    playerY < 300
  ) {

    handiHits += 1

    addCombo()

    addScore(
      180 + level * 100
    )

    createBurst(
      handiX + 55,
      handiY + 40,
      30
    )

    if (
      handiHits >= handiRequired
    ) {

      handiBroken = true

      addScore(
        600 + level * 250
      )

      festivalTicks = 220

      createBurst(
        handiX + 55,
        handiY + 40,
        100
      )

      showMessage(
        "JAY SHREE KRISHNA! DAHI HANDI PHODLI!",
        190
      )

    } else {

      showMessage(
        "HANDI HIT " +
        handiHits +
        " / " +
        handiRequired,
        75
      )
    }
  }
}


// ============================================================
// PLAYER UPDATE
// ============================================================

def updatePlayer(): Unit = {

  if (
    !running ||
    paused ||
    stage < 2
  ) {
    return
  }


  if (leftPressed) {
    playerX -= 6.5
  }

  if (rightPressed) {
    playerX += 6.5
  }


  playerX =
    clampValue(
      playerX,
      0,
      GAME_WIDTH - PLAYER_WIDTH
    )


  velocityY += GRAVITY_VALUE
  playerY += velocityY


  val groundY =
    GAME_HEIGHT -
    GROUND_HEIGHT -
    PLAYER_HEIGHT


  if (playerY >= groundY) {

    playerY = groundY
    velocityY = 0.0
    jumping = false
  }


  checkPlatformLanding()


  if (
    playerY >
    GAME_HEIGHT + 60
  ) {

    loseLife()
  }


  updateCheckpoint()
  updateCoins()
  updateMakhan()
  updateEnemies()
  updatePlatforms()
  hitHandi()
}


// ============================================================
// PARTICLE UPDATE
// ============================================================

def updateParticles(): Unit = {

  var i: Int =
    particles.length - 1

  while (i >= 0) {

    val p =
      particles(i)

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

    p.life -= 1

    if (p.life <= 0) {
      particles.remove(i)
    }

    i -= 1
  }
}


// ============================================================
// EFFECT UPDATE
// ============================================================

def updateEffects(): Unit = {

  if (messageTicks > 0) {
    messageTicks -= 1
  }

  if (festivalTicks > 0) {
    festivalTicks -= 1
  }

  if (glowTicks > 0) {
    glowTicks -= 1
  }

  if (invincibleTicks > 0) {
    invincibleTicks -= 1
  }
}


// ============================================================
// CLOCK
// ============================================================

def clockTick(): Unit = {

  if (
    running &&
    !paused &&
    stage >= 2
  ) {

    timeLeft -= 1

    if (timeLeft <= 0) {

      timeLeft = LEVEL_TIME
      loseLife()
    }
  }
}


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

def drawStar(
  g: Graphics2D,
  cx: Int,
  cy: Int,
  outerRadius: Int,
  innerRadius: Int
): Unit = {

  val polygon =
    new Polygon()

  var i: Int = 0

  while (i < 10) {

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

    val radius =
      if ((i % 2) == 0)
        outerRadius
      else
        innerRadius

    val px =
      cx +
      (
        math.cos(angle) *
        radius
      ).toInt

    val py =
      cy +
      (
        math.sin(angle) *
        radius
      ).toInt

    polygon.addPoint(
      px,
      py
    )

    i += 1
  }

  g.fillPolygon(
    polygon
  )
}


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

festivalPanel =
  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
      )


      // ======================================================
      // BIRTH SCREEN
      // ======================================================

      if (stage == 0) {

        g.setPaint(
          new GradientPaint(
            0,
            0,
            new Color(
              20,
              25,
              95
            ),
            0,
            getHeight,
            new Color(
              105,
              35,
              120
            )
          )
        )

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


        var i: Int = 0

        while (i < stars.length) {

          val star =
            stars(i)

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

          g.fillOval(
            star.x.toInt,
            star.y.toInt,
            3,
            3
          )

          i += 1
        }


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

        g.fillOval(
          430,
          75,
          340,
          340
        )


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

        g.fillRect(
          310,
          405,
          580,
          245
        )


        val roof =
          new Polygon()

        roof.addPoint(
          280,
          405
        )

        roof.addPoint(
          600,
          210
        )

        roof.addPoint(
          920,
          405
        )

        g.fillPolygon(roof)


        g.setColor(
          new Color(
            200,
            145,
            55
          )
        )

        g.fillRoundRect(
          455,
          465,
          290,
          78,
          25,
          25
        )


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

        g.fillOval(
          545,
          415,
          110,
          110
        )


        g.setColor(
          Color.WHITE
        )

        g.setFont(
          new java.awt.Font(
            "Serif",
            java.awt.Font.BOLD,
            44
          )
        )

        g.drawString(
          "SHREE KRISHNA JANMA",
          355,
          595
        )


        g.setFont(
          new java.awt.Font(
            "Arial",
            java.awt.Font.BOLD,
            21
          )
        )

        g.drawString(
          "GOKUL ASHTAMI ULTIMATE FESTIVAL",
          390,
          635
        )


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

        g.drawString(
          "CLICK / SPACE TO START",
          455,
          690
        )


        drawMessage(
          g
        )

        return
      }


      // ======================================================
      // PALNA SCREEN
      // ======================================================

      if (stage == 1) {

        g.setPaint(
          new GradientPaint(
            0,
            0,
            new Color(
              85,
              155,
              235
            ),
            0,
            getHeight,
            new Color(
              255,
              220,
              165
            )
          )
        )

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


        g.setColor(
          new Color(
            216,
            172,
            95
          )
        )

        g.fillRect(
          300,
          275,
          600,
          355
        )


        val roof =
          new Polygon()

        roof.addPoint(
          245,
          275
        )

        roof.addPoint(
          600,
          80
        )

        roof.addPoint(
          955,
          275
        )

        g.setColor(
          new Color(
            175,
            92,
            45
          )
        )

        g.fillPolygon(roof)


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

        g.setStroke(
          new BasicStroke(5)
        )

        g.drawLine(
          420,
          250,
          420,
          505
        )

        g.drawLine(
          780,
          250,
          780,
          505
        )


        g.setColor(
          new Color(
            175,
            110,
            48
          )
        )

        g.fillRoundRect(
          415,
          465,
          370,
          85,
          25,
          25
        )


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

        g.fillOval(
          540,
          410,
          100,
          100
        )


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

        g.fillOval(
          558,
          390,
          64,
          64
        )


        g.setColor(
          Color.WHITE
        )

        g.setFont(
          new java.awt.Font(
            "Serif",
            java.awt.Font.BOLD,
            38
          )
        )

        g.drawString(
          "BAL GOPAL PALNA UTSAV",
          385,
          125
        )


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

        g.drawString(
          "CLICK / SPACE TO JHULA",
          455,
          680
        )


        drawMessage(g)

        return
      }


      // ======================================================
      // MAIN WORLD BACKGROUND
      // ======================================================

      g.setPaint(
        new GradientPaint(
          0,
          0,
          if (level >= 4)
            new Color(
              60,
              80,
              165
            )
          else
            new Color(
              80,
              170,
              240
            ),
          0,
          getHeight,
          if (level >= 4)
            new Color(
              185,
              100,
              145
            )
          else
            new Color(
              255,
              220,
              140
            )
        )
      )

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


      // ======================================================
      // SUN / MOON
      // ======================================================

      g.setColor(
        new Color(
          255,
          240,
          135
        )
      )

      g.fillOval(
        950,
        55,
        100,
        100
      )


      // ======================================================
      // CLOUDS
      // ======================================================

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

      g.fillOval(
        40,
        85,
        120,
        52
      )

      g.fillOval(
        110,
        55,
        130,
        68
      )

      g.fillOval(
        205,
        88,
        110,
        50
      )

      g.fillOval(
        700,
        100,
        120,
        50
      )

      g.fillOval(
        760,
        70,
        135,
        68
      )

      g.fillOval(
        865,
        100,
        115,
        50
      )


      // ======================================================
      // HILLS
      // ======================================================

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

      g.fillOval(
        -220,
        430,
        680,
        300
      )

      g.fillOval(
        230,
        430,
        660,
        300
      )

      g.fillOval(
        730,
        430,
        700,
        300
      )


      // ======================================================
      // GROUND
      // ======================================================

      g.setColor(
        new Color(
          65,
          145,
          65
        )
      )

      g.fillRect(
        0,
        GAME_HEIGHT - GROUND_HEIGHT,
        getWidth,
        GROUND_HEIGHT
      )


      // ======================================================
      // TEMPLE
      // ======================================================

      g.setColor(
        new Color(
          235,
          185,
          105
        )
      )

      g.fillRect(
        970,
        330,
        200,
        250
      )

      val templeRoof =
        new Polygon()

      templeRoof.addPoint(
        940,
        330
      )

      templeRoof.addPoint(
        1070,
        205
      )

      templeRoof.addPoint(
        1200,
        330
      )

      g.setColor(
        new Color(
          185,
          100,
          50
        )
      )

      g.fillPolygon(
        templeRoof
      )


      // ======================================================
      // FLOWERS
      // ======================================================

      var fi: Int = 0

      while (fi < flowers.length) {

        val flower =
          flowers(fi)

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

        g.setStroke(
          new BasicStroke(2)
        )

        g.drawLine(
          flower.x,
          flower.y,
          flower.x,
          flower.y - flower.size * 2
        )


        val flowerColor =
          if (flower.kind == 0)
            new Color(245, 75, 100)
          else if (flower.kind == 1)
            new Color(255, 220, 60)
          else if (flower.kind == 2)
            new Color(255, 145, 70)
          else
            Color.WHITE

        g.setColor(
          flowerColor
        )

        g.fillOval(
          flower.x - flower.size / 2,
          flower.y -
          flower.size * 2 -
          flower.size / 2,
          flower.size,
          flower.size
        )

        fi += 1
      }


      // ======================================================
      // PLATFORMS
      // ======================================================

      var pi: Int = 0

      while (pi < platforms.length) {

        val platform =
          platforms(pi)

        g.setColor(
          new Color(
            160,
            102,
            55
          )
        )

        g.fillRoundRect(
          platform.x.toInt,
          platform.y.toInt,
          platform.width,
          platform.height,
          10,
          10
        )

        g.setColor(
          new Color(
            95,
            55,
            30
          )
        )

        g.drawRoundRect(
          platform.x.toInt,
          platform.y.toInt,
          platform.width,
          platform.height,
          10,
          10
        )

        pi += 1
      }


      // ======================================================
      // GOVINDA PYRAMID
      // ======================================================

      var row: Int = 0

      while (row < 3) {

        val count =
          5 - row

        var col: Int = 0

        while (col < count) {

          val px: Int =
            295 +
            row * 60 +
            col * 125

          val py: Int =
            515 -
            row * 70


          g.setColor(
            if ((col + row) % 2 == 0)
              new Color(45, 100, 210)
            else
              new Color(225, 65, 75)
          )

          g.fillRoundRect(
            px,
            py,
            72,
            60,
            14,
            14
          )


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

          g.fillOval(
            px + 15,
            py - 24,
            42,
            42
          )

          col += 1
        }

        row += 1
      }


      // ======================================================
      // TOP GOVINDA
      // ======================================================

      g.setColor(
        new Color(
          45,
          90,
          210
        )
      )

      g.fillRoundRect(
        520,
        350,
        72,
        62,
        14,
        14
      )

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

      g.fillOval(
        535,
        328,
        42,
        42
      )


      // ======================================================
      // PEACOCK FEATHER
      // ======================================================

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

      g.setStroke(
        new BasicStroke(3)
      )

      g.drawLine(
        555,
        330,
        570,
        298
      )

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

      g.fillOval(
        567,
        291,
        12,
        15
      )


      // ======================================================
      // HANDI ROPE
      // ======================================================

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

      g.setStroke(
        new BasicStroke(5)
      )

      g.drawLine(
        (handiX + 55).toInt,
        0,
        (handiX + 55).toInt,
        handiY.toInt
      )


      // ======================================================
      // HANDI
      // ======================================================

      if (!handiBroken) {

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

        g.fillOval(
          handiX.toInt,
          handiY.toInt,
          110,
          82
        )

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

        g.drawOval(
          handiX.toInt,
          handiY.toInt,
          110,
          82
        )

        g.setColor(Color.WHITE)

        g.fillOval(
          handiX.toInt + 10,
          handiY.toInt + 5,
          90,
          20
        )

        g.setColor(
          new Color(
            145,
            85,
            40
          )
        )

        g.fillRect(
          handiX.toInt + 30,
          handiY.toInt - 10,
          50,
          13
        )

      } else {

        g.setColor(
          new Color(
            185,
            120,
            60
          )
        )

        g.fillArc(
          handiX.toInt,
          handiY.toInt,
          52,
          48,
          0,
          180
        )

        g.fillArc(
          handiX.toInt + 58,
          handiY.toInt + 4,
          52,
          48,
          0,
          180
        )
      }


      // ======================================================
      // COINS
      // ======================================================

      var ci: Int = 0

      while (ci < coins.length) {

        val coin =
          coins(ci)

        if (!coin.collected) {

          g.setColor(
            new Color(
              255,
              210,
              40
            )
          )

          g.fillOval(
            coin.x.toInt,
            coin.y.toInt,
            26,
            26
          )

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

          g.drawOval(
            coin.x.toInt + 4,
            coin.y.toInt + 4,
            18,
            18
          )
        }

        ci += 1
      }


      // ======================================================
      // MAKHAN
      // ======================================================

      var mi: Int = 0

      while (mi < makhan.length) {

        val item =
          makhan(mi)

        if (!item.collected) {

          g.setColor(
            new Color(
              175,
              112,
              58
            )
          )

          g.fillOval(
            item.x.toInt,
            item.y.toInt,
            38,
            35
          )

          g.setColor(Color.WHITE)

          g.fillOval(
            item.x.toInt + 5,
            item.y.toInt + 3,
            28,
            12
          )
        }

        mi += 1
      }


      // ======================================================
      // ENEMIES
      // ======================================================

      var ei: Int = 0

      while (ei < enemies.length) {

        val enemy =
          enemies(ei)

        if (enemy.active) {

          g.setColor(
            new Color(
              95,
              65,
              150
            )
          )

          g.fillRoundRect(
            enemy.x.toInt,
            enemy.y.toInt,
            46,
            46,
            12,
            12
          )

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

          g.fillOval(
            enemy.x.toInt + 6,
            enemy.y.toInt - 14,
            34,
            34
          )
        }

        ei += 1
      }


      // ======================================================
      // PLAYER
      // ======================================================

      if (
        invincibleTicks <= 0 ||
        invincibleTicks % 8 < 4
      ) {

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

        g.fillRoundRect(
          playerX.toInt,
          playerY.toInt,
          PLAYER_WIDTH,
          PLAYER_HEIGHT,
          12,
          12
        )

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

        g.fillOval(
          playerX.toInt + 6,
          playerY.toInt - 16,
          34,
          34
        )

        g.setColor(
          new Color(
            220,
            45,
            55
          )
        )

        g.fillRect(
          playerX.toInt + 4,
          playerY.toInt - 6,
          38,
          7
        )


        // PEACOCK FEATHER

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

        g.setStroke(
          new BasicStroke(3)
        )

        g.drawLine(
          playerX.toInt + 28,
          playerY.toInt - 2,
          playerX.toInt + 38,
          playerY.toInt - 25
        )

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

        g.fillOval(
          playerX.toInt + 34,
          playerY.toInt - 32,
          11,
          15
        )
      }


      // ======================================================
      // GLOW
      // ======================================================

      if (glowTicks > 0) {

        g.setColor(
          new Color(
            255,
            230,
            90,
            100
          )
        )

        g.fillOval(
          playerX.toInt - 15,
          playerY.toInt - 35,
          75,
          75
        )
      }


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

      var pindex: Int = 0

      while (pindex < particles.length) {

        val p =
          particles(pindex)

        val particleColor =
          if (p.kind == 0)
            new Color(255, 215, 50)
          else if (p.kind == 1)
            new Color(255, 100, 100)
          else if (p.kind == 2)
            new Color(100, 220, 255)
          else
            Color.WHITE

        g.setColor(
          particleColor
        )

        val particleSize =
          if (p.life % 2 == 0)
            7
          else
            4

        g.fillOval(
          p.x.toInt,
          p.y.toInt,
          particleSize,
          particleSize
        )

        pindex += 1
      }


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

      drawHUD(g)

      drawMessage(g)


      // ======================================================
      // PAUSE
      // ======================================================

      if (
        paused &&
        running
      ) {

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

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

        g.setColor(Color.WHITE)

        g.setFont(
          new java.awt.Font(
            "Arial",
            java.awt.Font.BOLD,
            58
          )
        )

        g.drawString(
          "PAUSED",
          470,
          340
        )

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

        g.drawString(
          "PRESS P TO CONTINUE",
          470,
          380
        )
      }


      // ======================================================
      // CELEBRATION
      // ======================================================

      if (stage == 3) {

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

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

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

        g.setFont(
          new java.awt.Font(
            "Serif",
            java.awt.Font.BOLD,
            52
          )
        )

        g.drawString(
          "JAY SHREE KRISHNA!",
          355,
          250
        )

        g.setFont(
          new java.awt.Font(
            "Arial",
            java.awt.Font.BOLD,
            32
          )
        )

        g.drawString(
          "GRAND DAHI HANDI MAHOTSAV",
          335,
          315
        )

        g.setColor(Color.WHITE)

        g.setFont(
          new java.awt.Font(
            "Arial",
            java.awt.Font.BOLD,
            23
          )
        )

        g.drawString(
          "GOVINDA AALA RE!",
          475,
          370
        )
      }


      // ======================================================
      // FINAL SCREEN
      // ======================================================

      if (!running) {

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

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


        if (victory) {

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

          g.setFont(
            new java.awt.Font(
              "Arial",
              java.awt.Font.BOLD,
              46
            )
          )

          g.drawString(
            "JANMASHTAMI CHAMPION!",
            300,
            245
          )

        } else {

          g.setColor(Color.WHITE)

          g.setFont(
            new java.awt.Font(
              "Arial",
              java.awt.Font.BOLD,
              50
            )
          )

          g.drawString(
            "GAME OVER",
            440,
            245
          )
        }


        g.setColor(Color.WHITE)

        g.setFont(
          new java.awt.Font(
            "Arial",
            java.awt.Font.BOLD,
            22
          )
        )

        g.drawString(
          "FINAL SCORE: " + score,
          465,
          315
        )

        g.drawString(
          "COINS: " + coinCount,
          500,
          350
        )

        g.drawString(
          "MAKHAN: " + makhanCount,
          490,
          385
        )

        g.drawString(
          "BEST COMBO: " + bestCombo,
          470,
          420
        )

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

        g.drawString(
          "PRESS R OR CLICK RESET",
          450,
          475
        )
      }
    }


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

    def drawHUD(
      g: Graphics2D
    ): Unit = {

      if (stage < 1) {
        return
      }

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

      g.fillRoundRect(
        14,
        14,
        790,
        80,
        16,
        16
      )


      g.setColor(Color.WHITE)

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

      g.drawString(
        "GOKUL ASHTAMI",
        28,
        40
      )

      g.drawString(
        "LEVEL " + level,
        185,
        40
      )

      g.drawString(
        "SCORE " + score,
        280,
        40
      )

      g.drawString(
        "LIVES " + lives,
        410,
        40
      )


      if (stage >= 2) {

        g.drawString(
          "TIME " + timeLeft,
          515,
          40
        )

        g.drawString(
          "COMBO x" + combo,
          625,
          40
        )
      }


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

      g.drawString(
        "COINS " +
        coinCount +
        "   MAKHAN " +
        makhanCount +
        "   BEST " +
        bestCombo,
        28,
        68
      )


      if (
        stage == 2 &&
        !handiBroken
      ) {

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

        g.fillRoundRect(
          385,
          108,
          430,
          42,
          14,
          14
        )

        g.setColor(Color.WHITE)

        g.drawString(
          "BREAK HANDI: " +
          handiHits +
          " / " +
          handiRequired,
          500,
          136
        )
      }


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

      g.fillRoundRect(
        845,
        14,
        335,
        80,
        14,
        14
      )

      g.setColor(Color.WHITE)

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

      g.drawString(
        "A/D OR LEFT/RIGHT = MOVE",
        865,
        38
      )

      g.drawString(
        "W/UP/SPACE/MOUSE = JUMP",
        865,
        60
      )

      g.drawString(
        "P = PAUSE    R = RESET",
        865,
        81
      )
    }


    // ========================================================
    // MESSAGE
    // ========================================================

    def drawMessage(
      g: Graphics2D
    ): Unit = {

      if (
        messageTicks > 0 &&
        messageText != ""
      ) {

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

        g.fillRoundRect(
          230,
          165,
          740,
          62,
          18,
          18
        )

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

        g.setFont(
          new java.awt.Font(
            "Arial",
            java.awt.Font.BOLD,
            21
          )
        )

        g.drawString(
          messageText,
          270,
          205
        )
      }
    }
  }


// ============================================================
// KEYBOARD INPUT
// ============================================================

festivalPanel.setFocusable(true)

festivalPanel.addKeyListener(
  new KeyAdapter {

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

      val keyCode: Int =
        event.getKeyCode


      if (
        keyCode == KeyEvent.VK_LEFT ||
        keyCode == KeyEvent.VK_A
      ) {

        leftPressed = true
      }


      if (
        keyCode == KeyEvent.VK_RIGHT ||
        keyCode == KeyEvent.VK_D
      ) {

        rightPressed = true
      }


      if (
        keyCode == KeyEvent.VK_UP ||
        keyCode == KeyEvent.VK_W ||
        keyCode == KeyEvent.VK_SPACE
      ) {

        if (stage == 0) {

          nextStage()

        } else if (stage == 1) {

          nextStage()

        } else if (
          running &&
          !paused &&
          stage >= 2 &&
          !jumping
        ) {

          velocityY = JUMP_VALUE
          jumping = true
        }
      }


      if (
        keyCode == KeyEvent.VK_P &&
        running
      ) {

        paused = !paused
      }


      if (
        keyCode == KeyEvent.VK_R
      ) {

        startFestival()
        festivalPanel.repaint()
      }
    }


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

      val keyCode: Int =
        event.getKeyCode


      if (
        keyCode == KeyEvent.VK_LEFT ||
        keyCode == KeyEvent.VK_A
      ) {

        leftPressed = false
      }


      if (
        keyCode == KeyEvent.VK_RIGHT ||
        keyCode == KeyEvent.VK_D
      ) {

        rightPressed = false
      }
    }
  }
)


// ============================================================
// MOUSE INPUT
// ============================================================

festivalPanel.addMouseListener(
  new MouseAdapter {

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

      if (stage == 0) {

        nextStage()

      } else if (stage == 1) {

        nextStage()

      } else if (
        running &&
        !paused &&
        stage >= 2 &&
        !jumping
      ) {

        velocityY = JUMP_VALUE
        jumping = true
      }

      festivalPanel.requestFocusInWindow()
    }
  }
)


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

mainTimer =
  new Timer(
    25,
    new ActionListener {

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

        if (
          running &&
          !paused
        ) {

          updatePlayer()
          updateParticles()
          updateEffects()

          festivalPanel.repaint()
        }
      }
    }
  )


// ============================================================
// CLOCK LOOP
// ============================================================

clockTimer =
  new Timer(
    1000,
    new ActionListener {

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

        clockTick()
        festivalPanel.repaint()
      }
    }
  )


// ============================================================
// RESET BUTTON
// ============================================================

val resetButton =
  new JButton(
    "RESET JANMASHTAMI"
  )

resetButton.setFont(
  new java.awt.Font(
    "Arial",
    java.awt.Font.BOLD,
    14
  )
)

resetButton.addActionListener(
  new ActionListener {

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

      startFestival()

      festivalPanel.requestFocusInWindow()
      festivalPanel.repaint()
    }
  }
)


// ============================================================
// NEXT BUTTON
// ============================================================

val nextButton =
  new JButton(
    "NEXT FESTIVAL"
  )

nextButton.setFont(
  new java.awt.Font(
    "Arial",
    java.awt.Font.BOLD,
    14
  )
)

nextButton.addActionListener(
  new ActionListener {

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

      if (
        stage == 0 ||
        stage == 1 ||
        (stage == 3 && running)
      ) {

        nextStage()
      }

      festivalPanel.requestFocusInWindow()
      festivalPanel.repaint()
    }
  }
)


// ============================================================
// FRAME
// ============================================================

gameFrame =
  new JFrame(
    "GOKUL ASHTAMI - ULTRA JANMASHTAMI FESTIVAL"
  )

gameFrame.setDefaultCloseOperation(
  WindowConstants.DISPOSE_ON_CLOSE
)

gameFrame.setSize(
  GAME_WIDTH,
  GAME_HEIGHT
)

gameFrame.setLocationRelativeTo(
  null
)

gameFrame.setLayout(
  new BorderLayout()
)


// ============================================================
// PANEL
// ============================================================

gameFrame.add(
  festivalPanel,
  BorderLayout.CENTER
)


// ============================================================
// BOTTOM PANEL
// ============================================================

val bottomPanel =
  new JPanel(
    new BorderLayout()
  )


bottomPanel.add(
  resetButton,
  BorderLayout.WEST
)


bottomPanel.add(
  new JLabel(
    "SHREE KRISHNA JANMA MAHOTSAV | GOVINDA AALA RE!",
    SwingConstants.CENTER
  ),
  BorderLayout.CENTER
)


bottomPanel.add(
  nextButton,
  BorderLayout.EAST
)


gameFrame.add(
  bottomPanel,
  BorderLayout.SOUTH
)


// ============================================================
// CLOSE CLEANUP
// ============================================================

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

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

      if (mainTimer != null) {
        mainTimer.stop()
      }

      if (clockTimer != null) {
        clockTimer.stop()
      }
    }
  }
)


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

startFestival()

mainTimer.start()
clockTimer.start()

gameFrame.setVisible(true)

festivalPanel.requestFocusInWindow()
festivalPanel.repaint()