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