Code edit (4 edits merged)
Please save this source code
User prompt
The refund button just says refund instead of displaying the amount that will be refunded like it should.
User prompt
When the refund is offered use the refundButton with + followed by the refund price that will be gained at the bottom in the same place that the deal price is located on the deal button.
User prompt
Increase refund to half of the current deal cost.
User prompt
make sure bosses dont get permenantly frozen by giving them periods of freezing immunity ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
when new poker chips are spawned, place them on a layer lower than the previous chip
User prompt
analyze code, look for the most poorly performing sections or memory leaks and refactor for efficiency and performance without losing ANY functionality
User prompt
analyze code and refactor for efficiency and performance without losing ANY functionality
User prompt
Please fix the bug: 'TypeError: floatingText.style is undefined' in or related to this line: 'floatingText.style.fontSize = size || 40;' Line Number: 3107
User prompt
experience a lot of slowdown, analyze pool sizes and animation methods. look for memory leaks
User prompt
still experiencing a lot of performance issues. analyze and provide any refactoring as needed
User prompt
im experience a lot of slowdown with bullets. analyze pool system and optimize
Code edit (1 edits merged)
Please save this source code
User prompt
during a boss wave do not start the next wave until both bosses have been defeated
User prompt
after a poker chip has been frozen add a 2 second immunity period before it can be frozen again
User prompt
update as needed with: // 1. Add freeze properties to PokerChip constructor (add after burn properties): self.freezeDuration = 0; self.originalSpeed = 0; self.iceCube = null; // Will hold the ice cube graphic // 2. Add freeze methods to PokerChip class (add after applyBurn method): self.applyFreeze = function(duration) { // Don't freeze if already frozen - just refresh duration if (self.freezeDuration > 0) { self.freezeDuration = Math.max(self.freezeDuration, duration); return; } self.freezeDuration = duration; // Store original speed and stop movement self.originalSpeed = self.speed; self.speed = 0; // Create ice cube encasement if (!self.iceCube) { self.iceCube = LK.getAsset('iceCube', { anchorX: 0.5, anchorY: 0.5 }); self.iceCube.scale.set(1.2); // Slightly larger than chip self.iceCube.alpha = 0.8; self.addChild(self.iceCube); } self.iceCube.visible = true; // Ice formation animation self.iceCube.scale.set(0.1); self.iceCube.alpha = 0; tween(self.iceCube, { scaleX: 1.2, scaleY: 1.2, alpha: 0.8 }, { duration: 300, easing: tween.backOut }); }; self.processFreeze = function() { if (self.freezeDuration <= 0) return; self.freezeDuration--; // Create occasional ice sparkle effects if (Math.random() < 0.1) { createIceSparkles(self.x, self.y); } // When freeze expires, break the ice if (self.freezeDuration <= 0) { self.speed = self.originalSpeed; if (self.iceCube) { // Ice breaking animation tween(self.iceCube, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 200, easing: tween.quadOut, onFinish: function() { self.iceCube.visible = false; } }); // Create ice shatter particles createIceShatterEffect(self.x, self.y); } } }; // 3. In PokerChip activate method, add after burn reset: // NEW: Reset freeze status to ensure clean state self.freezeDuration = 0; self.originalSpeed = 0; if (self.iceCube) { self.iceCube.visible = false; } // 4. In PokerChip update method, add after processBurnDamage(): // NEW: Process freeze effect self.processFreeze(); // 5. In Bullet update method, add after burn effect check: // NEW: Check if this is a spades bullet and freeze mod is equipped if (self.suit === 'spades' && ModSystem.getEquippedModAsset('spades') === 'freezeSpadeMod') { // Calculate freeze duration (3 seconds base) var freezeDuration = 180; // 180 ticks at 60fps = 3 seconds // Apply freeze effect self.target.applyFreeze(freezeDuration); } // 6. Add these effect functions after createBurnEffect function: function createIceSparkles(x, y) { var numSparkles = 2; for (var i = 0; i < numSparkles; i++) { var sparkle = LK.getAsset('iceCube', { anchorX: 0.5, anchorY: 0.5 }); sparkle.x = x + (Math.random() - 0.5) * 60; sparkle.y = y + (Math.random() - 0.5) * 60; sparkle.scale.set(0.1 + Math.random() * 0.1); sparkle.alpha = 0.6; sparkle.tint = 0x88ddff; // Light blue tint gameLayer.addChild(sparkle); tween(sparkle, { y: sparkle.y - 20, alpha: 0, scaleX: 0.05, scaleY: 0.05 }, { duration: 800, easing: tween.quadOut, onFinish: function(p) { return function() { if (p.parent) { p.parent.removeChild(p); } }; }(sparkle) }); } } function createIceShatterEffect(x, y) { var numShards = 6; for (var i = 0; i < numShards; i++) { var shard = LK.getAsset('iceCube', { anchorX: 0.5, anchorY: 0.5 }); shard.x = x; shard.y = y; shard.scale.set(0.2 + Math.random() * 0.2); shard.tint = 0xaaeeff; // Ice blue tint var angle = (Math.PI * 2 * i) / numShards + (Math.random() - 0.5) * 0.5; var distance = 40 + Math.random() * 30; var targetX = x + Math.cos(angle) * distance; var targetY = y + Math.sin(angle) * distance; gameLayer.addChild(shard); tween(shard, { x: targetX, y: targetY, alpha: 0, rotation: Math.random() * Math.PI * 2, scaleX: 0.05, scaleY: 0.05 }, { duration: 600, easing: tween.quadOut, onFinish: function(p) { return function() { if (p.parent) { p.parent.removeChild(p); } }; }(shard) }); } } ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
make sure when playing a 5th card of the same value onto a row it doesnt nullify the existing 4 of a kind
User prompt
Jokers cannot be played onto the board, they can only be used to level up cards. remove support for their board presence
Code edit (1 edits merged)
Please save this source code
User prompt
update as needed with: self.fire = function () { var target = self.findTarget(); if (!target) { return; } // Check if this is a clubs card with spreadshot mod equipped var isSpreadshot = (self.cardData.suit === 'clubs' && ModSystem.getEquippedModAsset('clubs') === 'spreadClubMod'); if (isSpreadshot) { // Spreadshot: reduced damage but hits multiple targets var spreadDamage = Math.floor(self.damage * 0.7); // 30% damage reduction var maxTargets = Math.min(1 + self.level, 5); // 1 + level targets, max 5 // Find multiple targets var targets = self.isPlayerCard ? activePlayerChips : activeAIChips; var validTargets = []; // Get all valid targets and sort by distance for (var i = 0; i < targets.length; i++) { var chip = targets[i]; if (chip.active && chip.health > 0 && chip.visible) { var dx = chip.x - self.x; var dy = chip.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); validTargets.push({chip: chip, distance: distance}); } } // Sort by distance (closest first) validTargets.sort(function(a, b) { return a.distance - b.distance; }); // Fire at up to maxTargets var targetsHit = Math.min(maxTargets, validTargets.length); for (var i = 0; i < targetsHit; i++) { var bullet = PoolManager.getBullet(); if (bullet) { bullet.activate(self.x, self.y, validTargets[i].chip, spreadDamage, self.cardData.suit, self.isPlayerCard); gameLayer.addChild(bullet); activeBullets.push(bullet); } } } else { // Normal single-target firing var bullet = PoolManager.getBullet(); if (bullet) { bullet.activate(self.x, self.y, target, self.damage, self.cardData.suit, self.isPlayerCard); gameLayer.addChild(bullet); activeBullets.push(bullet); } } self.lastFired = LK.ticks; // Visual feedback for firing (enhanced for spreadshot) tween.stop(self, { scaleX: true, scaleY: true }); var scaleAmount = isSpreadshot ? 0.8 : 0.9; // More dramatic scale for spreadshot tween(self, { scaleX: scaleAmount, scaleY: scaleAmount }, { duration: 100, easing: tween.quadOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 150, easing: tween.elasticOut }); } }); }; ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
after every 10 levels, new cards dealt start one level higher
User prompt
update as needed with: update: function update() { this.waveTimer++; // Check if wave is complete if (this.waveTimer >= this.waveDuration) { // Check if both bosses were defeated in a boss wave if (this.isBossWave(this.waveNumber) && this.playerBossDefeated && this.aiBossDefeated) { console.log("Both bosses defeated! Moving to next round!"); createFloatingText('ROUND COMPLETE!', SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 0x00ff00); // Reset boss defeat flags this.playerBossDefeated = false; this.aiBossDefeated = false; } this.waveTimer = 0; this.waveNumber++; this.playerSpawnTimer = 0; this.aiSpawnTimer = 0; this.bossSpawned = false; // Reset boss spawn flag var waveType = this.isBossWave(this.waveNumber) ? "BOSS WAVE" : "Wave"; console.log(waveType + " " + this.waveNumber + " starting!"); return; } // BOSS WAVE LOGIC - Spawn bosses immediately at start of wave if (this.isBossWave(this.waveNumber)) { if (!this.bossSpawned && this.waveTimer === 1) { // Only on first tick of boss wave console.log("=== BOSS WAVE " + this.waveNumber + " STARTING ==="); // Spawn player boss var playerBossValue = this.getBossChipValue(this.waveNumber); ChipSpawner.spawnChip(playerBossValue, true); console.log("PLAYER BOSS spawned with value:", playerBossValue, "Active player chips:", activePlayerChips.length); // Spawn AI boss var aiBossValue = this.getBossChipValue(this.waveNumber); ChipSpawner.spawnChip(aiBossValue, false); console.log("AI BOSS spawned with value:", aiBossValue, "Active AI chips:", activeAIChips.length); this.bossSpawned = true; } // Boss defeat detection - only after bosses have had time to spawn if (this.bossSpawned && this.waveTimer > 120) { // 2 second delay console.log("Checking boss status - Player chips:", activePlayerChips.length, "AI chips:", activeAIChips.length); // Check if player side boss is defeated if (!this.playerBossDefeated && activePlayerChips.length === 0) { this.playerBossDefeated = true; console.log("Player defeated the boss!"); createFloatingText('BOSS DEFEATED!', SCREEN_WIDTH / 2, PLAYER_AREA_Y + SLOT_HEIGHT, 0xffd700); } // Check if AI side boss is defeated if (!this.aiBossDefeated && activeAIChips.length === 0) { this.aiBossDefeated = true; console.log("AI defeated the boss!"); createFloatingText('AI BOSS DEFEATED!', SCREEN_WIDTH / 2, AI_AREA_Y + SLOT_HEIGHT, 0xffd700); } // If both bosses defeated, immediately end the wave if (this.playerBossDefeated && this.aiBossDefeated) { this.waveTimer = this.waveDuration - 1; // Set to end on next tick } } return; // Don't do normal spawning during boss waves } // NORMAL WAVE LOGIC var currentSpawnInterval = this.spawnInterval; if (this.waveNumber === 1) { currentSpawnInterval = 90; // Slower spawning for wave 1 (1.5 seconds) } else if (this.waveNumber >= 2) { currentSpawnInterval = Math.max(45, this.spawnInterval - Math.floor((this.waveNumber - 2) * 2)); // Faster spawning for wave 2+ } // Spawn on player side this.playerSpawnTimer++; if (this.playerSpawnTimer >= currentSpawnInterval) { this.playerSpawnTimer = 0; this.spawnChip(true); } // Spawn on AI side this.aiSpawnTimer++; if (this.aiSpawnTimer >= currentSpawnInterval) { this.aiSpawnTimer = 0; this.spawnChip(false); } }
User prompt
update as needed with: function calculateGreedBonus(isPlayerSide, baseChipsEarned) { // Only calculate bonus if greed mod is equipped if (ModSystem.getEquippedModAsset('diamonds') !== 'chipsDiamondMod') { return 0; } var playArea = isPlayerSide ? gameState.playerPlayArea : gameState.aiPlayArea; var diamondCount = 0; for (var row = 0; row < PLAY_AREA_ROWS; row++) { for (var col = 0; col < PLAY_AREA_COLS; col++) { var card = playArea[row][col]; if (card && card.cardData.suit === 'diamonds') { diamondCount++; } } } // 5% bonus per diamond card (caps at 50% for full diamond board) var bonusPercentage = diamondCount * 0.05; return Math.ceil(baseChipsEarned * bonusPercentage); } self.die = function () { var chipsEarned = Math.ceil(self.value * 1.5); if (self.isPlayerSide) { var greedBonus = calculateGreedBonus(true, chipsEarned); var totalEarned = chipsEarned + greedBonus; gameState.playerChips += totalEarned; if (greedBonus > 0) { createFloatingText('+' + formatNumberWithSuffix(totalEarned) + ' (+' + formatNumberWithSuffix(greedBonus) + ' greed)', self.x, self.y - 30, 0xffd700, 35); } } else { var greedBonus = calculateGreedBonus(false, chipsEarned); gameState.aiChips += chipsEarned + greedBonus; } PoolManager.returnChip(self); };
User prompt
update as needed with: // Alternative even more conservative approach: function calculateGreedBonus(isPlayerSide) { // Only calculate bonus if greed mod is equipped if (ModSystem.getEquippedModAsset('diamonds') !== 'chipsDiamondMod') { return 0; } var playArea = isPlayerSide ? gameState.playerPlayArea : gameState.aiPlayArea; var diamondCount = 0; var totalLevels = 0; // Count diamond cards and sum their levels for (var row = 0; row < PLAY_AREA_ROWS; row++) { for (var col = 0; col < PLAY_AREA_COLS; col++) { var card = playArea[row][col]; if (card && card.cardData.suit === 'diamonds') { diamondCount++; totalLevels += card.level; } } } // Bonus = number of diamonds + total levels (much more conservative) return diamondCount + totalLevels; }
User prompt
update as needed with: update: function update() { this.waveTimer++; // Check if wave is complete if (this.waveTimer >= this.waveDuration) { // Check if both bosses were defeated in a boss wave if (this.isBossWave(this.waveNumber) && this.playerBossDefeated && this.aiBossDefeated) { console.log("Both bosses defeated! Moving to next round!"); createFloatingText('ROUND COMPLETE!', SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 0x00ff00); // Reset boss defeat flags this.playerBossDefeated = false; this.aiBossDefeated = false; } this.waveTimer = 0; this.waveNumber++; this.playerSpawnTimer = 0; this.aiSpawnTimer = 0; this.bossSpawned = false; // Reset boss spawn flag var waveType = this.isBossWave(this.waveNumber) ? "BOSS WAVE" : "Wave"; console.log(waveType + " " + this.waveNumber + " starting!"); return; } // Special handling for boss waves - spawn both bosses at the same time if (this.isBossWave(this.waveNumber)) { if (!this.bossSpawned) { // Spawn boss on both sides simultaneously this.spawnChip(true); // Player side this.spawnChip(false); // AI side this.bossSpawned = true; console.log("BOSS WAVE " + this.waveNumber + " - Bosses spawned on both sides!"); } // During boss wave, check if no enemies remain (boss defeated) // BUT only after giving the boss time to actually spawn and appear var bossSpawnDelay = 60; // 1 second delay (60 ticks at 60fps) if (this.waveTimer >= bossSpawnDelay) { // Check if player side boss is defeated if (!this.playerBossDefeated && activePlayerChips.length === 0) { this.playerBossDefeated = true; console.log("Player defeated the boss!"); createFloatingText('BOSS DEFEATED!', SCREEN_WIDTH / 2, PLAYER_AREA_Y + SLOT_HEIGHT, 0xffd700); } // Check if AI side boss is defeated if (!this.aiBossDefeated && activeAIChips.length === 0) { this.aiBossDefeated = true; console.log("AI defeated the boss!"); createFloatingText('AI BOSS DEFEATED!', SCREEN_WIDTH / 2, AI_AREA_Y + SLOT_HEIGHT, 0xffd700); } // If both bosses defeated, immediately end the wave if (this.playerBossDefeated && this.aiBossDefeated) { this.waveTimer = this.waveDuration - 1; // Set to end on next tick } } } else { // Normal wave spawning logic var currentSpawnInterval = this.spawnInterval; if (this.waveNumber === 1) { currentSpawnInterval = 90; // Slower spawning for wave 1 (1.5 seconds) } else if (this.waveNumber >= 2) { currentSpawnInterval = Math.max(45, this.spawnInterval - Math.floor((this.waveNumber - 2) * 2)); // Faster spawning for wave 2+ } // Spawn on player side this.playerSpawnTimer++; if (this.playerSpawnTimer >= currentSpawnInterval) { this.playerSpawnTimer = 0; this.spawnChip(true); } // Spawn on AI side this.aiSpawnTimer++; if (this.aiSpawnTimer >= currentSpawnInterval) { this.aiSpawnTimer = 0; this.spawnChip(false); } } }
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ /**** * Bullet Class ****/ var Bullet = Container.expand(function () { var self = Container.call(this); self.active = false; self.target = null; self.damage = 10; self.speed = 15.6; self.isPlayerCard = true; self.isSeekingLastPosition = false; self.targetLastX = 0; self.targetLastY = 0; self.suit = null; var currentGraphic = null; var suitGraphics = {}; // Initialize suit graphics ['hearts', 'diamonds', 'clubs', 'spades'].forEach(function (suit) { var graphic = self.attachAsset(ModSystem.getEquippedModAsset(suit) || suit + 'Suit', { anchorX: 0.5, anchorY: 0.5 }); graphic.scale.set(0.3); graphic.visible = false; suitGraphics[suit] = graphic; }); self.activate = function (startX, startY, target, damage, suit, isPlayerCard) { self.active = true; self.visible = true; self.x = startX; self.y = startY; self.target = target; self.damage = damage; self.isPlayerCard = isPlayerCard; self.suit = suit || 'hearts'; self.isSeekingLastPosition = false; if (self.target) { self.targetLastX = self.target.x; self.targetLastY = self.target.y; } if (currentGraphic) currentGraphic.visible = false; currentGraphic = suitGraphics[self.suit] || suitGraphics['hearts']; currentGraphic.visible = true; }; self.findNewTarget = function () { var targets = self.isPlayerCard ? activePlayerChips : activeAIChips; var bestTarget = null; var shortestDistance = Infinity; targets.forEach(function (chip) { if (chip.active) { var dx = chip.x - self.x; var dy = chip.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < shortestDistance) { shortestDistance = distance; bestTarget = chip; } } }); return bestTarget; }; self.update = function () { if (!self.active) return; if (self.target && !self.target.active) { var newTarget = self.findNewTarget(); if (newTarget) { self.target = newTarget; self.targetLastX = newTarget.x; self.targetLastY = newTarget.y; } else { self.isSeekingLastPosition = true; self.target = null; } } if (self.isSeekingLastPosition) { var dx = self.targetLastX - self.x; var dy = self.targetLastY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < self.speed) { self.isSeekingLastPosition = false; if (currentGraphic) currentGraphic.visible = false; PoolManager.returnBullet(self); } else { var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; } } else if (self.target) { self.targetLastX = self.target.x; self.targetLastY = self.target.y; var dx = self.target.x - self.x; var dy = self.target.y - self.y; var hitRadiusSq = 85 * 85; // Use squared radius for performance var distanceSq = dx * dx + dy * dy; // Since bullet speed is much smaller than hit radius, a simple distance check is sufficient and more performant. if (distanceSq < hitRadiusSq) { self.target.takeDamage(self.damage); // Apply special effects based on mod if (self.suit === 'hearts' && ModSystem.getEquippedModAsset('hearts') === 'burnHeartMod') { var burnDamage = self.damage * 0.1; var burnDuration = 8; self.target.applyBurn(burnDamage, burnDuration); } if (self.suit === 'spades' && ModSystem.getEquippedModAsset('spades') === 'freezeSpadeMod') { self.target.applyFreeze(180); } if (currentGraphic) currentGraphic.visible = false; PoolManager.returnBullet(self); } else { // Move towards target var distance = Math.sqrt(distanceSq); self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } } else { if (currentGraphic) currentGraphic.visible = false; PoolManager.returnBullet(self); } }; return self; }); /**** * Card Class ****/ var Card = Container.expand(function (cardData) { var self = Container.call(this); self.cardData = cardData; self.level = 1; self.isInPlay = false; self.isPlayerCard = true; self.playSlotX = 0; self.playSlotY = 0; self.lastFired = 0; self.fireRate = 60; self.damage = 35; self.range = 200; self.handBonus = 1; // Outlines var createOutline = function createOutline(color, alpha) { var outline = self.attachAsset('card', { anchorX: 0.5, anchorY: 0.5 }); outline.scale.set(1.1); outline.alpha = alpha; outline.tint = color; outline.visible = false; return outline; }; self.redOutline = createOutline(0xff0000, 1.0); self.greenOutline = createOutline(0x00ff00, 0.7); // Card graphics var cardGraphics = self.attachAsset('card', { anchorX: 0.5, anchorY: 0.5 }); // Card value display if (cardData.suit !== 'joker') { var valueText = new Text2(cardData.value, { size: 56, fill: CardSystem.suitColors[cardData.suit] || 0x000000, weight: 800, stroke: 0x000000, strokeThickness: 0 }); valueText.anchor.set(0, 0); valueText.x = -95; valueText.y = -135; self.addChild(valueText); } // Suit graphics var suitAssetMap = { hearts: 'heartSuit', diamonds: 'diamondSuit', clubs: 'clubSuit', spades: 'spadeSuit' }; var suitAssetId = ModSystem.getEquippedModAsset(cardData.suit) || suitAssetMap[cardData.suit]; if (suitAssetId) { var suitGraphics = self.attachAsset(suitAssetId, { anchorX: 0.5, anchorY: 0.5 }); suitGraphics.y = -15; suitGraphics.scale.set(0.8); } else if (cardData.suit === 'joker') { var jokerGraphics = self.attachAsset('jokerSuit', { anchorX: 0.5, anchorY: 0.5 }); jokerGraphics.y = -15; jokerGraphics.scale.set(1.5); } // Level text var levelText = new Text2('Lvl 1', { size: 45, fill: 0x000000, weight: 800, stroke: 0x000000, strokeThickness: 0 }); levelText.anchor.set(0.5, 1); levelText.y = 128; self.addChild(levelText); self.activate = function (x, y, inPlay, isPlayerCard) { self.x = x; self.y = y; self.isInPlay = inPlay || false; self.isPlayerCard = isPlayerCard !== undefined ? isPlayerCard : true; self.visible = true; if (inPlay) self.calculateStats(); }; self.calculateStats = function () { var baseDamage = 10; var baseFireRate = 60; self.damage = Math.floor(baseDamage * Math.pow(1.6, self.level - 1) * self.handBonus); self.fireRate = Math.max(10, Math.floor(baseFireRate / (Math.pow(1.2, self.level - 1) * self.handBonus))); }; self.setLevel = function (newLevel) { if (self.cardData.suit === 'joker') { self.level = 1; levelText.visible = false; } else { self.level = newLevel; levelText.setText('Lvl ' + self.level); } self.calculateStats(); }; self.canMergeWith = function (otherCard) { if (!otherCard || otherCard === self || otherCard.cardData.suit === 'joker') return false; if (self.cardData.suit === 'joker') return true; var sameLevel = self.level === otherCard.level; var sameSuit = self.cardData.suit === otherCard.cardData.suit; var sameValue = self.cardData.value === otherCard.cardData.value; return sameLevel && (sameSuit || sameValue); }; self.mergeWith = function (otherCard) { if (!self.canMergeWith(otherCard)) return null; var newLevel = otherCard.level + 1; if (self.cardData.suit === 'joker') { var mergedCard = new Card(otherCard.cardData); mergedCard.setLevel(newLevel); return mergedCard; } var randomSuit = CardSystem.suits[Math.floor(Math.random() * CardSystem.suits.length)]; var randomValue = CardSystem.values[Math.floor(Math.random() * CardSystem.values.length)]; var newCardData = { suit: randomSuit, value: randomValue, id: randomSuit + '_' + randomValue }; var mergedCard = new Card(newCardData); mergedCard.setLevel(newLevel); return mergedCard; }; self.findTarget = function () { var targets = self.isPlayerCard ? activePlayerChips : activeAIChips; var bestTarget = null; var highestProgress = -1; targets.forEach(function (chip) { if (chip.active && chip.health > 0 && chip.visible && chip.pathProgress > highestProgress) { highestProgress = chip.pathProgress; bestTarget = chip; } }); return bestTarget; }; self.fire = function () { var target = self.findTarget(); if (!target) return; var isSpreadshot = self.cardData.suit === 'clubs' && ModSystem.getEquippedModAsset('clubs') === 'spreadClubMod'; if (isSpreadshot) { var spreadDamage = Math.floor(self.damage * 0.6); var maxTargets = Math.min(1 + self.level, 5); var targets = self.isPlayerCard ? activePlayerChips : activeAIChips; var validTargets = targets.filter(function (chip) { return chip.active && chip.health > 0 && chip.visible; }).map(function (chip) { return { chip: chip, distance: Math.sqrt(Math.pow(chip.x - self.x, 2) + Math.pow(chip.y - self.y, 2)) }; }).sort(function (a, b) { return a.distance - b.distance; }).slice(0, maxTargets); validTargets.forEach(function (_ref) { var chip = _ref.chip; var bullet = PoolManager.getBullet(); if (bullet) { bullet.activate(self.x, self.y, chip, spreadDamage, self.cardData.suit, self.isPlayerCard); gameLayer.addChild(bullet); activeBullets.push(bullet); } }); } else { var bullet = PoolManager.getBullet(); if (bullet) { bullet.activate(self.x, self.y, target, self.damage, self.cardData.suit, self.isPlayerCard); gameLayer.addChild(bullet); activeBullets.push(bullet); } } self.lastFired = LK.ticks; // Fire animation tween.stop(self, { scaleX: true, scaleY: true }); var scaleAmount = isSpreadshot ? 0.8 : 0.9; tween(self, { scaleX: scaleAmount, scaleY: scaleAmount }, Object.assign({}, ANIMATION_CONFIGS.cardFire, { onFinish: function onFinish() { return tween(self, { scaleX: 1, scaleY: 1 }, ANIMATION_CONFIGS.cardFireReturn); } })); }; self.update = function () { if (!self.isInPlay) return; if (LK.ticks - self.lastFired >= self.fireRate) { self.fire(); } }; self.setLevel(1); return self; }); /**** * Poker Chip Enemy Class ****/ var PokerChip = Container.expand(function () { var self = Container.call(this); self.active = false; self.health = 100; self.maxHealth = 100; self.value = 1; self.speed = 0.05; self.pathProgress = 0; self.isPlayerSide = true; self.damageFlashTimer = 0; self.isBoss = false; // Burn properties self.burnDamage = 0; self.burnDuration = 0; self.burnTickTimer = 0; self.burnTickInterval = 30; // Freeze properties self.freezeDuration = 0; self.originalSpeed = 0; self.iceCube = null; self.freezeImmunityTimer = 0; // Chip graphics var chipValues = [1, 5, 10, 25, 100]; var chipColors = ['yellowChip', 'redChip', 'greenChip', 'blueChip', 'purpleChip']; var chipGraphicsAssets = {}; var chipGraphics = null; chipValues.forEach(function (value, index) { var graphic = self.attachAsset(chipColors[index], { anchorX: 0.5, anchorY: 0.5 }); graphic.visible = false; chipGraphicsAssets[value] = graphic; }); var healthText = new Text2('', { size: 80, fill: 0xffffff, weight: 800, stroke: 0x000000, strokeThickness: 8 }); healthText.anchor.set(0.5, 0.5); self.addChild(healthText); self.applyBurn = function (damage, duration) { self.burnDamage += damage; self.burnDuration = Math.max(self.burnDuration, duration); self.burnTickTimer = 0; AnimationHelper.createParticleEffect(self.x, self.y, 'burnHeartMod', { count: 4, floatUp: 50 }); }; self.applyFreeze = function (duration) { if (self.freezeImmunityTimer > 0) return; if (self.freezeDuration > 0) { self.freezeDuration = Math.max(self.freezeDuration, duration); return; } self.freezeDuration = duration; self.originalSpeed = self.speed; self.speed = 0; if (!self.iceCube) { self.iceCube = LK.getAsset('iceCube', { anchorX: 0.5, anchorY: 0.5 }); self.iceCube.scale.set(1.2); self.iceCube.alpha = 0.8; self.addChild(self.iceCube); } self.iceCube.visible = true; self.iceCube.scale.set(0.1); self.iceCube.alpha = 0; tween(self.iceCube, { scaleX: 1.2, scaleY: 1.2, alpha: 0.8 }, { duration: 300, easing: tween.backOut }); }; self.processFreeze = function () { if (self.freezeImmunityTimer > 0) self.freezeImmunityTimer--; if (self.freezeDuration <= 0) return; self.freezeDuration--; if (Math.random() < 0.1) { AnimationHelper.createParticleEffect(self.x, self.y, 'iceCube', { count: 2, scale: { min: 0.1, max: 0.2 }, alpha: 0.6, tint: 0x88ddff, floatUp: 20 }); } if (self.freezeDuration <= 0) { self.speed = self.originalSpeed; self.freezeImmunityTimer = 120; if (self.iceCube) { tween(self.iceCube, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 200, easing: tween.quadOut, onFinish: function onFinish() { return self.iceCube.visible = false; } }); AnimationHelper.createParticleEffect(self.x, self.y, 'iceCube', { count: 6, tint: 0xaaeeff, rotation: Math.PI * 2 }); } } }; self.processBurnDamage = function () { if (self.burnDuration <= 0) { self.burnDamage = 0; self.burnTickTimer = 0; return; } self.burnTickTimer++; if (self.burnTickTimer >= self.burnTickInterval) { self.burnTickTimer = 0; self.burnDuration--; var actualBurnDamage = Math.ceil(self.burnDamage); self.health -= actualBurnDamage; self.updateHealthText(); createFloatingText('-' + actualBurnDamage, self.x + (Math.random() - 0.5) * 40, self.y - 30, 0xff4400, 30); AnimationHelper.createParticleEffect(self.x, self.y, 'burnHeartMod', { count: 4, floatUp: 50 }); if (self.health <= 0) { self.active = false; self.die(); return; } self.burnDamage *= 0.9; if (self.burnDuration <= 0) { self.burnDamage = 0; self.burnTickTimer = 0; } } }; self.activate = function (value, isPlayerSide, startPos) { self.active = true; self.visible = true; self.value = value; self.isPlayerSide = isPlayerSide; self.isBoss = value >= 100; // Calculate health self.maxHealth = value * 50; var healthMultiplier = Math.pow(2, Math.floor(WaveSystem.waveNumber / 10)); self.maxHealth *= healthMultiplier; self.health = self.maxHealth; // Reset all states self.pathProgress = 0; self.burnDamage = 0; self.burnDuration = 0; self.burnTickTimer = 0; self.freezeDuration = 0; self.originalSpeed = 0; self.freezeImmunityTimer = 0; self.damageFlashTimer = 0; self.speed = 0.03; if (self.iceCube) self.iceCube.visible = false; self.setChipAppearance(); self.x = startPos.x; self.y = startPos.y; }; self.updateHealthText = function () { healthText.setText(formatNumberWithSuffix(Math.max(0, self.health))); }; self.setChipAppearance = function () { if (chipGraphics) chipGraphics.visible = false; chipGraphics = chipGraphicsAssets[self.value] || chipGraphicsAssets[1]; if (chipGraphics) chipGraphics.visible = true; self.updateHealthText(); }; self.takeDamage = function (damage) { self.health -= damage; self.updateHealthText(); self.damageFlashTimer = 10; if (self.health <= 0) { self.active = false; self.die(); } }; self.die = function () { var chipsEarned = Math.ceil(self.value * 1.5); if (self.isPlayerSide) { var greedBonus = calculateGreedBonus(true, chipsEarned); var totalEarned = chipsEarned + greedBonus; gameState.playerChips += totalEarned; if (greedBonus > 0) { createFloatingText("+".concat(formatNumberWithSuffix(totalEarned), " (+").concat(formatNumberWithSuffix(greedBonus), " greed)"), self.x, self.y - 30, 0xffd700, 35); } } else { var greedBonus = calculateGreedBonus(false, chipsEarned); gameState.aiChips += chipsEarned + greedBonus; } PoolManager.returnChip(self); }; self.update = function () { if (!self.active) return; // Handle damage flash if (self.damageFlashTimer > 0) { self.damageFlashTimer--; if (chipGraphics) { chipGraphics.tint = self.damageFlashTimer > 0 ? 0xff0000 : 0xffffff; } } self.processBurnDamage(); self.processFreeze(); if (!self.active) return; self.pathProgress += self.speed; var pathPos = PathSystem.getPositionAlongPath(self.pathProgress, self.isPlayerSide); if (pathPos.completed) { var heartsToRemove = self.isBoss ? 2 : 1; if (self.isPlayerSide) { gameState.playerLives -= heartsToRemove; } else { gameState.aiLives -= heartsToRemove; } PoolManager.returnChip(self); return; } self.x = pathPos.x; self.y = pathPos.y; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x0f3d0f }); /**** * Game Code ****/ /**** * Constants ****/ /**** * Card System ****/ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; } function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); } function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } var SCREEN_WIDTH = 2048; var SCREEN_HEIGHT = 2732; var PLAY_AREA_COLS = 5; var PLAY_AREA_ROWS = 2; var SLOT_WIDTH = 300; var SLOT_HEIGHT = 420; var DEAL_SLOT_WIDTH = 240; var DEAL_SLOT_HEIGHT = 330; var HAND_GAP = 30; var HAND_WIDTH = 5 * DEAL_SLOT_WIDTH + 4 * HAND_GAP; var HAND_START_X = (SCREEN_WIDTH - HAND_WIDTH) / 2; // Area positioning var AI_AREA_X = (SCREEN_WIDTH - PLAY_AREA_COLS * SLOT_WIDTH) / 2; var AI_AREA_Y = 150; var PLAYER_AREA_X = (SCREEN_WIDTH - PLAY_AREA_COLS * SLOT_WIDTH) / 2; var PLAYER_AREA_Y = SCREEN_HEIGHT - 1300; var PLAYER_DEAL_AREA_Y = PLAYER_AREA_Y + PLAY_AREA_ROWS * SLOT_HEIGHT + 100; // Common animation configs var ANIMATION_CONFIGS = { cardFire: { duration: 100, easing: tween.quadOut }, cardFireReturn: { duration: 150, easing: tween.elasticOut }, cardMerge: { duration: 300, easing: tween.elasticOut }, cardDeal: { duration: 250, easing: tween.cubicOut }, cardSettle: { duration: 150, easing: tween.quadIn }, particleFade: { duration: 600, easing: tween.quadOut } }; /**** * Utility Functions ****/ function formatNumberWithSuffix(number) { var num = Math.round(number); var suffixes = [{ value: 1e12, suffix: 'T' }, { value: 1e9, suffix: 'B' }, { value: 1e6, suffix: 'M' }, { value: 1e3, suffix: 'K' }]; for (var _i = 0, _suffixes = suffixes; _i < _suffixes.length; _i++) { var _suffixes$_i = _suffixes[_i], value = _suffixes$_i.value, suffix = _suffixes$_i.suffix; if (num >= value) { return (num / value).toFixed(1).replace(/\.0$/, '') + suffix; } } return num.toString(); } function distancePointToLine(px, py, x1, y1, x2, y2) { var A = px - x1, B = py - y1; var C = x2 - x1, D = y2 - y1; var lenSq = C * C + D * D; if (lenSq === 0) return Math.sqrt(A * A + B * B); var param = Math.max(0, Math.min(1, (A * C + B * D) / lenSq)); var xx = x1 + param * C; var yy = y1 + param * D; return Math.sqrt((px - xx) * (px - xx) + (py - yy) * (py - yy)); } function getHandSlotPosition(index) { return { x: HAND_START_X + index * (DEAL_SLOT_WIDTH + HAND_GAP) + DEAL_SLOT_WIDTH / 2, y: PLAYER_DEAL_AREA_Y + DEAL_SLOT_HEIGHT / 2 }; } /**** * Animation Helpers ****/ var AnimationHelper = { createParticleEffect: function createParticleEffect(x, y, particleAsset, config) { var defaults = { count: 4, spread: 80, scale: { min: 0.3, max: 0.5 }, distance: { min: 25, max: 65 }, duration: { min: 400, max: 900 }, tint: 0xffffff, alpha: 1 }; var settings = Object.assign({}, defaults, config); for (var i = 0; i < settings.count; i++) { var particle = LK.getAsset(particleAsset, { anchorX: 0.5, anchorY: 0.5 }); particle.x = x + (Math.random() - 0.5) * settings.spread; particle.y = y + (Math.random() - 0.5) * settings.spread; particle.scale.set(settings.scale.min + Math.random() * (settings.scale.max - settings.scale.min)); particle.tint = settings.tint; particle.alpha = settings.alpha; var angle = Math.random() * Math.PI * 2; var distance = settings.distance.min + Math.random() * (settings.distance.max - settings.distance.min); var duration = settings.duration.min + Math.random() * (settings.duration.max - settings.duration.min); var targetX = x + Math.cos(angle) * distance; var targetY = y + Math.sin(angle) * distance - (settings.floatUp || 0); gameLayer.addChild(particle); tween(particle, { x: targetX, y: targetY, alpha: 0, scaleX: settings.endScale || 0.1, scaleY: settings.endScale || 0.1, rotation: settings.rotation || 0 }, { duration: duration, easing: tween.quadOut, onFinish: function onFinish() { return particle.parent && particle.parent.removeChild(particle); } }); } } }; var CardSystem = { suits: ['hearts', 'diamonds', 'clubs', 'spades'], values: ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'], suitColors: { 'hearts': 0xff0000, 'diamonds': 0xff0000, 'clubs': 0x000000, 'spades': 0x000000 }, createDeck: function createDeck() { var _this = this; var deck = []; this.suits.forEach(function (suit) { _this.values.forEach(function (value) { deck.push({ suit: suit, value: value, id: "".concat(suit, "_").concat(value) }); }); }); deck.push({ suit: 'joker', value: 'red', id: 'joker_red' }); deck.push({ suit: 'joker', value: 'black', id: 'joker_black' }); return this.shuffleDeck(deck); }, shuffleDeck: function shuffleDeck(deck) { for (var i = deck.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var _ref2 = [deck[j], deck[i]]; deck[i] = _ref2[0]; deck[j] = _ref2[1]; } return deck; }, getCardValue: function getCardValue(card) { var valueMap = { 'A': 14, 'K': 13, 'Q': 12, 'J': 11 }; if (card.cardData.suit === 'joker') return 14; return valueMap[card.cardData.value] || parseInt(card.cardData.value); }, evaluatePokerHand: function evaluatePokerHand(cards) { var _this2 = this; if (!cards || cards.length === 0) { return { type: 'none', strength: 0, multiplier: 1, contributingCards: [] }; } var sortedCards = _toConsumableArray(cards).sort(function (a, b) { return _this2.getCardValue(b) - _this2.getCardValue(a); }); var values = sortedCards.map(function (card) { return _this2.getCardValue(card); }); var suits = sortedCards.map(function (card) { return card.cardData.suit; }); var valueCounts = {}; var suitCounts = {}; values.forEach(function (value) { return valueCounts[value] = (valueCounts[value] || 0) + 1; }); suits.forEach(function (suit) { return suitCounts[suit] = (suitCounts[suit] || 0) + 1; }); var counts = Object.values(valueCounts).sort(function (a, b) { return b - a; }); var isFlush = cards.length === 5 && Object.keys(suitCounts).length === 1; var isStraight = cards.length === 5 && this.checkStraight(values); // Check hand types in order of strength var handChecks = [{ check: function check() { return isFlush && isStraight && values[0] === 14 && values[4] === 10; }, result: { type: 'royal_flush', strength: 10, multiplier: 25, contributingCards: sortedCards } }, { check: function check() { return isFlush && isStraight; }, result: { type: 'straight_flush', strength: 9, multiplier: 12, contributingCards: sortedCards } }, { check: function check() { return counts[0] >= 4; }, result: function result() { var quadValue = parseInt(Object.keys(valueCounts).find(function (v) { return valueCounts[v] >= 4; })); return { type: 'four_of_a_kind', strength: 8, multiplier: 8, contributingCards: sortedCards.filter(function (c) { return _this2.getCardValue(c) === quadValue; }) }; } }, { check: function check() { return counts[0] === 3 && counts[1] === 2; }, result: { type: 'full_house', strength: 7, multiplier: 5, contributingCards: sortedCards } }, { check: function check() { return isFlush; }, result: { type: 'flush', strength: 6, multiplier: 3.5, contributingCards: sortedCards } }, { check: function check() { return isStraight; }, result: { type: 'straight', strength: 5, multiplier: 2.5, contributingCards: sortedCards } }, { check: function check() { return counts[0] === 3; }, result: function result() { var tripValue = parseInt(Object.keys(valueCounts).find(function (v) { return valueCounts[v] === 3; })); return { type: 'three_of_a_kind', strength: 4, multiplier: 2, contributingCards: sortedCards.filter(function (c) { return _this2.getCardValue(c) === tripValue; }) }; } }, { check: function check() { return counts[0] === 2 && counts[1] === 2; }, result: function result() { var pairValues = Object.keys(valueCounts).filter(function (v) { return valueCounts[v] === 2; }).map(function (v) { return parseInt(v); }); return { type: 'two_pair', strength: 3, multiplier: 1.5, contributingCards: sortedCards.filter(function (c) { return pairValues.includes(_this2.getCardValue(c)); }) }; } }, { check: function check() { return counts[0] === 2; }, result: function result() { var pairValue = parseInt(Object.keys(valueCounts).find(function (v) { return valueCounts[v] === 2; })); return { type: 'one_pair', strength: 2, multiplier: 1.2, contributingCards: sortedCards.filter(function (c) { return _this2.getCardValue(c) === pairValue; }) }; } }]; for (var _i2 = 0, _handChecks = handChecks; _i2 < _handChecks.length; _i2++) { var _handChecks$_i = _handChecks[_i2], check = _handChecks$_i.check, result = _handChecks$_i.result; if (check()) { return typeof result === 'function' ? result() : result; } } return { type: 'high_card', strength: 1, multiplier: 1, contributingCards: [sortedCards[0]] }; }, checkStraight: function checkStraight(values) { if (values.length !== 5) return false; // Check ace-low straight if (values[0] === 14 && values[1] === 5 && values[2] === 4 && values[3] === 3 && values[4] === 2) return true; // Check normal straight for (var i = 0; i < 4; i++) { if (values[i] - values[i + 1] !== 1) return false; } return true; } }; /**** * Object Pool Manager ****/ var PoolManager = { chipPool: [], nextChipIndex: 0, bulletPool: [], nextBulletIndex: 0, CHIP_POOL_SIZE: 50, BULLET_POOL_SIZE: 100, init: function init() { this.nextChipIndex = 0; this.nextBulletIndex = 0; if (this.chipPool.length === 0) { for (var i = 0; i < this.CHIP_POOL_SIZE; i++) { var chip = new PokerChip(); chip.active = false; chip.visible = false; this.chipPool.push(chip); } } if (this.bulletPool.length === 0) { for (var i = 0; i < this.BULLET_POOL_SIZE; i++) { var bullet = new Bullet(); bullet.active = false; bullet.visible = false; this.bulletPool.push(bullet); } } }, getChip: function getChip() { for (var i = 0; i < this.CHIP_POOL_SIZE; i++) { var index = (this.nextChipIndex + i) % this.CHIP_POOL_SIZE; var chip = this.chipPool[index]; if (chip && !chip.active) { this.nextChipIndex = (index + 1) % this.CHIP_POOL_SIZE; return chip; } } return null; }, getBullet: function getBullet() { for (var i = 0; i < this.BULLET_POOL_SIZE; i++) { var index = (this.nextBulletIndex + i) % this.BULLET_POOL_SIZE; var bullet = this.bulletPool[index]; if (bullet && !bullet.active) { this.nextBulletIndex = (index + 1) % this.BULLET_POOL_SIZE; return bullet; } } return null; }, returnChip: function returnChip(chip) { chip.active = false; chip.visible = false; chip.health = 0; // Reset all visual components chip.children.forEach(function (child) { if (child.tint !== undefined) child.tint = 0xffffff; }); // Clear burn/freeze states chip.burnDamage = 0; chip.burnDuration = 0; chip.burnTickTimer = 0; chip.freezeDuration = 0; chip.freezeImmunityTimer = 0; // Remove from active arrays var index = activePlayerChips.indexOf(chip); if (index > -1) { activePlayerChips.splice(index, 1); } else { index = activeAIChips.indexOf(chip); if (index > -1) { activeAIChips.splice(index, 1); } } // Remove from containers if (chip.parent) chip.parent.removeChild(chip); }, returnBullet: function returnBullet(bullet) { var explosionColor = bullet.suit === 'hearts' || bullet.suit === 'diamonds' ? 0xff0000 : 0x333333; AnimationHelper.createParticleEffect(bullet.x, bullet.y, 'explosionParticle', { count: 5, tint: explosionColor, scale: { min: 0.5, max: 1 }, distance: { min: 20, max: 70 } }); bullet.active = false; bullet.visible = false; bullet.target = null; var index = activeBullets.indexOf(bullet); if (index > -1) { activeBullets.splice(index, 1); } if (bullet.parent) bullet.parent.removeChild(bullet); } }; /**** * Path System ****/ var PathSystem = { playerPath: [], aiPath: [], playerPathLength: 0, aiPathLength: 0, init: function init() { var padding = 130; var verticalPadding = padding - 55; // Player path var leftX = PLAYER_AREA_X - padding + 10; var rightX = PLAYER_AREA_X + PLAY_AREA_COLS * SLOT_WIDTH + padding - 30; var topY = PLAYER_AREA_Y - verticalPadding; var bottomY = PLAYER_AREA_Y + PLAY_AREA_ROWS * SLOT_HEIGHT + verticalPadding; this.playerPath = [{ x: leftX, y: bottomY }, { x: leftX, y: topY }, { x: rightX, y: topY }, { x: rightX, y: bottomY }]; // AI path (upside down mirror) var aiLeftX = AI_AREA_X - padding; var aiRightX = AI_AREA_X + PLAY_AREA_COLS * SLOT_WIDTH + padding; var aiVerticalPadding = verticalPadding - 25; var aiTopY = AI_AREA_Y - aiVerticalPadding; var aiBottomY = AI_AREA_Y + PLAY_AREA_ROWS * SLOT_HEIGHT + aiVerticalPadding; this.aiPath = [{ x: aiLeftX, y: aiTopY }, { x: aiLeftX, y: aiBottomY }, { x: aiRightX, y: aiBottomY }, { x: aiRightX, y: aiTopY }]; // Cache path lengths this.playerPathLength = this.calculatePathLength(this.playerPath); this.aiPathLength = this.calculatePathLength(this.aiPath); }, getPathStart: function getPathStart(isPlayerSide) { var path = isPlayerSide ? this.playerPath : this.aiPath; var baseStart = path[0]; var pathDirection = path[1]; var dx = pathDirection.x - baseStart.x; var dy = pathDirection.y - baseStart.y; var length = Math.sqrt(dx * dx + dy * dy); var normalizedX = dx / length; var normalizedY = dy / length; var pathOffset = Math.random() * 80; var sideOffset = (Math.random() - 0.5) * 40; return { x: baseStart.x - normalizedX * pathOffset + normalizedY * sideOffset, y: baseStart.y - normalizedY * pathOffset - normalizedX * sideOffset }; }, getPositionAlongPath: function getPositionAlongPath(progress, isPlayerSide) { var path = isPlayerSide ? this.playerPath : this.aiPath; var pathLength = isPlayerSide ? this.playerPathLength : this.aiPathLength; var targetDistance = progress / 100 * pathLength; if (targetDistance >= pathLength) { var lastPoint = path[path.length - 1]; return { x: lastPoint.x, y: lastPoint.y, completed: true }; } var currentDistance = 0; for (var i = 0; i < path.length - 1; i++) { var segmentLength = this.getDistance(path[i], path[i + 1]); if (currentDistance + segmentLength >= targetDistance) { var segmentProgress = (targetDistance - currentDistance) / segmentLength; return { x: path[i].x + (path[i + 1].x - path[i].x) * segmentProgress, y: path[i].y + (path[i + 1].y - path[i].y) * segmentProgress, completed: false }; } currentDistance += segmentLength; } var lastPoint = path[path.length - 1]; return { x: lastPoint.x, y: lastPoint.y, completed: true }; }, calculatePathLength: function calculatePathLength(path) { var total = 0; for (var i = 0; i < path.length - 1; i++) { total += this.getDistance(path[i], path[i + 1]); } return total; }, getDistance: function getDistance(p1, p2) { var dx = p2.x - p1.x; var dy = p2.y - p1.y; return Math.sqrt(dx * dx + dy * dy); } }; /**** * Chip Spawner ****/ var ChipSpawner = { spawnChip: function spawnChip(value, isPlayerSide) { var chip = PoolManager.getChip(); if (!chip) return; var activeChips = isPlayerSide ? activePlayerChips : activeAIChips; var minDistance = 85; var newPos = PathSystem.getPathStart(isPlayerSide); if (activeChips.length > 0) { var foundPosition = false; // Try random positions first for (var attempt = 0; attempt < 25; attempt++) { var candidatePos = PathSystem.getPathStart(isPlayerSide); var isValid = activeChips.every(function (otherChip) { var dx = candidatePos.x - otherChip.x; var dy = candidatePos.y - otherChip.y; return Math.sqrt(dx * dx + dy * dy) >= minDistance; }); if (isValid) { newPos = candidatePos; foundPosition = true; break; } } // Spiral search if random failed if (!foundPosition) { var baseStart = PathSystem.getPathStart(isPlayerSide); var searchRadius = minDistance; var angleStep = Math.PI / 6; spiral: for (var i = 0; i < 30; i++) { for (var angle = 0; angle < Math.PI * 2; angle += angleStep) { var testX = baseStart.x + Math.cos(angle) * searchRadius; var testY = baseStart.y + Math.sin(angle) * searchRadius; var isValid = activeChips.every(function (otherChip) { var dx = testX - otherChip.x; var dy = testY - otherChip.y; return Math.sqrt(dx * dx + dy * dy) >= minDistance; }); if (isValid) { newPos = { x: testX, y: testY }; foundPosition = true; break spiral; } } searchRadius += 20; } } } chip.activate(value, isPlayerSide, newPos); if (isPlayerSide) { activePlayerChips.push(chip); activePlayerChipsContainer.addChild(chip); } else { activeAIChips.push(chip); activeAIChipsContainer.addChild(chip); } } }; /**** * Mod System ****/ var ModSystem = { equippedMods: { hearts: null, diamonds: null, clubs: null, spades: null }, modData: { burnHeartMod: { suit: 'hearts', name: 'Flame', description: 'Hearts bullets apply burning damage over time. Burn damage is 10% of hit damage per tick for 4 seconds. Burn effects stack.' }, chipsDiamondMod: { suit: 'diamonds', name: 'Greed', description: 'Earn bonus chips when enemies are defeated, based on diamonds on board. Bonus scales with card level.' }, freezeSpadeMod: { suit: 'spades', name: 'Freeze', description: 'Spades have a chance to freeze enemies in place when dealing damage. Duration scales with card level.' }, spreadClubMod: { suit: 'clubs', name: 'Spreadshot', description: 'Clubs deal reduced damage but hit multiple enemies. Extra targets scale with card level.' } }, currentlyDisplayedMod: null, modDisplayContainer: null, topSuitGraphics: [], init: function init() { if (storage.equippedMods) { this.equippedMods = storage.equippedMods; } }, saveToStorage: function saveToStorage() { storage.equippedMods = this.equippedMods; }, equipMod: function equipMod(modAssetId) { var modData = this.modData[modAssetId]; if (!modData) return; this.equippedMods[modData.suit] = modAssetId; this.saveToStorage(); this.updateTopSuitDisplay(); this.hideModDisplay(); }, updateTopSuitDisplay: function updateTopSuitDisplay() { var _this3 = this; var suitAssets = ['heartSuit', 'diamondSuit', 'clubSuit', 'spadeSuit']; var suits = ['hearts', 'diamonds', 'clubs', 'spades']; this.topSuitGraphics.forEach(function (container, i) { if (!container) return; // Remove previous icon if (container.children.length > 1) { container.removeChildAt(1); } // Add new icon var suit = suits[i]; var assetId = _this3.equippedMods[suit] || suitAssets[i]; var suitIcon = LK.getAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); suitIcon.scale.set(1.2); suitIcon.x = 0; suitIcon.y = 0; container.addChild(suitIcon); }); }, showModDisplay: function showModDisplay(modAssetId, sourceX, sourceY) { var _this4 = this; if (this.currentlyDisplayedMod) return; var modData = this.modData[modAssetId]; if (!modData) return; this.currentlyDisplayedMod = modAssetId; this.modDisplayContainer = new Container(); this.modDisplayContainer.interactive = true; uiLayer.addChild(this.modDisplayContainer); // Background blocker var bgBlocker = new Container(); bgBlocker.interactive = true; bgBlocker.hitArea = new Rectangle(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); this.modDisplayContainer.addChild(bgBlocker); // Mod display var enlargedMod = LK.getAsset(modAssetId, { anchorX: 0.5, anchorY: 0.5 }); enlargedMod.x = SCREEN_WIDTH / 2; enlargedMod.y = SCREEN_HEIGHT / 2 - 450; enlargedMod.scale.set(1); this.modDisplayContainer.addChild(enlargedMod); // Description overlay var overlayWidth = 600; var overlayHeight = 400; var descriptionOverlay = new Container(); var overlayBg = LK.getAsset('card', { anchorX: 0.5, anchorY: 0.5 }); overlayBg.scale.set(overlayWidth / 150, overlayHeight / 240); overlayBg.tint = 0x000000; overlayBg.alpha = 0.8; descriptionOverlay.addChild(overlayBg); var nameText = new Text2(modData.name, { size: 60, fill: 0xffffff, weight: 800, stroke: 0x000000, strokeThickness: 3 }); nameText.anchor.set(0.5, 0); nameText.y = -overlayHeight / 2 - 30; descriptionOverlay.addChild(nameText); var descText = new Text2(modData.description, { size: 50, fill: 0xffffff, weight: 600, stroke: 0x000000, strokeThickness: 2, align: 'center', wordWrap: true, wordWrapWidth: overlayWidth }); descText.anchor.set(0.5, 0.5); descText.y = 20; descriptionOverlay.addChild(descText); var equipButton = LK.getAsset('card', { anchorX: 0.5, anchorY: 0.5, interactive: true }); equipButton.scale.set(1.5, 0.5); equipButton.tint = 0x000000; equipButton.alpha = 0.8; equipButton.y = overlayHeight / 2 + 150; descriptionOverlay.addChild(equipButton); var equipText = new Text2('Equip', { size: 50, fill: 0xffffff, weight: 800, stroke: 0x000000, strokeThickness: 3 }); equipText.anchor.set(0.5, 0.5); equipText.y = equipButton.y; descriptionOverlay.addChild(equipText); descriptionOverlay.x = SCREEN_WIDTH / 2; descriptionOverlay.y = SCREEN_HEIGHT / 2 + 250; descriptionOverlay.alpha = 0; this.modDisplayContainer.addChild(descriptionOverlay); equipButton.down = function () { return _this4.equipMod(modAssetId); }; tween(enlargedMod, { scaleX: 4, scaleY: 4 }, { duration: 300, easing: tween.backOut }); tween(descriptionOverlay, { alpha: 1 }, { duration: 200, delay: 150 }); bgBlocker.down = function () { return _this4.hideModDisplay(); }; }, hideModDisplay: function hideModDisplay() { var _this5 = this; if (!this.modDisplayContainer) return; tween(this.modDisplayContainer, { alpha: 0 }, { duration: 200, onFinish: function onFinish() { if (_this5.modDisplayContainer && _this5.modDisplayContainer.parent) { _this5.modDisplayContainer.parent.removeChild(_this5.modDisplayContainer); } _this5.modDisplayContainer = null; _this5.currentlyDisplayedMod = null; } }); }, getEquippedModAsset: function getEquippedModAsset(suit) { return this.equippedMods[suit] || null; } }; /**** * Wave System ****/ var WaveSystem = { playerSpawnTimer: 0, aiSpawnTimer: 0, waveNumber: 1, waveTimer: 0, waveDuration: 1800, spawnInterval: 45, bossSpawned: false, playerBossDefeated: false, aiBossDefeated: false, getChipValue: function getChipValue(waveNumber) { var waveConfigs = [{ maxWave: 1, values: [{ chance: 1, value: 1 }] }, { maxWave: 3, values: [{ chance: 0.2, value: 5 }, { chance: 1, value: 1 }] }, { maxWave: 6, values: [{ chance: 0.35, value: 5 }, { chance: 1, value: 1 }] }, { maxWave: 9, values: [{ chance: 0.65, value: 5 }, { chance: 1, value: 1 }] }, { maxWave: 15, values: [{ chance: 0.15, value: 10 }, { chance: 0.7, value: 5 }, { chance: 1, value: 1 }] }, { maxWave: 19, values: [{ chance: 0.3, value: 10 }, { chance: 0.8, value: 5 }, { chance: 1, value: 1 }] }, { maxWave: Infinity, values: [{ chance: 0.08, value: 25 }, { chance: 0.4, value: 10 }, { chance: 0.85, value: 5 }, { chance: 1, value: 1 }] }]; var config = waveConfigs.find(function (c) { return waveNumber <= c.maxWave; }); var rand = Math.random(); var _iterator = _createForOfIteratorHelper(config.values), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var _step$value = _step.value, chance = _step$value.chance, value = _step$value.value; if (rand < chance) return value; } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return 1; }, getBossChipValue: function getBossChipValue(waveNumber) { var bossLevel = Math.floor(waveNumber / 10); var baseBossValue = 210; return baseBossValue * Math.pow(2, bossLevel - 1); }, isBossWave: function isBossWave(waveNumber) { return waveNumber % 10 === 0; }, spawnChip: function spawnChip(isPlayerSide) { if (this.isBossWave(this.waveNumber)) { if (!this.bossSpawned) { var bossValue = this.getBossChipValue(this.waveNumber); ChipSpawner.spawnChip(bossValue, isPlayerSide); this.bossSpawned = true; } return; } var chipValue = this.getChipValue(this.waveNumber); ChipSpawner.spawnChip(chipValue, isPlayerSide); }, update: function update() { this.waveTimer++; if (this.waveTimer >= this.waveDuration) { if (this.isBossWave(this.waveNumber) && (!this.playerBossDefeated || !this.aiBossDefeated)) { // Wait for boss defeat } else { if (this.isBossWave(this.waveNumber) && this.playerBossDefeated && this.aiBossDefeated) { createFloatingText('ROUND COMPLETE!', SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 0x00ff00); this.playerBossDefeated = false; this.aiBossDefeated = false; } this.waveTimer = 0; this.waveNumber++; this.playerSpawnTimer = 0; this.aiSpawnTimer = 0; this.bossSpawned = false; return; } } if (this.isBossWave(this.waveNumber)) { if (!this.bossSpawned && this.waveTimer === 1) { var bossValue = this.getBossChipValue(this.waveNumber); ChipSpawner.spawnChip(bossValue, true); ChipSpawner.spawnChip(bossValue, false); this.bossSpawned = true; } if (this.bossSpawned && this.waveTimer > 120) { if (!this.playerBossDefeated && activePlayerChips.length === 0) { this.playerBossDefeated = true; createFloatingText('BOSS DEFEATED!', SCREEN_WIDTH / 2, PLAYER_AREA_Y + SLOT_HEIGHT, 0xffd700); } if (!this.aiBossDefeated && activeAIChips.length === 0) { this.aiBossDefeated = true; createFloatingText('AI BOSS DEFEATED!', SCREEN_WIDTH / 2, AI_AREA_Y + SLOT_HEIGHT, 0xffd700); } if (this.playerBossDefeated && this.aiBossDefeated) { this.waveTimer = this.waveDuration - 1; } } return; } // Normal wave spawning var currentSpawnInterval = this.waveNumber === 1 ? 90 : Math.max(45, this.spawnInterval - Math.floor((this.waveNumber - 2) * 2)); this.playerSpawnTimer++; if (this.playerSpawnTimer >= currentSpawnInterval) { this.playerSpawnTimer = 0; this.spawnChip(true); } this.aiSpawnTimer++; if (this.aiSpawnTimer >= currentSpawnInterval) { this.aiSpawnTimer = 0; this.spawnChip(false); } } }; /**** * Game State ****/ var gameState = { playerChips: 200, aiChips: 200, playerLives: 3, aiLives: 3, isPlayerTurn: true, dealCost: 25, dealCount: 0, playerDeck: [], playerHand: [], playerPlayArea: [], aiDeck: [], aiPlayArea: [] }; /**** * Game Variables ****/ var currentGameState = 'start'; var startScreenElements = []; var gameElements = []; var activePlayerChips = []; var activeAIChips = []; var activeBullets = []; var playerHandNameTexts = []; var backgroundSuits = []; var selectedCard = null; var isDragging = false; var originalCardPosition = null; var activePlayerChipsContainer = new Container(); var activeAIChipsContainer = new Container(); var playerLifeHearts = []; var aiLifeHearts = []; var opponentNameText = null; var playerNameText = null; var lastPlayerLives = 0; var lastAiLives = 0; /**** * Initialize Layers ****/ var gameLayer = new Container(); var floorBackground = LK.getAsset('floorbackround', { anchorX: 0.5, anchorY: 0.5 }); floorBackground.x = SCREEN_WIDTH / 2; floorBackground.y = SCREEN_HEIGHT / 2; gameLayer.addChild(floorBackground); var uiLayer = new Container(); game.addChild(gameLayer); game.addChild(uiLayer); /**** * UI Elements ****/ var playerChipsText = new Text2('Chips: 200', { size: 50, fill: 0xffd700, weight: 800, stroke: 0x000000, strokeThickness: 0 }); playerChipsText.x = 50; playerChipsText.y = SCREEN_HEIGHT - 120; playerChipsText.visible = false; uiLayer.addChild(playerChipsText); gameElements.push(playerChipsText); var waveText = new Text2('Wave: 1', { size: 40, fill: 0xffffff, weight: 800, stroke: 0x000000, strokeThickness: 0 }); waveText.x = SCREEN_WIDTH - 200; waveText.y = 50; waveText.visible = false; uiLayer.addChild(waveText); gameElements.push(waveText); // Deal/Discard button var discardAreaContainer = new Container(); var discardAreaGraphic = discardAreaContainer.attachAsset('dealButton', { anchorX: 0.5, anchorY: 0.5 }); var discardText = new Text2('-25', { size: 50, fill: 0xffffff, weight: 800, stroke: 0x000000, strokeThickness: 0 }); discardText.anchor.set(0.5, 0.5); discardText.y = 110; discardAreaContainer.addChild(discardText); var handStartXForDiscard = (SCREEN_WIDTH - HAND_WIDTH) / 2; var discardX = Math.min(handStartXForDiscard + HAND_WIDTH + 30 + DEAL_SLOT_WIDTH / 2 + 20, SCREEN_WIDTH - DEAL_SLOT_WIDTH / 2 - 20); discardAreaContainer.x = discardX; discardAreaContainer.y = PLAYER_DEAL_AREA_Y + DEAL_SLOT_HEIGHT / 2; discardAreaContainer.visible = false; uiLayer.addChild(discardAreaContainer); gameElements.push(discardAreaContainer); discardAreaContainer.down = function () { if (!isDragging && gameState.playerChips >= gameState.dealCost) { dealNewHand(); } }; /**** * Game Functions ****/ function calculateGreedBonus(isPlayerSide, baseChipsEarned) { if (ModSystem.getEquippedModAsset('diamonds') !== 'chipsDiamondMod') return 0; var playArea = isPlayerSide ? gameState.playerPlayArea : gameState.aiPlayArea; var diamondCount = 0; for (var row = 0; row < PLAY_AREA_ROWS; row++) { for (var col = 0; col < PLAY_AREA_COLS; col++) { var card = playArea[row][col]; if (card && card.cardData.suit === 'diamonds') diamondCount++; } } var bonusPercentage = diamondCount * 0.05; return Math.ceil(baseChipsEarned * bonusPercentage); } function createStartScreen() { ModSystem.init(); startScreenElements.forEach(function (element) { if (element.parent) element.parent.removeChild(element); }); startScreenElements = []; // Background animation var startScreenAnimContainer = new Container(); uiLayer.addChild(startScreenAnimContainer); startScreenElements.push(startScreenAnimContainer); var suitAssets = ['heartSuit', 'diamondSuit', 'clubSuit', 'spadeSuit']; var spacing = 400; var numCols = Math.ceil(SCREEN_WIDTH / spacing) + 3; var numRows = Math.ceil(SCREEN_HEIGHT / spacing) + 3; backgroundSuits = []; for (var row = 0; row < numRows; row++) { for (var col = 0; col < numCols; col++) { var suitIndex = (row + col) % suitAssets.length; var suit = LK.getAsset(suitAssets[suitIndex], { anchorX: 0.5, anchorY: 0.5 }); suit.x = col * spacing - spacing; suit.y = row * spacing - spacing; suit.alpha = 0.8; suit.scale.set(1.5); suit.baseX = col * spacing; suit.baseY = row * spacing; startScreenAnimContainer.addChild(suit); backgroundSuits.push(suit); } } // Logo var gameLogo = LK.getAsset('titleLogo', { anchorX: 0.5, anchorY: 0.5 }); gameLogo.x = SCREEN_WIDTH / 2; gameLogo.y = SCREEN_HEIGHT / 2 - 200; uiLayer.addChild(gameLogo); startScreenElements.push(gameLogo); // Bottom bar var startBottomBar = LK.getAsset('bottomBar', { anchorX: 0.5, anchorY: 1 }); startBottomBar.x = SCREEN_WIDTH / 2; startBottomBar.y = SCREEN_HEIGHT; uiLayer.addChild(startBottomBar); startScreenElements.push(startBottomBar); // Selection highlight var titleScreenSelection = LK.getAsset('titleScreenSelection', { anchorX: 0.5, anchorY: 1 }); titleScreenSelection.alpha = 0.3; uiLayer.addChild(titleScreenSelection); startScreenElements.push(titleScreenSelection); var currentTitleScreenSelection = "battle"; // Play button and icon var playButton = LK.getAsset('playButton', { anchorX: 0.5, anchorY: 0.5, interactive: true }); playButton.x = SCREEN_WIDTH / 2; playButton.y = SCREEN_HEIGHT - 100; var battleIcon = LK.getAsset('battleIcon', { anchorX: 0.5, anchorY: 1, interactive: true }); battleIcon.x = playButton.x; battleIcon.y = playButton.y - playButton.height / 2 - 10; uiLayer.addChild(battleIcon); uiLayer.addChild(playButton); startScreenElements.push(battleIcon); startScreenElements.push(playButton); // Mods button and icon var suitModButton = LK.getAsset('suitModButton', { anchorX: 0.5, anchorY: 0.5, interactive: true }); suitModButton.x = playButton.x + playButton.width / 2 + 50 + suitModButton.width / 2; suitModButton.y = playButton.y; var modsIcon = LK.getAsset('modsIcon', { anchorX: 0.5, anchorY: 1, interactive: true }); modsIcon.x = suitModButton.x; modsIcon.y = suitModButton.y - suitModButton.height / 2 - 10; uiLayer.addChild(modsIcon); uiLayer.addChild(suitModButton); startScreenElements.push(modsIcon); startScreenElements.push(suitModButton); // Selection highlight functions function updateTitleScreenSelectionHighlight() { var targetX, targetY, targetW, targetH; if (currentTitleScreenSelection === "battle") { targetX = playButton.x; targetY = playButton.y + playButton.height / 2; targetW = playButton.width * 1.1; targetH = playButton.height + battleIcon.height + 20; } else { targetX = suitModButton.x; targetY = suitModButton.y + suitModButton.height / 2; targetW = suitModButton.width * 1.1; targetH = suitModButton.height + modsIcon.height + 20; } tween.stop(titleScreenSelection); tween(titleScreenSelection, { x: targetX, y: targetY, width: targetW, height: targetH }, { duration: 180, easing: tween.cubicOut }); } // Set initial highlight titleScreenSelection.width = playButton.width * 1.2; titleScreenSelection.height = playButton.height + battleIcon.height + 20; titleScreenSelection.x = playButton.x; titleScreenSelection.y = playButton.y + playButton.height / 2; // Button handlers playButton.down = battleIcon.down = function () { if (currentTitleScreenSelection !== "battle") { currentTitleScreenSelection = "battle"; updateTitleScreenSelectionHighlight(); } startGame(); }; suitModButton.down = modsIcon.down = function () { if (currentTitleScreenSelection !== "mods") { currentTitleScreenSelection = "mods"; updateTitleScreenSelectionHighlight(); } modsContainer.visible = !modsContainer.visible; gameLogo.visible = !modsContainer.visible; if (modsContainer.visible) ModSystem.updateTopSuitDisplay(); }; // Mods container var modsContainer = new Container(); modsContainer.visible = false; uiLayer.addChild(modsContainer); startScreenElements.push(modsContainer); var totalBarWidth = SCREEN_WIDTH - 200; var suitSpacing = totalBarWidth / suitAssets.length; var startX = SCREEN_WIDTH / 2 - totalBarWidth / 2 + suitSpacing / 2; var yOffset = SCREEN_HEIGHT * 0.1; var circleY = 300 + yOffset; ModSystem.topSuitGraphics = []; for (var i = 0; i < suitAssets.length; i++) { var suitContainer = new Container(); var circleBg = LK.getAsset('card', { anchorX: 0.5, anchorY: 0.5 }); circleBg.scale.set(1.5); suitContainer.addChild(circleBg); suitContainer.x = startX + i * suitSpacing; suitContainer.y = circleY - SCREEN_HEIGHT * 0.05; modsContainer.addChild(suitContainer); ModSystem.topSuitGraphics.push(suitContainer); } var lineBreak = LK.getAsset('lineBreak', { anchorX: 0.5, anchorY: 0.5 }); lineBreak.x = SCREEN_WIDTH / 2; lineBreak.y = circleY + 360 - SCREEN_HEIGHT * 0.05; modsContainer.addChild(lineBreak); // Mod grid var gridContainer = new Container(); gridContainer.x = SCREEN_WIDTH / 2; gridContainer.y = circleY + 450 - SCREEN_HEIGHT * 0.05; modsContainer.addChild(gridContainer); var cardForSizing = LK.getAsset('card', {}); var cellWidth = cardForSizing.width; var cellHeight = cardForSizing.height; var colSpacing = 160; var rowSpacing = 140; var gridTotalWidth = 4 * cellWidth + 3 * colSpacing; var gridStartX = -gridTotalWidth / 2; var gridStartY = 50; var suitModAssets = ['burnHeartMod', 'chipsDiamondMod', 'spreadClubMod', 'freezeSpadeMod']; for (var r = 0; r < 3; r++) { for (var c = 0; c < 4; c++) { var cell = LK.getAsset('card', { anchorX: 0, anchorY: 0 }); cell.scale.set(1.3); cell.x = gridStartX + c * (cellWidth + colSpacing); cell.y = gridStartY + r * (cellHeight + rowSpacing); gridContainer.addChild(cell); if (r === 0 && suitModAssets[c]) { var modAsset = LK.getAsset(suitModAssets[c], { anchorX: 0.5, anchorY: 0.5 }); modAsset.x = cell.x + cell.width * cell.scale.x / 2; modAsset.y = cell.y + cell.height * cell.scale.y / 2; gridContainer.addChild(modAsset); cell.interactive = true; cell.down = function (assetId, modRef) { return function () { return ModSystem.showModDisplay(assetId, modRef.x, modRef.y); }; }(suitModAssets[c], modAsset); } } } currentGameState = 'start'; } function startGame() { startScreenElements.forEach(function (element) { if (element.parent) element.parent.removeChild(element); }); startScreenElements = []; backgroundSuits = []; gameElements.forEach(function (element) { return element.visible = true; }); currentGameState = 'playing'; initializeGame(); } function initializeGame() { ModSystem.init(); PoolManager.init(); PathSystem.init(); // Initialize play areas gameState.playerPlayArea = Array(PLAY_AREA_ROWS).fill().map(function () { return Array(PLAY_AREA_COLS).fill(null); }); gameState.aiPlayArea = Array(PLAY_AREA_ROWS).fill().map(function () { return Array(PLAY_AREA_COLS).fill(null); }); playerHandNameTexts = [null, null]; // Create decks gameState.playerDeck = CardSystem.createDeck(); gameState.aiDeck = CardSystem.createDeck(); // Setup UI drawPlayAreas(); createLifeDisplays(); lastPlayerLives = gameState.playerLives; lastAiLives = gameState.aiLives; // Initialize player hand gameState.playerHand = Array(5).fill(null); // Start spawning LK.setTimeout(function () { ChipSpawner.spawnChip(1, true); ChipSpawner.spawnChip(1, false); }, 1000); } function createLifeDisplays() { // Clean up existing [opponentNameText, playerNameText].concat(_toConsumableArray(playerLifeHearts), _toConsumableArray(aiLifeHearts)).forEach(function (element) { if (element && element.parent) element.parent.removeChild(element); }); playerLifeHearts = []; aiLifeHearts = []; var heartSpacing = 110; var heartScale = 0.5; var startX_AI = 130; var startX_Player = SCREEN_WIDTH - 130; var yPos = SCREEN_HEIGHT / 2 - 127; var labelYPos = yPos - 60; // Create name labels var totalAIHeartsWidth = (gameState.aiLives - 1) * heartSpacing; opponentNameText = new Text2('Opponent', { size: 40, fill: 0xffffff, weight: 800 }); opponentNameText.anchor.set(0.5, 1); opponentNameText.x = startX_AI + totalAIHeartsWidth / 2; opponentNameText.y = labelYPos; uiLayer.addChild(opponentNameText); var totalPlayerHeartsWidth = (gameState.playerLives - 1) * heartSpacing; playerNameText = new Text2('Player', { size: 40, fill: 0xffffff, weight: 800 }); playerNameText.anchor.set(0.5, 1); playerNameText.x = startX_Player - totalPlayerHeartsWidth / 2; playerNameText.y = labelYPos; uiLayer.addChild(playerNameText); // Create hearts for (var i = 0; i < gameState.aiLives; i++) { var heart = LK.getAsset('heartSuit', { anchorX: 0.5, anchorY: 0.5, scaleX: heartScale, scaleY: heartScale }); heart.x = startX_AI + i * heartSpacing; heart.y = yPos; uiLayer.addChild(heart); aiLifeHearts.push(heart); } for (var i = 0; i < gameState.playerLives; i++) { var heart = LK.getAsset('heartSuit', { anchorX: 0.5, anchorY: 0.5, scaleX: heartScale, scaleY: heartScale }); heart.x = startX_Player - i * heartSpacing; heart.y = yPos; uiLayer.addChild(heart); playerLifeHearts.push(heart); } } function dealNewHand() { if (gameState.playerChips < gameState.dealCost) return; var emptySlotIndex = gameState.playerHand.findIndex(function (card) { return !card; }); if (emptySlotIndex === -1) return; gameState.playerChips -= gameState.dealCost; gameState.dealCount++; gameState.dealCost = Math.floor(25 * Math.pow(1.05, gameState.dealCount)); if (gameState.playerDeck.length === 0) { gameState.playerDeck = CardSystem.createDeck(); } var cardData = gameState.playerDeck.pop(); var card = new Card(cardData); var startLevel = Math.floor((WaveSystem.waveNumber - 1) / 10) + 1; card.setLevel(startLevel); var slotPos = getHandSlotPosition(emptySlotIndex); var startX = slotPos.x + (Math.random() - 0.5) * 50; var startY = SCREEN_HEIGHT + DEAL_SLOT_HEIGHT; card.activate(startX, startY, false, true); card.rotation = 0; card.scaleX = 1; card.scaleY = 0.01; uiLayer.addChild(card); gameState.playerHand[emptySlotIndex] = card; tween(card, { x: slotPos.x, y: slotPos.y, scaleY: 1.2 }, Object.assign({}, ANIMATION_CONFIGS.cardDeal, { delay: emptySlotIndex * 80, onFinish: function onFinish() { return tween(card, { scaleY: 1 }, ANIMATION_CONFIGS.cardSettle); } })); updateUI(); } function updateUI() { playerChipsText.setText('Chips: ' + formatNumberWithSuffix(gameState.playerChips)); if (!isDragging) { discardText.setText('-' + formatNumberWithSuffix(gameState.dealCost)); discardText.fill = 0xffffff; if (gameState.playerChips >= gameState.dealCost) { discardAreaGraphic.tint = 0xffffff; discardAreaGraphic.alpha = 1.0; } else { discardAreaGraphic.tint = 0x666666; discardAreaGraphic.alpha = 1.0; } } waveText.setText('Wave: ' + WaveSystem.waveNumber); } function drawPlayAreas() { // Player play area for (var row = 0; row < PLAY_AREA_ROWS; row++) { for (var col = 0; col < PLAY_AREA_COLS; col++) { var slot = new Container(); var slotGraphics = slot.attachAsset('card', { anchorX: 0.5, anchorY: 0.5 }); slotGraphics.alpha = 0.5; slot.x = PLAYER_AREA_X + col * SLOT_WIDTH + SLOT_WIDTH / 2; slot.y = PLAYER_AREA_Y + row * SLOT_HEIGHT + SLOT_HEIGHT / 2; gameLayer.addChild(slot); } } // AI play area for (var row = 0; row < PLAY_AREA_ROWS; row++) { for (var col = 0; col < PLAY_AREA_COLS; col++) { var slot = new Container(); var slotGraphics = slot.attachAsset('card', { anchorX: 0.5, anchorY: 0.5 }); slotGraphics.alpha = 0.5; slotGraphics.tint = 0xff8888; slot.x = AI_AREA_X + col * SLOT_WIDTH + SLOT_WIDTH / 2; slot.y = AI_AREA_Y + row * SLOT_HEIGHT + SLOT_HEIGHT / 2; gameLayer.addChild(slot); } } // Player hand slots for (var i = 0; i < 5; i++) { var dealSlot = new Container(); var dealSlotGraphics = dealSlot.attachAsset('dealSlot', { anchorX: 0.5, anchorY: 0.5 }); dealSlotGraphics.alpha = 0.5; var slotPos = getHandSlotPosition(i); dealSlot.x = slotPos.x; dealSlot.y = slotPos.y; gameLayer.addChild(dealSlot); } } function getSlotPosition(row, col, isPlayerArea) { var baseX = isPlayerArea ? PLAYER_AREA_X : AI_AREA_X; var baseY = isPlayerArea ? PLAYER_AREA_Y : AI_AREA_Y; return { x: baseX + col * SLOT_WIDTH + SLOT_WIDTH / 2, y: baseY + row * SLOT_HEIGHT + SLOT_HEIGHT / 2 }; } function getSlotFromPosition(x, y) { // Check player play area if (x >= PLAYER_AREA_X && x <= PLAYER_AREA_X + PLAY_AREA_COLS * SLOT_WIDTH && y >= PLAYER_AREA_Y && y <= PLAYER_AREA_Y + PLAY_AREA_ROWS * SLOT_HEIGHT) { var col = Math.floor((x - PLAYER_AREA_X) / SLOT_WIDTH); var row = Math.floor((y - PLAYER_AREA_Y) / SLOT_HEIGHT); if (col >= 0 && col < PLAY_AREA_COLS && row >= 0 && row < PLAY_AREA_ROWS) { return { area: 'player', row: row, col: col }; } } // Check player hand area if (y >= PLAYER_DEAL_AREA_Y && y <= PLAYER_DEAL_AREA_Y + DEAL_SLOT_HEIGHT) { for (var i = 0; i < 5; i++) { var slotPos = getHandSlotPosition(i); var slotXStart = slotPos.x - DEAL_SLOT_WIDTH / 2; var slotXEnd = slotPos.x + DEAL_SLOT_WIDTH / 2; if (x >= slotXStart && x <= slotXEnd) { return { area: 'hand', index: i }; } } } // Check discard area if (discardAreaContainer && x >= discardAreaContainer.x - DEAL_SLOT_WIDTH / 2 && x <= discardAreaContainer.x + DEAL_SLOT_WIDTH / 2 && y >= discardAreaContainer.y - DEAL_SLOT_HEIGHT / 2 && y <= discardAreaContainer.y + DEAL_SLOT_HEIGHT / 2) { return { area: 'discard' }; } return null; } function evaluateRowHand(row, isPlayerArea) { var playArea = isPlayerArea ? gameState.playerPlayArea : gameState.aiPlayArea; var cards = []; for (var col = 0; col < PLAY_AREA_COLS; col++) { if (playArea[row][col]) cards.push(playArea[row][col]); } return CardSystem.evaluatePokerHand(cards); } function updateHandNameDisplay(row, handEval) { var existingText = playerHandNameTexts[row]; var shouldShowText = handEval.strength > 1; if (shouldShowText) { var handName = handEval.type.replace(/_/g, ' ').toUpperCase(); if (existingText) { if (existingText.text !== handName) { existingText.setText(handName); } existingText.visible = true; } else { var newText = new Text2(handName, { size: 50, fill: 0xffffff, weight: '800', stroke: 0x000000, strokeThickness: 0 }); newText.anchor.set(0.5, 0); newText.alpha = 0.8; newText.x = PLAYER_AREA_X + PLAY_AREA_COLS * SLOT_WIDTH / 2; newText.y = PLAYER_AREA_Y + (row + 1) * SLOT_HEIGHT - 20; uiLayer.addChild(newText); playerHandNameTexts[row] = newText; } } else if (existingText) { existingText.visible = false; } } function applyHandBonuses() { // Player cards for (var row = 0; row < PLAY_AREA_ROWS; row++) { var handEval = evaluateRowHand(row, true); updateHandNameDisplay(row, handEval); var contributingCards = handEval.contributingCards || []; for (var col = 0; col < PLAY_AREA_COLS; col++) { var card = gameState.playerPlayArea[row][col]; if (card) { card.handBonus = handEval.multiplier; card.calculateStats(); card.redOutline.visible = handEval.strength > 1 && contributingCards.includes(card); } } } // AI cards AISystem.applyAIHandBonuses(); } /**** * AI System ****/ var AISystem = { thinkTimer: 0, thinkDelay: 60, update: function update() { this.thinkTimer++; if (this.thinkTimer >= this.thinkDelay) { this.thinkTimer = 0; this.makeMove(); } }, shouldDeal: function shouldDeal() { if (gameState.aiChips < gameState.dealCost) return false; return this.countEmptySlots() > 0; }, countEmptySlots: function countEmptySlots() { var count = 0; for (var row = 0; row < PLAY_AREA_ROWS; row++) { for (var col = 0; col < PLAY_AREA_COLS; col++) { if (!gameState.aiPlayArea[row][col]) count++; } } return count; }, makeMove: function makeMove() { if (this.tryMergeCards()) return; if (this.tryPlaceCards()) return; if (this.optimizeCardPositions()) return; if (this.shouldDeal()) this.dealAIHand(); }, tryMergeCards: function tryMergeCards() { for (var row1 = 0; row1 < PLAY_AREA_ROWS; row1++) { for (var col1 = 0; col1 < PLAY_AREA_COLS; col1++) { var card1 = gameState.aiPlayArea[row1][col1]; if (!card1) continue; for (var row2 = 0; row2 < PLAY_AREA_ROWS; row2++) { for (var col2 = 0; col2 < PLAY_AREA_COLS; col2++) { if (row1 === row2 && col1 === col2) continue; var card2 = gameState.aiPlayArea[row2][col2]; if (!card2) continue; if (card1.canMergeWith(card2)) { this.mergeCards(card1, card2, row1, col1, row2, col2); return true; } } } } } return false; }, mergeCards: function mergeCards(card1, card2, row1, col1, row2, col2) { var mergedCard = card1.mergeWith(card2); if (!mergedCard) return; gameLayer.removeChild(card1); gameLayer.removeChild(card2); gameState.aiPlayArea[row1][col1] = null; gameState.aiPlayArea[row2][col2] = null; var pos = getSlotPosition(row1, col1, false); mergedCard.activate(pos.x, pos.y, true, false); gameLayer.addChild(mergedCard); gameState.aiPlayArea[row1][col1] = mergedCard; mergedCard.alpha = 0; mergedCard.scale.set(1.5); tween(mergedCard, { alpha: 1, scaleX: 1, scaleY: 1 }, ANIMATION_CONFIGS.cardMerge); createFloatingText('+Level Up!', mergedCard.x, mergedCard.y - 50, 0x00ff00); this.applyAIHandBonuses(); }, tryPlaceCards: function tryPlaceCards() { return false; // AI deals directly to board }, optimizeCardPositions: function optimizeCardPositions() { return this.tryCompletePokerHands(); }, tryCompletePokerHands: function tryCompletePokerHands() { for (var row = 0; row < PLAY_AREA_ROWS; row++) { var rowCards = []; var emptyPositions = []; for (var col = 0; col < PLAY_AREA_COLS; col++) { if (gameState.aiPlayArea[row][col]) { rowCards.push({ card: gameState.aiPlayArea[row][col], col: col }); } else { emptyPositions.push(col); } } if (rowCards.length >= 3 && emptyPositions.length > 0) { if (this.tryMoveCardToCompleteHand(row, rowCards, emptyPositions[0])) { return true; } } } return false; }, tryMoveCardToCompleteHand: function tryMoveCardToCompleteHand(targetRow, existingCards, targetCol) { for (var row = 0; row < PLAY_AREA_ROWS; row++) { if (row === targetRow) continue; for (var col = 0; col < PLAY_AREA_COLS; col++) { var card = gameState.aiPlayArea[row][col]; if (!card) continue; if (this.cardHelpsHand(card, existingCards) && Math.random() < 0.3) { gameState.aiPlayArea[row][col] = null; gameState.aiPlayArea[targetRow][targetCol] = card; var newPos = getSlotPosition(targetRow, targetCol, false); tween(card, { x: newPos.x, y: newPos.y }, { duration: 300, easing: tween.quadOut }); this.applyAIHandBonuses(); return true; } } } return false; }, cardHelpsHand: function cardHelpsHand(card, existingCards) { var suits = {}; var values = {}; existingCards.forEach(function (_ref3) { var c = _ref3.card; suits[c.cardData.suit] = (suits[c.cardData.suit] || 0) + 1; values[c.cardData.value] = (values[c.cardData.value] || 0) + 1; }); return suits[card.cardData.suit] >= 2 || values[card.cardData.value] >= 1; }, dealAIHand: function dealAIHand() { if (gameState.aiChips < gameState.dealCost) return; gameState.aiChips -= gameState.dealCost; var cardData; do { if (gameState.aiDeck.length === 0) { gameState.aiDeck = CardSystem.createDeck(); } cardData = gameState.aiDeck.pop(); } while (cardData.suit === 'joker'); var card = new Card(cardData); var startLevel = Math.floor((WaveSystem.waveNumber - 1) / 10) + 1; card.setLevel(startLevel); var bestSlot = this.findBestEmptySlot(); if (!bestSlot) return; var pos = getSlotPosition(bestSlot.row, bestSlot.col, false); var startY = -SLOT_HEIGHT; card.activate(pos.x, startY, true, false); card.rotation = Math.PI * 4; card.scale.set(0.1); gameLayer.addChild(card); gameState.aiPlayArea[bestSlot.row][bestSlot.col] = card; tween(card, { y: pos.y, rotation: 0, scaleX: 1, scaleY: 1 }, { duration: 600, easing: tween.quadOut }); this.applyAIHandBonuses(); }, findBestEmptySlot: function findBestEmptySlot() { var emptySlots = []; for (var row = 0; row < PLAY_AREA_ROWS; row++) { for (var col = 0; col < PLAY_AREA_COLS; col++) { if (!gameState.aiPlayArea[row][col]) { var score = this.evaluateSlotScore(row, col); emptySlots.push({ row: row, col: col, score: score }); } } } if (emptySlots.length === 0) return null; emptySlots.sort(function (a, b) { return b.score - a.score; }); return emptySlots[0]; }, evaluateSlotScore: function evaluateSlotScore(row, col) { var score = 0; var cardsInRow = 0; for (var c = 0; c < PLAY_AREA_COLS; c++) { if (gameState.aiPlayArea[row][c]) cardsInRow++; } score += cardsInRow * 10; score += (2 - Math.abs(col - 2)) * 2; return score; }, applyAIHandBonuses: function applyAIHandBonuses() { for (var row = 0; row < PLAY_AREA_ROWS; row++) { var handEval = evaluateRowHand(row, false); var contributingCards = handEval.contributingCards || []; for (var col = 0; col < PLAY_AREA_COLS; col++) { var card = gameState.aiPlayArea[row][col]; if (card) { card.handBonus = handEval.multiplier; card.calculateStats(); card.redOutline.visible = handEval.strength > 1 && contributingCards.includes(card); } } } } }; /**** * Input Handling ****/ game.down = function (x, y, obj) { if (currentGameState !== 'playing') return; // Check hand cards for (var i = 0; i < gameState.playerHand.length; i++) { var card = gameState.playerHand[i]; if (!card) continue; var slotPos = getHandSlotPosition(i); if (Math.abs(x - slotPos.x) < DEAL_SLOT_WIDTH / 2 && Math.abs(y - slotPos.y) < DEAL_SLOT_HEIGHT / 2) { selectedCard = card; isDragging = true; originalCardPosition = { area: 'hand', index: i }; uiLayer.addChild(selectedCard); return; } } // Check play area cards for (var row = 0; row < PLAY_AREA_ROWS; row++) { for (var col = 0; col < PLAY_AREA_COLS; col++) { var card = gameState.playerPlayArea[row][col]; if (!card) continue; var pos = getSlotPosition(row, col, true); if (Math.abs(x - pos.x) < SLOT_WIDTH / 2 && Math.abs(y - pos.y) < SLOT_HEIGHT / 2) { selectedCard = card; isDragging = true; originalCardPosition = { area: 'player', row: row, col: col }; gameState.playerPlayArea[row][col] = null; gameLayer.addChild(selectedCard); return; } } } }; game.move = function (x, y, obj) { if (currentGameState !== 'playing' || !isDragging || !selectedCard) return; selectedCard.x = x; selectedCard.y = y; // Highlight mergeable cards for (var row = 0; row < PLAY_AREA_ROWS; row++) { for (var col = 0; col < PLAY_AREA_COLS; col++) { var card = gameState.playerPlayArea[row][col]; if (card) { card.greenOutline.visible = selectedCard.canMergeWith(card); } } } gameState.playerHand.forEach(function (card) { if (card && card !== selectedCard) { card.greenOutline.visible = selectedCard.canMergeWith(card); } }); // Update discard area discardAreaGraphic.tint = 0x440000; var isOverDiscard = discardAreaContainer && x >= discardAreaContainer.x - DEAL_SLOT_WIDTH / 2 && x <= discardAreaContainer.x + DEAL_SLOT_WIDTH / 2 && y >= discardAreaContainer.y - DEAL_SLOT_HEIGHT / 2 && y <= discardAreaContainer.y + DEAL_SLOT_HEIGHT / 2; if (isOverDiscard) { discardAreaGraphic.alpha = 1.0; discardText.setText('Sell Card'); discardText.fill = 0xffd700; } else { discardAreaGraphic.alpha = 0.7; discardText.setText('Discard'); discardText.fill = 0x999999; } }; game.up = function (x, y, obj) { if (currentGameState !== 'playing' || !isDragging || !selectedCard) return; isDragging = false; // Clear outlines [].concat(_toConsumableArray(gameState.playerPlayArea.flat()), _toConsumableArray(gameState.playerHand)).filter(function (card) { return card; }).forEach(function (card) { return card.greenOutline.visible = false; }); updateUI(); var targetSlot = getSlotFromPosition(x, y); if (!targetSlot) { returnCardToOriginalPosition(); selectedCard = null; originalCardPosition = null; applyHandBonuses(); return; } // Handle discard if (targetSlot.area === 'discard') { var chipRefund = 5 + selectedCard.level * 2; gameState.playerChips += chipRefund; if (originalCardPosition.area === 'hand') { gameState.playerHand[originalCardPosition.index] = null; } if (selectedCard.parent) selectedCard.parent.removeChild(selectedCard); createFloatingText('+' + formatNumberWithSuffix(chipRefund) + ' Chips', selectedCard.x, selectedCard.y, 0xffd700); selectedCard = null; originalCardPosition = null; updateUI(); applyHandBonuses(); return; } // Handle placement if (targetSlot.area === 'player') { handlePlayAreaPlacement(targetSlot); } else if (targetSlot.area === 'hand') { handleHandPlacement(targetSlot); } selectedCard = null; originalCardPosition = null; applyHandBonuses(); }; function handlePlayAreaPlacement(targetSlot) { var existingCard = gameState.playerPlayArea[targetSlot.row][targetSlot.col]; if (existingCard && selectedCard.canMergeWith(existingCard)) { // Merge cards var mergedCard = selectedCard.mergeWith(existingCard); if (!mergedCard) return; gameLayer.removeChild(existingCard); var handIndex = gameState.playerHand.indexOf(selectedCard); if (handIndex !== -1) { uiLayer.removeChild(selectedCard); gameState.playerHand[handIndex] = null; } else { gameLayer.removeChild(selectedCard); } var pos = getSlotPosition(targetSlot.row, targetSlot.col, true); mergedCard.activate(pos.x, pos.y, true); gameLayer.addChild(mergedCard); gameState.playerPlayArea[targetSlot.row][targetSlot.col] = mergedCard; mergedCard.alpha = 0; mergedCard.scale.set(2); tween(mergedCard, { alpha: 1, scaleX: 1, scaleY: 1 }, ANIMATION_CONFIGS.cardMerge); createFloatingText('+Level Up!', mergedCard.x, mergedCard.y - 50, 0x00ff00); } else if (!existingCard) { // Place in empty slot if (selectedCard.cardData.suit === 'joker') { createFloatingText('Jokers can only level up other cards.', selectedCard.x, selectedCard.y, 0xffcc00); returnCardToOriginalPosition(); return; } var pos = getSlotPosition(targetSlot.row, targetSlot.col, true); selectedCard.activate(pos.x, pos.y, true); var handIndex = gameState.playerHand.indexOf(selectedCard); if (handIndex !== -1) { uiLayer.removeChild(selectedCard); gameLayer.addChild(selectedCard); gameState.playerHand[handIndex] = null; } gameState.playerPlayArea[targetSlot.row][targetSlot.col] = selectedCard; } else { // Swap cards if (selectedCard.cardData.suit === 'joker') { createFloatingText('Jokers can only level up other cards.', selectedCard.x, selectedCard.y, 0xffcc00); returnCardToOriginalPosition(); return; } var swappedCard = existingCard; // Place selected card var pos1 = getSlotPosition(targetSlot.row, targetSlot.col, true); selectedCard.activate(pos1.x, pos1.y, true); var handIndex = gameState.playerHand.indexOf(selectedCard); if (handIndex !== -1) { uiLayer.removeChild(selectedCard); gameLayer.addChild(selectedCard); gameState.playerHand[handIndex] = null; } gameState.playerPlayArea[targetSlot.row][targetSlot.col] = selectedCard; // Place swapped card back if (originalCardPosition.area === 'player') { var pos2 = getSlotPosition(originalCardPosition.row, originalCardPosition.col, true); swappedCard.activate(pos2.x, pos2.y, true); gameState.playerPlayArea[originalCardPosition.row][originalCardPosition.col] = swappedCard; } else { var slotPos = getHandSlotPosition(originalCardPosition.index); swappedCard.activate(slotPos.x, slotPos.y, false, true); gameLayer.removeChild(swappedCard); uiLayer.addChild(swappedCard); gameState.playerHand[originalCardPosition.index] = swappedCard; } } } function handleHandPlacement(targetSlot) { var existingCard = gameState.playerHand[targetSlot.index]; if (existingCard && existingCard !== selectedCard && selectedCard.canMergeWith(existingCard)) { // Merge in hand var mergedCard = selectedCard.mergeWith(existingCard); if (!mergedCard) { returnCardToOriginalPosition(); return; } // Remove old cards var handIndex1 = gameState.playerHand.indexOf(selectedCard); if (handIndex1 !== -1) { uiLayer.removeChild(selectedCard); gameState.playerHand[handIndex1] = null; } else { gameLayer.removeChild(selectedCard); } var handIndex2 = gameState.playerHand.indexOf(existingCard); if (handIndex2 !== -1) { uiLayer.removeChild(existingCard); gameState.playerHand[handIndex2] = null; } // Place merged card var slotPos = getHandSlotPosition(targetSlot.index); mergedCard.activate(slotPos.x, slotPos.y, false, true); uiLayer.addChild(mergedCard); gameState.playerHand[targetSlot.index] = mergedCard; mergedCard.alpha = 0; mergedCard.scale.set(2); tween(mergedCard, { alpha: 1, scaleX: 1, scaleY: 1 }, ANIMATION_CONFIGS.cardMerge); createFloatingText('+Level Up!', mergedCard.x, mergedCard.y - 50, 0x00ff00); } else if (existingCard && existingCard !== selectedCard) { // Swap cards var swappedCard = existingCard; // Move selected card to target slot var slotPos1 = getHandSlotPosition(targetSlot.index); selectedCard.activate(slotPos1.x, slotPos1.y, false, true); if (originalCardPosition.area === 'player') { gameLayer.removeChild(selectedCard); uiLayer.addChild(selectedCard); } else { gameState.playerHand[originalCardPosition.index] = null; } gameState.playerHand[targetSlot.index] = selectedCard; // Move swapped card to original position if (originalCardPosition.area === 'player') { var origPos = getSlotPosition(originalCardPosition.row, originalCardPosition.col, true); swappedCard.activate(origPos.x, origPos.y, true); uiLayer.removeChild(swappedCard); gameLayer.addChild(swappedCard); gameState.playerPlayArea[originalCardPosition.row][originalCardPosition.col] = swappedCard; } else { var slotPos2 = getHandSlotPosition(originalCardPosition.index); swappedCard.activate(slotPos2.x, slotPos2.y, false, true); gameState.playerHand[originalCardPosition.index] = swappedCard; } } else { returnCardToOriginalPosition(); } } function returnCardToOriginalPosition() { if (!selectedCard || !originalCardPosition) return; if (originalCardPosition.area === 'hand') { var slotPos = getHandSlotPosition(originalCardPosition.index); selectedCard.x = slotPos.x; selectedCard.y = slotPos.y; gameState.playerHand[originalCardPosition.index] = selectedCard; } else if (originalCardPosition.area === 'player') { var pos = getSlotPosition(originalCardPosition.row, originalCardPosition.col, true); selectedCard.x = pos.x; selectedCard.y = pos.y; gameState.playerPlayArea[originalCardPosition.row][originalCardPosition.col] = selectedCard; } } function createFloatingText(text, x, y, color, size) { var textOptions = { size: size || 40, fill: color || 0xffffff, weight: 800, stroke: 0x000000, strokeThickness: 0 }; if (color === 0x00ff00) { textOptions.strokeThickness = 6; textOptions.size = (size || 40) * 2; } var floatingText = new Text2(text, textOptions); floatingText.anchor.set(0.5, 0.5); floatingText.x = x; floatingText.y = y; floatingText.alpha = 1; uiLayer.addChild(floatingText); var animationDistance = size && size > 40 ? 120 : 80; var animationDuration = size && size > 40 ? 2000 : 1500; tween(floatingText, { y: y - animationDistance, alpha: 0 }, { duration: animationDuration, easing: tween.quadOut, onFinish: function onFinish() { return uiLayer.removeChild(floatingText); } }); } /**** * Main Game Loop ****/ game.update = function () { if (currentGameState !== 'playing') { if (currentGameState === 'start' && backgroundSuits.length > 0) { var speed = 0.5; var spacing = 400; var numCols = Math.ceil(SCREEN_WIDTH / spacing) + 3; var numRows = Math.ceil(SCREEN_HEIGHT / spacing) + 3; var patternWidth = numCols * spacing; var patternHeight = numRows * spacing; backgroundSuits.forEach(function (suit) { suit.x += speed; suit.y += speed; if (suit.x > SCREEN_WIDTH + spacing * 1.5) { suit.x -= patternWidth; } if (suit.y > SCREEN_HEIGHT + spacing * 1.5) { suit.y -= patternHeight; } }); } return; } // Chip cleanup is handled by the PoolManager when chips are returned to the pool. // The filter here is redundant and causes performance issues due to array recreation every frame. // Update systems WaveSystem.update(); // Update entities activePlayerChips.forEach(function (chip) { return chip.update(); }); activeAIChips.forEach(function (chip) { return chip.update(); }); activeBullets.forEach(function (bullet) { return bullet.update(); }); // Update cards for (var row = 0; row < PLAY_AREA_ROWS; row++) { for (var col = 0; col < PLAY_AREA_COLS; col++) { var playerCard = gameState.playerPlayArea[row][col]; var aiCard = gameState.aiPlayArea[row][col]; if (playerCard) playerCard.update(); if (aiCard) aiCard.update(); } } // Update AI AISystem.update(); // Update life displays while (gameState.playerLives < lastPlayerLives) { lastPlayerLives--; var heartToFade = playerLifeHearts[lastPlayerLives]; if (heartToFade) { tween(heartToFade, { alpha: 0.2 }, { duration: 500 }); } } while (gameState.aiLives < lastAiLives) { lastAiLives--; var heartToFade = aiLifeHearts[lastAiLives]; if (heartToFade) { tween(heartToFade, { alpha: 0.2 }, { duration: 500 }); } } // Check win/lose if (gameState.playerLives <= 0) { showGameOver(false); } else if (gameState.aiLives <= 0) { showGameOver(true); } updateUI(); }; /**** * Game Over ****/ function showGameOver(playerWon) { if (playerWon) { LK.showYouWin(); } else { LK.showGameOver(); } } var background = LK.getAsset('background', { anchorX: 0.5, anchorY: 0 }); background.x = SCREEN_WIDTH / 2; background.y = 50; background.visible = false; gameLayer.addChild(background); gameElements.push(background); // Chip racks var chipRack1 = LK.getAsset('chipRack', { anchorX: 0.5, anchorY: 0 }); var rackWidth = chipRack1.width; chipRack1.x = SCREEN_WIDTH / 2 - rackWidth / 2; chipRack1.y = 60 - chipRack1.height * 0.75 + 30; chipRack1.visible = false; gameLayer.addChild(chipRack1); gameElements.push(chipRack1); var chipRack2 = LK.getAsset('chipRack', { anchorX: 0.5, anchorY: 0 }); chipRack2.x = SCREEN_WIDTH / 2 + rackWidth / 2; chipRack2.y = 60 - chipRack2.height * 0.75 + 30; chipRack2.visible = false; gameLayer.addChild(chipRack2); gameElements.push(chipRack2); // Border var border = LK.getAsset('border', { anchorX: 0.5, anchorY: 0.5 }); border.x = SCREEN_WIDTH / 2; border.y = SCREEN_HEIGHT / 2; border.visible = false; gameLayer.addChild(border); gameElements.push(border); // Bottom bar var bottomBar = LK.getAsset('bottomBar', { anchorX: 0.5, anchorY: 1 }); bottomBar.x = SCREEN_WIDTH / 2; bottomBar.y = SCREEN_HEIGHT; bottomBar.visible = false; gameLayer.addChild(bottomBar); gameElements.push(bottomBar); // Chip stack var chipStack = LK.getAsset('chipStack', { anchorX: 0.5, anchorY: 1 }); chipStack.x = playerChipsText.x + playerChipsText.width / 2; chipStack.y = playerChipsText.y - 10; chipStack.visible = false; uiLayer.addChild(chipStack); gameElements.push(chipStack); // Initialize containers gameLayer.addChildAt(activePlayerChipsContainer, gameLayer.getChildIndex(bottomBar)); gameLayer.addChildAt(activeAIChipsContainer, gameLayer.getChildIndex(bottomBar)); // Start game createStartScreen();
===================================================================
--- original.js
+++ change.js
@@ -94,20 +94,16 @@
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
}
} else if (self.target) {
- var prevX = self.x;
- var prevY = self.y;
self.targetLastX = self.target.x;
self.targetLastY = self.target.y;
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
- var angle = Math.atan2(dy, dx);
- var newX = self.x + Math.cos(angle) * self.speed;
- var newY = self.y + Math.sin(angle) * self.speed;
- var hitRadius = 85;
- var collisionDistance = distancePointToLine(self.target.x, self.target.y, prevX, prevY, newX, newY);
- if (collisionDistance <= hitRadius) {
+ var hitRadiusSq = 85 * 85; // Use squared radius for performance
+ var distanceSq = dx * dx + dy * dy;
+ // Since bullet speed is much smaller than hit radius, a simple distance check is sufficient and more performant.
+ if (distanceSq < hitRadiusSq) {
self.target.takeDamage(self.damage);
// Apply special effects based on mod
if (self.suit === 'hearts' && ModSystem.getEquippedModAsset('hearts') === 'burnHeartMod') {
var burnDamage = self.damage * 0.1;
@@ -119,10 +115,12 @@
}
if (currentGraphic) currentGraphic.visible = false;
PoolManager.returnBullet(self);
} else {
- self.x = newX;
- self.y = newY;
+ // Move towards target
+ var distance = Math.sqrt(distanceSq);
+ self.x += dx / distance * self.speed;
+ self.y += dy / distance * self.speed;
}
} else {
if (currentGraphic) currentGraphic.visible = false;
PoolManager.returnBullet(self);
@@ -2880,15 +2878,10 @@
});
}
return;
}
- // Clean up dead chips
- activePlayerChips = activePlayerChips.filter(function (chip) {
- return chip.active && chip.health > 0;
- });
- activeAIChips = activeAIChips.filter(function (chip) {
- return chip.active && chip.health > 0;
- });
+ // Chip cleanup is handled by the PoolManager when chips are returned to the pool.
+ // The filter here is redundant and causes performance issues due to array recreation every frame.
// Update systems
WaveSystem.update();
// Update entities
activePlayerChips.forEach(function (chip) {
A long rack of different colored poker chips seen from above. Anime style.. In-Game asset. 2d. High contrast. No shadows
A graphic for the center of a joker card.
a 2:3 format thin black border with nothing in the center. In-Game asset. 2d. High contrast. No shadows
A small white explosion particle.. In-Game asset. 2d. High contrast. No shadows
Make the blue a lighter blue.
Make this in a white instead of blue. Keep everything else the same.
A couple different sized stacks of these chips beside each other.
Just the spade from this picture with a blue snowflake in the middle of it.
Just the heart from this picture with a flame in the cent t of it.
Just the club from this picture with 1. **Fan/Spray Symbol** - Three or more lines radiating outward from a central point, yellow in color, in the center of the club.
Just the diamond from this picture with a dollar sign in the center
A white circle with a lightening gradient towards the edge.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A simple golden line break.. In-Game asset. 2d. High contrast. No shadows
A fanned card hand that shows a royal flush in spades. Anime style. In-Game asset. 2d. High contrast. No shadows
An SVG of the word 'Battle'. text in yellow with a black outline. In-Game asset. 2d. High contrast. No shadows
change the text to say "Mods"
The four card suits arranged in 2x2 grid layout, no lines. Anime style. In-Game asset. 2d. High contrast. No shadows
A single ice crystal. anime style. In-Game asset. 2d. High contrast. No shadows
Change the text to say ‘Refund’. Change the cards to a trash can.
A completely blank playing card with textured surface. Slightly used edges with a couple nicks out of it. Black background. In-Game asset. 2d. High contrast. No shadows
A 3:2 ratio rectangular green button that says “PvP” using this yellow font.
Change the text to say ‘Co-op’
Change the font to say ‘Victory!’
Change the text to say ‘Defeat!’
A 2:3 ratio rectangular picture that shows two card playing cats in a casino very close face to face with teeth bared and fists clenched as if they’re about to fight. Each cat has a different card suit pattern on the fur of their forehead. One is wearing a suit and the other is wearing tan leather jacket with a striped tank top underneath. Anime style.. In-Game asset. 2d. High contrast. No shadows
Show these same cats smiling and instead of clenched fists they’re grasping hands because they’re friends.
Incorporate these two cats heads into a game logo for a poker based tower defense that includes the name “Double Down Defense”. Put their heads offset on either side with eyes open and looking at the logo.
A small treasure chest with poker themed graphics on it. Anime style. In-Game asset. 2d. High contrast. No shadows
The hearts card suit symbol with two linked hearts in the center of it. Anime style.. In-Game asset. 2d. High contrast. No shadows
The diamond card suit with a coin in the center. The coin has a ‘2X’ in the center. Anime style.. In-Game asset. 2d. High contrast. No shadows
Just the club from this picture with a clock in the center.
Just the spade from this image with a land mine in the center of it.
Just the mine from this image.
Just the heart from this image with a piggy bank in the center.
Just the diamond from this picture with a sword with a small arrow pointing up in the center of the diamond.
Just the club from this picture with an icon in the center of it that represents a projectile bouncing at an angle off of a surface.
Just the spade with a skull in the center of it. Anime style.
This chest with the top open and nothing inside.
Change the text to say Shop
An old style cash register. The numeric read out says 7.77. Anime style.. In-Game asset. 2d. High contrast. No shadows
A giant question mark. Anime style.. In-Game asset. 2d. High contrast. No shadows
A shield with a spade and heart card suit coat of arms on it with a sword crossed downwards, behind it. icon. Anime style.. In-Game asset. 2d. High contrast. No shadows
Change the text to say ‘Draw’
The back of a playing card. Blue pattern. Anime style.. In-Game asset. 2d. High contrast. No shadows
The back of a playing card. Red pattern with a heart in the center. Anime style.. In-Game asset. 2d. High contrast. No shadows
Change the blue color to gold and put a blue glowing shield in the center.
Change the image of the shield in the center into a red question mark lined with gold.
Change the image of a shield in the center into a robotic cheetah head. Change the background color in the center to green.
Change the image of the shield into the center to a steel padlock with a keyhole on it. Change the background color in the center to yellow.
Change the shield in the center to a picture of a winding snake and the background color in the center in purple.
Change the word to say Slots
An icon of a slot machine. Anime style. High definition.. In-Game asset. 2d. High contrast. No shadows
A “Spin” button for an electronic slot machine. Button is red and font is in yellow. Anime style. High definition.. In-Game asset. 2d. High contrast. No shadows
Create a symbol that’s just the white cats head looking straight forward with X’s for eyes and his tongue hanging out of the side of his mouth.
Change the text to say Paytable and adjust button shape to accommodate.
Change the button to say $10 and make it square.
Change the button to say $25
Change the button to say $50
Change the button to say $100
Change the button color to light green.
Change the button color to light green.
Change the button color to light green.
Change the button color to light green.
Add an icon of the four card suits in the center of the heart. Anime style.
Just the spade from this picture with an icon of a cartoon spy with a hood and a mask on in the center of the spade.
Just the club from this picture with a small bubbling green vial of poison in the center of it. Anime style.
Just the diamond from this picture with an icon of a scale in the center.
titleSong
Music
pvpMusic
Music
getReady
Sound effect
gameStart
Sound effect
cardLand
Sound effect
shootSound
Sound effect
levelUp
Sound effect
buttonPress
Sound effect
pokerChipDie
Sound effect
roulette
Sound effect
victory
Sound effect
defeat
Sound effect
slotButton
Sound effect
slotSpin
Sound effect
reelStop
Sound effect
slotWin
Sound effect