User prompt
Make the AI snake work more like the player in terms of movement
User prompt
The AI snake body isn’t attached to the head
User prompt
Make make the AI spawn just a little less frequently
User prompt
Make the food respawn after the AI takes it
User prompt
Make the AI be able to be killed my the players tail
User prompt
He still moves WAY too fast
User prompt
Make the ai snake move as slow as the player
User prompt
When he steals your food, it doesn’t respawn
User prompt
Make it spawn way more
User prompt
Make the ai snake move as slow as the player
User prompt
Make the spike aura go infront of the obstacle
User prompt
Make the AI snakes move WAY slower
User prompt
Make spike obstacles not disappear after touch one segment. Make it stay infinitely until despawning
User prompt
Make AI snakes that spawn very rarely and steal a food from you if you don’t stop them
User prompt
Make spikes spawn even more ofter
User prompt
Prevent food from spawning inside of an obstacle
User prompt
Make the spike obstacle spawn more often
User prompt
Make a spike asset that spawns around the spike obstacle to make it stand out more
User prompt
Make the spike obstacle use its own asset
User prompt
Add a spike obstacle that destroys the segments it touches. It will delete the segments and bring your score down. Make them spawn rarely
User prompt
Make the normal obstacle despawn as well, but less ofter as the moving ones
User prompt
Please fix the bug: 'TypeError: tween.to is not a function. (In 'tween.to(self.asset, { tint: 0xff0000 }, 400)', 'tween.to' is undefined)' in or related to this line: 'self._rainbowTween = tween.to(self.asset, {' Line Number: 54 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Make moving obstacles despawn
User prompt
Add more content (no shop)
User prompt
Make the hit boxes for the segments smaller
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // AISnake (AI snake that moves toward food and steals it) var AISnake = Container.expand(function () { var self = Container.call(this); self.length = 3 + Math.floor(Math.random() * 2); // 3-4 segments self.segments = []; self.dir = 0; self.speed = Math.max(4, SNAKE_INIT_SPEED * 0.35); // AI snake moves much slower than player self.target = null; self.stealing = false; self.lastFood = null; // Create head var head = self.attachAsset('aiSnakeHead', { anchorX: 0.5, anchorY: 0.5 }); self.asset = head; self.segments.push(self); // Create body segments for (var i = 1; i < self.length; ++i) { var seg = new Container(); seg.asset = seg.attachAsset('aiSnakeBody', { anchorX: 0.5, anchorY: 0.5 }); seg.x = self.x - i * 60; seg.y = self.y; self.addChild(seg); self.segments.push(seg); } // Custom hitbox self.hitboxScale = 0.6; // Custom intersects for head self.intersects = function (other) { var scaleA = typeof self.hitboxScale === "number" ? self.hitboxScale : 1; var scaleB = typeof other.hitboxScale === "number" ? other.hitboxScale : 1; var ax = self.x, ay = self.y, aw = self.asset.width * scaleA, ah = self.asset.height * scaleA; var bx = other.x, by = other.y, bw = other.asset && other.asset.width ? other.asset.width * scaleB : 0, bh = other.asset && other.asset.height ? other.asset.height * scaleB : 0; var rA = Math.max(aw, ah) / 2; var rB = Math.max(bw, bh) / 2; var dx = ax - bx, dy = ay - by; return dx * dx + dy * dy < (rA + rB) * (rA + rB); }; // Update method self.update = function () { // Save last position for event triggers if (self.lastX === undefined) self.lastX = self.x; if (self.lastY === undefined) self.lastY = self.y; // Find food if not already targeting if (!self.target || self.target.destroyed) { if (typeof food !== "undefined" && food && !food.destroyed) { self.target = food; } else { self.target = null; } } // Move toward food using player-like movement if (self.target) { // Calculate desired direction var dx = self.target.x - self.x; var dy = self.target.y - self.y; var desiredDir = Math.atan2(dy, dx); // Smoothly turn toward the target (like player) var d = desiredDir - self.dir; while (d > Math.PI) d -= 2 * Math.PI; while (d < -Math.PI) d += 2 * Math.PI; var turn = Math.sign(d) * Math.min(Math.abs(d), SNAKE_TURN_ANGLE); self.dir += turn; // Move head forward self.x += Math.cos(self.dir) * self.speed; self.y += Math.sin(self.dir) * self.speed; // Clamp to bounds self.x = Math.max(100 + self.asset.width / 2, Math.min(2048 - 100 - self.asset.width / 2, self.x)); self.y = Math.max(100 + self.asset.height / 2, Math.min(2732 - 100 - self.asset.height / 2, self.y)); // Move body segments to follow the previous segment, like player for (var i = 1; i < self.segments.length; ++i) { var seg = self.segments[i]; var target = self.segments[i - 1]; var sdx = target.x - seg.x; var sdy = target.y - seg.y; var sdist = Math.sqrt(sdx * sdx + sdy * sdy); var desired = self.speed * 0.9; if (sdist > desired) { var move = (sdist - desired) * 0.5; seg.x += sdx / sdist * move; seg.y += sdy / sdist * move; } } // Steal food if close enough if (self.target && self.intersects(self.target)) { self.stealing = true; // Remove food from game if (typeof food !== "undefined" && food === self.target) { food.destroy(); food = null; // Always respawn food after AI steals it if (typeof spawnFood === "function") { spawnFood(); } } self.target = null; } } // Despawn if off screen or after stealing if (self.stealing) { // Move off screen quickly self.x += Math.cos(self.dir) * self.speed * 2; self.y += Math.sin(self.dir) * self.speed * 2; if (self.x < -200 || self.x > 2048 + 200 || self.y < -200 || self.y > 2732 + 200) { self._despawn = true; } } self.lastX = self.x; self.lastY = self.y; }; return self; }); // Food var Food = Container.expand(function () { var self = Container.call(this); self.asset = self.attachAsset('food', { anchorX: 0.5, anchorY: 0.5 }); self.hitboxScale = 0.6; return self; }); // FoodPurple var FoodPurple = Container.expand(function () { var self = Container.call(this); self.asset = self.attachAsset('foodPurple', { anchorX: 0.5, anchorY: 0.5 }); self.hitboxScale = 0.6; return self; }); // FoodRainbow (rare, big bonus) var FoodRainbow = Container.expand(function () { var self = Container.call(this); self.asset = self.attachAsset('food', { anchorX: 0.5, anchorY: 0.5 }); self.hitboxScale = 0.6; // Animate color cycling // Animate color cycling using tween plugin API tween(self.asset, { tint: 0xff0000 }, { duration: 400, easing: tween.linear, onFinish: function onFinish() { // No-op, will be repeated } }); // Manually cycle through rainbow colors in update (since tween can't do this directly) self.update = function () { var t = LK.ticks % 60 / 60; var r = Math.floor(127 * (Math.sin(2 * Math.PI * t) + 1)); var g = Math.floor(127 * (Math.sin(2 * Math.PI * t + 2) + 1)); var b = Math.floor(127 * (Math.sin(2 * Math.PI * t + 4) + 1)); self.asset.tint = r << 16 | g << 8 | b; }; return self; }); // MovingObstacle var MovingObstacle = Container.expand(function () { var self = Container.call(this); self.asset = self.attachAsset('obstacleMoving', { anchorX: 0.5, anchorY: 0.5 }); self.hitboxScale = 0.7; // Pick a random direction and speed var angle = Math.random() * Math.PI * 2; var speed = 4 + Math.random() * 4; self.vx = Math.cos(angle) * speed; self.vy = Math.sin(angle) * speed; self.update = function () { // Save last position for event triggers if (self.lastX === undefined) self.lastX = self.x; if (self.lastY === undefined) self.lastY = self.y; self.x += self.vx; self.y += self.vy; // Despawn if out of bounds (with 150px margin) if (self.x < -150 || self.x > 2048 + 150 || self.y < -150 || self.y > 2732 + 150) { // Mark for removal by setting a flag self._despawn = true; return; } // Bounce off walls (100px margin) if (self.x < 100 + self.asset.width / 2 && self.vx < 0) self.vx *= -1; if (self.x > 2048 - 100 - self.asset.width / 2 && self.vx > 0) self.vx *= -1; if (self.y < 100 + self.asset.height / 2 && self.vy < 0) self.vy *= -1; if (self.y > 2732 - 100 - self.asset.height / 2 && self.vy > 0) self.vy *= -1; self.lastX = self.x; self.lastY = self.y; }; return self; }); // Obstacle (static) var Obstacle = Container.expand(function () { var self = Container.call(this); self.asset = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 0.5 }); self.hitboxScale = 0.7; // Add despawn timer and update method for static obstacles self._despawnTimer = 0; self._despawnInterval = 60 * 60; // Try to despawn every 60 seconds (3600 ticks) self.update = function () { if (self._despawnTimer === undefined) self._despawnTimer = 0; self._despawnTimer++; // Only check every _despawnInterval ticks if (self._despawnTimer >= self._despawnInterval) { self._despawnTimer = 0; // 40% chance to despawn (less often than moving obstacles) if (Math.random() < 0.4) { self._despawn = true; } } }; return self; }); // PowerUp var PowerUp = Container.expand(function () { var self = Container.call(this); self.type = 'invincible'; // or 'shrink' self.asset = null; self.hitboxScale = 0.6; self.setType = function (type) { if (self.asset) self.removeChild(self.asset); self.type = type; if (type === 'invincible') { self.asset = self.attachAsset('powerupInvincible', { anchorX: 0.5, anchorY: 0.5 }); } else if (type === 'shrink') { self.asset = self.attachAsset('powerupShrink', { anchorX: 0.5, anchorY: 0.5 }); } }; self.setType('invincible'); return self; }); // Snake Segment (body or head) var SnakeSegment = Container.expand(function () { var self = Container.call(this); self.isHead = false; self.asset = null; // Custom hitbox size (smaller than visual asset) self.hitboxScale = 0.6; // 60% of asset size for both head and body self.setType = function (type) { if (self.asset) self.removeChild(self.asset); if (type === 'head') { self.asset = self.attachAsset('snakeHead', { anchorX: 0.5, anchorY: 0.5 }); self.isHead = true; } else { self.asset = self.attachAsset('snakeBody', { anchorX: 0.5, anchorY: 0.5 }); self.isHead = false; } }; self.setType('body'); // Custom intersects method for smaller hitbox self.intersects = function (other) { // Use hitboxScale for both this and other if available, else default to 1 var scaleA = typeof self.hitboxScale === "number" ? self.hitboxScale : 1; var scaleB = typeof other.hitboxScale === "number" ? other.hitboxScale : 1; var ax = self.x, ay = self.y, aw = self.asset.width * scaleA, ah = self.asset.height * scaleA; var bx = other.x, by = other.y, bw = other.asset && other.asset.width ? other.asset.width * scaleB : 0, bh = other.asset && other.asset.height ? other.asset.height * scaleB : 0; // Circle collision (since snake is ellipse/circle) var rA = Math.max(aw, ah) / 2; var rB = Math.max(bw, bh) / 2; var dx = ax - bx; var dy = ay - by; return dx * dx + dy * dy < (rA + rB) * (rA + rB); }; return self; }); // SpikeObstacle (destroys snake segments it touches, rare spawn) var SpikeObstacle = Container.expand(function () { var self = Container.call(this); // Add spike obstacle asset first (so aura is in front) self.asset = self.attachAsset('spikeObstacle', { anchorX: 0.5, anchorY: 0.5 }); // Add spike aura in front of the spike asset self.aura = self.attachAsset('spikeAura', { anchorX: 0.5, anchorY: 0.5 }); self.aura.alpha = 0.35; self.hitboxScale = 0.7; // Add despawn timer and update method for static obstacles self._despawnTimer = 0; self._despawnInterval = 60 * 45; // Try to despawn every 45 seconds (less often than normal obstacles) self.update = function () { if (self._despawnTimer === undefined) self._despawnTimer = 0; self._despawnTimer++; // Only check every _despawnInterval ticks if (self._despawnTimer >= self._despawnInterval) { self._despawnTimer = 0; // 60% chance to despawn (spikes are rare, so despawn more often) if (Math.random() < 0.6) { self._despawn = true; } } // Animate aura pulse if (self.aura) { var t = LK.ticks % 60 / 60; self.aura.alpha = 0.25 + 0.15 * Math.sin(2 * Math.PI * t); var scale = 1.05 + 0.08 * Math.sin(2 * Math.PI * t); self.aura.scaleX = scale; self.aura.scaleY = scale; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181818 }); /**** * Game Code ****/ // Music // Sounds // Obstacles // Power-ups // Food // Snake head and body // --- Game constants --- var SNAKE_INIT_LENGTH = 5; var SNAKE_INIT_SPEED = 16; // pixels per tick var SNAKE_MIN_SPEED = 8; var SNAKE_MAX_SPEED = 40; var SNAKE_TURN_ANGLE = Math.PI / 18; // 10 degrees per input var FOOD_SCORE = 10; var POWERUP_SCORE = 25; var POWERUP_DURATION = 300; // ticks (5 seconds) var OBSTACLE_COUNT = 3; var OBSTACLE_SPEED = 6; // --- Game state --- var snake = []; var aiSnakes = []; // Array of AI snakes var snakeDir = 0; // in radians, 0 = right var snakeNextDir = 0; var snakeSpeed = SNAKE_INIT_SPEED; var snakeGrow = 0; var food = null; var powerup = null; var powerupActive = false; var powerupType = null; var powerupTimer = 0; var obstacles = []; var score = 0; var isInvincible = false; var lastTouch = null; var dragStart = null; var dragAngle = null; var dragActive = false; var gameOver = false; // --- GUI --- var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // --- Helper functions --- function randomPos(margin, avoidList, avoidRadius) { margin = margin || 150; avoidList = avoidList || []; avoidRadius = avoidRadius || 120; // Default: avoid within 120px of any avoidList item var tries = 0; while (tries < 50) { var x = margin + Math.random() * (2048 - 2 * margin); var y = margin + Math.random() * (2732 - 2 * margin); var ok = true; for (var i = 0; i < avoidList.length; ++i) { var obj = avoidList[i]; var dx = obj.x - x; var dy = obj.y - y; if (dx * dx + dy * dy < avoidRadius * avoidRadius) { ok = false; break; } } if (ok) { return { x: x, y: y }; } tries++; } // Fallback: just return a random position if we can't find a good one return { x: margin + Math.random() * (2048 - 2 * margin), y: margin + Math.random() * (2732 - 2 * margin) }; } function dist2(a, b) { var dx = a.x - b.x, dy = a.y - b.y; return dx * dx + dy * dy; } function angleBetween(a, b) { return Math.atan2(b.y - a.y, b.x - a.x); } function wrapPos(pos) { // Not used, but could be for wrap-around if (pos.x < 0) pos.x += 2048; if (pos.x > 2048) pos.x -= 2048; if (pos.y < 0) pos.y += 2732; if (pos.y > 2732) pos.y -= 2732; } function spawnFood() { if (food) food.destroy(); // 5% chance for rainbow food, 20% for purple, else normal var r = Math.random(); if (r < 0.05) { food = new FoodRainbow(); food.isRainbow = true; food.isPurple = false; } else if (r < 0.25) { food = new FoodPurple(); food.isPurple = true; food.isRainbow = false; } else { food = new Food(); food.isPurple = false; food.isRainbow = false; } // Avoid spawning on the snake and obstacles var avoidList = []; for (var i = 0; i < snake.length; ++i) { avoidList.push(snake[i]); } // Also avoid obstacles for (var i = 0; i < obstacles.length; ++i) { avoidList.push(obstacles[i]); } var pos = randomPos(200, avoidList, 120); food.x = pos.x; food.y = pos.y; game.addChild(food); } function spawnPowerUp() { if (powerup) powerup.destroy(); powerup = new PowerUp(); // Avoid spawning on the snake var avoidList = []; for (var i = 0; i < snake.length; ++i) { avoidList.push(snake[i]); } var pos = randomPos(200, avoidList, 120); // 67% invincible, 33% shrink var r = Math.random(); var t; if (r < 0.67) t = 'invincible';else t = 'shrink'; powerup.setType(t); powerup.x = pos.x; powerup.y = pos.y; game.addChild(powerup); } function spawnObstacles() { for (var i = 0; i < obstacles.length; ++i) { obstacles[i].destroy(); } obstacles = []; // Avoid spawning on the snake var avoidList = []; for (var j = 0; j < snake.length; ++j) { avoidList.push(snake[j]); } for (var i = 0; i < OBSTACLE_COUNT; ++i) { var obs = new Obstacle(); var pos = randomPos(300, avoidList, 140); obs.x = pos.x; obs.y = pos.y; // Add this obstacle to avoidList so next ones don't overlap avoidList.push(obs); obstacles.push(obs); game.addChild(obs); } } function resetSnake() { for (var i = 0; i < snake.length; ++i) { snake[i].destroy(); } snake = []; var startX = 2048 / 2, startY = 2732 / 2; for (var i = 0; i < SNAKE_INIT_LENGTH; ++i) { var seg = new SnakeSegment(); if (i === 0) { seg.setType('head'); // Head spawns in front of the body, not on top of the first body segment seg.x = startX; seg.y = startY; } else { seg.setType('body'); // Each body segment spawns behind the previous one, extending left from the head seg.x = startX - i * snakeSpeed * 1.2; seg.y = startY; } game.addChild(seg); snake.push(seg); } snakeDir = 0; snakeNextDir = 0; snakeSpeed = SNAKE_INIT_SPEED; snakeGrow = 0; isInvincible = false; powerupActive = false; powerupType = null; powerupTimer = 0; } function updateScore(val) { score = val; scoreTxt.setText(score); LK.setScore(score); } function activatePowerUp(type) { powerupActive = true; powerupType = type; powerupTimer = POWERUP_DURATION; if (type === 'invincible') { isInvincible = true; // Flash snake for (var i = 0; i < snake.length; ++i) { LK.effects.flashObject(snake[i], 0xf1c40f, 500); } } else if (type === 'shrink') { // Remove 3 segments from the tail, but always keep at least 5 var minLen = 5; var removeCount = Math.min(3, snake.length - minLen); for (var i = 0; i < removeCount; ++i) { var seg = snake.pop(); seg.destroy(); } } } function deactivatePowerUp() { powerupActive = false; powerupType = null; powerupTimer = 0; isInvincible = false; } function endGame() { if (gameOver) return; gameOver = true; LK.effects.flashScreen(0xff0000, 1000); LK.getSound('hit').play(); LK.showGameOver(); } // --- Input handling (touch/drag to steer) --- game.down = function (x, y, obj) { // Only allow drag from outside top left 100x100 if (x < 100 && y < 100) return; dragStart = { x: x, y: y }; dragAngle = null; dragActive = true; }; game.move = function (x, y, obj) { if (!dragActive) return; if (!dragStart) return; // Calculate angle from snake head to drag point var head = snake[0]; var dx = x - head.x; var dy = y - head.y; if (dx * dx + dy * dy < 100) return; // Ignore tiny drags var angle = Math.atan2(dy, dx); snakeNextDir = angle; dragAngle = angle; }; game.up = function (x, y, obj) { dragActive = false; dragStart = null; dragAngle = null; }; // --- Main game update loop --- game.update = function () { if (gameOver) return; // Power-up timer if (powerupActive) { powerupTimer--; if (powerupTimer <= 0) { deactivatePowerUp(); } } // --- AI Snake logic --- // Update AI snakes for (var i = aiSnakes.length - 1; i >= 0; --i) { var ai = aiSnakes[i]; if (ai.update) ai.update(); // Check if AI snake collides with any player tail segment (not head) // Only if not already despawning if (!ai._despawn && snake.length > 4) { // Start from segment 4 (skip head and first 3 for leniency) for (var s = 4; s < snake.length; ++s) { var seg = snake[s]; // Only check if segment exists and not destroyed if (seg && !seg.destroyed) { // Track last intersection state for this ai/segment pair if (!ai.lastTailIntersect) ai.lastTailIntersect = {}; if (ai.lastTailIntersect[s] === undefined) ai.lastTailIntersect[s] = false; var nowIntersect = ai.intersects(seg); if (!ai.lastTailIntersect[s] && nowIntersect) { // AI snake dies! LK.effects.flashObject(ai, 0xff0000, 600); ai._despawn = true; // Optionally, give player a score bonus for killing AI updateScore(score + 30); break; } ai.lastTailIntersect[s] = nowIntersect; } } } // Remove if marked for despawn if (ai._despawn) { ai.destroy(); aiSnakes.splice(i, 1); } } // Spawn a new AI snake less frequently (about every 14-18 seconds, only one at a time) if (aiSnakes.length === 0 && LK.ticks > 60 * 5 && LK.ticks % (60 * (14 + Math.floor(Math.random() * 5))) === 0 && food && !food.isRainbow) { // Only spawn if food exists and is not rainbow (AI doesn't steal rainbow food) var avoidList = []; for (var j = 0; j < snake.length; ++j) avoidList.push(snake[j]); for (var k = 0; k < obstacles.length; ++k) avoidList.push(obstacles[k]); // Spawn at random edge var edge = Math.floor(Math.random() * 4); var pos; if (edge === 0) { // left pos = { x: 120, y: 200 + Math.random() * (2732 - 400) }; } else if (edge === 1) { // right pos = { x: 2048 - 120, y: 200 + Math.random() * (2732 - 400) }; } else if (edge === 2) { // top pos = { x: 200 + Math.random() * (2048 - 400), y: 120 }; } else { // bottom pos = { x: 200 + Math.random() * (2048 - 400), y: 2732 - 120 }; } // Avoid spawning on snake or obstacles var ok = true; for (var i = 0; i < avoidList.length; ++i) { var obj = avoidList[i]; var dx = obj.x - pos.x; var dy = obj.y - pos.y; if (dx * dx + dy * dy < 200 * 200) { ok = false; break; } } if (ok) { var ai = new AISnake(); ai.x = pos.x; ai.y = pos.y; aiSnakes.push(ai); game.addChild(ai); } } // Update obstacles (moving, static, and spike) for (var i = obstacles.length - 1; i >= 0; --i) { if (obstacles[i].update) obstacles[i].update(); // Remove moving obstacles that have been marked for despawn if (obstacles[i] instanceof MovingObstacle && obstacles[i]._despawn) { obstacles[i].destroy(); obstacles.splice(i, 1); continue; } // Remove static obstacles that have been marked for despawn if (obstacles[i] instanceof Obstacle && obstacles[i]._despawn) { obstacles[i].destroy(); obstacles.splice(i, 1); continue; } // Remove spike obstacles that have been marked for despawn if (typeof SpikeObstacle !== "undefined" && obstacles[i] instanceof SpikeObstacle && obstacles[i]._despawn) { obstacles[i].destroy(); obstacles.splice(i, 1); continue; } } // Obstacles are static, no update needed // Snake direction: smooth turn toward nextDir var d = snakeNextDir - snakeDir; while (d > Math.PI) d -= 2 * Math.PI; while (d < -Math.PI) d += 2 * Math.PI; if (Math.abs(d) > 0.01) { var turn = Math.sign(d) * Math.min(Math.abs(d), SNAKE_TURN_ANGLE); snakeDir += turn; } // Move snake head var head = snake[0]; var prevPos = { x: head.x, y: head.y }; head.x += Math.cos(snakeDir) * snakeSpeed; head.y += Math.sin(snakeDir) * snakeSpeed; // Clamp to bounds (leave 100px margin for UI) head.x = Math.max(100 + head.asset.width / 2, Math.min(2048 - 100 - head.asset.width / 2, head.x)); head.y = Math.max(100 + head.asset.height / 2, Math.min(2732 - 100 - head.asset.height / 2, head.y)); // Move body segments to follow for (var i = 1; i < snake.length; ++i) { var seg = snake[i]; var target = snake[i - 1]; var dx = target.x - seg.x; var dy = target.y - seg.y; var dist = Math.sqrt(dx * dx + dy * dy); var desired = snakeSpeed * 0.9; if (dist > desired) { var move = (dist - desired) * 0.5; seg.x += dx / dist * move; seg.y += dy / dist * move; } } // Grow snake if needed if (snakeGrow > 0) { var tail = snake[snake.length - 1]; var newSeg = new SnakeSegment(); newSeg.setType('body'); newSeg.x = tail.x; newSeg.y = tail.y; game.addChild(newSeg); snake.push(newSeg); snakeGrow--; } // Check collision with food if (food && head.intersects(food)) { LK.getSound('eat').play(); if (food.isRainbow) { updateScore(score + FOOD_SCORE * 10); snakeGrow += 12; // Flash the whole snake rainbow for (var i = 0; i < snake.length; ++i) { LK.effects.flashObject(snake[i], 0xffffff, 800); } } else if (food.isPurple) { updateScore(score + FOOD_SCORE * 3); snakeGrow += 6; } else { updateScore(score + FOOD_SCORE); snakeGrow += 2; } food.destroy(); food = null; // If an AI snake stole the food, don't respawn (AI snake update will handle it) // Only respawn food if it was NOT stolen by an AI snake this frame if (!aiSnakes.some(function (ai) { return ai.stealing; })) { spawnFood(); } // Chance to spawn powerup if (!powerup && Math.random() < 0.2) { spawnPowerUp(); } } // Check collision with powerup if (powerup && head.intersects(powerup)) { LK.getSound('powerup').play(); updateScore(score + POWERUP_SCORE); activatePowerUp(powerup.type); // Flash the screen for feedback LK.effects.flashScreen(0xf1c40f, 400); powerup.destroy(); powerup = null; } // Check collision with obstacles for (var i = 0; i < obstacles.length; ++i) { // SpikeObstacle: destroys segments it touches, reduces score if (typeof SpikeObstacle !== "undefined" && obstacles[i] instanceof SpikeObstacle) { // Check all segments (not just head) for (var s = 0; s < snake.length; ++s) { var seg = snake[s]; if (seg.intersects(obstacles[i])) { // Only destroy if not invincible if (!isInvincible) { // Flash segment and obstacle LK.effects.flashObject(seg, 0xda2cff, 400); LK.effects.flashObject(obstacles[i], 0xda2cff, 400); // Remove segment (but always keep at least 2: head + 1) if (snake.length > 2) { seg.destroy(); snake.splice(s, 1); // Reduce score, but not below zero updateScore(Math.max(0, score - 25)); // Play hit sound LK.getSound('hit').play(); // Do NOT remove spike after hit; spike stays infinitely until despawn break; // Only one segment hit per spike per frame } else { // If only 2 left, end game endGame(); return; } } else { // Invincible: just destroy spike LK.effects.flashObject(obstacles[i], 0xffffff, 300); obstacles[i].destroy(); obstacles.splice(i, 1); i--; break; } } } continue; } // Normal/moving obstacle logic if (head.intersects(obstacles[i])) { if (!isInvincible) { endGame(); return; } else { // Flash obstacle and destroy it LK.effects.flashObject(obstacles[i], 0xffffff, 300); obstacles[i].destroy(); obstacles.splice(i, 1); i--; } } } // Check collision with self (skip first 4 segments for leniency) if (!isInvincible) { for (var i = 4; i < snake.length; ++i) { if (head.intersects(snake[i])) { endGame(); return; } } } // Evolving obstacles: every 30 seconds, add a new one (max 8) if (LK.ticks % (60 * 30) === 0 && obstacles.length < 8) { var obs; // 40% chance for spike, 30% moving, 30% static var r = Math.random(); if (r < 0.40) { obs = new SpikeObstacle(); } else if (r < 0.70) { obs = new MovingObstacle(); } else { obs = new Obstacle(); } // Avoid spawning on the snake and other obstacles var avoidList = []; for (var j = 0; j < snake.length; ++j) { avoidList.push(snake[j]); } for (var k = 0; k < obstacles.length; ++k) { avoidList.push(obstacles[k]); } var pos = randomPos(300, avoidList, 140); obs.x = pos.x; obs.y = pos.y; obstacles.push(obs); game.addChild(obs); } // After 2 minutes, every 20 seconds, add a new moving obstacle (max 12) if (LK.ticks > 60 * 120 && LK.ticks % (60 * 20) === 0 && obstacles.length < 12) { var obs2 = new MovingObstacle(); // Avoid spawning on the snake and other obstacles var avoidList2 = []; for (var j = 0; j < snake.length; ++j) { avoidList2.push(snake[j]); } for (var k = 0; k < obstacles.length; ++k) { avoidList2.push(obstacles[k]); } var pos2 = randomPos(300, avoidList2, 140); obs2.x = pos2.x; obs2.y = pos2.y; obstacles.push(obs2); game.addChild(obs2); } }; // --- Game start/reset --- function startGame() { gameOver = false; updateScore(0); resetSnake(); // Remove all AI snakes for (var i = 0; i < aiSnakes.length; ++i) { aiSnakes[i].destroy(); } aiSnakes = []; spawnFood(); if (powerup) { powerup.destroy(); powerup = null; } spawnObstacles(); deactivatePowerUp(); LK.playMusic('snakebg', { fade: { start: 0, end: 1, duration: 1000 } }); } startGame(); // --- Game over/win handling is automatic by LK --- // --- Music is started on game start ---
===================================================================
--- original.js
+++ change.js
@@ -67,45 +67,40 @@
} else {
self.target = null;
}
}
- // Move toward food
+ // Move toward food using player-like movement
if (self.target) {
+ // Calculate desired direction
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
- var dist = Math.sqrt(dx * dx + dy * dy);
- if (dist > 1) {
- self.dir = Math.atan2(dy, dx);
- var move = Math.min(self.speed, dist);
- self.x += Math.cos(self.dir) * move;
- self.y += Math.sin(self.dir) * move;
- }
- // Move body segments to follow the head with a trailing effect
- var followDist = 60;
+ var desiredDir = Math.atan2(dy, dx);
+ // Smoothly turn toward the target (like player)
+ var d = desiredDir - self.dir;
+ while (d > Math.PI) d -= 2 * Math.PI;
+ while (d < -Math.PI) d += 2 * Math.PI;
+ var turn = Math.sign(d) * Math.min(Math.abs(d), SNAKE_TURN_ANGLE);
+ self.dir += turn;
+ // Move head forward
+ self.x += Math.cos(self.dir) * self.speed;
+ self.y += Math.sin(self.dir) * self.speed;
+ // Clamp to bounds
+ self.x = Math.max(100 + self.asset.width / 2, Math.min(2048 - 100 - self.asset.width / 2, self.x));
+ self.y = Math.max(100 + self.asset.height / 2, Math.min(2732 - 100 - self.asset.height / 2, self.y));
+ // Move body segments to follow the previous segment, like player
for (var i = 1; i < self.segments.length; ++i) {
var seg = self.segments[i];
- var targetSeg = self.segments[i - 1];
- // Save last position for each segment for smooth following
- if (seg.lastX === undefined) seg.lastX = seg.x;
- if (seg.lastY === undefined) seg.lastY = seg.y;
- var sdx = targetSeg.x - seg.x;
- var sdy = targetSeg.y - seg.y;
+ var target = self.segments[i - 1];
+ var sdx = target.x - seg.x;
+ var sdy = target.y - seg.y;
var sdist = Math.sqrt(sdx * sdx + sdy * sdy);
- if (sdist > followDist) {
- // Move segment toward previous segment, but clamp to followDist
- var move = sdist - followDist;
+ var desired = self.speed * 0.9;
+ if (sdist > desired) {
+ var move = (sdist - desired) * 0.5;
seg.x += sdx / sdist * move;
seg.y += sdy / sdist * move;
}
- // Optionally, interpolate for smoothness (helps visually attach)
- seg.x = seg.x * 0.7 + targetSeg.x * 0.3;
- seg.y = seg.y * 0.7 + targetSeg.y * 0.3;
- seg.lastX = seg.x;
- seg.lastY = seg.y;
}
- // Clamp to bounds
- self.x = Math.max(100 + self.asset.width / 2, Math.min(2048 - 100 - self.asset.width / 2, self.x));
- self.y = Math.max(100 + self.asset.height / 2, Math.min(2732 - 100 - self.asset.height / 2, self.y));
// Steal food if close enough
if (self.target && self.intersects(self.target)) {
self.stealing = true;
// Remove food from game
@@ -363,15 +358,15 @@
/****
* Game Code
****/
-// --- Game constants ---
-// Snake head and body
-// Food
-// Power-ups
-// Obstacles
-// Sounds
// Music
+// Sounds
+// Obstacles
+// Power-ups
+// Food
+// Snake head and body
+// --- Game constants ---
var SNAKE_INIT_LENGTH = 5;
var SNAKE_INIT_SPEED = 16; // pixels per tick
var SNAKE_MIN_SPEED = 8;
var SNAKE_MAX_SPEED = 40;