Code Sketch
yoiiiiiii
Category: Programming
import javax.swing._
import java.awt._
import java.awt.event._
import java.awt.geom._
import java.util.Random
import scala.collection.mutable.ArrayBuffer
// ================================================================
// NIGHTFALL: HORROR SURVIVAL
// Original Java2D horror battle-royale style game
// No external assets required
// ================================================================
val GAME_W = 1440
val GAME_H = 860
val HUD_H = 90
val WORLD_W = GAME_W
val WORLD_H = GAME_H - HUD_H
val PLAYER_RADIUS = 17.0
val PLAYER_SPEED = 4.2
val SPRINT_SPEED = 6.8
val BULLET_SPEED = 15.0
val ENEMY_SPEED = 1.65
val MAX_AMMO = 30
val MAX_HP = 100.0
val MAX_ARMOR = 100.0
val ZONE_START = 600.0
val ZONE_MIN = 105.0
val frame = new JFrame("NIGHTFALL : HORROR SURVIVAL")
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
frame.setSize(GAME_W, GAME_H)
frame.setResizable(false)
frame.setLocationRelativeTo(null)
// ================================================================
// GAME STATE
// ================================================================
var gameState = "MENU"
var difficulty = "NORMAL"
var paused = false
var running = true
var playerX = WORLD_W / 2.0
var playerY = WORLD_H / 2.0
var playerAngle = 0.0
var playerHP = MAX_HP
var playerArmor = 25.0
var stamina = 100.0
var ammo = MAX_AMMO
var reserveAmmo = 120
var medkits = 2
var kills = 0
var aliveEnemies = 18
var survivalTime = 0.0
var zoneRadius = ZONE_START
var zoneCenterX = WORLD_W / 2.0
var zoneCenterY = WORLD_H / 2.0
var mouseX = WORLD_W / 2
var mouseY = WORLD_H / 2
var mousePressedNow = false
var lastShotTick = 0L
var reloadTicks = 0
var reloadMax = 0
var flashTicks = 0
var damageFlash = 0
var screenShake = 0.0
var message = "SURVIVE THE NIGHT"
var messageTicks = 240
var animTick = 0L
var lastDamageTick = -100L
var showMap = true
var showHelp = false
var difficultyEnemyHealth = 1.0
var difficultyEnemyDamage = 1.0
var difficultyEnemySpeed = 1.0
var difficultyZoneDamage = 1.0
var random = new Random()
// ================================================================
// DATA TYPES
// ================================================================
case class Bullet(
var x: Double,
var y: Double,
var dx: Double,
var dy: Double,
var life: Int,
var fromPlayer: Boolean,
var damage: Double
)
case class Enemy(
var x: Double,
var y: Double,
var angle: Double,
var hp: Double,
var maxHp: Double,
var speed: Double,
var cooldown: Int,
var kind: String,
var state: String,
var fear: Double
)
case class Loot(
var x: Double,
var y: Double,
var kind: String,
var amount: Int,
var taken: Boolean
)
case class Wall(
x: Int,
y: Int,
w: Int,
h: Int
)
// ================================================================
// COLLECTIONS
// ================================================================
val bullets = ArrayBuffer[Bullet]()
val enemies = ArrayBuffer[Enemy]()
val loot = ArrayBuffer[Loot]()
val walls = ArrayBuffer[Wall]()
// ================================================================
// BASIC HELPERS
// ================================================================
def clamp(
v: Double,
lo: Double,
hi: Double
): Double = {
Math.max(
lo,
Math.min(
hi,
v
)
)
}
def dist(
x1: Double,
y1: Double,
x2: Double,
y2: Double
): Double = {
val dx = x1 - x2
val dy = y1 - y2
Math.sqrt(
dx * dx +
dy * dy
)
}
def normAngle(
a: Double
): Double = {
var v = a
while (v > Math.PI) {
v -= Math.PI * 2.0
}
while (v < -Math.PI) {
v += Math.PI * 2.0
}
v
}
def say(
s: String,
ticks: Int
): Unit = {
message = s
messageTicks = ticks
}
def randomDouble(
min: Double,
max: Double
): Double = {
min +
random.nextDouble() *
(max - min)
}
def randomInt(
min: Int,
max: Int
): Int = {
min +
random.nextInt(
Math.max(
1,
max - min + 1
)
)
}
// ================================================================
// DIFFICULTY
// ================================================================
def setDifficulty(
d: String
): Unit = {
difficulty = d
if (d == "EASY") {
difficultyEnemyHealth = 0.80
difficultyEnemyDamage = 0.65
difficultyEnemySpeed = 0.78
difficultyZoneDamage = 0.65
}
else if (d == "HARD") {
difficultyEnemyHealth = 1.45
difficultyEnemyDamage = 1.45
difficultyEnemySpeed = 1.28
difficultyZoneDamage = 1.40
}
else {
difficultyEnemyHealth = 1.0
difficultyEnemyDamage = 1.0
difficultyEnemySpeed = 1.0
difficultyZoneDamage = 1.0
}
}
// ================================================================
// COLLISION
// ================================================================
def circleRectCollision(
cx: Double,
cy: Double,
r: Double,
wall: Wall
): Boolean = {
val nearestX =
clamp(
cx,
wall.x.toDouble,
(wall.x + wall.w).toDouble
)
val nearestY =
clamp(
cy,
wall.y.toDouble,
(wall.y + wall.h).toDouble
)
val dx =
cx - nearestX
val dy =
cy - nearestY
dx * dx +
dy * dy <
r * r
}
def blocked(
x: Double,
y: Double,
radius: Double
): Boolean = {
if (
x < radius ||
y < radius ||
x > WORLD_W - radius ||
y > WORLD_H - radius
) {
return true
}
var i = 0
while (
i < walls.length
) {
if (
circleRectCollision(
x,
y,
radius,
walls(i)
)
) {
return true
}
i += 1
}
false
}
def moveObject(
oldX: Double,
oldY: Double,
newX: Double,
newY: Double,
radius: Double
): (Double, Double) = {
var nx = newX
var ny = newY
if (
blocked(
nx,
oldY,
radius
)
) {
nx = oldX
}
if (
blocked(
nx,
ny,
radius
)
) {
ny = oldY
}
(
nx,
ny
)
}
def lineBlocked(
x1: Double,
y1: Double,
x2: Double,
y2: Double
): Boolean = {
val d =
dist(
x1,
y1,
x2,
y2
)
val steps =
Math.max(
1,
(
d / 10.0
).toInt
)
var i = 1
while (
i < steps
) {
val t =
i.toDouble /
steps.toDouble
val x =
x1 +
(
x2 -
x1
) *
t
val y =
y1 +
(
y2 -
y1
) *
t
if (
blocked(
x,
y,
2.0
)
) {
return true
}
i += 1
}
false
}
// ================================================================
// WORLD GENERATION
// ================================================================
def buildWorld(): Unit = {
walls.clear()
loot.clear()
enemies.clear()
bullets.clear()
// Outer atmosphere walls
walls += Wall(
90,
90,
1260,
28
)
walls += Wall(
90,
WORLD_H - 118,
1260,
28
)
walls += Wall(
90,
90,
28,
610
)
walls += Wall(
1322,
90,
28,
610
)
// Large abandoned-house structures
walls += Wall(
250,
160,
220,
35
)
walls += Wall(
250,
160,
35,
180
)
walls += Wall(
435,
160,
35,
180
)
walls += Wall(
970,
160,
220,
35
)
walls += Wall(
1155,
160,
35,
180
)
walls += Wall(
970,
305,
220,
35
)
walls += Wall(
250,
450,
300,
35
)
walls += Wall(
250,
450,
35,
170
)
walls += Wall(
515,
450,
35,
170
)
walls += Wall(
900,
450,
290,
35
)
walls += Wall(
1155,
450,
35,
170
)
walls += Wall(
900,
585,
290,
35
)
// Small barriers
walls += Wall(
610,
120,
120,
28
)
walls += Wall(
710,
260,
28,
130
)
walls += Wall(
810,
410,
28,
150
)
walls += Wall(
650,
590,
150,
28
)
walls += Wall(
580,
330,
110,
28
)
// Loot
loot += Loot(
175,
145,
"AMMO",
30,
false
)
loot += Loot(
515,
125,
"MEDKIT",
1,
false
)
loot += Loot(
770,
135,
"ARMOR",
35,
false
)
loot += Loot(
1240,
150,
"AMMO",
45,
false
)
loot += Loot(
1210,
370,
"MEDKIT",
1,
false
)
loot += Loot(
190,
390,
"ARMOR",
30,
false
)
loot += Loot(
620,
390,
"AMMO",
35,
false
)
loot += Loot(
820,
600,
"MEDKIT",
1,
false
)
loot += Loot(
1240,
640,
"AMMO",
40,
false
)
loot += Loot(
180,
640,
"MEDKIT",
1,
false
)
loot += Loot(
700,
680,
"ARMOR",
40,
false
)
loot += Loot(
1000,
380,
"AMMO",
35,
false
)
loot += Loot(
870,
210,
"MEDKIT",
1,
false
)
spawnEnemies()
}
def validSpawn(
x: Double,
y: Double
): Boolean = {
!blocked(
x,
y,
22.0
) &&
dist(
x,
y,
playerX,
playerY
) > 220.0
}
def spawnEnemies(): Unit = {
var created = 0
var tries = 0
while (
created < 18 &&
tries < 4000
) {
tries += 1
val x =
randomDouble(
140.0,
1300.0
)
val y =
randomDouble(
130.0,
WORLD_H - 145.0
)
if (
validSpawn(
x,
y
)
) {
val kind =
if (
created % 5 ==
0
)
"BRUTE"
else if (
created % 3 ==
0
)
"STALKER"
else
"HUNTER"
val multiplier =
if (
kind == "BRUTE"
)
1.55
else if (
kind == "STALKER"
)
0.82
else
1.0
val hp =
70.0 *
multiplier *
difficultyEnemyHealth
val speed =
1.3 *
multiplier *
difficultyEnemySpeed
enemies += Enemy(
x,
y,
randomDouble(
-Math.PI,
Math.PI
),
hp,
hp,
speed,
randomInt(
20,
100
),
kind,
"HUNT",
0.0
)
created += 1
}
}
aliveEnemies =
enemies.length
}
buildWorld()
// ================================================================
// RESET GAME
// ================================================================
def resetGame(): Unit = {
playerX =
WORLD_W / 2.0
playerY =
WORLD_H / 2.0
playerAngle =
0.0
playerHP =
100.0
playerArmor =
25.0
stamina =
100.0
ammo =
MAX_AMMO
reserveAmmo =
120
medkits =
2
kills =
0
survivalTime =
0.0
zoneRadius =
ZONE_START
zoneCenterX =
WORLD_W / 2.0
zoneCenterY =
WORLD_H / 2.0
paused =
false
running =
true
reloadTicks =
0
reloadMax =
0
flashTicks =
0
damageFlash =
0
screenShake =
0.0
animTick =
0L
lastShotTick =
-100L
lastDamageTick =
-100L
showHelp =
false
showMap =
true
mousePressedNow =
false
setDifficulty(
difficulty
)
buildWorld()
say(
"THE NIGHT HAS BEGUN. SURVIVE.",
260
)
}
def startGame(): Unit = {
resetGame()
gameState =
"PLAYING"
frame.requestFocus()
}
// ================================================================
// PLAYER
// ================================================================
def updatePlayer(): Unit = {
if (
paused
) {
return
}
var mx = 0.0
var my = 0.0
if (
keyDown(
KeyEvent.VK_W
)
) {
my -= 1.0
}
if (
keyDown(
KeyEvent.VK_S
)
) {
my += 1.0
}
if (
keyDown(
KeyEvent.VK_A
)
) {
mx -= 1.0
}
if (
keyDown(
KeyEvent.VK_D
)
) {
mx += 1.0
}
val moving =
mx != 0.0 ||
my != 0.0
val sprinting =
keyDown(
KeyEvent.VK_SHIFT
) &&
stamina > 2.0 &&
moving
var speed =
PLAYER_SPEED
if (
sprinting
) {
speed =
SPRINT_SPEED
stamina =
clamp(
stamina -
0.95,
0.0,
100.0
)
}
else {
stamina =
clamp(
stamina +
0.42,
0.0,
100.0
)
}
if (
moving
) {
val len =
Math.sqrt(
mx * mx +
my * my
)
mx /=
len
my /=
len
val pos =
moveObject(
playerX,
playerY,
playerX +
mx *
speed,
playerY +
my *
speed,
PLAYER_RADIUS
)
playerX =
pos._1
playerY =
pos._2
}
playerAngle =
Math.atan2(
mouseY -
playerY,
mouseX -
playerX
)
if (
mousePressedNow &&
reloadTicks <= 0
) {
shoot()
}
if (
keyPressedOnce(
KeyEvent.VK_R
)
) {
startReload()
}
if (
keyPressedOnce(
KeyEvent.VK_Q
)
) {
useMedkit()
}
if (
keyPressedOnce(
KeyEvent.VK_E
)
) {
pickupNearby()
}
}
// ================================================================
// KEYBOARD STATE
// ================================================================
val keyStates =
Array.fill[Boolean](
512
)(false)
val keyPressedStates =
Array.fill[Boolean](
512
)(false)
def keyDown(
code: Int
): Boolean = {
if (
code >= 0 &&
code < keyStates.length
) {
keyStates(code)
}
else {
false
}
}
def keyPressedOnce(
code: Int
): Boolean = {
if (
code >= 0 &&
code < keyPressedStates.length
) {
if (
keyPressedStates(code)
) {
keyPressedStates(code) =
false
true
}
else {
false
}
}
else {
false
}
}
// ================================================================
// SHOOTING
// ================================================================
def shoot(): Unit = {
if (
reloadTicks > 0
) {
return
}
if (
ammo <= 0
) {
say(
"NO AMMO - PRESS R TO RELOAD",
80
)
return
}
if (
animTick -
lastShotTick <
7L
) {
return
}
lastShotTick =
animTick
ammo -=
1
val spread =
randomDouble(
-0.035,
0.035
)
val a =
playerAngle +
spread
val dx =
Math.cos(a)
val dy =
Math.sin(a)
bullets += Bullet(
playerX +
dx *
26.0,
playerY +
dy *
26.0,
dx *
BULLET_SPEED,
dy *
BULLET_SPEED,
90,
true,
28.0
)
flashTicks =
3
screenShake =
clamp(
screenShake +
1.1,
0.0,
8.0
)
}
def startReload(): Unit = {
if (
reloadTicks > 0
) {
return
}
if (
ammo >= MAX_AMMO
) {
return
}
if (
reserveAmmo <= 0
) {
say(
"NO RESERVE AMMO",
80
)
return
}
reloadMax =
65
reloadTicks =
reloadMax
say(
"RELOADING...",
70
)
}
def finishReload(): Unit = {
val need =
MAX_AMMO -
ammo
val give =
Math.min(
need,
reserveAmmo
)
ammo +=
give
reserveAmmo -=
give
say(
"MAGAZINE READY",
65
)
}
def useMedkit(): Unit = {
if (
medkits <= 0
) {
say(
"NO MEDKIT",
70
)
return
}
if (
playerHP >=
99.0
) {
say(
"HEALTH ALREADY FULL",
70
)
return
}
medkits -=
1
playerHP =
clamp(
playerHP +
45.0,
0.0,
MAX_HP
)
say(
"MEDKIT USED +45 HP",
90
)
}
// ================================================================
// LOOT
// ================================================================
def pickupNearby(): Unit = {
var bestIndex =
-1
var bestDistance =
55.0
var i =
0
while (
i < loot.length
) {
if (
!loot(i).taken
) {
val d =
dist(
playerX,
playerY,
loot(i).x,
loot(i).y
)
if (
d < bestDistance
) {
bestDistance =
d
bestIndex =
i
}
}
i +=
1
}
if (
bestIndex >= 0
) {
val item =
loot(bestIndex)
item.taken =
true
if (
item.kind ==
"AMMO"
) {
reserveAmmo =
Math.min(
240,
reserveAmmo +
item.amount
)
say(
"AMMO +" +
item.amount,
80
)
}
else if (
item.kind ==
"MEDKIT"
) {
medkits =
Math.min(
5,
medkits +
item.amount
)
say(
"MEDKIT +1",
80
)
}
else if (
item.kind ==
"ARMOR"
) {
playerArmor =
clamp(
playerArmor +
item.amount.toDouble,
0.0,
MAX_ARMOR
)
say(
"ARMOR +" +
item.amount,
80
)
}
}
else {
say(
"NO LOOT NEARBY",
70
)
}
}
// ================================================================
// BULLET UPDATE
// ================================================================
def updateBullets(): Unit = {
var i =
bullets.length -
1
while (
i >= 0
) {
val b =
bullets(i)
b.x +=
b.dx
b.y +=
b.dy
b.life -=
1
var remove =
b.life <= 0
if (
!remove &&
blocked(
b.x,
b.y,
3.0
)
) {
remove =
true
}
if (
!remove &&
b.fromPlayer
) {
var j =
enemies.length -
1
while (
j >= 0 &&
!remove
) {
val e =
enemies(j)
if (
dist(
b.x,
b.y,
e.x,
e.y
) <
22.0
) {
e.hp -=
b.damage
e.fear =
clamp(
e.fear +
25.0,
0.0,
100.0
)
remove =
true
screenShake =
clamp(
screenShake +
0.4,
0.0,
8.0
)
if (
e.hp <=
0.0
) {
kills +=
1
if (
kills %
4 ==
0
) {
say(
"HUNTER DOWN",
80
)
}
maybeDropLoot(
e.x,
e.y
)
enemies.remove(j)
aliveEnemies =
enemies.length
}
}
j -=
1
}
}
else if (
!remove &&
!b.fromPlayer
) {
if (
dist(
b.x,
b.y,
playerX,
playerY
) <
PLAYER_RADIUS +
4.0
) {
damagePlayer(
b.damage
)
remove =
true
}
}
if (
remove
) {
bullets.remove(i)
}
i -=
1
}
}
def maybeDropLoot(
x: Double,
y: Double
): Unit = {
val chance =
random.nextDouble()
if (
chance <
0.42
) {
loot += Loot(
x,
y,
"AMMO",
randomInt(
10,
26
),
false
)
}
else if (
chance <
0.63
) {
loot += Loot(
x,
y,
"ARMOR",
randomInt(
12,
28
),
false
)
}
else if (
chance <
0.78
) {
loot += Loot(
x,
y,
"MEDKIT",
1,
false
)
}
}
// ================================================================
// PLAYER DAMAGE
// ================================================================
def damagePlayer(
damage: Double
): Unit = {
if (
animTick -
lastDamageTick <
4L
) {
return
}
lastDamageTick =
animTick
var remaining =
damage *
difficultyEnemyDamage
if (
playerArmor >
0.0
) {
val armorDamage =
Math.min(
playerArmor,
remaining *
0.60
)
playerArmor -=
armorDamage
remaining -=
armorDamage
}
playerHP -=
remaining
playerHP =
clamp(
playerHP,
0.0,
MAX_HP
)
damageFlash =
14
screenShake =
clamp(
screenShake +
3.0,
0.0,
10.0
)
if (
playerHP <=
0.0
) {
gameState =
"LOSE"
running =
false
}
}
// ================================================================
// ENEMY AI
// ================================================================
def updateEnemies(): Unit = {
var i =
enemies.length -
1
while (
i >= 0
) {
val e =
enemies(i)
val d =
dist(
e.x,
e.y,
playerX,
playerY
)
val targetAngle =
Math.atan2(
playerY -
e.y,
playerX -
e.x
)
e.angle =
normAngle(
e.angle +
clamp(
normAngle(
targetAngle -
e.angle
),
-0.065,
0.065
)
)
if (
d < 520.0
) {
e.state =
"CHASE"
}
else {
e.state =
"ROAM"
}
if (
e.fear >
0.0
) {
e.fear =
clamp(
e.fear -
0.22,
0.0,
100.0
)
}
var moveSpeed =
e.speed
if (
e.kind ==
"STALKER"
) {
moveSpeed *=
1.18
if (
d < 220.0
) {
moveSpeed *=
1.12
}
}
if (
e.kind ==
"BRUTE"
) {
moveSpeed *=
0.72
}
if (
d > 48.0 &&
d < 700.0
) {
val nx =
e.x +
Math.cos(
e.angle
) *
moveSpeed
val ny =
e.y +
Math.sin(
e.angle
) *
moveSpeed
val pos =
moveObject(
e.x,
e.y,
nx,
ny,
20.0
)
e.x =
pos._1
e.y =
pos._2
}
if (
d <
300.0 &&
!lineBlocked(
e.x,
e.y,
playerX,
playerY
)
) {
if (
e.cooldown >
0
) {
e.cooldown -=
1
}
else {
enemyShoot(
e
)
e.cooldown =
if (
e.kind ==
"STALKER"
)
48
else if (
e.kind ==
"BRUTE"
)
90
else
68
}
}
else {
if (
e.cooldown >
0
) {
e.cooldown -=
1
}
}
if (
d <
(
if (
e.kind ==
"BRUTE"
)
32.0
else
27.0
)
) {
if (
e.kind ==
"BRUTE"
) {
damagePlayer(
7.5
)
}
else {
damagePlayer(
4.0
)
}
}
i -=
1
}
aliveEnemies =
enemies.length
}
def enemyShoot(
e: Enemy
): Unit = {
val baseAngle =
Math.atan2(
playerY -
e.y,
playerX -
e.x
)
val spread =
if (
e.kind ==
"BRUTE"
)
0.18
else if (
e.kind ==
"STALKER"
)
0.05
else
0.10
val a =
baseAngle +
randomDouble(
-spread,
spread
)
val damage =
if (
e.kind ==
"BRUTE"
)
20.0
else if (
e.kind ==
"STALKER"
)
11.0
else
14.0
bullets += Bullet(
e.x +
Math.cos(a) *
23.0,
e.y +
Math.sin(a) *
23.0,
Math.cos(a) *
(
BULLET_SPEED *
0.76
),
Math.sin(a) *
(
BULLET_SPEED *
0.76
),
100,
false,
damage
)
}
// ================================================================
// ZONE
// ================================================================
def updateZone(): Unit = {
val shrinkRate =
if (
survivalTime <
30.0
)
0.0
else if (
survivalTime <
80.0
)
0.38
else if (
survivalTime <
140.0
)
0.72
else
1.0
if (
zoneRadius >
ZONE_MIN
) {
zoneRadius =
Math.max(
ZONE_MIN,
zoneRadius -
shrinkRate *
0.016
)
}
val d =
dist(
playerX,
playerY,
zoneCenterX,
zoneCenterY
)
if (
d >
zoneRadius
) {
damagePlayer(
3.2 *
difficultyZoneDamage
)
if (
animTick %
60L ==
0L
) {
say(
"THE STORM IS CLOSING IN!",
75
)
}
}
}
// ================================================================
// WIN CONDITION
// ================================================================
def checkWin(): Unit = {
if (
gameState !=
"PLAYING"
) {
return
}
if (
enemies.isEmpty
) {
gameState =
"WIN"
running =
false
say(
"THE NIGHT IS YOURS.",
300
)
}
}
// ================================================================
// TIMER UPDATE
// ================================================================
def updateGame(): Unit = {
if (
gameState !=
"PLAYING" ||
paused ||
!running
) {
return
}
survivalTime +=
0.016
animTick +=
1L
if (
messageTicks >
0
) {
messageTicks -=
1
}
if (
flashTicks >
0
) {
flashTicks -=
1
}
if (
damageFlash >
0
) {
damageFlash -=
1
}
if (
screenShake >
0.0
) {
screenShake =
Math.max(
0.0,
screenShake -
0.25
)
}
if (
reloadTicks >
0
) {
reloadTicks -=
1
if (
reloadTicks ==
0
) {
finishReload()
}
}
updatePlayer()
updateBullets()
updateEnemies()
updateZone()
checkWin()
}
// ================================================================
// DRAW WORLD
// ================================================================
def drawBackground(
g: Graphics2D
): Unit = {
g.setColor(
new Color(
7,
9,
14
)
)
g.fillRect(
0,
0,
WORLD_W,
WORLD_H
)
// Ground grid
g.setColor(
new Color(
20,
23,
31
)
)
var gx =
0
while (
gx <= WORLD_W
) {
g.drawLine(
gx,
0,
gx,
WORLD_H
)
gx +=
80
}
var gy =
0
while (
gy <= WORLD_H
) {
g.drawLine(
0,
gy,
WORLD_W,
gy
)
gy +=
80
}
// Ground patches
var i =
0
while (
i < 80
) {
val x =
(
i * 173
) %
WORLD_W
val y =
(
i * 97
) %
WORLD_H
val r =
2 +
(
i %
5
)
g.setColor(
new Color(
28,
30,
36,
100
)
)
g.fillOval(
x,
y,
r,
r
)
i +=
1
}
}
def drawWalls(
g: Graphics2D
): Unit = {
var i =
0
while (
i < walls.length
) {
val w =
walls(i)
g.setColor(
new Color(
25,
28,
35
)
)
g.fillRect(
w.x,
w.y,
w.w,
w.h
)
g.setColor(
new Color(
78,
82,
92
)
)
g.drawRect(
w.x,
w.y,
w.w,
w.h
)
g.setColor(
new Color(
10,
12,
17
)
)
g.drawLine(
w.x,
w.y,
w.x +
w.w,
w.y +
w.h
)
i +=
1
}
}
// ================================================================
// DRAW LOOT
// ================================================================
def drawLoot(
g: Graphics2D
): Unit = {
var i =
0
while (
i < loot.length
) {
val item =
loot(i)
if (
!item.taken
) {
val pulse =
Math.sin(
animTick.toDouble *
0.08 +
i
) *
3.0
if (
item.kind ==
"AMMO"
) {
g.setColor(
new Color(
220,
205,
95
)
)
g.fillRoundRect(
(
item.x -
10.0
).toInt,
(
item.y -
7.0 +
pulse
).toInt,
20,
14,
5,
5
)
}
else if (
item.kind ==
"MEDKIT"
) {
g.setColor(
new Color(
70,
210,
125
)
)
g.fillRoundRect(
(
item.x -
12.0
).toInt,
(
item.y -
10.0 +
pulse
).toInt,
24,
20,
5,
5
)
g.setColor(
Color.WHITE
)
g.fillRect(
(
item.x -
2.0
).toInt,
(
item.y -
7.0 +
pulse
).toInt,
4,
14
)
g.fillRect(
(
item.x -
7.0
).toInt,
(
item.y -
2.0 +
pulse
).toInt,
14,
4
)
}
else {
g.setColor(
new Color(
90,
145,
235
)
)
g.fillOval(
(
item.x -
12.0
).toInt,
(
item.y -
12.0 +
pulse
).toInt,
24,
24
)
g.setColor(
new Color(
190,
220,
255
)
)
g.drawOval(
(
item.x -
15.0
).toInt,
(
item.y -
15.0 +
pulse
).toInt,
30,
30
)
}
}
i +=
1
}
}
// ================================================================
// DRAW ENEMIES
// ================================================================
def drawEnemies(
g: Graphics2D
): Unit = {
var i =
0
while (
i < enemies.length
) {
val e =
enemies(i)
val glow =
if (
e.kind ==
"BRUTE"
)
22
else if (
e.kind ==
"STALKER"
)
16
else
12
g.setColor(
new Color(
120,
10,
22,
30
)
)
g.fillOval(
(
e.x -
30.0
).toInt,
(
e.y -
30.0
).toInt,
60,
60
)
if (
e.kind ==
"BRUTE"
) {
g.setColor(
new Color(
70,
55,
62
)
)
g.fillOval(
(
e.x -
24.0
).toInt,
(
e.y -
26.0
).toInt,
48,
52
)
g.setColor(
new Color(
215,
58,
66
)
)
g.fillOval(
(
e.x -
12.0
).toInt,
(
e.y -
8.0
).toInt,
7,
7
)
g.fillOval(
(
e.x +
5.0
).toInt,
(
e.y -
8.0
).toInt,
7,
7
)
}
else if (
e.kind ==
"STALKER"
) {
g.setColor(
new Color(
24,
28,
38
)
)
g.fillOval(
(
e.x -
17.0
).toInt,
(
e.y -
21.0
).toInt,
34,
44
)
g.setColor(
new Color(
235,
70,
95
)
)
g.fillOval(
(
e.x -
8.0
).toInt,
(
e.y -
7.0
).toInt,
6,
6
)
g.fillOval(
(
e.x +
3.0
).toInt,
(
e.y -
7.0
).toInt,
6,
6
)
}
else {
g.setColor(
new Color(
42,
44,
53
)
)
g.fillOval(
(
e.x -
19.0
).toInt,
(
e.y -
22.0
).toInt,
38,
42
)
g.setColor(
new Color(
220,
55,
70
)
)
g.fillOval(
(
e.x -
8.0
).toInt,
(
e.y -
6.0
).toInt,
6,
6
)
g.fillOval(
(
e.x +
3.0
).toInt,
(
e.y -
6.0
).toInt,
6,
6
)
}
// Health bar
val barW =
if (
e.kind ==
"BRUTE"
)
52
else
42
val hpRatio =
clamp(
e.hp /
e.maxHp,
0.0,
1.0
)
g.setColor(
new Color(
8,
8,
10,
200
)
)
g.fillRect(
(
e.x -
barW /
2.0
).toInt,
(
e.y -
36.0
).toInt,
barW,
5
)
g.setColor(
new Color(
215,
60,
72
)
)
g.fillRect(
(
e.x -
barW /
2.0
).toInt,
(
e.y -
36.0
).toInt,
(
barW *
hpRatio
).toInt,
5
)
i +=
1
}
}
// ================================================================
// DRAW BULLETS
// ================================================================
def drawBullets(
g: Graphics2D
): Unit = {
var i =
0
while (
i < bullets.length
) {
val b =
bullets(i)
if (
b.fromPlayer
) {
g.setColor(
new Color(
255,
238,
170
)
)
}
else {
g.setColor(
new Color(
255,
65,
85
)
)
}
g.fillOval(
(
b.x -
3.0
).toInt,
(
b.y -
3.0
).toInt,
6,
6
)
i +=
1
}
}
// ================================================================
// PLAYER DRAW
// ================================================================
def drawPlayer(
g: Graphics2D
): Unit = {
g.setColor(
new Color(
0,
0,
0,
90
)
)
g.fillOval(
(
playerX -
24.0
).toInt,
(
playerY -
19.0
).toInt,
48,
38
)
g.setColor(
new Color(
60,
170,
235
)
)
g.fillOval(
(
playerX -
17.0
).toInt,
(
playerY -
17.0
).toInt,
34,
34
)
g.setColor(
new Color(
180,
220,
255
)
)
g.drawOval(
(
playerX -
21.0
).toInt,
(
playerY -
21.0
).toInt,
42,
42
)
val gunX =
playerX +
Math.cos(
playerAngle
) *
27.0
val gunY =
playerY +
Math.sin(
playerAngle
) *
27.0
g.setStroke(
new BasicStroke(
6.0f
)
)
g.setColor(
new Color(
30,
34,
40
)
)
g.drawLine(
playerX.toInt,
playerY.toInt,
gunX.toInt,
gunY.toInt
)
g.setStroke(
new BasicStroke(
1.0f
)
)
}
// ================================================================
// LIGHTING / FOG
// ================================================================
def drawLighting(
g: Graphics2D
): Unit = {
val darkness =
if (
difficulty ==
"HARD"
)
132
else
112
g.setColor(
new Color(
0,
0,
8,
darkness
)
)
g.fillRect(
0,
0,
WORLD_W,
WORLD_H
)
val r1 =
165 +
(
Math.sin(
animTick.toDouble *
0.06
) *
9.0
).toInt
g.setColor(
new Color(
255,
242,
205,
28
)
)
g.fillOval(
(
playerX -
r1
).toInt,
(
playerY -
r1
).toInt,
r1 * 2,
r1 * 2
)
val r2 =
90
g.setColor(
new Color(
255,
245,
220,
25
)
)
g.fillOval(
(
playerX -
r2
).toInt,
(
playerY -
r2
).toInt,
r2 * 2,
r2 * 2
)
// Zone overlay
g.setColor(
new Color(
90,
120,
170,
40
)
)
g.fillOval(
(
zoneCenterX -
zoneRadius
).toInt,
(
zoneCenterY -
zoneRadius
).toInt,
(
zoneRadius *
2.0
).toInt,
(
zoneRadius *
2.0
).toInt
)
g.setColor(
new Color(
90,
180,
235,
170
)
)
g.setStroke(
new BasicStroke(
4.0f
)
)
g.drawOval(
(
zoneCenterX -
zoneRadius
).toInt,
(
zoneCenterY -
zoneRadius
).toInt,
(
zoneRadius *
2.0
).toInt,
(
zoneRadius *
2.0
).toInt
)
g.setStroke(
new BasicStroke(
1.0f
)
)
// Border storm effect
g.setColor(
new Color(
35,
80,
145,
32
)
)
g.fillRect(
0,
0,
40,
WORLD_H
)
g.fillRect(
WORLD_W -
40,
0,
40,
WORLD_H
)
g.fillRect(
0,
0,
WORLD_W,
40
)
g.fillRect(
0,
WORLD_H -
40,
WORLD_W,
40
)
}
// ================================================================
// MINIMAP
// ================================================================
def drawMiniMap(
g: Graphics2D
): Unit = {
if (
!showMap
) {
return
}
val x0 =
20
val y0 =
18
val mw =
225
val mh =
155
g.setColor(
new Color(
4,
6,
10,
215
)
)
g.fillRoundRect(
x0,
y0,
mw,
mh,
16,
16
)
val scaleX =
mw.toDouble /
WORLD_W.toDouble
val scaleY =
mh.toDouble /
WORLD_H.toDouble
var i =
0
while (
i < walls.length
) {
val w =
walls(i)
g.setColor(
new Color(
70,
75,
85
)
)
g.fillRect(
(
x0 +
w.x *
scaleX
).toInt,
(
y0 +
w.y *
scaleY
).toInt,
Math.max(
1,
(
w.w *
scaleX
).toInt
),
Math.max(
1,
(
w.h *
scaleY
).toInt
)
)
i +=
1
}
g.setColor(
new Color(
65,
180,
240
)
)
g.fillOval(
(
x0 +
playerX *
scaleX -
4.0
).toInt,
(
y0 +
playerY *
scaleY -
4.0
).toInt,
8,
8
)
var eIndex =
0
while (
eIndex <
enemies.length
) {
val e =
enemies(eIndex)
g.setColor(
new Color(
225,
60,
70
)
)
g.fillOval(
(
x0 +
e.x *
scaleX -
3.0
).toInt,
(
y0 +
e.y *
scaleY -
3.0
).toInt,
6,
6
)
eIndex +=
1
}
g.setColor(
new Color(
100,
185,
235
)
)
g.drawOval(
(
x0 +
(
zoneCenterX -
zoneRadius
) *
scaleX
).toInt,
(
y0 +
(
zoneCenterY -
zoneRadius
) *
scaleY
).toInt,
(
zoneRadius *
2.0 *
scaleX
).toInt,
(
zoneRadius *
2.0 *
scaleY
).toInt
)
g.setColor(
Color.WHITE
)
g.setFont(
new Font(
"SansSerif",
Font.BOLD,
12
)
)
g.drawString(
"NIGHT MAP",
x0 +
10,
y0 +
16
)
}
// ================================================================
// HUD
// ================================================================
def drawHud(
g: Graphics2D
): Unit = {
g.setColor(
new Color(
4,
6,
10,
245
)
)
g.fillRect(
0,
WORLD_H,
GAME_W,
HUD_H
)
// HP
drawStatusBar(
g,
20,
WORLD_H +
16,
230,
playerHP,
100.0,
"HEALTH",
new Color(
220,
70,
85
)
)
// Armor
drawStatusBar(
g,
270,
WORLD_H +
16,
230,
playerArmor,
100.0,
"ARMOR",
new Color(
80,
150,
235
)
)
// Stamina
drawStatusBar(
g,
520,
WORLD_H +
16,
190,
stamina,
100.0,
"STAMINA",
new Color(
80,
205,
130
)
)
g.setColor(
Color.WHITE
)
g.setFont(
new Font(
"SansSerif",
Font.BOLD,
16
)
)
g.drawString(
"AMMO " +
ammo +
"/" +
reserveAmmo,
735,
WORLD_H +
31
)
g.drawString(
"MEDKITS " +
medkits,
865,
WORLD_H +
31
)
g.drawString(
"KILLS " +
kills,
1005,
WORLD_H +
31
)
g.drawString(
"ENEMIES " +
aliveEnemies,
1115,
WORLD_H +
31
)
g.setFont(
new Font(
"SansSerif",
Font.PLAIN,
14
)
)
g.setColor(
new Color(
210,
220,
235
)
)
g.drawString(
"TIME " +
formatTime(
survivalTime
),
20,
WORLD_H +
61
)
g.drawString(
"ZONE " +
zoneRadius.toInt,
145,
WORLD_H +
61
)
g.drawString(
"WASD MOVE SHIFT SPRINT MOUSE AIM LMB FIRE R RELOAD Q MEDKIT E LOOT P PAUSE M MAP",
300,
WORLD_H +
61
)
if (
messageTicks >
0
) {
g.setColor(
new Color(
255,
233,
170
)
)
g.setFont(
new Font(
"SansSerif",
Font.BOLD,
15
)
)
g.drawString(
message,
20,
WORLD_H -
12
)
}
if (
reloadTicks >
0
) {
val ratio =
1.0 -
reloadTicks.toDouble /
reloadMax.toDouble
g.setColor(
new Color(
12,
14,
18,
230
)
)
g.fillRoundRect(
GAME_W / 2 -
130,
WORLD_H -
54,
260,
25,
10,
10
)
g.setColor(
new Color(
230,
190,
90
)
)
g.fillRoundRect(
GAME_W / 2 -
130,
WORLD_H -
54,
(
260 *
ratio
).toInt,
25,
10,
10
)
g.setColor(
Color.WHITE
)
g.drawString(
"RELOADING",
GAME_W / 2 -
42,
WORLD_H -
36
)
}
}
def drawStatusBar(
g: Graphics2D,
x: Int,
y: Int,
w: Int,
value: Double,
max: Double,
label: String,
fill: Color
): Unit = {
g.setColor(
new Color(
10,
12,
16
)
)
g.fillRoundRect(
x,
y,
w,
20,
8,
8
)
g.setColor(
fill
)
g.fillRoundRect(
x,
y,
(
w *
clamp(
value /
max,
0.0,
1.0
)
).toInt,
20,
8,
8
)
g.setColor(
Color.WHITE
)
g.setFont(
new Font(
"SansSerif",
Font.BOLD,
12
)
)
g.drawString(
label,
x +
8,
y +
14
)
}
def formatTime(
t: Double
): String = {
val total =
Math.max(
0,
t.toInt
)
val m =
total /
60
val s =
total %
60
(
if (
m < 10
)
"0"
else
""
) +
m +
":" +
(
if (
s < 10
)
"0"
else
""
) +
s
}
// ================================================================
// CROSSHAIR
// ================================================================
def drawCrosshair(
g: Graphics2D
): Unit = {
g.setColor(
new Color(
240,
245,
250,
210
)
)
val cx =
mouseX
val cy =
mouseY
g.drawLine(
cx -
10,
cy,
cx -
3,
cy
)
g.drawLine(
cx +
3,
cy,
cx +
10,
cy
)
g.drawLine(
cx,
cy -
10,
cx,
cy -
3
)
g.drawLine(
cx,
cy +
3,
cx,
cy +
10
)
g.drawOval(
cx -
2,
cy -
2,
4,
4
)
}
// ================================================================
// HELP SCREEN
// ================================================================
def drawHelp(
g: Graphics2D
): Unit = {
if (
!showHelp
) {
return
}
g.setColor(
new Color(
3,
5,
10,
245
)
)
g.fillRoundRect(
250,
90,
940,
525,
24,
24
)
g.setColor(
Color.WHITE
)
g.setFont(
new Font(
"SansSerif",
Font.BOLD,
34
)
)
g.drawString(
"NIGHTFALL : HOW TO SURVIVE",
500,
140
)
val lines =
Array(
"WASD Move your character",
"SHIFT Sprint while stamina is available",
"MOUSE Aim the weapon",
"LEFT CLICK Fire",
"R Reload",
"Q Use a medkit",
"E Pick up nearby loot",
"M Toggle minimap",
"P / ESC Pause / resume",
"",
"AMMO Yellow boxes",
"MEDKIT Green boxes",
"ARMOR Blue orbs",
"",
"Stay inside the blue safe zone.",
"Enemies become dangerous when they get close.",
"Eliminate all hunters to clear the night.",
"",
"This is an original Java2D game and uses no external assets."
)
g.setColor(
new Color(
220,
228,
238
)
)
g.setFont(
new Font(
"SansSerif",
Font.PLAIN,
17
)
)
var i =
0
while (
i < lines.length
) {
g.drawString(
lines(i),
310,
180 +
i *
22
)
i +=
1
}
g.setColor(
new Color(
245,
215,
120
)
)
g.setFont(
new Font(
"SansSerif",
Font.BOLD,
15
)
)
g.drawString(
"Press F1 to close this screen",
585,
590
)
}
// ================================================================
// MENU
// ================================================================
def drawMenu(
g: Graphics2D
): Unit = {
g.setColor(
new Color(
2,
3,
7
)
)
g.fillRect(
0,
0,
GAME_W,
GAME_H
)
// Moon
g.setColor(
new Color(
210,
215,
225,
100
)
)
g.fillOval(
1070,
80,
150,
150
)
// Horror fog
var i =
0
while (
i < 16
) {
g.setColor(
new Color(
60,
80,
105,
14
)
)
g.fillOval(
60 +
i *
95,
300 +
(
Math.sin(
i
) *
60
).toInt,
280,
100
)
i +=
1
}
g.setColor(
new Color(
14,
18,
26
)
)
g.fillRect(
230,
250,
980,
330
)
// House silhouette
val roof =
new Polygon()
roof.addPoint(
280,
250
)
roof.addPoint(
720,
105
)
roof.addPoint(
1160,
250
)
g.setColor(
new Color(
8,
10,
15
)
)
g.fillPolygon(
roof
)
g.setColor(
new Color(
30,
35,
45
)
)
g.fillRect(
410,
300,
620,
280
)
// Windows
var wx =
450
while (
wx < 980
) {
g.setColor(
new Color(
220,
180,
80,
160
)
)
g.fillRect(
wx,
350,
52,
60
)
wx +=
90
}
g.setColor(
new Color(
220,
230,
245
)
)
g.setFont(
new Font(
"SansSerif",
Font.BOLD,
60
)
)
g.drawString(
"NIGHTFALL",
530,
180
)
g.setFont(
new Font(
"SansSerif",
Font.BOLD,
24
)
)
g.setColor(
new Color(
230,
75,
90
)
)
g.drawString(
"HORROR SURVIVAL",
590,
220
)
g.setColor(
new Color(
190,
200,
215
)
)
g.setFont(
new Font(
"SansSerif",
Font.PLAIN,
16
)
)
g.drawString(
"SURVIVE THE STORM ? HUNT THE HUNTERS ? ESCAPE THE NIGHT",
455,
610
)
// Buttons
drawMenuButton(
g,
510,
650,
420,
52,
"START GAME"
)
drawMenuButton(
g,
510,
714,
420,
52,
"HOW TO PLAY"
)
drawMenuButton(
g,
510,
778,
420,
52,
"QUIT"
)
g.setColor(
new Color(
205,
215,
228
)
)
g.setFont(
new Font(
"SansSerif",
Font.BOLD,
14
)
)
g.drawString(
"DIFFICULTY:",
970,
668
)
drawDifficultyButton(
g,
970,
685,
"EASY"
)
drawDifficultyButton(
g,
1055,
685,
"NORMAL"
)
drawDifficultyButton(
g,
970,
730,
"HARD"
)
if (
showHelp
) {
drawHelp(
g
)
}
}
def drawMenuButton(
g: Graphics2D,
x: Int,
y: Int,
w: Int,
h: Int,
text: String
): Unit = {
g.setColor(
new Color(
22,
27,
38,
240
)
)
g.fillRoundRect(
x,
y,
w,
h,
16,
16
)
g.setColor(
new Color(
105,
120,
145
)
)
g.drawRoundRect(
x,
y,
w,
h,
16,
16
)
g.setColor(
Color.WHITE
)
g.setFont(
new Font(
"SansSerif",
Font.BOLD,
19
)
)
val tw =
g.getFontMetrics.stringWidth(
text
)
g.drawString(
text,
x +
(
w -
tw
) /
2,
y +
33
)
}
def drawDifficultyButton(
g: Graphics2D,
x: Int,
y: Int,
text: String
): Unit = {
if (
text ==
difficulty
) {
g.setColor(
new Color(
80,
145,
105
)
)
}
else {
g.setColor(
new Color(
35,
42,
55
)
)
}
g.fillRoundRect(
x,
y,
78,
36,
10,
10
)
g.setColor(
Color.WHITE
)
g.drawRoundRect(
x,
y,
78,
36,
10,
10
)
g.setFont(
new Font(
"SansSerif",
Font.BOLD,
11
)
)
g.drawString(
text,
x +
10,
y +
23
)
}
// ================================================================
// END SCREEN
// ================================================================
def drawEndScreen(
g: Graphics2D,
won: Boolean
): Unit = {
if (
won
) {
g.setColor(
new Color(
5,
30,
25
)
)
}
else {
g.setColor(
new Color(
28,
5,
12
)
)
}
g.fillRect(
0,
0,
GAME_W,
GAME_H
)
g.setColor(
Color.WHITE
)
g.setFont(
new Font(
"SansSerif",
Font.BOLD,
56
)
)
g.drawString(
if (
won
)
"NIGHT SURVIVED"
else
"YOU WERE HUNTED",
455,
210
)
g.setFont(
new Font(
"SansSerif",
Font.PLAIN,
23
)
)
g.drawString(
"SURVIVAL TIME : " +
formatTime(
survivalTime
),
545,
280
)
g.drawString(
"HUNTERS DOWN : " +
kills,
570,
325
)
g.drawString(
"DIFFICULTY : " +
difficulty,
590,
370
)
g.setColor(
new Color(
225,
235,
245
)
)
g.setFont(
new Font(
"SansSerif",
Font.BOLD,
18
)
)
g.drawString(
"ENTER / R = RESTART",
585,
450
)
g.drawString(
"M = MAIN MENU",
610,
485
)
}
// ================================================================
// FULL PLAYING SCREEN
// ================================================================
def drawGame(
g: Graphics2D
): Unit = {
val old =
g.getTransform
var sx =
0
var sy =
0
if (
screenShake >
0.0
) {
sx =
randomInt(
-screenShake.toInt,
screenShake.toInt
)
sy =
randomInt(
-screenShake.toInt,
screenShake.toInt
)
}
g.translate(
sx,
sy
)
drawBackground(g)
drawWalls(g)
drawLoot(g)
drawEnemies(g)
drawBullets(g)
drawPlayer(g)
drawLighting(g)
drawMiniMap(g)
drawCrosshair(g)
g.setTransform(
old
)
drawHud(g)
if (
paused
) {
g.setColor(
new Color(
0,
0,
0,
180
)
)
g.fillRect(
0,
0,
GAME_W,
GAME_H
)
g.setColor(
Color.WHITE
)
g.setFont(
new Font(
"SansSerif",
Font.BOLD,
54
)
)
g.drawString(
"PAUSED",
620,
300
)
g.setFont(
new Font(
"SansSerif",
Font.PLAIN,
18
)
)
g.drawString(
"Press P or ESC to resume",
575,
345
)
}
if (
damageFlash >
0
) {
g.setColor(
new Color(
210,
40,
55,
35 +
damageFlash *
5
)
)
g.fillRect(
0,
0,
GAME_W,
WORLD_H
)
}
if (
flashTicks >
0
) {
g.setColor(
new Color(
255,
245,
210,
80
)
)
val fx =
playerX.toInt
val fy =
playerY.toInt
g.fillOval(
fx -
50,
fy -
50,
100,
100
)
}
if (
showHelp
) {
drawHelp(
g
)
}
}
// ================================================================
// PANEL
// ================================================================
val panel =
new JPanel {
setFocusable(
true
)
override def paintComponent(
graphics: Graphics
): Unit = {
super.paintComponent(
graphics
)
val g =
graphics.asInstanceOf[
Graphics2D
]
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
)
g.setRenderingHint(
RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_SPEED
)
if (
gameState ==
"MENU"
) {
drawMenu(
g
)
}
else if (
gameState ==
"PLAYING"
) {
drawGame(
g
)
}
else if (
gameState ==
"WIN"
) {
drawEndScreen(
g,
true
)
}
else if (
gameState ==
"LOSE"
) {
drawEndScreen(
g,
false
)
}
}
addKeyListener(
new KeyAdapter {
override def keyPressed(
e: KeyEvent
): Unit = {
val c =
e.getKeyCode
if (
c >= 0 &&
c <
keyStates.length
) {
if (
!keyStates(c)
) {
keyPressedStates(c) =
true
}
keyStates(c) =
true
}
if (
c ==
KeyEvent.VK_ESCAPE
) {
if (
gameState ==
"PLAYING"
) {
paused =
!paused
}
}
if (
c ==
KeyEvent.VK_P &&
gameState ==
"PLAYING"
) {
paused =
!paused
}
if (
c ==
KeyEvent.VK_F1
) {
showHelp =
!showHelp
}
if (
c ==
KeyEvent.VK_M &&
gameState ==
"PLAYING"
) {
showMap =
!showMap
}
if (
(
c ==
KeyEvent.VK_ENTER ||
c ==
KeyEvent.VK_R
) &&
(
gameState ==
"WIN" ||
gameState ==
"LOSE"
)
) {
startGame()
}
if (
c ==
KeyEvent.VK_M &&
(
gameState ==
"WIN" ||
gameState ==
"LOSE"
)
) {
gameState =
"MENU"
running =
true
}
repaint()
}
override def keyReleased(
e: KeyEvent
): Unit = {
val c =
e.getKeyCode
if (
c >= 0 &&
c <
keyStates.length
) {
keyStates(c) =
false
}
}
}
)
addMouseListener(
new MouseAdapter {
override def mousePressed(
e: MouseEvent
): Unit = {
mouseX =
e.getX
mouseY =
e.getY
if (
gameState ==
"MENU"
) {
handleMenuClick(
mouseX,
mouseY
)
}
else if (
gameState ==
"PLAYING"
) {
mousePressedNow =
true
}
requestFocusInWindow()
repaint()
}
override def mouseReleased(
e: MouseEvent
): Unit = {
mousePressedNow =
false
repaint()
}
}
)
addMouseMotionListener(
new MouseMotionAdapter {
override def mouseMoved(
e: MouseEvent
): Unit = {
mouseX =
e.getX
mouseY =
e.getY
repaint()
}
override def mouseDragged(
e: MouseEvent
): Unit = {
mouseX =
e.getX
mouseY =
e.getY
repaint()
}
}
)
}
// ================================================================
// MENU CLICK
// ================================================================
def handleMenuClick(
x: Int,
y: Int
): Unit = {
if (
x >= 510 &&
x <= 930 &&
y >= 650 &&
y < 702
) {
startGame()
}
else if (
x >= 510 &&
x <= 930 &&
y >= 714 &&
y < 766
) {
showHelp =
!showHelp
}
else if (
x >= 510 &&
x <= 930 &&
y >= 778 &&
y < 830
) {
frame.dispose()
}
if (
x >= 970 &&
x < 1048 &&
y >= 685 &&
y < 721
) {
setDifficulty(
"EASY"
)
}
if (
x >= 1055 &&
x < 1133 &&
y >= 685 &&
y < 721
) {
setDifficulty(
"NORMAL"
)
}
if (
x >= 970 &&
x < 1048 &&
y >= 730 &&
y < 766
) {
setDifficulty(
"HARD"
)
}
}
// ================================================================
// INITIAL FOCUS
// ================================================================
frame.setContentPane(
panel
)
frame.setVisible(
true
)
panel.requestFocusInWindow()
// ================================================================
// GAME TIMER
// ================================================================
val timer =
new javax.swing.Timer(
16,
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
updateGame()
panel.repaint()
}
}
)
timer.start()