Code Sketch
bhavesh shahare
// ============================================================
// OPERATION SINDOOR - ULTRA CINEMATIC ARCADE EDITION
// CLEAN KOJO / SCALA VERSION
// ============================================================
cleari()
originBottomLeft()
// ============================================================
// GAME STATE
// ============================================================
var gameState = 0
var letterTextTyped = 0
var letterTimer = 0.0
// 0 = briefing
// 1 = hero entry
// 2 = 90 second defense
// 3 = cinematic finale
// 4 = final tribute
var gameClock = 90.0
var missionTime = 0.0
var finalTimer = 0.0
var finalFade = 0.0
// ============================================================
// SCORE
// ============================================================
var missilesDestroyed = 0
val targetMissiles = 20
// ============================================================
// HERO
// ============================================================
var heroX = cwidth / 2.0 - 180.0
var heroY = 35.0
var heroState = "WALKING"
// ============================================================
// TANK
// ============================================================
var tankX = cwidth / 2.0 + 90.0
val tankY = 32.0
var cannonAngle = math.Pi / 2.0
var muzzleFlashTimer = 0
var tankPulse = 0.0
// ============================================================
// MISSILE LAUNCHER
// ============================================================
var missileX = cwidth / 2.0 - 220.0
var missileY = 145.0
var missileAngle = math.Pi / 2.0
var selectedMissileType = 1
// ============================================================
// FINAL MISSILE
// ============================================================
var finaleMissileX = cwidth / 2.0 + 240.0
var finaleMissileY = cheight + 150.0
var finaleAngle = -math.Pi / 2.0
var finaleTime = 0.0
// ============================================================
// TIMERS
// ============================================================
var shootCooldown = 0
var launchCooldown = 0
var enemySpawnTimer = 0
// ============================================================
// GLOBAL ANIMATION
// ============================================================
var worldTime = 0.0
var waveAngle = 0.0
var chakraRotation = 0.0
var starDrift = 0.0
var cameraShake = 0.0
var screenFlash = 0.0
// ============================================================
// BRIEFING LETTER
// ============================================================
val officialLetterText =
"TOP SECRET DIRECTIVE: PART 2 - THE RETURN\n" +
"SUBJECT: ASTRONAUT DEFENSE MISSION\n\n" +
"Welcome back, Commander.\n" +
"A national defense alert has been activated.\n" +
"Hold the defense line for 90 SECONDS.\n" +
"Neutralize at least 20 incoming targets.\n\n" +
"Use the control panel to move the tank,\n" +
"rotate the cannon and launch interceptors.\n\n" +
"Click CONFIRM COMMAND to begin."
// ============================================================
// AUDIO
// ============================================================
var audioEnabled = false
try {
setNoteInstrument(Instrument.PIANO)
audioEnabled = true
} catch {
case _: Exception =>
}
def playFx(note: Int, duration: Int): Unit = {
if (audioEnabled) {
try {
playNote(note, duration)
} catch {
case _: Exception =>
}
}
}
// ============================================================
// STAR FIELD
// ============================================================
val starCount = 280
val starX =
Array.fill(starCount)(
randomDouble(0.0, cwidth)
)
val starY =
Array.fill(starCount)(
randomDouble(130.0, cheight)
)
val starSize =
Array.fill(starCount)(
randomDouble(0.7, 2.8)
)
// ============================================================
// PARTICLES
// ============================================================
case class DynamicParticle(
var x: Double,
var y: Double,
var vx: Double,
var vy: Double,
var life: Double,
col: Color,
var size: Double
)
val particles =
ArrayBuffer.empty[DynamicParticle]
def spawnParticle(
x: Double,
y: Double,
col: Color,
amount: Int
): Unit = {
for (i <- 0 until amount) {
particles.append(
DynamicParticle(
x,
y,
randomDouble(-3.0, 3.0),
randomDouble(-2.5, 2.5),
1.0,
col,
randomDouble(2.0, 6.0)
)
)
}
}
def updateParticles(): Unit = {
particles.filterInPlace(
_.life > 0.0
)
particles.foreach { p =>
p.x += p.vx
p.y += p.vy
p.vx *= 0.98
p.vy *= 0.98
p.life -= 0.035
p.size *= 0.995
}
}
def drawParticles(
c: CanvasDraw
): Unit = {
particles.foreach { p =>
val alpha =
math.max(
0,
math.min(
255,
(p.life * 255.0).toInt
)
)
c.fill(
p.col.getRed,
p.col.getGreen,
p.col.getBlue,
alpha
)
c.noStroke()
c.ellipse(
p.x,
p.y,
p.size,
p.size
)
}
}
// ============================================================
// EXPLOSION SPARK
// ============================================================
class ExplosionSpark(
x0: Double,
y0: Double,
r: Int,
g: Int,
b: Int
) {
var x = x0
var y = y0
var vx =
randomDouble(-7.0, 7.0)
var vy =
randomDouble(-7.0, 7.0)
var life = 1.0
def step(): Unit = {
x += vx
y += vy
vx *= 0.93
vy *= 0.93
life -= 0.06
}
def view(
c: CanvasDraw
): Unit = {
if (life > 0.0) {
c.fill(
r,
g,
b,
math.max(
0,
math.min(
255,
(life * 255.0).toInt
)
)
)
c.noStroke()
c.ellipse(
x,
y,
4.0,
4.0
)
}
}
}
val sparks =
ArrayBuffer.empty[ExplosionSpark]
// ============================================================
// SHOCKWAVE
// ============================================================
class Shockwave(
val x: Double,
val y: Double
) {
var radius = 5.0
var alpha = 240.0
var active = true
def step(): Unit = {
radius += 6.0
alpha -= 14.0
if (alpha <= 0.0) {
active = false
}
}
def view(
c: CanvasDraw
): Unit = {
if (active) {
c.noFill()
c.stroke(
255,
180,
70,
math.max(
0,
math.min(
255,
alpha.toInt
)
)
)
c.strokeWeight(3)
c.ellipse(
x,
y,
radius * 2.0,
radius * 2.0
)
}
}
}
val shockwaves =
ArrayBuffer.empty[Shockwave]
// ============================================================
// TANK SHELL
// ============================================================
class TankShell(
var x: Double,
var y: Double,
val vx: Double,
val vy: Double
) {
var active = true
def step(): Unit = {
x += vx
y += vy
if (
x < -50.0 ||
x > cwidth + 50.0 ||
y < -50.0 ||
y > cheight + 50.0
) {
active = false
}
}
def view(
c: CanvasDraw
): Unit = {
c.fill(
255,
200,
70
)
c.noStroke()
c.ellipse(
x,
y,
9.0,
9.0
)
}
}
val tankShells =
ArrayBuffer.empty[TankShell]
// ============================================================
// ENEMY MISSILE
// ============================================================
class EnemyMissile(
var x: Double,
var y: Double,
val vy: Double,
val style: Int
) {
var active = true
var exploded = false
val trail =
ArrayBuffer.empty[(Double, Double)]
def step(): Unit = {
if (!exploded) {
y += vy
if (style == 2) {
x +=
math.sin(
worldTime * 2.2 +
y * 0.025
) * 1.8
}
if (style == 3) {
x +=
math.cos(
worldTime * 2.5 +
y * 0.03
) * 2.2
}
trail.append(
(x, y)
)
if (trail.length > 12) {
trail.remove(0)
}
if (y < -50.0) {
active = false
}
}
}
def view(
c: CanvasDraw
): Unit = {
if (!exploded) {
var i = 0
while (i < trail.length) {
val p = trail(i)
val alpha =
math.max(
0,
math.min(
255,
(
i.toDouble /
trail.length.toDouble *
150.0
).toInt
)
)
c.fill(
255,
100,
60,
alpha
)
c.noStroke()
c.ellipse(
p._1,
p._2,
4.0,
4.0
)
i += 1
}
c.pushMatrix()
c.translate(
x,
y
)
c.rotate(
-math.Pi / 2.0
)
c.fill(
185,
40,
40
)
c.stroke(
70,
10,
10
)
c.strokeWeight(1)
c.rect(
-17,
-6,
34,
12
)
c.fill(
255,
160,
70
)
c.noStroke()
c.ellipse(
18,
0,
11,
11
)
c.popMatrix()
}
}
}
val enemyMissiles =
ArrayBuffer.empty[EnemyMissile]
// ============================================================
// STRATEGIC MISSILE
// ============================================================
class StrategicMissile(
var x: Double,
var y: Double,
val speed: Double,
var angle: Double,
val missileType: Int
) {
var active = true
val trail =
ArrayBuffer.empty[(Double, Double)]
def step(): Unit = {
if (missileType == 2) {
angle += 0.01
}
if (missileType == 3) {
angle +=
math.sin(
worldTime * 4.0
) * 0.02
}
x +=
math.cos(angle) *
speed
y +=
math.sin(angle) *
speed
trail.append(
(x, y)
)
if (trail.length > 16) {
trail.remove(0)
}
if (
x < -100.0 ||
x > cwidth + 100.0 ||
y < -100.0 ||
y > cheight + 100.0
) {
active = false
}
}
def view(
c: CanvasDraw
): Unit = {
var i = 0
while (i < trail.length) {
val p = trail(i)
val alpha =
math.max(
0,
math.min(
255,
(
i.toDouble /
trail.length.toDouble *
160.0
).toInt
)
)
c.fill(
255,
170,
50,
alpha
)
c.noStroke()
c.ellipse(
p._1,
p._2,
5.0,
5.0
)
i += 1
}
c.pushMatrix()
c.translate(
x,
y
)
c.rotate(
angle
)
c.fill(
220,
225,
235
)
c.stroke(
50,
50,
60
)
c.strokeWeight(1)
c.rect(
-18,
-6,
36,
12
)
c.fill(
220,
40,
40
)
c.noStroke()
c.ellipse(
18,
0,
12,
12
)
c.popMatrix()
}
}
val strategicMissiles =
ArrayBuffer.empty[StrategicMissile]
// ============================================================
// FIREWORK
// ============================================================
class Firework {
val colorPalette = Array[Color](
cm.rgb(
255,
153,
51
),
cm.rgb(
255,
255,
255
),
cm.rgb(
18,
136,
7
)
)
val colorIndex =
math.min(
2,
math.max(
0,
randomDouble(
0.0,
3.0
).toInt
)
)
val col: Color =
colorPalette(colorIndex)
val x =
randomDouble(
50.0,
cwidth - 50.0
)
val y =
randomDouble(
240.0,
cheight - 100.0
)
var radius = 0.0
var active = true
def step(): Unit = {
radius += 5.0
if (radius > 105.0) {
active = false
}
}
def view(
c: CanvasDraw
): Unit = {
if (active) {
for (i <- 0 until 28) {
val angle =
i.toDouble *
math.Pi *
2.0 /
28.0
val distance =
radius *
(
0.65 +
math.abs(
math.sin(
worldTime +
i.toDouble
)
) *
0.35
)
val px =
x +
math.cos(angle) *
distance
val py =
y +
math.sin(angle) *
distance
val alpha =
math.max(
0,
math.min(
255,
(200.0 - radius).toInt
)
)
c.fill(
col.getRed,
col.getGreen,
col.getBlue,
alpha
)
c.noStroke()
c.ellipse(
px,
py,
3.0,
3.0
)
}
}
}
}
val fireworks =
ArrayBuffer.empty[Firework]
// ============================================================
// EXPLOSION EFFECT
// ============================================================
def createExplosionEffects(
x: Double,
y: Double
): Unit = {
shockwaves.append(
new Shockwave(
x,
y
)
)
for (i <- 0 until 20) {
sparks.append(
new ExplosionSpark(
x,
y,
255,
random(
90,
220
),
20
)
)
}
spawnParticle(
x,
y,
cm.rgb(
255,
180,
60
),
15
)
cameraShake = 8.0
screenFlash = 0.25
playFx(
55,
60
)
}
// ============================================================
// CONTROL SYSTEM
// ============================================================
def handleControls(): Unit = {
if (
gameState == 2 &&
isMousePressed
) {
val mx =
mouseX
val my =
mouseY
if (
mx >= 20 &&
mx <= 70 &&
my >= 90 &&
my <= 125
) {
tankX -= 4.0
} else if (
mx >= 75 &&
mx <= 125 &&
my >= 90 &&
my <= 125
) {
tankX += 4.0
} else if (
mx >= 130 &&
mx <= 180 &&
my >= 90 &&
my <= 125
) {
cannonAngle -= 0.04
} else if (
mx >= 185 &&
mx <= 235 &&
my >= 90 &&
my <= 125
) {
cannonAngle += 0.04
} else if (
mx >= 240 &&
mx <= 295 &&
my >= 90 &&
my <= 125
) {
if (shootCooldown <= 0) {
val sx =
tankX +
math.cos(
cannonAngle
) * 65.0
val sy =
tankY +
44.0 +
math.sin(
cannonAngle
) * 65.0
tankShells.append(
new TankShell(
sx,
sy,
math.cos(
cannonAngle
) * 10.0,
math.sin(
cannonAngle
) * 10.0
)
)
shootCooldown = 12
muzzleFlashTimer = 6
tankPulse = 1.0
playFx(
65,
40
)
}
} else if (
mx >= 300 &&
mx <= 345 &&
my >= 90 &&
my <= 125
) {
missileAngle -= 0.04
} else if (
mx >= 350 &&
mx <= 395 &&
my >= 90 &&
my <= 125
) {
missileAngle += 0.04
} else if (
mx >= 400 &&
mx <= 445 &&
my >= 90 &&
my <= 125
) {
selectedMissileType = 1
} else if (
mx >= 450 &&
mx <= 495 &&
my >= 90 &&
my <= 125
) {
selectedMissileType = 2
} else if (
mx >= 500 &&
mx <= 545 &&
my >= 90 &&
my <= 125
) {
selectedMissileType = 3
} else if (
mx >= 555 &&
mx <= 630 &&
my >= 90 &&
my <= 125
) {
if (launchCooldown <= 0) {
val tipX =
missileX +
math.cos(
missileAngle
) * 55.0
val tipY =
missileY +
math.sin(
missileAngle
) * 55.0
var speed = 7.0
if (selectedMissileType == 2) {
speed = 6.0
}
if (selectedMissileType == 3) {
speed = 8.5
}
strategicMissiles.append(
new StrategicMissile(
tipX,
tipY,
speed,
missileAngle,
selectedMissileType
)
)
launchCooldown = 18
playFx(
60,
50
)
}
}
}
}
// ============================================================
// COLLISION
// ============================================================
def checkCollisions(): Unit = {
repeatFor(enemyMissiles) { enemy =>
if (!enemy.exploded) {
repeatFor(tankShells) { shell =>
if (
shell.active &&
math.abs(shell.x - enemy.x) < 30.0 &&
math.abs(shell.y - enemy.y) < 30.0
) {
enemy.exploded = true
shell.active = false
missilesDestroyed += 1
createExplosionEffects(
enemy.x,
enemy.y
)
}
}
repeatFor(strategicMissiles) { sm =>
if (
sm.active &&
math.abs(sm.x - enemy.x) < 38.0 &&
math.abs(sm.y - enemy.y) < 38.0
) {
enemy.exploded = true
sm.active = false
missilesDestroyed += 1
createExplosionEffects(
enemy.x,
enemy.y
)
}
}
}
}
}
// ============================================================
// UPDATE OBJECTS
// ============================================================
def updateObjects(): Unit = {
tankShells.filterInPlace(
_.active
)
repeatFor(tankShells)(
_.step()
)
strategicMissiles.filterInPlace(
_.active
)
repeatFor(strategicMissiles)(
_.step()
)
enemyMissiles.filterInPlace(
_.active
)
repeatFor(enemyMissiles)(
_.step()
)
sparks.filterInPlace(
_.life > 0.0
)
repeatFor(sparks)(
_.step()
)
shockwaves.filterInPlace(
_.active
)
repeatFor(shockwaves)(
_.step()
)
fireworks.filterInPlace(
_.active
)
repeatFor(fireworks)(
_.step()
)
}
// ============================================================
// UPDATE GAME
// ============================================================
def updateState(): Unit = {
worldTime += 0.025
waveAngle += 0.08
chakraRotation += 0.05
starDrift += 0.01
if (shootCooldown > 0) {
shootCooldown -= 1
}
if (launchCooldown > 0) {
launchCooldown -= 1
}
if (muzzleFlashTimer > 0) {
muzzleFlashTimer -= 1
}
tankPulse *= 0.92
cameraShake *= 0.90
screenFlash *= 0.88
updateParticles()
// ==========================================================
// BRIEFING
// ==========================================================
if (gameState == 0) {
letterTimer += 0.04
if (
letterTimer >= 0.035 &&
letterTextTyped < officialLetterText.length
) {
letterTextTyped += 1
letterTimer = 0.0
if (
letterTextTyped % 6 == 0
) {
playFx(
70,
15
)
}
}
return
}
// ==========================================================
// HERO ENTRY
// ==========================================================
if (gameState == 1) {
if (heroX < tankX - 20.0) {
heroX += 2.4
} else {
heroState = "BOARDED"
gameState = 2
playFx(
72,
100
)
}
return
}
// ==========================================================
// MAIN DEFENSE
// ==========================================================
if (gameState == 2) {
handleControls()
gameClock -= 0.033
missionTime += 0.033
updateObjects()
checkCollisions()
if (tankX < 60.0) {
tankX = 60.0
}
if (tankX > cwidth - 60.0) {
tankX = cwidth - 60.0
}
enemySpawnTimer += 1
var spawnDelay = 72
if (gameClock < 60.0) {
spawnDelay = 58
}
if (gameClock < 30.0) {
spawnDelay = 42
}
if (
enemySpawnTimer >= spawnDelay
) {
enemySpawnTimer = 0
val ex =
randomDouble(
70.0,
cwidth - 70.0
)
val r =
randomDouble(
0.0,
1.0
)
val style =
if (
gameClock < 30.0 &&
r > 0.55
) {
3
} else if (r > 0.25) {
2
} else {
1
}
var speed = -0.45
if (gameClock < 60.0) {
speed = -0.52
}
if (gameClock < 30.0) {
speed = -0.60
}
enemyMissiles.append(
new EnemyMissile(
ex,
cheight + 25.0,
speed,
style
)
)
}
if (
randomDouble(0.0, 1.0) < 0.025
) {
fireworks.append(
new Firework()
)
}
if (
randomDouble(0.0, 1.0) < 0.06
) {
spawnParticle(
randomDouble(
0.0,
cwidth
),
randomDouble(
140.0,
cheight
),
cm.rgb(
80,
180,
255
),
1
)
}
if (gameClock <= 0.0) {
gameClock = 0.0
gameState = 3
finaleMissileX =
tankX + 220.0
finaleMissileY =
cheight + 120.0
finaleTime = 0.0
playFx(
44,
180
)
}
return
}
// ==========================================================
// CINEMATIC FINALE
// ==========================================================
if (gameState == 3) {
finaleTime += 0.04
finaleMissileX +=
(
tankX -
finaleMissileX
) * 0.055
finaleMissileY -= 6.5
spawnParticle(
finaleMissileX,
finaleMissileY + 18.0,
cm.rgb(
255,
100,
20
),
1
)
updateObjects()
if (
finaleMissileY <
tankY + 30.0
) {
createExplosionEffects(
tankX,
tankY + 35.0
)
gameState = 4
finalTimer = 0.0
finalFade = 0.0
playFx(
38,
220
)
}
return
}
// ==========================================================
// FINAL TRIBUTE
// ==========================================================
if (gameState == 4) {
finalTimer += 0.04
finalFade += 0.006
if (finalFade > 1.0) {
finalFade = 1.0
}
updateObjects()
return
}
}
// ============================================================
// STAR DRAW
// ============================================================
def drawStars(
c: CanvasDraw
): Unit = {
for (i <- 0 until starCount) {
val alpha =
math.max(
50,
math.min(
255,
(
70.0 +
math.abs(
math.sin(
worldTime * 3.0 +
i.toDouble
)
) * 170.0
).toInt
)
)
c.fill(
255,
255,
255,
alpha
)
c.noStroke()
val drift =
math.sin(
worldTime * 0.2 +
i.toDouble
) * 2.0
c.ellipse(
starX(i) + drift,
starY(i),
starSize(i),
starSize(i)
)
}
}
// ============================================================
// SPACE BACKGROUND
// ============================================================
def drawSpaceBackground(
c: CanvasDraw
): Unit = {
c.background(
2,
5,
18
)
drawStars(c)
c.fill(
30,
70,
150,
25
)
c.noStroke()
c.ellipse(
170,
cheight - 100,
330,
180
)
c.fill(
100,
30,
160,
20
)
c.ellipse(
cwidth - 170,
220,
300,
160
)
}
// ============================================================
// TANK DRAW
// ============================================================
def drawTank(
c: CanvasDraw
): Unit = {
c.fill(
0,
0,
0,
80
)
c.noStroke()
c.ellipse(
tankX,
tankY - 4,
125,
16
)
c.fill(
28,
30,
34
)
c.stroke(
10,
10,
12
)
c.strokeWeight(1)
c.rect(
tankX - 52,
tankY,
104,
24
)
c.fill(
75,
80,
75
)
for (i <- -4 to 4) {
c.ellipse(
tankX + i * 11,
tankY + 12,
9,
9
)
}
c.fill(
55,
88,
48
)
c.rect(
tankX - 42,
tankY + 23,
84,
22
)
c.fill(
45,
75,
40
)
c.ellipse(
tankX,
tankY + 45,
46,
25
)
val barrel = 68.0
val tipX =
tankX +
math.cos(
cannonAngle
) * barrel
val tipY =
tankY +
45.0 +
math.sin(
cannonAngle
) * barrel
c.stroke(
25,
50,
20
)
c.strokeWeight(
8.0 + tankPulse * 2.0
)
c.line(
tankX,
tankY + 45.0,
tipX,
tipY
)
if (muzzleFlashTimer > 0) {
c.noStroke()
c.fill(
255,
170,
30,
230
)
c.ellipse(
tipX,
tipY,
42,
42
)
c.fill(
255,
250,
180,
240
)
c.ellipse(
tipX,
tipY,
18,
18
)
}
}
// ============================================================
// LAUNCHER
// ============================================================
def drawLauncher(
c: CanvasDraw
): Unit = {
c.fill(
45,
48,
55
)
c.stroke(
18,
22,
25
)
c.strokeWeight(2)
c.ellipse(
missileX,
missileY,
58,
58
)
c.pushMatrix()
c.translate(
missileX,
missileY
)
c.rotate(
missileAngle
)
c.fill(
60,
78,
70
)
c.rect(
-14,
-16,
62,
32
)
c.fill(
220,
30,
30
)
c.noStroke()
c.rect(
16,
-16,
8,
32
)
c.popMatrix()
}
// ============================================================
// HERO DRAW
// ============================================================
def drawHero(
c: CanvasDraw,
x: Double,
y: Double
): Unit = {
if (heroState == "BOARDED") {
return
}
val step =
math.sin(
worldTime * 10.0
) * 4.0
c.fill(
0,
0,
0,
70
)
c.noStroke()
c.ellipse(
x,
y - 5,
25,
7
)
c.fill(
240,
242,
248
)
c.stroke(
150,
160,
175
)
c.strokeWeight(1)
c.rect(
x - 7,
y,
14,
20
)
c.fill(
20,
155,
220
)
c.ellipse(
x,
y + 27,
18,
18
)
c.fill(
30,
145,
215
)
c.ellipse(
x,
y + 27,
11,
7
)
c.stroke(
225,
230,
240
)
c.strokeWeight(4)
c.line(
x - 3,
y,
x - 6 - step,
y - 10
)
c.line(
x + 3,
y,
x + 6 + step,
y - 10
)
c.line(
x - 7,
y + 15,
x - 13,
y + 5
)
c.line(
x + 7,
y + 15,
x + 13,
y + 5
)
}
// ============================================================
// FLAG
// ============================================================
def drawFlag(
c: CanvasDraw
): Unit = {
val fx =
cwidth - 145.0
val fy =
cheight - 165.0
c.stroke(
220,
220,
220
)
c.strokeWeight(5)
c.line(
fx,
fy - 100.0,
fx,
fy + 80.0
)
var x = 0.0
while (x < 115.0) {
val wave =
math.sin(
waveAngle +
x * 0.08
) * 6.0
c.strokeWeight(3)
c.stroke(
255,
153,
51
)
c.line(
fx + x,
fy + 43.0 + wave,
fx + x,
fy + 63.0 + wave
)
c.stroke(
255,
255,
255
)
c.line(
fx + x,
fy + 23.0 + wave,
fx + x,
fy + 43.0 + wave
)
c.stroke(
18,
136,
7
)
c.line(
fx + x,
fy + wave,
fx + x,
fy + 23.0 + wave
)
x += 3.0
}
c.noFill()
c.stroke(
0,
0,
150
)
c.strokeWeight(2)
c.ellipse(
fx + 58.0,
fy + 32.0,
17.0,
17.0
)
}
// ============================================================
// HUD
// ============================================================
def drawHUD(
c: CanvasDraw
): Unit = {
c.fill(
8,
14,
30,
235
)
c.stroke(
255,
215,
0
)
c.strokeWeight(1.5)
c.rect(
18,
cheight - 55,
430,
42
)
c.fill(
255,
215,
0
)
c.text(
s"TIME: ${gameClock.toInt}s",
30,
cheight - 31
)
c.fill(
120,
255,
190
)
c.text(
s"TARGETS: $missilesDestroyed / $targetMissiles",
160,
cheight - 31
)
val ratio =
math.max(
0.0,
math.min(
1.0,
gameClock / 90.0
)
)
c.fill(
25,
35,
50
)
c.rect(
310,
cheight - 48,
120,
8
)
c.fill(
255,
153,
51
)
c.rect(
310,
cheight - 48,
120.0 * ratio,
8
)
}
// ============================================================
// CONTROL BUTTONS
// ============================================================
def drawControls(
c: CanvasDraw
): Unit = {
c.fill(
12,
18,
32,
235
)
c.stroke(
0,
255,
205
)
c.strokeWeight(1)
c.rect(
15,
88,
665,
48
)
c.fill(
55,
55,
85
)
c.rect(
20,
96,
50,
28
)
c.rect(
75,
96,
50,
28
)
c.fill(
255,
255,
255
)
c.text(
"T-",
38,
114
)
c.text(
"T+",
94,
114
)
c.fill(
70,
42,
42
)
c.rect(
130,
96,
50,
28
)
c.rect(
185,
96,
50,
28
)
c.fill(
255,
255,
255
)
c.text(
"C-",
148,
114
)
c.text(
"C+",
203,
114
)
c.fill(
205,
75,
0
)
c.rect(
240,
96,
55,
28
)
c.fill(
255,
255,
255
)
c.text(
"FIRE",
252,
114
)
c.fill(
40,
90,
90
)
c.rect(
300,
96,
45,
28
)
c.rect(
350,
96,
45,
28
)
c.fill(
255,
255,
255
)
c.text(
"M-",
313,
114
)
c.text(
"M+",
363,
114
)
c.fill(
if (selectedMissileType == 1) 180 else 80,
30,
120
)
c.rect(
400,
96,
45,
28
)
c.fill(
if (selectedMissileType == 2) 180 else 80,
30,
120
)
c.rect(
450,
96,
45,
28
)
c.fill(
if (selectedMissileType == 3) 180 else 80,
30,
120
)
c.rect(
500,
96,
45,
28
)
c.fill(
255,
255,
255
)
c.text(
"A",
418,
114
)
c.text(
"B",
468,
114
)
c.text(
"C",
518,
114
)
c.fill(
145,
0,
180
)
c.rect(
555,
96,
75,
28
)
c.fill(
255,
255,
255
)
c.text(
"LAUNCH",
565,
114
)
}
// ============================================================
// BRIEFING
// ============================================================
def drawBriefing(
c: CanvasDraw
): Unit = {
c.background(
4,
7,
18
)
drawStars(c)
val lx =
cwidth / 2.0 - 270.0
val ly =
cheight / 2.0 - 150.0
c.fill(
12,
22,
42,
245
)
c.stroke(
0,
255,
205
)
c.strokeWeight(2)
c.rect(
lx,
ly,
540,
320
)
c.fill(
255,
215,
0
)
c.text(
"OPERATION SINDOOR - PART 2",
lx + 145,
ly + 25
)
val shown =
officialLetterText.substring(
0,
letterTextTyped
)
val lines =
shown.split("\n")
var yy =
ly + 55.0
for (line <- lines) {
c.fill(
220,
240,
255
)
c.text(
line,
lx + 20,
yy
)
yy += 17.0
}
if (
letterTextTyped >=
officialLetterText.length
) {
val bx =
lx + 170.0
val by =
ly - 55.0
c.fill(
0,
185,
125
)
c.stroke(
255,
255,
255
)
c.strokeWeight(2)
c.rect(
bx,
by,
200,
36
)
c.fill(
255,
255,
255
)
c.text(
"CONFIRM COMMAND",
bx + 32,
by + 23
)
if (
isMousePressed &&
mouseX >= bx &&
mouseX <= bx + 200 &&
mouseY >= by &&
mouseY <= by + 36
) {
gameState = 1
playFx(
72,
120
)
}
}
}
// ============================================================
// HERO ENTRY SCREEN
// ============================================================
def drawEntry(
c: CanvasDraw
): Unit = {
drawSpaceBackground(c)
drawTank(c)
drawLauncher(c)
drawHero(
c,
heroX,
heroY
)
drawFlag(c)
c.fill(
255,
215,
90
)
c.text(
"COMMANDER ARRIVAL",
cwidth / 2.0 - 75,
cheight - 35
)
}
// ============================================================
// FINAL MISSILE
// ============================================================
def drawFinalMissile(
c: CanvasDraw
): Unit = {
if (gameState != 3) {
return
}
c.pushMatrix()
c.translate(
finaleMissileX,
finaleMissileY
)
c.rotate(
finaleAngle
)
c.fill(
220,
55,
45
)
c.stroke(
80,
20,
20
)
c.strokeWeight(2)
c.rect(
-32,
-11,
64,
22
)
c.fill(
255,
210,
80
)
c.noStroke()
c.ellipse(
30,
0,
20,
20
)
c.popMatrix()
}
// ============================================================
// FINAL TRIBUTE
// ============================================================
def drawFinalTribute(
c: CanvasDraw
): Unit = {
c.background(
0,
0,
0
)
val a =
math.max(
0,
math.min(
255,
(
finalFade *
255.0
).toInt
)
)
for (i <- 0 until 80) {
val sx =
math.abs(
math.sin(
worldTime * 0.35 +
i.toDouble
)
) *
(
cwidth - 1.0
)
val sy =
40.0 +
math.abs(
math.sin(
i.toDouble * 1.7
)
) *
(
cheight - 80.0
)
c.fill(
255,
255,
255,
math.min(
a,
160
)
)
c.noStroke()
c.ellipse(
sx,
sy,
2.0,
2.0
)
}
c.fill(
255,
255,
255,
a
)
c.text(
"THE END",
cwidth / 2.0 - 42,
cheight / 2.0 + 100
)
c.text(
"MISSION COMPLETE",
cwidth / 2.0 - 66,
cheight / 2.0 + 58
)
c.fill(
255,
220,
150,
a
)
c.text(
"PRAY FOR OUR ARMY SOLDIERS",
cwidth / 2.0 - 125,
cheight / 2.0 + 5
)
c.fill(
235,
235,
235,
a
)
c.text(
"Respect. Courage. Service.",
cwidth / 2.0 - 85,
cheight / 2.0 - 35
)
c.fill(
255,
215,
90,
a
)
c.text(
"JAI HIND",
cwidth / 2.0 - 35,
cheight / 2.0 - 85
)
c.strokeWeight(4)
c.stroke(
255,
153,
51,
a
)
c.line(
cwidth / 2.0 - 160,
55,
cwidth / 2.0 + 160,
55
)
c.stroke(
255,
255,
255,
a
)
c.line(
cwidth / 2.0 - 160,
48,
cwidth / 2.0 + 160,
48
)
c.stroke(
18,
136,
7,
a
)
c.line(
cwidth / 2.0 - 160,
41,
cwidth / 2.0 + 160,
41
)
}
// ============================================================
// MAIN VIEW
// ============================================================
def viewState(
c: CanvasDraw
): Unit = {
if (gameState == 0) {
drawBriefing(c)
return
}
if (gameState == 1) {
drawEntry(c)
return
}
if (gameState == 4) {
drawFinalTribute(c)
return
}
drawSpaceBackground(c)
if (cameraShake > 0.5) {
c.pushMatrix()
c.translate(
randomDouble(
-cameraShake,
cameraShake
),
randomDouble(
-cameraShake,
cameraShake
)
)
}
drawFlag(c)
drawLauncher(c)
drawTank(c)
drawParticles(c)
repeatFor(fireworks)(
_.view(c)
)
repeatFor(tankShells)(
_.view(c)
)
repeatFor(strategicMissiles)(
_.view(c)
)
repeatFor(enemyMissiles)(
_.view(c)
)
repeatFor(sparks)(
_.view(c)
)
repeatFor(shockwaves)(
_.view(c)
)
if (gameState == 2) {
drawControls(c)
drawHUD(c)
}
if (gameState == 3) {
drawFinalMissile(c)
c.fill(
255,
90,
60
)
c.text(
"FINAL APPROACH",
cwidth / 2.0 - 60,
cheight - 40
)
}
if (cameraShake > 0.5) {
c.popMatrix()
}
if (screenFlash > 0.01) {
c.fill(
255,
230,
160,
math.max(
0,
math.min(
255,
(
screenFlash * 255.0
).toInt
)
)
)
c.noStroke()
c.rect(
0,
0,
cwidth,
cheight
)
}
}
// ============================================================
// MAIN LOOP
// ============================================================
animateWithCanvasDraw { c =>
updateState()
viewState(c)
}