User prompt
Please fix the bug: 'Uncaught TypeError: snakeHead.containsPoint is not a function' in or related to this line: 'if (snakeHead.containsPoint({' Line Number: 217
Code edit (1 edits merged)
Please save this source code
User prompt
Marvin's Tummy Trouble
Initial prompt
Okay, here's a comic strip based on your prompt. It focuses on the scenario of the animals inside Marvin the Snake's tummy, struggling to escape, and Marvin having a hard time keeping them in. I've kept the internal voices silent within the snake's tummy as requested, focusing on their actions. Panel 1 (A long, winding panel. Marvin the Snake, huge and green, is coiled up in a forest. He looks stressed and uncomfortable. His scales are bulging slightly. Sweat beads on his brow.) Speech Balloon (Marvin): Ugh... This is the worst buffet ever. Seriously regretting that all-you-can-eat special. Caption: Marvin – Feeling the consequences. Panel 2 (Close-up on Marvin's midsection. We can see bumps and bulges moving erratically under his skin. It's clear there's a lot of chaotic activity going on inside.) Speech Balloon (Marvin): Okay, okay, settle down in there! I get it, nobody likes a cramped space. But seriously, give a snake a break! Panel 3 (A split panel. On the left, a close-up of William The Deer, headbutting against what is presumably the internal wall of Marvin's stomach. We don't see his face, just his antlers pushing hard. The right side shows Peter The Pig squealing and kicking. Again, facial expression is not visible for focus) Caption: Inside the Belly – A symphony of struggle. Panel 4 (Panel showing Marvin contorting from the inside struggle.) Speech Balloon (Marvin): Oof! Careful, guys! That tickles! (grimaces) And...kind of hurts! Caption: Marvin - Trying to keep his composure. Panel 5 (Wide shot. Marvin is now desperately trying to tie himself in a knot with him making an X shape, his tail wrapped around him and attempting to hold everything in. He looks panicked.) Speech Balloon (Marvin): Plan B! Containment Protocol 7! Engage...knot-ification! Panel 6 (Close up on Marvin's face. He is sweating, desperate, and his eyes are wide.) Speech balloon (Marvin): Someone Help Me, PLEASE!!!!. Hopefully, this brings your vision to life! Let me know if you want any alterations. With Keyboard
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Animal: tries to escape from inside Marvin's belly var Animal = Container.expand(function () { var self = Container.call(this); // Pick a random color for each animal var colors = [0xffa726, 0x29b6f6, 0xab47bc, 0xff7043, 0x66bb6a, 0xffeb3b]; var color = colors[Math.floor(Math.random() * colors.length)]; var animal = self.attachAsset('animal', { anchorX: 0.5, anchorY: 0.5, color: color }); // Escape direction (angle in radians) self.angle = Math.random() * Math.PI * 2; // Escape speed self.speed = 6 + Math.random() * 2; // Wobble for fun self.wobble = Math.random() * Math.PI * 2; // Used for escape state self.isEscaping = false; self.escapeTimer = 0; // Used for stress self.lastEscaping = false; // Used for tracking if outside self.hasEscaped = false; // Used for animal "push" animation self.pushStrength = 0; return self; }); // SnakeBodySegment: a draggable segment of Marvin's body var SnakeBodySegment = Container.expand(function () { var self = Container.call(this); // Attach body asset var body = self.attachAsset('snakeBody', { anchorX: 0.5, anchorY: 0.5 }); // Used for dragging self.isDragging = false; // Used for physics self.vx = 0; self.vy = 0; // Used for linking to previous/next segments self.prev = null; self.next = null; // Used for stress effect self.stressPulse = 0; // Used for animal collision self.segmentIndex = 0; // Drag event handlers self.down = function (x, y, obj) { self.isDragging = true; }; self.up = function (x, y, obj) { self.isDragging = false; }; return self; }); // SnakeHead: Marvin's head, draggable, slightly larger var SnakeHead = Container.expand(function () { var self = Container.call(this); var head = self.attachAsset('snakeHead', { anchorX: 0.5, anchorY: 0.5 }); // Used for dragging self.isDragging = false; // Used for physics self.vx = 0; self.vy = 0; // Used for linking to next segment self.next = null; // Drag event handlers self.down = function (x, y, obj) { self.isDragging = true; }; self.up = function (x, y, obj) { self.isDragging = false; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222b1a }); /**** * Game Code ****/ // Stress bar fill // Stress bar background // Animal (ellipse, random color, will be set per instance) // Snake head (ellipse, slightly larger, yellow-green) // Snake body segment (ellipse, green) // Game constants var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var BELLY_RADIUS = 420; // Radius of Marvin's belly (where animals are contained) var SNAKE_SEGMENTS = 7; // Number of body segments (not counting head) var SNAKE_LENGTH = 900; // Total length of snake body (for initial placement) var ANIMAL_COUNT = 3; // Number of animals inside belly at once var ANIMAL_PUSH_INTERVAL = 120; // Ticks between animal escape attempts var ANIMAL_PUSH_DURATION = 60; // How long an animal pushes per attempt var ANIMAL_PUSH_STRENGTH = 60; // How far animals try to push per attempt var STRESS_PER_PUSH = 0.12; // How much stress increases per animal push var STRESS_DECAY = 0.002; // How much stress decays per tick var STRESS_MAX = 1.0; // Max stress var STRESS_BAR_WIDTH = 600; var STRESS_BAR_HEIGHT = 60; // Game state var snakeHead; var snakeSegments = []; var animals = []; var stress = 0; var stressBarBg, stressBarFill, stressBarText; var score = 0; var scoreTxt; var dragNode = null; var lastMoveX = 0, lastMoveY = 0; // Center of Marvin's belly var bellyCenterX = GAME_WIDTH / 2; var bellyCenterY = GAME_HEIGHT / 2 + 200; // --- Setup Snake --- // Create snake head snakeHead = new SnakeHead(); snakeHead.x = bellyCenterX + SNAKE_LENGTH / 2; snakeHead.y = bellyCenterY; game.addChild(snakeHead); // Create snake body segments, arranged in a gentle S-curve snakeSegments = []; for (var i = 0; i < SNAKE_SEGMENTS; i++) { var seg = new SnakeBodySegment(); var t = i / (SNAKE_SEGMENTS - 1); // S-curve: x = center + cos(t*PI) * amplitude, y = center + sin(t*PI) * amplitude var angle = Math.PI * (t - 0.5); var amplitude = SNAKE_LENGTH / 2.2; seg.x = bellyCenterX + Math.cos(angle) * amplitude; seg.y = bellyCenterY + Math.sin(angle) * amplitude * 0.7; seg.segmentIndex = i; game.addChild(seg); snakeSegments.push(seg); // Link segments if (i > 0) { seg.prev = snakeSegments[i - 1]; snakeSegments[i - 1].next = seg; } } snakeHead.next = snakeSegments[0]; // --- Setup Animals --- animals = []; for (var i = 0; i < ANIMAL_COUNT; i++) { var animal = new Animal(); // Place randomly inside belly var angle = Math.random() * Math.PI * 2; var r = Math.random() * (BELLY_RADIUS - 80); animal.x = bellyCenterX + Math.cos(angle) * r; animal.y = bellyCenterY + Math.sin(angle) * r; game.addChild(animal); animals.push(animal); } // --- Setup Stress Bar --- stressBarBg = LK.getAsset('stressBarBg', { anchorX: 0, anchorY: 0.5, x: (GAME_WIDTH - STRESS_BAR_WIDTH) / 2, y: 120 }); LK.gui.top.addChild(stressBarBg); stressBarFill = LK.getAsset('stressBarFill', { anchorX: 0, anchorY: 0.5, x: (GAME_WIDTH - STRESS_BAR_WIDTH) / 2, y: 120 }); LK.gui.top.addChild(stressBarFill); stressBarText = new Text2('Stress', { size: 48, fill: "#fff" }); stressBarText.anchor.set(0.5, 0.5); stressBarText.x = GAME_WIDTH / 2; stressBarText.y = 120; LK.gui.top.addChild(stressBarText); // --- Setup Score --- scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); scoreTxt.x = GAME_WIDTH / 2; scoreTxt.y = 200; // --- Dragging Logic --- game.down = function (x, y, obj) { // Check if touching head or any segment var found = null; // Manual ellipse hit test for snakeHead var headAsset = snakeHead.children[0]; var hx = snakeHead.x; var hy = snakeHead.y; var hw = headAsset.width; var hh = headAsset.height; var dx = x - hx; var dy = y - hy; if (dx * dx / (hw / 2 * (hw / 2)) + dy * dy / (hh / 2 * (hh / 2)) <= 1) { found = snakeHead; } else { for (var i = 0; i < snakeSegments.length; i++) { var seg = snakeSegments[i]; var segAsset = seg.children[0]; var sx = seg.x; var sy = seg.y; var sw = segAsset.width; var sh = segAsset.height; var sdx = x - sx; var sdy = y - sy; if (sdx * sdx / (sw / 2 * (sw / 2)) + sdy * sdy / (sh / 2 * (sh / 2)) <= 1) { found = seg; break; } } } if (found) { dragNode = found; dragNode.isDragging = true; lastMoveX = x; lastMoveY = y; } }; game.up = function (x, y, obj) { if (dragNode) { dragNode.isDragging = false; dragNode = null; } }; function clamp(val, min, max) { return Math.max(min, Math.min(max, val)); } // --- Move Handler --- game.move = function (x, y, obj) { if (dragNode && dragNode.isDragging) { // Move the dragged node to the new position, but clamp inside game area dragNode.x = clamp(x, 120, GAME_WIDTH - 120); dragNode.y = clamp(y, 220, GAME_HEIGHT - 120); lastMoveX = x; lastMoveY = y; } }; // --- Game Update --- game.update = function () { // --- Snake Body Physics: segments follow each other like a rope --- // Head is controlled by drag, but if not dragging, it relaxes toward center if (!snakeHead.isDragging) { // Gently return to default position var dx = bellyCenterX + SNAKE_LENGTH / 2 - snakeHead.x; var dy = bellyCenterY - snakeHead.y; snakeHead.x += dx * 0.01; snakeHead.y += dy * 0.01; } // Each segment follows the previous one, with a fixed distance var prevX = snakeHead.x; var prevY = snakeHead.y; var segmentDist = SNAKE_LENGTH / (SNAKE_SEGMENTS + 1); for (var i = 0; i < snakeSegments.length; i++) { var seg = snakeSegments[i]; var dx = prevX - seg.x; var dy = prevY - seg.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 1) { var pull = (dist - segmentDist) * 0.22; seg.x += dx / dist * pull; seg.y += dy / dist * pull; } // Small spring to belly center if not being dragged if (!seg.isDragging) { var cx = bellyCenterX + Math.cos(Math.PI * (i / (SNAKE_SEGMENTS - 1) - 0.5)) * BELLY_RADIUS * 0.7; var cy = bellyCenterY + Math.sin(Math.PI * (i / (SNAKE_SEGMENTS - 1) - 0.5)) * BELLY_RADIUS * 0.3; seg.x += (cx - seg.x) * 0.003; seg.y += (cy - seg.y) * 0.003; } prevX = seg.x; prevY = seg.y; } // --- Animals try to escape --- for (var i = 0; i < animals.length; i++) { var animal = animals[i]; // If not escaping, randomly decide to push if (!animal.isEscaping && LK.ticks % ANIMAL_PUSH_INTERVAL === i * 23 % ANIMAL_PUSH_INTERVAL) { animal.isEscaping = true; animal.escapeTimer = ANIMAL_PUSH_DURATION; animal.angle = Math.random() * Math.PI * 2; animal.pushStrength = ANIMAL_PUSH_STRENGTH + Math.random() * 20; } // If escaping, move outward if (animal.isEscaping) { animal.escapeTimer--; // Wobble for fun animal.wobble += 0.2; var wobbleR = Math.sin(animal.wobble) * 8; var dx = Math.cos(animal.angle) * (animal.pushStrength + wobbleR); var dy = Math.sin(animal.angle) * (animal.pushStrength + wobbleR); animal.x += dx / ANIMAL_PUSH_DURATION; animal.y += dy / ANIMAL_PUSH_DURATION; // Increase stress stress += STRESS_PER_PUSH / 60; if (stress > STRESS_MAX) stress = STRESS_MAX; // If timer done, stop escaping if (animal.escapeTimer <= 0) { animal.isEscaping = false; } } else { // Gently drift back toward belly center var dx = bellyCenterX - animal.x; var dy = bellyCenterY - animal.y; animal.x += dx * 0.01; animal.y += dy * 0.01; } // --- Check if animal is outside belly (escaped) --- var distToCenter = Math.sqrt((animal.x - bellyCenterX) * (animal.x - bellyCenterX) + (animal.y - bellyCenterY) * (animal.y - bellyCenterY)); if (!animal.hasEscaped && distToCenter > BELLY_RADIUS) { // Check if any snake segment is close enough to block var blocked = false; for (var j = 0; j < snakeSegments.length; j++) { var seg = snakeSegments[j]; var dseg = Math.sqrt((animal.x - seg.x) * (animal.x - seg.x) + (animal.y - seg.y) * (animal.y - seg.y)); if (dseg < 120) { blocked = true; // Bounce animal back in var angle = Math.atan2(animal.y - seg.y, animal.x - seg.x); animal.x = seg.x + Math.cos(angle) * 120; animal.y = seg.y + Math.sin(angle) * 120; // Add stress for close call stress += 0.04; if (stress > STRESS_MAX) stress = STRESS_MAX; // Fun pulse effect tween(seg, { scaleX: 1.2, scaleY: 1.2 }, { duration: 80, easing: tween.easeOut, onFinish: function onFinish() { tween(seg, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeIn }); } }); break; } } // Also check head var dhead = Math.sqrt((animal.x - snakeHead.x) * (animal.x - snakeHead.x) + (animal.y - snakeHead.y) * (animal.y - snakeHead.y)); if (!blocked && dhead < 130) { blocked = true; var angle = Math.atan2(animal.y - snakeHead.y, animal.x - snakeHead.x); animal.x = snakeHead.x + Math.cos(angle) * 130; animal.y = snakeHead.y + Math.sin(angle) * 130; stress += 0.05; if (stress > STRESS_MAX) stress = STRESS_MAX; tween(snakeHead, { scaleX: 1.18, scaleY: 1.18 }, { duration: 80, easing: tween.easeOut, onFinish: function onFinish() { tween(snakeHead, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeIn }); } }); } if (!blocked) { // Animal escapes! animal.hasEscaped = true; // Flash screen, show game over LK.effects.flashScreen(0xff0000, 900); LK.showGameOver(); return; } } } // --- Stress decay --- stress -= STRESS_DECAY; if (stress < 0) stress = 0; // --- Stress bar update --- var fillWidth = Math.floor(STRESS_BAR_WIDTH * clamp(stress, 0, 1)); stressBarFill.width = fillWidth; // --- Score: survive time, +1 per 2 seconds --- if (LK.ticks % 120 === 0) { score++; scoreTxt.setText(score); } // --- Win condition: survive 60 seconds --- if (score >= 30) { LK.showYouWin(); return; } }; // --- End of Game Code ---
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Animal: tries to escape from inside Marvin's belly
var Animal = Container.expand(function () {
var self = Container.call(this);
// Pick a random color for each animal
var colors = [0xffa726, 0x29b6f6, 0xab47bc, 0xff7043, 0x66bb6a, 0xffeb3b];
var color = colors[Math.floor(Math.random() * colors.length)];
var animal = self.attachAsset('animal', {
anchorX: 0.5,
anchorY: 0.5,
color: color
});
// Escape direction (angle in radians)
self.angle = Math.random() * Math.PI * 2;
// Escape speed
self.speed = 6 + Math.random() * 2;
// Wobble for fun
self.wobble = Math.random() * Math.PI * 2;
// Used for escape state
self.isEscaping = false;
self.escapeTimer = 0;
// Used for stress
self.lastEscaping = false;
// Used for tracking if outside
self.hasEscaped = false;
// Used for animal "push" animation
self.pushStrength = 0;
return self;
});
// SnakeBodySegment: a draggable segment of Marvin's body
var SnakeBodySegment = Container.expand(function () {
var self = Container.call(this);
// Attach body asset
var body = self.attachAsset('snakeBody', {
anchorX: 0.5,
anchorY: 0.5
});
// Used for dragging
self.isDragging = false;
// Used for physics
self.vx = 0;
self.vy = 0;
// Used for linking to previous/next segments
self.prev = null;
self.next = null;
// Used for stress effect
self.stressPulse = 0;
// Used for animal collision
self.segmentIndex = 0;
// Drag event handlers
self.down = function (x, y, obj) {
self.isDragging = true;
};
self.up = function (x, y, obj) {
self.isDragging = false;
};
return self;
});
// SnakeHead: Marvin's head, draggable, slightly larger
var SnakeHead = Container.expand(function () {
var self = Container.call(this);
var head = self.attachAsset('snakeHead', {
anchorX: 0.5,
anchorY: 0.5
});
// Used for dragging
self.isDragging = false;
// Used for physics
self.vx = 0;
self.vy = 0;
// Used for linking to next segment
self.next = null;
// Drag event handlers
self.down = function (x, y, obj) {
self.isDragging = true;
};
self.up = function (x, y, obj) {
self.isDragging = false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222b1a
});
/****
* Game Code
****/
// Stress bar fill
// Stress bar background
// Animal (ellipse, random color, will be set per instance)
// Snake head (ellipse, slightly larger, yellow-green)
// Snake body segment (ellipse, green)
// Game constants
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var BELLY_RADIUS = 420; // Radius of Marvin's belly (where animals are contained)
var SNAKE_SEGMENTS = 7; // Number of body segments (not counting head)
var SNAKE_LENGTH = 900; // Total length of snake body (for initial placement)
var ANIMAL_COUNT = 3; // Number of animals inside belly at once
var ANIMAL_PUSH_INTERVAL = 120; // Ticks between animal escape attempts
var ANIMAL_PUSH_DURATION = 60; // How long an animal pushes per attempt
var ANIMAL_PUSH_STRENGTH = 60; // How far animals try to push per attempt
var STRESS_PER_PUSH = 0.12; // How much stress increases per animal push
var STRESS_DECAY = 0.002; // How much stress decays per tick
var STRESS_MAX = 1.0; // Max stress
var STRESS_BAR_WIDTH = 600;
var STRESS_BAR_HEIGHT = 60;
// Game state
var snakeHead;
var snakeSegments = [];
var animals = [];
var stress = 0;
var stressBarBg, stressBarFill, stressBarText;
var score = 0;
var scoreTxt;
var dragNode = null;
var lastMoveX = 0,
lastMoveY = 0;
// Center of Marvin's belly
var bellyCenterX = GAME_WIDTH / 2;
var bellyCenterY = GAME_HEIGHT / 2 + 200;
// --- Setup Snake ---
// Create snake head
snakeHead = new SnakeHead();
snakeHead.x = bellyCenterX + SNAKE_LENGTH / 2;
snakeHead.y = bellyCenterY;
game.addChild(snakeHead);
// Create snake body segments, arranged in a gentle S-curve
snakeSegments = [];
for (var i = 0; i < SNAKE_SEGMENTS; i++) {
var seg = new SnakeBodySegment();
var t = i / (SNAKE_SEGMENTS - 1);
// S-curve: x = center + cos(t*PI) * amplitude, y = center + sin(t*PI) * amplitude
var angle = Math.PI * (t - 0.5);
var amplitude = SNAKE_LENGTH / 2.2;
seg.x = bellyCenterX + Math.cos(angle) * amplitude;
seg.y = bellyCenterY + Math.sin(angle) * amplitude * 0.7;
seg.segmentIndex = i;
game.addChild(seg);
snakeSegments.push(seg);
// Link segments
if (i > 0) {
seg.prev = snakeSegments[i - 1];
snakeSegments[i - 1].next = seg;
}
}
snakeHead.next = snakeSegments[0];
// --- Setup Animals ---
animals = [];
for (var i = 0; i < ANIMAL_COUNT; i++) {
var animal = new Animal();
// Place randomly inside belly
var angle = Math.random() * Math.PI * 2;
var r = Math.random() * (BELLY_RADIUS - 80);
animal.x = bellyCenterX + Math.cos(angle) * r;
animal.y = bellyCenterY + Math.sin(angle) * r;
game.addChild(animal);
animals.push(animal);
}
// --- Setup Stress Bar ---
stressBarBg = LK.getAsset('stressBarBg', {
anchorX: 0,
anchorY: 0.5,
x: (GAME_WIDTH - STRESS_BAR_WIDTH) / 2,
y: 120
});
LK.gui.top.addChild(stressBarBg);
stressBarFill = LK.getAsset('stressBarFill', {
anchorX: 0,
anchorY: 0.5,
x: (GAME_WIDTH - STRESS_BAR_WIDTH) / 2,
y: 120
});
LK.gui.top.addChild(stressBarFill);
stressBarText = new Text2('Stress', {
size: 48,
fill: "#fff"
});
stressBarText.anchor.set(0.5, 0.5);
stressBarText.x = GAME_WIDTH / 2;
stressBarText.y = 120;
LK.gui.top.addChild(stressBarText);
// --- Setup Score ---
scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.x = GAME_WIDTH / 2;
scoreTxt.y = 200;
// --- Dragging Logic ---
game.down = function (x, y, obj) {
// Check if touching head or any segment
var found = null;
// Manual ellipse hit test for snakeHead
var headAsset = snakeHead.children[0];
var hx = snakeHead.x;
var hy = snakeHead.y;
var hw = headAsset.width;
var hh = headAsset.height;
var dx = x - hx;
var dy = y - hy;
if (dx * dx / (hw / 2 * (hw / 2)) + dy * dy / (hh / 2 * (hh / 2)) <= 1) {
found = snakeHead;
} else {
for (var i = 0; i < snakeSegments.length; i++) {
var seg = snakeSegments[i];
var segAsset = seg.children[0];
var sx = seg.x;
var sy = seg.y;
var sw = segAsset.width;
var sh = segAsset.height;
var sdx = x - sx;
var sdy = y - sy;
if (sdx * sdx / (sw / 2 * (sw / 2)) + sdy * sdy / (sh / 2 * (sh / 2)) <= 1) {
found = seg;
break;
}
}
}
if (found) {
dragNode = found;
dragNode.isDragging = true;
lastMoveX = x;
lastMoveY = y;
}
};
game.up = function (x, y, obj) {
if (dragNode) {
dragNode.isDragging = false;
dragNode = null;
}
};
function clamp(val, min, max) {
return Math.max(min, Math.min(max, val));
}
// --- Move Handler ---
game.move = function (x, y, obj) {
if (dragNode && dragNode.isDragging) {
// Move the dragged node to the new position, but clamp inside game area
dragNode.x = clamp(x, 120, GAME_WIDTH - 120);
dragNode.y = clamp(y, 220, GAME_HEIGHT - 120);
lastMoveX = x;
lastMoveY = y;
}
};
// --- Game Update ---
game.update = function () {
// --- Snake Body Physics: segments follow each other like a rope ---
// Head is controlled by drag, but if not dragging, it relaxes toward center
if (!snakeHead.isDragging) {
// Gently return to default position
var dx = bellyCenterX + SNAKE_LENGTH / 2 - snakeHead.x;
var dy = bellyCenterY - snakeHead.y;
snakeHead.x += dx * 0.01;
snakeHead.y += dy * 0.01;
}
// Each segment follows the previous one, with a fixed distance
var prevX = snakeHead.x;
var prevY = snakeHead.y;
var segmentDist = SNAKE_LENGTH / (SNAKE_SEGMENTS + 1);
for (var i = 0; i < snakeSegments.length; i++) {
var seg = snakeSegments[i];
var dx = prevX - seg.x;
var dy = prevY - seg.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 1) {
var pull = (dist - segmentDist) * 0.22;
seg.x += dx / dist * pull;
seg.y += dy / dist * pull;
}
// Small spring to belly center if not being dragged
if (!seg.isDragging) {
var cx = bellyCenterX + Math.cos(Math.PI * (i / (SNAKE_SEGMENTS - 1) - 0.5)) * BELLY_RADIUS * 0.7;
var cy = bellyCenterY + Math.sin(Math.PI * (i / (SNAKE_SEGMENTS - 1) - 0.5)) * BELLY_RADIUS * 0.3;
seg.x += (cx - seg.x) * 0.003;
seg.y += (cy - seg.y) * 0.003;
}
prevX = seg.x;
prevY = seg.y;
}
// --- Animals try to escape ---
for (var i = 0; i < animals.length; i++) {
var animal = animals[i];
// If not escaping, randomly decide to push
if (!animal.isEscaping && LK.ticks % ANIMAL_PUSH_INTERVAL === i * 23 % ANIMAL_PUSH_INTERVAL) {
animal.isEscaping = true;
animal.escapeTimer = ANIMAL_PUSH_DURATION;
animal.angle = Math.random() * Math.PI * 2;
animal.pushStrength = ANIMAL_PUSH_STRENGTH + Math.random() * 20;
}
// If escaping, move outward
if (animal.isEscaping) {
animal.escapeTimer--;
// Wobble for fun
animal.wobble += 0.2;
var wobbleR = Math.sin(animal.wobble) * 8;
var dx = Math.cos(animal.angle) * (animal.pushStrength + wobbleR);
var dy = Math.sin(animal.angle) * (animal.pushStrength + wobbleR);
animal.x += dx / ANIMAL_PUSH_DURATION;
animal.y += dy / ANIMAL_PUSH_DURATION;
// Increase stress
stress += STRESS_PER_PUSH / 60;
if (stress > STRESS_MAX) stress = STRESS_MAX;
// If timer done, stop escaping
if (animal.escapeTimer <= 0) {
animal.isEscaping = false;
}
} else {
// Gently drift back toward belly center
var dx = bellyCenterX - animal.x;
var dy = bellyCenterY - animal.y;
animal.x += dx * 0.01;
animal.y += dy * 0.01;
}
// --- Check if animal is outside belly (escaped) ---
var distToCenter = Math.sqrt((animal.x - bellyCenterX) * (animal.x - bellyCenterX) + (animal.y - bellyCenterY) * (animal.y - bellyCenterY));
if (!animal.hasEscaped && distToCenter > BELLY_RADIUS) {
// Check if any snake segment is close enough to block
var blocked = false;
for (var j = 0; j < snakeSegments.length; j++) {
var seg = snakeSegments[j];
var dseg = Math.sqrt((animal.x - seg.x) * (animal.x - seg.x) + (animal.y - seg.y) * (animal.y - seg.y));
if (dseg < 120) {
blocked = true;
// Bounce animal back in
var angle = Math.atan2(animal.y - seg.y, animal.x - seg.x);
animal.x = seg.x + Math.cos(angle) * 120;
animal.y = seg.y + Math.sin(angle) * 120;
// Add stress for close call
stress += 0.04;
if (stress > STRESS_MAX) stress = STRESS_MAX;
// Fun pulse effect
tween(seg, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 80,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(seg, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeIn
});
}
});
break;
}
}
// Also check head
var dhead = Math.sqrt((animal.x - snakeHead.x) * (animal.x - snakeHead.x) + (animal.y - snakeHead.y) * (animal.y - snakeHead.y));
if (!blocked && dhead < 130) {
blocked = true;
var angle = Math.atan2(animal.y - snakeHead.y, animal.x - snakeHead.x);
animal.x = snakeHead.x + Math.cos(angle) * 130;
animal.y = snakeHead.y + Math.sin(angle) * 130;
stress += 0.05;
if (stress > STRESS_MAX) stress = STRESS_MAX;
tween(snakeHead, {
scaleX: 1.18,
scaleY: 1.18
}, {
duration: 80,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(snakeHead, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeIn
});
}
});
}
if (!blocked) {
// Animal escapes!
animal.hasEscaped = true;
// Flash screen, show game over
LK.effects.flashScreen(0xff0000, 900);
LK.showGameOver();
return;
}
}
}
// --- Stress decay ---
stress -= STRESS_DECAY;
if (stress < 0) stress = 0;
// --- Stress bar update ---
var fillWidth = Math.floor(STRESS_BAR_WIDTH * clamp(stress, 0, 1));
stressBarFill.width = fillWidth;
// --- Score: survive time, +1 per 2 seconds ---
if (LK.ticks % 120 === 0) {
score++;
scoreTxt.setText(score);
}
// --- Win condition: survive 60 seconds ---
if (score >= 30) {
LK.showYouWin();
return;
}
};
// --- End of Game Code ---