/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var CreatureCard = Container.expand(function (creatureData) {
var self = Container.call(this);
self.creatureData = creatureData || {
type: 'basic',
level: 'Basic',
dietType: 'herbivore',
name: 'Basic Herbivore',
terrainRequirement: 'land',
climateRequirement: 'any'
};
// Create creature visual
var creatureAsset;
if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'herbivore') {
creatureAsset = self.attachAsset('creatureBasicHerbivore', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'herbivore') {
creatureAsset = self.attachAsset('creatureAdvancedHerbivore', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'carnivore') {
creatureAsset = self.attachAsset('creatureBasicCarnivore', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'carnivore') {
creatureAsset = self.attachAsset('creatureAdvancedCarnivore', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Add creature type color band at bottom
var typeStripAsset;
if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'herbivore') {
typeStripAsset = 'creatureTypeBasicHerbivore';
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'herbivore') {
typeStripAsset = 'creatureTypeAdvancedHerbivore';
} else if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'carnivore') {
typeStripAsset = 'creatureTypeBasicCarnivore';
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'carnivore') {
typeStripAsset = 'creatureTypeAdvancedCarnivore';
}
if (typeStripAsset) {
var typeStrip = self.attachAsset(typeStripAsset, {
anchorX: 0.5,
anchorY: 0.5
});
typeStrip.y = 95; // Position at bottom of creature card
}
// Add creature name
self.nameText = new Text2(self.creatureData.name, {
size: 18,
fill: 0xFFFFFF
});
self.nameText.anchor.set(0.5, 0);
self.nameText.x = 0;
self.nameText.y = -100;
self.addChild(self.nameText);
// Add requirement text
var reqText = "Req: ";
if (self.creatureData.terrainRequirement && self.creatureData.terrainRequirement !== 'any') {
reqText += self.creatureData.terrainRequirement;
}
if (self.creatureData.climateRequirement && self.creatureData.climateRequirement !== 'any') {
if (reqText !== "Req: ") {
reqText += ", ";
}
reqText += self.creatureData.climateRequirement;
}
if (reqText !== "Req: ") {
self.requirementText = new Text2(reqText, {
size: 14,
fill: 0xFFFFFF
});
self.requirementText.anchor.set(0.5, 0);
self.requirementText.x = 0;
self.requirementText.y = 60;
self.addChild(self.requirementText);
}
self.isInPlay = false;
self.linkMarkers = [];
self.activeLinks = [];
self.extinctionMarkers = 0;
self.linkRequirement = 0;
self.safeLinks = 0;
// Set link requirements and safe link levels based on creature type
if (self.creatureData.dietType === 'carnivore') {
if (self.creatureData.level === 'Basic') {
self.linkRequirement = 1; // Basic carnivores need 1 link
} else if (self.creatureData.level === 'Advanced') {
self.linkRequirement = 2; // Advanced carnivores need 2 links
}
} else if (self.creatureData.dietType === 'herbivore') {
if (self.creatureData.level === 'Basic') {
self.safeLinks = 1; // Basic herbivores can safely handle 1 link
} else if (self.creatureData.level === 'Advanced') {
self.safeLinks = 2; // Advanced herbivores can safely handle 2 links
}
}
// Create visual link markers for carnivores
self.createLinkMarkers = function () {
if (self.creatureData.dietType === 'carnivore' && self.linkRequirement > 0) {
for (var i = 0; i < self.linkRequirement; i++) {
var linkMarker = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.15,
scaleY: 0.15
});
linkMarker.tint = 0xFF0000; // Red color for unlinked markers
linkMarker.x = (i - (self.linkRequirement - 1) / 2) * 25;
linkMarker.y = -80;
linkMarker.isLinked = false;
linkMarker.targetHerbivore = null;
linkMarker.carnivore = self;
linkMarker.markerIndex = i;
// Link markers are no longer directly interactive
linkMarker.move = function (x, y, obj) {};
linkMarker.down = function (x, y, obj) {};
self.addChild(linkMarker);
self.linkMarkers.push(linkMarker);
}
// Create personal "Alter Links" button for this carnivore
self.createPersonalAlterLinksButton = function () {
if (self.isInPlay) {
var alterButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.2
});
alterButton.tint = 0xFFD700; // Gold color
var alterButtonText = new Text2("Alter Links", {
size: 12,
fill: 0x000000
});
alterButtonText.anchor.set(0.5, 0.5);
alterButton.addChild(alterButtonText);
alterButton.x = 0;
alterButton.y = 0;
alterButton.carnivoreCard = self;
alterButton.down = function () {
if (gamePhase === 'dusk' && linkAdjustmentMode) {
resetCardLinks(self);
}
};
self.addChild(alterButton);
self.personalAlterButton = alterButton;
}
};
}
};
// Create extinction marker visuals
self.extinctionMarkerVisuals = [];
self.updateExtinctionMarkers = function () {
// Remove existing visuals
for (var i = 0; i < self.extinctionMarkerVisuals.length; i++) {
if (self.extinctionMarkerVisuals[i].parent) {
self.extinctionMarkerVisuals[i].parent.removeChild(self.extinctionMarkerVisuals[i]);
}
}
self.extinctionMarkerVisuals = [];
// Create new visuals for current extinction markers
for (var i = 0; i < self.extinctionMarkers; i++) {
var marker = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.1,
scaleY: 0.1
});
marker.tint = 0x000000; // Black for extinction markers
marker.x = (i - (self.extinctionMarkers - 1) / 2) * 20;
marker.y = 80;
self.addChild(marker);
self.extinctionMarkerVisuals.push(marker);
}
};
self.die = function () {
if (self.isInPlay) {
// Add creature to graveyard and draw event card based on creature tier
graveyard.push(self.creatureData);
// Draw event card only once when creature dies
addEventCardToDeck(self.creatureData.level);
// Visual death effect
tween(self, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 500,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
LK.getSound('creatureDie').play();
}
};
self.move = function (x, y, obj) {
if (Math.abs(x) < cardBoundsForZoom && Math.abs(y) < cardHeightBoundsForZoom) {
zoomCard(self);
showCardInfo(self);
if (!self.isInPlay) {
showPossibleMoves(self);
} //{1B_close}
} else {
// Mouse moved away from this card
if (hoveredCard === self) {
hideCardInfo();
if (!self.isInPlay) {
hidePossibleMoves();
} //{1G_close}
}
// Clear zoom if moving away from card
if (currentHoveredCardForZoom === self) {
clearZoom();
}
}
};
self.down = function (x, y, obj) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
// Prevent selecting creature/terrain cards if event cards are in hand
if (hasEventCardsInHand) {
//{1G_event}
return;
}
selectedCard = self;
draggedCard = self;
originalCardPosition = {
x: self.x,
y: self.y
};
showPossibleMoves(self);
}
};
// Add "Special" text in the center for any creature card with special: true
if (self.creatureData && self.creatureData.special) {
self.specialText = new Text2("Special", {
size: 32,
fill: 0xFF0000,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.specialText.anchor.set(0.5, 0.5);
self.specialText.x = 0;
self.specialText.y = 0;
self.addChild(self.specialText);
}
// Add "Tough" text under Special text if present
var nextYOffset = 40;
if (self.creatureData && self.creatureData.tough) {
self.toughText = new Text2("Tough", {
size: 24,
fill: 0xFFD700,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.toughText.anchor.set(0.5, 0.5);
self.toughText.x = 0;
self.toughText.y = self.specialText ? self.specialText.y + nextYOffset : nextYOffset;
self.addChild(self.toughText);
nextYOffset = self.toughText.y + nextYOffset;
} else if (self.specialText) {
nextYOffset = self.specialText.y + nextYOffset;
}
// Add "Squishy" text under Special/Tough text if present
if (self.creatureData && self.creatureData.squishy) {
self.squishyText = new Text2("Squishy", {
size: 24,
fill: 0xFF69B4,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.squishyText.anchor.set(0.5, 0.5);
self.squishyText.x = 0;
self.squishyText.y = nextYOffset;
self.addChild(self.squishyText);
nextYOffset = self.squishyText.y + 40;
}
// Add "Stinky" text under Special/Tough/Squishy text if present
if (self.creatureData && self.creatureData.stinky) {
self.stinkyText = new Text2("Stinky", {
size: 24,
fill: 0x228B22,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.stinkyText.anchor.set(0.5, 0.5);
self.stinkyText.x = 0;
self.stinkyText.y = nextYOffset;
self.addChild(self.stinkyText);
nextYOffset = self.stinkyText.y + 40;
}
// Add "Flying" and "Swimming" text under Special/Tough text if present
var flyingOrSwimmingYOffset = 40;
if (self.creatureData && self.creatureData.flying) {
self.flyingText = new Text2("Flying", {
size: 24,
fill: 0x00BFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.flyingText.anchor.set(0.5, 0.5);
self.flyingText.x = 0;
self.flyingText.y = nextYOffset;
self.addChild(self.flyingText);
nextYOffset = self.flyingText.y + flyingOrSwimmingYOffset;
}
if (self.creatureData && self.creatureData.swimming) {
self.swimmingText = new Text2("Swimming", {
size: 24,
fill: 0x1E90FF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.swimmingText.anchor.set(0.5, 0.5);
self.swimmingText.x = 0;
self.swimmingText.y = nextYOffset;
self.addChild(self.swimmingText);
nextYOffset = self.swimmingText.y + flyingOrSwimmingYOffset;
}
// Add "Efficient" text under Special/Flying/Swimming text if present
if (self.creatureData && self.creatureData.efficient) {
self.efficientText = new Text2("Efficient", {
size: 24,
fill: 0x32CD32,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.efficientText.anchor.set(0.5, 0.5);
self.efficientText.x = 0;
self.efficientText.y = nextYOffset;
self.addChild(self.efficientText);
nextYOffset = self.efficientText.y + 40;
}
// Add "Whale Chow" text for BH07 and BH13
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 = self.whaleChowText.y + 40;
}
// Add "Bully" text for creatures with bully property
if (self.creatureData && self.creatureData.bully) {
self.bullyText = new Text2("Bully", {
size: 24,
//{2U_bully1}
fill: 0xFF6347,
//{2U_bully2}
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
//{2U_bully3}
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 = self.bullyText.y + 40;
} //{2U_bully6}
// Add "Whale Chow Eater" text under Special 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 = self.whaleChowEaterText.y + 40;
}
return self;
});
var EventCard = Container.expand(function (eventData) {
var self = Container.call(this);
self.eventData = eventData || {
type: 'basic',
cardType: 'event',
level: 'Basic',
name: 'Event Card',
effect: 'environmental'
};
// Create event card visual
var eventAsset = self.attachAsset('eventCard', {
anchorX: 0.5,
anchorY: 0.5
});
// Add event text - placeholder "EVENT!!!" in red
var eventText = new Text2("EVENT!!!", {
size: 32,
fill: 0xFF0000
});
eventText.anchor.set(0.5, 0.5);
eventText.x = 0;
eventText.y = 0;
self.addChild(eventText);
// Add event level indicator
var levelText = new Text2(self.eventData.level, {
size: 16,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0);
levelText.x = 0;
levelText.y = -80;
self.addChild(levelText);
self.isInPlay = false;
self.move = function (x, y, obj) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
// Check if mouse is actually over this card
if (Math.abs(x) < cardBoundsForZoom && Math.abs(y) < cardHeightBoundsForZoom) {
// Trigger zoom on hover
zoomCard(self);
showCardInfo(self);
showPossibleMoves(self);
} else {
if (hoveredCard === self) {
hideCardInfo();
hidePossibleMoves();
}
// Clear zoom if moving away from card
if (currentHoveredCardForZoom === self) {
clearZoom();
}
}
}
};
self.down = function (x, y, obj) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
// Event cards are played immediately by clicking them
// Move to event discard pile
var handIndex = playerHand.indexOf(self);
if (handIndex !== -1) {
playerHand.splice(handIndex, 1);
}
// Remove from game
if (self.parent) {
self.parent.removeChild(self);
}
// Add event card data to event discard pile
var cardData = self.eventData;
if (!eventDiscardPile) {
eventDiscardPile = [];
}
eventDiscardPile.push(cardData);
// Update hand positions
updateHandPositions();
// Check if there are more event cards in hand
var moreEventCards = false;
for (var i = 0; i < playerHand.length; i++) {
if (playerHand[i].eventData) {
moreEventCards = true;
break;
}
}
// If no more event cards, progress to Noon phase
if (!moreEventCards) {
hasEventCardsInHand = false;
gamePhase = 'noon';
// Update UI
phaseText.setText("Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber);
// Discard any remaining cards in hand
var remainingCards = playerHand.slice();
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
}
}
};
return self;
});
var PlanetSlot = Container.expand(function (slotX, slotY) {
var self = Container.call(this);
self.slotX = slotX;
self.slotY = slotY;
self.terrainCard = null;
self.isHighlighted = false;
self.pinkBorder = null;
self.highlight = function () {
if (!self.isHighlighted && self.terrainCard) {
self.isHighlighted = true;
// Create pink border around the terrain card
if (!self.pinkBorder) {
self.pinkBorder = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
self.pinkBorder.tint = 0xFF1493; // Brighter deep pink color
self.pinkBorder.alpha = 0.9;
// Scale border to be larger than terrain card
self.pinkBorder.scaleX = self.terrainCard.scaleX * 1.3;
self.pinkBorder.scaleY = self.terrainCard.scaleY * 1.3;
self.pinkBorder.x = self.terrainCard.x;
self.pinkBorder.y = self.terrainCard.y;
// Add border behind terrain card
var terrainIndex = game.getChildIndex(self.terrainCard);
game.addChildAt(self.pinkBorder, terrainIndex);
}
}
};
self.unhighlight = function () {
if (self.isHighlighted) {
self.isHighlighted = false;
// Remove pink border
if (self.pinkBorder && self.pinkBorder.parent) {
self.pinkBorder.parent.removeChild(self.pinkBorder);
self.pinkBorder = null;
}
}
};
self.down = function (x, y, obj) {
if (selectedCard && selectedCard.creatureData && self.terrainCard && canPlaceCreatureOnTerrain(selectedCard, self.terrainCard)) {
placeCreatureOnStack(selectedCard, self.terrainCard, self.slotX, self.slotY);
}
};
return self;
});
var TerrainCard = Container.expand(function (terrainData) {
var self = Container.call(this);
self.terrainData = terrainData || {
type: 'basic',
level: 'Basic',
subtype: 'land',
landType: 'flat',
waterType: null,
climate: 'temperate'
};
// Create terrain card visual based on subtype and landType/waterType
var terrainAsset;
if (self.terrainData.subtype === 'land') {
if (self.terrainData.landType === 'flat') {
terrainAsset = self.attachAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.terrainData.landType === 'hills') {
terrainAsset = self.attachAsset('terrainLandHills', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.terrainData.landType === 'mountain') {
terrainAsset = self.attachAsset('terrainLandMountain', {
anchorX: 0.5,
anchorY: 0.5
});
}
} else if (self.terrainData.subtype === 'water') {
if (self.terrainData.waterType === 'sea') {
terrainAsset = self.attachAsset('terrainWaterSea', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.terrainData.waterType === 'fresh') {
terrainAsset = self.attachAsset('terrainWaterFresh', {
anchorX: 0.5,
anchorY: 0.5
});
}
}
// Add terrain type indicator strip at top
var terrainStripAsset = self.terrainData.level === 'Advanced' ? 'advancedTerrainStrip' : 'basicTerrainStrip';
var terrainStrip = self.attachAsset(terrainStripAsset, {
anchorX: 0.5,
anchorY: 0.5
});
terrainStrip.y = -147;
// For advanced terrain cards, tint the strip based on colorBand
if (self.terrainData.level === 'Advanced' && self.terrainData.colorBand) {
var colorMap = {
'grey': 0xC0C0C0,
'darkblue': 0x87CEEB,
'brown': 0xD2B48C,
'purple': 0x800080,
'lightblue': 0xADD8E6
};
if (colorMap[self.terrainData.colorBand]) {
terrainStrip.tint = colorMap[self.terrainData.colorBand];
}
}
// Add terrain type text (subtype and second subtype in the color band)
var terrainTypeText = '';
var textColor = 0xFFFFFF; // Default white for advanced terrain
if (self.terrainData.level === 'Advanced') {
// For advanced terrain: display subtype and specific terrain type
terrainTypeText = self.terrainData.subtype.toUpperCase() + ' - ';
terrainTypeText += (self.terrainData.landType || self.terrainData.waterType).toUpperCase();
} else {
// For basic terrain: just display the specific terrain type
terrainTypeText = (self.terrainData.landType || self.terrainData.waterType).toUpperCase();
textColor = 0x000000; // Black text for basic terrain
}
self.typeText = new Text2(terrainTypeText, {
size: 24,
fill: textColor,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif"
});
self.typeText.anchor.set(0.5, 0.5);
self.typeText.x = 0;
self.typeText.y = -147;
self.addChild(self.typeText);
// Add climate requirement display for advanced terrain cards (top right under color band with colored circles)
if (self.terrainData.level === 'Advanced' && self.terrainData.climateRequirement && self.terrainData.climateRequirement !== 'any') {
var climateDisplay = self.terrainData.climateRequirement;
var climateArray = [];
if (climateDisplay.indexOf('/') !== -1) {
// Multiple climate requirements - split them
climateArray = climateDisplay.split('/');
} else {
// Single climate requirement
climateArray = [climateDisplay];
}
// Create colored circles with climate letters
var circleSize = 30;
var circleSpacing = 35;
var startX = 100;
var startY = -115;
for (var c = 0; c < climateArray.length; c++) {
var climateType = climateArray[c].trim();
var circleColor = 0xFFFFFF;
var letterChar = '';
if (climateType.indexOf('hot') !== -1) {
circleColor = 0xFF4444;
letterChar = 'H';
} else if (climateType.indexOf('cold') !== -1) {
circleColor = 0x4444FF;
letterChar = 'C';
} else if (climateType.indexOf('temperate') !== -1) {
circleColor = 0xFFFF44;
letterChar = 'T';
}
// Create circle background
var climateCircle = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.08,
scaleY: 0.08
});
climateCircle.tint = circleColor;
climateCircle.x = startX + c * circleSpacing;
climateCircle.y = startY;
self.addChild(climateCircle);
// Add letter text on circle
var climateLetterText = new Text2(letterChar, {
size: 16,
fill: 0x000000,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif"
});
climateLetterText.anchor.set(0.5, 0.5);
climateLetterText.x = startX + c * circleSpacing;
climateLetterText.y = startY;
self.addChild(climateLetterText);
}
}
// Add "Special" text in the center for any card with special: true
if (self.terrainData && self.terrainData.special) {
self.specialText = new Text2("Special", {
size: 32,
fill: 0xFF0000,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.specialText.anchor.set(0.5, 0.5);
self.specialText.x = 0;
self.specialText.y = 0;
self.addChild(self.specialText);
}
// Add card ID at center of bottom for advanced terrain cards
if (self.terrainData.level === 'Advanced' && self.terrainData.id) {
self.cardIdText = new Text2(self.terrainData.id, {
size: 20,
fill: 0xFFFFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif"
});
self.cardIdText.anchor.set(0.5, 0.5);
self.cardIdText.x = 0;
self.cardIdText.y = 140; // Bottom center of card
self.addChild(self.cardIdText);
}
// Climate will be displayed as row background instead of on individual cards
self.gridX = -1;
self.gridY = -1;
self.creatureStack = [];
self.move = function (x, y, obj) {
if (Math.abs(x) < 75 && Math.abs(y) < 100) {
// Trigger zoom on hover
zoomCard(self);
showCardInfo(self);
if (!self.isInPlay) {
showPossibleMoves(self);
} //{5q_close}
} else {
// Mouse moved away from this card
if (hoveredCard === self) {
hideCardInfo();
if (!self.isInPlay) {
hidePossibleMoves();
} //{5v_close}
}
// Clear zoom if moving away from card
if (currentHoveredCardForZoom === self) {
clearZoom();
}
}
};
self.down = function (x, y, obj) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
// Prevent selecting creature/terrain cards if event cards are in hand
if (hasEventCardsInHand) {
//{3H_event}
return;
}
selectedCard = self;
draggedCard = self;
originalCardPosition = {
x: self.x,
y: self.y
};
showPossibleMoves(self);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a252f
});
/****
* Game Code
****/
// 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 all cards in hand for hover
for (var i = 0; i < playerHand.length; i++) {
var card = playerHand[i];
// Get card bounds based on card type
var halfWidth = 80;
var halfHeight = 110;
// Check if mouse is within card bounds
if (Math.abs(card.x - x) < halfWidth && Math.abs(card.y - y) < halfHeight) {
cardUnderMouse = card;
break;
}
}
// Check all cards in play (creatures and terrain on board)
if (!cardUnderMouse) {
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain) {
// Check creatures in stack first (top creature is most visible)
if (terrain.creatureStack && terrain.creatureStack.length > 0) {
for (var c = terrain.creatureStack.length - 1; c >= 0; c--) {
var creature = terrain.creatureStack[c];
var creatureHalfWidth = 80;
var creatureHalfHeight = 110;
if (Math.abs(creature.x - x) < creatureHalfWidth && Math.abs(creature.y - y) < creatureHalfHeight) {
cardUnderMouse = creature;
break;
}
}
}
if (cardUnderMouse) break;
}
// Check terrain card if no creature found
if (!cardUnderMouse && terrain) {
var terrainHalfWidth = 75;
var terrainHalfHeight = 100;
if (Math.abs(terrain.x - x) < terrainHalfWidth && Math.abs(terrain.y - y) < terrainHalfHeight) {
cardUnderMouse = terrain;
}
}
}
if (cardUnderMouse) break;
}
}
// Decide what to do based on result
if (cardUnderMouse && cardUnderMouse !== currentlyHoveredCard) {
// Mouse over a new card
if (currentlyHoveredCard) {
clearZoom();
}
zoomCard(cardUnderMouse);
showCardInfo(cardUnderMouse);
showPossibleMoves(cardUnderMouse);
currentlyHoveredCard = cardUnderMouse;
} else if (!cardUnderMouse && currentlyHoveredCard) {
// Mouse moved off all cards
clearZoom();
hideCardInfo();
hidePossibleMoves();
currentlyHoveredCard = null;
}
}
var currentlyHoveredCard = null;
game.move = function (x, y, obj) {
currentMouseX = x;
currentMouseY = y;
// Call centralized mouse move handler for zoom
onMouseMove(x, y);
if (draggedCard) {
draggedCard.x = x;
draggedCard.y = y;
// Ensure dragged card is always on top
game.removeChild(draggedCard);
game.addChild(draggedCard);
// Highlights are already shown from the card's down event
} else if (draggedLinkMarker && gamePhase === 'dusk' && linkAdjustmentMode) {
// Handle link marker dragging - highlight valid targets only while holding
var carnivore = draggedLinkMarker.carnivore;
highlightValidLinkTargets(carnivore);
// Create temporary visual line from carnivore to cursor
// Remove any existing temp line
if (game.tempLinkLine && game.tempLinkLine.parent) {
game.tempLinkLine.parent.removeChild(game.tempLinkLine);
}
// Create new temp line
var tempLine = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.01,
scaleY: 1
});
tempLine.tint = 0xFFFF00; // Yellow temp line
tempLine.alpha = 0.5;
var dx = x - carnivore.x;
var dy = y - carnivore.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
tempLine.x = carnivore.x + dx / 2;
tempLine.y = carnivore.y + dy / 2;
tempLine.rotation = angle;
tempLine.scaleY = distance / 336;
game.addChild(tempLine);
game.tempLinkLine = tempLine;
} else {
// Check if mouse is over hand area, if not, hide card info
var handY = 2400;
var inHandArea = y > handY - 150 && y < handY + 150;
if (!inHandArea) {
hideCardInfo();
hidePossibleMoves();
}
}
};
// Game state variables
var currentRound = 0;
var maxRounds = 5;
var cardsDrawnThisTurn = 0;
var maxCardsPerTurn = 3;
var planetBoard = [];
var planetSlots = [];
var playerHand = [];
var maxHandSize = 3;
var mainDeck = [];
var basicTerrainPool = [];
var selectedCard = null;
var draggedCard = null;
var originalCardPosition = null;
var scryTokenActive = false; // Start without scry token
var scryCards = []; // Cards in scry mode
var scryMode = false; // Whether scry is active
var creaturesWentExtinct = false; // Track if creatures went extinct this round
var cardInfoText = null;
var hoveredCard = null;
var drawPhase = 'initial'; // 'initial', 'forced'
var cardsDrawnInPhase = [];
var discardPile = [];
var eventDiscardPile = []; // Discard pile specifically for event cards
var linkLines = []; // Visual link lines between carnivores and herbivores
var gamePhase = 'dawn'; // Track current game phase: 'dawn', 'draw', 'noon', 'dusk', 'night', 'end'
var turnNumber = 1;
var draggedLinkMarker = null;
var draggedLinkCarnivore = null;
var linkHighlights = [];
var eventCardsToPlay = [];
var mustPlayCard = null;
var basicEventDeck = [];
var advancedEventDeck = [];
var phaseTransitionTimer = null;
var scryDeniedNextRound = false;
var hasEventCardsInHand = false;
// Planet layout: 2-4-6-4-2 formation
var planetLayout = [2, 4, 6, 4, 2];
var planetWidth = 6; // Max cards in any row
var planetHeight = 5; // Number of rows
// UI Elements
var roundText = new Text2("Round: " + currentRound, {
size: 48,
fill: 0xFFFFFF
});
roundText.anchor.set(0.5, 0);
LK.gui.top.addChild(roundText);
roundText.y = 100;
// Add a second round display text for compatibility with code using roundDisplayText
var roundDisplayText = new Text2("Round: " + currentRound, {
size: 36,
fill: 0xFFFFFF
});
roundDisplayText.anchor.set(0.5, 0);
LK.gui.top.addChild(roundDisplayText);
roundDisplayText.y = 160;
var deckCountText = new Text2("Deck: " + mainDeck.length, {
size: 36,
fill: 0xFFFFFF
});
deckCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(deckCountText);
deckCountText.x = 120;
deckCountText.y = 150;
var discardCountText = new Text2("Discard: 0", {
size: 36,
fill: 0xFFFFFF
});
discardCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(discardCountText);
discardCountText.x = 120;
discardCountText.y = 190;
var basicEventCountText = new Text2("Basic Events: 10", {
size: 28,
fill: 0x00FF00
});
basicEventCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(basicEventCountText);
basicEventCountText.x = 120;
basicEventCountText.y = 230;
var advancedEventCountText = new Text2("Advanced Events: 10", {
size: 28,
fill: 0xFF6600
});
advancedEventCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(advancedEventCountText);
advancedEventCountText.x = 120;
advancedEventCountText.y = 260;
var graveyardCountText = new Text2("Graveyard: 0", {
size: 28,
//{4f_graveyard}
fill: 0xFFFFFF
}); //{4g_graveyard}
graveyardCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(graveyardCountText);
graveyardCountText.x = 120;
graveyardCountText.y = 290;
var eventDiscardCountText = new Text2("Event Discard: 0", {
size: 28,
fill: 0x9932CC
});
eventDiscardCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(eventDiscardCountText);
eventDiscardCountText.x = 120;
eventDiscardCountText.y = 320;
var graveyard = [];
var at15PlayedLastTurn = false; // Track if AT15 was played to enforce draw 4 next turn
var at18Active = false; // Track if AT18 special is being processed
var at18CarnivoreList = []; // Carnivores available for AT18 selection
var scryText = new Text2("Scry", {
size: 32,
fill: 0xFFFFFF
});
scryText.anchor.set(0, 0);
scryText.tint = 0x888888; // Start gray/inactive
LK.gui.topRight.addChild(scryText);
scryText.x = -300;
scryText.y = 190;
// Add scry button functionality
scryText.down = function () {
if (scryTokenActive && !scryMode && mainDeck.length >= 3) {
activateScry();
}
};
// Initialize basic terrain pool (23 cards total)
// BT01-05: Land - Flatland
// BT06-10: Land - Hills
// BT11-14: Land - Mountains
// BT15-20: Water - Sea
// BT21-23: Water - Fresh Water
function createBasicTerrainPool() {
basicTerrainPool = [];
// BT01-05: Land - Flatland (5 cards)
for (var i = 0; i < 5; i++) {
basicTerrainPool.push({
type: 'basic',
level: 'Basic',
id: 'BT' + (i + 1).toString().padStart(2, '0'),
subtype: 'land',
landType: 'flat',
climate: null // Will be assigned during setup
});
}
// BT06-10: Land - Hills (5 cards)
for (var i = 0; i < 5; i++) {
basicTerrainPool.push({
type: 'basic',
level: 'Basic',
id: 'BT' + (i + 6).toString().padStart(2, '0'),
subtype: 'land',
landType: 'hills',
climate: null // Will be assigned during setup
});
}
// BT11-14: Land - Mountains (4 cards)
for (var i = 0; i < 4; i++) {
basicTerrainPool.push({
type: 'basic',
level: 'Basic',
id: 'BT' + (i + 11).toString().padStart(2, '0'),
subtype: 'land',
landType: 'mountain',
climate: null // Will be assigned during setup
});
}
// BT15-20: Water - Sea (6 cards)
for (var i = 0; i < 6; i++) {
basicTerrainPool.push({
type: 'basic',
level: 'Basic',
id: 'BT' + (i + 15).toString().padStart(2, '0'),
subtype: 'water',
waterType: 'sea',
climate: null // Will be assigned during setup
});
}
// BT21-23: Water - Fresh Water (3 cards)
for (var i = 0; i < 3; i++) {
basicTerrainPool.push({
type: 'basic',
level: 'Basic',
id: 'BT' + (i + 21).toString().padStart(2, '0'),
subtype: 'water',
waterType: 'fresh',
climate: null // Will be assigned during setup
});
}
// Shuffle terrain pool
for (var i = basicTerrainPool.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = basicTerrainPool[i];
basicTerrainPool[i] = basicTerrainPool[j];
basicTerrainPool[j] = temp;
}
}
// Setup planet board with terrain cards
function setupPlanet() {
// Create 2D array for planet board
for (var y = 0; y < planetHeight; y++) {
planetBoard[y] = [];
planetSlots[y] = [];
for (var x = 0; x < planetWidth; x++) {
planetBoard[y][x] = null;
planetSlots[y][x] = null;
}
}
// Place terrain cards according to 2-4-6-4-2 layout
var terrainIndex = 0;
var startY = 400;
var cardSpacing = 320;
// Add climate row backgrounds first
for (var row = 0; row < planetLayout.length; row++) {
var climate;
if (row === 0 || row === 4) {
climate = 'cold'; // Rows 1 & 5
} else if (row === 1 || row === 3) {
climate = 'temperate'; // Rows 2 & 4
} else {
climate = 'hot'; // Row 3
}
// Create climate row background
var climateRowAsset = 'climateRow' + climate.charAt(0).toUpperCase() + climate.slice(1);
var climateRowBg = LK.getAsset(climateRowAsset, {
anchorX: 0.5,
anchorY: 0.5
});
climateRowBg.x = 1024; // Center of screen
climateRowBg.y = startY + row * 420;
climateRowBg.alpha = 0.3; // Semi-transparent background
game.addChild(climateRowBg);
// Add climate label text
var climateLabel = new Text2(climate.toUpperCase(), {
size: 32,
fill: 0x000000,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif"
});
climateLabel.anchor.set(0.5, 0.5);
climateLabel.rotation = -Math.PI / 2; // Rotate 90 degrees counter-clockwise to make vertical
climateLabel.x = 50; // Move left to make fully visible
climateLabel.y = startY + row * 420;
game.addChild(climateLabel);
}
for (var row = 0; row < planetLayout.length; row++) {
var cardsInRow = planetLayout[row];
var startX = 1024 - cardsInRow * cardSpacing / 2 + cardSpacing / 2 + 50; // Shift cards right by 50px
// Assign climate based on row
var climate;
if (row === 0 || row === 4) {
climate = 'cold'; // Rows 1 & 5
} else if (row === 1 || row === 3) {
climate = 'temperate'; // Rows 2 & 4
} else {
climate = 'hot'; // Row 3
}
for (var col = 0; col < cardsInRow && terrainIndex < basicTerrainPool.length; col++) {
// Assign climate to terrain card
var terrainData = basicTerrainPool[terrainIndex];
terrainData.climate = climate;
// Create terrain card
var terrainCard = new TerrainCard(terrainData);
terrainCard.x = startX + col * cardSpacing;
terrainCard.y = startY + row * 420;
terrainCard.gridX = col;
terrainCard.gridY = row;
game.addChild(terrainCard);
// Store in board
var boardX = Math.floor((planetWidth - cardsInRow) / 2) + col;
planetBoard[row][boardX] = terrainCard;
// Create slot for this position
var slot = new PlanetSlot(boardX, row);
slot.terrainCard = terrainCard;
planetSlots[row][boardX] = slot;
terrainIndex++;
}
}
}
// Create main deck with advanced terrain cards and creature cards
function createInitialDeck() {
mainDeck = [];
// Add 20 Advanced Terrain Cards (AT01-AT20)
// AT01-02: Fresh Water - Grey band, no climate requirements
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'water',
waterType: 'fresh',
id: 'AT01',
colorBand: 'grey',
climateRequirement: 'any',
name: 'Advanced Fresh Water'
});
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'water',
waterType: 'fresh',
id: 'AT02',
colorBand: 'grey',
climateRequirement: 'any',
name: 'Advanced Fresh Water'
});
// AT03-07: Sea Water - Dark Blue band
// AT03: Hot, AT04: Cold, AT05: Temperate, AT06: Hot or Temperate, AT07: Special
var seaCardConfigs = [{
id: 'AT03',
climate: 'hot',
special: false
}, {
id: 'AT04',
climate: 'cold',
special: false
}, {
id: 'AT05',
climate: 'temperate',
special: false
}, {
id: 'AT06',
climate: 'hot/temperate',
special: false
}, {
id: 'AT07',
climate: 'any',
special: true
}];
for (var i = 0; i < seaCardConfigs.length; i++) {
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'water',
waterType: 'sea',
id: seaCardConfigs[i].id,
colorBand: 'darkblue',
climateRequirement: seaCardConfigs[i].climate,
special: seaCardConfigs[i].special,
name: 'Advanced Sea Water'
});
}
// AT08-12: Flat Land - Brown band
// AT08: Hot, AT09: Temperate, AT10: Cold, AT11: Temperate or Cold, AT12: Temperate or Hot + Special
var flatCardConfigs = [{
id: 'AT08',
climate: 'hot',
special: false
}, {
id: 'AT09',
climate: 'temperate',
special: false
}, {
id: 'AT10',
climate: 'cold',
special: false
}, {
id: 'AT11',
climate: 'temperate/cold',
special: false
}, {
id: 'AT12',
climate: 'temperate/hot',
special: true
}];
for (var i = 0; i < flatCardConfigs.length; i++) {
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'land',
landType: 'flat',
id: flatCardConfigs[i].id,
colorBand: 'brown',
climateRequirement: flatCardConfigs[i].climate,
special: flatCardConfigs[i].special,
name: 'Advanced Flat Land'
});
}
// AT13-17: Hills - Purple band
// AT13: Temperate, AT14: Cold, AT15: Hot + Special, AT16: Temperate or Cold, AT17: Temperate or Hot
var hillsCardConfigs = [{
id: 'AT13',
climate: 'temperate',
special: false
}, {
id: 'AT14',
climate: 'cold',
special: false
}, {
id: 'AT15',
climate: 'hot',
special: true
}, {
id: 'AT16',
climate: 'temperate/cold',
special: false
}, {
id: 'AT17',
climate: 'temperate/hot',
special: false
}];
for (var i = 0; i < hillsCardConfigs.length; i++) {
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'land',
landType: 'hills',
id: hillsCardConfigs[i].id,
colorBand: 'purple',
climateRequirement: hillsCardConfigs[i].climate,
special: hillsCardConfigs[i].special,
name: 'Advanced Hills'
});
}
// AT18-20: Mountains - Light Blue band
// AT18: No climate requirements + Special, AT19: Temperate or Cold, AT20: Temperate or Hot
var mountainCardConfigs = [{
id: 'AT18',
climate: 'any',
special: true
}, {
id: 'AT19',
climate: 'temperate/cold',
special: false
}, {
id: 'AT20',
climate: 'temperate/hot',
special: false
}];
for (var i = 0; i < mountainCardConfigs.length; i++) {
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'land',
landType: 'mountain',
id: mountainCardConfigs[i].id,
colorBand: 'lightblue',
climateRequirement: mountainCardConfigs[i].climate,
special: mountainCardConfigs[i].special,
name: 'Advanced Mountains'
});
}
// Add 33 Basic Herbivore Cards (BH01-BH33)
var basicHerbivoreConfigs = [{
id: 'BH01',
name: 'Pond Turtle',
waterType: 'fresh',
colorBand: 'grey',
//{bh01_terrain}
climate: 'temperate',
//{bh01_climate}
special: false //{bh01_special}
}, {
id: 'BH02',
name: 'Crayfish',
waterType: 'fresh',
colorBand: 'grey',
climate: 'cold',
special: false
}, {
id: 'BH03',
name: 'Newt',
waterType: 'fresh',
colorBand: 'grey',
climate: 'temperate/cold',
special: true
}, {
id: 'BH04',
name: 'Toad',
waterType: 'fresh',
colorBand: 'grey',
climate: 'temperate/hot',
special: true
}, {
id: 'BH05',
name: 'Mud Skipper',
waterType: 'fresh',
colorBand: 'grey',
climate: 'temperate/hot',
special: true
}, {
id: 'BH06',
name: 'Tropical Crab',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'hot',
special: false
}, {
id: 'BH07',
name: 'Plankton',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'cold',
special: true
}, {
id: 'BH08',
name: 'Limpet',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate',
special: false
}, {
id: 'BH09',
name: 'Red Crab',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'hot',
special: false
}, {
id: 'BH10',
name: 'Jelly Fish',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'cold',
special: true
}, {
id: 'BH11',
name: 'Mussel',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate',
special: true
}, {
id: 'BH12',
name: 'Whelk',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/cold',
special: false
}, {
id: 'BH13',
name: 'Krill',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/hot',
special: true
}, {
id: 'BH14',
name: 'Hare',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate',
special: false
}, {
id: 'BH15',
name: 'Meerkat',
landType: 'flat',
colorBand: 'brown',
climate: 'hot',
special: false
}, {
id: 'BH16',
name: 'Pheasant',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate',
special: true
}, {
id: 'BH17',
name: 'Tortoise',
landType: 'flat',
colorBand: 'brown',
climate: 'hot',
special: false
}, {
id: 'BH18',
name: 'Goose',
landType: 'flat',
colorBand: 'brown',
climate: 'cold',
special: true
}, {
id: 'BH19',
name: 'Pig',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate/hot',
special: false
}, {
id: 'BH20',
name: 'Platypus',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate/cold',
special: true
}, {
id: 'BH21',
name: 'Rabbit',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate',
special: false
}, {
id: 'BH22',
name: 'Chicken',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate',
special: false
}, {
id: 'BH23',
name: 'Ice Squirrel',
landType: 'hills',
colorBand: 'purple',
climate: 'cold',
special: false
}, {
id: 'BH24',
name: 'Pea Fowl',
landType: 'hills',
colorBand: 'purple',
climate: 'hot',
special: false
}, {
id: 'BH25',
name: 'Mole-Rat',
landType: 'hills',
colorBand: 'purple',
climate: 'hot',
special: false
}, {
id: 'BH26',
name: 'Guinnea pig',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate/cold',
special: true
}, {
id: 'BH27',
name: 'Wombat',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate/hot',
special: true
}, {
id: 'BH28',
name: 'Marmoset',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate',
special: false
}, {
id: 'BH29',
name: 'Porcupine',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate',
special: false
}, {
id: 'BH30',
name: 'Skunk',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'hot',
special: true
}, {
id: 'BH31',
name: 'Shaggy Sheep',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'cold',
special: true
}, {
id: 'BH32',
name: 'Worms',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate/hot',
special: true
}, {
id: 'BH33',
name: 'Giant Snail',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate/cold',
special: true
}];
for (var i = 0; i < basicHerbivoreConfigs.length; i++) {
var config = basicHerbivoreConfigs[i];
var subtype = config.waterType ? 'water' : 'land';
var waterType = config.waterType || null;
var landType = config.landType || null;
mainDeck.push({
type: 'basic',
level: 'Basic',
cardType: 'creature',
dietType: 'herbivore',
name: config.name,
id: config.id,
subtype: subtype,
waterType: waterType,
landType: landType,
colorBand: config.colorBand,
climateRequirement: config.climate,
special: config.special,
flying: config.id === 'BH18' || config.id === 'BH16' ? true : false,
swimming: config.id === 'BH02' || config.id === 'BH07' || config.id === 'BH10' || config.id === 'BH13' || config.id === 'BH20' ? true : false,
tough: config.id === 'BH31' ? true : false,
squishy: config.id === 'BH03' || config.id === 'BH27' ? true : false,
stinky: config.id === 'BH04' || config.id === 'BH30' ? true : false,
efficient: config.id === 'BC03' ? true : false,
// Efficient for BC03 (Puffin)
whaleFood: config.id === 'BH07' || config.id === 'BH13' ? true : false,
whaleChow: config.id === 'BH07' || config.id === 'BH13' ? true : false,
bully: config.id === 'BH07' || config.id === 'BH13' ? true : false
});
}
// Add 8 Advanced Carnivore Cards (AC01-AC08)
var advancedCarnivoreConfigs = [{
id: 'AC01',
name: 'Pike',
waterType: 'fresh',
colorBand: 'grey',
climate: 'any',
special: true
}, {
id: 'AC02',
name: 'Great White Shark',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'any',
special: true,
bully: true //{c4_bully}
}, {
id: 'AC03',
name: 'Whale',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'any',
special: true
}, {
id: 'AC04',
name: 'Snakes!',
landType: 'any',
colorBand: 'brown',
climate: 'any',
special: true
}, {
id: 'AC05',
name: 'Bear',
landType: 'any',
colorBand: 'brown',
climate: 'any',
special: true
}, {
id: 'AC06',
name: 'Buzzard',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate/cold',
special: true
}, {
id: 'AC07',
name: 'Tiger',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate/hot',
special: true
}, {
id: 'AC08',
name: 'Leopard',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate/hot',
special: true
}];
for (var i = 0; i < advancedCarnivoreConfigs.length; i++) {
var config = advancedCarnivoreConfigs[i];
var subtype = config.waterType ? 'water' : 'land';
var waterType = config.waterType || null;
var landType = config.landType || null;
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'creature',
dietType: 'carnivore',
name: config.name,
id: config.id,
subtype: subtype,
waterType: waterType,
landType: landType,
colorBand: config.colorBand,
climateRequirement: config.climate,
special: config.special,
flying: config.id === 'AC06' ? true : false,
swimming: config.id === 'AC01' || config.id === 'AC02' || config.id === 'AC03' ? true : false,
efficient: config.id === 'AC02' || config.id === 'AC04' || config.id === 'AC06' ? true : false,
// Efficient for AC02, AC04, AC06
isWhale: config.id === 'AC03' ? true : false,
bully: config.id === 'AC02' ? true : false //{c9_bully}
});
}
// Add 10 Basic Carnivore Cards (BC01-BC10)
var basicCarnivoreConfigs = [{
id: 'BC01',
name: 'King Fisher',
waterType: 'fresh',
colorBand: 'grey',
climate: 'any',
special: true
}, {
id: 'BC02',
name: 'Penguin',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/cold',
special: false
}, {
id: 'BC03',
name: 'Puffin',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/cold',
special: true,
fussy: true //{d7_fussy}
}, {
id: 'BC04',
name: 'Tuna',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/hot',
special: true,
hunterHunter: true //{dc_hh}
}, {
id: 'BC05',
name: 'Eagle',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'any',
special: true
}, {
id: 'BC06',
name: 'Chameleon',
landType: 'any',
colorBand: 'brown',
climate: 'any',
special: true
}, {
id: 'BC07',
name: 'Bat',
landType: 'flat',
colorBand: 'brown',
climate: 'hot',
special: true
}, {
//{9D_bc08}
id: 'BC08',
name: 'Albatross',
landType: 'hills',
colorBand: 'purple',
//{9E_bc08}
climate: 'cold',
//{9F_bc08}
special: true //{9G_bc08}
}, {
//{9D_bc09}
id: 'BC09',
name: 'Pendo',
landType: 'flat',
colorBand: 'brown',
//{9E_bc09}
climate: 'temperate/cold',
//{9F_bc09}
special: false //{9G_bc09}
}, {
//{9D_bc10}
id: 'BC10',
name: 'Stoat',
landType: 'hills',
colorBand: 'purple',
//{9E_bc10}
climate: 'hot/temperate',
//{9F_bc10}
special: false //{9G_bc10}
}];
for (var i = 0; i < basicCarnivoreConfigs.length; i++) {
var config = basicCarnivoreConfigs[i];
var subtype = config.waterType ? 'water' : 'land';
var waterType = config.waterType || null;
var landType = config.landType || null;
mainDeck.push({
type: 'basic',
level: 'Basic',
cardType: 'creature',
dietType: 'carnivore',
name: config.name,
id: config.id,
subtype: subtype,
waterType: waterType,
landType: landType,
colorBand: config.colorBand,
climateRequirement: config.climate,
special: config.special,
flying: config.id === 'BC01' || config.id === 'BC03' || config.id === 'BC05' || config.id === 'BC07' || config.id === 'BC08' || config.id === 'BC09' ? true : false,
swimming: config.id === 'BC04' ? true : false,
efficient: config.id === 'BC03' ? true : false,
// Efficient for BC03 (Puffin)//{dU_comment}
hunterHunter: config.id === 'BC04' ? true : false,
// Hunter-Hunter for BC04 (Tuna)
fussy: config.id === 'BC03' ? true : false //{dU_fussy}
// Fussy for BC03 (Puffin)
});
}
// Add 12 Advanced Herbivore Cards (AH01-AH12)
var advancedHerbivoreConfigs = [{
id: 'AH01',
name: 'Salmon',
waterType: 'fresh',
colorBand: 'grey',
//{ah01_terrain}
climate: 'any',
//{ah01_climate}
special: true //{ah01_special}
}, {
id: 'AH02',
name: 'Lobster',
waterType: 'sea',
colorBand: 'darkblue',
//{ah02_terrain}
climate: 'temperate',
//{ah02_climate}
special: false //{ah02_special}
}, {
id: 'AH03',
name: 'Herring',
waterType: 'sea',
colorBand: 'darkblue',
//{ah03_terrain}
climate: 'temperate/cold',
//{ah03_climate}
special: true //{ah03_special}
}, {
id: 'AH04',
name: 'Maceral',
waterType: 'sea',
colorBand: 'darkblue',
//{ah04_terrain}
climate: 'temperate/hot',
//{ah04_climate}
special: true //{ah04_special}
}, {
id: 'AH05',
name: 'Wildebeest',
landType: 'flat',
colorBand: 'brown',
//{ah05_terrain}
climate: 'hot',
//{ah05_climate}
special: false //{ah05_special}
}, {
id: 'AH06',
name: 'Moose',
landType: 'flat',
colorBand: 'brown',
//{ah06_terrain}
climate: 'cold',
//{ah06_climate}
special: false //{ah06_special}
}, {
id: 'AH07',
name: 'Gazelle',
landType: 'flat',
colorBand: 'brown',
//{ah07_terrain}
climate: 'temperate',
//{ah07_climate}
special: false //{ah07_special}
}, {
id: 'AH08',
name: 'Ibex',
landType: 'hills',
colorBand: 'purple',
//{ah08_terrain}
climate: 'hot',
//{ah08_climate}
special: false //{ah08_special}
}, {
id: 'AH09',
name: 'Arctic Hare',
landType: 'hills',
colorBand: 'purple',
//{ah09_terrain}
climate: 'cold',
//{ah09_climate}
special: false //{ah09_special}
}, {
id: 'AH10',
name: 'Elephant',
landType: 'hills',
colorBand: 'purple',
//{ah10_terrain}
climate: 'temperate',
//{ah10_climate}
special: true //{ah10_special}
}, {
id: 'AH11',
name: 'Mountain Goat',
landType: 'mountain',
colorBand: 'lightblue',
//{ah11_terrain}
climate: 'hot',
//{ah11_climate}
special: true //{ah11_special}
}, {
id: 'AH12',
name: 'Llama',
landType: 'mountain',
colorBand: 'lightblue',
//{ah12_terrain}
climate: 'temperate',
//{ah12_climate}
special: true //{ah12_special}
}]; //{ah12_end}
for (var i = 0; i < advancedHerbivoreConfigs.length; i++) {
var config = advancedHerbivoreConfigs[i];
var subtype = config.waterType ? 'water' : 'land';
var waterType = config.waterType || null;
var landType = config.landType || null;
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'creature',
dietType: 'herbivore',
name: config.name,
id: config.id,
subtype: subtype,
waterType: waterType,
landType: landType,
colorBand: config.colorBand,
climateRequirement: config.climate,
special: config.special,
swimming: config.id === 'AC01' || config.id === 'AC02' || config.id === 'AC03' || config.id === 'AH01' || config.id === 'AH02' || config.id === 'AH03' || config.id === 'AH04' ? true : false,
tough: config.id === 'AH10' ? true : false,
efficient: config.id === 'AC02' || config.id === 'AC04' || config.id === 'AC06' ? true : false,
// Efficient for AC02, AC04, AC06
whaleFood: config.id === 'AH01' ? false : config.id === 'AH02' || config.id === 'AH03' || config.id === 'AH04' ? false : false
});
}
// Shuffle deck
shuffleDeck();
}
function shuffleDeck() {
for (var i = mainDeck.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = mainDeck[i];
mainDeck[i] = mainDeck[j];
mainDeck[j] = temp;
}
}
function shuffleEventDeck(eventDeck) {
for (var i = eventDeck.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = eventDeck[i];
eventDeck[i] = eventDeck[j];
eventDeck[j] = temp;
}
}
function showCardInfo(card) {
hoveredCard = card;
// Remove existing info text if any
if (cardInfoText && cardInfoText.parent) {
cardInfoText.parent.removeChild(cardInfoText);
}
var infoString = "";
if (card.creatureData) {
// Creature card information
var creature = card.creatureData;
if (creature) {
infoString = creature.name + " | Level: " + creature.level + " " + creature.dietType;
if (creature && creature.terrainRequirement && creature.terrainRequirement !== 'any') {
infoString += " | Terrain: " + creature.terrainRequirement;
}
if (creature && creature.climateRequirement && creature.climateRequirement !== 'any') {
infoString += " | Climate: " + creature.climateRequirement;
}
// Add special rules text for Tough
if (creature && creature.tough) {
infoString += " | Tough: Creature requires 3 or more extinction markers before it must become extinct";
}
if (creature && creature.squishy) {
infoString += " | Squishy: Creature requires only 1 or more extinction markers before it must become extinct";
}
if (creature && creature.stinky) {
infoString += " | Stinky: Discard all non-Stinky creatures. Phew!";
}
if (creature && creature.efficient) {
infoString += " | Efficient: This creature can make diagonal links as well as adjacent ones";
}
if (creature && creature.whaleFood) {
infoString += " | Whale chow!: This creature can only be linked to whales";
}
if (creature && creature.id === 'AC03') {
infoString += " | Whale: This creature can link to Krill and Plankton";
}
if (creature && creature.hunterHunter) {
infoString += " | Hunter-Hunter: This creature can only link to Carnivore cards NOT Herbivores!";
} //{fl_hh}
if (creature && creature.fussy) {
infoString += " | Fussy: This creature can only link to Basic Herbivore cards NOT Advanced Herbivores!";
} //{fl_fussy}
if (creature && creature.bully) {
infoString += " | Bully: This creature can create links to Basic Carnivores as well as Basic Herbivores!";
} //{fl_bully}
}
} else if (card.terrainData) {
// Terrain card information
var terrain = card.terrainData;
if (terrain) {
infoString = terrain.name || terrain.level + " " + (terrain.landType || terrain.waterType);
infoString += " | Level: " + terrain.level + " terrain";
if (terrain.climateRequirement && terrain.climateRequirement !== 'any') {
infoString += " | Climate Required: " + terrain.climateRequirement;
}
// Add special rules text for AT15
if (terrain.id === 'AT15') {
infoString += " | Ancient: Draw 4 cards next turn";
} //{ej_special}
// Add special rules text for AT18
if (terrain.id === 'AT18') {
infoString += " | Hostile: Put 1 Carnivore card from the discard pile onto the top of the play deck if any are present";
} //{ej_special_AT18}
}
}
// Create info text at bottom of screen
cardInfoText = new Text2(infoString, {
size: 28,
fill: 0xFFFFFF
});
cardInfoText.anchor.set(0.5, 1);
cardInfoText.x = 1024; // Center of screen
cardInfoText.y = 2700; // Bottom of screen
game.addChild(cardInfoText);
}
function hideCardInfo() {
if (cardInfoText && cardInfoText.parent) {
cardInfoText.parent.removeChild(cardInfoText);
cardInfoText = null;
}
hoveredCard = null;
}
function zoomCard(card) {
// If a different card was previously zoomed, clear it
if (currentHoveredCardForZoom && currentHoveredCardForZoom !== card) {
clearZoom();
}
// If card is already zoomed, do nothing
if (currentHoveredCardForZoom === card && zoomedCard) {
return;
}
// Clear existing zoomed card
if (zoomedCard && zoomedCard.parent) {
zoomedCard.parent.removeChild(zoomedCard);
zoomedCard = null;
}
// Track current hovered card
currentHoveredCardForZoom = card;
// Create a clone of the card for zooming
var clonedCard;
if (card.creatureData) {
clonedCard = new CreatureCard(card.creatureData);
} else if (card.terrainData) {
clonedCard = new TerrainCard(card.terrainData);
} else if (card.eventData) {
clonedCard = new EventCard(card.eventData);
} else {
return;
}
// 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 = 680;
// 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;
}
}
}
}
}
return false;
}
function discardCards(cards) {
for (var i = 0; i < cards.length; i++) {
var card = cards[i];
// Remove from hand
var handIndex = playerHand.indexOf(card);
if (handIndex !== -1) {
playerHand.splice(handIndex, 1);
}
// Remove from game
if (card.parent) {
card.parent.removeChild(card);
}
// Extract raw card data and add to discard pile
var cardData = card.creatureData || card.terrainData || card.eventData;
discardPile.push(cardData);
}
updateHandPositions();
// Clear zoom if hand is now empty
clearZoomIfNoCards(); //{ip_new}
}
function drawCard() {
if (mainDeck.length === 0) {
endRound();
return null;
}
// Enforce maximum hand size of 3
if (playerHand.length >= maxHandSize) {
return null; // Hand is full
}
if (drawPhase === 'complete') {
return null; // Draw phase already complete
}
var cardData = mainDeck.pop();
var card;
// Create appropriate card type based on cardType
if (cardData.cardType === 'terrain') {
card = new TerrainCard(cardData);
// Scale terrain cards in hand to match creature cards
card.scaleX = 0.7;
card.scaleY = 0.7;
// Scale terrain cards in hand to match creature cards
card.scaleX = 0.7;
card.scaleY = 0.7;
} else {
// Default to creature card
card = new CreatureCard(cardData);
}
if (drawPhase === 'initial') {
// Initial draw phase - draw up to 3 cards
playerHand.push(card);
cardsDrawnInPhase.push(card);
cardsDrawnThisTurn++;
if (cardsDrawnInPhase.length >= 3) {
// Check if any of the 3 cards have valid placements
if (!hasValidPlacements(cardsDrawnInPhase)) {
// Discard all 3 cards and enter forced draw phase
discardCards(cardsDrawnInPhase);
cardsDrawnInPhase = [];
drawPhase = 'forced';
// Continue drawing in forced phase
return drawCard();
} else {
// At least one card has valid placement, end draw phase
drawPhase = 'complete';
}
}
} else if (drawPhase === 'forced') {
// Forced draw phase - draw one card at a time until playable
if (hasValidPlacements([card])) {
// This card can be played, add to hand and must be played
playerHand.push(card);
drawPhase = 'complete';
// Mark this card as must play
card.mustPlay = true;
} else {
// Card cannot be played, discard it and draw another
discardCards([card]);
return drawCard();
}
} else {
return null; // Draw phase complete
}
// Position card in hand
updateHandPositions();
game.addChild(card);
// Update UI
deckCountText.setText("Deck: " + mainDeck.length);
LK.getSound('cardDraw').play();
// Check for Stinky effect on newly drawn card
checkAndProcessStinkyEffect(); //{j8_stinky}
return card;
}
function startDrawPhase() {
if (mainDeck.length === 0) {
endRound();
return;
}
drawPhase = 'initial';
cardsDrawnInPhase = [];
cardsDrawnThisTurn = 0;
// Draw initial 3 cards
for (var i = 0; i < 3; i++) {
if (mainDeck.length > 0) {
drawCard();
}
}
// Check for Stinky creatures after drawing initial 3 cards
checkAndProcessStinkyEffect();
}
function checkAndProcessStinkyEffect() {
// Check if any creature in hand has Stinky attribute
var hasStinkyCreature = false;
var stinkyCreature = null;
for (var i = 0; i < playerHand.length; i++) {
//{stinky_check_1}
var card = playerHand[i];
if (card.creatureData && card.creatureData.stinky) {
//{stinky_check_2}
hasStinkyCreature = true;
stinkyCreature = card;
break;
} //{stinky_check_3}
} //{stinky_check_4}
// If Stinky creature found, discard all non-stinky creatures
if (hasStinkyCreature) {
//{stinky_process_1}
var toDiscard = [];
for (var j = 0; j < playerHand.length; j++) {
//{stinky_process_2}
var c = playerHand[j];
// Discard only if it has creatureData AND does NOT have stinky property true
if (c.creatureData && !c.creatureData.stinky) {
//{stinky_process_3}
toDiscard.push(c);
} //{stinky_process_4}
} //{stinky_process_5}
// Execute discard
if (toDiscard.length > 0) {
//{stinky_process_6}
discardCards(toDiscard);
} //{stinky_process_7}
} //{stinky_process_8}
// Check for event cards in hand after stinky processing and set flag
hasEventCardsInHand = false;
for (var k = 0; k < playerHand.length; k++) {
if (playerHand[k].eventData) {
hasEventCardsInHand = true;
break;
}
}
}
function checkAndShowEventCardDebugBox() {
// Check if any event cards are in hand
var eventCardsInHand = [];
for (var i = 0; i < playerHand.length; i++) {
//{debug_event_1}
if (playerHand[i].eventData) {
//{debug_event_2}
eventCardsInHand.push(playerHand[i]);
} //{debug_event_3}
} //{debug_event_4}
// If event cards found, show debug box
if (eventCardsInHand.length > 0) {
//{debug_event_5}
showEventCardDebugBox();
} //{debug_event_6}
} //{debug_event_7}
function showEventCardDebugBox() {
// Create debug notification box
var eventDebugBox = LK.getAsset('terrainLandFlat', {
//{debug_event_8}
anchorX: 0.5,
//{debug_event_9}
anchorY: 0.5 //{debug_event_10}
}); //{debug_event_11}
eventDebugBox.tint = 0xA020F0; // Purple for event cards
eventDebugBox.alpha = 0.95; //{debug_event_12}
eventDebugBox.scaleX = 2.0;
eventDebugBox.scaleY = 1.0;
eventDebugBox.x = 1024;
eventDebugBox.y = 1000;
game.addChild(eventDebugBox); //{debug_event_13}
// Create debug text
var debugText = new Text2("Event card/s drawn, click to resolve", {
//{debug_event_14}
size: 32,
//{debug_event_15}
fill: 0xFFFFFF //{debug_event_16}
}); //{debug_event_17}
debugText.anchor.set(0.5, 0.5);
debugText.x = 0;
debugText.y = 0;
eventDebugBox.addChild(debugText); //{debug_event_18}
// Make box clickable to resolve event cards
eventDebugBox.down = function () {
//{debug_event_19}
// Discard all cards from current hand
var cardsToDiscard = playerHand.slice(); //{debug_event_20}
discardCards(cardsToDiscard); //{debug_event_21}
// Remove debug box
if (eventDebugBox.parent) {
//{debug_event_22}
eventDebugBox.parent.removeChild(eventDebugBox); //{debug_event_23}
} //{debug_event_24}
// Progress to next phase (Noon/Dusk depending on current phase)
gamePhase = 'dusk'; //{debug_event_25}
processDuskPhase(); //{debug_event_26}
}; //{debug_event_27}
} //{debug_event_28}
function updateHandPositions() {
var handY = 2400;
var handStartX = 1024 - playerHand.length * 200 / 2 + 100;
for (var i = 0; i < playerHand.length; i++) {
var card = playerHand[i];
if (!card.isInPlay) {
tween(card, {
x: handStartX + i * 200,
y: handY
}, {
duration: 300
});
}
}
}
function canPlaceTerrainOnTerrain(advancedTerrainCard, basicTerrainCard) {
if (!advancedTerrainCard || !basicTerrainCard) {
return false;
}
var advancedTerrain = advancedTerrainCard.terrainData;
var basicTerrain = basicTerrainCard.terrainData;
// Check if this is actually an advanced terrain card
if (!advancedTerrain || advancedTerrain.level !== 'Advanced') {
return false;
}
// Check if target is a basic terrain card
if (!basicTerrain || basicTerrain.level !== 'Basic') {
return false;
}
// Check if terrain types match (land on land, water on water)
if (advancedTerrain.subtype !== basicTerrain.subtype) {
return false;
}
// Check if specific terrain types match
if (advancedTerrain.subtype === 'land') {
if (advancedTerrain.landType !== basicTerrain.landType) {
return false;
}
} else if (advancedTerrain.subtype === 'water') {
if (advancedTerrain.waterType !== basicTerrain.waterType) {
return false;
}
}
// Check climate requirements
if (advancedTerrain.climateRequirement && advancedTerrain.climateRequirement !== 'any') {
var climateMatches = false;
if (advancedTerrain.climateRequirement.indexOf('/') !== -1) {
// Handle multiple climate requirements (e.g., "temperate/hot")
var allowedClimates = advancedTerrain.climateRequirement.split('/');
for (var k = 0; k < allowedClimates.length; k++) {
if (allowedClimates[k] === basicTerrain.climate) {
climateMatches = true;
break;
}
}
} else {
// Single climate requirement
climateMatches = advancedTerrain.climateRequirement === basicTerrain.climate;
}
if (!climateMatches) {
return false;
}
}
return true;
}
function canPlaceCreatureOnTerrain(creatureCard, terrainCard) {
if (!creatureCard || !terrainCard) {
return false;
}
var creature = creatureCard.creatureData;
var terrain = terrainCard.terrainData;
// Check if this is actually a creature card
if (!creature) {
return false;
}
// Check terrain type matching based on creature's subtype
if (creature.subtype === 'water') {
// Creature requires water terrain
if (terrain.subtype !== 'water') {
return false;
}
// Check specific water type matching - creature waterType must match terrain waterType
if (creature.waterType && terrain.waterType && creature.waterType !== terrain.waterType) {
return false;
}
} else if (creature.subtype === 'land') {
// Creature requires land terrain
if (terrain.subtype !== 'land') {
return false;
}
// Check specific land type matching - creature landType must match terrain landType
if (creature.landType && terrain.landType && creature.landType !== terrain.landType) {
return false;
}
}
// If creature has no subtype or terrain type, it cannot be placed
if (!creature.subtype) {
return false;
}
// Check climate requirements - use basic terrain climate if advanced terrain is placed on basic
var terrainClimate = terrain.climate;
// If this is an advanced terrain on basic terrain, use the basic terrain's climate
if (terrainCard.basicTerrainUnderneath && terrainCard.basicTerrainUnderneath.terrainData) {
terrainClimate = terrainCard.basicTerrainUnderneath.terrainData.climate;
}
if (creature.climateRequirement && creature.climateRequirement !== 'any') {
var climateMatches = false;
if (creature.climateRequirement.indexOf('/') !== -1) {
// Handle multiple climate requirements (e.g., "temperate/hot")
var allowedClimates = creature.climateRequirement.split('/');
for (var k = 0; k < allowedClimates.length; k++) {
if (allowedClimates[k] === terrainClimate) {
climateMatches = true;
break;
}
}
} else {
// Single climate requirement
climateMatches = creature.climateRequirement === terrainClimate;
}
if (!climateMatches) {
return false;
}
}
// Advanced creatures can only be placed on advanced terrain
if (creature.level === 'Advanced') {
if (terrain.level !== 'Advanced') {
return false;
}
}
// Basic herbivores can only be placed on basic terrain (but advanced terrain counts as valid if it's on basic terrain)
if (creature.level === 'Basic' && creature.dietType === 'herbivore') {
// Allow placement on advanced terrain that's placed on basic terrain, or directly on basic terrain
if (terrain.level !== 'Basic' && !terrainCard.basicTerrainUnderneath) {
return false;
}
}
// Check creature stacking rules - verify top card in stack before allowing placement
if (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 - advanced creatures cannot be placed on empty terrain
if (creature.level === 'Advanced') {
return false; // Advanced creatures require existing creatures to stack on
}
}
return true;
}
function placeTerrainOnTerrain(advancedTerrainCard, basicTerrainCard, gridX, gridY) {
if (!canPlaceTerrainOnTerrain(advancedTerrainCard, basicTerrainCard)) {
return false;
}
// Remove card from hand
var handIndex = playerHand.indexOf(advancedTerrainCard);
if (handIndex !== -1) {
playerHand.splice(handIndex, 1);
}
// Set up advanced terrain card
advancedTerrainCard.gridX = gridX;
advancedTerrainCard.gridY = gridY;
advancedTerrainCard.isInPlay = true;
// Copy creature stack from basic terrain to advanced terrain
advancedTerrainCard.creatureStack = basicTerrainCard.creatureStack || [];
// Position advanced terrain card on top of basic terrain and adopt its dimensions
var targetX = basicTerrainCard.x;
var targetY = basicTerrainCard.y;
var targetScaleX = basicTerrainCard.scaleX;
var targetScaleY = basicTerrainCard.scaleY;
tween(advancedTerrainCard, {
x: targetX,
y: targetY,
scaleX: targetScaleX,
scaleY: targetScaleY
}, {
duration: 300
});
// Keep basic terrain underneath but update board references to advanced terrain
planetBoard[gridY][gridX] = advancedTerrainCard;
planetSlots[gridY][gridX].terrainCard = advancedTerrainCard;
// Store reference to basic terrain underneath
advancedTerrainCard.basicTerrainUnderneath = basicTerrainCard;
// Add advanced terrain to game at specific z-index (above basic terrain but below creatures)
var basicTerrainIndex = game.getChildIndex(basicTerrainCard);
game.addChildAt(advancedTerrainCard, basicTerrainIndex + 1);
// Check for invalid links when terrain changes (shouldn't affect creature types, but safety check)
if (advancedTerrainCard.creatureStack.length > 0) {
var topCreature = advancedTerrainCard.creatureStack[advancedTerrainCard.creatureStack.length - 1];
checkAndRemoveInvalidLinks(gridX, gridY, topCreature);
}
// Update creature positions if any exist and ensure they stay on top
for (var i = 0; i < advancedTerrainCard.creatureStack.length; i++) {
var creature = advancedTerrainCard.creatureStack[i];
// Store current position to prevent unwanted movement
var currentX = creature.x;
var currentY = creature.y;
// Remove and re-add creature to ensure it's on top
game.removeChild(creature);
game.addChild(creature);
// Keep creature at its current position - no animation needed
creature.x = currentX;
creature.y = currentY;
}
// Re-render all link lines to ensure they appear above the newly placed terrain
for (var linkIdx = 0; linkIdx < linkLines.length; linkIdx++) {
var linkLine = linkLines[linkIdx];
if (linkLine.parent) {
game.removeChild(linkLine);
game.addChild(linkLine);
}
}
// Update hand positions
updateHandPositions();
// Clear selection
selectedCard = null;
draggedCard = null;
// Reset draw phase after successful placement
if (advancedTerrainCard.mustPlay) {
drawPhase = 'complete';
}
// Check if AT15 was played and set flag for next turn
if (advancedTerrainCard.terrainData && advancedTerrainCard.terrainData.id === 'AT15') {
at15PlayedLastTurn = true;
}
// Check if AT18 was played and trigger special
if (advancedTerrainCard.terrainData && advancedTerrainCard.terrainData.id === 'AT18') {
processAT18Special();
} //{if_AT18}
// Check if this was the last event card or must play card
if (eventCardsToPlay.length > 0) {
//{ig}</antml>
// Remove this card from event cards to play
var eventIndex = eventCardsToPlay.indexOf(advancedTerrainCard);
if (eventIndex !== -1) {
eventCardsToPlay.splice(eventIndex, 1);
}
// If no more event cards to play, clear mustPlayCard and check for phase progression
if (eventCardsToPlay.length === 0) {
mustPlayCard = null;
// Update event cards tracking
hasEventCardsInHand = false;
// Discard remaining non-event cards and progress to dusk
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
if (!playerHand[i].eventData) {
remainingCards.push(playerHand[i]);
}
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
}
} else if (advancedTerrainCard.mustPlay || mustPlayCard === advancedTerrainCard) {
// This was a must play card, clear it and progress to dusk
mustPlayCard = null;
// Discard remaining cards
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
remainingCards.push(playerHand[i]);
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
} else {
// Regular card played, discard remaining cards and progress to dusk
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
remainingCards.push(playerHand[i]);
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
}
// Reset turn
cardsDrawnThisTurn = 0;
LK.getSound('cardPlace').play();
// Show adjust links button after card placement
showAdjustLinksButton();
return true;
}
function checkAndRemoveInvalidLinks(gridX, gridY, newTopCreature) {
// Check all links that involve creatures at this position
var invalidLinks = [];
// Check links FROM carnivores at this position
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
if (creature.creatureData.dietType === 'carnivore') {
// Check each active link from this carnivore
for (var j = creature.activeLinks.length - 1; j >= 0; j--) {
var link = creature.activeLinks[j];
var targetHerbivore = link.target;
// If the new top creature is a carnivore and the target is now covered, link becomes invalid
if (newTopCreature && newTopCreature.creatureData.dietType === 'carnivore' && targetHerbivore.gridX === gridX && targetHerbivore.gridY === gridY) {
invalidLinks.push({
carnivore: creature,
herbivore: targetHerbivore
});
}
}
}
}
}
// Check links TO herbivores at this position (if new creature is carnivore, herbivores below become invalid targets)
if (newTopCreature && newTopCreature.creatureData.dietType === 'carnivore') {
// Find all links pointing to herbivores at this position that are now covered
for (var linkIndex = linkLines.length - 1; linkIndex >= 0; linkIndex--) {
var linkLine = linkLines[linkIndex];
if (linkLine.herbivore.gridX === gridX && linkLine.herbivore.gridY === gridY) {
// This herbivore is now covered by a carnivore, so links to it are invalid
invalidLinks.push({
carnivore: linkLine.carnivore,
herbivore: linkLine.herbivore
});
}
}
}
// Remove invalid links with fall-off animation
for (var k = 0; k < invalidLinks.length; k++) {
var invalidLink = invalidLinks[k];
animateLinkFallOff(invalidLink.carnivore, invalidLink.herbivore);
}
}
function animateLinkFallOff(carnivore, herbivore) {
// Find the link line to animate
var linkLineToRemove = null;
for (var i = 0; i < linkLines.length; i++) {
if (linkLines[i].carnivore === carnivore && linkLines[i].herbivore === herbivore && !linkLines[i].isFallingOff) {
linkLineToRemove = linkLines[i];
break;
}
}
if (linkLineToRemove) {
// Mark link as falling off immediately to prevent duplicate animations
linkLineToRemove.isFallingOff = true;
// Remove from arrays immediately to clear logical ghost link
removeLink(carnivore, herbivore);
// Animate the link falling off
tween(linkLineToRemove, {
alpha: 0,
scaleY: linkLineToRemove.scaleY * 0.1,
y: linkLineToRemove.y + 50
}, {
duration: 500,
onFinish: function onFinish() {
// Remove the visual line after animation
if (linkLineToRemove.parent) linkLineToRemove.parent.removeChild(linkLineToRemove);
}
});
} else {
// No visual line found, just remove the link immediately
removeLink(carnivore, herbivore);
}
}
function placeCreatureOnStack(creatureCard, terrainCard, gridX, gridY) {
if (!canPlaceCreatureOnTerrain(creatureCard, terrainCard)) {
return false;
}
// Remove card from hand
var handIndex = playerHand.indexOf(creatureCard);
if (handIndex !== -1) {
playerHand.splice(handIndex, 1);
}
// Check for invalid links before placing the new creature
checkAndRemoveInvalidLinks(gridX, gridY, creatureCard);
// When ANY carnivore is played onto another creature, remove all links to and from the creature being covered
if (creatureCard.creatureData.dietType === 'carnivore' && terrainCard.creatureStack.length > 0) {
var creatureBelow = terrainCard.creatureStack[terrainCard.creatureStack.length - 1];
// Remove all links FROM the creature being covered (if it's a carnivore)
if (creatureBelow.creatureData.dietType === 'carnivore') {
for (var linkIdx = creatureBelow.activeLinks.length - 1; linkIdx >= 0; linkIdx--) {
animateLinkFallOff(creatureBelow, creatureBelow.activeLinks[linkIdx].target);
}
}
// Remove all links TO the creature being covered (if it's a herbivore or carnivore)
for (var linkIdx = linkLines.length - 1; linkIdx >= 0; linkIdx--) {
if (linkLines[linkIdx].herbivore === creatureBelow || linkLines[linkIdx].carnivore === creatureBelow && creatureBelow.creatureData.dietType === 'carnivore') {
animateLinkFallOff(linkLines[linkIdx].carnivore, linkLines[linkIdx].herbivore);
}
}
}
// If placing an advanced herbivore on top of a basic herbivore, remove all links to the basic herbivore below
if (creatureCard.creatureData.dietType === 'herbivore' && creatureCard.creatureData.level === 'Advanced') {
if (terrainCard.creatureStack.length > 0) {
var basicHerbivoreBelow = terrainCard.creatureStack[terrainCard.creatureStack.length - 1];
if (basicHerbivoreBelow.creatureData.dietType === 'herbivore' && basicHerbivoreBelow.creatureData.level === 'Basic') {
// Remove all links pointing to the basic herbivore below
for (var linkIdx = linkLines.length - 1; linkIdx >= 0; linkIdx--) {
if (linkLines[linkIdx].herbivore === basicHerbivoreBelow) {
animateLinkFallOff(linkLines[linkIdx].carnivore, basicHerbivoreBelow);
}
}
}
}
}
// Push existing creatures down in the stack (move them south)
var stackOffset = 20;
for (var i = 0; i < terrainCard.creatureStack.length; i++) {
var existingCreature = terrainCard.creatureStack[i];
// Clear extinction markers when another creature is placed on top
existingCreature.extinctionMarkers = 0;
existingCreature.updateExtinctionMarkers();
tween(existingCreature, {
y: existingCreature.y + stackOffset
}, {
duration: 200
});
}
// Add new creature to top of stack
terrainCard.creatureStack.push(creatureCard);
creatureCard.gridX = gridX;
creatureCard.gridY = gridY;
creatureCard.isInPlay = true;
// Position new creature at the top position (where first creature would go)
var targetX = terrainCard.x;
var colorBarHeight = 42;
var targetY = terrainCard.y - terrainCard.height * terrainCard.scaleY / 2 + colorBarHeight + 150; // Position even lower, just below the color bar with 150px offset
// Calculate scale needed to match terrain card dimensions completely
var terrainWidth = 252; // Terrain card asset width
var terrainHeight = 336; // Terrain card asset height
var creatureWidth = 160; // Creature card asset width
var creatureHeight = 220; // Creature card asset height
// Scale to match terrain dimensions exactly, but reduce Y scale by half the color bar height with moderate scaling
var targetScaleX = terrainWidth / creatureWidth * terrainCard.scaleX;
var targetScaleY = (terrainHeight - colorBarHeight / 2) / creatureHeight * terrainCard.scaleY * 0.95; // Moderate 5% reduction to sit flush with color bar
// Start with small scale and animate up to target scale like advanced terrain
creatureCard.scaleX = 0.1;
creatureCard.scaleY = 0.1;
tween(creatureCard, {
x: targetX,
y: targetY,
scaleX: targetScaleX,
scaleY: targetScaleY
}, {
duration: 300
});
// Apply creature effects
applyCreatureEffect(creatureCard);
// Create link markers for carnivores when placed
creatureCard.createLinkMarkers();
// Update hand positions
updateHandPositions();
// Clear selection
selectedCard = null;
draggedCard = null;
// Reset draw phase after successful placement
if (creatureCard.mustPlay) {
drawPhase = 'complete';
}
// Check if this was the last event card or must play card
if (eventCardsToPlay.length > 0) {
// Remove this card from event cards to play
var eventIndex = eventCardsToPlay.indexOf(creatureCard);
if (eventIndex !== -1) {
eventCardsToPlay.splice(eventIndex, 1);
}
// If no more event cards to play, clear mustPlayCard and check for phase progression
if (eventCardsToPlay.length === 0) {
mustPlayCard = null;
// Update event cards tracking
hasEventCardsInHand = false;
// Discard remaining non-event cards and progress to dusk
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
if (!playerHand[i].eventData) {
remainingCards.push(playerHand[i]);
}
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
}
} else if (creatureCard.mustPlay || mustPlayCard === creatureCard) {
// This was a must play card, clear it and progress to dusk
mustPlayCard = null;
// Discard remaining cards
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
remainingCards.push(playerHand[i]);
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
} else {
// Regular card played, discard remaining cards and progress to dusk
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
remainingCards.push(playerHand[i]);
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
}
// Reset turn
cardsDrawnThisTurn = 0;
LK.getSound('cardPlace').play();
// Show adjust links button after card placement
showAdjustLinksButton();
return true;
}
function applyCreatureEffect(creatureCard) {
var creature = creatureCard.creatureData;
// Stack effects can be implemented here without health mechanics
}
function getAdjacentTerrains(gridX, gridY) {
var neighbors = [];
var directions = [{
x: -1,
y: 0
}, {
x: 1,
y: 0
}, {
x: 0,
y: -1
}, {
x: 0,
y: 1
}];
for (var i = 0; i < directions.length; i++) {
var newX = gridX + directions[i].x;
var newY = gridY + directions[i].y;
if (newX >= 0 && newX < planetWidth && newY >= 0 && newY < planetHeight) {
neighbors.push(planetBoard[newY][newX]);
}
}
return neighbors;
}
function createLink(carnivore, herbivore) {
if (!carnivore || !herbivore) {
return false;
}
if (carnivore.creatureData.dietType !== 'carnivore') {
return false;
}
if (herbivore.creatureData.dietType !== 'herbivore') {
return false;
}
// Check if carnivore and herbivore are adjacent
if (!areCreaturesAdjacent(carnivore, herbivore)) {
return false;
}
// Additional check: ensure we're linking to the top creature only
var herbivoreGridX = herbivore.gridX;
var herbivoreGridY = herbivore.gridY;
var terrain = planetBoard[herbivoreGridY][herbivoreGridX];
if (terrain && terrain.creatureStack.length > 0) {
var topCreature = terrain.creatureStack[terrain.creatureStack.length - 1];
if (topCreature !== herbivore) {
return false; // Can only link to top creature in stack
}
}
// For basic carnivores, check if already linked to this herbivore
if (carnivore.creatureData.level === 'Basic') {
for (var i = 0; i < carnivore.activeLinks.length; i++) {
if (carnivore.activeLinks[i].target === herbivore) {
return false; // Basic carnivores can only have one link per herbivore
}
}
}
// Check if this is a valid link target considering all special rules
if (!isValidLinkTarget(carnivore, herbivore)) {
return false; // Cannot link due to special rules (Whale food, etc)
} //{mL_new}
// Find an unlinked marker
var availableMarker = null;
for (var i = 0; i < carnivore.linkMarkers.length; i++) {
if (!carnivore.linkMarkers[i].isLinked) {
availableMarker = carnivore.linkMarkers[i];
break;
}
}
if (!availableMarker) {
return false;
} // No available link markers
// Create the link
availableMarker.isLinked = true;
availableMarker.targetHerbivore = herbivore;
availableMarker.tint = 0x00FF00; // Green for linked
// Add to active links
carnivore.activeLinks.push({
marker: availableMarker,
target: herbivore
});
// Create visual link line
createLinkLine(carnivore, herbivore);
return true;
}
function removeLink(carnivore, herbivore) {
if (!carnivore || !herbivore) {
return false;
}
// Find and remove the link
for (var i = carnivore.activeLinks.length - 1; i >= 0; i--) {
var link = carnivore.activeLinks[i];
if (link.target === herbivore) {
link.marker.isLinked = false;
link.marker.targetHerbivore = null;
link.marker.tint = 0xFF0000; // Red for unlinked
carnivore.activeLinks.splice(i, 1);
// Remove visual link line
removeLinkLine(carnivore, herbivore);
return true;
}
}
return false;
}
function isValidLinkTarget(carnivore, herbivore) {
if (!carnivore || !herbivore) {
return false;
}
// Check Whale food restriction: only Whale (AC03) can link to Whale food creatures
if (herbivore.creatureData && herbivore.creatureData.whaleFood) {
if (carnivore.creatureData.id !== 'AC03') {
return false; // Cannot link to Whale food unless carnivore is Whale
}
}
// Check Hunter-Hunter restriction: can only link to Carnivores, not Herbivores
if (carnivore.creatureData && carnivore.creatureData.hunterHunter) {
if (herbivore.creatureData.dietType !== 'carnivore') {
return false; // Hunter-Hunter can only link to carnivores
} //{n4_hh}
} //{n4_hh_end}
// Check Fussy restriction: can only link to Basic Herbivores
if (carnivore.creatureData && carnivore.creatureData.fussy) {
if (herbivore.creatureData.level !== 'Basic' || herbivore.creatureData.dietType !== 'herbivore') {
return false; // Fussy can only link to Basic Herbivores
} //{n4_fussy}
} //{n4_fussy_end}
// Check Bully special: can link to both herbivores and basic carnivores
if (carnivore.creatureData && carnivore.creatureData.bully) {
// Bully can link to Herbivores (any level) OR Basic Carnivores
// If target is a carnivore, it must be Basic level
if (herbivore.creatureData.dietType === 'carnivore') {
if (herbivore.creatureData.level !== 'Basic') {
return false; // Bully can only link to Basic Carnivores, not Advanced
}
}
// Otherwise (Herbivore target or valid Basic Carnivore target), allow the link
return true;
}
return true;
}
function areCreaturesAdjacent(creature1, creature2) {
if (!creature1.isInPlay || !creature2.isInPlay) {
return false;
}
var dx = Math.abs(creature1.gridX - creature2.gridX);
var dy = Math.abs(creature1.gridY - creature2.gridY);
// Check if creature1 has Efficient attribute
var creature1HasEfficient = creature1.creatureData && creature1.creatureData.efficient;
// Adjacent means exactly one grid space away (orthogonal or diagonal if Efficient)
if (creature1HasEfficient) {
// Efficient creatures can link to adjacent (including diagonal) creatures
return dx === 1 && dy === 0 || dx === 0 && dy === 1 || dx === 1 && dy === 1;
} else {
// Non-Efficient creatures can only link orthogonally (up, down, left, right)
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
}
}
function createLinkLine(carnivore, herbivore) {
// 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 second link to avoid stacking
var offsetDistance = 0;
if (existingLinkCount === 1) {
// Second link: offset perpendicular to link direction
// Perpendicular direction is 90 degrees from the link angle
var perpendicularAngle = angle + Math.PI / 2;
offsetDistance = 30; // Offset distance in pixels
var offsetX = Math.cos(perpendicularAngle) * offsetDistance;
var offsetY = Math.sin(perpendicularAngle) * offsetDistance;
// Apply offset to line position
line.x += offsetX;
line.y += offsetY;
// Change second link line to a different color for visualization
line.tint = 0xFF00FF; // Magenta/pink for second link
}
game.addChild(line);
// Add X2 indicator if this is the second link
if (existingLinkCount === 1) {
var x2Indicator = new Text2("X2", {
size: 20,
fill: 0xFFFFFF
}); //{e4_x2}
x2Indicator.anchor.set(0.5, 0.5);
x2Indicator.x = 0;
x2Indicator.y = 0;
line.addChild(x2Indicator);
x2Indicator.lineOwner = line;
} //{e4_x2_end}
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 (topCreature.creatureData.dietType === 'herbivore' && areCreaturesAdjacent(carnivore, topCreature)) {
// Check if this is a valid link target considering all special rules
if (!isValidLinkTarget(carnivore, topCreature)) {
// Cannot link to this herbivore due to special rules
continue;
} //{nx_new}
var canCreateLink = false;
// For basic carnivores, check if already linked to this herbivore
if (carnivore.creatureData.level === 'Basic') {
var hasExistingLink = false;
for (var j = 0; j < carnivore.activeLinks.length; j++) {
if (carnivore.activeLinks[j].target === topCreature) {
hasExistingLink = true;
break;
}
}
if (!hasExistingLink) {
canCreateLink = true;
}
} else {
// Advanced carnivores can always create links if they have available markers
var hasAvailableMarker = false;
for (var k = 0; k < carnivore.linkMarkers.length; k++) {
if (!carnivore.linkMarkers[k].isLinked) {
hasAvailableMarker = true;
break;
}
}
if (hasAvailableMarker) {
canCreateLink = true;
}
}
if (canCreateLink) {
// Create highlight around this herbivore
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
// Use different colors for diagonal highlights if Efficient
if (carnivoreHasEfficient) {
var dx = Math.abs(carnivore.gridX - topCreature.gridX);
var dy = Math.abs(carnivore.gridY - topCreature.gridY);
if (dx === 1 && dy === 1) {
// Diagonal link for Efficient creature
highlight.tint = 0x00FFFF; // Cyan for diagonal
} else {
// Orthogonal link
highlight.tint = 0x00FF00; // Green for orthogonal
}
} else {
highlight.tint = 0x00FF00; // Green highlight
}
highlight.alpha = 0.3;
highlight.scaleX = topCreature.scaleX * 1.2;
highlight.scaleY = topCreature.scaleY * 1.2;
highlight.x = topCreature.x;
highlight.y = topCreature.y;
highlight.targetCreature = topCreature;
game.addChild(highlight);
linkHighlights.push(highlight);
}
}
}
}
}
}
function clearLinkHighlights() {
for (var i = 0; i < linkHighlights.length; i++) {
if (linkHighlights[i].parent) {
linkHighlights[i].parent.removeChild(linkHighlights[i]);
}
}
linkHighlights = [];
}
function updateLinkMarkerStates() {
// Update all link markers based on their state and game phase
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
// ONLY process the top creature in the stack - creatures underneath are not active
var isTopCreature = i === terrain.creatureStack.length - 1;
if (!isTopCreature) {
// Skip creatures that are underneath other creatures - they cannot interact with links
continue;
} //{f9_underneath}
if (creature.creatureData.dietType === 'carnivore') {
// Check if this carnivore will become extinct (insufficient links)
var willBecomeExtinct = creature.activeLinks.length < creature.linkRequirement;
for (var j = 0; j < creature.linkMarkers.length; j++) {
var marker = creature.linkMarkers[j];
if (gamePhase === 'dusk' && linkAdjustmentMode) {
// During dusk phase with adjustment mode active, make markers interactive
if (marker.isLinked) {
marker.tint = 0x00FF00; // Green for linked
// Make linked markers draggable to remove links
marker.down = function (x, y, obj) {
draggedLinkMarker = this;
draggedLinkCarnivore = this.carnivore;
};
} else {
// Check if this marker can be legally linked
var hasValidTargets = false;
var adjacentTerrains = getAdjacentTerrains(gridX, gridY);
for (var k = 0; k < adjacentTerrains.length; k++) {
var adjTerrain = adjacentTerrains[k];
if (adjTerrain && adjTerrain.creatureStack.length > 0) {
// Only consider the top creature in each adjacent stack
var topHerbivore = adjTerrain.creatureStack[adjTerrain.creatureStack.length - 1];
if (topHerbivore.creatureData.dietType === 'herbivore') {
// For basic carnivores, check if already linked to this herbivore
if (creature.creatureData.level === 'Basic') {
var hasExistingLink = false;
for (var m = 0; m < creature.activeLinks.length; m++) {
if (creature.activeLinks[m].target === topHerbivore) {
hasExistingLink = true;
break;
}
}
if (!hasExistingLink) {
hasValidTargets = true;
break;
}
} else {
// Advanced carnivores can always link if they have available markers
hasValidTargets = true;
break;
}
}
}
if (hasValidTargets) {
break;
}
}
if (hasValidTargets) {
marker.tint = 0xFFFF00; // Yellow for linkable
// Make unlinked but valid markers draggable
marker.down = function (x, y, obj) {
draggedLinkMarker = this;
draggedLinkCarnivore = this.carnivore;
};
} else {
marker.tint = 0x808080; // Gray for non-linkable
marker.down = function (x, y, obj) {
// Do nothing for non-linkable markers
};
// Add N/A text
if (!marker.naText) {
marker.naText = new Text2("N/A", {
size: 8,
fill: 0xFFFFFF
});
marker.naText.anchor.set(0.5, 0.5);
marker.naText.x = 0;
marker.naText.y = 0;
marker.addChild(marker.naText);
}
}
}
} else {
// Outside dusk phase or not in adjustment mode, markers are not interactive
if (marker.isLinked) {
marker.tint = 0x00FF00; // Green for linked
} else {
marker.tint = 0xFF0000; // Red for unlinked
}
// Remove down handler and N/A text if present
marker.down = function (x, y, obj) {};
if (marker.naText && marker.naText.parent) {
marker.naText.parent.removeChild(marker.naText);
marker.naText = null;
}
}
// Flash markers if carnivore will become extinct
if (willBecomeExtinct) {
// Stop any existing flash animation
tween.stop(marker, {
alpha: true
});
// Start flashing animation
tween(marker, {
alpha: 0.3
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(marker, {
alpha: 1
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart the flash cycle if still extinct
if (creature.activeLinks.length < creature.linkRequirement) {
updateLinkMarkerStates();
}
}
});
}
});
} else {
// Stop flashing if no longer about to become extinct
tween.stop(marker, {
alpha: true
});
marker.alpha = 1;
}
}
}
}
}
}
}
}
function autoCreateLinks() {
// Auto-create mandatory links for carnivores
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var carnivore = terrain.creatureStack[i];
if (carnivore.creatureData.dietType === 'carnivore') {
// Try to create links for this carnivore
while (carnivore.activeLinks.length < carnivore.linkRequirement) {
var linkCreated = false;
// Find adjacent herbivores
var adjacentTerrains = getAdjacentTerrains(gridX, gridY);
for (var j = 0; j < adjacentTerrains.length && !linkCreated; j++) {
var adjTerrain = adjacentTerrains[j];
if (adjTerrain && adjTerrain.creatureStack.length > 0) {
// Only consider the top creature in each adjacent stack
var topHerbivore = adjTerrain.creatureStack[adjTerrain.creatureStack.length - 1];
if (topHerbivore.creatureData.dietType === 'herbivore') {
// Check if we can create a link
var hasExistingLink = false;
for (var l = 0; l < carnivore.activeLinks.length; l++) {
if (carnivore.activeLinks[l].target === topHerbivore) {
hasExistingLink = true;
break;
}
}
if (!hasExistingLink && createLink(carnivore, topHerbivore)) {
linkCreated = true;
}
}
}
}
if (!linkCreated) {
break;
} // No more links possible
}
}
}
}
}
}
}
function processLinkConsequences() {
// Hide the adjust links button when leaving dusk phase
hideAdjustLinksButton();
// NOTE: Extinction markers for herbivores are only processed during night phase
// This function only handles cleanup, not marker assignment
}
function processNightPhase() {
// Show visual cue that night phase is processing
showPhaseTransition("Night Phase - Checking links...", function () {
// Add extinction markers to carnivores without sufficient links
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
// ONLY process the top creature in the stack - creatures underneath are not active
var isTopCreature = i === terrain.creatureStack.length - 1;
if (!isTopCreature) {
// Skip creatures that are underneath other creatures
continue;
} //{ho_underneath}
if (creature.creatureData.dietType === 'carnivore') {
// For carnivores: check if they have sufficient links AT THIS MOMENT
if (creature.activeLinks.length < creature.linkRequirement) {
creature.extinctionMarkers += 1; // Only 1 marker per turn regardless of shortage
creature.updateExtinctionMarkers();
}
} else if (creature.creatureData.dietType === 'herbivore') {
// For herbivores: ONLY add extinction marker if it has 0 markers AND has excess links
// Once a herbivore has 1+ extinction markers, do NOT add more during Night phase
if (creature.extinctionMarkers === 0) {
//{t8_new}
// Count links pointing to them AT THIS MOMENT ONLY
var linksToThisHerbivore = 0;
for (var j = 0; j < linkLines.length; j++) {
//{t9_new}
if (linkLines[j].herbivore === creature) {
//{ta_new}
linksToThisHerbivore++;
} //{tb_new}
} //{tc_new}
// Add extinction marker ONLY if excess links exist
var excessLinks = linksToThisHerbivore - creature.safeLinks;
if (excessLinks > 0) {
//{td_new}
// Add exactly 1 marker if this herbivore has any excess links
creature.extinctionMarkers += 1;
creature.updateExtinctionMarkers(); //{te_new}
} //{tf_new}
} //{tg_new}
}
}
}
}
}
// Auto-progress to end turn phase
gamePhase = 'end';
processEndTurnPhase();
});
}
function processExtinctions() {
// Remove creatures with 2 or more extinction markers
var extinctCreatures = [];
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = terrain.creatureStack.length - 1; i >= 0; i--) {
var creature = terrain.creatureStack[i];
var extinctionThreshold = 2;
if (creature.creatureData && creature.creatureData.tough) {
extinctionThreshold = 3;
} else if (creature.creatureData && creature.creatureData.squishy) {
extinctionThreshold = 1;
}
if (creature.extinctionMarkers >= extinctionThreshold) {
extinctCreatures.push(creature);
terrain.creatureStack.splice(i, 1);
// Tween all creatures that were physically positioned below the dead creature UP by 20 pixels
for (var j = 0; j < i; j++) {
var creatureBelow = terrain.creatureStack[j];
tween(creatureBelow, {
y: creatureBelow.y - 20
}, {
duration: 200
});
}
creaturesWentExtinct = true; // Mark that creatures went extinct this round
scryDeniedNextRound = true; // Deny scry for next round when extinction occurs
// Clear extinction markers when creature leaves play
creature.extinctionMarkers = 0;
creature.updateExtinctionMarkers();
// Remove all links involving this creature with fall-off animation
if (creature.creatureData.dietType === 'carnivore') {
for (var j = creature.activeLinks.length - 1; j >= 0; j--) {
animateLinkFallOff(creature, creature.activeLinks[j].target);
}
} else {
// Remove links from carnivores to this herbivore with fall-off animation
for (var j = linkLines.length - 1; j >= 0; j--) {
if (linkLines[j].herbivore === creature) {
animateLinkFallOff(linkLines[j].carnivore, creature);
}
}
}
creature.die();
}
}
}
}
}
}
function createEventDecks() {
// Create 10 basic event cards
basicEventDeck = [];
for (var i = 1; i <= 10; i++) {
basicEventDeck.push({
type: 'basic',
cardType: 'event',
level: 'Basic',
name: 'Basic Event ' + i,
effect: 'environmental'
});
}
// Create 10 advanced event cards
advancedEventDeck = [];
for (var i = 1; i <= 10; i++) {
advancedEventDeck.push({
type: 'advanced',
cardType: 'event',
level: 'Advanced',
name: 'Advanced Event ' + i,
effect: 'catastrophic'
});
}
}
function addEventCardToDeck(creatureLevel) {
var eventCard = null;
// Determine which deck to draw from based on creature level
if (creatureLevel === 'Advanced') {
if (advancedEventDeck.length > 0) {
eventCard = advancedEventDeck.pop();
} else if (basicEventDeck.length > 0) {
// Fallback to basic events if advanced is empty
eventCard = basicEventDeck.pop();
}
} else {
if (basicEventDeck.length > 0) {
eventCard = basicEventDeck.pop();
} else if (advancedEventDeck.length > 0) {
// Fallback to advanced events if basic is empty
eventCard = advancedEventDeck.pop();
}
}
// If event card was drawn, add to discard pile (only draw 1 card per death)
if (eventCard) {
discardPile.push(eventCard);
}
}
function processDawnPhase() {
// Check for ongoing effects and costs that trigger during Dawn
// Check scry status at beginning of round - award token ONLY if no creatures went extinct last round
if (currentRound > 1 && !scryDeniedNextRound) {
scryTokenActive = true;
}
// Reset extinction tracking for the new round
creaturesWentExtinct = false;
// Reset scry denial for the new round
scryDeniedNextRound = false;
// Show visual cue that dawn phase is processing
showPhaseTransition("Dawn Phase - Processing...", function () {
gamePhase = 'draw';
processDrawPhase(); // Auto-progress to draw phase
});
}
function processDrawPhase() {
// Draw exactly 3 cards (or 4 if AT15 was played last turn), handling deck reshuffling as needed
var cardsToDrawTotal = 3;
if (at15PlayedLastTurn) {
cardsToDrawTotal = 4;
at15PlayedLastTurn = false; // Reset flag after using it
}
// 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++;
roundText.setText("Round: " + currentRound);
roundDisplayText.setText("Round: " + currentRound);
deckCountText.setText("Deck: " + mainDeck.length);
// Check for end game condition after round increment
if (currentRound >= 6) {
// Game ends immediately
LK.showYouWin();
return;
}
// Check and trigger scry status at beginning of new round
// Only activate scry if no extinctions occurred last round
if (!scryDeniedNextRound) {
scryTokenActive = true;
} //{ht_scry}
creaturesWentExtinct = false;
scryDeniedNextRound = false;
} else {
// No cards available, stop drawing
continueDrawing = false;
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
roundText.setText("Round: " + currentRound);
deckCountText.setText("Deck: " + mainDeck.length);
}
// Draw card button functionality
var drawButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
var drawButtonText = new Text2("DRAW", {
size: 32,
fill: 0xFFFFFF
});
drawButtonText.anchor.set(0.5, 0.5);
drawButton.addChild(drawButtonText);
drawButton.x = 1700;
drawButton.y = 2400;
game.addChild(drawButton);
drawButton.down = function () {
if (gamePhase === 'draw') {
processDrawPhase();
}
};
// Phase management buttons - redesigned for proper turn flow
var nextPhaseButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 0.8
});
nextPhaseButton.tint = 0x32CD32; // Green for next phase
var nextPhaseButtonText = new Text2("NEXT PHASE", {
size: 24,
fill: 0xFFFFFF
});
nextPhaseButtonText.anchor.set(0.5, 0.5);
nextPhaseButton.addChild(nextPhaseButtonText);
nextPhaseButton.x = 500;
nextPhaseButton.y = 2400;
game.addChild(nextPhaseButton);
var nextPhaseWarningBox = null;
function getCreaturesInDanger() {
var creaturesInDanger = [];
for (var gridY = 0; gridY < planetHeight; gridY++) {
//{kA_danger}
for (var gridX = 0; gridX < planetWidth; gridX++) {
//{kB_danger}
var terrain = planetBoard[gridY][gridX]; //{kC_danger}
if (terrain && terrain.creatureStack.length > 0) {
//{kD_danger}
for (var i = 0; i < terrain.creatureStack.length; i++) {
//{kE_danger}
var creature = terrain.creatureStack[i]; //{kF_danger}
// ONLY check top creatures in stack
var isTopCreature = i === terrain.creatureStack.length - 1; //{kG_danger}
if (!isTopCreature) {
//{kH_danger}
continue; //{kI_danger}
} //{kJ_danger}
// Check herbivores for excess links
if (creature.creatureData.dietType === 'herbivore') {
//{kK_danger}
var linksToThisHerbivore = 0; //{kL_danger}
for (var j = 0; j < linkLines.length; j++) {
//{kM_danger}
if (linkLines[j].herbivore === creature) {
//{kN_danger}
linksToThisHerbivore++; //{kO_danger}
} //{kP_danger}
} //{kQ_danger}
var excessLinks = linksToThisHerbivore - creature.safeLinks; //{kR_danger}
if (excessLinks > 0) {
//{kS_danger}
creaturesInDanger.push(creature); //{kT_danger}
} //{kU_danger}
} //{kV_danger}
// Check carnivores for insufficient links
else if (creature.creatureData.dietType === 'carnivore') {
//{kW_danger}
if (creature.activeLinks.length < creature.linkRequirement) {
//{kX_danger}
creaturesInDanger.push(creature); //{kY_danger}
} //{kZ_danger}
} //{l0_danger}
} //{l1_danger}
} //{l2_danger}
} //{l3_danger}
} //{l4_danger}
return creaturesInDanger; //{l5_danger}
} //{l6_danger}
function showNextPhaseWarning() {
if (nextPhaseWarningBox && nextPhaseWarningBox.parent) {
//{l7_danger}
return; // Warning already showing
} //{l8_danger}
// Create warning box
nextPhaseWarningBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
//{l9_danger}
anchorY: 0.5 //{la_danger}
}); //{lb_danger}
nextPhaseWarningBox.tint = 0xFF8C00; // Orange background
nextPhaseWarningBox.alpha = 0.95;
nextPhaseWarningBox.scaleX = 2.0;
nextPhaseWarningBox.scaleY = 1.2;
nextPhaseWarningBox.x = 500;
nextPhaseWarningBox.y = 2100;
game.addChild(nextPhaseWarningBox);
// Create warning text
var warningText = new Text2("Warning! These creatures are in danger!\nAre you sure you want to end the turn?", {
size: 20,
//{lc_danger}
fill: 0xFFFFFF //{ld_danger}
}); //{le_danger}
warningText.anchor.set(0.5, 0.5);
warningText.x = 0;
warningText.y = 0;
nextPhaseWarningBox.addChild(warningText);
} //{lf_danger}
function hideNextPhaseWarning() {
if (nextPhaseWarningBox && nextPhaseWarningBox.parent) {
//{lg_danger}
nextPhaseWarningBox.parent.removeChild(nextPhaseWarningBox); //{lh_danger}
nextPhaseWarningBox = null;
} //{li_danger}
} //{lj_danger}
function highlightCreaturesInDanger(creaturesInDanger) {
for (var i = 0; i < creaturesInDanger.length; i++) {
//{lk_danger}
var creature = creaturesInDanger[i]; //{ll_danger}
tween.stop(creature, {
//{lm_danger}
tint: true //{ln_danger}
}); //{lo_danger}
tween(creature, {
//{lp_danger}
tint: 0xFFFF00 //{lq_danger}
}, {
//{lr_danger}
duration: 200 //{ls_danger}
}); //{lt_danger}
} //{lu_danger}
} //{lv_danger}
function unhighlightCreaturesInDanger(creaturesInDanger) {
for (var i = 0; i < creaturesInDanger.length; i++) {
//{lw_danger}
var creature = creaturesInDanger[i]; //{lx_danger}
tween.stop(creature, {
//{ly_danger}
tint: true //{lz_danger}
}); //{mA_danger}
tween(creature, {
//{mB_danger}
tint: 0xFFFFFF //{mC_danger}
}, {
//{mD_danger}
duration: 200 //{mE_danger}
}); //{mF_danger}
} //{mG_danger}
} //{mH_danger}
nextPhaseButton.down = function () {
// Exit link adjustment mode if active and hide adjust links button
if (linkAdjustmentMode) {
deactivateLinkAdjustmentMode();
}
// Always hide adjust links button when next phase is clicked
hideAdjustLinksButton();
// Hide warning box when button is pressed
hideNextPhaseWarning(); //{mI_danger}
// Prevent skipping card play after draw: if player has not played a card after drawing, do not allow phase advance
if (gamePhase === 'draw' || gamePhase === 'noon' && (mustPlayCard || playerHand.length > 0 && hasValidPlacements(playerHand))) {
// If in draw phase, or in noon phase and must play a card, do not allow skipping
// Show a notification to the player
if (!game.noPlayableNotification) {
showNoPlayableCardsNotification();
}
return;
}
if (gamePhase === 'dawn') {
processDawnPhase();
} else if (gamePhase === 'draw') {
processDrawPhase();
} else if (gamePhase === 'noon') {
// Check if player has event cards that must be played first
if (eventCardsToPlay.length > 0 || mustPlayCard) {
// Must play event cards or mandatory card first
return;
}
gamePhase = 'dusk';
processDuskPhase();
} else if (gamePhase === 'dusk') {
// Apply consequences for over-hunted herbivores
processLinkConsequences();
clearLinkHighlights();
gamePhase = 'night';
processNightPhase();
} else if (gamePhase === 'night') {
processNightPhase();
} else if (gamePhase === 'end') {
processEndTurnPhase();
}
};
nextPhaseButton.move = function (x, y, obj) {
//{mJ_danger}
var creaturesInDanger = getCreaturesInDanger(); //{mK_danger}
if (creaturesInDanger.length > 0) {
//{mL_danger}
showNextPhaseWarning(); //{mM_danger}
highlightCreaturesInDanger(creaturesInDanger); //{mN_danger}
} else {
//{mO_danger}
hideNextPhaseWarning(); //{mP_danger}
unhighlightCreaturesInDanger(creaturesInDanger); //{mQ_danger}
} //{mR_danger}
}; //{mS_danger}
// Phase indicator
var phaseText = new Text2("Phase: NOON", {
size: 32,
fill: 0xFFFFFF
});
phaseText.anchor.set(0.5, 0);
phaseText.x = 1024;
phaseText.y = 2550;
game.addChild(phaseText);
// Link adjustment system variables
var linkAdjustmentMode = false;
var adjustLinkButton = null;
var cardsWithAdjustableLinks = [];
var currentlyAdjustingCard = null;
function createAdjustLinksButton() {
if (adjustLinkButton && adjustLinkButton.parent) {
adjustLinkButton.parent.removeChild(adjustLinkButton);
}
adjustLinkButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 0.8
});
adjustLinkButton.tint = 0xFFD700; // Gold for adjust links
var adjustButtonText = new Text2("Adjust Links?", {
size: 20,
fill: 0x000000
});
adjustButtonText.anchor.set(0.5, 0.5);
adjustLinkButton.addChild(adjustButtonText);
adjustLinkButton.x = 250; // Left of next phase button
adjustLinkButton.y = 2400;
game.addChild(adjustLinkButton);
adjustLinkButton.down = function () {
activateLinkAdjustmentMode();
};
}
function showAdjustLinksButton() {
createAdjustLinksButton();
}
function hideAdjustLinksButton() {
if (adjustLinkButton && adjustLinkButton.parent) {
adjustLinkButton.parent.removeChild(adjustLinkButton);
adjustLinkButton = null;
}
}
function activateLinkAdjustmentMode() {
linkAdjustmentMode = true;
currentlyAdjustingCard = null;
cardsWithAdjustableLinks = [];
// Find all carnivores on the board
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
if (creature.creatureData.dietType === 'carnivore') {
cardsWithAdjustableLinks.push(creature);
}
}
}
}
}
// Create and display personal "Alter Links" buttons on all carnivore cards
for (var i = 0; i < cardsWithAdjustableLinks.length; i++) {
var card = cardsWithAdjustableLinks[i];
card.createPersonalAlterLinksButton();
}
}
function deactivateLinkAdjustmentMode() {
linkAdjustmentMode = false;
// Remove personal alter buttons from all cards and restore card colors
for (var i = 0; i < cardsWithAdjustableLinks.length; i++) {
var card = cardsWithAdjustableLinks[i];
card.tint = 0xFFFFFF; // Return to normal color
// Remove personal alter button
if (card.personalAlterButton && card.personalAlterButton.parent) {
card.personalAlterButton.parent.removeChild(card.personalAlterButton);
card.personalAlterButton = null;
}
}
cardsWithAdjustableLinks = [];
currentlyAdjustingCard = null;
}
// Handle card dragging and link highlighting on hold
game.move = function (x, y, obj) {
if (draggedCard) {
draggedCard.x = x;
draggedCard.y = y;
// Ensure dragged card is always on top
game.removeChild(draggedCard);
game.addChild(draggedCard);
// Highlights are already shown from the card's down event
} else if (draggedLinkMarker && gamePhase === 'dusk' && linkAdjustmentMode) {
// Handle link marker dragging - highlight valid targets only while holding
var carnivore = draggedLinkMarker.carnivore;
highlightValidLinkTargets(carnivore);
// Create temporary visual line from carnivore to cursor
// Remove any existing temp line
if (game.tempLinkLine && game.tempLinkLine.parent) {
game.tempLinkLine.parent.removeChild(game.tempLinkLine);
}
// Create new temp line
var tempLine = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.01,
scaleY: 1
});
tempLine.tint = 0xFFFF00; // Yellow temp line
tempLine.alpha = 0.5;
var dx = x - carnivore.x;
var dy = y - carnivore.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
tempLine.x = carnivore.x + dx / 2;
tempLine.y = carnivore.y + dy / 2;
tempLine.rotation = angle;
tempLine.scaleY = distance / 336;
game.addChild(tempLine);
game.tempLinkLine = tempLine;
}
};
game.up = function (x, y, obj) {
if (draggedCard) {
var placed = false;
// Check if card was dropped on valid terrain
for (var gridY = 0; gridY < planetHeight && !placed; gridY++) {
for (var gridX = 0; gridX < planetWidth && !placed; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain) {
if (draggedCard.creatureData && canPlaceCreatureOnTerrain(draggedCard, terrain)) {
if (Math.abs(terrain.x - x) < 140 && Math.abs(terrain.y - y) < 168) {
placed = placeCreatureOnStack(draggedCard, terrain, gridX, gridY);
}
} else if (draggedCard.terrainData && canPlaceTerrainOnTerrain(draggedCard, terrain)) {
if (Math.abs(terrain.x - x) < 140 && Math.abs(terrain.y - y) < 168) {
placed = placeTerrainOnTerrain(draggedCard, terrain, gridX, gridY);
}
}
}
}
}
// If not placed, return to original position
if (!placed && originalCardPosition) {
tween(draggedCard, originalCardPosition, {
duration: 300
});
}
// Clear all highlights
hidePossibleMoves();
draggedCard = null;
originalCardPosition = null;
} else if (draggedLinkMarker && draggedLinkCarnivore && gamePhase === 'dusk' && linkAdjustmentMode) {
// Handle link marker release with new system
var linkCreated = false;
// Remove temporary line
if (game.tempLinkLine && game.tempLinkLine.parent) {
game.tempLinkLine.parent.removeChild(game.tempLinkLine);
game.tempLinkLine = null;
}
// Check if released over a valid herbivore
for (var i = 0; i < linkHighlights.length; i++) {
var highlight = linkHighlights[i];
if (highlight.targetCreature) {
var creature = highlight.targetCreature;
var distance = Math.sqrt(Math.pow(creature.x - x, 2) + Math.pow(creature.y - y, 2));
if (distance < 100) {
// Within range of the herbivore
// Create the link
if (createLink(draggedLinkCarnivore, creature)) {
linkCreated = true;
break;
}
}
}
}
// Clear highlights and reset drag state
clearLinkHighlights();
draggedLinkMarker = null;
draggedLinkCarnivore = null;
}
};
function processAT18Special() {
// Find all carnivore cards in discard pile
at18CarnivoreList = [];
for (var i = 0; i < discardPile.length; i++) {
//{at18_loop}
var cardData = discardPile[i];
if (cardData.cardType === 'creature' && cardData.dietType === 'carnivore') {
//{at18_check}
at18CarnivoreList.push(cardData);
} //{at18_found}
} //{at18_end_loop}
at18Active = true;
if (at18CarnivoreList.length === 0) {
// No carnivores in discard pile - show message
showAT18NoCarnivoredMessage();
} else {
//{at18_else}
// Display carnivores for selection
displayAT18CarnivoreSelection();
} //{at18_display_end}
} //{at18_process_end}
function showAT18NoCarnivoredMessage() {
// Create notification box
var notificationBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
//{at18_no_x}
anchorY: 0.5 //{at18_no_y}
}); //{at18_no_asset}
notificationBox.tint = 0x4169E1; // Blue background for info
notificationBox.alpha = 0.9;
notificationBox.scaleX = 2.5;
notificationBox.scaleY = 1.5;
notificationBox.x = 1024;
notificationBox.y = 1366;
game.addChild(notificationBox);
// Create notification text
var notificationText = new Text2("No Carnivore cards in Discard pile - Special does not apply", {
size: 28,
//{at18_no_text_size}
fill: 0xFFFFFF //{at18_no_text_fill}
}); //{at18_no_text}
notificationText.anchor.set(0.5, 0.5);
notificationText.x = 0;
notificationText.y = 0;
notificationBox.addChild(notificationText);
// Make the box clickable to dismiss
notificationBox.down = function () {
if (notificationBox.parent) {
notificationBox.parent.removeChild(notificationBox);
} //{at18_no_dismiss}
at18Active = false;
}; //{at18_no_down}
} //{at18_no_function_end}
function displayAT18CarnivoreSelection() {
// Create scry UI header
var scryHeader = new Text2("Pick 1 to add back onto the main deck", {
size: 32,
//{at18_header_size}
fill: 0xFFFFFF //{at18_header_fill}
}); //{at18_header_text}
scryHeader.anchor.set(0.5, 0);
scryHeader.x = 1024;
scryHeader.y = 300;
game.addChild(scryHeader);
scryHeader.at18Header = true;
// Display each carnivore card in a selectable list format
var cardYOffset = 500;
var cardYSpacing = 280;
for (var i = 0; i < at18CarnivoreList.length; i++) {
//{at18_display_loop}
var cardData = at18CarnivoreList[i];
var cardContainer = new CreatureCard(cardData);
cardContainer.scaleX = 0.6;
cardContainer.scaleY = 0.6;
cardContainer.x = 512 + i % 2 * 600;
cardContainer.y = cardYOffset + Math.floor(i / 2) * cardYSpacing;
cardContainer.cardDataIndex = i;
cardContainer.at18Selectable = true;
// Add click handler to select this card
cardContainer.down = function (x, y, obj) {
var selectedIndex = this.cardDataIndex;
selectAT18Carnivore(selectedIndex);
}; //{at18_selectable_down}
game.addChild(cardContainer);
} //{at18_display_loop_end}
} //{at18_display_function_end}
function selectAT18Carnivore(cardIndex) {
if (cardIndex < 0 || cardIndex >= at18CarnivoreList.length) {
return;
}
// Get the selected carnivore card data
var selectedCardData = at18CarnivoreList[cardIndex];
// Remove from discard pile
for (var i = discardPile.length - 1; i >= 0; i--) {
//{at18_remove_loop}
if (discardPile[i] === selectedCardData || discardPile[i].id === selectedCardData.id && discardPile[i].cardType === selectedCardData.cardType && discardPile[i].dietType === selectedCardData.dietType) {
//{at18_remove_check}
discardPile.splice(i, 1);
break;
} //{at18_remove_end}
} //{at18_remove_loop_end}
// Add to top of main deck
mainDeck.push(selectedCardData);
// Clear all AT18 UI elements
clearAT18UI();
// Update deck count
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
at18Active = false;
} //{at18_select_function_end}
function clearAT18UI() {
// Remove all AT18 selectable cards from game
for (var i = game.children.length - 1; i >= 0; i--) {
//{at18_clear_loop}
var child = game.children[i];
if (child.at18Selectable) {
//{at18_clear_check}
game.removeChild(child);
} //{at18_clear_removed}
if (child.at18Header) {
//{at18_clear_header}
game.removeChild(child);
} //{at18_clear_header_removed}
} //{at18_clear_loop_end}
} //{at18_clear_function_end}
function activateScry() {
if (!scryTokenActive || scryMode || mainDeck.length < 3) {
return;
}
scryMode = true;
scryCards = [];
// Take top 3 cards from deck
for (var i = 0; i < 3; i++) {
var cardData = mainDeck.pop();
var card = createCardFromData(cardData);
scryCards.push(card);
playerHand.push(card);
game.addChild(card);
}
updateHandPositions();
// Show return cards button
showReturnCardsButton();
}
function showReturnCardsButton() {
var returnButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.8
});
returnButton.tint = 0x8A2BE2; // Purple for scry actions
var returnButtonText = new Text2("READY TO RETURN CARDS", {
size: 20,
fill: 0xFFFFFF
});
returnButtonText.anchor.set(0.5, 0.5);
returnButton.addChild(returnButtonText);
returnButton.x = 1024;
returnButton.y = 2000;
game.addChild(returnButton);
returnButton.down = function () {
processScryReturn();
if (returnButton.parent) {
returnButton.parent.removeChild(returnButton);
}
};
}
function processScryReturn() {
// For each scry card in hand, player chooses where to return it
// For now, we'll implement a simple system - return to top of deck
for (var i = 0; i < scryCards.length; i++) {
var card = scryCards[i];
var handIndex = playerHand.indexOf(card);
if (handIndex !== -1) {
playerHand.splice(handIndex, 1);
if (card.parent) {
card.parent.removeChild(card);
}
// Return to top of deck (could be enhanced to let player choose top/bottom/discard)
var cardData = card.creatureData || card.terrainData || card.eventData;
// Create clean copy without event data to prevent hand state corruption
var cleanCardData = {};
cleanCardData.type = cardData.type;
cleanCardData.level = cardData.level;
cleanCardData.cardType = cardData.cardType;
if (cardData.dietType) {
cleanCardData.dietType = cardData.dietType;
}
if (cardData.name) {
cleanCardData.name = cardData.name;
}
if (cardData.terrainRequirement) {
cleanCardData.terrainRequirement = cardData.terrainRequirement;
}
if (cardData.climateRequirement) {
cleanCardData.climateRequirement = cardData.climateRequirement;
}
if (cardData.subtype) {
cleanCardData.subtype = cardData.subtype;
}
if (cardData.landType) {
cleanCardData.landType = cardData.landType;
}
if (cardData.waterType) {
cleanCardData.waterType = cardData.waterType;
}
if (cardData.climate) {
cleanCardData.climate = cardData.climate;
}
mainDeck.push(cleanCardData);
}
}
// Clear scry state
scryCards = [];
scryMode = false;
scryTokenActive = false; // Scry token consumed
updateHandPositions();
}
function showNoPlayableCardsNotification() {
// Create notification box
var notificationBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.5
});
notificationBox.tint = 0x8B0000; // Dark red background
notificationBox.alpha = 0.9;
notificationBox.x = 1024;
notificationBox.y = 1366;
game.addChild(notificationBox);
// Create notification text
var notificationText = new Text2("No playable cards! Click to continue", {
size: 36,
fill: 0xFFFFFF
});
notificationText.anchor.set(0.5, 0.5);
notificationText.x = 0;
notificationText.y = 0;
notificationBox.addChild(notificationText);
// Make the box clickable
notificationBox.down = function () {
// Remove the notification
if (notificationBox.parent) {
notificationBox.parent.removeChild(notificationBox);
}
// Discard all cards in hand
discardCards(playerHand.slice());
// Start draw 1, discard 1 cycle
processDrawPhase();
};
// Store reference for cleanup
game.noPlayableNotification = notificationBox;
}
// Initialize terrain-based game
createBasicTerrainPool();
setupPlanet();
createInitialDeck();
createEventDecks();
// Shuffle event decks on game start
shuffleEventDeck(basicEventDeck);
shuffleEventDeck(advancedEventDeck);
// Game starts with turn 1, round 1, in Dawn phase
currentRound = 1;
turnNumber = 1;
gamePhase = 'dawn';
roundText.setText("Round: " + currentRound);
game.update = function () {
// Update UI elements
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
basicEventCountText.setText("Basic Events: " + basicEventDeck.length);
advancedEventCountText.setText("Advanced Events: " + advancedEventDeck.length);
graveyardCountText.setText("Graveyard: " + graveyard.length);
eventDiscardCountText.setText("Event Discard: " + eventDiscardPile.length);
// Update scry token display
if (scryTokenActive) {
scryText.setText("Scry ✓");
scryText.tint = 0x00FF00; // Green when available
} else if (scryMode) {
scryText.setText("Scry (Active)");
scryText.tint = 0xFFFF00; // Yellow when in use
} else {
scryText.setText("Scry");
scryText.tint = 0x888888; // Gray when unavailable
}
// Update phase indicator
phaseText.setText("Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber);
// Check if event cards have been removed from hand
hasEventCardsInHand = false;
for (var i = 0; i < playerHand.length; i++) {
//{lV_event1}
if (playerHand[i].eventData) {
hasEventCardsInHand = true;
break;
}
}
// Update next phase button appearance based on event cards
if (gamePhase === 'noon') {
//{lV_event2}
if (hasEventCardsInHand) {
nextPhaseButton.tint = 0xFF0000; // Red when events must be played
nextPhaseButtonText.setText("Play events 1st!");
} else {
nextPhaseButton.tint = 0x32CD32; // Green when normal phase progression
nextPhaseButtonText.setText("NEXT PHASE");
}
}
// Update link marker states during dusk phase
if (gamePhase === 'dusk') {
updateLinkMarkerStates();
}
};
function resetCardLinks(carnivore) {
if (!carnivore || carnivore.creatureData.dietType !== 'carnivore') {
return;
}
// Remove all active links from this carnivore with fall-off animation
for (var i = carnivore.activeLinks.length - 1; i >= 0; i--) {
var link = carnivore.activeLinks[i];
animateLinkFallOff(carnivore, link.target);
}
// Reset all link markers to unlinked state
for (var j = 0; j < carnivore.linkMarkers.length; j++) {
var marker = carnivore.linkMarkers[j];
marker.isLinked = false;
marker.targetHerbivore = null;
marker.tint = 0xFF0000; // Red for unlinked
}
// Clear active links array
carnivore.activeLinks = [];
} /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var CreatureCard = Container.expand(function (creatureData) {
var self = Container.call(this);
self.creatureData = creatureData || {
type: 'basic',
level: 'Basic',
dietType: 'herbivore',
name: 'Basic Herbivore',
terrainRequirement: 'land',
climateRequirement: 'any'
};
// Create creature visual
var creatureAsset;
if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'herbivore') {
creatureAsset = self.attachAsset('creatureBasicHerbivore', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'herbivore') {
creatureAsset = self.attachAsset('creatureAdvancedHerbivore', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'carnivore') {
creatureAsset = self.attachAsset('creatureBasicCarnivore', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'carnivore') {
creatureAsset = self.attachAsset('creatureAdvancedCarnivore', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Add creature type color band at bottom
var typeStripAsset;
if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'herbivore') {
typeStripAsset = 'creatureTypeBasicHerbivore';
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'herbivore') {
typeStripAsset = 'creatureTypeAdvancedHerbivore';
} else if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'carnivore') {
typeStripAsset = 'creatureTypeBasicCarnivore';
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'carnivore') {
typeStripAsset = 'creatureTypeAdvancedCarnivore';
}
if (typeStripAsset) {
var typeStrip = self.attachAsset(typeStripAsset, {
anchorX: 0.5,
anchorY: 0.5
});
typeStrip.y = 95; // Position at bottom of creature card
}
// Add creature name
self.nameText = new Text2(self.creatureData.name, {
size: 18,
fill: 0xFFFFFF
});
self.nameText.anchor.set(0.5, 0);
self.nameText.x = 0;
self.nameText.y = -100;
self.addChild(self.nameText);
// Add requirement text
var reqText = "Req: ";
if (self.creatureData.terrainRequirement && self.creatureData.terrainRequirement !== 'any') {
reqText += self.creatureData.terrainRequirement;
}
if (self.creatureData.climateRequirement && self.creatureData.climateRequirement !== 'any') {
if (reqText !== "Req: ") {
reqText += ", ";
}
reqText += self.creatureData.climateRequirement;
}
if (reqText !== "Req: ") {
self.requirementText = new Text2(reqText, {
size: 14,
fill: 0xFFFFFF
});
self.requirementText.anchor.set(0.5, 0);
self.requirementText.x = 0;
self.requirementText.y = 60;
self.addChild(self.requirementText);
}
self.isInPlay = false;
self.linkMarkers = [];
self.activeLinks = [];
self.extinctionMarkers = 0;
self.linkRequirement = 0;
self.safeLinks = 0;
// Set link requirements and safe link levels based on creature type
if (self.creatureData.dietType === 'carnivore') {
if (self.creatureData.level === 'Basic') {
self.linkRequirement = 1; // Basic carnivores need 1 link
} else if (self.creatureData.level === 'Advanced') {
self.linkRequirement = 2; // Advanced carnivores need 2 links
}
} else if (self.creatureData.dietType === 'herbivore') {
if (self.creatureData.level === 'Basic') {
self.safeLinks = 1; // Basic herbivores can safely handle 1 link
} else if (self.creatureData.level === 'Advanced') {
self.safeLinks = 2; // Advanced herbivores can safely handle 2 links
}
}
// Create visual link markers for carnivores
self.createLinkMarkers = function () {
if (self.creatureData.dietType === 'carnivore' && self.linkRequirement > 0) {
for (var i = 0; i < self.linkRequirement; i++) {
var linkMarker = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.15,
scaleY: 0.15
});
linkMarker.tint = 0xFF0000; // Red color for unlinked markers
linkMarker.x = (i - (self.linkRequirement - 1) / 2) * 25;
linkMarker.y = -80;
linkMarker.isLinked = false;
linkMarker.targetHerbivore = null;
linkMarker.carnivore = self;
linkMarker.markerIndex = i;
// Link markers are no longer directly interactive
linkMarker.move = function (x, y, obj) {};
linkMarker.down = function (x, y, obj) {};
self.addChild(linkMarker);
self.linkMarkers.push(linkMarker);
}
// Create personal "Alter Links" button for this carnivore
self.createPersonalAlterLinksButton = function () {
if (self.isInPlay) {
var alterButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.2
});
alterButton.tint = 0xFFD700; // Gold color
var alterButtonText = new Text2("Alter Links", {
size: 12,
fill: 0x000000
});
alterButtonText.anchor.set(0.5, 0.5);
alterButton.addChild(alterButtonText);
alterButton.x = 0;
alterButton.y = 0;
alterButton.carnivoreCard = self;
alterButton.down = function () {
if (gamePhase === 'dusk' && linkAdjustmentMode) {
resetCardLinks(self);
}
};
self.addChild(alterButton);
self.personalAlterButton = alterButton;
}
};
}
};
// Create extinction marker visuals
self.extinctionMarkerVisuals = [];
self.updateExtinctionMarkers = function () {
// Remove existing visuals
for (var i = 0; i < self.extinctionMarkerVisuals.length; i++) {
if (self.extinctionMarkerVisuals[i].parent) {
self.extinctionMarkerVisuals[i].parent.removeChild(self.extinctionMarkerVisuals[i]);
}
}
self.extinctionMarkerVisuals = [];
// Create new visuals for current extinction markers
for (var i = 0; i < self.extinctionMarkers; i++) {
var marker = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.1,
scaleY: 0.1
});
marker.tint = 0x000000; // Black for extinction markers
marker.x = (i - (self.extinctionMarkers - 1) / 2) * 20;
marker.y = 80;
self.addChild(marker);
self.extinctionMarkerVisuals.push(marker);
}
};
self.die = function () {
if (self.isInPlay) {
// Add creature to graveyard and draw event card based on creature tier
graveyard.push(self.creatureData);
// Draw event card only once when creature dies
addEventCardToDeck(self.creatureData.level);
// Visual death effect
tween(self, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 500,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
LK.getSound('creatureDie').play();
}
};
self.move = function (x, y, obj) {
if (Math.abs(x) < cardBoundsForZoom && Math.abs(y) < cardHeightBoundsForZoom) {
zoomCard(self);
showCardInfo(self);
if (!self.isInPlay) {
showPossibleMoves(self);
} //{1B_close}
} else {
// Mouse moved away from this card
if (hoveredCard === self) {
hideCardInfo();
if (!self.isInPlay) {
hidePossibleMoves();
} //{1G_close}
}
// Clear zoom if moving away from card
if (currentHoveredCardForZoom === self) {
clearZoom();
}
}
};
self.down = function (x, y, obj) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
// Prevent selecting creature/terrain cards if event cards are in hand
if (hasEventCardsInHand) {
//{1G_event}
return;
}
selectedCard = self;
draggedCard = self;
originalCardPosition = {
x: self.x,
y: self.y
};
showPossibleMoves(self);
}
};
// Add "Special" text in the center for any creature card with special: true
if (self.creatureData && self.creatureData.special) {
self.specialText = new Text2("Special", {
size: 32,
fill: 0xFF0000,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.specialText.anchor.set(0.5, 0.5);
self.specialText.x = 0;
self.specialText.y = 0;
self.addChild(self.specialText);
}
// Add "Tough" text under Special text if present
var nextYOffset = 40;
if (self.creatureData && self.creatureData.tough) {
self.toughText = new Text2("Tough", {
size: 24,
fill: 0xFFD700,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.toughText.anchor.set(0.5, 0.5);
self.toughText.x = 0;
self.toughText.y = self.specialText ? self.specialText.y + nextYOffset : nextYOffset;
self.addChild(self.toughText);
nextYOffset = self.toughText.y + nextYOffset;
} else if (self.specialText) {
nextYOffset = self.specialText.y + nextYOffset;
}
// Add "Squishy" text under Special/Tough text if present
if (self.creatureData && self.creatureData.squishy) {
self.squishyText = new Text2("Squishy", {
size: 24,
fill: 0xFF69B4,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.squishyText.anchor.set(0.5, 0.5);
self.squishyText.x = 0;
self.squishyText.y = nextYOffset;
self.addChild(self.squishyText);
nextYOffset = self.squishyText.y + 40;
}
// Add "Stinky" text under Special/Tough/Squishy text if present
if (self.creatureData && self.creatureData.stinky) {
self.stinkyText = new Text2("Stinky", {
size: 24,
fill: 0x228B22,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.stinkyText.anchor.set(0.5, 0.5);
self.stinkyText.x = 0;
self.stinkyText.y = nextYOffset;
self.addChild(self.stinkyText);
nextYOffset = self.stinkyText.y + 40;
}
// Add "Flying" and "Swimming" text under Special/Tough text if present
var flyingOrSwimmingYOffset = 40;
if (self.creatureData && self.creatureData.flying) {
self.flyingText = new Text2("Flying", {
size: 24,
fill: 0x00BFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.flyingText.anchor.set(0.5, 0.5);
self.flyingText.x = 0;
self.flyingText.y = nextYOffset;
self.addChild(self.flyingText);
nextYOffset = self.flyingText.y + flyingOrSwimmingYOffset;
}
if (self.creatureData && self.creatureData.swimming) {
self.swimmingText = new Text2("Swimming", {
size: 24,
fill: 0x1E90FF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.swimmingText.anchor.set(0.5, 0.5);
self.swimmingText.x = 0;
self.swimmingText.y = nextYOffset;
self.addChild(self.swimmingText);
nextYOffset = self.swimmingText.y + flyingOrSwimmingYOffset;
}
// Add "Efficient" text under Special/Flying/Swimming text if present
if (self.creatureData && self.creatureData.efficient) {
self.efficientText = new Text2("Efficient", {
size: 24,
fill: 0x32CD32,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.efficientText.anchor.set(0.5, 0.5);
self.efficientText.x = 0;
self.efficientText.y = nextYOffset;
self.addChild(self.efficientText);
nextYOffset = self.efficientText.y + 40;
}
// Add "Whale Chow" text for BH07 and BH13
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 = self.whaleChowText.y + 40;
}
// Add "Bully" text for creatures with bully property
if (self.creatureData && self.creatureData.bully) {
self.bullyText = new Text2("Bully", {
size: 24,
//{2U_bully1}
fill: 0xFF6347,
//{2U_bully2}
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
//{2U_bully3}
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 = self.bullyText.y + 40;
} //{2U_bully6}
// Add "Whale Chow Eater" text under Special 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 = self.whaleChowEaterText.y + 40;
}
return self;
});
var EventCard = Container.expand(function (eventData) {
var self = Container.call(this);
self.eventData = eventData || {
type: 'basic',
cardType: 'event',
level: 'Basic',
name: 'Event Card',
effect: 'environmental'
};
// Create event card visual
var eventAsset = self.attachAsset('eventCard', {
anchorX: 0.5,
anchorY: 0.5
});
// Add event text - placeholder "EVENT!!!" in red
var eventText = new Text2("EVENT!!!", {
size: 32,
fill: 0xFF0000
});
eventText.anchor.set(0.5, 0.5);
eventText.x = 0;
eventText.y = 0;
self.addChild(eventText);
// Add event level indicator
var levelText = new Text2(self.eventData.level, {
size: 16,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0);
levelText.x = 0;
levelText.y = -80;
self.addChild(levelText);
self.isInPlay = false;
self.move = function (x, y, obj) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
// Check if mouse is actually over this card
if (Math.abs(x) < cardBoundsForZoom && Math.abs(y) < cardHeightBoundsForZoom) {
// Trigger zoom on hover
zoomCard(self);
showCardInfo(self);
showPossibleMoves(self);
} else {
if (hoveredCard === self) {
hideCardInfo();
hidePossibleMoves();
}
// Clear zoom if moving away from card
if (currentHoveredCardForZoom === self) {
clearZoom();
}
}
}
};
self.down = function (x, y, obj) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
// Event cards are played immediately by clicking them
// Move to event discard pile
var handIndex = playerHand.indexOf(self);
if (handIndex !== -1) {
playerHand.splice(handIndex, 1);
}
// Remove from game
if (self.parent) {
self.parent.removeChild(self);
}
// Add event card data to event discard pile
var cardData = self.eventData;
if (!eventDiscardPile) {
eventDiscardPile = [];
}
eventDiscardPile.push(cardData);
// Update hand positions
updateHandPositions();
// Check if there are more event cards in hand
var moreEventCards = false;
for (var i = 0; i < playerHand.length; i++) {
if (playerHand[i].eventData) {
moreEventCards = true;
break;
}
}
// If no more event cards, progress to Noon phase
if (!moreEventCards) {
hasEventCardsInHand = false;
gamePhase = 'noon';
// Update UI
phaseText.setText("Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber);
// Discard any remaining cards in hand
var remainingCards = playerHand.slice();
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
}
}
};
return self;
});
var PlanetSlot = Container.expand(function (slotX, slotY) {
var self = Container.call(this);
self.slotX = slotX;
self.slotY = slotY;
self.terrainCard = null;
self.isHighlighted = false;
self.pinkBorder = null;
self.highlight = function () {
if (!self.isHighlighted && self.terrainCard) {
self.isHighlighted = true;
// Create pink border around the terrain card
if (!self.pinkBorder) {
self.pinkBorder = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
self.pinkBorder.tint = 0xFF1493; // Brighter deep pink color
self.pinkBorder.alpha = 0.9;
// Scale border to be larger than terrain card
self.pinkBorder.scaleX = self.terrainCard.scaleX * 1.3;
self.pinkBorder.scaleY = self.terrainCard.scaleY * 1.3;
self.pinkBorder.x = self.terrainCard.x;
self.pinkBorder.y = self.terrainCard.y;
// Add border behind terrain card
var terrainIndex = game.getChildIndex(self.terrainCard);
game.addChildAt(self.pinkBorder, terrainIndex);
}
}
};
self.unhighlight = function () {
if (self.isHighlighted) {
self.isHighlighted = false;
// Remove pink border
if (self.pinkBorder && self.pinkBorder.parent) {
self.pinkBorder.parent.removeChild(self.pinkBorder);
self.pinkBorder = null;
}
}
};
self.down = function (x, y, obj) {
if (selectedCard && selectedCard.creatureData && self.terrainCard && canPlaceCreatureOnTerrain(selectedCard, self.terrainCard)) {
placeCreatureOnStack(selectedCard, self.terrainCard, self.slotX, self.slotY);
}
};
return self;
});
var TerrainCard = Container.expand(function (terrainData) {
var self = Container.call(this);
self.terrainData = terrainData || {
type: 'basic',
level: 'Basic',
subtype: 'land',
landType: 'flat',
waterType: null,
climate: 'temperate'
};
// Create terrain card visual based on subtype and landType/waterType
var terrainAsset;
if (self.terrainData.subtype === 'land') {
if (self.terrainData.landType === 'flat') {
terrainAsset = self.attachAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.terrainData.landType === 'hills') {
terrainAsset = self.attachAsset('terrainLandHills', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.terrainData.landType === 'mountain') {
terrainAsset = self.attachAsset('terrainLandMountain', {
anchorX: 0.5,
anchorY: 0.5
});
}
} else if (self.terrainData.subtype === 'water') {
if (self.terrainData.waterType === 'sea') {
terrainAsset = self.attachAsset('terrainWaterSea', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.terrainData.waterType === 'fresh') {
terrainAsset = self.attachAsset('terrainWaterFresh', {
anchorX: 0.5,
anchorY: 0.5
});
}
}
// Add terrain type indicator strip at top
var terrainStripAsset = self.terrainData.level === 'Advanced' ? 'advancedTerrainStrip' : 'basicTerrainStrip';
var terrainStrip = self.attachAsset(terrainStripAsset, {
anchorX: 0.5,
anchorY: 0.5
});
terrainStrip.y = -147;
// For advanced terrain cards, tint the strip based on colorBand
if (self.terrainData.level === 'Advanced' && self.terrainData.colorBand) {
var colorMap = {
'grey': 0xC0C0C0,
'darkblue': 0x87CEEB,
'brown': 0xD2B48C,
'purple': 0x800080,
'lightblue': 0xADD8E6
};
if (colorMap[self.terrainData.colorBand]) {
terrainStrip.tint = colorMap[self.terrainData.colorBand];
}
}
// Add terrain type text (subtype and second subtype in the color band)
var terrainTypeText = '';
var textColor = 0xFFFFFF; // Default white for advanced terrain
if (self.terrainData.level === 'Advanced') {
// For advanced terrain: display subtype and specific terrain type
terrainTypeText = self.terrainData.subtype.toUpperCase() + ' - ';
terrainTypeText += (self.terrainData.landType || self.terrainData.waterType).toUpperCase();
} else {
// For basic terrain: just display the specific terrain type
terrainTypeText = (self.terrainData.landType || self.terrainData.waterType).toUpperCase();
textColor = 0x000000; // Black text for basic terrain
}
self.typeText = new Text2(terrainTypeText, {
size: 24,
fill: textColor,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif"
});
self.typeText.anchor.set(0.5, 0.5);
self.typeText.x = 0;
self.typeText.y = -147;
self.addChild(self.typeText);
// Add climate requirement display for advanced terrain cards (top right under color band with colored circles)
if (self.terrainData.level === 'Advanced' && self.terrainData.climateRequirement && self.terrainData.climateRequirement !== 'any') {
var climateDisplay = self.terrainData.climateRequirement;
var climateArray = [];
if (climateDisplay.indexOf('/') !== -1) {
// Multiple climate requirements - split them
climateArray = climateDisplay.split('/');
} else {
// Single climate requirement
climateArray = [climateDisplay];
}
// Create colored circles with climate letters
var circleSize = 30;
var circleSpacing = 35;
var startX = 100;
var startY = -115;
for (var c = 0; c < climateArray.length; c++) {
var climateType = climateArray[c].trim();
var circleColor = 0xFFFFFF;
var letterChar = '';
if (climateType.indexOf('hot') !== -1) {
circleColor = 0xFF4444;
letterChar = 'H';
} else if (climateType.indexOf('cold') !== -1) {
circleColor = 0x4444FF;
letterChar = 'C';
} else if (climateType.indexOf('temperate') !== -1) {
circleColor = 0xFFFF44;
letterChar = 'T';
}
// Create circle background
var climateCircle = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.08,
scaleY: 0.08
});
climateCircle.tint = circleColor;
climateCircle.x = startX + c * circleSpacing;
climateCircle.y = startY;
self.addChild(climateCircle);
// Add letter text on circle
var climateLetterText = new Text2(letterChar, {
size: 16,
fill: 0x000000,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif"
});
climateLetterText.anchor.set(0.5, 0.5);
climateLetterText.x = startX + c * circleSpacing;
climateLetterText.y = startY;
self.addChild(climateLetterText);
}
}
// Add "Special" text in the center for any card with special: true
if (self.terrainData && self.terrainData.special) {
self.specialText = new Text2("Special", {
size: 32,
fill: 0xFF0000,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.specialText.anchor.set(0.5, 0.5);
self.specialText.x = 0;
self.specialText.y = 0;
self.addChild(self.specialText);
}
// Add card ID at center of bottom for advanced terrain cards
if (self.terrainData.level === 'Advanced' && self.terrainData.id) {
self.cardIdText = new Text2(self.terrainData.id, {
size: 20,
fill: 0xFFFFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif"
});
self.cardIdText.anchor.set(0.5, 0.5);
self.cardIdText.x = 0;
self.cardIdText.y = 140; // Bottom center of card
self.addChild(self.cardIdText);
}
// Climate will be displayed as row background instead of on individual cards
self.gridX = -1;
self.gridY = -1;
self.creatureStack = [];
self.move = function (x, y, obj) {
if (Math.abs(x) < 75 && Math.abs(y) < 100) {
// Trigger zoom on hover
zoomCard(self);
showCardInfo(self);
if (!self.isInPlay) {
showPossibleMoves(self);
} //{5q_close}
} else {
// Mouse moved away from this card
if (hoveredCard === self) {
hideCardInfo();
if (!self.isInPlay) {
hidePossibleMoves();
} //{5v_close}
}
// Clear zoom if moving away from card
if (currentHoveredCardForZoom === self) {
clearZoom();
}
}
};
self.down = function (x, y, obj) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
// Prevent selecting creature/terrain cards if event cards are in hand
if (hasEventCardsInHand) {
//{3H_event}
return;
}
selectedCard = self;
draggedCard = self;
originalCardPosition = {
x: self.x,
y: self.y
};
showPossibleMoves(self);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a252f
});
/****
* Game Code
****/
// 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 all cards in hand for hover
for (var i = 0; i < playerHand.length; i++) {
var card = playerHand[i];
// Get card bounds based on card type
var halfWidth = 80;
var halfHeight = 110;
// Check if mouse is within card bounds
if (Math.abs(card.x - x) < halfWidth && Math.abs(card.y - y) < halfHeight) {
cardUnderMouse = card;
break;
}
}
// Check all cards in play (creatures and terrain on board)
if (!cardUnderMouse) {
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain) {
// Check creatures in stack first (top creature is most visible)
if (terrain.creatureStack && terrain.creatureStack.length > 0) {
for (var c = terrain.creatureStack.length - 1; c >= 0; c--) {
var creature = terrain.creatureStack[c];
var creatureHalfWidth = 80;
var creatureHalfHeight = 110;
if (Math.abs(creature.x - x) < creatureHalfWidth && Math.abs(creature.y - y) < creatureHalfHeight) {
cardUnderMouse = creature;
break;
}
}
}
if (cardUnderMouse) break;
}
// Check terrain card if no creature found
if (!cardUnderMouse && terrain) {
var terrainHalfWidth = 75;
var terrainHalfHeight = 100;
if (Math.abs(terrain.x - x) < terrainHalfWidth && Math.abs(terrain.y - y) < terrainHalfHeight) {
cardUnderMouse = terrain;
}
}
}
if (cardUnderMouse) break;
}
}
// Decide what to do based on result
if (cardUnderMouse && cardUnderMouse !== currentlyHoveredCard) {
// Mouse over a new card
if (currentlyHoveredCard) {
clearZoom();
}
zoomCard(cardUnderMouse);
showCardInfo(cardUnderMouse);
showPossibleMoves(cardUnderMouse);
currentlyHoveredCard = cardUnderMouse;
} else if (!cardUnderMouse && currentlyHoveredCard) {
// Mouse moved off all cards
clearZoom();
hideCardInfo();
hidePossibleMoves();
currentlyHoveredCard = null;
}
}
var currentlyHoveredCard = null;
game.move = function (x, y, obj) {
currentMouseX = x;
currentMouseY = y;
// Call centralized mouse move handler for zoom
onMouseMove(x, y);
if (draggedCard) {
draggedCard.x = x;
draggedCard.y = y;
// Ensure dragged card is always on top
game.removeChild(draggedCard);
game.addChild(draggedCard);
// Highlights are already shown from the card's down event
} else if (draggedLinkMarker && gamePhase === 'dusk' && linkAdjustmentMode) {
// Handle link marker dragging - highlight valid targets only while holding
var carnivore = draggedLinkMarker.carnivore;
highlightValidLinkTargets(carnivore);
// Create temporary visual line from carnivore to cursor
// Remove any existing temp line
if (game.tempLinkLine && game.tempLinkLine.parent) {
game.tempLinkLine.parent.removeChild(game.tempLinkLine);
}
// Create new temp line
var tempLine = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.01,
scaleY: 1
});
tempLine.tint = 0xFFFF00; // Yellow temp line
tempLine.alpha = 0.5;
var dx = x - carnivore.x;
var dy = y - carnivore.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
tempLine.x = carnivore.x + dx / 2;
tempLine.y = carnivore.y + dy / 2;
tempLine.rotation = angle;
tempLine.scaleY = distance / 336;
game.addChild(tempLine);
game.tempLinkLine = tempLine;
} else {
// Check if mouse is over hand area, if not, hide card info
var handY = 2400;
var inHandArea = y > handY - 150 && y < handY + 150;
if (!inHandArea) {
hideCardInfo();
hidePossibleMoves();
}
}
};
// Game state variables
var currentRound = 0;
var maxRounds = 5;
var cardsDrawnThisTurn = 0;
var maxCardsPerTurn = 3;
var planetBoard = [];
var planetSlots = [];
var playerHand = [];
var maxHandSize = 3;
var mainDeck = [];
var basicTerrainPool = [];
var selectedCard = null;
var draggedCard = null;
var originalCardPosition = null;
var scryTokenActive = false; // Start without scry token
var scryCards = []; // Cards in scry mode
var scryMode = false; // Whether scry is active
var creaturesWentExtinct = false; // Track if creatures went extinct this round
var cardInfoText = null;
var hoveredCard = null;
var drawPhase = 'initial'; // 'initial', 'forced'
var cardsDrawnInPhase = [];
var discardPile = [];
var eventDiscardPile = []; // Discard pile specifically for event cards
var linkLines = []; // Visual link lines between carnivores and herbivores
var gamePhase = 'dawn'; // Track current game phase: 'dawn', 'draw', 'noon', 'dusk', 'night', 'end'
var turnNumber = 1;
var draggedLinkMarker = null;
var draggedLinkCarnivore = null;
var linkHighlights = [];
var eventCardsToPlay = [];
var mustPlayCard = null;
var basicEventDeck = [];
var advancedEventDeck = [];
var phaseTransitionTimer = null;
var scryDeniedNextRound = false;
var hasEventCardsInHand = false;
// Planet layout: 2-4-6-4-2 formation
var planetLayout = [2, 4, 6, 4, 2];
var planetWidth = 6; // Max cards in any row
var planetHeight = 5; // Number of rows
// UI Elements
var roundText = new Text2("Round: " + currentRound, {
size: 48,
fill: 0xFFFFFF
});
roundText.anchor.set(0.5, 0);
LK.gui.top.addChild(roundText);
roundText.y = 100;
// Add a second round display text for compatibility with code using roundDisplayText
var roundDisplayText = new Text2("Round: " + currentRound, {
size: 36,
fill: 0xFFFFFF
});
roundDisplayText.anchor.set(0.5, 0);
LK.gui.top.addChild(roundDisplayText);
roundDisplayText.y = 160;
var deckCountText = new Text2("Deck: " + mainDeck.length, {
size: 36,
fill: 0xFFFFFF
});
deckCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(deckCountText);
deckCountText.x = 120;
deckCountText.y = 150;
var discardCountText = new Text2("Discard: 0", {
size: 36,
fill: 0xFFFFFF
});
discardCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(discardCountText);
discardCountText.x = 120;
discardCountText.y = 190;
var basicEventCountText = new Text2("Basic Events: 10", {
size: 28,
fill: 0x00FF00
});
basicEventCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(basicEventCountText);
basicEventCountText.x = 120;
basicEventCountText.y = 230;
var advancedEventCountText = new Text2("Advanced Events: 10", {
size: 28,
fill: 0xFF6600
});
advancedEventCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(advancedEventCountText);
advancedEventCountText.x = 120;
advancedEventCountText.y = 260;
var graveyardCountText = new Text2("Graveyard: 0", {
size: 28,
//{4f_graveyard}
fill: 0xFFFFFF
}); //{4g_graveyard}
graveyardCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(graveyardCountText);
graveyardCountText.x = 120;
graveyardCountText.y = 290;
var eventDiscardCountText = new Text2("Event Discard: 0", {
size: 28,
fill: 0x9932CC
});
eventDiscardCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(eventDiscardCountText);
eventDiscardCountText.x = 120;
eventDiscardCountText.y = 320;
var graveyard = [];
var at15PlayedLastTurn = false; // Track if AT15 was played to enforce draw 4 next turn
var at18Active = false; // Track if AT18 special is being processed
var at18CarnivoreList = []; // Carnivores available for AT18 selection
var scryText = new Text2("Scry", {
size: 32,
fill: 0xFFFFFF
});
scryText.anchor.set(0, 0);
scryText.tint = 0x888888; // Start gray/inactive
LK.gui.topRight.addChild(scryText);
scryText.x = -300;
scryText.y = 190;
// Add scry button functionality
scryText.down = function () {
if (scryTokenActive && !scryMode && mainDeck.length >= 3) {
activateScry();
}
};
// Initialize basic terrain pool (23 cards total)
// BT01-05: Land - Flatland
// BT06-10: Land - Hills
// BT11-14: Land - Mountains
// BT15-20: Water - Sea
// BT21-23: Water - Fresh Water
function createBasicTerrainPool() {
basicTerrainPool = [];
// BT01-05: Land - Flatland (5 cards)
for (var i = 0; i < 5; i++) {
basicTerrainPool.push({
type: 'basic',
level: 'Basic',
id: 'BT' + (i + 1).toString().padStart(2, '0'),
subtype: 'land',
landType: 'flat',
climate: null // Will be assigned during setup
});
}
// BT06-10: Land - Hills (5 cards)
for (var i = 0; i < 5; i++) {
basicTerrainPool.push({
type: 'basic',
level: 'Basic',
id: 'BT' + (i + 6).toString().padStart(2, '0'),
subtype: 'land',
landType: 'hills',
climate: null // Will be assigned during setup
});
}
// BT11-14: Land - Mountains (4 cards)
for (var i = 0; i < 4; i++) {
basicTerrainPool.push({
type: 'basic',
level: 'Basic',
id: 'BT' + (i + 11).toString().padStart(2, '0'),
subtype: 'land',
landType: 'mountain',
climate: null // Will be assigned during setup
});
}
// BT15-20: Water - Sea (6 cards)
for (var i = 0; i < 6; i++) {
basicTerrainPool.push({
type: 'basic',
level: 'Basic',
id: 'BT' + (i + 15).toString().padStart(2, '0'),
subtype: 'water',
waterType: 'sea',
climate: null // Will be assigned during setup
});
}
// BT21-23: Water - Fresh Water (3 cards)
for (var i = 0; i < 3; i++) {
basicTerrainPool.push({
type: 'basic',
level: 'Basic',
id: 'BT' + (i + 21).toString().padStart(2, '0'),
subtype: 'water',
waterType: 'fresh',
climate: null // Will be assigned during setup
});
}
// Shuffle terrain pool
for (var i = basicTerrainPool.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = basicTerrainPool[i];
basicTerrainPool[i] = basicTerrainPool[j];
basicTerrainPool[j] = temp;
}
}
// Setup planet board with terrain cards
function setupPlanet() {
// Create 2D array for planet board
for (var y = 0; y < planetHeight; y++) {
planetBoard[y] = [];
planetSlots[y] = [];
for (var x = 0; x < planetWidth; x++) {
planetBoard[y][x] = null;
planetSlots[y][x] = null;
}
}
// Place terrain cards according to 2-4-6-4-2 layout
var terrainIndex = 0;
var startY = 400;
var cardSpacing = 320;
// Add climate row backgrounds first
for (var row = 0; row < planetLayout.length; row++) {
var climate;
if (row === 0 || row === 4) {
climate = 'cold'; // Rows 1 & 5
} else if (row === 1 || row === 3) {
climate = 'temperate'; // Rows 2 & 4
} else {
climate = 'hot'; // Row 3
}
// Create climate row background
var climateRowAsset = 'climateRow' + climate.charAt(0).toUpperCase() + climate.slice(1);
var climateRowBg = LK.getAsset(climateRowAsset, {
anchorX: 0.5,
anchorY: 0.5
});
climateRowBg.x = 1024; // Center of screen
climateRowBg.y = startY + row * 420;
climateRowBg.alpha = 0.3; // Semi-transparent background
game.addChild(climateRowBg);
// Add climate label text
var climateLabel = new Text2(climate.toUpperCase(), {
size: 32,
fill: 0x000000,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif"
});
climateLabel.anchor.set(0.5, 0.5);
climateLabel.rotation = -Math.PI / 2; // Rotate 90 degrees counter-clockwise to make vertical
climateLabel.x = 50; // Move left to make fully visible
climateLabel.y = startY + row * 420;
game.addChild(climateLabel);
}
for (var row = 0; row < planetLayout.length; row++) {
var cardsInRow = planetLayout[row];
var startX = 1024 - cardsInRow * cardSpacing / 2 + cardSpacing / 2 + 50; // Shift cards right by 50px
// Assign climate based on row
var climate;
if (row === 0 || row === 4) {
climate = 'cold'; // Rows 1 & 5
} else if (row === 1 || row === 3) {
climate = 'temperate'; // Rows 2 & 4
} else {
climate = 'hot'; // Row 3
}
for (var col = 0; col < cardsInRow && terrainIndex < basicTerrainPool.length; col++) {
// Assign climate to terrain card
var terrainData = basicTerrainPool[terrainIndex];
terrainData.climate = climate;
// Create terrain card
var terrainCard = new TerrainCard(terrainData);
terrainCard.x = startX + col * cardSpacing;
terrainCard.y = startY + row * 420;
terrainCard.gridX = col;
terrainCard.gridY = row;
game.addChild(terrainCard);
// Store in board
var boardX = Math.floor((planetWidth - cardsInRow) / 2) + col;
planetBoard[row][boardX] = terrainCard;
// Create slot for this position
var slot = new PlanetSlot(boardX, row);
slot.terrainCard = terrainCard;
planetSlots[row][boardX] = slot;
terrainIndex++;
}
}
}
// Create main deck with advanced terrain cards and creature cards
function createInitialDeck() {
mainDeck = [];
// Add 20 Advanced Terrain Cards (AT01-AT20)
// AT01-02: Fresh Water - Grey band, no climate requirements
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'water',
waterType: 'fresh',
id: 'AT01',
colorBand: 'grey',
climateRequirement: 'any',
name: 'Advanced Fresh Water'
});
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'water',
waterType: 'fresh',
id: 'AT02',
colorBand: 'grey',
climateRequirement: 'any',
name: 'Advanced Fresh Water'
});
// AT03-07: Sea Water - Dark Blue band
// AT03: Hot, AT04: Cold, AT05: Temperate, AT06: Hot or Temperate, AT07: Special
var seaCardConfigs = [{
id: 'AT03',
climate: 'hot',
special: false
}, {
id: 'AT04',
climate: 'cold',
special: false
}, {
id: 'AT05',
climate: 'temperate',
special: false
}, {
id: 'AT06',
climate: 'hot/temperate',
special: false
}, {
id: 'AT07',
climate: 'any',
special: true
}];
for (var i = 0; i < seaCardConfigs.length; i++) {
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'water',
waterType: 'sea',
id: seaCardConfigs[i].id,
colorBand: 'darkblue',
climateRequirement: seaCardConfigs[i].climate,
special: seaCardConfigs[i].special,
name: 'Advanced Sea Water'
});
}
// AT08-12: Flat Land - Brown band
// AT08: Hot, AT09: Temperate, AT10: Cold, AT11: Temperate or Cold, AT12: Temperate or Hot + Special
var flatCardConfigs = [{
id: 'AT08',
climate: 'hot',
special: false
}, {
id: 'AT09',
climate: 'temperate',
special: false
}, {
id: 'AT10',
climate: 'cold',
special: false
}, {
id: 'AT11',
climate: 'temperate/cold',
special: false
}, {
id: 'AT12',
climate: 'temperate/hot',
special: true
}];
for (var i = 0; i < flatCardConfigs.length; i++) {
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'land',
landType: 'flat',
id: flatCardConfigs[i].id,
colorBand: 'brown',
climateRequirement: flatCardConfigs[i].climate,
special: flatCardConfigs[i].special,
name: 'Advanced Flat Land'
});
}
// AT13-17: Hills - Purple band
// AT13: Temperate, AT14: Cold, AT15: Hot + Special, AT16: Temperate or Cold, AT17: Temperate or Hot
var hillsCardConfigs = [{
id: 'AT13',
climate: 'temperate',
special: false
}, {
id: 'AT14',
climate: 'cold',
special: false
}, {
id: 'AT15',
climate: 'hot',
special: true
}, {
id: 'AT16',
climate: 'temperate/cold',
special: false
}, {
id: 'AT17',
climate: 'temperate/hot',
special: false
}];
for (var i = 0; i < hillsCardConfigs.length; i++) {
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'land',
landType: 'hills',
id: hillsCardConfigs[i].id,
colorBand: 'purple',
climateRequirement: hillsCardConfigs[i].climate,
special: hillsCardConfigs[i].special,
name: 'Advanced Hills'
});
}
// AT18-20: Mountains - Light Blue band
// AT18: No climate requirements + Special, AT19: Temperate or Cold, AT20: Temperate or Hot
var mountainCardConfigs = [{
id: 'AT18',
climate: 'any',
special: true
}, {
id: 'AT19',
climate: 'temperate/cold',
special: false
}, {
id: 'AT20',
climate: 'temperate/hot',
special: false
}];
for (var i = 0; i < mountainCardConfigs.length; i++) {
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'land',
landType: 'mountain',
id: mountainCardConfigs[i].id,
colorBand: 'lightblue',
climateRequirement: mountainCardConfigs[i].climate,
special: mountainCardConfigs[i].special,
name: 'Advanced Mountains'
});
}
// Add 33 Basic Herbivore Cards (BH01-BH33)
var basicHerbivoreConfigs = [{
id: 'BH01',
name: 'Pond Turtle',
waterType: 'fresh',
colorBand: 'grey',
//{bh01_terrain}
climate: 'temperate',
//{bh01_climate}
special: false //{bh01_special}
}, {
id: 'BH02',
name: 'Crayfish',
waterType: 'fresh',
colorBand: 'grey',
climate: 'cold',
special: false
}, {
id: 'BH03',
name: 'Newt',
waterType: 'fresh',
colorBand: 'grey',
climate: 'temperate/cold',
special: true
}, {
id: 'BH04',
name: 'Toad',
waterType: 'fresh',
colorBand: 'grey',
climate: 'temperate/hot',
special: true
}, {
id: 'BH05',
name: 'Mud Skipper',
waterType: 'fresh',
colorBand: 'grey',
climate: 'temperate/hot',
special: true
}, {
id: 'BH06',
name: 'Tropical Crab',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'hot',
special: false
}, {
id: 'BH07',
name: 'Plankton',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'cold',
special: true
}, {
id: 'BH08',
name: 'Limpet',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate',
special: false
}, {
id: 'BH09',
name: 'Red Crab',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'hot',
special: false
}, {
id: 'BH10',
name: 'Jelly Fish',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'cold',
special: true
}, {
id: 'BH11',
name: 'Mussel',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate',
special: true
}, {
id: 'BH12',
name: 'Whelk',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/cold',
special: false
}, {
id: 'BH13',
name: 'Krill',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/hot',
special: true
}, {
id: 'BH14',
name: 'Hare',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate',
special: false
}, {
id: 'BH15',
name: 'Meerkat',
landType: 'flat',
colorBand: 'brown',
climate: 'hot',
special: false
}, {
id: 'BH16',
name: 'Pheasant',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate',
special: true
}, {
id: 'BH17',
name: 'Tortoise',
landType: 'flat',
colorBand: 'brown',
climate: 'hot',
special: false
}, {
id: 'BH18',
name: 'Goose',
landType: 'flat',
colorBand: 'brown',
climate: 'cold',
special: true
}, {
id: 'BH19',
name: 'Pig',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate/hot',
special: false
}, {
id: 'BH20',
name: 'Platypus',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate/cold',
special: true
}, {
id: 'BH21',
name: 'Rabbit',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate',
special: false
}, {
id: 'BH22',
name: 'Chicken',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate',
special: false
}, {
id: 'BH23',
name: 'Ice Squirrel',
landType: 'hills',
colorBand: 'purple',
climate: 'cold',
special: false
}, {
id: 'BH24',
name: 'Pea Fowl',
landType: 'hills',
colorBand: 'purple',
climate: 'hot',
special: false
}, {
id: 'BH25',
name: 'Mole-Rat',
landType: 'hills',
colorBand: 'purple',
climate: 'hot',
special: false
}, {
id: 'BH26',
name: 'Guinnea pig',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate/cold',
special: true
}, {
id: 'BH27',
name: 'Wombat',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate/hot',
special: true
}, {
id: 'BH28',
name: 'Marmoset',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate',
special: false
}, {
id: 'BH29',
name: 'Porcupine',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate',
special: false
}, {
id: 'BH30',
name: 'Skunk',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'hot',
special: true
}, {
id: 'BH31',
name: 'Shaggy Sheep',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'cold',
special: true
}, {
id: 'BH32',
name: 'Worms',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate/hot',
special: true
}, {
id: 'BH33',
name: 'Giant Snail',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate/cold',
special: true
}];
for (var i = 0; i < basicHerbivoreConfigs.length; i++) {
var config = basicHerbivoreConfigs[i];
var subtype = config.waterType ? 'water' : 'land';
var waterType = config.waterType || null;
var landType = config.landType || null;
mainDeck.push({
type: 'basic',
level: 'Basic',
cardType: 'creature',
dietType: 'herbivore',
name: config.name,
id: config.id,
subtype: subtype,
waterType: waterType,
landType: landType,
colorBand: config.colorBand,
climateRequirement: config.climate,
special: config.special,
flying: config.id === 'BH18' || config.id === 'BH16' ? true : false,
swimming: config.id === 'BH02' || config.id === 'BH07' || config.id === 'BH10' || config.id === 'BH13' || config.id === 'BH20' ? true : false,
tough: config.id === 'BH31' ? true : false,
squishy: config.id === 'BH03' || config.id === 'BH27' ? true : false,
stinky: config.id === 'BH04' || config.id === 'BH30' ? true : false,
efficient: config.id === 'BC03' ? true : false,
// Efficient for BC03 (Puffin)
whaleFood: config.id === 'BH07' || config.id === 'BH13' ? true : false,
whaleChow: config.id === 'BH07' || config.id === 'BH13' ? true : false,
bully: config.id === 'BH07' || config.id === 'BH13' ? true : false
});
}
// Add 8 Advanced Carnivore Cards (AC01-AC08)
var advancedCarnivoreConfigs = [{
id: 'AC01',
name: 'Pike',
waterType: 'fresh',
colorBand: 'grey',
climate: 'any',
special: true
}, {
id: 'AC02',
name: 'Great White Shark',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'any',
special: true,
bully: true //{c4_bully}
}, {
id: 'AC03',
name: 'Whale',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'any',
special: true
}, {
id: 'AC04',
name: 'Snakes!',
landType: 'any',
colorBand: 'brown',
climate: 'any',
special: true
}, {
id: 'AC05',
name: 'Bear',
landType: 'any',
colorBand: 'brown',
climate: 'any',
special: true
}, {
id: 'AC06',
name: 'Buzzard',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate/cold',
special: true
}, {
id: 'AC07',
name: 'Tiger',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate/hot',
special: true
}, {
id: 'AC08',
name: 'Leopard',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate/hot',
special: true
}];
for (var i = 0; i < advancedCarnivoreConfigs.length; i++) {
var config = advancedCarnivoreConfigs[i];
var subtype = config.waterType ? 'water' : 'land';
var waterType = config.waterType || null;
var landType = config.landType || null;
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'creature',
dietType: 'carnivore',
name: config.name,
id: config.id,
subtype: subtype,
waterType: waterType,
landType: landType,
colorBand: config.colorBand,
climateRequirement: config.climate,
special: config.special,
flying: config.id === 'AC06' ? true : false,
swimming: config.id === 'AC01' || config.id === 'AC02' || config.id === 'AC03' ? true : false,
efficient: config.id === 'AC02' || config.id === 'AC04' || config.id === 'AC06' ? true : false,
// Efficient for AC02, AC04, AC06
isWhale: config.id === 'AC03' ? true : false,
bully: config.id === 'AC02' ? true : false //{c9_bully}
});
}
// Add 10 Basic Carnivore Cards (BC01-BC10)
var basicCarnivoreConfigs = [{
id: 'BC01',
name: 'King Fisher',
waterType: 'fresh',
colorBand: 'grey',
climate: 'any',
special: true
}, {
id: 'BC02',
name: 'Penguin',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/cold',
special: false
}, {
id: 'BC03',
name: 'Puffin',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/cold',
special: true,
fussy: true //{d7_fussy}
}, {
id: 'BC04',
name: 'Tuna',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/hot',
special: true,
hunterHunter: true //{dc_hh}
}, {
id: 'BC05',
name: 'Eagle',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'any',
special: true
}, {
id: 'BC06',
name: 'Chameleon',
landType: 'any',
colorBand: 'brown',
climate: 'any',
special: true
}, {
id: 'BC07',
name: 'Bat',
landType: 'flat',
colorBand: 'brown',
climate: 'hot',
special: true
}, {
//{9D_bc08}
id: 'BC08',
name: 'Albatross',
landType: 'hills',
colorBand: 'purple',
//{9E_bc08}
climate: 'cold',
//{9F_bc08}
special: true //{9G_bc08}
}, {
//{9D_bc09}
id: 'BC09',
name: 'Pendo',
landType: 'flat',
colorBand: 'brown',
//{9E_bc09}
climate: 'temperate/cold',
//{9F_bc09}
special: false //{9G_bc09}
}, {
//{9D_bc10}
id: 'BC10',
name: 'Stoat',
landType: 'hills',
colorBand: 'purple',
//{9E_bc10}
climate: 'hot/temperate',
//{9F_bc10}
special: false //{9G_bc10}
}];
for (var i = 0; i < basicCarnivoreConfigs.length; i++) {
var config = basicCarnivoreConfigs[i];
var subtype = config.waterType ? 'water' : 'land';
var waterType = config.waterType || null;
var landType = config.landType || null;
mainDeck.push({
type: 'basic',
level: 'Basic',
cardType: 'creature',
dietType: 'carnivore',
name: config.name,
id: config.id,
subtype: subtype,
waterType: waterType,
landType: landType,
colorBand: config.colorBand,
climateRequirement: config.climate,
special: config.special,
flying: config.id === 'BC01' || config.id === 'BC03' || config.id === 'BC05' || config.id === 'BC07' || config.id === 'BC08' || config.id === 'BC09' ? true : false,
swimming: config.id === 'BC04' ? true : false,
efficient: config.id === 'BC03' ? true : false,
// Efficient for BC03 (Puffin)//{dU_comment}
hunterHunter: config.id === 'BC04' ? true : false,
// Hunter-Hunter for BC04 (Tuna)
fussy: config.id === 'BC03' ? true : false //{dU_fussy}
// Fussy for BC03 (Puffin)
});
}
// Add 12 Advanced Herbivore Cards (AH01-AH12)
var advancedHerbivoreConfigs = [{
id: 'AH01',
name: 'Salmon',
waterType: 'fresh',
colorBand: 'grey',
//{ah01_terrain}
climate: 'any',
//{ah01_climate}
special: true //{ah01_special}
}, {
id: 'AH02',
name: 'Lobster',
waterType: 'sea',
colorBand: 'darkblue',
//{ah02_terrain}
climate: 'temperate',
//{ah02_climate}
special: false //{ah02_special}
}, {
id: 'AH03',
name: 'Herring',
waterType: 'sea',
colorBand: 'darkblue',
//{ah03_terrain}
climate: 'temperate/cold',
//{ah03_climate}
special: true //{ah03_special}
}, {
id: 'AH04',
name: 'Maceral',
waterType: 'sea',
colorBand: 'darkblue',
//{ah04_terrain}
climate: 'temperate/hot',
//{ah04_climate}
special: true //{ah04_special}
}, {
id: 'AH05',
name: 'Wildebeest',
landType: 'flat',
colorBand: 'brown',
//{ah05_terrain}
climate: 'hot',
//{ah05_climate}
special: false //{ah05_special}
}, {
id: 'AH06',
name: 'Moose',
landType: 'flat',
colorBand: 'brown',
//{ah06_terrain}
climate: 'cold',
//{ah06_climate}
special: false //{ah06_special}
}, {
id: 'AH07',
name: 'Gazelle',
landType: 'flat',
colorBand: 'brown',
//{ah07_terrain}
climate: 'temperate',
//{ah07_climate}
special: false //{ah07_special}
}, {
id: 'AH08',
name: 'Ibex',
landType: 'hills',
colorBand: 'purple',
//{ah08_terrain}
climate: 'hot',
//{ah08_climate}
special: false //{ah08_special}
}, {
id: 'AH09',
name: 'Arctic Hare',
landType: 'hills',
colorBand: 'purple',
//{ah09_terrain}
climate: 'cold',
//{ah09_climate}
special: false //{ah09_special}
}, {
id: 'AH10',
name: 'Elephant',
landType: 'hills',
colorBand: 'purple',
//{ah10_terrain}
climate: 'temperate',
//{ah10_climate}
special: true //{ah10_special}
}, {
id: 'AH11',
name: 'Mountain Goat',
landType: 'mountain',
colorBand: 'lightblue',
//{ah11_terrain}
climate: 'hot',
//{ah11_climate}
special: true //{ah11_special}
}, {
id: 'AH12',
name: 'Llama',
landType: 'mountain',
colorBand: 'lightblue',
//{ah12_terrain}
climate: 'temperate',
//{ah12_climate}
special: true //{ah12_special}
}]; //{ah12_end}
for (var i = 0; i < advancedHerbivoreConfigs.length; i++) {
var config = advancedHerbivoreConfigs[i];
var subtype = config.waterType ? 'water' : 'land';
var waterType = config.waterType || null;
var landType = config.landType || null;
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'creature',
dietType: 'herbivore',
name: config.name,
id: config.id,
subtype: subtype,
waterType: waterType,
landType: landType,
colorBand: config.colorBand,
climateRequirement: config.climate,
special: config.special,
swimming: config.id === 'AC01' || config.id === 'AC02' || config.id === 'AC03' || config.id === 'AH01' || config.id === 'AH02' || config.id === 'AH03' || config.id === 'AH04' ? true : false,
tough: config.id === 'AH10' ? true : false,
efficient: config.id === 'AC02' || config.id === 'AC04' || config.id === 'AC06' ? true : false,
// Efficient for AC02, AC04, AC06
whaleFood: config.id === 'AH01' ? false : config.id === 'AH02' || config.id === 'AH03' || config.id === 'AH04' ? false : false
});
}
// Shuffle deck
shuffleDeck();
}
function shuffleDeck() {
for (var i = mainDeck.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = mainDeck[i];
mainDeck[i] = mainDeck[j];
mainDeck[j] = temp;
}
}
function shuffleEventDeck(eventDeck) {
for (var i = eventDeck.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = eventDeck[i];
eventDeck[i] = eventDeck[j];
eventDeck[j] = temp;
}
}
function showCardInfo(card) {
hoveredCard = card;
// Remove existing info text if any
if (cardInfoText && cardInfoText.parent) {
cardInfoText.parent.removeChild(cardInfoText);
}
var infoString = "";
if (card.creatureData) {
// Creature card information
var creature = card.creatureData;
if (creature) {
infoString = creature.name + " | Level: " + creature.level + " " + creature.dietType;
if (creature && creature.terrainRequirement && creature.terrainRequirement !== 'any') {
infoString += " | Terrain: " + creature.terrainRequirement;
}
if (creature && creature.climateRequirement && creature.climateRequirement !== 'any') {
infoString += " | Climate: " + creature.climateRequirement;
}
// Add special rules text for Tough
if (creature && creature.tough) {
infoString += " | Tough: Creature requires 3 or more extinction markers before it must become extinct";
}
if (creature && creature.squishy) {
infoString += " | Squishy: Creature requires only 1 or more extinction markers before it must become extinct";
}
if (creature && creature.stinky) {
infoString += " | Stinky: Discard all non-Stinky creatures. Phew!";
}
if (creature && creature.efficient) {
infoString += " | Efficient: This creature can make diagonal links as well as adjacent ones";
}
if (creature && creature.whaleFood) {
infoString += " | Whale chow!: This creature can only be linked to whales";
}
if (creature && creature.id === 'AC03') {
infoString += " | Whale: This creature can link to Krill and Plankton";
}
if (creature && creature.hunterHunter) {
infoString += " | Hunter-Hunter: This creature can only link to Carnivore cards NOT Herbivores!";
} //{fl_hh}
if (creature && creature.fussy) {
infoString += " | Fussy: This creature can only link to Basic Herbivore cards NOT Advanced Herbivores!";
} //{fl_fussy}
if (creature && creature.bully) {
infoString += " | Bully: This creature can create links to Basic Carnivores as well as Basic Herbivores!";
} //{fl_bully}
}
} else if (card.terrainData) {
// Terrain card information
var terrain = card.terrainData;
if (terrain) {
infoString = terrain.name || terrain.level + " " + (terrain.landType || terrain.waterType);
infoString += " | Level: " + terrain.level + " terrain";
if (terrain.climateRequirement && terrain.climateRequirement !== 'any') {
infoString += " | Climate Required: " + terrain.climateRequirement;
}
// Add special rules text for AT15
if (terrain.id === 'AT15') {
infoString += " | Ancient: Draw 4 cards next turn";
} //{ej_special}
// Add special rules text for AT18
if (terrain.id === 'AT18') {
infoString += " | Hostile: Put 1 Carnivore card from the discard pile onto the top of the play deck if any are present";
} //{ej_special_AT18}
}
}
// Create info text at bottom of screen
cardInfoText = new Text2(infoString, {
size: 28,
fill: 0xFFFFFF
});
cardInfoText.anchor.set(0.5, 1);
cardInfoText.x = 1024; // Center of screen
cardInfoText.y = 2700; // Bottom of screen
game.addChild(cardInfoText);
}
function hideCardInfo() {
if (cardInfoText && cardInfoText.parent) {
cardInfoText.parent.removeChild(cardInfoText);
cardInfoText = null;
}
hoveredCard = null;
}
function zoomCard(card) {
// If a different card was previously zoomed, clear it
if (currentHoveredCardForZoom && currentHoveredCardForZoom !== card) {
clearZoom();
}
// If card is already zoomed, do nothing
if (currentHoveredCardForZoom === card && zoomedCard) {
return;
}
// Clear existing zoomed card
if (zoomedCard && zoomedCard.parent) {
zoomedCard.parent.removeChild(zoomedCard);
zoomedCard = null;
}
// Track current hovered card
currentHoveredCardForZoom = card;
// Create a clone of the card for zooming
var clonedCard;
if (card.creatureData) {
clonedCard = new CreatureCard(card.creatureData);
} else if (card.terrainData) {
clonedCard = new TerrainCard(card.terrainData);
} else if (card.eventData) {
clonedCard = new EventCard(card.eventData);
} else {
return;
}
// 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 = 680;
// 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;
}
}
}
}
}
return false;
}
function discardCards(cards) {
for (var i = 0; i < cards.length; i++) {
var card = cards[i];
// Remove from hand
var handIndex = playerHand.indexOf(card);
if (handIndex !== -1) {
playerHand.splice(handIndex, 1);
}
// Remove from game
if (card.parent) {
card.parent.removeChild(card);
}
// Extract raw card data and add to discard pile
var cardData = card.creatureData || card.terrainData || card.eventData;
discardPile.push(cardData);
}
updateHandPositions();
// Clear zoom if hand is now empty
clearZoomIfNoCards(); //{ip_new}
}
function drawCard() {
if (mainDeck.length === 0) {
endRound();
return null;
}
// Enforce maximum hand size of 3
if (playerHand.length >= maxHandSize) {
return null; // Hand is full
}
if (drawPhase === 'complete') {
return null; // Draw phase already complete
}
var cardData = mainDeck.pop();
var card;
// Create appropriate card type based on cardType
if (cardData.cardType === 'terrain') {
card = new TerrainCard(cardData);
// Scale terrain cards in hand to match creature cards
card.scaleX = 0.7;
card.scaleY = 0.7;
// Scale terrain cards in hand to match creature cards
card.scaleX = 0.7;
card.scaleY = 0.7;
} else {
// Default to creature card
card = new CreatureCard(cardData);
}
if (drawPhase === 'initial') {
// Initial draw phase - draw up to 3 cards
playerHand.push(card);
cardsDrawnInPhase.push(card);
cardsDrawnThisTurn++;
if (cardsDrawnInPhase.length >= 3) {
// Check if any of the 3 cards have valid placements
if (!hasValidPlacements(cardsDrawnInPhase)) {
// Discard all 3 cards and enter forced draw phase
discardCards(cardsDrawnInPhase);
cardsDrawnInPhase = [];
drawPhase = 'forced';
// Continue drawing in forced phase
return drawCard();
} else {
// At least one card has valid placement, end draw phase
drawPhase = 'complete';
}
}
} else if (drawPhase === 'forced') {
// Forced draw phase - draw one card at a time until playable
if (hasValidPlacements([card])) {
// This card can be played, add to hand and must be played
playerHand.push(card);
drawPhase = 'complete';
// Mark this card as must play
card.mustPlay = true;
} else {
// Card cannot be played, discard it and draw another
discardCards([card]);
return drawCard();
}
} else {
return null; // Draw phase complete
}
// Position card in hand
updateHandPositions();
game.addChild(card);
// Update UI
deckCountText.setText("Deck: " + mainDeck.length);
LK.getSound('cardDraw').play();
// Check for Stinky effect on newly drawn card
checkAndProcessStinkyEffect(); //{j8_stinky}
return card;
}
function startDrawPhase() {
if (mainDeck.length === 0) {
endRound();
return;
}
drawPhase = 'initial';
cardsDrawnInPhase = [];
cardsDrawnThisTurn = 0;
// Draw initial 3 cards
for (var i = 0; i < 3; i++) {
if (mainDeck.length > 0) {
drawCard();
}
}
// Check for Stinky creatures after drawing initial 3 cards
checkAndProcessStinkyEffect();
}
function checkAndProcessStinkyEffect() {
// Check if any creature in hand has Stinky attribute
var hasStinkyCreature = false;
var stinkyCreature = null;
for (var i = 0; i < playerHand.length; i++) {
//{stinky_check_1}
var card = playerHand[i];
if (card.creatureData && card.creatureData.stinky) {
//{stinky_check_2}
hasStinkyCreature = true;
stinkyCreature = card;
break;
} //{stinky_check_3}
} //{stinky_check_4}
// If Stinky creature found, discard all non-stinky creatures
if (hasStinkyCreature) {
//{stinky_process_1}
var toDiscard = [];
for (var j = 0; j < playerHand.length; j++) {
//{stinky_process_2}
var c = playerHand[j];
// Discard only if it has creatureData AND does NOT have stinky property true
if (c.creatureData && !c.creatureData.stinky) {
//{stinky_process_3}
toDiscard.push(c);
} //{stinky_process_4}
} //{stinky_process_5}
// Execute discard
if (toDiscard.length > 0) {
//{stinky_process_6}
discardCards(toDiscard);
} //{stinky_process_7}
} //{stinky_process_8}
// Check for event cards in hand after stinky processing and set flag
hasEventCardsInHand = false;
for (var k = 0; k < playerHand.length; k++) {
if (playerHand[k].eventData) {
hasEventCardsInHand = true;
break;
}
}
}
function checkAndShowEventCardDebugBox() {
// Check if any event cards are in hand
var eventCardsInHand = [];
for (var i = 0; i < playerHand.length; i++) {
//{debug_event_1}
if (playerHand[i].eventData) {
//{debug_event_2}
eventCardsInHand.push(playerHand[i]);
} //{debug_event_3}
} //{debug_event_4}
// If event cards found, show debug box
if (eventCardsInHand.length > 0) {
//{debug_event_5}
showEventCardDebugBox();
} //{debug_event_6}
} //{debug_event_7}
function showEventCardDebugBox() {
// Create debug notification box
var eventDebugBox = LK.getAsset('terrainLandFlat', {
//{debug_event_8}
anchorX: 0.5,
//{debug_event_9}
anchorY: 0.5 //{debug_event_10}
}); //{debug_event_11}
eventDebugBox.tint = 0xA020F0; // Purple for event cards
eventDebugBox.alpha = 0.95; //{debug_event_12}
eventDebugBox.scaleX = 2.0;
eventDebugBox.scaleY = 1.0;
eventDebugBox.x = 1024;
eventDebugBox.y = 1000;
game.addChild(eventDebugBox); //{debug_event_13}
// Create debug text
var debugText = new Text2("Event card/s drawn, click to resolve", {
//{debug_event_14}
size: 32,
//{debug_event_15}
fill: 0xFFFFFF //{debug_event_16}
}); //{debug_event_17}
debugText.anchor.set(0.5, 0.5);
debugText.x = 0;
debugText.y = 0;
eventDebugBox.addChild(debugText); //{debug_event_18}
// Make box clickable to resolve event cards
eventDebugBox.down = function () {
//{debug_event_19}
// Discard all cards from current hand
var cardsToDiscard = playerHand.slice(); //{debug_event_20}
discardCards(cardsToDiscard); //{debug_event_21}
// Remove debug box
if (eventDebugBox.parent) {
//{debug_event_22}
eventDebugBox.parent.removeChild(eventDebugBox); //{debug_event_23}
} //{debug_event_24}
// Progress to next phase (Noon/Dusk depending on current phase)
gamePhase = 'dusk'; //{debug_event_25}
processDuskPhase(); //{debug_event_26}
}; //{debug_event_27}
} //{debug_event_28}
function updateHandPositions() {
var handY = 2400;
var handStartX = 1024 - playerHand.length * 200 / 2 + 100;
for (var i = 0; i < playerHand.length; i++) {
var card = playerHand[i];
if (!card.isInPlay) {
tween(card, {
x: handStartX + i * 200,
y: handY
}, {
duration: 300
});
}
}
}
function canPlaceTerrainOnTerrain(advancedTerrainCard, basicTerrainCard) {
if (!advancedTerrainCard || !basicTerrainCard) {
return false;
}
var advancedTerrain = advancedTerrainCard.terrainData;
var basicTerrain = basicTerrainCard.terrainData;
// Check if this is actually an advanced terrain card
if (!advancedTerrain || advancedTerrain.level !== 'Advanced') {
return false;
}
// Check if target is a basic terrain card
if (!basicTerrain || basicTerrain.level !== 'Basic') {
return false;
}
// Check if terrain types match (land on land, water on water)
if (advancedTerrain.subtype !== basicTerrain.subtype) {
return false;
}
// Check if specific terrain types match
if (advancedTerrain.subtype === 'land') {
if (advancedTerrain.landType !== basicTerrain.landType) {
return false;
}
} else if (advancedTerrain.subtype === 'water') {
if (advancedTerrain.waterType !== basicTerrain.waterType) {
return false;
}
}
// Check climate requirements
if (advancedTerrain.climateRequirement && advancedTerrain.climateRequirement !== 'any') {
var climateMatches = false;
if (advancedTerrain.climateRequirement.indexOf('/') !== -1) {
// Handle multiple climate requirements (e.g., "temperate/hot")
var allowedClimates = advancedTerrain.climateRequirement.split('/');
for (var k = 0; k < allowedClimates.length; k++) {
if (allowedClimates[k] === basicTerrain.climate) {
climateMatches = true;
break;
}
}
} else {
// Single climate requirement
climateMatches = advancedTerrain.climateRequirement === basicTerrain.climate;
}
if (!climateMatches) {
return false;
}
}
return true;
}
function canPlaceCreatureOnTerrain(creatureCard, terrainCard) {
if (!creatureCard || !terrainCard) {
return false;
}
var creature = creatureCard.creatureData;
var terrain = terrainCard.terrainData;
// Check if this is actually a creature card
if (!creature) {
return false;
}
// Check terrain type matching based on creature's subtype
if (creature.subtype === 'water') {
// Creature requires water terrain
if (terrain.subtype !== 'water') {
return false;
}
// Check specific water type matching - creature waterType must match terrain waterType
if (creature.waterType && terrain.waterType && creature.waterType !== terrain.waterType) {
return false;
}
} else if (creature.subtype === 'land') {
// Creature requires land terrain
if (terrain.subtype !== 'land') {
return false;
}
// Check specific land type matching - creature landType must match terrain landType
if (creature.landType && terrain.landType && creature.landType !== terrain.landType) {
return false;
}
}
// If creature has no subtype or terrain type, it cannot be placed
if (!creature.subtype) {
return false;
}
// Check climate requirements - use basic terrain climate if advanced terrain is placed on basic
var terrainClimate = terrain.climate;
// If this is an advanced terrain on basic terrain, use the basic terrain's climate
if (terrainCard.basicTerrainUnderneath && terrainCard.basicTerrainUnderneath.terrainData) {
terrainClimate = terrainCard.basicTerrainUnderneath.terrainData.climate;
}
if (creature.climateRequirement && creature.climateRequirement !== 'any') {
var climateMatches = false;
if (creature.climateRequirement.indexOf('/') !== -1) {
// Handle multiple climate requirements (e.g., "temperate/hot")
var allowedClimates = creature.climateRequirement.split('/');
for (var k = 0; k < allowedClimates.length; k++) {
if (allowedClimates[k] === terrainClimate) {
climateMatches = true;
break;
}
}
} else {
// Single climate requirement
climateMatches = creature.climateRequirement === terrainClimate;
}
if (!climateMatches) {
return false;
}
}
// Advanced creatures can only be placed on advanced terrain
if (creature.level === 'Advanced') {
if (terrain.level !== 'Advanced') {
return false;
}
}
// Basic herbivores can only be placed on basic terrain (but advanced terrain counts as valid if it's on basic terrain)
if (creature.level === 'Basic' && creature.dietType === 'herbivore') {
// Allow placement on advanced terrain that's placed on basic terrain, or directly on basic terrain
if (terrain.level !== 'Basic' && !terrainCard.basicTerrainUnderneath) {
return false;
}
}
// Check creature stacking rules - verify top card in stack before allowing placement
if (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 - advanced creatures cannot be placed on empty terrain
if (creature.level === 'Advanced') {
return false; // Advanced creatures require existing creatures to stack on
}
}
return true;
}
function placeTerrainOnTerrain(advancedTerrainCard, basicTerrainCard, gridX, gridY) {
if (!canPlaceTerrainOnTerrain(advancedTerrainCard, basicTerrainCard)) {
return false;
}
// Remove card from hand
var handIndex = playerHand.indexOf(advancedTerrainCard);
if (handIndex !== -1) {
playerHand.splice(handIndex, 1);
}
// Set up advanced terrain card
advancedTerrainCard.gridX = gridX;
advancedTerrainCard.gridY = gridY;
advancedTerrainCard.isInPlay = true;
// Copy creature stack from basic terrain to advanced terrain
advancedTerrainCard.creatureStack = basicTerrainCard.creatureStack || [];
// Position advanced terrain card on top of basic terrain and adopt its dimensions
var targetX = basicTerrainCard.x;
var targetY = basicTerrainCard.y;
var targetScaleX = basicTerrainCard.scaleX;
var targetScaleY = basicTerrainCard.scaleY;
tween(advancedTerrainCard, {
x: targetX,
y: targetY,
scaleX: targetScaleX,
scaleY: targetScaleY
}, {
duration: 300
});
// Keep basic terrain underneath but update board references to advanced terrain
planetBoard[gridY][gridX] = advancedTerrainCard;
planetSlots[gridY][gridX].terrainCard = advancedTerrainCard;
// Store reference to basic terrain underneath
advancedTerrainCard.basicTerrainUnderneath = basicTerrainCard;
// Add advanced terrain to game at specific z-index (above basic terrain but below creatures)
var basicTerrainIndex = game.getChildIndex(basicTerrainCard);
game.addChildAt(advancedTerrainCard, basicTerrainIndex + 1);
// Check for invalid links when terrain changes (shouldn't affect creature types, but safety check)
if (advancedTerrainCard.creatureStack.length > 0) {
var topCreature = advancedTerrainCard.creatureStack[advancedTerrainCard.creatureStack.length - 1];
checkAndRemoveInvalidLinks(gridX, gridY, topCreature);
}
// Update creature positions if any exist and ensure they stay on top
for (var i = 0; i < advancedTerrainCard.creatureStack.length; i++) {
var creature = advancedTerrainCard.creatureStack[i];
// Store current position to prevent unwanted movement
var currentX = creature.x;
var currentY = creature.y;
// Remove and re-add creature to ensure it's on top
game.removeChild(creature);
game.addChild(creature);
// Keep creature at its current position - no animation needed
creature.x = currentX;
creature.y = currentY;
}
// Re-render all link lines to ensure they appear above the newly placed terrain
for (var linkIdx = 0; linkIdx < linkLines.length; linkIdx++) {
var linkLine = linkLines[linkIdx];
if (linkLine.parent) {
game.removeChild(linkLine);
game.addChild(linkLine);
}
}
// Update hand positions
updateHandPositions();
// Clear selection
selectedCard = null;
draggedCard = null;
// Reset draw phase after successful placement
if (advancedTerrainCard.mustPlay) {
drawPhase = 'complete';
}
// Check if AT15 was played and set flag for next turn
if (advancedTerrainCard.terrainData && advancedTerrainCard.terrainData.id === 'AT15') {
at15PlayedLastTurn = true;
}
// Check if AT18 was played and trigger special
if (advancedTerrainCard.terrainData && advancedTerrainCard.terrainData.id === 'AT18') {
processAT18Special();
} //{if_AT18}
// Check if this was the last event card or must play card
if (eventCardsToPlay.length > 0) {
//{ig}</antml>
// Remove this card from event cards to play
var eventIndex = eventCardsToPlay.indexOf(advancedTerrainCard);
if (eventIndex !== -1) {
eventCardsToPlay.splice(eventIndex, 1);
}
// If no more event cards to play, clear mustPlayCard and check for phase progression
if (eventCardsToPlay.length === 0) {
mustPlayCard = null;
// Update event cards tracking
hasEventCardsInHand = false;
// Discard remaining non-event cards and progress to dusk
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
if (!playerHand[i].eventData) {
remainingCards.push(playerHand[i]);
}
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
}
} else if (advancedTerrainCard.mustPlay || mustPlayCard === advancedTerrainCard) {
// This was a must play card, clear it and progress to dusk
mustPlayCard = null;
// Discard remaining cards
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
remainingCards.push(playerHand[i]);
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
} else {
// Regular card played, discard remaining cards and progress to dusk
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
remainingCards.push(playerHand[i]);
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
}
// Reset turn
cardsDrawnThisTurn = 0;
LK.getSound('cardPlace').play();
// Show adjust links button after card placement
showAdjustLinksButton();
return true;
}
function checkAndRemoveInvalidLinks(gridX, gridY, newTopCreature) {
// Check all links that involve creatures at this position
var invalidLinks = [];
// Check links FROM carnivores at this position
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
if (creature.creatureData.dietType === 'carnivore') {
// Check each active link from this carnivore
for (var j = creature.activeLinks.length - 1; j >= 0; j--) {
var link = creature.activeLinks[j];
var targetHerbivore = link.target;
// If the new top creature is a carnivore and the target is now covered, link becomes invalid
if (newTopCreature && newTopCreature.creatureData.dietType === 'carnivore' && targetHerbivore.gridX === gridX && targetHerbivore.gridY === gridY) {
invalidLinks.push({
carnivore: creature,
herbivore: targetHerbivore
});
}
}
}
}
}
// Check links TO herbivores at this position (if new creature is carnivore, herbivores below become invalid targets)
if (newTopCreature && newTopCreature.creatureData.dietType === 'carnivore') {
// Find all links pointing to herbivores at this position that are now covered
for (var linkIndex = linkLines.length - 1; linkIndex >= 0; linkIndex--) {
var linkLine = linkLines[linkIndex];
if (linkLine.herbivore.gridX === gridX && linkLine.herbivore.gridY === gridY) {
// This herbivore is now covered by a carnivore, so links to it are invalid
invalidLinks.push({
carnivore: linkLine.carnivore,
herbivore: linkLine.herbivore
});
}
}
}
// Remove invalid links with fall-off animation
for (var k = 0; k < invalidLinks.length; k++) {
var invalidLink = invalidLinks[k];
animateLinkFallOff(invalidLink.carnivore, invalidLink.herbivore);
}
}
function animateLinkFallOff(carnivore, herbivore) {
// Find the link line to animate
var linkLineToRemove = null;
for (var i = 0; i < linkLines.length; i++) {
if (linkLines[i].carnivore === carnivore && linkLines[i].herbivore === herbivore && !linkLines[i].isFallingOff) {
linkLineToRemove = linkLines[i];
break;
}
}
if (linkLineToRemove) {
// Mark link as falling off immediately to prevent duplicate animations
linkLineToRemove.isFallingOff = true;
// Remove from arrays immediately to clear logical ghost link
removeLink(carnivore, herbivore);
// Animate the link falling off
tween(linkLineToRemove, {
alpha: 0,
scaleY: linkLineToRemove.scaleY * 0.1,
y: linkLineToRemove.y + 50
}, {
duration: 500,
onFinish: function onFinish() {
// Remove the visual line after animation
if (linkLineToRemove.parent) linkLineToRemove.parent.removeChild(linkLineToRemove);
}
});
} else {
// No visual line found, just remove the link immediately
removeLink(carnivore, herbivore);
}
}
function placeCreatureOnStack(creatureCard, terrainCard, gridX, gridY) {
if (!canPlaceCreatureOnTerrain(creatureCard, terrainCard)) {
return false;
}
// Remove card from hand
var handIndex = playerHand.indexOf(creatureCard);
if (handIndex !== -1) {
playerHand.splice(handIndex, 1);
}
// Check for invalid links before placing the new creature
checkAndRemoveInvalidLinks(gridX, gridY, creatureCard);
// When ANY carnivore is played onto another creature, remove all links to and from the creature being covered
if (creatureCard.creatureData.dietType === 'carnivore' && terrainCard.creatureStack.length > 0) {
var creatureBelow = terrainCard.creatureStack[terrainCard.creatureStack.length - 1];
// Remove all links FROM the creature being covered (if it's a carnivore)
if (creatureBelow.creatureData.dietType === 'carnivore') {
for (var linkIdx = creatureBelow.activeLinks.length - 1; linkIdx >= 0; linkIdx--) {
animateLinkFallOff(creatureBelow, creatureBelow.activeLinks[linkIdx].target);
}
}
// Remove all links TO the creature being covered (if it's a herbivore or carnivore)
for (var linkIdx = linkLines.length - 1; linkIdx >= 0; linkIdx--) {
if (linkLines[linkIdx].herbivore === creatureBelow || linkLines[linkIdx].carnivore === creatureBelow && creatureBelow.creatureData.dietType === 'carnivore') {
animateLinkFallOff(linkLines[linkIdx].carnivore, linkLines[linkIdx].herbivore);
}
}
}
// If placing an advanced herbivore on top of a basic herbivore, remove all links to the basic herbivore below
if (creatureCard.creatureData.dietType === 'herbivore' && creatureCard.creatureData.level === 'Advanced') {
if (terrainCard.creatureStack.length > 0) {
var basicHerbivoreBelow = terrainCard.creatureStack[terrainCard.creatureStack.length - 1];
if (basicHerbivoreBelow.creatureData.dietType === 'herbivore' && basicHerbivoreBelow.creatureData.level === 'Basic') {
// Remove all links pointing to the basic herbivore below
for (var linkIdx = linkLines.length - 1; linkIdx >= 0; linkIdx--) {
if (linkLines[linkIdx].herbivore === basicHerbivoreBelow) {
animateLinkFallOff(linkLines[linkIdx].carnivore, basicHerbivoreBelow);
}
}
}
}
}
// Push existing creatures down in the stack (move them south)
var stackOffset = 20;
for (var i = 0; i < terrainCard.creatureStack.length; i++) {
var existingCreature = terrainCard.creatureStack[i];
// Clear extinction markers when another creature is placed on top
existingCreature.extinctionMarkers = 0;
existingCreature.updateExtinctionMarkers();
tween(existingCreature, {
y: existingCreature.y + stackOffset
}, {
duration: 200
});
}
// Add new creature to top of stack
terrainCard.creatureStack.push(creatureCard);
creatureCard.gridX = gridX;
creatureCard.gridY = gridY;
creatureCard.isInPlay = true;
// Position new creature at the top position (where first creature would go)
var targetX = terrainCard.x;
var colorBarHeight = 42;
var targetY = terrainCard.y - terrainCard.height * terrainCard.scaleY / 2 + colorBarHeight + 150; // Position even lower, just below the color bar with 150px offset
// Calculate scale needed to match terrain card dimensions completely
var terrainWidth = 252; // Terrain card asset width
var terrainHeight = 336; // Terrain card asset height
var creatureWidth = 160; // Creature card asset width
var creatureHeight = 220; // Creature card asset height
// Scale to match terrain dimensions exactly, but reduce Y scale by half the color bar height with moderate scaling
var targetScaleX = terrainWidth / creatureWidth * terrainCard.scaleX;
var targetScaleY = (terrainHeight - colorBarHeight / 2) / creatureHeight * terrainCard.scaleY * 0.95; // Moderate 5% reduction to sit flush with color bar
// Start with small scale and animate up to target scale like advanced terrain
creatureCard.scaleX = 0.1;
creatureCard.scaleY = 0.1;
tween(creatureCard, {
x: targetX,
y: targetY,
scaleX: targetScaleX,
scaleY: targetScaleY
}, {
duration: 300
});
// Apply creature effects
applyCreatureEffect(creatureCard);
// Create link markers for carnivores when placed
creatureCard.createLinkMarkers();
// Update hand positions
updateHandPositions();
// Clear selection
selectedCard = null;
draggedCard = null;
// Reset draw phase after successful placement
if (creatureCard.mustPlay) {
drawPhase = 'complete';
}
// Check if this was the last event card or must play card
if (eventCardsToPlay.length > 0) {
// Remove this card from event cards to play
var eventIndex = eventCardsToPlay.indexOf(creatureCard);
if (eventIndex !== -1) {
eventCardsToPlay.splice(eventIndex, 1);
}
// If no more event cards to play, clear mustPlayCard and check for phase progression
if (eventCardsToPlay.length === 0) {
mustPlayCard = null;
// Update event cards tracking
hasEventCardsInHand = false;
// Discard remaining non-event cards and progress to dusk
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
if (!playerHand[i].eventData) {
remainingCards.push(playerHand[i]);
}
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
}
} else if (creatureCard.mustPlay || mustPlayCard === creatureCard) {
// This was a must play card, clear it and progress to dusk
mustPlayCard = null;
// Discard remaining cards
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
remainingCards.push(playerHand[i]);
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
} else {
// Regular card played, discard remaining cards and progress to dusk
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
remainingCards.push(playerHand[i]);
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
}
// Reset turn
cardsDrawnThisTurn = 0;
LK.getSound('cardPlace').play();
// Show adjust links button after card placement
showAdjustLinksButton();
return true;
}
function applyCreatureEffect(creatureCard) {
var creature = creatureCard.creatureData;
// Stack effects can be implemented here without health mechanics
}
function getAdjacentTerrains(gridX, gridY) {
var neighbors = [];
var directions = [{
x: -1,
y: 0
}, {
x: 1,
y: 0
}, {
x: 0,
y: -1
}, {
x: 0,
y: 1
}];
for (var i = 0; i < directions.length; i++) {
var newX = gridX + directions[i].x;
var newY = gridY + directions[i].y;
if (newX >= 0 && newX < planetWidth && newY >= 0 && newY < planetHeight) {
neighbors.push(planetBoard[newY][newX]);
}
}
return neighbors;
}
function createLink(carnivore, herbivore) {
if (!carnivore || !herbivore) {
return false;
}
if (carnivore.creatureData.dietType !== 'carnivore') {
return false;
}
if (herbivore.creatureData.dietType !== 'herbivore') {
return false;
}
// Check if carnivore and herbivore are adjacent
if (!areCreaturesAdjacent(carnivore, herbivore)) {
return false;
}
// Additional check: ensure we're linking to the top creature only
var herbivoreGridX = herbivore.gridX;
var herbivoreGridY = herbivore.gridY;
var terrain = planetBoard[herbivoreGridY][herbivoreGridX];
if (terrain && terrain.creatureStack.length > 0) {
var topCreature = terrain.creatureStack[terrain.creatureStack.length - 1];
if (topCreature !== herbivore) {
return false; // Can only link to top creature in stack
}
}
// For basic carnivores, check if already linked to this herbivore
if (carnivore.creatureData.level === 'Basic') {
for (var i = 0; i < carnivore.activeLinks.length; i++) {
if (carnivore.activeLinks[i].target === herbivore) {
return false; // Basic carnivores can only have one link per herbivore
}
}
}
// Check if this is a valid link target considering all special rules
if (!isValidLinkTarget(carnivore, herbivore)) {
return false; // Cannot link due to special rules (Whale food, etc)
} //{mL_new}
// Find an unlinked marker
var availableMarker = null;
for (var i = 0; i < carnivore.linkMarkers.length; i++) {
if (!carnivore.linkMarkers[i].isLinked) {
availableMarker = carnivore.linkMarkers[i];
break;
}
}
if (!availableMarker) {
return false;
} // No available link markers
// Create the link
availableMarker.isLinked = true;
availableMarker.targetHerbivore = herbivore;
availableMarker.tint = 0x00FF00; // Green for linked
// Add to active links
carnivore.activeLinks.push({
marker: availableMarker,
target: herbivore
});
// Create visual link line
createLinkLine(carnivore, herbivore);
return true;
}
function removeLink(carnivore, herbivore) {
if (!carnivore || !herbivore) {
return false;
}
// Find and remove the link
for (var i = carnivore.activeLinks.length - 1; i >= 0; i--) {
var link = carnivore.activeLinks[i];
if (link.target === herbivore) {
link.marker.isLinked = false;
link.marker.targetHerbivore = null;
link.marker.tint = 0xFF0000; // Red for unlinked
carnivore.activeLinks.splice(i, 1);
// Remove visual link line
removeLinkLine(carnivore, herbivore);
return true;
}
}
return false;
}
function isValidLinkTarget(carnivore, herbivore) {
if (!carnivore || !herbivore) {
return false;
}
// Check Whale food restriction: only Whale (AC03) can link to Whale food creatures
if (herbivore.creatureData && herbivore.creatureData.whaleFood) {
if (carnivore.creatureData.id !== 'AC03') {
return false; // Cannot link to Whale food unless carnivore is Whale
}
}
// Check Hunter-Hunter restriction: can only link to Carnivores, not Herbivores
if (carnivore.creatureData && carnivore.creatureData.hunterHunter) {
if (herbivore.creatureData.dietType !== 'carnivore') {
return false; // Hunter-Hunter can only link to carnivores
} //{n4_hh}
} //{n4_hh_end}
// Check Fussy restriction: can only link to Basic Herbivores
if (carnivore.creatureData && carnivore.creatureData.fussy) {
if (herbivore.creatureData.level !== 'Basic' || herbivore.creatureData.dietType !== 'herbivore') {
return false; // Fussy can only link to Basic Herbivores
} //{n4_fussy}
} //{n4_fussy_end}
// Check Bully special: can link to both herbivores and basic carnivores
if (carnivore.creatureData && carnivore.creatureData.bully) {
// Bully can link to Herbivores (any level) OR Basic Carnivores
// If target is a carnivore, it must be Basic level
if (herbivore.creatureData.dietType === 'carnivore') {
if (herbivore.creatureData.level !== 'Basic') {
return false; // Bully can only link to Basic Carnivores, not Advanced
}
}
// Otherwise (Herbivore target or valid Basic Carnivore target), allow the link
return true;
}
return true;
}
function areCreaturesAdjacent(creature1, creature2) {
if (!creature1.isInPlay || !creature2.isInPlay) {
return false;
}
var dx = Math.abs(creature1.gridX - creature2.gridX);
var dy = Math.abs(creature1.gridY - creature2.gridY);
// Check if creature1 has Efficient attribute
var creature1HasEfficient = creature1.creatureData && creature1.creatureData.efficient;
// Adjacent means exactly one grid space away (orthogonal or diagonal if Efficient)
if (creature1HasEfficient) {
// Efficient creatures can link to adjacent (including diagonal) creatures
return dx === 1 && dy === 0 || dx === 0 && dy === 1 || dx === 1 && dy === 1;
} else {
// Non-Efficient creatures can only link orthogonally (up, down, left, right)
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
}
}
function createLinkLine(carnivore, herbivore) {
// 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 second link to avoid stacking
var offsetDistance = 0;
if (existingLinkCount === 1) {
// Second link: offset perpendicular to link direction
// Perpendicular direction is 90 degrees from the link angle
var perpendicularAngle = angle + Math.PI / 2;
offsetDistance = 30; // Offset distance in pixels
var offsetX = Math.cos(perpendicularAngle) * offsetDistance;
var offsetY = Math.sin(perpendicularAngle) * offsetDistance;
// Apply offset to line position
line.x += offsetX;
line.y += offsetY;
// Change second link line to a different color for visualization
line.tint = 0xFF00FF; // Magenta/pink for second link
}
game.addChild(line);
// Add X2 indicator if this is the second link
if (existingLinkCount === 1) {
var x2Indicator = new Text2("X2", {
size: 20,
fill: 0xFFFFFF
}); //{e4_x2}
x2Indicator.anchor.set(0.5, 0.5);
x2Indicator.x = 0;
x2Indicator.y = 0;
line.addChild(x2Indicator);
x2Indicator.lineOwner = line;
} //{e4_x2_end}
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 (topCreature.creatureData.dietType === 'herbivore' && areCreaturesAdjacent(carnivore, topCreature)) {
// Check if this is a valid link target considering all special rules
if (!isValidLinkTarget(carnivore, topCreature)) {
// Cannot link to this herbivore due to special rules
continue;
} //{nx_new}
var canCreateLink = false;
// For basic carnivores, check if already linked to this herbivore
if (carnivore.creatureData.level === 'Basic') {
var hasExistingLink = false;
for (var j = 0; j < carnivore.activeLinks.length; j++) {
if (carnivore.activeLinks[j].target === topCreature) {
hasExistingLink = true;
break;
}
}
if (!hasExistingLink) {
canCreateLink = true;
}
} else {
// Advanced carnivores can always create links if they have available markers
var hasAvailableMarker = false;
for (var k = 0; k < carnivore.linkMarkers.length; k++) {
if (!carnivore.linkMarkers[k].isLinked) {
hasAvailableMarker = true;
break;
}
}
if (hasAvailableMarker) {
canCreateLink = true;
}
}
if (canCreateLink) {
// Create highlight around this herbivore
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
// Use different colors for diagonal highlights if Efficient
if (carnivoreHasEfficient) {
var dx = Math.abs(carnivore.gridX - topCreature.gridX);
var dy = Math.abs(carnivore.gridY - topCreature.gridY);
if (dx === 1 && dy === 1) {
// Diagonal link for Efficient creature
highlight.tint = 0x00FFFF; // Cyan for diagonal
} else {
// Orthogonal link
highlight.tint = 0x00FF00; // Green for orthogonal
}
} else {
highlight.tint = 0x00FF00; // Green highlight
}
highlight.alpha = 0.3;
highlight.scaleX = topCreature.scaleX * 1.2;
highlight.scaleY = topCreature.scaleY * 1.2;
highlight.x = topCreature.x;
highlight.y = topCreature.y;
highlight.targetCreature = topCreature;
game.addChild(highlight);
linkHighlights.push(highlight);
}
}
}
}
}
}
function clearLinkHighlights() {
for (var i = 0; i < linkHighlights.length; i++) {
if (linkHighlights[i].parent) {
linkHighlights[i].parent.removeChild(linkHighlights[i]);
}
}
linkHighlights = [];
}
function updateLinkMarkerStates() {
// Update all link markers based on their state and game phase
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
// ONLY process the top creature in the stack - creatures underneath are not active
var isTopCreature = i === terrain.creatureStack.length - 1;
if (!isTopCreature) {
// Skip creatures that are underneath other creatures - they cannot interact with links
continue;
} //{f9_underneath}
if (creature.creatureData.dietType === 'carnivore') {
// Check if this carnivore will become extinct (insufficient links)
var willBecomeExtinct = creature.activeLinks.length < creature.linkRequirement;
for (var j = 0; j < creature.linkMarkers.length; j++) {
var marker = creature.linkMarkers[j];
if (gamePhase === 'dusk' && linkAdjustmentMode) {
// During dusk phase with adjustment mode active, make markers interactive
if (marker.isLinked) {
marker.tint = 0x00FF00; // Green for linked
// Make linked markers draggable to remove links
marker.down = function (x, y, obj) {
draggedLinkMarker = this;
draggedLinkCarnivore = this.carnivore;
};
} else {
// Check if this marker can be legally linked
var hasValidTargets = false;
var adjacentTerrains = getAdjacentTerrains(gridX, gridY);
for (var k = 0; k < adjacentTerrains.length; k++) {
var adjTerrain = adjacentTerrains[k];
if (adjTerrain && adjTerrain.creatureStack.length > 0) {
// Only consider the top creature in each adjacent stack
var topHerbivore = adjTerrain.creatureStack[adjTerrain.creatureStack.length - 1];
if (topHerbivore.creatureData.dietType === 'herbivore') {
// For basic carnivores, check if already linked to this herbivore
if (creature.creatureData.level === 'Basic') {
var hasExistingLink = false;
for (var m = 0; m < creature.activeLinks.length; m++) {
if (creature.activeLinks[m].target === topHerbivore) {
hasExistingLink = true;
break;
}
}
if (!hasExistingLink) {
hasValidTargets = true;
break;
}
} else {
// Advanced carnivores can always link if they have available markers
hasValidTargets = true;
break;
}
}
}
if (hasValidTargets) {
break;
}
}
if (hasValidTargets) {
marker.tint = 0xFFFF00; // Yellow for linkable
// Make unlinked but valid markers draggable
marker.down = function (x, y, obj) {
draggedLinkMarker = this;
draggedLinkCarnivore = this.carnivore;
};
} else {
marker.tint = 0x808080; // Gray for non-linkable
marker.down = function (x, y, obj) {
// Do nothing for non-linkable markers
};
// Add N/A text
if (!marker.naText) {
marker.naText = new Text2("N/A", {
size: 8,
fill: 0xFFFFFF
});
marker.naText.anchor.set(0.5, 0.5);
marker.naText.x = 0;
marker.naText.y = 0;
marker.addChild(marker.naText);
}
}
}
} else {
// Outside dusk phase or not in adjustment mode, markers are not interactive
if (marker.isLinked) {
marker.tint = 0x00FF00; // Green for linked
} else {
marker.tint = 0xFF0000; // Red for unlinked
}
// Remove down handler and N/A text if present
marker.down = function (x, y, obj) {};
if (marker.naText && marker.naText.parent) {
marker.naText.parent.removeChild(marker.naText);
marker.naText = null;
}
}
// Flash markers if carnivore will become extinct
if (willBecomeExtinct) {
// Stop any existing flash animation
tween.stop(marker, {
alpha: true
});
// Start flashing animation
tween(marker, {
alpha: 0.3
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(marker, {
alpha: 1
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart the flash cycle if still extinct
if (creature.activeLinks.length < creature.linkRequirement) {
updateLinkMarkerStates();
}
}
});
}
});
} else {
// Stop flashing if no longer about to become extinct
tween.stop(marker, {
alpha: true
});
marker.alpha = 1;
}
}
}
}
}
}
}
}
function autoCreateLinks() {
// Auto-create mandatory links for carnivores
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var carnivore = terrain.creatureStack[i];
if (carnivore.creatureData.dietType === 'carnivore') {
// Try to create links for this carnivore
while (carnivore.activeLinks.length < carnivore.linkRequirement) {
var linkCreated = false;
// Find adjacent herbivores
var adjacentTerrains = getAdjacentTerrains(gridX, gridY);
for (var j = 0; j < adjacentTerrains.length && !linkCreated; j++) {
var adjTerrain = adjacentTerrains[j];
if (adjTerrain && adjTerrain.creatureStack.length > 0) {
// Only consider the top creature in each adjacent stack
var topHerbivore = adjTerrain.creatureStack[adjTerrain.creatureStack.length - 1];
if (topHerbivore.creatureData.dietType === 'herbivore') {
// Check if we can create a link
var hasExistingLink = false;
for (var l = 0; l < carnivore.activeLinks.length; l++) {
if (carnivore.activeLinks[l].target === topHerbivore) {
hasExistingLink = true;
break;
}
}
if (!hasExistingLink && createLink(carnivore, topHerbivore)) {
linkCreated = true;
}
}
}
}
if (!linkCreated) {
break;
} // No more links possible
}
}
}
}
}
}
}
function processLinkConsequences() {
// Hide the adjust links button when leaving dusk phase
hideAdjustLinksButton();
// NOTE: Extinction markers for herbivores are only processed during night phase
// This function only handles cleanup, not marker assignment
}
function processNightPhase() {
// Show visual cue that night phase is processing
showPhaseTransition("Night Phase - Checking links...", function () {
// Add extinction markers to carnivores without sufficient links
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
// ONLY process the top creature in the stack - creatures underneath are not active
var isTopCreature = i === terrain.creatureStack.length - 1;
if (!isTopCreature) {
// Skip creatures that are underneath other creatures
continue;
} //{ho_underneath}
if (creature.creatureData.dietType === 'carnivore') {
// For carnivores: check if they have sufficient links AT THIS MOMENT
if (creature.activeLinks.length < creature.linkRequirement) {
creature.extinctionMarkers += 1; // Only 1 marker per turn regardless of shortage
creature.updateExtinctionMarkers();
}
} else if (creature.creatureData.dietType === 'herbivore') {
// For herbivores: ONLY add extinction marker if it has 0 markers AND has excess links
// Once a herbivore has 1+ extinction markers, do NOT add more during Night phase
if (creature.extinctionMarkers === 0) {
//{t8_new}
// Count links pointing to them AT THIS MOMENT ONLY
var linksToThisHerbivore = 0;
for (var j = 0; j < linkLines.length; j++) {
//{t9_new}
if (linkLines[j].herbivore === creature) {
//{ta_new}
linksToThisHerbivore++;
} //{tb_new}
} //{tc_new}
// Add extinction marker ONLY if excess links exist
var excessLinks = linksToThisHerbivore - creature.safeLinks;
if (excessLinks > 0) {
//{td_new}
// Add exactly 1 marker if this herbivore has any excess links
creature.extinctionMarkers += 1;
creature.updateExtinctionMarkers(); //{te_new}
} //{tf_new}
} //{tg_new}
}
}
}
}
}
// Auto-progress to end turn phase
gamePhase = 'end';
processEndTurnPhase();
});
}
function processExtinctions() {
// Remove creatures with 2 or more extinction markers
var extinctCreatures = [];
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = terrain.creatureStack.length - 1; i >= 0; i--) {
var creature = terrain.creatureStack[i];
var extinctionThreshold = 2;
if (creature.creatureData && creature.creatureData.tough) {
extinctionThreshold = 3;
} else if (creature.creatureData && creature.creatureData.squishy) {
extinctionThreshold = 1;
}
if (creature.extinctionMarkers >= extinctionThreshold) {
extinctCreatures.push(creature);
terrain.creatureStack.splice(i, 1);
// Tween all creatures that were physically positioned below the dead creature UP by 20 pixels
for (var j = 0; j < i; j++) {
var creatureBelow = terrain.creatureStack[j];
tween(creatureBelow, {
y: creatureBelow.y - 20
}, {
duration: 200
});
}
creaturesWentExtinct = true; // Mark that creatures went extinct this round
scryDeniedNextRound = true; // Deny scry for next round when extinction occurs
// Clear extinction markers when creature leaves play
creature.extinctionMarkers = 0;
creature.updateExtinctionMarkers();
// Remove all links involving this creature with fall-off animation
if (creature.creatureData.dietType === 'carnivore') {
for (var j = creature.activeLinks.length - 1; j >= 0; j--) {
animateLinkFallOff(creature, creature.activeLinks[j].target);
}
} else {
// Remove links from carnivores to this herbivore with fall-off animation
for (var j = linkLines.length - 1; j >= 0; j--) {
if (linkLines[j].herbivore === creature) {
animateLinkFallOff(linkLines[j].carnivore, creature);
}
}
}
creature.die();
}
}
}
}
}
}
function createEventDecks() {
// Create 10 basic event cards
basicEventDeck = [];
for (var i = 1; i <= 10; i++) {
basicEventDeck.push({
type: 'basic',
cardType: 'event',
level: 'Basic',
name: 'Basic Event ' + i,
effect: 'environmental'
});
}
// Create 10 advanced event cards
advancedEventDeck = [];
for (var i = 1; i <= 10; i++) {
advancedEventDeck.push({
type: 'advanced',
cardType: 'event',
level: 'Advanced',
name: 'Advanced Event ' + i,
effect: 'catastrophic'
});
}
}
function addEventCardToDeck(creatureLevel) {
var eventCard = null;
// Determine which deck to draw from based on creature level
if (creatureLevel === 'Advanced') {
if (advancedEventDeck.length > 0) {
eventCard = advancedEventDeck.pop();
} else if (basicEventDeck.length > 0) {
// Fallback to basic events if advanced is empty
eventCard = basicEventDeck.pop();
}
} else {
if (basicEventDeck.length > 0) {
eventCard = basicEventDeck.pop();
} else if (advancedEventDeck.length > 0) {
// Fallback to advanced events if basic is empty
eventCard = advancedEventDeck.pop();
}
}
// If event card was drawn, add to discard pile (only draw 1 card per death)
if (eventCard) {
discardPile.push(eventCard);
}
}
function processDawnPhase() {
// Check for ongoing effects and costs that trigger during Dawn
// Check scry status at beginning of round - award token ONLY if no creatures went extinct last round
if (currentRound > 1 && !scryDeniedNextRound) {
scryTokenActive = true;
}
// Reset extinction tracking for the new round
creaturesWentExtinct = false;
// Reset scry denial for the new round
scryDeniedNextRound = false;
// Show visual cue that dawn phase is processing
showPhaseTransition("Dawn Phase - Processing...", function () {
gamePhase = 'draw';
processDrawPhase(); // Auto-progress to draw phase
});
}
function processDrawPhase() {
// Draw exactly 3 cards (or 4 if AT15 was played last turn), handling deck reshuffling as needed
var cardsToDrawTotal = 3;
if (at15PlayedLastTurn) {
cardsToDrawTotal = 4;
at15PlayedLastTurn = false; // Reset flag after using it
}
// 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++;
roundText.setText("Round: " + currentRound);
roundDisplayText.setText("Round: " + currentRound);
deckCountText.setText("Deck: " + mainDeck.length);
// Check for end game condition after round increment
if (currentRound >= 6) {
// Game ends immediately
LK.showYouWin();
return;
}
// Check and trigger scry status at beginning of new round
// Only activate scry if no extinctions occurred last round
if (!scryDeniedNextRound) {
scryTokenActive = true;
} //{ht_scry}
creaturesWentExtinct = false;
scryDeniedNextRound = false;
} else {
// No cards available, stop drawing
continueDrawing = false;
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
roundText.setText("Round: " + currentRound);
deckCountText.setText("Deck: " + mainDeck.length);
}
// Draw card button functionality
var drawButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
var drawButtonText = new Text2("DRAW", {
size: 32,
fill: 0xFFFFFF
});
drawButtonText.anchor.set(0.5, 0.5);
drawButton.addChild(drawButtonText);
drawButton.x = 1700;
drawButton.y = 2400;
game.addChild(drawButton);
drawButton.down = function () {
if (gamePhase === 'draw') {
processDrawPhase();
}
};
// Phase management buttons - redesigned for proper turn flow
var nextPhaseButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 0.8
});
nextPhaseButton.tint = 0x32CD32; // Green for next phase
var nextPhaseButtonText = new Text2("NEXT PHASE", {
size: 24,
fill: 0xFFFFFF
});
nextPhaseButtonText.anchor.set(0.5, 0.5);
nextPhaseButton.addChild(nextPhaseButtonText);
nextPhaseButton.x = 500;
nextPhaseButton.y = 2400;
game.addChild(nextPhaseButton);
var nextPhaseWarningBox = null;
function getCreaturesInDanger() {
var creaturesInDanger = [];
for (var gridY = 0; gridY < planetHeight; gridY++) {
//{kA_danger}
for (var gridX = 0; gridX < planetWidth; gridX++) {
//{kB_danger}
var terrain = planetBoard[gridY][gridX]; //{kC_danger}
if (terrain && terrain.creatureStack.length > 0) {
//{kD_danger}
for (var i = 0; i < terrain.creatureStack.length; i++) {
//{kE_danger}
var creature = terrain.creatureStack[i]; //{kF_danger}
// ONLY check top creatures in stack
var isTopCreature = i === terrain.creatureStack.length - 1; //{kG_danger}
if (!isTopCreature) {
//{kH_danger}
continue; //{kI_danger}
} //{kJ_danger}
// Check herbivores for excess links
if (creature.creatureData.dietType === 'herbivore') {
//{kK_danger}
var linksToThisHerbivore = 0; //{kL_danger}
for (var j = 0; j < linkLines.length; j++) {
//{kM_danger}
if (linkLines[j].herbivore === creature) {
//{kN_danger}
linksToThisHerbivore++; //{kO_danger}
} //{kP_danger}
} //{kQ_danger}
var excessLinks = linksToThisHerbivore - creature.safeLinks; //{kR_danger}
if (excessLinks > 0) {
//{kS_danger}
creaturesInDanger.push(creature); //{kT_danger}
} //{kU_danger}
} //{kV_danger}
// Check carnivores for insufficient links
else if (creature.creatureData.dietType === 'carnivore') {
//{kW_danger}
if (creature.activeLinks.length < creature.linkRequirement) {
//{kX_danger}
creaturesInDanger.push(creature); //{kY_danger}
} //{kZ_danger}
} //{l0_danger}
} //{l1_danger}
} //{l2_danger}
} //{l3_danger}
} //{l4_danger}
return creaturesInDanger; //{l5_danger}
} //{l6_danger}
function showNextPhaseWarning() {
if (nextPhaseWarningBox && nextPhaseWarningBox.parent) {
//{l7_danger}
return; // Warning already showing
} //{l8_danger}
// Create warning box
nextPhaseWarningBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
//{l9_danger}
anchorY: 0.5 //{la_danger}
}); //{lb_danger}
nextPhaseWarningBox.tint = 0xFF8C00; // Orange background
nextPhaseWarningBox.alpha = 0.95;
nextPhaseWarningBox.scaleX = 2.0;
nextPhaseWarningBox.scaleY = 1.2;
nextPhaseWarningBox.x = 500;
nextPhaseWarningBox.y = 2100;
game.addChild(nextPhaseWarningBox);
// Create warning text
var warningText = new Text2("Warning! These creatures are in danger!\nAre you sure you want to end the turn?", {
size: 20,
//{lc_danger}
fill: 0xFFFFFF //{ld_danger}
}); //{le_danger}
warningText.anchor.set(0.5, 0.5);
warningText.x = 0;
warningText.y = 0;
nextPhaseWarningBox.addChild(warningText);
} //{lf_danger}
function hideNextPhaseWarning() {
if (nextPhaseWarningBox && nextPhaseWarningBox.parent) {
//{lg_danger}
nextPhaseWarningBox.parent.removeChild(nextPhaseWarningBox); //{lh_danger}
nextPhaseWarningBox = null;
} //{li_danger}
} //{lj_danger}
function highlightCreaturesInDanger(creaturesInDanger) {
for (var i = 0; i < creaturesInDanger.length; i++) {
//{lk_danger}
var creature = creaturesInDanger[i]; //{ll_danger}
tween.stop(creature, {
//{lm_danger}
tint: true //{ln_danger}
}); //{lo_danger}
tween(creature, {
//{lp_danger}
tint: 0xFFFF00 //{lq_danger}
}, {
//{lr_danger}
duration: 200 //{ls_danger}
}); //{lt_danger}
} //{lu_danger}
} //{lv_danger}
function unhighlightCreaturesInDanger(creaturesInDanger) {
for (var i = 0; i < creaturesInDanger.length; i++) {
//{lw_danger}
var creature = creaturesInDanger[i]; //{lx_danger}
tween.stop(creature, {
//{ly_danger}
tint: true //{lz_danger}
}); //{mA_danger}
tween(creature, {
//{mB_danger}
tint: 0xFFFFFF //{mC_danger}
}, {
//{mD_danger}
duration: 200 //{mE_danger}
}); //{mF_danger}
} //{mG_danger}
} //{mH_danger}
nextPhaseButton.down = function () {
// Exit link adjustment mode if active and hide adjust links button
if (linkAdjustmentMode) {
deactivateLinkAdjustmentMode();
}
// Always hide adjust links button when next phase is clicked
hideAdjustLinksButton();
// Hide warning box when button is pressed
hideNextPhaseWarning(); //{mI_danger}
// Prevent skipping card play after draw: if player has not played a card after drawing, do not allow phase advance
if (gamePhase === 'draw' || gamePhase === 'noon' && (mustPlayCard || playerHand.length > 0 && hasValidPlacements(playerHand))) {
// If in draw phase, or in noon phase and must play a card, do not allow skipping
// Show a notification to the player
if (!game.noPlayableNotification) {
showNoPlayableCardsNotification();
}
return;
}
if (gamePhase === 'dawn') {
processDawnPhase();
} else if (gamePhase === 'draw') {
processDrawPhase();
} else if (gamePhase === 'noon') {
// Check if player has event cards that must be played first
if (eventCardsToPlay.length > 0 || mustPlayCard) {
// Must play event cards or mandatory card first
return;
}
gamePhase = 'dusk';
processDuskPhase();
} else if (gamePhase === 'dusk') {
// Apply consequences for over-hunted herbivores
processLinkConsequences();
clearLinkHighlights();
gamePhase = 'night';
processNightPhase();
} else if (gamePhase === 'night') {
processNightPhase();
} else if (gamePhase === 'end') {
processEndTurnPhase();
}
};
nextPhaseButton.move = function (x, y, obj) {
//{mJ_danger}
var creaturesInDanger = getCreaturesInDanger(); //{mK_danger}
if (creaturesInDanger.length > 0) {
//{mL_danger}
showNextPhaseWarning(); //{mM_danger}
highlightCreaturesInDanger(creaturesInDanger); //{mN_danger}
} else {
//{mO_danger}
hideNextPhaseWarning(); //{mP_danger}
unhighlightCreaturesInDanger(creaturesInDanger); //{mQ_danger}
} //{mR_danger}
}; //{mS_danger}
// Phase indicator
var phaseText = new Text2("Phase: NOON", {
size: 32,
fill: 0xFFFFFF
});
phaseText.anchor.set(0.5, 0);
phaseText.x = 1024;
phaseText.y = 2550;
game.addChild(phaseText);
// Link adjustment system variables
var linkAdjustmentMode = false;
var adjustLinkButton = null;
var cardsWithAdjustableLinks = [];
var currentlyAdjustingCard = null;
function createAdjustLinksButton() {
if (adjustLinkButton && adjustLinkButton.parent) {
adjustLinkButton.parent.removeChild(adjustLinkButton);
}
adjustLinkButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 0.8
});
adjustLinkButton.tint = 0xFFD700; // Gold for adjust links
var adjustButtonText = new Text2("Adjust Links?", {
size: 20,
fill: 0x000000
});
adjustButtonText.anchor.set(0.5, 0.5);
adjustLinkButton.addChild(adjustButtonText);
adjustLinkButton.x = 250; // Left of next phase button
adjustLinkButton.y = 2400;
game.addChild(adjustLinkButton);
adjustLinkButton.down = function () {
activateLinkAdjustmentMode();
};
}
function showAdjustLinksButton() {
createAdjustLinksButton();
}
function hideAdjustLinksButton() {
if (adjustLinkButton && adjustLinkButton.parent) {
adjustLinkButton.parent.removeChild(adjustLinkButton);
adjustLinkButton = null;
}
}
function activateLinkAdjustmentMode() {
linkAdjustmentMode = true;
currentlyAdjustingCard = null;
cardsWithAdjustableLinks = [];
// Find all carnivores on the board
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
if (creature.creatureData.dietType === 'carnivore') {
cardsWithAdjustableLinks.push(creature);
}
}
}
}
}
// Create and display personal "Alter Links" buttons on all carnivore cards
for (var i = 0; i < cardsWithAdjustableLinks.length; i++) {
var card = cardsWithAdjustableLinks[i];
card.createPersonalAlterLinksButton();
}
}
function deactivateLinkAdjustmentMode() {
linkAdjustmentMode = false;
// Remove personal alter buttons from all cards and restore card colors
for (var i = 0; i < cardsWithAdjustableLinks.length; i++) {
var card = cardsWithAdjustableLinks[i];
card.tint = 0xFFFFFF; // Return to normal color
// Remove personal alter button
if (card.personalAlterButton && card.personalAlterButton.parent) {
card.personalAlterButton.parent.removeChild(card.personalAlterButton);
card.personalAlterButton = null;
}
}
cardsWithAdjustableLinks = [];
currentlyAdjustingCard = null;
}
// Handle card dragging and link highlighting on hold
game.move = function (x, y, obj) {
if (draggedCard) {
draggedCard.x = x;
draggedCard.y = y;
// Ensure dragged card is always on top
game.removeChild(draggedCard);
game.addChild(draggedCard);
// Highlights are already shown from the card's down event
} else if (draggedLinkMarker && gamePhase === 'dusk' && linkAdjustmentMode) {
// Handle link marker dragging - highlight valid targets only while holding
var carnivore = draggedLinkMarker.carnivore;
highlightValidLinkTargets(carnivore);
// Create temporary visual line from carnivore to cursor
// Remove any existing temp line
if (game.tempLinkLine && game.tempLinkLine.parent) {
game.tempLinkLine.parent.removeChild(game.tempLinkLine);
}
// Create new temp line
var tempLine = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.01,
scaleY: 1
});
tempLine.tint = 0xFFFF00; // Yellow temp line
tempLine.alpha = 0.5;
var dx = x - carnivore.x;
var dy = y - carnivore.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
tempLine.x = carnivore.x + dx / 2;
tempLine.y = carnivore.y + dy / 2;
tempLine.rotation = angle;
tempLine.scaleY = distance / 336;
game.addChild(tempLine);
game.tempLinkLine = tempLine;
}
};
game.up = function (x, y, obj) {
if (draggedCard) {
var placed = false;
// Check if card was dropped on valid terrain
for (var gridY = 0; gridY < planetHeight && !placed; gridY++) {
for (var gridX = 0; gridX < planetWidth && !placed; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain) {
if (draggedCard.creatureData && canPlaceCreatureOnTerrain(draggedCard, terrain)) {
if (Math.abs(terrain.x - x) < 140 && Math.abs(terrain.y - y) < 168) {
placed = placeCreatureOnStack(draggedCard, terrain, gridX, gridY);
}
} else if (draggedCard.terrainData && canPlaceTerrainOnTerrain(draggedCard, terrain)) {
if (Math.abs(terrain.x - x) < 140 && Math.abs(terrain.y - y) < 168) {
placed = placeTerrainOnTerrain(draggedCard, terrain, gridX, gridY);
}
}
}
}
}
// If not placed, return to original position
if (!placed && originalCardPosition) {
tween(draggedCard, originalCardPosition, {
duration: 300
});
}
// Clear all highlights
hidePossibleMoves();
draggedCard = null;
originalCardPosition = null;
} else if (draggedLinkMarker && draggedLinkCarnivore && gamePhase === 'dusk' && linkAdjustmentMode) {
// Handle link marker release with new system
var linkCreated = false;
// Remove temporary line
if (game.tempLinkLine && game.tempLinkLine.parent) {
game.tempLinkLine.parent.removeChild(game.tempLinkLine);
game.tempLinkLine = null;
}
// Check if released over a valid herbivore
for (var i = 0; i < linkHighlights.length; i++) {
var highlight = linkHighlights[i];
if (highlight.targetCreature) {
var creature = highlight.targetCreature;
var distance = Math.sqrt(Math.pow(creature.x - x, 2) + Math.pow(creature.y - y, 2));
if (distance < 100) {
// Within range of the herbivore
// Create the link
if (createLink(draggedLinkCarnivore, creature)) {
linkCreated = true;
break;
}
}
}
}
// Clear highlights and reset drag state
clearLinkHighlights();
draggedLinkMarker = null;
draggedLinkCarnivore = null;
}
};
function processAT18Special() {
// Find all carnivore cards in discard pile
at18CarnivoreList = [];
for (var i = 0; i < discardPile.length; i++) {
//{at18_loop}
var cardData = discardPile[i];
if (cardData.cardType === 'creature' && cardData.dietType === 'carnivore') {
//{at18_check}
at18CarnivoreList.push(cardData);
} //{at18_found}
} //{at18_end_loop}
at18Active = true;
if (at18CarnivoreList.length === 0) {
// No carnivores in discard pile - show message
showAT18NoCarnivoredMessage();
} else {
//{at18_else}
// Display carnivores for selection
displayAT18CarnivoreSelection();
} //{at18_display_end}
} //{at18_process_end}
function showAT18NoCarnivoredMessage() {
// Create notification box
var notificationBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
//{at18_no_x}
anchorY: 0.5 //{at18_no_y}
}); //{at18_no_asset}
notificationBox.tint = 0x4169E1; // Blue background for info
notificationBox.alpha = 0.9;
notificationBox.scaleX = 2.5;
notificationBox.scaleY = 1.5;
notificationBox.x = 1024;
notificationBox.y = 1366;
game.addChild(notificationBox);
// Create notification text
var notificationText = new Text2("No Carnivore cards in Discard pile - Special does not apply", {
size: 28,
//{at18_no_text_size}
fill: 0xFFFFFF //{at18_no_text_fill}
}); //{at18_no_text}
notificationText.anchor.set(0.5, 0.5);
notificationText.x = 0;
notificationText.y = 0;
notificationBox.addChild(notificationText);
// Make the box clickable to dismiss
notificationBox.down = function () {
if (notificationBox.parent) {
notificationBox.parent.removeChild(notificationBox);
} //{at18_no_dismiss}
at18Active = false;
}; //{at18_no_down}
} //{at18_no_function_end}
function displayAT18CarnivoreSelection() {
// Create scry UI header
var scryHeader = new Text2("Pick 1 to add back onto the main deck", {
size: 32,
//{at18_header_size}
fill: 0xFFFFFF //{at18_header_fill}
}); //{at18_header_text}
scryHeader.anchor.set(0.5, 0);
scryHeader.x = 1024;
scryHeader.y = 300;
game.addChild(scryHeader);
scryHeader.at18Header = true;
// Display each carnivore card in a selectable list format
var cardYOffset = 500;
var cardYSpacing = 280;
for (var i = 0; i < at18CarnivoreList.length; i++) {
//{at18_display_loop}
var cardData = at18CarnivoreList[i];
var cardContainer = new CreatureCard(cardData);
cardContainer.scaleX = 0.6;
cardContainer.scaleY = 0.6;
cardContainer.x = 512 + i % 2 * 600;
cardContainer.y = cardYOffset + Math.floor(i / 2) * cardYSpacing;
cardContainer.cardDataIndex = i;
cardContainer.at18Selectable = true;
// Add click handler to select this card
cardContainer.down = function (x, y, obj) {
var selectedIndex = this.cardDataIndex;
selectAT18Carnivore(selectedIndex);
}; //{at18_selectable_down}
game.addChild(cardContainer);
} //{at18_display_loop_end}
} //{at18_display_function_end}
function selectAT18Carnivore(cardIndex) {
if (cardIndex < 0 || cardIndex >= at18CarnivoreList.length) {
return;
}
// Get the selected carnivore card data
var selectedCardData = at18CarnivoreList[cardIndex];
// Remove from discard pile
for (var i = discardPile.length - 1; i >= 0; i--) {
//{at18_remove_loop}
if (discardPile[i] === selectedCardData || discardPile[i].id === selectedCardData.id && discardPile[i].cardType === selectedCardData.cardType && discardPile[i].dietType === selectedCardData.dietType) {
//{at18_remove_check}
discardPile.splice(i, 1);
break;
} //{at18_remove_end}
} //{at18_remove_loop_end}
// Add to top of main deck
mainDeck.push(selectedCardData);
// Clear all AT18 UI elements
clearAT18UI();
// Update deck count
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
at18Active = false;
} //{at18_select_function_end}
function clearAT18UI() {
// Remove all AT18 selectable cards from game
for (var i = game.children.length - 1; i >= 0; i--) {
//{at18_clear_loop}
var child = game.children[i];
if (child.at18Selectable) {
//{at18_clear_check}
game.removeChild(child);
} //{at18_clear_removed}
if (child.at18Header) {
//{at18_clear_header}
game.removeChild(child);
} //{at18_clear_header_removed}
} //{at18_clear_loop_end}
} //{at18_clear_function_end}
function activateScry() {
if (!scryTokenActive || scryMode || mainDeck.length < 3) {
return;
}
scryMode = true;
scryCards = [];
// Take top 3 cards from deck
for (var i = 0; i < 3; i++) {
var cardData = mainDeck.pop();
var card = createCardFromData(cardData);
scryCards.push(card);
playerHand.push(card);
game.addChild(card);
}
updateHandPositions();
// Show return cards button
showReturnCardsButton();
}
function showReturnCardsButton() {
var returnButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.8
});
returnButton.tint = 0x8A2BE2; // Purple for scry actions
var returnButtonText = new Text2("READY TO RETURN CARDS", {
size: 20,
fill: 0xFFFFFF
});
returnButtonText.anchor.set(0.5, 0.5);
returnButton.addChild(returnButtonText);
returnButton.x = 1024;
returnButton.y = 2000;
game.addChild(returnButton);
returnButton.down = function () {
processScryReturn();
if (returnButton.parent) {
returnButton.parent.removeChild(returnButton);
}
};
}
function processScryReturn() {
// For each scry card in hand, player chooses where to return it
// For now, we'll implement a simple system - return to top of deck
for (var i = 0; i < scryCards.length; i++) {
var card = scryCards[i];
var handIndex = playerHand.indexOf(card);
if (handIndex !== -1) {
playerHand.splice(handIndex, 1);
if (card.parent) {
card.parent.removeChild(card);
}
// Return to top of deck (could be enhanced to let player choose top/bottom/discard)
var cardData = card.creatureData || card.terrainData || card.eventData;
// Create clean copy without event data to prevent hand state corruption
var cleanCardData = {};
cleanCardData.type = cardData.type;
cleanCardData.level = cardData.level;
cleanCardData.cardType = cardData.cardType;
if (cardData.dietType) {
cleanCardData.dietType = cardData.dietType;
}
if (cardData.name) {
cleanCardData.name = cardData.name;
}
if (cardData.terrainRequirement) {
cleanCardData.terrainRequirement = cardData.terrainRequirement;
}
if (cardData.climateRequirement) {
cleanCardData.climateRequirement = cardData.climateRequirement;
}
if (cardData.subtype) {
cleanCardData.subtype = cardData.subtype;
}
if (cardData.landType) {
cleanCardData.landType = cardData.landType;
}
if (cardData.waterType) {
cleanCardData.waterType = cardData.waterType;
}
if (cardData.climate) {
cleanCardData.climate = cardData.climate;
}
mainDeck.push(cleanCardData);
}
}
// Clear scry state
scryCards = [];
scryMode = false;
scryTokenActive = false; // Scry token consumed
updateHandPositions();
}
function showNoPlayableCardsNotification() {
// Create notification box
var notificationBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.5
});
notificationBox.tint = 0x8B0000; // Dark red background
notificationBox.alpha = 0.9;
notificationBox.x = 1024;
notificationBox.y = 1366;
game.addChild(notificationBox);
// Create notification text
var notificationText = new Text2("No playable cards! Click to continue", {
size: 36,
fill: 0xFFFFFF
});
notificationText.anchor.set(0.5, 0.5);
notificationText.x = 0;
notificationText.y = 0;
notificationBox.addChild(notificationText);
// Make the box clickable
notificationBox.down = function () {
// Remove the notification
if (notificationBox.parent) {
notificationBox.parent.removeChild(notificationBox);
}
// Discard all cards in hand
discardCards(playerHand.slice());
// Start draw 1, discard 1 cycle
processDrawPhase();
};
// Store reference for cleanup
game.noPlayableNotification = notificationBox;
}
// Initialize terrain-based game
createBasicTerrainPool();
setupPlanet();
createInitialDeck();
createEventDecks();
// Shuffle event decks on game start
shuffleEventDeck(basicEventDeck);
shuffleEventDeck(advancedEventDeck);
// Game starts with turn 1, round 1, in Dawn phase
currentRound = 1;
turnNumber = 1;
gamePhase = 'dawn';
roundText.setText("Round: " + currentRound);
game.update = function () {
// Update UI elements
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
basicEventCountText.setText("Basic Events: " + basicEventDeck.length);
advancedEventCountText.setText("Advanced Events: " + advancedEventDeck.length);
graveyardCountText.setText("Graveyard: " + graveyard.length);
eventDiscardCountText.setText("Event Discard: " + eventDiscardPile.length);
// Update scry token display
if (scryTokenActive) {
scryText.setText("Scry ✓");
scryText.tint = 0x00FF00; // Green when available
} else if (scryMode) {
scryText.setText("Scry (Active)");
scryText.tint = 0xFFFF00; // Yellow when in use
} else {
scryText.setText("Scry");
scryText.tint = 0x888888; // Gray when unavailable
}
// Update phase indicator
phaseText.setText("Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber);
// Check if event cards have been removed from hand
hasEventCardsInHand = false;
for (var i = 0; i < playerHand.length; i++) {
//{lV_event1}
if (playerHand[i].eventData) {
hasEventCardsInHand = true;
break;
}
}
// Update next phase button appearance based on event cards
if (gamePhase === 'noon') {
//{lV_event2}
if (hasEventCardsInHand) {
nextPhaseButton.tint = 0xFF0000; // Red when events must be played
nextPhaseButtonText.setText("Play events 1st!");
} else {
nextPhaseButton.tint = 0x32CD32; // Green when normal phase progression
nextPhaseButtonText.setText("NEXT PHASE");
}
}
// Update link marker states during dusk phase
if (gamePhase === 'dusk') {
updateLinkMarkerStates();
}
};
function resetCardLinks(carnivore) {
if (!carnivore || carnivore.creatureData.dietType !== 'carnivore') {
return;
}
// Remove all active links from this carnivore with fall-off animation
for (var i = carnivore.activeLinks.length - 1; i >= 0; i--) {
var link = carnivore.activeLinks[i];
animateLinkFallOff(carnivore, link.target);
}
// Reset all link markers to unlinked state
for (var j = 0; j < carnivore.linkMarkers.length; j++) {
var marker = carnivore.linkMarkers[j];
marker.isLinked = false;
marker.targetHerbivore = null;
marker.tint = 0xFF0000; // Red for unlinked
}
// Clear active links array
carnivore.activeLinks = [];
}