Code Sketch


sniper_0718
By: Mhalsakant School
cleari()

// --- Canvas & Road Setup ---
val canvasWidth = 800
val canvasHeight = 600
val roadWidth = 340

val bg = Picture.rectangle(canvasWidth, canvasHeight)
bg.setFillColor(Color(10, 25, 10, 255))
bg.setPenColor(noColor)
bg.setPosition(-canvasWidth / 2, -canvasHeight / 2)
bg.draw()

val road = Picture.rectangle(roadWidth, canvasHeight)
road.setFillColor(Color(35, 35, 38, 255))
road.setPenColor(noColor)
road.setPosition(-roadWidth / 2, -canvasHeight / 2)
road.draw()

// --- Road Borders ---
val lineLeft = Picture.rectangle(6, canvasHeight)
lineLeft.setFillColor(white)
lineLeft.setPenColor(noColor)
lineLeft.setPosition(-roadWidth / 2 + 5, -canvasHeight / 2)
lineLeft.draw()

val lineRight = Picture.rectangle(6, canvasHeight)
lineRight.setFillColor(white)
lineRight.setPenColor(noColor)
lineRight.setPosition(roadWidth / 2 - 11, -canvasHeight / 2)
lineRight.draw()

// --- Animated Lane Dashes ---
val dashCount = 8
val dashHeight = 40
val dashGap = 50
var dashOffset = 0.0

val yellowDashesL = Array.fill(dashCount) {
  val d = Picture.rectangle(4, dashHeight)
  d.setFillColor(Color(255, 204, 0, 255))
  d.setPenColor(noColor)
  d.draw()
  d
}

val yellowDashesR = Array.fill(dashCount) {
  val d = Picture.rectangle(4, dashHeight)
  d.setFillColor(Color(255, 204, 0, 255))
  d.setPenColor(noColor)
  d.draw()
  d
}

def updateDashes(): Unit = {
  dashOffset = (dashOffset - 12) % (dashHeight + dashGap)
  for (i <- 0 until dashCount) {
    val yPos = (canvasHeight / 2) - (i * (dashHeight + dashGap)) + dashOffset
    yellowDashesL(i).setPosition(-5, yPos)
    yellowDashesR(i).setPosition(1, yPos)
  }
}

// --- WALLET & ROUTE DATA ---
case class RouteInfo(name: String, distance: Double, reward: Int)

val routesList = Array(
  RouteInfo("Pune -> Satara", 1500.0, 1500),
  RouteInfo("Solapur -> Kolhapur", 1500.0, 1500),
  RouteInfo("Mumbai -> Karnatak", 3000.0, 3000)
)

var currentRouteIdx = 0
var playerWallet = 1000
var distanceMeters = routesList(currentRouteIdx).distance

// --- TOP-LEFT CORNER BALANCE DISPLAY ---
val walletBg = Picture.rectangle(210, 45)
walletBg.setFillColor(Color(15, 25, 45, 240))
walletBg.setPenColor(yellow)
walletBg.setPosition(-canvasWidth / 2 + 15, canvasHeight / 2 - 55)
walletBg.draw()

val walletText = Picture.text(s"BALANCE: ?$playerWallet", 16)
walletText.setFillColor(Color(0, 255, 120, 255))
walletText.setPosition(-canvasWidth / 2 + 25, canvasHeight / 2 - 40)
walletText.draw()

// --- GPS TRACKER ---
val mapBg = Picture.rectangle(170, 100)
mapBg.setFillColor(Color(15, 20, 35, 235))
mapBg.setPenColor(cyan)
mapBg.setPosition(canvasWidth / 2 - 190, canvasHeight / 2 - 120)
mapBg.draw()

val mapRoad = Picture.rectangle(140, 10)
mapRoad.setFillColor(Color(60, 60, 70, 255))
mapRoad.setPosition(canvasWidth / 2 - 175, canvasHeight / 2 - 75)
mapRoad.draw()

val mapTitle = Picture.text("GPS TRACKER", 11)
mapTitle.setFillColor(Color(0, 230, 255, 255))
mapTitle.setPosition(canvasWidth / 2 - 180, canvasHeight / 2 - 35)
mapTitle.draw()

val mapPlayerPin = Picture.circle(6)
mapPlayerPin.setFillColor(yellow)
mapPlayerPin.setPosition(canvasWidth / 2 - 175, canvasHeight / 2 - 75)
mapPlayerPin.draw()

// --- DESTINATION LIST PANEL ('M' Key) ---
val listPanelBg = Picture.rectangle(340, 190)
listPanelBg.setFillColor(Color(15, 15, 25, 245))
listPanelBg.setPenColor(cyan)
listPanelBg.setPosition(-170, -95)

val listHeader = Picture.text("--- ROUTE DESTINATION LIST ---", 14)
listHeader.setFillColor(Color(255, 200, 0, 255))
listHeader.setPosition(-150, 65)

val routeItem1 = Picture.text("1. Pune to Satara (1500m) - Reward: ?1500", 12)
routeItem1.setFillColor(white); routeItem1.setPosition(-155, 30)

val routeItem2 = Picture.text("2. Solapur to Kolhapur (1500m) - Reward: ?1500", 12)
routeItem2.setFillColor(white); routeItem2.setPosition(-155, 0)

val routeItem3 = Picture.text("3. Mumbai to Karnatak (3000m) - Reward: ?3000", 12)
routeItem3.setFillColor(white); routeItem3.setPosition(-155, -30)

val listCloseHint = Picture.text("Press 'M' to Close Menu", 11)
listCloseHint.setFillColor(gray); listCloseHint.setPosition(-140, -65)

var showDestinationList = false
var mKeyWasPressed = false

// --- HIGH-VISIBILITY TOLL NAKA GRAPHICS ---
var isTollActive = false
var tollY = canvasHeight / 2.0 + 500.0
var tollGateOpen = false
var tKeyWasPressed = false
var tollPaidForCurrent = false

// Toll Structure
val tollPlazaRoof = Picture.rectangle(roadWidth + 90, 60)
tollPlazaRoof.setFillColor(Color(220, 30, 30, 255)) // Bright Red Roof
tollPlazaRoof.setPenColor(white)

val tollPlazaText = Picture.text("TOLL PLAZA - TAX ?500", 16)
tollPlazaText.setFillColor(white)

val tollPillarL = Picture.rectangle(25, 80)
tollPillarL.setFillColor(Color(220, 220, 220, 255))
tollPillarL.setPenColor(black)

val tollPillarR = Picture.rectangle(25, 80)
tollPillarR.setFillColor(Color(220, 220, 220, 255))
tollPillarR.setPenColor(black)

val tollGateBar = Picture.rectangle(roadWidth - 20, 14)
tollGateBar.setFillColor(Color(255, 215, 0, 255)) // Yellow Barrier
tollGateBar.setPenColor(black)

val tollSignalAlert = Picture.text("?? TOLL PLAZA AHEAD! SLOW DOWN", 15)
tollSignalAlert.setFillColor(Color(255, 215, 0, 255))
tollSignalAlert.setPosition(-170, 160)

val tollPayHint = Picture.text("PRESS 'T' TO PAY ?500 TOLL & OPEN BARRIER", 14)
tollPayHint.setFillColor(Color(0, 255, 120, 255))
tollPayHint.setPosition(-185, 180)

def drawTollNaka(): Unit = {
  if (isTollActive) {
    tollPlazaRoof.setPosition(-roadWidth / 2 - 45, tollY + 20)
    tollPlazaRoof.draw()
    
    tollPlazaText.setPosition(-110, tollY + 40)
    tollPlazaText.draw()

    tollPillarL.setPosition(-roadWidth / 2 - 20, tollY - 40)
    tollPillarL.draw()
    
    tollPillarR.setPosition(roadWidth / 2 - 5, tollY - 40)
    tollPillarR.draw()

    if (!tollGateOpen) {
      tollGateBar.setPosition(-roadWidth / 2 + 10, tollY - 10)
      tollGateBar.draw()
    } else {
      tollGateBar.erase()
    }
  } else {
    tollPlazaRoof.erase()
    tollPlazaText.erase()
    tollPillarL.erase()
    tollPillarR.erase()
    tollGateBar.erase()
  }
}

// --- DESTINATION PARKING BAY ---
val parkingBay = Picture.rectangle(85, 150)
parkingBay.setFillColor(Color(0, 220, 80, 180))
parkingBay.setPenColor(yellow)
var parkingY = canvasHeight / 2.0 + 400.0

val parkingText = Picture.text("PARK HERE", 12)
parkingText.setFillColor(white)

// --- PLAYER TRUCK ---
val playerCab = Picture.rectangle(52, 40)
playerCab.setFillColor(Color(245, 245, 250, 255)); playerCab.setPenColor(black)

val playerGlass = Picture.rectangle(44, 16)
playerGlass.setFillColor(Color(20, 40, 60, 255)); playerGlass.setPenColor(noColor)

val playerBed = Picture.rectangle(50, 65)
playerBed.setFillColor(Color(180, 40, 40, 255)); playerBed.setPenColor(black)

val playerTailL = Picture.rectangle(8, 6); playerTailL.setFillColor(Color(220, 20, 20, 255))
val playerTailR = Picture.rectangle(8, 6); playerTailR.setFillColor(Color(220, 20, 20, 255))

playerBed.draw(); playerCab.draw(); playerGlass.draw()
playerTailL.draw(); playerTailR.draw()

// --- TRAFFIC ---
val trafficCarBody = Picture.rectangle(50, 90); trafficCarBody.setFillColor(Color(30, 100, 200, 255))
val trafficCarRoof = Picture.rectangle(42, 32); trafficCarRoof.setFillColor(Color(40, 120, 220, 255))
trafficCarBody.draw(); trafficCarRoof.draw()

var carX = -26.0
var carY = -220.0
val carSteerSpeed = 6.0

var leftEnemyX = -roadWidth / 2.0 + 25.0
var leftEnemyY = -canvasHeight / 2.0 - 150.0

def updatePlayerPos(): Unit = {
  playerBed.setPosition(carX + 1, carY)
  playerCab.setPosition(carX + 1, carY + 63)
  playerGlass.setPosition(carX + 5, carY + 80)
  playerTailL.setPosition(carX + 3, carY + 2)
  playerTailR.setPosition(carX + 39, carY + 2)
}

def updateLeftEnemyPos(): Unit = {
  trafficCarBody.setPosition(leftEnemyX, leftEnemyY)
  trafficCarRoof.setPosition(leftEnemyX + 4, leftEnemyY + 29)
}

updatePlayerPos(); updateLeftEnemyPos()

// --- HUD Displays ---
var gameOver = false
var routeCompleted = false

val hudText = Picture.text("", 15)
hudText.setFillColor(white)
hudText.setPosition(-canvasWidth / 2 + 230, canvasHeight / 2 - 40)
hudText.draw()

val helpText = Picture.text("Drive: SPACE | Steer: LEFT/RIGHT | Pay Toll: 'T' | Menu: 'M' | Park: 'P'", 12)
helpText.setFillColor(Color(0, 230, 255, 255))
helpText.setPosition(-canvasWidth / 2 + 20, -canvasHeight / 2 + 20)
helpText.draw()

val victoryText = Picture.text("", 20)
victoryText.setFillColor(Color(0, 255, 100, 255))
victoryText.setPosition(-250, 0)

val gameOverText = Picture.text("CRASHED INTO TOLL BARRIER! Press 'R' to Restart", 18)
gameOverText.setFillColor(Color(255, 50, 50, 255))
gameOverText.setPosition(-240, 0)

// --- Game Loop ---
animate {
  if (!gameOver && !routeCompleted) {
    val currentRoute = routesList(currentRouteIdx)
    hudText.update(s"ROUTE: ${currentRoute.name} | DIST: ${distanceMeters.toInt} M")
    walletText.update(s"BALANCE: ?$playerWallet")

    if (isKeyPressed(Kc.VK_LEFT)) carX -= carSteerSpeed
    if (isKeyPressed(Kc.VK_RIGHT)) carX += carSteerSpeed

    val minX = -roadWidth / 2 + 12
    val maxX = roadWidth / 2 - 64
    if (carX < minX) carX = minX
    if (carX > maxX) carX = maxX

    // Menu Toggle ('M')
    val mPressedNow = isKeyPressed(Kc.VK_M)
    if (mPressedNow && !mKeyWasPressed) {
      showDestinationList = !showDestinationList
      if (showDestinationList) {
        listPanelBg.draw(); listHeader.draw(); routeItem1.draw(); routeItem2.draw(); routeItem3.draw(); listCloseHint.draw()
      } else {
        listPanelBg.erase(); listHeader.erase(); routeItem1.erase(); routeItem2.erase(); routeItem3.erase(); listCloseHint.erase()
      }
    }
    mKeyWasPressed = mPressedNow

    // TOLL NAKA TRIGGER (??????? ???????? ????? 700M ???? ??????)
    val triggerTollDist = currentRoute.distance - 700.0
    if (distanceMeters <= triggerTollDist && distanceMeters >= triggerTollDist - 300 && !tollPaidForCurrent) {
      tollSignalAlert.draw()
    } else {
      tollSignalAlert.erase()
    }

    if (distanceMeters <= triggerTollDist - 100 && !isTollActive && !tollPaidForCurrent) {
      isTollActive = true
      tollY = canvasHeight / 2.0 + 400.0
      tollGateOpen = false
    }

    // TOLL PAY ('T' Key)
    val tPressedNow = isKeyPressed(Kc.VK_T)
    if (tPressedNow && !tKeyWasPressed && isTollActive) {
      if (Math.abs(carY - (tollY - 30)) < 220 && !tollGateOpen) {
        if (playerWallet >= 500) {
          playerWallet -= 500
          tollGateOpen = true
          tollPaidForCurrent = true
          tollPayHint.erase()
        }
      }
    }
    tKeyWasPressed = tPressedNow

    if (isTollActive && !tollGateOpen && Math.abs(carY - (tollY - 30)) < 220) {
      tollPayHint.draw()
    } else {
      tollPayHint.erase()
    }

    // DRIVING MECHANIC (???? ????? ??????? ????, SPACE ?????????? ???? ????)
    if (isKeyPressed(Kc.VK_SPACE)) {
      updateDashes()

      if (distanceMeters > 0) {
        distanceMeters -= 1.5
      }

      val progressRatio = (currentRoute.distance - distanceMeters) / currentRoute.distance
      val mapPinX = (canvasWidth / 2 - 175) + (progressRatio * 135.0)
      mapPlayerPin.setPosition(mapPinX, canvasHeight / 2 - 75)

      // Toll Plaza Movement
      if (isTollActive) {
        tollY -= 5.0
        drawTollNaka()

        // ?? ??? ????? ???? ??? ???? ????? ?? ??? ?????
        if (!tollGateOpen && Math.abs((carY + 50) - (tollY - 10)) < 20) {
          gameOver = true
          gameOverText.draw()
        }

        if (tollY < -canvasHeight / 2 - 120) {
          isTollActive = false
          drawTollNaka()
        }
      }

      // Destination Parking Area
      if (distanceMeters <= 0) {
        if (parkingY > 50) parkingY -= 5.0
        parkingBay.setPosition(roadWidth / 2 + 5, parkingY)
        parkingBay.draw()
        parkingText.setPosition(roadWidth / 2 + 12, parkingY + 60)
        parkingText.draw()
      }

      // Traffic Movement
      leftEnemyY += 2.5
      if (leftEnemyY > canvasHeight / 2 + 150) leftEnemyY = -canvasHeight / 2.0 - 150.0
      updateLeftEnemyPos()
    }

    // DESTINATION PARKING CLAIM
    val inParkingArea = carX > roadWidth / 2 - 75 && carY >= parkingY - 20 && carY <= parkingY + 100
    if (distanceMeters <= 0 && inParkingArea && isKeyPressed(Kc.VK_P)) {
      routeCompleted = true
      playerWallet += currentRoute.reward
      walletText.update(s"BALANCE: ?$playerWallet")
      victoryText.update(s"DESTINATION REACHED! EARNED ?${currentRoute.reward}!")
      victoryText.draw()
    }

    updatePlayerPos()
  } else if (gameOver || routeCompleted) {
    if (isKeyPressed(Kc.VK_R)) {
      if (routeCompleted) {
        currentRouteIdx = (currentRouteIdx + 1) % routesList.length
      }
      gameOver = false
      routeCompleted = false
      distanceMeters = routesList(currentRouteIdx).distance
      isTollActive = false
      tollGateOpen = false
      tollPaidForCurrent = false
      parkingY = canvasHeight / 2.0 + 400.0
      parkingBay.erase()
      parkingText.erase()
      drawTollNaka()
      gameOverText.erase()
      victoryText.erase()
      carX = -26.0
      updatePlayerPos()
    }
  }
}