Code edit (1 edits merged)
Please save this source code
User prompt
Move the cards and line breaks on the mod screen up 5%
User prompt
Move all the elements in the mod screen down by 10%. Move the smaller card assets a little further and place a line break asset between top four cards and the smaller ones below.
Code edit (1 edits merged)
Please save this source code
User prompt
Replace the suitCircle asset on the mod screen with the card asset
User prompt
Update as needed with: function createStartScreen() { // Clear any existing start screen elements startScreenElements.forEach(function (element) { if (element.parent) { element.parent.removeChild(element); } }); startScreenElements = []; // Add background animation var startScreenAnimContainer = new Container(); uiLayer.addChild(startScreenAnimContainer); startScreenElements.push(startScreenAnimContainer); var suitAssets = ['heartSuit', 'diamondSuit', 'clubSuit', 'spadeSuit']; var spacing = 400; // Calculate grid dimensions with extra buffer for seamless scrolling var numCols = Math.ceil(SCREEN_WIDTH / spacing) + 3; // Extra columns for buffer var numRows = Math.ceil(SCREEN_HEIGHT / spacing) + 3; // Extra rows for buffer // Total pattern size (must be divisible by spacing for seamless tiling) var patternWidth = numCols * spacing; var patternHeight = numRows * spacing; backgroundSuits = []; // Create the grid with proper offsets for (var row = 0; row < numRows; row++) { for (var col = 0; col < numCols; col++) { // Use modulo to create repeating pattern var suitIndex = (row + col) % suitAssets.length; var suitId = suitAssets[suitIndex]; var suit = LK.getAsset(suitId, { anchorX: 0.5, anchorY: 0.5 }); // Position with offset to ensure coverage suit.x = col * spacing - spacing; // Start one spacing to the left suit.y = row * spacing - spacing; // Start one spacing above suit.alpha = 0.8; suit.scale.set(1.5); // Store original position for wrapping calculations suit.baseX = col * spacing; suit.baseY = row * spacing; startScreenAnimContainer.addChild(suit); backgroundSuits.push(suit); } } // ... rest of the start screen code remains the same ... } // Update the animation in game.update: game.update = function () { // Only run game logic when in playing state if (currentGameState !== 'playing') { if (currentGameState === 'start' && backgroundSuits.length > 0) { var speed = 0.5; var spacing = 400; // Calculate pattern dimensions var numCols = Math.ceil(SCREEN_WIDTH / spacing) + 3; var numRows = Math.ceil(SCREEN_HEIGHT / spacing) + 3; var patternWidth = numCols * spacing; var patternHeight = numRows * spacing; for (var i = 0; i < backgroundSuits.length; i++) { var suit = backgroundSuits[i]; // Move diagonally suit.x += speed; suit.y += speed; // Wrap horizontally if (suit.x > SCREEN_WIDTH + spacing * 1.5) { suit.x -= patternWidth; } // Wrap vertically if (suit.y > SCREEN_HEIGHT + spacing * 1.5) { suit.y -= patternHeight; } } } return; } // ... rest of game update code ... };
User prompt
Please fix the bug: 'ReferenceError: Can't find variable: startScreenAnimContainer' in or related to this line: 'startScreenAnimContainer.x += speed;' Line Number: 2795
User prompt
Update with: // In createStartScreen(), after creating the suits, store the container's initial position startScreenAnimContainer.initialX = 0; startScreenAnimContainer.initialY = 0; // In the update function, replace the current animation with: if (currentGameState === 'start' && backgroundSuits.length > 0) { var speed = 0.5; var spacing = 400; // Move the entire container instead of individual suits startScreenAnimContainer.x += speed; startScreenAnimContainer.y += speed; // Reset the container position when it's moved one spacing unit if (startScreenAnimContainer.x >= spacing) { startScreenAnimContainer.x -= spacing; } if (startScreenAnimContainer.y >= spacing) { startScreenAnimContainer.y -= spacing; } }
User prompt
Update as needed with: // In createStartScreen(), replace the grid creation with: var suitAssets = ['heartSuit', 'diamondSuit', 'clubSuit', 'spadeSuit']; var spacing = 400; backgroundSuits = []; // Create enough suits to fill screen plus buffer var numX = Math.ceil(SCREEN_WIDTH / spacing) + 3; var numY = Math.ceil(SCREEN_HEIGHT / spacing) + 3; for (var i = 0; i < numX; i++) { for (var j = 0; j < numY; j++) { var suitId = suitAssets[(i + j) % suitAssets.length]; var suit = LK.getAsset(suitId, { anchorX: 0.5, anchorY: 0.5 }); // Start positions to cover screen with buffer suit.x = (i - 1) * spacing; suit.y = (j - 1) * spacing; suit.alpha = 0.8; suit.scale.set(1.5); startScreenAnimContainer.addChild(suit); backgroundSuits.push(suit); } } ``` And in the update function: ```javascript if (currentGameState === 'start' && backgroundSuits.length > 0) { var speed = 0.5; var spacing = 400; for (var i = 0; i < backgroundSuits.length; i++) { var suit = backgroundSuits[i]; suit.x += speed; suit.y += speed; // Seamless wrapping - when a suit goes off bottom-right, // wrap it to top-left if (suit.x > SCREEN_WIDTH + spacing) { suit.x -= (Math.ceil(SCREEN_WIDTH / spacing) + 2) * spacing; } if (suit.y > SCREEN_HEIGHT + spacing) { suit.y -= (Math.ceil(SCREEN_HEIGHT / spacing) + 2) * spacing; } } }
User prompt
Update with: if (currentGameState === 'start' && backgroundSuits.length > 0) { var speed = 0.5; var spacing = 400; var boundsWidth = (Math.ceil(SCREEN_WIDTH / spacing) + 2) * spacing; var boundsHeight = (Math.ceil(SCREEN_HEIGHT / spacing) + 2) * spacing; for (var i = 0; i < backgroundSuits.length; i++) { var suit = backgroundSuits[i]; suit.x += speed; suit.y += speed; // Fix: Only wrap when BOTH x and y are off-screen if (suit.x - 200 > SCREEN_WIDTH && suit.y - 200 > SCREEN_HEIGHT) { suit.x -= boundsWidth; suit.y -= boundsHeight; } } }
User prompt
Update with: if (currentGameState === 'start' && backgroundSuits.length > 0) { var speed = 0.5; var spacing = 400; var numCols = Math.ceil(SCREEN_WIDTH / spacing) + 2; var numRows = Math.ceil(SCREEN_HEIGHT / spacing) + 2; // Fix: Use exact grid spacing for wrapping var boundsWidth = numCols * spacing; var boundsHeight = numRows * spacing; for (var i = 0; i < backgroundSuits.length; i++) { var suit = backgroundSuits[i]; suit.x += speed; suit.y += speed; if (suit.x - 200 > SCREEN_WIDTH) { suit.x -= boundsWidth; } if (suit.y - 200 > SCREEN_HEIGHT) { suit.y -= boundsHeight; } } }
User prompt
Update with: if (currentGameState === 'start' && backgroundSuits.length > 0) { var speed = 0.5; var spacing = 400; var boundsWidth = (Math.ceil(SCREEN_WIDTH / spacing) + 2) * spacing; var boundsHeight = (Math.ceil(SCREEN_HEIGHT / spacing) + 2) * spacing; for (var i = 0; i < backgroundSuits.length; i++) { var suit = backgroundSuits[i]; suit.x += speed; suit.y += speed; // Fix: Wrap before they reach the edge to avoid overlap if (suit.x > SCREEN_WIDTH + spacing) { suit.x -= boundsWidth; } if (suit.y > SCREEN_HEIGHT + spacing) { suit.y -= boundsHeight; } } }
User prompt
Update with: for (var row = -1; row < numRows; row++) { for (var col = -1; col < numCols; col++) { // Create checkerboard pattern to avoid lines var suitIndex = (row + col + 2) % suitAssets.length; var suitId = suitAssets[suitIndex]; var suit = LK.getAsset(suitId, { anchorX: 0.5, anchorY: 0.5 }); // Add slight offset to break up the grid pattern var offsetX = ((row + col) % 2) * 50; // Alternate offset var offsetY = ((row + col) % 2) * 25; suit.x = col * spacing + offsetX; suit.y = row * spacing + offsetY; suit.alpha = 0.8; suit.scale.set(1.5); startScreenAnimContainer.addChild(suit); backgroundSuits.push(suit); } }
User prompt
Instead of using middleBar for the grid in the mode screen use the card asset. Make it three columns, keep the cards their original size and space it appropriately.
User prompt
Triple the size of the suitCircle on the mod screen and double the size of the suit assets.
User prompt
Remove middle bar from the top of the mod screen.
Code edit (5 edits merged)
Please save this source code
User prompt
Use suitCircle instead of the poker chip asset on the mod screen.
Code edit (1 edits merged)
Please save this source code
User prompt
More extreme and resolve a little later. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Y axis squish should start more extreme and resolve sooner. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Now that cards are being dealt from the bottom, let’s change the scale squish to the other direction, along the Y axis to make it look like cards are being laid onto the table. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
The play and mods button should also remain visible on the mods screen. The bottomBar is like a navigation bar.
User prompt
When the suit mods button is pressed we are going to bring up the Mods screen by removing the title logo, use the middle bar asset at the top of the screen with four circles equally spaced across it, in these circles are the hearts, clubs, diamonds and spades assets. Underneath it make a smaller grid of windows that are two columns wide, using the middle bar asset. Leave the bottomBar where it is.
User prompt
Add the suitModButton to the right of the play button on the title screen.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ /**** * Bullet Class ****/ var Bullet = Container.expand(function () { var self = Container.call(this); self.active = false; self.target = null; self.damage = 10; self.speed = 15.6; self.isPlayerCard = true; self.isSeekingLastPosition = false; self.targetLastX = 0; self.targetLastY = 0; var currentGraphic = null; var suitGraphics = { 'hearts': self.attachAsset('heartSuit', { anchorX: 0.5, anchorY: 0.5 }), 'diamonds': self.attachAsset('diamondSuit', { anchorX: 0.5, anchorY: 0.5 }), 'clubs': self.attachAsset('clubSuit', { anchorX: 0.5, anchorY: 0.5 }), 'spades': self.attachAsset('spadeSuit', { anchorX: 0.5, anchorY: 0.5 }) }; // Set scale and hide all suit graphics initially. for (var suit in suitGraphics) { var graphic = suitGraphics[suit]; graphic.scale.set(0.3); // Small bullets graphic.visible = false; } self.activate = function (startX, startY, target, damage, suit, isPlayerCard) { self.active = true; self.visible = true; self.x = startX; self.y = startY; self.target = target; self.damage = damage; self.isPlayerCard = isPlayerCard; self.suit = suit || 'hearts'; self.isSeekingLastPosition = false; if (self.target) { self.targetLastX = self.target.x; self.targetLastY = self.target.y; } if (currentGraphic) { currentGraphic.visible = false; } if (suit && suitGraphics[suit]) { currentGraphic = suitGraphics[suit]; } else { // Default to hearts if no suit specified currentGraphic = suitGraphics['hearts']; } currentGraphic.visible = true; }; self.findNewTarget = function () { // Use the same targeting logic as cards, but from bullet's perspective var targets = self.isPlayerCard ? activePlayerChips : activeAIChips; var bestTarget = null; var shortestDistance = Infinity; for (var i = 0; i < targets.length; i++) { if (targets[i].active) { var dx = targets[i].x - self.x; var dy = targets[i].y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < shortestDistance) { shortestDistance = distance; bestTarget = targets[i]; } } } return bestTarget; }; self.update = function () { if (!self.active) { return; } // If target is destroyed, try to find a new target instead of seeking last position if (self.target && !self.target.active) { var newTarget = self.findNewTarget(); if (newTarget) { self.target = newTarget; self.targetLastX = newTarget.x; self.targetLastY = newTarget.y; // Continue with normal targeting logic } else { // No new target available, seek last position self.isSeekingLastPosition = true; self.target = null; } } // If seeking, move towards the last position if (self.isSeekingLastPosition) { var dx = self.targetLastX - self.x; var dy = self.targetLastY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < self.speed) { // Arrived at destination, recycle bullet self.isSeekingLastPosition = false; if (currentGraphic) { currentGraphic.visible = false; } PoolManager.returnBullet(self); } else { // Move towards destination var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; } } else if (self.target) { // Store previous position for continuous collision detection var prevX = self.x; var prevY = self.y; // Update last known position self.targetLastX = self.target.x; self.targetLastY = self.target.y; var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Calculate where bullet will be after this frame var angle = Math.atan2(dy, dx); var newX = self.x + Math.cos(angle) * self.speed; var newY = self.y + Math.sin(angle) * self.speed; // RELIABLE COLLISION DETECTION: // Check if the bullet's path for this frame intersects the target's radius. // This prevents "tunneling" where a fast bullet could pass through a target between frames. var hitRadius = 85; // Visual radius of the chip, matches chip graphics var collisionDistance = distancePointToLine(self.target.x, self.target.y, prevX, prevY, newX, newY); if (collisionDistance <= hitRadius) { // Hit detected self.target.takeDamage(self.damage); if (currentGraphic) { currentGraphic.visible = false; } PoolManager.returnBullet(self); } else { // No hit, move bullet forward self.x = newX; self.y = newY; } } else { // No target and not seeking, recycle if (currentGraphic) { currentGraphic.visible = false; } PoolManager.returnBullet(self); } }; return self; }); // Helper function to calculate distance from point to line segment /**** * Object Pool Manager ****/ /**** * Card Class ****/ var Card = Container.expand(function (cardData) { var self = Container.call(this); self.cardData = cardData; self.level = 1; self.isInPlay = false; self.isPlayerCard = true; // Track which player owns this card self.playSlotX = 0; self.playSlotY = 0; self.lastFired = 0; self.fireRate = 60; // Base fire rate (ticks between shots) self.damage = 35; // Increased base damage self.range = 200; self.handBonus = 1; var redOutline = self.attachAsset('card', { anchorX: 0.5, anchorY: 0.5 }); redOutline.scale.set(1.1); redOutline.alpha = 1.0; redOutline.tint = 0xff0000; // Red color redOutline.visible = false; self.redOutline = redOutline; var greenOutline = self.attachAsset('card', { anchorX: 0.5, anchorY: 0.5 }); greenOutline.scale.set(1.1); greenOutline.alpha = 0.7; greenOutline.tint = 0x00ff00; greenOutline.visible = false; self.greenOutline = greenOutline; var cardGraphics = self.attachAsset('card', { anchorX: 0.5, anchorY: 0.5 }); // Define getSuitSymbol method before using it self.getSuitSymbol = function (suit) { switch (suit) { case 'hearts': return '♥'; case 'diamonds': return '♦'; case 'clubs': return '♣'; case 'spades': return '♠'; case 'joker': return '🃏'; default: return '?'; } }; // Card value in top left corner if (cardData.suit !== 'joker') { var valueText = new Text2(cardData.value, { size: 56, fill: CardSystem.suitColors[cardData.suit] || 0x000000, weight: 800, stroke: 0x000000, strokeThickness: 0 }); valueText.anchor.set(0, 0); valueText.x = -95; // Top left valueText.y = -135; self.addChild(valueText); } // Large suit symbol in center var suitAssetId = null; switch (cardData.suit) { case 'hearts': suitAssetId = 'heartSuit'; break; case 'diamonds': suitAssetId = 'diamondSuit'; break; case 'clubs': suitAssetId = 'clubSuit'; break; case 'spades': suitAssetId = 'spadeSuit'; break; } if (suitAssetId) { var suitGraphics = self.attachAsset(suitAssetId, { anchorX: 0.5, anchorY: 0.5 }); suitGraphics.y = -15; // Slightly above center // Scale to appropriate size suitGraphics.scaleX = suitGraphics.scaleY = 0.8; } else if (cardData.suit === 'joker') { // For jokers, use the joker suit asset var jokerSuitGraphics = self.attachAsset('jokerSuit', { anchorX: 0.5, anchorY: 0.5 }); jokerSuitGraphics.y = -15; // Slightly above center jokerSuitGraphics.scale.set(1.5); } // Level text at bottom var levelText = new Text2('Lvl 1', { size: 45, fill: 0x000000, weight: 800, stroke: 0x000000, strokeThickness: 0 }); levelText.anchor.set(0.5, 1); levelText.y = 128; // Bottom of card self.addChild(levelText); self.activate = function (x, y, inPlay, isPlayerCard) { self.x = x; self.y = y; self.isInPlay = inPlay || false; self.isPlayerCard = isPlayerCard !== undefined ? isPlayerCard : true; self.visible = true; if (inPlay) { self.calculateStats(); } }; self.calculateStats = function () { // Stats based on level only, not card face value var baseDamage = 10; // Reduced base damage var baseFireRate = 60; // Level scaling - more dramatic improvements per level self.damage = Math.floor(baseDamage * Math.pow(1.8, self.level - 1)); // Increased scaling self.fireRate = Math.max(15, Math.floor(baseFireRate / Math.pow(1.3, self.level - 1))); // Apply poker hand bonus self.damage = Math.floor(self.damage * self.handBonus); self.fireRate = Math.max(10, Math.floor(self.fireRate / self.handBonus)); }; self.setLevel = function (newLevel) { if (self.cardData.suit === 'joker') { self.level = 1; levelText.visible = false; self.calculateStats(); return; } self.level = newLevel; levelText.setText('Lvl ' + self.level); self.calculateStats(); // Visual feedback for higher levels if (self.level > 1) { // The glow effect was causing cards to become translucent. // Level up is already indicated by animation and floating text. } }; self.canMergeWith = function (otherCard) { if (!otherCard || otherCard === self) { return false; } // If the card being dropped onto is a Joker, it cannot be leveled up. if (otherCard.cardData.suit === 'joker') { return false; } // If the card being dragged is a Joker, it can merge with any non-Joker card. if (self.cardData.suit === 'joker') { return true; } // Must be same level AND (same suit OR same value) var sameLevel = self.level === otherCard.level; var sameSuit = self.cardData.suit === otherCard.cardData.suit; var sameValue = self.cardData.value === otherCard.cardData.value; return sameLevel && (sameSuit || sameValue); }; self.mergeWith = function (otherCard) { if (!self.canMergeWith(otherCard)) { return null; } // When merging, the new card levels up. The new level is one higher than the card on the board. var newLevel = otherCard.level + 1; // Special case: If the card being dragged is a Joker, the target card just increases its level if (self.cardData.suit === 'joker') { var mergedCard = new Card(otherCard.cardData); mergedCard.setLevel(newLevel); return mergedCard; } // The new card is of a random type, regardless of what was merged. var randomSuit = CardSystem.suits[Math.floor(Math.random() * CardSystem.suits.length)]; var randomValue = CardSystem.values[Math.floor(Math.random() * CardSystem.values.length)]; var newCardData = { suit: randomSuit, value: randomValue, id: randomSuit + '_' + randomValue }; var mergedCard = new Card(newCardData); mergedCard.setLevel(newLevel); return mergedCard; }; self.findTarget = function () { // Player cards target enemies attacking the player (activePlayerChips) // AI cards target enemies attacking the AI (activeAIChips) var targets = self.isPlayerCard ? activePlayerChips : activeAIChips; var bestTarget = null; var highestProgress = -1; for (var i = 0; i < targets.length; i++) { var chip = targets[i]; // Add extra validation for alive targets if (chip.active && chip.health > 0 && chip.visible && chip.pathProgress > highestProgress) { highestProgress = chip.pathProgress; bestTarget = chip; } } return bestTarget; }; self.fire = function () { var target = self.findTarget(); if (!target) { return; } var bullet = PoolManager.getBullet(); if (bullet) { bullet.activate(self.x, self.y, target, self.damage, self.cardData.suit, self.isPlayerCard); gameLayer.addChild(bullet); activeBullets.push(bullet); self.lastFired = LK.ticks; // Visual feedback for firing tween.stop(self, { scaleX: true, scaleY: true }); tween(self, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100, easing: tween.quadOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 150, easing: tween.elasticOut }); } }); } }; self.update = function () { if (!self.isInPlay) { return; } if (LK.ticks - self.lastFired >= self.fireRate) { self.fire(); } }; // Initialize with level 1 self.setLevel(1); return self; }); /**** * Poker Chip Enemy Class ****/ var PokerChip = Container.expand(function () { var self = Container.call(this); self.active = false; self.health = 100; // Increased base health self.maxHealth = 100; self.value = 1; self.speed = 0.05; self.pathProgress = 0; self.isPlayerSide = true; self.damageFlashTimer = 0; var chipGraphicsAssets = { 1: self.attachAsset('yellowChip', { anchorX: 0.5, anchorY: 0.5 }), 5: self.attachAsset('redChip', { anchorX: 0.5, anchorY: 0.5 }), 10: self.attachAsset('greenChip', { anchorX: 0.5, anchorY: 0.5 }), 25: self.attachAsset('blueChip', { anchorX: 0.5, anchorY: 0.5 }), 100: self.attachAsset('purpleChip', { anchorX: 0.5, anchorY: 0.5 }) }; var chipGraphics = null; // This will hold the current visible chip for (var val in chipGraphicsAssets) { chipGraphicsAssets[val].visible = false; } var healthText = new Text2('', { size: 80, fill: 0xffffff, weight: 800, stroke: 0x000000, strokeThickness: 8 }); healthText.anchor.set(0.5, 0.5); self.addChild(healthText); self.activate = function (value, isPlayerSide, startPos) { self.active = true; self.visible = true; self.value = value; self.isPlayerSide = isPlayerSide; // Health scales with chip value - more valuable chips are tankier self.maxHealth = value * 50; // Much higher base health self.health = self.maxHealth; self.pathProgress = 0; // Set speed based on chip value - 1 value chips move slower if (value === 1) { self.speed = 0.03; // Slower speed for 1 value chips } else { self.speed = 0.05; // Normal speed for other chips } // Recreate healthText to ensure proper styling if (healthText && healthText.parent) { healthText.parent.removeChild(healthText); } healthText = new Text2('', { size: 80, fill: 0xffffff, weight: 800, stroke: 0x000000, strokeThickness: 8 }); healthText.anchor.set(0.5, 0.5); self.addChild(healthText); self.setChipAppearance(); self.x = startPos.x; self.y = startPos.y; }; self.updateHealthText = function () { // Remove the old text object if (healthText && healthText.parent) { healthText.parent.removeChild(healthText); } // Create a new text object with all styling properties healthText = new Text2(formatNumberWithSuffix(Math.max(0, self.health)), { size: 80, fill: 0xffffff, weight: 800, stroke: 0x000000, strokeThickness: 8 }); healthText.anchor.set(0.5, 0.5); self.addChild(healthText); }; self.setChipAppearance = function () { if (chipGraphics) { chipGraphics.visible = false; } chipGraphics = chipGraphicsAssets[self.value] || chipGraphicsAssets[1]; if (chipGraphics) { chipGraphics.visible = true; } self.updateHealthText(); }; self.takeDamage = function (damage) { self.health -= damage; self.updateHealthText(); self.damageFlashTimer = 10; // Flash for 10 ticks instead of setTimeout if (self.health <= 0) { // IMMEDIATELY mark as inactive to prevent further hits self.active = false; self.die(); } }; self.die = function () { var chipsEarned = Math.ceil(self.value * 1.5); if (self.isPlayerSide) { gameState.playerChips += chipsEarned; } else { gameState.aiChips += chipsEarned; } PoolManager.returnChip(self); }; self.update = function () { if (!self.active) { return; } // Handle damage flash if (self.damageFlashTimer > 0) { self.damageFlashTimer--; if (chipGraphics) { chipGraphics.tint = self.damageFlashTimer > 0 ? 0xff0000 : 0xffffff; } } self.pathProgress += self.speed; var pathPos = PathSystem.getPositionAlongPath(self.pathProgress, self.isPlayerSide); if (pathPos.completed) { if (self.isPlayerSide) { gameState.playerLives--; } else { gameState.aiLives--; } PoolManager.returnChip(self); return; } self.x = pathPos.x; self.y = pathPos.y; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x0f3d0f }); /**** * Game Code ****/ // Helper function to calculate distance from point to line segment /**** * Poker Tower Defense - Complete Refactor ****/ /**** * Game Constants ****/ function distancePointToLine(px, py, x1, y1, x2, y2) { var A = px - x1; var B = py - y1; var C = x2 - x1; var D = y2 - y1; var dot = A * C + B * D; var lenSq = C * C + D * D; if (lenSq === 0) { // Line segment is actually a point return Math.sqrt(A * A + B * B); } var param = dot / lenSq; var xx, yy; if (param < 0) { xx = x1; yy = y1; } else if (param > 1) { xx = x2; yy = y2; } else { xx = x1 + param * C; yy = y1 + param * D; } var dx = px - xx; var dy = py - yy; return Math.sqrt(dx * dx + dy * dy); } function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) { throw o; } } } }; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) { return _arrayLikeToArray(r, a); } var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) { n[e] = r[e]; } return n; } var SCREEN_WIDTH = 2048; var SCREEN_HEIGHT = 2732; var PLAY_AREA_COLS = 5; var PLAY_AREA_ROWS = 2; var SLOT_WIDTH = 300; var SLOT_HEIGHT = 420; var DEAL_SLOT_WIDTH = 240; var DEAL_SLOT_HEIGHT = 330; // AI area positioning (top) var AI_AREA_X = (SCREEN_WIDTH - PLAY_AREA_COLS * SLOT_WIDTH) / 2; var AI_AREA_Y = 150; // Player area positioning (middle, with plenty of room below) var PLAYER_AREA_X = (SCREEN_WIDTH - PLAY_AREA_COLS * SLOT_WIDTH) / 2; var PLAYER_AREA_Y = SCREEN_HEIGHT - 1300; // Much higher up // Player deal area (hand slots) - below play area var PLAYER_DEAL_AREA_Y = PLAYER_AREA_Y + PLAY_AREA_ROWS * SLOT_HEIGHT + 100; /**** * Card System ****/ var CardSystem = { suits: ['hearts', 'diamonds', 'clubs', 'spades'], values: ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'], suitColors: { 'hearts': 0xff0000, 'diamonds': 0xff0000, 'clubs': 0x000000, 'spades': 0x000000 }, createDeck: function createDeck() { var deck = []; var _iterator = _createForOfIteratorHelper(this.suits), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var suit = _step.value; var _iterator2 = _createForOfIteratorHelper(this.values), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var value = _step2.value; deck.push({ suit: suit, value: value, id: suit + '_' + value }); } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } } // Add jokers } catch (err) { _iterator.e(err); } finally { _iterator.f(); } deck.push({ suit: 'joker', value: 'red', id: 'joker_red' }); deck.push({ suit: 'joker', value: 'black', id: 'joker_black' }); return this.shuffleDeck(deck); }, shuffleDeck: function shuffleDeck(deck) { for (var i = deck.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = deck[i]; deck[i] = deck[j]; deck[j] = temp; } return deck; }, getCardValue: function getCardValue(card) { if (card.cardData.suit === 'joker') { return 14; } // Jokers are highest if (card.cardData.value === 'A') { return 14; } // Aces high if (card.cardData.value === 'K') { return 13; } if (card.cardData.value === 'Q') { return 12; } if (card.cardData.value === 'J') { return 11; } return parseInt(card.cardData.value); }, evaluatePokerHand: function evaluatePokerHand(cards) { var _this = this; if (!cards || cards.length === 0) { return { type: 'none', strength: 0, multiplier: 1, contributingCards: [] }; } // Sort cards by value for easier analysis var sortedCards = cards.slice().sort(function (a, b) { return _this.getCardValue(b) - _this.getCardValue(a); }); var values = sortedCards.map(function (card) { return _this.getCardValue(card); }); var suits = sortedCards.map(function (card) { return card.cardData.suit; }); // Count values and suits var valueCounts = {}; var suitCounts = {}; values.forEach(function (value) { return valueCounts[value] = (valueCounts[value] || 0) + 1; }); suits.forEach(function (suit) { return suitCounts[suit] = (suitCounts[suit] || 0) + 1; }); var counts = Object.values(valueCounts).sort(function (a, b) { return b - a; }); // Only check for 5-card hands if we have 5 cards var isFlush = false; var isStraight = false; if (cards.length === 5) { isFlush = Object.keys(suitCounts).length === 1; isStraight = this.checkStraight(values); } // Royal Flush if (isFlush && isStraight && values[0] === 14 && values[4] === 10) { return { type: 'royal_flush', strength: 10, multiplier: 25, contributingCards: sortedCards }; } // Straight Flush if (isFlush && isStraight) { return { type: 'straight_flush', strength: 9, multiplier: 12, contributingCards: sortedCards }; } // Four of a Kind if (counts[0] === 4) { var quadValue; for (var v in valueCounts) { if (valueCounts[v] === 4) { quadValue = parseInt(v); break; } } return { type: 'four_of_a_kind', strength: 8, multiplier: 8, contributingCards: sortedCards.filter(function (c) { return _this.getCardValue(c) === quadValue; }) }; } // Full House if (counts[0] === 3 && counts[1] === 2) { return { type: 'full_house', strength: 7, multiplier: 5, contributingCards: sortedCards }; } // Flush if (isFlush) { return { type: 'flush', strength: 6, multiplier: 3.5, contributingCards: sortedCards }; } // Straight if (isStraight) { return { type: 'straight', strength: 5, multiplier: 2.5, contributingCards: sortedCards }; } // Three of a Kind if (counts[0] === 3) { var tripValue; for (var v in valueCounts) { if (valueCounts[v] === 3) { tripValue = parseInt(v); break; } } return { type: 'three_of_a_kind', strength: 4, multiplier: 2, contributingCards: sortedCards.filter(function (c) { return _this.getCardValue(c) === tripValue; }) }; } // Two Pair if (counts[0] === 2 && counts[1] === 2) { var pairValues = []; for (var v in valueCounts) { if (valueCounts[v] === 2) { pairValues.push(parseInt(v)); } } return { type: 'two_pair', strength: 3, multiplier: 1.5, contributingCards: sortedCards.filter(function (c) { return pairValues.indexOf(_this.getCardValue(c)) !== -1; }) }; } // One Pair if (counts[0] === 2) { var pairValue; for (var v in valueCounts) { if (valueCounts[v] === 2) { pairValue = parseInt(v); break; } } return { type: 'one_pair', strength: 2, multiplier: 1.2, contributingCards: sortedCards.filter(function (c) { return _this.getCardValue(c) === pairValue; }) }; } // High Card return { type: 'high_card', strength: 1, multiplier: 1, contributingCards: [sortedCards[0]] }; }, checkStraight: function checkStraight(values) { if (values.length !== 5) { return false; } // Check for ace-low straight (A, 2, 3, 4, 5) if (values[0] === 14 && values[1] === 5 && values[2] === 4 && values[3] === 3 && values[4] === 2) { return true; } // Check normal straight for (var i = 0; i < 4; i++) { if (values[i] - values[i + 1] !== 1) { return false; } } return true; } }; /**** * Object Pool Manager ****/ var PoolManager = { chipPool: [], bulletPool: [], cardPool: [], CHIP_POOL_SIZE: 50, BULLET_POOL_SIZE: 100, CARD_POOL_SIZE: 60, init: function init() { // Initialize pools for (var i = 0; i < this.CHIP_POOL_SIZE; i++) { var chip = new PokerChip(); chip.active = false; chip.visible = false; this.chipPool.push(chip); } for (var i = 0; i < this.BULLET_POOL_SIZE; i++) { var bullet = new Bullet(); bullet.active = false; bullet.visible = false; this.bulletPool.push(bullet); } }, getChip: function getChip() { for (var i = 0; i < this.chipPool.length; i++) { if (!this.chipPool[i].active) { return this.chipPool[i]; } } return null; }, getBullet: function getBullet() { for (var i = 0; i < this.bulletPool.length; i++) { if (!this.bulletPool[i].active) { return this.bulletPool[i]; } } return null; }, returnChip: function returnChip(chip) { // Force-set inactive state chip.active = false; chip.visible = false; chip.health = 0; // Ensure health is 0 // Clear ownership flag var wasPlayerSide = chip.isPlayerSide; chip.isPlayerSide = null; // Remove from active arrays (check both arrays to be safe) var playerIndex = activePlayerChips.indexOf(chip); if (playerIndex !== -1) { activePlayerChips.splice(playerIndex, 1); } var aiIndex = activeAIChips.indexOf(chip); if (aiIndex !== -1) { activeAIChips.splice(aiIndex, 1); } // Remove from containers if (chip.parent) { chip.parent.removeChild(chip); } // Double-check removal from both containers if (activePlayerChipsContainer.children.indexOf(chip) !== -1) { activePlayerChipsContainer.removeChild(chip); } if (activeAIChipsContainer.children.indexOf(chip) !== -1) { activeAIChipsContainer.removeChild(chip); } }, returnBullet: function returnBullet(bullet) { var explosionColor = 0x333333; // Dark Grey for clubs/spades if (bullet.suit === 'hearts' || bullet.suit === 'diamonds') { explosionColor = 0xff0000; // Red for hearts/diamonds } createExplosion(bullet.x, bullet.y, explosionColor); bullet.active = false; bullet.visible = false; bullet.target = null; // Add this line to clear the target reference var index = activeBullets.indexOf(bullet); if (index !== -1) { activeBullets.splice(index, 1); } gameLayer.removeChild(bullet); } }; /**** * Path System ****/ var PathSystem = { playerPath: [], aiPath: [], init: function init() { // Create player path - rectangular loop around the play area var padding = 130; // Distance from play area - increased by 50 pixels var verticalPadding = padding - 25 - 30; // Reduced by 25 + 30 to decrease side length by 110px total var leftX = PLAYER_AREA_X - padding + 10; var rightX = PLAYER_AREA_X + PLAY_AREA_COLS * SLOT_WIDTH + padding - 20 - 10; var topY = PLAYER_AREA_Y - verticalPadding; var bottomY = PLAYER_AREA_Y + PLAY_AREA_ROWS * SLOT_HEIGHT + verticalPadding; this.playerPath = [ // Start at bottom left { x: leftX, y: bottomY }, // Go up the left side { x: leftX, y: topY }, // Go across the top { x: rightX, y: topY }, // Go down the right side { x: rightX, y: bottomY }]; // Create AI path - UPSIDE DOWN mirror of player path var aiLeftX = AI_AREA_X - padding; var aiRightX = AI_AREA_X + PLAY_AREA_COLS * SLOT_WIDTH + padding; var aiVerticalPadding = verticalPadding - 40 + 15; // Additional 40px reduction on each side for AI (80px total), extended by 15px var aiTopY = AI_AREA_Y - aiVerticalPadding; var aiBottomY = AI_AREA_Y + PLAY_AREA_ROWS * SLOT_HEIGHT + aiVerticalPadding; this.aiPath = [ // Start at TOP left (opposite of player) { x: aiLeftX, y: aiTopY }, // Go DOWN the left side (opposite of player) { x: aiLeftX, y: aiBottomY }, // Go across the BOTTOM (opposite of player) { x: aiRightX, y: aiBottomY }, // Go UP the right side (opposite of player) { x: aiRightX, y: aiTopY }]; }, // Alternative offset method that spreads chips along the path direction: getPathStart: function getPathStart(isPlayerSide) { var baseStart = isPlayerSide ? this.playerPath[0] : this.aiPath[0]; var pathDirection = isPlayerSide ? this.playerPath[1] : this.aiPath[1]; // Calculate direction vector from start to next point var dx = pathDirection.x - baseStart.x; var dy = pathDirection.y - baseStart.y; var length = Math.sqrt(dx * dx + dy * dy); // Normalize direction vector var normalizedX = dx / length; var normalizedY = dy / length; // Random offset along the path (backward from start point) var pathOffset = Math.random() * 80; // 0-80 pixels back along path var sideOffset = (Math.random() - 0.5) * 40; // -20 to +20 pixels to the side return { x: baseStart.x - normalizedX * pathOffset + normalizedY * sideOffset, y: baseStart.y - normalizedY * pathOffset - normalizedX * sideOffset }; }, getPositionAlongPath: function getPositionAlongPath(progress, isPlayerSide) { var path = isPlayerSide ? this.playerPath : this.aiPath; var pathLength = this.calculatePathLength(path); var targetDistance = progress / 100 * pathLength; if (targetDistance >= pathLength) { return { x: path[path.length - 1].x, y: path[path.length - 1].y, completed: true }; } var currentDistance = 0; for (var i = 0; i < path.length - 1; i++) { var segmentLength = this.getDistance(path[i], path[i + 1]); if (currentDistance + segmentLength >= targetDistance) { var segmentProgress = (targetDistance - currentDistance) / segmentLength; return { x: path[i].x + (path[i + 1].x - path[i].x) * segmentProgress, y: path[i].y + (path[i + 1].y - path[i].y) * segmentProgress, completed: false }; } currentDistance += segmentLength; } return { x: path[path.length - 1].x, y: path[path.length - 1].y, completed: true }; }, calculatePathLength: function calculatePathLength(path) { var total = 0; for (var i = 0; i < path.length - 1; i++) { total += this.getDistance(path[i], path[i + 1]); } return total; }, getDistance: function getDistance(p1, p2) { var dx = p2.x - p1.x; var dy = p2.y - p1.y; return Math.sqrt(dx * dx + dy * dy); } }; /**** * Chip Spawner ****/ var ChipSpawner = { spawnChip: function spawnChip(value, isPlayerSide) { var chip = PoolManager.getChip(); if (!chip) { return; } var activeChips = isPlayerSide ? activePlayerChips : activeAIChips; var minDistance = 85; // Half the chip diameter (170/2) to allow controlled overlap var newPos; // If no other chips exist, we can place it anywhere. if (activeChips.length === 0) { newPos = PathSystem.getPathStart(isPlayerSide); } else { var maxAttempts = 25; // Try to find a free spot with random placement first. It's fast. var foundPosition = false; for (var attempt = 0; attempt < maxAttempts; attempt++) { var candidatePos = PathSystem.getPathStart(isPlayerSide); var isValid = true; for (var i = 0; i < activeChips.length; i++) { var otherChip = activeChips[i]; var dx = candidatePos.x - otherChip.x; var dy = candidatePos.y - otherChip.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < minDistance) { isValid = false; break; } } if (isValid) { newPos = candidatePos; foundPosition = true; break; } } // If random placements failed, use a more deterministic spiral search. if (!foundPosition) { var baseStart = PathSystem.getPathStart(isPlayerSide); var searchRadius = minDistance; // Start searching just outside the minimum distance. var angleStep = Math.PI / 6; // Check 12 directions. var spiralAttempts = 30; for (var i = 0; i < spiralAttempts; i++) { for (var angle = 0; angle < Math.PI * 2; angle += angleStep) { var testX = baseStart.x + Math.cos(angle) * searchRadius; var testY = baseStart.y + Math.sin(angle) * searchRadius; var isCandidateValid = true; for (var j = 0; j < activeChips.length; j++) { var otherChip = activeChips[j]; var dx = testX - otherChip.x; var dy = testY - otherChip.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < minDistance) { isCandidateValid = false; break; } } if (isCandidateValid) { newPos = { x: testX, y: testY }; foundPosition = true; break; } } if (foundPosition) { break; } searchRadius += 20; // Spiral outwards. } } // Ultimate fallback if no position is found (should be very rare). if (!foundPosition) { newPos = PathSystem.getPathStart(isPlayerSide); } } chip.activate(value, isPlayerSide, newPos); // IMPORTANT: Add chip to active array BEFORE the container // This ensures that subsequent chips spawned in the same batch // will detect this chip when checking for overlaps if (isPlayerSide) { activePlayerChips.push(chip); activePlayerChipsContainer.addChild(chip); } else { activeAIChips.push(chip); activeAIChipsContainer.addChild(chip); } }, spawnChipAtPosition: function spawnChipAtPosition(value, isPlayerSide, position) { var chip = PoolManager.getChip(); if (!chip) { return; } chip.activate(value, isPlayerSide, position); // Add chip to active array and container if (isPlayerSide) { activePlayerChips.push(chip); activePlayerChipsContainer.addChild(chip); } else { activeAIChips.push(chip); activeAIChipsContainer.addChild(chip); } } }; /**** * Wave Spawning System ****/ var WaveSystem = { playerSpawnTimer: 0, aiSpawnTimer: 0, waveNumber: 1, waveTimer: 0, waveDuration: 1800, // 30 seconds per wave (30 * 60 ticks) spawnInterval: 60, // Much faster: spawn every 0.75 seconds instead of 2 seconds bossSpawned: false, // Track if boss has been spawned this wave getChipValue: function getChipValue(waveNumber) { // Wave 1: Only 1-chips (easier) if (waveNumber === 1) { return 1; } // Wave 2-3: More 5s introduced earlier for increased difficulty else if (waveNumber <= 3) { return Math.random() < 0.2 ? 5 : 1; // 20% chance of 5-chip (increased from 10%) } // Wave 4-6: Even more 5s mixed in else if (waveNumber <= 6) { return Math.random() < 0.35 ? 5 : 1; // 35% chance of 5-chip (increased from 25%) } // Wave 7-9: More 5s than 1s for increased difficulty else if (waveNumber <= 9) { return Math.random() < 0.65 ? 5 : 1; // 65% chance of 5-chip (increased from 50%) } // Wave 11-15: Introduce 10-chips earlier and more frequently else if (waveNumber <= 15) { var rand = Math.random(); if (rand < 0.15) { return 10; } else if (rand < 0.7) { return 5; } else { return 1; } } // Wave 16-19: More variety with higher values else if (waveNumber <= 19) { var rand = Math.random(); if (rand < 0.3) { return 10; } else if (rand < 0.8) { return 5; } else { return 1; } } // Wave 21+: Keep scaling gradually with higher difficulty else if (!this.isBossWave(waveNumber)) { var rand = Math.random(); if (rand < 0.08) { return 25; } else if (rand < 0.4) { return 10; } else if (rand < 0.85) { return 5; } else { return 1; } } }, getBossChipValue: function getBossChipValue(waveNumber) { // The health of the first boss is based on the total health of the preceding wave. // Subsequent bosses have their health increased proportionally (doubling each time). var bossLevel = Math.floor(waveNumber / 10); // Base value derived from total health of wave 9 enemies (~10.5k health -> 210 value) var baseBossValue = 210; return baseBossValue * Math.pow(2, bossLevel - 1); }, isBossWave: function isBossWave(waveNumber) { return waveNumber % 10 === 0; }, spawnChip: function spawnChip(isPlayerSide) { if (this.isBossWave(this.waveNumber)) { // Boss wave: spawn ONE big enemy at the start, then nothing if (!this.bossSpawned) { var bossValue = this.getBossChipValue(this.waveNumber); ChipSpawner.spawnChip(bossValue, isPlayerSide); this.bossSpawned = true; console.log("BOSS spawned with value:", bossValue); } // Don't spawn anything else during boss wave return; } // Normal wave spawning - always single enemy var chipValue = this.getChipValue(this.waveNumber); ChipSpawner.spawnChip(chipValue, isPlayerSide); }, playerBossDefeated: false, aiBossDefeated: false, update: function update() { this.waveTimer++; // Check if wave is complete if (this.waveTimer >= this.waveDuration) { // Check if both bosses were defeated in a boss wave if (this.isBossWave(this.waveNumber) && this.playerBossDefeated && this.aiBossDefeated) { console.log("Both bosses defeated! Moving to next round!"); createFloatingText('ROUND COMPLETE!', SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 0x00ff00); // Reset boss defeat flags this.playerBossDefeated = false; this.aiBossDefeated = false; } this.waveTimer = 0; this.waveNumber++; this.playerSpawnTimer = 0; this.aiSpawnTimer = 0; this.bossSpawned = false; // Reset boss spawn flag var waveType = this.isBossWave(this.waveNumber) ? "BOSS WAVE" : "Wave"; console.log(waveType + " " + this.waveNumber + " starting!"); return; } // During boss wave, check if no enemies remain (boss defeated) if (this.isBossWave(this.waveNumber) && this.bossSpawned) { // Check if player side boss is defeated if (!this.playerBossDefeated && activePlayerChips.length === 0) { this.playerBossDefeated = true; console.log("Player defeated the boss!"); createFloatingText('BOSS DEFEATED!', SCREEN_WIDTH / 2, PLAYER_AREA_Y + SLOT_HEIGHT, 0xffd700); } // Check if AI side boss is defeated if (!this.aiBossDefeated && activeAIChips.length === 0) { this.aiBossDefeated = true; console.log("AI defeated the boss!"); createFloatingText('AI BOSS DEFEATED!', SCREEN_WIDTH / 2, AI_AREA_Y + SLOT_HEIGHT, 0xffd700); } // If both bosses defeated, immediately end the wave if (this.playerBossDefeated && this.aiBossDefeated) { this.waveTimer = this.waveDuration - 1; // Set to end on next tick } } // Dynamic spawn interval based on wave number var currentSpawnInterval = this.spawnInterval; if (this.waveNumber === 1) { currentSpawnInterval = 90; // Slower spawning for wave 1 (1.5 seconds) } else if (this.waveNumber >= 2) { currentSpawnInterval = Math.max(45, this.spawnInterval - Math.floor((this.waveNumber - 2) * 2)); // Faster spawning for wave 2+ } // Spawn on player side this.playerSpawnTimer++; if (this.playerSpawnTimer >= currentSpawnInterval) { this.playerSpawnTimer = 0; this.spawnChip(true); } // Spawn on AI side this.aiSpawnTimer++; if (this.aiSpawnTimer >= currentSpawnInterval) { this.aiSpawnTimer = 0; this.spawnChip(false); } } }; /**** * Game State ****/ var gameState = { playerChips: 200, // Increased starting gold aiChips: 200, playerLives: 3, aiLives: 3, isPlayerTurn: true, dealCost: 25, // Initial deal cost dealCount: 0, playerDeck: [], playerHand: [], playerPlayArea: [], aiDeck: [], aiPlayArea: [] }; // Game state management var currentGameState = 'start'; // 'start' or 'playing' var startScreenElements = []; var gameElements = []; /**** * Game Variables ****/ var activePlayerChips = []; var activeAIChips = []; var activeBullets = []; var playerHandNameTexts = []; var backgroundSuits = []; var selectedCard = null; var isDragging = false; var originalCardPosition = null; var activePlayerChipsContainer = new Container(); // Container for player chips var activeAIChipsContainer = new Container(); // Container for AI chips var playerLifeHearts = []; var aiLifeHearts = []; var opponentNameText = null; var playerNameText = null; var lastPlayerLives = 0; var lastAiLives = 0; var gameLayer = new Container(); var floorBackground = LK.getAsset('floorbackround', { anchorX: 0.5, anchorY: 0.5 }); floorBackground.x = SCREEN_WIDTH / 2; floorBackground.y = SCREEN_HEIGHT / 2; gameLayer.addChild(floorBackground); var uiLayer = new Container(); game.addChild(gameLayer); game.addChild(uiLayer); /**** * UI Elements ****/ var playerChipsText = new Text2('Chips: 200', { size: 50, fill: 0xffd700, weight: 800, stroke: 0x000000, strokeThickness: 0 }); playerChipsText.x = 50; playerChipsText.y = SCREEN_HEIGHT - 120; playerChipsText.visible = false; uiLayer.addChild(playerChipsText); gameElements.push(playerChipsText); // Add AI stats too for clarity var waveText = new Text2('Wave: 1', { size: 40, fill: 0xffffff, weight: 800, stroke: 0x000000, strokeThickness: 0 }); waveText.x = SCREEN_WIDTH - 200; waveText.y = 50; waveText.visible = false; uiLayer.addChild(waveText); gameElements.push(waveText); var discardAreaContainer = new Container(); var discardAreaGraphic = discardAreaContainer.attachAsset('dealButton', { anchorX: 0.5, anchorY: 0.5 }); var discardText = new Text2('-25', { size: 50, fill: 0xffffff, weight: 800, stroke: 0x000000, strokeThickness: 0 }); discardText.anchor.set(0.5, 0.5); discardText.y = 110; discardAreaContainer.addChild(discardText); // Position it right of the hand var handWidthForDiscard = 5 * DEAL_SLOT_WIDTH + 4 * 30; var handStartXForDiscard = (SCREEN_WIDTH - handWidthForDiscard) / 2; var discardX = handStartXForDiscard + handWidthForDiscard + 30 + DEAL_SLOT_WIDTH / 2; // Make sure it doesn't go off screen if (discardX + DEAL_SLOT_WIDTH / 2 > SCREEN_WIDTH) { discardX = SCREEN_WIDTH - DEAL_SLOT_WIDTH / 2 - 20; } discardAreaContainer.x = discardX + 10 + 10; discardAreaContainer.y = PLAYER_DEAL_AREA_Y + DEAL_SLOT_HEIGHT / 2 + 10 - 10; discardAreaContainer.visible = false; uiLayer.addChild(discardAreaContainer); gameElements.push(discardAreaContainer); discardAreaContainer.down = function () { if (!isDragging && gameState.playerChips >= gameState.dealCost) { dealNewHand(); } }; // Add this new cleanup function: function cleanupDeadChips() { // Clean player chips for (var i = activePlayerChips.length - 1; i >= 0; i--) { var chip = activePlayerChips[i]; if (!chip.active || chip.health <= 0) { console.log("Force-cleaning dead player chip"); PoolManager.returnChip(chip); } } // Clean AI chips for (var i = activeAIChips.length - 1; i >= 0; i--) { var chip = activeAIChips[i]; if (!chip.active || chip.health <= 0) { console.log("Force-cleaning dead AI chip"); PoolManager.returnChip(chip); } } } /**** * Game Functions ****/ function createStartScreen() { // Clear any existing start screen elements startScreenElements.forEach(function (element) { if (element.parent) { element.parent.removeChild(element); } }); startScreenElements = []; // Add background animation var startScreenAnimContainer = new Container(); uiLayer.addChild(startScreenAnimContainer); startScreenElements.push(startScreenAnimContainer); var suitAssets = ['heartSuit', 'diamondSuit', 'clubSuit', 'spadeSuit']; var spacing = 400; var numCols = Math.ceil(SCREEN_WIDTH / spacing) + 2; var numRows = Math.ceil(SCREEN_HEIGHT / spacing) + 2; backgroundSuits = []; for (var row = -1; row < numRows; row++) { for (var col = -1; col < numCols; col++) { var suitId = suitAssets[(row + col + 2) % suitAssets.length]; var suit = LK.getAsset(suitId, { anchorX: 0.5, anchorY: 0.5 }); suit.x = col * spacing; suit.y = row * spacing; suit.alpha = 0.8; suit.scale.set(1.5); startScreenAnimContainer.addChild(suit); backgroundSuits.push(suit); } } // Add game logo centered var gameLogo = LK.getAsset('titleLogo', { anchorX: 0.5, anchorY: 0.5 }); gameLogo.x = SCREEN_WIDTH / 2; gameLogo.y = SCREEN_HEIGHT / 2 - 200; // Positioned above center uiLayer.addChild(gameLogo); startScreenElements.push(gameLogo); // Add bottom bar at same position as main screen var startBottomBar = LK.getAsset('bottomBar', { anchorX: 0.5, anchorY: 1 }); startBottomBar.x = SCREEN_WIDTH / 2; startBottomBar.y = SCREEN_HEIGHT; uiLayer.addChild(startBottomBar); startScreenElements.push(startBottomBar); // Add play button in center of bottom bar var playButton = LK.getAsset('playButton', { anchorX: 0.5, anchorY: 0.5 }); playButton.x = SCREEN_WIDTH / 2; playButton.y = SCREEN_HEIGHT - 195; // Center of bottom bar uiLayer.addChild(playButton); startScreenElements.push(playButton); // Add click handler to play button playButton.down = function () { startGame(); }; // Add suit mod button to the right of play button var suitModButton = LK.getAsset('suitModButton', { anchorX: 0.5, anchorY: 0.5 }); suitModButton.x = playButton.x + playButton.width / 2 + 50 + suitModButton.width / 2; // Position to the right with 50px gap suitModButton.y = playButton.y; uiLayer.addChild(suitModButton); startScreenElements.push(suitModButton); // Mods Screen Elements (initially hidden) var modsContainer = new Container(); modsContainer.visible = false; uiLayer.addChild(modsContainer); startScreenElements.push(modsContainer); // Top Bar with Suit Circles var topBar = LK.getAsset('middleBar', { anchorX: 0.5, anchorY: 0.5 }); topBar.x = SCREEN_WIDTH / 2; topBar.y = 250; topBar.width = SCREEN_WIDTH - 200; topBar.height = 200; // Scaled height modsContainer.addChild(topBar); var suitAssets = ['heartSuit', 'diamondSuit', 'clubSuit', 'spadeSuit']; var numCircles = suitAssets.length; var totalBarWidth = topBar.width; var spacing = totalBarWidth / numCircles; var startX = topBar.x - totalBarWidth / 2 + spacing / 2; for (var i = 0; i < numCircles; i++) { var circleX = startX + i * spacing; var circleY = topBar.y; // Use a chip asset as a "circle" background. 'greenChip' is 170x170. var circleBg = LK.getAsset('greenChip', { anchorX: 0.5, anchorY: 0.5 }); circleBg.scale.set(0.9); // Scale it down a bit to fit bar height circleBg.x = circleX; circleBg.y = circleY; modsContainer.addChild(circleBg); var suitIcon = LK.getAsset(suitAssets[i], { anchorX: 0.5, anchorY: 0.5 }); suitIcon.scale.set(0.6); // Scale to fit inside the circle suitIcon.x = circleX; suitIcon.y = circleY; modsContainer.addChild(suitIcon); } // Grid of windows below var gridContainer = new Container(); gridContainer.x = SCREEN_WIDTH / 2; gridContainer.y = topBar.y + topBar.height / 2 + 100; // Position below top bar modsContainer.addChild(gridContainer); var cols = 2; var rows = 4; var cellWidth = (SCREEN_WIDTH - 400) / cols; // Added padding var cellHeight = 180; var colSpacing = 80; var rowSpacing = 40; var gridTotalWidth = cols * cellWidth + (cols - 1) * colSpacing; var gridStartX = -gridTotalWidth / 2; var gridStartY = 50; for (var r = 0; r < rows; r++) { for (var c = 0; c < cols; c++) { var cell = LK.getAsset('middleBar', { anchorX: 0, anchorY: 0 }); cell.width = cellWidth; cell.height = cellHeight; cell.x = gridStartX + c * (cellWidth + colSpacing); cell.y = gridStartY + r * (cellHeight + rowSpacing); gridContainer.addChild(cell); } } suitModButton.down = function () { // Toggle between the mods screen and the title screen view var isModsVisible = modsContainer.visible; modsContainer.visible = !isModsVisible; gameLogo.visible = isModsVisible; startScreenAnimContainer.visible = isModsVisible; }; currentGameState = 'start'; } function startGame() { // Clear start screen elements startScreenElements.forEach(function (element) { if (element.parent) { element.parent.removeChild(element); } }); startScreenElements = []; backgroundSuits = []; // Show game elements gameElements.forEach(function (element) { element.visible = true; }); currentGameState = 'playing'; initializeGame(); } function initializeGame() { PoolManager.init(); PathSystem.init(); // Initialize play areas gameState.playerPlayArea = []; gameState.aiPlayArea = []; playerHandNameTexts = [null, null]; for (var row = 0; row < PLAY_AREA_ROWS; row++) { gameState.playerPlayArea[row] = []; gameState.aiPlayArea[row] = []; for (var col = 0; col < PLAY_AREA_COLS; col++) { gameState.playerPlayArea[row][col] = null; gameState.aiPlayArea[row][col] = null; } } // Create initial decks gameState.playerDeck = CardSystem.createDeck(); gameState.aiDeck = CardSystem.createDeck(); // Draw grid lines drawPlayAreas(); drawPaths(); createLifeDisplays(); lastPlayerLives = gameState.playerLives; lastAiLives = gameState.aiLives; // Initialize player's hand with empty slots gameState.playerHand = [null, null, null, null, null]; // Center bar removed // Start the wave system - first waves begin immediately LK.setTimeout(function () { ChipSpawner.spawnChip(1, true); // First enemy on player side ChipSpawner.spawnChip(1, false); // First enemy on AI side }, 1000); } function createLifeDisplays() { // Clear any existing hearts and labels if (opponentNameText && opponentNameText.parent) { opponentNameText.parent.removeChild(opponentNameText); } if (playerNameText && playerNameText.parent) { playerNameText.parent.removeChild(playerNameText); } playerLifeHearts.forEach(function (h) { if (h.parent) { h.parent.removeChild(h); } }); playerLifeHearts = []; aiLifeHearts.forEach(function (h) { if (h.parent) { h.parent.removeChild(h); } }); aiLifeHearts = []; var heartSpacing = 110; var heartScale = 0.5; var startX_AI = 200 - 70; var startX_Player = SCREEN_WIDTH - 200 + 70; var yPos = SCREEN_HEIGHT / 2 - 137 + 10; var labelYPos = yPos - 60; // Opponent Name Text var totalAIHeartsWidth = (gameState.aiLives - 1) * heartSpacing; opponentNameText = new Text2('Opponent', { size: 40, fill: 0xffffff, weight: 800 }); opponentNameText.anchor.set(0.5, 1); opponentNameText.x = startX_AI + totalAIHeartsWidth / 2; opponentNameText.y = labelYPos; uiLayer.addChild(opponentNameText); // Player Name Text var totalPlayerHeartsWidth = (gameState.playerLives - 1) * heartSpacing; playerNameText = new Text2('Player', { size: 40, fill: 0xffffff, weight: 800 }); playerNameText.anchor.set(0.5, 1); playerNameText.x = startX_Player - totalPlayerHeartsWidth / 2; playerNameText.y = labelYPos; uiLayer.addChild(playerNameText); // AI Lives (left side) for (var i = 0; i < gameState.aiLives; i++) { var heart = LK.getAsset('heartSuit', { anchorX: 0.5, anchorY: 0.5, scaleX: heartScale, scaleY: heartScale }); heart.x = startX_AI + i * heartSpacing; heart.y = yPos; uiLayer.addChild(heart); aiLifeHearts.push(heart); } // Player Lives (right side) for (var i = 0; i < gameState.playerLives; i++) { var heart = LK.getAsset('heartSuit', { anchorX: 0.5, anchorY: 0.5, scaleX: heartScale, scaleY: heartScale }); heart.x = startX_Player - i * heartSpacing; heart.y = yPos; uiLayer.addChild(heart); playerLifeHearts.push(heart); } } function dealNewHand() { if (gameState.playerChips < gameState.dealCost) { return; } // Find an empty slot in the player's hand var emptySlotIndex = -1; for (var i = 0; i < gameState.playerHand.length; i++) { if (!gameState.playerHand[i]) { emptySlotIndex = i; break; } } // If hand is full, do nothing if (emptySlotIndex === -1) { return; } gameState.playerChips -= gameState.dealCost; gameState.dealCount++; gameState.dealCost = Math.floor(25 * Math.pow(1.05, gameState.dealCount)); // Lower base cost for single card if (gameState.playerDeck.length === 0) { gameState.playerDeck = CardSystem.createDeck(); } var cardData = gameState.playerDeck.pop(); var card = new Card(cardData); var i = emptySlotIndex; var handWidth = 5 * DEAL_SLOT_WIDTH + 4 * 30; // 5 slots + 4 gaps of 30px var handStartX = (SCREEN_WIDTH - handWidth) / 2; var slotX = handStartX + i * DEAL_SLOT_WIDTH + i * 30 + DEAL_SLOT_WIDTH / 2; var slotY = PLAYER_DEAL_AREA_Y + DEAL_SLOT_HEIGHT / 2; // Start position - off to the bottom var startX = slotX + (Math.random() - 0.5) * 50; // Slight horizontal variance var startY = SCREEN_HEIGHT + DEAL_SLOT_HEIGHT; card.activate(startX, startY, false, true); card.rotation = 0; // No spin card.scaleX = 1; // Normal width card.scaleY = 0.1; // Very thin/squished vertically - like laying flat on table uiLayer.addChild(card); gameState.playerHand[i] = card; // Animate flying and expanding with a slight "settle" bounce tween(card, { x: slotX, y: slotY, scaleY: 1.1 // Slightly overshoot on Y-axis }, { duration: 300, easing: tween.quadOut, delay: i * 80, onFinish: function onFinish() { // Settle back to normal size tween(card, { scaleY: 1 }, { duration: 150, easing: tween.quadInOut }); } }); updateUI(); } function updateUI() { playerChipsText.setText('Chips: ' + formatNumberWithSuffix(gameState.playerChips)); // Update combined deal/discard button text and appearance when not dragging if (!isDragging) { discardText.setText('-' + formatNumberWithSuffix(gameState.dealCost)); discardText.fill = 0xffffff; // Update button color based on affordability if (gameState.playerChips >= gameState.dealCost) { discardAreaGraphic.tint = 0xffffff; // No tint when affordable discardAreaGraphic.alpha = 1.0; // Full alpha when affordable } else { discardAreaGraphic.tint = 0x666666; // Grey tint when not affordable discardAreaGraphic.alpha = 1.0; // Keep full alpha even when not affordable } } waveText.setText('Wave: ' + WaveSystem.waveNumber); } function drawPlayAreas() { // Draw player play area slots for (var row = 0; row < PLAY_AREA_ROWS; row++) { for (var col = 0; col < PLAY_AREA_COLS; col++) { var slot = new Container(); var slotGraphics = slot.attachAsset('card', { anchorX: 0.5, anchorY: 0.5 }); slotGraphics.alpha = 0.5; slot.x = PLAYER_AREA_X + col * SLOT_WIDTH + SLOT_WIDTH / 2; slot.y = PLAYER_AREA_Y + row * SLOT_HEIGHT + SLOT_HEIGHT / 2; gameLayer.addChild(slot); } } // Draw AI play area slots for (var row = 0; row < PLAY_AREA_ROWS; row++) { for (var col = 0; col < PLAY_AREA_COLS; col++) { var slot = new Container(); var slotGraphics = slot.attachAsset('card', { anchorX: 0.5, anchorY: 0.5 }); slotGraphics.alpha = 0.5; slotGraphics.tint = 0xff8888; // Red tint for AI area slot.x = AI_AREA_X + col * SLOT_WIDTH + SLOT_WIDTH / 2; slot.y = AI_AREA_Y + row * SLOT_HEIGHT + SLOT_HEIGHT / 2; gameLayer.addChild(slot); } } // Draw player deal area slots (hand) for (var i = 0; i < 5; i++) { var dealSlot = new Container(); var dealSlotGraphics = dealSlot.attachAsset('dealSlot', { anchorX: 0.5, anchorY: 0.5 }); dealSlotGraphics.alpha = 0.5; var handWidth = 5 * DEAL_SLOT_WIDTH + 4 * 30; // 5 slots + 4 gaps of 30px var handStartX = (SCREEN_WIDTH - handWidth) / 2; dealSlot.x = handStartX + i * DEAL_SLOT_WIDTH + i * 30 + DEAL_SLOT_WIDTH / 2; dealSlot.y = PLAYER_DEAL_AREA_Y + DEAL_SLOT_HEIGHT / 2; gameLayer.addChild(dealSlot); } } function drawPaths() { // Path lines removed - enemies still follow invisible paths } // drawPathSegment function removed - no longer needed since path lines are removed function getSlotPosition(row, col, isPlayerArea) { var baseX = isPlayerArea ? PLAYER_AREA_X : AI_AREA_X; var baseY = isPlayerArea ? PLAYER_AREA_Y : AI_AREA_Y; return { x: baseX + col * SLOT_WIDTH + SLOT_WIDTH / 2, y: baseY + row * SLOT_HEIGHT + SLOT_HEIGHT / 2 }; } function getSlotFromPosition(x, y) { // Check player play area if (x >= PLAYER_AREA_X && x <= PLAYER_AREA_X + PLAY_AREA_COLS * SLOT_WIDTH && y >= PLAYER_AREA_Y && y <= PLAYER_AREA_Y + PLAY_AREA_ROWS * SLOT_HEIGHT) { var col = Math.floor((x - PLAYER_AREA_X) / SLOT_WIDTH); var row = Math.floor((y - PLAYER_AREA_Y) / SLOT_HEIGHT); if (col >= 0 && col < PLAY_AREA_COLS && row >= 0 && row < PLAY_AREA_ROWS) { return { area: 'player', row: row, col: col }; } } // Check player hand area if (y >= PLAYER_DEAL_AREA_Y && y <= PLAYER_DEAL_AREA_Y + DEAL_SLOT_HEIGHT) { var handWidth = 5 * DEAL_SLOT_WIDTH + 4 * 30; // 5 slots + 4 gaps of 30px var handStartX = (SCREEN_WIDTH - handWidth) / 2; for (var i = 0; i < 5; i++) { var slotXStart = handStartX + i * (DEAL_SLOT_WIDTH + 30); var slotXEnd = slotXStart + DEAL_SLOT_WIDTH; if (x >= slotXStart && x <= slotXEnd) { return { area: 'hand', index: i }; } } } // Check discard area if (discardAreaContainer && x >= discardAreaContainer.x - DEAL_SLOT_WIDTH / 2 && x <= discardAreaContainer.x + DEAL_SLOT_WIDTH / 2 && y >= discardAreaContainer.y - DEAL_SLOT_HEIGHT / 2 && y <= discardAreaContainer.y + DEAL_SLOT_HEIGHT / 2) { return { area: 'discard' }; } return null; } function evaluateRowHand(row, isPlayerArea) { var playArea = isPlayerArea ? gameState.playerPlayArea : gameState.aiPlayArea; var cards = []; for (var col = 0; col < PLAY_AREA_COLS; col++) { if (playArea[row][col]) { cards.push(playArea[row][col]); } } return CardSystem.evaluatePokerHand(cards); } function updateHandNameDisplay(row, handEval) { var existingText = playerHandNameTexts[row]; var shouldShowText = handEval.strength > 1; if (shouldShowText) { var handName = handEval.type.replace(/_/g, ' ').toUpperCase(); if (existingText) { if (existingText.text !== handName) { existingText.setText(handName); } existingText.visible = true; } else { var newText = new Text2(handName, { size: 50, fill: 0xffffff, weight: '800', stroke: 0x000000, strokeThickness: 0 }); newText.anchor.set(0.5, 0); newText.alpha = 0.8; newText.x = PLAYER_AREA_X + PLAY_AREA_COLS * SLOT_WIDTH / 2; newText.y = PLAYER_AREA_Y + (row + 1) * SLOT_HEIGHT - 20; uiLayer.addChild(newText); playerHandNameTexts[row] = newText; } } else { if (existingText) { existingText.visible = false; } } } function applyHandBonuses() { // Apply bonuses to player cards for (var row = 0; row < PLAY_AREA_ROWS; row++) { var handEval = evaluateRowHand(row, true); updateHandNameDisplay(row, handEval); var contributingCards = handEval.contributingCards || []; for (var col = 0; col < PLAY_AREA_COLS; col++) { var card = gameState.playerPlayArea[row][col]; if (card) { card.handBonus = handEval.multiplier; card.calculateStats(); card.redOutline.visible = handEval.strength > 1 && contributingCards.indexOf(card) !== -1; } } } // Apply bonuses to AI cards AISystem.applyAIHandBonuses(); } /**** * AI System ****/ var AISystem = { thinkTimer: 0, thinkDelay: 60, // Think every second (60 ticks) update: function update() { this.thinkTimer++; if (this.thinkTimer >= this.thinkDelay) { this.thinkTimer = 0; this.makeMove(); } }, shouldDeal: function shouldDeal() { // Deal if we can afford it and have empty slots if (gameState.aiChips < gameState.dealCost) { return false; } var emptySlots = this.countEmptySlots(); // Deal if there are any empty slots at all return emptySlots > 0; }, countEmptySlots: function countEmptySlots() { var count = 0; for (var row = 0; row < PLAY_AREA_ROWS; row++) { for (var col = 0; col < PLAY_AREA_COLS; col++) { if (!gameState.aiPlayArea[row][col]) { count++; } } } return count; }, countLowLevelCards: function countLowLevelCards() { var count = 0; for (var row = 0; row < PLAY_AREA_ROWS; row++) { for (var col = 0; col < PLAY_AREA_COLS; col++) { var card = gameState.aiPlayArea[row][col]; if (card && card.level <= 2) { count++; } } } return count; }, makeMove: function makeMove() { // Priority order: Merge > Place > Optimize > Deal. Only one action per cycle. if (this.tryMergeCards()) { // Successfully merged, wait for next think cycle return; } if (this.tryPlaceCards()) { // Successfully placed cards return; } if (this.optimizeCardPositions()) { // Successfully optimized, wait for next think cycle return; } // As a last resort, try to deal a new card if possible if (this.shouldDeal()) { this.dealAIHand(); } }, tryMergeCards: function tryMergeCards() { // Look for mergeable cards for (var row1 = 0; row1 < PLAY_AREA_ROWS; row1++) { for (var col1 = 0; col1 < PLAY_AREA_COLS; col1++) { var card1 = gameState.aiPlayArea[row1][col1]; if (!card1) { continue; } // Look for a card to merge with for (var row2 = 0; row2 < PLAY_AREA_ROWS; row2++) { for (var col2 = 0; col2 < PLAY_AREA_COLS; col2++) { if (row1 === row2 && col1 === col2) { continue; } var card2 = gameState.aiPlayArea[row2][col2]; if (!card2) { continue; } if (card1.canMergeWith(card2)) { this.mergeCards(card1, card2, row1, col1, row2, col2); return true; } } } } } return false; }, mergeCards: function mergeCards(card1, card2, row1, col1, row2, col2) { var mergedCard = card1.mergeWith(card2); if (mergedCard) { // Remove old cards gameLayer.removeChild(card1); gameLayer.removeChild(card2); gameState.aiPlayArea[row1][col1] = null; gameState.aiPlayArea[row2][col2] = null; // Place merged card in the first position var pos = getSlotPosition(row1, col1, false); mergedCard.activate(pos.x, pos.y, true, false); gameLayer.addChild(mergedCard); gameState.aiPlayArea[row1][col1] = mergedCard; // Merge animation mergedCard.alpha = 0; mergedCard.scaleX = mergedCard.scaleY = 1.5; tween(mergedCard, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.elasticOut }); createFloatingText('+Level Up!', mergedCard.x, mergedCard.y - 50, 0x00ff00); this.applyAIHandBonuses(); } }, tryPlaceCards: function tryPlaceCards() { // AI doesn't have a "hand" like player, it deals directly to board // This function is for future expansion return false; }, optimizeCardPositions: function optimizeCardPositions() { // Move stronger cards to better positions (like completing poker hands) // For now, just try to complete rows for hand bonuses return this.tryCompletePokerHands(); }, tryCompletePokerHands: function tryCompletePokerHands() { for (var row = 0; row < PLAY_AREA_ROWS; row++) { var rowCards = []; var emptyPositions = []; for (var col = 0; col < PLAY_AREA_COLS; col++) { if (gameState.aiPlayArea[row][col]) { rowCards.push({ card: gameState.aiPlayArea[row][col], col: col }); } else { emptyPositions.push(col); } } // If row is almost complete, try to fill it strategically if (rowCards.length >= 3 && emptyPositions.length > 0) { // Look for cards in other rows that might complete a hand if (this.tryMoveCardToCompleteHand(row, rowCards, emptyPositions[0])) { return true; // A move was made, so we are done for this 'think' cycle } } } return false; // No move was made }, tryMoveCardToCompleteHand: function tryMoveCardToCompleteHand(targetRow, existingCards, targetCol) { // Look for cards in other positions that might help complete a hand for (var row = 0; row < PLAY_AREA_ROWS; row++) { if (row === targetRow) { continue; } for (var col = 0; col < PLAY_AREA_COLS; col++) { var card = gameState.aiPlayArea[row][col]; if (!card) { continue; } // Simple heuristic: move cards of same suit or sequential values var shouldMove = this.cardHelpsHand(card, existingCards); if (shouldMove && Math.random() < 0.3) { // 30% chance to move // Move the card gameState.aiPlayArea[row][col] = null; gameState.aiPlayArea[targetRow][targetCol] = card; var newPos = getSlotPosition(targetRow, targetCol, false); tween(card, { x: newPos.x, y: newPos.y }, { duration: 300, easing: tween.quadOut }); this.applyAIHandBonuses(); return true; } } } return false; }, cardHelpsHand: function cardHelpsHand(card, existingCards) { // Simple heuristic to see if a card might help complete a poker hand var suits = {}; var values = {}; existingCards.forEach(function (cardInfo) { var c = cardInfo.card.cardData; suits[c.suit] = (suits[c.suit] || 0) + 1; values[c.value] = (values[c.value] || 0) + 1; }); // Check if card matches existing suits or values var cardSuit = card.cardData.suit; var cardValue = card.cardData.value; return suits[cardSuit] >= 2 || values[cardValue] >= 1; }, dealAIHand: function dealAIHand() { if (gameState.aiChips < gameState.dealCost) { return; } gameState.aiChips -= gameState.dealCost; // Deal one card to an empty slot if (gameState.aiDeck.length === 0) { gameState.aiDeck = CardSystem.createDeck(); } var cardData = gameState.aiDeck.pop(); var card = new Card(cardData); // Find best empty slot (prefer completing rows) var bestSlot = this.findBestEmptySlot(); if (bestSlot) { var pos = getSlotPosition(bestSlot.row, bestSlot.col, false); // Set initial off-screen position and properties for animation var startY = -SLOT_HEIGHT; // Come from top of the screen card.activate(pos.x, startY, true, false); card.rotation = Math.PI * 4; // Two full spins card.scale.set(0.1); // Start small gameLayer.addChild(card); gameState.aiPlayArea[bestSlot.row][bestSlot.col] = card; // Deal animation tween(card, { y: pos.y, rotation: 0, scaleX: 1, scaleY: 1 }, { duration: 600, easing: tween.quadOut }); this.applyAIHandBonuses(); } }, findBestEmptySlot: function findBestEmptySlot() { var emptySlots = []; for (var row = 0; row < PLAY_AREA_ROWS; row++) { for (var col = 0; col < PLAY_AREA_COLS; col++) { if (!gameState.aiPlayArea[row][col]) { var score = this.evaluateSlotScore(row, col); emptySlots.push({ row: row, col: col, score: score }); } } } if (emptySlots.length === 0) { return null; } // Sort by score (higher is better) emptySlots.sort(function (a, b) { return b.score - a.score; }); return emptySlots[0]; }, evaluateSlotScore: function evaluateSlotScore(row, col) { var score = 0; var cardsInRow = 0; // Count cards in this row for (var c = 0; c < PLAY_AREA_COLS; c++) { if (gameState.aiPlayArea[row][c]) { cardsInRow++; } } // Prefer completing rows score += cardsInRow * 10; // Slight preference for middle positions score += (2 - Math.abs(col - 2)) * 2; return score; }, applyAIHandBonuses: function applyAIHandBonuses() { // Apply poker hand bonuses to AI cards for (var row = 0; row < PLAY_AREA_ROWS; row++) { var handEval = evaluateRowHand(row, false); var contributingCards = handEval.contributingCards || []; for (var col = 0; col < PLAY_AREA_COLS; col++) { var card = gameState.aiPlayArea[row][col]; if (card) { card.handBonus = handEval.multiplier; card.calculateStats(); card.redOutline.visible = handEval.strength > 1 && contributingCards.indexOf(card) !== -1; } } } } }; /**** * Input Handling ****/ game.down = function (x, y, obj) { // Only handle input during playing state if (currentGameState !== 'playing') { return; } // Check if clicking on a card in hand for (var i = 0; i < gameState.playerHand.length; i++) { var card = gameState.playerHand[i]; if (card && Math.abs(x - card.x) < DEAL_SLOT_WIDTH / 2 && Math.abs(y - card.y) < DEAL_SLOT_HEIGHT / 2) { selectedCard = card; isDragging = true; originalCardPosition = { area: 'hand', index: i }; uiLayer.addChild(selectedCard); return; } } // Check if clicking on a card in play area for (var row = 0; row < PLAY_AREA_ROWS; row++) { for (var col = 0; col < PLAY_AREA_COLS; col++) { var card = gameState.playerPlayArea[row][col]; if (card && Math.abs(x - card.x) < SLOT_WIDTH / 2 && Math.abs(y - card.y) < SLOT_HEIGHT / 2) { selectedCard = card; isDragging = true; originalCardPosition = { area: 'player', row: row, col: col }; // Remove from current position gameState.playerPlayArea[row][col] = null; gameLayer.addChild(selectedCard); return; } } } }; game.move = function (x, y, obj) { // Only handle input during playing state if (currentGameState !== 'playing') { return; } if (isDragging && selectedCard) { selectedCard.x = x; selectedCard.y = y; // Highlight mergeable cards // Player's play area for (var row = 0; row < PLAY_AREA_ROWS; row++) { for (var col = 0; col < PLAY_AREA_COLS; col++) { var card = gameState.playerPlayArea[row][col]; if (card) { card.greenOutline.visible = selectedCard.canMergeWith(card); } } } // Player's hand for (var i = 0; i < gameState.playerHand.length; i++) { var card = gameState.playerHand[i]; if (card && card !== selectedCard) { card.greenOutline.visible = selectedCard.canMergeWith(card); } } // Switch to discard mode when dragging discardAreaGraphic.tint = 0x440000; // Dark red for discard // Highlight discard area on hover var isOverDiscard = discardAreaContainer && x >= discardAreaContainer.x - DEAL_SLOT_WIDTH / 2 && x <= discardAreaContainer.x + DEAL_SLOT_WIDTH / 2 && y >= discardAreaContainer.y - DEAL_SLOT_HEIGHT / 2 && y <= discardAreaContainer.y + DEAL_SLOT_HEIGHT / 2; if (isOverDiscard) { discardAreaGraphic.alpha = 1.0; discardText.setText('Sell Card'); discardText.fill = 0xffd700; // Gold color } else { discardAreaGraphic.alpha = 0.7; discardText.setText('Discard'); discardText.fill = 0x999999; } } else { // When not dragging, show as deal button discardAreaGraphic.alpha = 1.0; // Reset to full alpha updateUI(); } }; game.up = function (x, y, obj) { // Only handle input during playing state if (currentGameState !== 'playing') { return; } if (isDragging && selectedCard) { isDragging = false; // Clear all temporary green outlines for (var row = 0; row < PLAY_AREA_ROWS; row++) { for (var col = 0; col < PLAY_AREA_COLS; col++) { var card = gameState.playerPlayArea[row][col]; if (card) { card.greenOutline.visible = false; } } } for (var i = 0; i < gameState.playerHand.length; i++) { var card = gameState.playerHand[i]; if (card) { card.greenOutline.visible = false; } } // Reset to deal button mode updateUI(); var targetSlot = getSlotFromPosition(x, y); if (targetSlot) { // Handle dropping card on discard area if (targetSlot.area === 'discard') { var chipRefund = 5 + selectedCard.level * 2; gameState.playerChips += chipRefund; // Remove from original position data if (originalCardPosition.area === 'hand') { gameState.playerHand[originalCardPosition.index] = null; } else if (originalCardPosition.area === 'player') { // This is already null from game.down, so this is just for safety. gameState.playerPlayArea[originalCardPosition.row][originalCardPosition.col] = null; } // Remove card graphic from scene and memory if (selectedCard.parent) { selectedCard.parent.removeChild(selectedCard); } createFloatingText('+' + formatNumberWithSuffix(chipRefund) + ' Chips', selectedCard.x, selectedCard.y, 0xffd700); selectedCard = null; originalCardPosition = null; updateUI(); // Recalculate bonuses if a card was removed from play area if (originalCardPosition && originalCardPosition.area === 'player') { applyHandBonuses(); } return; // Exit early } if (targetSlot.area === 'player') { var existingCard = gameState.playerPlayArea[targetSlot.row][targetSlot.col]; if (existingCard && selectedCard.canMergeWith(existingCard)) { // Merge in play area var mergedCard = selectedCard.mergeWith(existingCard); if (mergedCard) { gameLayer.removeChild(existingCard); var handIndex = gameState.playerHand.indexOf(selectedCard); if (handIndex !== -1) { uiLayer.removeChild(selectedCard); gameState.playerHand[handIndex] = null; } else { gameLayer.removeChild(selectedCard); } var pos = getSlotPosition(targetSlot.row, targetSlot.col, true); mergedCard.activate(pos.x, pos.y, true); gameLayer.addChild(mergedCard); gameState.playerPlayArea[targetSlot.row][targetSlot.col] = mergedCard; mergedCard.alpha = 0; mergedCard.scaleX = mergedCard.scaleY = 2; tween(mergedCard, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.elasticOut }); createFloatingText('+Level Up!', mergedCard.x, mergedCard.y - 50, 0x00ff00); } } else if (!existingCard) { // Place in empty slot var pos = getSlotPosition(targetSlot.row, targetSlot.col, true); selectedCard.activate(pos.x, pos.y, true); var handIndex = gameState.playerHand.indexOf(selectedCard); if (handIndex !== -1) { uiLayer.removeChild(selectedCard); gameLayer.addChild(selectedCard); gameState.playerHand[handIndex] = null; } gameState.playerPlayArea[targetSlot.row][targetSlot.col] = selectedCard; } else { // Card exists, but cannot merge: swap them var swappedCard = gameState.playerPlayArea[targetSlot.row][targetSlot.col]; // Place selectedCard in target play area slot var pos1 = getSlotPosition(targetSlot.row, targetSlot.col, true); selectedCard.activate(pos1.x, pos1.y, true); var handIndex = gameState.playerHand.indexOf(selectedCard); if (handIndex !== -1) { uiLayer.removeChild(selectedCard); gameLayer.addChild(selectedCard); gameState.playerHand[handIndex] = null; } gameState.playerPlayArea[targetSlot.row][targetSlot.col] = selectedCard; // Place swappedCard in original position if (originalCardPosition.area === 'player') { var pos2 = getSlotPosition(originalCardPosition.row, originalCardPosition.col, true); swappedCard.activate(pos2.x, pos2.y, true); gameState.playerPlayArea[originalCardPosition.row][originalCardPosition.col] = swappedCard; } else { // original was hand var origHandIndex = originalCardPosition.index; var handWidth = 5 * DEAL_SLOT_WIDTH + 4 * 30; var handStartX = (SCREEN_WIDTH - handWidth) / 2; var slotX = handStartX + origHandIndex * DEAL_SLOT_WIDTH + origHandIndex * 30 + DEAL_SLOT_WIDTH / 2; var slotY = PLAYER_DEAL_AREA_Y + DEAL_SLOT_HEIGHT / 2; swappedCard.activate(slotX, slotY, false, true); gameLayer.removeChild(swappedCard); uiLayer.addChild(swappedCard); gameState.playerHand[origHandIndex] = swappedCard; } } } else if (targetSlot.area === 'hand') { var existingCard = gameState.playerHand[targetSlot.index]; if (existingCard && existingCard !== selectedCard && selectedCard.canMergeWith(existingCard)) { // Merge in hand var mergedCard = selectedCard.mergeWith(existingCard); if (mergedCard) { // Remove old cards var handIndex1 = gameState.playerHand.indexOf(selectedCard); if (handIndex1 !== -1) { uiLayer.removeChild(selectedCard); gameState.playerHand[handIndex1] = null; } else { gameLayer.removeChild(selectedCard); } var handIndex2 = gameState.playerHand.indexOf(existingCard); if (handIndex2 !== -1) { uiLayer.removeChild(existingCard); gameState.playerHand[handIndex2] = null; } // Place merged card in hand var handWidth = 5 * DEAL_SLOT_WIDTH + 4 * 30; // 5 slots + 4 gaps of 30px var handStartX = (SCREEN_WIDTH - handWidth) / 2; var slotX = handStartX + targetSlot.index * DEAL_SLOT_WIDTH + targetSlot.index * 30 + DEAL_SLOT_WIDTH / 2; var slotY = PLAYER_DEAL_AREA_Y + DEAL_SLOT_HEIGHT / 2; mergedCard.activate(slotX, slotY, false, true); uiLayer.addChild(mergedCard); gameState.playerHand[targetSlot.index] = mergedCard; // Merge animation mergedCard.alpha = 0; mergedCard.scaleX = mergedCard.scaleY = 2; tween(mergedCard, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.elasticOut }); createFloatingText('+Level Up!', mergedCard.x, mergedCard.y - 50, 0x00ff00); } else { returnCardToOriginalPosition(); } } else if (existingCard && existingCard !== selectedCard) { // Cannot merge, so swap var swappedCard = gameState.playerHand[targetSlot.index]; // Move selectedCard to target hand slot var targetHandIndex = targetSlot.index; var handWidth1 = 5 * DEAL_SLOT_WIDTH + 4 * 30; var handStartX1 = (SCREEN_WIDTH - handWidth1) / 2; var slotX1 = handStartX1 + targetHandIndex * DEAL_SLOT_WIDTH + targetHandIndex * 30 + DEAL_SLOT_WIDTH / 2; var slotY1 = PLAYER_DEAL_AREA_Y + DEAL_SLOT_HEIGHT / 2; selectedCard.activate(slotX1, slotY1, false, true); if (originalCardPosition.area === 'player') { gameLayer.removeChild(selectedCard); uiLayer.addChild(selectedCard); } else { gameState.playerHand[originalCardPosition.index] = null; } gameState.playerHand[targetHandIndex] = selectedCard; // Move swappedCard to original position if (originalCardPosition.area === 'player') { var origPos = getSlotPosition(originalCardPosition.row, originalCardPosition.col, true); swappedCard.activate(origPos.x, origPos.y, true); uiLayer.removeChild(swappedCard); gameLayer.addChild(swappedCard); gameState.playerPlayArea[originalCardPosition.row][originalCardPosition.col] = swappedCard; } else { // original was hand var origHandIndex = originalCardPosition.index; var handWidth2 = 5 * DEAL_SLOT_WIDTH + 4 * 30; var handStartX2 = (SCREEN_WIDTH - handWidth2) / 2; var slotX2 = handStartX2 + origHandIndex * DEAL_SLOT_WIDTH + origHandIndex * 30 + DEAL_SLOT_WIDTH / 2; var slotY2 = PLAYER_DEAL_AREA_Y + DEAL_SLOT_HEIGHT / 2; swappedCard.activate(slotX2, slotY2, false, true); gameState.playerHand[origHandIndex] = swappedCard; } } else { returnCardToOriginalPosition(); } } } else { returnCardToOriginalPosition(); } selectedCard = null; originalCardPosition = null; applyHandBonuses(); } }; function returnCardToOriginalPosition() { if (!selectedCard || !originalCardPosition) { return; } if (originalCardPosition.area === 'hand') { // Return to hand slot var handIndex = originalCardPosition.index; var handWidth = 5 * DEAL_SLOT_WIDTH + 4 * 30; // 5 slots + 4 gaps of 30px var handStartX = (SCREEN_WIDTH - handWidth) / 2; var slotX = handStartX + handIndex * DEAL_SLOT_WIDTH + handIndex * 30 + DEAL_SLOT_WIDTH / 2; var slotY = PLAYER_DEAL_AREA_Y + DEAL_SLOT_HEIGHT / 2; selectedCard.x = slotX; selectedCard.y = slotY; gameState.playerHand[handIndex] = selectedCard; } else if (originalCardPosition.area === 'player') { // Return to play area slot var pos = getSlotPosition(originalCardPosition.row, originalCardPosition.col, true); selectedCard.x = pos.x; selectedCard.y = pos.y; gameState.playerPlayArea[originalCardPosition.row][originalCardPosition.col] = selectedCard; } } function createFloatingText(text, x, y, color, size) { var textOptions = { size: size || 40, fill: color || 0xffffff, weight: 800, stroke: 0x000000, strokeThickness: 0 }; // Add black outline for green level up text if (color === 0x00ff00) { textOptions.strokeThickness = 6; textOptions.size = (size || 40) * 2; // Make it twice as big } var floatingText = new Text2(text, textOptions); floatingText.anchor.set(0.5, 0.5); floatingText.x = x; floatingText.y = y; floatingText.alpha = 1; uiLayer.addChild(floatingText); // Use larger animation for important messages var animationDistance = size && size > 40 ? 120 : 80; var animationDuration = size && size > 40 ? 2000 : 1500; tween(floatingText, { y: y - animationDistance, alpha: 0 }, { duration: animationDuration, easing: tween.quadOut, onFinish: function onFinish() { uiLayer.removeChild(floatingText); } }); } function createExplosion(x, y, color) { var numParticles = 5; for (var i = 0; i < numParticles; i++) { var particle = LK.getAsset('explosionParticle', { anchorX: 0.5, anchorY: 0.5 }); particle.x = x; particle.y = y; particle.tint = color; particle.scale.set(0.5 + Math.random() * 0.5); var angle = Math.random() * Math.PI * 2; var distance = Math.random() * 50 + 20; var duration = 500 + Math.random() * 500; var targetX = x + Math.cos(angle) * distance; var targetY = y + Math.sin(angle) * distance; gameLayer.addChild(particle); tween(particle, { x: targetX, y: targetY, alpha: 0 }, { duration: duration, easing: tween.quadOut, onFinish: function (p) { return function () { if (p.parent) { p.parent.removeChild(p); } }; }(particle) }); } } /**** * Main Game Loop ****/ game.update = function () { // Only run game logic when in playing state if (currentGameState !== 'playing') { if (currentGameState === 'start' && backgroundSuits.length > 0) { var speed = 0.5; var spacing = 400; var boundsWidth = (Math.ceil(SCREEN_WIDTH / spacing) + 2) * spacing; var boundsHeight = (Math.ceil(SCREEN_HEIGHT / spacing) + 2) * spacing; for (var i = 0; i < backgroundSuits.length; i++) { var suit = backgroundSuits[i]; suit.x += speed; suit.y += speed; if (suit.x - 200 > SCREEN_WIDTH) { suit.x -= boundsWidth; } if (suit.y - 200 > SCREEN_HEIGHT) { suit.y -= boundsHeight; } } } return; } // Clean up dead chips immediately cleanupDeadChips(); // Update wave spawning system WaveSystem.update(); // Update active chips for (var i = activePlayerChips.length - 1; i >= 0; i--) { activePlayerChips[i].update(); } for (var i = activeAIChips.length - 1; i >= 0; i--) { activeAIChips[i].update(); } // Update active bullets for (var i = activeBullets.length - 1; i >= 0; i--) { activeBullets[i].update(); } // Update cards in play for (var row = 0; row < PLAY_AREA_ROWS; row++) { for (var col = 0; col < PLAY_AREA_COLS; col++) { if (gameState.playerPlayArea[row][col]) { gameState.playerPlayArea[row][col].update(); } if (gameState.aiPlayArea[row][col]) { gameState.aiPlayArea[row][col].update(); } } } // Update AI AISystem.update(); // Check win/lose conditions // Update life displays while (gameState.playerLives < lastPlayerLives) { lastPlayerLives--; var heartToFade = playerLifeHearts[lastPlayerLives]; if (heartToFade) { tween(heartToFade, { alpha: 0.2 }, { duration: 500 }); } } while (gameState.aiLives < lastAiLives) { lastAiLives--; var heartToFade = aiLifeHearts[lastAiLives]; if (heartToFade) { tween(heartToFade, { alpha: 0.2 }, { duration: 500 }); } } if (gameState.playerLives <= 0) { showGameOver(false); } else if (gameState.aiLives <= 0) { showGameOver(true); } updateUI(); }; /**** * Game Over ****/ function showGameOver(playerWon) { if (playerWon) { LK.showYouWin(); } else { LK.showGameOver(); } } /**** * Utility Functions ****/ function formatNumberWithSuffix(number) { var num = Math.round(number); if (num < 1000) { return num.toString(); } if (num < 1000000) { return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'K'; } if (num < 1000000000) { return (num / 1000000).toFixed(1).replace(/\.0$/, '') + 'M'; } if (num < 1000000000000) { return (num / 1000000000).toFixed(1).replace(/\.0$/, '') + 'B'; } return (num / 1000000000000).toFixed(1).replace(/\.0$/, '') + 'T'; } function displayHandInfo() { // Show current poker hand evaluations for debugging for (var row = 0; row < PLAY_AREA_ROWS; row++) { var handEval = evaluateRowHand(row, true); console.log('Player Row ' + row + ':', handEval.type, 'Multiplier:', handEval.multiplier); } } var background = LK.getAsset('background', { anchorX: 0.5, anchorY: 0 }); background.x = SCREEN_WIDTH / 2; background.y = 50; background.visible = false; gameLayer.addChild(background); gameElements.push(background); // Place two chipracks side by side in the center at the top of the screen var chipRack1 = LK.getAsset('chipRack', { anchorX: 0.5, anchorY: 0 }); var rackWidth = chipRack1.width; var rackHeight = chipRack1.height; chipRack1.x = SCREEN_WIDTH / 2 - rackWidth / 2; chipRack1.y = 60 - rackHeight * 0.75 + 30; chipRack1.visible = false; gameLayer.addChild(chipRack1); gameElements.push(chipRack1); var chipRack2 = LK.getAsset('chipRack', { anchorX: 0.5, anchorY: 0 }); chipRack2.x = SCREEN_WIDTH / 2 + rackWidth / 2; chipRack2.y = 60 - rackHeight * 0.75 + 30; chipRack2.visible = false; gameLayer.addChild(chipRack2); gameElements.push(chipRack2); var border = LK.getAsset('border', { anchorX: 0.5, anchorY: 0.5 }); border.x = SCREEN_WIDTH / 2; border.y = SCREEN_HEIGHT / 2; border.visible = false; gameLayer.addChild(border); gameElements.push(border); var bottomBar = LK.getAsset('bottomBar', { anchorX: 0.5, anchorY: 1 }); bottomBar.x = SCREEN_WIDTH / 2; bottomBar.y = SCREEN_HEIGHT; bottomBar.visible = false; gameLayer.addChild(bottomBar); gameElements.push(bottomBar); // Show start screen instead of initializing game immediately createStartScreen(); // Add active chips container below the bottom bar gameLayer.addChildAt(activePlayerChipsContainer, gameLayer.getChildIndex(bottomBar)); gameLayer.addChildAt(activeAIChipsContainer, gameLayer.getChildIndex(bottomBar)); // Add chipStack asset above chips display var chipStack = LK.getAsset('chipStack', { anchorX: 0.5, anchorY: 1 }); chipStack.x = playerChipsText.x + playerChipsText.width / 2; chipStack.y = playerChipsText.y - 10; chipStack.visible = false; uiLayer.addChild(chipStack); gameElements.push(chipStack); ; ;
===================================================================
--- original.js
+++ change.js
@@ -584,15 +584,15 @@
/****
* Game Code
****/
+// Helper function to calculate distance from point to line segment
/****
-* Game Constants
+* Poker Tower Defense - Complete Refactor
****/
/****
-* Poker Tower Defense - Complete Refactor
+* Game Constants
****/
-// Helper function to calculate distance from point to line segment
function distancePointToLine(px, py, x1, y1, x2, y2) {
var A = px - x1;
var B = py - y1;
var C = x2 - x1;
@@ -1878,25 +1878,25 @@
var startX = slotX + (Math.random() - 0.5) * 50; // Slight horizontal variance
var startY = SCREEN_HEIGHT + DEAL_SLOT_HEIGHT;
card.activate(startX, startY, false, true);
card.rotation = 0; // No spin
- card.scaleX = 0.1; // Very thin/squished horizontally
- card.scaleY = 1; // Normal height
+ card.scaleX = 1; // Normal width
+ card.scaleY = 0.1; // Very thin/squished vertically - like laying flat on table
uiLayer.addChild(card);
gameState.playerHand[i] = card;
// Animate flying and expanding with a slight "settle" bounce
tween(card, {
x: slotX,
y: slotY,
- scaleX: 1.1 // Slightly overshoot
+ scaleY: 1.1 // Slightly overshoot on Y-axis
}, {
duration: 300,
easing: tween.quadOut,
delay: i * 80,
onFinish: function onFinish() {
// Settle back to normal size
tween(card, {
- scaleX: 1
+ scaleY: 1
}, {
duration: 150,
easing: tween.quadInOut
});
A long rack of different colored poker chips seen from above. Anime style.. In-Game asset. 2d. High contrast. No shadows
A graphic for the center of a joker card.
a 2:3 format thin black border with nothing in the center. In-Game asset. 2d. High contrast. No shadows
A small white explosion particle.. In-Game asset. 2d. High contrast. No shadows
Make the blue a lighter blue.
Make this in a white instead of blue. Keep everything else the same.
A couple different sized stacks of these chips beside each other.
Just the spade from this picture with a blue snowflake in the middle of it.
Just the heart from this picture with a flame in the cent t of it.
Just the club from this picture with 1. **Fan/Spray Symbol** - Three or more lines radiating outward from a central point, yellow in color, in the center of the club.
Just the diamond from this picture with a dollar sign in the center
A white circle with a lightening gradient towards the edge.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A simple golden line break.. In-Game asset. 2d. High contrast. No shadows
A fanned card hand that shows a royal flush in spades. Anime style. In-Game asset. 2d. High contrast. No shadows
An SVG of the word 'Battle'. text in yellow with a black outline. In-Game asset. 2d. High contrast. No shadows
change the text to say "Mods"
The four card suits arranged in 2x2 grid layout, no lines. Anime style. In-Game asset. 2d. High contrast. No shadows
A single ice crystal. anime style. In-Game asset. 2d. High contrast. No shadows
Change the text to say ‘Refund’. Change the cards to a trash can.
A completely blank playing card with textured surface. Slightly used edges with a couple nicks out of it. Black background. In-Game asset. 2d. High contrast. No shadows
A 3:2 ratio rectangular green button that says “PvP” using this yellow font.
Change the text to say ‘Co-op’
Change the font to say ‘Victory!’
Change the text to say ‘Defeat!’
A 2:3 ratio rectangular picture that shows two card playing cats in a casino very close face to face with teeth bared and fists clenched as if they’re about to fight. Each cat has a different card suit pattern on the fur of their forehead. One is wearing a suit and the other is wearing tan leather jacket with a striped tank top underneath. Anime style.. In-Game asset. 2d. High contrast. No shadows
Show these same cats smiling and instead of clenched fists they’re grasping hands because they’re friends.
Incorporate these two cats heads into a game logo for a poker based tower defense that includes the name “Double Down Defense”. Put their heads offset on either side with eyes open and looking at the logo.
A small treasure chest with poker themed graphics on it. Anime style. In-Game asset. 2d. High contrast. No shadows
The hearts card suit symbol with two linked hearts in the center of it. Anime style.. In-Game asset. 2d. High contrast. No shadows
The diamond card suit with a coin in the center. The coin has a ‘2X’ in the center. Anime style.. In-Game asset. 2d. High contrast. No shadows
Just the club from this picture with a clock in the center.
Just the spade from this image with a land mine in the center of it.
Just the mine from this image.
Just the heart from this image with a piggy bank in the center.
Just the diamond from this picture with a sword with a small arrow pointing up in the center of the diamond.
Just the club from this picture with an icon in the center of it that represents a projectile bouncing at an angle off of a surface.
Just the spade with a skull in the center of it. Anime style.
This chest with the top open and nothing inside.
Change the text to say Shop
An old style cash register. The numeric read out says 7.77. Anime style.. In-Game asset. 2d. High contrast. No shadows
A giant question mark. Anime style.. In-Game asset. 2d. High contrast. No shadows
A shield with a spade and heart card suit coat of arms on it with a sword crossed downwards, behind it. icon. Anime style.. In-Game asset. 2d. High contrast. No shadows
Change the text to say ‘Draw’
The back of a playing card. Blue pattern. Anime style.. In-Game asset. 2d. High contrast. No shadows
The back of a playing card. Red pattern with a heart in the center. Anime style.. In-Game asset. 2d. High contrast. No shadows