User prompt
Büyü rakiptede olmalı
User prompt
Büyüler kuleye hasar vermemeli
User prompt
Herkes neden birbirine tek atıyor düzelt artık şu hatayı karakterlerin altında kaç hasar verecek yazıyorsa o kadar hasar versin daha fazla vurmasın
User prompt
Tüm karakterlerin canını 6 arttır
User prompt
Şimşek Bolt pasifini kaldır
User prompt
Oyuna büyü ekle ilk büyü elev topu olsun değen rakibe 4 hasar versin büyüler kart gibi kullanılmaz sadece rakibe fırlatılır yada kendi takımını bufflama özellikleri olsun
User prompt
Kartın altında kaç hasar vuracak yazıyorsa o kadar hasar vursun daha fazla vurmasın
User prompt
Kartlar ölüp desteye geri geldiğinde Eski hallerinde gelsinler 0 canla desteye gelmesinler
User prompt
Kulelerin canı 50 olsun
User prompt
Bütün kartların canını 4 hasarını 1 arttır
User prompt
Kartlar ölüp desteye geri geldiğinde öldükleri şekil 0 canla gelmesin normalde canları ne kadarsa öyle gelsin
User prompt
Air wisp canı 3 olsun
User prompt
Artık mana her tur 1 gelsin ama her 3 tur da 1, 2 mana gelsin
User prompt
İnfinity minionu oyundan sil
User prompt
İnfinity Minionun canı kaçsa o kadar hasara ölsün
User prompt
Lan diğer karakterlerde nasıl yapıyorsan infinity minionu da öyle yap hep tek yiyor
User prompt
İşte o hataları düzelt
User prompt
İnfinity Minionun canı 9 olsun
User prompt
Lan Minion niye hep tek yiyor 8 canı var
User prompt
Lan beyinsiz yapay zeka düzelt şu infinity Minionun canını 8 yap herkes tek atıyor 8 canı var 8
User prompt
İnfinity Minionun canı 8 herkes tek atıyor düzelt
User prompt
Sıkıntı luciferida değil sıkıntı bütün kartlarda hasarları yazdığı gibi yap fazladan hasar olmasın
User prompt
1 hasara sahip Air wisp nasıl benim 8 canlı infinity Minionuma tek attı
User prompt
1 hasar Air wisp neden infinity Minion 8 canı varken tek attı
User prompt
Assetlere getir
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Card = Container.expand(function (cardData) { var self = Container.call(this); // Card properties self.cardData = cardData || { name: "Basic Card", cost: 1, attack: 2, health: 2, description: "A basic creature card", passive: null // Passive ability type }; // Damage tracking to prevent duplicate triggers self.damageDealtThisTurn = 0; self.hasTriggeredLifeSteal = false; // Ensure cardData has valid numeric values if (typeof self.cardData.attack !== 'number' || isNaN(self.cardData.attack)) { self.cardData.attack = 2; } if (typeof self.cardData.health !== 'number' || isNaN(self.cardData.health)) { self.cardData.health = 2; } if (typeof self.cardData.cost !== 'number' || isNaN(self.cardData.cost)) { self.cardData.cost = 1; } self.maxHealth = self.cardData.health; self.currentHealth = self.cardData.health; self.isPlayable = false; self.isOnBattlefield = false; self.hasAttacked = false; // Create card graphics based on card type var cardAssetName = 'cardBack'; // default var symbolAssetName = null; // Determine assets based on card name switch (self.cardData.name) { case "Fire Imp": cardAssetName = 'fireImpBg'; symbolAssetName = 'fireImpSymbol'; break; case "Water Spirit": cardAssetName = 'waterSpiritBg'; symbolAssetName = 'waterSpiritSymbol'; break; case "Earth Golem": cardAssetName = 'earthGolemBg'; symbolAssetName = 'earthGolemSymbol'; break; case "Air Wisp": cardAssetName = 'airWispBg'; symbolAssetName = 'airWispSymbol'; break; case "Lightning Bolt": cardAssetName = 'lightningBoltBg'; symbolAssetName = 'lightningBoltSymbol'; break; case "Lucifer": cardAssetName = 'walterSpiritBg'; symbolAssetName = 'walterSpiritSymbol'; break; case "Shadow Drake": cardAssetName = 'shadowDrakeBg'; symbolAssetName = 'shadowDrakeSymbol'; break; case "Michael Demiurgos": cardAssetName = 'michaelDemiurgosBg'; symbolAssetName = 'michaelDemiurgosSymbol'; break; case "Infinity Minion": cardAssetName = 'infinityMinionBg'; symbolAssetName = 'infinityMinionSymbol'; break; } var cardBg = self.attachAsset(cardAssetName, { anchorX: 0.5, anchorY: 0.5 }); // Add symbol if available if (symbolAssetName) { var cardSymbol = self.attachAsset(symbolAssetName, { anchorX: 0.5, anchorY: 0.5 }); cardSymbol.x = 0; cardSymbol.y = -20; // Position symbol in upper middle area } // Card text elements var nameText = new Text2(self.cardData.name, { size: 24, fill: 0x2C3E50 }); nameText.anchor.set(0.5, 0); nameText.x = 0; nameText.y = -100; self.addChild(nameText); var costText = new Text2(self.cardData.cost.toString(), { size: 32, fill: 0x9B59B6 }); costText.anchor.set(0.5, 0.5); costText.x = -70; costText.y = -100; self.addChild(costText); var attackText = new Text2(self.cardData.attack.toString(), { size: 28, fill: 0xE74C3C }); attackText.anchor.set(0.5, 0.5); attackText.x = -50; attackText.y = 90; self.addChild(attackText); // Store reference to attack text for potential updates self.attackText = attackText; var healthText = new Text2(self.currentHealth.toString(), { size: 28, fill: 0x27AE60 }); healthText.anchor.set(0.5, 0.5); healthText.x = 50; healthText.y = 90; self.addChild(healthText); // Store reference to health text self.healthText = healthText; self.updateHealthDisplay = function () { // Ensure we display a valid number, not NaN var displayHealth = isNaN(self.currentHealth) ? 0 : Math.max(0, self.currentHealth); // Ensure currentHealth is always a valid number if (isNaN(self.currentHealth)) { self.currentHealth = 0; } if (isNaN(self.maxHealth)) { self.maxHealth = self.cardData.health || 1; } // Update health text display if (self.healthText) { self.healthText.setText(displayHealth.toString()); } else if (healthText) { healthText.setText(displayHealth.toString()); } // Update attack text if it exists and attack value changed if (self.attackText && self.cardData.attack !== undefined) { self.attackText.setText(self.cardData.attack.toString()); } // Debug log for Lightning Bolt if (self.cardData.name === "Lightning Bolt") { console.log("Lightning Bolt health - current:", self.currentHealth, "max:", self.maxHealth, "display:", displayHealth); } if (self.currentHealth <= 0) { cardBg.alpha = 0.5; } }; self.takeDamage = function (damage, attacker) { // Check for passive abilities that might reduce damage var damageReduction = self.triggerPassive("take_damage", { damage: damage }); var finalDamage = Math.max(0, damage - damageReduction); self.currentHealth -= finalDamage; // Ensure currentHealth is always a valid number if (isNaN(self.currentHealth) || self.currentHealth < 0) { self.currentHealth = 0; } self.updateHealthDisplay(); // If card dies, remove it immediately if (self.currentHealth <= 0) { // Check if card is on battlefield before trying to find player var ownerPlayer = null; if (self.isOnBattlefield) { // Safely check which player owns this card for (var i = 0; i < humanPlayer.battlefield.length; i++) { if (humanPlayer.battlefield[i] === self) { ownerPlayer = humanPlayer; break; } } if (!ownerPlayer) { for (var i = 0; i < aiPlayer.battlefield.length; i++) { if (aiPlayer.battlefield[i] === self) { ownerPlayer = aiPlayer; break; } } } } // Trigger death passive before removing if (ownerPlayer) { self.triggerPassive("death", { player: ownerPlayer }); } // Remove from battlefield arrays and add back to deck var humanIndex = humanPlayer.battlefield.indexOf(self); if (humanIndex >= 0) { humanPlayer.battlefield.splice(humanIndex, 1); // Reset card properties and add back to deck self.currentHealth = self.maxHealth; self.hasAttacked = false; self.damageDealtThisTurn = 0; self.hasTriggeredLifeSteal = false; self.isOnBattlefield = false; // Reset any passive-specific properties if (self.cardData.passive === "shield") { self.shieldActive = true; } // Add back to human player's deck humanPlayer.deck.push(self); } var aiIndex = aiPlayer.battlefield.indexOf(self); if (aiIndex >= 0) { aiPlayer.battlefield.splice(aiIndex, 1); // Reset card properties and add back to deck self.currentHealth = self.maxHealth; self.hasAttacked = false; self.damageDealtThisTurn = 0; self.hasTriggeredLifeSteal = false; self.isOnBattlefield = false; // Reset any passive-specific properties if (self.cardData.passive === "shield") { self.shieldActive = true; } // Add back to AI player's deck aiPlayer.deck.push(self); } // Clear lane assignment self.laneIndex = undefined; self.isOnBattlefield = false; // Death animation and removal from game animateCardDeath(self); LK.setTimeout(function () { if (game.children.includes(self)) { game.removeChild(self); } // Update battlefield layout after card removal arrangeBattlefield(); }, 600); // Match the death animation duration } // Trigger attacker's life steal passive if damage was dealt if (attacker && finalDamage > 0 && attacker.cardData.passive === "life_steal") { // Heal attacker for 2 HP - but only if attacker is still alive if (attacker.currentHealth > 0) { attacker.currentHealth = Math.min(attacker.maxHealth, attacker.currentHealth + 2); attacker.updateHealthDisplay(); // Life steal animation - green glow tween(attacker, { tint: 0x00ff00 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(attacker, { tint: 0xFFFFFF }, { duration: 300, easing: tween.easeIn }); } }); // Healing number popup var healText = new Text2("+2", { size: 36, fill: 0x00ff00 }); healText.anchor.set(0.5, 0.5); healText.x = attacker.x + (Math.random() - 0.5) * 60; healText.y = attacker.y - 50; healText.alpha = 1.0; game.addChild(healText); // Animate heal number floating up and fading tween(healText, { y: healText.y - 80, alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { game.removeChild(healText); } }); } } // Enhanced damage animation // Screen shake for significant damage if (damage >= 3) { LK.effects.flashScreen(0xff4444, 200); } // Damage number popup animation var damageText = new Text2("-" + damage.toString(), { size: 40, fill: 0xff0000 }); damageText.anchor.set(0.5, 0.5); damageText.x = self.x + (Math.random() - 0.5) * 60; damageText.y = self.y - 50; damageText.alpha = 1.0; game.addChild(damageText); // Animate damage number floating up and fading tween(damageText, { y: damageText.y - 80, alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { game.removeChild(damageText); } }); // Card damage animation - recoil and flash tween(self, { x: self.x + (Math.random() - 0.5) * 30, y: self.y + (Math.random() - 0.5) * 20, scaleX: 0.9, scaleY: 0.9, tint: 0xff4444 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { x: self.x, y: self.y, scaleX: 1.0, scaleY: 1.0, tint: 0xFFFFFF }, { duration: 300, easing: tween.easeOut }); } }); // Flash red when taking damage LK.effects.flashObject(self, 0xff0000, 500); }; self.canAttack = function () { return self.isOnBattlefield && !self.hasAttacked && self.currentHealth > 0; }; self.attack = function (target) { if (self.canAttack() && target) { // Check if both cards are in the same lane if (self.laneIndex !== undefined && target.laneIndex !== undefined && self.laneIndex !== target.laneIndex) { return; // Cannot attack cards in different lanes } // Store original position var originalX = self.x; var originalY = self.y; // Calculate target position (move towards target) var targetX = target.x; var targetY = target.y; // Animate attack: move toward target, then back tween(self, { x: targetX, y: targetY }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { // Collision animation - both cards shake and flash on impact var collisionDuration = 150; // Shake the attacking card tween(self, { x: targetX + 20 }, { duration: collisionDuration / 3, easing: tween.easeInOut, onFinish: function onFinish() { tween(self, { x: targetX - 20 }, { duration: collisionDuration / 3, easing: tween.easeInOut, onFinish: function onFinish() { tween(self, { x: targetX }, { duration: collisionDuration / 3, easing: tween.easeInOut }); } }); } }); // Shake the target card tween(target, { x: target.x - 15 }, { duration: collisionDuration / 3, easing: tween.easeInOut, onFinish: function onFinish() { tween(target, { x: target.x + 15 }, { duration: collisionDuration / 3, easing: tween.easeInOut, onFinish: function onFinish() { tween(target, { x: target.x }, { duration: collisionDuration / 3, easing: tween.easeInOut }); } }); } }); // Flash both cards white for collision effect tween(self, { tint: 0xFFFFFF }, { duration: collisionDuration, easing: tween.easeInOut, onFinish: function onFinish() { tween(self, { tint: 0xFFFFFF }, { duration: collisionDuration, easing: tween.easeInOut }); } }); tween(target, { tint: 0xFFFFFF }, { duration: collisionDuration, easing: tween.easeInOut, onFinish: function onFinish() { tween(target, { tint: 0xFFFFFF }, { duration: collisionDuration, easing: tween.easeInOut }); } }); // Check for passive damage boost var damageBoost = self.triggerPassive("attack", { attacker: self, target: target }); var totalDamage = self.cardData.attack + (damageBoost || 0); // Debug logging for Lucifer if (self.cardData.name === "Lucifer") { console.log("Lucifer attacking:", target.cardData.name, "with damage:", totalDamage, "base attack:", self.cardData.attack, "boost:", damageBoost); } // Deal damage when collision happens target.takeDamage(totalDamage, self); // After collision, animate return to original position LK.setTimeout(function () { tween(self, { x: originalX, y: originalY }, { duration: 300, easing: tween.easeIn }); }, collisionDuration); } }); self.hasAttacked = true; cardBg.alpha = 0.7; LK.getSound('attack').play(); } }; self.resetForNewTurn = function () { self.hasAttacked = false; self.damageDealtThisTurn = 0; self.hasTriggeredLifeSteal = false; if (self.currentHealth > 0) { cardBg.alpha = 1.0; } }; // Passive ability system self.triggerPassive = function (trigger, context) { if (!self.cardData.passive || self.currentHealth <= 0) return; switch (self.cardData.passive) { case "heal_on_turn_start": if (trigger === "turn_start") { self.currentHealth = Math.min(self.maxHealth, self.currentHealth + 1); self.updateHealthDisplay(); // Heal animation tween(self, { tint: 0x00ff00 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { tint: 0xFFFFFF }, { duration: 300, easing: tween.easeIn }); } }); } break; case "damage_boost": if (trigger === "attack" && context && context.attacker === self) { return 1; // Add 1 extra damage } break; case "shield": if (trigger === "take_damage" && context) { if (self.shieldActive !== false) { self.shieldActive = false; // Shield break animation tween(self, { tint: 0x0099ff }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { tint: 0xFFFFFF }, { duration: 200, easing: tween.easeIn }); } }); return 0; // Negate damage } } break; case "summon_ally": if (trigger === "death" && context && context.player) { // Summon a 1/1 creature in available lane var player = context.player; if (player.battlefield.length < 3) { var availableLanes = [0, 1, 2]; for (var i = 0; i < player.battlefield.length; i++) { var occupiedLane = player.battlefield[i].laneIndex; if (occupiedLane !== undefined) { var laneIdx = availableLanes.indexOf(occupiedLane); if (laneIdx >= 0) availableLanes.splice(laneIdx, 1); } } if (availableLanes.length > 0) { var newCard = new Card({ name: "Spirit Token", cost: 0, attack: 1, health: 1, description: "A spirit summoned from death" }); newCard.laneIndex = availableLanes[0]; newCard.isOnBattlefield = true; player.battlefield.push(newCard); game.addChild(newCard); } } } break; case "regenerate": if (trigger === "end_turn") { if (self.currentHealth < self.maxHealth && self.currentHealth > 0) { self.currentHealth = Math.min(self.maxHealth, self.currentHealth + 1); self.updateHealthDisplay(); // Regenerate animation tween(self, { tint: 0x00ff88 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { tint: 0xFFFFFF }, { duration: 400, easing: tween.easeIn }); } }); } } break; case "life_steal": if (trigger === "deal_damage" && context && context.damage && context.target) { // Heal self for 2 HP when dealing damage self.currentHealth = Math.min(self.maxHealth, self.currentHealth + 2); self.updateHealthDisplay(); // Life steal animation - green glow tween(self, { tint: 0x00ff00 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { tint: 0xFFFFFF }, { duration: 300, easing: tween.easeIn }); } }); // Healing number popup var healText = new Text2("+2", { size: 36, fill: 0x00ff00 }); healText.anchor.set(0.5, 0.5); healText.x = self.x + (Math.random() - 0.5) * 60; healText.y = self.y - 50; healText.alpha = 1.0; game.addChild(healText); // Animate heal number floating up and fading tween(healText, { y: healText.y - 80, alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { game.removeChild(healText); } }); } break; case "explosion": if (trigger === "death" && context && self.laneIndex !== undefined) { // Find opposing card in same lane var opposingPlayer = context.player === humanPlayer ? aiPlayer : humanPlayer; for (var i = 0; i < opposingPlayer.battlefield.length; i++) { var opposingCard = opposingPlayer.battlefield[i]; if (opposingCard.laneIndex === self.laneIndex && opposingCard.currentHealth > 0) { // Deal 2 explosion damage to opposing card opposingCard.takeDamage(2, self); // Explosion animation tween(opposingCard, { tint: 0xff8800 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(opposingCard, { tint: 0xFFFFFF }, { duration: 200, easing: tween.easeIn }); } }); // Explosion effect on self LK.effects.flashScreen(0xff8800, 300); break; } } } break; case "lightning_charge": if (trigger === "turn_start" && self.isOnBattlefield) { // Increase attack by 1 each turn while on battlefield self.cardData.attack = self.cardData.attack + 1; self.updateHealthDisplay(); // This will also update attack display // Lightning charge animation tween(self, { tint: 0x00BFFF }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { tint: 0xFFFFFF }, { duration: 300, easing: tween.easeIn }); } }); // Lightning effect on screen LK.effects.flashScreen(0x00BFFF, 200); } break; case "heal_allies": if (trigger === "turn_start" && self.isOnBattlefield && context && context.player) { // Heal all teammates by 1 health each turn var player = context.player; for (var i = 0; i < player.battlefield.length; i++) { var ally = player.battlefield[i]; if (ally !== self && ally.currentHealth > 0 && ally.currentHealth < ally.maxHealth) { ally.currentHealth = Math.min(ally.maxHealth, ally.currentHealth + 1); ally.updateHealthDisplay(); // Healing animation for ally tween(ally, { tint: 0x00ff88 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(ally, { tint: 0xFFFFFF }, { duration: 300, easing: tween.easeIn }); } }); // Healing number popup var healText = new Text2("+1", { size: 32, fill: 0x00ff88 }); healText.anchor.set(0.5, 0.5); healText.x = ally.x + (Math.random() - 0.5) * 40; healText.y = ally.y - 40; healText.alpha = 1.0; game.addChild(healText); // Animate heal number floating up and fading tween(healText, { y: healText.y - 60, alpha: 0, scaleX: 1.2, scaleY: 1.2 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { game.removeChild(healText); } }); } } } break; } return 0; }; self.hasPassive = function () { return self.cardData.passive !== null && self.cardData.passive !== undefined; }; // Method to validate and fix card stats self.validateStats = function () { // Ensure all stats are valid numbers if (typeof self.cardData.attack !== 'number' || isNaN(self.cardData.attack)) { self.cardData.attack = 1; } if (typeof self.cardData.health !== 'number' || isNaN(self.cardData.health)) { self.cardData.health = 1; } if (typeof self.cardData.cost !== 'number' || isNaN(self.cardData.cost)) { self.cardData.cost = 1; } if (typeof self.currentHealth !== 'number' || isNaN(self.currentHealth)) { self.currentHealth = self.cardData.health; } if (typeof self.maxHealth !== 'number' || isNaN(self.maxHealth)) { self.maxHealth = self.cardData.health; } // Ensure current health doesn't exceed max health if (self.currentHealth > self.maxHealth) { self.currentHealth = self.maxHealth; } // Update display after validation self.updateHealthDisplay(); }; return self; }); var Player = Container.expand(function (isHuman) { var self = Container.call(this); self.isHuman = isHuman || false; self.health = 30; self.maxMana = 10; self.currentMana = 3; self.hand = []; self.battlefield = []; self.deck = []; // Initialize deck with basic cards var cardTypes = [{ name: "Fire Imp", cost: 1, attack: 2, health: 2, description: "A small fire creature" }, { name: "Water Spirit", cost: 2, attack: 2, health: 3, description: "A defensive water creature" }, { name: "Earth Golem", cost: 6, attack: 2, health: 12, description: "A powerful earth creature", passive: "explosion" }, { name: "Air Wisp", cost: 1, attack: 1, health: 2, description: "A quick air creature" }, { name: "Lightning Bolt", cost: 3, attack: 2, health: 4, description: "A shocking creature that increases damage each turn", passive: "lightning_charge" }, { name: "Lucifer", cost: 4, attack: 2, health: 5, description: "A mystical water spirit with high endurance", passive: "life_steal" }, { name: "Shadow Drake", cost: 3, attack: 3, health: 4, description: "A powerful shadow dragon with shield ability", passive: "shield" }, { name: "Michael Demiurgos", cost: 4, attack: 4, health: 4, description: "An archangel with divine power that heals all teammates each turn", passive: "heal_allies" }, { name: "Infinity Minion", cost: 3, attack: 1, health: 8, description: "A resilient minion with high health" }]; // Add exactly one of each card type to the deck for (var i = 0; i < cardTypes.length; i++) { var newCard = new Card(cardTypes[i]); newCard.validateStats(); // Ensure stats are correct self.deck.push(newCard); } // Shuffle the deck to randomize card order for (var i = self.deck.length - 1; i > 0; i--) { var randomIndex = Math.floor(Math.random() * (i + 1)); var temp = self.deck[i]; self.deck[i] = self.deck[randomIndex]; self.deck[randomIndex] = temp; } self.drawCard = function () { if (self.deck.length > 0 && self.hand.length < 7) { var card = self.deck.pop(); self.hand.push(card); LK.getSound('cardDraw').play(); return card; } return null; }; self.canPlayCard = function (card) { return card && card.cardData.cost <= self.currentMana; }; self.playCard = function (card) { var handIndex = self.hand.indexOf(card); if (handIndex >= 0 && self.canPlayCard(card) && self.battlefield.length < 3) { self.hand.splice(handIndex, 1); self.battlefield.push(card); self.currentMana -= card.cardData.cost; card.isOnBattlefield = true; LK.getSound('cardPlay').play(); return true; } return false; }; self.takeDamage = function (damage) { self.health -= damage; if (self.health < 0) self.health = 0; // Tower damage animation animateTowerDamage(!self.isHuman); // Damage number popup for tower var damageText = new Text2("-" + damage.toString(), { size: 60, fill: 0xff0000 }); damageText.anchor.set(0.5, 0.5); damageText.x = 1024 + (Math.random() - 0.5) * 100; damageText.y = self.isHuman ? 1700 : 400; damageText.alpha = 1.0; game.addChild(damageText); // Animate damage number tween(damageText, { y: damageText.y - 120, alpha: 0, scaleX: 2.0, scaleY: 2.0 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { game.removeChild(damageText); } }); }; self.startTurn = function () { self.currentMana = Math.min(self.maxMana, self.currentMana + 2); if (self.maxMana < 10) self.maxMana++; // Reset battlefield cards and trigger turn start passives for (var i = 0; i < self.battlefield.length; i++) { self.battlefield[i].resetForNewTurn(); self.battlefield[i].triggerPassive("turn_start", { player: self }); } // Draw a card self.drawCard(); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2c3e50 }); /**** * Game Code ****/ // Generic card back // Walter Spirit card assets // Lightning Bolt card assets // Air Wisp card assets // Earth Golem card assets // Water Spirit card assets // Fire Imp card assets // Game state variables var currentPlayer = 0; // 0 = human, 1 = AI var gamePhase = "playing"; // "playing", "gameOver" var selectedCard = null; var draggedCard = null; var turnCounter = 0; // Track total turns taken var combatPhase = false; // Track if we're in combat phase // Create players var humanPlayer = new Player(true); var aiPlayer = new Player(false); var players = [humanPlayer, aiPlayer]; // Create game areas var opponentAreaBg = game.addChild(LK.getAsset('opponentArea', { anchorX: 0, anchorY: 0, x: 0, y: 0, alpha: 0.3 })); var battlefieldBg = game.addChild(LK.getAsset('battlefield', { anchorX: 0, anchorY: 0, x: 0, y: 400, alpha: 0.8 })); var playerAreaBg = game.addChild(LK.getAsset('playerArea', { anchorX: 0, anchorY: 0, x: 0, y: 1800, alpha: 0.3 })); // Create visible lane graphics var lanePositions = [600, 1024, 1448]; // Left, Center, Right lanes - centered var lanes = []; // Create lanes with borders for visual clarity for (var i = 0; i < 3; i++) { // Lane border (darker background) var laneBorder = game.addChild(LK.getAsset('laneBorder', { anchorX: 0.5, anchorY: 0.5, x: lanePositions[i], y: 985, alpha: 0.8 })); // Lane background (lighter) var lane = game.addChild(LK.getAsset('lane', { anchorX: 0.5, anchorY: 0.5, x: lanePositions[i], y: 985, alpha: 0.7 })); lanes.push({ border: laneBorder, background: lane }); // Add lane number text var laneText = new Text2("Lane " + (i + 1), { size: 24, fill: 0xECF0F1 }); laneText.anchor.set(0.5, 0.5); laneText.x = lanePositions[i]; laneText.y = 1255; laneText.alpha = 0.6; game.addChild(laneText); } // UI Elements var playerHealthText = new Text2("Health: 30", { size: 48, fill: 0xECF0F1 }); playerHealthText.anchor.set(0, 0.5); playerHealthText.x = 50; playerHealthText.y = 1950; game.addChild(playerHealthText); var opponentHealthText = new Text2("Enemy: 30", { size: 48, fill: 0xECF0F1 }); opponentHealthText.anchor.set(0, 0.5); opponentHealthText.x = 50; opponentHealthText.y = 150; game.addChild(opponentHealthText); var manaText = new Text2("Mana: 3/3", { size: 36, fill: 0x9B59B6 }); manaText.anchor.set(0, 0.5); manaText.x = 50; manaText.y = 2000; game.addChild(manaText); var aiManaText = new Text2("Enemy Mana: 3/3", { size: 36, fill: 0x9B59B6 }); aiManaText.anchor.set(0, 0.5); aiManaText.x = 50; aiManaText.y = 100; game.addChild(aiManaText); var turnText = new Text2("Your Turn", { size: 42, fill: 0xF39C12 }); turnText.anchor.set(0.5, 0.5); turnText.x = 1024; turnText.y = 100; game.addChild(turnText); // End turn button var endTurnBtn = game.addChild(LK.getAsset('endTurnButton', { anchorX: 0.5, anchorY: 0.5, x: 1800, y: 1950 })); var endTurnText = new Text2("End Turn", { size: 28, fill: 0x2C3E50 }); endTurnText.anchor.set(0.5, 0.5); endTurnText.x = 1800; endTurnText.y = 1950; game.addChild(endTurnText); // Initialize starting hands for (var i = 0; i < 4; i++) { humanPlayer.drawCard(); aiPlayer.drawCard(); } function updateUI() { playerHealthText.setText("Health: " + humanPlayer.health); opponentHealthText.setText("Enemy: " + aiPlayer.health); manaText.setText("Mana: " + humanPlayer.currentMana + "/" + humanPlayer.maxMana); aiManaText.setText("Enemy Mana: " + aiPlayer.currentMana + "/" + aiPlayer.maxMana); if (combatPhase) { turnText.setText("Combat Phase"); turnText.fill = "#e67e22"; } else if (currentPlayer === 0) { turnText.setText("Your Turn"); turnText.fill = "#f39c12"; } else { turnText.setText("Enemy Turn"); turnText.fill = "#e74c3c"; } } function arrangeHand() { var handCards = humanPlayer.hand; var startX = 1024 - handCards.length * 100; for (var i = 0; i < handCards.length; i++) { var card = handCards[i]; if (!game.children.includes(card)) { game.addChild(card); } card.x = startX + i * 200; card.y = 2300; card.isPlayable = humanPlayer.canPlayCard(card); // Visual feedback for playable cards if (card.isPlayable && currentPlayer === 0) { card.alpha = 1.0; } else { card.alpha = 0.6; } } } function arrangeBattlefield() { // Define 3 lane positions var lanePositions = [600, 1024, 1448]; // Left, Center, Right lanes - centered // Player battlefield - maintain lane assignments var playerCards = humanPlayer.battlefield; for (var i = 0; i < playerCards.length; i++) { var card = playerCards[i]; if (!game.children.includes(card)) { game.addChild(card); } // Use the card's assigned lane position instead of array index if (card.laneIndex !== undefined) { card.x = lanePositions[card.laneIndex]; card.y = 1120; } } // AI battlefield - maintain lane assignments var aiCards = aiPlayer.battlefield; for (var i = 0; i < aiCards.length; i++) { var card = aiCards[i]; if (!game.children.includes(card)) { game.addChild(card); } // Use the card's assigned lane position instead of array index if (card.laneIndex !== undefined) { card.x = lanePositions[card.laneIndex]; card.y = 650; } } } function resolveCombat() { // Process lanes sequentially - lane 1, then lane 2, then lane 3 processLaneCombat(0, function () { // Lane 1 complete, process lane 2 processLaneCombat(1, function () { // Lane 2 complete, process lane 3 processLaneCombat(2, function () { // All lanes complete, update battlefield LK.setTimeout(function () { arrangeBattlefield(); updateUI(); }, 500); }); }); }); } function processLaneCombat(laneIndex, onComplete) { var humanCard = null; var aiCard = null; // Find cards in this lane for (var i = 0; i < humanPlayer.battlefield.length; i++) { if (humanPlayer.battlefield[i].laneIndex === laneIndex && humanPlayer.battlefield[i].currentHealth > 0) { humanCard = humanPlayer.battlefield[i]; break; } } for (var i = 0; i < aiPlayer.battlefield.length; i++) { if (aiPlayer.battlefield[i].laneIndex === laneIndex && aiPlayer.battlefield[i].currentHealth > 0) { aiCard = aiPlayer.battlefield[i]; break; } } // Determine combat outcome for this lane if (humanCard && humanCard.currentHealth > 0 && aiCard && aiCard.currentHealth > 0) { // Cards fight each other - animate card-to-card combat var humanDamage = humanCard.cardData.attack; var aiDamage = aiCard.cardData.attack; animateCardVsCard(humanCard, aiCard, humanDamage, aiDamage, 0); // Wait for combat animation to complete before continuing LK.setTimeout(onComplete, 1200); } else if (humanCard && humanCard.currentHealth > 0 && !aiCard) { // Human card attacks AI tower - use card's actual attack value animateCardToTower(humanCard, "ai", 0, 0); // Wait for tower attack animation to complete before continuing LK.setTimeout(onComplete, 1000); } else if (aiCard && aiCard.currentHealth > 0 && !humanCard) { // AI card attacks human tower - use card's actual attack value animateCardToTower(aiCard, "human", 0, 0); // Wait for tower attack animation to complete before continuing LK.setTimeout(onComplete, 1000); } else { // No combat in this lane, proceed immediately LK.setTimeout(onComplete, 100); } } function animateCardVsCard(card1, card2, damage1, damage2, delay) { LK.setTimeout(function () { // Store original positions var originalX1 = card1.x; var originalY1 = card1.y; var originalX2 = card2.x; var originalY2 = card2.y; // Calculate midpoint for collision var midX = (card1.x + card2.x) / 2; var midY = (card1.y + card2.y) / 2; // Phase 1: Both cards move toward each other tween(card1, { x: midX, y: midY - 20, scaleX: 1.1, scaleY: 1.1 }, { duration: 300, easing: tween.easeOut }); tween(card2, { x: midX, y: midY + 20, scaleX: 1.1, scaleY: 1.1 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { // Phase 2: Collision effect - shake and flash LK.getSound('attack').play(); // Shake both cards tween(card1, { x: midX + 15, rotation: 0.1 }, { duration: 60, easing: tween.easeInOut, onFinish: function onFinish() { tween(card1, { x: midX - 15, rotation: -0.1 }, { duration: 60, easing: tween.easeInOut, onFinish: function onFinish() { tween(card1, { x: midX, rotation: 0 }, { duration: 60, easing: tween.easeInOut }); } }); } }); tween(card2, { x: midX - 15, rotation: -0.1 }, { duration: 60, easing: tween.easeInOut, onFinish: function onFinish() { tween(card2, { x: midX + 15, rotation: 0.1 }, { duration: 60, easing: tween.easeInOut, onFinish: function onFinish() { tween(card2, { x: midX, rotation: 0 }, { duration: 60, easing: tween.easeInOut }); } }); } }); // Flash white for collision tween(card1, { tint: 0xFFFFFF }, { duration: 150, easing: tween.easeInOut, onFinish: function onFinish() { tween(card1, { tint: 0xFFFFFF }, { duration: 150, easing: tween.easeInOut }); } }); tween(card2, { tint: 0xFFFFFF }, { duration: 150, easing: tween.easeInOut, onFinish: function onFinish() { tween(card2, { tint: 0xFFFFFF }, { duration: 150, easing: tween.easeInOut }); } }); // Apply damage after collision LK.setTimeout(function () { card1.takeDamage(damage2, card2); card2.takeDamage(damage1, card1); }, 180); // Phase 3: Return to original positions LK.setTimeout(function () { tween(card1, { x: originalX1, y: originalY1, scaleX: 1.0, scaleY: 1.0 }, { duration: 400, easing: tween.easeIn }); tween(card2, { x: originalX2, y: originalY2, scaleX: 1.0, scaleY: 1.0 }, { duration: 400, easing: tween.easeIn }); }, 300); } }); }, delay); } function animateCardToTower(card, target, damage, delay) { LK.setTimeout(function () { // Store original position var originalX = card.x; var originalY = card.y; // Determine tower position var towerX = 1024; // Center of screen var towerY = target === "ai" ? 100 : 1800; // AI or human area var targetX = towerX + (Math.random() - 0.5) * 200; // Add some randomness var targetY = towerY + (Math.random() - 0.5) * 100; // Phase 1: Card charges toward tower tween(card, { x: targetX, y: targetY, scaleX: 1.2, scaleY: 1.2, rotation: Math.PI * 0.1 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { // Phase 2: Impact effect LK.getSound('attack').play(); // Use card's actual attack value instead of passed damage parameter var actualDamage = card.cardData.attack; // Flash the tower area if (target === "ai") { LK.effects.flashObject(opponentAreaBg, 0xff0000, 400); LK.effects.flashScreen(0xff0000, 200); aiPlayer.takeDamage(actualDamage); } else { LK.effects.flashObject(playerAreaBg, 0xff0000, 400); LK.effects.flashScreen(0xff0000, 200); humanPlayer.takeDamage(actualDamage); } // Shake the card on impact tween(card, { x: targetX + 20, rotation: Math.PI * 0.15 }, { duration: 80, easing: tween.easeInOut, onFinish: function onFinish() { tween(card, { x: targetX - 20, rotation: Math.PI * 0.05 }, { duration: 80, easing: tween.easeInOut, onFinish: function onFinish() { tween(card, { x: targetX, rotation: Math.PI * 0.1 }, { duration: 80, easing: tween.easeInOut }); } }); } }); // Phase 3: Return to original position LK.setTimeout(function () { tween(card, { x: originalX, y: originalY, scaleX: 1.0, scaleY: 1.0, rotation: 0 }, { duration: 500, easing: tween.easeIn }); }, 300); } }); }, delay); } function animateCardDeath(card) { // Death animation - fade out while spinning and shrinking tween(card, { alpha: 0, scaleX: 0.2, scaleY: 0.2, rotation: Math.PI * 2, y: card.y - 100 }, { duration: 600, easing: tween.easeIn }); } function animateTowerDamage(isAI) { // Tower damage animation - screen shake and flash var targetArea = isAI ? opponentAreaBg : playerAreaBg; var healthText = isAI ? opponentHealthText : playerHealthText; // Flash the area red LK.effects.flashObject(targetArea, 0xff0000, 500); // Screen shake effect LK.effects.flashScreen(0xff0000, 300); // Animate health text tween(healthText, { scaleX: 1.3, scaleY: 1.3, tint: 0xff0000 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(healthText, { scaleX: 1.0, scaleY: 1.0, tint: 0xFFFFFF }, { duration: 300, easing: tween.easeIn }); } }); } function checkGameOver() { if (humanPlayer.health <= 0) { gamePhase = "gameOver"; LK.showGameOver(); return true; } else if (aiPlayer.health <= 0) { gamePhase = "gameOver"; LK.showYouWin(); return true; } return false; } function endTurn() { // Trigger end turn passives for current player var currentPlayerObj = players[currentPlayer]; for (var i = 0; i < currentPlayerObj.battlefield.length; i++) { currentPlayerObj.battlefield[i].triggerPassive("end_turn", { player: currentPlayerObj }); } // Switch to next player turnCounter++; currentPlayer = 1 - currentPlayer; // Check if both players have completed their turns (every 2 turns) if (turnCounter % 2 === 0) { // Combat happens only after both players finish their turns combatPhase = true; turnText.setText("Combat Phase"); turnText.fill = "#e67e22"; // Resolve combat after both players complete their turns LK.setTimeout(function () { resolveCombat(); combatPhase = false; // Continue with next player's turn after combat players[currentPlayer].startTurn(); if (currentPlayer === 1) { // AI turn LK.setTimeout(function () { performAITurn(); }, 1000); } if (!checkGameOver()) { updateUI(); arrangeHand(); arrangeBattlefield(); } }, 1000); } else { // Just switch player without combat players[currentPlayer].startTurn(); if (currentPlayer === 1) { // AI turn LK.setTimeout(function () { performAITurn(); }, 1000); } if (!checkGameOver()) { updateUI(); arrangeHand(); arrangeBattlefield(); } } } function performAITurn() { // Simple AI: play random playable card if battlefield has space var playableCards = aiPlayer.hand.filter(function (card) { return aiPlayer.canPlayCard(card); }); if (playableCards.length > 0 && aiPlayer.battlefield.length < 3) { var randomCard = playableCards[Math.floor(Math.random() * playableCards.length)]; // Find available lane for AI var availableLanes = [0, 1, 2]; for (var i = 0; i < aiPlayer.battlefield.length; i++) { var occupiedLane = aiPlayer.battlefield[i].laneIndex; if (occupiedLane !== undefined) { var laneIdx = availableLanes.indexOf(occupiedLane); if (laneIdx >= 0) availableLanes.splice(laneIdx, 1); } } if (availableLanes.length > 0 && aiPlayer.playCard(randomCard)) { // Filter out lanes where Lucifer would face another Lucifer var validLanes = availableLanes.slice(); // Copy array if (randomCard.cardData.name === "Lucifer") { validLanes = availableLanes.filter(function (lane) { // Check if human has Lucifer in this lane for (var k = 0; k < humanPlayer.battlefield.length; k++) { if (humanPlayer.battlefield[k].laneIndex === lane && humanPlayer.battlefield[k].cardData.name === "Lucifer") { return false; // This lane is blocked for Lucifer } } return true; // Lane is safe for Lucifer }); } // Use valid lanes, fallback to available lanes if no valid lanes for Lucifer var lanesToUse = validLanes.length > 0 ? validLanes : availableLanes; var selectedLane = lanesToUse[Math.floor(Math.random() * lanesToUse.length)]; randomCard.laneIndex = selectedLane; randomCard.targetLane = selectedLane; // Immediately update display after AI plays card updateUI(); arrangeHand(); arrangeBattlefield(); } } // Combat is now handled automatically at end of turn // AI just plays cards, combat resolution happens automatically // End AI turn LK.setTimeout(function () { if (!checkGameOver()) { endTurn(); } }, 1500); } // Card zoom preview variables var zoomPreviewCard = null; var zoomPreviewBg = null; var zoomPreviewTimeout = null; var isShowingZoom = false; // Double-tap tracking variables var lastTappedCard = null; var lastTapTime = 0; var doubleTapThreshold = 500; // milliseconds // Card info display variables var cardInfoDisplay = null; var cardInfoBg = null; var isShowingCardInfo = false; function createZoomPreview(card) { if (isShowingZoom) return; // Create dark background overlay zoomPreviewBg = game.addChild(LK.getAsset('battlefield', { anchorX: 0, anchorY: 0, x: 0, y: 0, alpha: 0.8, tint: 0x000000 })); zoomPreviewBg.width = 2048; zoomPreviewBg.height = 2732; // Create zoomed card preview zoomPreviewCard = new Card(card.cardData); zoomPreviewCard.x = 1024; zoomPreviewCard.y = 1000; zoomPreviewCard.scaleX = 2.5; zoomPreviewCard.scaleY = 2.5; zoomPreviewCard.alpha = 0; game.addChild(zoomPreviewCard); // Add card name text var nameText = new Text2(card.cardData.name, { size: 60, fill: 0xFFFFFF }); nameText.anchor.set(0.5, 0.5); nameText.x = 1024; nameText.y = 700; nameText.alpha = 0; game.addChild(nameText); zoomPreviewCard.nameDisplay = nameText; // Add passive description if card has passive if (card.hasPassive()) { var passiveText = new Text2("Passive: " + getPassiveDescription(card.cardData.passive), { size: 36, fill: 0xF39C12 }); passiveText.anchor.set(0.5, 0.5); passiveText.x = 1024; passiveText.y = 1400; passiveText.alpha = 0; game.addChild(passiveText); zoomPreviewCard.passiveDisplay = passiveText; } // Add description text var descText = new Text2(card.cardData.description, { size: 32, fill: 0xECF0F1 }); descText.anchor.set(0.5, 0.5); descText.x = 1024; descText.y = card.hasPassive() ? 1500 : 1400; descText.alpha = 0; game.addChild(descText); zoomPreviewCard.descDisplay = descText; // Animate zoom in tween(zoomPreviewBg, { alpha: 0.8 }, { duration: 300, easing: tween.easeOut }); tween(zoomPreviewCard, { alpha: 1.0, scaleX: 2.5, scaleY: 2.5 }, { duration: 400, easing: tween.easeOut }); tween(nameText, { alpha: 1.0 }, { duration: 400, easing: tween.easeOut }); tween(descText, { alpha: 1.0 }, { duration: 400, easing: tween.easeOut }); if (zoomPreviewCard.passiveDisplay) { tween(zoomPreviewCard.passiveDisplay, { alpha: 1.0 }, { duration: 400, easing: tween.easeOut }); } isShowingZoom = true; } function destroyZoomPreview() { if (!isShowingZoom) return; if (zoomPreviewCard) { if (zoomPreviewCard.nameDisplay) { game.removeChild(zoomPreviewCard.nameDisplay); } if (zoomPreviewCard.passiveDisplay) { game.removeChild(zoomPreviewCard.passiveDisplay); } if (zoomPreviewCard.descDisplay) { game.removeChild(zoomPreviewCard.descDisplay); } game.removeChild(zoomPreviewCard); zoomPreviewCard = null; } if (zoomPreviewBg) { game.removeChild(zoomPreviewBg); zoomPreviewBg = null; } isShowingZoom = false; } function getPassiveDescription(passiveType) { switch (passiveType) { case "heal_on_turn_start": return "Heals 1 HP at turn start"; case "damage_boost": return "Deals +1 damage when attacking"; case "shield": return "Blocks first damage taken"; case "summon_ally": return "Summons 1/1 Spirit Token on death"; case "regenerate": return "Heals 1 HP at turn end"; case "life_steal": return "Heals 2 HP when dealing damage"; case "explosion": return "Deals 2 damage to opposing card on death"; case "lightning_charge": return "Increases damage by 1 each turn on battlefield"; case "heal_allies": return "Heals all teammates by 1 health each turn"; default: return "No passive ability"; } } function createCardInfoDisplay(card) { if (isShowingCardInfo) { destroyCardInfoDisplay(); } // Create semi-transparent background at bottom cardInfoBg = game.addChild(LK.getAsset('playerArea', { anchorX: 0, anchorY: 0, x: 0, y: 2200, alpha: 0.8, tint: 0x2c3e50 })); // Create card name text var nameText = new Text2(card.cardData.name, { size: 48, fill: 0xFFFFFF }); nameText.anchor.set(0.5, 0.5); nameText.x = 1024; nameText.y = 2320; game.addChild(nameText); // Create passive text if card has passive var passiveText = null; if (card.hasPassive()) { var passiveDesc = getPassiveDescription(card.cardData.passive); passiveText = new Text2("Pasif: " + passiveDesc, { size: 36, fill: 0xF39C12 }); passiveText.anchor.set(0.5, 0.5); passiveText.x = 1024; passiveText.y = 2380; game.addChild(passiveText); } else { passiveText = new Text2("Pasif: Yok", { size: 36, fill: 0x95A5A6 }); passiveText.anchor.set(0.5, 0.5); passiveText.x = 1024; passiveText.y = 2380; game.addChild(passiveText); } // Store references for cleanup cardInfoDisplay = { background: cardInfoBg, nameText: nameText, passiveText: passiveText }; isShowingCardInfo = true; // Auto-hide after 3 seconds LK.setTimeout(function () { destroyCardInfoDisplay(); }, 3000); } function destroyCardInfoDisplay() { if (!isShowingCardInfo || !cardInfoDisplay) return; if (cardInfoDisplay.background) { game.removeChild(cardInfoDisplay.background); } if (cardInfoDisplay.nameText) { game.removeChild(cardInfoDisplay.nameText); } if (cardInfoDisplay.passiveText) { game.removeChild(cardInfoDisplay.passiveText); } cardInfoDisplay = null; cardInfoBg = null; isShowingCardInfo = false; } // Event handlers game.down = function (x, y, obj) { if (gamePhase !== "playing" || currentPlayer !== 0 || combatPhase) return; // Close zoom preview if showing if (isShowingZoom) { destroyZoomPreview(); return; } // Close card info display if showing if (isShowingCardInfo) { destroyCardInfoDisplay(); } // Check if end turn button was clicked if (x >= 1700 && x <= 1900 && y >= 1910 && y <= 1990) { endTurn(); return; } // Check if a hand card was clicked for (var i = 0; i < humanPlayer.hand.length; i++) { var card = humanPlayer.hand[i]; var cardBounds = { left: card.x - 90, right: card.x + 90, top: card.y - 125, bottom: card.y + 125 }; if (x >= cardBounds.left && x <= cardBounds.right && y >= cardBounds.top && y <= cardBounds.bottom) { // Check for double-tap var currentTime = Date.now(); if (lastTappedCard === card && currentTime - lastTapTime < doubleTapThreshold) { // Double-tap detected - show card info createCardInfoDisplay(card); lastTappedCard = null; lastTapTime = 0; return; } // Record this tap lastTappedCard = card; lastTapTime = currentTime; if (card.isPlayable) { selectedCard = card; draggedCard = card; // Start long press timer for zoom preview if (zoomPreviewTimeout) { LK.clearTimeout(zoomPreviewTimeout); } zoomPreviewTimeout = LK.setTimeout(function () { if (selectedCard === card) { createZoomPreview(card); draggedCard = null; // Cancel drag when showing zoom } }, 800); // 800ms long press } return; } } // Check battlefield cards for zoom preview var allBattlefieldCards = humanPlayer.battlefield.concat(aiPlayer.battlefield); for (var i = 0; i < allBattlefieldCards.length; i++) { var card = allBattlefieldCards[i]; var cardBounds = { left: card.x - 90, right: card.x + 90, top: card.y - 125, bottom: card.y + 125 }; if (x >= cardBounds.left && x <= cardBounds.right && y >= cardBounds.top && y <= cardBounds.bottom) { // Check for double-tap on battlefield cards var currentTime = Date.now(); if (lastTappedCard === card && currentTime - lastTapTime < doubleTapThreshold) { // Double-tap detected - show card info createCardInfoDisplay(card); lastTappedCard = null; lastTapTime = 0; return; } // Record this tap lastTappedCard = card; lastTapTime = currentTime; // Start long press timer for battlefield card zoom if (zoomPreviewTimeout) { LK.clearTimeout(zoomPreviewTimeout); } zoomPreviewTimeout = LK.setTimeout(function () { createZoomPreview(card); }, 800); return; } } // Battlefield cards can no longer be manually selected for attacking // Combat is now automatic at end of turn }; game.move = function (x, y, obj) { // Cancel zoom preview if user starts moving if (zoomPreviewTimeout) { LK.clearTimeout(zoomPreviewTimeout); zoomPreviewTimeout = null; } if (draggedCard) { draggedCard.x = x; draggedCard.y = y; } }; game.up = function (x, y, obj) { // Clear zoom preview timeout if (zoomPreviewTimeout) { LK.clearTimeout(zoomPreviewTimeout); zoomPreviewTimeout = null; } if (draggedCard && y < 1350 && y > 700) { // Determine which lane the card was dropped in var targetLane = -1; if (x >= 380 && x < 812) targetLane = 0; // Left lane else if (x >= 812 && x < 1236) targetLane = 1; // Center lane else if (x >= 1236 && x < 1668) targetLane = 2; // Right lane // Check if lane is available and card can be played if (targetLane >= 0 && humanPlayer.battlefield.length < 3) { // Check if target lane is already occupied var laneOccupied = false; for (var i = 0; i < humanPlayer.battlefield.length; i++) { if (humanPlayer.battlefield[i].laneIndex === targetLane) { laneOccupied = true; break; } } // Check if placing Lucifer would face another Lucifer in same lane var luciferBlocked = false; if (draggedCard.cardData.name === "Lucifer") { // Check if AI has Lucifer in the target lane for (var j = 0; j < aiPlayer.battlefield.length; j++) { if (aiPlayer.battlefield[j].laneIndex === targetLane && aiPlayer.battlefield[j].cardData.name === "Lucifer") { luciferBlocked = true; break; } } } if (!laneOccupied && !luciferBlocked && humanPlayer.playCard(draggedCard)) { draggedCard.laneIndex = targetLane; draggedCard.targetLane = targetLane; draggedCard = null; selectedCard = null; updateUI(); arrangeHand(); arrangeBattlefield(); } } } selectedCard = null; if (draggedCard) { arrangeHand(); draggedCard = null; } // Dead cards are now automatically removed in takeDamage function updateUI(); arrangeBattlefield(); checkGameOver(); }; // Mana regeneration timer var manaRegenTimer = 0; var manaRegenInterval = 500; // Regenerate mana every 500ms game.update = function () { if (gamePhase === "playing") { // Handle mana regeneration during active turn if (currentPlayer === 0 && !combatPhase) { // Only for human player during their turn manaRegenTimer += LK.deltaTime; if (manaRegenTimer >= manaRegenInterval) { if (humanPlayer.currentMana < humanPlayer.maxMana) { humanPlayer.currentMana = Math.min(humanPlayer.maxMana, humanPlayer.currentMana + 2); // Mana regeneration visual effect var manaOrbEffect = game.addChild(LK.getAsset('manaOrb', { anchorX: 0.5, anchorY: 0.5, x: 50, y: 2000, alpha: 0.8 })); tween(manaOrbEffect, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { game.removeChild(manaOrbEffect); } }); } manaRegenTimer = 0; } } updateUI(); } }; // Initial setup updateUI(); arrangeHand(); arrangeBattlefield();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Card = Container.expand(function (cardData) {
var self = Container.call(this);
// Card properties
self.cardData = cardData || {
name: "Basic Card",
cost: 1,
attack: 2,
health: 2,
description: "A basic creature card",
passive: null // Passive ability type
};
// Damage tracking to prevent duplicate triggers
self.damageDealtThisTurn = 0;
self.hasTriggeredLifeSteal = false;
// Ensure cardData has valid numeric values
if (typeof self.cardData.attack !== 'number' || isNaN(self.cardData.attack)) {
self.cardData.attack = 2;
}
if (typeof self.cardData.health !== 'number' || isNaN(self.cardData.health)) {
self.cardData.health = 2;
}
if (typeof self.cardData.cost !== 'number' || isNaN(self.cardData.cost)) {
self.cardData.cost = 1;
}
self.maxHealth = self.cardData.health;
self.currentHealth = self.cardData.health;
self.isPlayable = false;
self.isOnBattlefield = false;
self.hasAttacked = false;
// Create card graphics based on card type
var cardAssetName = 'cardBack'; // default
var symbolAssetName = null;
// Determine assets based on card name
switch (self.cardData.name) {
case "Fire Imp":
cardAssetName = 'fireImpBg';
symbolAssetName = 'fireImpSymbol';
break;
case "Water Spirit":
cardAssetName = 'waterSpiritBg';
symbolAssetName = 'waterSpiritSymbol';
break;
case "Earth Golem":
cardAssetName = 'earthGolemBg';
symbolAssetName = 'earthGolemSymbol';
break;
case "Air Wisp":
cardAssetName = 'airWispBg';
symbolAssetName = 'airWispSymbol';
break;
case "Lightning Bolt":
cardAssetName = 'lightningBoltBg';
symbolAssetName = 'lightningBoltSymbol';
break;
case "Lucifer":
cardAssetName = 'walterSpiritBg';
symbolAssetName = 'walterSpiritSymbol';
break;
case "Shadow Drake":
cardAssetName = 'shadowDrakeBg';
symbolAssetName = 'shadowDrakeSymbol';
break;
case "Michael Demiurgos":
cardAssetName = 'michaelDemiurgosBg';
symbolAssetName = 'michaelDemiurgosSymbol';
break;
case "Infinity Minion":
cardAssetName = 'infinityMinionBg';
symbolAssetName = 'infinityMinionSymbol';
break;
}
var cardBg = self.attachAsset(cardAssetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Add symbol if available
if (symbolAssetName) {
var cardSymbol = self.attachAsset(symbolAssetName, {
anchorX: 0.5,
anchorY: 0.5
});
cardSymbol.x = 0;
cardSymbol.y = -20; // Position symbol in upper middle area
}
// Card text elements
var nameText = new Text2(self.cardData.name, {
size: 24,
fill: 0x2C3E50
});
nameText.anchor.set(0.5, 0);
nameText.x = 0;
nameText.y = -100;
self.addChild(nameText);
var costText = new Text2(self.cardData.cost.toString(), {
size: 32,
fill: 0x9B59B6
});
costText.anchor.set(0.5, 0.5);
costText.x = -70;
costText.y = -100;
self.addChild(costText);
var attackText = new Text2(self.cardData.attack.toString(), {
size: 28,
fill: 0xE74C3C
});
attackText.anchor.set(0.5, 0.5);
attackText.x = -50;
attackText.y = 90;
self.addChild(attackText);
// Store reference to attack text for potential updates
self.attackText = attackText;
var healthText = new Text2(self.currentHealth.toString(), {
size: 28,
fill: 0x27AE60
});
healthText.anchor.set(0.5, 0.5);
healthText.x = 50;
healthText.y = 90;
self.addChild(healthText);
// Store reference to health text
self.healthText = healthText;
self.updateHealthDisplay = function () {
// Ensure we display a valid number, not NaN
var displayHealth = isNaN(self.currentHealth) ? 0 : Math.max(0, self.currentHealth);
// Ensure currentHealth is always a valid number
if (isNaN(self.currentHealth)) {
self.currentHealth = 0;
}
if (isNaN(self.maxHealth)) {
self.maxHealth = self.cardData.health || 1;
}
// Update health text display
if (self.healthText) {
self.healthText.setText(displayHealth.toString());
} else if (healthText) {
healthText.setText(displayHealth.toString());
}
// Update attack text if it exists and attack value changed
if (self.attackText && self.cardData.attack !== undefined) {
self.attackText.setText(self.cardData.attack.toString());
}
// Debug log for Lightning Bolt
if (self.cardData.name === "Lightning Bolt") {
console.log("Lightning Bolt health - current:", self.currentHealth, "max:", self.maxHealth, "display:", displayHealth);
}
if (self.currentHealth <= 0) {
cardBg.alpha = 0.5;
}
};
self.takeDamage = function (damage, attacker) {
// Check for passive abilities that might reduce damage
var damageReduction = self.triggerPassive("take_damage", {
damage: damage
});
var finalDamage = Math.max(0, damage - damageReduction);
self.currentHealth -= finalDamage;
// Ensure currentHealth is always a valid number
if (isNaN(self.currentHealth) || self.currentHealth < 0) {
self.currentHealth = 0;
}
self.updateHealthDisplay();
// If card dies, remove it immediately
if (self.currentHealth <= 0) {
// Check if card is on battlefield before trying to find player
var ownerPlayer = null;
if (self.isOnBattlefield) {
// Safely check which player owns this card
for (var i = 0; i < humanPlayer.battlefield.length; i++) {
if (humanPlayer.battlefield[i] === self) {
ownerPlayer = humanPlayer;
break;
}
}
if (!ownerPlayer) {
for (var i = 0; i < aiPlayer.battlefield.length; i++) {
if (aiPlayer.battlefield[i] === self) {
ownerPlayer = aiPlayer;
break;
}
}
}
}
// Trigger death passive before removing
if (ownerPlayer) {
self.triggerPassive("death", {
player: ownerPlayer
});
}
// Remove from battlefield arrays and add back to deck
var humanIndex = humanPlayer.battlefield.indexOf(self);
if (humanIndex >= 0) {
humanPlayer.battlefield.splice(humanIndex, 1);
// Reset card properties and add back to deck
self.currentHealth = self.maxHealth;
self.hasAttacked = false;
self.damageDealtThisTurn = 0;
self.hasTriggeredLifeSteal = false;
self.isOnBattlefield = false;
// Reset any passive-specific properties
if (self.cardData.passive === "shield") {
self.shieldActive = true;
}
// Add back to human player's deck
humanPlayer.deck.push(self);
}
var aiIndex = aiPlayer.battlefield.indexOf(self);
if (aiIndex >= 0) {
aiPlayer.battlefield.splice(aiIndex, 1);
// Reset card properties and add back to deck
self.currentHealth = self.maxHealth;
self.hasAttacked = false;
self.damageDealtThisTurn = 0;
self.hasTriggeredLifeSteal = false;
self.isOnBattlefield = false;
// Reset any passive-specific properties
if (self.cardData.passive === "shield") {
self.shieldActive = true;
}
// Add back to AI player's deck
aiPlayer.deck.push(self);
}
// Clear lane assignment
self.laneIndex = undefined;
self.isOnBattlefield = false;
// Death animation and removal from game
animateCardDeath(self);
LK.setTimeout(function () {
if (game.children.includes(self)) {
game.removeChild(self);
}
// Update battlefield layout after card removal
arrangeBattlefield();
}, 600); // Match the death animation duration
}
// Trigger attacker's life steal passive if damage was dealt
if (attacker && finalDamage > 0 && attacker.cardData.passive === "life_steal") {
// Heal attacker for 2 HP - but only if attacker is still alive
if (attacker.currentHealth > 0) {
attacker.currentHealth = Math.min(attacker.maxHealth, attacker.currentHealth + 2);
attacker.updateHealthDisplay();
// Life steal animation - green glow
tween(attacker, {
tint: 0x00ff00
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(attacker, {
tint: 0xFFFFFF
}, {
duration: 300,
easing: tween.easeIn
});
}
});
// Healing number popup
var healText = new Text2("+2", {
size: 36,
fill: 0x00ff00
});
healText.anchor.set(0.5, 0.5);
healText.x = attacker.x + (Math.random() - 0.5) * 60;
healText.y = attacker.y - 50;
healText.alpha = 1.0;
game.addChild(healText);
// Animate heal number floating up and fading
tween(healText, {
y: healText.y - 80,
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
game.removeChild(healText);
}
});
}
}
// Enhanced damage animation
// Screen shake for significant damage
if (damage >= 3) {
LK.effects.flashScreen(0xff4444, 200);
}
// Damage number popup animation
var damageText = new Text2("-" + damage.toString(), {
size: 40,
fill: 0xff0000
});
damageText.anchor.set(0.5, 0.5);
damageText.x = self.x + (Math.random() - 0.5) * 60;
damageText.y = self.y - 50;
damageText.alpha = 1.0;
game.addChild(damageText);
// Animate damage number floating up and fading
tween(damageText, {
y: damageText.y - 80,
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
game.removeChild(damageText);
}
});
// Card damage animation - recoil and flash
tween(self, {
x: self.x + (Math.random() - 0.5) * 30,
y: self.y + (Math.random() - 0.5) * 20,
scaleX: 0.9,
scaleY: 0.9,
tint: 0xff4444
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
x: self.x,
y: self.y,
scaleX: 1.0,
scaleY: 1.0,
tint: 0xFFFFFF
}, {
duration: 300,
easing: tween.easeOut
});
}
});
// Flash red when taking damage
LK.effects.flashObject(self, 0xff0000, 500);
};
self.canAttack = function () {
return self.isOnBattlefield && !self.hasAttacked && self.currentHealth > 0;
};
self.attack = function (target) {
if (self.canAttack() && target) {
// Check if both cards are in the same lane
if (self.laneIndex !== undefined && target.laneIndex !== undefined && self.laneIndex !== target.laneIndex) {
return; // Cannot attack cards in different lanes
}
// Store original position
var originalX = self.x;
var originalY = self.y;
// Calculate target position (move towards target)
var targetX = target.x;
var targetY = target.y;
// Animate attack: move toward target, then back
tween(self, {
x: targetX,
y: targetY
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Collision animation - both cards shake and flash on impact
var collisionDuration = 150;
// Shake the attacking card
tween(self, {
x: targetX + 20
}, {
duration: collisionDuration / 3,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
x: targetX - 20
}, {
duration: collisionDuration / 3,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
x: targetX
}, {
duration: collisionDuration / 3,
easing: tween.easeInOut
});
}
});
}
});
// Shake the target card
tween(target, {
x: target.x - 15
}, {
duration: collisionDuration / 3,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(target, {
x: target.x + 15
}, {
duration: collisionDuration / 3,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(target, {
x: target.x
}, {
duration: collisionDuration / 3,
easing: tween.easeInOut
});
}
});
}
});
// Flash both cards white for collision effect
tween(self, {
tint: 0xFFFFFF
}, {
duration: collisionDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
tint: 0xFFFFFF
}, {
duration: collisionDuration,
easing: tween.easeInOut
});
}
});
tween(target, {
tint: 0xFFFFFF
}, {
duration: collisionDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(target, {
tint: 0xFFFFFF
}, {
duration: collisionDuration,
easing: tween.easeInOut
});
}
});
// Check for passive damage boost
var damageBoost = self.triggerPassive("attack", {
attacker: self,
target: target
});
var totalDamage = self.cardData.attack + (damageBoost || 0);
// Debug logging for Lucifer
if (self.cardData.name === "Lucifer") {
console.log("Lucifer attacking:", target.cardData.name, "with damage:", totalDamage, "base attack:", self.cardData.attack, "boost:", damageBoost);
}
// Deal damage when collision happens
target.takeDamage(totalDamage, self);
// After collision, animate return to original position
LK.setTimeout(function () {
tween(self, {
x: originalX,
y: originalY
}, {
duration: 300,
easing: tween.easeIn
});
}, collisionDuration);
}
});
self.hasAttacked = true;
cardBg.alpha = 0.7;
LK.getSound('attack').play();
}
};
self.resetForNewTurn = function () {
self.hasAttacked = false;
self.damageDealtThisTurn = 0;
self.hasTriggeredLifeSteal = false;
if (self.currentHealth > 0) {
cardBg.alpha = 1.0;
}
};
// Passive ability system
self.triggerPassive = function (trigger, context) {
if (!self.cardData.passive || self.currentHealth <= 0) return;
switch (self.cardData.passive) {
case "heal_on_turn_start":
if (trigger === "turn_start") {
self.currentHealth = Math.min(self.maxHealth, self.currentHealth + 1);
self.updateHealthDisplay();
// Heal animation
tween(self, {
tint: 0x00ff00
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
tint: 0xFFFFFF
}, {
duration: 300,
easing: tween.easeIn
});
}
});
}
break;
case "damage_boost":
if (trigger === "attack" && context && context.attacker === self) {
return 1; // Add 1 extra damage
}
break;
case "shield":
if (trigger === "take_damage" && context) {
if (self.shieldActive !== false) {
self.shieldActive = false;
// Shield break animation
tween(self, {
tint: 0x0099ff
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
tint: 0xFFFFFF
}, {
duration: 200,
easing: tween.easeIn
});
}
});
return 0; // Negate damage
}
}
break;
case "summon_ally":
if (trigger === "death" && context && context.player) {
// Summon a 1/1 creature in available lane
var player = context.player;
if (player.battlefield.length < 3) {
var availableLanes = [0, 1, 2];
for (var i = 0; i < player.battlefield.length; i++) {
var occupiedLane = player.battlefield[i].laneIndex;
if (occupiedLane !== undefined) {
var laneIdx = availableLanes.indexOf(occupiedLane);
if (laneIdx >= 0) availableLanes.splice(laneIdx, 1);
}
}
if (availableLanes.length > 0) {
var newCard = new Card({
name: "Spirit Token",
cost: 0,
attack: 1,
health: 1,
description: "A spirit summoned from death"
});
newCard.laneIndex = availableLanes[0];
newCard.isOnBattlefield = true;
player.battlefield.push(newCard);
game.addChild(newCard);
}
}
}
break;
case "regenerate":
if (trigger === "end_turn") {
if (self.currentHealth < self.maxHealth && self.currentHealth > 0) {
self.currentHealth = Math.min(self.maxHealth, self.currentHealth + 1);
self.updateHealthDisplay();
// Regenerate animation
tween(self, {
tint: 0x00ff88
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
tint: 0xFFFFFF
}, {
duration: 400,
easing: tween.easeIn
});
}
});
}
}
break;
case "life_steal":
if (trigger === "deal_damage" && context && context.damage && context.target) {
// Heal self for 2 HP when dealing damage
self.currentHealth = Math.min(self.maxHealth, self.currentHealth + 2);
self.updateHealthDisplay();
// Life steal animation - green glow
tween(self, {
tint: 0x00ff00
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
tint: 0xFFFFFF
}, {
duration: 300,
easing: tween.easeIn
});
}
});
// Healing number popup
var healText = new Text2("+2", {
size: 36,
fill: 0x00ff00
});
healText.anchor.set(0.5, 0.5);
healText.x = self.x + (Math.random() - 0.5) * 60;
healText.y = self.y - 50;
healText.alpha = 1.0;
game.addChild(healText);
// Animate heal number floating up and fading
tween(healText, {
y: healText.y - 80,
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
game.removeChild(healText);
}
});
}
break;
case "explosion":
if (trigger === "death" && context && self.laneIndex !== undefined) {
// Find opposing card in same lane
var opposingPlayer = context.player === humanPlayer ? aiPlayer : humanPlayer;
for (var i = 0; i < opposingPlayer.battlefield.length; i++) {
var opposingCard = opposingPlayer.battlefield[i];
if (opposingCard.laneIndex === self.laneIndex && opposingCard.currentHealth > 0) {
// Deal 2 explosion damage to opposing card
opposingCard.takeDamage(2, self);
// Explosion animation
tween(opposingCard, {
tint: 0xff8800
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(opposingCard, {
tint: 0xFFFFFF
}, {
duration: 200,
easing: tween.easeIn
});
}
});
// Explosion effect on self
LK.effects.flashScreen(0xff8800, 300);
break;
}
}
}
break;
case "lightning_charge":
if (trigger === "turn_start" && self.isOnBattlefield) {
// Increase attack by 1 each turn while on battlefield
self.cardData.attack = self.cardData.attack + 1;
self.updateHealthDisplay(); // This will also update attack display
// Lightning charge animation
tween(self, {
tint: 0x00BFFF
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
tint: 0xFFFFFF
}, {
duration: 300,
easing: tween.easeIn
});
}
});
// Lightning effect on screen
LK.effects.flashScreen(0x00BFFF, 200);
}
break;
case "heal_allies":
if (trigger === "turn_start" && self.isOnBattlefield && context && context.player) {
// Heal all teammates by 1 health each turn
var player = context.player;
for (var i = 0; i < player.battlefield.length; i++) {
var ally = player.battlefield[i];
if (ally !== self && ally.currentHealth > 0 && ally.currentHealth < ally.maxHealth) {
ally.currentHealth = Math.min(ally.maxHealth, ally.currentHealth + 1);
ally.updateHealthDisplay();
// Healing animation for ally
tween(ally, {
tint: 0x00ff88
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(ally, {
tint: 0xFFFFFF
}, {
duration: 300,
easing: tween.easeIn
});
}
});
// Healing number popup
var healText = new Text2("+1", {
size: 32,
fill: 0x00ff88
});
healText.anchor.set(0.5, 0.5);
healText.x = ally.x + (Math.random() - 0.5) * 40;
healText.y = ally.y - 40;
healText.alpha = 1.0;
game.addChild(healText);
// Animate heal number floating up and fading
tween(healText, {
y: healText.y - 60,
alpha: 0,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
game.removeChild(healText);
}
});
}
}
}
break;
}
return 0;
};
self.hasPassive = function () {
return self.cardData.passive !== null && self.cardData.passive !== undefined;
};
// Method to validate and fix card stats
self.validateStats = function () {
// Ensure all stats are valid numbers
if (typeof self.cardData.attack !== 'number' || isNaN(self.cardData.attack)) {
self.cardData.attack = 1;
}
if (typeof self.cardData.health !== 'number' || isNaN(self.cardData.health)) {
self.cardData.health = 1;
}
if (typeof self.cardData.cost !== 'number' || isNaN(self.cardData.cost)) {
self.cardData.cost = 1;
}
if (typeof self.currentHealth !== 'number' || isNaN(self.currentHealth)) {
self.currentHealth = self.cardData.health;
}
if (typeof self.maxHealth !== 'number' || isNaN(self.maxHealth)) {
self.maxHealth = self.cardData.health;
}
// Ensure current health doesn't exceed max health
if (self.currentHealth > self.maxHealth) {
self.currentHealth = self.maxHealth;
}
// Update display after validation
self.updateHealthDisplay();
};
return self;
});
var Player = Container.expand(function (isHuman) {
var self = Container.call(this);
self.isHuman = isHuman || false;
self.health = 30;
self.maxMana = 10;
self.currentMana = 3;
self.hand = [];
self.battlefield = [];
self.deck = [];
// Initialize deck with basic cards
var cardTypes = [{
name: "Fire Imp",
cost: 1,
attack: 2,
health: 2,
description: "A small fire creature"
}, {
name: "Water Spirit",
cost: 2,
attack: 2,
health: 3,
description: "A defensive water creature"
}, {
name: "Earth Golem",
cost: 6,
attack: 2,
health: 12,
description: "A powerful earth creature",
passive: "explosion"
}, {
name: "Air Wisp",
cost: 1,
attack: 1,
health: 2,
description: "A quick air creature"
}, {
name: "Lightning Bolt",
cost: 3,
attack: 2,
health: 4,
description: "A shocking creature that increases damage each turn",
passive: "lightning_charge"
}, {
name: "Lucifer",
cost: 4,
attack: 2,
health: 5,
description: "A mystical water spirit with high endurance",
passive: "life_steal"
}, {
name: "Shadow Drake",
cost: 3,
attack: 3,
health: 4,
description: "A powerful shadow dragon with shield ability",
passive: "shield"
}, {
name: "Michael Demiurgos",
cost: 4,
attack: 4,
health: 4,
description: "An archangel with divine power that heals all teammates each turn",
passive: "heal_allies"
}, {
name: "Infinity Minion",
cost: 3,
attack: 1,
health: 8,
description: "A resilient minion with high health"
}];
// Add exactly one of each card type to the deck
for (var i = 0; i < cardTypes.length; i++) {
var newCard = new Card(cardTypes[i]);
newCard.validateStats(); // Ensure stats are correct
self.deck.push(newCard);
}
// Shuffle the deck to randomize card order
for (var i = self.deck.length - 1; i > 0; i--) {
var randomIndex = Math.floor(Math.random() * (i + 1));
var temp = self.deck[i];
self.deck[i] = self.deck[randomIndex];
self.deck[randomIndex] = temp;
}
self.drawCard = function () {
if (self.deck.length > 0 && self.hand.length < 7) {
var card = self.deck.pop();
self.hand.push(card);
LK.getSound('cardDraw').play();
return card;
}
return null;
};
self.canPlayCard = function (card) {
return card && card.cardData.cost <= self.currentMana;
};
self.playCard = function (card) {
var handIndex = self.hand.indexOf(card);
if (handIndex >= 0 && self.canPlayCard(card) && self.battlefield.length < 3) {
self.hand.splice(handIndex, 1);
self.battlefield.push(card);
self.currentMana -= card.cardData.cost;
card.isOnBattlefield = true;
LK.getSound('cardPlay').play();
return true;
}
return false;
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health < 0) self.health = 0;
// Tower damage animation
animateTowerDamage(!self.isHuman);
// Damage number popup for tower
var damageText = new Text2("-" + damage.toString(), {
size: 60,
fill: 0xff0000
});
damageText.anchor.set(0.5, 0.5);
damageText.x = 1024 + (Math.random() - 0.5) * 100;
damageText.y = self.isHuman ? 1700 : 400;
damageText.alpha = 1.0;
game.addChild(damageText);
// Animate damage number
tween(damageText, {
y: damageText.y - 120,
alpha: 0,
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
game.removeChild(damageText);
}
});
};
self.startTurn = function () {
self.currentMana = Math.min(self.maxMana, self.currentMana + 2);
if (self.maxMana < 10) self.maxMana++;
// Reset battlefield cards and trigger turn start passives
for (var i = 0; i < self.battlefield.length; i++) {
self.battlefield[i].resetForNewTurn();
self.battlefield[i].triggerPassive("turn_start", {
player: self
});
}
// Draw a card
self.drawCard();
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2c3e50
});
/****
* Game Code
****/
// Generic card back
// Walter Spirit card assets
// Lightning Bolt card assets
// Air Wisp card assets
// Earth Golem card assets
// Water Spirit card assets
// Fire Imp card assets
// Game state variables
var currentPlayer = 0; // 0 = human, 1 = AI
var gamePhase = "playing"; // "playing", "gameOver"
var selectedCard = null;
var draggedCard = null;
var turnCounter = 0; // Track total turns taken
var combatPhase = false; // Track if we're in combat phase
// Create players
var humanPlayer = new Player(true);
var aiPlayer = new Player(false);
var players = [humanPlayer, aiPlayer];
// Create game areas
var opponentAreaBg = game.addChild(LK.getAsset('opponentArea', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 0.3
}));
var battlefieldBg = game.addChild(LK.getAsset('battlefield', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 400,
alpha: 0.8
}));
var playerAreaBg = game.addChild(LK.getAsset('playerArea', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 1800,
alpha: 0.3
}));
// Create visible lane graphics
var lanePositions = [600, 1024, 1448]; // Left, Center, Right lanes - centered
var lanes = [];
// Create lanes with borders for visual clarity
for (var i = 0; i < 3; i++) {
// Lane border (darker background)
var laneBorder = game.addChild(LK.getAsset('laneBorder', {
anchorX: 0.5,
anchorY: 0.5,
x: lanePositions[i],
y: 985,
alpha: 0.8
}));
// Lane background (lighter)
var lane = game.addChild(LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
x: lanePositions[i],
y: 985,
alpha: 0.7
}));
lanes.push({
border: laneBorder,
background: lane
});
// Add lane number text
var laneText = new Text2("Lane " + (i + 1), {
size: 24,
fill: 0xECF0F1
});
laneText.anchor.set(0.5, 0.5);
laneText.x = lanePositions[i];
laneText.y = 1255;
laneText.alpha = 0.6;
game.addChild(laneText);
}
// UI Elements
var playerHealthText = new Text2("Health: 30", {
size: 48,
fill: 0xECF0F1
});
playerHealthText.anchor.set(0, 0.5);
playerHealthText.x = 50;
playerHealthText.y = 1950;
game.addChild(playerHealthText);
var opponentHealthText = new Text2("Enemy: 30", {
size: 48,
fill: 0xECF0F1
});
opponentHealthText.anchor.set(0, 0.5);
opponentHealthText.x = 50;
opponentHealthText.y = 150;
game.addChild(opponentHealthText);
var manaText = new Text2("Mana: 3/3", {
size: 36,
fill: 0x9B59B6
});
manaText.anchor.set(0, 0.5);
manaText.x = 50;
manaText.y = 2000;
game.addChild(manaText);
var aiManaText = new Text2("Enemy Mana: 3/3", {
size: 36,
fill: 0x9B59B6
});
aiManaText.anchor.set(0, 0.5);
aiManaText.x = 50;
aiManaText.y = 100;
game.addChild(aiManaText);
var turnText = new Text2("Your Turn", {
size: 42,
fill: 0xF39C12
});
turnText.anchor.set(0.5, 0.5);
turnText.x = 1024;
turnText.y = 100;
game.addChild(turnText);
// End turn button
var endTurnBtn = game.addChild(LK.getAsset('endTurnButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1800,
y: 1950
}));
var endTurnText = new Text2("End Turn", {
size: 28,
fill: 0x2C3E50
});
endTurnText.anchor.set(0.5, 0.5);
endTurnText.x = 1800;
endTurnText.y = 1950;
game.addChild(endTurnText);
// Initialize starting hands
for (var i = 0; i < 4; i++) {
humanPlayer.drawCard();
aiPlayer.drawCard();
}
function updateUI() {
playerHealthText.setText("Health: " + humanPlayer.health);
opponentHealthText.setText("Enemy: " + aiPlayer.health);
manaText.setText("Mana: " + humanPlayer.currentMana + "/" + humanPlayer.maxMana);
aiManaText.setText("Enemy Mana: " + aiPlayer.currentMana + "/" + aiPlayer.maxMana);
if (combatPhase) {
turnText.setText("Combat Phase");
turnText.fill = "#e67e22";
} else if (currentPlayer === 0) {
turnText.setText("Your Turn");
turnText.fill = "#f39c12";
} else {
turnText.setText("Enemy Turn");
turnText.fill = "#e74c3c";
}
}
function arrangeHand() {
var handCards = humanPlayer.hand;
var startX = 1024 - handCards.length * 100;
for (var i = 0; i < handCards.length; i++) {
var card = handCards[i];
if (!game.children.includes(card)) {
game.addChild(card);
}
card.x = startX + i * 200;
card.y = 2300;
card.isPlayable = humanPlayer.canPlayCard(card);
// Visual feedback for playable cards
if (card.isPlayable && currentPlayer === 0) {
card.alpha = 1.0;
} else {
card.alpha = 0.6;
}
}
}
function arrangeBattlefield() {
// Define 3 lane positions
var lanePositions = [600, 1024, 1448]; // Left, Center, Right lanes - centered
// Player battlefield - maintain lane assignments
var playerCards = humanPlayer.battlefield;
for (var i = 0; i < playerCards.length; i++) {
var card = playerCards[i];
if (!game.children.includes(card)) {
game.addChild(card);
}
// Use the card's assigned lane position instead of array index
if (card.laneIndex !== undefined) {
card.x = lanePositions[card.laneIndex];
card.y = 1120;
}
}
// AI battlefield - maintain lane assignments
var aiCards = aiPlayer.battlefield;
for (var i = 0; i < aiCards.length; i++) {
var card = aiCards[i];
if (!game.children.includes(card)) {
game.addChild(card);
}
// Use the card's assigned lane position instead of array index
if (card.laneIndex !== undefined) {
card.x = lanePositions[card.laneIndex];
card.y = 650;
}
}
}
function resolveCombat() {
// Process lanes sequentially - lane 1, then lane 2, then lane 3
processLaneCombat(0, function () {
// Lane 1 complete, process lane 2
processLaneCombat(1, function () {
// Lane 2 complete, process lane 3
processLaneCombat(2, function () {
// All lanes complete, update battlefield
LK.setTimeout(function () {
arrangeBattlefield();
updateUI();
}, 500);
});
});
});
}
function processLaneCombat(laneIndex, onComplete) {
var humanCard = null;
var aiCard = null;
// Find cards in this lane
for (var i = 0; i < humanPlayer.battlefield.length; i++) {
if (humanPlayer.battlefield[i].laneIndex === laneIndex && humanPlayer.battlefield[i].currentHealth > 0) {
humanCard = humanPlayer.battlefield[i];
break;
}
}
for (var i = 0; i < aiPlayer.battlefield.length; i++) {
if (aiPlayer.battlefield[i].laneIndex === laneIndex && aiPlayer.battlefield[i].currentHealth > 0) {
aiCard = aiPlayer.battlefield[i];
break;
}
}
// Determine combat outcome for this lane
if (humanCard && humanCard.currentHealth > 0 && aiCard && aiCard.currentHealth > 0) {
// Cards fight each other - animate card-to-card combat
var humanDamage = humanCard.cardData.attack;
var aiDamage = aiCard.cardData.attack;
animateCardVsCard(humanCard, aiCard, humanDamage, aiDamage, 0);
// Wait for combat animation to complete before continuing
LK.setTimeout(onComplete, 1200);
} else if (humanCard && humanCard.currentHealth > 0 && !aiCard) {
// Human card attacks AI tower - use card's actual attack value
animateCardToTower(humanCard, "ai", 0, 0);
// Wait for tower attack animation to complete before continuing
LK.setTimeout(onComplete, 1000);
} else if (aiCard && aiCard.currentHealth > 0 && !humanCard) {
// AI card attacks human tower - use card's actual attack value
animateCardToTower(aiCard, "human", 0, 0);
// Wait for tower attack animation to complete before continuing
LK.setTimeout(onComplete, 1000);
} else {
// No combat in this lane, proceed immediately
LK.setTimeout(onComplete, 100);
}
}
function animateCardVsCard(card1, card2, damage1, damage2, delay) {
LK.setTimeout(function () {
// Store original positions
var originalX1 = card1.x;
var originalY1 = card1.y;
var originalX2 = card2.x;
var originalY2 = card2.y;
// Calculate midpoint for collision
var midX = (card1.x + card2.x) / 2;
var midY = (card1.y + card2.y) / 2;
// Phase 1: Both cards move toward each other
tween(card1, {
x: midX,
y: midY - 20,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 300,
easing: tween.easeOut
});
tween(card2, {
x: midX,
y: midY + 20,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Phase 2: Collision effect - shake and flash
LK.getSound('attack').play();
// Shake both cards
tween(card1, {
x: midX + 15,
rotation: 0.1
}, {
duration: 60,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(card1, {
x: midX - 15,
rotation: -0.1
}, {
duration: 60,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(card1, {
x: midX,
rotation: 0
}, {
duration: 60,
easing: tween.easeInOut
});
}
});
}
});
tween(card2, {
x: midX - 15,
rotation: -0.1
}, {
duration: 60,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(card2, {
x: midX + 15,
rotation: 0.1
}, {
duration: 60,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(card2, {
x: midX,
rotation: 0
}, {
duration: 60,
easing: tween.easeInOut
});
}
});
}
});
// Flash white for collision
tween(card1, {
tint: 0xFFFFFF
}, {
duration: 150,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(card1, {
tint: 0xFFFFFF
}, {
duration: 150,
easing: tween.easeInOut
});
}
});
tween(card2, {
tint: 0xFFFFFF
}, {
duration: 150,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(card2, {
tint: 0xFFFFFF
}, {
duration: 150,
easing: tween.easeInOut
});
}
});
// Apply damage after collision
LK.setTimeout(function () {
card1.takeDamage(damage2, card2);
card2.takeDamage(damage1, card1);
}, 180);
// Phase 3: Return to original positions
LK.setTimeout(function () {
tween(card1, {
x: originalX1,
y: originalY1,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 400,
easing: tween.easeIn
});
tween(card2, {
x: originalX2,
y: originalY2,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 400,
easing: tween.easeIn
});
}, 300);
}
});
}, delay);
}
function animateCardToTower(card, target, damage, delay) {
LK.setTimeout(function () {
// Store original position
var originalX = card.x;
var originalY = card.y;
// Determine tower position
var towerX = 1024; // Center of screen
var towerY = target === "ai" ? 100 : 1800; // AI or human area
var targetX = towerX + (Math.random() - 0.5) * 200; // Add some randomness
var targetY = towerY + (Math.random() - 0.5) * 100;
// Phase 1: Card charges toward tower
tween(card, {
x: targetX,
y: targetY,
scaleX: 1.2,
scaleY: 1.2,
rotation: Math.PI * 0.1
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
// Phase 2: Impact effect
LK.getSound('attack').play();
// Use card's actual attack value instead of passed damage parameter
var actualDamage = card.cardData.attack;
// Flash the tower area
if (target === "ai") {
LK.effects.flashObject(opponentAreaBg, 0xff0000, 400);
LK.effects.flashScreen(0xff0000, 200);
aiPlayer.takeDamage(actualDamage);
} else {
LK.effects.flashObject(playerAreaBg, 0xff0000, 400);
LK.effects.flashScreen(0xff0000, 200);
humanPlayer.takeDamage(actualDamage);
}
// Shake the card on impact
tween(card, {
x: targetX + 20,
rotation: Math.PI * 0.15
}, {
duration: 80,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(card, {
x: targetX - 20,
rotation: Math.PI * 0.05
}, {
duration: 80,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(card, {
x: targetX,
rotation: Math.PI * 0.1
}, {
duration: 80,
easing: tween.easeInOut
});
}
});
}
});
// Phase 3: Return to original position
LK.setTimeout(function () {
tween(card, {
x: originalX,
y: originalY,
scaleX: 1.0,
scaleY: 1.0,
rotation: 0
}, {
duration: 500,
easing: tween.easeIn
});
}, 300);
}
});
}, delay);
}
function animateCardDeath(card) {
// Death animation - fade out while spinning and shrinking
tween(card, {
alpha: 0,
scaleX: 0.2,
scaleY: 0.2,
rotation: Math.PI * 2,
y: card.y - 100
}, {
duration: 600,
easing: tween.easeIn
});
}
function animateTowerDamage(isAI) {
// Tower damage animation - screen shake and flash
var targetArea = isAI ? opponentAreaBg : playerAreaBg;
var healthText = isAI ? opponentHealthText : playerHealthText;
// Flash the area red
LK.effects.flashObject(targetArea, 0xff0000, 500);
// Screen shake effect
LK.effects.flashScreen(0xff0000, 300);
// Animate health text
tween(healthText, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0xff0000
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(healthText, {
scaleX: 1.0,
scaleY: 1.0,
tint: 0xFFFFFF
}, {
duration: 300,
easing: tween.easeIn
});
}
});
}
function checkGameOver() {
if (humanPlayer.health <= 0) {
gamePhase = "gameOver";
LK.showGameOver();
return true;
} else if (aiPlayer.health <= 0) {
gamePhase = "gameOver";
LK.showYouWin();
return true;
}
return false;
}
function endTurn() {
// Trigger end turn passives for current player
var currentPlayerObj = players[currentPlayer];
for (var i = 0; i < currentPlayerObj.battlefield.length; i++) {
currentPlayerObj.battlefield[i].triggerPassive("end_turn", {
player: currentPlayerObj
});
}
// Switch to next player
turnCounter++;
currentPlayer = 1 - currentPlayer;
// Check if both players have completed their turns (every 2 turns)
if (turnCounter % 2 === 0) {
// Combat happens only after both players finish their turns
combatPhase = true;
turnText.setText("Combat Phase");
turnText.fill = "#e67e22";
// Resolve combat after both players complete their turns
LK.setTimeout(function () {
resolveCombat();
combatPhase = false;
// Continue with next player's turn after combat
players[currentPlayer].startTurn();
if (currentPlayer === 1) {
// AI turn
LK.setTimeout(function () {
performAITurn();
}, 1000);
}
if (!checkGameOver()) {
updateUI();
arrangeHand();
arrangeBattlefield();
}
}, 1000);
} else {
// Just switch player without combat
players[currentPlayer].startTurn();
if (currentPlayer === 1) {
// AI turn
LK.setTimeout(function () {
performAITurn();
}, 1000);
}
if (!checkGameOver()) {
updateUI();
arrangeHand();
arrangeBattlefield();
}
}
}
function performAITurn() {
// Simple AI: play random playable card if battlefield has space
var playableCards = aiPlayer.hand.filter(function (card) {
return aiPlayer.canPlayCard(card);
});
if (playableCards.length > 0 && aiPlayer.battlefield.length < 3) {
var randomCard = playableCards[Math.floor(Math.random() * playableCards.length)];
// Find available lane for AI
var availableLanes = [0, 1, 2];
for (var i = 0; i < aiPlayer.battlefield.length; i++) {
var occupiedLane = aiPlayer.battlefield[i].laneIndex;
if (occupiedLane !== undefined) {
var laneIdx = availableLanes.indexOf(occupiedLane);
if (laneIdx >= 0) availableLanes.splice(laneIdx, 1);
}
}
if (availableLanes.length > 0 && aiPlayer.playCard(randomCard)) {
// Filter out lanes where Lucifer would face another Lucifer
var validLanes = availableLanes.slice(); // Copy array
if (randomCard.cardData.name === "Lucifer") {
validLanes = availableLanes.filter(function (lane) {
// Check if human has Lucifer in this lane
for (var k = 0; k < humanPlayer.battlefield.length; k++) {
if (humanPlayer.battlefield[k].laneIndex === lane && humanPlayer.battlefield[k].cardData.name === "Lucifer") {
return false; // This lane is blocked for Lucifer
}
}
return true; // Lane is safe for Lucifer
});
}
// Use valid lanes, fallback to available lanes if no valid lanes for Lucifer
var lanesToUse = validLanes.length > 0 ? validLanes : availableLanes;
var selectedLane = lanesToUse[Math.floor(Math.random() * lanesToUse.length)];
randomCard.laneIndex = selectedLane;
randomCard.targetLane = selectedLane;
// Immediately update display after AI plays card
updateUI();
arrangeHand();
arrangeBattlefield();
}
}
// Combat is now handled automatically at end of turn
// AI just plays cards, combat resolution happens automatically
// End AI turn
LK.setTimeout(function () {
if (!checkGameOver()) {
endTurn();
}
}, 1500);
}
// Card zoom preview variables
var zoomPreviewCard = null;
var zoomPreviewBg = null;
var zoomPreviewTimeout = null;
var isShowingZoom = false;
// Double-tap tracking variables
var lastTappedCard = null;
var lastTapTime = 0;
var doubleTapThreshold = 500; // milliseconds
// Card info display variables
var cardInfoDisplay = null;
var cardInfoBg = null;
var isShowingCardInfo = false;
function createZoomPreview(card) {
if (isShowingZoom) return;
// Create dark background overlay
zoomPreviewBg = game.addChild(LK.getAsset('battlefield', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 0.8,
tint: 0x000000
}));
zoomPreviewBg.width = 2048;
zoomPreviewBg.height = 2732;
// Create zoomed card preview
zoomPreviewCard = new Card(card.cardData);
zoomPreviewCard.x = 1024;
zoomPreviewCard.y = 1000;
zoomPreviewCard.scaleX = 2.5;
zoomPreviewCard.scaleY = 2.5;
zoomPreviewCard.alpha = 0;
game.addChild(zoomPreviewCard);
// Add card name text
var nameText = new Text2(card.cardData.name, {
size: 60,
fill: 0xFFFFFF
});
nameText.anchor.set(0.5, 0.5);
nameText.x = 1024;
nameText.y = 700;
nameText.alpha = 0;
game.addChild(nameText);
zoomPreviewCard.nameDisplay = nameText;
// Add passive description if card has passive
if (card.hasPassive()) {
var passiveText = new Text2("Passive: " + getPassiveDescription(card.cardData.passive), {
size: 36,
fill: 0xF39C12
});
passiveText.anchor.set(0.5, 0.5);
passiveText.x = 1024;
passiveText.y = 1400;
passiveText.alpha = 0;
game.addChild(passiveText);
zoomPreviewCard.passiveDisplay = passiveText;
}
// Add description text
var descText = new Text2(card.cardData.description, {
size: 32,
fill: 0xECF0F1
});
descText.anchor.set(0.5, 0.5);
descText.x = 1024;
descText.y = card.hasPassive() ? 1500 : 1400;
descText.alpha = 0;
game.addChild(descText);
zoomPreviewCard.descDisplay = descText;
// Animate zoom in
tween(zoomPreviewBg, {
alpha: 0.8
}, {
duration: 300,
easing: tween.easeOut
});
tween(zoomPreviewCard, {
alpha: 1.0,
scaleX: 2.5,
scaleY: 2.5
}, {
duration: 400,
easing: tween.easeOut
});
tween(nameText, {
alpha: 1.0
}, {
duration: 400,
easing: tween.easeOut
});
tween(descText, {
alpha: 1.0
}, {
duration: 400,
easing: tween.easeOut
});
if (zoomPreviewCard.passiveDisplay) {
tween(zoomPreviewCard.passiveDisplay, {
alpha: 1.0
}, {
duration: 400,
easing: tween.easeOut
});
}
isShowingZoom = true;
}
function destroyZoomPreview() {
if (!isShowingZoom) return;
if (zoomPreviewCard) {
if (zoomPreviewCard.nameDisplay) {
game.removeChild(zoomPreviewCard.nameDisplay);
}
if (zoomPreviewCard.passiveDisplay) {
game.removeChild(zoomPreviewCard.passiveDisplay);
}
if (zoomPreviewCard.descDisplay) {
game.removeChild(zoomPreviewCard.descDisplay);
}
game.removeChild(zoomPreviewCard);
zoomPreviewCard = null;
}
if (zoomPreviewBg) {
game.removeChild(zoomPreviewBg);
zoomPreviewBg = null;
}
isShowingZoom = false;
}
function getPassiveDescription(passiveType) {
switch (passiveType) {
case "heal_on_turn_start":
return "Heals 1 HP at turn start";
case "damage_boost":
return "Deals +1 damage when attacking";
case "shield":
return "Blocks first damage taken";
case "summon_ally":
return "Summons 1/1 Spirit Token on death";
case "regenerate":
return "Heals 1 HP at turn end";
case "life_steal":
return "Heals 2 HP when dealing damage";
case "explosion":
return "Deals 2 damage to opposing card on death";
case "lightning_charge":
return "Increases damage by 1 each turn on battlefield";
case "heal_allies":
return "Heals all teammates by 1 health each turn";
default:
return "No passive ability";
}
}
function createCardInfoDisplay(card) {
if (isShowingCardInfo) {
destroyCardInfoDisplay();
}
// Create semi-transparent background at bottom
cardInfoBg = game.addChild(LK.getAsset('playerArea', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 2200,
alpha: 0.8,
tint: 0x2c3e50
}));
// Create card name text
var nameText = new Text2(card.cardData.name, {
size: 48,
fill: 0xFFFFFF
});
nameText.anchor.set(0.5, 0.5);
nameText.x = 1024;
nameText.y = 2320;
game.addChild(nameText);
// Create passive text if card has passive
var passiveText = null;
if (card.hasPassive()) {
var passiveDesc = getPassiveDescription(card.cardData.passive);
passiveText = new Text2("Pasif: " + passiveDesc, {
size: 36,
fill: 0xF39C12
});
passiveText.anchor.set(0.5, 0.5);
passiveText.x = 1024;
passiveText.y = 2380;
game.addChild(passiveText);
} else {
passiveText = new Text2("Pasif: Yok", {
size: 36,
fill: 0x95A5A6
});
passiveText.anchor.set(0.5, 0.5);
passiveText.x = 1024;
passiveText.y = 2380;
game.addChild(passiveText);
}
// Store references for cleanup
cardInfoDisplay = {
background: cardInfoBg,
nameText: nameText,
passiveText: passiveText
};
isShowingCardInfo = true;
// Auto-hide after 3 seconds
LK.setTimeout(function () {
destroyCardInfoDisplay();
}, 3000);
}
function destroyCardInfoDisplay() {
if (!isShowingCardInfo || !cardInfoDisplay) return;
if (cardInfoDisplay.background) {
game.removeChild(cardInfoDisplay.background);
}
if (cardInfoDisplay.nameText) {
game.removeChild(cardInfoDisplay.nameText);
}
if (cardInfoDisplay.passiveText) {
game.removeChild(cardInfoDisplay.passiveText);
}
cardInfoDisplay = null;
cardInfoBg = null;
isShowingCardInfo = false;
}
// Event handlers
game.down = function (x, y, obj) {
if (gamePhase !== "playing" || currentPlayer !== 0 || combatPhase) return;
// Close zoom preview if showing
if (isShowingZoom) {
destroyZoomPreview();
return;
}
// Close card info display if showing
if (isShowingCardInfo) {
destroyCardInfoDisplay();
}
// Check if end turn button was clicked
if (x >= 1700 && x <= 1900 && y >= 1910 && y <= 1990) {
endTurn();
return;
}
// Check if a hand card was clicked
for (var i = 0; i < humanPlayer.hand.length; i++) {
var card = humanPlayer.hand[i];
var cardBounds = {
left: card.x - 90,
right: card.x + 90,
top: card.y - 125,
bottom: card.y + 125
};
if (x >= cardBounds.left && x <= cardBounds.right && y >= cardBounds.top && y <= cardBounds.bottom) {
// Check for double-tap
var currentTime = Date.now();
if (lastTappedCard === card && currentTime - lastTapTime < doubleTapThreshold) {
// Double-tap detected - show card info
createCardInfoDisplay(card);
lastTappedCard = null;
lastTapTime = 0;
return;
}
// Record this tap
lastTappedCard = card;
lastTapTime = currentTime;
if (card.isPlayable) {
selectedCard = card;
draggedCard = card;
// Start long press timer for zoom preview
if (zoomPreviewTimeout) {
LK.clearTimeout(zoomPreviewTimeout);
}
zoomPreviewTimeout = LK.setTimeout(function () {
if (selectedCard === card) {
createZoomPreview(card);
draggedCard = null; // Cancel drag when showing zoom
}
}, 800); // 800ms long press
}
return;
}
}
// Check battlefield cards for zoom preview
var allBattlefieldCards = humanPlayer.battlefield.concat(aiPlayer.battlefield);
for (var i = 0; i < allBattlefieldCards.length; i++) {
var card = allBattlefieldCards[i];
var cardBounds = {
left: card.x - 90,
right: card.x + 90,
top: card.y - 125,
bottom: card.y + 125
};
if (x >= cardBounds.left && x <= cardBounds.right && y >= cardBounds.top && y <= cardBounds.bottom) {
// Check for double-tap on battlefield cards
var currentTime = Date.now();
if (lastTappedCard === card && currentTime - lastTapTime < doubleTapThreshold) {
// Double-tap detected - show card info
createCardInfoDisplay(card);
lastTappedCard = null;
lastTapTime = 0;
return;
}
// Record this tap
lastTappedCard = card;
lastTapTime = currentTime;
// Start long press timer for battlefield card zoom
if (zoomPreviewTimeout) {
LK.clearTimeout(zoomPreviewTimeout);
}
zoomPreviewTimeout = LK.setTimeout(function () {
createZoomPreview(card);
}, 800);
return;
}
}
// Battlefield cards can no longer be manually selected for attacking
// Combat is now automatic at end of turn
};
game.move = function (x, y, obj) {
// Cancel zoom preview if user starts moving
if (zoomPreviewTimeout) {
LK.clearTimeout(zoomPreviewTimeout);
zoomPreviewTimeout = null;
}
if (draggedCard) {
draggedCard.x = x;
draggedCard.y = y;
}
};
game.up = function (x, y, obj) {
// Clear zoom preview timeout
if (zoomPreviewTimeout) {
LK.clearTimeout(zoomPreviewTimeout);
zoomPreviewTimeout = null;
}
if (draggedCard && y < 1350 && y > 700) {
// Determine which lane the card was dropped in
var targetLane = -1;
if (x >= 380 && x < 812) targetLane = 0; // Left lane
else if (x >= 812 && x < 1236) targetLane = 1; // Center lane
else if (x >= 1236 && x < 1668) targetLane = 2; // Right lane
// Check if lane is available and card can be played
if (targetLane >= 0 && humanPlayer.battlefield.length < 3) {
// Check if target lane is already occupied
var laneOccupied = false;
for (var i = 0; i < humanPlayer.battlefield.length; i++) {
if (humanPlayer.battlefield[i].laneIndex === targetLane) {
laneOccupied = true;
break;
}
}
// Check if placing Lucifer would face another Lucifer in same lane
var luciferBlocked = false;
if (draggedCard.cardData.name === "Lucifer") {
// Check if AI has Lucifer in the target lane
for (var j = 0; j < aiPlayer.battlefield.length; j++) {
if (aiPlayer.battlefield[j].laneIndex === targetLane && aiPlayer.battlefield[j].cardData.name === "Lucifer") {
luciferBlocked = true;
break;
}
}
}
if (!laneOccupied && !luciferBlocked && humanPlayer.playCard(draggedCard)) {
draggedCard.laneIndex = targetLane;
draggedCard.targetLane = targetLane;
draggedCard = null;
selectedCard = null;
updateUI();
arrangeHand();
arrangeBattlefield();
}
}
}
selectedCard = null;
if (draggedCard) {
arrangeHand();
draggedCard = null;
}
// Dead cards are now automatically removed in takeDamage function
updateUI();
arrangeBattlefield();
checkGameOver();
};
// Mana regeneration timer
var manaRegenTimer = 0;
var manaRegenInterval = 500; // Regenerate mana every 500ms
game.update = function () {
if (gamePhase === "playing") {
// Handle mana regeneration during active turn
if (currentPlayer === 0 && !combatPhase) {
// Only for human player during their turn
manaRegenTimer += LK.deltaTime;
if (manaRegenTimer >= manaRegenInterval) {
if (humanPlayer.currentMana < humanPlayer.maxMana) {
humanPlayer.currentMana = Math.min(humanPlayer.maxMana, humanPlayer.currentMana + 2);
// Mana regeneration visual effect
var manaOrbEffect = game.addChild(LK.getAsset('manaOrb', {
anchorX: 0.5,
anchorY: 0.5,
x: 50,
y: 2000,
alpha: 0.8
}));
tween(manaOrbEffect, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
game.removeChild(manaOrbEffect);
}
});
}
manaRegenTimer = 0;
}
}
updateUI();
}
};
// Initial setup
updateUI();
arrangeHand();
arrangeBattlefield();
End turn button fark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Kart alanı dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Ateş ruhu karakteri dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Lightning spirit character Dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Kocaman kayadan oluşan golem kırmızı parıldayan gözlere sahip dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Air wisp character dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Arka planı doldur sadece aynısını yap
Koridor yukarıdan bakış dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Ateş 🔥 dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Ulan yatay tarsfa doğru geniş yap yüksekliğe doğru küçük yap
Shadow drake dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Michael demiurgos dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
İnfinity Minion character dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Fireball dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Magic stand dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Play button Dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Deck yaz ama düzgün bir arka planla
Büyü asası logosu Dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Suikastçı bıçağı logosu Dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Kalkan logosu Dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Warrior logo Dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Sadece freeze büyüsü yap insan olmasın Dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Sadece play yerine Wiki yaz
Spell icon Dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
İpuçları icon Dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Synergy icon Dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Game Rules dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Frost Wolf Man Dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Water spirit dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Phoenix woman Dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Void Stalker Dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows
Crystal guardian Dark souls style 2d pixel art. In-Game asset. 2d. High contrast. No shadows