Code Sketch


sniper_0718
By: Mhalsakant School
import javax.swing._
import java.awt.{Color, Dimension, Graphics, Graphics2D}
import java.awt.event._
import javax.sound.sampled._

class GamePanel extends JPanel with ActionListener {
  setPreferredSize(new Dimension(800, 600))
  setFocusable(true)
  requestFocusInWindow()

  // Game States
  var gameState = "START" // START, PLAYING, WIN, GAMEOVER
  var showMap = false

  // Player Stats & Position
  var posX = 1.5
  var posY = 1.5
  var angle = 0.0
  var playerHealth = 100
  var isJumping = false
  var jumpHeight = 0.0
  var jumpDirection = 1

  // Glue Wall (G Key)
  var glueWallActive = false
  var glueWallX = 0.0
  var glueWallY = 0.0

  // Gun Inventory
  val guns: scala.collection.immutable.List[String] = scala.collection.immutable.List("Pistol", "AK47", "Sniper", "Shotgun", "SMG")
  var selectedGunIndex = 1
  var activeGun: Option[String] = Some("AK47")
  var gunRotationAngle = 0.0

  // Bullets list
  var bullets: scala.collection.immutable.List[scala.collection.mutable.Map[String, Double]] = scala.collection.immutable.List()

  // 3 Killers placed on the map
  var targets = scala.collection.mutable.ListBuffer(
    scala.collection.mutable.Map[String, Any]("x" -> 6.5, "y" -> 1.5, "alive" -> true),
    scala.collection.mutable.Map[String, Any]("x" -> 3.5, "y" -> 5.5, "alive" -> true),
    scala.collection.mutable.Map[String, Any]("x" -> 6.5, "y" -> 5.5, "alive" -> true)
  )

  // 3D Map Grid: 1 = Wall, 2 = Container (Jump over with SPACE), 0 = Open Space
  val worldMap = Array(
    Array(1, 1, 1, 1, 1, 1, 1, 1),
    Array(1, 0, 0, 2, 0, 0, 0, 1),
    Array(1, 0, 1, 0, 1, 0, 2, 1),
    Array(1, 0, 0, 0, 2, 0, 0, 1),
    Array(1, 0, 1, 0, 1, 0, 0, 1),
    Array(1, 0, 2, 0, 0, 0, 0, 1),
    Array(1, 1, 1, 1, 1, 1, 1, 1)
  )

  val timer = new Timer(30, this)
  timer.start()

  // Gunshot Sound Effect
  def playGunshotSound(): Unit = {
    new Thread(new Runnable {
      def run(): Unit = {
        try {
          val sampleRate = 8000f
          val buf = new Array[Byte](800)
          val af = new AudioFormat(sampleRate, 8, 1, true, false)
          val td = AudioSystem.getSourceDataLine(af)
          td.open(af)
          td.start()
          for (i <- buf.indices) {
            buf(i) = ((Math.random() * 255) - 128).toByte
          }
          td.write(buf, 0, buf.length)
          td.drain()
          td.close()
        } catch {
          case _: Exception => 
        }
      }
    }).start()
  }

  // Keyboard Controls
  addKeyListener(new KeyAdapter {
    override def keyPressed(e: KeyEvent): Unit = {
      val moveSpeed = 0.2
      val rotSpeed = 0.1

      e.getKeyCode match {
        case KeyEvent.VK_ENTER =>
          if (gameState == "START" || gameState == "WIN" || gameState == "GAMEOVER") {
            resetGame()
            gameState = "PLAYING"
          } else {
            activeGun = Some(guns(selectedGunIndex))
          }
        case KeyEvent.VK_F => // Shoot
          if (gameState == "PLAYING") {
            playGunshotSound()
            bullets = bullets :+ scala.collection.mutable.Map(
              "x" -> posX, 
              "y" -> posY, 
              "dirX" -> Math.cos(angle), 
              "dirY" -> Math.sin(angle)
            )
          }
        case KeyEvent.VK_SPACE => // Jump over containers
          if (gameState == "PLAYING" && !isJumping) {
            isJumping = true
          }
        case KeyEvent.VK_G => // Glue Wall (White Circle)
          if (gameState == "PLAYING") {
            glueWallActive = true
            glueWallX = posX + Math.cos(angle) * 1.0
            glueWallY = posY + Math.sin(angle) * 1.0
          }
        case KeyEvent.VK_M =>
          if (gameState == "PLAYING") showMap = !showMap
        case KeyEvent.VK_W =>
          if (gameState == "PLAYING") {
            val newX = posX + Math.cos(angle) * moveSpeed
            val newY = posY + Math.sin(angle) * moveSpeed
            val tile = worldMap(newY.toInt)(newX.toInt)
            if (tile == 0 || (tile == 2 && isJumping)) { 
              posX = newX; posY = newY 
            }
          }
        case KeyEvent.VK_S =>
          if (gameState == "PLAYING") {
            val newX = posX - Math.cos(angle) * moveSpeed
            val newY = posY - Math.sin(angle) * moveSpeed
            val tile = worldMap(newY.toInt)(newX.toInt)
            if (tile == 0 || (tile == 2 && isJumping)) { 
              posX = newX; posY = newY 
            }
          }
        case KeyEvent.VK_A =>
          if (gameState == "PLAYING") angle -= rotSpeed
        case KeyEvent.VK_D =>
          if (gameState == "PLAYING") angle += rotSpeed
        case _ =>
      }
      repaint()
    }
  })

  // Mouse Listener for Gun Box
  addMouseListener(new MouseAdapter {
    override def mouseClicked(e: MouseEvent): Unit = {
      requestFocusInWindow()
      if (gameState == "PLAYING" && e.getX >= 620 && e.getX <= 780 && e.getY >= 20 && e.getY <= 180) {
        val clickedIndex = ((e.getY - 40) / 25).toInt
        if (clickedIndex >= 0 && clickedIndex < guns.length) {
          selectedGunIndex = clickedIndex
          activeGun = Some(guns(selectedGunIndex))
          repaint()
        }
      }
    }
  })

  def resetGame(): Unit = {
    posX = 1.5
    posY = 1.5
    angle = 0.0
    playerHealth = 100
    isJumping = false
    jumpHeight = 0.0
    glueWallActive = false
    bullets = List()
    targets(0)("alive") = true
    targets(1)("alive") = true
    targets(2)("alive") = true
  }

  override def actionPerformed(e: ActionEvent): Unit = {
    if (gameState == "PLAYING") {
      gunRotationAngle += 0.08
      
      // Jump Physics Animation
      if (isJumping) {
        jumpHeight += jumpDirection * 15
        if (jumpHeight >= 45) jumpDirection = -1
        if (jumpHeight <= 0) {
          jumpHeight = 0
          jumpDirection = 1
          isJumping = false
        }
      }

      // 1. Move Player Bullets
      bullets = bullets.map { b =>
        b("x") = b("x") + b("dirX") * 0.4
        b("y") = b("y") + b("dirY") * 0.4
        b
      }.filter { b =>
        val bx = b("x").toInt
        val by = b("y").toInt
        bx >= 0 && bx < 8 && by >= 0 && by < 7 && worldMap(by)(bx) != 1
      }

      // 2. Killers Collision Check & Counter Attack
      var activeBullets = List[scala.collection.mutable.Map[String, Double]]()
      for (b <- bullets) {
        var hit = false
        targets.foreach { t =>
          if (t("alive").asInstanceOf[Boolean]) {
            val dist = Math.hypot(t("x").asInstanceOf[Double] - b("x"), t("y").asInstanceOf[Double] - b("y"))
            if (dist < 0.6) {
              t("alive") = false
              hit = true
            }
          }
        }
        if (!hit) activeBullets = activeBullets :+ b
      }
      bullets = activeBullets

      // Killers attack player if close
      targets.foreach { t =>
        if (t("alive").asInstanceOf[Boolean]) {
          val distToPlayer = Math.hypot(t("x").asInstanceOf[Double] - posX, t("y").asInstanceOf[Double] - posY)
          if (distToPlayer < 1.2) {
            // Check if protected by Glue Wall
            val distToGlue = if (glueWallActive) Math.hypot(glueWallX - posX, glueWallY - posY) else 999.0
            if (!glueWallActive || distToGlue > 1.0) {
              playerHealth -= 1
              if (playerHealth <= 0) {
                gameState = "GAMEOVER"
              }
            }
          }
        }
      }

      // Check Win condition
      if (targets.forall(t => !t("alive").asInstanceOf[Boolean])) {
        gameState = "WIN"
      }
    }
    repaint()
  }

  override def paintComponent(g: Graphics): Unit = {
    super.paintComponent(g)
    val g2d = g.asInstanceOf[Graphics2D]

    val width = getWidth
    val height = getHeight

    if (gameState == "START") {
      g2d.setColor(new Color(40, 20, 10))
      g2d.fillRect(0, 0, width, height)
      g2d.setColor(Color.WHITE)
      g2d.drawString("3D KILLER GAME - CONTAINERS & GLUE WALL", width / 2 - 135, height / 2 - 40)
      g2d.setColor(Color.YELLOW)
      g2d.drawString("PRESS ENTER TO START", width / 2 - 90, height / 2)
    } else if (gameState == "WIN") {
      g2d.setColor(new Color(40, 20, 10))
      g2d.fillRect(0, 0, width, height)
      g2d.setColor(Color.GREEN)
      g2d.drawString("VICTORY! ALL KILLERS ELIMINATED.", width / 2 - 110, height / 2 - 20)
      g2d.setColor(Color.WHITE)
      g2d.drawString("PRESS ENTER TO RESTART", width / 2 - 85, height / 2 + 20)
    } else if (gameState == "GAMEOVER") {
      g2d.setColor(Color.BLACK)
      g2d.fillRect(0, 0, width, height)
      g2d.setColor(Color.RED)
      g2d.drawString("GAME OVER! KILLERS GOT YOU.", width / 2 - 95, height / 2 - 20)
      g2d.setColor(Color.WHITE)
      g2d.drawString("PRESS ENTER TO RETRY", width / 2 - 75, height / 2 + 20)
    } else {
      // Brown Theme Ceiling & Floor (adjusted with jump height)
      g2d.setColor(new Color(60, 30, 10))
      g2d.fillRect(0, 0, width, (height / 2 + jumpHeight).toInt)
      g2d.setColor(new Color(90, 50, 20))
      g2d.fillRect(0, (height / 2 + jumpHeight).toInt, width, height - (height / 2 + jumpHeight).toInt)

      // Raycasting Walls & Containers
      val numRays = width
      val fov = Math.PI / 3
      for (i <- 0 until numRays) {
        val rayAngle = (angle - fov / 2.0) + (i.toDouble / numRays.toDouble) * fov
        var distance = 0.0
        var hitType = 0 // 1 = Wall, 2 = Container

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

        while (hitType == 0 && distance < 16.0) {
          distance += 0.05
          val testX = (posX + eyeX * distance).toInt
          val testY = (posY + eyeY * distance).toInt
          if (testX < 0 || testX >= 8 || testY < 0 || testY >= 7) {
            hitType = 1
            distance = 16.0
          } else {
            val t = worldMap(testY)(testX)
            if (t > 0) hitType = t
          }
        }

        val correctedDist = distance * Math.cos(rayAngle - angle)
        val wallHeight = Math.min(height, (height / correctedDist).toInt)
        val ceiling = (height / 2.0) - (wallHeight / 2.0) + jumpHeight

        if (hitType == 1) {
          val shade = Math.max(20, 200 - (distance * 12).toInt)
          g2d.setColor(new Color(shade, shade / 2, 10))
        } else {
          // Container Color (Orange/Yellowish Box)
          g2d.setColor(new Color(200, 130, 30))
        }
        g2d.fillRect(i, ceiling.toInt, 1, wallHeight)
      }

      // Render Killers in POV
      targets.foreach { t =>
        if (t("alive").asInstanceOf[Boolean]) {
          val tx = t("x").asInstanceOf[Double] - posX
          val ty = t("y").asInstanceOf[Double] - posY
          
          var spriteAngle = Math.atan2(ty, tx) - angle
          while (spriteAngle < -Math.PI) spriteAngle += 2 * Math.PI
          while (spriteAngle > Math.PI) spriteAngle -= 2 * Math.PI

          val dist = Math.hypot(tx, ty)
          if (dist > 0.2 && Math.abs(spriteAngle) < Math.PI / 2) {
            val screenX = ((width / 2.0) + (Math.tan(spriteAngle) * (width / 2.0))).toInt
            val spriteHeight = Math.min(height, (height / dist)).toInt
            val spriteWidth = spriteHeight
            
            g2d.setColor(Color.RED)
            g2d.fillOval(screenX - spriteWidth / 4, (height / 2) - (spriteHeight / 2) + jumpHeight.toInt, spriteWidth / 2, spriteHeight / 2)
            g2d.setColor(new Color(150, 0, 0))
            g2d.fillRect(screenX - spriteWidth / 5, (height / 2) + jumpHeight.toInt, spriteWidth * 2 / 5, spriteHeight / 2)
          }
        }
      }

      // Render Glue Wall if active (White Circle)
      if (glueWallActive) {
        val gx = glueWallX - posX
        val gy = glueWallY - posY
        var gAngle = Math.atan2(gy, gx) - angle
        while (gAngle < -Math.PI) gAngle += 2 * Math.PI
        while (gAngle > Math.PI) gAngle -= 2 * Math.PI
        val gDist = Math.hypot(gx, gy)
        if (gDist > 0.2 && Math.abs(gAngle) < Math.PI / 2) {
          val gScreenX = ((width / 2.0) + (Math.tan(gAngle) * (width / 2.0))).toInt
          val gSize = Math.max(10, (100.0 / gDist).toInt)
          g2d.setColor(Color.WHITE)
          g2d.drawOval(gScreenX - gSize / 2, (height / 2) - gSize / 2 + jumpHeight.toInt, gSize, gSize)
        }
      }

      // Center Crosshair
      g2d.setColor(Color.WHITE)
      g2d.drawLine(width / 2 - 10, height / 2 + jumpHeight.toInt, width / 2 + 10, height / 2 + jumpHeight.toInt)
      g2d.drawLine(width / 2, height / 2 - 10 + jumpHeight.toInt, width / 2, height / 2 + 10 + jumpHeight.toInt)

      // Active Gun in Hands
      activeGun.foreach { gun =>
        g2d.setColor(Color.DARK_GRAY)
        g2d.fillRect(width / 2 - 30, height - 120, 60, 100)
        g2d.setColor(Color.BLACK)
        g2d.fillRect(width / 2 - 10, height - 140, 20, 30)
        g2d.setColor(Color.WHITE)
        g2d.drawString(s"Equipped: $gun", width / 2 - 50, height - 130)
      }

      // Health Bar & Inventory Box
      g2d.setColor(Color.RED)
      g2d.fillRect(20, 15, 180, 25)
      g2d.setColor(Color.GREEN)
      g2d.fillRect(20, 15, playerHealth * 180 / 100, 25)
      g2d.setColor(Color.WHITE)
      g2d.drawRect(20, 15, 180, 25)
      g2d.drawString(s"Health: $playerHealth%", 70, 32)

      // Right-Side Gun Inventory Box
      g2d.setColor(new Color(0, 0, 0, 220))
      g2d.fillRect(610, 15, 175, 175)
      g2d.setColor(Color.LIGHT_GRAY)
      g2d.drawRect(610, 15, 175, 175)
      g2d.drawString("3D Gun Inventory", 630, 35)

      for (i <- guns.indices) {
        if (i == selectedGunIndex) g2d.setColor(Color.GREEN) else g2d.setColor(Color.WHITE)
        val xOffset = (Math.sin(gunRotationAngle + i) * 8).toInt
        g2d.drawString(s"${i + 1}. ${guns(i)}", 625 + xOffset, 70 + (i * 25))
      }

      // Minimap ('M' to toggle)
      if (showMap) {
        g2d.setColor(new Color(0, 0, 0, 220))
        g2d.fillRect(20, 50, 150, 150)
        g2d.setColor(Color.WHITE)
        g2d.drawRect(20, 50, 150, 150)
        g2d.drawString("MAP (Blue:You|Orange:Box|Red:Killer)", 22, 68)

        g2d.setColor(Color.BLUE)
        g2d.fillOval(20 + (posX * 18).toInt, 50 + (posY * 18).toInt, 8, 8)

        targets.foreach { t =>
          if (t("alive").asInstanceOf[Boolean]) {
            g2d.setColor(Color.RED)
            g2d.fillOval(20 + (t("x").asInstanceOf[Double] * 18).toInt, 50 + (t("y").asInstanceOf[Double] * 18).toInt, 6, 6)
          }
        }
      }

      g2d.setColor(Color.WHITE)
      g2d.drawString("Controls: WASD=Move | SPACE=Jump Box | F=Shoot | G=Glue Wall | M=Map", 15, 575)
    }
  }
}

SwingUtilities.invokeLater(new Runnable {
  def run(): Unit = {
    val frame = new JFrame("True 3D Game - Containers, Jump & Glue Wall")
    val panel = new GamePanel()
    frame.add(panel)
    frame.pack()
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
    frame.setResizable(false)
    frame.setLocationRelativeTo(null)
    frame.setVisible(true)
  }
})