Code Sketch
Bixuu steal and egg onlt one player
Category: Programming
import java.awt.{BasicStroke, BorderLayout, CardLayout, Color, Dimension, Font, GradientPaint, Graphics, Graphics2D, GridLayout, Polygon, RenderingHints}
import java.awt.event.{ActionEvent, ActionListener, KeyEvent, WindowAdapter, WindowEvent}
import java.io.{File, FileInputStream, FileOutputStream}
import java.util.Properties
import javax.swing.{AbstractAction, BorderFactory, JButton, JComponent, JDialog, JFrame, JLabel, JOptionPane, JPanel, JScrollPane, JPasswordField, JTextField, KeyStroke, SwingConstants, SwingUtilities, Timer, WindowConstants}
import javax.swing.border.EmptyBorder
import scala.collection.mutable.ArrayBuffer
import scala.util.Random
object SpringSeasonCatchTheEgg {
private val TITLE = "SPRING SEASON - CATCH THE EGG 2.5D"
private val ADMIN_PASSWORD = "BIXUU@SSCTE"
private val MAX_SPEED = 1000000
private val rng = new Random()
private case class Biome(
name: String,
gate: Int,
sky: Color,
ground: Color,
accent: Color,
guardian: String,
guardianColor: Color
)
private val biomes = Vector(
Biome("HEN FOREST", 0, new Color(139,210,245), new Color(81,157,78), new Color(250,220,100), "CHICKEN", new Color(238,190,65)),
Biome("TIGER JUNGLE", 20, new Color(115,190,145), new Color(39,112,58), new Color(244,155,55), "TIGER", new Color(231,135,40)),
Biome("LAKE", 40, new Color(117,207,235), new Color(45,137,165), new Color(220,250,255), "SWAN", Color.WHITE),
Biome("DESERT", 60, new Color(249,211,139), new Color(190,145,78), new Color(255,232,150), "SCORPION", new Color(166,105,48)),
Biome("ARCTIC", 80, new Color(211,238,250), new Color(134,184,210), Color.WHITE, "POLAR FOX", new Color(225,230,236)),
Biome("WATER", 100, new Color(78,163,215), new Color(26,96,154), new Color(155,226,255), "SHARK", new Color(103,132,154)),
Biome("DRAGON PEAK", 100, new Color(151,120,173), new Color(97,67,96), new Color(255,126,91), "DRAGON", new Color(211,81,72)),
Biome("CAVE", 120, new Color(100,108,119), new Color(52,58,69), new Color(185,195,208), "BAT", new Color(65,67,88)),
Biome("VOLCANO", 140, new Color(110,63,54), new Color(77,35,30), new Color(255,111,47), "LAVA LIZARD", new Color(207,91,49)),
Biome("ABYSS OCEAN", 160, new Color(34,65,113), new Color(10,27,69), new Color(85,190,230), "OCTOPUS", new Color(158,94,186)),
Biome("PREHISTORIC", 180, new Color(173,187,119), new Color(92,111,53), new Color(206,171,105), "DINO", new Color(89,139,75)),
Biome("HEAVEN", 200, new Color(218,235,255), new Color(165,197,229), Color.WHITE, "PEGASUS", new Color(246,246,255)),
Biome("COSMIC", 220, new Color(68,57,128), new Color(29,23,72), new Color(184,135,255), "SPACE CAT", new Color(164,134,242)),
Biome("TOY BOX", 240, new Color(255,193,211), new Color(126,188,232), new Color(255,237,115), "TOY ROBOT", new Color(223,91,108)),
Biome("DINO VALLEY", 260, new Color(158,183,107), new Color(81,120,55), new Color(219,185,119), "RAPTOR", new Color(97,144,69)),
Biome("SKY CASTLE", 280, new Color(146,202,242), new Color(106,153,207), Color.WHITE, "SKY BIRD", new Color(244,244,250)),
Biome("DISCO", 300, new Color(103,71,156), new Color(45,35,94), new Color(255,105,214), "DISCO FOX", new Color(217,100,200)),
Biome("CANDY KINGDOM", 320, new Color(255,190,216), new Color(233,141,181), new Color(255,245,160), "CANDY BEAR", new Color(220,128,158)),
Biome("CRYSTAL CAVES", 340, new Color(122,113,194), new Color(67,63,132), new Color(176,239,255), "CRYSTAL GOLEM", new Color(116,203,219)),
Biome("SKELETON MARSH", 360, new Color(114,134,100), new Color(50,72,54), new Color(207,218,167), "MARSH TURTLE", new Color(106,135,92)),
Biome("RAINBOW REALM", 400, new Color(147,196,245), new Color(97,160,132), Color.WHITE, "RAINBOW LION", new Color(226,151,75)),
Biome("APPLE ORCHARD", 420, new Color(164,218,247), new Color(88,151,77), new Color(220,48,55), "APPLE BEAR", new Color(153,91,61)),
Biome("STEEL FACTORY", 500, new Color(112,132,149), new Color(58,70,83), new Color(255,184,84), "STEEL BOT", new Color(118,140,154))
)
private val saveDir = new File(System.getProperty("user.home"), ".spring-season-catch-egg")
private val props = new Properties()
private var playerId = ""
private var playerName = "Player"
private var coins = 0L
private var speed = 0
private var eggsDelivered = 0L
private var pets = 0
private var selectedBiome = 0
private var eventActive = true
private var frame: JFrame = _
private var cards: CardLayout = _
private var root: JPanel = _
private var gamePanel: GamePanel = _
private var statsLabel: JLabel = _
private var statsTimer: Timer = _
def main(args: Array[String]): Unit = {
if (java.awt.GraphicsEnvironment.isHeadless) {
System.err.println("A desktop display is required. Run this in Kojo on your computer.")
return
}
SwingUtilities.invokeLater(new Runnable {
override def run(): Unit = {
try {
createWindow()
} catch {
case ex: Throwable =>
ex.printStackTrace()
JOptionPane.showMessageDialog(null, "Startup error: " + ex.toString, TITLE, JOptionPane.ERROR_MESSAGE)
}
}
})
}
private def profileFile: File = {
val safeId = playerId.trim.toLowerCase(java.util.Locale.ROOT)
new File(saveDir, safeId + ".properties")
}
// Every new Player ID starts with zero progress. Returning IDs load only their own file.
private def loadProfile(id: String, enteredName: String): Unit = {
playerId = id.trim
playerName = enteredName.trim.take(20)
coins = 0L
speed = 0
eggsDelivered = 0L
pets = 0
selectedBiome = 0
eventActive = true
props.clear()
val file = profileFile
if (file.isFile) {
var in: FileInputStream = null
try {
in = new FileInputStream(file)
props.load(in)
playerName = props.getProperty("playerName", playerName)
coins = readLong("coins", 0L)
speed = math.max(0, math.min(MAX_SPEED, readInt("speed", 0)))
eggsDelivered = readLong("eggsDelivered", 0L)
pets = math.max(0, readInt("pets", 0))
selectedBiome = math.max(0, math.min(biomes.length - 1, readInt("selectedBiome", 0)))
} catch {
case ex: Exception =>
System.err.println("Profile load warning: " + ex.toString)
coins = 0L
speed = 0
eggsDelivered = 0L
pets = 0
selectedBiome = 0
} finally {
if (in != null) in.close()
}
} else {
// Brand-new account: all values remain zero.
saveGame()
}
}
private def readLong(key: String, defaultValue: Long): Long = {
try props.getProperty(key, defaultValue.toString).toLong
catch { case _: Exception => defaultValue }
}
private def readInt(key: String, defaultValue: Int): Int = {
try props.getProperty(key, defaultValue.toString).toInt
catch { case _: Exception => defaultValue }
}
private def saveGame(): Unit = {
if (playerId.trim.isEmpty) return
var out: FileOutputStream = null
try {
if (!saveDir.exists()) saveDir.mkdirs()
props.setProperty("playerId", playerId)
props.setProperty("playerName", playerName)
props.setProperty("coins", coins.toString)
props.setProperty("speed", speed.toString)
props.setProperty("eggsDelivered", eggsDelivered.toString)
props.setProperty("pets", pets.toString)
props.setProperty("selectedBiome", selectedBiome.toString)
out = new FileOutputStream(profileFile)
props.store(out, TITLE)
} catch {
case ex: Exception => System.err.println("Save warning: " + ex.toString)
} finally {
if (out != null) out.close()
}
}
private def createWindow(): Unit = {
frame = new JFrame(TITLE)
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE)
frame.setSize(1180, 780)
frame.setMinimumSize(new Dimension(900, 620))
frame.setLocationRelativeTo(null)
cards = new CardLayout()
root = new JPanel(cards)
root.add(createLoginScreen(), "login")
root.add(new JPanel(), "game")
frame.setContentPane(root)
frame.addWindowListener(new WindowAdapter {
override def windowClosing(e: WindowEvent): Unit = {
saveGame()
if (statsTimer != null) statsTimer.stop()
if (gamePanel != null) gamePanel.stopGame()
frame.dispose()
System.exit(0)
}
})
cards.show(root, "login")
frame.setVisible(true)
frame.toFront()
}
private def createLoginScreen(): JPanel = {
val background = new JPanel(new BorderLayout())
background.setBackground(new Color(30, 105, 73))
background.setBorder(new EmptyBorder(35, 45, 35, 45))
val box = new JPanel()
box.setLayout(new javax.swing.BoxLayout(box, javax.swing.BoxLayout.Y_AXIS))
box.setBackground(new Color(255, 250, 226))
box.setBorder(new EmptyBorder(35, 50, 35, 50))
val title = new JLabel("SPRING SEASON")
title.setForeground(new Color(35, 130, 78))
title.setFont(new Font("Arial", Font.BOLD, 40))
title.setAlignmentX(java.awt.Component.CENTER_ALIGNMENT)
val subtitle = new JLabel("CATCH THE EGG - 2.5D ADVENTURE")
subtitle.setFont(new Font("Arial", Font.BOLD, 22))
subtitle.setAlignmentX(java.awt.Component.CENTER_ALIGNMENT)
val idPrompt = new JLabel("Create or enter your Player ID (3-24 letters/numbers)")
idPrompt.setFont(new Font("Arial", Font.BOLD, 15))
idPrompt.setAlignmentX(java.awt.Component.CENTER_ALIGNMENT)
val idField = new JTextField("", 20)
idField.setMaximumSize(new Dimension(310, 40))
idField.setFont(new Font("Arial", Font.PLAIN, 17))
val prompt = new JLabel("Enter your player name")
prompt.setFont(new Font("Arial", Font.BOLD, 17))
prompt.setAlignmentX(java.awt.Component.CENTER_ALIGNMENT)
val nameField = new JTextField(playerName, 20)
nameField.setMaximumSize(new Dimension(310, 42))
nameField.setFont(new Font("Arial", Font.PLAIN, 18))
val play = new JButton("SIGN IN AND PLAY")
play.setFont(new Font("Arial", Font.BOLD, 19))
play.setBackground(new Color(54, 164, 94))
play.setForeground(Color.WHITE)
play.setAlignmentX(java.awt.Component.CENTER_ALIGNMENT)
val hint = new JLabel("Each new Player ID starts at ZERO ? Steal eggs ? Escape to base ? Hatch pets")
hint.setAlignmentX(java.awt.Component.CENTER_ALIGNMENT)
val action = new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
val id = idField.getText.trim
val text = nameField.getText.trim
if (!id.matches("[A-Za-z0-9_-]{3,24}")) {
JOptionPane.showMessageDialog(frame, "Player ID must be 3-24 characters: letters, numbers, _ or -.")
} else if (text.isEmpty) {
JOptionPane.showMessageDialog(frame, "Please enter your player name.")
} else {
loadProfile(id, text)
JOptionPane.showMessageDialog(
frame,
"Welcome, " + playerName + "!\nPlayer ID: " + playerId +
"\nBixuBucks: " + coins + "\nSpeed: " + speed + "\nPets: " + pets,
"PLAYER PROFILE",
JOptionPane.INFORMATION_MESSAGE
)
openGame()
}
}
}
play.addActionListener(action)
idField.addActionListener(action)
nameField.addActionListener(action)
box.add(title)
box.add(javax.swing.Box.createVerticalStrut(10))
box.add(subtitle)
box.add(javax.swing.Box.createVerticalStrut(32))
box.add(idPrompt)
box.add(javax.swing.Box.createVerticalStrut(8))
box.add(idField)
box.add(javax.swing.Box.createVerticalStrut(15))
box.add(prompt)
box.add(javax.swing.Box.createVerticalStrut(10))
box.add(nameField)
box.add(javax.swing.Box.createVerticalStrut(22))
box.add(play)
box.add(javax.swing.Box.createVerticalStrut(25))
box.add(hint)
background.add(box, BorderLayout.CENTER)
background
}
private def openGame(): Unit = {
if (gamePanel != null) gamePanel.stopGame()
if (statsTimer != null) statsTimer.stop()
val screen = new JPanel(new BorderLayout())
screen.add(createToolbar(), BorderLayout.NORTH)
gamePanel = new GamePanel()
screen.add(gamePanel, BorderLayout.CENTER)
root.add(screen, "game")
root.revalidate()
root.repaint()
cards.show(root, "game")
gamePanel.startGame()
}
private def statsText(): String = {
"BixuBucks: " + coins + " Speed: " + speed + " Eggs: " + eggsDelivered + " Pets: " + pets + " Biome: " + biomes(selectedBiome).name
}
private def createToolbar(): JPanel = {
val bar = new JPanel(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 8, 8))
bar.setBackground(new Color(25, 43, 66))
val name = new JLabel("PLAYER: " + playerName + " [ID: " + playerId + "]")
name.setForeground(Color.WHITE)
name.setFont(new Font("Arial", Font.BOLD, 13))
statsLabel = new JLabel(statsText())
statsLabel.setForeground(new Color(255, 225, 120))
statsLabel.setFont(new Font("Arial", Font.BOLD, 13))
val biomesButton = new JButton("BIOMES [M]")
biomesButton.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = showBiomes(false)
})
val saveButton = new JButton("SAVE")
saveButton.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
saveGame()
JOptionPane.showMessageDialog(frame, "Game saved.")
}
})
val adminButton = new JButton("ADMIN PANEL")
adminButton.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = openAdminPanel()
})
val helpButton = new JButton("HOW TO PLAY")
val switchButton = new JButton("SWITCH ID")
switchButton.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
saveGame()
if (gamePanel != null) gamePanel.stopGame()
if (statsTimer != null) statsTimer.stop()
cardLayoutSafeShowLogin()
}
})
helpButton.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = showHelp()
})
statsTimer = new Timer(300, new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
if (statsLabel != null) statsLabel.setText(statsText())
}
})
statsTimer.start()
bar.add(name)
bar.add(statsLabel)
bar.add(biomesButton)
bar.add(saveButton)
bar.add(adminButton)
bar.add(helpButton)
bar.add(switchButton)
bar
}
private def cardLayoutSafeShowLogin(): Unit = {
if (cards != null && root != null) {
cards.show(root, "login")
frame.toFront()
}
}
private def showHelp(): Unit = {
JOptionPane.showMessageDialog(
frame,
"W / A / S / D = Move\nE = Pick up egg or deliver it at your base\nT = Train on the treadmill (stand on it and hold T)\nM = Open biome list\n\nEvery new Player ID starts with 0 BixuBucks, 0 Speed, 0 Pets and 0 Eggs. Steal the egg from the guardian nest. The guardian chases you. Run back to your base and press E near the delivery pen. A delivered egg hatches into a pet and your pets earn BixuBucks over time.",
"HOW TO PLAY",
JOptionPane.INFORMATION_MESSAGE
)
}
private def showBiomes(adminUnlock: Boolean): Unit = {
val dialog = new JDialog(frame, "BIOME DISCOVERY - 23 AREAS", true)
val outer = new JPanel(new BorderLayout(8, 8))
outer.setBackground(new Color(12, 21, 40))
outer.setBorder(new EmptyBorder(14, 14, 14, 14))
val heading = new JLabel("BIOME DISCOVERY | YOUR SPEED: " + speed)
heading.setForeground(new Color(255, 215, 112))
heading.setFont(new Font("Arial", Font.BOLD, 21))
outer.add(heading, BorderLayout.NORTH)
val list = new JPanel(new GridLayout(0, 2, 9, 9))
list.setBackground(new Color(12, 21, 40))
var i = 0
while (i < biomes.length) {
val index = i
val b = biomes(index)
val unlocked = adminUnlock || index == 0 || speed >= b.gate
val card = new JPanel(new BorderLayout(5, 5))
card.setBackground(new Color(28, 42, 65))
card.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(if (selectedBiome == index) b.accent else new Color(78, 102, 132), 2),
new EmptyBorder(9, 10, 9, 10)
))
val label = new JLabel(f"${index + 1}%02d ${b.name}")
label.setForeground(Color.WHITE)
label.setFont(new Font("Arial", Font.BOLD, 14))
val gate = new JLabel("Required Speed: " + b.gate + " Guardian: " + b.guardian)
gate.setForeground(new Color(193, 210, 231))
gate.setFont(new Font("Arial", Font.PLAIN, 11))
val button = new JButton(if (unlocked) "ENTER BIOME" else "LOCKED - NEED " + (b.gate - speed) + " MORE SPEED")
button.setEnabled(unlocked)
button.setBackground(if (unlocked) new Color(54, 145, 102) else new Color(75, 79, 91))
button.setForeground(Color.WHITE)
button.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
selectedBiome = index
saveGame()
if (gamePanel != null) gamePanel.changeBiome()
dialog.dispose()
}
})
val labels = new JPanel(new GridLayout(2, 1, 2, 2))
labels.setOpaque(false)
labels.add(label)
labels.add(gate)
card.add(labels, BorderLayout.CENTER)
card.add(button, BorderLayout.SOUTH)
list.add(card)
i += 1
}
val scroll = new JScrollPane(list)
scroll.getVerticalScrollBar.setUnitIncrement(18)
outer.add(scroll, BorderLayout.CENTER)
val close = new JButton("CLOSE")
close.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = dialog.dispose()
})
val footer = new JPanel(new java.awt.FlowLayout(java.awt.FlowLayout.RIGHT))
footer.setOpaque(false)
footer.add(close)
outer.add(footer, BorderLayout.SOUTH)
dialog.setContentPane(outer)
dialog.setSize(820, 640)
dialog.setLocationRelativeTo(frame)
dialog.setVisible(true)
}
private def openAdminPanel(): Unit = {
val field = new JPasswordField(18)
val result = JOptionPane.showConfirmDialog(frame, field, "GAME CREATOR ADMIN LOGIN", JOptionPane.OK_CANCEL_OPTION)
if (result != JOptionPane.OK_OPTION) return
if (new String(field.getPassword) != ADMIN_PASSWORD) {
JOptionPane.showMessageDialog(frame, "Wrong admin password.", "Access denied", JOptionPane.ERROR_MESSAGE)
return
}
val options = Array[AnyRef]("Give BixuBucks / Coins", "Set speed", "Give pets", "Unlock/select any biome", "Start spring event", "Stop spring event", "View stats", "Close")
var open = true
while (open) {
val choice = JOptionPane.showOptionDialog(frame,
"GAME CREATOR PANEL\nPlayer: " + playerName + "\nPlayer ID: " + playerId + "\nBixuBucks: " + coins + "\nSpeed: " + speed + "\nPets: " + pets,
"ADMIN PANEL", JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options(0))
choice match {
case 0 =>
val text = JOptionPane.showInputDialog(frame, "BixuBucks to add to Player ID " + playerId + ":")
if (text != null) {
try {
val amount = text.trim.toLong
if (amount > 0L) coins = if (Long.MaxValue - coins < amount) Long.MaxValue else coins + amount
saveGame()
} catch { case _: NumberFormatException => JOptionPane.showMessageDialog(frame, "Enter a valid whole number.") }
}
case 1 =>
val text = JOptionPane.showInputDialog(frame, "Set speed from 0 to " + MAX_SPEED + ":")
if (text != null) {
try {
val value = text.trim.toInt
if (value >= 0 && value <= MAX_SPEED) speed = value
else JOptionPane.showMessageDialog(frame, "Speed must be between 0 and " + MAX_SPEED)
saveGame()
} catch { case _: NumberFormatException => JOptionPane.showMessageDialog(frame, "Enter a valid whole number.") }
}
case 2 =>
val text = JOptionPane.showInputDialog(frame, "Number of pets to add:")
if (text != null) {
try {
val amount = text.trim.toInt
if (amount >= 0 && amount <= 1000000) pets = math.min(1000000, pets + amount)
else JOptionPane.showMessageDialog(frame, "Enter a value from 0 to 1000000.")
saveGame()
} catch { case _: NumberFormatException => JOptionPane.showMessageDialog(frame, "Enter a valid whole number.") }
}
case 3 => showBiomes(true)
case 4 => eventActive = true
case 5 => eventActive = false
case 6 => JOptionPane.showMessageDialog(frame, "Player: " + playerName + "\nPlayer ID: " + playerId + "\nBixuBucks: " + coins + "\nSpeed: " + speed + "\nEggs delivered: " + eggsDelivered + "\nPets: " + pets + "\nBiome: " + biomes(selectedBiome).name)
case _ => open = false
}
if (gamePanel != null) gamePanel.repaint()
}
}
private class GamePanel extends JPanel {
private var px = 165.0
private var py = 492.0
private var guardianX = 865.0
private var guardianY = 290.0
private var carryingEgg = false
private var nestHasEgg = true
private var guardianChasing = false
private var respawnTicks = 0
private var tick = 0
private var left = false
private var right = false
private var up = false
private var down = false
private var trainHeld = false
private var trainCounter = 0
private var incomeCounter = 0
private var running = false
private var statusMessage = "Go to the guardian nest and press E to take the egg."
private val baseX = 170.0
private val baseY = 470.0
private val treadmillX = 235.0
private val treadmillY = 405.0
private val nestX = 905.0
private val nestY = 275.0
private val colors = Array(new Color(255, 244, 190), new Color(190, 231, 255), new Color(255, 204, 222), new Color(235, 235, 245))
setPreferredSize(new Dimension(1120, 650))
setBackground(new Color(140, 205, 235))
setFocusable(true)
setupKeys()
private val timer = new Timer(16, new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = updateGame()
})
def startGame(): Unit = {
running = true
timer.start()
requestFocusInWindow()
}
def stopGame(): Unit = {
running = false
timer.stop()
}
def changeBiome(): Unit = {
guardianX = nestX - 55.0
guardianY = nestY + 28.0
nestHasEgg = true
carryingEgg = false
guardianChasing = false
statusMessage = "New biome: " + biomes(selectedBiome).name + ". Find the guardian nest!"
repaint()
}
private def setupKeys(): Unit = {
val input = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
val actions = getActionMap
input.put(KeyStroke.getKeyStroke("pressed W"), "upOn")
input.put(KeyStroke.getKeyStroke("released W"), "upOff")
input.put(KeyStroke.getKeyStroke("pressed S"), "downOn")
input.put(KeyStroke.getKeyStroke("released S"), "downOff")
input.put(KeyStroke.getKeyStroke("pressed A"), "leftOn")
input.put(KeyStroke.getKeyStroke("released A"), "leftOff")
input.put(KeyStroke.getKeyStroke("pressed D"), "rightOn")
input.put(KeyStroke.getKeyStroke("released D"), "rightOff")
input.put(KeyStroke.getKeyStroke("pressed UP"), "upOn")
input.put(KeyStroke.getKeyStroke("released UP"), "upOff")
input.put(KeyStroke.getKeyStroke("pressed DOWN"), "downOn")
input.put(KeyStroke.getKeyStroke("released DOWN"), "downOff")
input.put(KeyStroke.getKeyStroke("pressed LEFT"), "leftOn")
input.put(KeyStroke.getKeyStroke("released LEFT"), "leftOff")
input.put(KeyStroke.getKeyStroke("pressed RIGHT"), "rightOn")
input.put(KeyStroke.getKeyStroke("released RIGHT"), "rightOff")
input.put(KeyStroke.getKeyStroke("pressed T"), "trainOn")
input.put(KeyStroke.getKeyStroke("released T"), "trainOff")
input.put(KeyStroke.getKeyStroke("pressed E"), "interact")
input.put(KeyStroke.getKeyStroke("pressed M"), "biomes")
actions.put("upOn", new AbstractAction { override def actionPerformed(e: ActionEvent): Unit = up = true })
actions.put("upOff", new AbstractAction { override def actionPerformed(e: ActionEvent): Unit = up = false })
actions.put("downOn", new AbstractAction { override def actionPerformed(e: ActionEvent): Unit = down = true })
actions.put("downOff", new AbstractAction { override def actionPerformed(e: ActionEvent): Unit = down = false })
actions.put("leftOn", new AbstractAction { override def actionPerformed(e: ActionEvent): Unit = left = true })
actions.put("leftOff", new AbstractAction { override def actionPerformed(e: ActionEvent): Unit = left = false })
actions.put("rightOn", new AbstractAction { override def actionPerformed(e: ActionEvent): Unit = right = true })
actions.put("rightOff", new AbstractAction { override def actionPerformed(e: ActionEvent): Unit = right = false })
actions.put("trainOn", new AbstractAction { override def actionPerformed(e: ActionEvent): Unit = trainHeld = true })
actions.put("trainOff", new AbstractAction { override def actionPerformed(e: ActionEvent): Unit = trainHeld = false })
actions.put("interact", new AbstractAction { override def actionPerformed(e: ActionEvent): Unit = interact() })
actions.put("biomes", new AbstractAction { override def actionPerformed(e: ActionEvent): Unit = showBiomes(false) })
}
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 interact(): Unit = {
if (carryingEgg) {
if (distance(px, py, baseX, baseY) < 105.0) deliverEgg()
else statusMessage = "Run back to your BASE and press E near the delivery pen!"
} else if (nestHasEgg && distance(px, py, nestX, nestY) < 92.0) {
carryingEgg = true
nestHasEgg = false
guardianChasing = true
statusMessage = "EGG STOLEN! The guardian is chasing you - RUN TO YOUR BASE!"
} else if (distance(px, py, baseX, baseY) < 105.0) {
statusMessage = "Your base is safe. Go to the guardian nest to steal an egg."
} else {
statusMessage = "Get close to the NEST or your BASE, then press E."
}
repaint()
}
private def deliverEgg(): Unit = {
carryingEgg = false
guardianChasing = false
guardianX = nestX - 55.0
guardianY = nestY + 28.0
pets += 1
eggsDelivered += 1L
val reward = 60L + selectedBiome.toLong * 35L
coins = if (Long.MaxValue - coins < reward) Long.MaxValue else coins + reward
statusMessage = "DELIVERED! Egg hatched into a pet. Your pets now earn coins over time."
respawnTicks = 150
saveGame()
}
private def updateGame(): Unit = {
if (!running) return
tick += 1
val carryingMultiplier = if (carryingEgg) 0.72 else 1.0
val movement = (3.0 + math.min(5.5, speed.toDouble / 50000.0)) * carryingMultiplier
if (left) px -= movement
if (right) px += movement
if (up) py -= movement
if (down) py += movement
px = math.max(28.0, math.min(getWidth.toDouble - 28.0, px))
py = math.max(125.0, math.min(getHeight.toDouble - 55.0, py))
if (trainHeld && distance(px, py, treadmillX, treadmillY) < 92.0) {
trainCounter += 1
if (trainCounter >= 18) {
trainCounter = 0
if (speed < MAX_SPEED) speed += 1
if (tick % 120 == 0) saveGame()
statusMessage = "TRAINING! Speed: " + speed
}
} else {
trainCounter = 0
}
if (guardianChasing && carryingEgg) {
val dx = px - guardianX
val dy = py - guardianY
val d = math.max(1.0, math.sqrt(dx * dx + dy * dy))
val guardStep = 1.75 + selectedBiome.toDouble * 0.035
guardianX += (dx / d) * guardStep
guardianY += (dy / d) * guardStep
if (d < 38.0) {
carryingEgg = false
guardianChasing = false
nestHasEgg = true
guardianX = nestX - 55.0
guardianY = nestY + 28.0
px = baseX
py = baseY
statusMessage = "The guardian tagged you! The egg went back to the nest. Try another run!"
}
} else if (!guardianChasing) {
val dx = (nestX - 55.0) - guardianX
val dy = (nestY + 28.0) - guardianY
guardianX += dx * 0.035
guardianY += dy * 0.035
}
if (respawnTicks > 0) {
respawnTicks -= 1
if (respawnTicks == 0) {
nestHasEgg = true
statusMessage = "A new egg is ready at the guardian nest."
}
}
incomeCounter += 1
if (incomeCounter >= 60) {
incomeCounter = 0
if (pets > 0) {
val income = pets.toLong * (2L + selectedBiome.toLong)
coins = if (Long.MaxValue - coins < income) Long.MaxValue else coins + income
}
}
repaint()
}
override protected def paintComponent(graphics: Graphics): Unit = {
super.paintComponent(graphics)
val g = graphics.create().asInstanceOf[Graphics2D]
try {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
val w = getWidth
val h = getHeight
val b = biomes(selectedBiome)
// Background and distant hills.
g.setPaint(new GradientPaint(0, 0, b.sky, 0, h, b.ground))
g.fillRect(0, 0, w, h)
g.setColor(new Color(255, 255, 255, 55))
g.fillOval(-80, 80, 330, 165)
g.fillOval(240, 42, 360, 180)
g.fillOval(700, 60, 360, 170)
// 2.5D road joining the home base to the guardian's nest.
g.setColor(new Color(91, 91, 78, 65))
g.setStroke(new BasicStroke(105.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND))
g.drawLine(280, 490, 815, 330)
g.setColor(new Color(205, 185, 135, 190))
g.setStroke(new BasicStroke(82.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND))
g.drawLine(280, 482, 815, 322)
g.setColor(new Color(255, 255, 255, 80))
g.setStroke(new BasicStroke(3.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND))
g.drawLine(300, 478, 800, 328)
drawBase(g)
drawNestArea(g, b)
drawBiomeDecor(g, w, h, selectedBiome, b.accent)
// HUD panel.
g.setColor(new Color(8, 20, 34, 205))
g.fillRoundRect(14, 12, 525, 112, 16, 16)
g.setColor(new Color(255, 218, 112))
g.setFont(new Font("Arial", Font.BOLD, 21))
g.drawString("SPRING SEASON - CATCH THE EGG", 28, 40)
g.setColor(Color.WHITE)
g.setFont(new Font("Arial", Font.BOLD, 14))
g.drawString("BIOME: " + b.name + " GUARDIAN: " + b.guardian, 28, 65)
g.drawString("BIXUBUCKS: " + coins + " SPEED: " + speed + " PETS: " + pets + " DELIVERED: " + eggsDelivered, 28, 89)
g.setColor(new Color(255, 235, 175))
g.setFont(new Font("Arial", Font.BOLD, 12))
g.drawString(if (carryingEgg) "CARRYING AN EGG - ESCAPE!" else if (nestHasEgg) "EGG READY IN THE NEST" else "NEST IS EMPTY - WAIT FOR A NEW EGG", 28, 110)
// Characters are drawn by their depth (y coordinate).
val playerDrawY = py
if (guardianY < playerDrawY) {
drawGuardian(g, guardianX, guardianY, b)
drawPlayer(g, px, py, carryingEgg)
} else {
drawPlayer(g, px, py, carryingEgg)
drawGuardian(g, guardianX, guardianY, b)
}
// Bottom instructions and status.
g.setColor(new Color(7, 18, 29, 195))
g.fillRoundRect(14, h - 70, w - 28, 56, 13, 13)
g.setColor(Color.WHITE)
g.setFont(new Font("Arial", Font.BOLD, 13))
g.drawString("WASD / ARROWS: MOVE E: STEAL / DELIVER HOLD T ON TREADMILL: TRAIN M: BIOMES", 28, h - 47)
g.setColor(new Color(255, 226, 132))
g.setFont(new Font("Arial", Font.PLAIN, 12))
g.drawString(statusMessage, 28, h - 26)
} finally {
g.dispose()
}
}
private def drawBase(g: Graphics2D): Unit = {
// Floating isometric base platform.
val top = new Polygon(Array(35, 315, 370, 95), Array(420, 365, 505, 570), 4)
val side = new Polygon(Array(95, 370, 370, 95), Array(570, 505, 535, 600), 4)
g.setColor(new Color(79, 94, 73))
g.fillPolygon(side)
g.setColor(new Color(107, 166, 93))
g.fillPolygon(top)
g.setColor(new Color(222, 205, 145))
g.setStroke(new BasicStroke(4.0f))
g.drawPolygon(top)
// Little base house.
g.setColor(new Color(238, 222, 177))
g.fillRoundRect(76, 385, 112, 85, 10, 10)
g.setColor(new Color(155, 83, 55))
val roof = new Polygon(Array(62, 132, 202), Array(390, 338, 390), 3)
g.fillPolygon(roof)
g.setColor(new Color(99, 61, 43))
g.fillRoundRect(116, 423, 31, 47, 6, 6)
g.setColor(new Color(120, 190, 218))
g.fillRect(86, 405, 22, 21)
g.fillRect(156, 405, 22, 21)
g.setColor(Color.WHITE)
g.setFont(new Font("Arial", Font.BOLD, 13))
g.drawString("YOUR BASE", 83, 487)
// Pet pen with small pets.
g.setColor(new Color(91, 59, 36))
g.setStroke(new BasicStroke(5.0f))
g.drawRect(215, 430, 105, 76)
g.setColor(new Color(160, 115, 70))
g.fillRect(216, 431, 103, 5)
g.fillRect(216, 468, 103, 5)
var p = 0
while (p < math.min(pets, 5)) {
g.setColor(new Color(248, 227, 174))
g.fillOval(226 + p * 17, 444 + (p % 2) * 8, 15, 15)
p += 1
}
g.setColor(Color.WHITE)
g.setFont(new Font("Arial", Font.BOLD, 11))
g.drawString("PET PEN", 238, 521)
// Treadmill - player's training zone.
g.setColor(new Color(45, 50, 65))
g.fillRoundRect(190, 365, 125, 47, 9, 9)
g.setColor(new Color(190, 205, 214))
g.fillRoundRect(199, 373, 106, 26, 5, 5)
g.setColor(new Color(65, 75, 88))
var stripe = 0
while (stripe < 7) {
g.drawLine(202 + stripe * 15, 375, 202 + stripe * 15, 397)
stripe += 1
}
g.setColor(new Color(255, 221, 95))
g.setFont(new Font("Arial", Font.BOLD, 12))
g.drawString("TREADMILL", 212, 355)
g.setColor(new Color(245, 245, 245))
g.setFont(new Font("Arial", Font.PLAIN, 10))
g.drawString("HOLD T", 230, 428)
}
private def drawNestArea(g: Graphics2D, b: Biome): Unit = {
// Guardian platform in 2.5D.
g.setColor(new Color(45, 55, 52, 75))
g.fillOval(695, 365, 365, 105)
g.setColor(new Color(125, 121, 93))
g.fillOval(700, 335, 350, 112)
g.setColor(new Color(177, 161, 111))
g.fillOval(716, 326, 320, 94)
g.setColor(new Color(231, 205, 145))
g.drawOval(716, 326, 320, 94)
// Nest bowl and sticks.
g.setColor(new Color(112, 72, 39))
g.fillOval(854, 275, 112, 72)
g.setColor(new Color(165, 112, 65))
g.setStroke(new BasicStroke(5.0f))
g.drawArc(850, 269, 118, 83, 5, 172)
g.drawArc(858, 277, 105, 67, 185, 160)
if (nestHasEgg) {
drawEggSprite(g, 902, 286, b.accent, 1.0)
} else {
g.setColor(new Color(45, 35, 25, 150))
g.setFont(new Font("Arial", Font.BOLD, 12))
g.drawString("EGG TAKEN", 875, 320)
}
g.setColor(Color.WHITE)
g.setFont(new Font("Arial", Font.BOLD, 13))
g.drawString("GUARDIAN NEST", 838, 395)
g.setColor(new Color(255, 255, 255, 210))
g.setFont(new Font("Arial", Font.PLAIN, 11))
g.drawString("Go close and press E", 850, 411)
}
private def drawBiomeDecor(g: Graphics2D, w: Int, h: Int, index: Int, accent: Color): Unit = {
var x = 20
while (x < w) {
if (index == 21) {
// Apple orchard trees.
g.setColor(new Color(105, 69, 39))
g.fillRoundRect(x + 10, 170, 14, 125, 5, 5)
g.setColor(new Color(47, 130, 62))
g.fillOval(x - 9, 127, 54, 70)
g.setColor(new Color(220, 43, 54))
g.fillOval(x + 1, 151, 11, 11)
g.fillOval(x + 24, 166, 10, 10)
g.fillOval(x + 10, 180, 10, 10)
} else if (index == 0 || index == 1 || index == 10 || index == 14 || index == 19) {
g.setColor(new Color(51, 95, 51, 190))
g.fillRect(x + 10, 165, 10, 115)
g.fillOval(x - 8, 125, 49, 57)
} else if (index == 4 || index == 5 || index == 9 || index == 18) {
g.setColor(new Color(240, 249, 255, 180))
g.fillOval(x + 5, 160, 8, 8)
g.fillOval(x + 25, 207, 6, 6)
} else {
g.setColor(new Color(accent.getRed, accent.getGreen, accent.getBlue, 160))
g.fillOval(x + 4, 180 + (x % 3) * 10, 13, 13)
g.fillOval(x + 23, 221, 8, 8)
}
x += 155
}
}
private def drawGuardian(g: Graphics2D, x: Double, y: Double, b: Biome): Unit = {
val ix = x.toInt
val iy = y.toInt
val scale = 0.82 + math.max(0.0, math.min(1.0, (y - 150.0) / 450.0)) * 0.32
val bodyW = (70 * scale).toInt
val bodyH = (48 * scale).toInt
g.setColor(new Color(30, 30, 30, 65))
g.fillOval(ix - bodyW / 2, iy + 20, bodyW, 17)
g.setColor(b.guardianColor)
g.fillOval(ix - bodyW / 2, iy - bodyH / 2, bodyW, bodyH)
g.setColor(b.guardianColor.darker())
g.fillOval(ix - (bodyW * 0.25).toInt, iy - (bodyH * 0.72).toInt, (bodyW * 0.5).toInt, (bodyH * 0.58).toInt)
// Ears / horns.
g.fillOval(ix - (bodyW * 0.37).toInt, iy - (bodyH * 0.72).toInt, (bodyW * 0.2).toInt, (bodyH * 0.35).toInt)
g.fillOval(ix + (bodyW * 0.17).toInt, iy - (bodyH * 0.72).toInt, (bodyW * 0.2).toInt, (bodyH * 0.35).toInt)
g.setColor(Color.WHITE)
g.fillOval(ix - 12, iy - 8, 9, 11)
g.fillOval(ix + 4, iy - 8, 9, 11)
g.setColor(Color.BLACK)
g.fillOval(ix - 9, iy - 5, 4, 5)
g.fillOval(ix + 7, iy - 5, 4, 5)
g.setColor(new Color(55, 35, 28))
g.drawArc(ix - 8, iy + 2, 16, 10, 190, 160)
g.setColor(Color.WHITE)
g.setFont(new Font("Arial", Font.BOLD, 11))
g.drawString(b.guardian, ix - b.guardian.length * 3, iy - (bodyH / 2) - 18)
if (guardianChasing) {
g.setColor(new Color(255, 80, 75))
g.setFont(new Font("Arial", Font.BOLD, 11))
g.drawString("CHASE!", ix - 20, iy - (bodyH / 2) - 31)
}
}
private def drawPlayer(g: Graphics2D, x: Double, y: Double, hasEgg: Boolean): Unit = {
val ix = x.toInt
val iy = y.toInt
val scale = 0.78 + math.max(0.0, math.min(1.0, (y - 130.0) / 470.0)) * 0.32
val w = (40 * scale).toInt
val h = (60 * scale).toInt
g.setColor(new Color(30, 30, 30, 65))
g.fillOval(ix - w / 2 - 5, iy + 3, w + 10, 15)
// Legs and shoes.
g.setColor(new Color(40, 49, 100))
g.fillRoundRect(ix - 14, iy - 18, 11, 25, 6, 6)
g.fillRoundRect(ix + 3, iy - 18, 11, 25, 6, 6)
g.setColor(new Color(52, 39, 30))
g.fillRoundRect(ix - 16, iy + 2, 15, 8, 5, 5)
g.fillRoundRect(ix + 2, iy + 2, 15, 8, 5, 5)
// Torso, arms and head.
g.setColor(new Color(51, 112, 231))
g.fillRoundRect(ix - w / 2, iy - h + 20, w, 34, 11, 11)
g.setColor(new Color(255, 202, 158))
g.fillRoundRect(ix - w / 2 - 7, iy - h + 25, 10, 29, 7, 7)
g.fillRoundRect(ix + w / 2 - 3, iy - h + 25, 10, 29, 7, 7)
g.fillOval(ix - 15, iy - h - 2, 30, 30)
g.setColor(new Color(70, 45, 27))
g.fillArc(ix - 16, iy - h - 5, 32, 21, 0, 180)
g.setColor(Color.BLACK)
g.fillOval(ix - 7, iy - h + 9, 3, 4)
g.fillOval(ix + 4, iy - h + 9, 3, 4)
g.setColor(Color.WHITE)
g.setFont(new Font("Arial", Font.BOLD, 11))
g.drawString(playerName, ix - playerName.length * 3, iy - h - 12)
if (hasEgg) drawEggSprite(g, ix, iy - h - 22, new Color(255, 245, 196), 0.82)
}
private def drawEggSprite(g: Graphics2D, x: Int, y: Int, color: Color, scale: Double): Unit = {
val w = (26 * scale).toInt
val h = (34 * scale).toInt
g.setColor(new Color(35, 35, 35, 45))
g.fillOval(x - w / 2 + 2, y - h / 2 + 3, w, h)
g.setColor(color)
g.fillOval(x - w / 2, y - h / 2, w, h)
g.setColor(new Color(234, 124, 148))
g.fillOval(x - 5, y - 4, 5, 5)
g.setColor(new Color(135, 194, 245))
g.fillOval(x + 3, y + 5, 5, 5)
}
}
}
// Kojo Code Exchange script entry point.
SpringSeasonCatchTheEgg.main(Array.empty[String])