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 ****/ // 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 self._rainbowTween = tween.to(self.asset, { tint: 0xff0000 }, 400).yoyo(true).repeat(Infinity).onUpdate(function () { // Cycle through rainbow colors 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; }).start(); 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; // Obstacles are static: no velocity, no update needed 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; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181818 }); /**** * Game Code ****/ // --- Game constants --- // Snake head and body // Food // Power-ups // Obstacles // Sounds // Music 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 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 var avoidList = []; for (var i = 0; i < snake.length; ++i) { avoidList.push(snake[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(); } } // Update moving obstacles 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); } } // 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; 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) { 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; // 50% chance for moving obstacle if (Math.random() < 0.5) { 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(); 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
@@ -65,8 +65,14 @@
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;
@@ -433,10 +439,15 @@
deactivatePowerUp();
}
}
// Update moving obstacles
- for (var i = 0; i < obstacles.length; ++i) {
+ 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);
+ }
}
// Obstacles are static, no update needed
// Snake direction: smooth turn toward nextDir
var d = snakeNextDir - snakeDir;