Code Sketch
Bixuu02
Category: Programming
import java.awt._
import java.awt.event._
import java.awt.geom.AffineTransform
import java.awt.image.BufferedImage
import java.io._
import java.net._
import java.security.MessageDigest
import java.util.{Base64, Locale, Properties}
import javax.swing._
import javax.swing.border.EmptyBorder
import scala.collection.mutable
import scala.util.Random
/**
* BIXUU ANIME FIGHTING CARDS - 3D LOOK EDITION
* Scala + Java Swing/AWT; designed for Kojo environments without Scala Swing.
* Visuals use Java2D perspective, glow, gradients and animation (not a true 3D engine).
*/
object BixuuCardsBattle {
case class Fighter(name: String, power: Int, speed: Int, defense: Int,
chakra: Int, special: String, color: Color)
class PlayerAccount(val username: String, val passwordHash: String) {
var wins = 0
var losses = 0
var battles = 0
val cards = mutable.ArrayBuffer[String]()
}
val random = new Random()
val bg = new Color(7, 10, 25)
val panelColor = new Color(18, 24, 47)
val gold = new Color(255, 195, 65)
val textColor = new Color(225, 235, 255)
val purple = new Color(115, 75, 255)
val seriesNames = Vector("Naruto Shippuden", "Demon Slayer", "Jujutsu Kaisen", "Pokemon")
val stats = Vector("POWER", "SPEED", "DEFENSE", "CHAKRA")
val narutoCards = Vector(
Fighter("Naruto Uzumaki",96,94,90,99,"RASENGAN",new Color(255,140,30)),
Fighter("Sasuke Uchiha",97,96,88,98,"CHIDORI",new Color(90,100,220)),
Fighter("Sakura Haruno",86,80,94,78,"SUPER PUNCH",new Color(235,100,150)),
Fighter("Kakashi Hatake",92,93,90,94,"LIGHTNING BLADE",new Color(80,155,195)),
Fighter("Itachi Uchiha",98,95,90,99,"TSUKUYOMI",new Color(170,35,65)),
Fighter("Madara Uchiha",100,96,99,100,"PERFECT SUSANOO",new Color(155,45,50)),
Fighter("Obito Uchiha",97,92,96,98,"KAMUI",new Color(100,75,160)),
Fighter("Pain",96,88,94,98,"ALMIGHTY PUSH",new Color(180,70,75)),
Fighter("Minato Namikaze",95,100,87,96,"FLYING RAJIN",new Color(235,190,55)),
Fighter("Gaara",90,78,99,92,"SAND SHIELD",new Color(175,105,75)),
Fighter("Rock Lee",92,99,88,65,"LOTUS ATTACK",new Color(70,165,95)),
Fighter("Shikamaru Nara",82,78,83,80,"SHADOW JUTSU",new Color(90,130,90)),
Fighter("Jiraiya",94,85,92,96,"SAGE RASENGAN",new Color(200,95,45)),
Fighter("Orochimaru",93,90,94,97,"SNAKE JUTSU",new Color(150,190,130)),
Fighter("Neji Hyuga",88,92,85,88,"GENTLE FIST",new Color(180,195,210)),
Fighter("Tsunade",94,79,98,90,"HEALING POWER",new Color(195,125,100))
)
val demonCards = Vector(
Fighter("Tanjiro Kamado",94,93,88,94,"WATER BREATHING",new Color(50,135,180)),
Fighter("Nezuko Kamado",93,91,92,90,"BLOOD DEMON ART",new Color(190,80,125)),
Fighter("Zenitsu Agatsuma",96,100,78,83,"THUNDERCLAP",new Color(235,195,50)),
Fighter("Inosuke Hashibira",92,96,85,80,"BEAST BREATHING",new Color(100,165,125)),
Fighter("Giyu Tomioka",96,94,94,95,"WATER HASHIRA",new Color(65,100,185)),
Fighter("Shinobu Kocho",88,97,81,91,"INSECT BREATHING",new Color(165,115,210)),
Fighter("Kyojuro Rengoku",97,94,92,94,"FLAME BREATHING",new Color(225,100,35)),
Fighter("Tengen Uzui",94,98,91,89,"SOUND BREATHING",new Color(180,180,190)),
Fighter("Mitsuri Kanroji",93,91,89,90,"LOVE BREATHING",new Color(225,110,160)),
Fighter("Muichiro Tokito",95,98,88,94,"MIST BREATHING",new Color(90,170,175)),
Fighter("Akaza",98,96,96,93,"DESTRUCTIVE DEATH",new Color(195,90,100)),
Fighter("Kokushibo",100,97,98,99,"MOON BREATHING",new Color(145,70,105))
)
val jujutsuCards = Vector(
Fighter("Yuji Itadori",91,93,89,88,"BLACK FLASH",new Color(190,80,105)),
Fighter("Megumi Fushiguro",91,92,86,94,"TEN SHADOWS",new Color(75,95,160)),
Fighter("Nobara Kugisaki",87,88,85,87,"RESONANCE",new Color(195,115,65)),
Fighter("Satoru Gojo",100,99,99,100,"INFINITY",new Color(90,165,230)),
Fighter("Ryomen Sukuna",100,98,99,100,"MALEVOLENT SHRINE",new Color(170,65,70)),
Fighter("Yuta Okkotsu",97,91,93,99,"CURSED ENERGY",new Color(115,155,190)),
Fighter("Maki Zenin",92,98,91,72,"CURSED TOOLS",new Color(75,145,100)),
Fighter("Toge Inumaki",88,89,80,96,"CURSED SPEECH",new Color(170,155,190)),
Fighter("Kento Nanami",93,85,94,88,"OVERTIME",new Color(190,165,90)),
Fighter("Mahito",96,94,91,98,"IDLE TRANSFIGURATION",new Color(145,100,175))
)
val pokemonCards = Vector(
Fighter("Pikachu",88,100,70,87,"THUNDERBOLT",new Color(240,205,45)),
Fighter("Charizard",97,92,91,95,"FIRE BLAST",new Color(235,100,50)),
Fighter("Mewtwo",100,96,92,100,"PSYSTRIKE",new Color(145,115,190)),
Fighter("Eevee",72,85,70,75,"QUICK ATTACK",new Color(180,135,95)),
Fighter("Lucario",93,94,89,92,"AURA SPHERE",new Color(65,115,190)),
Fighter("Greninja",94,100,82,91,"WATER SHURIKEN",new Color(65,145,200)),
Fighter("Gengar",91,92,80,95,"SHADOW BALL",new Color(120,85,165)),
Fighter("Dragonite",96,86,95,94,"DRAGON CLAW",new Color(225,150,75)),
Fighter("Blastoise",93,78,98,93,"HYDRO PUMP",new Color(65,125,200)),
Fighter("Venusaur",91,75,96,90,"SOLAR BEAM",new Color(85,160,100)),
Fighter("Snorlax",88,48,100,85,"BODY SLAM",new Color(110,155,170)),
Fighter("Garchomp",97,95,93,91,"DRAGON RUSH",new Color(110,115,180))
)
def cardsFor(series: String): Vector[Fighter] = {
if (series == "Naruto Shippuden") narutoCards
else if (series == "Demon Slayer") demonCards
else if (series == "Jujutsu Kaisen") jujutsuCards
else pokemonCards
}
def randomFighter(series: String): Fighter = {
val set = cardsFor(series); set(random.nextInt(set.size))
}
def findFighter(series: String, name: String): Fighter = {
cardsFor(series).find(_.name == name).getOrElse(cardsFor(series).head)
}
val dataDir = new File(System.getProperty("user.home"), ".bixuu_anime_fighting_cards_3d")
var frame: JFrame = _
var currentAccount: PlayerAccount = _
var currentSeries = "Naruto Shippuden"
var currentDifficulty = "MEDIUM"
var currentLevel = 1
var playerCard: Fighter = _
var enemyCard: Fighter = _
var selected = false
var battleReady = false
var battleResolved = false
var isLanBattle = false
var currentOpponentName = "ROBOT ENEMY"
var playerView: NinjaCardView = _
var enemyView: NinjaCardView = _
var messageLabel: JLabel = _
var scoreLabel: JLabel = _
var statPanel: JPanel = _
var actionPanel: JPanel = _
var rootPanel: JPanel = _
var statButtons = Vector.empty[JButton]
@volatile var animTick = 0L
def sha256(value: String): String = {
val bytes = MessageDigest.getInstance("SHA-256").digest(value.getBytes("UTF-8"))
bytes.map(b => "%02x".format(b & 0xff)).mkString
}
def accountFile(username: String): File = new File(dataDir, sha256(username.trim.toLowerCase(Locale.ROOT)) + ".properties")
def encodeText(value: String): String = Base64.getEncoder.encodeToString(value.getBytes("UTF-8"))
def decodeText(value: String): String = new String(Base64.getDecoder.decode(value), "UTF-8")
def saveAccount(a: PlayerAccount): Unit = {
dataDir.mkdirs()
val p = new Properties()
p.setProperty("username", a.username)
p.setProperty("passwordHash", a.passwordHash)
p.setProperty("wins", a.wins.toString)
p.setProperty("losses", a.losses.toString)
p.setProperty("battles", a.battles.toString)
p.setProperty("cards", a.cards.map(encodeText).mkString(","))
val out = new FileOutputStream(accountFile(a.username))
try p.store(out, "Bixuu Anime Fighting Cards 3D") finally out.close()
}
def loadAccount(username: String): PlayerAccount = {
val file = accountFile(username)
if (!file.isFile) return null
val p = new Properties()
val in = new FileInputStream(file)
try p.load(in) finally in.close()
val savedName = p.getProperty("username", "")
val savedHash = p.getProperty("passwordHash", "")
if (savedName.isEmpty || savedHash.isEmpty) return null
val a = new PlayerAccount(savedName, savedHash)
try a.wins = p.getProperty("wins", "0").toInt catch { case _: Exception => a.wins = 0 }
try a.losses = p.getProperty("losses", "0").toInt catch { case _: Exception => a.losses = 0 }
try a.battles = p.getProperty("battles", "0").toInt catch { case _: Exception => a.battles = 0 }
val raw = p.getProperty("cards", "")
if (raw.nonEmpty) raw.split(",").filter(_.nonEmpty).foreach { item =>
try a.cards += decodeText(item) catch { case _: Exception => () }
}
a
}
def label(text: String, size: Int, color: Color): JLabel = {
val l = new JLabel(text, SwingConstants.CENTER)
l.setFont(new Font("Segoe UI", Font.BOLD, size)); l.setForeground(color); l
}
def title(text: String): JLabel = {
val l = label(text, 25, gold)
l.setBorder(BorderFactory.createEmptyBorder(14, 6, 14, 6)); l
}
def button(text: String): JButton = {
val b = new JButton(text)
b.setFont(new Font("Segoe UI", Font.BOLD, 13))
b.setForeground(Color.WHITE); b.setBackground(new Color(32, 35, 72))
b.setFocusPainted(false); b.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(125, 110, 255), 1),
BorderFactory.createEmptyBorder(8, 12, 8, 12)))
b.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR))
b.addMouseListener(new MouseAdapter {
override def mouseEntered(e: MouseEvent): Unit = { b.setBackground(new Color(65, 48, 125)); b.repaint() }
override def mouseExited(e: MouseEvent): Unit = { b.setBackground(new Color(32, 35, 72)); b.repaint() }
})
b
}
def plainPanel(layout: LayoutManager): JPanel = {
val p = new JPanel(layout); p.setOpaque(false); p
}
class CosmicPanel(layout: LayoutManager) extends JPanel(layout) {
setOpaque(true); setBackground(bg)
override def paintComponent(graphics: Graphics): Unit = {
val g = graphics.create().asInstanceOf[Graphics2D]
try {
val w = getWidth; val h = getHeight
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g.setPaint(new GradientPaint(0, 0, new Color(8, 10, 27), w, h, new Color(32, 15, 66)))
g.fillRect(0, 0, w, h)
val pulse = (Math.sin(animTick / 18.0) * 0.5 + 0.5)
g.setColor(new Color(45, 75, 190, (15 + pulse * 24).toInt))
g.fillOval(w / 2 - w / 3, h / 2 - h / 3, w * 2 / 3, h * 2 / 3)
var i = 0
while (i < 85) {
val x = Math.floorMod(i * 131 + (animTick * (i % 3 + 1) / 11).toInt, math.max(1, w))
val y = Math.floorMod(i * 79 + (i % 5) * 21, math.max(1, h))
val a = 75 + Math.floorMod(i * 17 + animTick.toInt, 150)
g.setColor(new Color(170, 195, 255, a))
g.fillOval(x, y, if (i % 8 == 0) 3 else 2, if (i % 8 == 0) 3 else 2)
i += 1
}
g.setColor(new Color(90, 65, 210, 30))
for (x <- 0 until w by 48) g.drawLine(x, 0, x - 90, h)
} finally g.dispose()
}
}
def screen(p: JPanel, width: Int, height: Int): Unit = {
frame.setContentPane(p); frame.setSize(width, height); frame.setLocationRelativeTo(null)
frame.revalidate(); frame.repaint(); frame.setVisible(true)
}
def showMessage(text: String, caption: String = "BIXUU", kind: Int = JOptionPane.INFORMATION_MESSAGE): Unit =
JOptionPane.showMessageDialog(frame, text, caption, kind)
def showLogin(): Unit = {
val page = new CosmicPanel(new BorderLayout(12, 12))
page.setBorder(new EmptyBorder(22, 28, 22, 28))
page.add(title("BIXUU 3D ANIME CARD BATTLES"), BorderLayout.NORTH)
val form = new JPanel(new GridLayout(4, 1, 9, 9)); form.setOpaque(false)
form.setBorder(new EmptyBorder(25, 120, 20, 120))
val username = new JTextField(); val password = new JPasswordField()
username.setFont(new Font("Segoe UI", Font.PLAIN, 18)); password.setFont(new Font("Segoe UI", Font.PLAIN, 18))
form.add(label("PLAYER ID / USERNAME", 14, textColor)); form.add(username)
form.add(label("PASSWORD", 14, textColor)); form.add(password)
page.add(form, BorderLayout.CENTER)
val footer = plainPanel(new BorderLayout(8, 8))
footer.add(label("Create an account or log in to continue.", 13, Color.LIGHT_GRAY), BorderLayout.NORTH)
val buttons = new JPanel(new GridLayout(1, 3, 10, 10)); buttons.setOpaque(false)
val login = button("LOGIN"); val create = button("CREATE ACCOUNT"); val restore = button("RESTORE BACKUP")
login.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = {
val name = username.getText.trim; val pass = new String(password.getPassword)
if (name.isEmpty || pass.isEmpty) showMessage("Enter your ID and password.", "LOGIN", JOptionPane.WARNING_MESSAGE)
else try {
val a = loadAccount(name)
if (a == null) showMessage("Account not found. Create it first.", "LOGIN FAILED", JOptionPane.ERROR_MESSAGE)
else if (a.passwordHash != sha256(pass)) showMessage("Incorrect password.", "LOGIN FAILED", JOptionPane.ERROR_MESSAGE)
else { currentAccount = a; showHome() }
} catch { case ex: Exception => showMessage("Login error: " + ex.getMessage, "ERROR", JOptionPane.ERROR_MESSAGE) }
}})
create.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = {
val name = username.getText.trim; val pass = new String(password.getPassword)
if (!name.matches("[A-Za-z0-9_. -]{3,24}")) showMessage("Use 3-24 letters, numbers, spaces, dot, dash or underscore.", "INVALID ID", JOptionPane.WARNING_MESSAGE)
else if (pass.length < 4) showMessage("Password must contain at least 4 characters.", "PASSWORD", JOptionPane.WARNING_MESSAGE)
else if (accountFile(name).exists()) showMessage("This ID already exists. Log in instead.", "ACCOUNT EXISTS", JOptionPane.WARNING_MESSAGE)
else try { currentAccount = new PlayerAccount(name, sha256(pass)); saveAccount(currentAccount); showHome() }
catch { case ex: Exception => showMessage("Could not save account: " + ex.getMessage, "SAVE ERROR", JOptionPane.ERROR_MESSAGE) }
}})
restore.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = restoreBackup() })
buttons.add(login); buttons.add(create); buttons.add(restore); footer.add(buttons, BorderLayout.CENTER)
page.add(footer, BorderLayout.SOUTH); screen(page, 850, 610)
}
def showHome(): Unit = {
val page = new CosmicPanel(new BorderLayout(12, 12)); page.setBorder(new EmptyBorder(16, 22, 18, 22))
page.add(title("THE PLAY BATTLE OF CARDS - 3D EDITION"), BorderLayout.NORTH)
page.add(label("WELCOME, " + currentAccount.username.toUpperCase(Locale.ROOT), 17, textColor), BorderLayout.CENTER)
val menu = new JPanel(new GridLayout(4, 2, 12, 12)); menu.setOpaque(false)
menu.setBorder(new EmptyBorder(15, 55, 20, 55))
val anime = button("ANIME FIGHTING CARDS"); val pokemon = button("POKEMON CARDS")
val multiplayer = button("MULTIPLAYER - AUTO IP / PORT"); val uno = button("UNO CARD GAME")
val profile = button("MY PROFILE"); val collection = button("CARD COLLECTION")
val backup = button("EXPORT ACCOUNT BACKUP"); val logout = button("LOG OUT")
anime.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = showSeriesMenu() })
pokemon.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = showDifficulty("Pokemon") })
multiplayer.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = showRoomBrowser() })
uno.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = startUno() })
profile.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = showProfile() })
collection.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = showCollection() })
backup.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = exportBackup() })
logout.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = { closePeer(); currentAccount = null; showLogin() } })
Vector(anime,pokemon,multiplayer,uno,profile,collection,backup,logout).foreach(b => menu.add(b))
page.add(menu, BorderLayout.SOUTH); screen(page, 950, 710)
}
def showSeriesMenu(): Unit = {
val page = new CosmicPanel(new BorderLayout(10, 10)); page.add(title("CHOOSE YOUR ANIME UNIVERSE"), BorderLayout.NORTH)
val grid = new JPanel(new GridLayout(4, 1, 12, 12)); grid.setOpaque(false); grid.setBorder(new EmptyBorder(25, 100, 25, 100))
val names = Vector("NARUTO SHIPPUDEN", "DEMON SLAYER", "JUJUTSU KAISEN", "BACK TO HOME")
names.zipWithIndex.foreach { case (name, i) =>
val b = button(name); b.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = {
if (i == 3) showHome() else showDifficulty(seriesNames(i))
}}); grid.add(b)
}
page.add(grid, BorderLayout.CENTER); screen(page, 800, 620)
}
def showDifficulty(series: String): Unit = {
val page = new CosmicPanel(new BorderLayout(10, 10)); page.add(title(series.toUpperCase(Locale.ROOT) + " - SELECT DIFFICULTY"), BorderLayout.NORTH)
val options = new JPanel(new GridLayout(4, 1, 12, 12)); options.setOpaque(false); options.setBorder(new EmptyBorder(25, 110, 25, 110))
Vector("EASY", "MEDIUM", "HARD", "BACK").foreach { d =>
val b = button(if (d == "BACK") d else d + " - 30 LEVELS")
b.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = {
if (d == "BACK") { if (series == "Pokemon") showHome() else showSeriesMenu() }
else startCampaign(series, d)
}}); options.add(b)
}
page.add(options, BorderLayout.CENTER); screen(page, 800, 620)
}
def startCampaign(series: String, difficulty: String): Unit = {
closePeer(); isLanBattle = false; currentSeries = series; currentDifficulty = difficulty; currentLevel = 1; showBattle()
}
def statValue(f: Fighter, stat: String): Int = stat match {
case "POWER" => f.power; case "SPEED" => f.speed; case "DEFENSE" => f.defense; case "CHAKRA" => f.chakra; case _ => 0
}
def robotStat(f: Fighter, stat: String): Int = {
val bonus = if (currentDifficulty == "EASY") -7 else if (currentDifficulty == "HARD") 8 else 0
math.max(1, math.min(100, statValue(f, stat) + bonus + (currentLevel - 1) / 5))
}
class NinjaCardView(val fighter: Fighter, val isPlayerCard: Boolean) extends JPanel {
var revealed = false
var chosen = false
var flipScale = 1.0
setPreferredSize(new Dimension(280, 345)); setMinimumSize(new Dimension(190, 250)); setOpaque(false)
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR))
addMouseListener(new MouseAdapter { override def mouseClicked(e: MouseEvent): Unit = {
if (isPlayerCard && revealed && battleReady && !battleResolved) {
selected = true; chosen = true
if (messageLabel != null) messageLabel.setText("CARD SELECTED - CHOOSE A STAT BELOW!")
updateBattleControls(); repaint()
}
}})
override def paintComponent(graphics: Graphics): Unit = {
val g = graphics.create().asInstanceOf[Graphics2D]
try {
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 cw = math.min(w - 26, 270); val ch = math.min(h - 18, 330)
val x = (w - cw) / 2; val y = (h - ch) / 2
g.setColor(new Color(0, 0, 0, 115)); g.fillRoundRect(x + 7, y + 11, cw, ch, 28, 28)
g.translate(w / 2.0, 0.0); g.scale(math.max(0.055, flipScale), 1.0); g.translate(-w / 2.0, 0.0)
if (!revealed) {
g.setPaint(new GradientPaint(x, y, new Color(45, 30, 105), x + cw, y + ch, new Color(12, 15, 48)))
g.fillRoundRect(x, y, cw, ch, 28, 28)
g.setPaint(new GradientPaint(x, y, gold, x + cw, y + ch, purple)); g.setStroke(new BasicStroke(4))
g.drawRoundRect(x + 5, y + 5, cw - 10, ch - 10, 24, 24)
g.setColor(new Color(255, 255, 255, 35)); g.setStroke(new BasicStroke(1.2f))
var r = 0
while (r < 7) { g.drawOval(x + 30 + r * 8, y + 62 + r * 8, cw - 60 - r * 16, cw - 60 - r * 16); r += 1 }
g.setColor(gold); g.setFont(new Font("Segoe UI", Font.BOLD, 29)); drawCentered(g, "BIXUU", x, cw, y + ch / 2 - 4)
g.setFont(new Font("Segoe UI", Font.BOLD, 12)); drawCentered(g, "LEGENDARY CARD", x, cw, y + ch / 2 + 26)
g.setColor(new Color(170, 200, 255, 160)); g.fillOval(x + cw / 2 - 5, y + 65, 10, 10)
} else {
val glow = (Math.sin(animTick / 9.0) * 0.5 + 0.5)
g.setPaint(new GradientPaint(x, y, fighter.color.brighter(), x + cw, y + ch, new Color(11, 15, 32)))
g.fillRoundRect(x, y, cw, ch, 28, 28)
g.setPaint(new GradientPaint(x, y, new Color(8, 12, 28, 225), x + cw, y + ch, new Color(22, 22, 54, 235)))
g.fillRoundRect(x + 8, y + 8, cw - 16, ch - 16, 22, 22)
g.setColor(new Color(fighter.color.getRed, fighter.color.getGreen, fighter.color.getBlue, 155))
g.setStroke(new BasicStroke(3.0f)); g.drawRoundRect(x + 4, y + 4, cw - 8, ch - 8, 25, 25)
g.setColor(new Color(255, 210, 90, (65 + glow * 120).toInt)); g.setStroke(new BasicStroke(1.5f))
g.drawRoundRect(x + 11, y + 11, cw - 22, ch - 22, 20, 20)
g.setFont(new Font("Segoe UI", Font.BOLD, if (fighter.name.length > 15) 15 else 18))
g.setColor(Color.WHITE); drawCentered(g, fighter.name, x, cw, y + 34)
g.setFont(new Font("Segoe UI", Font.BOLD, 11)); g.setColor(fighter.color.brighter())
val family = if (currentSeries == "Pokemon") "POKEMON" else currentSeries.toUpperCase(Locale.ROOT)
drawCentered(g, family, x, cw, y + 55)
val cx = x + cw / 2; val artY = y + 72; val artH = math.max(75, ch / 3)
g.setColor(new Color(fighter.color.getRed, fighter.color.getGreen, fighter.color.getBlue, 45))
g.fillOval(cx - 62, artY - 4, 124, 124)
g.setColor(new Color(255, 255, 255, 25)); g.fillOval(cx - 54, artY + 2, 108, 108)
g.setPaint(new GradientPaint(cx - 38, artY + 8, fighter.color.brighter(), cx + 35, artY + artH, fighter.color.darker()))
g.fillOval(cx - 33, artY + 10, 66, 68)
g.fillRoundRect(cx - 48, artY + 70, 96, 54, 28, 20)
g.setColor(new Color(255, 255, 255, 220)); g.fillOval(cx - 19, artY + 34, 9, 10); g.fillOval(cx + 10, artY + 34, 9, 10)
g.setColor(new Color(20, 20, 35)); g.fillOval(cx - 16, artY + 37, 4, 5); g.fillOval(cx + 13, artY + 37, 4, 5)
g.setColor(Color.WHITE); g.setFont(new Font("Segoe UI", Font.BOLD, 12))
val baseY = y + ch - 91
drawStat(g, x + 21, baseY, "POWER", fighter.power, cw - 42)
drawStat(g, x + 21, baseY + 21, "SPEED", fighter.speed, cw - 42)
drawStat(g, x + 21, baseY + 42, "DEFENSE", fighter.defense, cw - 42)
drawStat(g, x + 21, baseY + 63, "CHAKRA", fighter.chakra, cw - 42)
g.setFont(new Font("Segoe UI", Font.BOLD, 10)); g.setColor(new Color(255, 220, 125))
drawCentered(g, fighter.special, x, cw, y + ch - 13)
if (isPlayerCard && chosen) {
g.setColor(new Color(75, 255, 180, 220)); g.setStroke(new BasicStroke(4)); g.drawRoundRect(x + 2, y + 2, cw - 4, ch - 4, 26, 26)
g.setFont(new Font("Segoe UI", Font.BOLD, 12)); drawCentered(g, "SELECTED!", x, cw, y + ch - 30)
}
}
} finally g.dispose()
}
private def drawCentered(g: Graphics2D, s: String, x: Int, w: Int, y: Int): Unit = {
val m = g.getFontMetrics; g.drawString(s, x + (w - m.stringWidth(s)) / 2, y)
}
private def drawStat(g: Graphics2D, x: Int, y: Int, name: String, value: Int, width: Int): Unit = {
g.setColor(new Color(230, 238, 255)); g.drawString(name, x, y + 10)
val bx = x + 66; val bw = math.max(30, width - 102)
g.setColor(new Color(45, 48, 75)); g.fillRoundRect(bx, y, bw, 8, 8, 8)
g.setPaint(new GradientPaint(bx, y, fighter.color.brighter(), bx + bw, y + 8, fighter.color.darker()))
g.fillRoundRect(bx, y, bw * value / 100, 8, 8, 8)
g.setColor(gold); g.drawString(value.toString, bx + bw + 8, y + 10)
}
}
class ArenaPanel extends JPanel(new GridLayout(1, 2, 16, 8)) {
setOpaque(false)
override def paintComponent(graphics: Graphics): Unit = {
val g = graphics.create().asInstanceOf[Graphics2D]
try {
val w = getWidth; val h = getHeight
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g.setPaint(new GradientPaint(0, 0, new Color(14, 18, 52, 225), w, h, new Color(31, 12, 55, 230)))
g.fillRoundRect(0, 0, w, h, 34, 34)
val pulse = (Math.sin(animTick / 8.0) * 0.5 + 0.5)
g.setColor(new Color(80, 95, 255, (18 + pulse * 28).toInt))
g.fillOval(w / 2 - 110, h / 2 - 115, 220, 220)
var i = 0
while (i < 26) {
val xx = Math.floorMod(i * 83 + (animTick * (1 + i % 4) / 4).toInt, math.max(1, w))
val yy = Math.floorMod(i * 59 + (i % 7) * 13, math.max(1, h))
g.setColor(new Color(120, 185, 255, 70 + (i % 4) * 30))
g.fillOval(xx, yy, 3 + i % 3, 3 + i % 3); i += 1
}
g.setColor(new Color(255, 210, 100, 85)); g.setStroke(new BasicStroke(2f))
g.drawRoundRect(2, 2, w - 4, h - 4, 32, 32)
} finally g.dispose()
}
}
def flipCard(view: NinjaCardView, done: () => Unit): Unit = {
var step = 0
val total = 14
val timer = new Timer(24, new ActionListener { override def actionPerformed(e: ActionEvent): Unit = {
step += 1
val angle = Math.PI * step / total
view.flipScale = math.max(0.045, Math.abs(Math.cos(angle)))
if (step == 7) view.revealed = true
view.repaint()
if (step >= total) { val t = e.getSource.asInstanceOf[Timer]; t.stop(); view.flipScale = 1.0; view.repaint(); done() }
}})
timer.start()
}
def showBattle(): Unit = {
selected = false; battleReady = false; battleResolved = false
if (!isLanBattle) { playerCard = randomFighter(currentSeries); enemyCard = randomFighter(currentSeries); while (enemyCard.name == playerCard.name) enemyCard = randomFighter(currentSeries); currentOpponentName = "ROBOT ENEMY" }
val page = new CosmicPanel(new BorderLayout(10, 10)); page.setBorder(new EmptyBorder(10, 14, 10, 14))
page.add(title(currentSeries.toUpperCase(Locale.ROOT) + " | LEVEL " + currentLevel + "/30" + (if (isLanBattle) " | LAN DUEL" else "")), BorderLayout.NORTH)
val arena = new ArenaPanel()
val left = plainPanel(new BorderLayout(4, 4)); left.add(label("YOUR CARD", 14, textColor), BorderLayout.NORTH)
playerView = new NinjaCardView(playerCard, true); left.add(playerView, BorderLayout.CENTER)
val right = plainPanel(new BorderLayout(4, 4)); right.add(label(currentOpponentName, 14, textColor), BorderLayout.NORTH)
enemyView = new NinjaCardView(enemyCard, false); right.add(enemyView, BorderLayout.CENTER)
arena.add(left); arena.add(right); page.add(arena, BorderLayout.CENTER)
actionPanel = plainPanel(new BorderLayout(7, 7))
messageLabel = label("CARD REVEAL INCOMING...", 15, gold)
actionPanel.add(messageLabel, BorderLayout.NORTH)
statPanel = new JPanel(new GridLayout(1, 4, 8, 8)); statPanel.setOpaque(false)
statButtons = stats.map { stat =>
val b = button("CHOOSE " + stat); b.setEnabled(false)
b.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = fight(stat) })
statPanel.add(b); b
}
actionPanel.add(statPanel, BorderLayout.CENTER)
val footer = plainPanel(new BorderLayout(5, 5))
scoreLabel = label("WINS: " + currentAccount.wins + " | LOSSES: " + currentAccount.losses + " | CARDS: " + currentAccount.cards.size, 12, textColor)
footer.add(scoreLabel, BorderLayout.CENTER)
val back = button("BACK TO HOME")
back.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = { closePeer(); isLanBattle = false; showHome() } })
footer.add(back, BorderLayout.EAST); actionPanel.add(footer, BorderLayout.SOUTH)
page.add(actionPanel, BorderLayout.SOUTH); screen(page, 1050, 820)
flipCard(playerView, () => flipCard(enemyView, () => { battleReady = true; messageLabel.setText("CARDS REVEALED! TAP YOUR CARD, THEN CHOOSE A STAT."); updateBattleControls() }))
}
def updateBattleControls(): Unit = {
statButtons.foreach(_.setEnabled(battleReady && selected && !battleResolved))
if (playerView != null) playerView.repaint()
}
def fight(stat: String): Unit = {
if (!battleReady || !selected || battleResolved) return
if (isLanBattle && peer != null) { fightLan(stat); return }
battleResolved = true; updateBattleControls()
val mine = statValue(playerCard, stat); val theirs = robotStat(enemyCard, stat)
currentAccount.battles += 1
if (mine > theirs) {
currentAccount.wins += 1; currentAccount.cards += (currentSeries + "|" + enemyCard.name); saveAccount(currentAccount)
showResult("YOU WIN!", stat + ": " + mine + " vs " + theirs, "NEW CARD UNLOCKED: " + enemyCard.name, true, false)
} else if (mine < theirs) {
currentAccount.losses += 1; saveAccount(currentAccount)
showResult("ROBOT WINS!", stat + ": " + mine + " vs " + theirs, "Try again in the next battle!", false, false)
} else {
saveAccount(currentAccount); showResult("DRAW!", stat + ": " + mine + " vs " + theirs, "Both cards have equal stats.", false, false)
}
}
def showResult(result: String, detail: String, reward: String, won: Boolean, multiplayerResult: Boolean): Unit = {
scoreLabel.setText("WINS: " + currentAccount.wins + " | LOSSES: " + currentAccount.losses + " | CARDS: " + currentAccount.cards.size)
messageLabel.setText("<html><center><b>" + result + "</b><br>" + detail + "<br>" + reward + "</center></html>")
statButtons.foreach(_.setEnabled(false))
val resultPanel = new JPanel(new GridLayout(2, 1, 6, 6)); resultPanel.setOpaque(false)
resultPanel.add(label(result, 21, if (won) new Color(100, 255, 145) else new Color(255, 180, 100)))
resultPanel.add(label(reward, 13, textColor))
val next = if (multiplayerResult) button("RETURN TO HOME") else if (currentLevel >= 30) button("FINISH CAMPAIGN") else button("NEXT LEVEL")
next.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = {
if (multiplayerResult) { closePeer(); isLanBattle = false; showHome() }
else if (currentLevel >= 30) { showMessage("You completed all 30 levels!\nWins: " + currentAccount.wins + "\nCards: " + currentAccount.cards.size, "CAMPAIGN COMPLETE"); showHome() }
else { currentLevel += 1; showBattle() }
}})
resultPanel.add(next)
actionPanel.remove(statPanel); actionPanel.add(resultPanel, BorderLayout.CENTER)
actionPanel.revalidate(); actionPanel.repaint(); rootPanel = frame.getContentPane.asInstanceOf[JPanel]; rootPanel.revalidate(); rootPanel.repaint()
}
def collectionText: String = {
if (currentAccount.cards.isEmpty) "YOUR COLLECTION IS EMPTY.\n\nWin a battle to unlock a card."
else {
val grouped = currentAccount.cards.groupBy((s: String) => s).toSeq.sortBy(_._1)
val lines = grouped.map { case (key, copies) =>
val parts = key.split("\\|", 2); val series = if (parts.length > 1) parts(0) else "Unknown"; val name = if (parts.length > 1) parts(1) else key
series + " - " + name + " x" + copies.size
}
"MY EARNED CARDS\n\n" + lines.mkString("\n") + "\n\nTOTAL CARDS: " + currentAccount.cards.size + "\nUNIQUE CARDS: " + grouped.size
}
}
def showCollection(): Unit = showTextPage("MY CARD COLLECTION", collectionText)
def showProfile(): Unit = {
val a = currentAccount
showTextPage("MY PROFILE", "PLAYER PROFILE\n\nPlayer ID: " + a.username + "\n\nBattles: " + a.battles + "\nWins: " + a.wins + "\nLosses: " + a.losses + "\nCards collected: " + a.cards.size + "\n\nSave folder:\n" + dataDir.getAbsolutePath)
}
def showTextPage(pageTitle: String, body: String): Unit = {
val page = new CosmicPanel(new BorderLayout(10, 10)); page.setBorder(new EmptyBorder(14, 18, 14, 18)); page.add(title(pageTitle), BorderLayout.NORTH)
val area = new JTextArea(body); area.setEditable(false); area.setLineWrap(true); area.setWrapStyleWord(true)
area.setFont(new Font("Consolas", Font.PLAIN, 15)); area.setForeground(textColor); area.setBackground(panelColor); area.setBorder(new EmptyBorder(14, 14, 14, 14))
page.add(new JScrollPane(area), BorderLayout.CENTER)
val back = button("BACK TO HOME"); back.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = showHome() })
val footer = plainPanel(new FlowLayout(FlowLayout.CENTER)); footer.add(back); page.add(footer, BorderLayout.SOUTH); screen(page, 820, 700)
}
def exportBackup(): Unit = {
if (currentAccount == null) return
try {
saveAccount(currentAccount); val chooser = new JFileChooser(); chooser.setDialogTitle("Export Account Backup")
chooser.setSelectedFile(new File(currentAccount.username + ".bixuu"))
if (chooser.showSaveDialog(frame) == JFileChooser.APPROVE_OPTION) {
val in = accountFile(currentAccount.username).toPath; val out = chooser.getSelectedFile.toPath
java.nio.file.Files.copy(in, out, java.nio.file.StandardCopyOption.REPLACE_EXISTING)
showMessage("Account backup exported.", "BACKUP")
}
} catch { case ex: Exception => showMessage("Backup failed: " + ex.getMessage, "ERROR", JOptionPane.ERROR_MESSAGE) }
}
def restoreBackup(): Unit = {
val chooser = new JFileChooser(); chooser.setDialogTitle("Restore Bixuu Account Backup")
if (chooser.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION) {
try {
val p = new Properties(); val in = new FileInputStream(chooser.getSelectedFile)
try p.load(in) finally in.close()
val username = p.getProperty("username", "").trim
val hash = p.getProperty("passwordHash", "")
if (username.isEmpty || hash.isEmpty) throw new IOException("This is not a valid Bixuu account backup.")
dataDir.mkdirs(); val target = accountFile(username)
if (target.exists()) {
val choice = JOptionPane.showConfirmDialog(frame, "This account already exists. Replace it with the backup?", "RESTORE ACCOUNT", JOptionPane.YES_NO_OPTION)
if (choice != JOptionPane.YES_OPTION) return
}
java.nio.file.Files.copy(chooser.getSelectedFile.toPath, target.toPath, java.nio.file.StandardCopyOption.REPLACE_EXISTING)
showMessage("Account restored. Log in with the original username and password.", "RESTORE COMPLETE")
} catch { case ex: Exception => showMessage("Restore failed: " + ex.getMessage, "RESTORE ERROR", JOptionPane.ERROR_MESSAGE) }
}
}
// ------------------------- AUTOMATIC LAN IP / PORT -------------------------
val DISCOVERY_PORT = 50678 // fixed UDP beacon port; the TCP game port itself is assigned dynamically by the OS
case class RoomEntry(id: String, name: String, series: String, host: String, port: Int, owner: String, lastSeen: Long) {
override def toString: String = name + " | " + series + " | " + owner + " | " + host + ":" + port
}
class LanPeer(val socket: Socket, val input: DataInputStream, val output: DataOutputStream,
val remoteUser: String, val series: String)
@volatile var peer: LanPeer = _
@volatile var activeServer: ServerSocket = _
@volatile var activeBeaconSocket: DatagramSocket = _
@volatile var roomDiscoverySocket: DatagramSocket = _
@volatile var roomDiscoveryRunning = false
@volatile var beaconRunning = false
val discoveredRooms = mutable.LinkedHashMap[String, RoomEntry]()
var roomModel: DefaultListModel[RoomEntry] = _
var roomList: JList[RoomEntry] = _
var roomStatus: JLabel = _
def netEncode(value: String): String = Base64.getUrlEncoder.withoutPadding().encodeToString(value.getBytes("UTF-8"))
def netDecode(value: String): String = new String(Base64.getUrlDecoder.decode(value), "UTF-8")
def localIPAddresses(): String = {
val found = mutable.ArrayBuffer[String]()
try {
val interfaces = NetworkInterface.getNetworkInterfaces
while (interfaces.hasMoreElements) {
val ni = interfaces.nextElement()
if (ni.isUp && !ni.isLoopback) {
val addresses = ni.getInetAddresses
while (addresses.hasMoreElements) {
val a = addresses.nextElement()
if (a.isInstanceOf[Inet4Address] && !a.isLoopbackAddress && !a.isLinkLocalAddress) found += a.getHostAddress
}
}
}
} catch { case _: Exception => () }
if (found.isEmpty) "IP not detected - check Wi-Fi" else found.distinct.mkString(", ")
}
def stopRoomDiscovery(): Unit = {
roomDiscoveryRunning = false
val ds = roomDiscoverySocket; roomDiscoverySocket = null
if (ds != null) try ds.close() catch { case _: Exception => () }
}
def stopBeacon(): Unit = {
beaconRunning = false
val ds = activeBeaconSocket; activeBeaconSocket = null
if (ds != null) try ds.close() catch { case _: Exception => () }
}
def closePeer(): Unit = {
stopRoomDiscovery(); stopBeacon()
val p = peer; peer = null
if (p != null) try p.socket.close() catch { case _: Exception => () }
val s = activeServer; activeServer = null
if (s != null) try s.close() catch { case _: Exception => () }
}
def startBeacon(port: Int, roomName: String, series: String, owner: String): Unit = {
stopBeacon()
try {
val ds = new DatagramSocket(); ds.setBroadcast(true); activeBeaconSocket = ds; beaconRunning = true
val t = new Thread(new Runnable { override def run(): Unit = {
while (beaconRunning && (activeBeaconSocket eq ds)) {
try {
val line = "BIXUU3D|" + netEncode(roomName) + "|" + netEncode(series) + "|" + port + "|" + netEncode(owner)
val bytes = line.getBytes("UTF-8")
val packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("255.255.255.255"), DISCOVERY_PORT)
ds.send(packet); Thread.sleep(900L)
} catch { case _: InterruptedException => beaconRunning = false; case _: Exception => Thread.sleep(500L) }
}
}})
t.setDaemon(true); t.setName("bixuu-lan-beacon"); t.start()
} catch { case ex: Exception => showMessage("Room discovery beacon could not start: " + ex.getMessage, "NETWORK", JOptionPane.WARNING_MESSAGE) }
}
def startRoomDiscovery(): Unit = {
stopRoomDiscovery(); discoveredRooms.synchronized { discoveredRooms.clear() }
try {
val ds = new DatagramSocket(null); ds.setReuseAddress(true); ds.bind(new InetSocketAddress(DISCOVERY_PORT)); ds.setSoTimeout(1000)
roomDiscoverySocket = ds; roomDiscoveryRunning = true
val t = new Thread(new Runnable { override def run(): Unit = {
while (roomDiscoveryRunning && (roomDiscoverySocket eq ds)) {
try {
val buffer = new Array[Byte](2048); val packet = new DatagramPacket(buffer, buffer.length); ds.receive(packet)
val line = new String(packet.getData, packet.getOffset, packet.getLength, "UTF-8"); val p = line.split("\\|", -1)
if (p.length >= 5 && p(0) == "BIXUU3D") {
val entry = RoomEntry(packet.getAddress.getHostAddress + ":" + p(3), netDecode(p(1)), netDecode(p(2)), packet.getAddress.getHostAddress, p(3).toInt, netDecode(p(4)), System.currentTimeMillis())
discoveredRooms.synchronized { discoveredRooms(entry.id) = entry }
SwingUtilities.invokeLater(new Runnable { override def run(): Unit = refreshRoomList() })
}
} catch {
case _: SocketTimeoutException =>
val cutoff = System.currentTimeMillis() - 5000L
discoveredRooms.synchronized { discoveredRooms.retain((id, r) => r.lastSeen >= cutoff) }
SwingUtilities.invokeLater(new Runnable { override def run(): Unit = refreshRoomList() })
case _: Exception => if (roomDiscoveryRunning) try Thread.sleep(250L) catch { case _: Exception => () }
}
}
}})
t.setDaemon(true); t.setName("bixuu-room-discovery"); t.start()
} catch { case ex: Exception => if (roomStatus != null) roomStatus.setText("Auto-scan unavailable. Use JOIN BY IP:PORT. " + ex.getMessage) }
}
def refreshRoomList(): Unit = {
if (roomModel == null || roomList == null) return
val old = Option(roomList.getSelectedValue).map(_.id).getOrElse("")
val rooms = discoveredRooms.synchronized { discoveredRooms.values.toVector.sortBy(r => (r.name.toLowerCase(Locale.ROOT), r.host)) }
roomModel.clear(); rooms.foreach(roomModel.addElement)
if (old.nonEmpty) { val idx = rooms.indexWhere(_.id == old); if (idx >= 0) roomList.setSelectedIndex(idx) }
if (roomStatus != null) roomStatus.setText(if (rooms.isEmpty) "Searching local Wi-Fi/LAN for rooms... Use JOIN BY IP:PORT if none appear." else "Found " + rooms.size + " room(s). Select one and press JOIN.")
}
def showRoomBrowser(): Unit = {
stopRoomDiscovery()
roomModel = new DefaultListModel[RoomEntry](); roomList = new JList[RoomEntry](roomModel)
roomList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); roomList.setFont(new Font("Segoe UI", Font.PLAIN, 14))
roomList.setBackground(panelColor); roomList.setForeground(textColor); roomList.setSelectionBackground(new Color(70, 66, 140))
roomList.addMouseListener(new MouseAdapter { override def mouseClicked(e: MouseEvent): Unit = if (e.getClickCount == 2) joinSelectedRoom() })
val page = new CosmicPanel(new BorderLayout(10, 10)); page.setBorder(new EmptyBorder(15, 18, 15, 18))
page.add(title("MULTIPLAYER ROOMS - AUTO IP / AUTO PORT"), BorderLayout.NORTH)
val center = plainPanel(new BorderLayout(8, 8)); center.add(new JScrollPane(roomList), BorderLayout.CENTER)
roomStatus = label("Starting room scan...", 12, Color.LIGHT_GRAY); center.add(roomStatus, BorderLayout.SOUTH); page.add(center, BorderLayout.CENTER)
val controls = new JPanel(new GridLayout(1, 4, 8, 8)); controls.setOpaque(false)
val create = button("CREATE ROOM"); val join = button("JOIN SELECTED"); val manual = button("JOIN BY IP:PORT"); val back = button("BACK")
create.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = createLanRoom() })
join.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = joinSelectedRoom() })
manual.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = joinByAddress() })
back.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = { stopRoomDiscovery(); showHome() } })
Vector(create, join, manual, back).foreach(b => controls.add(b)); page.add(controls, BorderLayout.SOUTH)
screen(page, 990, 650); startRoomDiscovery()
}
def createLanRoom(): Unit = {
closePeer()
val opts: Array[Object] = seriesNames.map(_.asInstanceOf[Object]).toArray
val choice = JOptionPane.showInputDialog(frame, "Choose the card series:", "CREATE LAN ROOM", JOptionPane.QUESTION_MESSAGE, null, opts, opts(0))
if (choice == null) { showRoomBrowser(); return }
val series = choice.toString
val defaultName = currentAccount.username + "'s Room"
val name = JOptionPane.showInputDialog(frame, "Room name:", defaultName)
if (name == null || name.trim.length < 2) { showRoomBrowser(); return }
val roomName = name.trim
var server: ServerSocket = null
try {
server = new ServerSocket(0) // OS automatically selects a currently free TCP port
server.setSoTimeout(0); activeServer = server
val port = server.getLocalPort; val myCard = randomFighter(series)
startBeacon(port, roomName, series, currentAccount.username)
val page = new CosmicPanel(new BorderLayout(10, 10)); page.setBorder(new EmptyBorder(20, 24, 20, 24))
page.add(title("ROOM CREATED - WAITING FOR YOUR FRIEND"), BorderLayout.NORTH)
val info = "Room: " + roomName + "\nSeries: " + series + "\n\nDetected local IP address(es):\n" + localIPAddresses() + "\n\nAUTO PORT: " + port + "\n\nYour friend can select this room from the room browser. If automatic discovery is blocked, share one IP and this port as IP:PORT.\n\nAllow this app through Windows Firewall on a private network."
val area = new JTextArea(info); area.setEditable(false); area.setFont(new Font("Consolas", Font.PLAIN, 15)); area.setForeground(textColor); area.setBackground(panelColor); area.setBorder(new EmptyBorder(18, 18, 18, 18))
page.add(area, BorderLayout.CENTER)
val cancel = button("CANCEL ROOM"); cancel.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = { closePeer(); showRoomBrowser() } })
val footer = plainPanel(new FlowLayout(FlowLayout.CENTER)); footer.add(cancel); page.add(footer, BorderLayout.SOUTH); screen(page, 800, 580)
val serverRef = server
val t = new Thread(new Runnable { override def run(): Unit = {
var socket: Socket = null
try {
socket = serverRef.accept(); socket.setTcpNoDelay(true)
val in = new DataInputStream(new BufferedInputStream(socket.getInputStream))
val out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream))
val command = in.readUTF(); val remoteName = in.readUTF()
if (command != "JOIN") throw new IOException("Invalid join request")
out.writeUTF("WELCOME"); out.writeUTF(series); out.writeUTF(currentAccount.username); out.writeUTF(myCard.name); out.flush()
val remoteCard = findFighter(series, in.readUTF())
val connected = new LanPeer(socket, in, out, remoteName, series)
SwingUtilities.invokeLater(new Runnable { override def run(): Unit = {
stopBeacon(); activeServer = null; currentSeries = series; currentDifficulty = "MEDIUM"; currentLevel = 1
isLanBattle = true; currentOpponentName = remoteName; playerCard = myCard; enemyCard = remoteCard; peer = connected; showBattle()
}})
try serverRef.close() catch { case _: Exception => () }
} catch {
case ex: Exception =>
if (socket != null) try socket.close() catch { case _: Exception => () }
SwingUtilities.invokeLater(new Runnable { override def run(): Unit = {
if (activeServer eq serverRef) { closePeer(); showMessage("Room closed or connection failed: " + ex.getMessage, "LAN ROOM", JOptionPane.WARNING_MESSAGE); showRoomBrowser() }
}})
}
}})
t.setDaemon(true); t.setName("bixuu-room-host"); t.start()
} catch { case ex: Exception => showMessage("Could not create room: " + ex.getMessage, "ROOM ERROR", JOptionPane.ERROR_MESSAGE); showRoomBrowser() }
}
def joinSelectedRoom(): Unit = {
if (roomList == null || roomList.getSelectedValue == null) { showMessage("Select a room first or press REFRESH by re-opening the browser.", "JOIN ROOM"); return }
val r = roomList.getSelectedValue; stopRoomDiscovery(); connectToRoom(r.host, r.port)
}
def joinByAddress(): Unit = {
val raw = JOptionPane.showInputDialog(frame, "Enter host IP:PORT (example 192.168.1.5:52145):", "JOIN BY IP:PORT", JOptionPane.QUESTION_MESSAGE)
if (raw == null || raw.trim.isEmpty) return
val parts = raw.trim.split(":", -1)
if (parts.length != 2) { showMessage("Enter IP:PORT, for example 192.168.1.5:52145", "INVALID ADDRESS", JOptionPane.WARNING_MESSAGE); return }
val port = try parts(1).toInt catch { case _: Exception => -1 }
if (port < 1 || port > 65535) { showMessage("Port must be between 1 and 65535.", "INVALID PORT", JOptionPane.WARNING_MESSAGE); return }
stopRoomDiscovery(); connectToRoom(parts(0).trim, port)
}
def connectToRoom(host: String, port: Int): Unit = {
val waitPage = new CosmicPanel(new BorderLayout()); waitPage.add(title("CONNECTING TO " + host + ":" + port + " ..."), BorderLayout.CENTER); screen(waitPage, 650, 330)
val t = new Thread(new Runnable { override def run(): Unit = {
var socket: Socket = null
try {
socket = new Socket(); socket.connect(new InetSocketAddress(host, port), 8000); socket.setTcpNoDelay(true)
val in = new DataInputStream(new BufferedInputStream(socket.getInputStream))
val out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream))
out.writeUTF("JOIN"); out.writeUTF(currentAccount.username); out.flush()
val welcome = in.readUTF(); if (welcome != "WELCOME") throw new IOException("Host rejected the join request")
val series = in.readUTF(); val hostName = in.readUTF(); val hostCardName = in.readUTF()
val myCard = randomFighter(series); out.writeUTF(myCard.name); out.flush()
val connected = new LanPeer(socket, in, out, hostName, series); val remoteCard = findFighter(series, hostCardName)
SwingUtilities.invokeLater(new Runnable { override def run(): Unit = {
currentSeries = series; currentDifficulty = "MEDIUM"; currentLevel = 1; isLanBattle = true
currentOpponentName = hostName; playerCard = myCard; enemyCard = remoteCard; peer = connected; showBattle()
}})
} catch {
case ex: Exception =>
if (socket != null) try socket.close() catch { case _: Exception => () }
SwingUtilities.invokeLater(new Runnable { override def run(): Unit = {
showMessage("Could not join room: " + ex.getMessage + "\nCheck the IP, port, Wi-Fi and Firewall settings.", "JOIN ERROR", JOptionPane.ERROR_MESSAGE)
showRoomBrowser()
}})
}
}})
t.setDaemon(true); t.setName("bixuu-room-join"); t.start()
}
def fightLan(stat: String): Unit = {
val connected = peer
if (connected == null) { showMessage("Multiplayer connection was lost.", "NETWORK", JOptionPane.ERROR_MESSAGE); closePeer(); isLanBattle = false; showHome(); return }
battleResolved = true; updateBattleControls(); messageLabel.setText("SENDING YOUR MOVE - WAITING FOR FRIEND...")
val t = new Thread(new Runnable { override def run(): Unit = {
try {
connected.output.synchronized { connected.output.writeUTF(stat); connected.output.flush() }
val otherStat = connected.input.readUTF()
SwingUtilities.invokeLater(new Runnable { override def run(): Unit = finishLanBattle(stat, otherStat) })
} catch { case ex: Exception => SwingUtilities.invokeLater(new Runnable { override def run(): Unit = {
showMessage("Connection lost: " + ex.getMessage, "MULTIPLAYER ERROR", JOptionPane.ERROR_MESSAGE); closePeer(); isLanBattle = false; showHome()
}}) }
}})
t.setDaemon(true); t.setName("bixuu-lan-fight"); t.start()
}
def finishLanBattle(myStat: String, otherStat: String): Unit = {
val mine = statValue(playerCard, myStat); val theirs = statValue(enemyCard, otherStat); currentAccount.battles += 1
if (mine > theirs) {
currentAccount.wins += 1; currentAccount.cards += (currentSeries + "|" + enemyCard.name); saveAccount(currentAccount)
showResult("YOU WIN THE LAN DUEL!", myStat + ": " + mine + " vs friend's " + otherStat + ": " + theirs, "NEW CARD: " + enemyCard.name, true, true)
} else if (mine < theirs) {
currentAccount.losses += 1; saveAccount(currentAccount)
showResult("YOUR FRIEND WINS!", myStat + ": " + mine + " vs friend's " + otherStat + ": " + theirs, "Play again to win a card!", false, true)
} else { saveAccount(currentAccount); showResult("LAN DUEL DRAW!", myStat + ": " + mine + " vs friend's " + otherStat + ": " + theirs, "Both values match.", false, true) }
}
// ------------------------------- UNO -------------------------------
case class UnoCard(colorName: String, value: Int) { def display: String = colorName + " " + value }
val unoColors = Vector("RED", "BLUE", "GREEN", "YELLOW")
var unoDeck = mutable.ArrayBuffer[UnoCard]()
var unoDiscard = mutable.ArrayBuffer[UnoCard]()
var unoPlayerHand = mutable.ArrayBuffer[UnoCard]()
var unoRobotHand = mutable.ArrayBuffer[UnoCard]()
var unoFinished = false
var unoStatus = ""
def unoColor(name: String): Color = name match {
case "RED" => new Color(210, 55, 55); case "BLUE" => new Color(45, 95, 210)
case "GREEN" => new Color(40, 145, 85); case _ => new Color(235, 205, 55)
}
def shuffleUnoDeck(): Unit = {
val cards = mutable.ArrayBuffer[UnoCard]()
unoColors.foreach { c => cards += UnoCard(c, 0); for (n <- 1 to 9) { cards += UnoCard(c, n); cards += UnoCard(c, n) } }
unoDeck.clear(); unoDeck ++= random.shuffle(cards.toVector)
}
def startUno(): Unit = {
closePeer(); isLanBattle = false; unoFinished = false; shuffleUnoDeck(); unoDiscard.clear(); unoPlayerHand.clear(); unoRobotHand.clear()
for (i <- 0 until 7) { unoPlayerHand += unoDeck.remove(0); unoRobotHand += unoDeck.remove(0) }
unoDiscard += unoDeck.remove(0); unoStatus = "Match the colour or number."; showUnoGame()
}
def unoPlayable(c: UnoCard): Boolean = { val top = unoDiscard.last; c.colorName == top.colorName || c.value == top.value }
def drawUnoFromDeck(): UnoCard = {
if (unoDeck.isEmpty && unoDiscard.size > 1) { val top = unoDiscard.last; unoDeck ++= random.shuffle(unoDiscard.dropRight(1).toVector); unoDiscard.clear(); unoDiscard += top }
if (unoDeck.nonEmpty) unoDeck.remove(0) else UnoCard("RED", 0)
}
def showUnoGame(): Unit = {
val page = new CosmicPanel(new BorderLayout(8, 8)); page.setBorder(new EmptyBorder(12, 15, 12, 15)); page.add(title("BIXUU UNO CARD GAME"), BorderLayout.NORTH)
val center = plainPanel(new BorderLayout(8, 8)); center.add(label("ROBOT CARDS: " + unoRobotHand.size + " | TOP CARD: " + unoDiscard.last.display, 17, gold), BorderLayout.NORTH)
val handPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 6)); handPanel.setBackground(panelColor)
for (i <- unoPlayerHand.indices) {
val c = unoPlayerHand(i); val idx = i; val b = new JButton(c.display); b.setPreferredSize(new Dimension(95, 60)); b.setFont(new Font("Segoe UI", Font.BOLD, 12))
b.setBackground(unoColor(c.colorName)); b.setForeground(if (c.colorName == "YELLOW") Color.BLACK else Color.WHITE); b.setEnabled(unoPlayable(c))
b.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = playUnoCard(idx) }); handPanel.add(b)
}
val scroll = new JScrollPane(handPanel); scroll.setPreferredSize(new Dimension(780, 170)); center.add(scroll, BorderLayout.CENTER)
center.add(label(unoStatus, 13, textColor), BorderLayout.SOUTH); page.add(center, BorderLayout.CENTER)
val footer = new JPanel(new GridLayout(1, 2, 10, 10)); footer.setOpaque(false)
val draw = button("DRAW CARD"); val back = button("BACK TO HOME"); draw.setEnabled(!unoPlayerHand.exists(unoPlayable))
draw.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = drawUnoCard() })
back.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = showHome() })
footer.add(draw); footer.add(back); page.add(footer, BorderLayout.SOUTH); screen(page, 930, 570)
}
def playUnoCard(index: Int): Unit = {
if (unoFinished || index < 0 || index >= unoPlayerHand.size) return
val c = unoPlayerHand(index); if (!unoPlayable(c)) return
unoPlayerHand.remove(index); unoDiscard += c
if (unoPlayerHand.isEmpty) { finishUno(true); return }
unoStatus = "You played " + c.display + ". Robot's turn."; robotUnoTurn()
}
def drawUnoCard(): Unit = {
if (unoFinished) return
if (unoPlayerHand.exists(unoPlayable)) { unoStatus = "You already have a playable card."; showUnoGame() }
else {
val c = drawUnoFromDeck(); unoPlayerHand += c
if (unoPlayable(c)) { unoStatus = "You drew " + c.display + ". You may play it."; showUnoGame() }
else { unoStatus = "You drew " + c.display + ". Robot's turn."; robotUnoTurn() }
}
}
def robotUnoTurn(): Unit = {
if (unoFinished) return
var choices = unoRobotHand.indices.filter(i => unoPlayable(unoRobotHand(i)))
if (choices.isEmpty) { unoRobotHand += drawUnoFromDeck(); choices = unoRobotHand.indices.filter(i => unoPlayable(unoRobotHand(i))) }
if (choices.nonEmpty) { val c = unoRobotHand.remove(choices(random.nextInt(choices.size))); unoDiscard += c; unoStatus = "Robot played " + c.display + "."; if (unoRobotHand.isEmpty) { finishUno(false); return } }
else unoStatus = "Robot cannot play and passes."
showUnoGame()
}
def finishUno(playerWon: Boolean): Unit = {
if (unoFinished) return; unoFinished = true; currentAccount.battles += 1
if (playerWon) currentAccount.wins += 1 else currentAccount.losses += 1
saveAccount(currentAccount); showMessage(if (playerWon) "UNO! YOU WIN!" else "ROBOT WINS THIS UNO GAME!", "UNO RESULT"); showHome()
}
def run(): Unit = SwingUtilities.invokeLater(new Runnable { override def run(): Unit = {
frame = new JFrame("Bixuu Anime Fighting Cards - 3D Edition")
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.setMinimumSize(new Dimension(760, 600))
dataDir.mkdirs()
val animation = new Timer(33, new ActionListener { override def actionPerformed(e: ActionEvent): Unit = { animTick += 1; if (frame != null) frame.repaint() } })
animation.start()
showLogin()
}})
}
BixuuCardsBattle.run()