Code Sketch


sniper_0718
By: Mhalsakant School
import java.awt.{Color, Font, Graphics2D, RenderingHints}
import java.awt.event.{KeyEvent, KeyListener, ActionListener, ActionEvent}
import javax.swing.{JFrame, JPanel, Timer}
import scala.math._

object Realistic5DayGrannyGame {

  val ScreenWidth = 640
  val ScreenHeight = 480

  // 1 = Dark Brown Wall, 0 = Floor, 2 = Wooden Door, 3 = Brown Bed, 4 = Key, 6 = Flowerpot
  val mapWidth = 8
  val mapHeight = 8
  var map: Array[Array[Int]] = Array(
    Array(1,1,1,2,1,1,1,1), // Top Wall with SINGLE DOOR (2)
    Array(1,0,0,0,0,0,4,1), // Key (4) in corner
    Array(1,0,3,3,0,0,0,1), // Brown Wooden Bed (3)
    Array(1,0,3,3,0,0,0,1),
    Array(1,0,0,0,0,0,0,1),
    Array(1,6,0,0,0,0,0,1), // Flowerpot (6)
    Array(1,0,0,0,0,0,0,1),
    Array(1,1,1,1,1,1,1,1)  // Bottom Solid Wall
  )

  // Game States & 5-Day System
  var gameStarted = false
  var gameOver = false
  var gameEscaped = false
  var currentDay = 1      // Starts at Day 1
  var showingDayScreen = false
  var dayScreenTimer = 0

  // Player Settings
  var playerX = 4.0
  var playerY = 5.0
  var playerA = -Pi / 2.0
  val fov = Pi / 3.0
  var isHiding = false
  var isCrouching = false
  var hasKey = false

  // Flowerpot Settings
  var hasFlowerpotInHand = false
  var flowerPotDropped = false
  var droppedPotX = 0.0
  var droppedPotY = 0.0

  // Realistic Granny AI
  var grannyX = 1.5
  var grannyY = 1.5
  var grannyChasing = false
  val grannySpeed = 0.024

  // Controls
  var wPressed, sPressed, aPressed, dPressed = false
  var leftKey, rightKey = false

  // GUI Canvas
  val frame = new JFrame("3D Granny Horror - 5 Days Escape System")
  val panel = new JPanel() {
    override def paintComponent(gNative: java.awt.Graphics): Unit = {
      super.paintComponent(gNative)
      val g = gNative.asInstanceOf[Graphics2D]
      g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)

      if (!gameStarted) {
        g.setColor(Color.BLACK)
        g.fillRect(0, 0, ScreenWidth, ScreenHeight)

        g.setColor(new Color(180, 20, 20))
        g.setFont(new Font("Arial", Font.BOLD, 32))
        g.drawString("GRANNY: 5 DAYS ESCAPE", ScreenWidth / 2 - 200, ScreenHeight / 2 - 70)

        g.setColor(Color.LIGHT_GRAY)
        g.setFont(new Font("Arial", Font.PLAIN, 15))
        g.drawString("Press ENTER to Start (You have 5 Days)", ScreenWidth / 2 - 140, ScreenHeight / 2 - 20)
        g.drawString("W,A,S,D : Walk | Left/Right Arrows : Turn", ScreenWidth / 2 - 150, ScreenHeight / 2 + 20)
        g.drawString("C : Crouch (???? ?????)", ScreenWidth / 2 - 90, ScreenHeight / 2 + 50)
        g.drawString("E : Pick Flowerpot / Pick Key / Open Door", ScreenWidth / 2 - 170, ScreenHeight / 2 + 80)
        g.drawString("SPACE : Throw Flowerpot (?????? ??? ????)", ScreenWidth / 2 - 160, ScreenHeight / 2 + 110)
        g.drawString("H : Hide under Brown Bed (??????? ???)", ScreenWidth / 2 - 140, ScreenHeight / 2 + 140)

      } else if (showingDayScreen) {
        // --- ?? DAY TRANSITION SCREEN ---
        g.setColor(Color.BLACK)
        g.fillRect(0, 0, ScreenWidth, ScreenHeight)

        g.setColor(Color.DARK_GRAY)
        g.setFont(new Font("Serif", Font.BOLD, 48))
        g.drawString(s"DAY $currentDay", ScreenWidth / 2 - 80, ScreenHeight / 2)

      } else if (gameOver) {
        // --- GAME OVER SCREEN (AFTER DAY 5) ---
        g.setColor(new Color(30, 0, 0))
        g.fillRect(0, 0, ScreenWidth, ScreenHeight)
        g.setColor(Color.RED)
        g.setFont(new Font("Arial", Font.BOLD, 36))
        g.drawString("GAME OVER!", ScreenWidth / 2 - 120, ScreenHeight / 2 - 30)
        g.setFont(new Font("Arial", Font.PLAIN, 20))
        g.drawString("Granny caught you on Day 5!", ScreenWidth / 2 - 130, ScreenHeight / 2 + 10)

        g.setColor(Color.WHITE)
        g.setFont(new Font("Arial", Font.PLAIN, 16))
        g.drawString("Press ENTER to Restart Game from Day 1", ScreenWidth / 2 - 150, ScreenHeight / 2 + 60)

      } else if (gameEscaped) {
        g.setColor(new Color(0, 40, 0))
        g.fillRect(0, 0, ScreenWidth, ScreenHeight)
        g.setColor(Color.GREEN)
        g.setFont(new Font("Arial", Font.BOLD, 36))
        g.drawString("YOU ESCAPED THE ROOM!", ScreenWidth / 2 - 220, ScreenHeight / 2 - 20)
        g.setColor(Color.WHITE)
        g.setFont(new Font("Arial", Font.PLAIN, 18))
        g.drawString("Press ENTER to Play Again", ScreenWidth / 2 - 120, ScreenHeight / 2 + 40)

      } else {
        if (isHiding) {
          g.setColor(Color.BLACK)
          g.fillRect(0, 0, ScreenWidth, ScreenHeight)
          g.setColor(new Color(40, 30, 20))
          g.fillRect(0, ScreenHeight - 120, ScreenWidth, 120)
          g.setColor(Color.YELLOW)
          g.setFont(new Font("Arial", Font.BOLD, 22))
          g.drawString("HIDING UNDER THE BROWN BED...", ScreenWidth / 2 - 180, ScreenHeight / 2)
          g.setFont(new Font("Arial", Font.PLAIN, 16))
          g.drawString("Press 'H' to exit", ScreenWidth / 2 - 60, ScreenHeight / 2 + 40)

        } else {
          val horizonShift = if (isCrouching) -60 else 0

          // ? 1. WOODEN BROWN CEILING
          g.setColor(new Color(45, 25, 10)) 
          g.fillRect(0, 0, ScreenWidth, ScreenHeight / 2 + horizonShift)

          // ? 2. GREY TILE FLOOR
          g.setColor(new Color(55, 55, 60)) 
          g.fillRect(0, ScreenHeight / 2 + horizonShift, ScreenWidth, ScreenHeight / 2 - horizonShift)

          val depthBuffer = new Array[Double](ScreenWidth)

          // 3D Raycasting Engine
          for (x <- 0 until ScreenWidth) {
            val rayAngle = (playerA - fov / 2.0) + (x.toDouble / ScreenWidth.toDouble) * fov
            var distanceToWall = 0.0
            var hitWall = false
            var wallType = 1

            val eyeX = cos(rayAngle)
            val eyeY = sin(rayAngle)

            while (!hitWall && distanceToWall < 10.0) {
              distanceToWall += 0.03
              val checkX = (playerX + eyeX * distanceToWall).toInt
              val checkY = (playerY + eyeY * distanceToWall).toInt

              if (checkX < 0 || checkX >= mapWidth || checkY < 0 || checkY >= mapHeight) {
                hitWall = true
                distanceToWall = 10.0
              } else if (map(checkY)(checkX) > 0) {
                hitWall = true
                wallType = map(checkY)(checkX)
              }
            }

            val correctedDist = distanceToWall * cos(rayAngle - playerA)
            depthBuffer(x) = correctedDist

            val ceiling = (ScreenHeight / 2.0) - (ScreenHeight / correctedDist) + horizonShift
            val floor = ScreenHeight - ceiling + (horizonShift * 2)
            val wallHeight = floor - ceiling

            val brightness = max(0, min(180, (180 * (1.0 - correctedDist / 8.5)).toInt))

            wallType match {
              // ? Dark Brown Realistic Wall Shading
              case 1 => g.setColor(new Color((brightness * 0.45).toInt, (brightness * 0.25).toInt, (brightness * 0.12).toInt))
              // Wooden Door
              case 2 => g.setColor(new Color(brightness, (brightness * 0.5).toInt, (brightness * 0.2).toInt)) 
              // ?? Brown Bed
              case 3 => g.setColor(new Color((brightness * 0.55).toInt, (brightness * 0.3).toInt, (brightness * 0.15).toInt)) 
              // Key Marker
              case 4 => g.setColor(new Color(brightness, brightness, 0)) 
              // Flowerpot
              case 6 => g.setColor(new Color(brightness, (brightness * 0.3).toInt, 0)) 
              case _ => g.setColor(Color.BLACK)
            }

            g.fillRect(x, ceiling.toInt, 1, wallHeight.toInt)
          }

          // ? RENDER REALISTIC 3D GRANNY
          val ex = grannyX - playerX
          val ey = grannyY - playerY
          var grannyAngle = atan2(ey, ex) - playerA
          while (grannyAngle < -Pi) grannyAngle += 2 * Pi
          while (grannyAngle > Pi) grannyAngle -= 2 * Pi

          val grannyDist = sqrt(ex * ex + ey * ey)
          if (grannyDist > 0.2 && abs(grannyAngle) < fov) {
            val screenX = ((ScreenWidth / 2) * (1 + tan(grannyAngle) / tan(fov / 2))).toInt
            val spriteSize = min(ScreenWidth, (ScreenHeight / grannyDist).toInt)
            val spriteTop = (ScreenHeight / 2) - spriteSize / 2 + horizonShift
            val startX = max(0, screenX - spriteSize / 2)
            val endX = min(ScreenWidth - 1, screenX + spriteSize / 2)

            for (x <- startX to endX) {
              if (x >= 0 && x < ScreenWidth && depthBuffer(x) > grannyDist) {
                val gShade = max(20, min(200, (200 * (1.0 - grannyDist / 8.0)).toInt))
                g.setColor(new Color(gShade, gShade, (gShade * 0.9).toInt))
                g.fillRect(x, spriteTop, 1, spriteSize)

                if (grannyChasing) g.setColor(Color.RED) else g.setColor(Color.WHITE)
                val eyeSize = max(2, spriteSize / 8)
                g.fillRect(x, spriteTop + spriteSize / 5, 1, eyeSize)
              }
            }
          }

          // ? CENTER CROSSHAIR POINTER
          g.setColor(Color.WHITE)
          g.drawLine(ScreenWidth / 2 - 5, ScreenHeight / 2, ScreenWidth / 2 + 5, ScreenHeight / 2)
          g.drawLine(ScreenWidth / 2, ScreenHeight / 2 - 5, ScreenWidth / 2, ScreenHeight / 2 + 5)

          // ? FLOWERPOT IN HAND
          if (hasFlowerpotInHand) {
            val potY = ScreenHeight - 110 + horizonShift
            g.setColor(new Color(160, 60, 20))
            g.fillRect(ScreenWidth / 2 + 80, potY, 90, 80)
            g.setColor(new Color(30, 120, 30))
            g.fillOval(ScreenWidth / 2 + 95, potY - 40, 60, 50)

            g.setColor(Color.YELLOW)
            g.setFont(new Font("Arial", Font.BOLD, 14))
            g.drawString("Holding Flowerpot! Press 'SPACE' to Throw", ScreenWidth / 2 - 140, ScreenHeight - 30)
          }

          // HUD Indicators
          g.setColor(Color.RED)
          g.setFont(new Font("Arial", Font.BOLD, 16))
          g.drawString(s"DAY $currentDay / 5", ScreenWidth - 110, 30)

          if (isCrouching) {
            g.setColor(Color.CYAN)
            g.setFont(new Font("Arial", Font.BOLD, 14))
            g.drawString("[ CROUCHING ]", ScreenWidth / 2 - 50, ScreenHeight - 10)
          }

          if (grannyChasing) {
            g.setColor(Color.RED)
            g.setFont(new Font("Arial", Font.BOLD, 15))
            g.drawString("?? GRANNY SPOTTED YOU! SHE IS RUNNING!", ScreenWidth / 2 - 180, 40)
          } else if (flowerPotDropped) {
            g.setColor(Color.ORANGE)
            g.setFont(new Font("Arial", Font.BOLD, 15))
            g.drawString("?? POT SMASHED! Granny is checking noise...", ScreenWidth / 2 - 180, 40)
          }

          // Key HUD
          g.setColor(Color.WHITE)
          g.setFont(new Font("Arial", Font.BOLD, 13))
          if (hasKey) g.drawString("Key Collected: YES ?", 15, 25) else g.drawString("Key Collected: NO", 15, 25)
        }
      }
    }
  }

  // Game Logic Loop
  val gameLoop = new Timer(16, new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = {
      if (showingDayScreen) {
        dayScreenTimer -= 1
        if (dayScreenTimer <= 0) {
          showingDayScreen = false
        }
      } else if (gameStarted && !gameOver && !gameEscaped) {
        val moveSpeed = if (isCrouching) 0.02 else 0.04
        val rotSpeed = 0.04

        if (!isHiding) {
          if (leftKey) playerA -= rotSpeed
          if (rightKey) playerA += rotSpeed

          var newX = playerX
          var newY = playerY

          if (wPressed) { newX += cos(playerA) * moveSpeed; newY += sin(playerA) * moveSpeed }
          if (sPressed) { newX -= cos(playerA) * moveSpeed; newY -= sin(playerA) * moveSpeed }
          if (aPressed) { newX += sin(playerA) * moveSpeed; newY -= cos(playerA) * moveSpeed }
          if (dPressed) { newX -= sin(playerA) * moveSpeed; newY += cos(playerA) * moveSpeed }

          if (map(newY.toInt)(playerX.toInt) == 0 || map(newY.toInt)(playerX.toInt) == 3) playerY = newY
          if (map(playerY.toInt)(newX.toInt) == 0 || map(playerY.toInt)(newX.toInt) == 3) playerX = newX
        }

        // Line-of-sight check for Granny
        val distToPlayer = sqrt(pow(playerX - grannyX, 2) + pow(playerY - grannyY, 2))
        if (distToPlayer < 3.5 && !isHiding && !isCrouching) {
          grannyChasing = true
        }

        if (grannyChasing || flowerPotDropped) {
          val targetX = if (grannyChasing) playerX else droppedPotX
          val targetY = if (grannyChasing) playerY else droppedPotY

          val dx = targetX - grannyX
          val dy = targetY - grannyY
          val dist = sqrt(dx * dx + dy * dy)

          if (dist > 0.4) {
            val grannyNewX = grannyX + (dx / dist) * grannySpeed
            val grannyNewY = grannyY + (dy / dist) * grannySpeed

            if (map(grannyY.toInt)(grannyNewX.toInt) == 0) grannyX = grannyNewX
            if (map(grannyNewY.toInt)(grannyX.toInt) == 0) grannyY = grannyNewY
          } else if (flowerPotDropped && dist <= 0.4) {
            flowerPotDropped = false
          }
        }

        // ? CATCH LOGIC (ADVANCE TO NEXT DAY)
        if (distToPlayer < 0.5 && !isHiding) {
          if (currentDay < 5) {
            currentDay += 1
            respawnPlayerForNextDay()
          } else {
            gameOver = true // Game Over on Day 5
          }
        }
      }
      panel.repaint()
    }
  })

  def respawnPlayerForNextDay(): Unit = {
    playerX = 4.0
    playerY = 5.0
    playerA = -Pi / 2.0
    grannyX = 1.5
    grannyY = 1.5
    grannyChasing = false
    isHiding = false
    isCrouching = false
    flowerPotDropped = false
    showingDayScreen = true
    dayScreenTimer = 120 // Show Day Screen for 2 seconds
  }

  // Actions
  def handleAction(): Unit = {
    val frontX = (playerX + cos(playerA) * 0.8).toInt
    val frontY = (playerY + sin(playerA) * 0.8).toInt

    if (frontX >= 0 && frontX < mapWidth && frontY >= 0 && frontY < mapHeight) {
      val cell = map(frontY)(frontX)
      if (cell == 4) {
        hasKey = true
        map(frontY)(frontX) = 0
      } else if (cell == 2) {
        if (hasKey) gameEscaped = true
      } else if (cell == 6 && !hasFlowerpotInHand) {
        hasFlowerpotInHand = true
        map(frontY)(frontX) = 0
      }
    }
  }

  def throwFlowerpot(): Unit = {
    if (hasFlowerpotInHand) {
      hasFlowerpotInHand = false
      flowerPotDropped = true
      droppedPotX = playerX + cos(playerA) * 1.5
      droppedPotY = playerY + sin(playerA) * 1.5
    }
  }

  def toggleHide(): Unit = {
    val currentCell = map(playerY.toInt)(playerX.toInt)
    if (currentCell == 3 || isHiding) {
      isHiding = !isHiding
    }
  }

  def toggleCrouch(): Unit = {
    if (!isHiding) {
      isCrouching = !isCrouching
    }
  }

  def restartGame(): Unit = {
    currentDay = 1
    hasKey = false
    hasFlowerpotInHand = false
    gameOver = false
    gameEscaped = false
    gameStarted = true
    // Reset flowerpot on map if taken
    map(5)(1) = 6
    map(1)(6) = 4
    respawnPlayerForNextDay()
  }

  def run(): Unit = {
    panel.setPreferredSize(new java.awt.Dimension(ScreenWidth, ScreenHeight))
    panel.setFocusable(true)

    panel.addKeyListener(new KeyListener {
      override def keyTyped(e: KeyEvent): Unit = {}

      override def keyPressed(e: KeyEvent): Unit = {
        e.getKeyCode match {
          case KeyEvent.VK_ENTER => if (!gameStarted || gameOver || gameEscaped) restartGame()
          case KeyEvent.VK_W     => wPressed = true
          case KeyEvent.VK_S     => sPressed = true
          case KeyEvent.VK_A     => aPressed = true
          case KeyEvent.VK_D     => dPressed = true
          case KeyEvent.VK_LEFT  => leftKey = true
          case KeyEvent.VK_RIGHT => rightKey = true
          case KeyEvent.VK_E     => handleAction()
          case KeyEvent.VK_SPACE => throwFlowerpot()
          case KeyEvent.VK_H     => toggleHide()
          case KeyEvent.VK_C     => toggleCrouch()
          case _ =>
        }
      }

      override def keyReleased(e: KeyEvent): Unit = {
        e.getKeyCode match {
          case KeyEvent.VK_W     => wPressed = false
          case KeyEvent.VK_S     => sPressed = false
          case KeyEvent.VK_A     => aPressed = false
          case KeyEvent.VK_D     => dPressed = false
          case KeyEvent.VK_LEFT  => leftKey = false
          case KeyEvent.VK_RIGHT => rightKey = false
          case _ =>
        }
      }
    })

    frame.setContentPane(panel)
    frame.pack()
    frame.setLocationRelativeTo(null)
    frame.setVisible(true)
    panel.requestFocusInWindow()
    gameLoop.start()
  }

  def main(args: Array[String]): Unit = {
    run()
  }
}

// Start 5-Day Granny Game
Realistic5DayGrannyGame.run()