Code Sketch


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

object Realistic5DayGrannyGame {

  val ScreenWidth = 900
  val ScreenHeight = 700

  // Expanded 20x20 Map Legend (Huge House with Multiple Rooms & Corridors):
  // 0 = Floor, 1 = Wall, 2 = Main Exit Door (Blue on Map)
  // 3 = Bed (Hiding spot), 10 = Stairs Area, 11 = Drawer 1, 12 = Drawer 2
  val mapWidth = 20
  val mapHeight = 20
  var map: Array[Array[Int]] = Array(
    Array(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1),
    Array(1,3,3,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,1), // Bedroom with Beds (3)
    Array(1,3,3,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,1),
    Array(1,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,1),
    Array(1,1,0,1,1,0,0,0,1,1,1,1,1,0,1,1,1,1,0,1), // Big hallways
    Array(1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1),
    Array(1,0,11,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1), // Drawer 1 (Cutter & Hammer)
    Array(1,0,12,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1), // Drawer 2 (Safe Key & Chit Code)
    Array(1,0,0,0,0,0,0,1,1,1,1,1,1,0,1,0,0,0,0,1),
    Array(1,0,0,0,0,0,0,1,0,0,0,0,1,0,1,1,1,1,0,1), 
    Array(1,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,1),
    Array(1,0,0,0,0,0,0,1,0,10,10,0,1,0,0,0,0,0,0,1), // Stairs (10)
    Array(1,0,0,0,0,0,0,1,0,10,10,0,1,0,0,0,0,0,0,1),
    Array(1,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,1),
    Array(1,0,0,0,0,0,0,1,1,1,0,1,1,1,1,1,0,1,1,1),
    Array(1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,2,0,1), // Main Exit Door (2)
    Array(1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1),
    Array(1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1),
    Array(1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1),
    Array(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)
  )

  // Game States
  var gameStarted = false
  var gameOver = false
  var gameEscaped = false
  var showingStairs = false 
  var showingMap = false 
  var currentDay = 1
  var showingDayScreen = false
  var dayScreenTimer = 0

  // Player Settings & Inventory
  var playerX = 2.0
  var playerY = 2.0
  var playerA = 0.0
  val fov = Pi / 3.0
  var isHiding = true 
  
  var hasCutter = false
  var hasHammer = false
  var hasSafeKey = false
  var hasChitCode = false

  var drawer1Opened = false
  var drawer2Opened = false
  var actionMessage = ""
  var messageTimer = 0

  // Granny AI
  var grannyX = 15.0
  var grannyY = 15.0
  var grannyChasing = false
  val grannySpeed = 0.015 

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

  // GUI Canvas
  val frame = new JFrame("3D Granny Horror - Massive House Extension")
  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, 36))
        g.drawString("GRANNY: HUGE MANSION ESCAPE", ScreenWidth / 2 - 250, ScreenHeight / 2 - 120)

        g.setColor(Color.LIGHT_GRAY)
        g.setFont(new Font("Arial", Font.PLAIN, 16))
        g.drawString("Press ENTER to Get Up From Bed & Start", ScreenWidth / 2 - 160, ScreenHeight / 2 - 50)
        g.drawString("W,A,S,D : Walk | Left/Right : Turn", ScreenWidth / 2 - 140, ScreenHeight / 2 - 10)
        g.drawString("E : Search Drawers / Open Door | R : Hide in Bed", ScreenWidth / 2 - 190, ScreenHeight / 2 + 30)
        g.drawString("M : Toggle Mini-Map (Right Corner)", ScreenWidth / 2 - 140, ScreenHeight / 2 + 70)

      } else if (showingDayScreen) {
        g.setColor(Color.BLACK)
        g.fillRect(0, 0, ScreenWidth, ScreenHeight)

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

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

        g.setColor(Color.WHITE)
        g.setFont(new Font("Arial", Font.PLAIN, 18))
        g.drawString("Press ENTER to Restart", ScreenWidth / 2 - 100, ScreenHeight / 2 + 70)

      } else if (showingStairs) {
        // --- REALISTIC STAIRS WITH GRILLING ---
        g.setColor(new Color(15, 10, 5))
        g.fillRect(0, 0, ScreenWidth, ScreenHeight)

        g.setColor(new Color(45, 25, 12))
        g.fillRect(200, 60, ScreenWidth - 400, ScreenHeight - 120)

        val numSteps = 8
        for (i <- 0 until numSteps) {
          val y = 100 + i * 60
          val leftX = 220 + i * 30
          val rightX = ScreenWidth - 220 - i * 30
          val w = rightX - leftX
          val h = 45

          g.setColor(new Color(65, 35, 15))
          g.fillRect(leftX, y + 15, w, h - 15)

          g.setColor(new Color(150, 90, 40))
          g.fillRect(leftX, y, w, 15)

          g.setColor(new Color(190, 115, 50))
          g.fillRect(leftX, y, w, 4)

          g.setColor(new Color(30, 15, 5))
          g.drawRect(leftX, y, w, h)
        }

        g.setStroke(new BasicStroke(6))
        g.setColor(new Color(55, 30, 12))
        g.drawLine(210, 110, 410, 560) 
        g.drawLine(ScreenWidth - 210, 110, ScreenWidth - 410, 560) 

        g.setStroke(new BasicStroke(3))
        g.setColor(new Color(65, 35, 15))
        for (i <- 0 until 7) {
          val px = 230 + i * 28
          val py = 120 + i * 65
          g.drawLine(px, py, px, py + 50)

          val rpx = ScreenWidth - 230 - i * 28
          g.drawLine(rpx, py, rpx, py + 50)
        }

        g.setColor(Color.YELLOW)
        g.setFont(new Font("Arial", Font.BOLD, 28))
        g.drawString("STAIRS DOWN TO FREEDOM!", ScreenWidth / 2 - 190, 45)
        g.setFont(new Font("Arial", Font.PLAIN, 18))
        g.drawString("Press 'E' to go down the stairs and escape!", ScreenWidth / 2 - 190, ScreenHeight - 40)

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

      } else {
        // Ceiling & Floor
        g.setColor(new Color(50, 40, 30)) 
        g.fillRect(0, 0, ScreenWidth, ScreenHeight / 2)
        g.setColor(new Color(70, 70, 75)) 
        g.fillRect(0, ScreenHeight / 2, ScreenWidth, ScreenHeight / 2)

        val depthBuffer = new Array[Double](ScreenWidth)

        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 < 18.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 = 18.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)
          val floor = ScreenHeight - ceiling
          val wallHeight = floor - ceiling

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

          wallType match {
            case 1  => g.setColor(new Color((brightness * 0.85).toInt, (brightness * 0.6).toInt, (brightness * 0.35).toInt)) 
            case 2  => g.setColor(new Color(50, 20, 5))     // Main Exit Door
            case 3  => g.setColor(new Color((brightness * 0.7).toInt, (brightness * 0.4).toInt, (brightness * 0.2).toInt)) // Bed
            case 10 => g.setColor(new Color(150, 90, 40)) // Stairs marker
            case 11 => g.setColor(if (drawer1Opened) new Color(90, 50, 20) else new Color(130, 75, 35))
            case 12 => g.setColor(if (drawer2Opened) new Color(90, 50, 20) else new Color(130, 75, 35))
            case _  => g.setColor(Color.BLACK)
          }

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

          // Draw Golden Circle handle on main door
          if (wallType == 2 && abs(x - ScreenWidth / 2) < 15) {
            g.setColor(new Color(255, 215, 0)) 
            g.fillOval(ScreenWidth / 2 - 10, ScreenHeight / 2 - 10, 20, 20)
          }
        }

        // Render Granny Sprite
        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.0 * (1.0 + tan(grannyAngle) / tan(fov / 2.0))).toInt
          val spriteSize = min(ScreenWidth, (ScreenHeight / grannyDist).toInt)
          val spriteTop = (ScreenHeight / 2) - spriteSize / 2
          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(30, min(220, (220 * (1.0 - grannyDist / 10.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)
            }
          }
        }

        // Crosshair (+)
        g.setColor(Color.WHITE)
        g.drawLine(ScreenWidth / 2 - 8, ScreenHeight / 2, ScreenWidth / 2 + 8, ScreenHeight / 2)
        g.drawLine(ScreenWidth / 2, ScreenHeight / 2 - 8, ScreenWidth / 2, ScreenHeight / 2 + 8)

        // Check what player is looking at for interactive prompt
        val lookX = (playerX + cos(playerA) * 0.9).toInt
        val lookY = (playerY + sin(playerA) * 0.9).toInt
        if (lookX >= 0 && lookX < mapWidth && lookY >= 0 && lookY < mapHeight) {
          val targetCell = map(lookY)(lookX)
          if (targetCell == 11) {
            g.setColor(Color.YELLOW)
            g.setFont(new Font("Arial", Font.BOLD, 16))
            val msg = if (!drawer1Opened) "[ PRESS 'E' TO SEARCH DRAWER 1 ]" else "[ DRAWER 1 ALREADY SEARCHED ]"
            g.drawString(msg, ScreenWidth / 2 - 140, ScreenHeight / 2 + 50)
          } else if (targetCell == 12) {
            g.setColor(Color.YELLOW)
            g.setFont(new Font("Arial", Font.BOLD, 16))
            val msg = if (!drawer2Opened) "[ PRESS 'E' TO SEARCH DRAWER 2 ]" else "[ DRAWER 2 ALREADY SEARCHED ]"
            g.drawString(msg, ScreenWidth / 2 - 140, ScreenHeight / 2 + 50)
          } else if (targetCell == 2) {
            g.setColor(Color.YELLOW)
            g.setFont(new Font("Arial", Font.BOLD, 16))
            val prompt = if (hasCutter && hasHammer && hasSafeKey && hasChitCode) "[ PRESS 'E' TO AUTO-UNLOCK DOOR & ESCAPE ]"
                         else "[ LOCKED! COLLECT ALL 4 ITEMS FROM DRAWERS FIRST ]"
            g.drawString(prompt, ScreenWidth / 2 - 210, ScreenHeight / 2 + 50)
          } else if (targetCell == 3) {
            g.setColor(Color.YELLOW)
            g.setFont(new Font("Arial", Font.BOLD, 16))
            g.drawString("[ PRESS 'R' TO HIDE IN BED ]", ScreenWidth / 2 - 120, ScreenHeight / 2 + 50)
          }
        }

        // Temporary action message banner
        if (messageTimer > 0 && actionMessage.nonEmpty) {
          g.setColor(new Color(0, 0, 0, 200))
          g.fillRect(ScreenWidth / 2 - 250, ScreenHeight / 2 - 90, 500, 45)
          g.setColor(Color.GREEN)
          g.setFont(new Font("Arial", Font.BOLD, 15))
          g.drawString(actionMessage, ScreenWidth / 2 - 230, ScreenHeight / 2 - 62)
        }

        g.setColor(Color.RED)
        g.setFont(new Font("Arial", Font.BOLD, 18))
        g.drawString(s"DAY $currentDay / 5", ScreenWidth - 130, 35)

        if (isHiding) {
          g.setColor(Color.YELLOW)
          g.setFont(new Font("Arial", Font.BOLD, 16))
          g.drawString("[ HIDDEN IN BED - PRESS 'ENTER' OR 'R' TO GET UP / GRANNY LEFT ]", ScreenWidth / 2 - 260, ScreenHeight - 30)
        }

        if (grannyChasing) {
          g.setColor(Color.RED)
          g.setFont(new Font("Arial", Font.BOLD, 16))
          g.drawString("GRANNY IS CHASING YOU! (Hide in Bed with 'R' or Check M)", ScreenWidth / 2 - 230, 50)
        }

        // --- VISIBLE INVENTORY HUD ---
        g.setColor(new Color(0, 0, 0, 180))
        g.fillRect(20, ScreenHeight - 65, ScreenWidth - 40, 50)
        g.setColor(Color.YELLOW)
        g.drawRect(20, ScreenHeight - 65, ScreenWidth - 40, 50)

        g.setFont(new Font("Arial", Font.BOLD, 14))
        g.drawString("INVENTORY ITEMS:", 35, ScreenHeight - 35)

        g.setColor(if (hasCutter) new Color(0, 200, 0) else new Color(100, 100, 100))
        g.fillRect(180, ScreenHeight - 53, 75, 26)
        g.setColor(Color.WHITE)
        g.drawString("Cutter", 195, ScreenHeight - 35)

        g.setColor(if (hasHammer) new Color(0, 200, 0) else new Color(100, 100, 100))
        g.fillRect(270, ScreenHeight - 53, 75, 26)
        g.setColor(Color.WHITE)
        g.drawString("Hammer", 280, ScreenHeight - 35)

        g.setColor(if (hasSafeKey) new Color(0, 200, 0) else new Color(100, 100, 100))
        g.fillRect(360, ScreenHeight - 53, 85, 26)
        g.setColor(Color.WHITE)
        g.drawString("Safe Key", 370, ScreenHeight - 35)

        g.setColor(if (hasChitCode) new Color(0, 200, 0) else new Color(100, 100, 100))
        g.fillRect(460, ScreenHeight - 53, 90, 26)
        g.setColor(Color.WHITE)
        g.drawString("Chit Code", 470, ScreenHeight - 35)

        // --- RIGHT CORNER MINI MAP (If M pressed) ---
        if (showingMap) {
          val mapSize = 180
          val mapX = ScreenWidth - mapSize - 20
          val mapY = 50

          g.setColor(new Color(0, 0, 0, 220))
          g.fillRect(mapX, mapY, mapSize, mapSize)
          g.setColor(Color.YELLOW)
          g.drawRect(mapX, mapY, mapSize, mapSize)

          val tileSize = mapSize / mapWidth
          for (y <- 0 until mapHeight; x <- 0 until mapWidth) {
            map(y)(x) match {
              case 1 => g.setColor(Color.DARK_GRAY)
              case 2 => g.setColor(Color.BLUE) // Main Exit Door is BLUE on Map!
              case 3 => g.setColor(new Color(100, 50, 20))
              case 11 | 12 => g.setColor(new Color(130, 75, 35))
              case 10 => g.setColor(Color.ORANGE)
              case _ => g.setColor(Color.BLACK)
            }
            g.fillRect(mapX + x * tileSize, mapY + y * tileSize, tileSize - 1, tileSize - 1)
          }

          // Player (Green) & Granny (Red)
          g.setColor(Color.GREEN)
          g.fillOval(mapX + (playerX * tileSize).toInt - 3, mapY + (playerY * tileSize).toInt - 3, 6, 6)
          g.setColor(Color.RED)
          g.fillOval(mapX + (grannyX * tileSize).toInt - 3, mapY + (grannyY * tileSize).toInt - 3, 6, 6)
        }
      }
    }
  }

  def updateGame(): Unit = {
    if (messageTimer > 0) messageTimer -= 1

    if (showingDayScreen) {
      dayScreenTimer -= 1
      if (dayScreenTimer <= 0) {
        showingDayScreen = false
      }
    } else if (gameStarted && !gameOver && !gameEscaped && !showingStairs) {
      val moveSpeed = 0.09
      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 += sin(playerA) * moveSpeed }

        def isWalkable(c: Int): Boolean = c == 0 || c == 3 || c == 10 || c == 8 || c == 9 || c == 11 || c == 12
        if (isWalkable(map(playerY.toInt)(newX.toInt))) playerX = newX
        if (isWalkable(map(newY.toInt)(playerX.toInt))) playerY = newY
      }

      val distToPlayer = sqrt(pow(playerX - grannyX, 2) + pow(playerY - grannyY, 2))
      if (distToPlayer < 3.5 && !isHiding) {
        grannyChasing = true
      }

      if (grannyChasing && !isHiding) {
        val dx = playerX - grannyX
        val dy = playerY - grannyY
        val dist = sqrt(dx * dx + dy * dy)

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

          def isWalkableG(c: Int): Boolean = c == 0 || c == 3 || c == 10 || c == 8 || c == 9 || c == 11 || c == 12
          if (isWalkableG(map(grannyY.toInt)(grannyNewX.toInt))) grannyX = grannyNewX
          if (isWalkableG(map(grannyNewY.toInt)(grannyX.toInt))) grannyY = grannyNewY
        }
      }

      if (distToPlayer < 0.5 && !isHiding) {
        if (currentDay < 5) {
          currentDay += 1
          respawnPlayerForNextDay()
        } else {
          gameOver = true 
        }
      }
    }
    panel.repaint()
  }

  val gameLoop: Timer = new Timer(16, new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = {
      updateGame()
    }
  })

  def respawnPlayerForNextDay(): Unit = {
    playerX = 2.0
    playerY = 2.0
    playerA = 0.0
    grannyX = 15.0
    grannyY = 15.0
    grannyChasing = false
    isHiding = true 
    showingStairs = false
    showingMap = false
    showingDayScreen = true
    dayScreenTimer = 120 
  }

  def showMsg(msg: String): Unit = {
    actionMessage = msg
    messageTimer = 120
  }

  def handleAction(): Unit = {
    if (showingStairs) {
      gameEscaped = true
      showingStairs = false
      panel.repaint()
      return
    }

    val frontX = (playerX + cos(playerA) * 0.9).toInt
    val frontY = (playerY + sin(playerA) * 0.9).toInt

    if (frontX >= 0 && frontX < mapWidth && frontY >= 0 && frontY < mapHeight) {
      val cell = map(frontY)(frontX)
      if (cell == 11) {
        drawer1Opened = true
        hasCutter = true
        hasHammer = true
        showMsg("Found CUTTER & HAMMER in Drawer 1!")
      }
      else if (cell == 12) {
        drawer2Opened = true
        hasSafeKey = true
        hasChitCode = true
        showMsg("Found SAFE KEY & CHIT CODE in Drawer 2!")
      }
      else if (cell == 2) {
        if (hasCutter && hasHammer && hasSafeKey && hasChitCode) {
          showMsg("Sensor cut, Planks removed & Door unlocked automatically!")
          showingStairs = true
          panel.repaint()
        } else {
          showMsg("Door Locked! You need Cutter, Hammer, Safe Key & Chit Code.")
        }
      }
      else if (cell == 10) {
        showingStairs = true
        panel.repaint()
      }
    }
  }

  def toggleHide(): Unit = {
    val currentCell = map(playerY.toInt)(playerX.toInt)
    if (currentCell == 3 || isHiding) {
      isHiding = !isHiding
      if (isHiding) {
        grannyChasing = false
        grannyX = 15.0 // Reset Granny away
        grannyY = 15.0
        showMsg("Hidden in Bed! Granny lost you and left.")
      }
    }
  }

  def restartGame(): Unit = {
    currentDay = 1
    hasCutter = false
    hasHammer = false
    hasSafeKey = false
    hasChitCode = false
    drawer1Opened = false
    drawer2Opened = false
    gameOver = false
    gameEscaped = false
    showingStairs = false
    showingMap = false
    gameStarted = true
    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()
            } else if (isHiding) {
              isHiding = false 
            }
          case KeyEvent.VK_M     => showingMap = !showingMap 
          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_R     => toggleHide() 
          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()
  }
}

Realistic5DayGrannyGame.run()