Code Sketch


sniper new cricket game
By: Mhalsakant School
Category: Art
import scala.util.Random

// Clear canvas and set speed
clear()
invisible()
setSpeed(fast) 

// --- Game Settings ---
val maxBalls = 12 // 2 Overs = 12 Balls
val maxWickets = 10

// --- Match State Variables ---
var currentInning = 1 // 1 = Player Batting, 2 = AI Batting
var ballsBowled = 0
var runs = 0
var wickets = 0

var targetRuns = 0
var aiRuns = 0
var aiWickets = 0

var ballX = -200.0
var ballY = 0.0
var ballHeight = 0.0
var ballVx = 0.0
var ballVy = 0.0
var swingFactor = 0.0 
var isBowled = false
var isHit = false

// --- Bowler Run-Up Variables ---
var bowlerX = -260.0
var bowlerY = 0.0
var isRunningUp = false
var strideState = 0
var bowlingPhase = "RUNUP"

// --- Fast Bat Swing Animation Variables ---
var isSwinging = false
var swingTimer = 0

// --- Stump Light Variables ---
var stumpLightTimer = 0
var isStumpGlowing = false

// --- Over Log ---
var overLog = scala.collection.mutable.ListBuffer[String]()

// Celebration & Status
var showCelebration = false
var celebrationTimer = 0
var statusMessage = "INNING 1: You are Batting First! Press SPACE to Bowl & hit 'S','D','W'!"

// ==========================================
// ?? HIGH-DETAIL REALISTIC STADIUM & CROWD
// ==========================================
val ground = Picture.rectangle(600, 400)
ground.setFillColor(Color(34, 139, 34))
ground.setPenColor(noColor)
ground.setPosition(-300, -200)
ground.draw()

// Outfield Turf Grass Pattern Lines
for (i <- -300 to 300 by 40) {
  val grassStrip = Picture.rectangle(20, 400)
  grassStrip.setFillColor(Color(46, 160, 46))
  grassStrip.setPenColor(noColor)
  grassStrip.setPosition(i, -200)
  grassStrip.draw()
}

// Crowd Stand Background (Top Boundaries)
val topStand = Picture.rectangle(600, 25)
topStand.setFillColor(Color(70, 70, 70))
topStand.setPosition(-300, 175)
topStand.draw()

for (cx <- -290 to 290 by 12) {
  val spectator = Picture.circle(4)
  spectator.setFillColor(Color(Random.nextInt(200) + 55, Random.nextInt(200) + 55, Random.nextInt(200) + 55))
  spectator.setPosition(cx, 185)
  spectator.draw()
}

// ? Realistic Turf Pitch Base
val pitchBase = Picture.rectangle(420, 84)
pitchBase.setFillColor(Color(160, 120, 80)) 
pitchBase.setPenColor(noColor)
pitchBase.setPosition(-210, -42)
pitchBase.draw()

val pitch = Picture.rectangle(400, 76)
pitch.setFillColor(Color(218, 190, 145)) 
pitch.setPenColor(noColor)
pitch.setPosition(-200, -38)
pitch.draw()

// Wear Patch
val wearPatch = Picture.rectangle(120, 50)
wearPatch.setFillColor(Color(205, 175, 130))
wearPatch.setPenColor(noColor)
wearPatch.setPosition(-60, -25)
wearPatch.draw()

// White Crease Lines
def createCreaseLine(w: Int, h: Int, x: Int, y: Int): Picture = {
  val line = Picture.rectangle(w, h)
  line.setFillColor(cm.white)
  line.setPenColor(noColor)
  line.setPosition(x, y)
  line.draw()
  line
}

// Creases
createCreaseLine(3, 76, -180, -38)  
createCreaseLine(35, 2, -200, 30)   
createCreaseLine(35, 2, -200, -32)  
createCreaseLine(3, 76, 180, -38)   
createCreaseLine(35, 2, 165, 30)    
createCreaseLine(35, 2, 165, -32)   

// ==========================================
// ? BOWLER DESIGN
// ==========================================
val bowlerShadow = Picture.ellipse(28, 10)
bowlerShadow.setFillColor(Color(20, 80, 20))
bowlerShadow.setPenColor(noColor)
bowlerShadow.draw()

val shoeL = Picture.rectangle(6, 4); shoeL.setFillColor(cm.white); shoeL.draw()
val shoeR = Picture.rectangle(6, 4); shoeR.setFillColor(cm.white); shoeR.draw()
val legL = Picture.rectangle(5, 16); legL.setFillColor(Color(220, 220, 220)); legL.draw()
val legR = Picture.rectangle(5, 16); legR.setFillColor(Color(220, 220, 220)); legR.draw()

val torsoBase = Picture.rectangle(16, 22); torsoBase.setFillColor(Color(255, 82, 82)); torsoBase.draw()
val torsoHighlight = Picture.rectangle(8, 22); torsoHighlight.setFillColor(Color(255, 128, 128)); torsoHighlight.setPenColor(noColor); torsoHighlight.draw()

val armL = Picture.rectangle(4, 18); armL.setFillColor(Color(220, 220, 220)); armL.draw()
val armR = Picture.rectangle(4, 20); armR.setFillColor(Color(220, 220, 220)); armR.draw()
val head = Picture.circle(7); head.setFillColor(Color(240, 190, 150)); head.draw()
val cap = Picture.circle(7); cap.setFillColor(Color(180, 0, 0)); cap.draw()

def updateBowlerPosition(x: Double, y: Double, phase: String): Unit = {
  bowlerShadow.setPosition(x - 14, -22)
  shoeL.setPosition(x - 6, -18 + y)
  shoeR.setPosition(x + 4, -18 + y)
  legL.setPosition(x - 5, -14 + y)
  legR.setPosition(x + 5, -14 + y)
  torsoBase.setPosition(x - 8, 0 + y)
  torsoHighlight.setPosition(x - 8, 0 + y)
  
  phase match {
    case "JUMP" =>
      armL.setHeading(90); armR.setHeading(-120); legL.setHeading(20); legR.setHeading(-20)
    case "RELEASE" =>
      armL.setHeading(-45); armR.setHeading(110); legL.setHeading(-10); legR.setHeading(30)
    case "FOLLOW_THROUGH" =>
      armL.setHeading(-60); armR.setHeading(20); legL.setHeading(0); legR.setHeading(0)
    case _ =>
      if (strideState % 2 == 0) {
        armL.setHeading(55); armR.setHeading(-55); legL.setHeading(15); legR.setHeading(-15)
      } else {
        armL.setHeading(-40); armR.setHeading(70); legL.setHeading(-15); legR.setHeading(15)
      }
  }

  armL.setPosition(x - 12, 10 + y)
  armR.setPosition(x + 4, 12 + y)
  head.setPosition(x, 20 + y)
  cap.setPosition(x, 24 + y)
}

updateBowlerPosition(bowlerX, bowlerY, "RUNUP")

// ==========================================
// ? BATSMAN & SINGLE RIGID BAT
// ==========================================
val batsman = Picture.circle(12)
batsman.setFillColor(cm.blue)
batsman.setPosition(180, 0)
batsman.draw()

val batBlade = Picture.rectangle(11, 32)
batBlade.setFillColor(Color(245, 230, 175)) 
batBlade.setPenColor(Color(180, 150, 90))

val batSpine = Picture.rectangle(3, 32)
batSpine.setFillColor(Color(230, 210, 150))
batSpine.setPenColor(noColor)

val batShoulder = Picture.rectangle(7, 4)
batShoulder.setFillColor(Color(230, 215, 160))
batShoulder.setPenColor(Color(180, 150, 90))

val batHandle = Picture.rectangle(4, 14)
batHandle.setFillColor(Color(15, 15, 15)) 
batHandle.setPenColor(Color(50, 50, 50))

def updateBatPosition(angleDeg: Double): Unit = {
  val rad = math.toRadians(angleDeg)
  val pivotX = 178.0
  val pivotY = 12.0

  def setPartPosition(pic: Picture, offsetX: Double, offsetY: Double): Unit = {
    pic.setHeading(angleDeg)
    val rotatedX = pivotX + (offsetX * math.cos(rad) - offsetY * math.sin(rad))
    val rotatedY = pivotY + (offsetX * math.sin(rad) + offsetY * math.cos(rad))
    pic.setPosition(rotatedX, rotatedY)
  }

  setPartPosition(batBlade, -5, -40)
  setPartPosition(batSpine, -1, -40)
  setPartPosition(batShoulder, -3, -10)
  setPartPosition(batHandle, -2, -6)
}

batBlade.draw()
batSpine.draw()
batShoulder.draw()
batHandle.draw()

updateBatPosition(0.0)

// ==========================================
// ? STUMPS & BALL
// ==========================================
val stumpShadow = Picture.ellipse(22, 6)
stumpShadow.setFillColor(Color(160, 130, 90))
stumpShadow.setPenColor(noColor)
stumpShadow.setPosition(215, -22)
stumpShadow.draw()

val stumpBase = Picture.rectangle(18, 4)
stumpBase.setFillColor(Color(100, 100, 100))
stumpBase.setPenColor(noColor)
stumpBase.setPosition(217, -20)
stumpBase.draw()

val stump1 = Picture.rectangle(3, 30)
val stump2 = Picture.rectangle(3, 30)
val stump3 = Picture.rectangle(3, 30)

val defaultStumpColor = Color(245, 222, 179)
val defaultStumpBorder = Color(139, 69, 19)

stump1.setFillColor(defaultStumpColor); stump1.setPenColor(defaultStumpBorder); stump1.setPosition(217, -18); stump1.draw()
stump2.setFillColor(defaultStumpColor); stump2.setPenColor(defaultStumpBorder); stump2.setPosition(224, -18); stump2.draw()
stump3.setFillColor(defaultStumpColor); stump3.setPenColor(defaultStumpBorder); stump3.setPosition(231, -18); stump3.draw()

val bail1 = Picture.rectangle(7, 3)
val bail2 = Picture.rectangle(7, 3)
bail1.setFillColor(defaultStumpColor); bail1.setPenColor(defaultStumpBorder); bail1.setPosition(217, 12); bail1.draw()
bail2.setFillColor(defaultStumpColor); bail2.setPenColor(defaultStumpBorder); bail2.setPosition(224, 12); bail2.draw()

val stumpGlow = Picture.ellipse(32, 45)
stumpGlow.setFillColor(Color(0, 191, 255))
stumpGlow.setPenColor(noColor)
stumpGlow.setPosition(210, -20)
stumpGlow.draw()
stumpGlow.setOpacity(0.0)

def triggerStumpBlueLight(): Unit = {
  isStumpGlowing = true
  stumpLightTimer = 25
  stumpGlow.setOpacity(0.85)
  val glowColor = Color(0, 238, 255)
  val glowBorder = Color(255, 255, 255)
  stump1.setFillColor(glowColor); stump1.setPenColor(glowBorder)
  stump2.setFillColor(glowColor); stump2.setPenColor(glowBorder)
  stump3.setFillColor(glowColor); stump3.setPenColor(glowBorder)
  bail1.setFillColor(glowColor); bail1.setPenColor(glowBorder)
  bail2.setFillColor(glowColor); bail2.setPenColor(glowBorder)
}

def turnOffStumpLight(): Unit = {
  isStumpGlowing = false
  stumpGlow.setOpacity(0.0)
  stump1.setFillColor(defaultStumpColor); stump1.setPenColor(defaultStumpBorder)
  stump2.setFillColor(defaultStumpColor); stump2.setPenColor(defaultStumpBorder)
  stump3.setFillColor(defaultStumpColor); stump3.setPenColor(defaultStumpBorder)
  bail1.setFillColor(defaultStumpColor); bail1.setPenColor(defaultStumpBorder)
  bail2.setFillColor(defaultStumpColor); bail2.setPenColor(defaultStumpBorder)
}

val ballShadow = Picture.ellipse(12, 4)
ballShadow.setFillColor(Color(150, 130, 100))
ballShadow.setPenColor(noColor)
ballShadow.draw()

val ball = Picture.circle(6)
ball.setFillColor(Color(255, 255, 255))
ball.setPenColor(Color(180, 180, 180))
ball.draw()

def updateBallPosition(x: Double, y: Double, h: Double): Unit = {
  ball.setPosition(x, y + h)
  ballShadow.setPosition(x - 6, y - 12)
}

updateBallPosition(ballX, ballY, ballHeight)

// Confetti Particles (Set off-screen)
val confetti1 = Picture.circle(8); confetti1.setPosition(-1000, -1000); confetti1.draw(); confetti1.setOpacity(0.0)
val confetti2 = Picture.circle(10); confetti2.setPosition(-1000, -1000); confetti2.draw(); confetti2.setOpacity(0.0)
val confetti3 = Picture.circle(7); confetti3.setPosition(-1000, -1000); confetti3.draw(); confetti3.setOpacity(0.0)
val confetti4 = Picture.circle(9); confetti4.setPosition(-1000, -1000); confetti4.draw(); confetti4.setOpacity(0.0)

// ==========================================
// ? HUD & DISPLAY
// ==========================================
val boxPlayer = Picture.rectangle(260, 55); boxPlayer.setFillColor(Color(20, 20, 20)); boxPlayer.setPenColor(Color(0, 230, 118)); boxPlayer.setPenThickness(2); boxPlayer.setPosition(-285, 115); boxPlayer.draw()
val textPlayer = Picture.text("", 11); textPlayer.setFillColor(cm.white); textPlayer.setPosition(-275, 160); textPlayer.draw()
val boxAI = Picture.rectangle(260, 55); boxAI.setFillColor(Color(20, 20, 20)); boxAI.setPenColor(Color(255, 82, 82)); boxAI.setPenThickness(2); boxAI.setPosition(25, 115); boxAI.draw()
val textAI = Picture.text("", 11); textAI.setFillColor(cm.white); textAI.setPosition(35, 160); textAI.draw()
val statusText = Picture.text("", 11); statusText.setFillColor(cm.yellow); statusText.setPosition(-280, -180); statusText.draw()

def getOversString(balls: Int): String = s"${balls / 6}.${balls % 6} Ov"

def updateScoreDisplay(): Unit = {
  val crrVal = if (ballsBowled > 0) (runs.toDouble / ballsBowled) * 6 else 0.0
  val crr = f"$crrVal%.2f"
  val lastOverStr = overLog.takeRight(6).mkString(" ")

  if (currentInning == 1) {
    textPlayer.update(s"TEAM SNIPER (Batting)\nRuns: $runs/$wickets | ${getOversString(ballsBowled)} | CRR: $crr\nLog: [$lastOverStr]")
    textAI.update(s"TEAM AI (Bowling)\nYet to Bat (Target: TBD)")
  } else {
    textPlayer.update(s"TEAM SNIPER (Batting Done)\nScore: $runs/$wickets in 2.0 Ov")
    textAI.update(s"TEAM AI (Chasing)\nRuns: $aiRuns/$aiWickets | Target: $targetRuns | ${getOversString(ballsBowled)}\nLog: [$lastOverStr]")
  }
  statusText.update(statusMessage)
}

updateScoreDisplay()

def triggerCelebration(): Unit = {
  showCelebration = true; celebrationTimer = 40
  confetti1.setPosition(0, 50)
  confetti2.setPosition(-80, 80)
  confetti3.setPosition(80, 70)
  confetti4.setPosition(-120, 20)
  confetti1.setOpacity(1.0); confetti2.setOpacity(1.0); confetti3.setOpacity(1.0); confetti4.setOpacity(1.0)
}

def hideCelebration(): Unit = {
  showCelebration = false
  confetti1.setOpacity(0.0); confetti2.setOpacity(0.0); confetti3.setOpacity(0.0); confetti4.setOpacity(0.0)
  confetti1.setPosition(-1000, -1000)
  confetti2.setPosition(-1000, -1000)
  confetti3.setPosition(-1000, -1000)
  confetti4.setPosition(-1000, -1000)
}

def switchInnings(): Unit = {
  currentInning = 2; targetRuns = runs + 1; ballsBowled = 0; overLog.clear()
  isBowled = false; isHit = false; isRunningUp = false
  bowlerX = -260.0; bowlerY = 0.0; bowlingPhase = "RUNUP"
  updateBowlerPosition(bowlerX, bowlerY, bowlingPhase)
  statusMessage = s"INNING 2: AI Chasing Target of $targetRuns Runs! Press SPACE to Bowl."
  updateScoreDisplay()
}

def checkMatchEnd(): Unit = {
  if (currentInning == 2) {
    if (aiRuns >= targetRuns) {
      statusMessage = s"MATCH OVER! TEAM AI WON BY ${maxWickets - aiWickets} WICKETS!"
    } else if (ballsBowled >= maxBalls || aiWickets >= maxWickets) {
      if (aiRuns < targetRuns - 1) {
        statusMessage = s"? CONGRATS! TEAM SNIPER WON BY ${targetRuns - 1 - aiRuns} RUNS! ?"
        triggerCelebration()
      } else {
        statusMessage = "MATCH TIED! Equal Scores."
      }
    }
  }
}

def startRunUp(): Unit = {
  if (currentInning == 1 && (ballsBowled >= maxBalls || wickets >= maxWickets)) { switchInnings(); return }
  if (currentInning == 2 && (ballsBowled >= maxBalls || aiWickets >= maxWickets || aiRuns >= targetRuns)) { checkMatchEnd(); updateScoreDisplay(); return }

  if (!isBowled && !isRunningUp) {
    isRunningUp = true; bowlerX = -260.0; bowlerY = 0.0; bowlingPhase = "RUNUP"
    statusMessage = "Bowler running in..."
    updateScoreDisplay()
  }
}

def releaseBall(): Unit = {
  isBowled = true; isHit = false
  ballsBowled += 1; ballX = -190.0; ballY = 0.0; ballHeight = 4.0
  
  ballVx = 7.0 // Ball speed set to 7
  swingFactor = if (Random.nextBoolean()) 0.025 else -0.025
  ballVy = -swingFactor * 18

  val swingType = if (swingFactor > 0) "IN-SWING ?" else "OUT-SWING ?"
  statusMessage = if (currentInning == 1) s"$swingType! Hit 'S', 'D', or 'W'!" else s"Ball Bowled ($swingType)!"
  updateScoreDisplay()
}

// Player Bat Swing
def swingBat(shotType: String): Unit = {
  if (currentInning != 1 || wickets >= maxWickets) return

  isSwinging = true
  swingTimer = 3
  updateBatPosition(-50.0)

  if (isBowled && !isHit) {
    if (ballX >= 70 && ballX <= 195) {
      isHit = true
      val timing = math.abs(ballX - 150)

      if (timing > 35) {
        wickets += 1
        overLog += "W"
        statusMessage = "EDGE & CAUGHT IN SLIPS! OUT!"
        ballVx = 2.0; ballVy = 4.0
        triggerStumpBlueLight()
      } else {
        shotType match {
          case "STRAIGHT" =>
            if (timing < 20) { runs += 6; overLog += "6"; statusMessage = "GREAT SHOT! SIXER!"; ballVx = -(11.0 + Random.nextDouble() * 2.0); ballVy = 0.0 }
            else { runs += 4; overLog += "4"; statusMessage = "FOUR! Straight Drive!"; ballVx = -(9.0 + Random.nextDouble() * 2.0); ballVy = 0.5 }

          case "COVER" =>
            if (timing < 20) { runs += 6; overLog += "6"; statusMessage = "COVER DRIVE! SIX!"; ballVx = -(10.5 + Random.nextDouble() * 2.0); ballVy = -(5.0 + Random.nextDouble() * 2.0) }
            else { runs += 4; overLog += "4"; statusMessage = "FOUR! Cover Shot!"; ballVx = -(8.5 + Random.nextDouble() * 2.0); ballVy = -(3.5 + Random.nextDouble() * 2.0) }

          case _ =>
            if (timing < 20) { runs += 6; overLog += "6"; statusMessage = "PULL SHOT! SIX!"; ballVx = -(10.5 + Random.nextDouble() * 2.0); ballVy = 5.0 + Random.nextDouble() * 2.0 }
            else { runs += 4; overLog += "4"; statusMessage = "FOUR! Pull Shot!"; ballVx = -(8.5 + Random.nextDouble() * 2.0); ballVy = -2.5 - Random.nextDouble() * 2.0 }
        }
      }

      if (ballsBowled >= maxBalls || wickets >= maxWickets) switchInnings()
    } else {
      statusMessage = "MISSED THE TIMING!"
    }
    updateScoreDisplay()
  }
}

// AI Play
def aiAutoPlay(): Unit = {
  if (currentInning == 2 && isBowled && !isHit && ballX >= 100 && ballX <= 180) {
    val chance = Random.nextInt(100)
    if (chance < 55) {
      isSwinging = true; swingTimer = 3; updateBatPosition(-50.0); isHit = true
      val shotType = Random.nextInt(100)
      if (shotType < 25) { aiRuns += 6; overLog += "6"; statusMessage = "AI Hit a SIX!"; ballVx = -(10.5 + Random.nextDouble() * 2.0); ballVy = 3.5 + Random.nextDouble() * 2.0 }
      else if (shotType < 65) { aiRuns += 4; overLog += "4"; statusMessage = "AI Hit a FOUR!"; ballVx = -(8.5 + Random.nextDouble() * 2.0); ballVy = -2.5 - Random.nextDouble() * 2.0 }
      else { aiRuns += 1; overLog += "1"; statusMessage = "AI Took 1 Run!"; ballVx = -(6.0 + Random.nextDouble() * 1.0); ballVy = -1.0 }
      checkMatchEnd()
    }
    updateScoreDisplay()
  }
}

def resetGame(): Unit = {
  currentInning = 1; ballsBowled = 0; runs = 0; wickets = 0; targetRuns = 0; aiRuns = 0; aiWickets = 0; overLog.clear()
  isBowled = false; isHit = false; isRunningUp = false
  bowlerX = -260.0; bowlerY = 0.0; bowlingPhase = "RUNUP"
  isSwinging = false; updateBatPosition(0.0)
  updateBowlerPosition(bowlerX, bowlerY, bowlingPhase)
  turnOffStumpLight(); hideCelebration()
  statusMessage = "Game Reset! Inning 1: Press SPACE to Bowl."
  updateScoreDisplay()
}

// Key Listeners
onKeyPress { key =>
  key match {
    case ' ' => startRunUp()
    case 's' | 'S' => if (currentInning == 1) swingBat("STRAIGHT")
    case 'd' | 'D' => if (currentInning == 1) swingBat("COVER")
    case 'w' | 'W' => if (currentInning == 1) swingBat("PULL")
    case 'r' | 'R' => resetGame()
    case _ =>
  }
}

// Main Game Loop
animate {
  if (showCelebration) {
    celebrationTimer -= 1
    confetti1.setFillColor(Color(Random.nextInt(255), Random.nextInt(255), Random.nextInt(255)))
    confetti2.setFillColor(Color(Random.nextInt(255), Random.nextInt(255), Random.nextInt(255)))
    confetti3.setFillColor(Color(Random.nextInt(255), Random.nextInt(255), Random.nextInt(255)))
    confetti4.setFillColor(Color(Random.nextInt(255), Random.nextInt(255), Random.nextInt(255)))
    if (celebrationTimer <= 0) hideCelebration()
  }

  if (isStumpGlowing) {
    stumpLightTimer -= 1
    if (stumpLightTimer <= 0) turnOffStumpLight()
  }

  // Bowler Movement
  if (isRunningUp) {
    if (bowlerX < -220.0) { bowlerX += 7.0; strideState += 1; bowlingPhase = "RUNUP" }
    else if (bowlerX < -205.0) { bowlerX += 6.0; bowlerY = 10.0; bowlingPhase = "JUMP" }
    else if (bowlerX < -195.0) { bowlerX += 5.0; bowlerY = 3.0; bowlingPhase = "RELEASE" }
    else { isRunningUp = false; bowlerY = 0.0; bowlingPhase = "FOLLOW_THROUGH"; releaseBall() }
    updateBowlerPosition(bowlerX, bowlerY, bowlingPhase)
  }

  // Bat Swing Reset
  if (isSwinging) {
    swingTimer -= 1
    if (swingTimer <= 0) {
      isSwinging = false
      updateBatPosition(0.0)
    }
  }

  // Direct Swing Ball Movement (No Pitch Bounce)
  if (isBowled) {
    ballX += ballVx

    if (!isHit) {
      ballVy += swingFactor 
      if (ballY > 25.0) ballY = 25.0
      if (ballY < -25.0) ballY = -25.0
    }
    ballY += ballVy

    updateBallPosition(ballX, ballY, ballHeight)

    if (currentInning == 2) aiAutoPlay()

    // Bowled Check
    if (!isHit && ballX > 213) {
      isBowled = false
      triggerStumpBlueLight()

      if (currentInning == 1) {
        wickets += 1; overLog += "W"
        statusMessage = s"BOWLED OUT! ? ($wickets/$maxWickets Wkts)"
        if (wickets >= maxWickets || ballsBowled >= maxBalls) switchInnings()
      } else {
        aiWickets += 1; overLog += "W"
        statusMessage = s"OUT! AI WICKET TAKEN! ? ($aiWickets/$maxWickets Wkts)"
        checkMatchEnd()
      }
      updateScoreDisplay()
    }

    if (ballX < -340 || ballX > 340 || ballY < -200 || ballY > 200) {
      if (!isHit && currentInning == 1) overLog += "."
      isBowled = false
      if (currentInning == 1 && (ballsBowled >= maxBalls || wickets >= maxWickets)) switchInnings()
      else if (currentInning == 2) checkMatchEnd()
    }
  }
}