/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var CreatureCard = Container.expand(function (creatureData) {
var self = Container.call(this);
self.creatureData = creatureData || {
type: 'basic',
level: 'Basic',
dietType: 'herbivore',
name: 'Basic Herbivore',
terrainRequirement: 'land',
climateRequirement: 'any'
};
// Create creature visual
var creatureAsset;
if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'herbivore') {
creatureAsset = self.attachAsset('creatureBasicHerbivore', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'herbivore') {
creatureAsset = self.attachAsset('creatureAdvancedHerbivore', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'carnivore') {
creatureAsset = self.attachAsset('creatureBasicCarnivore', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'carnivore') {
creatureAsset = self.attachAsset('creatureAdvancedCarnivore', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Add creature type color band at bottom
var typeStripAsset;
if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'herbivore') {
typeStripAsset = 'creatureTypeBasicHerbivore';
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'herbivore') {
typeStripAsset = 'creatureTypeAdvancedHerbivore';
} else if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'carnivore') {
typeStripAsset = 'creatureTypeBasicCarnivore';
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'carnivore') {
typeStripAsset = 'creatureTypeAdvancedCarnivore';
}
if (typeStripAsset) {
var typeStrip = self.attachAsset(typeStripAsset, {
anchorX: 0.5,
anchorY: 0.5
});
typeStrip.y = 95; // Position at bottom of creature card
}
// Add creature name
self.nameText = new Text2(self.creatureData.name, {
size: 18,
fill: 0xFFFFFF
});
self.nameText.anchor.set(0.5, 0);
self.nameText.x = 0;
self.nameText.y = -100;
self.addChild(self.nameText);
// Add requirement text
var reqText = "Req: ";
if (self.creatureData.terrainRequirement && self.creatureData.terrainRequirement !== 'any') {
reqText += self.creatureData.terrainRequirement;
}
if (self.creatureData.climateRequirement && self.creatureData.climateRequirement !== 'any') {
if (reqText !== "Req: ") {
reqText += ", ";
}
reqText += self.creatureData.climateRequirement;
}
if (reqText !== "Req: ") {
self.requirementText = new Text2(reqText, {
size: 14,
fill: 0xFFFFFF
});
self.requirementText.anchor.set(0.5, 0);
self.requirementText.x = 0;
self.requirementText.y = 60;
self.addChild(self.requirementText);
}
self.isInPlay = false;
self.linkMarkers = [];
self.activeLinks = [];
self.extinctionMarkers = 0;
self.linkRequirement = 0;
self.safeLinks = 0;
// Set link requirements and safe link levels based on creature type
if (self.creatureData.dietType === 'carnivore') {
if (self.creatureData.level === 'Basic') {
self.linkRequirement = 1; // Basic carnivores need 1 link
} else if (self.creatureData.level === 'Advanced') {
self.linkRequirement = 2; // Advanced carnivores need 2 links
}
} else if (self.creatureData.dietType === 'herbivore') {
if (self.creatureData.level === 'Basic') {
self.safeLinks = 1; // Basic herbivores can safely handle 1 link
} else if (self.creatureData.level === 'Advanced') {
self.safeLinks = 2; // Advanced herbivores can safely handle 2 links
}
}
if (self.creatureData.fatty) {
self.linkRequirement = 3;
}
if (self.creatureData.nutritious) {
self.safeLinks = 2;
}
if (self.creatureData.bountiful) {
self.safeLinks = 3;
}
// Create visual link markers for carnivores
self.createLinkMarkers = function () {
if (self.creatureData.dietType === 'carnivore' && self.linkRequirement > 0) {
for (var i = 0; i < self.linkRequirement; i++) {
var linkMarker = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.15,
scaleY: 0.15
});
linkMarker.tint = 0xFF0000; // Red color for unlinked markers
linkMarker.x = (i - (self.linkRequirement - 1) / 2) * 25;
linkMarker.y = -80;
linkMarker.isLinked = false;
linkMarker.targetHerbivore = null;
linkMarker.carnivore = self;
linkMarker.markerIndex = i;
// Link markers are no longer directly interactive
linkMarker.move = function (x, y, obj) {};
linkMarker.down = function (x, y, obj) {};
self.addChild(linkMarker);
self.linkMarkers.push(linkMarker);
}
// Create personal "Alter Links" button for this carnivore
self.createPersonalAlterLinksButton = function () {
if (self.isInPlay) {
var alterButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.2
});
alterButton.tint = 0xFFD700; // Gold color
var alterButtonText = new Text2("Alter Links", {
size: 12,
fill: 0x000000
});
alterButtonText.anchor.set(0.5, 0.5);
alterButton.addChild(alterButtonText);
alterButton.x = 0;
alterButton.y = 0;
alterButton.carnivoreCard = self;
alterButton.down = function () {
if (gamePhase === 'dusk' && linkAdjustmentMode) {
resetCardLinks(self);
}
};
self.addChild(alterButton);
self.personalAlterButton = alterButton;
}
};
}
};
// Create extinction marker visuals
self.extinctionMarkerVisuals = [];
self.updateExtinctionMarkers = function () {
// Remove existing visuals
for (var i = 0; i < self.extinctionMarkerVisuals.length; i++) {
if (self.extinctionMarkerVisuals[i].parent) {
self.extinctionMarkerVisuals[i].parent.removeChild(self.extinctionMarkerVisuals[i]);
}
}
self.extinctionMarkerVisuals = [];
// Create new visuals for current extinction markers
for (var i = 0; i < self.extinctionMarkers; i++) {
var marker = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.1,
scaleY: 0.1
});
marker.tint = 0x000000; // Black for extinction markers
marker.x = (i - (self.extinctionMarkers - 1) / 2) * 20;
marker.y = 80;
self.addChild(marker);
self.extinctionMarkerVisuals.push(marker);
}
};
self.die = function () {
if (self.isInPlay) {
// Extinction Trigger interceptor
if (self.creatureData.extinctionTrigger) {
// Extinction Trigger bypasses the graveyard, events, and scry penalty
bonusPile.push(self.creatureData);
} else {
// Normal death
graveyard.push(self.creatureData);
scryDeniedNextRound = true; // Deny scry
addEventCardToDeck(self.creatureData.level); // Draw event
if (self.creatureData.unlucky) addEventCardToDeck(self.creatureData.level);
}
// Visual death effect
tween(self, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 500,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
LK.getSound('creatureDie').play();
}
};
self.move = function (x, y, obj) {
var globalX = self.x + x * self.scaleX;
var globalY = self.y + y * self.scaleY;
onMouseMove(globalX, globalY);
};
self.down = function (x, y, obj) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
// Prevent selecting creature/terrain cards if event cards are in hand
if (hasEventCardsInHand) {
//{1G_event}
return;
}
selectedCard = self;
draggedCard = self;
originalCardPosition = {
x: self.x,
y: self.y
};
showPossibleMoves(self);
}
};
// Determine if creature has any special properties
var hasAnySpecial = self.creatureData && (self.creatureData.tough || self.creatureData.squishy || self.creatureData.stinky || self.creatureData.flying || self.creatureData.swimming || self.creatureData.efficient || self.creatureData.whaleFood || self.creatureData.whaleChow || self.creatureData.bully || self.creatureData.seaBound || self.creatureData.id === 'AC03');
// Do NOT add "Special" text - start cascading special text directly below card name
var nextYOffset = -50; // Start position lower to avoid overlapping the creature name
// Add "Tough" text if present
if (self.creatureData && self.creatureData.tough) {
self.toughText = new Text2("Tough", {
size: 24,
fill: 0xFFD700,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.toughText.anchor.set(0.5, 0.5);
self.toughText.x = 0;
self.toughText.y = nextYOffset;
self.addChild(self.toughText);
nextYOffset = nextYOffset + 28;
}
// Add "Squishy" text if present
if (self.creatureData && self.creatureData.squishy) {
self.squishyText = new Text2("Squishy", {
size: 24,
fill: 0xFF69B4,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.squishyText.anchor.set(0.5, 0.5);
self.squishyText.x = 0;
self.squishyText.y = nextYOffset;
self.addChild(self.squishyText);
nextYOffset = nextYOffset + 40;
}
// Add "Fatty" text if present
if (self.creatureData && self.creatureData.fatty) {
self.fattyText = new Text2("Fatty", {
size: 24,
fill: 0xFF8C00,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.fattyText.anchor.set(0.5, 0.5);
self.fattyText.x = 0;
self.fattyText.y = nextYOffset;
self.addChild(self.fattyText);
nextYOffset = nextYOffset + 40;
}
// Add "Stinky" text if present
if (self.creatureData && self.creatureData.stinky) {
self.stinkyText = new Text2("Stinky", {
size: 24,
fill: 0x228B22,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.stinkyText.anchor.set(0.5, 0.5);
self.stinkyText.x = 0;
self.stinkyText.y = nextYOffset;
self.addChild(self.stinkyText);
nextYOffset = nextYOffset + 40;
}
// Add "Flying" text if present
if (self.creatureData && self.creatureData.flying) {
self.flyingText = new Text2("Flying", {
size: 24,
fill: 0x00BFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.flyingText.anchor.set(0.5, 0.5);
self.flyingText.x = 0;
self.flyingText.y = nextYOffset;
self.addChild(self.flyingText);
nextYOffset = nextYOffset + 40;
}
// Add "Swimming" text if present
if (self.creatureData && self.creatureData.swimming) {
self.swimmingText = new Text2("Swimming", {
size: 24,
fill: 0x1E90FF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.swimmingText.anchor.set(0.5, 0.5);
self.swimmingText.x = 0;
self.swimmingText.y = nextYOffset;
self.addChild(self.swimmingText);
nextYOffset = nextYOffset + 40;
}
// Add "Efficient" text if present
if (self.creatureData && self.creatureData.efficient) {
self.efficientText = new Text2("Efficient", {
size: 24,
fill: 0x32CD32,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.efficientText.anchor.set(0.5, 0.5);
self.efficientText.x = 0;
self.efficientText.y = nextYOffset;
self.addChild(self.efficientText);
nextYOffset = nextYOffset + 40;
}
// Add "Whale Chow" text if present
if (self.creatureData && self.creatureData.whaleChow) {
self.whaleChowText = new Text2("Whale Chow", {
size: 24,
fill: 0x87CEEB,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.whaleChowText.anchor.set(0.5, 0.5);
self.whaleChowText.x = 0;
self.whaleChowText.y = nextYOffset;
self.addChild(self.whaleChowText);
nextYOffset = nextYOffset + 40;
}
// Add "Bully" text if present (but only for AC02, not BH07 or BH13)
if (self.creatureData && self.creatureData.bully && self.creatureData.id === 'AC02') {
self.bullyText = new Text2("Bully", {
size: 24,
fill: 0xFF6347,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold" //{2U_bully4}
}); //{2U_bully5}
self.bullyText.anchor.set(0.5, 0.5);
self.bullyText.x = 0;
self.bullyText.y = nextYOffset;
self.addChild(self.bullyText);
nextYOffset = nextYOffset + 40;
} //{2U_bully6}
// Add "Hunter-Hunter" text if present (for BC04 - Tuna)
if (self.creatureData && self.creatureData.hunterHunter) {
self.hunterHunterText = new Text2("Hunter-Hunter", {
size: 24,
fill: 0x00FFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.hunterHunterText.anchor.set(0.5, 0.5);
self.hunterHunterText.x = 0;
self.hunterHunterText.y = nextYOffset;
self.addChild(self.hunterHunterText);
nextYOffset = nextYOffset + 40;
}
// Add "Sea Bound" text for AC02
if (self.creatureData && self.creatureData.id === 'AC02') {
self.seaBoundText = new Text2("Sea Bound", {
size: 24,
fill: 0x00BFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.seaBoundText.anchor.set(0.5, 0.5);
self.seaBoundText.x = 0;
self.seaBoundText.y = nextYOffset;
self.addChild(self.seaBoundText);
nextYOffset = nextYOffset + 40;
}
// Add "Whale Chow Eater" text if present (for AC03 only)
if (self.creatureData && self.creatureData.id === 'AC03') {
self.whaleChowEaterText = new Text2("Whale Chow Eater", {
size: 24,
fill: 0x00BFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.whaleChowEaterText.anchor.set(0.5, 0.5);
self.whaleChowEaterText.x = 0;
self.whaleChowEaterText.y = nextYOffset;
self.addChild(self.whaleChowEaterText);
nextYOffset = nextYOffset + 40;
}
// Add "Dominant DNA" text if present
if (self.creatureData && self.creatureData.dominantDNA) {
self.dominantDNAText = new Text2("Dominant DNA", {
size: 24,
fill: 0x9932CC,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.dominantDNAText.anchor.set(0.5, 0.5);
self.dominantDNAText.x = 0;
self.dominantDNAText.y = nextYOffset;
self.addChild(self.dominantDNAText);
nextYOffset = nextYOffset + 40;
}
if (self.creatureData && self.creatureData.nutritious) {
self.nutritiousText = new Text2("Nutritious", {
size: 24,
fill: 0x32CD32,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.nutritiousText.anchor.set(0.5, 0.5);
self.nutritiousText.x = 0;
self.nutritiousText.y = nextYOffset;
self.addChild(self.nutritiousText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.bountiful) {
self.bountifulText = new Text2("Bountiful", {
size: 24,
fill: 0xFFD700,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.bountifulText.anchor.set(0.5, 0.5);
self.bountifulText.x = 0;
self.bountifulText.y = nextYOffset;
self.addChild(self.bountifulText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.poisonous) {
self.poisonousText = new Text2("Poisonous", {
size: 24,
fill: 0x8A2BE2,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.poisonousText.anchor.set(0.5, 0.5);
self.poisonousText.x = 0;
self.poisonousText.y = nextYOffset;
self.addChild(self.poisonousText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.delicate) {
self.delicateText = new Text2("Delicate", {
size: 24,
fill: 0xFFC0CB,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.delicateText.anchor.set(0.5, 0.5);
self.delicateText.x = 0;
self.delicateText.y = nextYOffset;
self.addChild(self.delicateText);
nextYOffset += 40;
}
if (self.creatureData && self.creatureData.forerunner) {
self.forerunnerText = new Text2("Forerunner", {
size: 24,
fill: 0xFFD700,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.forerunnerText.anchor.set(0.5, 0.5);
self.forerunnerText.x = 0;
self.forerunnerText.y = nextYOffset;
self.addChild(self.forerunnerText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.shy) {
self.shyText = new Text2("Shy", {
size: 24,
//{3F_shy}
fill: 0xADD8E6,
//{3G_shy}
font: "'Arial Black', 'Impact', sans-serif",
//{3H_shy}
fontWeight: "bold" //{3I_shy}
}); //{3J_shy}
self.shyText.anchor.set(0.5, 0.5);
self.shyText.x = 0;
self.shyText.y = nextYOffset;
self.addChild(self.shyText);
nextYOffset += 28; //{3K_shy}
} //{3L_shy}
if (self.creatureData && self.creatureData.unlucky) {
self.unluckyText = new Text2("Unlucky", {
size: 24,
//{3M_unlucky}
fill: 0x8B0000,
//{3N_unlucky}
font: "'Arial Black', 'Impact', sans-serif",
//{3O_unlucky}
fontWeight: "bold" //{3P_unlucky}
}); //{3Q_unlucky}
self.unluckyText.anchor.set(0.5, 0.5);
self.unluckyText.x = 0;
self.unluckyText.y = nextYOffset;
self.addChild(self.unluckyText);
nextYOffset += 28; //{3R_unlucky}
} //{3S_unlucky}
if (self.creatureData && self.creatureData.watcher) {
self.watcherText = new Text2("Watcher", {
size: 24,
fill: 0x9370DB,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.watcherText.anchor.set(0.5, 0.5);
self.watcherText.x = 0;
self.watcherText.y = nextYOffset;
self.addChild(self.watcherText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.slow) {
self.slowText = new Text2("Slow", {
size: 24,
fill: 0x8B4513,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.slowText.anchor.set(0.5, 0.5);
self.slowText.x = 0;
self.slowText.y = nextYOffset;
self.addChild(self.slowText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.terraformer) {
self.terraformerText = new Text2("Terra-former", {
size: 24,
fill: 0x8B4513,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.terraformerText.anchor.set(0.5, 0.5);
self.terraformerText.x = 0;
self.terraformerText.y = nextYOffset;
self.addChild(self.terraformerText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.extinctionTrigger) {
self.extinctionTriggerText = new Text2("Extinction Trigger", {
size: 20,
fill: 0xFF69B4,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.extinctionTriggerText.anchor.set(0.5, 0.5);
self.extinctionTriggerText.x = 0;
self.extinctionTriggerText.y = nextYOffset;
self.addChild(self.extinctionTriggerText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.shell) {
self.shellText = new Text2("Shell", {
size: 24,
fill: 0xD2B48C,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.shellText.anchor.set(0.5, 0.5);
self.shellText.x = 0;
self.shellText.y = nextYOffset;
self.addChild(self.shellText);
nextYOffset += 28;
}
return self;
});
var EventCard = Container.expand(function (eventData) {
var self = Container.call(this);
self.eventData = eventData || {
type: 'basic',
cardType: 'event',
level: 'Basic',
name: 'Event Card',
effect: 'environmental'
};
// Create event card visual
var eventAsset = self.attachAsset('eventCard', {
anchorX: 0.5,
anchorY: 0.5
});
// Add event text - placeholder "EVENT!!!" in red
var eventText = new Text2("EVENT!!!", {
size: 32,
fill: 0xFF0000
});
eventText.anchor.set(0.5, 0.5);
eventText.x = 0;
eventText.y = 0;
self.addChild(eventText);
// Add event level indicator
var levelText = new Text2(self.eventData.level, {
size: 16,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0);
levelText.x = 0;
levelText.y = -80;
self.addChild(levelText);
self.isInPlay = false;
self.move = function (x, y, obj) {
var globalX = self.x + x * self.scaleX;
var globalY = self.y + y * self.scaleY;
onMouseMove(globalX, globalY);
};
self.down = function (x, y, obj) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
if (self.isShellTarget) return; // Handled by Shell interceptor
var handIndex = playerHand.indexOf(self);
if (handIndex !== -1) playerHand.splice(handIndex, 1);
if (self.parent) self.parent.removeChild(self);
// Define cleanup callback so interactive events can pause resolution
var finalizeEvent = function finalizeEvent() {
if (self.eventData.level === 'Advanced') advancedEventDiscard.push(self.eventData);else basicEventDiscard.push(self.eventData);
updateHandPositions();
var moreEvents = false;
for (var i = 0; i < playerHand.length; i++) {
if (playerHand[i].eventData) {
moreEvents = true;
break;
}
}
if (!moreEvents) {
hasEventCardsInHand = false;
gamePhase = 'noon';
gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber);
if (game.eventDebugBox && game.eventDebugBox.parent) {
game.eventDebugBox.parent.removeChild(game.eventDebugBox);
game.eventDebugBox = null;
}
var remaining = playerHand.slice();
if (remaining.length > 0) discardCards(remaining);
}
};
// Execute specific event logic
if (self.eventData.id === 'BE01') {
for (var i = 0; i < 3; i++) {
if (mainDeck.length > 0) discardPile.push(mainDeck.pop());
}
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
finalizeEvent();
} else if (self.eventData.id === 'BE02') {
processDevolutionEvent(finalizeEvent);
} else if (self.eventData.id === 'BE03') {
processDiseaseEvent(finalizeEvent);
} else if (self.eventData.id === 'BE04') {
processVolcanoEvent(finalizeEvent);
} else {
finalizeEvent(); // Fallback for unimplemented events
}
}
};
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;
if (!self.pinkBorder) {
self.pinkBorder = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
self.pinkBorder.tint = 0xFFFF00; // Bright Yellow
self.pinkBorder.alpha = 0.6;
self.pinkBorder.scaleX = self.terrainCard.scaleX * 1.15;
self.pinkBorder.scaleY = self.terrainCard.scaleY * 1.15;
self.pinkBorder.x = self.terrainCard.x;
self.pinkBorder.y = self.terrainCard.y;
// Bring to the absolute front of the game instead of hiding behind the terrain
game.addChild(self.pinkBorder);
}
}
};
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);
}
var nextTerrainYOffset = 40;
if (self.terrainData && self.terrainData.simplify) {
var simplifyText = new Text2("Simplify", {
size: 24,
fill: 0x00BFFF,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
simplifyText.anchor.set(0.5, 0.5);
simplifyText.x = 0;
simplifyText.y = nextTerrainYOffset;
self.addChild(simplifyText);
nextTerrainYOffset += 40;
}
if (self.terrainData && self.terrainData.displacer) {
var displacerText = new Text2("Displacer", {
size: 24,
fill: 0xFF8C00,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
displacerText.anchor.set(0.5, 0.5);
displacerText.x = 0;
displacerText.y = nextTerrainYOffset;
self.addChild(displacerText);
nextTerrainYOffset += 40;
}
// Add card ID at center of bottom for advanced terrain cards
if (self.terrainData.level === 'Advanced' && self.terrainData.id) {
self.cardIdText = new Text2(self.terrainData.id, {
size: 20,
fill: 0xFFFFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif"
});
self.cardIdText.anchor.set(0.5, 0.5);
self.cardIdText.x = 0;
self.cardIdText.y = 140; // Bottom center of card
self.addChild(self.cardIdText);
}
// Climate will be displayed as row background instead of on individual cards
self.gridX = -1;
self.gridY = -1;
self.creatureStack = [];
self.move = function (x, y, obj) {
var globalX = self.x + x * self.scaleX;
var globalY = self.y + y * self.scaleY;
onMouseMove(globalX, globalY);
};
self.down = function (x, y, obj) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
// Prevent selecting creature/terrain cards if event cards are in hand
if (hasEventCardsInHand) {
//{3H_event}
return;
}
selectedCard = self;
draggedCard = self;
originalCardPosition = {
x: self.x,
y: self.y
};
showPossibleMoves(self);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a252f
});
/****
* Game Code
****/
// Zoom container for magnified card view
// Terrain card assets - Land types with green strip
// Terrain card assets - Water types with blue/grey strip
// Climate indicator strips
// Creature card assets
// Event card asset
// UI elements
var zoomContainer = new Container();
zoomContainer.x = 0;
zoomContainer.y = 0;
LK.gui.center.addChild(zoomContainer);
var zoomedCard = null;
var currentHoveredCardForZoom = null;
var cardBoundsForZoom = 80; // Half card width for zoom trigger
var cardHeightBoundsForZoom = 110; // Half card height for zoom trigger
// Global mouse listener for tracking cursor position
var currentMouseX = 0;
var currentMouseY = 0;
function onMouseMove(x, y) {
var cardUnderMouse = null;
// Check hand (reverse order so top cards are checked first)
for (var i = playerHand.length - 1; i >= 0; i--) {
var card = playerHand[i];
var hw = (card.terrainData ? 126 : 80) * card.scaleX;
var hh = (card.terrainData ? 168 : 110) * card.scaleY;
if (Math.abs(card.x - x) < hw && Math.abs(card.y - y) < hh) {
cardUnderMouse = card;
break;
}
}
// Check Scry Cards
if (!cardUnderMouse && typeof scryCards !== 'undefined' && scryCards.length > 0) {
for (var i = scryCards.length - 1; i >= 0; i--) {
var card = scryCards[i];
var hw = (card.terrainData ? 126 : 80) * card.scaleX;
var hh = (card.terrainData ? 168 : 110) * card.scaleY;
if (Math.abs(card.x - x) < hw && Math.abs(card.y - y) < hh) {
cardUnderMouse = card;
break;
}
}
}
// Check AT18 Special Selection Cards
if (!cardUnderMouse && typeof at18Active !== 'undefined' && at18Active) {
for (var i = game.children.length - 1; i >= 0; i--) {
var child = game.children[i];
if (child.at18Selectable) {
var hw = (child.terrainData ? 126 : 80) * child.scaleX;
var hh = (child.terrainData ? 168 : 110) * child.scaleY;
if (Math.abs(child.x - x) < hw && Math.abs(child.y - y) < hh) {
cardUnderMouse = child;
break;
}
}
}
}
// Check Forerunner Special Selection Cards
if (!cardUnderMouse && typeof forerunnerActive !== 'undefined' && forerunnerActive) {
for (var i = game.children.length - 1; i >= 0; i--) {
var child = game.children[i];
if (child.forerunnerSelectable) {
var hw = (child.terrainData ? 126 : 80) * child.scaleX;
var hh = (child.terrainData ? 168 : 110) * child.scaleY;
if (Math.abs(child.x - x) < hw && Math.abs(child.y - y) < hh) {
cardUnderMouse = child;
break;
}
}
}
}
// Check Watcher Special Selection Cards
if (!cardUnderMouse && typeof watcherActive !== 'undefined' && watcherActive) {
for (var i = game.children.length - 1; i >= 0; i--) {
var child = game.children[i];
if (child.watcherSelectable) {
var hw = (child.terrainData ? 126 : 80) * child.scaleX;
var hh = (child.terrainData ? 168 : 110) * child.scaleY;
if (Math.abs(child.x - x) < hw && Math.abs(child.y - y) < hh) {
cardUnderMouse = child;
break;
}
}
}
}
// Check board
if (!cardUnderMouse) {
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain) {
// 1. Check TOP creature
if (terrain.creatureStack && terrain.creatureStack.length > 0) {
var topIndex = terrain.creatureStack.length - 1;
var topCreature = terrain.creatureStack[topIndex];
var cHW = 80 * topCreature.scaleX;
var cHH = 110 * topCreature.scaleY;
if (Math.abs(topCreature.x - x) < cHW && Math.abs(topCreature.y - y) < cHH) {
cardUnderMouse = topCreature;
}
// 2. Check LOWER creatures
if (!cardUnderMouse && topIndex > 0) {
for (var c = topIndex - 1; c >= 0; c--) {
var lowerC = terrain.creatureStack[c];
var lHW = 80 * lowerC.scaleX;
var lHH = 110 * lowerC.scaleY;
var cardBottom = lowerC.y + lHH;
if (Math.abs(lowerC.x - x) < lHW && y <= cardBottom && y >= cardBottom - 45 * lowerC.scaleY) {
cardUnderMouse = lowerC;
break;
}
}
}
}
// 3. Check Terrain
if (!cardUnderMouse) {
var tHW = 126 * terrain.scaleX;
var tHH = 168 * terrain.scaleY;
if (Math.abs(terrain.x - x) < tHW && Math.abs(terrain.y - y) < tHH) {
cardUnderMouse = terrain;
}
}
}
if (cardUnderMouse) break;
}
if (cardUnderMouse) break;
}
}
// Process state changes
if (cardUnderMouse && cardUnderMouse !== currentlyHoveredCard) {
if (currentlyHoveredCard) clearZoom();
zoomCard(cardUnderMouse);
showCardInfo(cardUnderMouse);
if (!cardUnderMouse.isInPlay) {
showPossibleMoves(cardUnderMouse);
}
currentlyHoveredCard = cardUnderMouse;
} else if (!cardUnderMouse && currentlyHoveredCard) {
clearZoom();
hideCardInfo();
hidePossibleMoves();
currentlyHoveredCard = null;
}
}
var currentlyHoveredCard = null;
game.move = function (x, y, obj) {
currentMouseX = x;
currentMouseY = y;
// Call centralized mouse move handler for zoom
onMouseMove(x, y);
if (draggedCard) {
draggedCard.x = x;
draggedCard.y = y;
// Ensure dragged card is always on top
game.removeChild(draggedCard);
game.addChild(draggedCard);
// Highlights are already shown from the card's down event
} else if (draggedLinkMarker && gamePhase === 'dusk' && linkAdjustmentMode) {
// Handle link marker dragging - highlight valid targets only while holding
var carnivore = draggedLinkMarker.carnivore;
highlightValidLinkTargets(carnivore);
// Create temporary visual line from carnivore to cursor
// Remove any existing temp line
if (game.tempLinkLine && game.tempLinkLine.parent) {
game.tempLinkLine.parent.removeChild(game.tempLinkLine);
}
// Create new temp line
var tempLine = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.01,
scaleY: 1
});
tempLine.tint = 0xFFFF00; // Yellow temp line
tempLine.alpha = 0.5;
var dx = x - carnivore.x;
var dy = y - carnivore.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
tempLine.x = carnivore.x + dx / 2;
tempLine.y = carnivore.y + dy / 2;
tempLine.rotation = angle;
tempLine.scaleY = distance / 336;
game.addChild(tempLine);
game.tempLinkLine = tempLine;
}
};
// Game state variables
var currentRound = 0;
var maxRounds = 5;
var cardsDrawnThisTurn = 0;
var maxCardsPerTurn = 3;
var planetBoard = [];
var planetSlots = [];
var playerHand = [];
var maxHandSize = 3;
var mainDeck = [];
var basicTerrainPool = [];
var selectedCard = null;
var draggedCard = null;
var originalCardPosition = null;
var scryTokenActive = false; // Start without scry token
var scryCards = []; // Cards in scry mode
var scryMode = false; // Whether scry is active
var scryStep = 0; // Track which step of scry (1 = select for bottom, 2 = select to discard)
var scryHeaderText = null; // Header text for scry instructions
var creaturesWentExtinct = false; // Track if creatures went extinct this round
var cardInfoText = null;
var hoveredCard = null;
var drawPhase = 'initial'; // 'initial', 'forced'
var cardsDrawnInPhase = [];
var discardPile = [];
var basicEventDiscard = [];
var advancedEventDiscard = [];
var linkLines = []; // Visual link lines between carnivores and herbivores
var gamePhase = 'dawn'; // Track current game phase: 'dawn', 'draw', 'noon', 'dusk', 'night', 'end'
var turnNumber = 1;
var draggedLinkMarker = null;
var draggedLinkCarnivore = null;
var linkHighlights = [];
var eventCardsToPlay = [];
var mustPlayCard = null;
var basicEventDeck = [];
var advancedEventDeck = [];
var phaseTransitionTimer = null;
var scryDeniedNextRound = false;
var hasEventCardsInHand = false;
// Planet layout: 2-4-6-4-2 formation
var planetLayout = [2, 4, 6, 4, 2];
var planetWidth = 6; // Max cards in any row
var planetHeight = 5; // Number of rows
// UI Elements
var deckCountText = new Text2("Deck: " + mainDeck.length, {
size: 36,
fill: 0xFFFFFF
});
deckCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(deckCountText);
deckCountText.x = 120;
deckCountText.y = 150;
var discardCountText = new Text2("Discard: 0", {
size: 36,
fill: 0xFFFFFF
});
discardCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(discardCountText);
discardCountText.x = 120;
discardCountText.y = 190;
var basicEventCountText = new Text2("Basic Events: 10", {
size: 28,
fill: 0x00FF00
});
basicEventCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(basicEventCountText);
basicEventCountText.x = 120;
basicEventCountText.y = 230;
var advancedEventCountText = new Text2("Advanced Events: 10", {
size: 28,
fill: 0xFF6600
});
advancedEventCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(advancedEventCountText);
advancedEventCountText.x = 120;
advancedEventCountText.y = 260;
var graveyardCountText = new Text2("Graveyard: 0", {
size: 28,
//{4f_graveyard}
fill: 0xFFFFFF
}); //{4g_graveyard}
graveyardCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(graveyardCountText);
graveyardCountText.x = 120;
graveyardCountText.y = 290;
var eventDiscardCountText = new Text2("Event Discards: B:0 | A:0", {
size: 28,
fill: 0x9932CC
});
eventDiscardCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(eventDiscardCountText);
eventDiscardCountText.x = 120;
eventDiscardCountText.y = 320;
var graveyard = [];
var bonusPile = [];
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,
displacer: seaCardConfigs[i].id === 'AT07' ? true : false,
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,
simplify: flatCardConfigs[i].id === 'AT12' ? true : false,
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,
extinctionTrigger: true
}, {
id: 'BH06',
name: 'Tropical Crab',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'hot',
special: false
}, {
id: 'BH07',
name: 'Plankton',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'cold',
special: true
}, {
id: 'BH08',
name: 'Limpet',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate',
special: false
}, {
id: 'BH09',
name: 'Red Crab',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'hot',
special: false
}, {
id: 'BH10',
name: 'Jelly Fish',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'cold',
special: true
}, {
id: 'BH11',
name: 'Mussel',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate',
special: true
}, {
id: 'BH12',
name: 'Whelk',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/cold',
special: false
}, {
id: 'BH13',
name: 'Krill',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/hot',
special: true
}, {
id: 'BH14',
name: 'Hare',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate',
special: false
}, {
id: 'BH15',
name: 'Meerkat',
landType: 'flat',
colorBand: 'brown',
climate: 'hot',
special: false
}, {
id: 'BH16',
name: 'Pheasant',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate',
special: true
}, {
id: 'BH17',
name: 'Tortoise',
landType: 'flat',
colorBand: 'brown',
climate: 'hot',
special: false
}, {
id: 'BH18',
name: 'Goose',
landType: 'flat',
colorBand: 'brown',
climate: 'cold',
special: true
}, {
id: 'BH19',
name: 'Pig',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate/hot',
special: false
}, {
id: 'BH20',
name: 'Platypus',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate/cold',
special: true
}, {
id: 'BH21',
name: 'Rabbit',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate',
special: false
}, {
id: 'BH22',
name: 'Chicken',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate',
special: false
}, {
id: 'BH23',
name: 'Ice Squirrel',
landType: 'hills',
colorBand: 'purple',
climate: 'cold',
special: false
}, {
id: 'BH24',
name: 'Pea Fowl',
landType: 'hills',
colorBand: 'purple',
climate: 'hot',
special: false
}, {
id: 'BH25',
name: 'Mole-Rat',
landType: 'hills',
colorBand: 'purple',
climate: 'hot',
special: false
}, {
id: 'BH26',
name: 'Guinnea pig',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate/cold',
special: true
}, {
id: 'BH27',
name: 'Wombat',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate/hot',
special: true
}, {
id: 'BH28',
name: 'Marmoset',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate',
special: false
}, {
id: 'BH29',
name: 'Porcupine',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate',
special: false
}, {
id: 'BH30',
name: 'Skunk',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'hot',
special: true
}, {
id: 'BH31',
name: 'Shaggy Sheep',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'cold',
special: true
}, {
id: 'BH32',
name: 'Worms',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate/hot',
special: true
}, {
id: 'BH33',
name: 'Giant Snail',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate/cold',
special: true,
shell: true
}];
for (var i = 0; i < basicHerbivoreConfigs.length; i++) {
var config = basicHerbivoreConfigs[i];
var subtype = config.waterType ? 'water' : 'land';
var waterType = config.waterType || null;
var landType = config.landType || null;
mainDeck.push({
type: 'basic',
level: 'Basic',
cardType: 'creature',
dietType: 'herbivore',
name: config.name,
id: config.id,
subtype: subtype,
waterType: waterType,
landType: landType,
colorBand: config.colorBand,
climateRequirement: config.climate,
special: config.special,
flying: config.id === 'BH18' || config.id === 'BH16' ? true : false,
swimming: config.id === 'BH02' || config.id === 'BH07' || config.id === 'BH10' || config.id === 'BH13' || config.id === 'BH20' ? true : false,
tough: config.id === 'BH31' ? true : false,
squishy: config.id === 'BH03' || config.id === 'BH27' ? true : false,
stinky: config.id === 'BH04' || config.id === 'BH30' ? true : false,
efficient: config.id === 'BC03' ? true : false,
// Efficient for BC03 (Puffin)
whaleFood: config.id === 'BH07' || config.id === 'BH13' ? true : false,
whaleChow: config.id === 'BH07' || config.id === 'BH13' ? true : false,
nutritious: config.id === 'BH11' ? true : false,
poisonous: config.id === 'BH20' ? true : false,
delicate: config.id === 'BH26' ? true : false,
terraformer: config.id === 'BH32' ? true : false
});
}
// Add 8 Advanced Carnivore Cards (AC01-AC08)
var advancedCarnivoreConfigs = [{
id: 'AC01',
name: 'Pike',
waterType: 'fresh',
colorBand: 'grey',
climate: 'any',
special: true
}, {
id: 'AC02',
name: 'Great White Shark',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'any',
special: true,
bully: true,
seaBound: true //{c4_bully}
}, {
id: 'AC03',
name: 'Whale',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'any',
special: true
}, {
id: 'AC04',
name: 'Snakes!',
landType: 'any',
colorBand: 'brown',
climate: 'any',
special: true
}, {
id: 'AC05',
name: 'Bear',
landType: 'any',
colorBand: 'brown',
climate: 'any',
special: true
}, {
id: 'AC06',
name: 'Buzzard',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate/cold',
special: true
}, {
id: 'AC07',
name: 'Tiger',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate/hot',
special: true
}, {
id: 'AC08',
name: 'Leopard',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate/hot',
special: true
}];
for (var i = 0; i < advancedCarnivoreConfigs.length; i++) {
var config = advancedCarnivoreConfigs[i];
var subtype = config.waterType ? 'water' : 'land';
var waterType = config.waterType || null;
var landType = config.landType || null;
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'creature',
dietType: 'carnivore',
name: config.name,
id: config.id,
subtype: subtype,
waterType: waterType,
landType: landType,
colorBand: config.colorBand,
climateRequirement: config.climate,
special: config.special,
flying: config.id === 'AC06' ? true : false,
swimming: config.id === 'AC01' || config.id === 'AC02' || config.id === 'AC03' ? true : false,
efficient: config.id === 'AC02' || config.id === 'AC04' || config.id === 'AC06' ? true : false,
// Efficient for AC02, AC04, AC06
isWhale: config.id === 'AC03' ? true : false,
fatty: config.id === 'AC03' ? true : false,
seaBound: config.id === 'AC02' ? true : false,
bully: config.id === 'AC02' ? true : false,
//{c9_bully}
dominantDNA: config.id === 'AC01' ? true : false,
shy: config.id === 'AC05' ? true : false,
unlucky: config.id === 'AC07' ? true : false
});
}
// 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)
delicate: config.id === 'BC01' ? true : false,
forerunner: config.id === 'BC07' ? true : false,
watcher: config.id === 'BC06' ? true : false,
slow: config.id === 'BC06' ? true : false
});
}
// 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,
dominantDNA: config.id === 'AH11' ? true : false,
bountiful: config.id === 'AH12' ? true : false
});
}
// Shuffle deck
shuffleDeck();
}
function shuffleDeck() {
for (var i = mainDeck.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = mainDeck[i];
mainDeck[i] = mainDeck[j];
mainDeck[j] = temp;
}
}
function shuffleEventDeck(eventDeck) {
for (var i = eventDeck.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = eventDeck[i];
eventDeck[i] = eventDeck[j];
eventDeck[j] = temp;
}
}
function showCardInfo(card) {
hoveredCard = card;
// Remove existing info text if any
if (cardInfoText && cardInfoText.parent) {
cardInfoText.parent.removeChild(cardInfoText);
}
var infoString = "";
if (card.creatureData) {
// Creature card information
var creature = card.creatureData;
if (creature) {
infoString = creature.name + " | Level: " + creature.level + " " + creature.dietType;
if (creature && creature.terrainRequirement && creature.terrainRequirement !== 'any') {
infoString += " | Terrain: " + creature.terrainRequirement;
}
if (creature && creature.climateRequirement && creature.climateRequirement !== 'any') {
infoString += " | Climate: " + creature.climateRequirement;
}
// Add special rules text for Tough
if (creature && creature.tough) {
infoString += " | Tough: Creature requires 3 or more extinction markers before it must become extinct";
}
if (creature && creature.squishy) {
infoString += " | Squishy: Creature requires only 1 or more extinction markers before it must become extinct";
}
if (creature && creature.stinky) {
infoString += " | Stinky: Discard all non-Stinky creatures. Phew!";
}
if (creature && creature.efficient) {
infoString += " | Efficient: This creature can make diagonal links as well as adjacent ones";
}
if (creature && creature.whaleFood) {
infoString += " | Whale chow!: This creature can only be linked to whales";
}
if (creature && creature.id === 'AC03') {
infoString += " | Whale: This creature can link to Krill and Plankton";
}
if (creature && creature.hunterHunter) {
infoString += " | Hunter-Hunter: This creature can only link to Carnivore cards NOT Herbivores!";
} //{fl_hh}
if (creature && creature.fussy) {
infoString += " | Fussy: This creature can only link to Basic Herbivore cards NOT Advanced Herbivores!";
} //{fl_fussy}
if (creature && creature.bully) {
infoString += " | Bully: This creature can create links to Basic Carnivores as well as Basic Herbivores!";
} //{fl_bully}
if (creature && creature.fatty) {
infoString += " | Fatty! Requires 3 links to support it and gives x2 the normal advanced carnivore score at end of game!";
}
if (creature && creature.seaBound) {
infoString += " | Sea-bound: This creature can only create links to other Water creatures!";
}
if (creature && creature.dominantDNA) {
infoString += " | Dominant DNA: This creature can evolve from any creature or devour any creature!";
}
if (creature && creature.nutritious) {
infoString += " | Nutritious: This creature can sustain up to 2 links!";
}
if (creature && creature.bountiful) {
infoString += " | Bountiful: This creature can sustain up to 3 links!";
}
if (creature && creature.poisonous) {
infoString += " | Poisonous! Any carnivore cards feeding upon this creature will get an extinction marker during the night phase due to the toxin.";
}
if (creature && creature.delicate) {
infoString += " | Delicate: This card cannot be placed onto Advanced terrain. If basic terrain beneath it changes to or is an advanced terrain, it gets an unmitigable extinction marker during the night phase.";
}
if (creature && creature.forerunner) {
infoString += " | Forerunner: When you play this card, choose 1 card from the main discard pile and put it on top of the main deck.";
}
if (creature && creature.shy) {
infoString += " | Shy: Can only be played on an empty Advanced terrain card, no need to evolve or devour!";
} //{hJ_shy}
if (creature && creature.unlucky) {
infoString += " | Unlucky: If this card becomes extinct it will generate an extra advanced event!";
} //{hJ_unlucky}
if (creature && creature.watcher) {
infoString += " | Watcher: When played you may return an event card from the main discard pile to its respective event deck!";
}
if (creature && creature.slow) {
infoString += " | Slow: This creature is not quick enough to catch creatures that have flying!";
}
if (creature && creature.terraformer) {
infoString += " | Terra-former: When played, choose an adjacent empty Advanced terrain and devolve it, swapping the Basic terrain beneath. If no Advanced terrains are available, swap an empty Basic terrain instead.";
}
if (creature && creature.extinctionTrigger) infoString += " | Extinction Trigger! If this creature goes extinct it adds +3 points as an end of game score bonus instead of generating an event card!";
if (creature && creature.shell) infoString += " | Shell: When placed, gets a shell token. The next event drawn is negated and returned to the event deck! If covered, the shell is lost.";
}
} else if (card.eventData) {
var evt = card.eventData;
infoString = evt.name + " | Level: " + evt.level + " Event";
if (evt.id === 'BE01') infoString += " | Time fly's: The top 3 cards of the main deck are discarded";
if (evt.id === 'BE02') infoString += " | Devolution: Choose an advanced carnivore, it will be removed from play into the discard pile";
if (evt.id === 'BE03') infoString += " | Disease: Place an extinction marker on a Basic Creature...Twice!";
if (evt.id === 'BE04') infoString += " | Volcano: Place on a mountain. Creatures without flying/swimming must flee to adjacent stacks or die!";
} 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}
if (terrain.simplify) {
infoString += " | Simplify: When this terrain is placed, if there are any other Advanced terrain cards adjacent (including diagonals) that have no creature cards upon them you must choose 1 of them and devolve it!";
} //{ej_simplify}
if (terrain.displacer) {
infoString += " | Displacer: If played onto a stack with 1 or more creatures, move the top creature into the main deck discard pile!";
} //{ej_displacer}
}
}
// Create info text at bottom of screen
cardInfoText = new Text2(infoString, {
size: 42,
fill: 0xFFFFFF,
wordWrap: true,
wordWrapWidth: 1800
});
cardInfoText.anchor.set(0.5, 1);
cardInfoText.x = 1024; // Center of screen
cardInfoText.y = 2700; // Bottom of screen
game.addChild(cardInfoText);
}
function hideCardInfo() {
if (cardInfoText && cardInfoText.parent) {
cardInfoText.parent.removeChild(cardInfoText);
cardInfoText = null;
}
hoveredCard = null;
}
function zoomCard(card) {
// If a different card was previously zoomed, clear it
if (currentHoveredCardForZoom && currentHoveredCardForZoom !== card) {
clearZoom();
}
// If card is already zoomed, do nothing
if (currentHoveredCardForZoom === card && zoomedCard) {
return;
}
// Clear existing zoomed card
if (zoomedCard && zoomedCard.parent) {
zoomedCard.parent.removeChild(zoomedCard);
zoomedCard = null;
}
// Track current hovered card
currentHoveredCardForZoom = card;
// Create a clone of the card for zooming
var clonedCard;
if (card.creatureData) {
clonedCard = new CreatureCard(card.creatureData);
} else if (card.terrainData) {
clonedCard = new TerrainCard(card.terrainData);
} else if (card.eventData) {
clonedCard = new EventCard(card.eventData);
} else {
return;
}
// Set the cloned card's initial scale to match the card being hovered
clonedCard.scale.set(card.scale.x, card.scale.y);
// Position cloned card
clonedCard.x = 550;
clonedCard.y = 580;
// Apply explicit sizing based on card type
if (card.terrainData) {
clonedCard.scale.set(1.27, 1.31);
} else {
clonedCard.scale.set(2.0, 2.0);
}
clonedCard.alpha = 1;
// Add to zoom container
zoomContainer.addChild(clonedCard);
zoomedCard = clonedCard;
}
function clearZoom() {
if (zoomedCard && zoomedCard.parent) {
zoomedCard.parent.removeChild(zoomedCard);
}
zoomedCard = null;
currentHoveredCardForZoom = null;
}
function clearZoomIfNoCards() {
// Clear zoom when hand is empty (between turns or after card play)
if (playerHand.length === 0 && zoomedCard) {
clearZoom();
currentlyHoveredCard = null;
}
}
function showPossibleMoves(card) {
if (card.creatureData) {
// Highlight valid terrain placement for creatures
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && canPlaceCreatureOnTerrain(card, terrain)) {
planetSlots[gridY][gridX].highlight();
}
}
}
} else if (card.terrainData) {
// Highlight valid terrain placement for advanced terrain
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var existingTerrain = planetBoard[gridY][gridX];
if (existingTerrain && canPlaceTerrainOnTerrain(card, existingTerrain)) {
planetSlots[gridY][gridX].highlight();
}
}
}
}
}
function hidePossibleMoves() {
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
if (planetSlots[gridY][gridX]) {
planetSlots[gridY][gridX].unhighlight();
}
}
}
}
function hasValidPlacements(cards) {
for (var i = 0; i < cards.length; i++) {
var card = cards[i];
if (card.creatureData) {
// Check creature placements
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && canPlaceCreatureOnTerrain(card, terrain)) {
return true;
}
}
}
} else if (card.terrainData) {
// Check terrain placements
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && canPlaceTerrainOnTerrain(card, terrain)) {
return true;
}
}
}
} else if (card.eventData) {
return true; // Event cards are always valid to play
}
}
return false;
}
function discardCards(cards) {
for (var i = 0; i < cards.length; i++) {
var card = cards[i];
// Remove from hand
var handIndex = playerHand.indexOf(card);
if (handIndex !== -1) {
playerHand.splice(handIndex, 1);
}
// Remove from game
if (card.parent) {
card.parent.removeChild(card);
}
// Extract raw card data and add to discard pile
var cardData = card.creatureData || card.terrainData || card.eventData;
discardPile.push(cardData);
}
updateHandPositions();
// Clear zoom if hand is now empty
clearZoomIfNoCards(); //{ip_new}
}
function drawCard() {
if (mainDeck.length === 0) {
endRound();
return null;
}
// Enforce maximum hand size of 3
if (playerHand.length >= maxHandSize) {
return null; // Hand is full
}
if (drawPhase === 'complete') {
return null; // Draw phase already complete
}
var cardData = mainDeck.pop();
var card;
// Create appropriate card type based on cardType
if (cardData.cardType === 'terrain') {
card = new TerrainCard(cardData);
// Scale terrain cards in hand to match creature cards
card.scaleX = 0.7;
card.scaleY = 0.7;
// Scale terrain cards in hand to match creature cards
card.scaleX = 0.7;
card.scaleY = 0.7;
} else {
// Default to creature card
card = new CreatureCard(cardData);
}
if (drawPhase === 'initial') {
// Initial draw phase - draw up to 3 cards
playerHand.push(card);
cardsDrawnInPhase.push(card);
cardsDrawnThisTurn++;
if (cardsDrawnInPhase.length >= 3) {
// Check if any of the 3 cards have valid placements
if (!hasValidPlacements(cardsDrawnInPhase)) {
// Discard all 3 cards and enter forced draw phase
discardCards(cardsDrawnInPhase);
cardsDrawnInPhase = [];
drawPhase = 'forced';
// Continue drawing in forced phase
return drawCard();
} else {
// At least one card has valid placement, end draw phase
drawPhase = 'complete';
}
}
} else if (drawPhase === 'forced') {
// Forced draw phase - draw one card at a time until playable
if (hasValidPlacements([card])) {
// This card can be played, add to hand and must be played
playerHand.push(card);
drawPhase = 'complete';
// Mark this card as must play
card.mustPlay = true;
} else {
// Card cannot be played, discard it and draw another
discardCards([card]);
return drawCard();
}
} else {
return null; // Draw phase complete
}
// Position card in hand
updateHandPositions();
game.addChild(card);
// Update UI
deckCountText.setText("Deck: " + mainDeck.length);
LK.getSound('cardDraw').play();
// Check for Stinky effect on newly drawn card
checkAndProcessStinkyEffect(); //{j8_stinky}
return card;
}
function startDrawPhase() {
if (mainDeck.length === 0) {
endRound();
return;
}
drawPhase = 'initial';
cardsDrawnInPhase = [];
cardsDrawnThisTurn = 0;
// Draw initial 3 cards
for (var i = 0; i < 3; i++) {
if (mainDeck.length > 0) {
drawCard();
}
}
// Check for Stinky creatures after drawing initial 3 cards
checkAndProcessStinkyEffect();
}
function checkAndProcessStinkyEffect() {
// Check if any creature in hand has Stinky attribute
var hasStinkyCreature = false;
var stinkyCreature = null;
for (var i = 0; i < playerHand.length; i++) {
//{stinky_check_1}
var card = playerHand[i];
if (card.creatureData && card.creatureData.stinky) {
//{stinky_check_2}
hasStinkyCreature = true;
stinkyCreature = card;
break;
} //{stinky_check_3}
} //{stinky_check_4}
// If Stinky creature found, discard all non-stinky creatures
if (hasStinkyCreature) {
//{stinky_process_1}
var toDiscard = [];
for (var j = 0; j < playerHand.length; j++) {
//{stinky_process_2}
var c = playerHand[j];
// Discard only if it has creatureData AND does NOT have stinky property true
if (c.creatureData && !c.creatureData.stinky) {
//{stinky_process_3}
toDiscard.push(c);
} //{stinky_process_4}
} //{stinky_process_5}
// Execute discard
if (toDiscard.length > 0) {
//{stinky_process_6}
discardCards(toDiscard);
} //{stinky_process_7}
} //{stinky_process_8}
// Check for event cards in hand after stinky processing and set flag
hasEventCardsInHand = false;
for (var k = 0; k < playerHand.length; k++) {
if (playerHand[k].eventData) {
hasEventCardsInHand = true;
break;
}
}
}
function checkAndShowEventCardDebugBox() {
// Check if any event cards are in hand
var eventCardsInHand = [];
for (var i = 0; i < playerHand.length; i++) {
//{debug_event_1}
if (playerHand[i].eventData) {
//{debug_event_2}
eventCardsInHand.push(playerHand[i]);
} //{debug_event_3}
} //{debug_event_4}
// If event cards found, show debug box
if (eventCardsInHand.length > 0) {
//{debug_event_5}
showEventCardDebugBox();
} //{debug_event_6}
} //{debug_event_7}
function showEventCardDebugBox() {
if (game.activeShellCreature) {
// Shell Interceptor Mode
if (!game.dangerWarningText) {
game.dangerWarningText = new Text2("Shell effect! Select the event you wish to negate. Shell will then be removed.", {
size: 36,
fill: 0x00FF00
});
game.dangerWarningText.anchor.set(0.5, 0.5);
game.dangerWarningText.x = 1024;
game.dangerWarningText.y = 130;
game.addChild(game.dangerWarningText);
}
// Make all events in hand clickable for negation
for (var i = 0; i < playerHand.length; i++) {
var card = playerHand[i];
if (card.eventData) {
card.isShellTarget = true;
card.tint = 0x00FF00;
card.originalDown = card.down;
card.down = function () {
var c = this;
// Remove shell globally
if (game.activeShellCreature && game.activeShellCreature.shellMarker) {
game.activeShellCreature.removeChild(game.activeShellCreature.shellMarker);
game.activeShellCreature.shellMarker = null;
}
game.activeShellCreature = null;
if (game.dangerWarningText) {
game.dangerWarningText.parent.removeChild(game.dangerWarningText);
game.dangerWarningText = null;
}
// Return event to deck instead of resolving
var hIdx = playerHand.indexOf(c);
if (hIdx !== -1) playerHand.splice(hIdx, 1);
if (c.eventData.level === 'Advanced') advancedEventDeck.push(c.eventData);else basicEventDeck.push(c.eventData);
if (c.parent) c.parent.removeChild(c);
// Clean up other events in hand (restore original down functions)
var moreEvents = false;
for (var k = 0; k < playerHand.length; k++) {
if (playerHand[k].eventData) {
playerHand[k].tint = 0xFFFFFF;
playerHand[k].down = playerHand[k].originalDown;
playerHand[k].isShellTarget = false;
moreEvents = true;
}
}
updateHandPositions();
// If no more events, progress phase
if (!moreEvents) {
hasEventCardsInHand = false;
gamePhase = 'noon';
gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber);
var remaining = playerHand.slice();
if (remaining.length > 0) discardCards(remaining);
} else {
// If other events remain, show normal debug box
showEventCardDebugBox();
}
};
}
}
} else {
// Normal Event Mode
var eventDebugBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 1.0
});
eventDebugBox.tint = 0xA020F0;
eventDebugBox.alpha = 0.95;
eventDebugBox.x = 1024;
eventDebugBox.y = 1000;
game.addChild(eventDebugBox);
var debugText = new Text2("Event card/s in hand! Click the cards to resolve them.", {
size: 34,
fill: 0xFFFFFF
});
debugText.anchor.set(0.5, 0.5);
debugText.x = 0;
debugText.y = 0;
eventDebugBox.addChild(debugText);
game.eventDebugBox = eventDebugBox;
}
}
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;
}
// Delicate creatures cannot be placed on Advanced terrain
if (creature.delicate && terrain.level === 'Advanced') {
return false;
}
// Check terrain type matching based on creature's subtype
if (creature.subtype === 'water') {
// Creature requires water terrain
if (terrain.subtype !== 'water') {
return false;
}
// Check specific water type matching - creature waterType must match terrain waterType
if (creature.waterType && terrain.waterType && creature.waterType !== terrain.waterType) {
return false;
}
} else if (creature.subtype === 'land') {
// Creature requires land terrain
if (terrain.subtype !== 'land') {
return false;
}
// Check specific land type matching - allow 'any' as wildcard
if (creature.landType && creature.landType !== 'any' && terrain.landType && creature.landType !== terrain.landType) {
return false;
}
}
// If creature has no subtype or terrain type, it cannot be placed
if (!creature.subtype) {
return false;
}
// Check climate requirements - use basic terrain climate if advanced terrain is placed on basic
var terrainClimate = terrain.climate;
// If this is an advanced terrain on basic terrain, use the basic terrain's climate
if (terrainCard.basicTerrainUnderneath && terrainCard.basicTerrainUnderneath.terrainData) {
terrainClimate = terrainCard.basicTerrainUnderneath.terrainData.climate;
}
if (creature.climateRequirement && creature.climateRequirement !== 'any') {
var climateMatches = false;
if (creature.climateRequirement.indexOf('/') !== -1) {
// Handle multiple climate requirements (e.g., "temperate/hot")
var allowedClimates = creature.climateRequirement.split('/');
for (var k = 0; k < allowedClimates.length; k++) {
if (allowedClimates[k] === terrainClimate) {
climateMatches = true;
break;
}
}
} else {
// Single climate requirement
climateMatches = creature.climateRequirement === terrainClimate;
}
if (!climateMatches) {
return false;
}
}
// Advanced creatures can only be placed on advanced terrain
if (creature.level === 'Advanced') {
if (terrain.level !== 'Advanced') {
return false;
}
}
// Basic herbivores can only be placed on basic terrain (but advanced terrain counts as valid if it's on basic terrain)
if (creature.level === 'Basic' && creature.dietType === 'herbivore') {
// Allow placement on advanced terrain that's placed on basic terrain, or directly on basic terrain
if (terrain.level !== 'Basic' && !terrainCard.basicTerrainUnderneath) {
return false;
}
}
// Dominant DNA bypasses ALL creature stacking restrictions (but still obeys terrain/climate checked above)
if (creature.dominantDNA && terrainCard.creatureStack.length > 0) {
return true;
}
// Shy creatures CANNOT be placed on occupied terrain
if (creature.shy) {
return false; //{lD_shy}
} //{lE_shy}
// Check creature stacking rules - verify top card in stack
if (terrainCard.creatureStack && terrainCard.creatureStack.length > 0) {
var topCreatureOnTerrain = terrainCard.creatureStack[terrainCard.creatureStack.length - 1];
// Prevent placing anything on advanced carnivores
if (topCreatureOnTerrain.creatureData.dietType === 'carnivore' && topCreatureOnTerrain.creatureData.level === 'Advanced') {
return false; // Cannot place anything on advanced carnivores
}
// Prevent placing Basic Herbivores on Advanced Carnivores (strict enforcement)
if (creature.level === 'Basic' && creature.dietType === 'herbivore' && topCreatureOnTerrain.creatureData.dietType === 'carnivore' && topCreatureOnTerrain.creatureData.level === 'Advanced') {
return false; // Cannot place Basic Herbivore on Advanced Carnivore
}
// Prevent placing Basic Herbivores on any other carnivore
if (creature.level === 'Basic' && creature.dietType === 'herbivore' && topCreatureOnTerrain.creatureData.dietType === 'carnivore' && topCreatureOnTerrain.creatureData.level === 'Basic') {
return false; // Cannot place Basic Herbivore on Basic Carnivore
} //{kH_basic}
}
// Basic carnivores can be placed on any terrain type (basic or advanced) - no restriction needed
// Check creature stacking rules
if (terrainCard.creatureStack.length > 0) {
var topCreatureInStack = terrainCard.creatureStack[terrainCard.creatureStack.length - 1];
// Basic creature stacking rules
if (creature.level === 'Basic') {
// Basic herbivore restrictions
if (creature.dietType === 'herbivore') {
if (topCreatureInStack.creatureData.level === 'Basic' && topCreatureInStack.creatureData.dietType === 'herbivore') {
return false; // Basic herbivores cannot be placed on stacks with other basic herbivores at top
}
if (topCreatureInStack.creatureData.level === 'Advanced' && topCreatureInStack.creatureData.dietType === 'herbivore') {
return false; // Basic herbivores cannot be placed on stacks with advanced herbivores at top
}
if (topCreatureInStack.creatureData.level === 'Basic' && topCreatureInStack.creatureData.dietType === 'carnivore') {
return false; // Basic herbivores cannot be placed on stacks with basic carnivores at top
}
if (topCreatureInStack.creatureData.level === 'Advanced' && topCreatureInStack.creatureData.dietType === 'carnivore') {
return false; // Basic herbivores cannot be placed on stacks with advanced carnivores at top
} //{l8_advanced}
}
// Basic carnivore restrictions
if (creature.dietType === 'carnivore') {
if (topCreatureInStack.creatureData.level === 'Basic' && topCreatureInStack.creatureData.dietType === 'carnivore') {
return false; // Basic carnivores cannot be placed on stacks with other basic carnivores at top
}
if (topCreatureInStack.creatureData.level === 'Advanced' && topCreatureInStack.creatureData.dietType === 'carnivore') {
return false; // Basic carnivores cannot be placed on stacks with advanced carnivores at top
}
if (topCreatureInStack.creatureData.level === 'Advanced' && topCreatureInStack.creatureData.dietType === 'herbivore') {
return false; // Basic carnivores cannot be placed on stacks with advanced herbivores at top
}
}
}
// Advanced creature stacking rules
else if (creature.level === 'Advanced') {
// 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
if (creature.shy) {
// Shy MUST be placed on empty Advanced terrain (terrain.level is already checked above)
return true; //{lF_shy}
} else if (creature.level === 'Advanced') {
return false; // Normal 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);
}
}
// Trigger Displacer
if (advancedTerrainCard.terrainData.displacer && advancedTerrainCard.creatureStack.length > 0) {
var topC = advancedTerrainCard.creatureStack.pop();
if (topC.creatureData.dietType === 'carnivore') {
for (var j = topC.activeLinks.length - 1; j >= 0; j--) animateLinkFallOff(topC, topC.activeLinks[j].target);
} else {
for (var j = linkLines.length - 1; j >= 0; j--) if (linkLines[j].herbivore === topC) animateLinkFallOff(linkLines[j].carnivore, topC);
}
var cData = topC.creatureData || topC.terrainData || topC.eventData;
discardPile.push(cData);
discardCountText.setText("Discard: " + discardPile.length);
if (topC.parent) topC.parent.removeChild(topC);
}
// Trigger Simplify
if (advancedTerrainCard.terrainData.simplify) {
processSimplifySpecial(gridX, gridY);
}
// Update hand positions
updateHandPositions();
// Clear selection
selectedCard = null;
draggedCard = null;
// Reset draw phase after successful placement
if (advancedTerrainCard.mustPlay) {
drawPhase = 'complete';
}
// Check if AT15 was played and set flag for next turn
if (advancedTerrainCard.terrainData && advancedTerrainCard.terrainData.id === 'AT15') {
at15PlayedLastTurn = true;
}
// Check if AT18 was played and trigger special
if (advancedTerrainCard.terrainData && advancedTerrainCard.terrainData.id === 'AT18') {
processAT18Special();
} //{if_AT18}
// Check if this was the last event card or must play card
if (eventCardsToPlay.length > 0) {
//{ig}</antml>
// Remove this card from event cards to play
var eventIndex = eventCardsToPlay.indexOf(advancedTerrainCard);
if (eventIndex !== -1) {
eventCardsToPlay.splice(eventIndex, 1);
}
// If no more event cards to play, clear mustPlayCard and check for phase progression
if (eventCardsToPlay.length === 0) {
mustPlayCard = null;
// Update event cards tracking
hasEventCardsInHand = false;
// Discard remaining non-event cards and progress to dusk
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
if (!playerHand[i].eventData) {
remainingCards.push(playerHand[i]);
}
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
}
} else if (advancedTerrainCard.mustPlay || mustPlayCard === advancedTerrainCard) {
// This was a must play card, clear it and progress to dusk
mustPlayCard = null;
// Discard remaining cards
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
remainingCards.push(playerHand[i]);
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
} else {
// Regular card played, discard remaining cards and progress to dusk
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
remainingCards.push(playerHand[i]);
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
}
// Reset turn
cardsDrawnThisTurn = 0;
LK.getSound('cardPlace').play();
// Show adjust links button after card placement
showAdjustLinksButton();
return true;
}
function checkAndRemoveInvalidLinks(gridX, gridY, newTopCreature) {
// Check all links that involve creatures at this position
var invalidLinks = [];
// Check links FROM carnivores at this position
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
if (creature.creatureData.dietType === 'carnivore') {
// Check each active link from this carnivore
for (var j = creature.activeLinks.length - 1; j >= 0; j--) {
var link = creature.activeLinks[j];
var targetHerbivore = link.target;
// If the new top creature is a carnivore and the target is now covered, link becomes invalid
if (newTopCreature && newTopCreature.creatureData.dietType === 'carnivore' && targetHerbivore.gridX === gridX && targetHerbivore.gridY === gridY) {
invalidLinks.push({
carnivore: creature,
herbivore: targetHerbivore
});
}
}
}
}
}
// Check links TO herbivores at this position (if new creature is carnivore, herbivores below become invalid targets)
if (newTopCreature && newTopCreature.creatureData.dietType === 'carnivore') {
// Find all links pointing to herbivores at this position that are now covered
for (var linkIndex = linkLines.length - 1; linkIndex >= 0; linkIndex--) {
var linkLine = linkLines[linkIndex];
if (linkLine.herbivore.gridX === gridX && linkLine.herbivore.gridY === gridY) {
// This herbivore is now covered by a carnivore, so links to it are invalid
invalidLinks.push({
carnivore: linkLine.carnivore,
herbivore: linkLine.herbivore
});
}
}
}
// Remove invalid links with fall-off animation
for (var k = 0; k < invalidLinks.length; k++) {
var invalidLink = invalidLinks[k];
animateLinkFallOff(invalidLink.carnivore, invalidLink.herbivore);
}
}
function animateLinkFallOff(carnivore, herbivore) {
// Find the link line to animate
var linkLineToRemove = null;
for (var i = 0; i < linkLines.length; i++) {
if (linkLines[i].carnivore === carnivore && linkLines[i].herbivore === herbivore && !linkLines[i].isFallingOff) {
linkLineToRemove = linkLines[i];
break;
}
}
if (linkLineToRemove) {
// Mark link as falling off immediately to prevent duplicate animations
linkLineToRemove.isFallingOff = true;
// Remove from arrays immediately to clear logical ghost link
removeLink(carnivore, herbivore);
// Animate the link falling off
tween(linkLineToRemove, {
alpha: 0,
scaleY: linkLineToRemove.scaleY * 0.1,
y: linkLineToRemove.y + 50
}, {
duration: 500,
onFinish: function onFinish() {
// Remove the visual line after animation
if (linkLineToRemove.parent) linkLineToRemove.parent.removeChild(linkLineToRemove);
}
});
} else {
// No visual line found, just remove the link immediately
removeLink(carnivore, herbivore);
}
}
function placeCreatureOnStack(creatureCard, terrainCard, gridX, gridY) {
if (!canPlaceCreatureOnTerrain(creatureCard, terrainCard)) {
return false;
}
// Remove card from hand
var handIndex = playerHand.indexOf(creatureCard);
if (handIndex !== -1) {
playerHand.splice(handIndex, 1);
}
// Check for invalid links before placing the new creature
checkAndRemoveInvalidLinks(gridX, gridY, creatureCard);
// Remove shell from covered creature
if (terrainCard.creatureStack.length > 0) {
var coveredC = terrainCard.creatureStack[terrainCard.creatureStack.length - 1];
if (coveredC.shellMarker) {
coveredC.removeChild(coveredC.shellMarker);
coveredC.shellMarker = null;
if (game.activeShellCreature === coveredC) game.activeShellCreature = null;
}
}
// 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
if (creature.forerunner) {
processForerunnerSpecial();
}
if (creature && creature.watcher) {
processWatcherSpecial();
}
if (creature && creature.terraformer) {
processTerraformerSpecial(creatureCard.gridX, creatureCard.gridY);
}
if (creature && creature.shell) {
var shellMarker = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.2,
scaleY: 0.2
});
shellMarker.tint = 0x8B4513;
shellMarker.x = 0;
shellMarker.y = -100;
creatureCard.addChild(shellMarker);
creatureCard.shellMarker = shellMarker;
game.activeShellCreature = creatureCard;
}
}
function getAdjacentTerrains(gridX, gridY, includeDiagonals) {
var neighbors = [];
var directions = [{
x: -1,
y: 0
}, {
x: 1,
y: 0
}, {
x: 0,
y: -1
}, {
x: 0,
y: 1
}];
// Add diagonals if the creature is Efficient
if (includeDiagonals) {
directions.push({
x: -1,
y: -1
}, {
x: 1,
y: -1
}, {
x: -1,
y: 1
}, {
x: 1,
y: 1
});
}
for (var i = 0; i < directions.length; i++) {
var newX = gridX + directions[i].x;
var newY = gridY + directions[i].y;
if (newX >= 0 && newX < planetWidth && newY >= 0 && newY < planetHeight) {
neighbors.push(planetBoard[newY][newX]);
}
}
return neighbors;
}
function createLink(carnivore, herbivore) {
if (!carnivore || !herbivore) {
return false;
}
if (carnivore.creatureData.dietType !== 'carnivore') {
return false;
}
// Check if carnivore and herbivore are adjacent
if (!areCreaturesAdjacent(carnivore, herbivore)) {
return false;
}
// Additional check: ensure we're linking to the top creature only
var herbivoreGridX = herbivore.gridX;
var herbivoreGridY = herbivore.gridY;
var terrain = planetBoard[herbivoreGridY][herbivoreGridX];
if (terrain && terrain.creatureStack.length > 0) {
var topCreature = terrain.creatureStack[terrain.creatureStack.length - 1];
if (topCreature !== herbivore) {
return false; // Can only link to top creature in stack
}
}
// For basic carnivores, check if already linked to this herbivore
if (carnivore.creatureData.level === 'Basic') {
for (var i = 0; i < carnivore.activeLinks.length; i++) {
if (carnivore.activeLinks[i].target === herbivore) {
return false; // Basic carnivores can only have one link per herbivore
}
}
}
// Check if this is a valid link target considering all special rules
if (!isValidLinkTarget(carnivore, herbivore)) {
return false; // Cannot link due to special rules (Whale food, etc)
} //{mL_new}
// Find an unlinked marker
var availableMarker = null;
for (var i = 0; i < carnivore.linkMarkers.length; i++) {
if (!carnivore.linkMarkers[i].isLinked) {
availableMarker = carnivore.linkMarkers[i];
break;
}
}
if (!availableMarker) {
return false;
} // No available link markers
// Create the link
availableMarker.isLinked = true;
availableMarker.targetHerbivore = herbivore;
availableMarker.tint = 0x00FF00; // Green for linked
// Add to active links
carnivore.activeLinks.push({
marker: availableMarker,
target: herbivore
});
// Create visual link line
createLinkLine(carnivore, herbivore);
return true;
}
function removeLink(carnivore, herbivore) {
if (!carnivore || !herbivore) {
return false;
}
// Find and remove the link
for (var i = carnivore.activeLinks.length - 1; i >= 0; i--) {
var link = carnivore.activeLinks[i];
if (link.target === herbivore) {
link.marker.isLinked = false;
link.marker.targetHerbivore = null;
link.marker.tint = 0xFF0000; // Red for unlinked
carnivore.activeLinks.splice(i, 1);
// Remove visual link line
removeLinkLine(carnivore, herbivore);
return true;
}
}
return false;
}
function isValidLinkTarget(carnivore, herbivore) {
if (!carnivore || !herbivore) {
return false;
}
if (carnivore.creatureData.slow && (herbivore.creatureData.flying || herbivore.creatureData.fly)) {
return false; // Slow creatures cannot link to flying creatures
}
if (carnivore.creatureData.seaBound) {
if (herbivore.creatureData.subtype !== 'water') {
return false; // Sea-bound creatures can only link to water creatures
}
}
var targetIsCarnivore = herbivore.creatureData.dietType === 'carnivore';
if (targetIsCarnivore) {
// Target is a carnivore
if (carnivore.creatureData.hunterHunter || carnivore.creatureData.bully) {
// Hunter-Hunter or Bully can link to carnivores
if (carnivore.creatureData.bully && herbivore.creatureData.level !== 'Basic') {
return false; // Bully can only link to Basic Carnivores
}
return true;
}
return false; // Cannot link to carnivore unless Hunter-Hunter or Bully
} else {
// Target is a herbivore
if (carnivore.creatureData.hunterHunter) {
return false; // Hunter-Hunter cannot link to herbivores
} //{n4_hh}
} //{n4_hh_end}
if (carnivore.creatureData.fussy) {
if (herbivore.creatureData.level !== 'Basic') {
return false; // Fussy can only link to Basic Herbivores
} //{n4_fussy}
} //{n4_fussy_end}
if (herbivore.creatureData.whaleFood) {
if (carnivore.creatureData.id !== 'AC03') {
return false; // Cannot link to Whale food unless carnivore is Whale
}
}
return true;
}
function areCreaturesAdjacent(creature1, creature2) {
if (!creature1.isInPlay || !creature2.isInPlay) {
return false;
}
var dx = Math.abs(creature1.gridX - creature2.gridX);
var dy = Math.abs(creature1.gridY - creature2.gridY);
// Check if creature1 has Efficient attribute
var creature1HasEfficient = creature1.creatureData && creature1.creatureData.efficient;
// Adjacent means exactly one grid space away (orthogonal or diagonal if Efficient)
if (creature1HasEfficient) {
// Efficient creatures can link to adjacent (including diagonal) creatures
return dx === 1 && dy === 0 || dx === 0 && dy === 1 || dx === 1 && dy === 1;
} else {
// Non-Efficient creatures can only link orthogonally (up, down, left, right)
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
}
}
function createLinkLine(carnivore, herbivore) {
// Count how many links ALREADY exist between this carnivore and herbivore BEFORE adding the new one
var existingLinkCount = 0;
for (var i = 0; i < linkLines.length; i++) {
if (linkLines[i].carnivore === carnivore && linkLines[i].herbivore === herbivore) {
existingLinkCount++;
}
} //{e4_duplicate}
// Create visual arrow line using a thicker rectangle
var line = LK.getAsset('terrainLandFlat', {
anchorX: 0,
anchorY: 0.5
});
line.tint = 0xFFFF00; // Yellow link line by default
line.alpha = 0.8;
// Position arrow starting from slightly inside carnivore border towards herbivore
var dx = herbivore.x - carnivore.x;
var dy = herbivore.y - carnivore.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
// Calculate starting position slightly inside carnivore border (about 20px inward)
var startOffsetX = Math.cos(angle) * 20;
var startOffsetY = Math.sin(angle) * 20;
var startX = carnivore.x + startOffsetX;
var startY = carnivore.y + startOffsetY;
// Calculate gap distance (reduce total distance by both card radii plus small gap)
var gapDistance = distance - 100; // 50px from each card edge creates gap
line.x = startX;
line.y = startY;
line.rotation = angle;
line.scaleX = gapDistance / 252; // Scale based on gap distance
line.scaleY = 0.08; // Thicker line (8% of original height)
line.carnivore = carnivore;
line.herbivore = herbivore;
line.linkIndex = existingLinkCount; // Track which link number this is
// Calculate perpendicular offset for multiple links to avoid stacking
if (existingLinkCount >= 1) {
// 2nd link goes one way (+90 deg), 3rd link goes the opposite way (-90 deg)
var perpendicularAngle = existingLinkCount === 1 ? angle + Math.PI / 2 : angle - Math.PI / 2;
var offsetDistance = 30; // Offset distance in pixels
var offsetX = Math.cos(perpendicularAngle) * offsetDistance;
var offsetY = Math.sin(perpendicularAngle) * offsetDistance;
// Apply offset to line position
line.x += offsetX;
line.y += offsetY;
// Change line color and create indicator based on link number
var indicatorString = "";
if (existingLinkCount === 1) {
line.tint = 0xFF00FF; // Magenta for 2nd link
indicatorString = "X2";
} else {
line.tint = 0x00BFFF; // Deep Sky Blue for 3rd link
indicatorString = "X3";
}
// Add the text indicator
var linkIndicator = new Text2(indicatorString, {
size: 20,
fill: 0xFFFFFF
});
linkIndicator.anchor.set(0.5, 0.5);
linkIndicator.x = 0;
linkIndicator.y = 0;
line.addChild(linkIndicator);
linkIndicator.lineOwner = line;
}
game.addChild(line);
linkLines.push(line);
}
function removeLinkLine(carnivore, herbivore) {
for (var i = linkLines.length - 1; i >= 0; i--) {
var line = linkLines[i];
if (line.carnivore === carnivore && line.herbivore === herbivore) {
if (!line.isFallingOff) {
if (line.parent) line.parent.removeChild(line);
}
linkLines.splice(i, 1);
return; // Remove only ONE link and return
}
}
}
function highlightValidLinkTargets(carnivore) {
// Clear existing highlights
clearLinkHighlights();
if (!carnivore || carnivore.creatureData.dietType !== 'carnivore') {
return;
}
// Check if carnivore has Efficient attribute
var carnivoreHasEfficient = carnivore.creatureData && carnivore.creatureData.efficient;
// Find adjacent herbivores that can be linked to (including diagonals for Efficient)
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
// Only consider the top creature in each stack
var topCreature = terrain.creatureStack[terrain.creatureStack.length - 1];
if (areCreaturesAdjacent(carnivore, topCreature)) {
// Check if this is a valid link target considering all special rules
if (!isValidLinkTarget(carnivore, topCreature)) {
// Cannot link to this herbivore due to special rules
continue;
} //{nx_new}
var canCreateLink = false;
// For basic carnivores, check if already linked to this herbivore
if (carnivore.creatureData.level === 'Basic') {
var hasExistingLink = false;
for (var j = 0; j < carnivore.activeLinks.length; j++) {
if (carnivore.activeLinks[j].target === topCreature) {
hasExistingLink = true;
break;
}
}
if (!hasExistingLink) {
canCreateLink = true;
}
} else {
// Advanced carnivores can always create links if they have available markers
var hasAvailableMarker = false;
for (var k = 0; k < carnivore.linkMarkers.length; k++) {
if (!carnivore.linkMarkers[k].isLinked) {
hasAvailableMarker = true;
break;
}
}
if (hasAvailableMarker) {
canCreateLink = true;
}
}
if (canCreateLink) {
// Create highlight around this herbivore
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
// Use different colors for diagonal highlights if Efficient
if (carnivoreHasEfficient) {
var dx = Math.abs(carnivore.gridX - topCreature.gridX);
var dy = Math.abs(carnivore.gridY - topCreature.gridY);
if (dx === 1 && dy === 1) {
// Diagonal link for Efficient creature
highlight.tint = 0x00FFFF; // Cyan for diagonal
} else {
// Orthogonal link
highlight.tint = 0x00FF00; // Green for orthogonal
}
} else {
highlight.tint = 0x00FF00; // Green highlight
}
highlight.alpha = 0.3;
highlight.scaleX = topCreature.scaleX * 1.2;
highlight.scaleY = topCreature.scaleY * 1.2;
highlight.x = topCreature.x;
highlight.y = topCreature.y;
highlight.targetCreature = topCreature;
game.addChild(highlight);
linkHighlights.push(highlight);
}
}
}
}
}
}
function clearLinkHighlights() {
for (var i = 0; i < linkHighlights.length; i++) {
if (linkHighlights[i].parent) {
linkHighlights[i].parent.removeChild(linkHighlights[i]);
}
}
linkHighlights = [];
}
function updateLinkMarkerStates() {
// Update all link markers based on their state and game phase
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
// ONLY process the top creature in the stack - creatures underneath are not active
var isTopCreature = i === terrain.creatureStack.length - 1;
if (!isTopCreature) {
// Skip creatures that are underneath other creatures - they cannot interact with links
continue;
} //{f9_underneath}
if (creature.creatureData.dietType === 'carnivore') {
// Check if this carnivore will become extinct (insufficient links)
var willBecomeExtinct = creature.activeLinks.length < creature.linkRequirement;
for (var j = 0; j < creature.linkMarkers.length; j++) {
var marker = creature.linkMarkers[j];
if (gamePhase === 'dusk' && linkAdjustmentMode) {
// During dusk phase with adjustment mode active, make markers interactive
if (marker.isLinked) {
marker.tint = 0x00FF00; // Green for linked
// Make linked markers draggable to remove links
marker.down = function (x, y, obj) {
draggedLinkMarker = this;
draggedLinkCarnivore = this.carnivore;
};
} else {
// Check if this marker can be legally linked
var hasValidTargets = false;
var isEfficient = creature.creatureData && creature.creatureData.efficient;
var adjacentTerrains = getAdjacentTerrains(gridX, gridY, isEfficient);
for (var k = 0; k < adjacentTerrains.length; k++) {
var adjTerrain = adjacentTerrains[k];
if (adjTerrain && adjTerrain.creatureStack.length > 0) {
// Only consider the top creature in each adjacent stack
var topHerbivore = adjTerrain.creatureStack[adjTerrain.creatureStack.length - 1];
if (isValidLinkTarget(creature, topHerbivore)) {
// For basic carnivores, check if already linked to this herbivore
if (creature.creatureData.level === 'Basic') {
var hasExistingLink = false;
for (var m = 0; m < creature.activeLinks.length; m++) {
if (creature.activeLinks[m].target === topHerbivore) {
hasExistingLink = true;
break;
}
}
if (!hasExistingLink) {
hasValidTargets = true;
break;
}
} else {
// Advanced carnivores can always link if they have available markers
hasValidTargets = true;
break;
}
}
}
if (hasValidTargets) {
break;
}
}
if (hasValidTargets) {
marker.tint = 0xFFFF00; // Yellow for linkable
// Make unlinked but valid markers draggable
marker.down = function (x, y, obj) {
draggedLinkMarker = this;
draggedLinkCarnivore = this.carnivore;
};
} else {
marker.tint = 0x808080; // Gray for non-linkable
marker.down = function (x, y, obj) {
// Do nothing for non-linkable markers
};
// Add N/A text
if (!marker.naText) {
marker.naText = new Text2("N/A", {
size: 8,
fill: 0xFFFFFF
});
marker.naText.anchor.set(0.5, 0.5);
marker.naText.x = 0;
marker.naText.y = 0;
marker.addChild(marker.naText);
}
}
}
} else {
// Outside dusk phase or not in adjustment mode, markers are not interactive
if (marker.isLinked) {
marker.tint = 0x00FF00; // Green for linked
} else {
marker.tint = 0xFF0000; // Red for unlinked
}
// Remove down handler and N/A text if present
marker.down = function (x, y, obj) {};
if (marker.naText && marker.naText.parent) {
marker.naText.parent.removeChild(marker.naText);
marker.naText = null;
}
}
// Flash markers if carnivore will become extinct
if (willBecomeExtinct) {
// Stop any existing flash animation
tween.stop(marker, {
alpha: true
});
// Start flashing animation
tween(marker, {
alpha: 0.3
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(marker, {
alpha: 1
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart the flash cycle if still extinct
if (creature.activeLinks.length < creature.linkRequirement) {
updateLinkMarkerStates();
}
}
});
}
});
} else {
// Stop flashing if no longer about to become extinct
tween.stop(marker, {
alpha: true
});
marker.alpha = 1;
}
}
}
}
}
}
}
}
function autoCreateLinks() {
// Auto-create mandatory links for carnivores
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var carnivore = terrain.creatureStack[i];
if (carnivore.creatureData.dietType === 'carnivore') {
// Try to create links for this carnivore
while (carnivore.activeLinks.length < carnivore.linkRequirement) {
var linkCreated = false;
// Find adjacent herbivores
var isEfficient = carnivore.creatureData && carnivore.creatureData.efficient;
var adjacentTerrains = getAdjacentTerrains(gridX, gridY, isEfficient);
for (var j = 0; j < adjacentTerrains.length && !linkCreated; j++) {
var adjTerrain = adjacentTerrains[j];
if (adjTerrain && adjTerrain.creatureStack.length > 0) {
// Only consider the top creature in each adjacent stack
var topHerbivore = adjTerrain.creatureStack[adjTerrain.creatureStack.length - 1];
if (topHerbivore.creatureData.dietType === 'herbivore') {
// Check if we can create a link
var hasExistingLink = false;
for (var l = 0; l < carnivore.activeLinks.length; l++) {
if (carnivore.activeLinks[l].target === topHerbivore) {
hasExistingLink = true;
break;
}
}
if (!hasExistingLink && createLink(carnivore, topHerbivore)) {
linkCreated = true;
}
}
}
}
if (!linkCreated) {
break;
} // No more links possible
}
}
}
}
}
}
}
function processLinkConsequences() {
// Hide the adjust links button when leaving dusk phase
hideAdjustLinksButton();
// NOTE: Extinction markers for herbivores are only processed during night phase
// This function only handles cleanup, not marker assignment
}
function processNightPhase() {
// Show visual cue that night phase is processing
showPhaseTransition("Night Phase - Checking links...", function () {
// Add extinction markers to carnivores without sufficient links
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
// ONLY process the top creature in the stack - creatures underneath are not active
var isTopCreature = i === terrain.creatureStack.length - 1;
if (!isTopCreature) {
// Skip creatures that are underneath other creatures
continue;
} //{ho_underneath}
if (creature.creatureData.dietType === 'carnivore') {
// For carnivores: check if they have sufficient links AT THIS MOMENT
if (creature.activeLinks.length < creature.linkRequirement) {
creature.extinctionMarkers += 1; // Only 1 marker per turn regardless of shortage
creature.updateExtinctionMarkers();
}
// Check if this carnivore is poisoned (feeding on Poisonous creature)
for (var p = 0; p < creature.activeLinks.length; p++) {
if (creature.activeLinks[p].target.creatureData.poisonous) {
creature.extinctionMarkers += 1;
creature.updateExtinctionMarkers();
}
}
} else if (creature.creatureData.dietType === 'herbivore') {
// Check if Delicate creature is on Advanced terrain
if (creature.creatureData.delicate && terrain.terrainData.level === 'Advanced') {
creature.extinctionMarkers += 1;
creature.updateExtinctionMarkers();
}
// 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++;
} //{tg_new}
}
// Add extinction markers equal to the number of excess links EVERY Night Phase
var excessLinks = linksToThisHerbivore - creature.safeLinks;
if (excessLinks > 0) {
// No cap, no armor! Add exactly as many markers as there are excess links
creature.extinctionMarkers += excessLinks;
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);
// Tween all creatures that were physically positioned below the dead creature UP by 20 pixels
for (var j = 0; j < i; j++) {
var creatureBelow = terrain.creatureStack[j];
tween(creatureBelow, {
y: creatureBelow.y - 20
}, {
duration: 200
});
}
creaturesWentExtinct = true; // Mark that creatures went extinct this round
scryDeniedNextRound = true; // Deny scry for next round when extinction occurs
// Clear extinction markers when creature leaves play
creature.extinctionMarkers = 0;
creature.updateExtinctionMarkers();
// Remove all links involving this creature with fall-off animation
if (creature.creatureData.dietType === 'carnivore') {
for (var j = creature.activeLinks.length - 1; j >= 0; j--) {
animateLinkFallOff(creature, creature.activeLinks[j].target);
}
} else {
// Remove links from carnivores to this herbivore with fall-off animation
for (var j = linkLines.length - 1; j >= 0; j--) {
if (linkLines[j].herbivore === creature) {
animateLinkFallOff(linkLines[j].carnivore, creature);
}
}
}
creature.die();
}
}
}
}
}
}
function createEventDecks() {
basicEventDeck = [];
basicEventDeck.push({
type: 'basic',
cardType: 'event',
level: 'Basic',
id: 'BE01',
name: "Time fly's"
});
basicEventDeck.push({
type: 'basic',
cardType: 'event',
level: 'Basic',
id: 'BE02',
name: 'Devolution'
});
basicEventDeck.push({
type: 'basic',
cardType: 'event',
level: 'Basic',
id: 'BE03',
name: 'Disease'
});
basicEventDeck.push({
type: 'basic',
cardType: 'event',
level: 'Basic',
id: 'BE04',
name: 'Volcano'
});
for (var i = 5; i <= 10; i++) {
basicEventDeck.push({
type: 'basic',
cardType: 'event',
level: 'Basic',
id: 'BE0' + i,
name: 'Basic Event ' + i
});
}
advancedEventDeck = [];
for (var i = 1; i <= 10; i++) {
advancedEventDeck.push({
type: 'advanced',
cardType: 'event',
level: 'Advanced',
id: 'AE0' + i,
name: 'Advanced Event ' + i,
effect: 'catastrophic'
});
}
}
function addEventCardToDeck(creatureLevel) {
var eventCard = null;
if (creatureLevel === 'Advanced') {
if (advancedEventDeck.length === 0 && advancedEventDiscard.length > 0) {
advancedEventDeck = advancedEventDiscard.slice();
advancedEventDiscard = [];
shuffleEventDeck(advancedEventDeck);
}
if (advancedEventDeck.length > 0) eventCard = advancedEventDeck.pop();else if (basicEventDeck.length > 0) eventCard = basicEventDeck.pop();
} else {
if (basicEventDeck.length === 0 && basicEventDiscard.length > 0) {
basicEventDeck = basicEventDiscard.slice();
basicEventDiscard = [];
shuffleEventDeck(basicEventDeck);
}
if (basicEventDeck.length > 0) eventCard = basicEventDeck.pop();else if (advancedEventDeck.length > 0) eventCard = advancedEventDeck.pop();
}
if (eventCard) discardPile.push(eventCard);
}
function processDawnPhase() {
// Show visual cue that dawn phase is processing
showPhaseTransition("Dawn Phase - Processing...", function () {
gamePhase = 'draw';
processDrawPhase(); // Auto-progress to draw phase
});
}
function processDrawPhase() {
// Draw exactly 3 cards (or 4 if AT15 was played last turn), handling deck reshuffling as needed
var cardsToDrawTotal = 3;
if (at15PlayedLastTurn) {
cardsToDrawTotal = 4;
at15PlayedLastTurn = false; // Reset flag after using it
}
// Step 1: Create validHandFound boolean
var validHandFound = false;
// Step 2: Create master while loop that runs as long as validHandFound is false
while (!validHandFound) {
// Step 3: Draw cards until the player's hand reaches the required amount
var continueDrawing = true;
var cardsDrawnThisPhase = 0;
while (continueDrawing && playerHand.length < cardsToDrawTotal) {
// Check if deck is empty, reshuffle if needed
if (mainDeck.length === 0) {
// Shuffle discard pile to become new deck
if (discardPile.length > 0) {
mainDeck = discardPile.slice();
discardPile = [];
shuffleDeck();
currentRound++;
deckCountText.setText("Deck: " + mainDeck.length);
// Check for end game condition after round increment
if (currentRound >= 6) {
// Game ends immediately
LK.showYouWin();
return;
}
// Check and trigger scry status at beginning of new round
// Only activate scry if no extinctions occurred last round
if (!scryDeniedNextRound) {
scryTokenActive = true;
} //{ht_scry}
creaturesWentExtinct = false;
scryDeniedNextRound = false;
} else {
// No cards available, stop drawing
continueDrawing = false;
break; // Exit drawing loop if both deck and discard are empty
}
}
// Draw one card if deck has cards
if (mainDeck.length > 0) {
var cardData = mainDeck.pop();
var card = createCardFromData(cardData);
playerHand.push(card);
game.addChild(card);
// Update hand positions after each card is added
updateHandPositions();
cardsDrawnThisPhase++;
} else {
// No more cards in deck, stop drawing
continueDrawing = false;
}
}
// Step 4: After the hand is filled, update the hand positions. If the hand is completely empty, break the master loop.
updateHandPositions();
if (playerHand.length === 0) {
// Hand is empty because both deck and discard are empty
break; // Exit master while loop
} //{uv_new}
// Check for Stinky effect after drawing cards
checkAndProcessStinkyEffect();
// Update hand positions again in case Stinky effect discarded adjacent cards
updateHandPositions();
// Step 5: Check if the current hand has valid placements
if (hasValidPlacements(playerHand)) {
// Hand has at least one valid placement, set validHandFound to true to exit master loop
validHandFound = true; //{uC_new}
} else {
//{uD_new}
// Hand is unplayable, discard all cards and loop will repeat to draw a fresh hand
discardCards(playerHand.slice()); //{uB_new}
} //{uE_new}
} //{uF_new}
// Step 6: After the master loop finishes, update the deck and discard UI text, play the draw sound, and change the game phase to 'noon'
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
LK.getSound('cardDraw').play();
gamePhase = 'noon';
}
function createCardFromData(cardData) {
var card;
if (cardData.cardType === 'terrain') {
card = new TerrainCard(cardData);
card.scaleX = 0.6;
card.scaleY = 0.6;
} else if (cardData.cardType === 'event') {
// Create event card using EventCard class
card = new EventCard(cardData);
} else {
// Default to creature card
card = new CreatureCard(cardData);
}
return card;
}
function processNoonPhase() {
// Check for event cards in hand - they must be played first
eventCardsToPlay = [];
for (var i = 0; i < playerHand.length; i++) {
if (playerHand[i].eventData) {
eventCardsToPlay.push(playerHand[i]);
}
}
if (eventCardsToPlay.length > 0) {
// Player must play event cards first
mustPlayCard = eventCardsToPlay[0]; // Force first event card
return;
}
// Check if player has valid moves
var validCards = [];
for (var i = 0; i < playerHand.length; i++) {
if (hasValidPlacements([playerHand[i]])) {
validCards.push(playerHand[i]);
}
}
if (validCards.length === 0 && playerHand.length === 3) {
// Show no playable cards notification
showNoPlayableCardsNotification();
return;
}
// Player can choose which card to play (if multiple valid options)
if (validCards.length > 1) {
// Discard non-chosen cards (this would be handled by player interaction)
// For now, just continue - player will choose
}
}
function showPhaseTransition(message, callback) {
// Clear any existing transition timer
if (phaseTransitionTimer) {
LK.clearTimeout(phaseTransitionTimer);
}
// Create transition message
var transitionText = new Text2(message, {
size: 40,
fill: 0xFFFF00
});
transitionText.anchor.set(0.5, 0.5);
transitionText.x = 1024;
transitionText.y = 1366;
transitionText.alpha = 0;
game.addChild(transitionText);
// Fade in message
tween(transitionText, {
alpha: 1
}, {
duration: 500,
onFinish: function onFinish() {
// Show message for 1 second, then fade out and execute callback
phaseTransitionTimer = LK.setTimeout(function () {
tween(transitionText, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
if (transitionText.parent) {
transitionText.parent.removeChild(transitionText);
}
if (callback) {
callback();
}
}
});
}, 1000);
}
});
}
function processDuskPhase() {
// Show visual cue that dusk phase is processing
showPhaseTransition("Dusk Phase - Arrange links...", function () {
// Show the adjust links button at start of dusk phase
showAdjustLinksButton();
// Update link marker states to make them interactive
updateLinkMarkerStates();
// Player now manually arranges links - no auto-progression
// Phase will advance when player clicks next phase button
});
}
function processEndTurnPhase() {
// Show visual cue that end turn phase is processing
showPhaseTransition("End Turn - Processing extinctions...", function () {
// Process extinctions
processExtinctions();
// Extinction markers persist - they are NOT cleared here
turnNumber++;
gamePhase = 'dawn'; // Start next turn
processDawnPhase(); // Auto-progress to next dawn phase
});
}
function endRound() {
currentRound++;
if (currentRound > maxRounds) {
// Game complete
LK.showYouWin();
return;
}
// Reshuffle deck
createInitialDeck();
// Reset turn counter and draw phase
cardsDrawnThisTurn = 0;
drawPhase = 'complete';
cardsDrawnInPhase = [];
// Update UI
deckCountText.setText("Deck: " + mainDeck.length);
}
// Draw card button functionality
var drawButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
var drawButtonText = new Text2("DRAW", {
size: 32,
fill: 0xFFFFFF
});
drawButtonText.anchor.set(0.5, 0.5);
drawButton.addChild(drawButtonText);
drawButton.x = 1700;
drawButton.y = 2400;
game.addChild(drawButton);
drawButton.down = function () {
if (gamePhase === 'draw') {
processDrawPhase();
}
};
// Phase management buttons - redesigned for proper turn flow
var nextPhaseButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 0.8
});
nextPhaseButton.tint = 0x32CD32; // Green for next phase
var nextPhaseButtonText = new Text2("NEXT PHASE", {
size: 24,
fill: 0xFFFFFF
});
nextPhaseButtonText.anchor.set(0.5, 0.5);
nextPhaseButton.addChild(nextPhaseButtonText);
nextPhaseButton.x = 500;
nextPhaseButton.y = 2400;
game.addChild(nextPhaseButton);
var nextPhaseWarningBox = null;
function getCreaturesInDanger() {
var creaturesInDanger = [];
for (var gridY = 0; gridY < planetHeight; gridY++) {
//{kA_danger}
for (var gridX = 0; gridX < planetWidth; gridX++) {
//{kB_danger}
var terrain = planetBoard[gridY][gridX]; //{kC_danger}
if (terrain && terrain.creatureStack.length > 0) {
//{kD_danger}
for (var i = 0; i < terrain.creatureStack.length; i++) {
//{kE_danger}
var creature = terrain.creatureStack[i]; //{kF_danger}
// ONLY check top creatures in stack
var isTopCreature = i === terrain.creatureStack.length - 1; //{kG_danger}
if (!isTopCreature) {
//{kH_danger}
continue; //{kI_danger}
} //{kJ_danger}
// Check for Delicate creatures on Advanced terrain
if (creature.creatureData.delicate && terrain.terrainData.level === 'Advanced') {
if (creaturesInDanger.indexOf(creature) === -1) creaturesInDanger.push(creature);
}
// 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}
// Check if this carnivore is poisoned (feeding on Poisonous creature)
for (var p = 0; p < creature.activeLinks.length; p++) {
if (creature.activeLinks[p].target.creatureData.poisonous) {
if (creaturesInDanger.indexOf(creature) === -1) creaturesInDanger.push(creature);
break;
}
}
} //{l0_danger}
} //{l1_danger}
} //{l2_danger}
} //{l3_danger}
} //{l4_danger}
return creaturesInDanger; //{l5_danger}
} //{l6_danger}
function showNextPhaseWarning() {
if (nextPhaseWarningBox && nextPhaseWarningBox.parent) {
//{l7_danger}
return; // Warning already showing
} //{l8_danger}
// Create warning box
nextPhaseWarningBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
//{l9_danger}
anchorY: 0.5 //{la_danger}
}); //{lb_danger}
nextPhaseWarningBox.tint = 0xFF8C00; // Orange background
nextPhaseWarningBox.alpha = 0.95;
nextPhaseWarningBox.scaleX = 2.0;
nextPhaseWarningBox.scaleY = 1.2;
nextPhaseWarningBox.x = 500;
nextPhaseWarningBox.y = 2100;
game.addChild(nextPhaseWarningBox);
// Create warning text
var warningText = new Text2("Warning! These creatures are in danger!\nAre you sure you want to end the turn?", {
size: 20,
//{lc_danger}
fill: 0xFFFFFF //{ld_danger}
}); //{le_danger}
warningText.anchor.set(0.5, 0.5);
warningText.x = 0;
warningText.y = 0;
nextPhaseWarningBox.addChild(warningText);
} //{lf_danger}
function hideNextPhaseWarning() {
if (nextPhaseWarningBox && nextPhaseWarningBox.parent) {
//{lg_danger}
nextPhaseWarningBox.parent.removeChild(nextPhaseWarningBox); //{lh_danger}
nextPhaseWarningBox = null;
} //{li_danger}
} //{lj_danger}
function highlightCreaturesInDanger(creaturesInDanger) {
for (var i = 0; i < creaturesInDanger.length; i++) {
//{lk_danger}
var creature = creaturesInDanger[i]; //{ll_danger}
tween.stop(creature, {
//{lm_danger}
tint: true //{ln_danger}
}); //{lo_danger}
tween(creature, {
//{lp_danger}
tint: 0xFFFF00 //{lq_danger}
}, {
//{lr_danger}
duration: 200 //{ls_danger}
}); //{lt_danger}
} //{lu_danger}
} //{lv_danger}
function unhighlightCreaturesInDanger(creaturesInDanger) {
for (var i = 0; i < creaturesInDanger.length; i++) {
//{lw_danger}
var creature = creaturesInDanger[i]; //{lx_danger}
tween.stop(creature, {
//{ly_danger}
tint: true //{lz_danger}
}); //{mA_danger}
tween(creature, {
//{mB_danger}
tint: 0xFFFFFF //{mC_danger}
}, {
//{mD_danger}
duration: 200 //{mE_danger}
}); //{mF_danger}
} //{mG_danger}
} //{mH_danger}
nextPhaseButton.down = function () {
// Exit link adjustment mode if active and hide adjust links button
if (linkAdjustmentMode) {
deactivateLinkAdjustmentMode();
}
// Always hide adjust links button when next phase is clicked
hideAdjustLinksButton();
// Hide warning box when button is pressed
hideNextPhaseWarning(); //{mI_danger}
// Disable button completely if events must be played
if (gamePhase === 'noon' && hasEventCardsInHand) {
return;
}
// Prevent skipping card play if the player holds valid normal cards
if (gamePhase === 'draw' || gamePhase === 'noon' && playerHand.length > 0 && hasValidPlacements(playerHand)) {
// Show a safe, harmless fading text warning instead of the destructive discard loop
if (!game.safeSkipWarning) {
var safeWarning = new Text2("You have playable cards! You must play one.", {
size: 40,
fill: 0xFF0000
});
safeWarning.anchor.set(0.5, 0.5);
safeWarning.x = 1024;
safeWarning.y = 1366;
game.addChild(safeWarning);
game.safeSkipWarning = safeWarning;
tween(safeWarning, {
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
if (safeWarning.parent) safeWarning.parent.removeChild(safeWarning);
game.safeSkipWarning = null;
}
});
}
return;
}
if (gamePhase === 'dawn') {
processDawnPhase();
} else if (gamePhase === 'draw') {
processDrawPhase();
} else if (gamePhase === 'noon') {
// Check if player has event cards that must be played first
if (eventCardsToPlay.length > 0 || mustPlayCard) {
// Must play event cards or mandatory card first
return;
}
gamePhase = 'dusk';
processDuskPhase();
} else if (gamePhase === 'dusk') {
// Apply consequences for over-hunted herbivores
processLinkConsequences();
clearLinkHighlights();
gamePhase = 'night';
processNightPhase();
} else if (gamePhase === 'night') {
processNightPhase();
} else if (gamePhase === 'end') {
processEndTurnPhase();
}
};
function updateDangerHighlights() {
var inDangerList = [];
if (gamePhase === 'noon' || gamePhase === 'dusk') {
inDangerList = getCreaturesInDanger();
}
for (var gridY = 0; gridY < planetHeight; gridY++) {
var _loop = function _loop() {
terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack && terrain.creatureStack.length > 0) {
topCreature = terrain.creatureStack[terrain.creatureStack.length - 1];
isInDanger = false;
for (i = 0; i < inDangerList.length; i++) {
if (inDangerList[i] === topCreature) {
isInDanger = true;
break;
}
}
if (isInDanger) {
// Only trigger the highlight creation once
if (!topCreature.isDangerFlashing) {
topCreature.isDangerFlashing = true;
// Create halo AS A CHILD so it inherits placement movement and scaling
if (!topCreature.dangerHalo) {
halo = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
halo.tint = 0xFF0000;
halo.alpha = 0.2;
// Reduced scale to 1.05 so it stays tight to the card and doesn't conflict
halo.scaleX = 1.05;
halo.scaleY = 1.05;
halo.x = 0;
halo.y = 0;
topCreature.addChildAt(halo, 0);
topCreature.dangerHalo = halo;
// Attach the pulse function directly to the halo to avoid closure/scope bugs
halo.pulse = function () {
if (!this.parent) return;
var currentHalo = this;
tween(currentHalo, {
alpha: 0.8
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!currentHalo.parent) return;
tween(currentHalo, {
alpha: 0.2
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: currentHalo.pulse.bind(currentHalo)
});
}
});
};
halo.pulse();
}
}
} else {
if (topCreature.isDangerFlashing) {
topCreature.isDangerFlashing = false;
if (topCreature.dangerHalo && topCreature.dangerHalo.parent) {
tween.stop(topCreature.dangerHalo);
topCreature.dangerHalo.parent.removeChild(topCreature.dangerHalo);
topCreature.dangerHalo = null;
}
}
}
}
},
terrain,
topCreature,
isInDanger,
i,
halo;
for (var gridX = 0; gridX < planetWidth; gridX++) {
_loop();
}
}
// Process global warning text safely
if (gamePhase === 'noon' && inDangerList.length > 0) {
if (!game.dangerWarningText || !game.dangerWarningText.parent) {
var _flashText = function flashText() {
if (!game.dangerWarningText || !game.dangerWarningText.parent) return;
tween(game.dangerWarningText, {
alpha: 0.3
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!game.dangerWarningText || !game.dangerWarningText.parent) return;
tween(game.dangerWarningText, {
alpha: 1
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: _flashText
});
}
});
};
var warningText = new Text2("Warning! Creature/s are in danger!", {
size: 64,
fill: 0xFF0000
});
warningText.anchor.set(0.5, 0);
warningText.x = 1024;
warningText.y = 130;
game.addChild(warningText);
game.dangerWarningText = warningText;
_flashText();
}
} else {
if (game.dangerWarningText && game.dangerWarningText.parent) {
tween.stop(game.dangerWarningText);
game.dangerWarningText.parent.removeChild(game.dangerWarningText);
game.dangerWarningText = null;
}
}
}
var gameStatusText = new Text2("Round: 1 | Phase: DAWN | Turn: 1", {
size: 40,
fill: 0xFFFF00
});
gameStatusText.anchor.set(0.5, 0);
gameStatusText.x = 1024;
gameStatusText.y = 60;
game.addChild(gameStatusText);
// Link adjustment system variables
var linkAdjustmentMode = false;
var adjustLinkButton = null;
var cardsWithAdjustableLinks = [];
var currentlyAdjustingCard = null;
function createAdjustLinksButton() {
if (adjustLinkButton && adjustLinkButton.parent) {
adjustLinkButton.parent.removeChild(adjustLinkButton);
}
adjustLinkButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 0.8
});
adjustLinkButton.tint = 0xFFD700; // Gold for adjust links
var adjustButtonText = new Text2("Adjust Links?", {
size: 20,
fill: 0x000000
});
adjustButtonText.anchor.set(0.5, 0.5);
adjustLinkButton.addChild(adjustButtonText);
adjustLinkButton.x = 250; // Left of next phase button
adjustLinkButton.y = 2400;
game.addChild(adjustLinkButton);
adjustLinkButton.down = function () {
activateLinkAdjustmentMode();
};
}
function showAdjustLinksButton() {
createAdjustLinksButton();
}
function hideAdjustLinksButton() {
if (adjustLinkButton && adjustLinkButton.parent) {
adjustLinkButton.parent.removeChild(adjustLinkButton);
adjustLinkButton = null;
}
}
function activateLinkAdjustmentMode() {
linkAdjustmentMode = true;
currentlyAdjustingCard = null;
cardsWithAdjustableLinks = [];
// Find all carnivores on the board
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
if (creature.creatureData.dietType === 'carnivore') {
cardsWithAdjustableLinks.push(creature);
}
}
}
}
}
// Create and display personal "Alter Links" buttons on all carnivore cards
for (var i = 0; i < cardsWithAdjustableLinks.length; i++) {
var card = cardsWithAdjustableLinks[i];
card.createPersonalAlterLinksButton();
}
}
function deactivateLinkAdjustmentMode() {
linkAdjustmentMode = false;
// Remove personal alter buttons from all cards and restore card colors
for (var i = 0; i < cardsWithAdjustableLinks.length; i++) {
var card = cardsWithAdjustableLinks[i];
card.tint = 0xFFFFFF; // Return to normal color
// Remove personal alter button
if (card.personalAlterButton && card.personalAlterButton.parent) {
card.personalAlterButton.parent.removeChild(card.personalAlterButton);
card.personalAlterButton = null;
}
}
cardsWithAdjustableLinks = [];
currentlyAdjustingCard = null;
}
// Handle card dragging and link highlighting on hold
game.move = function (x, y, obj) {
if (draggedCard) {
draggedCard.x = x;
draggedCard.y = y;
// Ensure dragged card is always on top
game.removeChild(draggedCard);
game.addChild(draggedCard);
// Highlights are already shown from the card's down event
} else if (draggedLinkMarker && gamePhase === 'dusk' && linkAdjustmentMode) {
// Handle link marker dragging - highlight valid targets only while holding
var carnivore = draggedLinkMarker.carnivore;
highlightValidLinkTargets(carnivore);
// Create temporary visual line from carnivore to cursor
// Remove any existing temp line
if (game.tempLinkLine && game.tempLinkLine.parent) {
game.tempLinkLine.parent.removeChild(game.tempLinkLine);
}
// Create new temp line
var tempLine = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.01,
scaleY: 1
});
tempLine.tint = 0xFFFF00; // Yellow temp line
tempLine.alpha = 0.5;
var dx = x - carnivore.x;
var dy = y - carnivore.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
tempLine.x = carnivore.x + dx / 2;
tempLine.y = carnivore.y + dy / 2;
tempLine.rotation = angle;
tempLine.scaleY = distance / 336;
game.addChild(tempLine);
game.tempLinkLine = tempLine;
}
};
game.up = function (x, y, obj) {
if (draggedCard) {
var placed = false;
// Check if card was dropped on valid terrain
for (var gridY = 0; gridY < planetHeight && !placed; gridY++) {
for (var gridX = 0; gridX < planetWidth && !placed; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain) {
if (draggedCard.creatureData && canPlaceCreatureOnTerrain(draggedCard, terrain)) {
if (Math.abs(terrain.x - x) < 140 && Math.abs(terrain.y - y) < 168) {
placed = placeCreatureOnStack(draggedCard, terrain, gridX, gridY);
}
} else if (draggedCard.terrainData && canPlaceTerrainOnTerrain(draggedCard, terrain)) {
if (Math.abs(terrain.x - x) < 140 && Math.abs(terrain.y - y) < 168) {
placed = placeTerrainOnTerrain(draggedCard, terrain, gridX, gridY);
}
}
}
}
}
// If not placed, return to original position
if (!placed && originalCardPosition) {
tween(draggedCard, originalCardPosition, {
duration: 300
});
}
// Clear all highlights
hidePossibleMoves();
draggedCard = null;
originalCardPosition = null;
} else if (draggedLinkMarker && draggedLinkCarnivore && gamePhase === 'dusk' && linkAdjustmentMode) {
// Handle link marker release with new system
var linkCreated = false;
// Remove temporary line
if (game.tempLinkLine && game.tempLinkLine.parent) {
game.tempLinkLine.parent.removeChild(game.tempLinkLine);
game.tempLinkLine = null;
}
// Check if released over a valid herbivore
for (var i = 0; i < linkHighlights.length; i++) {
var highlight = linkHighlights[i];
if (highlight.targetCreature) {
var creature = highlight.targetCreature;
var distance = Math.sqrt(Math.pow(creature.x - x, 2) + Math.pow(creature.y - y, 2));
if (distance < 100) {
// Within range of the herbivore
// Create the link
if (createLink(draggedLinkCarnivore, creature)) {
linkCreated = true;
break;
}
}
}
}
// Clear highlights and reset drag state
clearLinkHighlights();
draggedLinkMarker = null;
draggedLinkCarnivore = null;
}
};
function processAT18Special() {
// Find all carnivore cards in discard pile
at18CarnivoreList = [];
for (var i = 0; i < discardPile.length; i++) {
//{at18_loop}
var cardData = discardPile[i];
if (cardData.cardType === 'creature' && cardData.dietType === 'carnivore') {
//{at18_check}
at18CarnivoreList.push(cardData);
} //{at18_found}
} //{at18_end_loop}
at18Active = true;
if (at18CarnivoreList.length === 0) {
// No carnivores in discard pile - show message
showAT18NoCarnivoredMessage();
} else {
//{at18_else}
// Display carnivores for selection
displayAT18CarnivoreSelection();
} //{at18_display_end}
} //{at18_process_end}
function showAT18NoCarnivoredMessage() {
// Create notification box
var notificationBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
//{at18_no_x}
anchorY: 0.5 //{at18_no_y}
}); //{at18_no_asset}
notificationBox.tint = 0x4169E1; // Blue background for info
notificationBox.alpha = 0.9;
notificationBox.scaleX = 2.5;
notificationBox.scaleY = 1.5;
notificationBox.x = 1024;
notificationBox.y = 1366;
game.addChild(notificationBox);
// Create notification text
var notificationText = new Text2("No Carnivore cards in Discard pile - Special does not apply", {
size: 28,
//{at18_no_text_size}
fill: 0xFFFFFF //{at18_no_text_fill}
}); //{at18_no_text}
notificationText.anchor.set(0.5, 0.5);
notificationText.x = 0;
notificationText.y = 0;
notificationBox.addChild(notificationText);
// Make the box clickable to dismiss
notificationBox.down = function () {
if (notificationBox.parent) {
notificationBox.parent.removeChild(notificationBox);
} //{at18_no_dismiss}
at18Active = false;
}; //{at18_no_down}
} //{at18_no_function_end}
function displayAT18CarnivoreSelection() {
// Create scry UI header
var scryHeader = new Text2("Pick 1 to add back onto the main deck", {
size: 32,
//{at18_header_size}
fill: 0xFFFFFF //{at18_header_fill}
}); //{at18_header_text}
scryHeader.anchor.set(0.5, 0);
scryHeader.x = 1024;
scryHeader.y = 300;
game.addChild(scryHeader);
scryHeader.at18Header = true;
// Display each carnivore card in a selectable list format
var cardYOffset = 500;
var cardYSpacing = 280;
for (var i = 0; i < at18CarnivoreList.length; i++) {
//{at18_display_loop}
var cardData = at18CarnivoreList[i];
var cardContainer = new CreatureCard(cardData);
cardContainer.scaleX = 0.6;
cardContainer.scaleY = 0.6;
cardContainer.x = 512 + i % 2 * 600;
cardContainer.y = cardYOffset + Math.floor(i / 2) * cardYSpacing;
cardContainer.cardDataIndex = i;
cardContainer.at18Selectable = true;
// Add click handler to select this card
cardContainer.down = function (x, y, obj) {
var selectedIndex = this.cardDataIndex;
selectAT18Carnivore(selectedIndex);
}; //{at18_selectable_down}
game.addChild(cardContainer);
} //{at18_display_loop_end}
} //{at18_display_function_end}
function selectAT18Carnivore(cardIndex) {
if (cardIndex < 0 || cardIndex >= at18CarnivoreList.length) {
return;
}
// Get the selected carnivore card data
var selectedCardData = at18CarnivoreList[cardIndex];
// Remove from discard pile
for (var i = discardPile.length - 1; i >= 0; i--) {
//{at18_remove_loop}
if (discardPile[i] === selectedCardData || discardPile[i].id === selectedCardData.id && discardPile[i].cardType === selectedCardData.cardType && discardPile[i].dietType === selectedCardData.dietType) {
//{at18_remove_check}
discardPile.splice(i, 1);
break;
} //{at18_remove_end}
} //{at18_remove_loop_end}
// Add to top of main deck
mainDeck.push(selectedCardData);
// Clear all AT18 UI elements
clearAT18UI();
// Update deck count
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
at18Active = false;
} //{at18_select_function_end}
function clearAT18UI() {
// Remove all AT18 selectable cards from game
for (var i = game.children.length - 1; i >= 0; i--) {
//{at18_clear_loop}
var child = game.children[i];
if (child.at18Selectable) {
//{at18_clear_check}
game.removeChild(child);
} //{at18_clear_removed}
if (child.at18Header) {
//{at18_clear_header}
game.removeChild(child);
} //{at18_clear_header_removed}
} //{at18_clear_loop_end}
} //{at18_clear_function_end}
function activateScry() {
if (!scryTokenActive || scryMode || mainDeck.length < 3) {
return;
}
scryMode = true;
scryStep = 1;
scryCards = [];
// Pop 3 cards from mainDeck and create them
for (var i = 0; i < 3; i++) {
var cardData = mainDeck.pop();
var card = createCardFromData(cardData);
scryCards.push(card);
// Do NOT push to playerHand - only to scryCards
game.addChild(card);
}
// Position cards in center of screen side-by-side
var scryCardPositions = [512, 1024, 1536];
for (var i = 0; i < scryCards.length; i++) {
scryCards[i].x = scryCardPositions[i];
scryCards[i].y = 1000;
scryCards[i].scaleX = 1.5;
scryCards[i].scaleY = 1.5;
// Assign down function to each card
scryCards[i].down = function (card) {
return function (x, y, obj) {
processScrySelection(card);
};
}(scryCards[i]);
}
// Create header text
scryHeaderText = new Text2("SCRY: Select 1 card to put on the\nBOTTOM of the main deck", {
size: 56,
fill: 0xFFFF00,
align: 'center'
});
scryHeaderText.anchor.set(0.5, 0);
scryHeaderText.x = 1024;
scryHeaderText.y = 120;
game.addChild(scryHeaderText);
}
function processScrySelection(selectedCard) {
// Find the index of selectedCard in scryCards
var cardIndex = -1;
for (var i = 0; i < scryCards.length; i++) {
if (scryCards[i] === selectedCard) {
cardIndex = i;
break;
}
}
if (cardIndex === -1) {
return; // Card not found
}
// Extract clean copy of card data
var cardData = selectedCard.creatureData || selectedCard.terrainData || selectedCard.eventData;
var cleanCardData = {};
cleanCardData.type = cardData.type;
cleanCardData.level = cardData.level;
cleanCardData.cardType = cardData.cardType;
if (cardData.dietType) cleanCardData.dietType = cardData.dietType;
if (cardData.name) cleanCardData.name = cardData.name;
if (cardData.terrainRequirement) cleanCardData.terrainRequirement = cardData.terrainRequirement;
if (cardData.climateRequirement) cleanCardData.climateRequirement = cardData.climateRequirement;
if (cardData.subtype) cleanCardData.subtype = cardData.subtype;
if (cardData.landType) cleanCardData.landType = cardData.landType;
if (cardData.waterType) cleanCardData.waterType = cardData.waterType;
if (cardData.climate) cleanCardData.climate = cardData.climate;
if (cardData.id) cleanCardData.id = cardData.id;
if (cardData.colorBand) cleanCardData.colorBand = cardData.colorBand;
if (scryStep === 1) {
// Put card on BOTTOM of deck
mainDeck.unshift(cleanCardData);
// Remove from screen
if (selectedCard.parent) {
selectedCard.parent.removeChild(selectedCard);
}
// Remove from scryCards array
scryCards.splice(cardIndex, 1);
// Update header text
scryHeaderText.setText("SCRY: Select 1 card to DISCARD.\nThe last card goes on TOP of the deck.");
scryStep = 2;
} else if (scryStep === 2) {
// Put card in discard pile
discardPile.push(cleanCardData);
// Remove from screen
if (selectedCard.parent) {
selectedCard.parent.removeChild(selectedCard);
}
// Remove from scryCards array
scryCards.splice(cardIndex, 1);
// Now process the last remaining card automatically
if (scryCards.length === 1) {
var lastCard = scryCards[0];
var lastCardData = lastCard.creatureData || lastCard.terrainData || lastCard.eventData;
var cleanLastCardData = {};
cleanLastCardData.type = lastCardData.type;
cleanLastCardData.level = lastCardData.level;
cleanLastCardData.cardType = lastCardData.cardType;
if (lastCardData.dietType) cleanLastCardData.dietType = lastCardData.dietType;
if (lastCardData.name) cleanLastCardData.name = lastCardData.name;
if (lastCardData.terrainRequirement) cleanLastCardData.terrainRequirement = lastCardData.terrainRequirement;
if (lastCardData.climateRequirement) cleanLastCardData.climateRequirement = lastCardData.climateRequirement;
if (lastCardData.subtype) cleanLastCardData.subtype = lastCardData.subtype;
if (lastCardData.landType) cleanLastCardData.landType = lastCardData.landType;
if (lastCardData.waterType) cleanLastCardData.waterType = lastCardData.waterType;
if (lastCardData.climate) cleanLastCardData.climate = lastCardData.climate;
if (lastCardData.id) cleanLastCardData.id = lastCardData.id;
if (lastCardData.colorBand) cleanLastCardData.colorBand = lastCardData.colorBand;
// Put on TOP of deck
mainDeck.push(cleanLastCardData);
// Remove from screen
if (lastCard.parent) {
lastCard.parent.removeChild(lastCard);
}
// Clear scryCards array
scryCards = [];
// Remove header text
if (scryHeaderText && scryHeaderText.parent) {
scryHeaderText.parent.removeChild(scryHeaderText);
scryHeaderText = null;
}
// Reset scry state
scryMode = false;
scryTokenActive = false;
scryStep = 0;
// Update UI
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
}
}
}
function showNoPlayableCardsNotification() {
// Create notification box
var notificationBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.5
});
notificationBox.tint = 0x8B0000; // Dark red background
notificationBox.alpha = 0.9;
notificationBox.x = 1024;
notificationBox.y = 1366;
game.addChild(notificationBox);
// Create notification text
var notificationText = new Text2("No playable cards! Click to continue", {
size: 36,
fill: 0xFFFFFF
});
notificationText.anchor.set(0.5, 0.5);
notificationText.x = 0;
notificationText.y = 0;
notificationBox.addChild(notificationText);
// Make the box clickable
notificationBox.down = function () {
// Remove the notification
if (notificationBox.parent) {
notificationBox.parent.removeChild(notificationBox);
}
// Discard all cards in hand
discardCards(playerHand.slice());
// Start draw 1, discard 1 cycle
processDrawPhase();
};
// Store reference for cleanup
game.noPlayableNotification = notificationBox;
}
// Initialize terrain-based game
createBasicTerrainPool();
setupPlanet();
createInitialDeck();
createEventDecks();
// Shuffle event decks on game start
shuffleEventDeck(basicEventDeck);
shuffleEventDeck(advancedEventDeck);
// Game starts with turn 1, round 1, in Dawn phase
currentRound = 1;
turnNumber = 1;
gamePhase = 'dawn';
game.update = function () {
// Update UI elements
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
basicEventCountText.setText("Basic Events: " + basicEventDeck.length);
advancedEventCountText.setText("Advanced Events: " + advancedEventDeck.length);
graveyardCountText.setText("Graveyard: " + graveyard.length);
eventDiscardCountText.setText("Event Discards: B:" + basicEventDiscard.length + " | A:" + advancedEventDiscard.length);
// Update scry token display
if (scryTokenActive) {
scryText.setText("Scry ✓");
scryText.tint = 0x00FF00; // Green when available
} else if (scryMode) {
scryText.setText("Scry (Active)");
scryText.tint = 0xFFFF00; // Yellow when in use
} else {
scryText.setText("Scry");
scryText.tint = 0x888888; // Gray when unavailable
}
// Update round and phase indicator
gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber);
// Check if event cards have been removed from hand
hasEventCardsInHand = false;
for (var i = 0; i < playerHand.length; i++) {
//{lV_event1}
if (playerHand[i].eventData) {
hasEventCardsInHand = true;
break;
}
}
// Update next phase button appearance based on event cards
if (gamePhase === 'noon') {
//{lV_event2}
if (hasEventCardsInHand) {
nextPhaseButton.tint = 0xFF0000; // Red when events must be played
nextPhaseButtonText.setText("Play events 1st!");
} else {
nextPhaseButton.tint = 0x32CD32; // Green when normal phase progression
nextPhaseButtonText.setText("NEXT PHASE");
}
}
// Update link marker states during dusk phase
if (gamePhase === 'dusk') {
updateLinkMarkerStates();
}
// Update danger highlights for creatures at risk of extinction
updateDangerHighlights();
};
function resetCardLinks(carnivore) {
if (!carnivore || carnivore.creatureData.dietType !== 'carnivore') {
return;
}
// Remove all active links from this carnivore with fall-off animation
for (var i = carnivore.activeLinks.length - 1; i >= 0; i--) {
var link = carnivore.activeLinks[i];
animateLinkFallOff(carnivore, link.target);
}
// Reset all link markers to unlinked state
for (var j = 0; j < carnivore.linkMarkers.length; j++) {
var marker = carnivore.linkMarkers[j];
marker.isLinked = false;
marker.targetHerbivore = null;
marker.tint = 0xFF0000; // Red for unlinked
}
// Clear active links array
carnivore.activeLinks = [];
}
var simplifyActive = false;
var simplifyHeaderText = null;
function processSimplifySpecial(originX, originY) {
var targets = [];
var neighbors = getAdjacentTerrains(originX, originY, true);
for (var i = 0; i < neighbors.length; i++) {
var adj = neighbors[i];
if (adj && adj.terrainData && adj.terrainData.level === 'Advanced' && (!adj.creatureStack || adj.creatureStack.length === 0)) {
targets.push(adj);
}
}
if (targets.length > 0) {
simplifyActive = true;
simplifyHeaderText = new Text2("Select a terrain for devolution!", {
size: 56,
fill: 0xFFFF00,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
simplifyHeaderText.x = 1024;
simplifyHeaderText.y = 120;
game.addChild(simplifyHeaderText);
for (var t = 0; t < targets.length; t++) {
var target = targets[t];
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.tint = 0x0000FF;
highlight.alpha = 0.5;
highlight.scaleX = target.scaleX * 1.1;
highlight.scaleY = target.scaleY * 1.1;
highlight.x = target.x;
highlight.y = target.y;
highlight.targetTerrain = target;
highlight.isSimplifyHighlight = true;
highlight.down = function () {
if (!simplifyActive) return;
var tTerrain = this.targetTerrain;
var tX = tTerrain.gridX;
var tY = tTerrain.gridY;
var basicUnder = tTerrain.basicTerrainUnderneath;
discardPile.push(tTerrain.terrainData);
discardCountText.setText("Discard: " + discardPile.length);
planetBoard[tY][tX] = basicUnder;
planetSlots[tY][tX].terrainCard = basicUnder;
if (tTerrain.parent) tTerrain.parent.removeChild(tTerrain);
for (var j = game.children.length - 1; j >= 0; j--) {
if (game.children[j].isSimplifyHighlight) game.removeChild(game.children[j]);
}
if (simplifyHeaderText && simplifyHeaderText.parent) simplifyHeaderText.parent.removeChild(simplifyHeaderText);
simplifyActive = false;
};
game.addChild(highlight);
}
}
}
var watcherActive = false;
var watcherList = [];
function processWatcherSpecial() {
watcherList = [];
for (var i = 0; i < discardPile.length; i++) {
if (discardPile[i].cardType === 'event') watcherList.push(discardPile[i]);
}
watcherActive = true;
if (watcherList.length === 0) {
var notifyBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.5,
tint: 0x4169E1,
alpha: 0.9,
x: 1024,
y: 1366
});
var notifyText = new Text2("No Event cards in Discard - Watcher does not apply", {
size: 28,
fill: 0xFFFFFF,
anchorX: 0.5,
anchorY: 0.5
});
notifyBox.addChild(notifyText);
game.addChild(notifyBox);
notifyBox.down = function () {
if (notifyBox.parent) notifyBox.parent.removeChild(notifyBox);
watcherActive = false;
};
} else {
var header = new Text2("Choose an event to return to the event deck", {
size: 56,
fill: 0xFFFF00,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
header.x = 1024;
header.y = 120;
header.watcherHeader = true;
game.addChild(header);
for (var i = 0; i < watcherList.length; i++) {
var cardContainer = createCardFromData(watcherList[i]);
cardContainer.scaleX = 0.6;
cardContainer.scaleY = 0.6;
cardContainer.x = 512 + i % 2 * 600;
cardContainer.y = 500 + Math.floor(i / 2) * 280;
cardContainer.cardDataIndex = i;
cardContainer.watcherSelectable = true;
cardContainer.down = function () {
var selectedData = watcherList[this.cardDataIndex];
for (var d = discardPile.length - 1; d >= 0; d--) {
if (discardPile[d] === selectedData || discardPile[d].name === selectedData.name) {
discardPile.splice(d, 1);
break;
}
}
if (selectedData.level === 'Advanced') advancedEventDeck.push(selectedData);else basicEventDeck.push(selectedData);
for (var k = game.children.length - 1; k >= 0; k--) {
var c = game.children[k];
if (c.watcherSelectable || c.watcherHeader) game.removeChild(c);
}
basicEventCountText.setText("Basic Events: " + basicEventDeck.length);
advancedEventCountText.setText("Advanced Events: " + advancedEventDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
watcherActive = false;
};
game.addChild(cardContainer);
}
}
}
var forerunnerActive = false;
var forerunnerList = [];
function processForerunnerSpecial() {
forerunnerList = discardPile.slice(); // Copy entire discard pile
forerunnerActive = true;
if (forerunnerList.length === 0) {
var notifyBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.5,
tint: 0x4169E1,
alpha: 0.9,
x: 1024,
y: 1366
});
var notifyText = new Text2("Discard pile is empty - Forerunner does not apply", {
size: 28,
fill: 0xFFFFFF,
anchorX: 0.5,
anchorY: 0.5
});
notifyBox.addChild(notifyText);
game.addChild(notifyBox);
notifyBox.down = function () {
if (notifyBox.parent) notifyBox.parent.removeChild(notifyBox);
forerunnerActive = false;
};
} else {
var header = new Text2("Choose a card to place on top of the main deck,\nall others will be returned to the discard pile", {
size: 48,
fill: 0xFFFF00,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
header.x = 1024;
header.y = 120;
header.forerunnerHeader = true;
game.addChild(header);
var cardYOffset = 500;
var cardYSpacing = 280;
for (var i = 0; i < forerunnerList.length; i++) {
var cardContainer = createCardFromData(forerunnerList[i]);
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.forerunnerSelectable = true;
cardContainer.down = function () {
var selectedData = forerunnerList[this.cardDataIndex];
for (var d = discardPile.length - 1; d >= 0; d--) {
if (discardPile[d] === selectedData || discardPile[d].id === selectedData.id) {
discardPile.splice(d, 1);
break;
}
}
mainDeck.push(selectedData);
for (var k = game.children.length - 1; k >= 0; k--) {
var c = game.children[k];
if (c.forerunnerSelectable || c.forerunnerHeader) game.removeChild(c);
}
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
forerunnerActive = false;
};
game.addChild(cardContainer);
}
}
}
var devolutionActive = false;
var devolutionHeaderText = null;
function processDevolutionEvent(onComplete) {
var targets = [];
for (var y = 0; y < planetHeight; y++) {
for (var x = 0; x < planetWidth; x++) {
var terrain = planetBoard[y][x];
if (terrain && terrain.creatureStack && terrain.creatureStack.length > 0) {
var topC = terrain.creatureStack[terrain.creatureStack.length - 1];
if (topC.creatureData.dietType === 'carnivore' && topC.creatureData.level === 'Advanced') targets.push(topC);
}
}
}
if (targets.length === 0) {
onComplete(); // No targets, event fizzles instantly
} else {
devolutionActive = true;
devolutionHeaderText = new Text2("Select an advanced carnivore to go back in time!", {
size: 56,
fill: 0xFF0000,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
devolutionHeaderText.x = 1024;
devolutionHeaderText.y = 120;
game.addChild(devolutionHeaderText);
for (var t = 0; t < targets.length; t++) {
var target = targets[t];
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.tint = 0xDDA0DD;
highlight.alpha = 0.6; // Light purple
highlight.scaleX = target.scaleX * 1.15;
highlight.scaleY = target.scaleY * 1.15;
highlight.x = target.x;
highlight.y = target.y;
highlight.targetCreature = target;
highlight.isDevolutionHighlight = true;
highlight.down = function () {
if (!devolutionActive) return;
var tC = this.targetCreature;
for (var j = tC.activeLinks.length - 1; j >= 0; j--) animateLinkFallOff(tC, tC.activeLinks[j].target);
for (var j = linkLines.length - 1; j >= 0; j--) if (linkLines[j].herbivore === tC) animateLinkFallOff(linkLines[j].carnivore, tC);
var stack = planetBoard[tC.gridY][tC.gridX].creatureStack;
var idx = stack.indexOf(tC);
if (idx !== -1) stack.splice(idx, 1);
discardPile.push(tC.creatureData);
discardCountText.setText("Discard: " + discardPile.length);
if (tC.parent) tC.parent.removeChild(tC);
for (var j = game.children.length - 1; j >= 0; j--) {
if (game.children[j].isDevolutionHighlight) game.removeChild(game.children[j]);
}
if (devolutionHeaderText && devolutionHeaderText.parent) devolutionHeaderText.parent.removeChild(devolutionHeaderText);
devolutionActive = false;
onComplete(); // Finish event resolution
};
game.addChild(highlight);
}
}
}
var terraformerActive = false;
var terraformerHeaderText = null;
function processTerraformerSpecial(originX, originY) {
var targets = [];
var neighbors = getAdjacentTerrains(originX, originY, true);
var targetLevel = 'Advanced';
// First pass: Check for empty Advanced terrains
for (var i = 0; i < neighbors.length; i++) {
var adj = neighbors[i];
if (adj && adj.terrainData && adj.terrainData.level === 'Advanced' && (!adj.creatureStack || adj.creatureStack.length === 0)) {
targets.push(adj);
}
}
// Second pass: If no Advanced terrains found, check for empty Basic terrains
if (targets.length === 0) {
targetLevel = 'Basic';
for (var i = 0; i < neighbors.length; i++) {
var adj = neighbors[i];
if (adj && adj.terrainData && adj.terrainData.level === 'Basic' && (!adj.creatureStack || adj.creatureStack.length === 0)) {
targets.push(adj);
}
}
}
if (targets.length > 0) {
terraformerActive = true;
terraformerHeaderText = new Text2("Select 1 terrain card to be removed,\nthe basic terrain will then shift", {
size: 56,
fill: 0xFFFF00,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
terraformerHeaderText.x = 1024;
terraformerHeaderText.y = 120;
game.addChild(terraformerHeaderText);
for (var t = 0; t < targets.length; t++) {
var target = targets[t];
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.tint = 0xFFFF00;
highlight.alpha = 0.6;
highlight.scaleX = target.scaleX * 1.1;
highlight.scaleY = target.scaleY * 1.1;
highlight.x = target.x;
highlight.y = target.y;
highlight.targetTerrain = target;
highlight.isTerraformerHighlight = true;
highlight.targetLevel = targetLevel;
highlight.down = function () {
if (!terraformerActive) return;
var tTerrain = this.targetTerrain;
var tX = tTerrain.gridX;
var tY = tTerrain.gridY;
var basicUnder = tTerrain.basicTerrainUnderneath;
// 1. If Advanced, discard the Advanced card
if (this.targetLevel === 'Advanced') {
discardPile.push(tTerrain.terrainData);
discardCountText.setText("Discard: " + discardPile.length);
if (tTerrain.parent) tTerrain.parent.removeChild(tTerrain);
}
// 2. Grab a fresh basic terrain from the unused pool if available
var targetBasicToSwap = this.targetLevel === 'Advanced' ? basicUnder : tTerrain;
if (basicTerrainPool.length > planetLayout.reduce(function (a, b) {
return a + b;
}, 0)) {
// Pop a truly unused card from the pool
var newBasicData = basicTerrainPool.pop();
// Inherit the climate of the grid slot
newBasicData.climate = targetBasicToSwap.terrainData.climate;
var newBasicCard = new TerrainCard(newBasicData);
newBasicCard.gridX = tX;
newBasicCard.gridY = tY;
newBasicCard.isInPlay = true;
newBasicCard.x = targetBasicToSwap.x;
newBasicCard.y = targetBasicToSwap.y;
newBasicCard.scaleX = targetBasicToSwap.scaleX;
newBasicCard.scaleY = targetBasicToSwap.scaleY;
planetBoard[tY][tX] = newBasicCard;
planetSlots[tY][tX].terrainCard = newBasicCard;
var basicIndex = game.getChildIndex(targetBasicToSwap);
if (targetBasicToSwap.parent) targetBasicToSwap.parent.removeChild(targetBasicToSwap);
game.addChildAt(newBasicCard, basicIndex);
} else {
// If pool is empty, just revert Advanced to Basic without swapping
if (this.targetLevel === 'Advanced') {
planetBoard[tY][tX] = basicUnder;
planetSlots[tY][tX].terrainCard = basicUnder;
}
}
// Cleanup UI
for (var j = game.children.length - 1; j >= 0; j--) {
if (game.children[j].isTerraformerHighlight) game.removeChild(game.children[j]);
}
if (terraformerHeaderText && terraformerHeaderText.parent) terraformerHeaderText.parent.removeChild(terraformerHeaderText);
terraformerActive = false;
};
game.addChild(highlight);
}
}
}
;
var diseaseActive = false;
var diseaseHeaderText = null;
function processDiseaseEvent(onComplete) {
var targets = [];
for (var y = 0; y < planetHeight; y++) {
for (var x = 0; x < planetWidth; x++) {
var terrain = planetBoard[y][x];
if (terrain && terrain.creatureStack) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
if (terrain.creatureStack[i].creatureData.level === 'Basic') targets.push(terrain.creatureStack[i]);
}
}
}
}
if (targets.length === 0) {
onComplete();
return;
}
diseaseActive = true;
var picks = 0;
diseaseHeaderText = new Text2("Select a creature to become sick!", {
size: 56,
fill: 0xFF0000,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
diseaseHeaderText.x = 1024;
diseaseHeaderText.y = 120;
game.addChild(diseaseHeaderText);
for (var t = 0; t < targets.length; t++) {
var target = targets[t];
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.tint = 0xDDA0DD;
highlight.alpha = 0.6;
highlight.scaleX = target.scaleX * 1.15;
highlight.scaleY = target.scaleY * 1.15;
highlight.x = target.x;
highlight.y = target.y;
highlight.targetCreature = target;
highlight.isDiseaseHighlight = true;
highlight.down = function () {
if (!diseaseActive) return;
var tC = this.targetCreature;
tC.extinctionMarkers += 1;
tC.updateExtinctionMarkers();
picks++;
if (picks === 1) {
diseaseHeaderText.setText("And now another sickens....");
} else if (picks === 2) {
for (var j = game.children.length - 1; j >= 0; j--) {
if (game.children[j].isDiseaseHighlight) game.removeChild(game.children[j]);
}
if (diseaseHeaderText && diseaseHeaderText.parent) diseaseHeaderText.parent.removeChild(diseaseHeaderText);
diseaseActive = false;
onComplete();
}
};
game.addChild(highlight);
}
}
var volcanoActive = false;
var volcanoHeaderText = null;
function processVolcanoEvent(onComplete) {
var targets = [];
for (var y = 0; y < planetHeight; y++) {
for (var x = 0; x < planetWidth; x++) {
var terrain = planetBoard[y][x];
if (terrain && terrain.terrainData && terrain.terrainData.landType === 'mountain') {
// Priority: Only mountains with creatures, OR any mountain if none have creatures
if (terrain.creatureStack && terrain.creatureStack.length > 0) {
targets.push(terrain);
}
}
}
}
// If no occupied mountains exist, grab all empty mountains
if (targets.length === 0) {
for (var y = 0; y < planetHeight; y++) {
for (var x = 0; x < planetWidth; x++) {
var terrain = planetBoard[y][x];
if (terrain && terrain.terrainData && terrain.terrainData.landType === 'mountain') {
targets.push(terrain);
}
}
}
}
if (targets.length === 0) {
onComplete();
return;
} // No mountains at all
volcanoActive = true;
volcanoHeaderText = new Text2("Select a Mountain to become a raging volcano!", {
size: 56,
fill: 0xFF0000,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
volcanoHeaderText.x = 1024;
volcanoHeaderText.y = 120;
game.addChild(volcanoHeaderText);
for (var t = 0; t < targets.length; t++) {
var target = targets[t];
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.tint = 0xDDA0DD;
highlight.alpha = 0.6;
highlight.scaleX = target.scaleX * 1.15;
highlight.scaleY = target.scaleY * 1.15;
highlight.x = target.x;
highlight.y = target.y;
highlight.targetTerrain = target;
highlight.isVolcanoHighlight = true;
highlight.down = function () {
if (!volcanoActive) return;
var selectedMountain = this.targetTerrain;
// Cleanup mountain highlights
for (var j = game.children.length - 1; j >= 0; j--) {
if (game.children[j].isVolcanoHighlight) game.removeChild(game.children[j]);
}
// Start the recursive fleeing logic
processVolcanoFlee(selectedMountain, onComplete);
};
game.addChild(highlight);
}
}
function processVolcanoFlee(mountain, onComplete) {
if (!mountain.creatureStack || mountain.creatureStack.length === 0) {
if (volcanoHeaderText && volcanoHeaderText.parent) volcanoHeaderText.parent.removeChild(volcanoHeaderText);
volcanoActive = false;
onComplete();
return; // Event over
}
var topC = mountain.creatureStack[mountain.creatureStack.length - 1];
if (topC.creatureData.flying || topC.creatureData.swimming) {
volcanoHeaderText.setText("Lucky creatures!");
LK.setTimeout(function () {
if (volcanoHeaderText && volcanoHeaderText.parent) volcanoHeaderText.parent.removeChild(volcanoHeaderText);
volcanoActive = false;
onComplete();
}, 1500);
return;
}
volcanoHeaderText.setText("Unlucky creature - Flee to survive!");
// Find valid escape routes
var validEscapes = [];
var neighbors = getAdjacentTerrains(mountain.gridX, mountain.gridY, true); // Diagonals included
// Temporarily pop the creature to prevent self-collision during checks
mountain.creatureStack.pop();
for (var n = 0; n < neighbors.length; n++) {
var adj = neighbors[n];
if (adj && canPlaceCreatureOnTerrain(topC, adj)) {
validEscapes.push(adj);
}
}
// Put it back
mountain.creatureStack.push(topC);
if (validEscapes.length === 0) {
// Die without event card
var stack = mountain.creatureStack;
stack.pop();
graveyard.push(topC.creatureData);
scryDeniedNextRound = true;
for (var j = topC.activeLinks.length - 1; j >= 0; j--) animateLinkFallOff(topC, topC.activeLinks[j].target);
for (var j = linkLines.length - 1; j >= 0; j--) if (linkLines[j].herbivore === topC) animateLinkFallOff(linkLines[j].carnivore, topC);
if (topC.parent) topC.parent.removeChild(topC);
// Check next creature down
processVolcanoFlee(mountain, onComplete);
} else {
volcanoHeaderText.setText("Choose a new home!");
var fleeHighlights = [];
for (var e = 0; e < validEscapes.length; e++) {
var esc = validEscapes[e];
var hlt = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
hlt.tint = 0xFFFF00;
hlt.alpha = 0.6;
hlt.scaleX = esc.scaleX * 1.15;
hlt.scaleY = esc.scaleY * 1.15;
hlt.x = esc.x;
hlt.y = esc.y;
hlt.targetEscape = esc;
hlt.isFleeHighlight = true;
hlt.down = function () {
var chosenEsc = this.targetEscape;
for (var k = game.children.length - 1; k >= 0; k--) if (game.children[k].isFleeHighlight) game.removeChild(game.children[k]);
// Move creature physically
var movingC = mountain.creatureStack.pop();
placeCreatureOnStack(movingC, chosenEsc, chosenEsc.gridX, chosenEsc.gridY);
// Check next creature down
processVolcanoFlee(mountain, onComplete);
};
game.addChild(hlt);
fleeHighlights.push(hlt);
}
}
} /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var CreatureCard = Container.expand(function (creatureData) {
var self = Container.call(this);
self.creatureData = creatureData || {
type: 'basic',
level: 'Basic',
dietType: 'herbivore',
name: 'Basic Herbivore',
terrainRequirement: 'land',
climateRequirement: 'any'
};
// Create creature visual
var creatureAsset;
if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'herbivore') {
creatureAsset = self.attachAsset('creatureBasicHerbivore', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'herbivore') {
creatureAsset = self.attachAsset('creatureAdvancedHerbivore', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'carnivore') {
creatureAsset = self.attachAsset('creatureBasicCarnivore', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'carnivore') {
creatureAsset = self.attachAsset('creatureAdvancedCarnivore', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Add creature type color band at bottom
var typeStripAsset;
if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'herbivore') {
typeStripAsset = 'creatureTypeBasicHerbivore';
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'herbivore') {
typeStripAsset = 'creatureTypeAdvancedHerbivore';
} else if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'carnivore') {
typeStripAsset = 'creatureTypeBasicCarnivore';
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'carnivore') {
typeStripAsset = 'creatureTypeAdvancedCarnivore';
}
if (typeStripAsset) {
var typeStrip = self.attachAsset(typeStripAsset, {
anchorX: 0.5,
anchorY: 0.5
});
typeStrip.y = 95; // Position at bottom of creature card
}
// Add creature name
self.nameText = new Text2(self.creatureData.name, {
size: 18,
fill: 0xFFFFFF
});
self.nameText.anchor.set(0.5, 0);
self.nameText.x = 0;
self.nameText.y = -100;
self.addChild(self.nameText);
// Add requirement text
var reqText = "Req: ";
if (self.creatureData.terrainRequirement && self.creatureData.terrainRequirement !== 'any') {
reqText += self.creatureData.terrainRequirement;
}
if (self.creatureData.climateRequirement && self.creatureData.climateRequirement !== 'any') {
if (reqText !== "Req: ") {
reqText += ", ";
}
reqText += self.creatureData.climateRequirement;
}
if (reqText !== "Req: ") {
self.requirementText = new Text2(reqText, {
size: 14,
fill: 0xFFFFFF
});
self.requirementText.anchor.set(0.5, 0);
self.requirementText.x = 0;
self.requirementText.y = 60;
self.addChild(self.requirementText);
}
self.isInPlay = false;
self.linkMarkers = [];
self.activeLinks = [];
self.extinctionMarkers = 0;
self.linkRequirement = 0;
self.safeLinks = 0;
// Set link requirements and safe link levels based on creature type
if (self.creatureData.dietType === 'carnivore') {
if (self.creatureData.level === 'Basic') {
self.linkRequirement = 1; // Basic carnivores need 1 link
} else if (self.creatureData.level === 'Advanced') {
self.linkRequirement = 2; // Advanced carnivores need 2 links
}
} else if (self.creatureData.dietType === 'herbivore') {
if (self.creatureData.level === 'Basic') {
self.safeLinks = 1; // Basic herbivores can safely handle 1 link
} else if (self.creatureData.level === 'Advanced') {
self.safeLinks = 2; // Advanced herbivores can safely handle 2 links
}
}
if (self.creatureData.fatty) {
self.linkRequirement = 3;
}
if (self.creatureData.nutritious) {
self.safeLinks = 2;
}
if (self.creatureData.bountiful) {
self.safeLinks = 3;
}
// Create visual link markers for carnivores
self.createLinkMarkers = function () {
if (self.creatureData.dietType === 'carnivore' && self.linkRequirement > 0) {
for (var i = 0; i < self.linkRequirement; i++) {
var linkMarker = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.15,
scaleY: 0.15
});
linkMarker.tint = 0xFF0000; // Red color for unlinked markers
linkMarker.x = (i - (self.linkRequirement - 1) / 2) * 25;
linkMarker.y = -80;
linkMarker.isLinked = false;
linkMarker.targetHerbivore = null;
linkMarker.carnivore = self;
linkMarker.markerIndex = i;
// Link markers are no longer directly interactive
linkMarker.move = function (x, y, obj) {};
linkMarker.down = function (x, y, obj) {};
self.addChild(linkMarker);
self.linkMarkers.push(linkMarker);
}
// Create personal "Alter Links" button for this carnivore
self.createPersonalAlterLinksButton = function () {
if (self.isInPlay) {
var alterButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.2
});
alterButton.tint = 0xFFD700; // Gold color
var alterButtonText = new Text2("Alter Links", {
size: 12,
fill: 0x000000
});
alterButtonText.anchor.set(0.5, 0.5);
alterButton.addChild(alterButtonText);
alterButton.x = 0;
alterButton.y = 0;
alterButton.carnivoreCard = self;
alterButton.down = function () {
if (gamePhase === 'dusk' && linkAdjustmentMode) {
resetCardLinks(self);
}
};
self.addChild(alterButton);
self.personalAlterButton = alterButton;
}
};
}
};
// Create extinction marker visuals
self.extinctionMarkerVisuals = [];
self.updateExtinctionMarkers = function () {
// Remove existing visuals
for (var i = 0; i < self.extinctionMarkerVisuals.length; i++) {
if (self.extinctionMarkerVisuals[i].parent) {
self.extinctionMarkerVisuals[i].parent.removeChild(self.extinctionMarkerVisuals[i]);
}
}
self.extinctionMarkerVisuals = [];
// Create new visuals for current extinction markers
for (var i = 0; i < self.extinctionMarkers; i++) {
var marker = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.1,
scaleY: 0.1
});
marker.tint = 0x000000; // Black for extinction markers
marker.x = (i - (self.extinctionMarkers - 1) / 2) * 20;
marker.y = 80;
self.addChild(marker);
self.extinctionMarkerVisuals.push(marker);
}
};
self.die = function () {
if (self.isInPlay) {
// Extinction Trigger interceptor
if (self.creatureData.extinctionTrigger) {
// Extinction Trigger bypasses the graveyard, events, and scry penalty
bonusPile.push(self.creatureData);
} else {
// Normal death
graveyard.push(self.creatureData);
scryDeniedNextRound = true; // Deny scry
addEventCardToDeck(self.creatureData.level); // Draw event
if (self.creatureData.unlucky) addEventCardToDeck(self.creatureData.level);
}
// Visual death effect
tween(self, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 500,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
LK.getSound('creatureDie').play();
}
};
self.move = function (x, y, obj) {
var globalX = self.x + x * self.scaleX;
var globalY = self.y + y * self.scaleY;
onMouseMove(globalX, globalY);
};
self.down = function (x, y, obj) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
// Prevent selecting creature/terrain cards if event cards are in hand
if (hasEventCardsInHand) {
//{1G_event}
return;
}
selectedCard = self;
draggedCard = self;
originalCardPosition = {
x: self.x,
y: self.y
};
showPossibleMoves(self);
}
};
// Determine if creature has any special properties
var hasAnySpecial = self.creatureData && (self.creatureData.tough || self.creatureData.squishy || self.creatureData.stinky || self.creatureData.flying || self.creatureData.swimming || self.creatureData.efficient || self.creatureData.whaleFood || self.creatureData.whaleChow || self.creatureData.bully || self.creatureData.seaBound || self.creatureData.id === 'AC03');
// Do NOT add "Special" text - start cascading special text directly below card name
var nextYOffset = -50; // Start position lower to avoid overlapping the creature name
// Add "Tough" text if present
if (self.creatureData && self.creatureData.tough) {
self.toughText = new Text2("Tough", {
size: 24,
fill: 0xFFD700,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.toughText.anchor.set(0.5, 0.5);
self.toughText.x = 0;
self.toughText.y = nextYOffset;
self.addChild(self.toughText);
nextYOffset = nextYOffset + 28;
}
// Add "Squishy" text if present
if (self.creatureData && self.creatureData.squishy) {
self.squishyText = new Text2("Squishy", {
size: 24,
fill: 0xFF69B4,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.squishyText.anchor.set(0.5, 0.5);
self.squishyText.x = 0;
self.squishyText.y = nextYOffset;
self.addChild(self.squishyText);
nextYOffset = nextYOffset + 40;
}
// Add "Fatty" text if present
if (self.creatureData && self.creatureData.fatty) {
self.fattyText = new Text2("Fatty", {
size: 24,
fill: 0xFF8C00,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.fattyText.anchor.set(0.5, 0.5);
self.fattyText.x = 0;
self.fattyText.y = nextYOffset;
self.addChild(self.fattyText);
nextYOffset = nextYOffset + 40;
}
// Add "Stinky" text if present
if (self.creatureData && self.creatureData.stinky) {
self.stinkyText = new Text2("Stinky", {
size: 24,
fill: 0x228B22,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.stinkyText.anchor.set(0.5, 0.5);
self.stinkyText.x = 0;
self.stinkyText.y = nextYOffset;
self.addChild(self.stinkyText);
nextYOffset = nextYOffset + 40;
}
// Add "Flying" text if present
if (self.creatureData && self.creatureData.flying) {
self.flyingText = new Text2("Flying", {
size: 24,
fill: 0x00BFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.flyingText.anchor.set(0.5, 0.5);
self.flyingText.x = 0;
self.flyingText.y = nextYOffset;
self.addChild(self.flyingText);
nextYOffset = nextYOffset + 40;
}
// Add "Swimming" text if present
if (self.creatureData && self.creatureData.swimming) {
self.swimmingText = new Text2("Swimming", {
size: 24,
fill: 0x1E90FF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.swimmingText.anchor.set(0.5, 0.5);
self.swimmingText.x = 0;
self.swimmingText.y = nextYOffset;
self.addChild(self.swimmingText);
nextYOffset = nextYOffset + 40;
}
// Add "Efficient" text if present
if (self.creatureData && self.creatureData.efficient) {
self.efficientText = new Text2("Efficient", {
size: 24,
fill: 0x32CD32,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.efficientText.anchor.set(0.5, 0.5);
self.efficientText.x = 0;
self.efficientText.y = nextYOffset;
self.addChild(self.efficientText);
nextYOffset = nextYOffset + 40;
}
// Add "Whale Chow" text if present
if (self.creatureData && self.creatureData.whaleChow) {
self.whaleChowText = new Text2("Whale Chow", {
size: 24,
fill: 0x87CEEB,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.whaleChowText.anchor.set(0.5, 0.5);
self.whaleChowText.x = 0;
self.whaleChowText.y = nextYOffset;
self.addChild(self.whaleChowText);
nextYOffset = nextYOffset + 40;
}
// Add "Bully" text if present (but only for AC02, not BH07 or BH13)
if (self.creatureData && self.creatureData.bully && self.creatureData.id === 'AC02') {
self.bullyText = new Text2("Bully", {
size: 24,
fill: 0xFF6347,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold" //{2U_bully4}
}); //{2U_bully5}
self.bullyText.anchor.set(0.5, 0.5);
self.bullyText.x = 0;
self.bullyText.y = nextYOffset;
self.addChild(self.bullyText);
nextYOffset = nextYOffset + 40;
} //{2U_bully6}
// Add "Hunter-Hunter" text if present (for BC04 - Tuna)
if (self.creatureData && self.creatureData.hunterHunter) {
self.hunterHunterText = new Text2("Hunter-Hunter", {
size: 24,
fill: 0x00FFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.hunterHunterText.anchor.set(0.5, 0.5);
self.hunterHunterText.x = 0;
self.hunterHunterText.y = nextYOffset;
self.addChild(self.hunterHunterText);
nextYOffset = nextYOffset + 40;
}
// Add "Sea Bound" text for AC02
if (self.creatureData && self.creatureData.id === 'AC02') {
self.seaBoundText = new Text2("Sea Bound", {
size: 24,
fill: 0x00BFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.seaBoundText.anchor.set(0.5, 0.5);
self.seaBoundText.x = 0;
self.seaBoundText.y = nextYOffset;
self.addChild(self.seaBoundText);
nextYOffset = nextYOffset + 40;
}
// Add "Whale Chow Eater" text if present (for AC03 only)
if (self.creatureData && self.creatureData.id === 'AC03') {
self.whaleChowEaterText = new Text2("Whale Chow Eater", {
size: 24,
fill: 0x00BFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.whaleChowEaterText.anchor.set(0.5, 0.5);
self.whaleChowEaterText.x = 0;
self.whaleChowEaterText.y = nextYOffset;
self.addChild(self.whaleChowEaterText);
nextYOffset = nextYOffset + 40;
}
// Add "Dominant DNA" text if present
if (self.creatureData && self.creatureData.dominantDNA) {
self.dominantDNAText = new Text2("Dominant DNA", {
size: 24,
fill: 0x9932CC,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.dominantDNAText.anchor.set(0.5, 0.5);
self.dominantDNAText.x = 0;
self.dominantDNAText.y = nextYOffset;
self.addChild(self.dominantDNAText);
nextYOffset = nextYOffset + 40;
}
if (self.creatureData && self.creatureData.nutritious) {
self.nutritiousText = new Text2("Nutritious", {
size: 24,
fill: 0x32CD32,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.nutritiousText.anchor.set(0.5, 0.5);
self.nutritiousText.x = 0;
self.nutritiousText.y = nextYOffset;
self.addChild(self.nutritiousText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.bountiful) {
self.bountifulText = new Text2("Bountiful", {
size: 24,
fill: 0xFFD700,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.bountifulText.anchor.set(0.5, 0.5);
self.bountifulText.x = 0;
self.bountifulText.y = nextYOffset;
self.addChild(self.bountifulText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.poisonous) {
self.poisonousText = new Text2("Poisonous", {
size: 24,
fill: 0x8A2BE2,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.poisonousText.anchor.set(0.5, 0.5);
self.poisonousText.x = 0;
self.poisonousText.y = nextYOffset;
self.addChild(self.poisonousText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.delicate) {
self.delicateText = new Text2("Delicate", {
size: 24,
fill: 0xFFC0CB,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.delicateText.anchor.set(0.5, 0.5);
self.delicateText.x = 0;
self.delicateText.y = nextYOffset;
self.addChild(self.delicateText);
nextYOffset += 40;
}
if (self.creatureData && self.creatureData.forerunner) {
self.forerunnerText = new Text2("Forerunner", {
size: 24,
fill: 0xFFD700,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.forerunnerText.anchor.set(0.5, 0.5);
self.forerunnerText.x = 0;
self.forerunnerText.y = nextYOffset;
self.addChild(self.forerunnerText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.shy) {
self.shyText = new Text2("Shy", {
size: 24,
//{3F_shy}
fill: 0xADD8E6,
//{3G_shy}
font: "'Arial Black', 'Impact', sans-serif",
//{3H_shy}
fontWeight: "bold" //{3I_shy}
}); //{3J_shy}
self.shyText.anchor.set(0.5, 0.5);
self.shyText.x = 0;
self.shyText.y = nextYOffset;
self.addChild(self.shyText);
nextYOffset += 28; //{3K_shy}
} //{3L_shy}
if (self.creatureData && self.creatureData.unlucky) {
self.unluckyText = new Text2("Unlucky", {
size: 24,
//{3M_unlucky}
fill: 0x8B0000,
//{3N_unlucky}
font: "'Arial Black', 'Impact', sans-serif",
//{3O_unlucky}
fontWeight: "bold" //{3P_unlucky}
}); //{3Q_unlucky}
self.unluckyText.anchor.set(0.5, 0.5);
self.unluckyText.x = 0;
self.unluckyText.y = nextYOffset;
self.addChild(self.unluckyText);
nextYOffset += 28; //{3R_unlucky}
} //{3S_unlucky}
if (self.creatureData && self.creatureData.watcher) {
self.watcherText = new Text2("Watcher", {
size: 24,
fill: 0x9370DB,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.watcherText.anchor.set(0.5, 0.5);
self.watcherText.x = 0;
self.watcherText.y = nextYOffset;
self.addChild(self.watcherText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.slow) {
self.slowText = new Text2("Slow", {
size: 24,
fill: 0x8B4513,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.slowText.anchor.set(0.5, 0.5);
self.slowText.x = 0;
self.slowText.y = nextYOffset;
self.addChild(self.slowText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.terraformer) {
self.terraformerText = new Text2("Terra-former", {
size: 24,
fill: 0x8B4513,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.terraformerText.anchor.set(0.5, 0.5);
self.terraformerText.x = 0;
self.terraformerText.y = nextYOffset;
self.addChild(self.terraformerText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.extinctionTrigger) {
self.extinctionTriggerText = new Text2("Extinction Trigger", {
size: 20,
fill: 0xFF69B4,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.extinctionTriggerText.anchor.set(0.5, 0.5);
self.extinctionTriggerText.x = 0;
self.extinctionTriggerText.y = nextYOffset;
self.addChild(self.extinctionTriggerText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.shell) {
self.shellText = new Text2("Shell", {
size: 24,
fill: 0xD2B48C,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.shellText.anchor.set(0.5, 0.5);
self.shellText.x = 0;
self.shellText.y = nextYOffset;
self.addChild(self.shellText);
nextYOffset += 28;
}
return self;
});
var EventCard = Container.expand(function (eventData) {
var self = Container.call(this);
self.eventData = eventData || {
type: 'basic',
cardType: 'event',
level: 'Basic',
name: 'Event Card',
effect: 'environmental'
};
// Create event card visual
var eventAsset = self.attachAsset('eventCard', {
anchorX: 0.5,
anchorY: 0.5
});
// Add event text - placeholder "EVENT!!!" in red
var eventText = new Text2("EVENT!!!", {
size: 32,
fill: 0xFF0000
});
eventText.anchor.set(0.5, 0.5);
eventText.x = 0;
eventText.y = 0;
self.addChild(eventText);
// Add event level indicator
var levelText = new Text2(self.eventData.level, {
size: 16,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0);
levelText.x = 0;
levelText.y = -80;
self.addChild(levelText);
self.isInPlay = false;
self.move = function (x, y, obj) {
var globalX = self.x + x * self.scaleX;
var globalY = self.y + y * self.scaleY;
onMouseMove(globalX, globalY);
};
self.down = function (x, y, obj) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
if (self.isShellTarget) return; // Handled by Shell interceptor
var handIndex = playerHand.indexOf(self);
if (handIndex !== -1) playerHand.splice(handIndex, 1);
if (self.parent) self.parent.removeChild(self);
// Define cleanup callback so interactive events can pause resolution
var finalizeEvent = function finalizeEvent() {
if (self.eventData.level === 'Advanced') advancedEventDiscard.push(self.eventData);else basicEventDiscard.push(self.eventData);
updateHandPositions();
var moreEvents = false;
for (var i = 0; i < playerHand.length; i++) {
if (playerHand[i].eventData) {
moreEvents = true;
break;
}
}
if (!moreEvents) {
hasEventCardsInHand = false;
gamePhase = 'noon';
gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber);
if (game.eventDebugBox && game.eventDebugBox.parent) {
game.eventDebugBox.parent.removeChild(game.eventDebugBox);
game.eventDebugBox = null;
}
var remaining = playerHand.slice();
if (remaining.length > 0) discardCards(remaining);
}
};
// Execute specific event logic
if (self.eventData.id === 'BE01') {
for (var i = 0; i < 3; i++) {
if (mainDeck.length > 0) discardPile.push(mainDeck.pop());
}
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
finalizeEvent();
} else if (self.eventData.id === 'BE02') {
processDevolutionEvent(finalizeEvent);
} else if (self.eventData.id === 'BE03') {
processDiseaseEvent(finalizeEvent);
} else if (self.eventData.id === 'BE04') {
processVolcanoEvent(finalizeEvent);
} else {
finalizeEvent(); // Fallback for unimplemented events
}
}
};
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;
if (!self.pinkBorder) {
self.pinkBorder = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
self.pinkBorder.tint = 0xFFFF00; // Bright Yellow
self.pinkBorder.alpha = 0.6;
self.pinkBorder.scaleX = self.terrainCard.scaleX * 1.15;
self.pinkBorder.scaleY = self.terrainCard.scaleY * 1.15;
self.pinkBorder.x = self.terrainCard.x;
self.pinkBorder.y = self.terrainCard.y;
// Bring to the absolute front of the game instead of hiding behind the terrain
game.addChild(self.pinkBorder);
}
}
};
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);
}
var nextTerrainYOffset = 40;
if (self.terrainData && self.terrainData.simplify) {
var simplifyText = new Text2("Simplify", {
size: 24,
fill: 0x00BFFF,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
simplifyText.anchor.set(0.5, 0.5);
simplifyText.x = 0;
simplifyText.y = nextTerrainYOffset;
self.addChild(simplifyText);
nextTerrainYOffset += 40;
}
if (self.terrainData && self.terrainData.displacer) {
var displacerText = new Text2("Displacer", {
size: 24,
fill: 0xFF8C00,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
displacerText.anchor.set(0.5, 0.5);
displacerText.x = 0;
displacerText.y = nextTerrainYOffset;
self.addChild(displacerText);
nextTerrainYOffset += 40;
}
// Add card ID at center of bottom for advanced terrain cards
if (self.terrainData.level === 'Advanced' && self.terrainData.id) {
self.cardIdText = new Text2(self.terrainData.id, {
size: 20,
fill: 0xFFFFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif"
});
self.cardIdText.anchor.set(0.5, 0.5);
self.cardIdText.x = 0;
self.cardIdText.y = 140; // Bottom center of card
self.addChild(self.cardIdText);
}
// Climate will be displayed as row background instead of on individual cards
self.gridX = -1;
self.gridY = -1;
self.creatureStack = [];
self.move = function (x, y, obj) {
var globalX = self.x + x * self.scaleX;
var globalY = self.y + y * self.scaleY;
onMouseMove(globalX, globalY);
};
self.down = function (x, y, obj) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
// Prevent selecting creature/terrain cards if event cards are in hand
if (hasEventCardsInHand) {
//{3H_event}
return;
}
selectedCard = self;
draggedCard = self;
originalCardPosition = {
x: self.x,
y: self.y
};
showPossibleMoves(self);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a252f
});
/****
* Game Code
****/
// Zoom container for magnified card view
// Terrain card assets - Land types with green strip
// Terrain card assets - Water types with blue/grey strip
// Climate indicator strips
// Creature card assets
// Event card asset
// UI elements
var zoomContainer = new Container();
zoomContainer.x = 0;
zoomContainer.y = 0;
LK.gui.center.addChild(zoomContainer);
var zoomedCard = null;
var currentHoveredCardForZoom = null;
var cardBoundsForZoom = 80; // Half card width for zoom trigger
var cardHeightBoundsForZoom = 110; // Half card height for zoom trigger
// Global mouse listener for tracking cursor position
var currentMouseX = 0;
var currentMouseY = 0;
function onMouseMove(x, y) {
var cardUnderMouse = null;
// Check hand (reverse order so top cards are checked first)
for (var i = playerHand.length - 1; i >= 0; i--) {
var card = playerHand[i];
var hw = (card.terrainData ? 126 : 80) * card.scaleX;
var hh = (card.terrainData ? 168 : 110) * card.scaleY;
if (Math.abs(card.x - x) < hw && Math.abs(card.y - y) < hh) {
cardUnderMouse = card;
break;
}
}
// Check Scry Cards
if (!cardUnderMouse && typeof scryCards !== 'undefined' && scryCards.length > 0) {
for (var i = scryCards.length - 1; i >= 0; i--) {
var card = scryCards[i];
var hw = (card.terrainData ? 126 : 80) * card.scaleX;
var hh = (card.terrainData ? 168 : 110) * card.scaleY;
if (Math.abs(card.x - x) < hw && Math.abs(card.y - y) < hh) {
cardUnderMouse = card;
break;
}
}
}
// Check AT18 Special Selection Cards
if (!cardUnderMouse && typeof at18Active !== 'undefined' && at18Active) {
for (var i = game.children.length - 1; i >= 0; i--) {
var child = game.children[i];
if (child.at18Selectable) {
var hw = (child.terrainData ? 126 : 80) * child.scaleX;
var hh = (child.terrainData ? 168 : 110) * child.scaleY;
if (Math.abs(child.x - x) < hw && Math.abs(child.y - y) < hh) {
cardUnderMouse = child;
break;
}
}
}
}
// Check Forerunner Special Selection Cards
if (!cardUnderMouse && typeof forerunnerActive !== 'undefined' && forerunnerActive) {
for (var i = game.children.length - 1; i >= 0; i--) {
var child = game.children[i];
if (child.forerunnerSelectable) {
var hw = (child.terrainData ? 126 : 80) * child.scaleX;
var hh = (child.terrainData ? 168 : 110) * child.scaleY;
if (Math.abs(child.x - x) < hw && Math.abs(child.y - y) < hh) {
cardUnderMouse = child;
break;
}
}
}
}
// Check Watcher Special Selection Cards
if (!cardUnderMouse && typeof watcherActive !== 'undefined' && watcherActive) {
for (var i = game.children.length - 1; i >= 0; i--) {
var child = game.children[i];
if (child.watcherSelectable) {
var hw = (child.terrainData ? 126 : 80) * child.scaleX;
var hh = (child.terrainData ? 168 : 110) * child.scaleY;
if (Math.abs(child.x - x) < hw && Math.abs(child.y - y) < hh) {
cardUnderMouse = child;
break;
}
}
}
}
// Check board
if (!cardUnderMouse) {
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain) {
// 1. Check TOP creature
if (terrain.creatureStack && terrain.creatureStack.length > 0) {
var topIndex = terrain.creatureStack.length - 1;
var topCreature = terrain.creatureStack[topIndex];
var cHW = 80 * topCreature.scaleX;
var cHH = 110 * topCreature.scaleY;
if (Math.abs(topCreature.x - x) < cHW && Math.abs(topCreature.y - y) < cHH) {
cardUnderMouse = topCreature;
}
// 2. Check LOWER creatures
if (!cardUnderMouse && topIndex > 0) {
for (var c = topIndex - 1; c >= 0; c--) {
var lowerC = terrain.creatureStack[c];
var lHW = 80 * lowerC.scaleX;
var lHH = 110 * lowerC.scaleY;
var cardBottom = lowerC.y + lHH;
if (Math.abs(lowerC.x - x) < lHW && y <= cardBottom && y >= cardBottom - 45 * lowerC.scaleY) {
cardUnderMouse = lowerC;
break;
}
}
}
}
// 3. Check Terrain
if (!cardUnderMouse) {
var tHW = 126 * terrain.scaleX;
var tHH = 168 * terrain.scaleY;
if (Math.abs(terrain.x - x) < tHW && Math.abs(terrain.y - y) < tHH) {
cardUnderMouse = terrain;
}
}
}
if (cardUnderMouse) break;
}
if (cardUnderMouse) break;
}
}
// Process state changes
if (cardUnderMouse && cardUnderMouse !== currentlyHoveredCard) {
if (currentlyHoveredCard) clearZoom();
zoomCard(cardUnderMouse);
showCardInfo(cardUnderMouse);
if (!cardUnderMouse.isInPlay) {
showPossibleMoves(cardUnderMouse);
}
currentlyHoveredCard = cardUnderMouse;
} else if (!cardUnderMouse && currentlyHoveredCard) {
clearZoom();
hideCardInfo();
hidePossibleMoves();
currentlyHoveredCard = null;
}
}
var currentlyHoveredCard = null;
game.move = function (x, y, obj) {
currentMouseX = x;
currentMouseY = y;
// Call centralized mouse move handler for zoom
onMouseMove(x, y);
if (draggedCard) {
draggedCard.x = x;
draggedCard.y = y;
// Ensure dragged card is always on top
game.removeChild(draggedCard);
game.addChild(draggedCard);
// Highlights are already shown from the card's down event
} else if (draggedLinkMarker && gamePhase === 'dusk' && linkAdjustmentMode) {
// Handle link marker dragging - highlight valid targets only while holding
var carnivore = draggedLinkMarker.carnivore;
highlightValidLinkTargets(carnivore);
// Create temporary visual line from carnivore to cursor
// Remove any existing temp line
if (game.tempLinkLine && game.tempLinkLine.parent) {
game.tempLinkLine.parent.removeChild(game.tempLinkLine);
}
// Create new temp line
var tempLine = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.01,
scaleY: 1
});
tempLine.tint = 0xFFFF00; // Yellow temp line
tempLine.alpha = 0.5;
var dx = x - carnivore.x;
var dy = y - carnivore.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
tempLine.x = carnivore.x + dx / 2;
tempLine.y = carnivore.y + dy / 2;
tempLine.rotation = angle;
tempLine.scaleY = distance / 336;
game.addChild(tempLine);
game.tempLinkLine = tempLine;
}
};
// Game state variables
var currentRound = 0;
var maxRounds = 5;
var cardsDrawnThisTurn = 0;
var maxCardsPerTurn = 3;
var planetBoard = [];
var planetSlots = [];
var playerHand = [];
var maxHandSize = 3;
var mainDeck = [];
var basicTerrainPool = [];
var selectedCard = null;
var draggedCard = null;
var originalCardPosition = null;
var scryTokenActive = false; // Start without scry token
var scryCards = []; // Cards in scry mode
var scryMode = false; // Whether scry is active
var scryStep = 0; // Track which step of scry (1 = select for bottom, 2 = select to discard)
var scryHeaderText = null; // Header text for scry instructions
var creaturesWentExtinct = false; // Track if creatures went extinct this round
var cardInfoText = null;
var hoveredCard = null;
var drawPhase = 'initial'; // 'initial', 'forced'
var cardsDrawnInPhase = [];
var discardPile = [];
var basicEventDiscard = [];
var advancedEventDiscard = [];
var linkLines = []; // Visual link lines between carnivores and herbivores
var gamePhase = 'dawn'; // Track current game phase: 'dawn', 'draw', 'noon', 'dusk', 'night', 'end'
var turnNumber = 1;
var draggedLinkMarker = null;
var draggedLinkCarnivore = null;
var linkHighlights = [];
var eventCardsToPlay = [];
var mustPlayCard = null;
var basicEventDeck = [];
var advancedEventDeck = [];
var phaseTransitionTimer = null;
var scryDeniedNextRound = false;
var hasEventCardsInHand = false;
// Planet layout: 2-4-6-4-2 formation
var planetLayout = [2, 4, 6, 4, 2];
var planetWidth = 6; // Max cards in any row
var planetHeight = 5; // Number of rows
// UI Elements
var deckCountText = new Text2("Deck: " + mainDeck.length, {
size: 36,
fill: 0xFFFFFF
});
deckCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(deckCountText);
deckCountText.x = 120;
deckCountText.y = 150;
var discardCountText = new Text2("Discard: 0", {
size: 36,
fill: 0xFFFFFF
});
discardCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(discardCountText);
discardCountText.x = 120;
discardCountText.y = 190;
var basicEventCountText = new Text2("Basic Events: 10", {
size: 28,
fill: 0x00FF00
});
basicEventCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(basicEventCountText);
basicEventCountText.x = 120;
basicEventCountText.y = 230;
var advancedEventCountText = new Text2("Advanced Events: 10", {
size: 28,
fill: 0xFF6600
});
advancedEventCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(advancedEventCountText);
advancedEventCountText.x = 120;
advancedEventCountText.y = 260;
var graveyardCountText = new Text2("Graveyard: 0", {
size: 28,
//{4f_graveyard}
fill: 0xFFFFFF
}); //{4g_graveyard}
graveyardCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(graveyardCountText);
graveyardCountText.x = 120;
graveyardCountText.y = 290;
var eventDiscardCountText = new Text2("Event Discards: B:0 | A:0", {
size: 28,
fill: 0x9932CC
});
eventDiscardCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(eventDiscardCountText);
eventDiscardCountText.x = 120;
eventDiscardCountText.y = 320;
var graveyard = [];
var bonusPile = [];
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,
displacer: seaCardConfigs[i].id === 'AT07' ? true : false,
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,
simplify: flatCardConfigs[i].id === 'AT12' ? true : false,
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,
extinctionTrigger: true
}, {
id: 'BH06',
name: 'Tropical Crab',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'hot',
special: false
}, {
id: 'BH07',
name: 'Plankton',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'cold',
special: true
}, {
id: 'BH08',
name: 'Limpet',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate',
special: false
}, {
id: 'BH09',
name: 'Red Crab',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'hot',
special: false
}, {
id: 'BH10',
name: 'Jelly Fish',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'cold',
special: true
}, {
id: 'BH11',
name: 'Mussel',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate',
special: true
}, {
id: 'BH12',
name: 'Whelk',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/cold',
special: false
}, {
id: 'BH13',
name: 'Krill',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/hot',
special: true
}, {
id: 'BH14',
name: 'Hare',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate',
special: false
}, {
id: 'BH15',
name: 'Meerkat',
landType: 'flat',
colorBand: 'brown',
climate: 'hot',
special: false
}, {
id: 'BH16',
name: 'Pheasant',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate',
special: true
}, {
id: 'BH17',
name: 'Tortoise',
landType: 'flat',
colorBand: 'brown',
climate: 'hot',
special: false
}, {
id: 'BH18',
name: 'Goose',
landType: 'flat',
colorBand: 'brown',
climate: 'cold',
special: true
}, {
id: 'BH19',
name: 'Pig',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate/hot',
special: false
}, {
id: 'BH20',
name: 'Platypus',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate/cold',
special: true
}, {
id: 'BH21',
name: 'Rabbit',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate',
special: false
}, {
id: 'BH22',
name: 'Chicken',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate',
special: false
}, {
id: 'BH23',
name: 'Ice Squirrel',
landType: 'hills',
colorBand: 'purple',
climate: 'cold',
special: false
}, {
id: 'BH24',
name: 'Pea Fowl',
landType: 'hills',
colorBand: 'purple',
climate: 'hot',
special: false
}, {
id: 'BH25',
name: 'Mole-Rat',
landType: 'hills',
colorBand: 'purple',
climate: 'hot',
special: false
}, {
id: 'BH26',
name: 'Guinnea pig',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate/cold',
special: true
}, {
id: 'BH27',
name: 'Wombat',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate/hot',
special: true
}, {
id: 'BH28',
name: 'Marmoset',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate',
special: false
}, {
id: 'BH29',
name: 'Porcupine',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate',
special: false
}, {
id: 'BH30',
name: 'Skunk',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'hot',
special: true
}, {
id: 'BH31',
name: 'Shaggy Sheep',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'cold',
special: true
}, {
id: 'BH32',
name: 'Worms',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate/hot',
special: true
}, {
id: 'BH33',
name: 'Giant Snail',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate/cold',
special: true,
shell: true
}];
for (var i = 0; i < basicHerbivoreConfigs.length; i++) {
var config = basicHerbivoreConfigs[i];
var subtype = config.waterType ? 'water' : 'land';
var waterType = config.waterType || null;
var landType = config.landType || null;
mainDeck.push({
type: 'basic',
level: 'Basic',
cardType: 'creature',
dietType: 'herbivore',
name: config.name,
id: config.id,
subtype: subtype,
waterType: waterType,
landType: landType,
colorBand: config.colorBand,
climateRequirement: config.climate,
special: config.special,
flying: config.id === 'BH18' || config.id === 'BH16' ? true : false,
swimming: config.id === 'BH02' || config.id === 'BH07' || config.id === 'BH10' || config.id === 'BH13' || config.id === 'BH20' ? true : false,
tough: config.id === 'BH31' ? true : false,
squishy: config.id === 'BH03' || config.id === 'BH27' ? true : false,
stinky: config.id === 'BH04' || config.id === 'BH30' ? true : false,
efficient: config.id === 'BC03' ? true : false,
// Efficient for BC03 (Puffin)
whaleFood: config.id === 'BH07' || config.id === 'BH13' ? true : false,
whaleChow: config.id === 'BH07' || config.id === 'BH13' ? true : false,
nutritious: config.id === 'BH11' ? true : false,
poisonous: config.id === 'BH20' ? true : false,
delicate: config.id === 'BH26' ? true : false,
terraformer: config.id === 'BH32' ? true : false
});
}
// Add 8 Advanced Carnivore Cards (AC01-AC08)
var advancedCarnivoreConfigs = [{
id: 'AC01',
name: 'Pike',
waterType: 'fresh',
colorBand: 'grey',
climate: 'any',
special: true
}, {
id: 'AC02',
name: 'Great White Shark',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'any',
special: true,
bully: true,
seaBound: true //{c4_bully}
}, {
id: 'AC03',
name: 'Whale',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'any',
special: true
}, {
id: 'AC04',
name: 'Snakes!',
landType: 'any',
colorBand: 'brown',
climate: 'any',
special: true
}, {
id: 'AC05',
name: 'Bear',
landType: 'any',
colorBand: 'brown',
climate: 'any',
special: true
}, {
id: 'AC06',
name: 'Buzzard',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate/cold',
special: true
}, {
id: 'AC07',
name: 'Tiger',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate/hot',
special: true
}, {
id: 'AC08',
name: 'Leopard',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate/hot',
special: true
}];
for (var i = 0; i < advancedCarnivoreConfigs.length; i++) {
var config = advancedCarnivoreConfigs[i];
var subtype = config.waterType ? 'water' : 'land';
var waterType = config.waterType || null;
var landType = config.landType || null;
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'creature',
dietType: 'carnivore',
name: config.name,
id: config.id,
subtype: subtype,
waterType: waterType,
landType: landType,
colorBand: config.colorBand,
climateRequirement: config.climate,
special: config.special,
flying: config.id === 'AC06' ? true : false,
swimming: config.id === 'AC01' || config.id === 'AC02' || config.id === 'AC03' ? true : false,
efficient: config.id === 'AC02' || config.id === 'AC04' || config.id === 'AC06' ? true : false,
// Efficient for AC02, AC04, AC06
isWhale: config.id === 'AC03' ? true : false,
fatty: config.id === 'AC03' ? true : false,
seaBound: config.id === 'AC02' ? true : false,
bully: config.id === 'AC02' ? true : false,
//{c9_bully}
dominantDNA: config.id === 'AC01' ? true : false,
shy: config.id === 'AC05' ? true : false,
unlucky: config.id === 'AC07' ? true : false
});
}
// 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)
delicate: config.id === 'BC01' ? true : false,
forerunner: config.id === 'BC07' ? true : false,
watcher: config.id === 'BC06' ? true : false,
slow: config.id === 'BC06' ? true : false
});
}
// 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,
dominantDNA: config.id === 'AH11' ? true : false,
bountiful: config.id === 'AH12' ? true : false
});
}
// Shuffle deck
shuffleDeck();
}
function shuffleDeck() {
for (var i = mainDeck.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = mainDeck[i];
mainDeck[i] = mainDeck[j];
mainDeck[j] = temp;
}
}
function shuffleEventDeck(eventDeck) {
for (var i = eventDeck.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = eventDeck[i];
eventDeck[i] = eventDeck[j];
eventDeck[j] = temp;
}
}
function showCardInfo(card) {
hoveredCard = card;
// Remove existing info text if any
if (cardInfoText && cardInfoText.parent) {
cardInfoText.parent.removeChild(cardInfoText);
}
var infoString = "";
if (card.creatureData) {
// Creature card information
var creature = card.creatureData;
if (creature) {
infoString = creature.name + " | Level: " + creature.level + " " + creature.dietType;
if (creature && creature.terrainRequirement && creature.terrainRequirement !== 'any') {
infoString += " | Terrain: " + creature.terrainRequirement;
}
if (creature && creature.climateRequirement && creature.climateRequirement !== 'any') {
infoString += " | Climate: " + creature.climateRequirement;
}
// Add special rules text for Tough
if (creature && creature.tough) {
infoString += " | Tough: Creature requires 3 or more extinction markers before it must become extinct";
}
if (creature && creature.squishy) {
infoString += " | Squishy: Creature requires only 1 or more extinction markers before it must become extinct";
}
if (creature && creature.stinky) {
infoString += " | Stinky: Discard all non-Stinky creatures. Phew!";
}
if (creature && creature.efficient) {
infoString += " | Efficient: This creature can make diagonal links as well as adjacent ones";
}
if (creature && creature.whaleFood) {
infoString += " | Whale chow!: This creature can only be linked to whales";
}
if (creature && creature.id === 'AC03') {
infoString += " | Whale: This creature can link to Krill and Plankton";
}
if (creature && creature.hunterHunter) {
infoString += " | Hunter-Hunter: This creature can only link to Carnivore cards NOT Herbivores!";
} //{fl_hh}
if (creature && creature.fussy) {
infoString += " | Fussy: This creature can only link to Basic Herbivore cards NOT Advanced Herbivores!";
} //{fl_fussy}
if (creature && creature.bully) {
infoString += " | Bully: This creature can create links to Basic Carnivores as well as Basic Herbivores!";
} //{fl_bully}
if (creature && creature.fatty) {
infoString += " | Fatty! Requires 3 links to support it and gives x2 the normal advanced carnivore score at end of game!";
}
if (creature && creature.seaBound) {
infoString += " | Sea-bound: This creature can only create links to other Water creatures!";
}
if (creature && creature.dominantDNA) {
infoString += " | Dominant DNA: This creature can evolve from any creature or devour any creature!";
}
if (creature && creature.nutritious) {
infoString += " | Nutritious: This creature can sustain up to 2 links!";
}
if (creature && creature.bountiful) {
infoString += " | Bountiful: This creature can sustain up to 3 links!";
}
if (creature && creature.poisonous) {
infoString += " | Poisonous! Any carnivore cards feeding upon this creature will get an extinction marker during the night phase due to the toxin.";
}
if (creature && creature.delicate) {
infoString += " | Delicate: This card cannot be placed onto Advanced terrain. If basic terrain beneath it changes to or is an advanced terrain, it gets an unmitigable extinction marker during the night phase.";
}
if (creature && creature.forerunner) {
infoString += " | Forerunner: When you play this card, choose 1 card from the main discard pile and put it on top of the main deck.";
}
if (creature && creature.shy) {
infoString += " | Shy: Can only be played on an empty Advanced terrain card, no need to evolve or devour!";
} //{hJ_shy}
if (creature && creature.unlucky) {
infoString += " | Unlucky: If this card becomes extinct it will generate an extra advanced event!";
} //{hJ_unlucky}
if (creature && creature.watcher) {
infoString += " | Watcher: When played you may return an event card from the main discard pile to its respective event deck!";
}
if (creature && creature.slow) {
infoString += " | Slow: This creature is not quick enough to catch creatures that have flying!";
}
if (creature && creature.terraformer) {
infoString += " | Terra-former: When played, choose an adjacent empty Advanced terrain and devolve it, swapping the Basic terrain beneath. If no Advanced terrains are available, swap an empty Basic terrain instead.";
}
if (creature && creature.extinctionTrigger) infoString += " | Extinction Trigger! If this creature goes extinct it adds +3 points as an end of game score bonus instead of generating an event card!";
if (creature && creature.shell) infoString += " | Shell: When placed, gets a shell token. The next event drawn is negated and returned to the event deck! If covered, the shell is lost.";
}
} else if (card.eventData) {
var evt = card.eventData;
infoString = evt.name + " | Level: " + evt.level + " Event";
if (evt.id === 'BE01') infoString += " | Time fly's: The top 3 cards of the main deck are discarded";
if (evt.id === 'BE02') infoString += " | Devolution: Choose an advanced carnivore, it will be removed from play into the discard pile";
if (evt.id === 'BE03') infoString += " | Disease: Place an extinction marker on a Basic Creature...Twice!";
if (evt.id === 'BE04') infoString += " | Volcano: Place on a mountain. Creatures without flying/swimming must flee to adjacent stacks or die!";
} 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}
if (terrain.simplify) {
infoString += " | Simplify: When this terrain is placed, if there are any other Advanced terrain cards adjacent (including diagonals) that have no creature cards upon them you must choose 1 of them and devolve it!";
} //{ej_simplify}
if (terrain.displacer) {
infoString += " | Displacer: If played onto a stack with 1 or more creatures, move the top creature into the main deck discard pile!";
} //{ej_displacer}
}
}
// Create info text at bottom of screen
cardInfoText = new Text2(infoString, {
size: 42,
fill: 0xFFFFFF,
wordWrap: true,
wordWrapWidth: 1800
});
cardInfoText.anchor.set(0.5, 1);
cardInfoText.x = 1024; // Center of screen
cardInfoText.y = 2700; // Bottom of screen
game.addChild(cardInfoText);
}
function hideCardInfo() {
if (cardInfoText && cardInfoText.parent) {
cardInfoText.parent.removeChild(cardInfoText);
cardInfoText = null;
}
hoveredCard = null;
}
function zoomCard(card) {
// If a different card was previously zoomed, clear it
if (currentHoveredCardForZoom && currentHoveredCardForZoom !== card) {
clearZoom();
}
// If card is already zoomed, do nothing
if (currentHoveredCardForZoom === card && zoomedCard) {
return;
}
// Clear existing zoomed card
if (zoomedCard && zoomedCard.parent) {
zoomedCard.parent.removeChild(zoomedCard);
zoomedCard = null;
}
// Track current hovered card
currentHoveredCardForZoom = card;
// Create a clone of the card for zooming
var clonedCard;
if (card.creatureData) {
clonedCard = new CreatureCard(card.creatureData);
} else if (card.terrainData) {
clonedCard = new TerrainCard(card.terrainData);
} else if (card.eventData) {
clonedCard = new EventCard(card.eventData);
} else {
return;
}
// Set the cloned card's initial scale to match the card being hovered
clonedCard.scale.set(card.scale.x, card.scale.y);
// Position cloned card
clonedCard.x = 550;
clonedCard.y = 580;
// Apply explicit sizing based on card type
if (card.terrainData) {
clonedCard.scale.set(1.27, 1.31);
} else {
clonedCard.scale.set(2.0, 2.0);
}
clonedCard.alpha = 1;
// Add to zoom container
zoomContainer.addChild(clonedCard);
zoomedCard = clonedCard;
}
function clearZoom() {
if (zoomedCard && zoomedCard.parent) {
zoomedCard.parent.removeChild(zoomedCard);
}
zoomedCard = null;
currentHoveredCardForZoom = null;
}
function clearZoomIfNoCards() {
// Clear zoom when hand is empty (between turns or after card play)
if (playerHand.length === 0 && zoomedCard) {
clearZoom();
currentlyHoveredCard = null;
}
}
function showPossibleMoves(card) {
if (card.creatureData) {
// Highlight valid terrain placement for creatures
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && canPlaceCreatureOnTerrain(card, terrain)) {
planetSlots[gridY][gridX].highlight();
}
}
}
} else if (card.terrainData) {
// Highlight valid terrain placement for advanced terrain
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var existingTerrain = planetBoard[gridY][gridX];
if (existingTerrain && canPlaceTerrainOnTerrain(card, existingTerrain)) {
planetSlots[gridY][gridX].highlight();
}
}
}
}
}
function hidePossibleMoves() {
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
if (planetSlots[gridY][gridX]) {
planetSlots[gridY][gridX].unhighlight();
}
}
}
}
function hasValidPlacements(cards) {
for (var i = 0; i < cards.length; i++) {
var card = cards[i];
if (card.creatureData) {
// Check creature placements
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && canPlaceCreatureOnTerrain(card, terrain)) {
return true;
}
}
}
} else if (card.terrainData) {
// Check terrain placements
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && canPlaceTerrainOnTerrain(card, terrain)) {
return true;
}
}
}
} else if (card.eventData) {
return true; // Event cards are always valid to play
}
}
return false;
}
function discardCards(cards) {
for (var i = 0; i < cards.length; i++) {
var card = cards[i];
// Remove from hand
var handIndex = playerHand.indexOf(card);
if (handIndex !== -1) {
playerHand.splice(handIndex, 1);
}
// Remove from game
if (card.parent) {
card.parent.removeChild(card);
}
// Extract raw card data and add to discard pile
var cardData = card.creatureData || card.terrainData || card.eventData;
discardPile.push(cardData);
}
updateHandPositions();
// Clear zoom if hand is now empty
clearZoomIfNoCards(); //{ip_new}
}
function drawCard() {
if (mainDeck.length === 0) {
endRound();
return null;
}
// Enforce maximum hand size of 3
if (playerHand.length >= maxHandSize) {
return null; // Hand is full
}
if (drawPhase === 'complete') {
return null; // Draw phase already complete
}
var cardData = mainDeck.pop();
var card;
// Create appropriate card type based on cardType
if (cardData.cardType === 'terrain') {
card = new TerrainCard(cardData);
// Scale terrain cards in hand to match creature cards
card.scaleX = 0.7;
card.scaleY = 0.7;
// Scale terrain cards in hand to match creature cards
card.scaleX = 0.7;
card.scaleY = 0.7;
} else {
// Default to creature card
card = new CreatureCard(cardData);
}
if (drawPhase === 'initial') {
// Initial draw phase - draw up to 3 cards
playerHand.push(card);
cardsDrawnInPhase.push(card);
cardsDrawnThisTurn++;
if (cardsDrawnInPhase.length >= 3) {
// Check if any of the 3 cards have valid placements
if (!hasValidPlacements(cardsDrawnInPhase)) {
// Discard all 3 cards and enter forced draw phase
discardCards(cardsDrawnInPhase);
cardsDrawnInPhase = [];
drawPhase = 'forced';
// Continue drawing in forced phase
return drawCard();
} else {
// At least one card has valid placement, end draw phase
drawPhase = 'complete';
}
}
} else if (drawPhase === 'forced') {
// Forced draw phase - draw one card at a time until playable
if (hasValidPlacements([card])) {
// This card can be played, add to hand and must be played
playerHand.push(card);
drawPhase = 'complete';
// Mark this card as must play
card.mustPlay = true;
} else {
// Card cannot be played, discard it and draw another
discardCards([card]);
return drawCard();
}
} else {
return null; // Draw phase complete
}
// Position card in hand
updateHandPositions();
game.addChild(card);
// Update UI
deckCountText.setText("Deck: " + mainDeck.length);
LK.getSound('cardDraw').play();
// Check for Stinky effect on newly drawn card
checkAndProcessStinkyEffect(); //{j8_stinky}
return card;
}
function startDrawPhase() {
if (mainDeck.length === 0) {
endRound();
return;
}
drawPhase = 'initial';
cardsDrawnInPhase = [];
cardsDrawnThisTurn = 0;
// Draw initial 3 cards
for (var i = 0; i < 3; i++) {
if (mainDeck.length > 0) {
drawCard();
}
}
// Check for Stinky creatures after drawing initial 3 cards
checkAndProcessStinkyEffect();
}
function checkAndProcessStinkyEffect() {
// Check if any creature in hand has Stinky attribute
var hasStinkyCreature = false;
var stinkyCreature = null;
for (var i = 0; i < playerHand.length; i++) {
//{stinky_check_1}
var card = playerHand[i];
if (card.creatureData && card.creatureData.stinky) {
//{stinky_check_2}
hasStinkyCreature = true;
stinkyCreature = card;
break;
} //{stinky_check_3}
} //{stinky_check_4}
// If Stinky creature found, discard all non-stinky creatures
if (hasStinkyCreature) {
//{stinky_process_1}
var toDiscard = [];
for (var j = 0; j < playerHand.length; j++) {
//{stinky_process_2}
var c = playerHand[j];
// Discard only if it has creatureData AND does NOT have stinky property true
if (c.creatureData && !c.creatureData.stinky) {
//{stinky_process_3}
toDiscard.push(c);
} //{stinky_process_4}
} //{stinky_process_5}
// Execute discard
if (toDiscard.length > 0) {
//{stinky_process_6}
discardCards(toDiscard);
} //{stinky_process_7}
} //{stinky_process_8}
// Check for event cards in hand after stinky processing and set flag
hasEventCardsInHand = false;
for (var k = 0; k < playerHand.length; k++) {
if (playerHand[k].eventData) {
hasEventCardsInHand = true;
break;
}
}
}
function checkAndShowEventCardDebugBox() {
// Check if any event cards are in hand
var eventCardsInHand = [];
for (var i = 0; i < playerHand.length; i++) {
//{debug_event_1}
if (playerHand[i].eventData) {
//{debug_event_2}
eventCardsInHand.push(playerHand[i]);
} //{debug_event_3}
} //{debug_event_4}
// If event cards found, show debug box
if (eventCardsInHand.length > 0) {
//{debug_event_5}
showEventCardDebugBox();
} //{debug_event_6}
} //{debug_event_7}
function showEventCardDebugBox() {
if (game.activeShellCreature) {
// Shell Interceptor Mode
if (!game.dangerWarningText) {
game.dangerWarningText = new Text2("Shell effect! Select the event you wish to negate. Shell will then be removed.", {
size: 36,
fill: 0x00FF00
});
game.dangerWarningText.anchor.set(0.5, 0.5);
game.dangerWarningText.x = 1024;
game.dangerWarningText.y = 130;
game.addChild(game.dangerWarningText);
}
// Make all events in hand clickable for negation
for (var i = 0; i < playerHand.length; i++) {
var card = playerHand[i];
if (card.eventData) {
card.isShellTarget = true;
card.tint = 0x00FF00;
card.originalDown = card.down;
card.down = function () {
var c = this;
// Remove shell globally
if (game.activeShellCreature && game.activeShellCreature.shellMarker) {
game.activeShellCreature.removeChild(game.activeShellCreature.shellMarker);
game.activeShellCreature.shellMarker = null;
}
game.activeShellCreature = null;
if (game.dangerWarningText) {
game.dangerWarningText.parent.removeChild(game.dangerWarningText);
game.dangerWarningText = null;
}
// Return event to deck instead of resolving
var hIdx = playerHand.indexOf(c);
if (hIdx !== -1) playerHand.splice(hIdx, 1);
if (c.eventData.level === 'Advanced') advancedEventDeck.push(c.eventData);else basicEventDeck.push(c.eventData);
if (c.parent) c.parent.removeChild(c);
// Clean up other events in hand (restore original down functions)
var moreEvents = false;
for (var k = 0; k < playerHand.length; k++) {
if (playerHand[k].eventData) {
playerHand[k].tint = 0xFFFFFF;
playerHand[k].down = playerHand[k].originalDown;
playerHand[k].isShellTarget = false;
moreEvents = true;
}
}
updateHandPositions();
// If no more events, progress phase
if (!moreEvents) {
hasEventCardsInHand = false;
gamePhase = 'noon';
gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber);
var remaining = playerHand.slice();
if (remaining.length > 0) discardCards(remaining);
} else {
// If other events remain, show normal debug box
showEventCardDebugBox();
}
};
}
}
} else {
// Normal Event Mode
var eventDebugBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 1.0
});
eventDebugBox.tint = 0xA020F0;
eventDebugBox.alpha = 0.95;
eventDebugBox.x = 1024;
eventDebugBox.y = 1000;
game.addChild(eventDebugBox);
var debugText = new Text2("Event card/s in hand! Click the cards to resolve them.", {
size: 34,
fill: 0xFFFFFF
});
debugText.anchor.set(0.5, 0.5);
debugText.x = 0;
debugText.y = 0;
eventDebugBox.addChild(debugText);
game.eventDebugBox = eventDebugBox;
}
}
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;
}
// Delicate creatures cannot be placed on Advanced terrain
if (creature.delicate && terrain.level === 'Advanced') {
return false;
}
// Check terrain type matching based on creature's subtype
if (creature.subtype === 'water') {
// Creature requires water terrain
if (terrain.subtype !== 'water') {
return false;
}
// Check specific water type matching - creature waterType must match terrain waterType
if (creature.waterType && terrain.waterType && creature.waterType !== terrain.waterType) {
return false;
}
} else if (creature.subtype === 'land') {
// Creature requires land terrain
if (terrain.subtype !== 'land') {
return false;
}
// Check specific land type matching - allow 'any' as wildcard
if (creature.landType && creature.landType !== 'any' && terrain.landType && creature.landType !== terrain.landType) {
return false;
}
}
// If creature has no subtype or terrain type, it cannot be placed
if (!creature.subtype) {
return false;
}
// Check climate requirements - use basic terrain climate if advanced terrain is placed on basic
var terrainClimate = terrain.climate;
// If this is an advanced terrain on basic terrain, use the basic terrain's climate
if (terrainCard.basicTerrainUnderneath && terrainCard.basicTerrainUnderneath.terrainData) {
terrainClimate = terrainCard.basicTerrainUnderneath.terrainData.climate;
}
if (creature.climateRequirement && creature.climateRequirement !== 'any') {
var climateMatches = false;
if (creature.climateRequirement.indexOf('/') !== -1) {
// Handle multiple climate requirements (e.g., "temperate/hot")
var allowedClimates = creature.climateRequirement.split('/');
for (var k = 0; k < allowedClimates.length; k++) {
if (allowedClimates[k] === terrainClimate) {
climateMatches = true;
break;
}
}
} else {
// Single climate requirement
climateMatches = creature.climateRequirement === terrainClimate;
}
if (!climateMatches) {
return false;
}
}
// Advanced creatures can only be placed on advanced terrain
if (creature.level === 'Advanced') {
if (terrain.level !== 'Advanced') {
return false;
}
}
// Basic herbivores can only be placed on basic terrain (but advanced terrain counts as valid if it's on basic terrain)
if (creature.level === 'Basic' && creature.dietType === 'herbivore') {
// Allow placement on advanced terrain that's placed on basic terrain, or directly on basic terrain
if (terrain.level !== 'Basic' && !terrainCard.basicTerrainUnderneath) {
return false;
}
}
// Dominant DNA bypasses ALL creature stacking restrictions (but still obeys terrain/climate checked above)
if (creature.dominantDNA && terrainCard.creatureStack.length > 0) {
return true;
}
// Shy creatures CANNOT be placed on occupied terrain
if (creature.shy) {
return false; //{lD_shy}
} //{lE_shy}
// Check creature stacking rules - verify top card in stack
if (terrainCard.creatureStack && terrainCard.creatureStack.length > 0) {
var topCreatureOnTerrain = terrainCard.creatureStack[terrainCard.creatureStack.length - 1];
// Prevent placing anything on advanced carnivores
if (topCreatureOnTerrain.creatureData.dietType === 'carnivore' && topCreatureOnTerrain.creatureData.level === 'Advanced') {
return false; // Cannot place anything on advanced carnivores
}
// Prevent placing Basic Herbivores on Advanced Carnivores (strict enforcement)
if (creature.level === 'Basic' && creature.dietType === 'herbivore' && topCreatureOnTerrain.creatureData.dietType === 'carnivore' && topCreatureOnTerrain.creatureData.level === 'Advanced') {
return false; // Cannot place Basic Herbivore on Advanced Carnivore
}
// Prevent placing Basic Herbivores on any other carnivore
if (creature.level === 'Basic' && creature.dietType === 'herbivore' && topCreatureOnTerrain.creatureData.dietType === 'carnivore' && topCreatureOnTerrain.creatureData.level === 'Basic') {
return false; // Cannot place Basic Herbivore on Basic Carnivore
} //{kH_basic}
}
// Basic carnivores can be placed on any terrain type (basic or advanced) - no restriction needed
// Check creature stacking rules
if (terrainCard.creatureStack.length > 0) {
var topCreatureInStack = terrainCard.creatureStack[terrainCard.creatureStack.length - 1];
// Basic creature stacking rules
if (creature.level === 'Basic') {
// Basic herbivore restrictions
if (creature.dietType === 'herbivore') {
if (topCreatureInStack.creatureData.level === 'Basic' && topCreatureInStack.creatureData.dietType === 'herbivore') {
return false; // Basic herbivores cannot be placed on stacks with other basic herbivores at top
}
if (topCreatureInStack.creatureData.level === 'Advanced' && topCreatureInStack.creatureData.dietType === 'herbivore') {
return false; // Basic herbivores cannot be placed on stacks with advanced herbivores at top
}
if (topCreatureInStack.creatureData.level === 'Basic' && topCreatureInStack.creatureData.dietType === 'carnivore') {
return false; // Basic herbivores cannot be placed on stacks with basic carnivores at top
}
if (topCreatureInStack.creatureData.level === 'Advanced' && topCreatureInStack.creatureData.dietType === 'carnivore') {
return false; // Basic herbivores cannot be placed on stacks with advanced carnivores at top
} //{l8_advanced}
}
// Basic carnivore restrictions
if (creature.dietType === 'carnivore') {
if (topCreatureInStack.creatureData.level === 'Basic' && topCreatureInStack.creatureData.dietType === 'carnivore') {
return false; // Basic carnivores cannot be placed on stacks with other basic carnivores at top
}
if (topCreatureInStack.creatureData.level === 'Advanced' && topCreatureInStack.creatureData.dietType === 'carnivore') {
return false; // Basic carnivores cannot be placed on stacks with advanced carnivores at top
}
if (topCreatureInStack.creatureData.level === 'Advanced' && topCreatureInStack.creatureData.dietType === 'herbivore') {
return false; // Basic carnivores cannot be placed on stacks with advanced herbivores at top
}
}
}
// Advanced creature stacking rules
else if (creature.level === 'Advanced') {
// 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
if (creature.shy) {
// Shy MUST be placed on empty Advanced terrain (terrain.level is already checked above)
return true; //{lF_shy}
} else if (creature.level === 'Advanced') {
return false; // Normal 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);
}
}
// Trigger Displacer
if (advancedTerrainCard.terrainData.displacer && advancedTerrainCard.creatureStack.length > 0) {
var topC = advancedTerrainCard.creatureStack.pop();
if (topC.creatureData.dietType === 'carnivore') {
for (var j = topC.activeLinks.length - 1; j >= 0; j--) animateLinkFallOff(topC, topC.activeLinks[j].target);
} else {
for (var j = linkLines.length - 1; j >= 0; j--) if (linkLines[j].herbivore === topC) animateLinkFallOff(linkLines[j].carnivore, topC);
}
var cData = topC.creatureData || topC.terrainData || topC.eventData;
discardPile.push(cData);
discardCountText.setText("Discard: " + discardPile.length);
if (topC.parent) topC.parent.removeChild(topC);
}
// Trigger Simplify
if (advancedTerrainCard.terrainData.simplify) {
processSimplifySpecial(gridX, gridY);
}
// Update hand positions
updateHandPositions();
// Clear selection
selectedCard = null;
draggedCard = null;
// Reset draw phase after successful placement
if (advancedTerrainCard.mustPlay) {
drawPhase = 'complete';
}
// Check if AT15 was played and set flag for next turn
if (advancedTerrainCard.terrainData && advancedTerrainCard.terrainData.id === 'AT15') {
at15PlayedLastTurn = true;
}
// Check if AT18 was played and trigger special
if (advancedTerrainCard.terrainData && advancedTerrainCard.terrainData.id === 'AT18') {
processAT18Special();
} //{if_AT18}
// Check if this was the last event card or must play card
if (eventCardsToPlay.length > 0) {
//{ig}</antml>
// Remove this card from event cards to play
var eventIndex = eventCardsToPlay.indexOf(advancedTerrainCard);
if (eventIndex !== -1) {
eventCardsToPlay.splice(eventIndex, 1);
}
// If no more event cards to play, clear mustPlayCard and check for phase progression
if (eventCardsToPlay.length === 0) {
mustPlayCard = null;
// Update event cards tracking
hasEventCardsInHand = false;
// Discard remaining non-event cards and progress to dusk
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
if (!playerHand[i].eventData) {
remainingCards.push(playerHand[i]);
}
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
}
} else if (advancedTerrainCard.mustPlay || mustPlayCard === advancedTerrainCard) {
// This was a must play card, clear it and progress to dusk
mustPlayCard = null;
// Discard remaining cards
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
remainingCards.push(playerHand[i]);
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
} else {
// Regular card played, discard remaining cards and progress to dusk
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
remainingCards.push(playerHand[i]);
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
}
// Reset turn
cardsDrawnThisTurn = 0;
LK.getSound('cardPlace').play();
// Show adjust links button after card placement
showAdjustLinksButton();
return true;
}
function checkAndRemoveInvalidLinks(gridX, gridY, newTopCreature) {
// Check all links that involve creatures at this position
var invalidLinks = [];
// Check links FROM carnivores at this position
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
if (creature.creatureData.dietType === 'carnivore') {
// Check each active link from this carnivore
for (var j = creature.activeLinks.length - 1; j >= 0; j--) {
var link = creature.activeLinks[j];
var targetHerbivore = link.target;
// If the new top creature is a carnivore and the target is now covered, link becomes invalid
if (newTopCreature && newTopCreature.creatureData.dietType === 'carnivore' && targetHerbivore.gridX === gridX && targetHerbivore.gridY === gridY) {
invalidLinks.push({
carnivore: creature,
herbivore: targetHerbivore
});
}
}
}
}
}
// Check links TO herbivores at this position (if new creature is carnivore, herbivores below become invalid targets)
if (newTopCreature && newTopCreature.creatureData.dietType === 'carnivore') {
// Find all links pointing to herbivores at this position that are now covered
for (var linkIndex = linkLines.length - 1; linkIndex >= 0; linkIndex--) {
var linkLine = linkLines[linkIndex];
if (linkLine.herbivore.gridX === gridX && linkLine.herbivore.gridY === gridY) {
// This herbivore is now covered by a carnivore, so links to it are invalid
invalidLinks.push({
carnivore: linkLine.carnivore,
herbivore: linkLine.herbivore
});
}
}
}
// Remove invalid links with fall-off animation
for (var k = 0; k < invalidLinks.length; k++) {
var invalidLink = invalidLinks[k];
animateLinkFallOff(invalidLink.carnivore, invalidLink.herbivore);
}
}
function animateLinkFallOff(carnivore, herbivore) {
// Find the link line to animate
var linkLineToRemove = null;
for (var i = 0; i < linkLines.length; i++) {
if (linkLines[i].carnivore === carnivore && linkLines[i].herbivore === herbivore && !linkLines[i].isFallingOff) {
linkLineToRemove = linkLines[i];
break;
}
}
if (linkLineToRemove) {
// Mark link as falling off immediately to prevent duplicate animations
linkLineToRemove.isFallingOff = true;
// Remove from arrays immediately to clear logical ghost link
removeLink(carnivore, herbivore);
// Animate the link falling off
tween(linkLineToRemove, {
alpha: 0,
scaleY: linkLineToRemove.scaleY * 0.1,
y: linkLineToRemove.y + 50
}, {
duration: 500,
onFinish: function onFinish() {
// Remove the visual line after animation
if (linkLineToRemove.parent) linkLineToRemove.parent.removeChild(linkLineToRemove);
}
});
} else {
// No visual line found, just remove the link immediately
removeLink(carnivore, herbivore);
}
}
function placeCreatureOnStack(creatureCard, terrainCard, gridX, gridY) {
if (!canPlaceCreatureOnTerrain(creatureCard, terrainCard)) {
return false;
}
// Remove card from hand
var handIndex = playerHand.indexOf(creatureCard);
if (handIndex !== -1) {
playerHand.splice(handIndex, 1);
}
// Check for invalid links before placing the new creature
checkAndRemoveInvalidLinks(gridX, gridY, creatureCard);
// Remove shell from covered creature
if (terrainCard.creatureStack.length > 0) {
var coveredC = terrainCard.creatureStack[terrainCard.creatureStack.length - 1];
if (coveredC.shellMarker) {
coveredC.removeChild(coveredC.shellMarker);
coveredC.shellMarker = null;
if (game.activeShellCreature === coveredC) game.activeShellCreature = null;
}
}
// 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
if (creature.forerunner) {
processForerunnerSpecial();
}
if (creature && creature.watcher) {
processWatcherSpecial();
}
if (creature && creature.terraformer) {
processTerraformerSpecial(creatureCard.gridX, creatureCard.gridY);
}
if (creature && creature.shell) {
var shellMarker = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.2,
scaleY: 0.2
});
shellMarker.tint = 0x8B4513;
shellMarker.x = 0;
shellMarker.y = -100;
creatureCard.addChild(shellMarker);
creatureCard.shellMarker = shellMarker;
game.activeShellCreature = creatureCard;
}
}
function getAdjacentTerrains(gridX, gridY, includeDiagonals) {
var neighbors = [];
var directions = [{
x: -1,
y: 0
}, {
x: 1,
y: 0
}, {
x: 0,
y: -1
}, {
x: 0,
y: 1
}];
// Add diagonals if the creature is Efficient
if (includeDiagonals) {
directions.push({
x: -1,
y: -1
}, {
x: 1,
y: -1
}, {
x: -1,
y: 1
}, {
x: 1,
y: 1
});
}
for (var i = 0; i < directions.length; i++) {
var newX = gridX + directions[i].x;
var newY = gridY + directions[i].y;
if (newX >= 0 && newX < planetWidth && newY >= 0 && newY < planetHeight) {
neighbors.push(planetBoard[newY][newX]);
}
}
return neighbors;
}
function createLink(carnivore, herbivore) {
if (!carnivore || !herbivore) {
return false;
}
if (carnivore.creatureData.dietType !== 'carnivore') {
return false;
}
// Check if carnivore and herbivore are adjacent
if (!areCreaturesAdjacent(carnivore, herbivore)) {
return false;
}
// Additional check: ensure we're linking to the top creature only
var herbivoreGridX = herbivore.gridX;
var herbivoreGridY = herbivore.gridY;
var terrain = planetBoard[herbivoreGridY][herbivoreGridX];
if (terrain && terrain.creatureStack.length > 0) {
var topCreature = terrain.creatureStack[terrain.creatureStack.length - 1];
if (topCreature !== herbivore) {
return false; // Can only link to top creature in stack
}
}
// For basic carnivores, check if already linked to this herbivore
if (carnivore.creatureData.level === 'Basic') {
for (var i = 0; i < carnivore.activeLinks.length; i++) {
if (carnivore.activeLinks[i].target === herbivore) {
return false; // Basic carnivores can only have one link per herbivore
}
}
}
// Check if this is a valid link target considering all special rules
if (!isValidLinkTarget(carnivore, herbivore)) {
return false; // Cannot link due to special rules (Whale food, etc)
} //{mL_new}
// Find an unlinked marker
var availableMarker = null;
for (var i = 0; i < carnivore.linkMarkers.length; i++) {
if (!carnivore.linkMarkers[i].isLinked) {
availableMarker = carnivore.linkMarkers[i];
break;
}
}
if (!availableMarker) {
return false;
} // No available link markers
// Create the link
availableMarker.isLinked = true;
availableMarker.targetHerbivore = herbivore;
availableMarker.tint = 0x00FF00; // Green for linked
// Add to active links
carnivore.activeLinks.push({
marker: availableMarker,
target: herbivore
});
// Create visual link line
createLinkLine(carnivore, herbivore);
return true;
}
function removeLink(carnivore, herbivore) {
if (!carnivore || !herbivore) {
return false;
}
// Find and remove the link
for (var i = carnivore.activeLinks.length - 1; i >= 0; i--) {
var link = carnivore.activeLinks[i];
if (link.target === herbivore) {
link.marker.isLinked = false;
link.marker.targetHerbivore = null;
link.marker.tint = 0xFF0000; // Red for unlinked
carnivore.activeLinks.splice(i, 1);
// Remove visual link line
removeLinkLine(carnivore, herbivore);
return true;
}
}
return false;
}
function isValidLinkTarget(carnivore, herbivore) {
if (!carnivore || !herbivore) {
return false;
}
if (carnivore.creatureData.slow && (herbivore.creatureData.flying || herbivore.creatureData.fly)) {
return false; // Slow creatures cannot link to flying creatures
}
if (carnivore.creatureData.seaBound) {
if (herbivore.creatureData.subtype !== 'water') {
return false; // Sea-bound creatures can only link to water creatures
}
}
var targetIsCarnivore = herbivore.creatureData.dietType === 'carnivore';
if (targetIsCarnivore) {
// Target is a carnivore
if (carnivore.creatureData.hunterHunter || carnivore.creatureData.bully) {
// Hunter-Hunter or Bully can link to carnivores
if (carnivore.creatureData.bully && herbivore.creatureData.level !== 'Basic') {
return false; // Bully can only link to Basic Carnivores
}
return true;
}
return false; // Cannot link to carnivore unless Hunter-Hunter or Bully
} else {
// Target is a herbivore
if (carnivore.creatureData.hunterHunter) {
return false; // Hunter-Hunter cannot link to herbivores
} //{n4_hh}
} //{n4_hh_end}
if (carnivore.creatureData.fussy) {
if (herbivore.creatureData.level !== 'Basic') {
return false; // Fussy can only link to Basic Herbivores
} //{n4_fussy}
} //{n4_fussy_end}
if (herbivore.creatureData.whaleFood) {
if (carnivore.creatureData.id !== 'AC03') {
return false; // Cannot link to Whale food unless carnivore is Whale
}
}
return true;
}
function areCreaturesAdjacent(creature1, creature2) {
if (!creature1.isInPlay || !creature2.isInPlay) {
return false;
}
var dx = Math.abs(creature1.gridX - creature2.gridX);
var dy = Math.abs(creature1.gridY - creature2.gridY);
// Check if creature1 has Efficient attribute
var creature1HasEfficient = creature1.creatureData && creature1.creatureData.efficient;
// Adjacent means exactly one grid space away (orthogonal or diagonal if Efficient)
if (creature1HasEfficient) {
// Efficient creatures can link to adjacent (including diagonal) creatures
return dx === 1 && dy === 0 || dx === 0 && dy === 1 || dx === 1 && dy === 1;
} else {
// Non-Efficient creatures can only link orthogonally (up, down, left, right)
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
}
}
function createLinkLine(carnivore, herbivore) {
// Count how many links ALREADY exist between this carnivore and herbivore BEFORE adding the new one
var existingLinkCount = 0;
for (var i = 0; i < linkLines.length; i++) {
if (linkLines[i].carnivore === carnivore && linkLines[i].herbivore === herbivore) {
existingLinkCount++;
}
} //{e4_duplicate}
// Create visual arrow line using a thicker rectangle
var line = LK.getAsset('terrainLandFlat', {
anchorX: 0,
anchorY: 0.5
});
line.tint = 0xFFFF00; // Yellow link line by default
line.alpha = 0.8;
// Position arrow starting from slightly inside carnivore border towards herbivore
var dx = herbivore.x - carnivore.x;
var dy = herbivore.y - carnivore.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
// Calculate starting position slightly inside carnivore border (about 20px inward)
var startOffsetX = Math.cos(angle) * 20;
var startOffsetY = Math.sin(angle) * 20;
var startX = carnivore.x + startOffsetX;
var startY = carnivore.y + startOffsetY;
// Calculate gap distance (reduce total distance by both card radii plus small gap)
var gapDistance = distance - 100; // 50px from each card edge creates gap
line.x = startX;
line.y = startY;
line.rotation = angle;
line.scaleX = gapDistance / 252; // Scale based on gap distance
line.scaleY = 0.08; // Thicker line (8% of original height)
line.carnivore = carnivore;
line.herbivore = herbivore;
line.linkIndex = existingLinkCount; // Track which link number this is
// Calculate perpendicular offset for multiple links to avoid stacking
if (existingLinkCount >= 1) {
// 2nd link goes one way (+90 deg), 3rd link goes the opposite way (-90 deg)
var perpendicularAngle = existingLinkCount === 1 ? angle + Math.PI / 2 : angle - Math.PI / 2;
var offsetDistance = 30; // Offset distance in pixels
var offsetX = Math.cos(perpendicularAngle) * offsetDistance;
var offsetY = Math.sin(perpendicularAngle) * offsetDistance;
// Apply offset to line position
line.x += offsetX;
line.y += offsetY;
// Change line color and create indicator based on link number
var indicatorString = "";
if (existingLinkCount === 1) {
line.tint = 0xFF00FF; // Magenta for 2nd link
indicatorString = "X2";
} else {
line.tint = 0x00BFFF; // Deep Sky Blue for 3rd link
indicatorString = "X3";
}
// Add the text indicator
var linkIndicator = new Text2(indicatorString, {
size: 20,
fill: 0xFFFFFF
});
linkIndicator.anchor.set(0.5, 0.5);
linkIndicator.x = 0;
linkIndicator.y = 0;
line.addChild(linkIndicator);
linkIndicator.lineOwner = line;
}
game.addChild(line);
linkLines.push(line);
}
function removeLinkLine(carnivore, herbivore) {
for (var i = linkLines.length - 1; i >= 0; i--) {
var line = linkLines[i];
if (line.carnivore === carnivore && line.herbivore === herbivore) {
if (!line.isFallingOff) {
if (line.parent) line.parent.removeChild(line);
}
linkLines.splice(i, 1);
return; // Remove only ONE link and return
}
}
}
function highlightValidLinkTargets(carnivore) {
// Clear existing highlights
clearLinkHighlights();
if (!carnivore || carnivore.creatureData.dietType !== 'carnivore') {
return;
}
// Check if carnivore has Efficient attribute
var carnivoreHasEfficient = carnivore.creatureData && carnivore.creatureData.efficient;
// Find adjacent herbivores that can be linked to (including diagonals for Efficient)
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
// Only consider the top creature in each stack
var topCreature = terrain.creatureStack[terrain.creatureStack.length - 1];
if (areCreaturesAdjacent(carnivore, topCreature)) {
// Check if this is a valid link target considering all special rules
if (!isValidLinkTarget(carnivore, topCreature)) {
// Cannot link to this herbivore due to special rules
continue;
} //{nx_new}
var canCreateLink = false;
// For basic carnivores, check if already linked to this herbivore
if (carnivore.creatureData.level === 'Basic') {
var hasExistingLink = false;
for (var j = 0; j < carnivore.activeLinks.length; j++) {
if (carnivore.activeLinks[j].target === topCreature) {
hasExistingLink = true;
break;
}
}
if (!hasExistingLink) {
canCreateLink = true;
}
} else {
// Advanced carnivores can always create links if they have available markers
var hasAvailableMarker = false;
for (var k = 0; k < carnivore.linkMarkers.length; k++) {
if (!carnivore.linkMarkers[k].isLinked) {
hasAvailableMarker = true;
break;
}
}
if (hasAvailableMarker) {
canCreateLink = true;
}
}
if (canCreateLink) {
// Create highlight around this herbivore
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
// Use different colors for diagonal highlights if Efficient
if (carnivoreHasEfficient) {
var dx = Math.abs(carnivore.gridX - topCreature.gridX);
var dy = Math.abs(carnivore.gridY - topCreature.gridY);
if (dx === 1 && dy === 1) {
// Diagonal link for Efficient creature
highlight.tint = 0x00FFFF; // Cyan for diagonal
} else {
// Orthogonal link
highlight.tint = 0x00FF00; // Green for orthogonal
}
} else {
highlight.tint = 0x00FF00; // Green highlight
}
highlight.alpha = 0.3;
highlight.scaleX = topCreature.scaleX * 1.2;
highlight.scaleY = topCreature.scaleY * 1.2;
highlight.x = topCreature.x;
highlight.y = topCreature.y;
highlight.targetCreature = topCreature;
game.addChild(highlight);
linkHighlights.push(highlight);
}
}
}
}
}
}
function clearLinkHighlights() {
for (var i = 0; i < linkHighlights.length; i++) {
if (linkHighlights[i].parent) {
linkHighlights[i].parent.removeChild(linkHighlights[i]);
}
}
linkHighlights = [];
}
function updateLinkMarkerStates() {
// Update all link markers based on their state and game phase
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
// ONLY process the top creature in the stack - creatures underneath are not active
var isTopCreature = i === terrain.creatureStack.length - 1;
if (!isTopCreature) {
// Skip creatures that are underneath other creatures - they cannot interact with links
continue;
} //{f9_underneath}
if (creature.creatureData.dietType === 'carnivore') {
// Check if this carnivore will become extinct (insufficient links)
var willBecomeExtinct = creature.activeLinks.length < creature.linkRequirement;
for (var j = 0; j < creature.linkMarkers.length; j++) {
var marker = creature.linkMarkers[j];
if (gamePhase === 'dusk' && linkAdjustmentMode) {
// During dusk phase with adjustment mode active, make markers interactive
if (marker.isLinked) {
marker.tint = 0x00FF00; // Green for linked
// Make linked markers draggable to remove links
marker.down = function (x, y, obj) {
draggedLinkMarker = this;
draggedLinkCarnivore = this.carnivore;
};
} else {
// Check if this marker can be legally linked
var hasValidTargets = false;
var isEfficient = creature.creatureData && creature.creatureData.efficient;
var adjacentTerrains = getAdjacentTerrains(gridX, gridY, isEfficient);
for (var k = 0; k < adjacentTerrains.length; k++) {
var adjTerrain = adjacentTerrains[k];
if (adjTerrain && adjTerrain.creatureStack.length > 0) {
// Only consider the top creature in each adjacent stack
var topHerbivore = adjTerrain.creatureStack[adjTerrain.creatureStack.length - 1];
if (isValidLinkTarget(creature, topHerbivore)) {
// For basic carnivores, check if already linked to this herbivore
if (creature.creatureData.level === 'Basic') {
var hasExistingLink = false;
for (var m = 0; m < creature.activeLinks.length; m++) {
if (creature.activeLinks[m].target === topHerbivore) {
hasExistingLink = true;
break;
}
}
if (!hasExistingLink) {
hasValidTargets = true;
break;
}
} else {
// Advanced carnivores can always link if they have available markers
hasValidTargets = true;
break;
}
}
}
if (hasValidTargets) {
break;
}
}
if (hasValidTargets) {
marker.tint = 0xFFFF00; // Yellow for linkable
// Make unlinked but valid markers draggable
marker.down = function (x, y, obj) {
draggedLinkMarker = this;
draggedLinkCarnivore = this.carnivore;
};
} else {
marker.tint = 0x808080; // Gray for non-linkable
marker.down = function (x, y, obj) {
// Do nothing for non-linkable markers
};
// Add N/A text
if (!marker.naText) {
marker.naText = new Text2("N/A", {
size: 8,
fill: 0xFFFFFF
});
marker.naText.anchor.set(0.5, 0.5);
marker.naText.x = 0;
marker.naText.y = 0;
marker.addChild(marker.naText);
}
}
}
} else {
// Outside dusk phase or not in adjustment mode, markers are not interactive
if (marker.isLinked) {
marker.tint = 0x00FF00; // Green for linked
} else {
marker.tint = 0xFF0000; // Red for unlinked
}
// Remove down handler and N/A text if present
marker.down = function (x, y, obj) {};
if (marker.naText && marker.naText.parent) {
marker.naText.parent.removeChild(marker.naText);
marker.naText = null;
}
}
// Flash markers if carnivore will become extinct
if (willBecomeExtinct) {
// Stop any existing flash animation
tween.stop(marker, {
alpha: true
});
// Start flashing animation
tween(marker, {
alpha: 0.3
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(marker, {
alpha: 1
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart the flash cycle if still extinct
if (creature.activeLinks.length < creature.linkRequirement) {
updateLinkMarkerStates();
}
}
});
}
});
} else {
// Stop flashing if no longer about to become extinct
tween.stop(marker, {
alpha: true
});
marker.alpha = 1;
}
}
}
}
}
}
}
}
function autoCreateLinks() {
// Auto-create mandatory links for carnivores
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var carnivore = terrain.creatureStack[i];
if (carnivore.creatureData.dietType === 'carnivore') {
// Try to create links for this carnivore
while (carnivore.activeLinks.length < carnivore.linkRequirement) {
var linkCreated = false;
// Find adjacent herbivores
var isEfficient = carnivore.creatureData && carnivore.creatureData.efficient;
var adjacentTerrains = getAdjacentTerrains(gridX, gridY, isEfficient);
for (var j = 0; j < adjacentTerrains.length && !linkCreated; j++) {
var adjTerrain = adjacentTerrains[j];
if (adjTerrain && adjTerrain.creatureStack.length > 0) {
// Only consider the top creature in each adjacent stack
var topHerbivore = adjTerrain.creatureStack[adjTerrain.creatureStack.length - 1];
if (topHerbivore.creatureData.dietType === 'herbivore') {
// Check if we can create a link
var hasExistingLink = false;
for (var l = 0; l < carnivore.activeLinks.length; l++) {
if (carnivore.activeLinks[l].target === topHerbivore) {
hasExistingLink = true;
break;
}
}
if (!hasExistingLink && createLink(carnivore, topHerbivore)) {
linkCreated = true;
}
}
}
}
if (!linkCreated) {
break;
} // No more links possible
}
}
}
}
}
}
}
function processLinkConsequences() {
// Hide the adjust links button when leaving dusk phase
hideAdjustLinksButton();
// NOTE: Extinction markers for herbivores are only processed during night phase
// This function only handles cleanup, not marker assignment
}
function processNightPhase() {
// Show visual cue that night phase is processing
showPhaseTransition("Night Phase - Checking links...", function () {
// Add extinction markers to carnivores without sufficient links
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
// ONLY process the top creature in the stack - creatures underneath are not active
var isTopCreature = i === terrain.creatureStack.length - 1;
if (!isTopCreature) {
// Skip creatures that are underneath other creatures
continue;
} //{ho_underneath}
if (creature.creatureData.dietType === 'carnivore') {
// For carnivores: check if they have sufficient links AT THIS MOMENT
if (creature.activeLinks.length < creature.linkRequirement) {
creature.extinctionMarkers += 1; // Only 1 marker per turn regardless of shortage
creature.updateExtinctionMarkers();
}
// Check if this carnivore is poisoned (feeding on Poisonous creature)
for (var p = 0; p < creature.activeLinks.length; p++) {
if (creature.activeLinks[p].target.creatureData.poisonous) {
creature.extinctionMarkers += 1;
creature.updateExtinctionMarkers();
}
}
} else if (creature.creatureData.dietType === 'herbivore') {
// Check if Delicate creature is on Advanced terrain
if (creature.creatureData.delicate && terrain.terrainData.level === 'Advanced') {
creature.extinctionMarkers += 1;
creature.updateExtinctionMarkers();
}
// 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++;
} //{tg_new}
}
// Add extinction markers equal to the number of excess links EVERY Night Phase
var excessLinks = linksToThisHerbivore - creature.safeLinks;
if (excessLinks > 0) {
// No cap, no armor! Add exactly as many markers as there are excess links
creature.extinctionMarkers += excessLinks;
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);
// Tween all creatures that were physically positioned below the dead creature UP by 20 pixels
for (var j = 0; j < i; j++) {
var creatureBelow = terrain.creatureStack[j];
tween(creatureBelow, {
y: creatureBelow.y - 20
}, {
duration: 200
});
}
creaturesWentExtinct = true; // Mark that creatures went extinct this round
scryDeniedNextRound = true; // Deny scry for next round when extinction occurs
// Clear extinction markers when creature leaves play
creature.extinctionMarkers = 0;
creature.updateExtinctionMarkers();
// Remove all links involving this creature with fall-off animation
if (creature.creatureData.dietType === 'carnivore') {
for (var j = creature.activeLinks.length - 1; j >= 0; j--) {
animateLinkFallOff(creature, creature.activeLinks[j].target);
}
} else {
// Remove links from carnivores to this herbivore with fall-off animation
for (var j = linkLines.length - 1; j >= 0; j--) {
if (linkLines[j].herbivore === creature) {
animateLinkFallOff(linkLines[j].carnivore, creature);
}
}
}
creature.die();
}
}
}
}
}
}
function createEventDecks() {
basicEventDeck = [];
basicEventDeck.push({
type: 'basic',
cardType: 'event',
level: 'Basic',
id: 'BE01',
name: "Time fly's"
});
basicEventDeck.push({
type: 'basic',
cardType: 'event',
level: 'Basic',
id: 'BE02',
name: 'Devolution'
});
basicEventDeck.push({
type: 'basic',
cardType: 'event',
level: 'Basic',
id: 'BE03',
name: 'Disease'
});
basicEventDeck.push({
type: 'basic',
cardType: 'event',
level: 'Basic',
id: 'BE04',
name: 'Volcano'
});
for (var i = 5; i <= 10; i++) {
basicEventDeck.push({
type: 'basic',
cardType: 'event',
level: 'Basic',
id: 'BE0' + i,
name: 'Basic Event ' + i
});
}
advancedEventDeck = [];
for (var i = 1; i <= 10; i++) {
advancedEventDeck.push({
type: 'advanced',
cardType: 'event',
level: 'Advanced',
id: 'AE0' + i,
name: 'Advanced Event ' + i,
effect: 'catastrophic'
});
}
}
function addEventCardToDeck(creatureLevel) {
var eventCard = null;
if (creatureLevel === 'Advanced') {
if (advancedEventDeck.length === 0 && advancedEventDiscard.length > 0) {
advancedEventDeck = advancedEventDiscard.slice();
advancedEventDiscard = [];
shuffleEventDeck(advancedEventDeck);
}
if (advancedEventDeck.length > 0) eventCard = advancedEventDeck.pop();else if (basicEventDeck.length > 0) eventCard = basicEventDeck.pop();
} else {
if (basicEventDeck.length === 0 && basicEventDiscard.length > 0) {
basicEventDeck = basicEventDiscard.slice();
basicEventDiscard = [];
shuffleEventDeck(basicEventDeck);
}
if (basicEventDeck.length > 0) eventCard = basicEventDeck.pop();else if (advancedEventDeck.length > 0) eventCard = advancedEventDeck.pop();
}
if (eventCard) discardPile.push(eventCard);
}
function processDawnPhase() {
// Show visual cue that dawn phase is processing
showPhaseTransition("Dawn Phase - Processing...", function () {
gamePhase = 'draw';
processDrawPhase(); // Auto-progress to draw phase
});
}
function processDrawPhase() {
// Draw exactly 3 cards (or 4 if AT15 was played last turn), handling deck reshuffling as needed
var cardsToDrawTotal = 3;
if (at15PlayedLastTurn) {
cardsToDrawTotal = 4;
at15PlayedLastTurn = false; // Reset flag after using it
}
// Step 1: Create validHandFound boolean
var validHandFound = false;
// Step 2: Create master while loop that runs as long as validHandFound is false
while (!validHandFound) {
// Step 3: Draw cards until the player's hand reaches the required amount
var continueDrawing = true;
var cardsDrawnThisPhase = 0;
while (continueDrawing && playerHand.length < cardsToDrawTotal) {
// Check if deck is empty, reshuffle if needed
if (mainDeck.length === 0) {
// Shuffle discard pile to become new deck
if (discardPile.length > 0) {
mainDeck = discardPile.slice();
discardPile = [];
shuffleDeck();
currentRound++;
deckCountText.setText("Deck: " + mainDeck.length);
// Check for end game condition after round increment
if (currentRound >= 6) {
// Game ends immediately
LK.showYouWin();
return;
}
// Check and trigger scry status at beginning of new round
// Only activate scry if no extinctions occurred last round
if (!scryDeniedNextRound) {
scryTokenActive = true;
} //{ht_scry}
creaturesWentExtinct = false;
scryDeniedNextRound = false;
} else {
// No cards available, stop drawing
continueDrawing = false;
break; // Exit drawing loop if both deck and discard are empty
}
}
// Draw one card if deck has cards
if (mainDeck.length > 0) {
var cardData = mainDeck.pop();
var card = createCardFromData(cardData);
playerHand.push(card);
game.addChild(card);
// Update hand positions after each card is added
updateHandPositions();
cardsDrawnThisPhase++;
} else {
// No more cards in deck, stop drawing
continueDrawing = false;
}
}
// Step 4: After the hand is filled, update the hand positions. If the hand is completely empty, break the master loop.
updateHandPositions();
if (playerHand.length === 0) {
// Hand is empty because both deck and discard are empty
break; // Exit master while loop
} //{uv_new}
// Check for Stinky effect after drawing cards
checkAndProcessStinkyEffect();
// Update hand positions again in case Stinky effect discarded adjacent cards
updateHandPositions();
// Step 5: Check if the current hand has valid placements
if (hasValidPlacements(playerHand)) {
// Hand has at least one valid placement, set validHandFound to true to exit master loop
validHandFound = true; //{uC_new}
} else {
//{uD_new}
// Hand is unplayable, discard all cards and loop will repeat to draw a fresh hand
discardCards(playerHand.slice()); //{uB_new}
} //{uE_new}
} //{uF_new}
// Step 6: After the master loop finishes, update the deck and discard UI text, play the draw sound, and change the game phase to 'noon'
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
LK.getSound('cardDraw').play();
gamePhase = 'noon';
}
function createCardFromData(cardData) {
var card;
if (cardData.cardType === 'terrain') {
card = new TerrainCard(cardData);
card.scaleX = 0.6;
card.scaleY = 0.6;
} else if (cardData.cardType === 'event') {
// Create event card using EventCard class
card = new EventCard(cardData);
} else {
// Default to creature card
card = new CreatureCard(cardData);
}
return card;
}
function processNoonPhase() {
// Check for event cards in hand - they must be played first
eventCardsToPlay = [];
for (var i = 0; i < playerHand.length; i++) {
if (playerHand[i].eventData) {
eventCardsToPlay.push(playerHand[i]);
}
}
if (eventCardsToPlay.length > 0) {
// Player must play event cards first
mustPlayCard = eventCardsToPlay[0]; // Force first event card
return;
}
// Check if player has valid moves
var validCards = [];
for (var i = 0; i < playerHand.length; i++) {
if (hasValidPlacements([playerHand[i]])) {
validCards.push(playerHand[i]);
}
}
if (validCards.length === 0 && playerHand.length === 3) {
// Show no playable cards notification
showNoPlayableCardsNotification();
return;
}
// Player can choose which card to play (if multiple valid options)
if (validCards.length > 1) {
// Discard non-chosen cards (this would be handled by player interaction)
// For now, just continue - player will choose
}
}
function showPhaseTransition(message, callback) {
// Clear any existing transition timer
if (phaseTransitionTimer) {
LK.clearTimeout(phaseTransitionTimer);
}
// Create transition message
var transitionText = new Text2(message, {
size: 40,
fill: 0xFFFF00
});
transitionText.anchor.set(0.5, 0.5);
transitionText.x = 1024;
transitionText.y = 1366;
transitionText.alpha = 0;
game.addChild(transitionText);
// Fade in message
tween(transitionText, {
alpha: 1
}, {
duration: 500,
onFinish: function onFinish() {
// Show message for 1 second, then fade out and execute callback
phaseTransitionTimer = LK.setTimeout(function () {
tween(transitionText, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
if (transitionText.parent) {
transitionText.parent.removeChild(transitionText);
}
if (callback) {
callback();
}
}
});
}, 1000);
}
});
}
function processDuskPhase() {
// Show visual cue that dusk phase is processing
showPhaseTransition("Dusk Phase - Arrange links...", function () {
// Show the adjust links button at start of dusk phase
showAdjustLinksButton();
// Update link marker states to make them interactive
updateLinkMarkerStates();
// Player now manually arranges links - no auto-progression
// Phase will advance when player clicks next phase button
});
}
function processEndTurnPhase() {
// Show visual cue that end turn phase is processing
showPhaseTransition("End Turn - Processing extinctions...", function () {
// Process extinctions
processExtinctions();
// Extinction markers persist - they are NOT cleared here
turnNumber++;
gamePhase = 'dawn'; // Start next turn
processDawnPhase(); // Auto-progress to next dawn phase
});
}
function endRound() {
currentRound++;
if (currentRound > maxRounds) {
// Game complete
LK.showYouWin();
return;
}
// Reshuffle deck
createInitialDeck();
// Reset turn counter and draw phase
cardsDrawnThisTurn = 0;
drawPhase = 'complete';
cardsDrawnInPhase = [];
// Update UI
deckCountText.setText("Deck: " + mainDeck.length);
}
// Draw card button functionality
var drawButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
var drawButtonText = new Text2("DRAW", {
size: 32,
fill: 0xFFFFFF
});
drawButtonText.anchor.set(0.5, 0.5);
drawButton.addChild(drawButtonText);
drawButton.x = 1700;
drawButton.y = 2400;
game.addChild(drawButton);
drawButton.down = function () {
if (gamePhase === 'draw') {
processDrawPhase();
}
};
// Phase management buttons - redesigned for proper turn flow
var nextPhaseButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 0.8
});
nextPhaseButton.tint = 0x32CD32; // Green for next phase
var nextPhaseButtonText = new Text2("NEXT PHASE", {
size: 24,
fill: 0xFFFFFF
});
nextPhaseButtonText.anchor.set(0.5, 0.5);
nextPhaseButton.addChild(nextPhaseButtonText);
nextPhaseButton.x = 500;
nextPhaseButton.y = 2400;
game.addChild(nextPhaseButton);
var nextPhaseWarningBox = null;
function getCreaturesInDanger() {
var creaturesInDanger = [];
for (var gridY = 0; gridY < planetHeight; gridY++) {
//{kA_danger}
for (var gridX = 0; gridX < planetWidth; gridX++) {
//{kB_danger}
var terrain = planetBoard[gridY][gridX]; //{kC_danger}
if (terrain && terrain.creatureStack.length > 0) {
//{kD_danger}
for (var i = 0; i < terrain.creatureStack.length; i++) {
//{kE_danger}
var creature = terrain.creatureStack[i]; //{kF_danger}
// ONLY check top creatures in stack
var isTopCreature = i === terrain.creatureStack.length - 1; //{kG_danger}
if (!isTopCreature) {
//{kH_danger}
continue; //{kI_danger}
} //{kJ_danger}
// Check for Delicate creatures on Advanced terrain
if (creature.creatureData.delicate && terrain.terrainData.level === 'Advanced') {
if (creaturesInDanger.indexOf(creature) === -1) creaturesInDanger.push(creature);
}
// 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}
// Check if this carnivore is poisoned (feeding on Poisonous creature)
for (var p = 0; p < creature.activeLinks.length; p++) {
if (creature.activeLinks[p].target.creatureData.poisonous) {
if (creaturesInDanger.indexOf(creature) === -1) creaturesInDanger.push(creature);
break;
}
}
} //{l0_danger}
} //{l1_danger}
} //{l2_danger}
} //{l3_danger}
} //{l4_danger}
return creaturesInDanger; //{l5_danger}
} //{l6_danger}
function showNextPhaseWarning() {
if (nextPhaseWarningBox && nextPhaseWarningBox.parent) {
//{l7_danger}
return; // Warning already showing
} //{l8_danger}
// Create warning box
nextPhaseWarningBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
//{l9_danger}
anchorY: 0.5 //{la_danger}
}); //{lb_danger}
nextPhaseWarningBox.tint = 0xFF8C00; // Orange background
nextPhaseWarningBox.alpha = 0.95;
nextPhaseWarningBox.scaleX = 2.0;
nextPhaseWarningBox.scaleY = 1.2;
nextPhaseWarningBox.x = 500;
nextPhaseWarningBox.y = 2100;
game.addChild(nextPhaseWarningBox);
// Create warning text
var warningText = new Text2("Warning! These creatures are in danger!\nAre you sure you want to end the turn?", {
size: 20,
//{lc_danger}
fill: 0xFFFFFF //{ld_danger}
}); //{le_danger}
warningText.anchor.set(0.5, 0.5);
warningText.x = 0;
warningText.y = 0;
nextPhaseWarningBox.addChild(warningText);
} //{lf_danger}
function hideNextPhaseWarning() {
if (nextPhaseWarningBox && nextPhaseWarningBox.parent) {
//{lg_danger}
nextPhaseWarningBox.parent.removeChild(nextPhaseWarningBox); //{lh_danger}
nextPhaseWarningBox = null;
} //{li_danger}
} //{lj_danger}
function highlightCreaturesInDanger(creaturesInDanger) {
for (var i = 0; i < creaturesInDanger.length; i++) {
//{lk_danger}
var creature = creaturesInDanger[i]; //{ll_danger}
tween.stop(creature, {
//{lm_danger}
tint: true //{ln_danger}
}); //{lo_danger}
tween(creature, {
//{lp_danger}
tint: 0xFFFF00 //{lq_danger}
}, {
//{lr_danger}
duration: 200 //{ls_danger}
}); //{lt_danger}
} //{lu_danger}
} //{lv_danger}
function unhighlightCreaturesInDanger(creaturesInDanger) {
for (var i = 0; i < creaturesInDanger.length; i++) {
//{lw_danger}
var creature = creaturesInDanger[i]; //{lx_danger}
tween.stop(creature, {
//{ly_danger}
tint: true //{lz_danger}
}); //{mA_danger}
tween(creature, {
//{mB_danger}
tint: 0xFFFFFF //{mC_danger}
}, {
//{mD_danger}
duration: 200 //{mE_danger}
}); //{mF_danger}
} //{mG_danger}
} //{mH_danger}
nextPhaseButton.down = function () {
// Exit link adjustment mode if active and hide adjust links button
if (linkAdjustmentMode) {
deactivateLinkAdjustmentMode();
}
// Always hide adjust links button when next phase is clicked
hideAdjustLinksButton();
// Hide warning box when button is pressed
hideNextPhaseWarning(); //{mI_danger}
// Disable button completely if events must be played
if (gamePhase === 'noon' && hasEventCardsInHand) {
return;
}
// Prevent skipping card play if the player holds valid normal cards
if (gamePhase === 'draw' || gamePhase === 'noon' && playerHand.length > 0 && hasValidPlacements(playerHand)) {
// Show a safe, harmless fading text warning instead of the destructive discard loop
if (!game.safeSkipWarning) {
var safeWarning = new Text2("You have playable cards! You must play one.", {
size: 40,
fill: 0xFF0000
});
safeWarning.anchor.set(0.5, 0.5);
safeWarning.x = 1024;
safeWarning.y = 1366;
game.addChild(safeWarning);
game.safeSkipWarning = safeWarning;
tween(safeWarning, {
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
if (safeWarning.parent) safeWarning.parent.removeChild(safeWarning);
game.safeSkipWarning = null;
}
});
}
return;
}
if (gamePhase === 'dawn') {
processDawnPhase();
} else if (gamePhase === 'draw') {
processDrawPhase();
} else if (gamePhase === 'noon') {
// Check if player has event cards that must be played first
if (eventCardsToPlay.length > 0 || mustPlayCard) {
// Must play event cards or mandatory card first
return;
}
gamePhase = 'dusk';
processDuskPhase();
} else if (gamePhase === 'dusk') {
// Apply consequences for over-hunted herbivores
processLinkConsequences();
clearLinkHighlights();
gamePhase = 'night';
processNightPhase();
} else if (gamePhase === 'night') {
processNightPhase();
} else if (gamePhase === 'end') {
processEndTurnPhase();
}
};
function updateDangerHighlights() {
var inDangerList = [];
if (gamePhase === 'noon' || gamePhase === 'dusk') {
inDangerList = getCreaturesInDanger();
}
for (var gridY = 0; gridY < planetHeight; gridY++) {
var _loop = function _loop() {
terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack && terrain.creatureStack.length > 0) {
topCreature = terrain.creatureStack[terrain.creatureStack.length - 1];
isInDanger = false;
for (i = 0; i < inDangerList.length; i++) {
if (inDangerList[i] === topCreature) {
isInDanger = true;
break;
}
}
if (isInDanger) {
// Only trigger the highlight creation once
if (!topCreature.isDangerFlashing) {
topCreature.isDangerFlashing = true;
// Create halo AS A CHILD so it inherits placement movement and scaling
if (!topCreature.dangerHalo) {
halo = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
halo.tint = 0xFF0000;
halo.alpha = 0.2;
// Reduced scale to 1.05 so it stays tight to the card and doesn't conflict
halo.scaleX = 1.05;
halo.scaleY = 1.05;
halo.x = 0;
halo.y = 0;
topCreature.addChildAt(halo, 0);
topCreature.dangerHalo = halo;
// Attach the pulse function directly to the halo to avoid closure/scope bugs
halo.pulse = function () {
if (!this.parent) return;
var currentHalo = this;
tween(currentHalo, {
alpha: 0.8
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!currentHalo.parent) return;
tween(currentHalo, {
alpha: 0.2
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: currentHalo.pulse.bind(currentHalo)
});
}
});
};
halo.pulse();
}
}
} else {
if (topCreature.isDangerFlashing) {
topCreature.isDangerFlashing = false;
if (topCreature.dangerHalo && topCreature.dangerHalo.parent) {
tween.stop(topCreature.dangerHalo);
topCreature.dangerHalo.parent.removeChild(topCreature.dangerHalo);
topCreature.dangerHalo = null;
}
}
}
}
},
terrain,
topCreature,
isInDanger,
i,
halo;
for (var gridX = 0; gridX < planetWidth; gridX++) {
_loop();
}
}
// Process global warning text safely
if (gamePhase === 'noon' && inDangerList.length > 0) {
if (!game.dangerWarningText || !game.dangerWarningText.parent) {
var _flashText = function flashText() {
if (!game.dangerWarningText || !game.dangerWarningText.parent) return;
tween(game.dangerWarningText, {
alpha: 0.3
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!game.dangerWarningText || !game.dangerWarningText.parent) return;
tween(game.dangerWarningText, {
alpha: 1
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: _flashText
});
}
});
};
var warningText = new Text2("Warning! Creature/s are in danger!", {
size: 64,
fill: 0xFF0000
});
warningText.anchor.set(0.5, 0);
warningText.x = 1024;
warningText.y = 130;
game.addChild(warningText);
game.dangerWarningText = warningText;
_flashText();
}
} else {
if (game.dangerWarningText && game.dangerWarningText.parent) {
tween.stop(game.dangerWarningText);
game.dangerWarningText.parent.removeChild(game.dangerWarningText);
game.dangerWarningText = null;
}
}
}
var gameStatusText = new Text2("Round: 1 | Phase: DAWN | Turn: 1", {
size: 40,
fill: 0xFFFF00
});
gameStatusText.anchor.set(0.5, 0);
gameStatusText.x = 1024;
gameStatusText.y = 60;
game.addChild(gameStatusText);
// Link adjustment system variables
var linkAdjustmentMode = false;
var adjustLinkButton = null;
var cardsWithAdjustableLinks = [];
var currentlyAdjustingCard = null;
function createAdjustLinksButton() {
if (adjustLinkButton && adjustLinkButton.parent) {
adjustLinkButton.parent.removeChild(adjustLinkButton);
}
adjustLinkButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 0.8
});
adjustLinkButton.tint = 0xFFD700; // Gold for adjust links
var adjustButtonText = new Text2("Adjust Links?", {
size: 20,
fill: 0x000000
});
adjustButtonText.anchor.set(0.5, 0.5);
adjustLinkButton.addChild(adjustButtonText);
adjustLinkButton.x = 250; // Left of next phase button
adjustLinkButton.y = 2400;
game.addChild(adjustLinkButton);
adjustLinkButton.down = function () {
activateLinkAdjustmentMode();
};
}
function showAdjustLinksButton() {
createAdjustLinksButton();
}
function hideAdjustLinksButton() {
if (adjustLinkButton && adjustLinkButton.parent) {
adjustLinkButton.parent.removeChild(adjustLinkButton);
adjustLinkButton = null;
}
}
function activateLinkAdjustmentMode() {
linkAdjustmentMode = true;
currentlyAdjustingCard = null;
cardsWithAdjustableLinks = [];
// Find all carnivores on the board
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
if (creature.creatureData.dietType === 'carnivore') {
cardsWithAdjustableLinks.push(creature);
}
}
}
}
}
// Create and display personal "Alter Links" buttons on all carnivore cards
for (var i = 0; i < cardsWithAdjustableLinks.length; i++) {
var card = cardsWithAdjustableLinks[i];
card.createPersonalAlterLinksButton();
}
}
function deactivateLinkAdjustmentMode() {
linkAdjustmentMode = false;
// Remove personal alter buttons from all cards and restore card colors
for (var i = 0; i < cardsWithAdjustableLinks.length; i++) {
var card = cardsWithAdjustableLinks[i];
card.tint = 0xFFFFFF; // Return to normal color
// Remove personal alter button
if (card.personalAlterButton && card.personalAlterButton.parent) {
card.personalAlterButton.parent.removeChild(card.personalAlterButton);
card.personalAlterButton = null;
}
}
cardsWithAdjustableLinks = [];
currentlyAdjustingCard = null;
}
// Handle card dragging and link highlighting on hold
game.move = function (x, y, obj) {
if (draggedCard) {
draggedCard.x = x;
draggedCard.y = y;
// Ensure dragged card is always on top
game.removeChild(draggedCard);
game.addChild(draggedCard);
// Highlights are already shown from the card's down event
} else if (draggedLinkMarker && gamePhase === 'dusk' && linkAdjustmentMode) {
// Handle link marker dragging - highlight valid targets only while holding
var carnivore = draggedLinkMarker.carnivore;
highlightValidLinkTargets(carnivore);
// Create temporary visual line from carnivore to cursor
// Remove any existing temp line
if (game.tempLinkLine && game.tempLinkLine.parent) {
game.tempLinkLine.parent.removeChild(game.tempLinkLine);
}
// Create new temp line
var tempLine = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.01,
scaleY: 1
});
tempLine.tint = 0xFFFF00; // Yellow temp line
tempLine.alpha = 0.5;
var dx = x - carnivore.x;
var dy = y - carnivore.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
tempLine.x = carnivore.x + dx / 2;
tempLine.y = carnivore.y + dy / 2;
tempLine.rotation = angle;
tempLine.scaleY = distance / 336;
game.addChild(tempLine);
game.tempLinkLine = tempLine;
}
};
game.up = function (x, y, obj) {
if (draggedCard) {
var placed = false;
// Check if card was dropped on valid terrain
for (var gridY = 0; gridY < planetHeight && !placed; gridY++) {
for (var gridX = 0; gridX < planetWidth && !placed; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain) {
if (draggedCard.creatureData && canPlaceCreatureOnTerrain(draggedCard, terrain)) {
if (Math.abs(terrain.x - x) < 140 && Math.abs(terrain.y - y) < 168) {
placed = placeCreatureOnStack(draggedCard, terrain, gridX, gridY);
}
} else if (draggedCard.terrainData && canPlaceTerrainOnTerrain(draggedCard, terrain)) {
if (Math.abs(terrain.x - x) < 140 && Math.abs(terrain.y - y) < 168) {
placed = placeTerrainOnTerrain(draggedCard, terrain, gridX, gridY);
}
}
}
}
}
// If not placed, return to original position
if (!placed && originalCardPosition) {
tween(draggedCard, originalCardPosition, {
duration: 300
});
}
// Clear all highlights
hidePossibleMoves();
draggedCard = null;
originalCardPosition = null;
} else if (draggedLinkMarker && draggedLinkCarnivore && gamePhase === 'dusk' && linkAdjustmentMode) {
// Handle link marker release with new system
var linkCreated = false;
// Remove temporary line
if (game.tempLinkLine && game.tempLinkLine.parent) {
game.tempLinkLine.parent.removeChild(game.tempLinkLine);
game.tempLinkLine = null;
}
// Check if released over a valid herbivore
for (var i = 0; i < linkHighlights.length; i++) {
var highlight = linkHighlights[i];
if (highlight.targetCreature) {
var creature = highlight.targetCreature;
var distance = Math.sqrt(Math.pow(creature.x - x, 2) + Math.pow(creature.y - y, 2));
if (distance < 100) {
// Within range of the herbivore
// Create the link
if (createLink(draggedLinkCarnivore, creature)) {
linkCreated = true;
break;
}
}
}
}
// Clear highlights and reset drag state
clearLinkHighlights();
draggedLinkMarker = null;
draggedLinkCarnivore = null;
}
};
function processAT18Special() {
// Find all carnivore cards in discard pile
at18CarnivoreList = [];
for (var i = 0; i < discardPile.length; i++) {
//{at18_loop}
var cardData = discardPile[i];
if (cardData.cardType === 'creature' && cardData.dietType === 'carnivore') {
//{at18_check}
at18CarnivoreList.push(cardData);
} //{at18_found}
} //{at18_end_loop}
at18Active = true;
if (at18CarnivoreList.length === 0) {
// No carnivores in discard pile - show message
showAT18NoCarnivoredMessage();
} else {
//{at18_else}
// Display carnivores for selection
displayAT18CarnivoreSelection();
} //{at18_display_end}
} //{at18_process_end}
function showAT18NoCarnivoredMessage() {
// Create notification box
var notificationBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
//{at18_no_x}
anchorY: 0.5 //{at18_no_y}
}); //{at18_no_asset}
notificationBox.tint = 0x4169E1; // Blue background for info
notificationBox.alpha = 0.9;
notificationBox.scaleX = 2.5;
notificationBox.scaleY = 1.5;
notificationBox.x = 1024;
notificationBox.y = 1366;
game.addChild(notificationBox);
// Create notification text
var notificationText = new Text2("No Carnivore cards in Discard pile - Special does not apply", {
size: 28,
//{at18_no_text_size}
fill: 0xFFFFFF //{at18_no_text_fill}
}); //{at18_no_text}
notificationText.anchor.set(0.5, 0.5);
notificationText.x = 0;
notificationText.y = 0;
notificationBox.addChild(notificationText);
// Make the box clickable to dismiss
notificationBox.down = function () {
if (notificationBox.parent) {
notificationBox.parent.removeChild(notificationBox);
} //{at18_no_dismiss}
at18Active = false;
}; //{at18_no_down}
} //{at18_no_function_end}
function displayAT18CarnivoreSelection() {
// Create scry UI header
var scryHeader = new Text2("Pick 1 to add back onto the main deck", {
size: 32,
//{at18_header_size}
fill: 0xFFFFFF //{at18_header_fill}
}); //{at18_header_text}
scryHeader.anchor.set(0.5, 0);
scryHeader.x = 1024;
scryHeader.y = 300;
game.addChild(scryHeader);
scryHeader.at18Header = true;
// Display each carnivore card in a selectable list format
var cardYOffset = 500;
var cardYSpacing = 280;
for (var i = 0; i < at18CarnivoreList.length; i++) {
//{at18_display_loop}
var cardData = at18CarnivoreList[i];
var cardContainer = new CreatureCard(cardData);
cardContainer.scaleX = 0.6;
cardContainer.scaleY = 0.6;
cardContainer.x = 512 + i % 2 * 600;
cardContainer.y = cardYOffset + Math.floor(i / 2) * cardYSpacing;
cardContainer.cardDataIndex = i;
cardContainer.at18Selectable = true;
// Add click handler to select this card
cardContainer.down = function (x, y, obj) {
var selectedIndex = this.cardDataIndex;
selectAT18Carnivore(selectedIndex);
}; //{at18_selectable_down}
game.addChild(cardContainer);
} //{at18_display_loop_end}
} //{at18_display_function_end}
function selectAT18Carnivore(cardIndex) {
if (cardIndex < 0 || cardIndex >= at18CarnivoreList.length) {
return;
}
// Get the selected carnivore card data
var selectedCardData = at18CarnivoreList[cardIndex];
// Remove from discard pile
for (var i = discardPile.length - 1; i >= 0; i--) {
//{at18_remove_loop}
if (discardPile[i] === selectedCardData || discardPile[i].id === selectedCardData.id && discardPile[i].cardType === selectedCardData.cardType && discardPile[i].dietType === selectedCardData.dietType) {
//{at18_remove_check}
discardPile.splice(i, 1);
break;
} //{at18_remove_end}
} //{at18_remove_loop_end}
// Add to top of main deck
mainDeck.push(selectedCardData);
// Clear all AT18 UI elements
clearAT18UI();
// Update deck count
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
at18Active = false;
} //{at18_select_function_end}
function clearAT18UI() {
// Remove all AT18 selectable cards from game
for (var i = game.children.length - 1; i >= 0; i--) {
//{at18_clear_loop}
var child = game.children[i];
if (child.at18Selectable) {
//{at18_clear_check}
game.removeChild(child);
} //{at18_clear_removed}
if (child.at18Header) {
//{at18_clear_header}
game.removeChild(child);
} //{at18_clear_header_removed}
} //{at18_clear_loop_end}
} //{at18_clear_function_end}
function activateScry() {
if (!scryTokenActive || scryMode || mainDeck.length < 3) {
return;
}
scryMode = true;
scryStep = 1;
scryCards = [];
// Pop 3 cards from mainDeck and create them
for (var i = 0; i < 3; i++) {
var cardData = mainDeck.pop();
var card = createCardFromData(cardData);
scryCards.push(card);
// Do NOT push to playerHand - only to scryCards
game.addChild(card);
}
// Position cards in center of screen side-by-side
var scryCardPositions = [512, 1024, 1536];
for (var i = 0; i < scryCards.length; i++) {
scryCards[i].x = scryCardPositions[i];
scryCards[i].y = 1000;
scryCards[i].scaleX = 1.5;
scryCards[i].scaleY = 1.5;
// Assign down function to each card
scryCards[i].down = function (card) {
return function (x, y, obj) {
processScrySelection(card);
};
}(scryCards[i]);
}
// Create header text
scryHeaderText = new Text2("SCRY: Select 1 card to put on the\nBOTTOM of the main deck", {
size: 56,
fill: 0xFFFF00,
align: 'center'
});
scryHeaderText.anchor.set(0.5, 0);
scryHeaderText.x = 1024;
scryHeaderText.y = 120;
game.addChild(scryHeaderText);
}
function processScrySelection(selectedCard) {
// Find the index of selectedCard in scryCards
var cardIndex = -1;
for (var i = 0; i < scryCards.length; i++) {
if (scryCards[i] === selectedCard) {
cardIndex = i;
break;
}
}
if (cardIndex === -1) {
return; // Card not found
}
// Extract clean copy of card data
var cardData = selectedCard.creatureData || selectedCard.terrainData || selectedCard.eventData;
var cleanCardData = {};
cleanCardData.type = cardData.type;
cleanCardData.level = cardData.level;
cleanCardData.cardType = cardData.cardType;
if (cardData.dietType) cleanCardData.dietType = cardData.dietType;
if (cardData.name) cleanCardData.name = cardData.name;
if (cardData.terrainRequirement) cleanCardData.terrainRequirement = cardData.terrainRequirement;
if (cardData.climateRequirement) cleanCardData.climateRequirement = cardData.climateRequirement;
if (cardData.subtype) cleanCardData.subtype = cardData.subtype;
if (cardData.landType) cleanCardData.landType = cardData.landType;
if (cardData.waterType) cleanCardData.waterType = cardData.waterType;
if (cardData.climate) cleanCardData.climate = cardData.climate;
if (cardData.id) cleanCardData.id = cardData.id;
if (cardData.colorBand) cleanCardData.colorBand = cardData.colorBand;
if (scryStep === 1) {
// Put card on BOTTOM of deck
mainDeck.unshift(cleanCardData);
// Remove from screen
if (selectedCard.parent) {
selectedCard.parent.removeChild(selectedCard);
}
// Remove from scryCards array
scryCards.splice(cardIndex, 1);
// Update header text
scryHeaderText.setText("SCRY: Select 1 card to DISCARD.\nThe last card goes on TOP of the deck.");
scryStep = 2;
} else if (scryStep === 2) {
// Put card in discard pile
discardPile.push(cleanCardData);
// Remove from screen
if (selectedCard.parent) {
selectedCard.parent.removeChild(selectedCard);
}
// Remove from scryCards array
scryCards.splice(cardIndex, 1);
// Now process the last remaining card automatically
if (scryCards.length === 1) {
var lastCard = scryCards[0];
var lastCardData = lastCard.creatureData || lastCard.terrainData || lastCard.eventData;
var cleanLastCardData = {};
cleanLastCardData.type = lastCardData.type;
cleanLastCardData.level = lastCardData.level;
cleanLastCardData.cardType = lastCardData.cardType;
if (lastCardData.dietType) cleanLastCardData.dietType = lastCardData.dietType;
if (lastCardData.name) cleanLastCardData.name = lastCardData.name;
if (lastCardData.terrainRequirement) cleanLastCardData.terrainRequirement = lastCardData.terrainRequirement;
if (lastCardData.climateRequirement) cleanLastCardData.climateRequirement = lastCardData.climateRequirement;
if (lastCardData.subtype) cleanLastCardData.subtype = lastCardData.subtype;
if (lastCardData.landType) cleanLastCardData.landType = lastCardData.landType;
if (lastCardData.waterType) cleanLastCardData.waterType = lastCardData.waterType;
if (lastCardData.climate) cleanLastCardData.climate = lastCardData.climate;
if (lastCardData.id) cleanLastCardData.id = lastCardData.id;
if (lastCardData.colorBand) cleanLastCardData.colorBand = lastCardData.colorBand;
// Put on TOP of deck
mainDeck.push(cleanLastCardData);
// Remove from screen
if (lastCard.parent) {
lastCard.parent.removeChild(lastCard);
}
// Clear scryCards array
scryCards = [];
// Remove header text
if (scryHeaderText && scryHeaderText.parent) {
scryHeaderText.parent.removeChild(scryHeaderText);
scryHeaderText = null;
}
// Reset scry state
scryMode = false;
scryTokenActive = false;
scryStep = 0;
// Update UI
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
}
}
}
function showNoPlayableCardsNotification() {
// Create notification box
var notificationBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.5
});
notificationBox.tint = 0x8B0000; // Dark red background
notificationBox.alpha = 0.9;
notificationBox.x = 1024;
notificationBox.y = 1366;
game.addChild(notificationBox);
// Create notification text
var notificationText = new Text2("No playable cards! Click to continue", {
size: 36,
fill: 0xFFFFFF
});
notificationText.anchor.set(0.5, 0.5);
notificationText.x = 0;
notificationText.y = 0;
notificationBox.addChild(notificationText);
// Make the box clickable
notificationBox.down = function () {
// Remove the notification
if (notificationBox.parent) {
notificationBox.parent.removeChild(notificationBox);
}
// Discard all cards in hand
discardCards(playerHand.slice());
// Start draw 1, discard 1 cycle
processDrawPhase();
};
// Store reference for cleanup
game.noPlayableNotification = notificationBox;
}
// Initialize terrain-based game
createBasicTerrainPool();
setupPlanet();
createInitialDeck();
createEventDecks();
// Shuffle event decks on game start
shuffleEventDeck(basicEventDeck);
shuffleEventDeck(advancedEventDeck);
// Game starts with turn 1, round 1, in Dawn phase
currentRound = 1;
turnNumber = 1;
gamePhase = 'dawn';
game.update = function () {
// Update UI elements
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
basicEventCountText.setText("Basic Events: " + basicEventDeck.length);
advancedEventCountText.setText("Advanced Events: " + advancedEventDeck.length);
graveyardCountText.setText("Graveyard: " + graveyard.length);
eventDiscardCountText.setText("Event Discards: B:" + basicEventDiscard.length + " | A:" + advancedEventDiscard.length);
// Update scry token display
if (scryTokenActive) {
scryText.setText("Scry ✓");
scryText.tint = 0x00FF00; // Green when available
} else if (scryMode) {
scryText.setText("Scry (Active)");
scryText.tint = 0xFFFF00; // Yellow when in use
} else {
scryText.setText("Scry");
scryText.tint = 0x888888; // Gray when unavailable
}
// Update round and phase indicator
gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber);
// Check if event cards have been removed from hand
hasEventCardsInHand = false;
for (var i = 0; i < playerHand.length; i++) {
//{lV_event1}
if (playerHand[i].eventData) {
hasEventCardsInHand = true;
break;
}
}
// Update next phase button appearance based on event cards
if (gamePhase === 'noon') {
//{lV_event2}
if (hasEventCardsInHand) {
nextPhaseButton.tint = 0xFF0000; // Red when events must be played
nextPhaseButtonText.setText("Play events 1st!");
} else {
nextPhaseButton.tint = 0x32CD32; // Green when normal phase progression
nextPhaseButtonText.setText("NEXT PHASE");
}
}
// Update link marker states during dusk phase
if (gamePhase === 'dusk') {
updateLinkMarkerStates();
}
// Update danger highlights for creatures at risk of extinction
updateDangerHighlights();
};
function resetCardLinks(carnivore) {
if (!carnivore || carnivore.creatureData.dietType !== 'carnivore') {
return;
}
// Remove all active links from this carnivore with fall-off animation
for (var i = carnivore.activeLinks.length - 1; i >= 0; i--) {
var link = carnivore.activeLinks[i];
animateLinkFallOff(carnivore, link.target);
}
// Reset all link markers to unlinked state
for (var j = 0; j < carnivore.linkMarkers.length; j++) {
var marker = carnivore.linkMarkers[j];
marker.isLinked = false;
marker.targetHerbivore = null;
marker.tint = 0xFF0000; // Red for unlinked
}
// Clear active links array
carnivore.activeLinks = [];
}
var simplifyActive = false;
var simplifyHeaderText = null;
function processSimplifySpecial(originX, originY) {
var targets = [];
var neighbors = getAdjacentTerrains(originX, originY, true);
for (var i = 0; i < neighbors.length; i++) {
var adj = neighbors[i];
if (adj && adj.terrainData && adj.terrainData.level === 'Advanced' && (!adj.creatureStack || adj.creatureStack.length === 0)) {
targets.push(adj);
}
}
if (targets.length > 0) {
simplifyActive = true;
simplifyHeaderText = new Text2("Select a terrain for devolution!", {
size: 56,
fill: 0xFFFF00,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
simplifyHeaderText.x = 1024;
simplifyHeaderText.y = 120;
game.addChild(simplifyHeaderText);
for (var t = 0; t < targets.length; t++) {
var target = targets[t];
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.tint = 0x0000FF;
highlight.alpha = 0.5;
highlight.scaleX = target.scaleX * 1.1;
highlight.scaleY = target.scaleY * 1.1;
highlight.x = target.x;
highlight.y = target.y;
highlight.targetTerrain = target;
highlight.isSimplifyHighlight = true;
highlight.down = function () {
if (!simplifyActive) return;
var tTerrain = this.targetTerrain;
var tX = tTerrain.gridX;
var tY = tTerrain.gridY;
var basicUnder = tTerrain.basicTerrainUnderneath;
discardPile.push(tTerrain.terrainData);
discardCountText.setText("Discard: " + discardPile.length);
planetBoard[tY][tX] = basicUnder;
planetSlots[tY][tX].terrainCard = basicUnder;
if (tTerrain.parent) tTerrain.parent.removeChild(tTerrain);
for (var j = game.children.length - 1; j >= 0; j--) {
if (game.children[j].isSimplifyHighlight) game.removeChild(game.children[j]);
}
if (simplifyHeaderText && simplifyHeaderText.parent) simplifyHeaderText.parent.removeChild(simplifyHeaderText);
simplifyActive = false;
};
game.addChild(highlight);
}
}
}
var watcherActive = false;
var watcherList = [];
function processWatcherSpecial() {
watcherList = [];
for (var i = 0; i < discardPile.length; i++) {
if (discardPile[i].cardType === 'event') watcherList.push(discardPile[i]);
}
watcherActive = true;
if (watcherList.length === 0) {
var notifyBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.5,
tint: 0x4169E1,
alpha: 0.9,
x: 1024,
y: 1366
});
var notifyText = new Text2("No Event cards in Discard - Watcher does not apply", {
size: 28,
fill: 0xFFFFFF,
anchorX: 0.5,
anchorY: 0.5
});
notifyBox.addChild(notifyText);
game.addChild(notifyBox);
notifyBox.down = function () {
if (notifyBox.parent) notifyBox.parent.removeChild(notifyBox);
watcherActive = false;
};
} else {
var header = new Text2("Choose an event to return to the event deck", {
size: 56,
fill: 0xFFFF00,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
header.x = 1024;
header.y = 120;
header.watcherHeader = true;
game.addChild(header);
for (var i = 0; i < watcherList.length; i++) {
var cardContainer = createCardFromData(watcherList[i]);
cardContainer.scaleX = 0.6;
cardContainer.scaleY = 0.6;
cardContainer.x = 512 + i % 2 * 600;
cardContainer.y = 500 + Math.floor(i / 2) * 280;
cardContainer.cardDataIndex = i;
cardContainer.watcherSelectable = true;
cardContainer.down = function () {
var selectedData = watcherList[this.cardDataIndex];
for (var d = discardPile.length - 1; d >= 0; d--) {
if (discardPile[d] === selectedData || discardPile[d].name === selectedData.name) {
discardPile.splice(d, 1);
break;
}
}
if (selectedData.level === 'Advanced') advancedEventDeck.push(selectedData);else basicEventDeck.push(selectedData);
for (var k = game.children.length - 1; k >= 0; k--) {
var c = game.children[k];
if (c.watcherSelectable || c.watcherHeader) game.removeChild(c);
}
basicEventCountText.setText("Basic Events: " + basicEventDeck.length);
advancedEventCountText.setText("Advanced Events: " + advancedEventDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
watcherActive = false;
};
game.addChild(cardContainer);
}
}
}
var forerunnerActive = false;
var forerunnerList = [];
function processForerunnerSpecial() {
forerunnerList = discardPile.slice(); // Copy entire discard pile
forerunnerActive = true;
if (forerunnerList.length === 0) {
var notifyBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.5,
tint: 0x4169E1,
alpha: 0.9,
x: 1024,
y: 1366
});
var notifyText = new Text2("Discard pile is empty - Forerunner does not apply", {
size: 28,
fill: 0xFFFFFF,
anchorX: 0.5,
anchorY: 0.5
});
notifyBox.addChild(notifyText);
game.addChild(notifyBox);
notifyBox.down = function () {
if (notifyBox.parent) notifyBox.parent.removeChild(notifyBox);
forerunnerActive = false;
};
} else {
var header = new Text2("Choose a card to place on top of the main deck,\nall others will be returned to the discard pile", {
size: 48,
fill: 0xFFFF00,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
header.x = 1024;
header.y = 120;
header.forerunnerHeader = true;
game.addChild(header);
var cardYOffset = 500;
var cardYSpacing = 280;
for (var i = 0; i < forerunnerList.length; i++) {
var cardContainer = createCardFromData(forerunnerList[i]);
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.forerunnerSelectable = true;
cardContainer.down = function () {
var selectedData = forerunnerList[this.cardDataIndex];
for (var d = discardPile.length - 1; d >= 0; d--) {
if (discardPile[d] === selectedData || discardPile[d].id === selectedData.id) {
discardPile.splice(d, 1);
break;
}
}
mainDeck.push(selectedData);
for (var k = game.children.length - 1; k >= 0; k--) {
var c = game.children[k];
if (c.forerunnerSelectable || c.forerunnerHeader) game.removeChild(c);
}
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
forerunnerActive = false;
};
game.addChild(cardContainer);
}
}
}
var devolutionActive = false;
var devolutionHeaderText = null;
function processDevolutionEvent(onComplete) {
var targets = [];
for (var y = 0; y < planetHeight; y++) {
for (var x = 0; x < planetWidth; x++) {
var terrain = planetBoard[y][x];
if (terrain && terrain.creatureStack && terrain.creatureStack.length > 0) {
var topC = terrain.creatureStack[terrain.creatureStack.length - 1];
if (topC.creatureData.dietType === 'carnivore' && topC.creatureData.level === 'Advanced') targets.push(topC);
}
}
}
if (targets.length === 0) {
onComplete(); // No targets, event fizzles instantly
} else {
devolutionActive = true;
devolutionHeaderText = new Text2("Select an advanced carnivore to go back in time!", {
size: 56,
fill: 0xFF0000,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
devolutionHeaderText.x = 1024;
devolutionHeaderText.y = 120;
game.addChild(devolutionHeaderText);
for (var t = 0; t < targets.length; t++) {
var target = targets[t];
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.tint = 0xDDA0DD;
highlight.alpha = 0.6; // Light purple
highlight.scaleX = target.scaleX * 1.15;
highlight.scaleY = target.scaleY * 1.15;
highlight.x = target.x;
highlight.y = target.y;
highlight.targetCreature = target;
highlight.isDevolutionHighlight = true;
highlight.down = function () {
if (!devolutionActive) return;
var tC = this.targetCreature;
for (var j = tC.activeLinks.length - 1; j >= 0; j--) animateLinkFallOff(tC, tC.activeLinks[j].target);
for (var j = linkLines.length - 1; j >= 0; j--) if (linkLines[j].herbivore === tC) animateLinkFallOff(linkLines[j].carnivore, tC);
var stack = planetBoard[tC.gridY][tC.gridX].creatureStack;
var idx = stack.indexOf(tC);
if (idx !== -1) stack.splice(idx, 1);
discardPile.push(tC.creatureData);
discardCountText.setText("Discard: " + discardPile.length);
if (tC.parent) tC.parent.removeChild(tC);
for (var j = game.children.length - 1; j >= 0; j--) {
if (game.children[j].isDevolutionHighlight) game.removeChild(game.children[j]);
}
if (devolutionHeaderText && devolutionHeaderText.parent) devolutionHeaderText.parent.removeChild(devolutionHeaderText);
devolutionActive = false;
onComplete(); // Finish event resolution
};
game.addChild(highlight);
}
}
}
var terraformerActive = false;
var terraformerHeaderText = null;
function processTerraformerSpecial(originX, originY) {
var targets = [];
var neighbors = getAdjacentTerrains(originX, originY, true);
var targetLevel = 'Advanced';
// First pass: Check for empty Advanced terrains
for (var i = 0; i < neighbors.length; i++) {
var adj = neighbors[i];
if (adj && adj.terrainData && adj.terrainData.level === 'Advanced' && (!adj.creatureStack || adj.creatureStack.length === 0)) {
targets.push(adj);
}
}
// Second pass: If no Advanced terrains found, check for empty Basic terrains
if (targets.length === 0) {
targetLevel = 'Basic';
for (var i = 0; i < neighbors.length; i++) {
var adj = neighbors[i];
if (adj && adj.terrainData && adj.terrainData.level === 'Basic' && (!adj.creatureStack || adj.creatureStack.length === 0)) {
targets.push(adj);
}
}
}
if (targets.length > 0) {
terraformerActive = true;
terraformerHeaderText = new Text2("Select 1 terrain card to be removed,\nthe basic terrain will then shift", {
size: 56,
fill: 0xFFFF00,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
terraformerHeaderText.x = 1024;
terraformerHeaderText.y = 120;
game.addChild(terraformerHeaderText);
for (var t = 0; t < targets.length; t++) {
var target = targets[t];
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.tint = 0xFFFF00;
highlight.alpha = 0.6;
highlight.scaleX = target.scaleX * 1.1;
highlight.scaleY = target.scaleY * 1.1;
highlight.x = target.x;
highlight.y = target.y;
highlight.targetTerrain = target;
highlight.isTerraformerHighlight = true;
highlight.targetLevel = targetLevel;
highlight.down = function () {
if (!terraformerActive) return;
var tTerrain = this.targetTerrain;
var tX = tTerrain.gridX;
var tY = tTerrain.gridY;
var basicUnder = tTerrain.basicTerrainUnderneath;
// 1. If Advanced, discard the Advanced card
if (this.targetLevel === 'Advanced') {
discardPile.push(tTerrain.terrainData);
discardCountText.setText("Discard: " + discardPile.length);
if (tTerrain.parent) tTerrain.parent.removeChild(tTerrain);
}
// 2. Grab a fresh basic terrain from the unused pool if available
var targetBasicToSwap = this.targetLevel === 'Advanced' ? basicUnder : tTerrain;
if (basicTerrainPool.length > planetLayout.reduce(function (a, b) {
return a + b;
}, 0)) {
// Pop a truly unused card from the pool
var newBasicData = basicTerrainPool.pop();
// Inherit the climate of the grid slot
newBasicData.climate = targetBasicToSwap.terrainData.climate;
var newBasicCard = new TerrainCard(newBasicData);
newBasicCard.gridX = tX;
newBasicCard.gridY = tY;
newBasicCard.isInPlay = true;
newBasicCard.x = targetBasicToSwap.x;
newBasicCard.y = targetBasicToSwap.y;
newBasicCard.scaleX = targetBasicToSwap.scaleX;
newBasicCard.scaleY = targetBasicToSwap.scaleY;
planetBoard[tY][tX] = newBasicCard;
planetSlots[tY][tX].terrainCard = newBasicCard;
var basicIndex = game.getChildIndex(targetBasicToSwap);
if (targetBasicToSwap.parent) targetBasicToSwap.parent.removeChild(targetBasicToSwap);
game.addChildAt(newBasicCard, basicIndex);
} else {
// If pool is empty, just revert Advanced to Basic without swapping
if (this.targetLevel === 'Advanced') {
planetBoard[tY][tX] = basicUnder;
planetSlots[tY][tX].terrainCard = basicUnder;
}
}
// Cleanup UI
for (var j = game.children.length - 1; j >= 0; j--) {
if (game.children[j].isTerraformerHighlight) game.removeChild(game.children[j]);
}
if (terraformerHeaderText && terraformerHeaderText.parent) terraformerHeaderText.parent.removeChild(terraformerHeaderText);
terraformerActive = false;
};
game.addChild(highlight);
}
}
}
;
var diseaseActive = false;
var diseaseHeaderText = null;
function processDiseaseEvent(onComplete) {
var targets = [];
for (var y = 0; y < planetHeight; y++) {
for (var x = 0; x < planetWidth; x++) {
var terrain = planetBoard[y][x];
if (terrain && terrain.creatureStack) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
if (terrain.creatureStack[i].creatureData.level === 'Basic') targets.push(terrain.creatureStack[i]);
}
}
}
}
if (targets.length === 0) {
onComplete();
return;
}
diseaseActive = true;
var picks = 0;
diseaseHeaderText = new Text2("Select a creature to become sick!", {
size: 56,
fill: 0xFF0000,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
diseaseHeaderText.x = 1024;
diseaseHeaderText.y = 120;
game.addChild(diseaseHeaderText);
for (var t = 0; t < targets.length; t++) {
var target = targets[t];
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.tint = 0xDDA0DD;
highlight.alpha = 0.6;
highlight.scaleX = target.scaleX * 1.15;
highlight.scaleY = target.scaleY * 1.15;
highlight.x = target.x;
highlight.y = target.y;
highlight.targetCreature = target;
highlight.isDiseaseHighlight = true;
highlight.down = function () {
if (!diseaseActive) return;
var tC = this.targetCreature;
tC.extinctionMarkers += 1;
tC.updateExtinctionMarkers();
picks++;
if (picks === 1) {
diseaseHeaderText.setText("And now another sickens....");
} else if (picks === 2) {
for (var j = game.children.length - 1; j >= 0; j--) {
if (game.children[j].isDiseaseHighlight) game.removeChild(game.children[j]);
}
if (diseaseHeaderText && diseaseHeaderText.parent) diseaseHeaderText.parent.removeChild(diseaseHeaderText);
diseaseActive = false;
onComplete();
}
};
game.addChild(highlight);
}
}
var volcanoActive = false;
var volcanoHeaderText = null;
function processVolcanoEvent(onComplete) {
var targets = [];
for (var y = 0; y < planetHeight; y++) {
for (var x = 0; x < planetWidth; x++) {
var terrain = planetBoard[y][x];
if (terrain && terrain.terrainData && terrain.terrainData.landType === 'mountain') {
// Priority: Only mountains with creatures, OR any mountain if none have creatures
if (terrain.creatureStack && terrain.creatureStack.length > 0) {
targets.push(terrain);
}
}
}
}
// If no occupied mountains exist, grab all empty mountains
if (targets.length === 0) {
for (var y = 0; y < planetHeight; y++) {
for (var x = 0; x < planetWidth; x++) {
var terrain = planetBoard[y][x];
if (terrain && terrain.terrainData && terrain.terrainData.landType === 'mountain') {
targets.push(terrain);
}
}
}
}
if (targets.length === 0) {
onComplete();
return;
} // No mountains at all
volcanoActive = true;
volcanoHeaderText = new Text2("Select a Mountain to become a raging volcano!", {
size: 56,
fill: 0xFF0000,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
volcanoHeaderText.x = 1024;
volcanoHeaderText.y = 120;
game.addChild(volcanoHeaderText);
for (var t = 0; t < targets.length; t++) {
var target = targets[t];
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.tint = 0xDDA0DD;
highlight.alpha = 0.6;
highlight.scaleX = target.scaleX * 1.15;
highlight.scaleY = target.scaleY * 1.15;
highlight.x = target.x;
highlight.y = target.y;
highlight.targetTerrain = target;
highlight.isVolcanoHighlight = true;
highlight.down = function () {
if (!volcanoActive) return;
var selectedMountain = this.targetTerrain;
// Cleanup mountain highlights
for (var j = game.children.length - 1; j >= 0; j--) {
if (game.children[j].isVolcanoHighlight) game.removeChild(game.children[j]);
}
// Start the recursive fleeing logic
processVolcanoFlee(selectedMountain, onComplete);
};
game.addChild(highlight);
}
}
function processVolcanoFlee(mountain, onComplete) {
if (!mountain.creatureStack || mountain.creatureStack.length === 0) {
if (volcanoHeaderText && volcanoHeaderText.parent) volcanoHeaderText.parent.removeChild(volcanoHeaderText);
volcanoActive = false;
onComplete();
return; // Event over
}
var topC = mountain.creatureStack[mountain.creatureStack.length - 1];
if (topC.creatureData.flying || topC.creatureData.swimming) {
volcanoHeaderText.setText("Lucky creatures!");
LK.setTimeout(function () {
if (volcanoHeaderText && volcanoHeaderText.parent) volcanoHeaderText.parent.removeChild(volcanoHeaderText);
volcanoActive = false;
onComplete();
}, 1500);
return;
}
volcanoHeaderText.setText("Unlucky creature - Flee to survive!");
// Find valid escape routes
var validEscapes = [];
var neighbors = getAdjacentTerrains(mountain.gridX, mountain.gridY, true); // Diagonals included
// Temporarily pop the creature to prevent self-collision during checks
mountain.creatureStack.pop();
for (var n = 0; n < neighbors.length; n++) {
var adj = neighbors[n];
if (adj && canPlaceCreatureOnTerrain(topC, adj)) {
validEscapes.push(adj);
}
}
// Put it back
mountain.creatureStack.push(topC);
if (validEscapes.length === 0) {
// Die without event card
var stack = mountain.creatureStack;
stack.pop();
graveyard.push(topC.creatureData);
scryDeniedNextRound = true;
for (var j = topC.activeLinks.length - 1; j >= 0; j--) animateLinkFallOff(topC, topC.activeLinks[j].target);
for (var j = linkLines.length - 1; j >= 0; j--) if (linkLines[j].herbivore === topC) animateLinkFallOff(linkLines[j].carnivore, topC);
if (topC.parent) topC.parent.removeChild(topC);
// Check next creature down
processVolcanoFlee(mountain, onComplete);
} else {
volcanoHeaderText.setText("Choose a new home!");
var fleeHighlights = [];
for (var e = 0; e < validEscapes.length; e++) {
var esc = validEscapes[e];
var hlt = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
hlt.tint = 0xFFFF00;
hlt.alpha = 0.6;
hlt.scaleX = esc.scaleX * 1.15;
hlt.scaleY = esc.scaleY * 1.15;
hlt.x = esc.x;
hlt.y = esc.y;
hlt.targetEscape = esc;
hlt.isFleeHighlight = true;
hlt.down = function () {
var chosenEsc = this.targetEscape;
for (var k = game.children.length - 1; k >= 0; k--) if (game.children[k].isFleeHighlight) game.removeChild(game.children[k]);
// Move creature physically
var movingC = mountain.creatureStack.pop();
placeCreatureOnStack(movingC, chosenEsc, chosenEsc.gridX, chosenEsc.gridY);
// Check next creature down
processVolcanoFlee(mountain, onComplete);
};
game.addChild(hlt);
fleeHighlights.push(hlt);
}
}
}