User prompt
The danger highlight stops flashing after 1 cycle because of a JavaScript closure bug (the `var` inside the for-loop gets overwritten before the tween's `onFinish` fires). The danger highlight is also too large, and the placement highlight for cards in hand is getting hidden behind the terrain. Please make these exact 2 changes: 1. FIX THE DANGER HALO SCALE AND ANIMATION LOOP: - Locate the `updateDangerHighlights()` function. - Look inside for the block where the halo is created (`if (!topCreature.dangerHalo) { ... }`). - Replace that ENTIRE halo creation and flashing block with this updated logic that attaches the pulse directly to the halo object and reduces the scale: ```javascript // Create halo AS A CHILD so it inherits placement movement and scaling if (!topCreature.dangerHalo) { var halo = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5 }); halo.tint = 0xFF0000; halo.alpha = 0.2; // Reduced scale to 1.05 so it stays tight to the card and doesn't conflict halo.scaleX = 1.05; halo.scaleY = 1.05; halo.x = 0; halo.y = 0; topCreature.addChildAt(halo, 0); topCreature.dangerHalo = halo; // Attach the pulse function directly to the halo to avoid closure/scope bugs halo.pulse = function() { if (!this.parent) return; var currentHalo = this; tween(currentHalo, { alpha: 0.8 }, { duration: 400, easing: tween.easeInOut, onFinish: function() { if (!currentHalo.parent) return; tween(currentHalo, { alpha: 0.2 }, { duration: 400, easing: tween.easeInOut, onFinish: currentHalo.pulse.bind(currentHalo) }); } }); }; halo.pulse(); } FIX THE PLACEMENT HIGHLIGHT FOR CARDS IN HAND: Locate the PlanetSlot class near the top of the file. Inside it, find the self.highlight = function () { block. Replace that entire self.highlight function with this updated version to change it to Green, tighten its scale, and bring it to the absolute front of the screen: code JavaScript self.highlight = function () { if (!self.isHighlighted && self.terrainCard) { self.isHighlighted = true; if (!self.pinkBorder) { self.pinkBorder = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5 }); // Changed to Bright Green and reduced alpha for a nice overlay effect self.pinkBorder.tint = 0x00FF00; self.pinkBorder.alpha = 0.4; // Tighter fit self.pinkBorder.scaleX = self.terrainCard.scaleX * 1.1; self.pinkBorder.scaleY = self.terrainCard.scaleY * 1.1; self.pinkBorder.x = self.terrainCard.x; self.pinkBorder.y = self.terrainCard.y; // Bring to the absolute front of the game instead of hiding behind the terrain game.addChild(self.pinkBorder); } } }; ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
The logic for giving herbivores extinction markers during the Night phase is artificially capped. They currently only receive exactly 1 marker, and only if they have 0 markers. They need to receive 1 marker for EVERY excess link they have, every single Night. Please make this exact change: 1. FIX HERBIVORE NIGHT PHASE MATH: - Locate the `processNightPhase()` function. - Look inside the nested loops for the block that starts with `} else if (creature.creatureData.dietType === 'herbivore') {`. - Replace that ENTIRE `else if` block (down to the end of the herbivore check) with this exact, uncapped logic: ```javascript } else if (creature.creatureData.dietType === 'herbivore') { // Count links pointing to them AT THIS MOMENT ONLY var linksToThisHerbivore = 0; for (var j = 0; j < linkLines.length; j++) { if (linkLines[j].herbivore === creature) { linksToThisHerbivore++; } } // Add extinction markers equal to the number of excess links var excessLinks = linksToThisHerbivore - creature.safeLinks; if (excessLinks > 0) { // No cap! Add exactly as many markers as there are excess links creature.extinctionMarkers += excessLinks; creature.updateExtinctionMarkers(); } }
User prompt
The UI text elements for the Round and Phase are rendering off-screen because they are attached to `LK.gui.top`. I need to delete all the old messy text variables, create a single unified gameStatusText attached directly to `game`, and add logic for a 3rd blue link line for the "Fatty" rule. Please make these exact 3 changes: 1. DELETE THE OLD TEXT VARIABLES AND CREATE A NEW ONE: - Search the global variables section for `var roundPhaseText`, `var phaseText`, and `var gameStatusText`. - COMPLETELY DELETE the declarations and setup blocks for ALL THREE of those variables. - Replace them with this single, correctly anchored text block: ```javascript var gameStatusText = new Text2("Round: 1 | Phase: DAWN | Turn: 1", { size: 40, fill: 0xFFFF00 }); gameStatusText.anchor.set(0.5, 0); gameStatusText.x = 1024; gameStatusText.y = 60; // Safely positioned right above the red danger text game.addChild(gameStatusText); CLEAN UP THE UPDATE LOOP: Locate the game.update = function () { loop. Look inside it for any lines using .setText for roundPhaseText or phaseText. DELETE those lines if they exist. Ensure there is ONLY ONE line updating the phase text in the update loop: gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber); ADD THE 3RD BLUE LINK LINE: Locate the createLinkLine function. Find the block of code that calculates the offset for the second link (it starts with if (existingLinkCount === 1) {). Replace that ENTIRE if (existingLinkCount === 1) block (including the X2 text indicator logic right below it) with this new block that handles both 2nd and 3rd links: code JavaScript // Calculate perpendicular offset for multiple links to avoid stacking if (existingLinkCount >= 1) { // 2nd link goes one way (+90 deg), 3rd link goes the opposite way (-90 deg) var perpendicularAngle = (existingLinkCount === 1) ? (angle + Math.PI / 2) : (angle - Math.PI / 2); var offsetDistance = 30; // Offset distance in pixels var offsetX = Math.cos(perpendicularAngle) * offsetDistance; var offsetY = Math.sin(perpendicularAngle) * offsetDistance; // Apply offset to line position line.x += offsetX; line.y += offsetY; // Change line color and create indicator based on link number var indicatorString = ""; if (existingLinkCount === 1) { line.tint = 0xFF00FF; // Magenta for 2nd link indicatorString = "X2"; } else { line.tint = 0x00BFFF; // Deep Sky Blue for 3rd link indicatorString = "X3"; } // Add the text indicator var linkIndicator = new Text2(indicatorString, { size: 20, fill: 0xFFFFFF }); linkIndicator.anchor.set(0.5, 0.5); linkIndicator.x = 0; linkIndicator.y = 0; line.addChild(linkIndicator); linkIndicator.lineOwner = line; }
User prompt
please add a new special: Name = "Dominant DNA". Card inheriting = 'AH11'. Effect = this card may be played onto a Basic Carnivore if terrain and climate requirements are met as well as being able to be played on a Basic Herbivore as normal. Card info text in UI = "Dominant DNA. Can evolve from Basic Carnivores or Basic Herbivores!". Text on card = "Dominant DNA"
User prompt
change the color 1. MAKE SEA-BOUND AN ABSOLUTE VETO: - Locate the `isValidLinkTarget(carnivore, herbivore)` function. - Find the "Sea-bound" check block that is currently near the bottom of the function. It looks like this: ```javascript if (carnivore.creatureData.seaBound) { if (herbivore.creatureData.subtype !== 'water') { return false; } } ``` - CUT that block of code from the bottom, and PASTE it at the VERY TOP of the function, immediately below the `if (!carnivore || !herbivore) { return false; }` line. - This ensures the Sea-bound restriction processes as an absolute veto before the Bully logic can return true. 2. CHANGE THE HUNTER-HUNTER TEXT COLOR: - Locate the `CreatureCard` class. - Scroll down to the cascading text section and find the block where the `hunterHunterText` (or similar Hunter-Hunter text) is created. - Change its `fill` color property to `0x00FFFF` (which is a bright Cyan that will stand out perfectly against the Basic Carnivore background).
User prompt
The Scry mechanic is firing every turn and ignoring the graveyard penalty because `processDawnPhase` (which runs every turn) is incorrectly handling round-based Scry logic and prematurely resetting the `scryDeniedNextRound` boolean. Please make these exact 2 changes: 1. REMOVE SCRY LOGIC FROM THE TURN LOOP: - Locate the `processDawnPhase()` function. - Completely delete all the `if` statements and boolean resets inside it so it ONLY contains the phase transition. It should look exactly like this: ```javascript function processDawnPhase() { // Show visual cue that dawn phase is processing showPhaseTransition("Dawn Phase - Processing...", function () { gamePhase = 'draw'; processDrawPhase(); // Auto-progress to draw phase }); } ``` 2. TIE THE PENALTY DIRECTLY TO THE GRAVEYARD: - Locate the `CreatureCard` class. - Find the `self.die = function () {` block inside that class. - Look for the line that says `graveyard.push(self.creatureData);` - Immediately below that line, add this exact code to ensure ANY death triggers the penalty: ```javascript scryDeniedNextRound = true; // Deny scry for next round because a creature entered the graveyard ```
User prompt
I need to adjust the UI positioning, size, and formatting for the Scry text and the Danger Warning text. Please make these exact 3 changes: 1. FIX THE SCRY HEADER CREATION: - Locate the `activateScry()` function. - Find the block of code where `scryHeaderText` is created (near the bottom of the function). - Replace that entire `scryHeaderText` creation block with this: ```javascript scryHeaderText = new Text2("SCRY: Select 1 card to put on the\nBOTTOM of the main deck", { size: 56, // Doubled size fill: 0xFFFF00, // Changed to Yellow align: 'center' }); scryHeaderText.anchor.set(0.5, 0); scryHeaderText.x = 1024; scryHeaderText.y = 120; // Moved to the top of the UI game.addChild(scryHeaderText); ``` 2. FIX THE SCRY HEADER UPDATE TEXT: - Locate the `processScrySelection(selectedCard)` function. - Look inside the `if (scryStep === 1)` block for the line where `scryHeaderText.setText(...)` is called. - Replace that line with this updated 2-line string: ```javascript scryHeaderText.setText("SCRY: Select 1 card to DISCARD.\nThe last card goes on TOP of the deck."); ``` 3. FIX THE DANGER WARNING TEXT: - Locate the `updateDangerHighlights()` function. - Scroll down to the `if (gamePhase === 'noon' && inDangerList.length > 0)` block. - Find where `var warningText = new Text2(...)` is defined and positioned. - Replace the creation and positioning of that warning text with this: ```javascript var warningText = new Text2("Warning! Creature/s are in danger!", { size: 64, // Doubled size fill: 0xFF0000 }); warningText.anchor.set(0.5, 0); warningText.x = 1024; warningText.y = 130; // Moved up by 2 lines game.addChild(warningText); game.dangerWarningText = warningText; ```
User prompt
The "Sea-bound" special rule is broken because while the property is on the card, it was never actually programmed into the link-targeting logic, and it is missing from the UI info text. Please make these exact 2 changes: 1. ENFORCE THE RULE IN LINK TARGETING: - Locate the `isValidLinkTarget(carnivore, herbivore)` function. - Look near the very bottom of that function, right before the final `return true;` line. - Insert this block of code there: ```javascript if (carnivore.creatureData.seaBound) { if (herbivore.creatureData.subtype !== 'water') { return false; // Sea-bound creatures can only link to water creatures } } ``` 2. ADD THE UI INFORMATION TEXT: - Locate the `showCardInfo(card)` function. - Find the list of `if` checks that append strings to `infoString` (where we just added the "Fatty" rule). - Add this block to that list: ```javascript if (creature && creature.seaBound) { infoString += " | Sea-bound: This creature can only create links to other Water creatures!"; } ```
User prompt
Card AC04 cannot be placed on land because it has `landType: 'any'`, but the `canPlaceCreatureOnTerrain` function does not recognize 'any' as a valid wildcard and fails the exact-match check. Please make this exact change: 1. FIX THE PLACEMENT WILDCARD LOGIC: - Locate the `canPlaceCreatureOnTerrain(creatureCard, terrainCard)` function. - Look for the block of code that checks `if (creature.subtype === 'water')` and `else if (creature.subtype === 'land')`. - Replace that entire subtype checking block (down to the `if (!creature.subtype)` line) with this corrected logic: ```javascript // Check terrain type matching based on creature's subtype if (creature.subtype === 'water') { // Creature requires water terrain if (terrain.subtype !== 'water') { return false; } // Check specific water type matching - allow 'any' as wildcard if (creature.waterType && creature.waterType !== 'any' && terrain.waterType && creature.waterType !== terrain.waterType) { return false; } } else if (creature.subtype === 'land') { // Creature requires land terrain if (terrain.subtype !== 'land') { return false; } // Check specific land type matching - allow 'any' as wildcard if (creature.landType && creature.landType !== 'any' && terrain.landType && creature.landType !== terrain.landType) { return false; } } ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
I need to add a new special rule called "Fatty" specifically for card AC03 (Whale). Please make these exact 4 changes: 1. ADD THE PROPERTY TO THE DECK GENERATOR: - Locate the `createInitialDeck()` function. - Find the loop where Advanced Carnivores are pushed to the `mainDeck` (around the line `isWhale: config.id === 'AC03' ? true : false,`). - Add this new line directly below the `isWhale` property inside that same `mainDeck.push` block: `fatty: config.id === 'AC03' ? true : false,` 2. OVERRIDE THE LINK REQUIREMENT: - Locate the `CreatureCard` class. - Find the section where `self.linkRequirement` is set: ```javascript if (self.creatureData.dietType === 'carnivore') { if (self.creatureData.level === 'Basic') { self.linkRequirement = 1; } else if (self.creatureData.level === 'Advanced') { self.linkRequirement = 2; } } ``` - Immediately AFTER that block, insert this override: ```javascript if (self.creatureData.fatty) { self.linkRequirement = 3; } ``` 3. ADD THE VISUAL "FATTY" TEXT TO THE CARD: - Further down in the `CreatureCard` class, locate the long section that adds cascading text (e.g., "Tough", "Squishy", "Stinky"). - Pick a spot in that chain (for example, right after the "Squishy" block) and add this exact block to display the Fatty text: ```javascript if (self.creatureData && self.creatureData.fatty) { self.fattyText = new Text2("Fatty", { size: 24, fill: 0xFF8C00, // Dark Orange color font: "'Arial Black', 'Impact', 'Tahoma', sans-serif", fontWeight: "bold" }); self.fattyText.anchor.set(0.5, 0.5); self.fattyText.x = 0; self.fattyText.y = nextYOffset; self.addChild(self.fattyText); nextYOffset = nextYOffset + 40; } ``` 4. ADD THE UI INFORMATION TEXT: - Locate the `showCardInfo(card)` function. - Find the list of `if` checks that append strings to `infoString` (like `if (creature && creature.tough)`). - Add this block to that list: ```javascript if (creature && creature.fatty) { infoString += " | Fatty! Requires 3 links to support it and gives x2 the normal advanced carnivore score at end of game!"; } ```
User prompt
The `nextPhaseButton` has a massive logic flaw. When players click it to skip their turn while holding playable cards, it calls `showNoPlayableCardsNotification()`, which incorrectly discards their hand and forces a redraw! Additionally, the button should be completely dead/unclickable if Event cards are in the hand. Please make this exact change: 1. FIX THE NEXT PHASE BUTTON CLICK LOGIC: - Locate the `nextPhaseButton.down = function () {` block. - Look just inside that function (below the warning box/adjust links hiding code). Find this specific block of code: ```javascript // Prevent skipping card play after draw: if player has not played a card after drawing, do not allow phase advance if (gamePhase === 'draw' || gamePhase === 'noon' && (mustPlayCard || playerHand.length > 0 && hasValidPlacements(playerHand))) { // If in draw phase, or in noon phase and must play a card, do not allow skipping // Show a notification to the player if (!game.noPlayableNotification) { showNoPlayableCardsNotification(); } return; } COMPLETELY DELETE that block of code, and replace it with this safe logic: code JavaScript // Disable button completely if events must be played if (gamePhase === 'noon' && hasEventCardsInHand) { return; } // Prevent skipping card play if the player holds valid normal cards if (gamePhase === 'draw' || (gamePhase === 'noon' && playerHand.length > 0 && hasValidPlacements(playerHand))) { // Show a safe, harmless fading text warning instead of the destructive discard loop if (!game.safeSkipWarning) { var safeWarning = new Text2("You have playable cards! You must play one.", { size: 40, fill: 0xFF0000 }); safeWarning.anchor.set(0.5, 0.5); safeWarning.x = 1024; safeWarning.y = 1366; game.addChild(safeWarning); game.safeSkipWarning = safeWarning; tween(safeWarning, { alpha: 0 }, { duration: 2000, onFinish: function() { if (safeWarning.parent) safeWarning.parent.removeChild(safeWarning); game.safeSkipWarning = null; } }); } return; } ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
The "Efficient" special rule is bugged because `getAdjacentTerrains` only checks orthogonal directions. Because it doesn't see diagonal targets, it refuses to turn the link markers yellow (draggable) unless an orthogonal target accidentally unlocks it first. Please make these exact 3 changes: 1/ UPDATE THE ADJACENT TERRAINS FUNCTION: - Locate the `function getAdjacentTerrains(gridX, gridY)` function. - Replace it entirely from top to bottom with this logic: ```javascript function getAdjacentTerrains(gridX, gridY, includeDiagonals) { var neighbors = []; var directions = [ {x: -1, y: 0}, {x: 1, y: 0}, {x: 0, y: -1}, {x: 0, y: 1} ]; // Add diagonals if the creature is Efficient if (includeDiagonals) { directions.push({x: -1, y: -1}, {x: 1, y: -1}, {x: -1, y: 1}, {x: 1, y: 1}); } for (var i = 0; i < directions.length; i++) { var newX = gridX + directions[i].x; var newY = gridY + directions[i].y; if (newX >= 0 && newX < planetWidth && newY >= 0 && newY < planetHeight) { neighbors.push(planetBoard[newY][newX]); } } return neighbors; } 2/ FIX THE MARKER UNLOCK LOGIC: Locate the updateLinkMarkerStates() function. Inside it, find this exact line: var adjacentTerrains = getAdjacentTerrains(gridX, gridY); Replace that single line with these two lines: code JavaScript var isEfficient = creature.creatureData && creature.creatureData.efficient; var adjacentTerrains = getAdjacentTerrains(gridX, gridY, isEfficient); 3/ FIX THE AUTO-CREATE LINKS LOGIC: Locate the autoCreateLinks() function. Inside it, find this exact line: var adjacentTerrains = getAdjacentTerrains(gridX, gridY); Replace that single line with these two lines: code JavaScript var isEfficient = carnivore.creatureData && carnivore.creatureData.efficient; var adjacentTerrains = getAdjacentTerrains(gridX, gridY, isEfficient);
User prompt
The UI currently has duplicated and messy text variables for the Round and Phase information, and they are overlapping. We need to unify this into one single element at the top of the screen and clean up the old references to prevent crashes. Please make these exact 4 changes: 1. DELETE OLD TEXT VARIABLES: - Locate the declarations for `var roundPhaseText` and `var phaseText` (near the bottom of the script, right above "Link adjustment system variables"). - COMPLETELY DELETE both of those variable blocks, including their `new Text2`, `anchor`, `x`, `y`, and `addChild` lines. 2. CREATE THE UNIFIED TEXT: - In the exact place where you just deleted those variables, insert this single clean text block: ```javascript var gameStatusText = new Text2("Round: 1 | Phase: DAWN | Turn: 1", { size: 32, fill: 0xFFFF00 }); gameStatusText.anchor.set(0.5, 0); gameStatusText.x = 1024; gameStatusText.y = 68; LK.gui.top.addChild(gameStatusText); UPDATE THE EVENT CARD CLASS: Locate the EventCard class at the top of the file. Inside its self.down = function block, find the line that says: phaseText.setText("Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber); Replace that line with: gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber); FIX THE GAME UPDATE LOOP: Locate the game.update = function () { loop at the bottom of the file. Find the lines that update the old texts: roundPhaseText.setText(...) and phaseText.setText(...). Delete BOTH of those old lines and replace them with this single line: gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber); code Code *** ### Why this works: 1. It deletes the ghosts. If we didn't explicitly delete the `phaseText.setText` from inside the `EventCard` class, the game would crash the instant you clicked an event card because the `phaseText` object would no longer exist. 2. It combines "Round", "Phase", and "Turn" into one highly-visible yellow string at `y: 68` (safely tucked at the top of the screen).
User prompt
The danger highlight is failing on newly placed creatures because the highlight is being created globally during the placement scaling animation (locking it at 0.1 scale), and the constant tween loop is conflicting with the card's movement tweens. Please locate the `updateDangerHighlights()` function. Replace the ENTIRE function from top to bottom with this exact logic: function updateDangerHighlights() { var inDangerList = []; if (gamePhase === 'noon' || gamePhase === 'dusk') { inDangerList = getCreaturesInDanger(); } for (var gridY = 0; gridY < planetHeight; gridY++) { for (var gridX = 0; gridX < planetWidth; gridX++) { var terrain = planetBoard[gridY][gridX]; if (terrain && terrain.creatureStack && terrain.creatureStack.length > 0) { var topCreature = terrain.creatureStack[terrain.creatureStack.length - 1]; var isInDanger = false; for (var i = 0; i < inDangerList.length; i++) { if (inDangerList[i] === topCreature) { isInDanger = true; break; } } if (isInDanger) { // Only trigger the highlight creation once if (!topCreature.isDangerFlashing) { topCreature.isDangerFlashing = true; // Create halo AS A CHILD so it inherits placement movement and scaling if (!topCreature.dangerHalo) { var halo = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5 }); halo.tint = 0xFF0000; halo.alpha = 0.2; // Scale relative to the card size halo.scaleX = 1.15; halo.scaleY = 1.15; halo.x = 0; halo.y = 0; // Add behind the card graphics topCreature.addChildAt(halo, 0); topCreature.dangerHalo = halo; } // Safely pulse the halo's alpha recursively without touching the card's tint function flashHalo() { if (!topCreature.isDangerFlashing || !topCreature.dangerHalo) return; tween(topCreature.dangerHalo, { alpha: 0.8 }, { duration: 400, easing: tween.easeInOut, onFinish: function() { if (!topCreature.isDangerFlashing || !topCreature.dangerHalo) return; tween(topCreature.dangerHalo, { alpha: 0.2 }, { duration: 400, easing: tween.easeInOut, onFinish: flashHalo }); } }); } flashHalo(); } } else { if (topCreature.isDangerFlashing) { topCreature.isDangerFlashing = false; if (topCreature.dangerHalo && topCreature.dangerHalo.parent) { tween.stop(topCreature.dangerHalo); topCreature.dangerHalo.parent.removeChild(topCreature.dangerHalo); topCreature.dangerHalo = null; } } } } } } // Process global warning text safely if (gamePhase === 'noon' && inDangerList.length > 0) { if (!game.dangerWarningText || !game.dangerWarningText.parent) { var warningText = new Text2("Warning! Creature/s are in danger!", { size: 32, fill: 0xFF0000 }); warningText.anchor.set(0.5, 0.5); warningText.x = 1024; warningText.y = 200; game.addChild(warningText); game.dangerWarningText = warningText; function flashText() { if (!game.dangerWarningText || !game.dangerWarningText.parent) return; tween(game.dangerWarningText, { alpha: 0.3 }, { duration: 400, easing: tween.easeInOut, onFinish: function() { if (!game.dangerWarningText || !game.dangerWarningText.parent) return; tween(game.dangerWarningText, { alpha: 1 }, { duration: 400, easing: tween.easeInOut, onFinish: flashText }); } }); } flashText(); } } else { if (game.dangerWarningText && game.dangerWarningText.parent) { tween.stop(game.dangerWarningText); game.dangerWarningText.parent.removeChild(game.dangerWarningText); game.dangerWarningText = null; } } } ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
The Event card debug logic is causing the game to discard the hand and trigger a "No playable cards" loop. This happens because the debug box forces a phase change, and because `hasValidPlacements` doesn't recognize Event cards as playable. Please make these exact 3 changes: 1. FIX 'hasValidPlacements' SO IT RECOGNIZES EVENTS: - Locate the `function hasValidPlacements(cards)` function. - Inside the loop, it currently checks `if (card.creatureData) { ... } else if (card.terrainData) { ... }`. - Add a third condition right below that: ```javascript else if (card.eventData) { return true; // Event cards are always valid to play } ``` 2. REMOVE CLICKABILITY FROM THE DEBUG BOX: - Locate the `showEventCardDebugBox()` function. - Change the `debugText` string to: `"Event card/s in hand! Click the cards to resolve them."` - Completely DELETE the `eventDebugBox.down = function () { ... };` block so the box is no longer clickable. - At the very bottom of the `showEventCardDebugBox()` function, add this line so we can track the box globally: `game.eventDebugBox = eventDebugBox;` 3. AUTO-CLEANUP THE DEBUG BOX WHEN EVENTS ARE RESOLVED: - Locate the `EventCard` class, and find its `self.down` function. - Inside `self.down`, find the `if (!moreEventCards) {` block. - Add this cleanup code inside that `if (!moreEventCards)` block so the debug box disappears when the last event is played: ```javascript if (game.eventDebugBox && game.eventDebugBox.parent) { game.eventDebugBox.parent.removeChild(game.eventDebugBox); game.eventDebugBox = null; } ```
User prompt
The central `onMouseMove` logic is working perfectly for the hand and the board, but it is currently ignoring special event cards (like Scry cards and AT18 selectable cards) because they aren't in the `playerHand` or `planetBoard` arrays. Please locate the `onMouseMove(x, y)` function. Inside that function, look for the `playerHand` checking loop. Right BELOW the `playerHand` loop, and right BEFORE the `// Check board` section, please INSERT this exact block of code to check for special event cards: // Check Scry Cards if (!cardUnderMouse && typeof scryCards !== 'undefined' && scryCards.length > 0) { for (var i = scryCards.length - 1; i >= 0; i--) { var card = scryCards[i]; var hw = (card.terrainData ? 126 : 80) * card.scaleX; var hh = (card.terrainData ? 168 : 110) * card.scaleY; if (Math.abs(card.x - x) < hw && Math.abs(card.y - y) < hh) { cardUnderMouse = card; break; } } } // Check AT18 Special Selection Cards if (!cardUnderMouse && typeof at18Active !== 'undefined' && at18Active) { for (var i = game.children.length - 1; i >= 0; i--) { var child = game.children[i]; if (child.at18Selectable) { var hw = (child.terrainData ? 126 : 80) * child.scaleX; var hh = (child.terrainData ? 168 : 110) * child.scaleY; if (Math.abs(child.x - x) < hw && Math.abs(child.y - y) < hh) { cardUnderMouse = child; break; } } } }
User prompt
The recent change broke the zoom and highlight logic because the interactive cards are swallowing the pointer events, preventing `onMouseMove` from firing. Furthermore, the hitboxes need to calculate the dynamic `scaleX` and `scaleY` of the cards. Please make these exact 3 changes: 1. ADD A PASSTHROUGH MOVE FUNCTION TO ALL CARDS: - Inside `CreatureCard`, `TerrainCard`, and `EventCard`, add this exact function right above their `self.down` functions: ```javascript self.move = function(x, y, obj) { var globalX = self.x + (x * self.scaleX); var globalY = self.y + (y * self.scaleY); onMouseMove(globalX, globalY); }; 2. FIX THE DESTRUCTIVE HIGHLIGHT LOGIC IN game.move: Locate game.move = function (x, y, obj) { Scroll to the bottom of that function and completely DELETE this entire else block: code JavaScript } else { // Check if mouse is over hand area, if not, hide card info var handY = 2400; var inHandArea = y > handY - 150 && y < handY + 150; if (!inHandArea) { hideCardInfo(); hidePossibleMoves(); } } (Delete it entirely so the function ends after the draggedLinkMarker block). 3. REPLACE onMouseMove WITH SCALE-ACCURATE LOGIC: Replace the entire function onMouseMove(x, y) from top to bottom with this exact code: code JavaScript function onMouseMove(x, y) { var cardUnderMouse = null; // Check hand (reverse order so top cards are checked first) for (var i = playerHand.length - 1; i >= 0; i--) { var card = playerHand[i]; var hw = (card.terrainData ? 126 : 80) * card.scaleX; var hh = (card.terrainData ? 168 : 110) * card.scaleY; if (Math.abs(card.x - x) < hw && Math.abs(card.y - y) < hh) { cardUnderMouse = card; break; } } // Check board if (!cardUnderMouse) { for (var gridY = 0; gridY < planetHeight; gridY++) { for (var gridX = 0; gridX < planetWidth; gridX++) { var terrain = planetBoard[gridY][gridX]; if (terrain) { // 1. Check TOP creature if (terrain.creatureStack && terrain.creatureStack.length > 0) { var topIndex = terrain.creatureStack.length - 1; var topCreature = terrain.creatureStack[topIndex]; var cHW = 80 * topCreature.scaleX; var cHH = 110 * topCreature.scaleY; if (Math.abs(topCreature.x - x) < cHW && Math.abs(topCreature.y - y) < cHH) { cardUnderMouse = topCreature; } // 2. Check LOWER creatures if (!cardUnderMouse && topIndex > 0) { for (var c = topIndex - 1; c >= 0; c--) { var lowerC = terrain.creatureStack[c]; var lHW = 80 * lowerC.scaleX; var lHH = 110 * lowerC.scaleY; var cardBottom = lowerC.y + lHH; if (Math.abs(lowerC.x - x) < lHW && y <= cardBottom && y >= cardBottom - (45 * lowerC.scaleY)) { cardUnderMouse = lowerC; break; } } } } // 3. Check Terrain if (!cardUnderMouse) { var tHW = 126 * terrain.scaleX; var tHH = 168 * terrain.scaleY; if (Math.abs(terrain.x - x) < tHW && Math.abs(terrain.y - y) < tHH) { cardUnderMouse = terrain; } } } if (cardUnderMouse) break; } if (cardUnderMouse) break; } } // Process state changes if (cardUnderMouse && cardUnderMouse !== currentlyHoveredCard) { if (currentlyHoveredCard) clearZoom(); zoomCard(cardUnderMouse); showCardInfo(cardUnderMouse); if (!cardUnderMouse.isInPlay) { showPossibleMoves(cardUnderMouse); } currentlyHoveredCard = cardUnderMouse; } else if (!cardUnderMouse && currentlyHoveredCard) { clearZoom(); hideCardInfo(); hidePossibleMoves(); currentlyHoveredCard = null; } }
User prompt
The zoom and hover logic is still broken because the individual card classes have their own `self.move` events that are overriding the centralized `onMouseMove` global logic. Furthermore, the hitboxes inside `onMouseMove` are too small for the board cards. Please make these exact explicit changes: 1. DELETE INDIVIDUAL HOVER EVENTS: - Go to the `CreatureCard` class and COMPLETELY DELETE the `self.move = function (x, y, obj) { ... }` block. - Go to the `EventCard` class and COMPLETELY DELETE the `self.move = function (x, y, obj) { ... }` block. - Go to the `TerrainCard` class and COMPLETELY DELETE the `self.move = function (x, y, obj) { ... }` block. (Do not delete `self.down`, only delete `self.move`). 2. FIX THE HITBOX MATH IN `onMouseMove`: - Locate the `onMouseMove` function. - Leave the `playerHand` checking loop exactly as it is (cards in hand are smaller). - Find the section that checks the board starting with `if (!cardUnderMouse) { for (var gridY = 0;...` - Replace that entire board-checking nested loop section with this exact logic: if (!cardUnderMouse) { for (var gridY = 0; gridY < planetHeight; gridY++) { for (var gridX = 0; gridX < planetWidth; gridX++) { var terrain = planetBoard[gridY][gridX]; if (terrain) { // Cards on the board are larger. Half-width is 126, half-height is 168. var boardHalfWidth = 126; var boardHalfHeight = 168; // 1. Check TOP creature first if (terrain.creatureStack && terrain.creatureStack.length > 0) { var topIndex = terrain.creatureStack.length - 1; var topCreature = terrain.creatureStack[topIndex]; if (Math.abs(topCreature.x - x) < boardHalfWidth && Math.abs(topCreature.y - y) < boardHalfHeight) { cardUnderMouse = topCreature; } // 2. Check LOWER creatures (only the bottom 45px color band) if (!cardUnderMouse && topIndex > 0) { for (var c = topIndex - 1; c >= 0; c--) { var lowerCreature = terrain.creatureStack[c]; var cardBottom = lowerCreature.y + boardHalfHeight; if (Math.abs(lowerCreature.x - x) < boardHalfWidth && y <= cardBottom && y >= cardBottom - 45) { cardUnderMouse = lowerCreature; break; } } } } // 3. Check Terrain ONLY if no creature was hovered if (!cardUnderMouse) { if (Math.abs(terrain.x - x) < boardHalfWidth && Math.abs(terrain.y - y) < boardHalfHeight) { cardUnderMouse = terrain; } } } if (cardUnderMouse) { break; } } if (cardUnderMouse) { break; } } }
User prompt
The hit-detection inside `onMouseMove` is selecting the terrain instead of the top creature, and it is struggling with the creature stack overlap. I have written the exact spatial math needed to fix this. Please locate the `onMouseMove` function. Inside that function, find the section that begins with `if (!cardUnderMouse) { for (var gridY = 0; ...` Replace that entire board-checking nested loop section with this exact logic: if (!cardUnderMouse) { for (var gridY = 0; gridY < planetHeight; gridY++) { for (var gridX = 0; gridX < planetWidth; gridX++) { var terrain = planetBoard[gridY][gridX]; if (terrain) { // 1. Check TOP creature first if (terrain.creatureStack && terrain.creatureStack.length > 0) { var topIndex = terrain.creatureStack.length - 1; var topCreature = terrain.creatureStack[topIndex]; if (Math.abs(topCreature.x - x) < 80 && Math.abs(topCreature.y - y) < 110) { cardUnderMouse = topCreature; } // 2. Check LOWER creatures (only the bottom 40px color band) if (!cardUnderMouse && topIndex > 0) { for (var c = topIndex - 1; c >= 0; c--) { var lowerCreature = terrain.creatureStack[c]; var cardBottom = lowerCreature.y + 110; if (Math.abs(lowerCreature.x - x) < 80 && y <= cardBottom && y >= cardBottom - 40) { cardUnderMouse = lowerCreature; break; } } } } // 3. Check Terrain ONLY if no creature was hovered if (!cardUnderMouse) { if (Math.abs(terrain.x - x) < 75 && Math.abs(terrain.y - y) < 100) { cardUnderMouse = terrain; } } } if (cardUnderMouse) { break; } } if (cardUnderMouse) { break; } } }
User prompt
Rewrite board-checking logic in onMouseMove to prioritize top creatures, check lower creatures' color bands only, then fallback to terrain
User prompt
I need to adjust the position of the zoomed/cloned cards again, this time back down by 200 pixels. Also please add a yellow text information line at the top of the UI which displays 'Round:' and 'Phase:' information.
User prompt
I need to adjust the position of the zoomed/cloned cards so they stop blocking the card information text at the bottom of the screen. I also need to move the new Scry header text to match that card info text location. Please make these exact explicit changes: 1. MOVE THE ZOOMED CARDS UP: - Locate the `zoomCard(card)` function. - Find the line where the Y position is set for the cloned card (it currently says `clonedCard.y = 680;`). - Change that line to `clonedCard.y = 380;` (subtracting 300 pixels to shift it safely upward and out of the way of the bottom UI). 2. REPOSITION THE SCRY HEADER TEXT: - Locate the `activateScry()` function where we create `scryHeaderText`. - Update its position and anchor properties to exactly match the `cardInfoText` at the bottom of the screen. - Set its properties to: `scryHeaderText.x = 1024;` `scryHeaderText.y = 2700;` `scryHeaderText.anchor.set(0.5, 1);`
User prompt
Please fix the bug: 'roundText is not defined' in or related to this line: 'roundText.setText("Round: " + currentRound);' Line Number: 4701
User prompt
Please fix the bug: 'roundText is not defined' in or related to this line: 'roundText.setText("Round: " + currentRound);' Line Number: 4701
User prompt
I need to refactor the UI text elements for the Round, Phase, and Turn information. AI had trouble with this previously, so please follow these exact explicit steps: 1. REMOVE OLD ROUND TEXT: - Completely delete the variables `roundText` and `roundDisplayText` (around line 600). - Search the entire codebase for `roundText.setText` and `roundDisplayText.setText` (specifically in the `endRound()` and `processDrawPhase()` functions) and delete those lines so the game doesn't crash. 2. UPDATE AND MOVE PHASE TEXT: - Locate the `phaseText` variable declaration. - Change its color to yellow: `fill: 0xFFFF00` - Move it to the top of the UI where the old round text used to be, but 1 line higher. Set its `y = 68` (which is 100 minus its font size of 32). - Ensure it is added to the top UI layer by changing `game.addChild(phaseText);` to `LK.gui.top.addChild(phaseText);` 3. UPDATE THE STRING LOGIC: - Locate the `game.update` function. - Change the text update line to include the round information. It should look exactly like this: `phaseText.setText("Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber + " | Round: " + currentRound);` 4. UPDATE THE RED WARNING TEXT: - Find the red warning text I added (this might be inside `showNextPhaseWarning()` where the fill is red, or the event warning text). - Increase its `size` property by exactly 2 points. - Move it up by 1 line by subtracting 32 from its current `y` position value. Finally, please increase the size of the card information text by 50% and make sure that it can flow into a second line below the 1st rather than disappear from view because it is too long
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var CreatureCard = Container.expand(function (creatureData) {
var self = Container.call(this);
self.creatureData = creatureData || {
type: 'basic',
level: 'Basic',
dietType: 'herbivore',
name: 'Basic Herbivore',
terrainRequirement: 'land',
climateRequirement: 'any'
};
// Create creature visual
var creatureAsset;
if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'herbivore') {
creatureAsset = self.attachAsset('creatureBasicHerbivore', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'herbivore') {
creatureAsset = self.attachAsset('creatureAdvancedHerbivore', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'carnivore') {
creatureAsset = self.attachAsset('creatureBasicCarnivore', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'carnivore') {
creatureAsset = self.attachAsset('creatureAdvancedCarnivore', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Add creature type color band at bottom
var typeStripAsset;
if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'herbivore') {
typeStripAsset = 'creatureTypeBasicHerbivore';
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'herbivore') {
typeStripAsset = 'creatureTypeAdvancedHerbivore';
} else if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'carnivore') {
typeStripAsset = 'creatureTypeBasicCarnivore';
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'carnivore') {
typeStripAsset = 'creatureTypeAdvancedCarnivore';
}
if (typeStripAsset) {
var typeStrip = self.attachAsset(typeStripAsset, {
anchorX: 0.5,
anchorY: 0.5
});
typeStrip.y = 95; // Position at bottom of creature card
}
// Add creature name
self.nameText = new Text2(self.creatureData.name, {
size: 18,
fill: 0xFFFFFF
});
self.nameText.anchor.set(0.5, 0);
self.nameText.x = 0;
self.nameText.y = -100;
self.addChild(self.nameText);
// Add requirement text
var reqText = "Req: ";
if (self.creatureData.terrainRequirement && self.creatureData.terrainRequirement !== 'any') {
reqText += self.creatureData.terrainRequirement;
}
if (self.creatureData.climateRequirement && self.creatureData.climateRequirement !== 'any') {
if (reqText !== "Req: ") {
reqText += ", ";
}
reqText += self.creatureData.climateRequirement;
}
if (reqText !== "Req: ") {
self.requirementText = new Text2(reqText, {
size: 14,
fill: 0xFFFFFF
});
self.requirementText.anchor.set(0.5, 0);
self.requirementText.x = 0;
self.requirementText.y = 60;
self.addChild(self.requirementText);
}
self.isInPlay = false;
self.linkMarkers = [];
self.activeLinks = [];
self.extinctionMarkers = 0;
self.linkRequirement = 0;
self.safeLinks = 0;
// Set link requirements and safe link levels based on creature type
if (self.creatureData.dietType === 'carnivore') {
if (self.creatureData.level === 'Basic') {
self.linkRequirement = 1; // Basic carnivores need 1 link
} else if (self.creatureData.level === 'Advanced') {
self.linkRequirement = 2; // Advanced carnivores need 2 links
}
} else if (self.creatureData.dietType === 'herbivore') {
if (self.creatureData.level === 'Basic') {
self.safeLinks = 1; // Basic herbivores can safely handle 1 link
} else if (self.creatureData.level === 'Advanced') {
self.safeLinks = 2; // Advanced herbivores can safely handle 2 links
}
}
if (self.creatureData.fatty) {
self.linkRequirement = 3;
}
// Create visual link markers for carnivores
self.createLinkMarkers = function () {
if (self.creatureData.dietType === 'carnivore' && self.linkRequirement > 0) {
for (var i = 0; i < self.linkRequirement; i++) {
var linkMarker = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.15,
scaleY: 0.15
});
linkMarker.tint = 0xFF0000; // Red color for unlinked markers
linkMarker.x = (i - (self.linkRequirement - 1) / 2) * 25;
linkMarker.y = -80;
linkMarker.isLinked = false;
linkMarker.targetHerbivore = null;
linkMarker.carnivore = self;
linkMarker.markerIndex = i;
// Link markers are no longer directly interactive
linkMarker.move = function (x, y, obj) {};
linkMarker.down = function (x, y, obj) {};
self.addChild(linkMarker);
self.linkMarkers.push(linkMarker);
}
// Create personal "Alter Links" button for this carnivore
self.createPersonalAlterLinksButton = function () {
if (self.isInPlay) {
var alterButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.2
});
alterButton.tint = 0xFFD700; // Gold color
var alterButtonText = new Text2("Alter Links", {
size: 12,
fill: 0x000000
});
alterButtonText.anchor.set(0.5, 0.5);
alterButton.addChild(alterButtonText);
alterButton.x = 0;
alterButton.y = 0;
alterButton.carnivoreCard = self;
alterButton.down = function () {
if (gamePhase === 'dusk' && linkAdjustmentMode) {
resetCardLinks(self);
}
};
self.addChild(alterButton);
self.personalAlterButton = alterButton;
}
};
}
};
// Create extinction marker visuals
self.extinctionMarkerVisuals = [];
self.updateExtinctionMarkers = function () {
// Remove existing visuals
for (var i = 0; i < self.extinctionMarkerVisuals.length; i++) {
if (self.extinctionMarkerVisuals[i].parent) {
self.extinctionMarkerVisuals[i].parent.removeChild(self.extinctionMarkerVisuals[i]);
}
}
self.extinctionMarkerVisuals = [];
// Create new visuals for current extinction markers
for (var i = 0; i < self.extinctionMarkers; i++) {
var marker = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.1,
scaleY: 0.1
});
marker.tint = 0x000000; // Black for extinction markers
marker.x = (i - (self.extinctionMarkers - 1) / 2) * 20;
marker.y = 80;
self.addChild(marker);
self.extinctionMarkerVisuals.push(marker);
}
};
self.die = function () {
if (self.isInPlay) {
// Add creature to graveyard and draw event card based on creature tier
graveyard.push(self.creatureData);
scryDeniedNextRound = true; // Deny scry for next round because a creature entered the graveyard
// Draw event card only once when creature dies
addEventCardToDeck(self.creatureData.level);
// Visual death effect
tween(self, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 500,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
LK.getSound('creatureDie').play();
}
};
self.move = function (x, y, obj) {
var globalX = self.x + x * self.scaleX;
var globalY = self.y + y * self.scaleY;
onMouseMove(globalX, globalY);
};
self.down = function (x, y, obj) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
// Prevent selecting creature/terrain cards if event cards are in hand
if (hasEventCardsInHand) {
//{1G_event}
return;
}
selectedCard = self;
draggedCard = self;
originalCardPosition = {
x: self.x,
y: self.y
};
showPossibleMoves(self);
}
};
// Determine if creature has any special properties
var hasAnySpecial = self.creatureData && (self.creatureData.tough || self.creatureData.squishy || self.creatureData.stinky || self.creatureData.flying || self.creatureData.swimming || self.creatureData.efficient || self.creatureData.whaleFood || self.creatureData.whaleChow || self.creatureData.bully || self.creatureData.seaBound || self.creatureData.id === 'AC03');
// Do NOT add "Special" text - start cascading special text directly below card name
var nextYOffset = -80; // Start position below card name
// Add "Tough" text if present
if (self.creatureData && self.creatureData.tough) {
self.toughText = new Text2("Tough", {
size: 24,
fill: 0xFFD700,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.toughText.anchor.set(0.5, 0.5);
self.toughText.x = 0;
self.toughText.y = nextYOffset;
self.addChild(self.toughText);
nextYOffset = nextYOffset + 40;
}
// Add "Squishy" text if present
if (self.creatureData && self.creatureData.squishy) {
self.squishyText = new Text2("Squishy", {
size: 24,
fill: 0xFF69B4,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.squishyText.anchor.set(0.5, 0.5);
self.squishyText.x = 0;
self.squishyText.y = nextYOffset;
self.addChild(self.squishyText);
nextYOffset = nextYOffset + 40;
}
// Add "Fatty" text if present
if (self.creatureData && self.creatureData.fatty) {
self.fattyText = new Text2("Fatty", {
size: 24,
fill: 0xFF8C00,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.fattyText.anchor.set(0.5, 0.5);
self.fattyText.x = 0;
self.fattyText.y = nextYOffset;
self.addChild(self.fattyText);
nextYOffset = nextYOffset + 40;
}
// Add "Stinky" text if present
if (self.creatureData && self.creatureData.stinky) {
self.stinkyText = new Text2("Stinky", {
size: 24,
fill: 0x228B22,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.stinkyText.anchor.set(0.5, 0.5);
self.stinkyText.x = 0;
self.stinkyText.y = nextYOffset;
self.addChild(self.stinkyText);
nextYOffset = nextYOffset + 40;
}
// Add "Flying" text if present
if (self.creatureData && self.creatureData.flying) {
self.flyingText = new Text2("Flying", {
size: 24,
fill: 0x00BFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.flyingText.anchor.set(0.5, 0.5);
self.flyingText.x = 0;
self.flyingText.y = nextYOffset;
self.addChild(self.flyingText);
nextYOffset = nextYOffset + 40;
}
// Add "Swimming" text if present
if (self.creatureData && self.creatureData.swimming) {
self.swimmingText = new Text2("Swimming", {
size: 24,
fill: 0x1E90FF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.swimmingText.anchor.set(0.5, 0.5);
self.swimmingText.x = 0;
self.swimmingText.y = nextYOffset;
self.addChild(self.swimmingText);
nextYOffset = nextYOffset + 40;
}
// Add "Efficient" text if present
if (self.creatureData && self.creatureData.efficient) {
self.efficientText = new Text2("Efficient", {
size: 24,
fill: 0x32CD32,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.efficientText.anchor.set(0.5, 0.5);
self.efficientText.x = 0;
self.efficientText.y = nextYOffset;
self.addChild(self.efficientText);
nextYOffset = nextYOffset + 40;
}
// Add "Whale Chow" text if present
if (self.creatureData && self.creatureData.whaleChow) {
self.whaleChowText = new Text2("Whale Chow", {
size: 24,
fill: 0x87CEEB,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.whaleChowText.anchor.set(0.5, 0.5);
self.whaleChowText.x = 0;
self.whaleChowText.y = nextYOffset;
self.addChild(self.whaleChowText);
nextYOffset = nextYOffset + 40;
}
// Add "Bully" text if present (but only for AC02, not BH07 or BH13)
if (self.creatureData && self.creatureData.bully && self.creatureData.id === 'AC02') {
self.bullyText = new Text2("Bully", {
size: 24,
fill: 0xFF6347,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold" //{2U_bully4}
}); //{2U_bully5}
self.bullyText.anchor.set(0.5, 0.5);
self.bullyText.x = 0;
self.bullyText.y = nextYOffset;
self.addChild(self.bullyText);
nextYOffset = nextYOffset + 40;
} //{2U_bully6}
// Add "Hunter-Hunter" text if present (for BC04 - Tuna)
if (self.creatureData && self.creatureData.hunterHunter) {
self.hunterHunterText = new Text2("Hunter-Hunter", {
size: 24,
fill: 0x00FFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.hunterHunterText.anchor.set(0.5, 0.5);
self.hunterHunterText.x = 0;
self.hunterHunterText.y = nextYOffset;
self.addChild(self.hunterHunterText);
nextYOffset = nextYOffset + 40;
}
// Add "Sea Bound" text for AC02
if (self.creatureData && self.creatureData.id === 'AC02') {
self.seaBoundText = new Text2("Sea Bound", {
size: 24,
fill: 0x00BFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.seaBoundText.anchor.set(0.5, 0.5);
self.seaBoundText.x = 0;
self.seaBoundText.y = nextYOffset;
self.addChild(self.seaBoundText);
nextYOffset = nextYOffset + 40;
}
// Add "Whale Chow Eater" text if present (for AC03 only)
if (self.creatureData && self.creatureData.id === 'AC03') {
self.whaleChowEaterText = new Text2("Whale Chow Eater", {
size: 24,
fill: 0x00BFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.whaleChowEaterText.anchor.set(0.5, 0.5);
self.whaleChowEaterText.x = 0;
self.whaleChowEaterText.y = nextYOffset;
self.addChild(self.whaleChowEaterText);
nextYOffset = nextYOffset + 40;
}
// Add "Dominant DNA" text if present
if (self.creatureData && self.creatureData.dominantDNA) {
self.dominantDNAText = new Text2("Dominant DNA", {
size: 24,
fill: 0x9932CC,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.dominantDNAText.anchor.set(0.5, 0.5);
self.dominantDNAText.x = 0;
self.dominantDNAText.y = nextYOffset;
self.addChild(self.dominantDNAText);
nextYOffset = nextYOffset + 40;
}
return self;
});
var EventCard = Container.expand(function (eventData) {
var self = Container.call(this);
self.eventData = eventData || {
type: 'basic',
cardType: 'event',
level: 'Basic',
name: 'Event Card',
effect: 'environmental'
};
// Create event card visual
var eventAsset = self.attachAsset('eventCard', {
anchorX: 0.5,
anchorY: 0.5
});
// Add event text - placeholder "EVENT!!!" in red
var eventText = new Text2("EVENT!!!", {
size: 32,
fill: 0xFF0000
});
eventText.anchor.set(0.5, 0.5);
eventText.x = 0;
eventText.y = 0;
self.addChild(eventText);
// Add event level indicator
var levelText = new Text2(self.eventData.level, {
size: 16,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0);
levelText.x = 0;
levelText.y = -80;
self.addChild(levelText);
self.isInPlay = false;
self.move = function (x, y, obj) {
var globalX = self.x + x * self.scaleX;
var globalY = self.y + y * self.scaleY;
onMouseMove(globalX, globalY);
};
self.down = function (x, y, obj) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
// Event cards are played immediately by clicking them
// Move to event discard pile
var handIndex = playerHand.indexOf(self);
if (handIndex !== -1) {
playerHand.splice(handIndex, 1);
}
// Remove from game
if (self.parent) {
self.parent.removeChild(self);
}
// Add event card data to event discard pile
var cardData = self.eventData;
if (!eventDiscardPile) {
eventDiscardPile = [];
}
eventDiscardPile.push(cardData);
// Update hand positions
updateHandPositions();
// Check if there are more event cards in hand
var moreEventCards = false;
for (var i = 0; i < playerHand.length; i++) {
if (playerHand[i].eventData) {
moreEventCards = true;
break;
}
}
// If no more event cards, progress to Noon phase
if (!moreEventCards) {
hasEventCardsInHand = false;
gamePhase = 'noon';
// Update UI
gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber);
// Clean up event debug box
if (game.eventDebugBox && game.eventDebugBox.parent) {
game.eventDebugBox.parent.removeChild(game.eventDebugBox);
game.eventDebugBox = null;
}
// Discard any remaining cards in hand
var remainingCards = playerHand.slice();
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
}
}
};
return self;
});
var PlanetSlot = Container.expand(function (slotX, slotY) {
var self = Container.call(this);
self.slotX = slotX;
self.slotY = slotY;
self.terrainCard = null;
self.isHighlighted = false;
self.pinkBorder = null;
self.highlight = function () {
if (!self.isHighlighted && self.terrainCard) {
self.isHighlighted = true;
// Create pink border around the terrain card
if (!self.pinkBorder) {
self.pinkBorder = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
self.pinkBorder.tint = 0xFF1493; // Brighter deep pink color
self.pinkBorder.alpha = 0.9;
// Scale border to be larger than terrain card
self.pinkBorder.scaleX = self.terrainCard.scaleX * 1.3;
self.pinkBorder.scaleY = self.terrainCard.scaleY * 1.3;
self.pinkBorder.x = self.terrainCard.x;
self.pinkBorder.y = self.terrainCard.y;
// Add border behind terrain card
var terrainIndex = game.getChildIndex(self.terrainCard);
game.addChildAt(self.pinkBorder, terrainIndex);
}
}
};
self.unhighlight = function () {
if (self.isHighlighted) {
self.isHighlighted = false;
// Remove pink border
if (self.pinkBorder && self.pinkBorder.parent) {
self.pinkBorder.parent.removeChild(self.pinkBorder);
self.pinkBorder = null;
}
}
};
self.down = function (x, y, obj) {
if (selectedCard && selectedCard.creatureData && self.terrainCard && canPlaceCreatureOnTerrain(selectedCard, self.terrainCard)) {
placeCreatureOnStack(selectedCard, self.terrainCard, self.slotX, self.slotY);
}
};
return self;
});
var TerrainCard = Container.expand(function (terrainData) {
var self = Container.call(this);
self.terrainData = terrainData || {
type: 'basic',
level: 'Basic',
subtype: 'land',
landType: 'flat',
waterType: null,
climate: 'temperate'
};
// Create terrain card visual based on subtype and landType/waterType
var terrainAsset;
if (self.terrainData.subtype === 'land') {
if (self.terrainData.landType === 'flat') {
terrainAsset = self.attachAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.terrainData.landType === 'hills') {
terrainAsset = self.attachAsset('terrainLandHills', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.terrainData.landType === 'mountain') {
terrainAsset = self.attachAsset('terrainLandMountain', {
anchorX: 0.5,
anchorY: 0.5
});
}
} else if (self.terrainData.subtype === 'water') {
if (self.terrainData.waterType === 'sea') {
terrainAsset = self.attachAsset('terrainWaterSea', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.terrainData.waterType === 'fresh') {
terrainAsset = self.attachAsset('terrainWaterFresh', {
anchorX: 0.5,
anchorY: 0.5
});
}
}
// Add terrain type indicator strip at top
var terrainStripAsset = self.terrainData.level === 'Advanced' ? 'advancedTerrainStrip' : 'basicTerrainStrip';
var terrainStrip = self.attachAsset(terrainStripAsset, {
anchorX: 0.5,
anchorY: 0.5
});
terrainStrip.y = -147;
// For advanced terrain cards, tint the strip based on colorBand
if (self.terrainData.level === 'Advanced' && self.terrainData.colorBand) {
var colorMap = {
'grey': 0xC0C0C0,
'darkblue': 0x87CEEB,
'brown': 0xD2B48C,
'purple': 0x800080,
'lightblue': 0xADD8E6
};
if (colorMap[self.terrainData.colorBand]) {
terrainStrip.tint = colorMap[self.terrainData.colorBand];
}
}
// Add terrain type text (subtype and second subtype in the color band)
var terrainTypeText = '';
var textColor = 0xFFFFFF; // Default white for advanced terrain
if (self.terrainData.level === 'Advanced') {
// For advanced terrain: display subtype and specific terrain type
terrainTypeText = self.terrainData.subtype.toUpperCase() + ' - ';
terrainTypeText += (self.terrainData.landType || self.terrainData.waterType).toUpperCase();
} else {
// For basic terrain: just display the specific terrain type
terrainTypeText = (self.terrainData.landType || self.terrainData.waterType).toUpperCase();
textColor = 0x000000; // Black text for basic terrain
}
self.typeText = new Text2(terrainTypeText, {
size: 24,
fill: textColor,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif"
});
self.typeText.anchor.set(0.5, 0.5);
self.typeText.x = 0;
self.typeText.y = -147;
self.addChild(self.typeText);
// Add climate requirement display for advanced terrain cards (top right under color band with colored circles)
if (self.terrainData.level === 'Advanced' && self.terrainData.climateRequirement && self.terrainData.climateRequirement !== 'any') {
var climateDisplay = self.terrainData.climateRequirement;
var climateArray = [];
if (climateDisplay.indexOf('/') !== -1) {
// Multiple climate requirements - split them
climateArray = climateDisplay.split('/');
} else {
// Single climate requirement
climateArray = [climateDisplay];
}
// Create colored circles with climate letters
var circleSize = 30;
var circleSpacing = 35;
var startX = 100;
var startY = -115;
for (var c = 0; c < climateArray.length; c++) {
var climateType = climateArray[c].trim();
var circleColor = 0xFFFFFF;
var letterChar = '';
if (climateType.indexOf('hot') !== -1) {
circleColor = 0xFF4444;
letterChar = 'H';
} else if (climateType.indexOf('cold') !== -1) {
circleColor = 0x4444FF;
letterChar = 'C';
} else if (climateType.indexOf('temperate') !== -1) {
circleColor = 0xFFFF44;
letterChar = 'T';
}
// Create circle background
var climateCircle = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.08,
scaleY: 0.08
});
climateCircle.tint = circleColor;
climateCircle.x = startX + c * circleSpacing;
climateCircle.y = startY;
self.addChild(climateCircle);
// Add letter text on circle
var climateLetterText = new Text2(letterChar, {
size: 16,
fill: 0x000000,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif"
});
climateLetterText.anchor.set(0.5, 0.5);
climateLetterText.x = startX + c * circleSpacing;
climateLetterText.y = startY;
self.addChild(climateLetterText);
}
}
// Add "Special" text in the center for any card with special: true
if (self.terrainData && self.terrainData.special) {
self.specialText = new Text2("Special", {
size: 32,
fill: 0xFF0000,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.specialText.anchor.set(0.5, 0.5);
self.specialText.x = 0;
self.specialText.y = 0;
self.addChild(self.specialText);
}
// Add card ID at center of bottom for advanced terrain cards
if (self.terrainData.level === 'Advanced' && self.terrainData.id) {
self.cardIdText = new Text2(self.terrainData.id, {
size: 20,
fill: 0xFFFFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif"
});
self.cardIdText.anchor.set(0.5, 0.5);
self.cardIdText.x = 0;
self.cardIdText.y = 140; // Bottom center of card
self.addChild(self.cardIdText);
}
// Climate will be displayed as row background instead of on individual cards
self.gridX = -1;
self.gridY = -1;
self.creatureStack = [];
self.move = function (x, y, obj) {
var globalX = self.x + x * self.scaleX;
var globalY = self.y + y * self.scaleY;
onMouseMove(globalX, globalY);
};
self.down = function (x, y, obj) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
// Prevent selecting creature/terrain cards if event cards are in hand
if (hasEventCardsInHand) {
//{3H_event}
return;
}
selectedCard = self;
draggedCard = self;
originalCardPosition = {
x: self.x,
y: self.y
};
showPossibleMoves(self);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a252f
});
/****
* Game Code
****/
// UI elements
// Event card asset
// Creature card assets
// Climate indicator strips
// Terrain card assets - Water types with blue/grey strip
// Terrain card assets - Land types with green strip
// Zoom container for magnified card view
var zoomContainer = new Container();
zoomContainer.x = 0;
zoomContainer.y = 0;
LK.gui.center.addChild(zoomContainer);
var zoomedCard = null;
var currentHoveredCardForZoom = null;
var cardBoundsForZoom = 80; // Half card width for zoom trigger
var cardHeightBoundsForZoom = 110; // Half card height for zoom trigger
// Global mouse listener for tracking cursor position
var currentMouseX = 0;
var currentMouseY = 0;
function onMouseMove(x, y) {
var cardUnderMouse = null;
// Check hand (reverse order so top cards are checked first)
for (var i = playerHand.length - 1; i >= 0; i--) {
var card = playerHand[i];
var hw = (card.terrainData ? 126 : 80) * card.scaleX;
var hh = (card.terrainData ? 168 : 110) * card.scaleY;
if (Math.abs(card.x - x) < hw && Math.abs(card.y - y) < hh) {
cardUnderMouse = card;
break;
}
}
// Check Scry Cards
if (!cardUnderMouse && typeof scryCards !== 'undefined' && scryCards.length > 0) {
for (var i = scryCards.length - 1; i >= 0; i--) {
var card = scryCards[i];
var hw = (card.terrainData ? 126 : 80) * card.scaleX;
var hh = (card.terrainData ? 168 : 110) * card.scaleY;
if (Math.abs(card.x - x) < hw && Math.abs(card.y - y) < hh) {
cardUnderMouse = card;
break;
}
}
}
// Check AT18 Special Selection Cards
if (!cardUnderMouse && typeof at18Active !== 'undefined' && at18Active) {
for (var i = game.children.length - 1; i >= 0; i--) {
var child = game.children[i];
if (child.at18Selectable) {
var hw = (child.terrainData ? 126 : 80) * child.scaleX;
var hh = (child.terrainData ? 168 : 110) * child.scaleY;
if (Math.abs(child.x - x) < hw && Math.abs(child.y - y) < hh) {
cardUnderMouse = child;
break;
}
}
}
}
// Check board
if (!cardUnderMouse) {
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain) {
// 1. Check TOP creature
if (terrain.creatureStack && terrain.creatureStack.length > 0) {
var topIndex = terrain.creatureStack.length - 1;
var topCreature = terrain.creatureStack[topIndex];
var cHW = 80 * topCreature.scaleX;
var cHH = 110 * topCreature.scaleY;
if (Math.abs(topCreature.x - x) < cHW && Math.abs(topCreature.y - y) < cHH) {
cardUnderMouse = topCreature;
}
// 2. Check LOWER creatures
if (!cardUnderMouse && topIndex > 0) {
for (var c = topIndex - 1; c >= 0; c--) {
var lowerC = terrain.creatureStack[c];
var lHW = 80 * lowerC.scaleX;
var lHH = 110 * lowerC.scaleY;
var cardBottom = lowerC.y + lHH;
if (Math.abs(lowerC.x - x) < lHW && y <= cardBottom && y >= cardBottom - 45 * lowerC.scaleY) {
cardUnderMouse = lowerC;
break;
}
}
}
}
// 3. Check Terrain
if (!cardUnderMouse) {
var tHW = 126 * terrain.scaleX;
var tHH = 168 * terrain.scaleY;
if (Math.abs(terrain.x - x) < tHW && Math.abs(terrain.y - y) < tHH) {
cardUnderMouse = terrain;
}
}
}
if (cardUnderMouse) break;
}
if (cardUnderMouse) break;
}
}
// Process state changes
if (cardUnderMouse && cardUnderMouse !== currentlyHoveredCard) {
if (currentlyHoveredCard) clearZoom();
zoomCard(cardUnderMouse);
showCardInfo(cardUnderMouse);
if (!cardUnderMouse.isInPlay) {
showPossibleMoves(cardUnderMouse);
}
currentlyHoveredCard = cardUnderMouse;
} else if (!cardUnderMouse && currentlyHoveredCard) {
clearZoom();
hideCardInfo();
hidePossibleMoves();
currentlyHoveredCard = null;
}
}
var currentlyHoveredCard = null;
game.move = function (x, y, obj) {
currentMouseX = x;
currentMouseY = y;
// Call centralized mouse move handler for zoom
onMouseMove(x, y);
if (draggedCard) {
draggedCard.x = x;
draggedCard.y = y;
// Ensure dragged card is always on top
game.removeChild(draggedCard);
game.addChild(draggedCard);
// Highlights are already shown from the card's down event
} else if (draggedLinkMarker && gamePhase === 'dusk' && linkAdjustmentMode) {
// Handle link marker dragging - highlight valid targets only while holding
var carnivore = draggedLinkMarker.carnivore;
highlightValidLinkTargets(carnivore);
// Create temporary visual line from carnivore to cursor
// Remove any existing temp line
if (game.tempLinkLine && game.tempLinkLine.parent) {
game.tempLinkLine.parent.removeChild(game.tempLinkLine);
}
// Create new temp line
var tempLine = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.01,
scaleY: 1
});
tempLine.tint = 0xFFFF00; // Yellow temp line
tempLine.alpha = 0.5;
var dx = x - carnivore.x;
var dy = y - carnivore.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
tempLine.x = carnivore.x + dx / 2;
tempLine.y = carnivore.y + dy / 2;
tempLine.rotation = angle;
tempLine.scaleY = distance / 336;
game.addChild(tempLine);
game.tempLinkLine = tempLine;
}
};
// Game state variables
var currentRound = 0;
var maxRounds = 5;
var cardsDrawnThisTurn = 0;
var maxCardsPerTurn = 3;
var planetBoard = [];
var planetSlots = [];
var playerHand = [];
var maxHandSize = 3;
var mainDeck = [];
var basicTerrainPool = [];
var selectedCard = null;
var draggedCard = null;
var originalCardPosition = null;
var scryTokenActive = false; // Start without scry token
var scryCards = []; // Cards in scry mode
var scryMode = false; // Whether scry is active
var scryStep = 0; // Track which step of scry (1 = select for bottom, 2 = select to discard)
var scryHeaderText = null; // Header text for scry instructions
var creaturesWentExtinct = false; // Track if creatures went extinct this round
var cardInfoText = null;
var hoveredCard = null;
var drawPhase = 'initial'; // 'initial', 'forced'
var cardsDrawnInPhase = [];
var discardPile = [];
var eventDiscardPile = []; // Discard pile specifically for event cards
var linkLines = []; // Visual link lines between carnivores and herbivores
var gamePhase = 'dawn'; // Track current game phase: 'dawn', 'draw', 'noon', 'dusk', 'night', 'end'
var turnNumber = 1;
var draggedLinkMarker = null;
var draggedLinkCarnivore = null;
var linkHighlights = [];
var eventCardsToPlay = [];
var mustPlayCard = null;
var basicEventDeck = [];
var advancedEventDeck = [];
var phaseTransitionTimer = null;
var scryDeniedNextRound = false;
var hasEventCardsInHand = false;
// Planet layout: 2-4-6-4-2 formation
var planetLayout = [2, 4, 6, 4, 2];
var planetWidth = 6; // Max cards in any row
var planetHeight = 5; // Number of rows
// UI Elements
var deckCountText = new Text2("Deck: " + mainDeck.length, {
size: 36,
fill: 0xFFFFFF
});
deckCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(deckCountText);
deckCountText.x = 120;
deckCountText.y = 150;
var discardCountText = new Text2("Discard: 0", {
size: 36,
fill: 0xFFFFFF
});
discardCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(discardCountText);
discardCountText.x = 120;
discardCountText.y = 190;
var basicEventCountText = new Text2("Basic Events: 10", {
size: 28,
fill: 0x00FF00
});
basicEventCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(basicEventCountText);
basicEventCountText.x = 120;
basicEventCountText.y = 230;
var advancedEventCountText = new Text2("Advanced Events: 10", {
size: 28,
fill: 0xFF6600
});
advancedEventCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(advancedEventCountText);
advancedEventCountText.x = 120;
advancedEventCountText.y = 260;
var graveyardCountText = new Text2("Graveyard: 0", {
size: 28,
//{4f_graveyard}
fill: 0xFFFFFF
}); //{4g_graveyard}
graveyardCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(graveyardCountText);
graveyardCountText.x = 120;
graveyardCountText.y = 290;
var eventDiscardCountText = new Text2("Event Discard: 0", {
size: 28,
fill: 0x9932CC
});
eventDiscardCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(eventDiscardCountText);
eventDiscardCountText.x = 120;
eventDiscardCountText.y = 320;
var graveyard = [];
var at15PlayedLastTurn = false; // Track if AT15 was played to enforce draw 4 next turn
var at18Active = false; // Track if AT18 special is being processed
var at18CarnivoreList = []; // Carnivores available for AT18 selection
var scryText = new Text2("Scry", {
size: 32,
fill: 0xFFFFFF
});
scryText.anchor.set(0, 0);
scryText.tint = 0x888888; // Start gray/inactive
LK.gui.topRight.addChild(scryText);
scryText.x = -300;
scryText.y = 190;
// Add scry button functionality
scryText.down = function () {
if (scryTokenActive && !scryMode && mainDeck.length >= 3) {
activateScry();
}
};
// Initialize basic terrain pool (23 cards total)
// BT01-05: Land - Flatland
// BT06-10: Land - Hills
// BT11-14: Land - Mountains
// BT15-20: Water - Sea
// BT21-23: Water - Fresh Water
function createBasicTerrainPool() {
basicTerrainPool = [];
// BT01-05: Land - Flatland (5 cards)
for (var i = 0; i < 5; i++) {
basicTerrainPool.push({
type: 'basic',
level: 'Basic',
id: 'BT' + (i + 1).toString().padStart(2, '0'),
subtype: 'land',
landType: 'flat',
climate: null // Will be assigned during setup
});
}
// BT06-10: Land - Hills (5 cards)
for (var i = 0; i < 5; i++) {
basicTerrainPool.push({
type: 'basic',
level: 'Basic',
id: 'BT' + (i + 6).toString().padStart(2, '0'),
subtype: 'land',
landType: 'hills',
climate: null // Will be assigned during setup
});
}
// BT11-14: Land - Mountains (4 cards)
for (var i = 0; i < 4; i++) {
basicTerrainPool.push({
type: 'basic',
level: 'Basic',
id: 'BT' + (i + 11).toString().padStart(2, '0'),
subtype: 'land',
landType: 'mountain',
climate: null // Will be assigned during setup
});
}
// BT15-20: Water - Sea (6 cards)
for (var i = 0; i < 6; i++) {
basicTerrainPool.push({
type: 'basic',
level: 'Basic',
id: 'BT' + (i + 15).toString().padStart(2, '0'),
subtype: 'water',
waterType: 'sea',
climate: null // Will be assigned during setup
});
}
// BT21-23: Water - Fresh Water (3 cards)
for (var i = 0; i < 3; i++) {
basicTerrainPool.push({
type: 'basic',
level: 'Basic',
id: 'BT' + (i + 21).toString().padStart(2, '0'),
subtype: 'water',
waterType: 'fresh',
climate: null // Will be assigned during setup
});
}
// Shuffle terrain pool
for (var i = basicTerrainPool.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = basicTerrainPool[i];
basicTerrainPool[i] = basicTerrainPool[j];
basicTerrainPool[j] = temp;
}
}
// Setup planet board with terrain cards
function setupPlanet() {
// Create 2D array for planet board
for (var y = 0; y < planetHeight; y++) {
planetBoard[y] = [];
planetSlots[y] = [];
for (var x = 0; x < planetWidth; x++) {
planetBoard[y][x] = null;
planetSlots[y][x] = null;
}
}
// Place terrain cards according to 2-4-6-4-2 layout
var terrainIndex = 0;
var startY = 400;
var cardSpacing = 320;
// Add climate row backgrounds first
for (var row = 0; row < planetLayout.length; row++) {
var climate;
if (row === 0 || row === 4) {
climate = 'cold'; // Rows 1 & 5
} else if (row === 1 || row === 3) {
climate = 'temperate'; // Rows 2 & 4
} else {
climate = 'hot'; // Row 3
}
// Create climate row background
var climateRowAsset = 'climateRow' + climate.charAt(0).toUpperCase() + climate.slice(1);
var climateRowBg = LK.getAsset(climateRowAsset, {
anchorX: 0.5,
anchorY: 0.5
});
climateRowBg.x = 1024; // Center of screen
climateRowBg.y = startY + row * 420;
climateRowBg.alpha = 0.3; // Semi-transparent background
game.addChild(climateRowBg);
// Add climate label text
var climateLabel = new Text2(climate.toUpperCase(), {
size: 32,
fill: 0x000000,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif"
});
climateLabel.anchor.set(0.5, 0.5);
climateLabel.rotation = -Math.PI / 2; // Rotate 90 degrees counter-clockwise to make vertical
climateLabel.x = 50; // Move left to make fully visible
climateLabel.y = startY + row * 420;
game.addChild(climateLabel);
}
for (var row = 0; row < planetLayout.length; row++) {
var cardsInRow = planetLayout[row];
var startX = 1024 - cardsInRow * cardSpacing / 2 + cardSpacing / 2 + 50; // Shift cards right by 50px
// Assign climate based on row
var climate;
if (row === 0 || row === 4) {
climate = 'cold'; // Rows 1 & 5
} else if (row === 1 || row === 3) {
climate = 'temperate'; // Rows 2 & 4
} else {
climate = 'hot'; // Row 3
}
for (var col = 0; col < cardsInRow && terrainIndex < basicTerrainPool.length; col++) {
// Assign climate to terrain card
var terrainData = basicTerrainPool[terrainIndex];
terrainData.climate = climate;
// Create terrain card
var terrainCard = new TerrainCard(terrainData);
terrainCard.x = startX + col * cardSpacing;
terrainCard.y = startY + row * 420;
terrainCard.gridX = col;
terrainCard.gridY = row;
game.addChild(terrainCard);
// Store in board
var boardX = Math.floor((planetWidth - cardsInRow) / 2) + col;
planetBoard[row][boardX] = terrainCard;
// Create slot for this position
var slot = new PlanetSlot(boardX, row);
slot.terrainCard = terrainCard;
planetSlots[row][boardX] = slot;
terrainIndex++;
}
}
}
// Create main deck with advanced terrain cards and creature cards
function createInitialDeck() {
mainDeck = [];
// Add 20 Advanced Terrain Cards (AT01-AT20)
// AT01-02: Fresh Water - Grey band, no climate requirements
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'water',
waterType: 'fresh',
id: 'AT01',
colorBand: 'grey',
climateRequirement: 'any',
name: 'Advanced Fresh Water'
});
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'water',
waterType: 'fresh',
id: 'AT02',
colorBand: 'grey',
climateRequirement: 'any',
name: 'Advanced Fresh Water'
});
// AT03-07: Sea Water - Dark Blue band
// AT03: Hot, AT04: Cold, AT05: Temperate, AT06: Hot or Temperate, AT07: Special
var seaCardConfigs = [{
id: 'AT03',
climate: 'hot',
special: false
}, {
id: 'AT04',
climate: 'cold',
special: false
}, {
id: 'AT05',
climate: 'temperate',
special: false
}, {
id: 'AT06',
climate: 'hot/temperate',
special: false
}, {
id: 'AT07',
climate: 'any',
special: true
}];
for (var i = 0; i < seaCardConfigs.length; i++) {
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'water',
waterType: 'sea',
id: seaCardConfigs[i].id,
colorBand: 'darkblue',
climateRequirement: seaCardConfigs[i].climate,
special: seaCardConfigs[i].special,
name: 'Advanced Sea Water'
});
}
// AT08-12: Flat Land - Brown band
// AT08: Hot, AT09: Temperate, AT10: Cold, AT11: Temperate or Cold, AT12: Temperate or Hot + Special
var flatCardConfigs = [{
id: 'AT08',
climate: 'hot',
special: false
}, {
id: 'AT09',
climate: 'temperate',
special: false
}, {
id: 'AT10',
climate: 'cold',
special: false
}, {
id: 'AT11',
climate: 'temperate/cold',
special: false
}, {
id: 'AT12',
climate: 'temperate/hot',
special: true
}];
for (var i = 0; i < flatCardConfigs.length; i++) {
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'land',
landType: 'flat',
id: flatCardConfigs[i].id,
colorBand: 'brown',
climateRequirement: flatCardConfigs[i].climate,
special: flatCardConfigs[i].special,
name: 'Advanced Flat Land'
});
}
// AT13-17: Hills - Purple band
// AT13: Temperate, AT14: Cold, AT15: Hot + Special, AT16: Temperate or Cold, AT17: Temperate or Hot
var hillsCardConfigs = [{
id: 'AT13',
climate: 'temperate',
special: false
}, {
id: 'AT14',
climate: 'cold',
special: false
}, {
id: 'AT15',
climate: 'hot',
special: true
}, {
id: 'AT16',
climate: 'temperate/cold',
special: false
}, {
id: 'AT17',
climate: 'temperate/hot',
special: false
}];
for (var i = 0; i < hillsCardConfigs.length; i++) {
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'land',
landType: 'hills',
id: hillsCardConfigs[i].id,
colorBand: 'purple',
climateRequirement: hillsCardConfigs[i].climate,
special: hillsCardConfigs[i].special,
name: 'Advanced Hills'
});
}
// AT18-20: Mountains - Light Blue band
// AT18: No climate requirements + Special, AT19: Temperate or Cold, AT20: Temperate or Hot
var mountainCardConfigs = [{
id: 'AT18',
climate: 'any',
special: true
}, {
id: 'AT19',
climate: 'temperate/cold',
special: false
}, {
id: 'AT20',
climate: 'temperate/hot',
special: false
}];
for (var i = 0; i < mountainCardConfigs.length; i++) {
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'land',
landType: 'mountain',
id: mountainCardConfigs[i].id,
colorBand: 'lightblue',
climateRequirement: mountainCardConfigs[i].climate,
special: mountainCardConfigs[i].special,
name: 'Advanced Mountains'
});
}
// Add 33 Basic Herbivore Cards (BH01-BH33)
var basicHerbivoreConfigs = [{
id: 'BH01',
name: 'Pond Turtle',
waterType: 'fresh',
colorBand: 'grey',
//{bh01_terrain}
climate: 'temperate',
//{bh01_climate}
special: false //{bh01_special}
}, {
id: 'BH02',
name: 'Crayfish',
waterType: 'fresh',
colorBand: 'grey',
climate: 'cold',
special: false
}, {
id: 'BH03',
name: 'Newt',
waterType: 'fresh',
colorBand: 'grey',
climate: 'temperate/cold',
special: true
}, {
id: 'BH04',
name: 'Toad',
waterType: 'fresh',
colorBand: 'grey',
climate: 'temperate/hot',
special: true
}, {
id: 'BH05',
name: 'Mud Skipper',
waterType: 'fresh',
colorBand: 'grey',
climate: 'temperate/hot',
special: true
}, {
id: 'BH06',
name: 'Tropical Crab',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'hot',
special: false
}, {
id: 'BH07',
name: 'Plankton',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'cold',
special: true
}, {
id: 'BH08',
name: 'Limpet',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate',
special: false
}, {
id: 'BH09',
name: 'Red Crab',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'hot',
special: false
}, {
id: 'BH10',
name: 'Jelly Fish',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'cold',
special: true
}, {
id: 'BH11',
name: 'Mussel',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate',
special: true
}, {
id: 'BH12',
name: 'Whelk',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/cold',
special: false
}, {
id: 'BH13',
name: 'Krill',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/hot',
special: true
}, {
id: 'BH14',
name: 'Hare',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate',
special: false
}, {
id: 'BH15',
name: 'Meerkat',
landType: 'flat',
colorBand: 'brown',
climate: 'hot',
special: false
}, {
id: 'BH16',
name: 'Pheasant',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate',
special: true
}, {
id: 'BH17',
name: 'Tortoise',
landType: 'flat',
colorBand: 'brown',
climate: 'hot',
special: false
}, {
id: 'BH18',
name: 'Goose',
landType: 'flat',
colorBand: 'brown',
climate: 'cold',
special: true
}, {
id: 'BH19',
name: 'Pig',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate/hot',
special: false
}, {
id: 'BH20',
name: 'Platypus',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate/cold',
special: true
}, {
id: 'BH21',
name: 'Rabbit',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate',
special: false
}, {
id: 'BH22',
name: 'Chicken',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate',
special: false
}, {
id: 'BH23',
name: 'Ice Squirrel',
landType: 'hills',
colorBand: 'purple',
climate: 'cold',
special: false
}, {
id: 'BH24',
name: 'Pea Fowl',
landType: 'hills',
colorBand: 'purple',
climate: 'hot',
special: false
}, {
id: 'BH25',
name: 'Mole-Rat',
landType: 'hills',
colorBand: 'purple',
climate: 'hot',
special: false
}, {
id: 'BH26',
name: 'Guinnea pig',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate/cold',
special: true
}, {
id: 'BH27',
name: 'Wombat',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate/hot',
special: true
}, {
id: 'BH28',
name: 'Marmoset',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate',
special: false
}, {
id: 'BH29',
name: 'Porcupine',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate',
special: false
}, {
id: 'BH30',
name: 'Skunk',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'hot',
special: true
}, {
id: 'BH31',
name: 'Shaggy Sheep',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'cold',
special: true
}, {
id: 'BH32',
name: 'Worms',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate/hot',
special: true
}, {
id: 'BH33',
name: 'Giant Snail',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate/cold',
special: true
}];
for (var i = 0; i < basicHerbivoreConfigs.length; i++) {
var config = basicHerbivoreConfigs[i];
var subtype = config.waterType ? 'water' : 'land';
var waterType = config.waterType || null;
var landType = config.landType || null;
mainDeck.push({
type: 'basic',
level: 'Basic',
cardType: 'creature',
dietType: 'herbivore',
name: config.name,
id: config.id,
subtype: subtype,
waterType: waterType,
landType: landType,
colorBand: config.colorBand,
climateRequirement: config.climate,
special: config.special,
flying: config.id === 'BH18' || config.id === 'BH16' ? true : false,
swimming: config.id === 'BH02' || config.id === 'BH07' || config.id === 'BH10' || config.id === 'BH13' || config.id === 'BH20' ? true : false,
tough: config.id === 'BH31' ? true : false,
squishy: config.id === 'BH03' || config.id === 'BH27' ? true : false,
stinky: config.id === 'BH04' || config.id === 'BH30' ? true : false,
efficient: config.id === 'BC03' ? true : false,
// Efficient for BC03 (Puffin)
whaleFood: config.id === 'BH07' || config.id === 'BH13' ? true : false,
whaleChow: config.id === 'BH07' || config.id === 'BH13' ? true : false
});
}
// Add 8 Advanced Carnivore Cards (AC01-AC08)
var advancedCarnivoreConfigs = [{
id: 'AC01',
name: 'Pike',
waterType: 'fresh',
colorBand: 'grey',
climate: 'any',
special: true
}, {
id: 'AC02',
name: 'Great White Shark',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'any',
special: true,
bully: true,
seaBound: true //{c4_bully}
}, {
id: 'AC03',
name: 'Whale',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'any',
special: true
}, {
id: 'AC04',
name: 'Snakes!',
landType: 'any',
colorBand: 'brown',
climate: 'any',
special: true
}, {
id: 'AC05',
name: 'Bear',
landType: 'any',
colorBand: 'brown',
climate: 'any',
special: true
}, {
id: 'AC06',
name: 'Buzzard',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate/cold',
special: true
}, {
id: 'AC07',
name: 'Tiger',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate/hot',
special: true
}, {
id: 'AC08',
name: 'Leopard',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate/hot',
special: true
}];
for (var i = 0; i < advancedCarnivoreConfigs.length; i++) {
var config = advancedCarnivoreConfigs[i];
var subtype = config.waterType ? 'water' : 'land';
var waterType = config.waterType || null;
var landType = config.landType || null;
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'creature',
dietType: 'carnivore',
name: config.name,
id: config.id,
subtype: subtype,
waterType: waterType,
landType: landType,
colorBand: config.colorBand,
climateRequirement: config.climate,
special: config.special,
flying: config.id === 'AC06' ? true : false,
swimming: config.id === 'AC01' || config.id === 'AC02' || config.id === 'AC03' ? true : false,
efficient: config.id === 'AC02' || config.id === 'AC04' || config.id === 'AC06' ? true : false,
// Efficient for AC02, AC04, AC06
isWhale: config.id === 'AC03' ? true : false,
fatty: config.id === 'AC03' ? true : false,
seaBound: config.id === 'AC02' ? true : false,
bully: config.id === 'AC02' ? true : false //{c9_bully}
});
}
// Add 10 Basic Carnivore Cards (BC01-BC10)
var basicCarnivoreConfigs = [{
id: 'BC01',
name: 'King Fisher',
waterType: 'fresh',
colorBand: 'grey',
climate: 'any',
special: true
}, {
id: 'BC02',
name: 'Penguin',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/cold',
special: false
}, {
id: 'BC03',
name: 'Puffin',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/cold',
special: true,
fussy: true //{d7_fussy}
}, {
id: 'BC04',
name: 'Tuna',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/hot',
special: true,
hunterHunter: true //{dc_hh}
}, {
id: 'BC05',
name: 'Eagle',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'any',
special: true
}, {
id: 'BC06',
name: 'Chameleon',
landType: 'any',
colorBand: 'brown',
climate: 'any',
special: true
}, {
id: 'BC07',
name: 'Bat',
landType: 'flat',
colorBand: 'brown',
climate: 'hot',
special: true
}, {
//{9D_bc08}
id: 'BC08',
name: 'Albatross',
landType: 'hills',
colorBand: 'purple',
//{9E_bc08}
climate: 'cold',
//{9F_bc08}
special: true //{9G_bc08}
}, {
//{9D_bc09}
id: 'BC09',
name: 'Pendo',
landType: 'flat',
colorBand: 'brown',
//{9E_bc09}
climate: 'temperate/cold',
//{9F_bc09}
special: false //{9G_bc09}
}, {
//{9D_bc10}
id: 'BC10',
name: 'Stoat',
landType: 'hills',
colorBand: 'purple',
//{9E_bc10}
climate: 'hot/temperate',
//{9F_bc10}
special: false //{9G_bc10}
}];
for (var i = 0; i < basicCarnivoreConfigs.length; i++) {
var config = basicCarnivoreConfigs[i];
var subtype = config.waterType ? 'water' : 'land';
var waterType = config.waterType || null;
var landType = config.landType || null;
mainDeck.push({
type: 'basic',
level: 'Basic',
cardType: 'creature',
dietType: 'carnivore',
name: config.name,
id: config.id,
subtype: subtype,
waterType: waterType,
landType: landType,
colorBand: config.colorBand,
climateRequirement: config.climate,
special: config.special,
flying: config.id === 'BC01' || config.id === 'BC03' || config.id === 'BC05' || config.id === 'BC07' || config.id === 'BC08' || config.id === 'BC09' ? true : false,
swimming: config.id === 'BC04' ? true : false,
efficient: config.id === 'BC03' ? true : false,
// Efficient for BC03 (Puffin)//{dU_comment}
hunterHunter: config.id === 'BC04' ? true : false,
// Hunter-Hunter for BC04 (Tuna)
fussy: config.id === 'BC03' ? true : false //{dU_fussy}
// Fussy for BC03 (Puffin)
});
}
// Add 12 Advanced Herbivore Cards (AH01-AH12)
var advancedHerbivoreConfigs = [{
id: 'AH01',
name: 'Salmon',
waterType: 'fresh',
colorBand: 'grey',
//{ah01_terrain}
climate: 'any',
//{ah01_climate}
special: true //{ah01_special}
}, {
id: 'AH02',
name: 'Lobster',
waterType: 'sea',
colorBand: 'darkblue',
//{ah02_terrain}
climate: 'temperate',
//{ah02_climate}
special: false //{ah02_special}
}, {
id: 'AH03',
name: 'Herring',
waterType: 'sea',
colorBand: 'darkblue',
//{ah03_terrain}
climate: 'temperate/cold',
//{ah03_climate}
special: true //{ah03_special}
}, {
id: 'AH04',
name: 'Maceral',
waterType: 'sea',
colorBand: 'darkblue',
//{ah04_terrain}
climate: 'temperate/hot',
//{ah04_climate}
special: true //{ah04_special}
}, {
id: 'AH05',
name: 'Wildebeest',
landType: 'flat',
colorBand: 'brown',
//{ah05_terrain}
climate: 'hot',
//{ah05_climate}
special: false //{ah05_special}
}, {
id: 'AH06',
name: 'Moose',
landType: 'flat',
colorBand: 'brown',
//{ah06_terrain}
climate: 'cold',
//{ah06_climate}
special: false //{ah06_special}
}, {
id: 'AH07',
name: 'Gazelle',
landType: 'flat',
colorBand: 'brown',
//{ah07_terrain}
climate: 'temperate',
//{ah07_climate}
special: false //{ah07_special}
}, {
id: 'AH08',
name: 'Ibex',
landType: 'hills',
colorBand: 'purple',
//{ah08_terrain}
climate: 'hot',
//{ah08_climate}
special: false //{ah08_special}
}, {
id: 'AH09',
name: 'Arctic Hare',
landType: 'hills',
colorBand: 'purple',
//{ah09_terrain}
climate: 'cold',
//{ah09_climate}
special: false //{ah09_special}
}, {
id: 'AH10',
name: 'Elephant',
landType: 'hills',
colorBand: 'purple',
//{ah10_terrain}
climate: 'temperate',
//{ah10_climate}
special: true //{ah10_special}
}, {
id: 'AH11',
name: 'Mountain Goat',
landType: 'mountain',
colorBand: 'lightblue',
//{ah11_terrain}
climate: 'hot',
//{ah11_climate}
special: true //{ah11_special}
}, {
id: 'AH12',
name: 'Llama',
landType: 'mountain',
colorBand: 'lightblue',
//{ah12_terrain}
climate: 'temperate',
//{ah12_climate}
special: true //{ah12_special}
}, {
id: 'AH11_DOMINANT',
name: 'Dominant DNA',
landType: 'any',
colorBand: 'lightblue',
climate: 'hot',
special: true,
dominantDNA: true
}]; //{ah12_end}
for (var i = 0; i < advancedHerbivoreConfigs.length; i++) {
var config = advancedHerbivoreConfigs[i];
var subtype = config.waterType ? 'water' : 'land';
var waterType = config.waterType || null;
var landType = config.landType || null;
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'creature',
dietType: 'herbivore',
name: config.name,
id: config.id,
subtype: subtype,
waterType: waterType,
landType: landType,
colorBand: config.colorBand,
climateRequirement: config.climate,
special: config.special,
swimming: config.id === 'AC01' || config.id === 'AC02' || config.id === 'AC03' || config.id === 'AH01' || config.id === 'AH02' || config.id === 'AH03' || config.id === 'AH04' ? true : false,
tough: config.id === 'AH10' ? true : false,
efficient: config.id === 'AC02' || config.id === 'AC04' || config.id === 'AC06' ? true : false,
// Efficient for AC02, AC04, AC06
whaleFood: config.id === 'AH01' ? false : config.id === 'AH02' || config.id === 'AH03' || config.id === 'AH04' ? false : false
});
}
// Shuffle deck
shuffleDeck();
}
function shuffleDeck() {
for (var i = mainDeck.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = mainDeck[i];
mainDeck[i] = mainDeck[j];
mainDeck[j] = temp;
}
}
function shuffleEventDeck(eventDeck) {
for (var i = eventDeck.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = eventDeck[i];
eventDeck[i] = eventDeck[j];
eventDeck[j] = temp;
}
}
function showCardInfo(card) {
hoveredCard = card;
// Remove existing info text if any
if (cardInfoText && cardInfoText.parent) {
cardInfoText.parent.removeChild(cardInfoText);
}
var infoString = "";
if (card.creatureData) {
// Creature card information
var creature = card.creatureData;
if (creature) {
infoString = creature.name + " | Level: " + creature.level + " " + creature.dietType;
if (creature && creature.terrainRequirement && creature.terrainRequirement !== 'any') {
infoString += " | Terrain: " + creature.terrainRequirement;
}
if (creature && creature.climateRequirement && creature.climateRequirement !== 'any') {
infoString += " | Climate: " + creature.climateRequirement;
}
// Add special rules text for Tough
if (creature && creature.tough) {
infoString += " | Tough: Creature requires 3 or more extinction markers before it must become extinct";
}
if (creature && creature.squishy) {
infoString += " | Squishy: Creature requires only 1 or more extinction markers before it must become extinct";
}
if (creature && creature.stinky) {
infoString += " | Stinky: Discard all non-Stinky creatures. Phew!";
}
if (creature && creature.efficient) {
infoString += " | Efficient: This creature can make diagonal links as well as adjacent ones";
}
if (creature && creature.whaleFood) {
infoString += " | Whale chow!: This creature can only be linked to whales";
}
if (creature && creature.id === 'AC03') {
infoString += " | Whale: This creature can link to Krill and Plankton";
}
if (creature && creature.hunterHunter) {
infoString += " | Hunter-Hunter: This creature can only link to Carnivore cards NOT Herbivores!";
} //{fl_hh}
if (creature && creature.fussy) {
infoString += " | Fussy: This creature can only link to Basic Herbivore cards NOT Advanced Herbivores!";
} //{fl_fussy}
if (creature && creature.bully) {
infoString += " | Bully: This creature can create links to Basic Carnivores as well as Basic Herbivores!";
} //{fl_bully}
if (creature && creature.fatty) {
infoString += " | Fatty! Requires 3 links to support it and gives x2 the normal advanced carnivore score at end of game!";
}
if (creature && creature.seaBound) {
infoString += " | Sea-bound: This creature can only create links to other Water creatures!";
}
if (creature && creature.dominantDNA) {
infoString += " | Dominant DNA. Can evolve from Basic Carnivores or Basic Herbivores!";
}
}
} else if (card.terrainData) {
// Terrain card information
var terrain = card.terrainData;
if (terrain) {
infoString = terrain.name || terrain.level + " " + (terrain.landType || terrain.waterType);
infoString += " | Level: " + terrain.level + " terrain";
if (terrain.climateRequirement && terrain.climateRequirement !== 'any') {
infoString += " | Climate Required: " + terrain.climateRequirement;
}
// Add special rules text for AT15
if (terrain.id === 'AT15') {
infoString += " | Ancient: Draw 4 cards next turn";
} //{ej_special}
// Add special rules text for AT18
if (terrain.id === 'AT18') {
infoString += " | Hostile: Put 1 Carnivore card from the discard pile onto the top of the play deck if any are present";
} //{ej_special_AT18}
}
}
// Create info text at bottom of screen
cardInfoText = new Text2(infoString, {
size: 42,
fill: 0xFFFFFF,
wordWrap: true,
wordWrapWidth: 1800
});
cardInfoText.anchor.set(0.5, 1);
cardInfoText.x = 1024; // Center of screen
cardInfoText.y = 2700; // Bottom of screen
game.addChild(cardInfoText);
}
function hideCardInfo() {
if (cardInfoText && cardInfoText.parent) {
cardInfoText.parent.removeChild(cardInfoText);
cardInfoText = null;
}
hoveredCard = null;
}
function zoomCard(card) {
// If a different card was previously zoomed, clear it
if (currentHoveredCardForZoom && currentHoveredCardForZoom !== card) {
clearZoom();
}
// If card is already zoomed, do nothing
if (currentHoveredCardForZoom === card && zoomedCard) {
return;
}
// Clear existing zoomed card
if (zoomedCard && zoomedCard.parent) {
zoomedCard.parent.removeChild(zoomedCard);
zoomedCard = null;
}
// Track current hovered card
currentHoveredCardForZoom = card;
// Create a clone of the card for zooming
var clonedCard;
if (card.creatureData) {
clonedCard = new CreatureCard(card.creatureData);
} else if (card.terrainData) {
clonedCard = new TerrainCard(card.terrainData);
} else if (card.eventData) {
clonedCard = new EventCard(card.eventData);
} else {
return;
}
// Set the cloned card's initial scale to match the card being hovered
clonedCard.scale.set(card.scale.x, card.scale.y);
// Position cloned card
clonedCard.x = 550;
clonedCard.y = 580;
// Apply explicit sizing based on card type
if (card.terrainData) {
clonedCard.scale.set(1.27, 1.31);
} else {
clonedCard.scale.set(2.0, 2.0);
}
clonedCard.alpha = 1;
// Add to zoom container
zoomContainer.addChild(clonedCard);
zoomedCard = clonedCard;
}
function clearZoom() {
if (zoomedCard && zoomedCard.parent) {
zoomedCard.parent.removeChild(zoomedCard);
}
zoomedCard = null;
currentHoveredCardForZoom = null;
}
function clearZoomIfNoCards() {
// Clear zoom when hand is empty (between turns or after card play)
if (playerHand.length === 0 && zoomedCard) {
clearZoom();
currentlyHoveredCard = null;
}
}
function showPossibleMoves(card) {
if (card.creatureData) {
// Highlight valid terrain placement for creatures
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && canPlaceCreatureOnTerrain(card, terrain)) {
planetSlots[gridY][gridX].highlight();
}
}
}
} else if (card.terrainData) {
// Highlight valid terrain placement for advanced terrain
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var existingTerrain = planetBoard[gridY][gridX];
if (existingTerrain && canPlaceTerrainOnTerrain(card, existingTerrain)) {
planetSlots[gridY][gridX].highlight();
}
}
}
}
}
function hidePossibleMoves() {
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
if (planetSlots[gridY][gridX]) {
planetSlots[gridY][gridX].unhighlight();
}
}
}
}
function hasValidPlacements(cards) {
for (var i = 0; i < cards.length; i++) {
var card = cards[i];
if (card.creatureData) {
// Check creature placements
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && canPlaceCreatureOnTerrain(card, terrain)) {
return true;
}
}
}
} else if (card.terrainData) {
// Check terrain placements
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && canPlaceTerrainOnTerrain(card, terrain)) {
return true;
}
}
}
} else if (card.eventData) {
return true; // Event cards are always valid to play
}
}
return false;
}
function discardCards(cards) {
for (var i = 0; i < cards.length; i++) {
var card = cards[i];
// Remove from hand
var handIndex = playerHand.indexOf(card);
if (handIndex !== -1) {
playerHand.splice(handIndex, 1);
}
// Remove from game
if (card.parent) {
card.parent.removeChild(card);
}
// Extract raw card data and add to discard pile
var cardData = card.creatureData || card.terrainData || card.eventData;
discardPile.push(cardData);
}
updateHandPositions();
// Clear zoom if hand is now empty
clearZoomIfNoCards(); //{ip_new}
}
function drawCard() {
if (mainDeck.length === 0) {
endRound();
return null;
}
// Enforce maximum hand size of 3
if (playerHand.length >= maxHandSize) {
return null; // Hand is full
}
if (drawPhase === 'complete') {
return null; // Draw phase already complete
}
var cardData = mainDeck.pop();
var card;
// Create appropriate card type based on cardType
if (cardData.cardType === 'terrain') {
card = new TerrainCard(cardData);
// Scale terrain cards in hand to match creature cards
card.scaleX = 0.7;
card.scaleY = 0.7;
// Scale terrain cards in hand to match creature cards
card.scaleX = 0.7;
card.scaleY = 0.7;
} else {
// Default to creature card
card = new CreatureCard(cardData);
}
if (drawPhase === 'initial') {
// Initial draw phase - draw up to 3 cards
playerHand.push(card);
cardsDrawnInPhase.push(card);
cardsDrawnThisTurn++;
if (cardsDrawnInPhase.length >= 3) {
// Check if any of the 3 cards have valid placements
if (!hasValidPlacements(cardsDrawnInPhase)) {
// Discard all 3 cards and enter forced draw phase
discardCards(cardsDrawnInPhase);
cardsDrawnInPhase = [];
drawPhase = 'forced';
// Continue drawing in forced phase
return drawCard();
} else {
// At least one card has valid placement, end draw phase
drawPhase = 'complete';
}
}
} else if (drawPhase === 'forced') {
// Forced draw phase - draw one card at a time until playable
if (hasValidPlacements([card])) {
// This card can be played, add to hand and must be played
playerHand.push(card);
drawPhase = 'complete';
// Mark this card as must play
card.mustPlay = true;
} else {
// Card cannot be played, discard it and draw another
discardCards([card]);
return drawCard();
}
} else {
return null; // Draw phase complete
}
// Position card in hand
updateHandPositions();
game.addChild(card);
// Update UI
deckCountText.setText("Deck: " + mainDeck.length);
LK.getSound('cardDraw').play();
// Check for Stinky effect on newly drawn card
checkAndProcessStinkyEffect(); //{j8_stinky}
return card;
}
function startDrawPhase() {
if (mainDeck.length === 0) {
endRound();
return;
}
drawPhase = 'initial';
cardsDrawnInPhase = [];
cardsDrawnThisTurn = 0;
// Draw initial 3 cards
for (var i = 0; i < 3; i++) {
if (mainDeck.length > 0) {
drawCard();
}
}
// Check for Stinky creatures after drawing initial 3 cards
checkAndProcessStinkyEffect();
}
function checkAndProcessStinkyEffect() {
// Check if any creature in hand has Stinky attribute
var hasStinkyCreature = false;
var stinkyCreature = null;
for (var i = 0; i < playerHand.length; i++) {
//{stinky_check_1}
var card = playerHand[i];
if (card.creatureData && card.creatureData.stinky) {
//{stinky_check_2}
hasStinkyCreature = true;
stinkyCreature = card;
break;
} //{stinky_check_3}
} //{stinky_check_4}
// If Stinky creature found, discard all non-stinky creatures
if (hasStinkyCreature) {
//{stinky_process_1}
var toDiscard = [];
for (var j = 0; j < playerHand.length; j++) {
//{stinky_process_2}
var c = playerHand[j];
// Discard only if it has creatureData AND does NOT have stinky property true
if (c.creatureData && !c.creatureData.stinky) {
//{stinky_process_3}
toDiscard.push(c);
} //{stinky_process_4}
} //{stinky_process_5}
// Execute discard
if (toDiscard.length > 0) {
//{stinky_process_6}
discardCards(toDiscard);
} //{stinky_process_7}
} //{stinky_process_8}
// Check for event cards in hand after stinky processing and set flag
hasEventCardsInHand = false;
for (var k = 0; k < playerHand.length; k++) {
if (playerHand[k].eventData) {
hasEventCardsInHand = true;
break;
}
}
}
function checkAndShowEventCardDebugBox() {
// Check if any event cards are in hand
var eventCardsInHand = [];
for (var i = 0; i < playerHand.length; i++) {
//{debug_event_1}
if (playerHand[i].eventData) {
//{debug_event_2}
eventCardsInHand.push(playerHand[i]);
} //{debug_event_3}
} //{debug_event_4}
// If event cards found, show debug box
if (eventCardsInHand.length > 0) {
//{debug_event_5}
showEventCardDebugBox();
} //{debug_event_6}
} //{debug_event_7}
function showEventCardDebugBox() {
// Create debug notification box
var eventDebugBox = LK.getAsset('terrainLandFlat', {
//{debug_event_8}
anchorX: 0.5,
//{debug_event_9}
anchorY: 0.5 //{debug_event_10}
}); //{debug_event_11}
eventDebugBox.tint = 0xA020F0; // Purple for event cards
eventDebugBox.alpha = 0.95; //{debug_event_12}
eventDebugBox.scaleX = 2.0;
eventDebugBox.scaleY = 1.0;
eventDebugBox.x = 1024;
eventDebugBox.y = 1000;
game.addChild(eventDebugBox); //{debug_event_13}
// Create debug text
var debugText = new Text2("Event card/s in hand! Click the cards to resolve them.", {
size: 34,
fill: 0xFFFFFF
});
debugText.anchor.set(0.5, 0.5);
debugText.x = 0;
debugText.y = 0;
eventDebugBox.addChild(debugText);
game.eventDebugBox = eventDebugBox;
} //{debug_event_28}
function updateHandPositions() {
var handY = 2400;
var handStartX = 1024 - playerHand.length * 200 / 2 + 100;
for (var i = 0; i < playerHand.length; i++) {
var card = playerHand[i];
if (!card.isInPlay) {
tween(card, {
x: handStartX + i * 200,
y: handY
}, {
duration: 300
});
}
}
}
function canPlaceTerrainOnTerrain(advancedTerrainCard, basicTerrainCard) {
if (!advancedTerrainCard || !basicTerrainCard) {
return false;
}
var advancedTerrain = advancedTerrainCard.terrainData;
var basicTerrain = basicTerrainCard.terrainData;
// Check if this is actually an advanced terrain card
if (!advancedTerrain || advancedTerrain.level !== 'Advanced') {
return false;
}
// Check if target is a basic terrain card
if (!basicTerrain || basicTerrain.level !== 'Basic') {
return false;
}
// Check if terrain types match (land on land, water on water)
if (advancedTerrain.subtype !== basicTerrain.subtype) {
return false;
}
// Check if specific terrain types match
if (advancedTerrain.subtype === 'land') {
if (advancedTerrain.landType !== basicTerrain.landType) {
return false;
}
} else if (advancedTerrain.subtype === 'water') {
if (advancedTerrain.waterType !== basicTerrain.waterType) {
return false;
}
}
// Check climate requirements
if (advancedTerrain.climateRequirement && advancedTerrain.climateRequirement !== 'any') {
var climateMatches = false;
if (advancedTerrain.climateRequirement.indexOf('/') !== -1) {
// Handle multiple climate requirements (e.g., "temperate/hot")
var allowedClimates = advancedTerrain.climateRequirement.split('/');
for (var k = 0; k < allowedClimates.length; k++) {
if (allowedClimates[k] === basicTerrain.climate) {
climateMatches = true;
break;
}
}
} else {
// Single climate requirement
climateMatches = advancedTerrain.climateRequirement === basicTerrain.climate;
}
if (!climateMatches) {
return false;
}
}
return true;
}
function canPlaceCreatureOnTerrain(creatureCard, terrainCard) {
if (!creatureCard || !terrainCard) {
return false;
}
var creature = creatureCard.creatureData;
var terrain = terrainCard.terrainData;
// Check if this is actually a creature card
if (!creature) {
return false;
}
// Check terrain type matching based on creature's subtype
if (creature.subtype === 'water') {
// Creature requires water terrain
if (terrain.subtype !== 'water') {
return false;
}
// Check specific water type matching - creature waterType must match terrain waterType
if (creature.waterType && terrain.waterType && creature.waterType !== terrain.waterType) {
return false;
}
} else if (creature.subtype === 'land') {
// Creature requires land terrain
if (terrain.subtype !== 'land') {
return false;
}
// Check specific land type matching - allow 'any' as wildcard
if (creature.landType && creature.landType !== 'any' && terrain.landType && creature.landType !== terrain.landType) {
return false;
}
}
// If creature has no subtype or terrain type, it cannot be placed
if (!creature.subtype) {
return false;
}
// Check climate requirements - use basic terrain climate if advanced terrain is placed on basic
var terrainClimate = terrain.climate;
// If this is an advanced terrain on basic terrain, use the basic terrain's climate
if (terrainCard.basicTerrainUnderneath && terrainCard.basicTerrainUnderneath.terrainData) {
terrainClimate = terrainCard.basicTerrainUnderneath.terrainData.climate;
}
if (creature.climateRequirement && creature.climateRequirement !== 'any') {
var climateMatches = false;
if (creature.climateRequirement.indexOf('/') !== -1) {
// Handle multiple climate requirements (e.g., "temperate/hot")
var allowedClimates = creature.climateRequirement.split('/');
for (var k = 0; k < allowedClimates.length; k++) {
if (allowedClimates[k] === terrainClimate) {
climateMatches = true;
break;
}
}
} else {
// Single climate requirement
climateMatches = creature.climateRequirement === terrainClimate;
}
if (!climateMatches) {
return false;
}
}
// Advanced creatures can only be placed on advanced terrain
if (creature.level === 'Advanced') {
if (terrain.level !== 'Advanced') {
return false;
}
}
// Basic herbivores can only be placed on basic terrain (but advanced terrain counts as valid if it's on basic terrain)
if (creature.level === 'Basic' && creature.dietType === 'herbivore') {
// Allow placement on advanced terrain that's placed on basic terrain, or directly on basic terrain
if (terrain.level !== 'Basic' && !terrainCard.basicTerrainUnderneath) {
return false;
}
}
// Check creature stacking rules - verify top card in stack before allowing placement
if (terrainCard.creatureStack && terrainCard.creatureStack.length > 0) {
var topCreatureOnTerrain = terrainCard.creatureStack[terrainCard.creatureStack.length - 1];
// Prevent placing anything on advanced carnivores
if (topCreatureOnTerrain.creatureData.dietType === 'carnivore' && topCreatureOnTerrain.creatureData.level === 'Advanced') {
return false; // Cannot place anything on advanced carnivores
}
// Prevent placing Basic Herbivores on Advanced Carnivores (strict enforcement)
if (creature.level === 'Basic' && creature.dietType === 'herbivore' && topCreatureOnTerrain.creatureData.dietType === 'carnivore' && topCreatureOnTerrain.creatureData.level === 'Advanced') {
return false; // Cannot place Basic Herbivore on Advanced Carnivore
}
// Prevent placing Basic Herbivores on any other carnivore
if (creature.level === 'Basic' && creature.dietType === 'herbivore' && topCreatureOnTerrain.creatureData.dietType === 'carnivore' && topCreatureOnTerrain.creatureData.level === 'Basic') {
return false; // Cannot place Basic Herbivore on Basic Carnivore
} //{kH_basic}
}
// Basic carnivores can be placed on any terrain type (basic or advanced) - no restriction needed
// Check creature stacking rules
if (terrainCard.creatureStack.length > 0) {
var topCreatureInStack = terrainCard.creatureStack[terrainCard.creatureStack.length - 1];
// Basic creature stacking rules
if (creature.level === 'Basic') {
// Basic herbivore restrictions
if (creature.dietType === 'herbivore') {
if (topCreatureInStack.creatureData.level === 'Basic' && topCreatureInStack.creatureData.dietType === 'herbivore') {
return false; // Basic herbivores cannot be placed on stacks with other basic herbivores at top
}
if (topCreatureInStack.creatureData.level === 'Advanced' && topCreatureInStack.creatureData.dietType === 'herbivore') {
return false; // Basic herbivores cannot be placed on stacks with advanced herbivores at top
}
if (topCreatureInStack.creatureData.level === 'Basic' && topCreatureInStack.creatureData.dietType === 'carnivore') {
return false; // Basic herbivores cannot be placed on stacks with basic carnivores at top
}
if (topCreatureInStack.creatureData.level === 'Advanced' && topCreatureInStack.creatureData.dietType === 'carnivore') {
return false; // Basic herbivores cannot be placed on stacks with advanced carnivores at top
} //{l8_advanced}
}
// Basic carnivore restrictions
if (creature.dietType === 'carnivore') {
if (topCreatureInStack.creatureData.level === 'Basic' && topCreatureInStack.creatureData.dietType === 'carnivore') {
return false; // Basic carnivores cannot be placed on stacks with other basic carnivores at top
}
if (topCreatureInStack.creatureData.level === 'Advanced' && topCreatureInStack.creatureData.dietType === 'carnivore') {
return false; // Basic carnivores cannot be placed on stacks with advanced carnivores at top
}
if (topCreatureInStack.creatureData.level === 'Advanced' && topCreatureInStack.creatureData.dietType === 'herbivore') {
return false; // Basic carnivores cannot be placed on stacks with advanced herbivores at top
}
}
}
// Advanced creature stacking rules
else if (creature.level === 'Advanced') {
// Dominant DNA can be placed on Basic Carnivores or Basic Herbivores
if (creature.dominantDNA) {
var validTarget = topCreatureInStack.creatureData && topCreatureInStack.creatureData.level === 'Basic' && (topCreatureInStack.creatureData.dietType === 'herbivore' || topCreatureInStack.creatureData.dietType === 'carnivore');
if (!validTarget) {
return false; // Dominant DNA can only be placed on Basic Carnivores or Basic Herbivores
}
}
// Advanced herbivores can only be placed on advanced terrain with basic herbivores at top
else if (creature.dietType === 'herbivore') {
var validTarget = topCreatureInStack.creatureData && topCreatureInStack.creatureData.level === 'Basic' && topCreatureInStack.creatureData.dietType === 'herbivore';
if (!validTarget) {
return false;
}
}
// Advanced carnivores can be placed on stacks with basic carnivores OR advanced herbivores at top
else if (creature.dietType === 'carnivore') {
var validTarget = topCreatureInStack.creatureData && topCreatureInStack.creatureData.level === 'Basic' && topCreatureInStack.creatureData.dietType === 'carnivore' || topCreatureInStack.creatureData && topCreatureInStack.creatureData.level === 'Advanced' && topCreatureInStack.creatureData.dietType === 'herbivore';
if (!validTarget) {
return false;
}
}
}
} else {
// No creatures in stack - advanced creatures cannot be placed on empty terrain
if (creature.level === 'Advanced') {
return false; // Advanced creatures require existing creatures to stack on
}
}
return true;
}
function placeTerrainOnTerrain(advancedTerrainCard, basicTerrainCard, gridX, gridY) {
if (!canPlaceTerrainOnTerrain(advancedTerrainCard, basicTerrainCard)) {
return false;
}
// Remove card from hand
var handIndex = playerHand.indexOf(advancedTerrainCard);
if (handIndex !== -1) {
playerHand.splice(handIndex, 1);
}
// Set up advanced terrain card
advancedTerrainCard.gridX = gridX;
advancedTerrainCard.gridY = gridY;
advancedTerrainCard.isInPlay = true;
// Copy creature stack from basic terrain to advanced terrain
advancedTerrainCard.creatureStack = basicTerrainCard.creatureStack || [];
// Position advanced terrain card on top of basic terrain and adopt its dimensions
var targetX = basicTerrainCard.x;
var targetY = basicTerrainCard.y;
var targetScaleX = basicTerrainCard.scaleX;
var targetScaleY = basicTerrainCard.scaleY;
tween(advancedTerrainCard, {
x: targetX,
y: targetY,
scaleX: targetScaleX,
scaleY: targetScaleY
}, {
duration: 300
});
// Keep basic terrain underneath but update board references to advanced terrain
planetBoard[gridY][gridX] = advancedTerrainCard;
planetSlots[gridY][gridX].terrainCard = advancedTerrainCard;
// Store reference to basic terrain underneath
advancedTerrainCard.basicTerrainUnderneath = basicTerrainCard;
// Add advanced terrain to game at specific z-index (above basic terrain but below creatures)
var basicTerrainIndex = game.getChildIndex(basicTerrainCard);
game.addChildAt(advancedTerrainCard, basicTerrainIndex + 1);
// Check for invalid links when terrain changes (shouldn't affect creature types, but safety check)
if (advancedTerrainCard.creatureStack.length > 0) {
var topCreature = advancedTerrainCard.creatureStack[advancedTerrainCard.creatureStack.length - 1];
checkAndRemoveInvalidLinks(gridX, gridY, topCreature);
}
// Update creature positions if any exist and ensure they stay on top
for (var i = 0; i < advancedTerrainCard.creatureStack.length; i++) {
var creature = advancedTerrainCard.creatureStack[i];
// Store current position to prevent unwanted movement
var currentX = creature.x;
var currentY = creature.y;
// Remove and re-add creature to ensure it's on top
game.removeChild(creature);
game.addChild(creature);
// Keep creature at its current position - no animation needed
creature.x = currentX;
creature.y = currentY;
}
// Re-render all link lines to ensure they appear above the newly placed terrain
for (var linkIdx = 0; linkIdx < linkLines.length; linkIdx++) {
var linkLine = linkLines[linkIdx];
if (linkLine.parent) {
game.removeChild(linkLine);
game.addChild(linkLine);
}
}
// Update hand positions
updateHandPositions();
// Clear selection
selectedCard = null;
draggedCard = null;
// Reset draw phase after successful placement
if (advancedTerrainCard.mustPlay) {
drawPhase = 'complete';
}
// Check if AT15 was played and set flag for next turn
if (advancedTerrainCard.terrainData && advancedTerrainCard.terrainData.id === 'AT15') {
at15PlayedLastTurn = true;
}
// Check if AT18 was played and trigger special
if (advancedTerrainCard.terrainData && advancedTerrainCard.terrainData.id === 'AT18') {
processAT18Special();
} //{if_AT18}
// Check if this was the last event card or must play card
if (eventCardsToPlay.length > 0) {
//{ig}</antml>
// Remove this card from event cards to play
var eventIndex = eventCardsToPlay.indexOf(advancedTerrainCard);
if (eventIndex !== -1) {
eventCardsToPlay.splice(eventIndex, 1);
}
// If no more event cards to play, clear mustPlayCard and check for phase progression
if (eventCardsToPlay.length === 0) {
mustPlayCard = null;
// Update event cards tracking
hasEventCardsInHand = false;
// Discard remaining non-event cards and progress to dusk
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
if (!playerHand[i].eventData) {
remainingCards.push(playerHand[i]);
}
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
}
} else if (advancedTerrainCard.mustPlay || mustPlayCard === advancedTerrainCard) {
// This was a must play card, clear it and progress to dusk
mustPlayCard = null;
// Discard remaining cards
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
remainingCards.push(playerHand[i]);
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
} else {
// Regular card played, discard remaining cards and progress to dusk
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
remainingCards.push(playerHand[i]);
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
}
// Reset turn
cardsDrawnThisTurn = 0;
LK.getSound('cardPlace').play();
// Show adjust links button after card placement
showAdjustLinksButton();
return true;
}
function checkAndRemoveInvalidLinks(gridX, gridY, newTopCreature) {
// Check all links that involve creatures at this position
var invalidLinks = [];
// Check links FROM carnivores at this position
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
if (creature.creatureData.dietType === 'carnivore') {
// Check each active link from this carnivore
for (var j = creature.activeLinks.length - 1; j >= 0; j--) {
var link = creature.activeLinks[j];
var targetHerbivore = link.target;
// If the new top creature is a carnivore and the target is now covered, link becomes invalid
if (newTopCreature && newTopCreature.creatureData.dietType === 'carnivore' && targetHerbivore.gridX === gridX && targetHerbivore.gridY === gridY) {
invalidLinks.push({
carnivore: creature,
herbivore: targetHerbivore
});
}
}
}
}
}
// Check links TO herbivores at this position (if new creature is carnivore, herbivores below become invalid targets)
if (newTopCreature && newTopCreature.creatureData.dietType === 'carnivore') {
// Find all links pointing to herbivores at this position that are now covered
for (var linkIndex = linkLines.length - 1; linkIndex >= 0; linkIndex--) {
var linkLine = linkLines[linkIndex];
if (linkLine.herbivore.gridX === gridX && linkLine.herbivore.gridY === gridY) {
// This herbivore is now covered by a carnivore, so links to it are invalid
invalidLinks.push({
carnivore: linkLine.carnivore,
herbivore: linkLine.herbivore
});
}
}
}
// Remove invalid links with fall-off animation
for (var k = 0; k < invalidLinks.length; k++) {
var invalidLink = invalidLinks[k];
animateLinkFallOff(invalidLink.carnivore, invalidLink.herbivore);
}
}
function animateLinkFallOff(carnivore, herbivore) {
// Find the link line to animate
var linkLineToRemove = null;
for (var i = 0; i < linkLines.length; i++) {
if (linkLines[i].carnivore === carnivore && linkLines[i].herbivore === herbivore && !linkLines[i].isFallingOff) {
linkLineToRemove = linkLines[i];
break;
}
}
if (linkLineToRemove) {
// Mark link as falling off immediately to prevent duplicate animations
linkLineToRemove.isFallingOff = true;
// Remove from arrays immediately to clear logical ghost link
removeLink(carnivore, herbivore);
// Animate the link falling off
tween(linkLineToRemove, {
alpha: 0,
scaleY: linkLineToRemove.scaleY * 0.1,
y: linkLineToRemove.y + 50
}, {
duration: 500,
onFinish: function onFinish() {
// Remove the visual line after animation
if (linkLineToRemove.parent) linkLineToRemove.parent.removeChild(linkLineToRemove);
}
});
} else {
// No visual line found, just remove the link immediately
removeLink(carnivore, herbivore);
}
}
function placeCreatureOnStack(creatureCard, terrainCard, gridX, gridY) {
if (!canPlaceCreatureOnTerrain(creatureCard, terrainCard)) {
return false;
}
// Remove card from hand
var handIndex = playerHand.indexOf(creatureCard);
if (handIndex !== -1) {
playerHand.splice(handIndex, 1);
}
// Check for invalid links before placing the new creature
checkAndRemoveInvalidLinks(gridX, gridY, creatureCard);
// When ANY carnivore is played onto another creature, remove all links to and from the creature being covered
if (creatureCard.creatureData.dietType === 'carnivore' && terrainCard.creatureStack.length > 0) {
var creatureBelow = terrainCard.creatureStack[terrainCard.creatureStack.length - 1];
// Remove all links FROM the creature being covered (if it's a carnivore)
if (creatureBelow.creatureData.dietType === 'carnivore') {
for (var linkIdx = creatureBelow.activeLinks.length - 1; linkIdx >= 0; linkIdx--) {
animateLinkFallOff(creatureBelow, creatureBelow.activeLinks[linkIdx].target);
}
}
// Remove all links TO the creature being covered (if it's a herbivore or carnivore)
for (var linkIdx = linkLines.length - 1; linkIdx >= 0; linkIdx--) {
if (linkLines[linkIdx].herbivore === creatureBelow || linkLines[linkIdx].carnivore === creatureBelow && creatureBelow.creatureData.dietType === 'carnivore') {
animateLinkFallOff(linkLines[linkIdx].carnivore, linkLines[linkIdx].herbivore);
}
}
}
// If placing an advanced herbivore on top of a basic herbivore, remove all links to the basic herbivore below
if (creatureCard.creatureData.dietType === 'herbivore' && creatureCard.creatureData.level === 'Advanced') {
if (terrainCard.creatureStack.length > 0) {
var basicHerbivoreBelow = terrainCard.creatureStack[terrainCard.creatureStack.length - 1];
if (basicHerbivoreBelow.creatureData.dietType === 'herbivore' && basicHerbivoreBelow.creatureData.level === 'Basic') {
// Remove all links pointing to the basic herbivore below
for (var linkIdx = linkLines.length - 1; linkIdx >= 0; linkIdx--) {
if (linkLines[linkIdx].herbivore === basicHerbivoreBelow) {
animateLinkFallOff(linkLines[linkIdx].carnivore, basicHerbivoreBelow);
}
}
}
}
}
// Push existing creatures down in the stack (move them south)
var stackOffset = 20;
for (var i = 0; i < terrainCard.creatureStack.length; i++) {
var existingCreature = terrainCard.creatureStack[i];
// Clear extinction markers when another creature is placed on top
existingCreature.extinctionMarkers = 0;
existingCreature.updateExtinctionMarkers();
tween(existingCreature, {
y: existingCreature.y + stackOffset
}, {
duration: 200
});
}
// Add new creature to top of stack
terrainCard.creatureStack.push(creatureCard);
creatureCard.gridX = gridX;
creatureCard.gridY = gridY;
creatureCard.isInPlay = true;
// Position new creature at the top position (where first creature would go)
var targetX = terrainCard.x;
var colorBarHeight = 42;
var targetY = terrainCard.y - terrainCard.height * terrainCard.scaleY / 2 + colorBarHeight + 150; // Position even lower, just below the color bar with 150px offset
// Calculate scale needed to match terrain card dimensions completely
var terrainWidth = 252; // Terrain card asset width
var terrainHeight = 336; // Terrain card asset height
var creatureWidth = 160; // Creature card asset width
var creatureHeight = 220; // Creature card asset height
// Scale to match terrain dimensions exactly, but reduce Y scale by half the color bar height with moderate scaling
var targetScaleX = terrainWidth / creatureWidth * terrainCard.scaleX;
var targetScaleY = (terrainHeight - colorBarHeight / 2) / creatureHeight * terrainCard.scaleY * 0.95; // Moderate 5% reduction to sit flush with color bar
// Start with small scale and animate up to target scale like advanced terrain
creatureCard.scaleX = 0.1;
creatureCard.scaleY = 0.1;
tween(creatureCard, {
x: targetX,
y: targetY,
scaleX: targetScaleX,
scaleY: targetScaleY
}, {
duration: 300
});
// Apply creature effects
applyCreatureEffect(creatureCard);
// Create link markers for carnivores when placed
creatureCard.createLinkMarkers();
// Update hand positions
updateHandPositions();
// Clear selection
selectedCard = null;
draggedCard = null;
// Reset draw phase after successful placement
if (creatureCard.mustPlay) {
drawPhase = 'complete';
}
// Check if this was the last event card or must play card
if (eventCardsToPlay.length > 0) {
// Remove this card from event cards to play
var eventIndex = eventCardsToPlay.indexOf(creatureCard);
if (eventIndex !== -1) {
eventCardsToPlay.splice(eventIndex, 1);
}
// If no more event cards to play, clear mustPlayCard and check for phase progression
if (eventCardsToPlay.length === 0) {
mustPlayCard = null;
// Update event cards tracking
hasEventCardsInHand = false;
// Discard remaining non-event cards and progress to dusk
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
if (!playerHand[i].eventData) {
remainingCards.push(playerHand[i]);
}
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
}
} else if (creatureCard.mustPlay || mustPlayCard === creatureCard) {
// This was a must play card, clear it and progress to dusk
mustPlayCard = null;
// Discard remaining cards
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
remainingCards.push(playerHand[i]);
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
} else {
// Regular card played, discard remaining cards and progress to dusk
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
remainingCards.push(playerHand[i]);
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
}
// Reset turn
cardsDrawnThisTurn = 0;
LK.getSound('cardPlace').play();
// Show adjust links button after card placement
showAdjustLinksButton();
return true;
}
function applyCreatureEffect(creatureCard) {
var creature = creatureCard.creatureData;
// Stack effects can be implemented here without health mechanics
}
function getAdjacentTerrains(gridX, gridY, includeDiagonals) {
var neighbors = [];
var directions = [{
x: -1,
y: 0
}, {
x: 1,
y: 0
}, {
x: 0,
y: -1
}, {
x: 0,
y: 1
}];
// Add diagonals if the creature is Efficient
if (includeDiagonals) {
directions.push({
x: -1,
y: -1
}, {
x: 1,
y: -1
}, {
x: -1,
y: 1
}, {
x: 1,
y: 1
});
}
for (var i = 0; i < directions.length; i++) {
var newX = gridX + directions[i].x;
var newY = gridY + directions[i].y;
if (newX >= 0 && newX < planetWidth && newY >= 0 && newY < planetHeight) {
neighbors.push(planetBoard[newY][newX]);
}
}
return neighbors;
}
function createLink(carnivore, herbivore) {
if (!carnivore || !herbivore) {
return false;
}
if (carnivore.creatureData.dietType !== 'carnivore') {
return false;
}
// Check if carnivore and herbivore are adjacent
if (!areCreaturesAdjacent(carnivore, herbivore)) {
return false;
}
// Additional check: ensure we're linking to the top creature only
var herbivoreGridX = herbivore.gridX;
var herbivoreGridY = herbivore.gridY;
var terrain = planetBoard[herbivoreGridY][herbivoreGridX];
if (terrain && terrain.creatureStack.length > 0) {
var topCreature = terrain.creatureStack[terrain.creatureStack.length - 1];
if (topCreature !== herbivore) {
return false; // Can only link to top creature in stack
}
}
// For basic carnivores, check if already linked to this herbivore
if (carnivore.creatureData.level === 'Basic') {
for (var i = 0; i < carnivore.activeLinks.length; i++) {
if (carnivore.activeLinks[i].target === herbivore) {
return false; // Basic carnivores can only have one link per herbivore
}
}
}
// Check if this is a valid link target considering all special rules
if (!isValidLinkTarget(carnivore, herbivore)) {
return false; // Cannot link due to special rules (Whale food, etc)
} //{mL_new}
// Find an unlinked marker
var availableMarker = null;
for (var i = 0; i < carnivore.linkMarkers.length; i++) {
if (!carnivore.linkMarkers[i].isLinked) {
availableMarker = carnivore.linkMarkers[i];
break;
}
}
if (!availableMarker) {
return false;
} // No available link markers
// Create the link
availableMarker.isLinked = true;
availableMarker.targetHerbivore = herbivore;
availableMarker.tint = 0x00FF00; // Green for linked
// Add to active links
carnivore.activeLinks.push({
marker: availableMarker,
target: herbivore
});
// Create visual link line
createLinkLine(carnivore, herbivore);
return true;
}
function removeLink(carnivore, herbivore) {
if (!carnivore || !herbivore) {
return false;
}
// Find and remove the link
for (var i = carnivore.activeLinks.length - 1; i >= 0; i--) {
var link = carnivore.activeLinks[i];
if (link.target === herbivore) {
link.marker.isLinked = false;
link.marker.targetHerbivore = null;
link.marker.tint = 0xFF0000; // Red for unlinked
carnivore.activeLinks.splice(i, 1);
// Remove visual link line
removeLinkLine(carnivore, herbivore);
return true;
}
}
return false;
}
function isValidLinkTarget(carnivore, herbivore) {
if (!carnivore || !herbivore) {
return false;
}
if (carnivore.creatureData.seaBound) {
if (herbivore.creatureData.subtype !== 'water') {
return false; // Sea-bound creatures can only link to water creatures
}
}
var targetIsCarnivore = herbivore.creatureData.dietType === 'carnivore';
if (targetIsCarnivore) {
// Target is a carnivore
if (carnivore.creatureData.hunterHunter || carnivore.creatureData.bully) {
// Hunter-Hunter or Bully can link to carnivores
if (carnivore.creatureData.bully && herbivore.creatureData.level !== 'Basic') {
return false; // Bully can only link to Basic Carnivores
}
return true;
}
return false; // Cannot link to carnivore unless Hunter-Hunter or Bully
} else {
// Target is a herbivore
if (carnivore.creatureData.hunterHunter) {
return false; // Hunter-Hunter cannot link to herbivores
} //{n4_hh}
} //{n4_hh_end}
if (carnivore.creatureData.fussy) {
if (herbivore.creatureData.level !== 'Basic') {
return false; // Fussy can only link to Basic Herbivores
} //{n4_fussy}
} //{n4_fussy_end}
if (herbivore.creatureData.whaleFood) {
if (carnivore.creatureData.id !== 'AC03') {
return false; // Cannot link to Whale food unless carnivore is Whale
}
}
return true;
}
function areCreaturesAdjacent(creature1, creature2) {
if (!creature1.isInPlay || !creature2.isInPlay) {
return false;
}
var dx = Math.abs(creature1.gridX - creature2.gridX);
var dy = Math.abs(creature1.gridY - creature2.gridY);
// Check if creature1 has Efficient attribute
var creature1HasEfficient = creature1.creatureData && creature1.creatureData.efficient;
// Adjacent means exactly one grid space away (orthogonal or diagonal if Efficient)
if (creature1HasEfficient) {
// Efficient creatures can link to adjacent (including diagonal) creatures
return dx === 1 && dy === 0 || dx === 0 && dy === 1 || dx === 1 && dy === 1;
} else {
// Non-Efficient creatures can only link orthogonally (up, down, left, right)
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
}
}
function createLinkLine(carnivore, herbivore) {
// Count how many links ALREADY exist between this carnivore and herbivore BEFORE adding the new one
var existingLinkCount = 0;
for (var i = 0; i < linkLines.length; i++) {
if (linkLines[i].carnivore === carnivore && linkLines[i].herbivore === herbivore) {
existingLinkCount++;
}
} //{e4_duplicate}
// Create visual arrow line using a thicker rectangle
var line = LK.getAsset('terrainLandFlat', {
anchorX: 0,
anchorY: 0.5
});
line.tint = 0xFFFF00; // Yellow link line by default
line.alpha = 0.8;
// Position arrow starting from slightly inside carnivore border towards herbivore
var dx = herbivore.x - carnivore.x;
var dy = herbivore.y - carnivore.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
// Calculate starting position slightly inside carnivore border (about 20px inward)
var startOffsetX = Math.cos(angle) * 20;
var startOffsetY = Math.sin(angle) * 20;
var startX = carnivore.x + startOffsetX;
var startY = carnivore.y + startOffsetY;
// Calculate gap distance (reduce total distance by both card radii plus small gap)
var gapDistance = distance - 100; // 50px from each card edge creates gap
line.x = startX;
line.y = startY;
line.rotation = angle;
line.scaleX = gapDistance / 252; // Scale based on gap distance
line.scaleY = 0.08; // Thicker line (8% of original height)
line.carnivore = carnivore;
line.herbivore = herbivore;
line.linkIndex = existingLinkCount; // Track which link number this is
// Calculate perpendicular offset for multiple links to avoid stacking
if (existingLinkCount >= 1) {
// 2nd link goes one way (+90 deg), 3rd link goes the opposite way (-90 deg)
var perpendicularAngle = existingLinkCount === 1 ? angle + Math.PI / 2 : angle - Math.PI / 2;
var offsetDistance = 30; // Offset distance in pixels
var offsetX = Math.cos(perpendicularAngle) * offsetDistance;
var offsetY = Math.sin(perpendicularAngle) * offsetDistance;
// Apply offset to line position
line.x += offsetX;
line.y += offsetY;
// Change line color and create indicator based on link number
var indicatorString = "";
if (existingLinkCount === 1) {
line.tint = 0xFF00FF; // Magenta for 2nd link
indicatorString = "X2";
} else {
line.tint = 0x00BFFF; // Deep Sky Blue for 3rd link
indicatorString = "X3";
}
// Add the text indicator
var linkIndicator = new Text2(indicatorString, {
size: 20,
fill: 0xFFFFFF
});
linkIndicator.anchor.set(0.5, 0.5);
linkIndicator.x = 0;
linkIndicator.y = 0;
line.addChild(linkIndicator);
linkIndicator.lineOwner = line;
}
game.addChild(line);
linkLines.push(line);
}
function removeLinkLine(carnivore, herbivore) {
for (var i = linkLines.length - 1; i >= 0; i--) {
var line = linkLines[i];
if (line.carnivore === carnivore && line.herbivore === herbivore) {
if (!line.isFallingOff) {
if (line.parent) line.parent.removeChild(line);
}
linkLines.splice(i, 1);
return; // Remove only ONE link and return
}
}
}
function highlightValidLinkTargets(carnivore) {
// Clear existing highlights
clearLinkHighlights();
if (!carnivore || carnivore.creatureData.dietType !== 'carnivore') {
return;
}
// Check if carnivore has Efficient attribute
var carnivoreHasEfficient = carnivore.creatureData && carnivore.creatureData.efficient;
// Find adjacent herbivores that can be linked to (including diagonals for Efficient)
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
// Only consider the top creature in each stack
var topCreature = terrain.creatureStack[terrain.creatureStack.length - 1];
if (areCreaturesAdjacent(carnivore, topCreature)) {
// Check if this is a valid link target considering all special rules
if (!isValidLinkTarget(carnivore, topCreature)) {
// Cannot link to this herbivore due to special rules
continue;
} //{nx_new}
var canCreateLink = false;
// For basic carnivores, check if already linked to this herbivore
if (carnivore.creatureData.level === 'Basic') {
var hasExistingLink = false;
for (var j = 0; j < carnivore.activeLinks.length; j++) {
if (carnivore.activeLinks[j].target === topCreature) {
hasExistingLink = true;
break;
}
}
if (!hasExistingLink) {
canCreateLink = true;
}
} else {
// Advanced carnivores can always create links if they have available markers
var hasAvailableMarker = false;
for (var k = 0; k < carnivore.linkMarkers.length; k++) {
if (!carnivore.linkMarkers[k].isLinked) {
hasAvailableMarker = true;
break;
}
}
if (hasAvailableMarker) {
canCreateLink = true;
}
}
if (canCreateLink) {
// Create highlight around this herbivore
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
// Use different colors for diagonal highlights if Efficient
if (carnivoreHasEfficient) {
var dx = Math.abs(carnivore.gridX - topCreature.gridX);
var dy = Math.abs(carnivore.gridY - topCreature.gridY);
if (dx === 1 && dy === 1) {
// Diagonal link for Efficient creature
highlight.tint = 0x00FFFF; // Cyan for diagonal
} else {
// Orthogonal link
highlight.tint = 0x00FF00; // Green for orthogonal
}
} else {
highlight.tint = 0x00FF00; // Green highlight
}
highlight.alpha = 0.3;
highlight.scaleX = topCreature.scaleX * 1.2;
highlight.scaleY = topCreature.scaleY * 1.2;
highlight.x = topCreature.x;
highlight.y = topCreature.y;
highlight.targetCreature = topCreature;
game.addChild(highlight);
linkHighlights.push(highlight);
}
}
}
}
}
}
function clearLinkHighlights() {
for (var i = 0; i < linkHighlights.length; i++) {
if (linkHighlights[i].parent) {
linkHighlights[i].parent.removeChild(linkHighlights[i]);
}
}
linkHighlights = [];
}
function updateLinkMarkerStates() {
// Update all link markers based on their state and game phase
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
// ONLY process the top creature in the stack - creatures underneath are not active
var isTopCreature = i === terrain.creatureStack.length - 1;
if (!isTopCreature) {
// Skip creatures that are underneath other creatures - they cannot interact with links
continue;
} //{f9_underneath}
if (creature.creatureData.dietType === 'carnivore') {
// Check if this carnivore will become extinct (insufficient links)
var willBecomeExtinct = creature.activeLinks.length < creature.linkRequirement;
for (var j = 0; j < creature.linkMarkers.length; j++) {
var marker = creature.linkMarkers[j];
if (gamePhase === 'dusk' && linkAdjustmentMode) {
// During dusk phase with adjustment mode active, make markers interactive
if (marker.isLinked) {
marker.tint = 0x00FF00; // Green for linked
// Make linked markers draggable to remove links
marker.down = function (x, y, obj) {
draggedLinkMarker = this;
draggedLinkCarnivore = this.carnivore;
};
} else {
// Check if this marker can be legally linked
var hasValidTargets = false;
var isEfficient = creature.creatureData && creature.creatureData.efficient;
var adjacentTerrains = getAdjacentTerrains(gridX, gridY, isEfficient);
for (var k = 0; k < adjacentTerrains.length; k++) {
var adjTerrain = adjacentTerrains[k];
if (adjTerrain && adjTerrain.creatureStack.length > 0) {
// Only consider the top creature in each adjacent stack
var topHerbivore = adjTerrain.creatureStack[adjTerrain.creatureStack.length - 1];
if (isValidLinkTarget(creature, topHerbivore)) {
// For basic carnivores, check if already linked to this herbivore
if (creature.creatureData.level === 'Basic') {
var hasExistingLink = false;
for (var m = 0; m < creature.activeLinks.length; m++) {
if (creature.activeLinks[m].target === topHerbivore) {
hasExistingLink = true;
break;
}
}
if (!hasExistingLink) {
hasValidTargets = true;
break;
}
} else {
// Advanced carnivores can always link if they have available markers
hasValidTargets = true;
break;
}
}
}
if (hasValidTargets) {
break;
}
}
if (hasValidTargets) {
marker.tint = 0xFFFF00; // Yellow for linkable
// Make unlinked but valid markers draggable
marker.down = function (x, y, obj) {
draggedLinkMarker = this;
draggedLinkCarnivore = this.carnivore;
};
} else {
marker.tint = 0x808080; // Gray for non-linkable
marker.down = function (x, y, obj) {
// Do nothing for non-linkable markers
};
// Add N/A text
if (!marker.naText) {
marker.naText = new Text2("N/A", {
size: 8,
fill: 0xFFFFFF
});
marker.naText.anchor.set(0.5, 0.5);
marker.naText.x = 0;
marker.naText.y = 0;
marker.addChild(marker.naText);
}
}
}
} else {
// Outside dusk phase or not in adjustment mode, markers are not interactive
if (marker.isLinked) {
marker.tint = 0x00FF00; // Green for linked
} else {
marker.tint = 0xFF0000; // Red for unlinked
}
// Remove down handler and N/A text if present
marker.down = function (x, y, obj) {};
if (marker.naText && marker.naText.parent) {
marker.naText.parent.removeChild(marker.naText);
marker.naText = null;
}
}
// Flash markers if carnivore will become extinct
if (willBecomeExtinct) {
// Stop any existing flash animation
tween.stop(marker, {
alpha: true
});
// Start flashing animation
tween(marker, {
alpha: 0.3
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(marker, {
alpha: 1
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart the flash cycle if still extinct
if (creature.activeLinks.length < creature.linkRequirement) {
updateLinkMarkerStates();
}
}
});
}
});
} else {
// Stop flashing if no longer about to become extinct
tween.stop(marker, {
alpha: true
});
marker.alpha = 1;
}
}
}
}
}
}
}
}
function autoCreateLinks() {
// Auto-create mandatory links for carnivores
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var carnivore = terrain.creatureStack[i];
if (carnivore.creatureData.dietType === 'carnivore') {
// Try to create links for this carnivore
while (carnivore.activeLinks.length < carnivore.linkRequirement) {
var linkCreated = false;
// Find adjacent herbivores
var isEfficient = carnivore.creatureData && carnivore.creatureData.efficient;
var adjacentTerrains = getAdjacentTerrains(gridX, gridY, isEfficient);
for (var j = 0; j < adjacentTerrains.length && !linkCreated; j++) {
var adjTerrain = adjacentTerrains[j];
if (adjTerrain && adjTerrain.creatureStack.length > 0) {
// Only consider the top creature in each adjacent stack
var topHerbivore = adjTerrain.creatureStack[adjTerrain.creatureStack.length - 1];
if (topHerbivore.creatureData.dietType === 'herbivore') {
// Check if we can create a link
var hasExistingLink = false;
for (var l = 0; l < carnivore.activeLinks.length; l++) {
if (carnivore.activeLinks[l].target === topHerbivore) {
hasExistingLink = true;
break;
}
}
if (!hasExistingLink && createLink(carnivore, topHerbivore)) {
linkCreated = true;
}
}
}
}
if (!linkCreated) {
break;
} // No more links possible
}
}
}
}
}
}
}
function processLinkConsequences() {
// Hide the adjust links button when leaving dusk phase
hideAdjustLinksButton();
// NOTE: Extinction markers for herbivores are only processed during night phase
// This function only handles cleanup, not marker assignment
}
function processNightPhase() {
// Show visual cue that night phase is processing
showPhaseTransition("Night Phase - Checking links...", function () {
// Add extinction markers to carnivores without sufficient links
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
// ONLY process the top creature in the stack - creatures underneath are not active
var isTopCreature = i === terrain.creatureStack.length - 1;
if (!isTopCreature) {
// Skip creatures that are underneath other creatures
continue;
} //{ho_underneath}
if (creature.creatureData.dietType === 'carnivore') {
// For carnivores: check if they have sufficient links AT THIS MOMENT
if (creature.activeLinks.length < creature.linkRequirement) {
creature.extinctionMarkers += 1; // Only 1 marker per turn regardless of shortage
creature.updateExtinctionMarkers();
}
} else if (creature.creatureData.dietType === 'herbivore') {
// For herbivores: ONLY add extinction marker if it has 0 markers AND has excess links
// Once a herbivore has 1+ extinction markers, do NOT add more during Night phase
if (creature.extinctionMarkers === 0) {
//{t8_new}
// Count links pointing to them AT THIS MOMENT ONLY
var linksToThisHerbivore = 0;
for (var j = 0; j < linkLines.length; j++) {
//{t9_new}
if (linkLines[j].herbivore === creature) {
//{ta_new}
linksToThisHerbivore++;
} //{tb_new}
} //{tc_new}
// Add extinction marker ONLY if excess links exist
var excessLinks = linksToThisHerbivore - creature.safeLinks;
if (excessLinks > 0) {
//{td_new}
// Add exactly 1 marker if this herbivore has any excess links
creature.extinctionMarkers += 1;
creature.updateExtinctionMarkers(); //{te_new}
} //{tf_new}
} //{tg_new}
}
}
}
}
}
// Auto-progress to end turn phase
gamePhase = 'end';
processEndTurnPhase();
});
}
function processExtinctions() {
// Remove creatures with 2 or more extinction markers
var extinctCreatures = [];
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = terrain.creatureStack.length - 1; i >= 0; i--) {
var creature = terrain.creatureStack[i];
var extinctionThreshold = 2;
if (creature.creatureData && creature.creatureData.tough) {
extinctionThreshold = 3;
} else if (creature.creatureData && creature.creatureData.squishy) {
extinctionThreshold = 1;
}
if (creature.extinctionMarkers >= extinctionThreshold) {
extinctCreatures.push(creature);
terrain.creatureStack.splice(i, 1);
// Tween all creatures that were physically positioned below the dead creature UP by 20 pixels
for (var j = 0; j < i; j++) {
var creatureBelow = terrain.creatureStack[j];
tween(creatureBelow, {
y: creatureBelow.y - 20
}, {
duration: 200
});
}
creaturesWentExtinct = true; // Mark that creatures went extinct this round
scryDeniedNextRound = true; // Deny scry for next round when extinction occurs
// Clear extinction markers when creature leaves play
creature.extinctionMarkers = 0;
creature.updateExtinctionMarkers();
// Remove all links involving this creature with fall-off animation
if (creature.creatureData.dietType === 'carnivore') {
for (var j = creature.activeLinks.length - 1; j >= 0; j--) {
animateLinkFallOff(creature, creature.activeLinks[j].target);
}
} else {
// Remove links from carnivores to this herbivore with fall-off animation
for (var j = linkLines.length - 1; j >= 0; j--) {
if (linkLines[j].herbivore === creature) {
animateLinkFallOff(linkLines[j].carnivore, creature);
}
}
}
creature.die();
}
}
}
}
}
}
function createEventDecks() {
// Create 10 basic event cards
basicEventDeck = [];
for (var i = 1; i <= 10; i++) {
basicEventDeck.push({
type: 'basic',
cardType: 'event',
level: 'Basic',
name: 'Basic Event ' + i,
effect: 'environmental'
});
}
// Create 10 advanced event cards
advancedEventDeck = [];
for (var i = 1; i <= 10; i++) {
advancedEventDeck.push({
type: 'advanced',
cardType: 'event',
level: 'Advanced',
name: 'Advanced Event ' + i,
effect: 'catastrophic'
});
}
}
function addEventCardToDeck(creatureLevel) {
var eventCard = null;
// Determine which deck to draw from based on creature level
if (creatureLevel === 'Advanced') {
if (advancedEventDeck.length > 0) {
eventCard = advancedEventDeck.pop();
} else if (basicEventDeck.length > 0) {
// Fallback to basic events if advanced is empty
eventCard = basicEventDeck.pop();
}
} else {
if (basicEventDeck.length > 0) {
eventCard = basicEventDeck.pop();
} else if (advancedEventDeck.length > 0) {
// Fallback to advanced events if basic is empty
eventCard = advancedEventDeck.pop();
}
}
// If event card was drawn, add to discard pile (only draw 1 card per death)
if (eventCard) {
discardPile.push(eventCard);
}
}
function processDawnPhase() {
// Show visual cue that dawn phase is processing
showPhaseTransition("Dawn Phase - Processing...", function () {
gamePhase = 'draw';
processDrawPhase(); // Auto-progress to draw phase
});
}
function processDrawPhase() {
// Draw exactly 3 cards (or 4 if AT15 was played last turn), handling deck reshuffling as needed
var cardsToDrawTotal = 3;
if (at15PlayedLastTurn) {
cardsToDrawTotal = 4;
at15PlayedLastTurn = false; // Reset flag after using it
}
// Step 1: Create validHandFound boolean
var validHandFound = false;
// Step 2: Create master while loop that runs as long as validHandFound is false
while (!validHandFound) {
// Step 3: Draw cards until the player's hand reaches the required amount
var continueDrawing = true;
var cardsDrawnThisPhase = 0;
while (continueDrawing && playerHand.length < cardsToDrawTotal) {
// Check if deck is empty, reshuffle if needed
if (mainDeck.length === 0) {
// Shuffle discard pile to become new deck
if (discardPile.length > 0) {
mainDeck = discardPile.slice();
discardPile = [];
shuffleDeck();
currentRound++;
deckCountText.setText("Deck: " + mainDeck.length);
// Check for end game condition after round increment
if (currentRound >= 6) {
// Game ends immediately
LK.showYouWin();
return;
}
// Check and trigger scry status at beginning of new round
// Only activate scry if no extinctions occurred last round
if (!scryDeniedNextRound) {
scryTokenActive = true;
} //{ht_scry}
creaturesWentExtinct = false;
scryDeniedNextRound = false;
} else {
// No cards available, stop drawing
continueDrawing = false;
break; // Exit drawing loop if both deck and discard are empty
}
}
// Draw one card if deck has cards
if (mainDeck.length > 0) {
var cardData = mainDeck.pop();
var card = createCardFromData(cardData);
playerHand.push(card);
game.addChild(card);
// Update hand positions after each card is added
updateHandPositions();
cardsDrawnThisPhase++;
} else {
// No more cards in deck, stop drawing
continueDrawing = false;
}
}
// Step 4: After the hand is filled, update the hand positions. If the hand is completely empty, break the master loop.
updateHandPositions();
if (playerHand.length === 0) {
// Hand is empty because both deck and discard are empty
break; // Exit master while loop
} //{uv_new}
// Check for Stinky effect after drawing cards
checkAndProcessStinkyEffect();
// Update hand positions again in case Stinky effect discarded adjacent cards
updateHandPositions();
// Step 5: Check if the current hand has valid placements
if (hasValidPlacements(playerHand)) {
// Hand has at least one valid placement, set validHandFound to true to exit master loop
validHandFound = true; //{uC_new}
} else {
//{uD_new}
// Hand is unplayable, discard all cards and loop will repeat to draw a fresh hand
discardCards(playerHand.slice()); //{uB_new}
} //{uE_new}
} //{uF_new}
// Step 6: After the master loop finishes, update the deck and discard UI text, play the draw sound, and change the game phase to 'noon'
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
LK.getSound('cardDraw').play();
gamePhase = 'noon';
}
function createCardFromData(cardData) {
var card;
if (cardData.cardType === 'terrain') {
card = new TerrainCard(cardData);
card.scaleX = 0.6;
card.scaleY = 0.6;
} else if (cardData.cardType === 'event') {
// Create event card using EventCard class
card = new EventCard(cardData);
} else {
// Default to creature card
card = new CreatureCard(cardData);
}
return card;
}
function processNoonPhase() {
// Check for event cards in hand - they must be played first
eventCardsToPlay = [];
for (var i = 0; i < playerHand.length; i++) {
if (playerHand[i].eventData) {
eventCardsToPlay.push(playerHand[i]);
}
}
if (eventCardsToPlay.length > 0) {
// Player must play event cards first
mustPlayCard = eventCardsToPlay[0]; // Force first event card
return;
}
// Check if player has valid moves
var validCards = [];
for (var i = 0; i < playerHand.length; i++) {
if (hasValidPlacements([playerHand[i]])) {
validCards.push(playerHand[i]);
}
}
if (validCards.length === 0 && playerHand.length === 3) {
// Show no playable cards notification
showNoPlayableCardsNotification();
return;
}
// Player can choose which card to play (if multiple valid options)
if (validCards.length > 1) {
// Discard non-chosen cards (this would be handled by player interaction)
// For now, just continue - player will choose
}
}
function showPhaseTransition(message, callback) {
// Clear any existing transition timer
if (phaseTransitionTimer) {
LK.clearTimeout(phaseTransitionTimer);
}
// Create transition message
var transitionText = new Text2(message, {
size: 40,
fill: 0xFFFF00
});
transitionText.anchor.set(0.5, 0.5);
transitionText.x = 1024;
transitionText.y = 1366;
transitionText.alpha = 0;
game.addChild(transitionText);
// Fade in message
tween(transitionText, {
alpha: 1
}, {
duration: 500,
onFinish: function onFinish() {
// Show message for 1 second, then fade out and execute callback
phaseTransitionTimer = LK.setTimeout(function () {
tween(transitionText, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
if (transitionText.parent) {
transitionText.parent.removeChild(transitionText);
}
if (callback) {
callback();
}
}
});
}, 1000);
}
});
}
function processDuskPhase() {
// Show visual cue that dusk phase is processing
showPhaseTransition("Dusk Phase - Arrange links...", function () {
// Show the adjust links button at start of dusk phase
showAdjustLinksButton();
// Update link marker states to make them interactive
updateLinkMarkerStates();
// Player now manually arranges links - no auto-progression
// Phase will advance when player clicks next phase button
});
}
function processEndTurnPhase() {
// Show visual cue that end turn phase is processing
showPhaseTransition("End Turn - Processing extinctions...", function () {
// Process extinctions
processExtinctions();
// Extinction markers persist - they are NOT cleared here
turnNumber++;
gamePhase = 'dawn'; // Start next turn
processDawnPhase(); // Auto-progress to next dawn phase
});
}
function endRound() {
currentRound++;
if (currentRound > maxRounds) {
// Game complete
LK.showYouWin();
return;
}
// Reshuffle deck
createInitialDeck();
// Reset turn counter and draw phase
cardsDrawnThisTurn = 0;
drawPhase = 'complete';
cardsDrawnInPhase = [];
// Update UI
deckCountText.setText("Deck: " + mainDeck.length);
}
// Draw card button functionality
var drawButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
var drawButtonText = new Text2("DRAW", {
size: 32,
fill: 0xFFFFFF
});
drawButtonText.anchor.set(0.5, 0.5);
drawButton.addChild(drawButtonText);
drawButton.x = 1700;
drawButton.y = 2400;
game.addChild(drawButton);
drawButton.down = function () {
if (gamePhase === 'draw') {
processDrawPhase();
}
};
// Phase management buttons - redesigned for proper turn flow
var nextPhaseButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 0.8
});
nextPhaseButton.tint = 0x32CD32; // Green for next phase
var nextPhaseButtonText = new Text2("NEXT PHASE", {
size: 24,
fill: 0xFFFFFF
});
nextPhaseButtonText.anchor.set(0.5, 0.5);
nextPhaseButton.addChild(nextPhaseButtonText);
nextPhaseButton.x = 500;
nextPhaseButton.y = 2400;
game.addChild(nextPhaseButton);
var nextPhaseWarningBox = null;
function getCreaturesInDanger() {
var creaturesInDanger = [];
for (var gridY = 0; gridY < planetHeight; gridY++) {
//{kA_danger}
for (var gridX = 0; gridX < planetWidth; gridX++) {
//{kB_danger}
var terrain = planetBoard[gridY][gridX]; //{kC_danger}
if (terrain && terrain.creatureStack.length > 0) {
//{kD_danger}
for (var i = 0; i < terrain.creatureStack.length; i++) {
//{kE_danger}
var creature = terrain.creatureStack[i]; //{kF_danger}
// ONLY check top creatures in stack
var isTopCreature = i === terrain.creatureStack.length - 1; //{kG_danger}
if (!isTopCreature) {
//{kH_danger}
continue; //{kI_danger}
} //{kJ_danger}
// Check herbivores for excess links
if (creature.creatureData.dietType === 'herbivore') {
//{kK_danger}
var linksToThisHerbivore = 0; //{kL_danger}
for (var j = 0; j < linkLines.length; j++) {
//{kM_danger}
if (linkLines[j].herbivore === creature) {
//{kN_danger}
linksToThisHerbivore++; //{kO_danger}
} //{kP_danger}
} //{kQ_danger}
var excessLinks = linksToThisHerbivore - creature.safeLinks; //{kR_danger}
if (excessLinks > 0) {
//{kS_danger}
creaturesInDanger.push(creature); //{kT_danger}
} //{kU_danger}
} //{kV_danger}
// Check carnivores for insufficient links
else if (creature.creatureData.dietType === 'carnivore') {
//{kW_danger}
if (creature.activeLinks.length < creature.linkRequirement) {
//{kX_danger}
creaturesInDanger.push(creature); //{kY_danger}
} //{kZ_danger}
} //{l0_danger}
} //{l1_danger}
} //{l2_danger}
} //{l3_danger}
} //{l4_danger}
return creaturesInDanger; //{l5_danger}
} //{l6_danger}
function showNextPhaseWarning() {
if (nextPhaseWarningBox && nextPhaseWarningBox.parent) {
//{l7_danger}
return; // Warning already showing
} //{l8_danger}
// Create warning box
nextPhaseWarningBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
//{l9_danger}
anchorY: 0.5 //{la_danger}
}); //{lb_danger}
nextPhaseWarningBox.tint = 0xFF8C00; // Orange background
nextPhaseWarningBox.alpha = 0.95;
nextPhaseWarningBox.scaleX = 2.0;
nextPhaseWarningBox.scaleY = 1.2;
nextPhaseWarningBox.x = 500;
nextPhaseWarningBox.y = 2100;
game.addChild(nextPhaseWarningBox);
// Create warning text
var warningText = new Text2("Warning! These creatures are in danger!\nAre you sure you want to end the turn?", {
size: 20,
//{lc_danger}
fill: 0xFFFFFF //{ld_danger}
}); //{le_danger}
warningText.anchor.set(0.5, 0.5);
warningText.x = 0;
warningText.y = 0;
nextPhaseWarningBox.addChild(warningText);
} //{lf_danger}
function hideNextPhaseWarning() {
if (nextPhaseWarningBox && nextPhaseWarningBox.parent) {
//{lg_danger}
nextPhaseWarningBox.parent.removeChild(nextPhaseWarningBox); //{lh_danger}
nextPhaseWarningBox = null;
} //{li_danger}
} //{lj_danger}
function highlightCreaturesInDanger(creaturesInDanger) {
for (var i = 0; i < creaturesInDanger.length; i++) {
//{lk_danger}
var creature = creaturesInDanger[i]; //{ll_danger}
tween.stop(creature, {
//{lm_danger}
tint: true //{ln_danger}
}); //{lo_danger}
tween(creature, {
//{lp_danger}
tint: 0xFFFF00 //{lq_danger}
}, {
//{lr_danger}
duration: 200 //{ls_danger}
}); //{lt_danger}
} //{lu_danger}
} //{lv_danger}
function unhighlightCreaturesInDanger(creaturesInDanger) {
for (var i = 0; i < creaturesInDanger.length; i++) {
//{lw_danger}
var creature = creaturesInDanger[i]; //{lx_danger}
tween.stop(creature, {
//{ly_danger}
tint: true //{lz_danger}
}); //{mA_danger}
tween(creature, {
//{mB_danger}
tint: 0xFFFFFF //{mC_danger}
}, {
//{mD_danger}
duration: 200 //{mE_danger}
}); //{mF_danger}
} //{mG_danger}
} //{mH_danger}
nextPhaseButton.down = function () {
// Exit link adjustment mode if active and hide adjust links button
if (linkAdjustmentMode) {
deactivateLinkAdjustmentMode();
}
// Always hide adjust links button when next phase is clicked
hideAdjustLinksButton();
// Hide warning box when button is pressed
hideNextPhaseWarning(); //{mI_danger}
// Disable button completely if events must be played
if (gamePhase === 'noon' && hasEventCardsInHand) {
return;
}
// Prevent skipping card play if the player holds valid normal cards
if (gamePhase === 'draw' || gamePhase === 'noon' && playerHand.length > 0 && hasValidPlacements(playerHand)) {
// Show a safe, harmless fading text warning instead of the destructive discard loop
if (!game.safeSkipWarning) {
var safeWarning = new Text2("You have playable cards! You must play one.", {
size: 40,
fill: 0xFF0000
});
safeWarning.anchor.set(0.5, 0.5);
safeWarning.x = 1024;
safeWarning.y = 1366;
game.addChild(safeWarning);
game.safeSkipWarning = safeWarning;
tween(safeWarning, {
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
if (safeWarning.parent) safeWarning.parent.removeChild(safeWarning);
game.safeSkipWarning = null;
}
});
}
return;
}
if (gamePhase === 'dawn') {
processDawnPhase();
} else if (gamePhase === 'draw') {
processDrawPhase();
} else if (gamePhase === 'noon') {
// Check if player has event cards that must be played first
if (eventCardsToPlay.length > 0 || mustPlayCard) {
// Must play event cards or mandatory card first
return;
}
gamePhase = 'dusk';
processDuskPhase();
} else if (gamePhase === 'dusk') {
// Apply consequences for over-hunted herbivores
processLinkConsequences();
clearLinkHighlights();
gamePhase = 'night';
processNightPhase();
} else if (gamePhase === 'night') {
processNightPhase();
} else if (gamePhase === 'end') {
processEndTurnPhase();
}
};
function updateDangerHighlights() {
var inDangerList = [];
if (gamePhase === 'noon' || gamePhase === 'dusk') {
inDangerList = getCreaturesInDanger();
}
for (var gridY = 0; gridY < planetHeight; gridY++) {
var _loop = function _loop() {
terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack && terrain.creatureStack.length > 0) {
topCreature = terrain.creatureStack[terrain.creatureStack.length - 1];
isInDanger = false;
for (i = 0; i < inDangerList.length; i++) {
if (inDangerList[i] === topCreature) {
isInDanger = true;
break;
}
}
if (isInDanger) {
// Only trigger the highlight creation once
if (!topCreature.isDangerFlashing) {
// Safely pulse the halo's alpha recursively without touching the card's tint
var _flashHalo = function flashHalo() {
if (!topCreature.isDangerFlashing || !topCreature.dangerHalo) return;
tween(topCreature.dangerHalo, {
alpha: 0.8
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!topCreature.isDangerFlashing || !topCreature.dangerHalo) return;
tween(topCreature.dangerHalo, {
alpha: 0.2
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: _flashHalo
});
}
});
};
topCreature.isDangerFlashing = true;
// Create halo AS A CHILD so it inherits placement movement and scaling
if (!topCreature.dangerHalo) {
halo = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
halo.tint = 0xFF0000;
halo.alpha = 0.2;
// Scale relative to the card size
halo.scaleX = 1.15;
halo.scaleY = 1.15;
halo.x = 0;
halo.y = 0;
// Add behind the card graphics
topCreature.addChildAt(halo, 0);
topCreature.dangerHalo = halo;
}
_flashHalo();
}
} else {
if (topCreature.isDangerFlashing) {
topCreature.isDangerFlashing = false;
if (topCreature.dangerHalo && topCreature.dangerHalo.parent) {
tween.stop(topCreature.dangerHalo);
topCreature.dangerHalo.parent.removeChild(topCreature.dangerHalo);
topCreature.dangerHalo = null;
}
}
}
}
},
terrain,
topCreature,
isInDanger,
i,
halo;
for (var gridX = 0; gridX < planetWidth; gridX++) {
_loop();
}
}
// Process global warning text safely
if (gamePhase === 'noon' && inDangerList.length > 0) {
if (!game.dangerWarningText || !game.dangerWarningText.parent) {
var _flashText = function flashText() {
if (!game.dangerWarningText || !game.dangerWarningText.parent) return;
tween(game.dangerWarningText, {
alpha: 0.3
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!game.dangerWarningText || !game.dangerWarningText.parent) return;
tween(game.dangerWarningText, {
alpha: 1
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: _flashText
});
}
});
};
var warningText = new Text2("Warning! Creature/s are in danger!", {
size: 64,
fill: 0xFF0000
});
warningText.anchor.set(0.5, 0);
warningText.x = 1024;
warningText.y = 130;
game.addChild(warningText);
game.dangerWarningText = warningText;
_flashText();
}
} else {
if (game.dangerWarningText && game.dangerWarningText.parent) {
tween.stop(game.dangerWarningText);
game.dangerWarningText.parent.removeChild(game.dangerWarningText);
game.dangerWarningText = null;
}
}
}
var gameStatusText = new Text2("Round: 1 | Phase: DAWN | Turn: 1", {
size: 40,
fill: 0xFFFF00
});
gameStatusText.anchor.set(0.5, 0);
gameStatusText.x = 1024;
gameStatusText.y = 60;
game.addChild(gameStatusText);
// Link adjustment system variables
var linkAdjustmentMode = false;
var adjustLinkButton = null;
var cardsWithAdjustableLinks = [];
var currentlyAdjustingCard = null;
function createAdjustLinksButton() {
if (adjustLinkButton && adjustLinkButton.parent) {
adjustLinkButton.parent.removeChild(adjustLinkButton);
}
adjustLinkButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 0.8
});
adjustLinkButton.tint = 0xFFD700; // Gold for adjust links
var adjustButtonText = new Text2("Adjust Links?", {
size: 20,
fill: 0x000000
});
adjustButtonText.anchor.set(0.5, 0.5);
adjustLinkButton.addChild(adjustButtonText);
adjustLinkButton.x = 250; // Left of next phase button
adjustLinkButton.y = 2400;
game.addChild(adjustLinkButton);
adjustLinkButton.down = function () {
activateLinkAdjustmentMode();
};
}
function showAdjustLinksButton() {
createAdjustLinksButton();
}
function hideAdjustLinksButton() {
if (adjustLinkButton && adjustLinkButton.parent) {
adjustLinkButton.parent.removeChild(adjustLinkButton);
adjustLinkButton = null;
}
}
function activateLinkAdjustmentMode() {
linkAdjustmentMode = true;
currentlyAdjustingCard = null;
cardsWithAdjustableLinks = [];
// Find all carnivores on the board
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
if (creature.creatureData.dietType === 'carnivore') {
cardsWithAdjustableLinks.push(creature);
}
}
}
}
}
// Create and display personal "Alter Links" buttons on all carnivore cards
for (var i = 0; i < cardsWithAdjustableLinks.length; i++) {
var card = cardsWithAdjustableLinks[i];
card.createPersonalAlterLinksButton();
}
}
function deactivateLinkAdjustmentMode() {
linkAdjustmentMode = false;
// Remove personal alter buttons from all cards and restore card colors
for (var i = 0; i < cardsWithAdjustableLinks.length; i++) {
var card = cardsWithAdjustableLinks[i];
card.tint = 0xFFFFFF; // Return to normal color
// Remove personal alter button
if (card.personalAlterButton && card.personalAlterButton.parent) {
card.personalAlterButton.parent.removeChild(card.personalAlterButton);
card.personalAlterButton = null;
}
}
cardsWithAdjustableLinks = [];
currentlyAdjustingCard = null;
}
// Handle card dragging and link highlighting on hold
game.move = function (x, y, obj) {
if (draggedCard) {
draggedCard.x = x;
draggedCard.y = y;
// Ensure dragged card is always on top
game.removeChild(draggedCard);
game.addChild(draggedCard);
// Highlights are already shown from the card's down event
} else if (draggedLinkMarker && gamePhase === 'dusk' && linkAdjustmentMode) {
// Handle link marker dragging - highlight valid targets only while holding
var carnivore = draggedLinkMarker.carnivore;
highlightValidLinkTargets(carnivore);
// Create temporary visual line from carnivore to cursor
// Remove any existing temp line
if (game.tempLinkLine && game.tempLinkLine.parent) {
game.tempLinkLine.parent.removeChild(game.tempLinkLine);
}
// Create new temp line
var tempLine = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.01,
scaleY: 1
});
tempLine.tint = 0xFFFF00; // Yellow temp line
tempLine.alpha = 0.5;
var dx = x - carnivore.x;
var dy = y - carnivore.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
tempLine.x = carnivore.x + dx / 2;
tempLine.y = carnivore.y + dy / 2;
tempLine.rotation = angle;
tempLine.scaleY = distance / 336;
game.addChild(tempLine);
game.tempLinkLine = tempLine;
}
};
game.up = function (x, y, obj) {
if (draggedCard) {
var placed = false;
// Check if card was dropped on valid terrain
for (var gridY = 0; gridY < planetHeight && !placed; gridY++) {
for (var gridX = 0; gridX < planetWidth && !placed; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain) {
if (draggedCard.creatureData && canPlaceCreatureOnTerrain(draggedCard, terrain)) {
if (Math.abs(terrain.x - x) < 140 && Math.abs(terrain.y - y) < 168) {
placed = placeCreatureOnStack(draggedCard, terrain, gridX, gridY);
}
} else if (draggedCard.terrainData && canPlaceTerrainOnTerrain(draggedCard, terrain)) {
if (Math.abs(terrain.x - x) < 140 && Math.abs(terrain.y - y) < 168) {
placed = placeTerrainOnTerrain(draggedCard, terrain, gridX, gridY);
}
}
}
}
}
// If not placed, return to original position
if (!placed && originalCardPosition) {
tween(draggedCard, originalCardPosition, {
duration: 300
});
}
// Clear all highlights
hidePossibleMoves();
draggedCard = null;
originalCardPosition = null;
} else if (draggedLinkMarker && draggedLinkCarnivore && gamePhase === 'dusk' && linkAdjustmentMode) {
// Handle link marker release with new system
var linkCreated = false;
// Remove temporary line
if (game.tempLinkLine && game.tempLinkLine.parent) {
game.tempLinkLine.parent.removeChild(game.tempLinkLine);
game.tempLinkLine = null;
}
// Check if released over a valid herbivore
for (var i = 0; i < linkHighlights.length; i++) {
var highlight = linkHighlights[i];
if (highlight.targetCreature) {
var creature = highlight.targetCreature;
var distance = Math.sqrt(Math.pow(creature.x - x, 2) + Math.pow(creature.y - y, 2));
if (distance < 100) {
// Within range of the herbivore
// Create the link
if (createLink(draggedLinkCarnivore, creature)) {
linkCreated = true;
break;
}
}
}
}
// Clear highlights and reset drag state
clearLinkHighlights();
draggedLinkMarker = null;
draggedLinkCarnivore = null;
}
};
function processAT18Special() {
// Find all carnivore cards in discard pile
at18CarnivoreList = [];
for (var i = 0; i < discardPile.length; i++) {
//{at18_loop}
var cardData = discardPile[i];
if (cardData.cardType === 'creature' && cardData.dietType === 'carnivore') {
//{at18_check}
at18CarnivoreList.push(cardData);
} //{at18_found}
} //{at18_end_loop}
at18Active = true;
if (at18CarnivoreList.length === 0) {
// No carnivores in discard pile - show message
showAT18NoCarnivoredMessage();
} else {
//{at18_else}
// Display carnivores for selection
displayAT18CarnivoreSelection();
} //{at18_display_end}
} //{at18_process_end}
function showAT18NoCarnivoredMessage() {
// Create notification box
var notificationBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
//{at18_no_x}
anchorY: 0.5 //{at18_no_y}
}); //{at18_no_asset}
notificationBox.tint = 0x4169E1; // Blue background for info
notificationBox.alpha = 0.9;
notificationBox.scaleX = 2.5;
notificationBox.scaleY = 1.5;
notificationBox.x = 1024;
notificationBox.y = 1366;
game.addChild(notificationBox);
// Create notification text
var notificationText = new Text2("No Carnivore cards in Discard pile - Special does not apply", {
size: 28,
//{at18_no_text_size}
fill: 0xFFFFFF //{at18_no_text_fill}
}); //{at18_no_text}
notificationText.anchor.set(0.5, 0.5);
notificationText.x = 0;
notificationText.y = 0;
notificationBox.addChild(notificationText);
// Make the box clickable to dismiss
notificationBox.down = function () {
if (notificationBox.parent) {
notificationBox.parent.removeChild(notificationBox);
} //{at18_no_dismiss}
at18Active = false;
}; //{at18_no_down}
} //{at18_no_function_end}
function displayAT18CarnivoreSelection() {
// Create scry UI header
var scryHeader = new Text2("Pick 1 to add back onto the main deck", {
size: 32,
//{at18_header_size}
fill: 0xFFFFFF //{at18_header_fill}
}); //{at18_header_text}
scryHeader.anchor.set(0.5, 0);
scryHeader.x = 1024;
scryHeader.y = 300;
game.addChild(scryHeader);
scryHeader.at18Header = true;
// Display each carnivore card in a selectable list format
var cardYOffset = 500;
var cardYSpacing = 280;
for (var i = 0; i < at18CarnivoreList.length; i++) {
//{at18_display_loop}
var cardData = at18CarnivoreList[i];
var cardContainer = new CreatureCard(cardData);
cardContainer.scaleX = 0.6;
cardContainer.scaleY = 0.6;
cardContainer.x = 512 + i % 2 * 600;
cardContainer.y = cardYOffset + Math.floor(i / 2) * cardYSpacing;
cardContainer.cardDataIndex = i;
cardContainer.at18Selectable = true;
// Add click handler to select this card
cardContainer.down = function (x, y, obj) {
var selectedIndex = this.cardDataIndex;
selectAT18Carnivore(selectedIndex);
}; //{at18_selectable_down}
game.addChild(cardContainer);
} //{at18_display_loop_end}
} //{at18_display_function_end}
function selectAT18Carnivore(cardIndex) {
if (cardIndex < 0 || cardIndex >= at18CarnivoreList.length) {
return;
}
// Get the selected carnivore card data
var selectedCardData = at18CarnivoreList[cardIndex];
// Remove from discard pile
for (var i = discardPile.length - 1; i >= 0; i--) {
//{at18_remove_loop}
if (discardPile[i] === selectedCardData || discardPile[i].id === selectedCardData.id && discardPile[i].cardType === selectedCardData.cardType && discardPile[i].dietType === selectedCardData.dietType) {
//{at18_remove_check}
discardPile.splice(i, 1);
break;
} //{at18_remove_end}
} //{at18_remove_loop_end}
// Add to top of main deck
mainDeck.push(selectedCardData);
// Clear all AT18 UI elements
clearAT18UI();
// Update deck count
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
at18Active = false;
} //{at18_select_function_end}
function clearAT18UI() {
// Remove all AT18 selectable cards from game
for (var i = game.children.length - 1; i >= 0; i--) {
//{at18_clear_loop}
var child = game.children[i];
if (child.at18Selectable) {
//{at18_clear_check}
game.removeChild(child);
} //{at18_clear_removed}
if (child.at18Header) {
//{at18_clear_header}
game.removeChild(child);
} //{at18_clear_header_removed}
} //{at18_clear_loop_end}
} //{at18_clear_function_end}
function activateScry() {
if (!scryTokenActive || scryMode || mainDeck.length < 3) {
return;
}
scryMode = true;
scryStep = 1;
scryCards = [];
// Pop 3 cards from mainDeck and create them
for (var i = 0; i < 3; i++) {
var cardData = mainDeck.pop();
var card = createCardFromData(cardData);
scryCards.push(card);
// Do NOT push to playerHand - only to scryCards
game.addChild(card);
}
// Position cards in center of screen side-by-side
var scryCardPositions = [512, 1024, 1536];
for (var i = 0; i < scryCards.length; i++) {
scryCards[i].x = scryCardPositions[i];
scryCards[i].y = 1000;
scryCards[i].scaleX = 1.5;
scryCards[i].scaleY = 1.5;
// Assign down function to each card
scryCards[i].down = function (card) {
return function (x, y, obj) {
processScrySelection(card);
};
}(scryCards[i]);
}
// Create header text
scryHeaderText = new Text2("SCRY: Select 1 card to put on the\nBOTTOM of the main deck", {
size: 56,
fill: 0xFFFF00,
align: 'center'
});
scryHeaderText.anchor.set(0.5, 0);
scryHeaderText.x = 1024;
scryHeaderText.y = 120;
game.addChild(scryHeaderText);
}
function processScrySelection(selectedCard) {
// Find the index of selectedCard in scryCards
var cardIndex = -1;
for (var i = 0; i < scryCards.length; i++) {
if (scryCards[i] === selectedCard) {
cardIndex = i;
break;
}
}
if (cardIndex === -1) {
return; // Card not found
}
// Extract clean copy of card data
var cardData = selectedCard.creatureData || selectedCard.terrainData || selectedCard.eventData;
var cleanCardData = {};
cleanCardData.type = cardData.type;
cleanCardData.level = cardData.level;
cleanCardData.cardType = cardData.cardType;
if (cardData.dietType) cleanCardData.dietType = cardData.dietType;
if (cardData.name) cleanCardData.name = cardData.name;
if (cardData.terrainRequirement) cleanCardData.terrainRequirement = cardData.terrainRequirement;
if (cardData.climateRequirement) cleanCardData.climateRequirement = cardData.climateRequirement;
if (cardData.subtype) cleanCardData.subtype = cardData.subtype;
if (cardData.landType) cleanCardData.landType = cardData.landType;
if (cardData.waterType) cleanCardData.waterType = cardData.waterType;
if (cardData.climate) cleanCardData.climate = cardData.climate;
if (cardData.id) cleanCardData.id = cardData.id;
if (cardData.colorBand) cleanCardData.colorBand = cardData.colorBand;
if (scryStep === 1) {
// Put card on BOTTOM of deck
mainDeck.unshift(cleanCardData);
// Remove from screen
if (selectedCard.parent) {
selectedCard.parent.removeChild(selectedCard);
}
// Remove from scryCards array
scryCards.splice(cardIndex, 1);
// Update header text
scryHeaderText.setText("SCRY: Select 1 card to DISCARD.\nThe last card goes on TOP of the deck.");
scryStep = 2;
} else if (scryStep === 2) {
// Put card in discard pile
discardPile.push(cleanCardData);
// Remove from screen
if (selectedCard.parent) {
selectedCard.parent.removeChild(selectedCard);
}
// Remove from scryCards array
scryCards.splice(cardIndex, 1);
// Now process the last remaining card automatically
if (scryCards.length === 1) {
var lastCard = scryCards[0];
var lastCardData = lastCard.creatureData || lastCard.terrainData || lastCard.eventData;
var cleanLastCardData = {};
cleanLastCardData.type = lastCardData.type;
cleanLastCardData.level = lastCardData.level;
cleanLastCardData.cardType = lastCardData.cardType;
if (lastCardData.dietType) cleanLastCardData.dietType = lastCardData.dietType;
if (lastCardData.name) cleanLastCardData.name = lastCardData.name;
if (lastCardData.terrainRequirement) cleanLastCardData.terrainRequirement = lastCardData.terrainRequirement;
if (lastCardData.climateRequirement) cleanLastCardData.climateRequirement = lastCardData.climateRequirement;
if (lastCardData.subtype) cleanLastCardData.subtype = lastCardData.subtype;
if (lastCardData.landType) cleanLastCardData.landType = lastCardData.landType;
if (lastCardData.waterType) cleanLastCardData.waterType = lastCardData.waterType;
if (lastCardData.climate) cleanLastCardData.climate = lastCardData.climate;
if (lastCardData.id) cleanLastCardData.id = lastCardData.id;
if (lastCardData.colorBand) cleanLastCardData.colorBand = lastCardData.colorBand;
// Put on TOP of deck
mainDeck.push(cleanLastCardData);
// Remove from screen
if (lastCard.parent) {
lastCard.parent.removeChild(lastCard);
}
// Clear scryCards array
scryCards = [];
// Remove header text
if (scryHeaderText && scryHeaderText.parent) {
scryHeaderText.parent.removeChild(scryHeaderText);
scryHeaderText = null;
}
// Reset scry state
scryMode = false;
scryTokenActive = false;
scryStep = 0;
// Update UI
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
}
}
}
function showNoPlayableCardsNotification() {
// Create notification box
var notificationBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.5
});
notificationBox.tint = 0x8B0000; // Dark red background
notificationBox.alpha = 0.9;
notificationBox.x = 1024;
notificationBox.y = 1366;
game.addChild(notificationBox);
// Create notification text
var notificationText = new Text2("No playable cards! Click to continue", {
size: 36,
fill: 0xFFFFFF
});
notificationText.anchor.set(0.5, 0.5);
notificationText.x = 0;
notificationText.y = 0;
notificationBox.addChild(notificationText);
// Make the box clickable
notificationBox.down = function () {
// Remove the notification
if (notificationBox.parent) {
notificationBox.parent.removeChild(notificationBox);
}
// Discard all cards in hand
discardCards(playerHand.slice());
// Start draw 1, discard 1 cycle
processDrawPhase();
};
// Store reference for cleanup
game.noPlayableNotification = notificationBox;
}
// Initialize terrain-based game
createBasicTerrainPool();
setupPlanet();
createInitialDeck();
createEventDecks();
// Shuffle event decks on game start
shuffleEventDeck(basicEventDeck);
shuffleEventDeck(advancedEventDeck);
// Game starts with turn 1, round 1, in Dawn phase
currentRound = 1;
turnNumber = 1;
gamePhase = 'dawn';
game.update = function () {
// Update UI elements
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
basicEventCountText.setText("Basic Events: " + basicEventDeck.length);
advancedEventCountText.setText("Advanced Events: " + advancedEventDeck.length);
graveyardCountText.setText("Graveyard: " + graveyard.length);
eventDiscardCountText.setText("Event Discard: " + eventDiscardPile.length);
// Update scry token display
if (scryTokenActive) {
scryText.setText("Scry ✓");
scryText.tint = 0x00FF00; // Green when available
} else if (scryMode) {
scryText.setText("Scry (Active)");
scryText.tint = 0xFFFF00; // Yellow when in use
} else {
scryText.setText("Scry");
scryText.tint = 0x888888; // Gray when unavailable
}
// Update round and phase indicator
gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber);
// Check if event cards have been removed from hand
hasEventCardsInHand = false;
for (var i = 0; i < playerHand.length; i++) {
//{lV_event1}
if (playerHand[i].eventData) {
hasEventCardsInHand = true;
break;
}
}
// Update next phase button appearance based on event cards
if (gamePhase === 'noon') {
//{lV_event2}
if (hasEventCardsInHand) {
nextPhaseButton.tint = 0xFF0000; // Red when events must be played
nextPhaseButtonText.setText("Play events 1st!");
} else {
nextPhaseButton.tint = 0x32CD32; // Green when normal phase progression
nextPhaseButtonText.setText("NEXT PHASE");
}
}
// Update link marker states during dusk phase
if (gamePhase === 'dusk') {
updateLinkMarkerStates();
}
// Update danger highlights for creatures at risk of extinction
updateDangerHighlights();
};
function resetCardLinks(carnivore) {
if (!carnivore || carnivore.creatureData.dietType !== 'carnivore') {
return;
}
// Remove all active links from this carnivore with fall-off animation
for (var i = carnivore.activeLinks.length - 1; i >= 0; i--) {
var link = carnivore.activeLinks[i];
animateLinkFallOff(carnivore, link.target);
}
// Reset all link markers to unlinked state
for (var j = 0; j < carnivore.linkMarkers.length; j++) {
var marker = carnivore.linkMarkers[j];
marker.isLinked = false;
marker.targetHerbivore = null;
marker.tint = 0xFF0000; // Red for unlinked
}
// Clear active links array
carnivore.activeLinks = [];
} ===================================================================
--- original.js
+++ change.js
@@ -751,15 +751,15 @@
/****
* Game Code
****/
-// Zoom container for magnified card view
-// Terrain card assets - Land types with green strip
-// Terrain card assets - Water types with blue/grey strip
-// Climate indicator strips
-// Creature card assets
-// Event card asset
// UI elements
+// Event card asset
+// Creature card assets
+// Climate indicator strips
+// Terrain card assets - Water types with blue/grey strip
+// Terrain card assets - Land types with green strip
+// Zoom container for magnified card view
var zoomContainer = new Container();
zoomContainer.x = 0;
zoomContainer.y = 0;
LK.gui.center.addChild(zoomContainer);
@@ -3157,36 +3157,39 @@
line.scaleY = 0.08; // Thicker line (8% of original height)
line.carnivore = carnivore;
line.herbivore = herbivore;
line.linkIndex = existingLinkCount; // Track which link number this is
- // Calculate perpendicular offset for second link to avoid stacking
- var offsetDistance = 0;
- if (existingLinkCount === 1) {
- // Second link: offset perpendicular to link direction
- // Perpendicular direction is 90 degrees from the link angle
- var perpendicularAngle = angle + Math.PI / 2;
- offsetDistance = 30; // Offset distance in pixels
+ // Calculate perpendicular offset for multiple links to avoid stacking
+ if (existingLinkCount >= 1) {
+ // 2nd link goes one way (+90 deg), 3rd link goes the opposite way (-90 deg)
+ var perpendicularAngle = existingLinkCount === 1 ? angle + Math.PI / 2 : angle - Math.PI / 2;
+ var offsetDistance = 30; // Offset distance in pixels
var offsetX = Math.cos(perpendicularAngle) * offsetDistance;
var offsetY = Math.sin(perpendicularAngle) * offsetDistance;
// Apply offset to line position
line.x += offsetX;
line.y += offsetY;
- // Change second link line to a different color for visualization
- line.tint = 0xFF00FF; // Magenta/pink for second link
- }
- game.addChild(line);
- // Add X2 indicator if this is the second link
- if (existingLinkCount === 1) {
- var x2Indicator = new Text2("X2", {
+ // Change line color and create indicator based on link number
+ var indicatorString = "";
+ if (existingLinkCount === 1) {
+ line.tint = 0xFF00FF; // Magenta for 2nd link
+ indicatorString = "X2";
+ } else {
+ line.tint = 0x00BFFF; // Deep Sky Blue for 3rd link
+ indicatorString = "X3";
+ }
+ // Add the text indicator
+ var linkIndicator = new Text2(indicatorString, {
size: 20,
fill: 0xFFFFFF
- }); //{e4_x2}
- x2Indicator.anchor.set(0.5, 0.5);
- x2Indicator.x = 0;
- x2Indicator.y = 0;
- line.addChild(x2Indicator);
- x2Indicator.lineOwner = line;
- } //{e4_x2_end}
+ });
+ linkIndicator.anchor.set(0.5, 0.5);
+ linkIndicator.x = 0;
+ linkIndicator.y = 0;
+ line.addChild(linkIndicator);
+ linkIndicator.lineOwner = line;
+ }
+ game.addChild(line);
linkLines.push(line);
}
function removeLinkLine(carnivore, herbivore) {
for (var i = linkLines.length - 1; i >= 0; i--) {
@@ -4193,15 +4196,15 @@
}
}
}
var gameStatusText = new Text2("Round: 1 | Phase: DAWN | Turn: 1", {
- size: 32,
+ size: 40,
fill: 0xFFFF00
});
gameStatusText.anchor.set(0.5, 0);
gameStatusText.x = 1024;
-gameStatusText.y = 68;
-LK.gui.top.addChild(gameStatusText);
+gameStatusText.y = 60;
+game.addChild(gameStatusText);
// Link adjustment system variables
var linkAdjustmentMode = false;
var adjustLinkButton = null;
var cardsWithAdjustableLinks = [];