From 020ab3f1f88609f5d49a7f40c4be020d1afc017f Mon Sep 17 00:00:00 2001 From: Dorian Date: Sun, 8 Mar 2026 22:16:50 +0000 Subject: [PATCH] refactor: extract fight types and constants Co-Authored-By: Claude Opus 4.6 --- frontend/src/game/FightScene.ts | 116 ++------------------------- frontend/src/game/fight/constants.ts | 91 +++++++++++++++++++++ frontend/src/game/fight/types.ts | 26 ++++++ 3 files changed, 122 insertions(+), 111 deletions(-) create mode 100644 frontend/src/game/fight/constants.ts create mode 100644 frontend/src/game/fight/types.ts diff --git a/frontend/src/game/FightScene.ts b/frontend/src/game/FightScene.ts index 85a4a81..7a27d1b 100644 --- a/frontend/src/game/FightScene.ts +++ b/frontend/src/game/FightScene.ts @@ -1,8 +1,10 @@ import kaplay from 'kaplay' import type { GameObj, SpriteComp, PosComp, ScaleComp, AnchorComp, OpacityComp, ColorComp, RotateComp, ZComp, LoadSpriteOpt } from 'kaplay' -type Fighter = GameObj import { generateSpriteSheet, generateJudgeSpriteSheet, generateHumanSpriteSheet, generateHumanFighterSpriteSheet, getBotColors, FRAME_SIZE, MAX_FRAMES, TOTAL_ROWS, ANIMATIONS, JUDGE_ANIMATIONS, JUDGE_MAX_FRAMES, JUDGE_ROWS, HUMAN_ANIMATIONS, HUMAN_MAX_FRAMES, HUMAN_ROWS, type SpriteCustomization } from './sprites' +import type { Fighter, FightSceneConfig, RoundEvent } from './fight/types' +export type { FightSceneConfig, RoundEvent } from './fight/types' +import { ARENA_THEMES, spriteAnims, CHALLENGE_THEMED, TIER_ULTIMATES, CREATOR_MOVES, CREATOR_ULTIMATES } from './fight/constants' import { sfxPunch, sfxKick, sfxSpecial, sfxCritical, sfxGunshot, sfxBulletHit, sfxJetpack, sfxExplosion, sfxKO, sfxWin, sfxWinAnnounce, sfxPerfect, @@ -27,120 +29,12 @@ import { sfxMissionFailed, sfxCrickets, sfxSadViolin, sfxEmotionalDamage, sfxDunDunDun, } from './audio' -export interface FightSceneConfig { - canvas: HTMLCanvasElement - botA: { name: string; seed: string; tier: number; archetype?: string; customization?: SpriteCustomization; wins?: number; losses?: number } - botB: { name: string; seed: string; tier: number; archetype?: string; customization?: SpriteCustomization; wins?: number; losses?: number } - arena: string - onReady?: () => void -} -export interface RoundEvent { - round: number - challengeType: string - winnerId: string | null - botAId: string - botBId: string - narration: string - isCritical: boolean - botAScore: number - botBScore: number - botAResponse?: string - botBResponse?: string -} -const ARENA_THEMES: Record = { - datacenter: { bg: '#0a0a1a', ground: '#1a1a3a', accent: '#00f0ff' }, - stackoverflow_ruins: { bg: '#1a0f00', ground: '#2a1f10', accent: '#f48024' }, - gpu_graveyard: { bg: '#0a0a0a', ground: '#1a1a1a', accent: '#76b900' }, - prompt_dungeon: { bg: '#0f0a1a', ground: '#1f1a2a', accent: '#b83dff' }, - silicon_valley_dojo: { bg: '#0a1a0a', ground: '#1a2a1a', accent: '#00ff41' }, - paper_mill: { bg: '#1a1a10', ground: '#2a2a20', accent: '#f0e68c' }, - localhost: { bg: '#000000', ground: '#111111', accent: '#00ff41' }, - the_cloud: { bg: '#0a0f1a', ground: '#1a1f2a', accent: '#4488ff' }, - hacker_news: { bg: '#1a0f00', ground: '#2a1f10', accent: '#ff6600' }, - the_singularity: { bg: '#1a0020', ground: '#2a0030', accent: '#ff00ff' }, - beach: { bg: '#0a2040', ground: '#c2a060', accent: '#ffcc00' }, - desert: { bg: '#1a1000', ground: '#a08040', accent: '#ff8800' }, - forest: { bg: '#051005', ground: '#1a3010', accent: '#44cc22' }, - jungle: { bg: '#081808', ground: '#1a3818', accent: '#33ff66' }, - outer_space: { bg: '#000008', ground: '#0a0a20', accent: '#8888ff' }, - colosseum: { bg: '#1a0f08', ground: '#3a2a18', accent: '#d4a030' }, - kitchen_stadium: { bg: '#1a0a0a', ground: '#2a1a10', accent: '#ff4400' }, - haunted_mansion: { bg: '#0a0510', ground: '#1a1020', accent: '#8844cc' }, - meme_dimension: { bg: '#1a0820', ground: '#2a1830', accent: '#ff44ff' }, - space_station: { bg: '#000010', ground: '#0a0a28', accent: '#44aaff' }, - junkyard: { bg: '#0f0f0a', ground: '#2a2a1a', accent: '#aa8833' }, - concert_hall: { bg: '#10001a', ground: '#20102a', accent: '#ff22aa' }, - savanna: { bg: '#1a1808', ground: '#8a7830', accent: '#dd9922' }, - server_room: { bg: '#000a05', ground: '#0a1a10', accent: '#00ff88' }, - sports_arena: { bg: '#0a0a1a', ground: '#1a2a4a', accent: '#ff3333' }, - bitcoin_mine: { bg: '#0a0808', ground: '#1a1210', accent: '#f7931a' }, - lightning_node: { bg: '#0a0018', ground: '#1a1030', accent: '#ffdd00' }, - satoshis_garage: { bg: '#0f0f0a', ground: '#1f1f14', accent: '#a0a080' }, - mempool: { bg: '#100a18', ground: '#201828', accent: '#cc44ff' }, - silk_road_ruins: { bg: '#0a0a0a', ground: '#181818', accent: '#338833' }, - block_height_tower: { bg: '#080810', ground: '#181828', accent: '#4488dd' }, - hodl_cave: { bg: '#080604', ground: '#1a1410', accent: '#f7931a' }, - whale_pool: { bg: '#001020', ground: '#0a2040', accent: '#0088ff' }, - node_farm: { bg: '#000a00', ground: '#0a1a0a', accent: '#22ff44' }, - genesis_block: { bg: '#0a0a00', ground: '#1a1a10', accent: '#ffd700' }, - pizza_day_parlor: { bg: '#1a0a00', ground: '#2a1a08', accent: '#ff6622' }, - difficulty_adjustment: { bg: '#100810', ground: '#201820', accent: '#ff4488' }, - fifty_one_percent: { bg: '#1a0000', ground: '#2a0808', accent: '#ff0000' }, - timechain_temple: { bg: '#08080a', ground: '#18182a', accent: '#88aaff' }, - nostr_relay_room: { bg: '#0a0014', ground: '#1a1028', accent: '#8844ff' }, -} -const spriteAnims = { - idle: { from: 0, to: ANIMATIONS.idle.frames - 1, loop: true, speed: 6 }, - attack: { from: MAX_FRAMES, to: MAX_FRAMES + ANIMATIONS.attack.frames - 1, loop: false, speed: 12 }, - kick: { from: MAX_FRAMES * 2, to: MAX_FRAMES * 2 + ANIMATIONS.kick.frames - 1, loop: false, speed: 10 }, - special: { from: MAX_FRAMES * 3, to: MAX_FRAMES * 3 + ANIMATIONS.special.frames - 1, loop: false, speed: 8 }, - hit: { from: MAX_FRAMES * 4, to: MAX_FRAMES * 4 + ANIMATIONS.hit.frames - 1, loop: false, speed: 8 }, - knockback: { from: MAX_FRAMES * 5, to: MAX_FRAMES * 5 + ANIMATIONS.knockback.frames - 1, loop: false, speed: 8 }, - ko: { from: MAX_FRAMES * 6, to: MAX_FRAMES * 6 + ANIMATIONS.ko.frames - 1, loop: false, speed: 6 }, - win: { from: MAX_FRAMES * 7, to: MAX_FRAMES * 7 + ANIMATIONS.win.frames - 1, loop: true, speed: 6 }, -} -// Challenge type -> themed choreography (shown ~60% of the time) + generic fallbacks -const CHALLENGE_THEMED: Record = { - speed_blitz: { themed: ['afterimageDash', 'lightningRush', 'rapidFlurry', 'tornadoSpin', 'corkscrewDive', 'warpDrive', 'backflipKick', 'bulletTimeAttack', 'lightningBolt', 'channelForceClose', 'segwitSlash', 'doubleSpend'], generic: ['dashPunch', 'multiHit', 'fullScreenDash', 'dashThrough', 'zoomRush', 'katanaCombo', 'cycloneKick', 'helicopterArms', 'breakdanceSweep'] }, - riddle: { themed: ['riddleBarrage', 'portalPunch', 'cloneStrike', 'mindBlast', 'hexCurse'], generic: ['teleportStrike', 'projectile', 'zoomRush', 'whipCrack', 'bombThrow', 'captchaTrap', 'glitchBeam'] }, - code_golf: { themed: ['golfClubSmash', 'golfCartDrive', 'stackOverflow', 'segfault', 'blueScreen'], generic: ['projectile', 'flyingKick', 'dashPunch', 'rapidFlurry', 'hammerSmash', 'bodySlam', 'homeRunSwing'] }, - roast_battle: { themed: ['fireBreath', 'dragonBreath', 'fireball', 'lavaSplash', 'heatVision', 'volcanoErupt'], generic: ['gunBurst', 'jetpackDive', 'multiHit', 'zoomRush', 'laserBeam', 'chainsawRev', 'rocketLauncher', 'dynamiteBlast'] }, - hallucination_check: { themed: ['cloneStrike', 'portalPunch', 'shadowClone', 'glitchBeam', 'voidRift'], generic: ['teleportStrike', 'dashThrough', 'riddleBarrage', 'rapidFlurry', 'sniperShot', 'darkVoid', 'memeBeam'] }, - token_economy: { themed: ['coinShower', 'bitcoinCrash', 'nftRugPull', 'vendingDrop', 'popupSpam', 'blockReward', 'utxoScatter', 'feeSpike', 'satPerByte', 'stackSatsBlast', 'rugPullYank', 'dustAttack', 'multisigStrike'], generic: ['dashPunch', 'flyingKick', 'uppercut', 'zoomRush', 'bombThrow', 'minigunSpray', 'pocketCannon'] }, - creative_writing: { themed: ['penStab', 'scrollBlast', 'bookStorm', 'enchantedArrow', 'newspaperRoll'], generic: ['jetpackDive', 'aerialSlam', 'projectile', 'rapidFlurry', 'katanaCombo', 'arcaneBarrage', 'memeBeam'] }, - math_blitz: { themed: ['mathAttack', 'dataStream', 'pixelBeam', 'hackerAttack', 'chainLightning', 'merkleRoot', 'difficultyRetarget', 'halvingStrike'], generic: ['multiHit', 'dashPunch', 'fullScreenDash', 'rapidFlurry', 'laserBeam', 'hammerSmash', 'pinballCombo'] }, - trap_card: { themed: ['trapCardAttack', 'captchaTrap', 'spiderWeb', 'flashBang', 'smokeBomb'], generic: ['gunBurst', 'teleportStrike', 'groundPound', 'zoomRush', 'sniperShot', 'rocketLauncher', 'minigunSpray'] }, - food_fight: { themed: ['pizzaSlam', 'bananaFling', 'pieSmash', 'hotdogWhip', 'watermelonBomb', 'sushiBarrage', 'burgerToss', 'tacoStorm', 'eggBombard', 'tomatoBarrage', 'sushiRoll', 'tacoSlam', 'iceCreamFreeze', 'wokToss', 'cakeSmash', 'curryBlast', 'lobsterClaw', 'steakSlap', 'watermelonBowl'], generic: ['popcornBlast', 'iceCreamFling', 'donutBarrage', 'cookieFling', 'nachoVolley', 'meatballStorm', 'candyBarrage'] }, - wrestling_match: { themed: ['piledriver', 'powerbomb', 'chokeslam', 'tombstone', 'rko', 'clothesline', 'suplex', 'germanSuplex'], generic: ['bodySlam', 'grappleFlurry', 'hurricanrana', 'moonsault', 'elbowDrop', 'spear', 'stunner'] }, - music_battle: { themed: ['bassDrop', 'guitarSolo', 'drumSolo', 'dubstepCannon', 'beatboxBlast', 'vinylScratch'], generic: ['airHornBlast', 'vuvuzelaBlast', 'sonicWave', 'bassDropBeam', 'karaokeAttack', 'airGuitar'] }, - magic_duel: { themed: ['kamehameha', 'spiritBomb', 'arcaneBarrage', 'darkVoid', 'holySmite', 'shadowClone', 'hexCurse', 'vortexPull', 'gravityCrush', 'darkPulse', 'prismBeam'], generic: ['fireball', 'iceLance', 'thunderStrike', 'portalPunch', 'crystalShards', 'scrollBlast', 'potionThrow'] }, - sports_showdown: { themed: ['homeRunSwing', 'slapShot', 'servingAce', 'fieldGoalKick', 'bodyCheck', 'soccerKick', 'basketballDunk'], generic: ['baseballBat', 'tennisRacket', 'hockeyStick', 'bowlingBallRoll', 'tennisBallVolley', 'dropKick'] }, - nature_clash: { themed: ['treeSmash', 'icebergDrop', 'lavaSplash', 'tornadoFling', 'tsunamiWave', 'avalanche', 'earthquakeStrike'], generic: ['vineWhip', 'sandstorm', 'thornBarrage', 'pollenCloud', 'windSlash', 'meteorStrike'] }, - space_war: { themed: ['blackHole', 'photonTorpedo', 'tractor_beam', 'warpDrive', 'alienAbduction', 'ionCannon', 'cosmicRay', 'bulletTimeAttack', 'novaBlast', 'photonCannon', 'sonicBoom', 'glacialRay'], generic: ['laserSword', 'plasmaSword', 'asteroidBelt', 'satelliteLaser', 'plasmaBeam', 'solarFlare'] }, - hack_battle: { themed: ['hackerAttack', 'bugSwarm', 'stackOverflow', 'segfault', 'blueScreen', 'malwareInject', 'bulletTimeAttack', 'hashSmash', 'miningRig', 'nodeSync', 'nonceGrind', 'proofOfWork'], generic: ['fourOhFour', 'ctrlAltDelete', 'aiUprising', 'captchaTrap', 'popupSpam', 'dataStream', 'glitchBeam'] }, - meme_war: { themed: ['selfieStrike', 'dabAttack', 'flossAttack', 'yeetThrow', 'tPoseAssert', 'emojiBarrage', 'memeBeam', 'selfieStun', 'vibeCheck', 'rubberDucky', 'confettiPop', 'touchGrassSmack'], generic: ['rubberChicken', 'fingerGuns', 'micDrop', 'ratioAttack', 'capThrow', 'touchGrass', 'noScope'] }, - animal_kingdom: { themed: ['sharkBite', 'bearSwipe', 'eagleDive', 'bullCharge', 'gorillaSlam', 'wolfPack', 'batSwarm'], generic: ['snakeLunge', 'scorpionSting', 'crabPinch', 'spiderWeb', 'beeSwarm', 'catScratch', 'dogPile', 'dolphinFlip'] }, - demolition: { themed: ['dynamiteBlast', 'c4Detonation', 'nukeStrike', 'grenadeBlast', 'rocketLauncher', 'volcanoErupt'], generic: ['fireworksBurst', 'partyPopper', 'pinataSmash', 'cherryBomb', 'confettiCannon', 'anvilDrop', 'pianoDrop'] }, - vehicle_mayhem: { themed: ['motorbikeCharge', 'carSmash', 'tankRoll', 'helicopterStrike', 'zamboniCrush', 'tractorPlow'], generic: ['shoppingCart', 'forkliftCharge', 'airplaneSwoop', 'rocketRide', 'unicycleRun', 'golfCartDrive', 'boatCannon'] }, - medieval_combat: { themed: ['swordSlash', 'katanaCombo', 'battleAxe', 'maceSwing', 'crossbowBolt', 'shieldBash', 'nunchakuSpin', 'tridentThrust', 'morningstar', 'scytheSlash', 'warhammerCrush'], generic: ['flailSwing', 'holyWater', 'dragonBreath', 'enchantedArrow', 'laserSword', 'scrollBlast'] }, - retro_mode: { themed: ['dashPunch', 'uppercut', 'rapidFlurry', 'flyingKick', 'multiHit', 'fullScreenDash', 'backflipKick', 'cycloneKick'], generic: ['bodySlam', 'grappleFlurry', 'suplex', 'katanaCombo', 'breakdanceSweep', 'corkscrewDive', 'pinballCombo'] }, -} -// Tier-gated ultimate move pools — cumulative (tier 3 bot can use tier 2+3 moves) -const TIER_ULTIMATES: Record = { - 2: ['ultimateRapidBarrage', 'ultimateOrbitalStrike', 'ultimateCloneSurround', 'ultimateGatlingFury', 'ultimateEarthquake', 'ultimateBouncingFury', 'ultimateTripleBeam', 'ultimateMegaThrow', 'ultimateTornadoGrab', 'ultimateMeteorShower'], - 3: ['ultimateScreenNuke', 'ultimateBlackHoleVortex', 'ultimateDimensionalRift', 'ultimateTimeSlow', 'ultimateSummonArmy', 'ultimateHellfireRain', 'ultimateCrystalCage', 'ultimateGigaDrill', 'ultimateSuperSlam', 'ultimateAvalancheDrop', 'ultimateBulletTimeBarrage'], - 4: ['ultimateFinaleRush', 'ultimateMeteorCrash', 'ultimateScreenShatter', 'ultimateGravityReverse', 'ultimateInfiniteCombo', 'ultimatePlasmaStorm', 'ultimateFrozenCoffin', 'ultimateLavaGeyser', 'ultimateChainGrab', 'ultimateRocketBarrage'], - 5: ['ultimateArmageddon', 'ultimateOmegaBeam', 'ultimateBeyondTheScreen', 'ultimateMatrixDodge', 'ultimateWorldEnder', 'ultimateAbsoluteZero', 'ultimateSolarCannon', 'ultimatePhantomStrike', 'ultimateGodPunch', 'ultimateVoidCollapse'], -} -// The Creator — exclusive moves (50% ultimate chance, 100% on crits, custom pool) -const CREATOR_MOVES = ['bitcoinRain', 'lightningInvoice', 'satoshiStrike', 'hashRateOverload', 'blockchainSlam', 'creatorMode', 'morphStrike', 'divineIntervention', 'bitcoinSwarm', 'satoshiTornado', 'hodlWave', 'bitcoinBarrage', 'bulletTimeAttack'] -const CREATOR_ULTIMATES = ['ultimateGenesisBlock', 'ultimateTheHalving', 'ultimateFullNodeCrush', 'ultimateSatoshiVision'] function pickChoreography(challengeType: string, isCritical: boolean, _round: number, attackerTier: number = 0, attackerArchetype?: string): string { // THE CREATOR: special move selection — 50% ultimate, 60% creator moves, rest normal @@ -10693,8 +10587,8 @@ export async function createFightScene(config: FightSceneConfig) { } // Parse and animate each bot's combo moves - const movesA = (event.botAResponse || '').split('|').map(s => s.trim()).filter(Boolean).slice(0, 3) - const movesB = (event.botBResponse || '').split('|').map(s => s.trim()).filter(Boolean).slice(0, 3) + const movesA = (event.botAResponse || '').split('|').map((s: string) => s.trim()).filter(Boolean).slice(0, 3) + const movesB = (event.botBResponse || '').split('|').map((s: string) => s.trim()).filter(Boolean).slice(0, 3) // Flash moves in parallel for both pads const animateCombo = async (side: 'a' | 'b', moves: string[]) => { diff --git a/frontend/src/game/fight/constants.ts b/frontend/src/game/fight/constants.ts new file mode 100644 index 0000000..b45ae04 --- /dev/null +++ b/frontend/src/game/fight/constants.ts @@ -0,0 +1,91 @@ +import { MAX_FRAMES, ANIMATIONS } from '../sprites' + +export const ARENA_THEMES: Record = { + datacenter: { bg: '#0a0a1a', ground: '#1a1a3a', accent: '#00f0ff' }, + stackoverflow_ruins: { bg: '#1a0f00', ground: '#2a1f10', accent: '#f48024' }, + gpu_graveyard: { bg: '#0a0a0a', ground: '#1a1a1a', accent: '#76b900' }, + prompt_dungeon: { bg: '#0f0a1a', ground: '#1f1a2a', accent: '#b83dff' }, + silicon_valley_dojo: { bg: '#0a1a0a', ground: '#1a2a1a', accent: '#00ff41' }, + paper_mill: { bg: '#1a1a10', ground: '#2a2a20', accent: '#f0e68c' }, + localhost: { bg: '#000000', ground: '#111111', accent: '#00ff41' }, + the_cloud: { bg: '#0a0f1a', ground: '#1a1f2a', accent: '#4488ff' }, + hacker_news: { bg: '#1a0f00', ground: '#2a1f10', accent: '#ff6600' }, + the_singularity: { bg: '#1a0020', ground: '#2a0030', accent: '#ff00ff' }, + beach: { bg: '#0a2040', ground: '#c2a060', accent: '#ffcc00' }, + desert: { bg: '#1a1000', ground: '#a08040', accent: '#ff8800' }, + forest: { bg: '#051005', ground: '#1a3010', accent: '#44cc22' }, + jungle: { bg: '#081808', ground: '#1a3818', accent: '#33ff66' }, + outer_space: { bg: '#000008', ground: '#0a0a20', accent: '#8888ff' }, + colosseum: { bg: '#1a0f08', ground: '#3a2a18', accent: '#d4a030' }, + kitchen_stadium: { bg: '#1a0a0a', ground: '#2a1a10', accent: '#ff4400' }, + haunted_mansion: { bg: '#0a0510', ground: '#1a1020', accent: '#8844cc' }, + meme_dimension: { bg: '#1a0820', ground: '#2a1830', accent: '#ff44ff' }, + space_station: { bg: '#000010', ground: '#0a0a28', accent: '#44aaff' }, + junkyard: { bg: '#0f0f0a', ground: '#2a2a1a', accent: '#aa8833' }, + concert_hall: { bg: '#10001a', ground: '#20102a', accent: '#ff22aa' }, + savanna: { bg: '#1a1808', ground: '#8a7830', accent: '#dd9922' }, + server_room: { bg: '#000a05', ground: '#0a1a10', accent: '#00ff88' }, + sports_arena: { bg: '#0a0a1a', ground: '#1a2a4a', accent: '#ff3333' }, + bitcoin_mine: { bg: '#0a0808', ground: '#1a1210', accent: '#f7931a' }, + lightning_node: { bg: '#0a0018', ground: '#1a1030', accent: '#ffdd00' }, + satoshis_garage: { bg: '#0f0f0a', ground: '#1f1f14', accent: '#a0a080' }, + mempool: { bg: '#100a18', ground: '#201828', accent: '#cc44ff' }, + silk_road_ruins: { bg: '#0a0a0a', ground: '#181818', accent: '#338833' }, + block_height_tower: { bg: '#080810', ground: '#181828', accent: '#4488dd' }, + hodl_cave: { bg: '#080604', ground: '#1a1410', accent: '#f7931a' }, + whale_pool: { bg: '#001020', ground: '#0a2040', accent: '#0088ff' }, + node_farm: { bg: '#000a00', ground: '#0a1a0a', accent: '#22ff44' }, + genesis_block: { bg: '#0a0a00', ground: '#1a1a10', accent: '#ffd700' }, + pizza_day_parlor: { bg: '#1a0a00', ground: '#2a1a08', accent: '#ff6622' }, + difficulty_adjustment: { bg: '#100810', ground: '#201820', accent: '#ff4488' }, + fifty_one_percent: { bg: '#1a0000', ground: '#2a0808', accent: '#ff0000' }, + timechain_temple: { bg: '#08080a', ground: '#18182a', accent: '#88aaff' }, + nostr_relay_room: { bg: '#0a0014', ground: '#1a1028', accent: '#8844ff' }, +} + +export const spriteAnims = { + idle: { from: 0, to: ANIMATIONS.idle.frames - 1, loop: true, speed: 6 }, + attack: { from: MAX_FRAMES, to: MAX_FRAMES + ANIMATIONS.attack.frames - 1, loop: false, speed: 12 }, + kick: { from: MAX_FRAMES * 2, to: MAX_FRAMES * 2 + ANIMATIONS.kick.frames - 1, loop: false, speed: 10 }, + special: { from: MAX_FRAMES * 3, to: MAX_FRAMES * 3 + ANIMATIONS.special.frames - 1, loop: false, speed: 8 }, + hit: { from: MAX_FRAMES * 4, to: MAX_FRAMES * 4 + ANIMATIONS.hit.frames - 1, loop: false, speed: 8 }, + knockback: { from: MAX_FRAMES * 5, to: MAX_FRAMES * 5 + ANIMATIONS.knockback.frames - 1, loop: false, speed: 8 }, + ko: { from: MAX_FRAMES * 6, to: MAX_FRAMES * 6 + ANIMATIONS.ko.frames - 1, loop: false, speed: 6 }, + win: { from: MAX_FRAMES * 7, to: MAX_FRAMES * 7 + ANIMATIONS.win.frames - 1, loop: true, speed: 6 }, +} + +export const CHALLENGE_THEMED: Record = { + speed_blitz: { themed: ['afterimageDash', 'lightningRush', 'rapidFlurry', 'tornadoSpin', 'corkscrewDive', 'warpDrive', 'backflipKick', 'bulletTimeAttack', 'lightningBolt', 'channelForceClose', 'segwitSlash', 'doubleSpend'], generic: ['dashPunch', 'multiHit', 'fullScreenDash', 'dashThrough', 'zoomRush', 'katanaCombo', 'cycloneKick', 'helicopterArms', 'breakdanceSweep'] }, + riddle: { themed: ['riddleBarrage', 'portalPunch', 'cloneStrike', 'mindBlast', 'hexCurse'], generic: ['teleportStrike', 'projectile', 'zoomRush', 'whipCrack', 'bombThrow', 'captchaTrap', 'glitchBeam'] }, + code_golf: { themed: ['golfClubSmash', 'golfCartDrive', 'stackOverflow', 'segfault', 'blueScreen'], generic: ['projectile', 'flyingKick', 'dashPunch', 'rapidFlurry', 'hammerSmash', 'bodySlam', 'homeRunSwing'] }, + roast_battle: { themed: ['fireBreath', 'dragonBreath', 'fireball', 'lavaSplash', 'heatVision', 'volcanoErupt'], generic: ['gunBurst', 'jetpackDive', 'multiHit', 'zoomRush', 'laserBeam', 'chainsawRev', 'rocketLauncher', 'dynamiteBlast'] }, + hallucination_check: { themed: ['cloneStrike', 'portalPunch', 'shadowClone', 'glitchBeam', 'voidRift'], generic: ['teleportStrike', 'dashThrough', 'riddleBarrage', 'rapidFlurry', 'sniperShot', 'darkVoid', 'memeBeam'] }, + token_economy: { themed: ['coinShower', 'bitcoinCrash', 'nftRugPull', 'vendingDrop', 'popupSpam', 'blockReward', 'utxoScatter', 'feeSpike', 'satPerByte', 'stackSatsBlast', 'rugPullYank', 'dustAttack', 'multisigStrike'], generic: ['dashPunch', 'flyingKick', 'uppercut', 'zoomRush', 'bombThrow', 'minigunSpray', 'pocketCannon'] }, + creative_writing: { themed: ['penStab', 'scrollBlast', 'bookStorm', 'enchantedArrow', 'newspaperRoll'], generic: ['jetpackDive', 'aerialSlam', 'projectile', 'rapidFlurry', 'katanaCombo', 'arcaneBarrage', 'memeBeam'] }, + math_blitz: { themed: ['mathAttack', 'dataStream', 'pixelBeam', 'hackerAttack', 'chainLightning', 'merkleRoot', 'difficultyRetarget', 'halvingStrike'], generic: ['multiHit', 'dashPunch', 'fullScreenDash', 'rapidFlurry', 'laserBeam', 'hammerSmash', 'pinballCombo'] }, + trap_card: { themed: ['trapCardAttack', 'captchaTrap', 'spiderWeb', 'flashBang', 'smokeBomb'], generic: ['gunBurst', 'teleportStrike', 'groundPound', 'zoomRush', 'sniperShot', 'rocketLauncher', 'minigunSpray'] }, + food_fight: { themed: ['pizzaSlam', 'bananaFling', 'pieSmash', 'hotdogWhip', 'watermelonBomb', 'sushiBarrage', 'burgerToss', 'tacoStorm', 'eggBombard', 'tomatoBarrage', 'sushiRoll', 'tacoSlam', 'iceCreamFreeze', 'wokToss', 'cakeSmash', 'curryBlast', 'lobsterClaw', 'steakSlap', 'watermelonBowl'], generic: ['popcornBlast', 'iceCreamFling', 'donutBarrage', 'cookieFling', 'nachoVolley', 'meatballStorm', 'candyBarrage'] }, + wrestling_match: { themed: ['piledriver', 'powerbomb', 'chokeslam', 'tombstone', 'rko', 'clothesline', 'suplex', 'germanSuplex'], generic: ['bodySlam', 'grappleFlurry', 'hurricanrana', 'moonsault', 'elbowDrop', 'spear', 'stunner'] }, + music_battle: { themed: ['bassDrop', 'guitarSolo', 'drumSolo', 'dubstepCannon', 'beatboxBlast', 'vinylScratch'], generic: ['airHornBlast', 'vuvuzelaBlast', 'sonicWave', 'bassDropBeam', 'karaokeAttack', 'airGuitar'] }, + magic_duel: { themed: ['kamehameha', 'spiritBomb', 'arcaneBarrage', 'darkVoid', 'holySmite', 'shadowClone', 'hexCurse', 'vortexPull', 'gravityCrush', 'darkPulse', 'prismBeam'], generic: ['fireball', 'iceLance', 'thunderStrike', 'portalPunch', 'crystalShards', 'scrollBlast', 'potionThrow'] }, + sports_showdown: { themed: ['homeRunSwing', 'slapShot', 'servingAce', 'fieldGoalKick', 'bodyCheck', 'soccerKick', 'basketballDunk'], generic: ['baseballBat', 'tennisRacket', 'hockeyStick', 'bowlingBallRoll', 'tennisBallVolley', 'dropKick'] }, + nature_clash: { themed: ['treeSmash', 'icebergDrop', 'lavaSplash', 'tornadoFling', 'tsunamiWave', 'avalanche', 'earthquakeStrike'], generic: ['vineWhip', 'sandstorm', 'thornBarrage', 'pollenCloud', 'windSlash', 'meteorStrike'] }, + space_war: { themed: ['blackHole', 'photonTorpedo', 'tractor_beam', 'warpDrive', 'alienAbduction', 'ionCannon', 'cosmicRay', 'bulletTimeAttack', 'novaBlast', 'photonCannon', 'sonicBoom', 'glacialRay'], generic: ['laserSword', 'plasmaSword', 'asteroidBelt', 'satelliteLaser', 'plasmaBeam', 'solarFlare'] }, + hack_battle: { themed: ['hackerAttack', 'bugSwarm', 'stackOverflow', 'segfault', 'blueScreen', 'malwareInject', 'bulletTimeAttack', 'hashSmash', 'miningRig', 'nodeSync', 'nonceGrind', 'proofOfWork'], generic: ['fourOhFour', 'ctrlAltDelete', 'aiUprising', 'captchaTrap', 'popupSpam', 'dataStream', 'glitchBeam'] }, + meme_war: { themed: ['selfieStrike', 'dabAttack', 'flossAttack', 'yeetThrow', 'tPoseAssert', 'emojiBarrage', 'memeBeam', 'selfieStun', 'vibeCheck', 'rubberDucky', 'confettiPop', 'touchGrassSmack'], generic: ['rubberChicken', 'fingerGuns', 'micDrop', 'ratioAttack', 'capThrow', 'touchGrass', 'noScope'] }, + animal_kingdom: { themed: ['sharkBite', 'bearSwipe', 'eagleDive', 'bullCharge', 'gorillaSlam', 'wolfPack', 'batSwarm'], generic: ['snakeLunge', 'scorpionSting', 'crabPinch', 'spiderWeb', 'beeSwarm', 'catScratch', 'dogPile', 'dolphinFlip'] }, + demolition: { themed: ['dynamiteBlast', 'c4Detonation', 'nukeStrike', 'grenadeBlast', 'rocketLauncher', 'volcanoErupt'], generic: ['fireworksBurst', 'partyPopper', 'pinataSmash', 'cherryBomb', 'confettiCannon', 'anvilDrop', 'pianoDrop'] }, + vehicle_mayhem: { themed: ['motorbikeCharge', 'carSmash', 'tankRoll', 'helicopterStrike', 'zamboniCrush', 'tractorPlow'], generic: ['shoppingCart', 'forkliftCharge', 'airplaneSwoop', 'rocketRide', 'unicycleRun', 'golfCartDrive', 'boatCannon'] }, + medieval_combat: { themed: ['swordSlash', 'katanaCombo', 'battleAxe', 'maceSwing', 'crossbowBolt', 'shieldBash', 'nunchakuSpin', 'tridentThrust', 'morningstar', 'scytheSlash', 'warhammerCrush'], generic: ['flailSwing', 'holyWater', 'dragonBreath', 'enchantedArrow', 'laserSword', 'scrollBlast'] }, + retro_mode: { themed: ['dashPunch', 'uppercut', 'rapidFlurry', 'flyingKick', 'multiHit', 'fullScreenDash', 'backflipKick', 'cycloneKick'], generic: ['bodySlam', 'grappleFlurry', 'suplex', 'katanaCombo', 'breakdanceSweep', 'corkscrewDive', 'pinballCombo'] }, +} + +export const TIER_ULTIMATES: Record = { + 2: ['ultimateRapidBarrage', 'ultimateOrbitalStrike', 'ultimateCloneSurround', 'ultimateGatlingFury', 'ultimateEarthquake', 'ultimateBouncingFury', 'ultimateTripleBeam', 'ultimateMegaThrow', 'ultimateTornadoGrab', 'ultimateMeteorShower'], + 3: ['ultimateScreenNuke', 'ultimateBlackHoleVortex', 'ultimateDimensionalRift', 'ultimateTimeSlow', 'ultimateSummonArmy', 'ultimateHellfireRain', 'ultimateCrystalCage', 'ultimateGigaDrill', 'ultimateSuperSlam', 'ultimateAvalancheDrop', 'ultimateBulletTimeBarrage'], + 4: ['ultimateFinaleRush', 'ultimateMeteorCrash', 'ultimateScreenShatter', 'ultimateGravityReverse', 'ultimateInfiniteCombo', 'ultimatePlasmaStorm', 'ultimateFrozenCoffin', 'ultimateLavaGeyser', 'ultimateChainGrab', 'ultimateRocketBarrage'], + 5: ['ultimateArmageddon', 'ultimateOmegaBeam', 'ultimateBeyondTheScreen', 'ultimateMatrixDodge', 'ultimateWorldEnder', 'ultimateAbsoluteZero', 'ultimateSolarCannon', 'ultimatePhantomStrike', 'ultimateGodPunch', 'ultimateVoidCollapse'], +} + +export const CREATOR_MOVES = ['bitcoinRain', 'lightningInvoice', 'satoshiStrike', 'hashRateOverload', 'blockchainSlam', 'creatorMode', 'morphStrike', 'divineIntervention', 'bitcoinSwarm', 'satoshiTornado', 'hodlWave', 'bitcoinBarrage', 'bulletTimeAttack'] +export const CREATOR_ULTIMATES = ['ultimateGenesisBlock', 'ultimateTheHalving', 'ultimateFullNodeCrush', 'ultimateSatoshiVision'] diff --git a/frontend/src/game/fight/types.ts b/frontend/src/game/fight/types.ts new file mode 100644 index 0000000..e1233b1 --- /dev/null +++ b/frontend/src/game/fight/types.ts @@ -0,0 +1,26 @@ +import type { GameObj, SpriteComp, PosComp, ScaleComp, AnchorComp, OpacityComp, ColorComp, RotateComp, ZComp } from 'kaplay' +import type { SpriteCustomization } from '../sprites' + +export type Fighter = GameObj + +export interface FightSceneConfig { + canvas: HTMLCanvasElement + botA: { name: string; seed: string; tier: number; archetype?: string; customization?: SpriteCustomization; wins?: number; losses?: number } + botB: { name: string; seed: string; tier: number; archetype?: string; customization?: SpriteCustomization; wins?: number; losses?: number } + arena: string + onReady?: () => void +} + +export interface RoundEvent { + round: number + challengeType: string + winnerId: string | null + botAId: string + botBId: string + narration: string + isCritical: boolean + botAScore: number + botBScore: number + botAResponse?: string + botBResponse?: string +}