/**** * 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 ---