User prompt
Please fix the zoom stacking issue so that only top cards zoom fully, and covered cards only zoom when hovering over their visible color bands. In CreatureCard, TerrainCard, and EventCard, find the self.move = function (x, y, obj) block. Delete all the hover/zoom logic inside them completely, leaving them totally empty like this: self.move = function (x, y, obj) {}; (We will handle all hit detection centrally now). In the central onMouseMove(x, y) function, find the section that loops through the board // Check all cards in play. Replace that entire block of board-checking loops with this precise visibility logic: code JavaScript if (!cardUnderMouse) { for (var gridY = 0; gridY < planetHeight; gridY++) { for (var gridX = 0; gridX < planetWidth; gridX++) { var terrain = planetBoard[gridY][gridX]; if (terrain) { var hasCreatures = terrain.creatureStack && terrain.creatureStack.length > 0; if (hasCreatures) { for (var c = terrain.creatureStack.length - 1; c >= 0; c--) { var creature = terrain.creatureStack[c]; var isTop = (c === terrain.creatureStack.length - 1); var localX = x - creature.x; var localY = y - creature.y; if (isTop) { if (Math.abs(localX) < 80 && Math.abs(localY) < 110) { cardUnderMouse = creature; break; } } else { // Exposed bottom band for cards pushed down if (Math.abs(localX) < 80 && localY > 70 && localY < 110) { cardUnderMouse = creature; break; } } } } if (cardUnderMouse) break; // Terrain check var localX = x - terrain.x; var localY = y - terrain.y; if (hasCreatures) { // Only the top color strip is visible when covered if (Math.abs(localX) < 75 && localY > -168 && localY < -120) { cardUnderMouse = terrain; } } else { // Full bounds if empty if (Math.abs(localX) < 75 && Math.abs(localY) < 100) { cardUnderMouse = terrain; } } } } if (cardUnderMouse) break; } }
User prompt
Please fix the visual stacking issue in processExtinctions() where creatures beneath an extinct creature fail to shift back up. Inside processExtinctions(), locate the block where you remove the dead creature: terrain.creatureStack.splice(i, 1);. Immediately below that line (before creaturesWentExtinct = true;), please add a loop to tween all the creatures that were physically positioned below the dead creature UP by 20 pixels, reversing their original offset: for (var j = 0; j < i; j++) { var creatureBelow = terrain.creatureStack[j]; tween(creatureBelow, { y: creatureBelow.y - 20 }, { duration: 200 }); } βͺπ‘ Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the zoom functionality so that Advanced Terrain cards are the same size as Creature cards when zoomed, and so that cards currently in play on the board can also be zoomed. In zoomCard(card), delete the clonedCard.scaleX *= 2.0; clonedCard.scaleY *= 2.0; lines. Replace them with explicit sizing: if (card.terrainData) { clonedCard.scale.set(1.27, 1.31); } else { clonedCard.scale.set(2.0, 2.0); } In both CreatureCard and TerrainCard, update the self.move functions. Remove the !self.isInPlay condition from the main if statement so it ALWAYS allows zoom and info on hover. Restrict only the possible moves. It should look exactly like this for CreatureCard: if (Math.abs(x) < cardBoundsForZoom && Math.abs(y) < cardHeightBoundsForZoom) { zoomCard(self); showCardInfo(self); if (!self.isInPlay) { showPossibleMoves(self); } } else { if (hoveredCard === self) { hideCardInfo(); if (!self.isInPlay) { hidePossibleMoves(); } } if (currentHoveredCardForZoom === self) { clearZoom(); } } (Do the exact same for TerrainCard, but keep its specific bounds: Math.abs(x) < 75 && Math.abs(y) < 100) In game.move, completely delete the else block at the bottom that checks inHandArea and hides the card info. It is aggressively breaking the zoom when mousing over the board. In onMouseMove(x,y), completely delete the safety check at the bottom if (playerHand.length === 0) { clearZoomIfNoCards(); } as this prevents board zooming when your hand is empty.
User prompt
Please fix the "ghost link" logic causing herbivores to receive extinction markers from removed links. The issue is happening because animateLinkFallOff waits 500ms before calling removeLink(). If a carnivore has 2 links to the same herbivore, calling animateLinkFallOff twice in a loop will find and animate the exact same visual line twice because it wasn't removed from the array yet. This cancels the first onFinish trigger, meaning the second link is permanently stuck in linkLines and carnivore.activeLinks. To fix this, please update these three functions: In animateLinkFallOff: Change the search condition to && !linkLines[i].isFallingOff. Once you find linkLineToRemove, instantly add linkLineToRemove.isFallingOff = true;. Also in animateLinkFallOff: Move the removeLink(carnivore, herbivore); call outside and before the tween so the arrays are instantly cleared. Inside onFinish, replace it with if (linkLineToRemove.parent) linkLineToRemove.parent.removeChild(linkLineToRemove); In removeLinkLine: We need to keep the visual alive for the tween. Wrap the removeChild call in a check: if (!line.isFallingOff) { if (line.parent) line.parent.removeChild(line); }. Always allow it to execute linkLines.splice(i, 1); so the logical ghost link is instantly removed from the array!
User prompt
Please fix two bugs in the canPlaceCreatureOnTerrain function that are allowing Basic Herbivores to be placed on Advanced Carnivores. Near the middle of the function, there is a check that reads if (terrain.creatureStack && terrain.creatureStack.length > 0). This silently fails because terrain is assigned to terrainCard.terrainData, which does not contain the creatureStack array. Please change this line and the one below it to use terrainCard instead, like this: if (terrainCard.creatureStack && terrainCard.creatureStack.length > 0) { var topCreatureOnTerrain = terrainCard.creatureStack[terrainCard.creatureStack.length - 1]; Lower down in the exact same function, inside the stacking rules under if (creature.level === 'Basic') -> if (creature.dietType === 'herbivore'), you prevented stacking on Basic Herbivores, Advanced Herbivores, and Basic Carnivores, but you forgot Advanced Carnivores. Please add this missing check: if (topCreatureInStack.creatureData.level === 'Advanced' && topCreatureInStack.creatureData.dietType === 'carnivore') { return false; }
User prompt
The "Bully" special rule does not function at all. Please erase and rewrite it so that any carnivore with "Bully" can create links to Basic Carnivores as well as Basic Herbivores, Displayes "Special" and "Bully" upon its card image and has the text: "can create links to Basic Carnivores as well as Basic Herbivores" displayed too.
User prompt
When an advanced carnivore makes 2 links to the same herbivore this must register as two links on that herbivore. Check all the code for links and find the reason that this is not working and fix it, make it visible in the UI as 2 separate links by making the second link a different color with the text "X2" displayed in the centre of the link marker
User prompt
update cards: 'BH13' and 'BH07' to display the "Special" text on the card (in the same way as all the other creatures that have Special=True, and also give them the text "Whale Chow" on their card. Give the card: 'AC03' the word "Whale chow eater" visible on its card
User prompt
There is a problem with herbivores and extinction markers. Please find the fault and fix this error: If a herbivore has 1 or more extinction markers, it receives another marker during the Night phase. This should not happen!!!! The only time a herbivore should receive an extinction marker is During the 'Night' phase and ONLY if it has too many links upon it at that time. Fix this issue by checking all your code
User prompt
increase hand-scaling for Terrain cards to 0.7 on both X + Y
User prompt
Please perform the following three steps to restore the intended card scaling logic for the entire game: 1. Revert the Asset Sizes: First, restore the terrain card assets to their original, larger dimensions. Update the width of the following assets to 252 and the height to 336:** * terrainLandFlat * terrainLandHills * terrainLandMountain * terrainWaterFresh * terrainWaterSea 2. Restore Hand-Scaling for Terrain Cards: In the drawCard function, re-insert the code that scales down terrain cards when they are drawn into the hand. Find the if (cardData.cardType === 'terrain') block and add these two lines back inside it: card.scaleX = 0.6; card.scaleY = 0.6;
User prompt
Standardize the Zoom Function: Now, update the zoomCard function to correctly handle all cards. When cloning a card to zoom, it should check the card's current scale and apply a multiplier to reach a final, uniform zoom size. Modify the zoomCard function to look like this: JavaScript function zoomCard(card) { if (currentHoveredCardForZoom && currentHoveredCardForZoom !== card) { clearZoom(); } if (currentHoveredCardForZoom === card && zoomedCard) { return; } if (zoomedCard && zoomedCard.parent) { zoomedCard.parent.removeChild(zoomedCard); zoomedCard = null; } currentHoveredCardForZoom = card; 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 and apply a universal 2.0 zoom multiplier to its current scale clonedCard.x = 550; clonedCard.y = 680; clonedCard.scaleX *= 2.0; clonedCard.scaleY *= 2.0; clonedCard.alpha = 1; zoomContainer.addChild(clonedCard); zoomedCard = clonedCard; }
User prompt
Please perform the following two steps to standardize all card sizes: 1. First, update the definitions for all terrain card assets to match the size of creature cards. Change the width of the following assets to 160 and the height to 220: * terrainLandFlat * terrainLandHills * terrainLandMountain * terrainWaterFresh * terrainWaterSea 2. Second, go to the drawCard function and completely remove the lines that scale down terrain cards. Specifically, delete these two lines of code: card.scaleX = 0.6; card.scaleY = 0.6;
User prompt
whatever changed in the last command actually made the issue with the advanced terrains and the scaled close function even more acute! please do the opposite of what you did last command so that the advanced terrain cards are scaled to the same value as all the other card types on hover over
User prompt
fix issue with the scaled clone function: This works for all cards perfectly except for advanced terrain cards - those cards are for some reason scaled higher than the rest of the cards so please
User prompt
find the reason why the "Bully" special rule is not functioning correctly and fix it
User prompt
find the reason why the "Stinky" special rule is not functioning correctly and fix it
User prompt
Implement the 'Stinky' special rule inside the card drawing logic (e.g., the drawCard function). Immediately after a new card is added to the playerHand, check if its data (creatureData or terrainData) has stinky: true. If it is stinky, trigger a hand-clearing event: iterate through the playerHand and remove any cards that are NOT Event cards AND do NOT have the stinky trait. Move the removed cards to the discardPile, destroy their visual assets/remove them from the game container, update the discardCountText, and recalculate the visual positions of the remaining cards in the hand so there are no empty gaps.
User prompt
Update the canPlaceCreatureOnTerrain function (and placeCreatureOnStack if necessary) to enforce creature stacking rules. Currently, it only checks if the terrain matches, which allows players to place a Basic Herbivore on top of an Advanced Carnivore. Please modify the logic so that it checks the target terrain's creatureStack array. If the stack is not empty, it must verify the creatureData of the top card and strictly prevent a Basic Herbivore from being placed onto an Advanced Carnivore.
User prompt
β Move the scaled clone (zoomed card) 50 pixels to the left and 20 pixels up
User prompt
please move the scaled clone to the left by 200
User prompt
the scaled clone appears off the UI edges (too low and too right) please centre it at the current centre of the "Draw" button
User prompt
please revert the scaled clone to normal opacity and move it into the bottom right corner (where the draw button is - no need to move the draw button).
User prompt
please make the scaled clone in the UI 50% see-through
User prompt
zoom function still does not work for cards that are in play (on the board) please find out why this is not working and correct it/make it so that cards in play (on the board) are also zoomed when the cursor is over them
/****
* 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
}
}
// 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);
// 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) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
// Check if mouse is actually over this card
if (Math.abs(x) < cardBoundsForZoom && Math.abs(y) < cardHeightBoundsForZoom) {
// Trigger zoom on hover
zoomCard(self);
showCardInfo(self);
showPossibleMoves(self);
} else {
// Mouse moved away from this card
if (hoveredCard === self) {
hideCardInfo();
hidePossibleMoves();
}
// Clear zoom if moving away from card
if (currentHoveredCardForZoom === self) {
clearZoom();
}
}
}
};
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);
}
};
// Add "Special" text in the center for any creature card with special: true
if (self.creatureData && self.creatureData.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 "Tough" text under Special text if present
var nextYOffset = 40;
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 = self.specialText ? self.specialText.y + nextYOffset : nextYOffset;
self.addChild(self.toughText);
nextYOffset = self.toughText.y + nextYOffset;
} else if (self.specialText) {
nextYOffset = self.specialText.y + nextYOffset;
}
// Add "Squishy" text under Special/Tough 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 = self.squishyText.y + 40;
}
// Add "Stinky" text under Special/Tough/Squishy 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 = self.stinkyText.y + 40;
}
// Add "Flying" and "Swimming" text under Special/Tough text if present
var flyingOrSwimmingYOffset = 40;
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 = self.flyingText.y + flyingOrSwimmingYOffset;
}
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 = self.swimmingText.y + flyingOrSwimmingYOffset;
}
// Add "Efficient" text under Special/Flying/Swimming 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 = self.efficientText.y + 40;
}
// Add "Whale" text under Special text if present (for AC03 only)
if (self.creatureData && self.creatureData.id === 'AC03') {
self.whaleText = new Text2("Whale", {
size: 24,
fill: 0x00BFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.whaleText.anchor.set(0.5, 0.5);
self.whaleText.x = 0;
self.whaleText.y = nextYOffset;
self.addChild(self.whaleText);
nextYOffset = self.whaleText.y + 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) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
// Check if mouse is actually over this card
if (Math.abs(x) < cardBoundsForZoom && Math.abs(y) < cardHeightBoundsForZoom) {
// Trigger zoom on hover
zoomCard(self);
showCardInfo(self);
showPossibleMoves(self);
} else {
if (hoveredCard === self) {
hideCardInfo();
hidePossibleMoves();
}
// Clear zoom if moving away from card
if (currentHoveredCardForZoom === self) {
clearZoom();
}
}
}
};
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
phaseText.setText("Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber);
// 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) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
// Check if mouse is actually over this card (scaled terrain cards)
if (Math.abs(x) < 75 && Math.abs(y) < 100) {
// Trigger zoom on hover
zoomCard(self);
showCardInfo(self);
showPossibleMoves(self);
} else {
// Mouse moved away from this card
if (hoveredCard === self) {
hideCardInfo();
hidePossibleMoves();
}
// Clear zoom if moving away from card
if (currentHoveredCardForZoom === self) {
clearZoom();
}
}
}
};
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 all cards in hand for hover
for (var i = 0; i < playerHand.length; i++) {
var card = playerHand[i];
// Get card bounds based on card type
var halfWidth = 80;
var halfHeight = 110;
// Check if mouse is within card bounds
if (Math.abs(card.x - x) < halfWidth && Math.abs(card.y - y) < halfHeight) {
cardUnderMouse = card;
break;
}
}
// Check all cards in play (creatures and terrain on board)
if (!cardUnderMouse) {
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain) {
// Check creatures in stack first (top creature is most visible)
if (terrain.creatureStack && terrain.creatureStack.length > 0) {
for (var c = terrain.creatureStack.length - 1; c >= 0; c--) {
var creature = terrain.creatureStack[c];
var creatureHalfWidth = 80;
var creatureHalfHeight = 110;
if (Math.abs(creature.x - x) < creatureHalfWidth && Math.abs(creature.y - y) < creatureHalfHeight) {
cardUnderMouse = creature;
break;
}
}
}
if (cardUnderMouse) break;
}
// Check terrain card if no creature found
if (!cardUnderMouse && terrain) {
var terrainHalfWidth = 75;
var terrainHalfHeight = 100;
if (Math.abs(terrain.x - x) < terrainHalfWidth && Math.abs(terrain.y - y) < terrainHalfHeight) {
cardUnderMouse = terrain;
}
}
}
if (cardUnderMouse) break;
}
}
// Decide what to do based on result
if (cardUnderMouse && cardUnderMouse !== currentlyHoveredCard) {
// Mouse over a new card
if (currentlyHoveredCard) {
clearZoom();
}
zoomCard(cardUnderMouse);
showCardInfo(cardUnderMouse);
showPossibleMoves(cardUnderMouse);
currentlyHoveredCard = cardUnderMouse;
} else if (!cardUnderMouse && currentlyHoveredCard) {
// Mouse moved off all cards
clearZoom();
hideCardInfo();
hidePossibleMoves();
currentlyHoveredCard = null;
}
// Safety check: clear zoom if hand is empty
if (playerHand.length === 0) {
clearZoomIfNoCards();
}
}
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;
} 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();
}
}
};
// 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 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 roundText = new Text2("Round: " + currentRound, {
size: 48,
fill: 0xFFFFFF
});
roundText.anchor.set(0.5, 0);
LK.gui.top.addChild(roundText);
roundText.y = 100;
// Add a second round display text for compatibility with code using roundDisplayText
var roundDisplayText = new Text2("Round: " + currentRound, {
size: 36,
fill: 0xFFFFFF
});
roundDisplayText.anchor.set(0.5, 0);
LK.gui.top.addChild(roundDisplayText);
roundDisplayText.y = 160;
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: false
}, {
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
});
}
// 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 //{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,
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}
}]; //{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 may also link to Basic Carnivore cards!";
} //{fl_bully}
}
} 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: 28,
fill: 0xFFFFFF
});
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;
}
// Position at bottom right corner of zoom container with 200% zoom, moved 200px left, adjusted 50px left and 20px up
clonedCard.x = 550;
clonedCard.y = 680;
clonedCard.scaleX = 2;
clonedCard.scaleY = 2;
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;
}
}
}
}
}
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.6;
card.scaleY = 0.6;
} 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 cards that are NOT Event cards AND NOT Stinky creatures
if (hasStinkyCreature) {
//{stinky_process_1}
var toDiscard = [];
for (var j = 0; j < playerHand.length; j++) {
//{stinky_process_2}
var c = playerHand[j];
// Check what the card IS:
// - Is it an Event card? Keep it.
// - Is it a Stinky creature? Keep it.
// - Otherwise (non-Stinky creature, terrain card)? Discard it.
var isEvent = c.eventData ? true : false;
var isStinky = c.creatureData && c.creatureData.stinky ? true : false;
if (!isEvent && !isStinky) {
//{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;
}
}
// If event cards are in hand, show the debug box
if (hasEventCardsInHand) {
showEventCardDebugBox();
}
}
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 drawn, click to resolve", {
//{debug_event_14}
size: 32,
//{debug_event_15}
fill: 0xFFFFFF //{debug_event_16}
}); //{debug_event_17}
debugText.anchor.set(0.5, 0.5);
debugText.x = 0;
debugText.y = 0;
eventDebugBox.addChild(debugText); //{debug_event_18}
// Make box clickable to resolve event cards
eventDebugBox.down = function () {
//{debug_event_19}
// Discard all cards from current hand
var cardsToDiscard = playerHand.slice(); //{debug_event_20}
discardCards(cardsToDiscard); //{debug_event_21}
// Remove debug box
if (eventDebugBox.parent) {
//{debug_event_22}
eventDebugBox.parent.removeChild(eventDebugBox); //{debug_event_23}
} //{debug_event_24}
// Progress to next phase (Noon/Dusk depending on current phase)
gamePhase = 'dusk'; //{debug_event_25}
processDuskPhase(); //{debug_event_26}
}; //{debug_event_27}
} //{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 - creature landType must match terrain landType
if (creature.landType && 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 (terrain.creatureStack && terrain.creatureStack.length > 0) {
var topCreatureOnTerrain = terrain.creatureStack[terrain.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
}
}
// 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') {
// Advanced herbivores can only be placed on advanced terrain with basic herbivores at top
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) {
linkLineToRemove = linkLines[i];
break;
}
}
if (linkLineToRemove) {
// 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 link after animation
removeLink(carnivore, herbivore);
}
});
} 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) {
var neighbors = [];
var directions = [{
x: -1,
y: 0
}, {
x: 1,
y: 0
}, {
x: 0,
y: -1
}, {
x: 0,
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;
}
if (herbivore.creatureData.dietType !== 'herbivore') {
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;
}
// Check Whale food restriction: only Whale (AC03) can link to Whale food creatures
if (herbivore.creatureData && herbivore.creatureData.whaleFood) {
if (carnivore.creatureData.id !== 'AC03') {
return false; // Cannot link to Whale food unless carnivore is Whale
}
}
// Check Hunter-Hunter restriction: can only link to Carnivores, not Herbivores
if (carnivore.creatureData && carnivore.creatureData.hunterHunter) {
if (herbivore.creatureData.dietType !== 'carnivore') {
return false; // Hunter-Hunter can only link to carnivores
} //{n4_hh}
} //{n4_hh_end}
// Check Fussy restriction: can only link to Basic Herbivores
if (carnivore.creatureData && carnivore.creatureData.fussy) {
if (herbivore.creatureData.level !== 'Basic' || herbivore.creatureData.dietType !== 'herbivore') {
return false; // Fussy can only link to Basic Herbivores
} //{n4_fussy}
} //{n4_fussy_end}
// Check Bully special: can link to both herbivores and basic carnivores
if (carnivore.creatureData && carnivore.creatureData.bully) {
// Bully can link to Herbivores (any level) OR Basic Carnivores
// If target is a carnivore, it must be Basic level
if (herbivore.creatureData.dietType === 'carnivore') {
if (herbivore.creatureData.level !== 'Basic') {
return false; // Bully can only link to Basic Carnivores, not Advanced
}
}
// Otherwise (Herbivore target or valid Basic Carnivore target), allow the link
return true;
}
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) {
// Remove existing line between these creatures if any
removeLinkLine(carnivore, herbivore);
// Count how many links already exist between this carnivore and herbivore
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
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 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
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 = 0xAA00FF; // Purple/magenta for second link
}
game.addChild(line);
// Add X2 indicator if this is the second link
if (existingLinkCount === 1) {
var x2Indicator = new Text2("X2", {
size: 20,
fill: 0xFF0000
}); //{e4_x2}
x2Indicator.anchor.set(0.5, 0.5);
x2Indicator.x = 0;
x2Indicator.y = 0;
line.addChild(x2Indicator);
x2Indicator.lineOwner = line;
} //{e4_x2_end}
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.parent) {
line.parent.removeChild(line);
}
linkLines.splice(i, 1);
break;
}
}
}
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 (topCreature.creatureData.dietType === 'herbivore' && 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 adjacentTerrains = getAdjacentTerrains(gridX, gridY);
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 (topHerbivore.creatureData.dietType === 'herbivore') {
// 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 adjacentTerrains = getAdjacentTerrains(gridX, gridY);
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: 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 marker ONLY if excess links exist
// Use Math.max to ensure we never add more than 1 marker per turn
var excessLinks = linksToThisHerbivore - creature.safeLinks;
if (excessLinks > 0) {
// Add exactly 1 marker if this herbivore has any excess links
creature.extinctionMarkers += 1;
creature.updateExtinctionMarkers();
}
}
}
}
}
}
// 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);
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() {
// Check for ongoing effects and costs that trigger during Dawn
// Check scry status at beginning of round - award token ONLY if no creatures went extinct last round
if (currentRound > 1 && !scryDeniedNextRound) {
scryTokenActive = true;
}
// Reset extinction tracking for the new round
creaturesWentExtinct = false;
// Reset scry denial for the new round
scryDeniedNextRound = false;
// 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
}
var cardsDrawnThisPhase = 0;
var drawnCards = [];
var continueDrawing = true;
// Iterative loop that continues until we have 3 cards or can't draw more
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++;
roundText.setText("Round: " + currentRound);
roundDisplayText.setText("Round: " + 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;
}
}
// Draw one card if deck has cards
if (mainDeck.length > 0 && continueDrawing) {
var cardData = mainDeck.pop();
var card = createCardFromData(cardData);
drawnCards.push(card);
playerHand.push(card);
game.addChild(card);
// Update hand positions after each card is added
updateHandPositions();
cardsDrawnThisPhase++;
} else if (mainDeck.length === 0) {
// No more cards in deck and discard is empty or was just exhausted, stop drawing
continueDrawing = false;
}
}
// Update UI
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
LK.getSound('cardDraw').play();
// Check if any cards have valid placements
if (!hasValidPlacements(drawnCards)) {
// Discard all cards in hand and continue drawing forced cards
discardCards(playerHand.slice());
// Clear drawn cards and continue loop
drawnCards = [];
// Continue loop to draw forced cards instead of recursive call
// Loop will continue iterating above since continueDrawing is true and playerHand.length < 3
} else {
// At least one card has valid placement, end draw phase
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
roundText.setText("Round: " + currentRound);
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}
// 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;
}
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();
}
};
nextPhaseButton.move = function (x, y, obj) {
//{mJ_danger}
var creaturesInDanger = getCreaturesInDanger(); //{mK_danger}
if (creaturesInDanger.length > 0) {
//{mL_danger}
showNextPhaseWarning(); //{mM_danger}
highlightCreaturesInDanger(creaturesInDanger); //{mN_danger}
} else {
//{mO_danger}
hideNextPhaseWarning(); //{mP_danger}
unhighlightCreaturesInDanger(creaturesInDanger); //{mQ_danger}
} //{mR_danger}
}; //{mS_danger}
// Phase indicator
var phaseText = new Text2("Phase: NOON", {
size: 32,
fill: 0xFFFFFF
});
phaseText.anchor.set(0.5, 0);
phaseText.x = 1024;
phaseText.y = 2550;
game.addChild(phaseText);
// 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;
} 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();
}
}
};
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;
scryCards = [];
// Take top 3 cards from deck
for (var i = 0; i < 3; i++) {
var cardData = mainDeck.pop();
var card = createCardFromData(cardData);
scryCards.push(card);
playerHand.push(card);
game.addChild(card);
}
updateHandPositions();
// Show return cards button
showReturnCardsButton();
}
function showReturnCardsButton() {
var returnButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.8
});
returnButton.tint = 0x8A2BE2; // Purple for scry actions
var returnButtonText = new Text2("READY TO RETURN CARDS", {
size: 20,
fill: 0xFFFFFF
});
returnButtonText.anchor.set(0.5, 0.5);
returnButton.addChild(returnButtonText);
returnButton.x = 1024;
returnButton.y = 2000;
game.addChild(returnButton);
returnButton.down = function () {
processScryReturn();
if (returnButton.parent) {
returnButton.parent.removeChild(returnButton);
}
};
}
function processScryReturn() {
// For each scry card in hand, player chooses where to return it
// For now, we'll implement a simple system - return to top of deck
for (var i = 0; i < scryCards.length; i++) {
var card = scryCards[i];
var handIndex = playerHand.indexOf(card);
if (handIndex !== -1) {
playerHand.splice(handIndex, 1);
if (card.parent) {
card.parent.removeChild(card);
}
// Return to top of deck (could be enhanced to let player choose top/bottom/discard)
var cardData = card.creatureData || card.terrainData || card.eventData;
// Create clean copy without event data to prevent hand state corruption
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;
}
mainDeck.push(cleanCardData);
}
}
// Clear scry state
scryCards = [];
scryMode = false;
scryTokenActive = false; // Scry token consumed
updateHandPositions();
}
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';
roundText.setText("Round: " + currentRound);
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 phase indicator
phaseText.setText("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();
}
};
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
@@ -3011,10 +3011,20 @@
if (herbivore.creatureData.level !== 'Basic' || herbivore.creatureData.dietType !== 'herbivore') {
return false; // Fussy can only link to Basic Herbivores
} //{n4_fussy}
} //{n4_fussy_end}
- // Check Bully special: can link to both herbivores and basic carnivores (no restriction, just allows more targets)
- // Bully has no restrictive rules, only permissive ones, so no return false needed
+ // Check Bully special: can link to both herbivores and basic carnivores
+ if (carnivore.creatureData && carnivore.creatureData.bully) {
+ // Bully can link to Herbivores (any level) OR Basic Carnivores
+ // If target is a carnivore, it must be Basic level
+ if (herbivore.creatureData.dietType === 'carnivore') {
+ if (herbivore.creatureData.level !== 'Basic') {
+ return false; // Bully can only link to Basic Carnivores, not Advanced
+ }
+ }
+ // Otherwise (Herbivore target or valid Basic Carnivore target), allow the link
+ return true;
+ }
return true;
}
function areCreaturesAdjacent(creature1, creature2) {
if (!creature1.isInPlay || !creature2.isInPlay) {