Code Sketch
Bixuu spring season event 9
Category: Programming
import java.awt.BorderLayout
import java.awt.Color
import java.awt.Dimension
import java.awt.FlowLayout
import java.awt.Font
import java.awt.GradientPaint
import java.awt.Graphics
import java.awt.Graphics2D
import java.awt.Polygon
import java.awt.RenderingHints
import java.awt.event.ActionEvent
import java.awt.event.ActionListener
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
import java.io.BufferedReader
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import java.io.PrintWriter
import java.net.InetSocketAddress
import java.net.ServerSocket
import java.net.Socket
import javax.swing.AbstractAction
import javax.swing.BorderFactory
import javax.swing.JButton
import javax.swing.JFrame
import javax.swing.JLabel
import javax.swing.JPanel
import javax.swing.JTextField
import javax.swing.JComponent
import javax.swing.KeyStroke
import javax.swing.SwingConstants
import javax.swing.SwingUtilities
import javax.swing.Timer
import javax.swing.WindowConstants
import scala.collection.mutable.ArrayBuffer
import scala.util.Random
final case class HorsemanCandy(x: Double, y: Double, kind: Int)
class HorsemanLanPeer(
hostMode: Boolean,
hostAddress: String,
port: Int,
localPacket: () => String,
receivePacket: String => Unit,
reportStatus: String => Unit
) {
@volatile private var running = true
@volatile private var serverSocket: ServerSocket = null
@volatile private var peerSocket: Socket = null
def start(): Unit = {
val worker = new Thread(new Runnable {
override def run(): Unit = {
try {
if (hostMode) runHost() else runClient()
} catch {
case ex: Exception =>
if (running) reportStatus("LAN unavailable: " + ex.getMessage + ". Continuing locally.")
}
}
}, "Horseman-LAN")
worker.setDaemon(true)
worker.start()
}
private def runHost(): Unit = {
serverSocket = new ServerSocket(port)
reportStatus("LAN Host ready. Ask the other player to Join using your local IP, port " + port + ".")
peerSocket = serverSocket.accept()
peerSocket.setSoTimeout(5000)
peerSocket.setTcpNoDelay(true)
communicateAsHost(peerSocket)
}
private def communicateAsHost(socket: Socket): Unit = {
val input = new BufferedReader(new InputStreamReader(socket.getInputStream, "UTF-8"))
val output = new PrintWriter(new OutputStreamWriter(socket.getOutputStream, "UTF-8"), true)
reportStatus("LAN connected: player position sharing is active.")
var line = input.readLine()
while (running && line != null) {
receivePacket(line)
output.println(localPacket())
line = input.readLine()
}
if (running) reportStatus("LAN player disconnected. You can keep playing solo.")
}
private def runClient(): Unit = {
peerSocket = new Socket()
peerSocket.connect(new InetSocketAddress(hostAddress, port), 5000)
peerSocket.setSoTimeout(5000)
peerSocket.setTcpNoDelay(true)
val input = new BufferedReader(new InputStreamReader(peerSocket.getInputStream, "UTF-8"))
val output = new PrintWriter(new OutputStreamWriter(peerSocket.getOutputStream, "UTF-8"), true)
reportStatus("Connected to LAN host. Sharing player position.")
while (running) {
output.println(localPacket())
val line = input.readLine()
if (line == null) {
running = false
} else {
receivePacket(line)
Thread.sleep(90L)
}
}
}
def stop(): Unit = {
running = false
try { if (peerSocket != null) peerSocket.close() } catch { case _: Exception => () }
try { if (serverSocket != null) serverSocket.close() } catch { case _: Exception => () }
}
}
class HorsemanWorldPanel(playerName: String, hudLabel: JLabel, statusLabel: JLabel) extends JPanel {
private val random = new Random()
private val worldWidth = 1000.0
private val worldHeight = 560.0
private val baseX = 132.0
private val baseY = 455.0
@volatile private var playerX = baseX
@volatile private var playerY = baseY
private var targetX = -1.0
private var targetY = -1.0
private var moveUp = false
private var moveDown = false
private var moveLeft = false
private var moveRight = false
private var candyCount = 0
private var coins = 0
private var bankedCandy = 0
private var capturedHorsemen = 0
private var netCharges = 3
private var carryingSeal = false
private var ghostMode = false
private var paused = false
private var dashTicks = 0
private var dashCooldown = 0
private var netFlashTicks = 0
private var tickCount = 0
private var horsemanOneActive = false
private var horsemanOneX = 865.0
private var horsemanOneY = 155.0
private var horsemanTwoActive = false
private var horsemanTwoX = 930.0
private var horsemanTwoY = 205.0
private var horsemanTwoTicks = 0
private var candyRushActive = false
private var candyRushTicks = 0
private var candyRushCountdown = 18 * 30
private var candyRushCount = 0
private var candyRushKind = 0
private var candyRushSpawnTicks = 0
private val candies = new ArrayBuffer[HorsemanCandy]()
@volatile private var remoteVisible = false
@volatile private var remoteX = 800.0
@volatile private var remoteY = 430.0
@volatile private var remoteCandy = 0
@volatile private var remoteName = "LAN Player"
@volatile private var remoteGhost = false
setPreferredSize(new Dimension(1000, 560))
setMinimumSize(new Dimension(800, 450))
setFocusable(true)
setBackground(new Color(10, 12, 32))
setToolTipText("WASD or arrow keys to move. Click the ground to walk.")
addMouseListener(new MouseAdapter {
override def mousePressed(event: MouseEvent): Unit = {
if (!paused) {
val sx = worldWidth / math.max(1, getWidth).toDouble
val sy = worldHeight / math.max(1, getHeight).toDouble
targetX = event.getX.toDouble * sx
targetY = event.getY.toDouble * sy
}
requestFocusInWindow()
}
})
private val gameTimer = new Timer(33, new ActionListener {
override def actionPerformed(event: ActionEvent): Unit = tickGame()
})
def startGame(): Unit = {
candies.clear()
var i = 0
while (i < 18) {
spawnOneCandy()
i += 1
}
gameTimer.start()
updateHud()
repaint()
}
def stopGame(): Unit = gameTimer.stop()
def setDirection(direction: String, pressed: Boolean): Unit = {
direction match {
case "up" => moveUp = pressed
case "down" => moveDown = pressed
case "left" => moveLeft = pressed
case "right" => moveRight = pressed
case _ =>
}
if (pressed) {
targetX = -1.0
targetY = -1.0
}
}
def releaseHorseman(): Unit = {
if (!horsemanOneActive && !carryingSeal) {
horsemanOneX = 875.0
horsemanOneY = 150.0 + random.nextInt(180).toDouble
horsemanOneActive = true
statusLabel.setText("The first Headless Horseman is released and chasing you. Collect Candy and escape!")
} else if (carryingSeal) {
statusLabel.setText("Take the captured seal back to your Base before releasing another Horseman.")
} else {
statusLabel.setText("The first Horseman is already chasing you!")
}
repaint()
}
def useMagicNet(): Unit = {
if (paused) return
if (netCharges <= 0) {
statusLabel.setText("No Magic Net charges left. Return to Base to recharge.")
return
}
val d1 = if (horsemanOneActive) distance(playerX, playerY, horsemanOneX, horsemanOneY) else 9999.0
val d2 = if (horsemanTwoActive) distance(playerX, playerY, horsemanTwoX, horsemanTwoY) else 9999.0
if (d1 < 145.0 || d2 < 145.0) {
netCharges -= 1
carryingSeal = true
if (d1 <= d2) horsemanOneActive = false else horsemanTwoActive = false
netFlashTicks = 24
statusLabel.setText("MAGIC NET! Horseman sealed. Return to the glowing Base to claim your reward.")
} else {
statusLabel.setText("Move closer to a Horseman before using the Magic Net.")
}
updateHud()
repaint()
}
def dash(): Unit = {
if (!paused && dashCooldown <= 0) {
dashTicks = 12
dashCooldown = 150
statusLabel.setText("COSMIC DASH! Keep moving toward the Base or Candy!")
}
}
def returnToBase(): Unit = {
targetX = baseX
targetY = baseY
moveUp = false
moveDown = false
moveLeft = false
moveRight = false
statusLabel.setText("Returning to Base. Reach the glowing circle to become safe and bank a captured seal.")
}
def togglePause(): Unit = {
paused = !paused
statusLabel.setText(if (paused) "Paused. Press P or Resume to continue." else "Run! Collect Candy, avoid the Horsemen and return to Base.")
repaint()
}
def packetSnapshot(): String = {
val safeName = playerName.replace("|", " ").replace("\n", " ")
safeName + "|" + playerX + "|" + playerY + "|" + candyCount + "|" + ghostMode
}
def updateRemote(packet: String): Unit = {
try {
val parts = packet.split("\\|", -1)
if (parts.length >= 5) {
remoteName = parts(0)
remoteX = parts(1).toDouble
remoteY = parts(2).toDouble
remoteCandy = parts(3).toInt
remoteGhost = parts(4).toBoolean
remoteVisible = true
}
} catch {
case _: Exception => ()
}
}
private def isInBase(x: Double, y: Double): Boolean = {
x >= 42.0 && x <= 220.0 && y >= 385.0 && y <= 535.0
}
private def playerSpeed: Double = {
val candyBoost = math.min(2.3, candyCount.toDouble * 0.045)
val normal = 2.5 + candyBoost
if (ghostMode) math.max(1.05, normal * 0.48) else normal
}
private def distance(x1: Double, y1: Double, x2: Double, y2: Double): Double = {
val dx = x1 - x2
val dy = y1 - y2
math.sqrt(dx * dx + dy * dy)
}
private def spawnOneCandy(): Unit = {
val x = 230.0 + random.nextInt(720).toDouble
val y = 260.0 + random.nextInt(245).toDouble
val kind = random.nextInt(4)
candies += HorsemanCandy(x, y, kind)
}
private def startCandyRush(): Unit = {
candyRushActive = true
candyRushCount += 1
candyRushKind = random.nextInt(4)
candyRushTicks = (14 + random.nextInt(8)) * 30
candyRushSpawnTicks = 0
var i = 0
while (i < 12) {
spawnOneCandy()
i += 1
}
val eventName = candyRushKind match {
case 0 => "PUMPKIN CANDY RUSH"
case 1 => "MOON CANDY RUSH"
case 2 => "SHADOW CANDY RUSH"
case _ => "STAR CANDY RUSH"
}
statusLabel.setText(eventName + "! Collect as much Candy as you can. Every two Rush events summon Horseman Two for 20 seconds!")
if (candyRushCount % 2 == 0) {
horsemanTwoActive = true
horsemanTwoX = 930.0
horsemanTwoY = 145.0 + random.nextInt(220).toDouble
horsemanTwoTicks = 20 * 30
}
}
private def moveToward(x: Double, y: Double, tx: Double, ty: Double, speed: Double): (Double, Double) = {
val dx = tx - x
val dy = ty - y
val length = math.sqrt(dx * dx + dy * dy)
if (length < 0.001) (x, y)
else (x + dx / length * speed, y + dy / length * speed)
}
private def tickGame(): Unit = {
if (!paused) {
tickCount += 1
if (dashCooldown > 0) dashCooldown -= 1
if (dashTicks > 0) dashTicks -= 1
if (netFlashTicks > 0) netFlashTicks -= 1
var dx = 0.0
var dy = 0.0
if (moveLeft) dx -= 1.0
if (moveRight) dx += 1.0
if (moveUp) dy -= 1.0
if (moveDown) dy += 1.0
if (dx == 0.0 && dy == 0.0 && targetX >= 0.0) {
dx = targetX - playerX
dy = targetY - playerY
val length = math.sqrt(dx * dx + dy * dy)
if (length < 5.0) {
targetX = -1.0
targetY = -1.0
dx = 0.0
dy = 0.0
} else {
dx = dx / length
dy = dy / length
}
} else if (dx != 0.0 || dy != 0.0) {
val length = math.sqrt(dx * dx + dy * dy)
dx = dx / length
dy = dy / length
}
var speed = playerSpeed
if (dashTicks > 0) speed *= 2.25
playerX = math.max(28.0, math.min(worldWidth - 28.0, playerX + dx * speed))
playerY = math.max(235.0, math.min(worldHeight - 24.0, playerY + dy * speed))
var candyIndex = candies.length - 1
while (candyIndex >= 0) {
val candy = candies(candyIndex)
if (distance(playerX, playerY, candy.x, candy.y) < 23.0) {
val reward = candy.kind match {
case 0 => 1
case 1 => 2
case 2 => 3
case _ => 4
}
candyCount += reward
candies.remove(candyIndex)
if (candyRushActive) coins += reward * 2 else coins += reward
}
candyIndex -= 1
}
if (tickCount % 95 == 0 && candies.length < 24) spawnOneCandy()
if (candyRushActive) {
candyRushTicks -= 1
candyRushSpawnTicks += 1
if (candyRushSpawnTicks >= 24 && candies.length < 38) {
candyRushSpawnTicks = 0
var i = 0
while (i < 3) {
spawnOneCandy()
i += 1
}
}
if (candyRushTicks <= 0) {
candyRushActive = false
candyRushCountdown = (24 + random.nextInt(23)) * 30
statusLabel.setText("Candy Rush ended. Next event time is randomized. Keep collecting Candy!")
}
} else {
candyRushCountdown -= 1
if (candyRushCountdown <= 0) startCandyRush()
}
if (horsemanTwoActive) {
horsemanTwoTicks -= 1
if (horsemanTwoTicks <= 0) {
horsemanTwoActive = false
statusLabel.setText("Horseman Two vanished after 20 seconds. Prepare for the next Candy Rush!")
}
}
val safe = isInBase(playerX, playerY)
if (safe && ghostMode) {
ghostMode = false
statusLabel.setText("You reached Base! The Ghost Curse is cleared and your full Candy speed is restored.")
}
if (safe && carryingSeal) {
carryingSeal = false
capturedHorsemen += 1
bankedCandy += 80
candyCount += 20
coins += 150
netCharges = math.min(5, netCharges + 2)
statusLabel.setText("SEAL DELIVERED! +80 Candy, +150 Coins, +20 speed Candy and 2 Net charges.")
}
if (horsemanOneActive && !safe) {
val p = moveToward(horsemanOneX, horsemanOneY, playerX, playerY, 1.75 + math.min(0.8, candyRushCount * 0.04))
horsemanOneX = p._1
horsemanOneY = p._2
if (distance(playerX, playerY, horsemanOneX, horsemanOneY) < 29.0) becomeGhost("The first Horseman caught you!")
}
if (horsemanTwoActive && !safe) {
val p = moveToward(horsemanTwoX, horsemanTwoY, playerX, playerY, 2.15)
horsemanTwoX = p._1
horsemanTwoY = p._2
if (distance(playerX, playerY, horsemanTwoX, horsemanTwoY) < 31.0) {
horsemanTwoActive = false
becomeGhost("Horseman Two caught you! Ghost Curse: your speed is reduced. Reach Base to recover.")
}
}
if (tickCount % 5 == 0) updateHud()
repaint()
}
}
private def becomeGhost(message: String): Unit = {
ghostMode = true
playerX = 520.0
playerY = 315.0
targetX = -1.0
targetY = -1.0
moveUp = false
moveDown = false
moveLeft = false
moveRight = false
horsemanOneActive = false
horsemanTwoActive = false
candyCount = math.max(0, candyCount - 8)
statusLabel.setText(message + " Run to the glowing Base!")
}
private def updateHud(): Unit = {
val eventText = if (candyRushActive) {
val seconds = math.max(0, candyRushTicks / 30)
"CANDY RUSH " + seconds + "s"
} else {
"Next Candy Rush: " + math.max(0, candyRushCountdown / 30) + "s"
}
val secondText = if (horsemanTwoActive) " | Horseman 2: " + math.max(0, horsemanTwoTicks / 30) + "s" else ""
val ghostText = if (ghostMode) " | GHOST CURSE: SPEED DRAINED" else ""
val sealText = if (carryingSeal) " | CARRYING SEAL: RETURN TO BASE" else ""
hudLabel.setText("Player: " + playerName + " | Candy: " + candyCount + " | Speed: " + playerSpeed.toInt.toString + " | Coins: " + coins + " | Nets: " + netCharges + " | " + eventText + secondText + ghostText + sealText)
}
private def colorForCandy(kind: Int): Color = {
kind match {
case 0 => new Color(255, 145, 50)
case 1 => new Color(125, 180, 255)
case 2 => new Color(195, 115, 255)
case _ => new Color(255, 225, 115)
}
}
private def drawCandy(g: Graphics2D, candy: HorsemanCandy): Unit = {
val scale = 0.62 + candy.y / 560.0 * 0.55
val size = 12.0 * scale
g.setColor(new Color(0, 0, 0, 90))
g.fillOval((candy.x - size).toInt, (candy.y + size * 0.7).toInt, (size * 2.2).toInt, (size * 0.7).toInt)
g.setColor(colorForCandy(candy.kind))
g.fillOval((candy.x - size).toInt, (candy.y - size).toInt, (size * 2).toInt, (size * 2).toInt)
g.setColor(new Color(255, 255, 255, 190))
g.fillOval((candy.x - size * 0.45).toInt, (candy.y - size * 0.55).toInt, (size * 0.55).toInt, (size * 0.55).toInt)
}
private def drawBase(g: Graphics2D): Unit = {
g.setColor(new Color(40, 210, 255, 45))
g.fillOval(45, 392, 185, 128)
g.setColor(new Color(75, 225, 255, 210))
g.setStroke(new java.awt.BasicStroke(4.0f))
g.drawOval(45, 392, 185, 128)
g.setColor(new Color(170, 245, 255))
g.setFont(new Font("SansSerif", Font.BOLD, 17))
g.drawString("SAFE BASE", 88, 447)
g.setFont(new Font("SansSerif", Font.PLAIN, 11))
g.drawString("Return here to clear Ghost Curse", 58, 466)
}
private def drawPlayer(g: Graphics2D, x: Double, y: Double, ghost: Boolean, name: String): Unit = {
val s = 0.62 + y / 560.0 * 0.62
val w = 25.0 * s
val h = 42.0 * s
g.setColor(new Color(0, 0, 0, 95))
g.fillOval((x - w).toInt, (y + h * 0.54).toInt, (w * 2.2).toInt, (w * 0.68).toInt)
val robeColor = if (ghost) new Color(120, 220, 255, 185) else new Color(65, 165, 255)
g.setColor(robeColor)
val robe = new Polygon(
Array((x - w * 0.55).toInt, (x + w * 0.55).toInt, (x + w).toInt, (x - w).toInt),
Array((y - h * 0.05).toInt, (y - h * 0.05).toInt, (y + h).toInt, (y + h).toInt), 4
)
g.fillPolygon(robe)
g.setColor(new Color(255, 220, 180))
g.fillOval((x - w * 0.42).toInt, (y - h * 0.82).toInt, (w * 0.84).toInt, (w * 0.84).toInt)
g.setColor(new Color(235, 245, 255))
g.setFont(new Font("SansSerif", Font.BOLD, math.max(10, (12 * s).toInt)))
g.drawString(name.take(12), (x - w).toInt, (y - h).toInt)
if (ghost) {
g.setColor(new Color(150, 245, 255, 120))
g.drawOval((x - w * 1.45).toInt, (y - h * 1.05).toInt, (w * 2.9).toInt, (h * 2.2).toInt)
}
}
private def drawHorseman(g: Graphics2D, x: Double, y: Double, second: Boolean): Unit = {
val s = 0.68 + y / 560.0 * 0.62
val w = 30.0 * s
val bodyColor = if (second) new Color(115, 150, 220, 180) else new Color(42, 35, 58)
g.setColor(new Color(0, 0, 0, 110))
g.fillOval((x - w * 1.2).toInt, (y + w * 0.7).toInt, (w * 2.8).toInt, (w * 0.8).toInt)
g.setColor(bodyColor)
g.fillOval((x - w).toInt, (y - w * 0.25).toInt, (w * 2.1).toInt, (w * 1.15).toInt)
g.fillRect((x - w * 0.75).toInt, (y + w * 0.4).toInt, (w * 0.35).toInt, (w * 0.9).toInt)
g.fillRect((x + w * 0.45).toInt, (y + w * 0.4).toInt, (w * 0.35).toInt, (w * 0.9).toInt)
g.setColor(if (second) new Color(90, 190, 255, 210) else new Color(24, 18, 34))
val cloak = new Polygon(
Array((x - w * 0.45).toInt, (x + w * 0.55).toInt, (x + w * 0.8).toInt, (x - w * 0.9).toInt),
Array((y - w * 1.6).toInt, (y - w * 1.6).toInt, (y + w * 0.8).toInt, (y + w * 0.8).toInt), 4
)
g.fillPolygon(cloak)
g.setColor(new Color(255, 130, 45))
g.fillOval((x - w * 0.22).toInt, (y - w * 2.15).toInt, (w * 0.65).toInt, (w * 0.75).toInt)
g.setColor(new Color(255, 220, 110))
g.fillOval((x - w * 0.06).toInt, (y - w * 2.0).toInt, (w * 0.23).toInt, (w * 0.28).toInt)
g.setColor(if (second) new Color(140, 220, 255) else new Color(255, 100, 55))
g.setFont(new Font("SansSerif", Font.BOLD, math.max(10, (11 * s).toInt)))
g.drawString(if (second) "HORSEMAN 2" else "HORSEMAN 1", (x - w * 1.1).toInt, (y - w * 2.35).toInt)
}
override protected def paintComponent(graphics: Graphics): Unit = {
super.paintComponent(graphics)
val g = graphics.create().asInstanceOf[Graphics2D]
val sx = getWidth.toDouble / worldWidth
val sy = getHeight.toDouble / worldHeight
g.scale(sx, sy)
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g.setPaint(new GradientPaint(0.0f, 0.0f, new Color(9, 12, 42), 0.0f, 270.0f, new Color(74, 32, 93)))
g.fillRect(0, 0, 1000, 560)
var star = 0
while (star < 85) {
val x = (star * 137 + 41) % 1000
val y = (star * 73 + 19) % 205
val r = if (star % 7 == 0) 3 else 1
g.setColor(if (star % 4 == 0) new Color(255, 215, 150, 200) else new Color(205, 220, 255, 175))
g.fillOval(x, y, r, r)
star += 1
}
g.setColor(new Color(160, 90, 210, 70))
g.fillOval(660, 28, 220, 220)
g.setColor(new Color(255, 170, 90, 210))
g.fillOval(720, 76, 115, 115)
g.setColor(new Color(18, 15, 45))
g.fillOval(760, 55, 112, 112)
g.setColor(new Color(255, 195, 120, 130))
g.drawOval(702, 60, 150, 150)
val ridgeOne = new Polygon(Array(0, 0, 120, 220, 340, 470, 600, 740, 860, 1000, 1000), Array(250, 210, 170, 225, 165, 225, 175, 230, 170, 220, 290), 11)
g.setColor(new Color(35, 28, 72))
g.fillPolygon(ridgeOne)
val ridgeTwo = new Polygon(Array(0, 120, 250, 390, 520, 660, 800, 1000, 1000, 0), Array(285, 240, 270, 215, 275, 230, 275, 235, 560, 560), 10)
g.setColor(new Color(27, 41, 70))
g.fillPolygon(ridgeTwo)
val ground = new Polygon(Array(0, 1000, 1000, 0), Array(260, 260, 560, 560), 4)
g.setPaint(new GradientPaint(0.0f, 260.0f, new Color(35, 65, 78), 0.0f, 560.0f, new Color(9, 36, 44)))
g.fillPolygon(ground)
g.setColor(new Color(95, 165, 155, 65))
var line = 0
while (line < 13) {
val yy = 280 + line * 23
g.drawLine(0, yy, 1000, yy)
line += 1
}
line = 0
while (line < 17) {
val xx = line * 62
g.drawLine(500, 260, xx, 560)
line += 1
}
drawBase(g)
if (candyRushActive) {
g.setColor(new Color(255, 190, 85, 35))
g.fillRect(0, 250, 1000, 310)
g.setColor(new Color(255, 215, 130))
g.setFont(new Font("SansSerif", Font.BOLD, 17))
g.drawString("CANDY RUSH IS LIVE!", 395, 285)
}
var i = 0
while (i < candies.length) {
drawCandy(g, candies(i))
i += 1
}
if (horsemanOneActive) drawHorseman(g, horsemanOneX, horsemanOneY, false)
if (horsemanTwoActive) drawHorseman(g, horsemanTwoX, horsemanTwoY, true)
if (remoteVisible) drawPlayer(g, remoteX, remoteY, remoteGhost, remoteName)
drawPlayer(g, playerX, playerY, ghostMode, playerName)
if (carryingSeal) {
g.setColor(new Color(130, 245, 255))
g.setFont(new Font("SansSerif", Font.BOLD, 15))
g.drawString("HORSEMAN SEAL - RETURN TO BASE", 330, 530)
}
if (netFlashTicks > 0) {
g.setColor(new Color(115, 245, 255, 170))
g.setStroke(new java.awt.BasicStroke(4.0f))
g.drawOval((playerX - 52).toInt, (playerY - 63).toInt, 104, 104)
}
if (ghostMode) {
g.setColor(new Color(125, 225, 255, 190))
g.setFont(new Font("SansSerif", Font.BOLD, 19))
g.drawString("GHOST CURSE! RUN TO BASE!", 360, 45)
}
if (paused) {
g.setColor(new Color(0, 0, 0, 155))
g.fillRect(0, 0, 1000, 560)
g.setColor(Color.WHITE)
g.setFont(new Font("SansSerif", Font.BOLD, 42))
g.drawString("PAUSED", 410, 285)
}
g.dispose()
}
}
class HorsemanGameScreen(
name: String,
mode: String,
onBack: () => Unit,
statusCallback: String => Unit
) extends JPanel(new BorderLayout()) {
val statusLabel = new JLabel("Collect Candy to increase your speed. Press R to release Horseman One.", SwingConstants.CENTER)
val hudLabel = new JLabel("", SwingConstants.CENTER)
val world = new HorsemanWorldPanel(name, hudLabel, statusLabel)
hudLabel.setOpaque(true)
hudLabel.setBackground(new Color(20, 18, 45))
hudLabel.setForeground(new Color(245, 230, 255))
hudLabel.setFont(new Font("SansSerif", Font.BOLD, 13))
hudLabel.setBorder(BorderFactory.createEmptyBorder(8, 6, 8, 6))
statusLabel.setOpaque(true)
statusLabel.setBackground(new Color(35, 28, 58))
statusLabel.setForeground(new Color(245, 230, 255))
statusLabel.setFont(new Font("SansSerif", Font.PLAIN, 12))
statusLabel.setBorder(BorderFactory.createEmptyBorder(7, 8, 7, 8))
private val buttons = new JPanel(new FlowLayout(FlowLayout.CENTER, 8, 7))
buttons.setBackground(new Color(24, 20, 48))
private def addGameButton(text: String, action: () => Unit): Unit = {
val button = new JButton(text)
button.setFont(new Font("SansSerif", Font.BOLD, 12))
button.addActionListener(new ActionListener {
override def actionPerformed(event: ActionEvent): Unit = action()
})
buttons.add(button)
}
addGameButton("RELEASE HORSEMAN [R]", () => world.releaseHorseman())
addGameButton("MAGIC NET [F]", () => world.useMagicNet())
addGameButton("COSMIC DASH [SPACE]", () => world.dash())
addGameButton("RETURN TO BASE [B]", () => world.returnToBase())
addGameButton("PAUSE [P]", () => world.togglePause())
addGameButton("BACK TO LOGIN", () => onBack())
add(hudLabel, BorderLayout.NORTH)
add(world, BorderLayout.CENTER)
val south = new JPanel(new BorderLayout())
south.setBackground(new Color(24, 20, 48))
south.add(buttons, BorderLayout.CENTER)
south.add(statusLabel, BorderLayout.SOUTH)
add(south, BorderLayout.SOUTH)
setPreferredSize(new Dimension(1020, 690))
def start(): Unit = world.startGame()
def stop(): Unit = world.stopGame()
def reportLan(message: String): Unit = {
SwingUtilities.invokeLater(new Runnable {
override def run(): Unit = {
statusLabel.setText(message)
statusCallback(message)
}
})
}
}
object HeadlessHorsemanChaseEvent9 {
private val lanPort = 48888
private var frame: JFrame = null
private var currentGame: HorsemanGameScreen = null
private var lanPeer: HorsemanLanPeer = null
private var currentPlayerName = "Player"
def main(args: Array[String]): Unit = {
SwingUtilities.invokeLater(new Runnable {
override def run(): Unit = showLogin()
})
}
private def showLogin(): Unit = {
if (frame == null) {
frame = new JFrame("SPRING SEASON EVENT 9 - THE HEADLESS HORSEMAN CHASE")
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE)
frame.setResizable(true)
frame.addWindowListener(new WindowAdapter {
override def windowClosing(event: WindowEvent): Unit = cleanup()
})
}
val root = new JPanel(new BorderLayout(12, 12))
root.setBackground(new Color(12, 13, 38))
root.setBorder(BorderFactory.createEmptyBorder(24, 28, 24, 28))
val title = new JLabel("THE HEADLESS HORSEMAN CHASE", SwingConstants.CENTER)
title.setForeground(new Color(255, 198, 120))
title.setFont(new Font("SansSerif", Font.BOLD, 30))
val subtitle = new JLabel("SPRING SEASON EVENT 9 | CANDY RUSH ? MAGIC NET ? ESCAPE TO BASE", SwingConstants.CENTER)
subtitle.setForeground(new Color(195, 210, 245))
subtitle.setFont(new Font("SansSerif", Font.BOLD, 13))
val header = new JPanel(new BorderLayout(4, 8))
header.setOpaque(false)
header.add(title, BorderLayout.CENTER)
header.add(subtitle, BorderLayout.SOUTH)
root.add(header, BorderLayout.NORTH)
val form = new JPanel(new java.awt.GridLayout(0, 1, 8, 8))
form.setOpaque(false)
val nameLabel = new JLabel("PLAYER NAME", SwingConstants.CENTER)
nameLabel.setForeground(Color.WHITE)
nameLabel.setFont(new Font("SansSerif", Font.BOLD, 15))
val nameField = new JTextField("Player", 20)
nameField.setHorizontalAlignment(SwingConstants.CENTER)
nameField.setFont(new Font("SansSerif", Font.PLAIN, 18))
val ipLabel = new JLabel("HOST IP FOR JOIN LAN (example: 192.168.1.10)", SwingConstants.CENTER)
ipLabel.setForeground(new Color(200, 215, 245))
val ipField = new JTextField("127.0.0.1", 20)
ipField.setHorizontalAlignment(SwingConstants.CENTER)
ipField.setFont(new Font("SansSerif", Font.PLAIN, 16))
form.add(nameLabel)
form.add(nameField)
form.add(ipLabel)
form.add(ipField)
val info = new JLabel("Candy makes you faster. Every second Candy Rush brings Horseman Two for 20 seconds.", SwingConstants.CENTER)
info.setForeground(new Color(255, 215, 155))
form.add(info)
root.add(form, BorderLayout.CENTER)
val controls = new JPanel(new FlowLayout(FlowLayout.CENTER, 14, 12))
controls.setOpaque(false)
def makeButton(text: String, action: () => Unit): JButton = {
val button = new JButton(text)
button.setFont(new Font("SansSerif", Font.BOLD, 14))
button.addActionListener(new ActionListener {
override def actionPerformed(event: ActionEvent): Unit = action()
})
button
}
controls.add(makeButton("SINGLE PLAYER", () => beginGame(nameField.getText, "solo", "")))
controls.add(makeButton("HOST LAN", () => beginGame(nameField.getText, "host", "")))
controls.add(makeButton("JOIN LAN", () => beginGame(nameField.getText, "client", ipField.getText.trim)))
root.add(controls, BorderLayout.SOUTH)
frame.setContentPane(root)
frame.setPreferredSize(new Dimension(900, 520))
frame.pack()
frame.setLocationRelativeTo(null)
frame.setVisible(true)
}
private def beginGame(rawName: String, mode: String, hostAddress: String): Unit = {
val trimmed = if (rawName == null) "" else rawName.trim
currentPlayerName = if (trimmed.isEmpty) "Player" else trimmed.take(16)
if (lanPeer != null) lanPeer.stop()
if (currentGame != null) currentGame.stop()
currentGame = new HorsemanGameScreen(
currentPlayerName,
mode,
() => returnToLogin(),
message => ()
)
frame.setContentPane(currentGame)
frame.setPreferredSize(new Dimension(1050, 760))
frame.pack()
frame.setLocationRelativeTo(null)
installKeyBindings(currentGame)
currentGame.start()
if (mode == "host") {
lanPeer = new HorsemanLanPeer(
true,
"",
lanPort,
() => currentGame.world.packetSnapshot(),
packet => currentGame.world.updateRemote(packet),
message => currentGame.reportLan(message)
)
lanPeer.start()
} else if (mode == "client") {
lanPeer = new HorsemanLanPeer(
false,
hostAddress,
lanPort,
() => currentGame.world.packetSnapshot(),
packet => currentGame.world.updateRemote(packet),
message => currentGame.reportLan(message)
)
lanPeer.start()
}
currentGame.requestFocusInWindow()
}
private def bindKey(stroke: String, actionName: String)(body: => Unit): Unit = {
val input = frame.getRootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
val actions = frame.getRootPane.getActionMap
input.put(KeyStroke.getKeyStroke(stroke), actionName)
actions.put(actionName, new AbstractAction {
override def actionPerformed(event: ActionEvent): Unit = body
})
}
private def installKeyBindings(game: HorsemanGameScreen): Unit = {
val world = game.world
bindKey("pressed W", "moveUp") { world.setDirection("up", true) }
bindKey("released W", "stopUp") { world.setDirection("up", false) }
bindKey("pressed S", "moveDown") { world.setDirection("down", true) }
bindKey("released S", "stopDown") { world.setDirection("down", false) }
bindKey("pressed A", "moveLeft") { world.setDirection("left", true) }
bindKey("released A", "stopLeft") { world.setDirection("left", false) }
bindKey("pressed D", "moveRight") { world.setDirection("right", true) }
bindKey("released D", "stopRight") { world.setDirection("right", false) }
bindKey("pressed UP", "moveUpArrow") { world.setDirection("up", true) }
bindKey("released UP", "stopUpArrow") { world.setDirection("up", false) }
bindKey("pressed DOWN", "moveDownArrow") { world.setDirection("down", true) }
bindKey("released DOWN", "stopDownArrow") { world.setDirection("down", false) }
bindKey("pressed LEFT", "moveLeftArrow") { world.setDirection("left", true) }
bindKey("released LEFT", "stopLeftArrow") { world.setDirection("left", false) }
bindKey("pressed RIGHT", "moveRightArrow") { world.setDirection("right", true) }
bindKey("released RIGHT", "stopRightArrow") { world.setDirection("right", false) }
bindKey("pressed SPACE", "dash") { world.dash() }
bindKey("pressed F", "magicNet") { world.useMagicNet() }
bindKey("pressed R", "releaseHorseman") { world.releaseHorseman() }
bindKey("pressed B", "returnBase") { world.returnToBase() }
bindKey("pressed P", "pause") { world.togglePause() }
bindKey("pressed ESCAPE", "backToLogin") { returnToLogin() }
}
private def returnToLogin(): Unit = {
if (lanPeer != null) {
lanPeer.stop()
lanPeer = null
}
if (currentGame != null) {
currentGame.stop()
currentGame = null
}
showLogin()
}
private def cleanup(): Unit = {
if (lanPeer != null) lanPeer.stop()
if (currentGame != null) currentGame.stop()
}
}
HeadlessHorsemanChaseEvent9.main(Array[String]())