Code Sketch
bixuu
Category: Programming
import java.awt.{BasicStroke, BorderLayout, CardLayout, Color, Component, Dimension, Font, GradientPaint, Graphics, Graphics2D, GridLayout, RenderingHints, Toolkit, Polygon, Frame, GraphicsEnvironment}
import java.awt.event.{ActionEvent, ActionListener, KeyEvent, KeyListener, MouseEvent, MouseListener, MouseAdapter, WindowAdapter, WindowEvent, FocusAdapter, FocusEvent}
import java.awt.geom.{Ellipse2D, RoundRectangle2D, Line2D, Rectangle2D}
import java.awt.image.BufferedImage
import java.awt.datatransfer.StringSelection
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import javax.swing._
import javax.swing.border._
import javax.swing.event.{ChangeEvent, ChangeListener}
import javax.swing.filechooser.FileNameExtensionFilter
import scala.util.{Random, Try}
/**
* BIXUU MEGA SUITE ? ROBOT + NAVRATRI + DIWALI + BIRD
* Single Scala source file. Four modules launch from one animated hub.
* Rendering is procedural; no external images, assets or libraries are required.
*/
object BixuuMegaSuite {
def main(args: Array[String]): Unit = {
SwingUtilities.invokeLater(new Runnable {
override def run(): Unit = createHub()
})
}
private def createHub(): Unit = {
val frame = new JFrame("BIXUU MEGA SUITE | FOUR WORLDS ? ONE LAUNCHER")
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
frame.setMinimumSize(new Dimension(900, 650))
frame.setSize(1120, 760)
frame.setLocationRelativeTo(null)
val root = new JPanel(new BorderLayout(0, 0))
root.setBackground(new Color(7, 11, 28))
val header = new HubHeader()
root.add(header, BorderLayout.NORTH)
val cards = new JPanel(new GridLayout(2, 2, 18, 18))
cards.setOpaque(false)
cards.setBorder(BorderFactory.createEmptyBorder(24, 28, 22, 28))
cards.add(new LaunchCard("BIXUU 02 ROBOT LAB", "Build ? Dance ? Wings ? Hologram", new Color(0, 210, 238), new Color(118, 73, 255), new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = BixuuRobotCodeCreator.launchFromHub()
}))
cards.add(new LaunchCard("NAVRATRI 3D ADVENTURE", "Garba ? Dandiya ? Festival missions", new Color(255, 126, 74), new Color(211, 61, 132), new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = Navratri3DAdventure.launch()
}))
cards.add(new LaunchCard("DIWALI FESTIVAL ADVENTURE", "Family journey ? Train ? Decorations", new Color(255, 190, 62), new Color(225, 93, 38), new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = BixuuDiwaliGame.launch()
}))
cards.add(new LaunchCard("BIXUU 02 BIRD ? SKY QUEST", "Flap ? Dodge ? Score ? Beat your best", new Color(70, 219, 185), new Color(42, 120, 222), new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = Bixuu02Bird.launch()
}))
root.add(cards, BorderLayout.CENTER)
val footer = new JLabel(" SELECT A WORLD TO OPEN IT ? CLOSE A GAME WINDOW TO RETURN HERE ? BIXUU 2026 ", SwingConstants.CENTER)
footer.setOpaque(true)
footer.setBackground(new Color(10, 17, 39))
footer.setForeground(new Color(151, 190, 224))
footer.setFont(new Font("SansSerif", Font.BOLD, 12))
footer.setBorder(BorderFactory.createEmptyBorder(14, 8, 14, 8))
root.add(footer, BorderLayout.SOUTH)
frame.setContentPane(root)
frame.setVisible(true)
header.startAnimation()
frame.addWindowListener(new WindowAdapter {
override def windowClosed(e: WindowEvent): Unit = header.stopAnimation()
})
}
private class HubHeader extends JPanel {
private var tick = 0.0
private val timer = new Timer(35, new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = { tick += 0.035; repaint() }
})
setPreferredSize(new Dimension(900, 190))
def startAnimation(): Unit = timer.start()
def stopAnimation(): Unit = timer.stop()
override protected def paintComponent(graphics: Graphics): Unit = {
super.paintComponent(graphics)
val g = graphics.create().asInstanceOf[Graphics2D]
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
val w = getWidth
val h = getHeight
g.setPaint(new GradientPaint(0, 0, new Color(9, 13, 43), w, h, new Color(42, 18, 75)))
g.fillRect(0, 0, w, h)
var i = 0
while (i < 55) {
val x = ((i * 97 + tick * (12 + (i % 4) * 5)) % math.max(1, w)).toInt
val y = 12 + ((i * 43) % math.max(1, h - 24))
val a = (90 + math.abs(math.sin(tick + i * 0.7)) * 150).toInt
g.setColor(new Color(130, 205, 255, a))
g.fillOval(x, y, if (i % 5 == 0) 4 else 2, if (i % 5 == 0) 4 else 2)
i += 1
}
val pulse = (math.sin(tick * 1.7) * 8).toInt
g.setColor(new Color(43, 220, 245, 35))
g.fillOval(w - 210 - pulse, -85 - pulse, 300 + pulse * 2, 300 + pulse * 2)
g.setFont(new Font("SansSerif", Font.BOLD, 34))
g.setColor(new Color(234, 247, 255))
g.drawString("BIXUU MEGA SUITE", 34, 67)
g.setFont(new Font("SansSerif", Font.BOLD, 15))
g.setColor(new Color(96, 232, 241))
g.drawString("ROBOT + NAVRATRI + DIWALI + BIRD", 38, 99)
g.setFont(new Font("SansSerif", Font.PLAIN, 13))
g.setColor(new Color(202, 205, 232))
g.drawString("Four Bixuu projects connected through one clean game launcher", 38, 128)
g.setFont(new Font("Monospaced", Font.BOLD, 10))
g.setColor(new Color(131, 248, 195))
g.drawString("SYSTEM READY ? 4 WORLDS AVAILABLE", 39, 158)
g.dispose()
}
}
private class LaunchCard(title: String, subtitle: String, c1: Color, c2: Color, listener: ActionListener) extends JButton(title) {
setOpaque(false)
setContentAreaFilled(false)
setFocusPainted(false)
setBorder(BorderFactory.createEmptyBorder(22, 24, 22, 24))
setHorizontalAlignment(SwingConstants.LEFT)
setVerticalAlignment(SwingConstants.CENTER)
setForeground(Color.WHITE)
setFont(new Font("SansSerif", Font.BOLD, 19))
setText("<html><div style='font-family:Sans-Serif;font-size:18px;font-weight:bold;'>" + title +
"</div><div style='font-family:Sans-Serif;font-size:12px;font-weight:normal;color:#e1e8ff;margin-top:10px;'>" + subtitle +
"</div><div style='font-family:Sans-Serif;font-size:12px;font-weight:bold;color:#ffffff;margin-top:18px;'>OPEN WORLD ➜</div></html>")
addActionListener(listener)
override protected def paintComponent(graphics: Graphics): Unit = {
val g = graphics.create().asInstanceOf[Graphics2D]
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
val over = getModel.isRollover || getModel.isArmed
val inset = if (over) 1 else 3
g.setPaint(new GradientPaint(0, 0, c1, getWidth, getHeight, c2))
g.fillRoundRect(inset, inset, getWidth - inset * 2, getHeight - inset * 2, 26, 26)
val outlineAlpha = if (over) 125 else 65
g.setColor(new Color(255, 255, 255, outlineAlpha))
g.setStroke(new BasicStroke(if (over) 2.2f else 1.2f))
g.drawRoundRect(inset, inset, getWidth - inset * 2, getHeight - inset * 2, 26, 26)
g.setColor(new Color(8, 10, 28, 45))
g.fillOval(getWidth - 92, getHeight - 90, 145, 145)
g.dispose()
super.paintComponent(graphics)
}
}
}
object Bixuu02Bird {
private final class Pipe(var x: Double, var gapY: Double, var passed: Boolean)
def launch(): Unit = {
if (GraphicsEnvironment.isHeadless) {
System.err.println("BIXUU 02 BIRD needs a desktop window.")
} else {
SwingUtilities.invokeLater(new Runnable {
override def run(): Unit = {
val frame = new JFrame("BIXUU 02 BIRD | SKY QUEST")
val panel = new BirdPanel()
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE)
frame.setContentPane(panel)
frame.setSize(1080, 720)
frame.setMinimumSize(new Dimension(850, 560))
frame.setLocationRelativeTo(null)
frame.addWindowListener(new WindowAdapter {
override def windowClosed(e: WindowEvent): Unit = panel.stopGame()
})
frame.setVisible(true)
panel.startGameLoop()
panel.requestFocusInWindow()
}
})
}
}
private class BirdPanel extends JPanel with KeyListener with MouseListener {
private val rng = new Random(2026L)
private val birdX = 245.0
private val birdRadius = 17.0
private val pipeWidth = 82.0
private val pipeGap = 190.0
private val pipeSpacing = 355.0
private var birdY = 315.0
private var birdVelocity = 0.0
private var score = 0
private var bestScore = 0
private var tick = 0.0
private var groundShift = 0.0
private var state = "READY"
private var pausedFrom = "PLAYING"
private val pipes = scala.collection.mutable.ArrayBuffer.empty[Pipe]
private var loopTimer: Timer = null
setFocusable(true)
setDoubleBuffered(true)
setBackground(new Color(70, 170, 225))
addKeyListener(this)
addMouseListener(this)
def startGameLoop(): Unit = {
if (loopTimer == null || !loopTimer.isRunning) {
loopTimer = new Timer(16, new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
updateWorld()
repaint()
}
})
loopTimer.setCoalesce(true)
loopTimer.start()
}
}
def stopGame(): Unit = if (loopTimer != null) loopTimer.stop()
private def startNewGame(): Unit = {
score = 0
birdY = getHeight.toDouble * 0.47
if (birdY < 180) birdY = 300.0
birdVelocity = 0.0
pipes.clear()
val base = math.max(900.0, getWidth.toDouble + 120.0)
pipes += new Pipe(base, randomGap(), false)
pipes += new Pipe(base + pipeSpacing, randomGap(), false)
pipes += new Pipe(base + pipeSpacing * 2.0, randomGap(), false)
state = "PLAYING"
flap()
}
private def randomGap(): Double = {
val minY = 180.0
val maxY = math.max(minY + 100.0, getHeight.toDouble - 205.0)
minY + rng.nextDouble() * (maxY - minY)
}
private def flap(): Unit = {
birdVelocity = -7.8
if (state == "READY") state = "PLAYING"
}
private def endGame(): Unit = {
state = "OVER"
if (score > bestScore) bestScore = score
}
private def updateWorld(): Unit = {
tick += 0.04
groundShift = (groundShift + 3.2) % 70.0
if (state == "PLAYING") {
birdVelocity = math.min(10.5, birdVelocity + 0.42)
birdY += birdVelocity
val speed = 4.6 + math.min(2.4, score * 0.08)
var i = 0
while (i < pipes.length) {
val p = pipes(i)
p.x -= speed
if (!p.passed && p.x + pipeWidth < birdX) {
p.passed = true
score += 1
}
i += 1
}
if (pipes.nonEmpty && pipes.head.x + pipeWidth < -10) {
val lastX = pipes.last.x
pipes.remove(0)
pipes += new Pipe(lastX + pipeSpacing, randomGap(), false)
}
val groundY = getHeight - 82.0
if (birdY - birdRadius < 0.0 || birdY + birdRadius > groundY) endGame()
else {
val birdRect = new Rectangle2D.Double(birdX - 16, birdY - 13, 34, 27)
var p = 0
while (p < pipes.length && state == "PLAYING") {
val obstacle = pipes(p)
val gapTop = obstacle.gapY - pipeGap / 2.0
val gapBottom = obstacle.gapY + pipeGap / 2.0
val topRect = new Rectangle2D.Double(obstacle.x, 0, pipeWidth, math.max(0, gapTop))
val bottomRect = new Rectangle2D.Double(obstacle.x, gapBottom, pipeWidth, math.max(0, groundY - gapBottom))
if (birdRect.intersects(topRect) || birdRect.intersects(bottomRect)) endGame()
p += 1
}
}
}
}
override protected def paintComponent(graphics: Graphics): Unit = {
super.paintComponent(graphics)
val g = graphics.create().asInstanceOf[Graphics2D]
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)
val w = getWidth
val h = getHeight
val groundY = h - 82
g.setPaint(new GradientPaint(0, 0, new Color(48, 151, 232), 0, groundY, new Color(200, 237, 255)))
g.fillRect(0, 0, w, h)
g.setColor(new Color(255, 246, 185, 225)); g.fillOval(w - 180, 55, 88, 88)
g.setColor(new Color(255, 255, 255, 95)); g.fillOval(w - 195, 38, 116, 116)
drawCloud(g, (w * 0.18 + tick * 12) % (w + 220) - 110, 135, 1.0)
drawCloud(g, (w * 0.62 + tick * 7) % (w + 260) - 130, 220, 0.78)
drawCloud(g, (w * 0.84 + tick * 9) % (w + 240) - 120, ninety(h), 0.62)
drawHill(g, 0, groundY - 115, w, 190, new Color(94, 191, 144))
drawHill(g, 0, groundY - 72, w, 150, new Color(59, 159, 113))
pipes.foreach(p => drawPipe(g, p.x.toInt, p.gapY, groundY))
drawBird(g, birdX.toInt, birdY.toInt)
g.setColor(new Color(27, 121, 78)); g.fillRect(0, groundY, w, 82)
g.setPaint(new GradientPaint(0, groundY, new Color(130, 225, 111), 0, groundY + 20, new Color(47, 157, 86)))
g.fillRect(0, groundY, w, 18)
g.setColor(new Color(218, 191, 118)); g.fillRect(0, groundY + 18, w, 64)
g.setColor(new Color(150, 112, 73))
var gx = -groundShift.toInt
while (gx < w) { g.drawLine(gx, groundY + 28, gx + 32, groundY + 58); g.drawLine(gx + 22, groundY + 19, gx + 50, groundY + 48); gx += 70 }
drawHud(g)
if (state == "READY") drawOverlay(g, "BIXUU 02 BIRD", "PRESS SPACE / CLICK TO START", "Fly through the sky gates and collect points")
else if (state == "PAUSED") drawOverlay(g, "PAUSED", "PRESS P TO CONTINUE", "Your sky adventure is waiting")
else if (state == "OVER") drawOverlay(g, "FLIGHT COMPLETE", "SCORE " + score + " BEST " + bestScore, "PRESS ENTER / CLICK TO PLAY AGAIN")
g.dispose()
}
private def ninety(h: Int): Int = math.max(90, math.min(300, h / 3))
private def drawCloud(g: Graphics2D, x: Double, y: Double, scale: Double): Unit = {
g.setColor(new Color(255, 255, 255, 190))
g.fillOval(x.toInt, y.toInt, (76*scale).toInt, (35*scale).toInt)
g.fillOval((x+25*scale).toInt, (y-17*scale).toInt, (58*scale).toInt, (50*scale).toInt)
g.fillOval((x+55*scale).toInt, (y-6*scale).toInt, (60*scale).toInt, (40*scale).toInt)
}
private def drawHill(g: Graphics2D, x: Int, y: Int, width: Int, height: Int, color: Color): Unit = {
g.setColor(color)
val poly = new Polygon()
poly.addPoint(x, y + height)
var i = 0
while (i <= 12) {
val xx = x + i * width / 12
val yy = y + (math.sin(i * 0.9 + tick * 0.05) * 35 + math.cos(i * 0.37) * 24).toInt
poly.addPoint(xx, yy)
i += 1
}
poly.addPoint(x + width, y + height)
g.fillPolygon(poly)
}
private def drawPipe(g: Graphics2D, x: Int, gapY: Double, groundY: Int): Unit = {
val topH = math.max(0, (gapY - pipeGap / 2.0).toInt)
val bottomY = (gapY + pipeGap / 2.0).toInt
val bottomH = math.max(0, groundY - bottomY)
g.setPaint(new GradientPaint(x, 0, new Color(23, 140, 81), x + pipeWidth.toInt, 0, new Color(109, 225, 122)))
g.fillRect(x, 0, pipeWidth.toInt, topH)
g.fillRect(x, bottomY, pipeWidth.toInt, bottomH)
g.setColor(new Color(15, 107, 69)); g.fillRect(x - 7, topH - 22, pipeWidth.toInt + 14, 22)
g.fillRect(x - 7, bottomY, pipeWidth.toInt + 14, 22)
g.setColor(new Color(183, 255, 177, 175)); g.fillRect(x + 12, 0, 9, math.max(0, topH - 22))
g.fillRect(x + 12, bottomY + 22, 9, math.max(0, bottomH - 22))
g.setColor(new Color(9, 88, 59, 180)); g.drawRect(x, 0, pipeWidth.toInt, topH)
g.drawRect(x, bottomY, pipeWidth.toInt, bottomH)
}
private def drawBird(g: Graphics2D, x: Int, y: Int): Unit = {
val wing = math.sin(tick * 12.0) * 8.0
g.setColor(new Color(0, 0, 0, 45)); g.fillOval(x - 22, y + 19, 48, 12)
g.setColor(new Color(255, 160, 39)); g.fillOval(x - 20, y - 17, 42, 34)
g.setColor(new Color(255, 222, 105)); g.fillOval(x - 13, y - 15, 27, 22)
g.setColor(new Color(222, 104, 35)); g.fillOval((x - 9).toInt, (y + wing - 3).toInt, 22, 13)
g.setColor(new Color(255, 235, 146)); g.fillOval(x + 7, y - 10, 17, 14)
g.setColor(Color.WHITE); g.fillOval(x + 7, y - 11, 10, 11)
g.setColor(new Color(28, 39, 61)); g.fillOval(x + 12, y - 8, 4, 5)
g.setColor(new Color(250, 103, 48)); val beak = new Polygon(); beak.addPoint(x + 20, y - 1); beak.addPoint(x + 38, y + 4); beak.addPoint(x + 20, y + 9); g.fillPolygon(beak)
g.setColor(new Color(183, 81, 38)); g.setStroke(new BasicStroke(2f)); g.drawOval(x - 20, y - 17, 42, 34)
}
private def drawHud(g: Graphics2D): Unit = {
g.setColor(new Color(6, 24, 52, 155)); g.fillRoundRect(22, 18, 232, 78, 18, 18)
g.setFont(new Font("SansSerif", Font.BOLD, 13)); g.setColor(new Color(151, 235, 255)); g.drawString("BIXUU 02 SKY QUEST", 38, 43)
g.setFont(new Font("SansSerif", Font.BOLD, 27)); g.setColor(Color.WHITE); g.drawString("SCORE " + score, 37, 78)
g.setFont(new Font("SansSerif", Font.BOLD, 13)); g.setColor(new Color(255, 245, 190)); g.drawString("BEST " + bestScore, getWidth - 112, 37)
}
private def drawOverlay(g: Graphics2D, title: String, line1: String, line2: String): Unit = {
val boxW = 520; val boxH = 220; val x = (getWidth - boxW) / 2; val y = (getHeight - boxH) / 2 - 15
g.setColor(new Color(5, 15, 38, 205)); g.fillRoundRect(x, y, boxW, boxH, 30, 30)
g.setStroke(new BasicStroke(2.5f)); g.setColor(new Color(116, 231, 250, 210)); g.drawRoundRect(x, y, boxW, boxH, 30, 30)
g.setFont(new Font("SansSerif", Font.BOLD, 30)); g.setColor(Color.WHITE); centered(g, title, getWidth/2, y + 57)
g.setFont(new Font("SansSerif", Font.BOLD, 17)); g.setColor(new Color(255, 216, 104)); centered(g, line1, getWidth/2, y + 111)
g.setFont(new Font("SansSerif", Font.PLAIN, 14)); g.setColor(new Color(213, 234, 255)); centered(g, line2, getWidth/2, y + 150)
g.setFont(new Font("SansSerif", Font.BOLD, 12)); g.setColor(new Color(126, 246, 209)); centered(g, "SPACE / UP / CLICK = FLAP P = PAUSE R = RESTART", getWidth/2, y + 190)
}
private def centered(g: Graphics2D, s: String, cx: Int, y: Int): Unit = g.drawString(s, cx - g.getFontMetrics.stringWidth(s) / 2, y)
private def handleFlap(): Unit = {
if (state == "READY" || state == "OVER") startNewGame()
else if (state == "PLAYING") flap()
}
override def keyPressed(e: KeyEvent): Unit = {
if (e.getKeyCode == KeyEvent.VK_SPACE || e.getKeyCode == KeyEvent.VK_UP) handleFlap()
else if (e.getKeyCode == KeyEvent.VK_ENTER) { if (state != "PLAYING") startNewGame() }
else if (e.getKeyCode == KeyEvent.VK_P) {
if (state == "PLAYING") state = "PAUSED" else if (state == "PAUSED") state = "PLAYING"
}
else if (e.getKeyCode == KeyEvent.VK_R) startNewGame()
repaint()
}
override def keyReleased(e: KeyEvent): Unit = {}
override def keyTyped(e: KeyEvent): Unit = {}
override def mouseClicked(e: MouseEvent): Unit = { handleFlap(); requestFocusInWindow(); repaint() }
override def mousePressed(e: MouseEvent): Unit = {}
override def mouseReleased(e: MouseEvent): Unit = {}
override def mouseEntered(e: MouseEvent): Unit = {}
override def mouseExited(e: MouseEvent): Unit = {}
}
}
/**
* BIXUU NAVRATRI 3D ADVENTURE
* Kojo-friendly Scala + Java Swing/AWT game. No external libraries or image files.
* A small custom perspective/isometric renderer gives the world a 3D look.
* Controls: WASD / Arrow Keys = move, E / ENTER / SPACE = interact,
* 1 / ENTER = start, P = pause, ESC = menu.
*/
object Navratri3DAdventure {
def main(args: Array[String]): Unit = {
SwingUtilities.invokeLater(new Runnable {
override def run(): Unit = createWindow()
})
}
def launch(): Unit = main(Array.empty[String])
private def createWindow(): Unit = {
val frame = new JFrame("BIXUU 02 | NAVRATRI 3D ADVENTURE")
val game = new GamePanel()
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE)
frame.setContentPane(game)
frame.setSize(1120, 740)
frame.setMinimumSize(new Dimension(920, 640))
frame.setLocationRelativeTo(null)
frame.setIconImage(makeIcon())
frame.addWindowListener(new WindowAdapter {
override def windowClosed(e: WindowEvent): Unit = game.stopLoop()
})
frame.setVisible(true)
game.startLoop()
SwingUtilities.invokeLater(new Runnable {
override def run(): Unit = game.requestFocusInWindow()
})
}
private def makeIcon(): BufferedImage = {
val image = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB)
val g = image.createGraphics()
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g.setPaint(new GradientPaint(0, 0, new Color(37, 21, 77), 64, 64, new Color(225, 79, 51)))
g.fillRoundRect(0, 0, 64, 64, 15, 15)
g.setColor(new Color(255, 199, 58))
g.fillOval(13, 34, 38, 16)
g.setColor(new Color(255, 237, 156))
g.fillOval(29, 20, 8, 18)
g.drawLine(22, 29, 42, 29)
g.dispose()
image
}
private class GamePanel extends JPanel with MouseListener with KeyListener {
private val random = new Random(41)
// Named palettes avoid Kojo/Scala parser ambiguity with Array(...)(index).
private val giftColors: Array[Color] = Array(
new Color(225, 57, 102),
new Color(57, 183, 148),
new Color(255, 190, 64),
new Color(113, 113, 230),
new Color(245, 119, 71)
)
private val dancerColors: Array[Color] = Array(
new Color(224, 63, 128),
new Color(69, 191, 153),
new Color(244, 177, 52),
new Color(111, 111, 222)
)
private val baggageColors: Array[Color] = Array(
new Color(232, 77, 104),
new Color(74, 198, 154),
new Color(252, 193, 70),
new Color(106, 127, 235),
new Color(235, 135, 67)
)
private var scene = "menu"
private var playerName = "Bixuu"
private var businessName = "Bixuu Industries"
private var playerX = 0.0
private var playerZ = 0.0
private var moveUp = false
private var moveDown = false
private var moveLeft = false
private var moveRight = false
private val heldKeys = scala.collection.mutable.Set.empty[Int]
private var tick = 0
private var walkFrame = 0
private var score = 0
private var toast = "Welcome! Your special Navratri invitation is waiting at the office."
private var toastTicks = 230
private var paused = false
private var carFixed = false
private var giftCollected = false
private var checkedIn = false
private var securityCleared = false
private var flightTicks = 0
private var baggageCleared = false
private var dressedForNavratri = false
private var danceStarted = false
private var danceStartTick = 0
private var danceTaps = 0
private var prize = ""
private val movementCodes: Set[Int] = Set(
KeyEvent.VK_W, KeyEvent.VK_A, KeyEvent.VK_S, KeyEvent.VK_D,
KeyEvent.VK_UP, KeyEvent.VK_DOWN, KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT
)
private val handledCodes: Set[Int] = movementCodes ++ Set(
KeyEvent.VK_E, KeyEvent.VK_ENTER, KeyEvent.VK_SPACE,
KeyEvent.VK_1, KeyEvent.VK_2, KeyEvent.VK_P, KeyEvent.VK_ESCAPE
)
setFocusable(true)
setFocusTraversalKeysEnabled(false)
setBackground(new Color(14, 15, 40))
addMouseListener(this)
addKeyListener(this)
addFocusListener(new FocusAdapter {
override def focusLost(e: FocusEvent): Unit = resetKeys()
})
// Panel-local keyboard handling: no global AWT dispatcher is required.
private def updateMovementFlags(): Unit = {
moveUp = heldKeys.contains(KeyEvent.VK_W) || heldKeys.contains(KeyEvent.VK_UP)
moveDown = heldKeys.contains(KeyEvent.VK_S) || heldKeys.contains(KeyEvent.VK_DOWN)
moveLeft = heldKeys.contains(KeyEvent.VK_A) || heldKeys.contains(KeyEvent.VK_LEFT)
moveRight = heldKeys.contains(KeyEvent.VK_D) || heldKeys.contains(KeyEvent.VK_RIGHT)
}
private def resetKeys(): Unit = {
heldKeys.clear()
updateMovementFlags()
}
private var loopTimer: Timer = null
def startLoop(): Unit = {
if (loopTimer == null || !loopTimer.isRunning) {
loopTimer = new Timer(30, new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
updateGame()
repaint()
}
})
loopTimer.setCoalesce(true)
loopTimer.start()
}
}
def stopLoop(): Unit = {
if (loopTimer != null) loopTimer.stop()
resetKeys()
}
private def showToast(message: String, duration: Int = 160): Unit = {
toast = message
toastTicks = duration
}
private def resetPosition(): Unit = {
playerX = 0.0
playerZ = 0.0
walkFrame = 0
moveUp = false
moveDown = false
moveLeft = false
moveRight = false
}
private def startGame(): Unit = {
scene = "office"
playerX = 0.0
playerZ = 0.0
score = 0
carFixed = false
giftCollected = false
checkedIn = false
securityCleared = false
flightTicks = 0
baggageCleared = false
dressedForNavratri = false
danceStarted = false
danceTaps = 0
prize = ""
paused = false
resetKeys()
showToast("A royal Navratri invitation arrived! Go to the glowing invitation on your office desk.", 250)
}
private def handleKeyPress(key: Int): Unit = {
if (key == KeyEvent.VK_ESCAPE) {
scene = "menu"
paused = false
resetPosition()
showToast("Choose START to begin your Navratri journey.", 130)
return
}
if (key == KeyEvent.VK_P && scene != "menu" && scene != "complete") {
paused = !paused
showToast(if (paused) "Game paused. Press P to continue." else "Game resumed.", 90)
return
}
if (scene == "menu") {
if (key == KeyEvent.VK_1 || key == KeyEvent.VK_ENTER || key == KeyEvent.VK_SPACE) startGame()
return
}
if (scene == "complete") {
if (key == KeyEvent.VK_ENTER || key == KeyEvent.VK_SPACE || key == KeyEvent.VK_E) {
scene = "menu"
showToast("Ready for another Navratri trip? Press ENTER to play.", 130)
}
return
}
if (paused) return
if (scene == "stadium" && danceStarted &&
(key == KeyEvent.VK_E || key == KeyEvent.VK_ENTER || key == KeyEvent.VK_SPACE)) {
danceTaps += 1
score += 5
showToast("Dandiya beat " + danceTaps + "! Keep tapping SPACE / ENTER / E to dance.", 45)
} else if (key == KeyEvent.VK_E || key == KeyEvent.VK_ENTER || key == KeyEvent.VK_SPACE) {
interact()
}
}
private def updateGame(): Unit = {
tick += 1
if (toastTicks > 0) toastTicks -= 1
if (scene == "menu" || scene == "complete" || paused) return
var dx = 0.0
var dz = 0.0
if (moveLeft) dx -= 1.0
if (moveRight) dx += 1.0
if (moveUp) dz += 1.0
if (moveDown) dz -= 1.0
if (dx != 0.0 || dz != 0.0) {
val length = math.sqrt(dx * dx + dz * dz)
playerX += (dx / length) * 0.095
playerZ += (dz / length) * 0.095
playerX = math.max(-6.0, math.min(6.0, playerX))
playerZ = math.max(-6.0, math.min(6.0, playerZ))
walkFrame += 1
}
if (scene == "plane" && flightTicks < 280) flightTicks += 1
if (scene == "stadium" && danceStarted && tick - danceStartTick >= 320) finishDance()
}
private def targetPoint(): (Double, Double) = scene match {
case "office" => (0.0, 2.35)
case "carTrouble" => (1.55, 0.72)
case "giftShop" => (0.0, 2.05)
case "mumbaiAirport" => (0.0, 2.15)
case "mumbaiSecurity" => (0.0, 1.8)
case "plane" => (0.0, 1.5)
case "gujaratAirport" => (0.0, 2.05)
case "home" => (0.0, 2.15)
case "dressChange" => (0.0, 2.05)
case "stadium" => (0.0, 1.75)
case _ => (0.0, 0.0)
}
private def nearTarget(radius: Double = 1.45): Boolean = {
val target = targetPoint()
val dx = playerX - target._1
val dz = playerZ - target._2
math.sqrt(dx * dx + dz * dz) <= radius
}
private def nextScene(next: String, message: String): Unit = {
scene = next
resetPosition()
showToast(message, 220)
}
private def interact(): Unit = {
if (!nearTarget()) {
showToast("Walk toward the glowing marker, then press E / ENTER.", 95)
return
}
scene match {
case "office" =>
score += 10
nextScene("carTrouble", "Invitation accepted! Your car has broken down on the road. Walk to the car and inspect it.")
case "carTrouble" =>
if (!carFixed) {
carFixed = true
score += 25
showToast("Mechanic fixed the car! Press E again near the car to drive to the gift shop.", 180)
} else {
nextScene("giftShop", "Car repaired! You drove to the gift shop. Collect a festive gift for the host.")
}
case "giftShop" =>
if (!giftCollected) {
giftCollected = true
score += 50
showToast("Gift collected! Press E again at the counter to continue to Mumbai Airport.", 180)
} else {
nextScene("mumbaiAirport", "You reached Mumbai Airport with the gift. Go to airline check-in.")
}
case "mumbaiAirport" =>
checkedIn = true
score += 25
nextScene("mumbaiSecurity", "Check-in complete! Show your boarding pass and go through airport security.")
case "mumbaiSecurity" =>
securityCleared = true
score += 25
flightTicks = 0
nextScene("plane", "Security check complete. Board the flight to Gujarat and enjoy the journey!")
case "plane" =>
if (flightTicks < 180) {
showToast("The plane is flying toward Gujarat. Please wait for landing: " + (flightTicks * 100 / 180) + "%.", 100)
} else {
score += 40
nextScene("gujaratAirport", "Welcome to Gujarat! Leave the plane and collect your luggage after arrival checks.")
}
case "gujaratAirport" =>
baggageCleared = true
score += 25
nextScene("home", "Arrival and baggage checks complete! Your car is ready. Drive to your home and get ready for Navratri.")
case "home" =>
nextScene("dressChange", "Welcome home! Go to your wardrobe and change into your colourful Navratri outfit.")
case "dressChange" =>
if (!dressedForNavratri) {
dressedForNavratri = true
score += 20
showToast("Navratri outfit ready! Press E again to travel to the Dandiya Stadium.", 180)
} else {
nextScene("stadium", "You reached the grand Gujarat Navratri stadium! Walk to the dance floor and press E to begin.")
}
case "stadium" =>
if (!danceStarted) {
danceStarted = true
danceStartTick = tick
danceTaps = 0
showToast("DANDIYA TIME! Tap SPACE / ENTER / E to the beat for 10 seconds. Aim for 12 beats for FIRST PRIZE!", 230)
}
case _ =>
}
}
private def finishDance(): Unit = {
prize = if (danceTaps >= 12) "FIRST PRIZE" else "SECOND PRIZE"
score += (if (prize == "FIRST PRIZE") 500 else 200)
scene = "complete"
paused = false
showToast("Amazing Navratri! You won " + prize + "!", 280)
}
// ---------- 3D projection helpers ----------
// Fixed isometric camera: the character visibly moves across the world
// instead of staying pinned to screen centre while the floor follows it.
private def project(x: Double, y: Double, z: Double): java.awt.Point = {
val sx = getWidth * 0.5 + (x - z) * 35.0
val sy = getHeight * 0.535 + (x + z) * 17.5 - y * 48.0
new java.awt.Point(sx.toInt, sy.toInt)
}
private def polygon(points: Seq[java.awt.Point]): Polygon = {
new Polygon(points.map(_.x).toArray, points.map(_.y).toArray, points.length)
}
private def drawBox(g: Graphics2D, x: Double, z: Double, width: Double, depth: Double,
height: Double, top: Color, left: Color, right: Color,
baseY: Double = 0.0): Unit = {
val a = project(x - width / 2, baseY + height, z - depth / 2)
val b = project(x + width / 2, baseY + height, z - depth / 2)
val c = project(x + width / 2, baseY + height, z + depth / 2)
val d = project(x - width / 2, baseY + height, z + depth / 2)
val A = project(x - width / 2, baseY, z - depth / 2)
val B = project(x + width / 2, baseY, z - depth / 2)
val C = project(x + width / 2, baseY, z + depth / 2)
val D = project(x - width / 2, baseY, z + depth / 2)
g.setColor(left)
g.fillPolygon(polygon(Seq(a, d, D, A)))
g.setColor(right)
g.fillPolygon(polygon(Seq(b, c, C, B)))
g.setColor(left.darker())
g.fillPolygon(polygon(Seq(d, c, C, D)))
g.setColor(top)
g.fillPolygon(polygon(Seq(a, b, c, d)))
g.setColor(new Color(15, 14, 36, 150))
g.setStroke(new BasicStroke(1f))
g.drawPolygon(polygon(Seq(a, b, c, d)))
}
private def drawGround(g: Graphics2D): Unit = {
var j = -9
while (j <= 9) {
var i = -9
while (i <= 9) {
// The ground grid is in fixed world coordinates, like the scenery.
val cx = i * 0.82
val cz = j * 0.82
val tile = polygon(Seq(
project(cx - 0.41, 0, cz - 0.41),
project(cx + 0.41, 0, cz - 0.41),
project(cx + 0.41, 0, cz + 0.41),
project(cx - 0.41, 0, cz + 0.41)
))
val tone = if ((i + j) % 2 == 0) new Color(72, 75, 105) else new Color(62, 65, 94)
g.setColor(tone)
g.fillPolygon(tile)
g.setColor(new Color(177, 175, 201, 22))
g.drawPolygon(tile)
i += 1
}
j += 1
}
}
private def drawMarker(g: Graphics2D, x: Double, z: Double, color: Color): Unit = {
val p = project(x, 0.05, z)
val pulse = (math.sin(tick * 0.16) * 5).toInt
g.setColor(new Color(color.getRed, color.getGreen, color.getBlue, 38))
g.fillOval(p.x - 21 - pulse, p.y - 8 - pulse / 3, 42 + pulse * 2, 19 + pulse)
g.setColor(color)
g.setStroke(new BasicStroke(2.2f))
g.drawOval(p.x - 15 - pulse, p.y - 6 - pulse / 3, 30 + pulse * 2, 13 + pulse)
g.fillOval(p.x - 4, p.y - 18, 8, 8)
g.drawLine(p.x, p.y - 10, p.x, p.y - 2)
g.setStroke(new BasicStroke(1f))
}
private def drawPerson(g: Graphics2D, x: Double, z: Double, shirt: Color, pants: Color, scale: Double = 1.0): Unit = {
val p = project(x, 0, z)
val bob = math.sin(tick * 0.12 + x * 2.0) * 1.4
g.setColor(new Color(0, 0, 0, 70))
g.fillOval(p.x - (13 * scale).toInt, p.y - 2, (26 * scale).toInt, (8 * scale).toInt)
g.setStroke(new BasicStroke((4.5 * scale).toFloat, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND))
g.setColor(pants)
g.drawLine(p.x - 4, p.y - 18, p.x - 6, p.y - 3)
g.drawLine(p.x + 4, p.y - 18, p.x + 6, p.y - 3)
g.setColor(shirt)
g.fillRoundRect(p.x - (9 * scale).toInt, p.y - (39 * scale).toInt + bob.toInt,
(18 * scale).toInt, (23 * scale).toInt, 7, 7)
g.setColor(new Color(241, 190, 147))
g.fillOval(p.x - (7 * scale).toInt, p.y - (54 * scale).toInt + bob.toInt,
(14 * scale).toInt, (15 * scale).toInt)
g.setColor(new Color(52, 31, 28))
g.fillArc(p.x - (8 * scale).toInt, p.y - (57 * scale).toInt + bob.toInt,
(16 * scale).toInt, (11 * scale).toInt, 0, 180)
g.setStroke(new BasicStroke(1f))
}
private def drawPlayer(g: Graphics2D): Unit = {
val p = project(playerX, 0, playerZ)
val bob = if (moveUp || moveDown || moveLeft || moveRight) math.sin(walkFrame * 0.7) * 3.0 else math.sin(tick * 0.06) * 1.0
g.setColor(new Color(0, 0, 0, 95))
g.fillOval(p.x - 21, p.y - 3, 42, 13)
val legSwing = if (moveUp || moveDown || moveLeft || moveRight) (math.sin(walkFrame * 0.7) * 5).toInt else 0
g.setStroke(new BasicStroke(7f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND))
g.setColor(new Color(42, 31, 76))
g.drawLine(p.x - 5, p.y - 20, p.x - 7 + legSwing, p.y - 3)
g.drawLine(p.x + 5, p.y - 20, p.x + 7 - legSwing, p.y - 3)
g.setColor(new Color(249, 166, 48))
g.fillRoundRect(p.x - 14, p.y - 45 + bob.toInt, 28, 27, 9, 9)
g.setColor(if (dressedForNavratri || scene == "stadium") new Color(212, 52, 131) else new Color(45, 142, 214))
g.fillRoundRect(p.x - 12, p.y - 44 + bob.toInt, 24, 21, 8, 8)
g.setColor(new Color(244, 192, 151))
g.fillOval(p.x - 10, p.y - 66 + bob.toInt, 20, 21)
g.setColor(new Color(57, 31, 30))
g.fillArc(p.x - 11, p.y - 70 + bob.toInt, 22, 15, 0, 180)
g.setColor(Color.BLACK)
g.fillOval(p.x - 5, p.y - 56 + bob.toInt, 2, 3)
g.fillOval(p.x + 4, p.y - 56 + bob.toInt, 2, 3)
if (scene == "stadium" && danceStarted) {
g.setStroke(new BasicStroke(4f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND))
val swing = (math.sin(tick * 0.45) * 12).toInt
g.setColor(new Color(249, 187, 67))
g.drawLine(p.x - 10, p.y - 39, p.x - 23, p.y - 49 - swing)
g.drawLine(p.x + 10, p.y - 39, p.x + 23, p.y - 49 + swing)
g.setColor(new Color(255, 238, 186))
g.drawLine(p.x - 27, p.y - 53 - swing, p.x - 17, p.y - 45 - swing)
g.drawLine(p.x + 17, p.y - 45 + swing, p.x + 27, p.y - 53 + swing)
}
g.setStroke(new BasicStroke(1f))
g.setFont(new Font("SansSerif", Font.BOLD, 10))
g.setColor(new Color(255, 238, 179))
centered(g, playerName.toUpperCase, p.x, p.y - 76 + bob.toInt)
}
private def drawCar(g: Graphics2D, x: Double, z: Double, broken: Boolean): Unit = {
drawBox(g, x, z, 2.0, 1.0, 0.48, new Color(228, 65, 70), new Color(142, 38, 58), new Color(179, 43, 53))
drawBox(g, x - 0.08, z, 1.12, 0.78, 0.36, new Color(105, 198, 225), new Color(43, 81, 119), new Color(62, 107, 145), 0.46)
val p1 = project(x - 0.62, 0.22, z - 0.46)
val p2 = project(x + 0.64, 0.22, z - 0.46)
val p3 = project(x - 0.62, 0.22, z + 0.46)
val p4 = project(x + 0.64, 0.22, z + 0.46)
Seq(p1, p2, p3, p4).foreach { p =>
g.setColor(new Color(23, 25, 42)); g.fillOval(p.x - 8, p.y - 4, 16, 13)
g.setColor(new Color(166, 183, 202)); g.fillOval(p.x - 4, p.y - 1, 8, 7)
}
val head = project(x + 0.7, 0.3, z - 0.45)
g.setColor(new Color(255, 243, 174)); g.fillOval(head.x - 4, head.y - 4, 8, 7)
if (broken) {
val smoke = project(x + 0.48, 1.0 + math.sin(tick * 0.1) * 0.15, z + 0.12)
g.setColor(new Color(124, 129, 150, 125)); g.fillOval(smoke.x - 13, smoke.y - 13, 26, 23)
g.setColor(new Color(184, 188, 202, 100)); g.fillOval(smoke.x + 2, smoke.y - 25, 20, 19)
}
if (carFixed) {
val light = project(x, 1.2, z)
g.setColor(new Color(125, 255, 192)); g.fillOval(light.x - 5, light.y - 5, 10, 10)
}
}
private def drawTree(g: Graphics2D, x: Double, z: Double): Unit = {
drawBox(g, x, z, 0.26, 0.26, 1.1, new Color(129, 78, 47), new Color(92, 53, 35), new Color(106, 61, 38))
val p = project(x, 1.65, z)
g.setColor(new Color(33, 110, 71)); g.fillOval(p.x - 23, p.y - 17, 46, 39)
g.setColor(new Color(45, 139, 79)); g.fillOval(p.x - 16, p.y - 31, 36, 35)
g.setColor(new Color(67, 164, 92)); g.fillOval(p.x - 11, p.y - 24, 20, 19)
}
private def drawHouse(g: Graphics2D, x: Double, z: Double, width: Double, name: String): Unit = {
drawBox(g, x, z, width, 2.1, 2.0, new Color(245, 184, 122), new Color(155, 87, 87), new Color(187, 103, 86))
val door = project(x, 0.8, z - 1.06)
g.setColor(new Color(92, 47, 57)); g.fillRoundRect(door.x - 16, door.y - 35, 32, 52, 5, 5)
val win1 = project(x - width * 0.27, 1.25, z - 1.08)
val win2 = project(x + width * 0.27, 1.25, z - 1.08)
Seq(win1, win2).foreach { p =>
g.setColor(new Color(33, 63, 97)); g.fillRoundRect(p.x - 13, p.y - 13, 26, 25, 4, 4)
g.setColor(new Color(132, 222, 238)); g.fillRect(p.x - 9, p.y - 9, 18, 17)
g.setColor(new Color(47, 63, 87)); g.drawLine(p.x, p.y - 9, p.x, p.y + 8)
}
val roofLeft = project(x - width / 2 - 0.2, 2.05, z - 1.05)
val roofTop = project(x, 3.0, z - 1.0)
val roofRight = project(x + width / 2 + 0.2, 2.05, z - 1.05)
g.setColor(new Color(137, 43, 75))
g.fillPolygon(polygon(Seq(roofLeft, roofTop, roofRight)))
g.setColor(new Color(255, 218, 151))
g.setFont(new Font("SansSerif", Font.BOLD, 11))
centered(g, name, project(x, 2.25, z - 1.15).x, project(x, 2.25, z - 1.15).y)
}
private def drawShop(g: Graphics2D): Unit = {
drawBox(g, 0.0, 2.65, 3.0, 1.3, 1.65, new Color(240, 153, 68), new Color(122, 53, 72), new Color(170, 66, 69))
drawBox(g, 0.0, 1.9, 2.0, 0.5, 0.65, new Color(255, 204, 107), new Color(111, 58, 64), new Color(145, 60, 61), 0.05)
val sign = project(0, 2.35, 1.95)
g.setColor(new Color(255, 238, 177))
g.setFont(new Font("SansSerif", Font.BOLD, 15))
centered(g, "GIFT SHOP", sign.x, sign.y)
var i = 0
while (i < 5) {
val x = -0.7 + i * 0.35
val p = project(x, 0.9 + (i % 2) * 0.08, 1.7)
g.setColor(giftColors(i))
g.fillRoundRect(p.x - 8, p.y - 8, 16, 15, 3, 3)
g.setColor(new Color(255, 232, 165)); g.drawLine(p.x, p.y - 8, p.x, p.y - 13)
i += 1
}
}
private def drawAirportBuilding(g: Graphics2D, gujarat: Boolean): Unit = {
drawBox(g, 0.0, 5.0, 8.6, 1.8, 2.2, new Color(159, 203, 229), new Color(63, 101, 147), new Color(83, 125, 162))
drawBox(g, 0.0, 4.0, 7.6, 0.5, 0.35, new Color(255, 188, 76), new Color(111, 76, 90), new Color(145, 85, 94), 2.05)
var i = -3
while (i <= 3) {
val p = project(i.toDouble, 1.25, 4.02)
g.setColor(new Color(29, 58, 93)); g.fillRoundRect(p.x - 20, p.y - 29, 40, 55, 5, 5)
g.setColor(new Color(147, 226, 239)); g.fillRect(p.x - 15, p.y - 24, 30, 43)
i += 1
}
val title = if (gujarat) "GUJARAT AIRPORT" else "MUMBAI INTERNATIONAL AIRPORT"
val p = project(0, 2.6, 4.05)
g.setColor(new Color(255, 244, 197)); g.setFont(new Font("SansSerif", Font.BOLD, 17)); centered(g, title, p.x, p.y)
}
private def drawDiyasAndGarlands(g: Graphics2D): Unit = {
var i = -4
while (i <= 4) {
val p = project(i * 0.72, 2.7, 1.0)
val pulse = (math.sin(tick * 0.12 + i) * 3).toInt
g.setColor(new Color(255, 199, 72, 50)); g.fillOval(p.x - 11 - pulse, p.y - 15 - pulse, 22 + pulse * 2, 22 + pulse * 2)
g.setColor(if (i % 2 == 0) new Color(255, 207, 77) else new Color(70, 234, 188))
g.fillOval(p.x - 4, p.y - 7, 8, 9)
i += 1
}
}
private def drawPlane(g: Graphics2D): Unit = {
// Plane body, wings and tail are projected boxes rather than a flat picture.
drawBox(g, 0.0, 2.0, 1.4, 4.0, 0.62, new Color(226, 234, 241), new Color(70, 115, 162), new Color(110, 159, 195), 0.45)
drawBox(g, 0.0, 2.0, 4.5, 0.72, 0.19, new Color(189, 218, 237), new Color(76, 124, 166), new Color(119, 164, 195), 0.5)
drawBox(g, 0.0, -0.1, 1.75, 0.8, 0.28, new Color(255, 196, 77), new Color(168, 99, 69), new Color(202, 126, 66), 0.58)
var i = -2
while (i <= 2) {
val p = project(0.72, 0.78, 0.5 + i * 0.67)
g.setColor(new Color(34, 61, 94)); g.fillRoundRect(p.x - 7, p.y - 5, 13, 10, 3, 3)
g.setColor(new Color(137, 218, 241)); g.fillRect(p.x - 5, p.y - 3, 9, 5)
i += 1
}
// Moving clouds create a sense of flight.
var c = 0
while (c < 9) {
val xx = ((c * 157 + tick * (c % 3 + 1)) % math.max(1, getWidth)).toInt
val yy = 120 + ((c * 47) % 190)
g.setColor(new Color(255, 255, 255, 95))
g.fillOval(xx, yy, 72, 24); g.fillOval(xx + 17, yy - 11, 38, 30); g.fillOval(xx + 38, yy - 4, 35, 25)
c += 1
}
g.setColor(new Color(255, 232, 163)); g.setFont(new Font("SansSerif", Font.BOLD, 18))
centered(g, "FLIGHT TO GUJARAT", getWidth / 2, 165)
g.setFont(new Font("SansSerif", Font.BOLD, 14))
centered(g, "IN-FLIGHT JOURNEY: " + math.min(100, flightTicks * 100 / 180) + "%", getWidth / 2, 193)
}
private def drawStadium(g: Graphics2D): Unit = {
// Large stepped arena, central dance floor, lights, and dandiya crowd.
drawBox(g, 0, 4.0, 9.0, 1.4, 1.1, new Color(107, 52, 111), new Color(55, 35, 89), new Color(75, 39, 103))
drawBox(g, 0, 2.4, 5.5, 1.3, 0.4, new Color(245, 175, 67), new Color(143, 60, 95), new Color(183, 65, 101))
drawBox(g, 0, 1.7, 3.9, 0.72, 0.16, new Color(64, 204, 181), new Color(28, 93, 108), new Color(35, 135, 132), 0.42)
val stageLabel = project(0, 1.3, 1.7)
g.setColor(new Color(255, 241, 192)); g.setFont(new Font("SansSerif", Font.BOLD, 14)); centered(g, "NAVRATRI DANDIYA", stageLabel.x, stageLabel.y)
var i = 0
while (i < 16) {
val angle = i * math.Pi * 2 / 16.0
val x = math.cos(angle) * 4.0
val z = 2.3 + math.sin(angle) * 2.1
drawPerson(g, x, z, dancerColors(i % dancerColors.length), new Color(53, 42, 85), 0.78)
}
// Decorative arches and strings of lights.
var n = -4
while (n <= 4) {
val poleX = n.toDouble
drawBox(g, poleX, 3.2, 0.12, 0.12, 2.6, new Color(255, 186, 70), new Color(143, 66, 94), new Color(178, 76, 108))
val light = project(poleX, 2.8, 3.2)
g.setColor(new Color(255, 222, 104, 70)); g.fillOval(light.x - 9, light.y - 9, 18, 18)
g.setColor(if (n % 2 == 0) new Color(255, 219, 95) else new Color(80, 246, 203))
g.fillOval(light.x - 4, light.y - 4, 8, 8)
n += 1
}
if (danceStarted) {
val p = project(0, 2.2, 1.5)
var k = 0
while (k < 8) {
val xx = p.x + ((math.sin(tick * 0.15 + k) * (22 + k * 3))).toInt
val yy = p.y - 65 - ((math.cos(tick * 0.18 + k) * 18).toInt)
g.setColor(if (k % 2 == 0) new Color(255, 206, 73, 150) else new Color(79, 236, 200, 140))
g.fillOval(xx, yy, 8, 8)
k += 1
}
}
}
private def drawSceneWorld(g: Graphics2D): Unit = {
val w = getWidth
val h = getHeight
val skyTop = if (scene == "stadium") new Color(25, 10, 59) else new Color(32, 40, 91)
val skyBottom = if (scene == "stadium") new Color(124, 36, 99) else new Color(207, 105, 110)
g.setPaint(new GradientPaint(0, 0, skyTop, 0, h, skyBottom))
g.fillRect(0, 0, w, h)
// Horizon glow and stars.
g.setColor(new Color(255, 213, 139, 35)); g.fillOval(w - 210, 35, 130, 130)
g.setColor(new Color(255, 231, 172, 225)); g.fillOval(w - 179, 66, 68, 68)
var s = 0
while (s < 45) {
val sx = (s * 139 + 23) % math.max(1, w)
val sy = (s * 59 + 17) % 230
g.setColor(new Color(255, 243, 204, 90 + (s % 7) * 20))
g.fillOval(sx, sy, if (s % 9 == 0) 3 else 2, if (s % 9 == 0) 3 else 2)
s += 1
}
drawGround(g)
// Environment props.
scene match {
case "office" =>
drawBox(g, -3.4, 3.0, 2.2, 1.0, 2.35, new Color(111, 142, 190), new Color(47, 64, 117), new Color(68, 88, 145))
drawBox(g, 0, 2.45, 2.2, 0.92, 0.7, new Color(154, 92, 67), new Color(86, 47, 51), new Color(115, 61, 55))
drawBox(g, 0, 2.35, 0.48, 0.34, 0.08, new Color(255, 230, 164), new Color(181, 129, 71), new Color(207, 151, 77), 0.73)
drawBox(g, 0, 2.35, 0.1, 0.08, 0.02, new Color(202, 49, 70), new Color(137, 43, 59), new Color(165, 52, 69), 0.82)
drawTree(g, -4.2, 0.4); drawTree(g, 4.2, 2.3)
drawMarker(g, 0, 2.35, new Color(255, 216, 78))
val title = project(0, 1.18, 2.0); g.setColor(new Color(255, 240, 190)); g.setFont(new Font("SansSerif", Font.BOLD, 12)); centered(g, "INVITATION", title.x, title.y)
case "carTrouble" =>
drawTree(g, -4.0, 2.0); drawTree(g, 4.5, 2.6)
drawBox(g, -1.2, 2.0, 8.5, 1.25, 0.14, new Color(65, 71, 88), new Color(40, 44, 62), new Color(46, 51, 67))
drawCar(g, 1.55, 0.72, !carFixed)
drawPerson(g, 3.0, 0.0, new Color(40, 135, 203), new Color(34, 41, 76), 0.95)
val p = project(3.0, 1.45, 0.0); g.setColor(new Color(255, 240, 198)); g.setFont(new Font("SansSerif", Font.BOLD, 11)); centered(g, "MECHANIC", p.x, p.y)
drawMarker(g, 1.55, 0.72, if (carFixed) new Color(102, 255, 174) else new Color(255, 110, 92))
case "giftShop" =>
drawShop(g)
drawTree(g, -3.4, 0.1); drawTree(g, 3.5, 0.2)
drawBox(g, 0.0, 1.95, 0.45, 0.42, 0.38, new Color(255, 198, 62), new Color(165, 72, 89), new Color(206, 85, 105), 0.68)
val gift = project(0, 1.4, 1.95); g.setColor(new Color(255, 242, 189)); g.setFont(new Font("SansSerif", Font.BOLD, 11)); centered(g, if (giftCollected) "GIFT COLLECTED" else "YOUR GIFT", gift.x, gift.y)
drawMarker(g, 0, 2.05, if (giftCollected) new Color(99, 255, 176) else new Color(255, 216, 79))
case "mumbaiAirport" =>
drawAirportBuilding(g, false)
drawBox(g, 0, 2.15, 2.5, 0.7, 0.75, new Color(84, 186, 202), new Color(42, 83, 118), new Color(55, 111, 139))
val p = project(0, 1.05, 1.7); g.setColor(new Color(255, 245, 199)); g.setFont(new Font("SansSerif", Font.BOLD, 13)); centered(g, "AIRLINE CHECK-IN", p.x, p.y)
drawBox(g, -2.3, 0.4, 0.42, 0.5, 0.62, new Color(199, 59, 105), new Color(111, 45, 82), new Color(143, 52, 94))
drawMarker(g, 0, 2.15, new Color(255, 220, 79))
case "mumbaiSecurity" =>
drawAirportBuilding(g, false)
drawBox(g, 0, 2.0, 1.45, 0.58, 1.62, new Color(68, 192, 199), new Color(30, 92, 125), new Color(43, 124, 153))
drawBox(g, 0, 2.0, 0.45, 0.22, 1.35, new Color(32, 54, 83), new Color(20, 39, 66), new Color(24, 46, 72), 0.12)
drawBox(g, -0.5, 1.3, 0.7, 0.45, 0.22, new Color(236, 166, 73), new Color(111, 71, 58), new Color(151, 87, 61))
drawPerson(g, 1.3, 1.4, new Color(45, 91, 176), new Color(36, 40, 76), 1.05)
val p = project(0, 2.0, 1.6); g.setColor(new Color(255, 241, 187)); g.setFont(new Font("SansSerif", Font.BOLD, 12)); centered(g, "SECURITY CHECK", p.x, p.y)
drawMarker(g, 0, 1.8, new Color(100, 246, 196))
case "plane" =>
drawPlane(g)
drawBox(g, -1.7, 0.3, 0.9, 0.72, 0.82, new Color(74, 92, 139), new Color(37, 49, 89), new Color(53, 66, 109))
drawBox(g, 1.7, 0.3, 0.9, 0.72, 0.82, new Color(74, 92, 139), new Color(37, 49, 89), new Color(53, 66, 109))
drawMarker(g, 0, 1.5, new Color(100, 246, 196))
case "gujaratAirport" =>
drawAirportBuilding(g, true)
drawBox(g, 0, 2.05, 3.5, 0.7, 0.62, new Color(87, 172, 186), new Color(43, 79, 115), new Color(57, 102, 132))
var b = -2
while (b <= 2) {
drawBox(g, b * 0.48, 2.0, 0.35, 0.35, 0.34, baggageColors(b + 2), new Color(68, 51, 92), new Color(88, 62, 113), 0.65)
b += 1
}
val p = project(0, 1.4, 1.65); g.setColor(new Color(255, 241, 193)); g.setFont(new Font("SansSerif", Font.BOLD, 12)); centered(g, "BAGGAGE CLAIM", p.x, p.y)
drawMarker(g, 0, 2.05, new Color(100, 246, 196))
case "home" =>
drawTree(g, -3.5, 1.6); drawTree(g, 3.7, 1.3)
drawHouse(g, 0, 3.0, 4.2, "BIXUU HOME")
drawCar(g, -2.4, 0.5, false)
drawDiyasAndGarlands(g)
drawMarker(g, 0, 2.15, new Color(255, 216, 79))
case "dressChange" =>
drawBox(g, 0, 4.2, 6.8, 1.7, 2.5, new Color(167, 112, 144), new Color(78, 48, 91), new Color(102, 56, 103))
drawBox(g, 0, 2.15, 1.2, 0.6, 2.0, new Color(229, 170, 92), new Color(117, 65, 65), new Color(157, 83, 73))
drawBox(g, -0.22, 2.1, 0.06, 0.5, 1.7, new Color(70, 38, 69), new Color(51, 33, 58), new Color(61, 38, 62), 0.12)
val p = project(0, 2.35, 1.7); g.setColor(new Color(255, 237, 185)); g.setFont(new Font("SansSerif", Font.BOLD, 13)); centered(g, "NAVRATRI OUTFIT", p.x, p.y)
drawMarker(g, 0, 2.05, if (dressedForNavratri) new Color(100, 255, 177) else new Color(255, 216, 79))
case "stadium" =>
drawStadium(g)
drawMarker(g, 0, 1.75, new Color(100, 246, 196))
case _ =>
}
drawPlayer(g)
// Goal hint floats above the glowing objective.
if (scene != "stadium" || !danceStarted) {
val t = targetPoint()
val p = project(t._1, 1.25, t._2)
g.setFont(new Font("SansSerif", Font.BOLD, 11))
g.setColor(new Color(255, 248, 218))
centered(g, "OBJECTIVE", p.x, p.y)
}
}
private def sceneTitle: String = scene match {
case "office" => "1 / 9 - THE INVITATION"
case "carTrouble" => if (carFixed) "2 / 9 - CAR REPAIRED" else "2 / 9 - CAR BREAKDOWN"
case "giftShop" => "3 / 9 - FESTIVE GIFT SHOP"
case "mumbaiAirport" => "4 / 9 - MUMBAI AIRPORT CHECK-IN"
case "mumbaiSecurity" => "5 / 9 - SECURITY CHECK"
case "plane" => "6 / 9 - FLIGHT TO GUJARAT"
case "gujaratAirport" => "7 / 9 - GUJARAT ARRIVAL"
case "home" => "8 / 9 - HOME IN GUJARAT"
case "dressChange" => "9 / 9 - GET READY FOR DANDIYA"
case "stadium" => "GRAND GUJARAT NAVRATRI STADIUM"
case _ => "NAVRATRI ADVENTURE"
}
private def objectiveText: String = scene match {
case "office" => "Find the glowing invitation on your office desk and press E."
case "carTrouble" => if (carFixed) "Car fixed! Press E near the car again to drive to the gift shop." else "Your car broke down. Go to the car and press E to call the mechanic."
case "giftShop" => if (giftCollected) "Gift collected! Press E again to continue to Mumbai Airport." else "Go to the gift shop counter and collect the host's gift."
case "mumbaiAirport" => "Reach airline check-in and collect your boarding pass."
case "mumbaiSecurity" => "Pass your carry-on bag through security and complete the check."
case "plane" => if (flightTicks < 180) "Flight in progress. Wait for Gujarat landing, then press E." else "We have landed! Walk to the marker and press E to leave the plane."
case "gujaratAirport" => "Collect your luggage and finish the arrival checks."
case "home" => "Drive to your home in Gujarat and go to the front door."
case "dressChange" => if (dressedForNavratri) "Outfit ready! Press E again to go to the Dandiya Stadium." else "Choose your colourful Navratri outfit at the wardrobe."
case "stadium" => if (danceStarted) "DANCE! Tap SPACE / ENTER / E to the beat. 12 beats = FIRST PRIZE!" else "Go to the dance floor and press E to start Garba / Dandiya."
case _ => "Enjoy your Navratri adventure!"
}
override def paintComponent(raw: Graphics): Unit = {
super.paintComponent(raw)
val g = raw.asInstanceOf[Graphics2D]
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON)
if (scene == "menu") drawMenu(g)
else if (scene == "complete") drawComplete(g)
else {
drawSceneWorld(g)
drawHUD(g)
if (paused) drawPause(g)
}
}
private def drawMenu(g: Graphics2D): Unit = {
val w = getWidth; val h = getHeight
g.setPaint(new GradientPaint(0, 0, new Color(20, 9, 57), 0, h, new Color(129, 31, 94)))
g.fillRect(0, 0, w, h)
var i = 0
while (i < 110) {
val x = (i * 137 + 19) % math.max(1, w)
val y = (i * 71 + 7) % math.max(1, h)
g.setColor(new Color(255, 240, 200, 80 + (i % 6) * 25))
g.fillOval(x, y, if (i % 8 == 0) 3 else 2, if (i % 8 == 0) 3 else 2)
i += 1
}
// Isometric floor and festive 3D scene preview.
val oldX = playerX; val oldZ = playerZ
playerX = 0; playerZ = 0
drawBox(g, 0, 3.3, 4.6, 1.5, 1.65, new Color(229, 139, 78), new Color(106, 46, 89), new Color(144, 57, 95))
drawBox(g, 0, 2.55, 2.7, 0.7, 0.35, new Color(255, 202, 83), new Color(121, 49, 79), new Color(173, 60, 96))
var n = -4
while (n <= 4) {
val p = project(n.toDouble, 2.1, 3.0)
g.setColor(if (n % 2 == 0) new Color(255, 215, 87) else new Color(69, 238, 199))
g.fillOval(p.x - 5, p.y - 5, 10, 10)
n += 1
}
playerX = oldX; playerZ = oldZ
g.setFont(new Font("SansSerif", Font.BOLD, 40))
g.setColor(new Color(255, 207, 75))
centered(g, "BIXUU NAVRATRI 3D ADVENTURE", w / 2, 83)
g.setFont(new Font("SansSerif", Font.BOLD, 18))
g.setColor(new Color(255, 238, 207))
centered(g, "INVITATION > CAR TROUBLE > GIFT > FLIGHT TO GUJARAT", w / 2, 125)
centered(g, "HOME > NAVRATRI OUTFIT > GARBA / DANDIYA > PRIZES", w / 2, 153)
g.setFont(new Font("SansSerif", Font.BOLD, 20))
g.setColor(new Color(119, 255, 214))
centered(g, "PRESS ENTER OR 1 TO START", w / 2, h - 95)
g.setFont(new Font("SansSerif", Font.PLAIN, 15))
g.setColor(new Color(244, 231, 255))
centered(g, "CLICK GAME WINDOW FIRST | WASD / ARROWS: MOVE | E / ENTER: INTERACT | P: PAUSE | ESC: MENU", w / 2, h - 59)
centered(g, "Dance challenge: tap SPACE / ENTER / E - 12 beats wins FIRST PRIZE!", w / 2, h - 31)
}
private def drawHUD(g: Graphics2D): Unit = {
val w = getWidth; val h = getHeight
g.setColor(new Color(12, 12, 35, 225)); g.fillRoundRect(15, 12, w - 30, 105, 18, 18)
g.setColor(new Color(255, 196, 75)); g.setStroke(new BasicStroke(1.5f)); g.drawRoundRect(15, 12, w - 30, 105, 18, 18)
g.setFont(new Font("SansSerif", Font.BOLD, 19)); g.setColor(new Color(255, 211, 103)); g.drawString("BIXUU NAVRATRI 3D", 31, 40)
g.setFont(new Font("SansSerif", Font.BOLD, 12)); g.setColor(Color.WHITE); g.drawString(playerName + " | " + businessName, 32, 61)
g.setFont(new Font("SansSerif", Font.BOLD, 14)); g.setColor(new Color(127, 249, 208)); g.drawString("SCORE: " + score, 32, 85)
g.setFont(new Font("SansSerif", Font.BOLD, 13)); g.setColor(new Color(255, 243, 215)); g.drawString(sceneTitle, 335, 39)
g.setFont(new Font("SansSerif", Font.PLAIN, 12)); g.setColor(new Color(224, 218, 245)); g.drawString(objectiveText, 335, 61)
g.setFont(new Font("SansSerif", Font.BOLD, 11)); g.setColor(new Color(255, 206, 105))
g.drawString("WASD / ARROWS MOVE | E / ENTER INTERACT | P PAUSE | ESC MENU", 335, 86)
if (scene == "stadium" && danceStarted) {
g.setColor(new Color(118, 255, 207)); g.drawString("DANDIYA BEATS: " + danceTaps + " / 12 FOR FIRST PRIZE", 335, 104)
} else if (scene == "plane") {
g.setColor(new Color(118, 255, 207)); g.drawString("FLIGHT: " + math.min(100, flightTicks * 100 / 180) + "%", 335, 104)
} else {
val bagStatus = if (giftCollected) "GIFT: READY" else "GIFT: NOT COLLECTED"
g.setColor(new Color(118, 255, 207)); g.drawString(bagStatus, 335, 104)
}
if (toastTicks > 0 && toast != null && toast.nonEmpty) {
val fm = g.getFontMetrics(new Font("SansSerif", Font.BOLD, 13))
g.setFont(new Font("SansSerif", Font.BOLD, 13))
val tw = math.min(w - 40, fm.stringWidth(toast) + 36)
val tx = (w - tw) / 2
g.setColor(new Color(13, 11, 36, 229)); g.fillRoundRect(tx, h - 66, tw, 32, 12, 12)
g.setColor(new Color(255, 205, 83)); g.drawRoundRect(tx, h - 66, tw, 32, 12, 12)
g.setColor(new Color(255, 246, 225)); centered(g, toast, w / 2, h - 45)
}
g.setFont(new Font("SansSerif", Font.BOLD, 11)); g.setColor(new Color(255, 230, 157))
centered(g, "Move toward the glowing ring. Get close, then press E / ENTER.", w / 2, h - 20)
}
private def drawPause(g: Graphics2D): Unit = {
g.setColor(new Color(8, 7, 26, 195)); g.fillRect(0, 0, getWidth, getHeight)
g.setFont(new Font("SansSerif", Font.BOLD, 42)); g.setColor(new Color(255, 214, 102))
centered(g, "PAUSED", getWidth / 2, getHeight / 2 - 8)
g.setFont(new Font("SansSerif", Font.BOLD, 17)); g.setColor(Color.WHITE)
centered(g, "Press P to continue", getWidth / 2, getHeight / 2 + 30)
}
private def drawComplete(g: Graphics2D): Unit = {
val w = getWidth; val h = getHeight
g.setPaint(new GradientPaint(0, 0, new Color(22, 8, 57), 0, h, new Color(119, 25, 93)))
g.fillRect(0, 0, w, h)
var i = 0
while (i < 70) {
val x = (i * 103 + tick * (i % 3 + 1)) % math.max(1, w)
val y = (i * 59 + tick) % math.max(1, h)
g.setColor(if (i % 2 == 0) new Color(255, 211, 76, 190) else new Color(73, 241, 204, 190))
g.fillOval(x, y, 4, 4)
i += 1
}
g.setColor(new Color(13, 9, 36, 220)); g.fillRoundRect(w / 2 - 300, h / 2 - 205, 600, 410, 35, 35)
g.setColor(new Color(255, 203, 77)); g.setStroke(new BasicStroke(3f)); g.drawRoundRect(w / 2 - 300, h / 2 - 205, 600, 410, 35, 35)
g.setFont(new Font("SansSerif", Font.BOLD, 34)); g.setColor(new Color(255, 209, 77))
centered(g, "SHUBH NAVRATRI!", w / 2, h / 2 - 130)
g.setFont(new Font("SansSerif", Font.BOLD, 23)); g.setColor(Color.WHITE)
centered(g, "DANDIYA CELEBRATION COMPLETE", w / 2, h / 2 - 83)
g.setFont(new Font("SansSerif", Font.BOLD, 38)); g.setColor(new Color(119, 255, 208))
centered(g, prize, w / 2, h / 2 - 18)
g.setFont(new Font("SansSerif", Font.BOLD, 22)); g.setColor(new Color(255, 228, 142))
centered(g, "FINAL SCORE: " + score, w / 2, h / 2 + 29)
g.setFont(new Font("SansSerif", Font.PLAIN, 16)); g.setColor(new Color(239, 229, 253))
centered(g, "You travelled to Gujarat, enjoyed Garba and Dandiya, and won your prize!", w / 2, h / 2 + 67)
g.setFont(new Font("SansSerif", Font.BOLD, 16)); g.setColor(new Color(255, 220, 119))
centered(g, "Press ENTER / SPACE to return to menu", w / 2, h / 2 + 125)
}
private def centered(g: Graphics2D, text: String, x: Int, y: Int): Unit = {
val fm = g.getFontMetrics
g.drawString(text, x - fm.stringWidth(text) / 2, y)
}
override def keyPressed(e: KeyEvent): Unit = {
val key = e.getKeyCode
if (handledCodes.contains(key) && !heldKeys.contains(key)) {
heldKeys += key
if (movementCodes.contains(key)) updateMovementFlags()
else handleKeyPress(key)
}
}
override def keyReleased(e: KeyEvent): Unit = {
heldKeys -= e.getKeyCode
updateMovementFlags()
}
override def keyTyped(e: KeyEvent): Unit = {}
override def mouseClicked(e: MouseEvent): Unit = {
if (scene == "menu") startGame()
requestFocusInWindow()
}
override def mousePressed(e: MouseEvent): Unit = {}
override def mouseReleased(e: MouseEvent): Unit = {}
override def mouseEntered(e: MouseEvent): Unit = {}
override def mouseExited(e: MouseEvent): Unit = {}
}
}
/**
* BIXUU DIWALI TRAIN ADVENTURE
* Complete standalone Scala + Java Swing/AWT game for Kojo.
* Controls: WASD / Arrow keys = move; E / ENTER / SPACE = interact;
* 1 = Gokuldham Family; 2 = Normal Family; P = pause; ESC = menu.
* No external libraries or image files required.
*/
object BixuuDiwaliGame {
def main(args: Array[String]): Unit = {
SwingUtilities.invokeLater(new Runnable {
override def run(): Unit = createWindow()
})
}
def launch(): Unit = main(Array.empty[String])
private def createWindow(): Unit = {
val frame = new JFrame("BIXUU | DIWALI FESTIVAL ADVENTURE")
val game = new GamePanel()
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE)
frame.setContentPane(game)
frame.setSize(1100, 720)
frame.setMinimumSize(new Dimension(900, 620))
frame.setLocationRelativeTo(null)
frame.setIconImage(makeAppIcon())
frame.addWindowFocusListener(new WindowAdapter {
override def windowLostFocus(e: WindowEvent): Unit = game.clearHeldKeys()
})
frame.addWindowListener(new WindowAdapter {
override def windowClosed(e: WindowEvent): Unit = game.stopLoop()
})
frame.setVisible(true)
game.startLoop()
game.requestFocusInWindow()
}
private def makeAppIcon(): BufferedImage = {
val img = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB)
val g = img.createGraphics()
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g.setPaint(new GradientPaint(0, 0, new Color(35, 15, 65), 64, 64, new Color(230, 88, 18)))
g.fillRoundRect(0, 0, 64, 64, 16, 16)
g.setColor(new Color(255, 205, 77)); g.fillOval(17, 33, 30, 13)
g.setColor(new Color(255, 242, 170)); g.fillOval(27, 24, 10, 15)
g.setColor(new Color(255, 255, 225)); g.fillOval(30, 27, 4, 10)
g.dispose()
img
}
private class DecoTarget(val x: Double, val y: Double, val label: String, var done: Boolean = false)
private class GamePanel extends JPanel with MouseListener with KeyListener {
private val rng = new Random(17)
private var phase = "menu" // menu, playing, complete
private var family = "Gokuldham Family"
private var familyName = "My Family"
private var playerName = "Bixuu"
private var score = 0
private var tick = 0
private var walkFrame = 0
private var pause = false
private var playerX = 160.0
private var playerY = 470.0
private var moveUp = false
private var moveDown = false
private var moveLeft = false
private var moveRight = false
private val heldKeys = scala.collection.mutable.Set.empty[Int]
private var scene = "letter" // letter, ticket, missed, wait, coach, goaStation, goaHome, diwali
private var ticketChecked = false
private var currentCoach = 1
private var coachCoins = Array(false, false, false)
private var trainArrivalTicks = 0
private var travelTicks = 0
private var ticketBooked = false
private var stage = 0
private var supplies = false
private var pujaDone = false
private var targets = Array.empty[DecoTarget]
private var stairAnimating = false
private var stairTick = 0
private var stairStartX = 0.0
private var stairStartY = 0.0
private var toast = "Welcome! Choose your family to begin."
private var toastTicks = 180
private var menuGokul = new java.awt.Rectangle(0, 0, 0, 0)
private var menuNormal = new java.awt.Rectangle(0, 0, 0, 0)
setFocusable(true)
setFocusTraversalKeysEnabled(false)
setBackground(new Color(16, 12, 35))
addMouseListener(this)
addKeyListener(this)
private val movementKeys: Set[Int] = Set(
KeyEvent.VK_W, KeyEvent.VK_A, KeyEvent.VK_S, KeyEvent.VK_D,
KeyEvent.VK_UP, KeyEvent.VK_DOWN, KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT
)
private val supportedKeys: Set[Int] = movementKeys ++ Set(
KeyEvent.VK_E, KeyEvent.VK_ENTER, KeyEvent.VK_SPACE,
KeyEvent.VK_1, KeyEvent.VK_2, KeyEvent.VK_NUMPAD1, KeyEvent.VK_NUMPAD2,
KeyEvent.VK_P, KeyEvent.VK_ESCAPE
)
// Panel-local keyboard handling keeps controls compatible with Kojo script mode.
def clearHeldKeys(): Unit = {
heldKeys.clear()
updateMovementFlags()
}
private def updateMovementFlags(): Unit = {
moveUp = heldKeys.contains(KeyEvent.VK_W) || heldKeys.contains(KeyEvent.VK_UP)
moveDown = heldKeys.contains(KeyEvent.VK_S) || heldKeys.contains(KeyEvent.VK_DOWN)
moveLeft = heldKeys.contains(KeyEvent.VK_A) || heldKeys.contains(KeyEvent.VK_LEFT)
moveRight = heldKeys.contains(KeyEvent.VK_D) || heldKeys.contains(KeyEvent.VK_RIGHT)
}
private def handleKeyPressed(code: Int): Unit = {
if (code == KeyEvent.VK_E || code == KeyEvent.VK_ENTER || code == KeyEvent.VK_SPACE) {
if (phase == "menu") startGame(family)
else interact()
} else if (code == KeyEvent.VK_1 || code == KeyEvent.VK_NUMPAD1) {
if (phase == "menu") startGame("Gokuldham Family")
} else if (code == KeyEvent.VK_2 || code == KeyEvent.VK_NUMPAD2) {
if (phase == "menu") startGame("Normal Family")
} else if (code == KeyEvent.VK_P) {
if (phase == "playing") {
pause = !pause
if (!pause) showToast("Game resumed.", 80)
}
} else if (code == KeyEvent.VK_ESCAPE) {
phase = "menu"
pause = false
stairAnimating = false
clearHeldKeys()
showToast("Choose a family and start a new Diwali adventure!", 150)
}
repaint()
}
private var loopTimer: Timer = null
def startLoop(): Unit = {
if (loopTimer == null || !loopTimer.isRunning) {
loopTimer = new Timer(30, new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
updateGame()
repaint()
}
})
loopTimer.setCoalesce(true)
loopTimer.start()
}
}
def stopLoop(): Unit = {
if (loopTimer != null) loopTimer.stop()
clearHeldKeys()
}
private def askName(prompt: String, defaultValue: String): String = {
val answer = JOptionPane.showInputDialog(this, prompt, defaultValue)
if (answer == null || answer.trim.isEmpty) defaultValue else answer.trim.take(24)
}
private def startGame(which: String): Unit = {
clearHeldKeys()
family = which
playerName = askName("Choose your character name:", "Bixuu")
familyName = if (family == "Normal Family") askName("Enter your family / house name:", "My Family") else "Gokuldham Society"
phase = "playing"
pause = false
score = 0
stage = 0
supplies = false
pujaDone = false
scene = "letter"
ticketChecked = false
ticketBooked = false
currentCoach = 1
coachCoins = Array(false, false, false)
trainArrivalTicks = 0
travelTicks = 0
stairAnimating = false
targets = Array.empty[DecoTarget]
playerX = math.max(70.0, getWidth * 0.16)
playerY = math.max(365.0, getHeight * 0.66)
showToast("A letter has arrived from Goa! Find it and read your family message.", 220)
requestFocusInWindow()
}
private def beginDiwaliMissions(): Unit = {
scene = "diwali"
stage = 0
supplies = false
pujaDone = false
stairAnimating = false
if (family == "Gokuldham Family") {
playerX = 410.0; playerY = 325.0
targets = Array(
new DecoTarget(505, 480, "Diyas"),
new DecoTarget(570, 500, "Flower Toran"),
new DecoTarget(640, 475, "Fairy Lights"),
new DecoTarget(685, 515, "Rangoli")
)
showToast("Welcome! Walk to the staircase and press E to go down.", 200)
} else {
playerX = 160.0; playerY = 480.0
targets = Array(
new DecoTarget(120, 475, "Diyas"),
new DecoTarget(185, 510, "Flower Toran"),
new DecoTarget(250, 470, "Fairy Lights"),
new DecoTarget(310, 510, "Rangoli")
)
showToast("You reached Goa! Buy decoration supplies, decorate your home, and perform puja.", 220)
}
}
private def showToast(message: String, ticks: Int): Unit = {
toast = message
toastTicks = ticks
}
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 updateGame(): Unit = {
tick += 1
if (toastTicks > 0) toastTicks -= 1
if (phase != "playing" || pause) return
if (scene == "wait" && trainArrivalTicks < 100) trainArrivalTicks += 1
if (scene == "coach" && travelTicks < 360) travelTicks += 1
if (stairAnimating) {
stairTick += 1
val progress = math.min(1.0, stairTick / 34.0)
playerX = stairStartX + (420.0 - stairStartX) * progress
playerY = stairStartY + (405.0 - stairStartY) * progress
walkFrame += 1
if (progress >= 1.0) {
stairAnimating = false
stage = 1
score += 20
showToast("You came down the stairs! Ask the society secretary for permission.", 190)
}
return
}
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) {
val len = math.sqrt(dx * dx + dy * dy)
val speed = if (scene == "coach") 3.6 else 3.35
playerX += dx / len * speed
playerY += dy / len * speed
playerX = math.max(30.0, math.min(getWidth - 30.0, playerX))
playerY = math.max(290.0, math.min(getHeight - 95.0, playerY))
walkFrame += 1
}
}
private def interact(): Unit = {
if (phase == "menu") { startGame(family); return }
if (phase == "complete") {
phase = "menu"
showToast("Choose a family and start a new adventure!", 150)
return
}
if (pause) { pause = false; showToast("Game resumed.", 80); return }
if (stairAnimating) return
if (scene != "diwali") interactJourney() else interactDiwali()
}
private def interactJourney(): Unit = {
val w = getWidth.toDouble; val h = getHeight.toDouble
scene match {
case "letter" =>
if (distance(playerX, playerY, w * 0.70, h * 0.63) < 112) {
scene = "ticket"; playerX = w * 0.12; playerY = h * 0.66
score += 10
showToast("Letter: Come to Goa for Diwali! Your grandparents are waiting. Go to the station ticket counter.", 230)
} else showToast("Move to the glowing letter on the table and press E / ENTER.", 120)
case "ticket" =>
if (distance(playerX, playerY, w * 0.78, h * 0.60) < 125) {
ticketBooked = true; scene = "missed"; playerX = w * 0.12; playerY = h * 0.66
score += 20
showToast("Ticket booked! Oh no, the train has left. Find the TTE on the platform.", 210)
} else showToast("Walk to the ticket counter and press E / ENTER to book your ticket.", 120)
case "missed" =>
if (distance(playerX, playerY, w * 0.70, h * 0.62) < 120) {
scene = "wait"; trainArrivalTicks = 0; playerX = w * 0.14; playerY = h * 0.66
score += 15
showToast("TTE: The next train is coming soon. Wait near the marked platform door.", 200)
} else showToast("Talk to the TTE (Train Ticket Examiner) to ask about the next train.", 125)
case "wait" =>
if (trainArrivalTicks < 100) showToast("The next train is on its way. Please wait a little.", 100)
else if (distance(playerX, playerY, w * 0.53, h * 0.62) < 150) {
scene = "coach"; currentCoach = 1; ticketChecked = false; travelTicks = 0
playerX = w * 0.12; playerY = h * 0.64
showToast("You boarded the next train! Show your ticket to the TTE in Coach 1.", 200)
} else showToast("The train has arrived! Walk to the glowing open door and press E.", 130)
case "coach" =>
val coinX = w * 0.44; val coinY = h * 0.57
if (!coachCoins(currentCoach - 1) && distance(playerX, playerY, coinX, coinY) < 70) {
coachCoins(currentCoach - 1) = true; score += 25
showToast("Coin collected! " + coachCoins.count(identity) + "/3 collected.", 130)
} else if (currentCoach == 1 && !ticketChecked && distance(playerX, playerY, w * 0.70, h * 0.59) < 110) {
ticketChecked = true; score += 35
showToast("TTE: Ticket verified! Collect your coins and move through all three coaches.", 190)
} else if (distance(playerX, playerY, w * 0.88, h * 0.64) < 105) {
if (!ticketChecked) showToast("Show your ticket to the TTE in Coach 1 first.", 130)
else if (currentCoach < 3) {
currentCoach += 1; playerX = w * 0.12; playerY = h * 0.64
showToast("You entered Coach " + currentCoach + ". Find the coin and move to the right-side door.", 170)
} else if (travelTicks < 220) {
showToast("The train is waiting at a signal. Journey progress: " + math.min(100, travelTicks * 100 / 220) + "%.", 115)
} else {
scene = "goaStation"; playerX = w * 0.12; playerY = h * 0.66; score += 50
showToast("The train has arrived in Goa! Walk to the station exit.", 200)
}
} else {
val hint = if (currentCoach == 1 && !ticketChecked) "Find the TTE in Coach 1 and show your ticket." else "Collect the glowing coin or walk to the door on the right."
showToast(hint, 110)
}
case "goaStation" =>
if (distance(playerX, playerY, w * 0.84, h * 0.64) < 130) {
scene = "goaHome"; playerX = w * 0.13; playerY = h * 0.66; score += 20
showToast("You are outside Goa station. Find your grandparents' house.", 200)
} else showToast("Walk to the glowing station exit on the right and press E.", 120)
case "goaHome" =>
if (distance(playerX, playerY, w * 0.74, h * 0.60) < 130) {
score += 50
showToast("Aaji-Ajoba: Welcome home! Happy Diwali! Let us prepare for the celebration.", 170)
beginDiwaliMissions()
} else showToast("Walk to Aaji-Ajoba near the front door and press E.", 120)
case _ => showToast("Follow the glowing marker, then press E / ENTER.", 100)
}
}
private def interactDiwali(): Unit = {
if (family == "Gokuldham Family") {
stage match {
case 0 =>
if (distance(playerX, playerY, 420, 355) < 105) {
stairAnimating = true; stairTick = 0; stairStartX = playerX; stairStartY = playerY
showToast("Walking down the staircase...", 70)
} else showToast("Walk close to the staircase in the middle building and press E.", 110)
case 1 =>
if (distance(playerX, playerY, 535, 430) < 100) {
stage = 2; score += 30
showToast("Secretary: Permission granted! Visit Abdul's shop for supplies.", 170)
} else showToast("Go near the society secretary and press E.", 110)
case 2 =>
if (distance(playerX, playerY, getWidth - 175, 420) < 115) buySupplies()
else showToast("Walk to Abdul's shop on the right and press E.", 110)
case 3 => decorateAtNearestTarget()
case 4 =>
if (distance(playerX, playerY, 535, 535) < 105) finishPuja()
else showToast("Go to the community puja area and press E.", 110)
case _ => phase = "complete"
}
} else {
stage match {
case 0 =>
if (distance(playerX, playerY, getWidth - 175, 420) < 115) buySupplies()
else showToast("Go to Abdul's shop on the right to buy supplies.", 110)
case 1 => decorateAtNearestTarget()
case 2 =>
if (distance(playerX, playerY, 225, 535) < 105) finishPuja()
else showToast("Return to your home puja area and press E.", 110)
case _ => phase = "complete"
}
}
}
private def buySupplies(): Unit = {
if (!supplies) {
supplies = true; score += 50
stage = if (family == "Gokuldham Family") 3 else 1
showToast("Abdul: Decoration supplies collected! Go decorate the glowing markers.", 180)
} else showToast("You already have all the decoration supplies. Happy Diwali!", 120)
}
private def decorateAtNearestTarget(): Unit = {
if (!supplies) { showToast("First collect decoration supplies from Abdul's shop!", 120); return }
var nearest: DecoTarget = null; var nearestD = 99999.0
targets.foreach { t =>
if (!t.done) {
val d = distance(playerX, playerY, t.x, t.y)
if (d < nearestD) { nearestD = d; nearest = t }
}
}
if (nearest != null && nearestD < 88) {
nearest.done = true; score += 25
val remaining = targets.count(t => !t.done)
if (remaining == 0) {
stage = if (family == "Gokuldham Family") 4 else 2
showToast("Fantastic! All four decorations are done. Go to the puja area.", 180)
} else showToast(nearest.label + " decorated! " + (4 - remaining) + "/4 complete.", 135)
} else {
val next = targets.find(t => !t.done)
if (next.isDefined) showToast("Move near the glowing " + next.get.label + " marker and press E.", 110)
else showToast("All decorations complete. Go to the puja area.", 100)
}
}
private def finishPuja(): Unit = {
if (!supplies) showToast("Collect the supplies before starting puja.", 110)
else if (targets.exists(t => !t.done)) showToast("Finish all four decoration tasks first.", 110)
else {
pujaDone = true; score += 100; phase = "complete"
showToast("Shubh Deepawali! Mission completed!", 250)
}
}
private def missionText: String = {
if (scene != "diwali") scene match {
case "letter" => "MISSION 1/7: Find the letter from Goa."
case "ticket" => "MISSION 2/7: Book your railway ticket."
case "missed" => "MISSION 3/7: Talk to the TTE."
case "wait" => "MISSION 4/7: Wait for and board the next train."
case "coach" => "MISSION 5/7: Verify ticket, collect coins, cross coaches."
case "goaStation" => "MISSION 6/7: Exit Goa station."
case "goaHome" => "MISSION 7/7: Greet Aaji-Ajoba."
case _ => "Continue your adventure."
} else if (family == "Gokuldham Family") stage match {
case 0 => "DIWALI 1/5: Go down the society staircase."
case 1 => "DIWALI 2/5: Ask the society secretary."
case 2 => "DIWALI 3/5: Collect supplies from Abdul."
case 3 => "DIWALI 4/5: Decorate all markers (" + targets.count(_.done) + "/4)."
case 4 => "DIWALI 5/5: Go to community puja."
case _ => "All missions completed!"
} else stage match {
case 0 => "DIWALI 1/3: Buy supplies from Abdul."
case 1 => "DIWALI 2/3: Decorate your home (" + targets.count(_.done) + "/4)."
case 2 => "DIWALI 3/3: Go to home puja."
case _ => "All missions completed!"
}
}
override def paintComponent(raw: Graphics): Unit = {
super.paintComponent(raw)
val g = raw.asInstanceOf[Graphics2D]
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON)
if (phase == "menu") drawMenu(g)
else {
if (scene == "diwali") drawDiwaliWorld(g) else drawJourneyScene(g)
drawHUD(g)
if (phase == "complete") drawComplete(g)
if (pause) drawPause(g)
}
}
private def font(size: Int, style: Int = Font.PLAIN): Font = new Font("SansSerif", style, size)
private def centered(g: Graphics2D, text: String, x: Int, y: Int): Unit = g.drawString(text, x - g.getFontMetrics.stringWidth(text) / 2, y)
private def drawMenu(g: Graphics2D): Unit = {
val w = getWidth; val h = getHeight
g.setPaint(new GradientPaint(0, 0, new Color(17, 10, 45), 0, h, new Color(84, 27, 62))); g.fillRect(0, 0, w, h)
drawStars(g, 0, 0, w, h, 90)
g.setColor(new Color(255, 224, 146, 35)); g.fillOval(w - 180, 35, 120, 120)
g.setColor(new Color(255, 235, 176)); g.fillOval(w - 153, 62, 66, 66)
g.setFont(font(40, Font.BOLD)); g.setColor(new Color(255, 200, 67)); centered(g, "BIXUU DIWALI TRAIN ADVENTURE", w / 2, 103)
g.setFont(font(17, Font.BOLD)); g.setColor(new Color(255, 239, 206)); centered(g, "LETTER > TICKET > TRAIN > GOA > DIWALI", w / 2, 142)
g.setFont(font(14, Font.PLAIN)); g.setColor(new Color(227, 218, 246)); centered(g, "Explore three train coaches, visit your grandparents, and celebrate Diwali", w / 2, 177)
val cardW = math.min(360, (w - 90) / 2); val cardH = 235; val gap = 28
val left = (w - (cardW * 2 + gap)) / 2; val top = 215
menuGokul = new java.awt.Rectangle(left, top, cardW, cardH)
menuNormal = new java.awt.Rectangle(left + cardW + gap, top, cardW, cardH)
drawFamilyCard(g, menuGokul, "GOKULDHAM FAMILY", "Train + Society missions", "Goa trip > stairs > permission", "Shop > decorate > community puja", true)
drawFamilyCard(g, menuNormal, "NORMAL FAMILY", "Train + Home missions", "Goa trip > grandparents' home", "Buy supplies > decorate > puja", false)
g.setFont(font(14, Font.BOLD)); g.setColor(new Color(255, 220, 124)); centered(g, "CLICK A CARD OR PRESS 1 / 2", w / 2, top + cardH + 35)
g.setFont(font(13, Font.PLAIN)); g.setColor(new Color(221, 211, 238)); centered(g, "WASD / ARROWS = MOVE E / ENTER / SPACE = INTERACT", w / 2, h - 27)
}
private def drawFamilyCard(g: Graphics2D, r: java.awt.Rectangle, heading: String, sub: String, line1: String, line2: String, society: Boolean): Unit = {
g.setPaint(new GradientPaint(r.x, r.y, new Color(57, 39, 84), r.x + r.width, r.y + r.height, new Color(29, 23, 59)))
g.fill(new RoundRectangle2D.Double(r.x, r.y, r.width, r.height, 28, 28))
g.setStroke(new BasicStroke(2f)); g.setColor(if (society) new Color(255, 185, 64) else new Color(71, 219, 193))
g.draw(new RoundRectangle2D.Double(r.x, r.y, r.width, r.height, 28, 28))
if (society) drawMiniBuilding(g, r.x + r.width / 2 - 48, r.y + 20, 96, 72) else drawMiniHouse(g, r.x + r.width / 2 - 46, r.y + 26, 92, 66)
g.setFont(font(20, Font.BOLD)); g.setColor(new Color(255, 219, 120)); centered(g, heading, r.x + r.width / 2, r.y + 121)
g.setFont(font(15, Font.BOLD)); g.setColor(Color.WHITE); centered(g, sub, r.x + r.width / 2, r.y + 150)
g.setFont(font(12, Font.PLAIN)); g.setColor(new Color(226, 219, 242)); centered(g, line1, r.x + r.width / 2, r.y + 176); centered(g, line2, r.x + r.width / 2, r.y + 195)
g.setFont(font(12, Font.BOLD)); g.setColor(if (society) new Color(255, 198, 86) else new Color(106, 237, 210))
centered(g, if (society) "CLICK TO PLAY (1)" else "CLICK TO PLAY (2)", r.x + r.width / 2, r.y + 219)
}
private def drawJourneyScene(g: Graphics2D): Unit = {
val w = getWidth; val h = getHeight
g.setPaint(new GradientPaint(0, 0, new Color(16, 20, 59), 0, h, new Color(82, 37, 74))); g.fillRect(0, 0, w, h)
drawStars(g, 0, 0, w, 255, 55)
g.setColor(new Color(255, 221, 155, 35)); g.fillOval(w - 175, 28, 105, 105)
g.setColor(new Color(255, 231, 171)); g.fillOval(w - 149, 54, 55, 55)
g.setPaint(new GradientPaint(0, 260, new Color(100, 92, 125), 0, h, new Color(35, 38, 61))); g.fillRect(0, 260, w, h - 260)
g.setColor(new Color(255, 255, 255, 15))
var gx = -40; while (gx < w + 100) { g.drawLine(gx, 330, gx + 125, h); gx += 100 }
var gy = 350; while (gy < h) { g.drawLine(0, gy, w, gy); gy += 46 }
scene match {
case "letter" =>
drawMiniHouse(g, 70, 300, 280, 190)
g.setColor(new Color(86, 48, 52)); g.fillRoundRect((w * 0.58).toInt, 400, 180, 95, 12, 12)
g.setColor(new Color(188, 111, 72)); g.fillRect((w * 0.57).toInt, 390, 190, 16)
g.setColor(new Color(255, 232, 185)); g.fillRoundRect((w * 0.66).toInt, 370, 66, 52, 6, 6)
g.setColor(new Color(174, 48, 52)); g.fillRoundRect((w * 0.67).toInt, 379, 48, 32, 4, 4)
drawMarker(g, (w * 0.70).toInt, (h * 0.63).toInt - 28, new Color(255, 214, 86))
drawPerson(g, w * 0.43, h * 0.62, new Color(77, 159, 184), new Color(242, 190, 143), tick)
g.setFont(font(16, Font.BOLD)); g.setColor(new Color(255, 231, 166)); centered(g, "LETTER FROM GOA", (w * 0.70).toInt, 355)
g.setFont(font(13)); g.setColor(Color.WHITE); centered(g, "Your grandparents invited you for Diwali", (w * 0.52).toInt, 545)
case "ticket" =>
drawStation(g, w, h, "CITY RAILWAY STATION"); drawRailTracks(g, w, h)
val cx = (w * 0.68).toInt; val cy = 345
g.setColor(new Color(47, 42, 72)); g.fillRoundRect(cx - 15, cy, 265, 126, 14, 14)
g.setColor(new Color(243, 167, 77)); g.fillRoundRect(cx - 15, cy - 18, 265, 35, 10, 10)
g.setFont(font(18, Font.BOLD)); g.setColor(new Color(62, 36, 55)); centered(g, "TICKET COUNTER", cx + 117, cy + 5)
drawPerson(g, w * 0.78, h * 0.60, new Color(64, 137, 190), new Color(245, 190, 140), tick)
case "missed" =>
drawStation(g, w, h, "PLATFORM 1 - DEPARTED"); drawRailTracks(g, w, h)
val trainX = w - ((tick * 7) % (w + 600)) - 480; drawPassengerTrain(g, trainX.toInt, 365, 146, 88)
drawPerson(g, w * 0.70, h * 0.62, new Color(43, 111, 170), new Color(244, 194, 145), tick)
g.setFont(font(16, Font.BOLD)); g.setColor(new Color(255, 206, 108)); centered(g, "OH NO! THE TRAIN HAS LEFT", w / 2, 305)
case "wait" =>
drawStation(g, w, h, "PLATFORM 2 - NEXT TRAIN"); drawRailTracks(g, w, h)
val p = math.min(1.0, trainArrivalTicks / 100.0); val tx = ((w + 80) + (w * 0.30 - (w + 80)) * p).toInt
drawPassengerTrain(g, tx, 365, 146, 88)
g.setFont(font(14, Font.BOLD)); g.setColor(new Color(255, 220, 130)); centered(g, if (trainArrivalTicks < 100) "NEXT TRAIN ARRIVING... " + trainArrivalTicks + "%" else "TRAIN ARRIVED - BOARD THE OPEN DOOR", w / 2, 300)
if (trainArrivalTicks >= 100) drawMarker(g, (w * 0.53).toInt, (h * 0.62).toInt - 35, new Color(100, 248, 193))
case "coach" =>
g.setPaint(new GradientPaint(0, 260, new Color(36, 47, 82), 0, h, new Color(23, 29, 50))); g.fillRect(0, 260, w, h - 260)
var i = 0
while (i < 5) {
val wx = 28 + i * ((w - 55) / 5)
g.setColor(new Color(15, 30, 58)); g.fillRoundRect(wx, 300, 138, 82, 10, 10)
g.setPaint(new GradientPaint(wx, 302, new Color(113, 197, 212), wx, 380, new Color(77, 145, 109))); g.fillRoundRect(wx + 5, 305, 128, 72, 8, 8)
val slide = (tick * 2 + i * 41) % 150; g.setColor(new Color(41, 119, 75)); g.fillOval(wx + slide - 15, 335, 35, 45); g.fillRect(wx + slide, 325, 7, 54); i += 1
}
g.setColor(new Color(255, 212, 124)); g.fillRect(0, 393, w, 7)
g.setPaint(new GradientPaint(0, 400, new Color(67, 77, 104), 0, h, new Color(36, 40, 62))); g.fillRect(0, 400, w, h - 400)
var sx = 25
while (sx < w - 80) { g.setColor(new Color(21, 29, 57)); g.fillRoundRect(sx, 420, 75, 92, 12, 12); g.setColor(new Color(178, 63, 79)); g.fillRoundRect(sx + 5, 425, 65, 42, 9, 9); sx += 145 }
g.setColor(new Color(18, 24, 42)); g.fillRoundRect(w - 105, 390, 72, 180, 9, 9); g.setColor(new Color(255, 194, 87)); g.drawRoundRect(w - 105, 390, 72, 180, 9, 9)
g.setFont(font(15, Font.BOLD)); g.setColor(new Color(255, 223, 143)); centered(g, "COACH " + currentCoach, w / 2, 285)
if (currentCoach == 1 && !ticketChecked) { drawPerson(g, w * 0.70, h * 0.59, new Color(43, 111, 170), new Color(244, 194, 145), tick); drawMarker(g, (w * 0.70).toInt, (h * 0.59).toInt - 35, new Color(98, 242, 192)) }
if (!coachCoins(currentCoach - 1)) { val cx = (w * 0.44).toInt; val cy = (h * 0.57).toInt; g.setColor(new Color(255, 205, 55, 60)); g.fillOval(cx - 20, cy - 20, 40, 40); g.setColor(new Color(255, 198, 42)); g.fillOval(cx - 10, cy - 10, 20, 20); g.setColor(new Color(255, 244, 163)); g.drawOval(cx - 6, cy - 6, 12, 12) }
g.setColor(new Color(255, 218, 115)); g.setFont(font(12, Font.BOLD)); g.drawString("COACH " + currentCoach + " / 3", 25, h - 98); g.drawString("Journey: " + math.min(100, travelTicks * 100 / 220) + "%", 25, h - 78)
case "goaStation" =>
drawStation(g, w, h, "GOA - MADGAON STATION"); drawRailTracks(g, w, h); drawPassengerTrain(g, 40, 365, 146, 88)
drawPalm(g, w - 210, 285); drawPalm(g, w - 105, 305); drawMarker(g, (w * 0.84).toInt, (h * 0.64).toInt - 30, new Color(100, 248, 193))
g.setFont(font(14, Font.BOLD)); g.setColor(new Color(255, 225, 144)); centered(g, "EXIT TO GOA", (w * 0.84).toInt, 340)
case "goaHome" =>
g.setColor(new Color(30, 80, 71)); g.fillRect(0, 260, w, h - 260); g.setColor(new Color(63, 117, 82)); g.fillOval(35, 280, 200, 125); g.fillOval(w - 260, 275, 225, 140)
drawPalm(g, 70, 310); drawPalm(g, w - 95, 315); drawMiniHouse(g, (w * 0.32).toInt, 318, 300, 210)
drawDiyas(g, (w * 0.32).toInt + 60, 505, 0.65); drawRangoli(g, (w * 0.32).toInt + 250, 545)
drawPerson(g, w * 0.70, h * 0.60, new Color(155, 88, 160), new Color(232, 179, 133), tick); drawPerson(g, w * 0.78, h * 0.60, new Color(65, 143, 104), new Color(236, 188, 142), tick + 4)
g.setFont(font(12, Font.BOLD)); g.setColor(new Color(255, 241, 199)); centered(g, "AAJI", (w * 0.70).toInt, (h * 0.60).toInt + 35); centered(g, "AJOBA", (w * 0.78).toInt, (h * 0.60).toInt + 35)
drawMarker(g, (w * 0.74).toInt, (h * 0.60).toInt - 38, new Color(255, 215, 84)); g.setFont(font(15, Font.BOLD)); centered(g, "AAJI-AJOBA'S HOUSE - GOA", w / 2, 285)
case _ =>
}
drawPlayer(g)
drawAmbient(g)
}
private def drawDiwaliWorld(g: Graphics2D): Unit = {
val w = getWidth; val h = getHeight
g.setPaint(new GradientPaint(0, 0, new Color(20, 17, 57), 0, 245, new Color(91, 40, 81))); g.fillRect(0, 0, w, h)
drawStars(g, 0, 0, w, 210, 55)
g.setColor(new Color(255, 223, 143, 32)); g.fillOval(w - 170, 25, 108, 108); g.setColor(new Color(255, 229, 164)); g.fillOval(w - 146, 49, 60, 60)
drawSkyline(g, w)
g.setPaint(new GradientPaint(0, 205, new Color(88, 76, 111), 0, h, new Color(39, 42, 64))); g.fillRect(0, 205, w, h - 205)
g.setColor(new Color(255, 194, 68, 120)); g.fillRoundRect(0, h - 70, w, 70, 14, 14); g.setColor(new Color(58, 42, 66)); g.fillRect(0, h - 65, w, 65)
drawRoadDashes(g, w, h)
drawNormalHouse(g); drawSociety(g); drawShop(g); drawSecretary(g); drawPujaArea(g); drawDecorationTargets(g); drawPlayer(g); drawAmbient(g)
}
private def drawHUD(g: Graphics2D): Unit = {
val w = getWidth; val h = getHeight
g.setColor(new Color(12, 12, 31, 225)); g.fillRoundRect(14, 12, w - 28, 94, 18, 18)
g.setColor(new Color(255, 199, 79)); g.setStroke(new BasicStroke(1.5f)); g.drawRoundRect(14, 12, w - 28, 94, 18, 18)
g.setFont(font(18, Font.BOLD)); g.setColor(new Color(255, 212, 108)); g.drawString(if (scene == "diwali") "BIXUU DIWALI ADVENTURE" else "BIXUU TRAIN ADVENTURE", 30, 38)
g.setFont(font(11, Font.BOLD)); g.setColor(Color.WHITE); g.drawString(playerName + " | " + (if (family == "Normal Family") familyName else family), 31, 57)
g.setFont(font(13, Font.BOLD)); g.setColor(new Color(131, 245, 205)); g.drawString("SCORE " + score + " COINS " + coachCoins.count(identity) + "/3", 31, 81)
g.setFont(font(13, Font.BOLD)); g.setColor(new Color(255, 243, 219)); g.drawString(missionText, 285, 39)
g.setFont(font(11, Font.PLAIN)); g.setColor(new Color(222, 218, 240)); g.drawString("WASD / ARROWS: MOVE E / ENTER / SPACE: INTERACT P: PAUSE ESC: MENU", 285, 61)
if (scene == "coach") { g.setColor(new Color(255, 211, 107)); g.drawString("Coach " + currentCoach + "/3 | Ticket " + (if (ticketChecked) "verified" else "not checked") + " | Journey " + math.min(100, travelTicks * 100 / 220) + "%", 285, 82) }
else if (scene == "wait") { g.setColor(new Color(255, 211, 107)); g.drawString("Train arrival: " + trainArrivalTicks + "%", 285, 82) }
else { g.setColor(new Color(255, 211, 107)); g.drawString(if (scene == "diwali") "Supplies: " + (if (supplies) "COLLECTED" else "NEEDED") + " Decorations: " + targets.count(_.done) + "/4" else "Goa family trip - Diwali celebration", 285, 82) }
if (toastTicks > 0 && toast != null && toast.nonEmpty) {
g.setFont(font(13, Font.BOLD)); val tw = math.min(w - 38, g.getFontMetrics.stringWidth(toast) + 34)
g.setColor(new Color(15, 12, 34, 232)); g.fillRoundRect((w - tw) / 2, h - 68, tw, 32, 12, 12)
g.setColor(new Color(255, 207, 93)); g.drawRoundRect((w - tw) / 2, h - 68, tw, 32, 12, 12)
g.setColor(new Color(255, 245, 222)); centered(g, toast, w / 2, h - 47)
}
g.setFont(font(11, Font.BOLD)); g.setColor(new Color(255, 230, 156)); centered(g, "Move close to the glowing marker, then press E / ENTER", w / 2, h - 25)
}
private def drawPlayer(g: Graphics2D): Unit = {
val moving = moveLeft || moveRight || moveUp || moveDown
val bob = if (moving) math.sin(walkFrame * 0.65) * 3.0 else math.sin(tick * 0.06) * 1.2
val x = playerX.toInt; val y = (playerY + bob).toInt
g.setColor(new Color(0, 0, 0, 75)); g.fillOval(x - 18, y + 27, 37, 13)
g.setStroke(new BasicStroke(6f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND))
val swing = if (moving) (math.sin(walkFrame * 0.65) * 4).toInt else 0
g.setColor(new Color(34, 44, 86)); g.drawLine(x - 5, y + 12, x - 7 + swing, y + 28); g.drawLine(x + 5, y + 12, x + 7 - swing, y + 28)
g.setColor(new Color(255, 198, 93)); g.fillRoundRect(x - 14, y - 2, 28, 24, 9, 9)
g.setColor(new Color(51, 115, 208)); g.fillRoundRect(x - 13, y - 2, 26, 18, 8, 8)
g.setColor(new Color(249, 191, 143)); g.fillOval(x - 10, y - 23, 20, 22)
g.setColor(new Color(56, 34, 30)); g.fillArc(x - 11, y - 26, 22, 16, 0, 180)
g.setColor(new Color(33, 25, 40)); g.fillOval(x - 5, y - 14, 2, 3); g.fillOval(x + 4, y - 14, 2, 3)
g.setColor(new Color(126, 55, 43)); g.drawArc(x - 4, y - 10, 8, 5, 200, 140)
g.setColor(new Color(247, 207, 105)); g.setFont(font(10, Font.BOLD)); centered(g, playerName.take(12).toUpperCase, x, y - 33)
}
private def drawDecorationTargets(g: Graphics2D): Unit = {
targets.foreach { t =>
if (t.done) {
t.label match {
case "Diyas" => drawDiyas(g, t.x.toInt - 21, t.y.toInt - 5, 0.52)
case "Flower Toran" => drawToran(g, t.x.toInt - 40, t.y.toInt - 14, 80, 20, true)
case "Fairy Lights" =>
var i = 0; while (i < 6) { val lx = t.x.toInt - 28 + i * 11; g.setColor(new Color(255, 228, 111)); g.fillOval(lx, t.y.toInt - 9 + (i % 2) * 4, 7, 7); i += 1 }
case "Rangoli" => drawRangoli(g, t.x.toInt, t.y.toInt)
case _ =>
}
g.setColor(new Color(96, 245, 174)); g.setFont(font(10, Font.BOLD)); centered(g, "DONE", t.x.toInt, t.y.toInt + 24)
} else if (phase == "playing" && ((family == "Gokuldham Family" && stage == 3) || (family == "Normal Family" && stage == 1))) {
drawMarker(g, t.x.toInt, t.y.toInt - 20 + (math.sin(tick * 0.12) * 4).toInt, new Color(82, 237, 201))
g.setColor(new Color(236, 255, 248)); g.setFont(font(10, Font.BOLD)); centered(g, t.label.toUpperCase, t.x.toInt, t.y.toInt + 20)
}
}
}
private def drawSecretary(g: Graphics2D): Unit = {
drawPerson(g, 535, 430, new Color(68, 137, 196), new Color(247, 194, 147), tick)
g.setFont(font(11, Font.BOLD)); g.setColor(new Color(250, 245, 220)); centered(g, "SECRETARY", 535, 459)
if (family == "Gokuldham Family" && stage == 1) drawMarker(g, 535, 383, new Color(93, 233, 180))
}
private def drawPujaArea(g: Graphics2D): Unit = {
val px = if (family == "Gokuldham Family") 535 else 225; val py = 535
g.setColor(new Color(20, 14, 30, 80)); g.fillOval(px - 48, py + 1, 96, 18)
g.setColor(new Color(145, 67, 83)); g.fillRoundRect(px - 38, py - 15, 76, 27, 5, 5)
g.setColor(new Color(255, 188, 70)); g.fillRect(px - 41, py - 20, 82, 8)
g.setColor(new Color(255, 223, 139)); g.fillOval(px - 22, py - 45, 44, 32)
g.setColor(new Color(185, 72, 57)); g.fillOval(px - 13, py - 38, 26, 24); g.setColor(new Color(255, 221, 117)); g.fillOval(px - 5, py - 31, 10, 10)
drawMiniDiyas(g, px - 27, py - 4); drawMiniDiyas(g, px + 7, py - 4)
g.setFont(font(11, Font.BOLD)); g.setColor(new Color(255, 242, 201)); centered(g, if (family == "Gokuldham Family") "COMMUNITY PUJA" else "HOME PUJA", px, py + 31)
if ((family == "Gokuldham Family" && stage == 4) || (family == "Normal Family" && stage == 2)) drawMarker(g, px, py - 65, new Color(255, 210, 77))
if (pujaDone) centered(g, "SHUBH DEEPAWALI!", px, py - 76)
}
private def drawShop(g: Graphics2D): Unit = {
val x = getWidth - 270; val y = 280; val w = 225; val h = 159
g.setColor(new Color(10, 7, 23, 90)); g.fillOval(x - 8, y + h - 1, w + 22, 23)
g.setPaint(new GradientPaint(x, y, new Color(94, 47, 85), x + w, y + h, new Color(49, 31, 65))); g.fillRect(x, y, w, h)
val roof = new Polygon(Array(x - 10, x + w / 2, x + w + 10), Array(y + 8, y - 34, y + 8), 3)
g.setColor(new Color(240, 137, 56)); g.fillPolygon(roof); g.setColor(new Color(255, 206, 112)); g.fillRect(x + 12, y + 13, w - 24, 38)
g.setFont(font(20, Font.BOLD)); g.setColor(new Color(75, 33, 58)); centered(g, "ABDUL'S SHOP", x + w / 2, y + 39)
g.setColor(new Color(41, 28, 53)); g.fillRoundRect(x + 18, y + 68, 91, 79, 6, 6); drawMiniDiyas(g, x + 29, y + 93)
g.setColor(new Color(244, 80, 115)); g.fillOval(x + 64, y + 105, 13, 13); g.setColor(new Color(89, 224, 175)); g.fillOval(x + 79, y + 105, 13, 13)
g.setColor(new Color(255, 226, 147)); g.fillRoundRect(x + 131, y + 91, 68, 52, 6, 6); g.setColor(new Color(64, 47, 76)); g.fillRect(x + 137, y + 97, 56, 39)
g.setColor(new Color(255, 219, 121)); g.setFont(font(12, Font.BOLD)); centered(g, "Lights", x + 165, y + 116)
if (supplies) { g.setColor(new Color(46, 220, 152)); g.fillOval(x + w - 31, y + 8, 20, 20); g.setColor(new Color(13, 65, 48)); centered(g, "?", x + w - 21, y + 23) }
}
private def drawSociety(g: Graphics2D): Unit = {
val x = 347; val y = 205; val w = 365; val h = 249
g.setColor(new Color(10, 7, 22, 100)); g.fillOval(x - 10, y + h - 2, w + 35, 32)
g.setPaint(new GradientPaint(x, y, new Color(89, 95, 151), x + w, y + h, new Color(53, 48, 94))); g.fillRect(x, y, w, h)
g.setColor(new Color(255, 198, 109)); g.fillRect(x, y, w, 10)
var row = 0
while (row < 3) { var col = 0; while (col < 4) { val wx = x + 22 + col * 83; val wy = y + 25 + row * 62
g.setColor(new Color(28, 27, 61)); g.fillRoundRect(wx, wy, 58, 43, 5, 5); g.setColor(new Color(255, 221, 135, 235)); g.fillRect(wx + 7, wy + 7, 44, 26)
g.setColor(new Color(48, 54, 91)); g.fillRect(wx + 7, wy + 7, 44, 5); g.setColor(new Color(30, 25, 53)); g.drawLine(wx + 29, wy + 7, wx + 29, wy + 33); g.drawLine(wx + 4, wy + 35, wx + 54, wy + 35); col += 1 }; row += 1 }
g.setColor(new Color(35, 31, 60)); g.fillRoundRect(x + 150, y + 171, 66, 78, 8, 8); g.setColor(new Color(255, 197, 90)); g.fillOval(x + 202, y + 211, 7, 7)
g.setColor(new Color(191, 190, 218)); var i = 0; while (i < 7) { g.fillRect(x + 43 + i * 7, y + 127 + i * 13, 66 - i * 7, 11); i += 1 }
g.setColor(new Color(255, 232, 178)); g.setFont(font(15, Font.BOLD)); centered(g, "GOKULDHAM SOCIETY", x + w / 2, y - 8)
drawToran(g, x + 14, y + 8, w - 28, 16, targets.exists(_.done))
}
private def drawNormalHouse(g: Graphics2D): Unit = {
val x = 24; val y = 255; val w = 300; val h = 190
g.setColor(new Color(15, 12, 27, 90)); g.fillOval(x - 2, y + h - 3, w + 35, 29)
g.setPaint(new GradientPaint(x, y, new Color(255, 180, 109), x + w, y + h, new Color(142, 69, 89))); g.fillRect(x, y + 40, w, h - 40)
g.setColor(new Color(95, 44, 73)); g.fillPolygon(new Polygon(Array(x - 18, x + w / 2, x + w + 18), Array(y + 48, y - 28, y + 48), 3))
g.setColor(new Color(255, 218, 149)); g.fillRect(x + 15, y + 56, w - 30, 8)
g.setColor(new Color(89, 45, 51)); g.fillRoundRect(x + 128, y + 95, 53, 95, 7, 7); g.setColor(new Color(255, 202, 95)); g.fillOval(x + 167, y + 142, 6, 6)
drawWindow(g, x + 35, y + 87, 52, 47); drawWindow(g, x + 215, y + 87, 52, 47)
g.setFont(font(14, Font.BOLD)); g.setColor(new Color(255, 246, 208)); centered(g, if (family == "Normal Family") familyName.take(16).toUpperCase else "NEIGHBOUR'S HOME", x + w / 2, y + 22)
drawToran(g, x + 106, y + 84, 106, 20, targets.exists(_.done))
if (family == "Normal Family" && targets.exists(t => t.label == "Diyas" && t.done)) { drawDiyas(g, x + 35, y + h - 6, 0.75); drawDiyas(g, x + 228, y + h - 6, 0.75) }
}
private def drawSkyline(g: Graphics2D, w: Int): Unit = {
val buildings = Array((0, 145, 78, 120), (80, 125, 90, 140), (180, 150, 65, 112), (250, 130, 90, 132), (700, 135, 86, 128), (800, 155, 66, 108), (875, 120, 100, 143), (970, 148, 80, 116))
buildings.foreach { b => val (x, y, bw, bh) = b; g.setColor(new Color(30, 25, 56)); g.fillRect(x, y, bw, bh); g.setColor(new Color(255, 195, 93, 90)); var wx = x + 10
while (wx < x + bw - 7) { var wy = y + 12; while (wy < y + bh - 7) { if ((wx + wy) % 3 != 0) g.fillRect(wx, wy, 5, 7); wy += 19 }; wx += 17 } }
}
private def drawStation(g: Graphics2D, w: Int, h: Int, title: String): Unit = {
g.setColor(new Color(38, 43, 76)); g.fillRect(0, 250, w, 130); g.setColor(new Color(255, 192, 91)); g.fillRect(0, 250, w, 10)
var bx = 18; while (bx < w) { g.setColor(new Color(62, 66, 98)); g.fillRoundRect(bx, 272, 125, 79, 12, 12); g.setColor(new Color(255, 220, 143, 160)); g.fillRect(bx + 11, 285, 103, 8); g.setColor(new Color(28, 39, 66)); g.fillRect(bx + 20, 307, 84, 29); bx += 145 }
g.setColor(new Color(24, 24, 46)); g.fillRoundRect(w / 2 - 230, 205, 460, 56, 15, 15); g.setColor(new Color(255, 206, 100)); g.setStroke(new BasicStroke(2f)); g.drawRoundRect(w / 2 - 230, 205, 460, 56, 15, 15)
g.setFont(font(21, Font.BOLD)); g.setColor(new Color(255, 228, 158)); centered(g, title, w / 2, 240)
}
private def drawRailTracks(g: Graphics2D, w: Int, h: Int): Unit = {
val y = h - 122; g.setColor(new Color(43, 45, 58)); g.fillRect(0, y - 7, w, 42)
g.setColor(new Color(199, 202, 208)); g.setStroke(new BasicStroke(4f)); g.drawLine(0, y, w, y); g.drawLine(0, y + 24, w, y + 24)
g.setColor(new Color(113, 94, 91)); var x = 0; while (x < w) { g.fillRect(x, y - 8, 11, 40); x += 27 }
}
private def drawPassengerTrain(g: Graphics2D, x: Int, y: Int, coachW: Int, coachH: Int): Unit = {
val totalW = coachW * 3 + 84; g.setColor(new Color(7, 9, 20, 95)); g.fillOval(x - 8, y + coachH + 28, totalW + 25, 23)
var i = 0
while (i < 3) { val cx = x + i * coachW; g.setPaint(new GradientPaint(cx, y, new Color(211, 225, 230), cx, y + coachH, new Color(70, 119, 164))); g.fillRoundRect(cx, y, coachW - 8, coachH, 13, 13)
g.setColor(new Color(207, 56, 67)); g.fillRect(cx + 3, y + coachH - 18, coachW - 14, 8); var wx = cx + 12
while (wx < cx + coachW - 25) { g.setColor(new Color(27, 49, 76)); g.fillRoundRect(wx, y + 13, 22, 27, 4, 4); g.setColor(new Color(154, 217, 233)); g.fillRect(wx + 3, y + 16, 16, 5); wx += 31 }
g.setColor(new Color(36, 46, 66)); g.fillOval(cx + 20, y + coachH - 4, 25, 25); g.fillOval(cx + coachW - 45, y + coachH - 4, 25, 25); g.setColor(new Color(176, 183, 196)); g.fillOval(cx + 26, y + coachH + 2, 13, 13); g.fillOval(cx + coachW - 39, y + coachH + 2, 13, 13); i += 1 }
val ex = x + coachW * 3; g.setPaint(new GradientPaint(ex, y + 10, new Color(225, 222, 209), ex + 84, y + coachH, new Color(54, 101, 149))); g.fillRoundRect(ex, y + 9, 84, coachH - 1, 18, 18)
g.setColor(new Color(255, 201, 74)); g.fillRoundRect(ex + 8, y + coachH - 30, 67, 12, 5, 5); g.setColor(new Color(28, 45, 70)); g.fillRoundRect(ex + 15, y + 18, 47, 25, 5, 5)
}
private def drawPalm(g: Graphics2D, x: Int, y: Int): Unit = {
g.setColor(new Color(103, 71, 48)); g.fillPolygon(new Polygon(Array(x - 4, x + 5, x + 10, x + 3), Array(y + 72, y + 72, y, y), 4))
g.setColor(new Color(37, 110, 69)); g.fillArc(x - 47, y - 20, 60, 38, 25, 135); g.fillArc(x - 20, y - 32, 62, 40, 15, 140); g.fillArc(x - 49, y - 4, 64, 37, 190, 130); g.fillArc(x - 22, y - 8, 62, 37, 210, 120)
}
private def drawPerson(g: Graphics2D, xx: Double, yy: Double, shirt: Color, skin: Color, frame: Int): Unit = {
val x = xx.toInt; val y = yy.toInt; g.setColor(new Color(0, 0, 0, 65)); g.fillOval(x - 15, y + 19, 31, 10)
g.setColor(new Color(35, 36, 66)); g.setStroke(new BasicStroke(5f)); g.drawLine(x - 4, y + 8, x - 7, y + 21); g.drawLine(x + 4, y + 8, x + 7, y + 21)
g.setColor(shirt); g.fillRoundRect(x - 13, y - 7, 26, 19, 7, 7); g.setColor(skin); g.fillOval(x - 9, y - 25, 18, 19); g.setColor(new Color(49, 30, 28)); g.fillArc(x - 10, y - 28, 20, 14, 0, 180)
}
private def drawAmbient(g: Graphics2D): Unit = {
var i = 0
while (i < 13) { val xx = ((i * 97 + tick * (i % 3 + 1)) % math.max(1, getWidth)).toInt; val yy = 225 + ((i * 41 + tick / 3) % math.max(1, getHeight - 270)).toInt; val alpha = 70 + ((math.sin(tick * 0.08 + i) + 1) * 60).toInt; g.setColor(new Color(255, 208, 93, alpha)); g.fillOval(xx, yy, 3, 3); i += 1 }
}
private def drawMarker(g: Graphics2D, x: Int, y: Int, c: Color): Unit = {
val pulse = (math.sin(tick * 0.13) * 3).toInt; g.setColor(new Color(c.getRed, c.getGreen, c.getBlue, 45)); g.fillOval(x - 15 - pulse, y - 15 - pulse, 30 + pulse * 2, 30 + pulse * 2); g.setColor(c); g.setStroke(new BasicStroke(2f)); g.drawOval(x - 8, y - 8, 16, 16); g.fillOval(x - 3, y - 3, 6, 6)
}
private def drawToran(g: Graphics2D, x: Int, y: Int, width: Int, height: Int, bright: Boolean): Unit = {
g.setColor(new Color(40, 26, 44)); g.setStroke(new BasicStroke(2f)); g.drawArc(x, y - 2, width, height * 2, 180, 180); var i = 0
while (i <= 9) { val xx = x + i * width / 9; val yy = y + height / 2 + (math.sin(i * math.Pi / 9) * height * 0.55).toInt; g.setColor(if (i % 2 == 0) new Color(255, 183, 65) else new Color(64, 205, 142)); g.fillOval(xx - 4, yy - 3, 8, 10); if (bright) { g.setColor(new Color(255, 222, 103, 70)); g.fillOval(xx - 9, yy - 8, 18, 18) }; i += 1 }
if (bright) { g.setColor(new Color(255, 224, 143)); g.setStroke(new BasicStroke(3f)); g.drawArc(x, y - 2, width, height * 2, 180, 180) }
}
private def drawDiyas(g: Graphics2D, x: Int, y: Int, scale: Double): Unit = {
var i = 0
while (i < 3) { val xx = x + (i * 25 * scale).toInt; val yy = y + (if (i == 1) -4 else 0); g.setColor(new Color(255, 190, 58, 45)); g.fillOval(xx - 8, yy - 12, math.max(1, (32 * scale).toInt), math.max(1, (28 * scale).toInt)); g.setColor(new Color(204, 91, 40)); g.fillOval(xx, yy, math.max(1, (21 * scale).toInt), math.max(1, (10 * scale).toInt)); g.setColor(new Color(255, 207, 82)); g.fillOval(xx + (5 * scale).toInt, yy - (5 * scale).toInt, math.max(1, (11 * scale).toInt), math.max(1, (7 * scale).toInt)); g.setColor(new Color(255, 249, 191)); g.fillOval(xx + (9 * scale).toInt, yy - (11 * scale).toInt, math.max(1, (4 * scale).toInt), math.max(1, (8 * scale).toInt)); i += 1 }
}
private def drawMiniDiyas(g: Graphics2D, x: Int, y: Int): Unit = {
var i = 0; while (i < 3) { val xx = x + i * 18; g.setColor(new Color(241, 128, 49)); g.fillOval(xx, y, 13, 7); g.setColor(new Color(255, 230, 125)); g.fillOval(xx + 4, y - 5, 5, 6); i += 1 }
}
private def drawRangoli(g: Graphics2D, x: Int, y: Int): Unit = {
val colors = Array(new Color(244, 86, 115), new Color(255, 205, 72), new Color(77, 212, 172), new Color(127, 115, 255))
g.setColor(new Color(255, 231, 151, 40)); g.fillOval(x - 27, y - 13, 54, 28); var i = 0
while (i < 8) { val angle = i * math.Pi / 4.0; val cx = x + (math.cos(angle) * 17).toInt; val cy = y + (math.sin(angle) * 8).toInt; g.setColor(colors(i % colors.length)); g.fillOval(cx - 5, cy - 4, 10, 8); i += 1 }
g.setColor(new Color(255, 244, 193)); g.fillOval(x - 4, y - 3, 8, 6)
}
private def drawMiniBuilding(g: Graphics2D, x: Int, y: Int, w: Int, h: Int): Unit = {
g.setColor(new Color(76, 69, 132)); g.fillRect(x, y, w, h); g.setColor(new Color(255, 205, 103)); g.fillRect(x, y, w, 6); var row = 0
while (row < 2) { var col = 0; while (col < 3) { g.setColor(new Color(255, 226, 153)); g.fillRect(x + 10 + col * 27, y + 13 + row * 23, 16, 14); col += 1 }; row += 1 }
}
private def drawMiniHouse(g: Graphics2D, x: Int, y: Int, w: Int, h: Int): Unit = {
val roof = new Polygon(Array(x - 5, x + w / 2, x + w + 5), Array(y + 22, y - 5, y + 22), 3); g.setColor(new Color(220, 117, 72)); g.fillPolygon(roof); g.setColor(new Color(249, 191, 128)); g.fillRect(x + 4, y + 20, w - 8, h - 20); g.setColor(new Color(88, 48, 62)); g.fillRect(x + w / 2 - 10, y + 38, 20, h - 38); g.setColor(new Color(255, 225, 153)); g.fillRect(x + 13, y + 31, 17, 16); g.fillRect(x + w - 30, y + 31, 17, 16)
}
private def drawWindow(g: Graphics2D, x: Int, y: Int, w: Int, h: Int): Unit = {
g.setColor(new Color(59, 46, 78)); g.fillRoundRect(x, y, w, h, 5, 5); g.setColor(new Color(255, 226, 153)); g.fillRect(x + 5, y + 5, w - 10, h - 10); g.setColor(new Color(72, 57, 85)); g.setStroke(new BasicStroke(3f)); g.drawLine(x + w / 2, y + 4, x + w / 2, y + h - 4); g.drawLine(x + 4, y + h / 2, x + w - 4, y + h / 2)
}
private def drawRoadDashes(g: Graphics2D, w: Int, h: Int): Unit = {
g.setColor(new Color(255, 224, 133, 100)); var x = 15; while (x < w) { g.fillRoundRect(x, h - 42, 34, 3, 4, 4); x += 65 }
}
private def drawStars(g: Graphics2D, x: Int, y: Int, w: Int, h: Int, count: Int): Unit = {
var i = 0; while (i < count) { val sx = x + ((i * 137 + 31) % math.max(1, w)); val sy = y + ((i * 67 + 13) % math.max(1, h)); val a = 70 + ((i * 29) % 160); g.setColor(new Color(255, 239, 199, a)); val s = if (i % 7 == 0) 3 else 2; g.fillOval(sx, sy, s, s); i += 1 }
}
private def drawComplete(g: Graphics2D): Unit = {
val w = getWidth; val h = getHeight; g.setColor(new Color(11, 8, 28, 210)); g.fillRect(0, 0, w, h); drawStars(g, 0, 0, w, h, 55)
g.setColor(new Color(255, 202, 72, 50)); g.fillOval(w / 2 - 200, h / 2 - 180, 400, 320)
g.setPaint(new GradientPaint(w / 2 - 220, h / 2 - 130, new Color(75, 43, 91), w / 2 + 220, h / 2 + 150, new Color(33, 27, 61))); g.fillRoundRect(w / 2 - 265, h / 2 - 160, 530, 315, 34, 34)
g.setStroke(new BasicStroke(3f)); g.setColor(new Color(255, 205, 82)); g.drawRoundRect(w / 2 - 265, h / 2 - 160, 530, 315, 34, 34)
drawDiyas(g, w / 2 - 180, h / 2 + 36, 1.0); drawDiyas(g, w / 2 + 125, h / 2 + 36, 1.0)
g.setFont(font(36, Font.BOLD)); g.setColor(new Color(255, 208, 80)); centered(g, "SHUBH DEEPAWALI!", w / 2, h / 2 - 89)
g.setFont(font(21, Font.BOLD)); g.setColor(Color.WHITE); centered(g, "MISSION COMPLETED", w / 2, h / 2 - 48)
g.setFont(font(18)); g.setColor(new Color(226, 222, 243)); centered(g, family + " - All tasks finished", w / 2, h / 2 - 12)
g.setFont(font(24, Font.BOLD)); g.setColor(new Color(121, 248, 201)); centered(g, "FINAL SCORE: " + score, w / 2, h / 2 + 29)
g.setFont(font(15, Font.BOLD)); g.setColor(new Color(255, 222, 139)); centered(g, "Press ENTER to play again | ESC for family selection", w / 2, h / 2 + 99)
}
private def drawPause(g: Graphics2D): Unit = {
g.setColor(new Color(9, 7, 22, 195)); g.fillRect(0, 0, getWidth, getHeight); g.setFont(font(34, Font.BOLD)); g.setColor(new Color(255, 211, 99)); centered(g, "PAUSED", getWidth / 2, getHeight / 2 - 10); g.setFont(font(16, Font.BOLD)); g.setColor(Color.WHITE); centered(g, "Press P or E to continue", getWidth / 2, getHeight / 2 + 28)
}
override def keyPressed(e: KeyEvent): Unit = {
val code = e.getKeyCode
if (supportedKeys.contains(code) && !heldKeys.contains(code)) {
heldKeys += code
updateMovementFlags()
if (!movementKeys.contains(code)) handleKeyPressed(code)
}
}
override def keyReleased(e: KeyEvent): Unit = {
heldKeys -= e.getKeyCode
updateMovementFlags()
}
override def keyTyped(e: KeyEvent): Unit = {}
override def mouseClicked(e: MouseEvent): Unit = {
if (phase == "menu") {
if (menuGokul.contains(e.getPoint)) startGame("Gokuldham Family")
else if (menuNormal.contains(e.getPoint)) startGame("Normal Family")
}
requestFocusInWindow()
}
override def mousePressed(e: MouseEvent): Unit = {}
override def mouseReleased(e: MouseEvent): Unit = {}
override def mouseEntered(e: MouseEvent): Unit = {}
override def mouseExited(e: MouseEvent): Unit = {}
}
}
/**
* BIXUU Robot Code Creator
* One-file Scala desktop app; uses Java Swing/AWT only (no external libraries).
* Compatible with common Scala 2.12/2.13 installations on a JDK.
*/
object BixuuRobotCodeCreator {
def launchFromHub(): Unit = launch()
/** Explicit launcher. Call this from main() or from a script-style Scala editor. */
def launch(): Unit = {
if (GraphicsEnvironment.isHeadless) {
System.err.println("BIXUU Robot Code Creator needs a desktop session to display its window. Run it on your Windows desktop, not in a headless/console-only runner.")
} else {
val showWindow = new Runnable {
override def run(): Unit = {
try {
try UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName) catch { case _: Throwable => () }
val frame = new CreatorFrame()
frame.setVisible(true)
frame.setState(Frame.NORMAL)
frame.toFront()
frame.requestFocus()
} catch {
case ex: Throwable =>
ex.printStackTrace()
JOptionPane.showMessageDialog(
null,
"The BIXUU window could not be opened.\n" + Option(ex.getMessage).getOrElse(ex.getClass.getName) +
"\n\nRun this as a desktop Scala Swing application, then check the console if it still fails.",
"BIXUU Robot Code Creator - Startup Error",
JOptionPane.ERROR_MESSAGE
)
}
}
}
if (SwingUtilities.isEventDispatchThread) showWindow.run()
else SwingUtilities.invokeLater(showWindow)
}
}
def main(args: Array[String]): Unit = launch()
private val appBg = new Color(5, 9, 25)
private val panelBg = new Color(11, 18, 43)
private val accent = new Color(0, 225, 255)
private val purple = new Color(185, 72, 255)
private val whiteBlue = new Color(215, 240, 255)
case class Theme(name: String, primary: Color, secondary: Color, background: Color)
val themes: Vector[Theme] = Vector(
Theme("Cyber Cyan + Violet", new Color(0, 225, 255), new Color(190, 65, 255), new Color(3, 7, 25)),
Theme("Disco Pink + Blue", new Color(255, 55, 175), new Color(0, 190, 255), new Color(17, 3, 26)),
Theme("Solar Gold", new Color(255, 190, 45), new Color(255, 75, 60), new Color(22, 9, 5)),
Theme("Matrix Green", new Color(60, 255, 145), new Color(0, 190, 180), new Color(2, 19, 14)),
Theme("Ice Blue", new Color(135, 210, 255), new Color(170, 130, 255), new Color(3, 12, 30))
)
case class RobotConfig(
title: String,
description: String,
theme: Theme,
dancing: Boolean,
disco: Boolean,
wings: Boolean,
hologram: Boolean,
particles: Boolean,
speed: Int
)
private def colorWithAlpha(c: Color, a: Int): Color =
new Color(c.getRed, c.getGreen, c.getBlue, math.max(0, math.min(255, a)))
private def scalaString(s: String): String = {
val clean = Option(s).getOrElse("")
"\"" + clean.flatMap {
case '\\' => "\\\\"
case '"' => "\\\""
case '\n' => "\\n"
case '\r' => "\\r"
case '\t' => "\\t"
case c if c < ' ' => " "
case c => c.toString
} + "\""
}
private def rgb(c: Color): String =
"new Color(" + c.getRed + ", " + c.getGreen + ", " + c.getBlue + ")"
private def standaloneSource(cfg: RobotConfig): String = {
val raw = """import java.awt._
import java.awt.event._
import java.awt.geom._
import javax.swing._
/** Generated by BIXUU Robot Code Creator. Run this file as a normal Scala app. */
object GeneratedBixuuRobot extends App {
val robotTitle = __TITLE__
val robotDescription = __DESC__
val primary = __PRIMARY__
val secondary = __SECONDARY__
val backgroundColor = __BACKGROUND__
val useDance = __DANCE__
val useDiscoLights = __DISCO__
val useWings = __WINGS__
val useHologram = __HOLOGRAM__
val useParticles = __PARTICLES__
val animationSpeed = __SPEED__
SwingUtilities.invokeLater(new Runnable {
override def run(): Unit = {
val frame = new JFrame("BIXUU ROBOT | " + robotTitle)
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
frame.setSize(1100, 760)
frame.setMinimumSize(new Dimension(800, 560))
frame.setLocationRelativeTo(null)
frame.setContentPane(new RobotCanvas)
frame.setVisible(true)
}
})
class RobotCanvas extends JPanel {
private var tick = 0.0
setBackground(backgroundColor)
val timer = new Timer(30, new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
tick += 0.025 + animationSpeed * 0.009
repaint()
}
})
timer.start()
private def alpha(c: Color, a: Int): Color = new Color(c.getRed, c.getGreen, c.getBlue, math.max(0, math.min(255, a)))
private def fillOval(g: Graphics2D, x: Double, y: Double, w: Double, h: Double, c: Color): Unit = {
g.setColor(c); g.fill(new Ellipse2D.Double(x, y, w, h))
}
private def outlineOval(g: Graphics2D, x: Double, y: Double, w: Double, h: Double, c: Color, sw: Float): Unit = {
g.setColor(c); g.setStroke(new BasicStroke(sw)); g.draw(new Ellipse2D.Double(x, y, w, h))
}
private def fillRound(g: Graphics2D, x: Double, y: Double, w: Double, h: Double, arc: Double, c: Color): Unit = {
g.setColor(c); g.fill(new RoundRectangle2D.Double(x, y, w, h, arc, arc))
}
private def outlineRound(g: Graphics2D, x: Double, y: Double, w: Double, h: Double, arc: Double, c: Color, sw: Float): Unit = {
g.setColor(c); g.setStroke(new BasicStroke(sw)); g.draw(new RoundRectangle2D.Double(x, y, w, h, arc, arc))
}
private def line(g: Graphics2D, x1: Double, y1: Double, x2: Double, y2: Double, c: Color, sw: Float): Unit = {
g.setColor(c); g.setStroke(new BasicStroke(sw, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); g.draw(new Line2D.Double(x1, y1, x2, y2))
}
override protected def paintComponent(graphics: Graphics): Unit = {
super.paintComponent(graphics)
val g = graphics.create().asInstanceOf[Graphics2D]
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)
val w = getWidth.toDouble; val h = getHeight.toDouble
g.setPaint(new GradientPaint(0f, 0f, backgroundColor, w.toFloat, h.toFloat, new Color(15, 10, 42)))
g.fillRect(0, 0, getWidth, getHeight)
val cx = w * 0.43
val cy = h * 0.56
val s = math.max(0.56, math.min(w / 850.0, h / 590.0))
val active = if (useDiscoLights) Color.getHSBColor(((tick * 0.12) % 1.0).toFloat, 0.88f, 1.0f) else primary
val bob = if (useDance) math.sin(tick * 2.2) * 5.0 * s else 0.0
val y = cy + bob
// Nebula rings and stars
for (i <- 0 until 8) {
val pulse = math.sin(tick + i * 0.42) * 11.0
outlineOval(g, cx - (112 + i * 15 + pulse) * s, cy - (112 + i * 15 + pulse) * s,
(224 + i * 30 + pulse * 2) * s, (224 + i * 30 + pulse * 2) * s,
alpha(if (i % 2 == 0) active else secondary, 20 + i * 3), 1.4f)
}
for (i <- 0 until 150) {
val sx = ((i * 137 + 31) % math.max(1, getWidth)).toDouble
val sy = ((i * 83 + 17) % math.max(1, getHeight)).toDouble
val tw = 1.0 + math.abs(math.sin(tick * 2 + i * 0.3)) * 2.0
fillOval(g, sx, sy, tw, tw, alpha(if (i % 8 == 0) secondary else new Color(175, 225, 255), 70 + (math.abs(math.sin(tick + i)) * 180).toInt))
}
// Neon floor and HUD corners
for (i <- 0 until 12) line(g, 0, h * 0.79 + i * 8, w, h * 0.79 + i * 8, alpha(active, 45), 1f)
line(g, 0, h * 0.79, w, h * 0.79, alpha(secondary, 170), 2f)
line(g, 12, 18, 48, 18, active, 2f); line(g, 12, 18, 12, 48, active, 2f)
line(g, w - 12, 18, w - 48, 18, active, 2f); line(g, w - 12, 18, w - 12, 48, active, 2f)
// Energy wings
if (useWings) {
val flap = math.sin(tick * 2.4) * 13 * s
val left = new java.awt.Polygon(
Array((cx - 28*s).toInt, (cx - 104*s).toInt, (cx - 80*s).toInt),
Array((y - 30*s).toInt, (y - 75*s - flap).toInt, (y + 32*s - flap).toInt), 3)
g.setColor(alpha(active, 28)); g.fillPolygon(left); g.setColor(alpha(active, 235)); g.setStroke(new BasicStroke(2.8f)); g.drawPolygon(left)
line(g, cx - 36*s, y - 23*s, cx - 88*s, y - 34*s - flap, alpha(new Color(205,245,255), 220), 1.8f)
val right = new java.awt.Polygon(
Array((cx + 28*s).toInt, (cx + 104*s).toInt, (cx + 80*s).toInt),
Array((y - 30*s).toInt, (y - 75*s + flap).toInt, (y + 32*s + flap).toInt), 3)
g.setColor(alpha(secondary, 28)); g.fillPolygon(right); g.setColor(alpha(secondary, 240)); g.setStroke(new BasicStroke(2.8f)); g.drawPolygon(right)
line(g, cx + 36*s, y - 23*s, cx + 88*s, y - 34*s + flap, alpha(new Color(220,240,255), 220), 1.8f)
fillOval(g, cx - 92*s, y - 16*s - flap, 10*s, 10*s, Color.WHITE)
fillOval(g, cx + 82*s, y - 16*s + flap, 10*s, 10*s, Color.WHITE)
}
// Energy orbit
for (i <- 0 until 4) outlineOval(g, cx - (76 + i*12)*s, y - (13 + i*3)*s,
(152 + i*24)*s, (26 + i*6)*s, alpha(if(i%2==0) active else secondary, 75 + (math.abs(math.sin(tick+i))*130).toInt), 1.8f)
// Arms and shoulders
val armSwing = if (useDance) math.sin(tick * 3.0) * 16 * s else 0.0
line(g, cx - 32*s, y - 27*s, cx - 55*s, y + 9*s + armSwing, new Color(30,55,95), 17*s.toFloat)
line(g, cx + 32*s, y - 27*s, cx + 55*s, y + 9*s - armSwing, new Color(30,55,95), 17*s.toFloat)
line(g, cx - 32*s, y - 27*s, cx - 55*s, y + 9*s + armSwing, new Color(175,205,230), 9*s.toFloat)
line(g, cx + 32*s, y - 27*s, cx + 55*s, y + 9*s - armSwing, new Color(175,205,230), 9*s.toFloat)
line(g, cx - 33*s, y - 27*s, cx - 55*s, y + 9*s + armSwing, active, 2*s.toFloat)
line(g, cx + 33*s, y - 27*s, cx + 55*s, y + 9*s - armSwing, active, 2*s.toFloat)
fillOval(g, cx - 65*s, y + 1*s + armSwing, 20*s, 20*s, new Color(160,195,225)); outlineOval(g, cx - 65*s, y + 1*s + armSwing, 20*s, 20*s, active, 2f)
fillOval(g, cx + 45*s, y + 1*s - armSwing, 20*s, 20*s, new Color(160,195,225)); outlineOval(g, cx + 45*s, y + 1*s - armSwing, 20*s, 20*s, active, 2f)
// Legs and boots
fillRound(g, cx - 25*s, y + 39*s, 18*s, 50*s, 8*s, new Color(110,145,185)); outlineRound(g, cx - 25*s, y + 39*s, 18*s, 50*s, 8*s, active, 2f)
fillRound(g, cx + 7*s, y + 39*s, 18*s, 50*s, 8*s, new Color(110,145,185)); outlineRound(g, cx + 7*s, y + 39*s, 18*s, 50*s, 8*s, active, 2f)
fillOval(g, cx - 31*s, y + 79*s, 34*s, 20*s, new Color(20,36,75)); outlineOval(g, cx - 31*s, y + 79*s, 34*s, 20*s, active, 2.2f)
fillOval(g, cx + 1*s, y + 79*s, 34*s, 20*s, new Color(20,36,75)); outlineOval(g, cx + 1*s, y + 79*s, 34*s, 20*s, active, 2.2f)
line(g, cx - 23*s, y + 91*s, cx - 7*s, y + 91*s, active, 3f); line(g, cx + 9*s, y + 91*s, cx + 25*s, y + 91*s, active, 3f)
// Torso armour
fillOval(g, cx - 43*s, y - 43*s, 29*s, 29*s, new Color(45,82,135)); outlineOval(g, cx - 43*s, y - 43*s, 29*s, 29*s, active, 2.4f)
fillOval(g, cx + 14*s, y - 43*s, 29*s, 29*s, new Color(45,82,135)); outlineOval(g, cx + 14*s, y - 43*s, 29*s, 29*s, active, 2.4f)
fillRound(g, cx - 37*s, y - 50*s, 74*s, 91*s, 22*s, new Color(95,130,175)); outlineRound(g, cx - 37*s, y - 50*s, 74*s, 91*s, 22*s, new Color(205,230,250), 2.6f)
fillRound(g, cx - 28*s, y - 37*s, 56*s, 54*s, 12*s, new Color(5,13,40)); outlineRound(g, cx - 28*s, y - 37*s, 56*s, 54*s, 10*s, active, 2.5f)
fillOval(g, cx - 22*s, y - 31*s, 44*s, 44*s, alpha(active, 60)); outlineOval(g, cx - 18*s, y - 27*s, 36*s, 36*s, active, 2.5f)
outlineOval(g, cx - 12*s, y - 21*s, 24*s, 24*s, secondary, 2f)
fillOval(g, cx - 7*s, y - 16*s, 14*s, 14*s, active); fillOval(g, cx - 3*s, y - 12*s, 6*s, 6*s, Color.WHITE)
fillRound(g, cx - 24*s, y + 38*s, 48*s, 13*s, 5*s, new Color(30,62,108)); outlineRound(g, cx - 24*s, y + 38*s, 48*s, 13*s, 5*s, active, 2f)
// Helmet and neon visor
fillRound(g, cx - 34*s, y - 126*s, 68*s, 57*s, 17*s, new Color(150,185,215)); outlineRound(g, cx - 34*s, y - 126*s, 68*s, 57*s, 17*s, new Color(225,245,255), 3f)
fillRound(g, cx - 27*s, y - 113*s, 54*s, 28*s, 8*s, new Color(2,8,28)); outlineRound(g, cx - 27*s, y - 113*s, 54*s, 28*s, 8*s, active, 2.3f)
val eyeGlow = 170 + (math.abs(math.sin(tick * 3.0)) * 85).toInt
fillOval(g, cx - 19*s, y - 105*s, 13*s, 8*s, alpha(active, eyeGlow)); fillOval(g, cx + 6*s, y - 105*s, 13*s, 8*s, alpha(active, eyeGlow))
fillOval(g, cx - 15*s, y - 104*s, 4*s, 4*s, Color.WHITE); fillOval(g, cx + 10*s, y - 104*s, 4*s, 4*s, Color.WHITE)
line(g, cx - 9*s, y - 82*s, cx + 9*s, y - 82*s, active, 2f)
fillRound(g, cx - 16*s, y - 132*s, 32*s, 8*s, 5*s, new Color(35,70,120)); fillOval(g, cx - 7*s, y - 140*s, 14*s, 14*s, active); fillOval(g, cx - 3*s, y - 136*s, 6*s, 6*s, Color.WHITE)
// Particles around the robot
if (useParticles) for (i <- 0 until 34) {
val a = tick * 0.7 + i * math.Pi / 17
val r = (88 + (i % 5) * 9) * s
val px = cx + math.cos(a) * r
val py = y - 22*s + math.sin(a * 1.25) * 75*s
fillOval(g, px, py, (2.5 + (i % 3)) * s, (2.5 + (i % 3)) * s, alpha(if (i % 2 == 0) active else secondary, 100 + (math.abs(math.sin(tick+i))*140).toInt))
}
// Hologram console
if (useHologram) {
val hx = cx + 118*s; val hy = y - 22*s + math.sin(tick) * 4*s
fillRound(g, hx, hy, 144*s, 83*s, 9*s, alpha(new Color(4,12,37), 235))
outlineRound(g, hx, hy, 144*s, 83*s, 9*s, active, 2f)
line(g, hx + 8*s, hy + 21*s, hx + 136*s, hy + 21*s, secondary, 2f)
g.setFont(new Font("Monospaced", Font.BOLD, math.max(9, (11*s).toInt))); g.setColor(active); g.drawString("BIXUU CORE", (hx+9*s).toInt, (hy+15*s).toInt)
g.setFont(new Font("Monospaced", Font.PLAIN, math.max(8, (9*s).toInt))); g.setColor(new Color(210,235,255)); g.drawString("SYSTEM ONLINE", (hx+9*s).toInt, (hy+39*s).toInt)
g.setColor(new Color(80,255,155)); g.drawString("DANCE: ACTIVE", (hx+9*s).toInt, (hy+53*s).toInt)
for (i <- 0 until 8) { val bh = (5 + math.abs(math.sin(tick*3+i*0.7))*17)*s; g.setColor(active); g.fill(new Rectangle2D.Double(hx+(9+i*16)*s, hy+65*s-bh, 6*s, bh)) }
}
// Title and footer
g.setFont(new Font("SansSerif", Font.BOLD, 17)); g.setColor(active); g.drawString(robotTitle.take(32), 23, 32)
g.setFont(new Font("Monospaced", Font.PLAIN, 11)); g.setColor(new Color(190,220,255)); g.drawString("BIXUU ROBOT SYSTEM | SCALA SWING", 23, 50)
g.setColor(new Color(0,255,150)); g.drawString("ONLINE", 23, getHeight - 18)
g.setColor(secondary); g.drawString("POWER " + (80 + (math.abs(math.sin(tick))*20).toInt) + "%", getWidth - 95, getHeight - 18)
g.dispose()
}
}
}
"""
raw
.replace("__TITLE__", scalaString(cfg.title))
.replace("__DESC__", scalaString(cfg.description))
.replace("__PRIMARY__", rgb(cfg.theme.primary))
.replace("__SECONDARY__", rgb(cfg.theme.secondary))
.replace("__BACKGROUND__", rgb(cfg.theme.background))
.replace("__DANCE__", cfg.dancing.toString)
.replace("__DISCO__", cfg.disco.toString)
.replace("__WINGS__", cfg.wings.toString)
.replace("__HOLOGRAM__", cfg.hologram.toString)
.replace("__PARTICLES__", cfg.particles.toString)
.replace("__SPEED__", cfg.speed.toString)
}
class CreatorFrame extends JFrame("BIXUU 02 ROBOT LAB | BUILD & ANIMATE") {
private val titleField = new JTextField("BIXUU 02", 22)
private val descriptionArea = new JTextArea("BIXUU 02 cyber robot with dance animation, energy wings, neon lights and hologram.", 4, 22)
private val themeBox = new JComboBox[String](themes.map(_.name).toArray)
private val danceBox = new JCheckBox("Dance animation", true)
private val discoBox = new JCheckBox("Disco / colour-changing lights", true)
private val wingsBox = new JCheckBox("Energy wings", true)
private val hologramBox = new JCheckBox("Hologram screen", true)
private val particlesBox = new JCheckBox("Energy particles", true)
private val speedSlider = new JSlider(1, 10, 6)
private val speedText = new JLabel("Animation speed: 6")
private val statusLabel = new JLabel("Ready ? create your robot!")
private val preview = new RobotPreview(readConfig())
private val codeArea = new JTextArea("Press GENERATE CODE to create a standalone Scala Swing animation.")
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE)
addWindowListener(new WindowAdapter {
override def windowClosed(e: WindowEvent): Unit = preview.stopAnimation()
})
// Fit the window to the current screen; a 1400x900 fixed window can start partly off-screen
// on smaller laptop displays and make it look as if no window opened.
private val screenSize = Toolkit.getDefaultToolkit.getScreenSize
private val initialWidth = math.max(800, math.min(1400, screenSize.width - 40))
private val initialHeight = math.max(600, math.min(900, screenSize.height - 70))
setMinimumSize(new Dimension(math.min(900, initialWidth), math.min(600, initialHeight)))
setSize(initialWidth, initialHeight)
setLocationRelativeTo(null)
private def styleButton(b: JButton, bg: Color): JButton = {
b.setBackground(bg); b.setForeground(Color.WHITE); b.setFocusPainted(false)
b.setFont(new Font("SansSerif", Font.BOLD, 12)); b.setBorder(new CompoundBorder(new LineBorder(colorWithAlpha(accent, 170), 1, true), new EmptyBorder(9, 10, 9, 10)))
b.setAlignmentX(Component.LEFT_ALIGNMENT); b
}
private def styleCheck(c: JCheckBox): Unit = {
c.setOpaque(false); c.setForeground(whiteBlue); c.setFont(new Font("SansSerif", Font.PLAIN, 13)); c.setFocusPainted(false)
}
private def label(text: String): JLabel = {
val l = new JLabel(text); l.setForeground(accent); l.setFont(new Font("SansSerif", Font.BOLD, 12)); l.setAlignmentX(Component.LEFT_ALIGNMENT); l
}
private def readConfig(): RobotConfig = {
val selected = Option(themeBox.getSelectedItem).map(_.toString).getOrElse(themes.head.name)
val theme = themes.find(_.name == selected).getOrElse(themes.head)
RobotConfig(
Option(titleField.getText).getOrElse("").trim match { case "" => "BIXUU ROBOT"; case x => x },
Option(descriptionArea.getText).getOrElse(""), theme,
danceBox.isSelected, discoBox.isSelected, wingsBox.isSelected,
hologramBox.isSelected, particlesBox.isSelected, speedSlider.getValue
)
}
private val controls = new JPanel()
controls.setBackground(panelBg)
controls.setBorder(new EmptyBorder(15, 15, 15, 15))
controls.setLayout(new BoxLayout(controls, BoxLayout.Y_AXIS))
controls.setPreferredSize(new Dimension(300, 700))
controls.add(label("ROBOT TITLE")); controls.add(Box.createVerticalStrut(6))
titleField.setBackground(new Color(5, 12, 32)); titleField.setForeground(Color.WHITE); titleField.setCaretColor(accent)
titleField.setBorder(new CompoundBorder(new LineBorder(new Color(0, 125, 180), 1, true), new EmptyBorder(8, 8, 8, 8)))
titleField.setMaximumSize(new Dimension(Short.MaxValue, 38)); controls.add(titleField)
controls.add(Box.createVerticalStrut(14)); controls.add(label("ROBOT DESCRIPTION")); controls.add(Box.createVerticalStrut(6))
descriptionArea.setLineWrap(true); descriptionArea.setWrapStyleWord(true); descriptionArea.setBackground(new Color(5, 12, 32)); descriptionArea.setForeground(whiteBlue); descriptionArea.setCaretColor(accent)
descriptionArea.setBorder(new EmptyBorder(8, 8, 8, 8))
val descScroll = new JScrollPane(descriptionArea); descScroll.setBorder(new LineBorder(new Color(0, 125, 180), 1, true)); descScroll.setMaximumSize(new Dimension(Short.MaxValue, 105)); controls.add(descScroll)
controls.add(Box.createVerticalStrut(14)); controls.add(label("LIGHTING STYLE")); controls.add(Box.createVerticalStrut(6))
themeBox.setBackground(new Color(5, 12, 32)); themeBox.setForeground(whiteBlue); themeBox.setMaximumSize(new Dimension(Short.MaxValue, 35)); controls.add(themeBox)
controls.add(Box.createVerticalStrut(14)); controls.add(label("ANIMATION FEATURES")); controls.add(Box.createVerticalStrut(6))
Vector(danceBox, discoBox, wingsBox, hologramBox, particlesBox).foreach { cb => styleCheck(cb); controls.add(cb); controls.add(Box.createVerticalStrut(5)) }
controls.add(Box.createVerticalStrut(12)); controls.add(label("ANIMATION SPEED"))
speedSlider.setOpaque(false); speedSlider.setForeground(accent); speedSlider.setMajorTickSpacing(3); speedSlider.setPaintTicks(true); speedSlider.setPaintLabels(true); speedSlider.setMaximumSize(new Dimension(Short.MaxValue, 55))
controls.add(speedSlider); controls.add(speedText)
controls.add(Box.createVerticalStrut(14))
val createButton = styleButton(new JButton("? CREATE / UPDATE ROBOT"), new Color(0, 100, 145))
val generateButton = styleButton(new JButton("? GENERATE CODE"), new Color(95, 35, 160))
val copyButton = styleButton(new JButton("? COPY CODE"), new Color(0, 105, 95))
val saveButton = styleButton(new JButton("? SAVE .SCALA FILE"), new Color(40, 70, 120))
Vector(createButton, generateButton, copyButton, saveButton).foreach { b => controls.add(b); controls.add(Box.createVerticalStrut(8)) }
controls.add(Box.createVerticalStrut(8))
statusLabel.setForeground(new Color(150, 220, 255)); statusLabel.setFont(new Font("SansSerif", Font.PLAIN, 11)); statusLabel.setAlignmentX(Component.LEFT_ALIGNMENT); controls.add(statusLabel)
controls.add(Box.createVerticalGlue())
preview.setBackground(appBg)
codeArea.setBackground(new Color(3, 8, 23)); codeArea.setForeground(new Color(180, 245, 220)); codeArea.setCaretColor(Color.WHITE)
codeArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); codeArea.setTabSize(2); codeArea.setLineWrap(false)
val codeScroll = new JScrollPane(codeArea); codeScroll.setBorder(new LineBorder(new Color(0, 135, 175), 1))
val previewTitle = new JLabel(" LIVE ROBOT PREVIEW ? CYBER DISCO MODE")
previewTitle.setOpaque(true); previewTitle.setBackground(new Color(7, 17, 42)); previewTitle.setForeground(accent); previewTitle.setFont(new Font("SansSerif", Font.BOLD, 12)); previewTitle.setBorder(new EmptyBorder(9, 8, 9, 8))
val codeTitle = new JLabel(" GENERATED SCALA SWING CODE")
codeTitle.setOpaque(true); codeTitle.setBackground(new Color(7, 17, 42)); codeTitle.setForeground(new Color(135, 255, 190)); codeTitle.setFont(new Font("SansSerif", Font.BOLD, 12)); codeTitle.setBorder(new EmptyBorder(9, 8, 9, 8))
val previewWrap = new JPanel(new BorderLayout()); previewWrap.setBackground(appBg); previewWrap.add(previewTitle, BorderLayout.NORTH); previewWrap.add(preview, BorderLayout.CENTER)
val codeWrap = new JPanel(new BorderLayout()); codeWrap.setBackground(appBg); codeWrap.add(codeTitle, BorderLayout.NORTH); codeWrap.add(codeScroll, BorderLayout.CENTER)
val verticalSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, previewWrap, codeWrap); verticalSplit.setResizeWeight(0.53); verticalSplit.setDividerSize(7); verticalSplit.setBorder(null); verticalSplit.setBackground(appBg)
val mainSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JScrollPane(controls), verticalSplit); mainSplit.setResizeWeight(0.23); mainSplit.setDividerLocation(315); mainSplit.setDividerSize(7); mainSplit.setBorder(null)
val header = new JPanel(new BorderLayout()); header.setBackground(new Color(4, 8, 25)); header.setBorder(new EmptyBorder(12, 18, 12, 18))
val brand = new JLabel("B I X U U | ZERO TWO ROBOT LAB")
brand.setForeground(accent); brand.setFont(new Font("SansSerif", Font.BOLD, 22))
val subtitle = new JLabel("SCALA SWING ? ANIMATION LAB ? CREATE / GENERATE / COPY")
subtitle.setForeground(new Color(185, 205, 235)); subtitle.setFont(new Font("Monospaced", Font.PLAIN, 11))
val brandPanel = new JPanel(); brandPanel.setOpaque(false); brandPanel.setLayout(new BoxLayout(brandPanel, BoxLayout.Y_AXIS)); brandPanel.add(brand); brandPanel.add(Box.createVerticalStrut(4)); brandPanel.add(subtitle)
header.add(brandPanel, BorderLayout.WEST)
val badge = new JLabel(" LIVE PREVIEW "); badge.setForeground(Color.WHITE); badge.setOpaque(true); badge.setBackground(new Color(0, 105, 120)); badge.setBorder(new EmptyBorder(8, 10, 8, 10)); header.add(badge, BorderLayout.EAST)
getContentPane.setLayout(new BorderLayout()); getContentPane.setBackground(appBg); getContentPane.add(header, BorderLayout.NORTH); getContentPane.add(mainSplit, BorderLayout.CENTER)
private def applyPromptHints(): Unit = {
// Small offline keyword helper: the prompt can switch common features/themes.
// The feature checkboxes remain editable, so users can fine-tune the result.
val p = Option(descriptionArea.getText).getOrElse("").toLowerCase
def has(words: String*): Boolean = words.exists(p.contains)
if (has("pink", "??????", "magenta")) themeBox.setSelectedItem("Disco Pink + Blue")
else if (has("gold", "golden", "??????", "????????")) themeBox.setSelectedItem("Solar Gold")
else if (has("green", "matrix", "?????", "?????")) themeBox.setSelectedItem("Matrix Green")
else if (has("ice blue", "icy", "??????????", "?????")) themeBox.setSelectedItem("Ice Blue")
else if (has("cyan", "violet", "purple", "??????", "????")) themeBox.setSelectedItem("Cyber Cyan + Violet")
if (has("no wings", "without wings", "wings off", "??? ???", "?????? ???")) wingsBox.setSelected(false)
else if (has("wings", "wing", "???", "??????")) wingsBox.setSelected(true)
if (has("no disco", "without disco", "disco off", "?????? ???", "??????? ???")) discoBox.setSelected(false)
else if (has("disco", "rainbow", "neon", "lighting", "lights", "???????", "???????", "????", "????")) discoBox.setSelected(true)
if (has("no dance", "without dance", "dance off", "????? ???", "??? ???")) danceBox.setSelected(false)
else if (has("dance", "dancing", "?????", "???????", "???????")) danceBox.setSelected(true)
if (has("no hologram", "without hologram", "????????? ???")) hologramBox.setSelected(false)
else if (has("hologram", "?????????")) hologramBox.setSelected(true)
if (has("no particles", "without particles", "particles off", "?? ???")) particlesBox.setSelected(false)
else if (has("particles", "sparks", "energy particles", "??", "???????")) particlesBox.setSelected(true)
if (has("very fast", "super fast", "??? ??????", "?????")) speedSlider.setValue(9)
else if (has("slow dance", "slow motion", "???")) speedSlider.setValue(3)
}
private def refreshPreview(): Unit = {
preview.setConfig(readConfig())
previewTitle.setText(" LIVE ROBOT PREVIEW ? " + readConfig().title.take(38))
speedText.setText("Animation speed: " + speedSlider.getValue)
statusLabel.setText("Preview updated ? " + readConfig().theme.name)
}
private def showError(msg: String): Unit = JOptionPane.showMessageDialog(this, msg, "BIXUU Robot Code Creator", JOptionPane.ERROR_MESSAGE)
createButton.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = { applyPromptHints(); refreshPreview() } })
themeBox.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = refreshPreview() })
speedSlider.addChangeListener(new ChangeListener {
override def stateChanged(e: ChangeEvent): Unit = {
speedText.setText("Animation speed: " + speedSlider.getValue)
refreshPreview()
}
})
Vector(danceBox, discoBox, wingsBox, hologramBox, particlesBox).foreach(_.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = refreshPreview() }))
generateButton.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
applyPromptHints()
refreshPreview()
val cfg = readConfig()
val generated = standaloneSource(cfg)
codeArea.setText(generated); codeArea.setCaretPosition(0)
statusLabel.setText("Generated Scala source for: " + cfg.title.take(30))
codeTitle.setText(" GENERATED SCALA SWING CODE ? " + generated.split("\\n", -1).length + " LINES")
}
})
copyButton.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
val text = codeArea.getText
if (text.trim.isEmpty || text.startsWith("Press GENERATE")) showError("First press GENERATE CODE.")
else Try { Toolkit.getDefaultToolkit.getSystemClipboard.setContents(new StringSelection(text), null) } match {
case scala.util.Success(_) => statusLabel.setText("Code copied to clipboard ? paste it into your Scala editor.")
case scala.util.Failure(_) => showError("Clipboard access failed. Select the code and copy it manually.")
}
}
})
saveButton.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
val text = codeArea.getText
if (text.trim.isEmpty || text.startsWith("Press GENERATE")) showError("First press GENERATE CODE.")
else {
val chooser = new JFileChooser(); chooser.setDialogTitle("Save generated Scala source")
chooser.setSelectedFile(new java.io.File("GeneratedBixuuRobot.scala")); chooser.setFileFilter(new FileNameExtensionFilter("Scala source (*.scala)", "scala"))
if (chooser.showSaveDialog(CreatorFrame.this) == JFileChooser.APPROVE_OPTION) {
var file = chooser.getSelectedFile
if (!file.getName.toLowerCase.endsWith(".scala")) file = new java.io.File(file.getParentFile, file.getName + ".scala")
Try { Files.write(file.toPath, text.getBytes(StandardCharsets.UTF_8)) } match {
case scala.util.Success(_) => statusLabel.setText("Saved: " + file.getName)
case scala.util.Failure(ex) => showError("Could not save file: " + ex.getMessage)
}
}
}
}
})
// Generate a useful first version so the code pane is not empty at launch.
codeArea.setText(standaloneSource(readConfig())); codeArea.setCaretPosition(0)
codeTitle.setText(" GENERATED SCALA SWING CODE")
refreshPreview()
}
class RobotPreview(initialConfig: RobotConfig) extends JPanel {
private var cfg = initialConfig
private var t = 0.0
setDoubleBuffered(true)
private val timer = new Timer(30, new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = { t += 0.025 + cfg.speed * 0.009; repaint() }
})
timer.start()
def stopAnimation(): Unit = timer.stop()
def setConfig(c: RobotConfig): Unit = { cfg = c; repaint() }
private def alpha(c: Color, a: Int): Color = colorWithAlpha(c, a)
private def fillOval(g: Graphics2D, x: Double, y: Double, w: Double, h: Double, c: Color): Unit = { g.setColor(c); g.fill(new Ellipse2D.Double(x, y, w, h)) }
private def outlineOval(g: Graphics2D, x: Double, y: Double, w: Double, h: Double, c: Color, sw: Float): Unit = { g.setColor(c); g.setStroke(new BasicStroke(sw)); g.draw(new Ellipse2D.Double(x, y, w, h)) }
private def fillRound(g: Graphics2D, x: Double, y: Double, w: Double, h: Double, arc: Double, c: Color): Unit = { g.setColor(c); g.fill(new RoundRectangle2D.Double(x, y, w, h, arc, arc)) }
private def outlineRound(g: Graphics2D, x: Double, y: Double, w: Double, h: Double, arc: Double, c: Color, sw: Float): Unit = { g.setColor(c); g.setStroke(new BasicStroke(sw)); g.draw(new RoundRectangle2D.Double(x, y, w, h, arc, arc)) }
private def line(g: Graphics2D, x1: Double, y1: Double, x2: Double, y2: Double, c: Color, sw: Float): Unit = { g.setColor(c); g.setStroke(new BasicStroke(sw, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); g.draw(new Line2D.Double(x1, y1, x2, y2)) }
override protected def paintComponent(graphics: Graphics): Unit = {
super.paintComponent(graphics)
val g = graphics.create().asInstanceOf[Graphics2D]
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)
val w = getWidth.toDouble; val h = getHeight.toDouble
g.setPaint(new GradientPaint(0f, 0f, cfg.theme.background, w.toFloat, h.toFloat, new Color(15, 10, 42)))
g.fillRect(0, 0, getWidth, getHeight)
val cx = w * 0.43; val cy = h * 0.56
val s = math.max(0.48, math.min(w / 850.0, h / 590.0))
val active = if (cfg.disco) Color.getHSBColor(((t * 0.12) % 1.0).toFloat, 0.88f, 1.0f) else cfg.theme.primary
val bob = if (cfg.dancing) math.sin(t * 2.2) * 5.0 * s else 0.0
val y = cy + bob
for (i <- 0 until 8) {
val pulse = math.sin(t + i * 0.42) * 11.0
outlineOval(g, cx - (112 + i * 15 + pulse) * s, cy - (112 + i * 15 + pulse) * s,
(224 + i * 30 + pulse * 2) * s, (224 + i * 30 + pulse * 2) * s,
alpha(if (i % 2 == 0) active else cfg.theme.secondary, 20 + i * 3), 1.4f)
}
for (i <- 0 until 150) {
val sx = ((i * 137 + 31) % math.max(1, getWidth)).toDouble
val sy = ((i * 83 + 17) % math.max(1, getHeight)).toDouble
val tw = 1.0 + math.abs(math.sin(t * 2 + i * 0.3)) * 2.0
fillOval(g, sx, sy, tw, tw, alpha(if (i % 8 == 0) cfg.theme.secondary else new Color(175, 225, 255), 70 + (math.abs(math.sin(t + i)) * 180).toInt))
}
for (i <- 0 until 12) line(g, 0, h * 0.79 + i * 8, w, h * 0.79 + i * 8, alpha(active, 45), 1f)
line(g, 0, h * 0.79, w, h * 0.79, alpha(cfg.theme.secondary, 170), 2f)
line(g, 12, 18, 48, 18, active, 2f); line(g, 12, 18, 12, 48, active, 2f)
line(g, w - 12, 18, w - 48, 18, active, 2f); line(g, w - 12, 18, w - 12, 48, active, 2f)
if (cfg.wings) {
val flap = math.sin(t * 2.4) * 13 * s
val left = new java.awt.Polygon(
Array((cx - 28*s).toInt, (cx - 104*s).toInt, (cx - 80*s).toInt),
Array((y - 30*s).toInt, (y - 75*s - flap).toInt, (y + 32*s - flap).toInt), 3)
g.setColor(alpha(active, 28)); g.fillPolygon(left); g.setColor(alpha(active, 235)); g.setStroke(new BasicStroke(2.8f)); g.drawPolygon(left)
line(g, cx - 36*s, y - 23*s, cx - 88*s, y - 34*s - flap, alpha(whiteBlue, 220), 1.8f)
val right = new java.awt.Polygon(
Array((cx + 28*s).toInt, (cx + 104*s).toInt, (cx + 80*s).toInt),
Array((y - 30*s).toInt, (y - 75*s + flap).toInt, (y + 32*s + flap).toInt), 3)
g.setColor(alpha(cfg.theme.secondary, 28)); g.fillPolygon(right); g.setColor(alpha(cfg.theme.secondary, 240)); g.setStroke(new BasicStroke(2.8f)); g.drawPolygon(right)
line(g, cx + 36*s, y - 23*s, cx + 88*s, y - 34*s + flap, alpha(whiteBlue, 220), 1.8f)
fillOval(g, cx - 92*s, y - 16*s - flap, 10*s, 10*s, Color.WHITE); fillOval(g, cx + 82*s, y - 16*s + flap, 10*s, 10*s, Color.WHITE)
}
for (i <- 0 until 4) outlineOval(g, cx - (76 + i*12)*s, y - (13 + i*3)*s, (152 + i*24)*s, (26 + i*6)*s,
alpha(if (i % 2 == 0) active else cfg.theme.secondary, 75 + (math.abs(math.sin(t+i))*130).toInt), 1.8f)
val armSwing = if (cfg.dancing) math.sin(t * 3.0) * 16 * s else 0.0
line(g, cx - 32*s, y - 27*s, cx - 55*s, y + 9*s + armSwing, new Color(30,55,95), 17*s.toFloat)
line(g, cx + 32*s, y - 27*s, cx + 55*s, y + 9*s - armSwing, new Color(30,55,95), 17*s.toFloat)
line(g, cx - 32*s, y - 27*s, cx - 55*s, y + 9*s + armSwing, new Color(175,205,230), 9*s.toFloat)
line(g, cx + 32*s, y - 27*s, cx + 55*s, y + 9*s - armSwing, new Color(175,205,230), 9*s.toFloat)
line(g, cx - 33*s, y - 27*s, cx - 55*s, y + 9*s + armSwing, active, 2*s.toFloat)
line(g, cx + 33*s, y - 27*s, cx + 55*s, y + 9*s - armSwing, active, 2*s.toFloat)
fillOval(g, cx - 65*s, y + 1*s + armSwing, 20*s, 20*s, new Color(160,195,225)); outlineOval(g, cx - 65*s, y + 1*s + armSwing, 20*s, 20*s, active, 2f)
fillOval(g, cx + 45*s, y + 1*s - armSwing, 20*s, 20*s, new Color(160,195,225)); outlineOval(g, cx + 45*s, y + 1*s - armSwing, 20*s, 20*s, active, 2f)
fillRound(g, cx - 25*s, y + 39*s, 18*s, 50*s, 8*s, new Color(110,145,185)); outlineRound(g, cx - 25*s, y + 39*s, 18*s, 50*s, 8*s, active, 2f)
fillRound(g, cx + 7*s, y + 39*s, 18*s, 50*s, 8*s, new Color(110,145,185)); outlineRound(g, cx + 7*s, y + 39*s, 18*s, 50*s, 8*s, active, 2f)
fillOval(g, cx - 31*s, y + 79*s, 34*s, 20*s, new Color(20,36,75)); outlineOval(g, cx - 31*s, y + 79*s, 34*s, 20*s, active, 2.2f)
fillOval(g, cx + 1*s, y + 79*s, 34*s, 20*s, new Color(20,36,75)); outlineOval(g, cx + 1*s, y + 79*s, 34*s, 20*s, active, 2.2f)
line(g, cx - 23*s, y + 91*s, cx - 7*s, y + 91*s, active, 3f); line(g, cx + 9*s, y + 91*s, cx + 25*s, y + 91*s, active, 3f)
fillOval(g, cx - 43*s, y - 43*s, 29*s, 29*s, new Color(45,82,135)); outlineOval(g, cx - 43*s, y - 43*s, 29*s, 29*s, active, 2.4f)
fillOval(g, cx + 14*s, y - 43*s, 29*s, 29*s, new Color(45,82,135)); outlineOval(g, cx + 14*s, y - 43*s, 29*s, 29*s, active, 2.4f)
fillRound(g, cx - 37*s, y - 50*s, 74*s, 91*s, 22*s, new Color(95,130,175)); outlineRound(g, cx - 37*s, y - 50*s, 74*s, 91*s, 22*s, new Color(205,230,250), 2.6f)
fillRound(g, cx - 28*s, y - 37*s, 56*s, 54*s, 12*s, new Color(5,13,40)); outlineRound(g, cx - 28*s, y - 37*s, 56*s, 54*s, 10*s, active, 2.5f)
fillOval(g, cx - 22*s, y - 31*s, 44*s, 44*s, alpha(active, 60)); outlineOval(g, cx - 18*s, y - 27*s, 36*s, 36*s, active, 2.5f)
outlineOval(g, cx - 12*s, y - 21*s, 24*s, 24*s, cfg.theme.secondary, 2f)
fillOval(g, cx - 7*s, y - 16*s, 14*s, 14*s, active); fillOval(g, cx - 3*s, y - 12*s, 6*s, 6*s, Color.WHITE)
fillRound(g, cx - 24*s, y + 38*s, 48*s, 13*s, 5*s, new Color(30,62,108)); outlineRound(g, cx - 24*s, y + 38*s, 48*s, 13*s, 5*s, active, 2f)
fillRound(g, cx - 34*s, y - 126*s, 68*s, 57*s, 17*s, new Color(150,185,215)); outlineRound(g, cx - 34*s, y - 126*s, 68*s, 57*s, 17*s, new Color(225,245,255), 3f)
fillRound(g, cx - 27*s, y - 113*s, 54*s, 28*s, 8*s, new Color(2,8,28)); outlineRound(g, cx - 27*s, y - 113*s, 54*s, 28*s, 8*s, active, 2.3f)
val eyeGlow = 170 + (math.abs(math.sin(t * 3.0)) * 85).toInt
fillOval(g, cx - 19*s, y - 105*s, 13*s, 8*s, alpha(active, eyeGlow)); fillOval(g, cx + 6*s, y - 105*s, 13*s, 8*s, alpha(active, eyeGlow))
fillOval(g, cx - 15*s, y - 104*s, 4*s, 4*s, Color.WHITE); fillOval(g, cx + 10*s, y - 104*s, 4*s, 4*s, Color.WHITE)
line(g, cx - 9*s, y - 82*s, cx + 9*s, y - 82*s, active, 2f)
fillRound(g, cx - 16*s, y - 132*s, 32*s, 8*s, 5*s, new Color(35,70,120)); fillOval(g, cx - 7*s, y - 140*s, 14*s, 14*s, active); fillOval(g, cx - 3*s, y - 136*s, 6*s, 6*s, Color.WHITE)
if (cfg.particles) for (i <- 0 until 34) {
val a = t * 0.7 + i * math.Pi / 17; val r = (88 + (i % 5) * 9) * s
val px = cx + math.cos(a) * r; val py = y - 22*s + math.sin(a * 1.25) * 75*s
fillOval(g, px, py, (2.5 + (i % 3))*s, (2.5 + (i % 3))*s, alpha(if (i % 2 == 0) active else cfg.theme.secondary, 100 + (math.abs(math.sin(t+i))*140).toInt))
}
if (cfg.hologram) {
val hx = cx + 118*s; val hy = y - 22*s + math.sin(t)*4*s
fillRound(g, hx, hy, 144*s, 83*s, 9*s, alpha(new Color(4,12,37), 235)); outlineRound(g, hx, hy, 144*s, 83*s, 9*s, active, 2f)
line(g, hx + 8*s, hy + 21*s, hx + 136*s, hy + 21*s, cfg.theme.secondary, 2f)
g.setFont(new Font("Monospaced", Font.BOLD, math.max(9, (11*s).toInt))); g.setColor(active); g.drawString("BIXUU CORE", (hx+9*s).toInt, (hy+15*s).toInt)
g.setFont(new Font("Monospaced", Font.PLAIN, math.max(8, (9*s).toInt))); g.setColor(whiteBlue); g.drawString("SYSTEM ONLINE", (hx+9*s).toInt, (hy+39*s).toInt)
g.setColor(new Color(80,255,155)); g.drawString("DANCE: ACTIVE", (hx+9*s).toInt, (hy+53*s).toInt)
for (i <- 0 until 8) { val bh = (5 + math.abs(math.sin(t*3+i*0.7))*17)*s; g.setColor(active); g.fill(new Rectangle2D.Double(hx+(9+i*16)*s, hy+65*s-bh, 6*s, bh)) }
}
g.setFont(new Font("SansSerif", Font.BOLD, 17)); g.setColor(active); g.drawString(cfg.title.take(32), 23, 32)
g.setFont(new Font("Monospaced", Font.PLAIN, 11)); g.setColor(whiteBlue); g.drawString("BIXUU ROBOT SYSTEM | SCALA SWING", 23, 50)
g.setColor(new Color(0,255,150)); g.drawString("ONLINE", 23, getHeight - 18)
g.setColor(cfg.theme.secondary); g.drawString("POWER " + (80 + (math.abs(math.sin(t))*20).toInt) + "%", getWidth - 95, getHeight - 18)
g.dispose()
}
}
}
// SINGLE AUTO-START: press Run once in Kojo to open the Bixuu launcher.
BixuuMegaSuite.main(Array.empty[String])