Code Sketch
Bixuu spring season event 7
Category: Programming
import java.awt.{BasicStroke, Color, Dimension, Font, GradientPaint, Graphics, Graphics2D, Polygon, RenderingHints, Rectangle}
import java.awt.event.{ActionEvent, ActionListener, KeyAdapter, KeyEvent, MouseAdapter, MouseEvent}
import java.awt.geom.{Ellipse2D, RoundRectangle2D}
import java.util.Locale
import java.util.prefs.Preferences
import javax.swing.{JFrame, JOptionPane, JPanel, SwingUtilities, Timer, WindowConstants}
import scala.collection.mutable.ArrayBuffer
import scala.util.Random
import scala.util.Try
object AlienTidalSurge {
private val AppTitle = "ALIEN TIDAL SURGE - EVENT 7"
private val WindowWidth = 1000
private val WindowHeight = 700
private val BackgroundTop = new Color(8, 18, 54)
private val BackgroundBottom = new Color(15, 82, 112)
private val Aqua = new Color(72, 235, 224)
private val BrightBlue = new Color(91, 177, 255)
private val Coral = new Color(255, 133, 116)
private val Gold = new Color(255, 211, 95)
private val White = new Color(239, 250, 255)
private val PanelColor = new Color(5, 25, 53, 225)
private case class UiButton(rect: Rectangle, label: String, action: () => Unit, enabled: Boolean)
private class Alien(var x: Double, var y: Double, val boss: Boolean, var hp: Int, val phase: Double) {
var alive = true
def radius: Double = if (boss) thirtyEight else 20.0
private val thirtyEight = 38.0
}
private class Pulse(var x: Double, var y: Double, val vx: Double, val vy: Double) {
var life = 75
}
private class Crystal(var x: Double, var y: Double) {
var phase = 0.0
}
private class GamePanel(val playerName: String) extends JPanel {
private val safeName = playerName.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9_-]", "_")
private val prefs = Preferences.userRoot().node("BixuuGames/AlienTidalSurge/players/" + safeName)
private val random = new Random()
private val buttons = ArrayBuffer[UiButton]()
private val aliens = ArrayBuffer[Alien]()
private val pulses = ArrayBuffer[Pulse]()
private val crystals = ArrayBuffer[Crystal]()
private var screen = "menu"
private var level = 1
private var selectedLevel = 1
private var highestLevel = math.max(1, math.min(10, prefs.getInt("highestLevel", 1)))
private var surgeCoins = math.max(0, prefs.getInt("surgeCoins", 0))
private var bestScore = math.max(0, prefs.getInt("bestScore", 0))
private var suitOwned = prefs.getBoolean("aquaSuit", false)
private var selectedSuit = math.max(0, math.min(2, prefs.getInt("selectedSuit", 0)))
private var playerX = 500.0
private var playerY = 500.0
private var up = false
private var down = false
private var left = false
private var right = false
private var paused = false
private var lives = 3
private var shieldTicks = 0
private var invulnerableTicks = 0
private var magnetTicks = 0
private var attackCooldown = 0
private var defeated = 0
private var enemyGoal = 4
private var score = 0
private var tick = 0
private var waveY = WindowHeight + 80.0
private var waveActive = false
private var waveCooldown = 300
private var waveAlreadyHit = false
private var message = "Explore the tide planet and collect energy crystals!"
private var messageTicks = 220
setPreferredSize(new Dimension(WindowWidth, WindowHeight))
setFocusable(true)
setBackground(BackgroundBottom)
private val gameTimer = new Timer(16, new ActionListener {
override def actionPerformed(event: ActionEvent): Unit = {
updateGame()
repaint()
}
})
def startLoop(): Unit = gameTimer.start()
private def saveProgress(): Unit = {
Try {
prefs.putInt("highestLevel", highestLevel)
prefs.putInt("surgeCoins", surgeCoins)
prefs.putInt("bestScore", bestScore)
prefs.putBoolean("aquaSuit", suitOwned)
prefs.putInt("selectedSuit", selectedSuit)
prefs.flush()
}
}
private def setMessage(value: String): Unit = {
message = value
messageTicks = 180
}
private def suitColor(index: Int): Color = {
index match {
case 0 => new Color(90, 245, 222)
case 1 => new Color(255, 191, 104)
case _ => new Color(179, 155, 255)
}
}
private def suitName(index: Int): String = {
index match {
case 0 => "TIDE RUNNER"
case 1 => "SOLAR EXPLORER"
case _ => "NEBULA GUARD"
}
}
private def addButton(g: Graphics2D, x: Int, y: Int, w: Int, h: Int,
label: String, enabled: Boolean = true)(action: => Unit): Unit = {
val rect = new Rectangle(x, y, w, h)
buttons += UiButton(rect, label, () => action, enabled)
val fill = if (enabled) new Color(9, 103, 128, 238) else new Color(65, 83, 101, 205)
g.setColor(fill)
g.fill(new RoundRectangle2D.Double(x, y, w, h, 22, 22))
g.setColor(if (enabled) Aqua else new Color(170, 190, 200))
g.setStroke(new BasicStroke(2.0f))
g.draw(new RoundRectangle2D.Double(x, y, w, h, 22, 22))
g.setFont(new Font("SansSerif", Font.BOLD, 15))
val fm = g.getFontMetrics
g.setColor(White)
g.drawString(label, x + (w - fm.stringWidth(label)) / 2, y + (h + fm.getAscent - fm.getDescent) / 2)
}
private def drawText(g: Graphics2D, value: String, x: Int, y: Int, size: Int,
color: Color, bold: Boolean = false): Unit = {
g.setFont(new Font("SansSerif", if (bold) Font.BOLD else Font.PLAIN, size))
g.setColor(color)
g.drawString(value, x, y)
}
private def drawPanel(g: Graphics2D, x: Int, y: Int, w: Int, h: Int): Unit = {
g.setColor(PanelColor)
g.fill(new RoundRectangle2D.Double(x, y, w, h, 24, 24))
g.setColor(new Color(92, 238, 226, 190))
g.setStroke(new BasicStroke(2.0f))
g.draw(new RoundRectangle2D.Double(x, y, w, h, 24, 24))
}
private def drawStar(g: Graphics2D, x: Int, y: Int, radius: Int, color: Color): Unit = {
val points = new Polygon()
var i = 0
while (i < 10) {
val angle = -math.Pi / 2.0 + i * math.Pi / 5.0
val r = if (i % 2 == 0) radius.toDouble else radius * 0.43
points.addPoint((x + math.cos(angle) * r).toInt, (y + math.sin(angle) * r).toInt)
i += 1
}
g.setColor(color)
g.fillPolygon(points)
}
private def drawAlien(g: Graphics2D, x: Double, y: Double, size: Double, boss: Boolean, frame: Int): Unit = {
val bob = math.sin(frame * 0.08 + x * 0.01) * 3.0
val r = size
val bodyColor = if (boss) new Color(171, 94, 255) else new Color(91, 226, 176)
g.setColor(new Color(0, 0, 0, 65))
g.fill(new Ellipse2D.Double(x - r * 0.8, y + r * 0.7, r * 1.6, r * 0.38))
g.setColor(bodyColor)
g.fill(new Ellipse2D.Double(x - r, y - r + bob, r * 2.0, r * 1.8))
g.setColor(new Color(215, 255, 244))
g.fill(new Ellipse2D.Double(x - r * 0.66, y - r * 0.48 + bob, r * 0.52, r * 0.58))
g.fill(new Ellipse2D.Double(x + r * 0.14, y - r * 0.48 + bob, r * 0.52, r * 0.58))
g.setColor(new Color(10, 38, 57))
g.fill(new Ellipse2D.Double(x - r * 0.49, y - r * 0.27 + bob, r * 0.19, r * 0.30))
g.fill(new Ellipse2D.Double(x + r * 0.30, y - r * 0.27 + bob, r * 0.19, r * 0.30))
g.setStroke(new BasicStroke(2.0f))
g.setColor(new Color(228, 255, 245))
g.drawArc((x - r * 0.24).toInt, (y + r * 0.10 + bob).toInt, (r * 0.48).toInt, (r * 0.30).toInt, 190, 160)
g.setColor(bodyColor.darker())
g.drawLine((x - r * 0.32).toInt, (y - r * 0.87 + bob).toInt, (x - r * 0.55).toInt, (y - r * 1.35 + bob).toInt)
g.drawLine((x + r * 0.32).toInt, (y - r * 0.87 + bob).toInt, (x + r * 0.55).toInt, (y - r * 1.35 + bob).toInt)
g.setColor(Gold)
g.fill(new Ellipse2D.Double(x - r * 0.63, y - r * 1.46 + bob, r * 0.18, r * 0.18))
g.fill(new Ellipse2D.Double(x + r * 0.46, y - r * 1.46 + bob, r * 0.18, r * 0.18))
}
private def drawPlayer(g: Graphics2D): Unit = {
val color = suitColor(selectedSuit)
if (shieldTicks > 0) {
g.setColor(new Color(83, 235, 255, 65))
g.fill(new Ellipse2D.Double(playerX - 34, playerY - 36, 68, 68))
g.setColor(new Color(118, 248, 255, 180))
g.setStroke(new BasicStroke(2.0f))
g.draw(new Ellipse2D.Double(playerX - 34, playerY - 36, 68, 68))
}
if (invulnerableTicks > 0 && tick % 8 < 4) return
g.setColor(new Color(0, 0, 0, 75))
g.fill(new Ellipse2D.Double(playerX - 20, playerY + 17, 40, 12))
g.setColor(color)
g.fill(new RoundRectangle2D.Double(playerX - 15, playerY - 22, 30, 43, 12, 12))
g.setColor(new Color(236, 252, 255))
g.fill(new Ellipse2D.Double(playerX - 13, playerY - 36, 26, 26))
g.setColor(new Color(7, 43, 70))
g.fill(new RoundRectangle2D.Double(playerX - 9, playerY - 29, 18, 10, 7, 7))
g.setColor(Gold)
g.fill(new Ellipse2D.Double(playerX - 5, playerY - 8, 10, 10))
}
private def drawCrystal(g: Graphics2D, x: Double, y: Double, phase: Double): Unit = {
val bob = math.sin(phase) * 4.0
val p = new Polygon()
p.addPoint(x.toInt, (y - 12 + bob).toInt)
p.addPoint((x + 9).toInt, (y + bob).toInt)
p.addPoint(x.toInt, (y + 12 + bob).toInt)
p.addPoint((x - 9).toInt, (y + bob).toInt)
g.setColor(new Color(105, 250, 245, 95))
g.fill(new Ellipse2D.Double(x - 17, y - 17 + bob, 34, 34))
g.setColor(Gold)
g.fillPolygon(p)
g.setColor(White)
g.drawLine(x.toInt, (y - 8 + bob).toInt, x.toInt, (y + 7 + bob).toInt)
}
private def drawBackground(g: Graphics2D): Unit = {
val w = getWidth
val h = getHeight
g.setPaint(new GradientPaint(0, 0, BackgroundTop, 0, h, BackgroundBottom))
g.fillRect(0, 0, w, h)
var i = 0
while (i < 44) {
val sx = (i * 197 + 47) % math.max(1, w)
val sy = (i * 113 + 29) % 330
val alpha = 80 + (i * 17) % 150
g.setColor(new Color(210, 245, 255, alpha))
g.fill(new Ellipse2D.Double(sx, sy, 2 + (i % 3), 2 + (i % 3)))
i += 1
}
g.setColor(new Color(84, 223, 229, 35))
g.fill(new Ellipse2D.Double(95, 75, 220, 110))
g.fill(new Ellipse2D.Double(690, 92, 180, 85))
val island = new Polygon()
island.addPoint(0, 440)
island.addPoint(140, 390)
island.addPoint(275, 425)
island.addPoint(430, 365)
island.addPoint(585, 423)
island.addPoint(760, 370)
island.addPoint(1000, 430)
island.addPoint(1000, h)
island.addPoint(0, h)
g.setColor(new Color(18, 83, 92))
g.fillPolygon(island)
g.setColor(new Color(28, 134, 131))
g.fillRect(0, 472, w, h - 472)
g.setColor(new Color(75, 218, 213, 95))
var wave = 0
while (wave < 5) {
val y = 492 + wave * 36 + (math.sin((tick + wave * 30) * 0.025) * 8).toInt
g.setStroke(new BasicStroke(2.0f))
g.drawArc((wave * 180 - 50), y, 250, 38, 0, 180)
g.drawArc((wave * 180 + 70), y + 8, 250, 30, 0, 180)
wave += 1
}
}
private def drawTidalSurge(g: Graphics2D): Unit = {
if (waveActive) {
val y = waveY.toInt
val waveShape = new Polygon()
waveShape.addPoint(0, y)
var x = 0
while (x <= getWidth) {
val yy = y + (math.sin((x + tick * 4) * 0.025) * 18.0).toInt
waveShape.addPoint(x, yy)
x += 25
}
waveShape.addPoint(getWidth, getHeight)
waveShape.addPoint(0, getHeight)
g.setColor(new Color(55, 205, 235, 105))
g.fillPolygon(waveShape)
g.setColor(new Color(149, 247, 255, 220))
g.setStroke(new BasicStroke(4.0f))
g.drawPolyline(waveShape.xpoints, waveShape.ypoints, waveShape.npoints - 2)
}
}
private def drawMenu(g: Graphics2D): Unit = {
drawBackground(g)
drawAlien(g, 780, 300, 52, true, tick)
drawAlien(g, 685, 365, 25, false, tick + 30)
drawAlien(g, 885, 380, 23, false, tick + 60)
drawText(g, "ALIEN", 92, 154, 56, White, true)
drawText(g, "TIDAL SURGE", 92, 214, 54, Aqua, true)
drawText(g, "EVENT 7 | THE PLANET OF RISING TIDES", 98, 250, 17, Gold, true)
drawText(g, "Pilot your aqua suit. Dodge the surge. Restore the crystal core.", 98, 286, 16, White)
drawPanel(g, 72, 335, 440, 260)
addButton(g, 105, 366, 185, 52, "START MISSION") { screen = "levels" }
addButton(g, 305, 366, 175, 52, "TIDE SHOP") { screen = "shop" }
addButton(g, 105, 434, 185, 52, "HOW TO PLAY") { screen = "help" }
addButton(g, 305, 434, 175, 52, "PLAYER PROFILE") { screen = "profile" }
drawText(g, "Pilot: " + playerName, 105, 535, 16, White, true)
drawText(g, "Tide Crystals: " + surgeCoins, 105, 562, 16, Gold, true)
drawText(g, "Best Score: " + bestScore, 305, 562, 16, Aqua, true)
drawText(g, "B = Back ENTER = Start", 105, 640, 14, new Color(195, 225, 240))
}
private def drawLevels(g: Graphics2D): Unit = {
drawBackground(g)
drawPanel(g, 90, 55, 820, 585)
drawText(g, "SELECT YOUR TIDE MISSION", 135, 103, 31, White, true)
drawText(g, "Clear missions to unlock the next island.", 138, 133, 15, Aqua)
var i = 1
while (i <= 10) {
val col = (i - 1) % 5
val row = (i - 1) / 5
val x = 145 + col * 145
val y = 185 + row * 145
val unlocked = i <= highestLevel
val selected = i == selectedLevel
g.setColor(if (selected) new Color(255, 205, 101, 220) else if (unlocked) new Color(20, 126, 142, 225) else new Color(55, 72, 92, 210))
g.fill(new RoundRectangle2D.Double(x, y, 112, 92, 18, 18))
g.setColor(if (unlocked) Aqua else new Color(130, 150, 165))
g.setStroke(new BasicStroke(if (selected) 4.0f else 2.0f))
g.draw(new RoundRectangle2D.Double(x, y, 112, 92, 18, 18))
drawText(g, "LEVEL " + i, x + 22, y + 37, 17, White, true)
drawText(g, if (unlocked) "UNLOCKED" else "LOCKED", x + 18, y + 64, 12, if (unlocked) Gold else new Color(175, 186, 197))
i += 1
}
addButton(g, 355, 505, 135, 48, "PLAY LEVEL") { startLevel(selectedLevel) }
addButton(g, 510, 505, 135, 48, "BACK (B)") { screen = "menu" }
drawText(g, "Click a level tile, then press PLAY LEVEL or ENTER.", 270, 590, 14, White)
}
private def drawShop(g: Graphics2D): Unit = {
drawBackground(g)
drawPanel(g, 110, 75, 780, 535)
drawText(g, "TIDE SHOP", 155, 125, 34, White, true)
drawText(g, "Crystals: " + surgeCoins, 690, 125, 19, Gold, true)
drawText(g, "Aqua Suit upgrade", 165, 220, 22, Aqua, true)
drawText(g, "Adds one extra starting life to each mission.", 165, 252, 15, White)
drawText(g, "Cost: 80 Tide Crystals", 165, 282, 16, Gold, true)
addButton(g, 650, 220, 185, 54, if (suitOwned) "OWNED" else "BUY UPGRADE", !suitOwned && surgeCoins >= 80) {
if (!suitOwned && surgeCoins >= 80) {
surgeCoins -= 80
suitOwned = true
saveProgress()
setMessage("Aqua Suit upgrade unlocked!")
}
}
drawText(g, "Suit colors", 165, 370, 22, Aqua, true)
drawText(g, "Tide Runner is free. Solar Explorer and Nebula Guard are cosmetic.", 165, 398, 14, White)
addButton(g, 165, 430, 180, 48, if (selectedSuit == 0) "TIDE RUNNER: ACTIVE" else "TIDE RUNNER") { selectedSuit = 0; saveProgress() }
addButton(g, 365, 430, 180, 48, if (selectedSuit == 1) "SOLAR: ACTIVE" else "SOLAR EXPLORER", surgeCoins >= 40 || selectedSuit == 1) {
if (selectedSuit == 1 || surgeCoins >= 40) { if (selectedSuit != 1) surgeCoins -= 40; selectedSuit = 1; saveProgress() }
}
addButton(g, 565, 430, 180, 48, if (selectedSuit == 2) "NEBULA: ACTIVE" else "NEBULA GUARD", surgeCoins >= 60 || selectedSuit == 2) {
if (selectedSuit == 2 || surgeCoins >= 60) { if (selectedSuit != 2) surgeCoins -= 60; selectedSuit = 2; saveProgress() }
}
addButton(g, 410, 535, 160, 48, "BACK (B)") { screen = "menu" }
}
private def drawHelp(g: Graphics2D): Unit = {
drawBackground(g)
drawPanel(g, 115, 70, 770, 545)
drawText(g, "HOW TO PLAY", 160, 120, 34, White, true)
drawText(g, "W A S D or Arrow Keys - Move your pilot", 170, 195, 18, White)
drawText(g, "SPACE - Fire a harmless energy pulse at alien drones", 170, 238, 18, Aqua)
drawText(g, "Q - Activate your shield (costs 15 crystals)", 170, 281, 18, White)
drawText(g, "E - Activate crystal magnet for a short time", 170, 324, 18, White)
drawText(g, "P - Pause / Resume R - Restart mission", 170, 367, 18, White)
drawText(g, "B - Return to the previous screen ENTER - Continue", 170, 410, 18, White)
drawText(g, "Touching an alien drone or a tidal surge costs one life.", 170, 475, 16, Gold, true)
drawText(g, "Collect crystals and clear the drone wave to unlock the next level.", 170, 507, 15, White)
addButton(g, 405, 550, 170, 46, "BACK (B)") { screen = "menu" }
}
private def drawProfile(g: Graphics2D): Unit = {
drawBackground(g)
drawPanel(g, 170, 100, 660, 480)
drawText(g, "PILOT PROFILE", 225, 155, 34, White, true)
drawText(g, "Player: " + playerName, 235, 235, 22, Aqua, true)
drawText(g, "Highest unlocked level: " + highestLevel + " / 10", 235, 285, 18, White)
drawText(g, "Tide Crystals: " + surgeCoins, 235, 330, 18, Gold, true)
drawText(g, "Best Score: " + bestScore, 235, 375, 18, White)
drawText(g, "Active suit: " + suitName(selectedSuit), 235, 420, 18, Aqua)
drawText(g, "Aqua Suit upgrade: " + (if (suitOwned) "OWNED" else "NOT OWNED"), 235, 465, 17, White)
addButton(g, 415, 515, 170, 48, "BACK (B)") { screen = "menu" }
}
private def drawGame(g: Graphics2D): Unit = {
drawBackground(g)
drawTidalSurge(g)
var i = 0
while (i < crystals.length) {
val c = crystals(i)
drawCrystal(g, c.x, c.y, c.phase)
i += 1
}
i = 0
while (i < aliens.length) {
val a = aliens(i)
drawAlien(g, a.x, a.y, a.radius, a.boss, tick + a.phase.toInt)
if (a.boss) {
g.setColor(new Color(8, 27, 49, 210))
g.fillRoundRect((a.x - 45).toInt, (a.y - 62).toInt, 90, 9, 8, 8)
g.setColor(Coral)
g.fillRoundRect((a.x - 45).toInt, (a.y - 62).toInt, (90.0 * a.hp / (3.0 + level / 3.0)).toInt.max(3), 9, 8, 8)
}
i += 1
}
i = 0
while (i < pulses.length) {
val p = pulses(i)
g.setColor(new Color(75, 243, 255, 90))
g.fill(new Ellipse2D.Double(p.x - 13, p.y - 13, 26, 26))
g.setColor(Aqua)
g.fill(new Ellipse2D.Double(p.x - 6, p.y - 6, 12, 12))
i += 1
}
drawPlayer(g)
drawPanel(g, 18, 16, 390, 72)
drawText(g, "LEVEL " + level + " / 10", 35, 45, 18, White, true)
drawText(g, "DRONES: " + defeated + " / " + enemyGoal, 175, 45, 16, Aqua, true)
drawText(g, "LIVES: " + lives, 35, 72, 15, Coral, true)
drawText(g, "CRYSTALS: " + surgeCoins, 175, 72, 15, Gold, true)
drawPanel(g, 780, 16, 200, 54)
drawText(g, "SCORE: " + score, 805, 49, 17, White, true)
if (messageTicks > 0) {
drawPanel(g, 250, 95, 500, 42)
drawText(g, message, 270, 122, 14, Gold, true)
}
if (paused) {
drawPanel(g, 320, 260, 360, 150)
drawText(g, "MISSION PAUSED", 382, 315, 27, White, true)
drawText(g, "Press P to resume", 410, 355, 16, Aqua)
}
}
private def drawComplete(g: Graphics2D): Unit = {
drawBackground(g)
drawPanel(g, 190, 145, 620, 410)
drawStar(g, 500, 230, 45, Gold)
drawText(g, "TIDE MISSION CLEAR!", 315, 310, 34, White, true)
drawText(g, "You restored this island's crystal core.", 320, 350, 17, Aqua)
drawText(g, "Score: " + score + " Crystals earned: " + (12 + level * 3), 320, 392, 17, Gold, true)
if (level < 10) addButton(g, 330, 440, 155, 52, "NEXT LEVEL") { startLevel(level + 1) }
else addButton(g, 330, 440, 155, 52, "PLAY AGAIN") { startLevel(1) }
addButton(g, 500, 440, 155, 52, "LEVELS") { screen = "levels" }
drawText(g, "ENTER = Next Level B = Level Select", 342, 525, 14, White)
}
private def drawGameOver(g: Graphics2D): Unit = {
drawBackground(g)
drawPanel(g, 205, 165, 590, 360)
drawText(g, "THE TIDE WAS TOO STRONG", 272, 245, 29, Coral, true)
drawText(g, "Your pilot can try this mission again.", 310, 290, 17, White)
drawText(g, "Final score: " + score, 385, 335, 19, Gold, true)
addButton(g, 330, 395, 155, 52, "RETRY (R)") { startLevel(level) }
addButton(g, 500, 395, 155, 52, "LEVELS") { screen = "levels" }
}
private def startLevel(value: Int): Unit = {
level = math.max(1, math.min(10, value))
selectedLevel = level
lives = if (suitOwned) 4 else 3
if (level > 1 && suitOwned) lives = 4
shieldTicks = 0
invulnerableTicks = 0
magnetTicks = 0
attackCooldown = 0
playerX = 500.0
playerY = 500.0
defeated = 0
enemyGoal = 3 + level
score = 0
paused = false
tick = 0
waveY = WindowHeight + 80.0
waveActive = false
waveCooldown = math.max(210, 380 - level * 14)
waveAlreadyHit = false
aliens.clear()
pulses.clear()
crystals.clear()
var i = 0
while (i < enemyGoal) {
val x = 90 + random.nextInt(820)
val y = 150 + random.nextInt(300)
val boss = (level % 5 == 0 && i == enemyGoal - 1)
val hp = if (boss) 3 + level / 3 else 1
aliens += new Alien(x.toDouble, y.toDouble, boss, hp, random.nextInt(100).toDouble)
i += 1
}
i = 0
while (i < 4 + level) {
crystals += new Crystal((80 + random.nextInt(840)).toDouble, (150 + random.nextInt(430)).toDouble)
i += 1
}
screen = "game"
setMessage(if (level % 5 == 0) "Boss drone detected! Keep moving!" else "Mission started. Collect crystals and tag the drones!")
requestFocusInWindow()
}
private def firePulse(): Unit = {
if (attackCooldown > 0 || paused || screen != "game") return
var target: Alien = null
var bestDistance = Double.MaxValue
var i = 0
while (i < aliens.length) {
val a = aliens(i)
val dx = a.x - playerX
val dy = a.y - playerY
val d = dx * dx + dy * dy
if (d < bestDistance) { bestDistance = d; target = a }
i += 1
}
var vx = 0.0
var vy = -8.0
if (target != null) {
val dx = target.x - playerX
val dy = target.y - playerY
val distance = math.max(1.0, math.sqrt(dx * dx + dy * dy))
vx = dx / distance * 8.0
vy = dy / distance * 8.0
}
pulses += new Pulse(playerX, playerY - 15, vx, vy)
attackCooldown = 18
}
private def updateGame(): Unit = {
tick += 1
if (messageTicks > 0) messageTicks -= 1
if (screen != "game" || paused) return
val speed = if (shieldTicks > 0) 4.2 else 3.7
if (up) playerY -= speed
if (down) playerY += speed
if (left) playerX -= speed
if (right) playerX += speed
playerX = math.max(28.0, math.min(getWidth - 28.0, playerX))
playerY = math.max(112.0, math.min(getHeight - 30.0, playerY))
if (attackCooldown > 0) attackCooldown -= 1
if (shieldTicks > 0) shieldTicks -= 1
if (invulnerableTicks > 0) invulnerableTicks -= 1
if (magnetTicks > 0) magnetTicks -= 1
var i = 0
while (i < aliens.length) {
val a = aliens(i)
if (a.alive) {
val dx = playerX - a.x
val dy = playerY - a.y
val distance = math.max(1.0, math.sqrt(dx * dx + dy * dy))
val enemySpeed = (if (a.boss) 1.05 else 0.65) + level * 0.075
a.x += dx / distance * enemySpeed
a.y += dy / distance * enemySpeed
if (distance < a.radius + 19 && invulnerableTicks <= 0) {
if (shieldTicks > 0) { shieldTicks = math.max(0, shieldTicks - 75); setMessage("Shield absorbed the drone bump!") }
else { lives -= 1; invulnerableTicks = 95; setMessage("Careful! You lost one life.") }
a.x -= dx / distance * 45
a.y -= dy / distance * 45
if (lives <= 0) { screen = "gameover"; saveProgress() }
}
}
i += 1
}
var pi = pulses.length - 1
while (pi >= 0) {
val p = pulses(pi)
p.x += p.vx
p.y += p.vy
p.life -= 1
var removed = p.life <= 0 || p.x < 0 || p.x > getWidth || p.y < 0 || p.y > getHeight
var ei = 0
while (ei < aliens.length && !removed) {
val a = aliens(ei)
val dx = a.x - p.x
val dy = a.y - p.y
if (a.alive && dx * dx + dy * dy < (a.radius + 9) * (a.radius + 9)) {
a.hp -= 1
removed = true
if (a.hp <= 0) {
a.alive = false
defeated += 1
crystals += new Crystal(a.x, a.y)
setMessage(if (a.boss) "Boss drone tagged! Crystal core recovered!" else "Drone tagged! Collect its crystal!")
}
}
ei += 1
}
if (removed) pulses.remove(pi)
pi -= 1
}
i = 0
while (i < crystals.length) {
val c = crystals(i)
c.phase += 0.12
val dx = playerX - c.x
val dy = playerY - c.y
val distance = math.sqrt(dx * dx + dy * dy)
if (magnetTicks > 0 && distance > 1.0) { c.x += dx / distance * 2.7; c.y += dy / distance * 2.7 }
if (distance < 25) {
surgeCoins += 1
score += 10
crystals.remove(i)
i -= 1
if (surgeCoins % 5 == 0) saveProgress()
}
i += 1
}
if (!waveActive) {
waveCooldown -= 1
if (waveCooldown <= 0) {
waveActive = true
waveAlreadyHit = false
waveY = getHeight + 35.0
setMessage("TIDAL SURGE! Move to higher ground!")
}
} else {
waveY -= 2.8 + level * 0.18
if (!waveAlreadyHit && math.abs(playerY - waveY) < 30.0) {
waveAlreadyHit = true
if (shieldTicks > 0) { shieldTicks = math.max(0, shieldTicks - 90); setMessage("Your shield blocked the tidal surge!") }
else if (invulnerableTicks <= 0) {
lives -= 1
invulnerableTicks = 110
setMessage("The surge hit your pilot! Keep moving!")
if (lives <= 0) screen = "gameover"
}
}
if (waveY < -30) {
waveActive = false
waveCooldown = math.max(190, 380 - level * 14)
}
}
if (defeated >= enemyGoal && screen == "game") {
screen = "complete"
val reward = 12 + level * 3
surgeCoins += reward
highestLevel = math.max(highestLevel, math.min(10, level + 1))
bestScore = math.max(bestScore, score)
saveProgress()
setMessage("Mission clear! You earned " + reward + " Tide Crystals.")
}
}
override def paintComponent(graphics: Graphics): Unit = {
super.paintComponent(graphics)
val g = graphics.create().asInstanceOf[Graphics2D]
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
buttons.clear()
screen match {
case "menu" => drawMenu(g)
case "levels" => drawLevels(g)
case "shop" => drawShop(g)
case "help" => drawHelp(g)
case "profile" => drawProfile(g)
case "game" => drawGame(g)
case "complete" => drawComplete(g)
case "gameover" => drawGameOver(g)
case _ => drawMenu(g)
}
g.dispose()
}
addMouseListener(new MouseAdapter {
override def mousePressed(event: MouseEvent): Unit = {
if (screen == "game" && event.getButton == MouseEvent.BUTTON3) {
firePulse()
return
}
if (screen == "levels") {
var levelIndex = 1
while (levelIndex <= 10) {
val col = (levelIndex - 1) % 5
val row = (levelIndex - 1) / 5
val rect = new Rectangle(145 + col * 145, 185 + row * 145, 112, 92)
if (rect.contains(event.getPoint) && levelIndex <= highestLevel) selectedLevel = levelIndex
levelIndex += 1
}
}
var i = buttons.length - 1
while (i >= 0) {
val b = buttons(i)
if (b.rect.contains(event.getPoint) && b.enabled) {
b.action()
repaint()
requestFocusInWindow()
return
}
i -= 1
}
if (screen == "game" && !paused && event.getButton == MouseEvent.BUTTON1) {
val mx = event.getX.toDouble
val my = event.getY.toDouble
val dx = mx - playerX
val dy = my - playerY
val distance = math.max(1.0, math.sqrt(dx * dx + dy * dy))
pulses += new Pulse(playerX, playerY - 15, dx / distance * 8.0, dy / distance * 8.0)
attackCooldown = 18
}
}
})
addKeyListener(new KeyAdapter {
override def keyPressed(event: KeyEvent): Unit = {
val code = event.getKeyCode
if (code == KeyEvent.VK_W || code == KeyEvent.VK_UP) up = true
if (code == KeyEvent.VK_S || code == KeyEvent.VK_DOWN) down = true
if (code == KeyEvent.VK_A || code == KeyEvent.VK_LEFT) left = true
if (code == KeyEvent.VK_D || code == KeyEvent.VK_RIGHT) right = true
if (code == KeyEvent.VK_P && screen == "game") paused = !paused
if (code == KeyEvent.VK_B) {
if (screen == "game" || screen == "complete" || screen == "gameover" || screen == "shop" || screen == "help" || screen == "profile") screen = "menu"
else if (screen == "levels") screen = "menu"
}
if (code == KeyEvent.VK_ENTER) {
if (screen == "menu") screen = "levels"
else if (screen == "levels") startLevel(selectedLevel)
else if (screen == "complete" && level < 10) startLevel(level + 1)
else if (screen == "complete" && level == 10) screen = "levels"
}
if (code == KeyEvent.VK_SPACE && screen == "game" && !paused) firePulse()
if (code == KeyEvent.VK_Q && screen == "game" && !paused) {
if (surgeCoins >= 15 && shieldTicks <= 0) {
surgeCoins -= 15
shieldTicks = 190
saveProgress()
setMessage("Aqua shield activated!")
} else if (shieldTicks > 0) setMessage("Shield is already active.")
else setMessage("You need 15 Tide Crystals for a shield.")
}
if (code == KeyEvent.VK_E && screen == "game" && !paused) {
if (surgeCoins >= 10 && magnetTicks <= 0) {
surgeCoins -= 10
magnetTicks = 240
saveProgress()
setMessage("Crystal magnet activated!")
} else if (magnetTicks > 0) setMessage("Crystal magnet is already active.")
else setMessage("You need 10 Tide Crystals for a magnet.")
}
if (code == KeyEvent.VK_R && screen == "gameover") startLevel(level)
repaint()
}
override def keyReleased(event: KeyEvent): Unit = {
val code = event.getKeyCode
if (code == KeyEvent.VK_W || code == KeyEvent.VK_UP) up = false
if (code == KeyEvent.VK_S || code == KeyEvent.VK_DOWN) down = false
if (code == KeyEvent.VK_A || code == KeyEvent.VK_LEFT) left = false
if (code == KeyEvent.VK_D || code == KeyEvent.VK_RIGHT) right = false
}
})
addFocusListener(new java.awt.event.FocusAdapter {
override def focusLost(event: java.awt.event.FocusEvent): Unit = {
up = false
down = false
left = false
right = false
}
})
}
def main(args: Array[String]): Unit = {
SwingUtilities.invokeLater(new Runnable {
override def run(): Unit = {
val entered = JOptionPane.showInputDialog(null, "Enter your Alien Tidal Surge pilot name:",
"Alien Tidal Surge Login", JOptionPane.QUESTION_MESSAGE)
val name = if (entered == null || entered.trim.isEmpty) "Pilot" else entered.trim.take(18)
val panel = new GamePanel(name)
val frame = new JFrame(AppTitle)
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
frame.setContentPane(panel)
frame.pack()
frame.setMinimumSize(new Dimension(850, 620))
frame.setLocationRelativeTo(null)
frame.setVisible(true)
panel.startLoop()
panel.requestFocusInWindow()
}
})
}
}
AlienTidalSurge.main(Array[String]())