User prompt
- Player can fart every eating lure
User prompt
- Fart is not working properly. Fix this - Remove win condition
User prompt
-Add a fart to player. It trigger when player kill the enemy. - Increase enemy speed - ıNCREASE ENEMY SPAWN RATE -
User prompt
Remove enemy fart mechanic
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'x')' in or related to this line: 'var dx = playerPos.x - self.head.x + (Math.random() - 0.5) * 200;' Line Number: 79
Code edit (1 edits merged)
Please save this source code
User prompt
Snake Minigun Mayhem
Initial prompt
Make me a classic snake game. But add a minigun to this snake. It will be shoot when enemy is nearby. Every time shoot the enemy snake going to fart as flame.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Bullet var Bullet = Container.expand(function () { var self = Container.call(this); var bullet = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.vx = 0; self.vy = 0; self.owner = null; // 'player' or 'enemy' self.update = function () { self.x += self.vx; self.y += self.vy; }; return self; }); // Enemy Snake Head var EnemyHead = Container.expand(function () { var self = Container.call(this); var head = self.attachAsset('enemyHead', { anchorX: 0.5, anchorY: 0.5 }); return self; }); // EnemyMegaFart effect (distinct visuals for enemy snake nuke fart, now asset-based) var EnemyMegaFart = Container.expand(function () { var self = Container.call(this); var fart = self.attachAsset('enemyNukeFart', { anchorX: 0.5, anchorY: 0.5, scaleX: 1, scaleY: 1, alpha: 0.85 }); // Animate: expand and fade out, then destroy self.play = function () { tween(fart, { scaleX: 2.2, scaleY: 2.2, alpha: 0 }, { duration: 400, easing: tween.easeOutCubic }); tween(self, {}, { duration: 400, onFinish: function onFinish() { self.destroy(); } }); }; return self; }); // Enemy Snake Segment var EnemySegment = Container.expand(function () { var self = Container.call(this); var body = self.attachAsset('enemyBody', { anchorX: 0.5, anchorY: 0.5 }); return self; }); // Enemy Snake var EnemySnake = Container.expand(function () { var self = Container.call(this); self.segments = []; self.head = new EnemyHead(); self.addChild(self.head); self.segments.push(self.head); self.length = 4 + Math.floor(Math.random() * 3); self.speed = 13 + Math.random() * 4; self.targetX = 1024 + (Math.random() - 0.5) * 800; self.targetY = 1366 + (Math.random() - 0.5) * 1200; self.lastMoveX = 0; self.lastMoveY = 1; self.alive = true; // Initialize body for (var i = 1; i < self.length; i++) { var seg = new EnemySegment(); seg.x = self.head.x; seg.y = self.head.y + i * 80; self.addChild(seg); self.segments.push(seg); } // Move snake self.moveSnake = function (playerPos) { // Move toward player, but with some randomness var dx = 0, dy = 0; if (playerPos && typeof playerPos.x === "number" && typeof playerPos.y === "number" && self.head && typeof self.head.x === "number" && typeof self.head.y === "number") { dx = playerPos.x - self.head.x + (Math.random() - 0.5) * 200; dy = playerPos.y - self.head.y + (Math.random() - 0.5) * 200; } else { // fallback: move randomly if playerPos or self.head is not valid dx = (Math.random() - 0.5) * 200; dy = (Math.random() - 0.5) * 200; } var dist = Math.sqrt(dx * dx + dy * dy); var moveX = 0, moveY = 0; if (dist > 10) { moveX = dx / dist; moveY = dy / dist; self.lastMoveX = moveX; self.lastMoveY = moveY; } else { moveX = self.lastMoveX; moveY = self.lastMoveY; } self.head.x += moveX * self.speed; self.head.y += moveY * self.speed; // Rotate head to face movement direction if (Math.abs(moveX) > 0.01 || Math.abs(moveY) > 0.01) { self.head.rotation = Math.atan2(moveY, moveX); } // Move body for (var i = self.segments.length - 1; i > 0; i--) { var prev = self.segments[i - 1]; var seg = self.segments[i]; var pdx = prev.x - seg.x; var pdy = prev.y - seg.y; var pdist = Math.sqrt(pdx * pdx + pdy * pdy); if (pdist > 80) { seg.x += pdx / pdist * Math.min(self.speed, pdist - 80); seg.y += pdy / pdist * Math.min(self.speed, pdist - 80); } // Rotate segment to face previous segment if (i > 0 && (Math.abs(pdx) > 0.01 || Math.abs(pdy) > 0.01)) { seg.rotation = Math.atan2(pdy, pdx); } } }; // Update self.update = function (playerPos) { if (!self.alive) { return; } self.moveSnake(playerPos); }; // Get head position self.getHeadPos = function () { return { x: self.head.x, y: self.head.y }; }; // Get direction vector self.getDirection = function () { return { x: self.lastMoveX, y: self.lastMoveY }; }; // Die self.die = function () { self.alive = false; // Fade out all segments, then return to pool var faded = 0; var total = self.segments.length; for (var i = 0; i < self.segments.length; i++) { tween(self.segments[i], { alpha: 0 }, { duration: 400, easing: tween.linear, onFinish: function onFinish() { faded++; // When all segments faded, return to pool if (faded === total) { // Remove from parent if still attached if (self.parent) { self.parent.removeChild(self); } // Reset state for reuse self.resetForPool(); // Add to pool if (enemySnakePool.indexOf(self) === -1) { enemySnakePool.push(self); } } } }); } }; // Add a resetForPool method to clear state for reuse self.resetForPool = function () { // Reset alpha and visibility for all segments for (var i = 0; i < self.segments.length; i++) { self.segments[i].alpha = 1; } self.visible = false; self.alive = false; // Optionally reset other state if needed }; return self; }); // (Flame Fart Burst removed) // Food var Food = Container.expand(function () { var self = Container.call(this); var food = self.attachAsset('food', { anchorX: 0.5, anchorY: 0.5 }); return self; }); // MegaFart effect (nuke particle, now asset-based) var MegaFart = Container.expand(function () { var self = Container.call(this); var fart = self.attachAsset('playerNukeFart', { anchorX: 0.5, anchorY: 0.5, scaleX: 1, scaleY: 1, alpha: 0.85 }); // Animate: expand and fade out, then destroy self.play = function () { tween(fart, { scaleX: 2.2, scaleY: 2.2, alpha: 0 }, { duration: 350, easing: tween.easeOutCubic }); tween(self, {}, { duration: 350, onFinish: function onFinish() { self.destroy(); } }); }; return self; }); // Player Snake var PlayerSnake = Container.expand(function () { var self = Container.call(this); self.segments = []; self.directions = []; self.head = new SnakeHead(); self.addChild(self.head); self.segments.push(self.head); self.length = 5; self.speed = 12; self.targetX = 1024; self.targetY = 1366; self.lastMoveX = 0; self.lastMoveY = -1; self.growPending = 0; self.lastFireTick = 0; // Initialize body for (var i = 1; i < self.length; i++) { var seg = new SnakeSegment(); seg.x = self.head.x; seg.y = self.head.y + i * 80; self.addChild(seg); self.segments.push(seg); } // Move snake self.moveSnake = function () { // Only move if dragNode is playerSnake (mouse/touch is held) if (_typeof(dragNode) === "object" && dragNode === self) { // Calculate direction to target var dx = self.targetX - self.head.x; var dy = self.targetY - self.head.y; var dist = Math.sqrt(dx * dx + dy * dy); var moveX = 0, moveY = 0; if (dist > 10) { moveX = dx / dist; moveY = dy / dist; self.lastMoveX = moveX; self.lastMoveY = moveY; } else { moveX = self.lastMoveX; moveY = self.lastMoveY; } // Move head self.head.x += moveX * self.speed; self.head.y += moveY * self.speed; // Rotate head to face movement direction if (dist > 10) { self.head.updateRotation(moveX, moveY); } // Move body for (var i = self.segments.length - 1; i > 0; i--) { var prev = self.segments[i - 1]; var seg = self.segments[i]; var pdx = prev.x - seg.x; var pdy = prev.y - seg.y; var pdist = Math.sqrt(pdx * pdx + pdy * pdy); if (pdist > 80) { seg.x += pdx / pdist * Math.min(self.speed, pdist - 80); seg.y += pdy / pdist * Math.min(self.speed, pdist - 80); } // Rotate segment to face previous segment seg.updateRotation(pdx, pdy); } } }; // Grow snake self.grow = function (n) { self.growPending += n; }; // Update self.update = function () { self.moveSnake(); // Handle growth if (self.growPending > 0) { var last = self.segments[self.segments.length - 1]; var seg = new SnakeSegment(); seg.x = last.x; seg.y = last.y; self.addChild(seg); self.segments.push(seg); self.growPending--; } }; // Get head position self.getHeadPos = function () { return { x: self.head.x, y: self.head.y }; }; // Get direction vector self.getDirection = function () { return { x: self.lastMoveX, y: self.lastMoveY }; }; return self; }); // Player Snake Head (uses snakeHead asset, rotates to face movement) var SnakeHead = Container.expand(function () { var self = Container.call(this); var head = self.attachAsset('snakeHead', { anchorX: 0.5, anchorY: 0.5 }); // Add a semi-transparent shooting range circle var rangeRadius = 600; // matches minigun range in game.update var rangeCircle = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: rangeRadius * 2 / 100, // centerCircle asset is 100x100 scaleY: rangeRadius * 2 / 100, alpha: 0.05, // subtle visibility x: 0, y: 0 }); self.addChild(rangeCircle); self._lastX = 0; self._lastY = 0; self._lastRotation = 0; self.updateRotation = function (dx, dy) { // Only update if movement is significant if (Math.abs(dx) > 0.01 || Math.abs(dy) > 0.01) { self._lastRotation = Math.atan2(dy, dx); self.rotation = self._lastRotation; } else { // If not moving, keep last rotation to prevent shaking self.rotation = self._lastRotation; } }; return self; }); // Player Snake Segment (uses snakeBody asset, rotates to face previous segment) var SnakeSegment = Container.expand(function () { var self = Container.call(this); var body = self.attachAsset('snakeBody', { anchorX: 0.5, anchorY: 0.5 }); self._lastRotation = 0; self.updateRotation = function (dx, dy) { if (Math.abs(dx) > 0.01 || Math.abs(dy) > 0.01) { self._lastRotation = Math.atan2(dy, dx); self.rotation = self._lastRotation; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181818 }); /**** * Game Code ****/ // Nuke fart assets (player and enemy, can be replaced with custom art) // (flame asset removed) // Flame fart burst // Bullet // Food // Enemy snake head // Enemy snake body // Snake head (player) // Snake body segment (player) // Game variables // Hexagonal tilemap ground image (slither.io style) function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } var playerSnake; var enemySnakes = []; var enemySnakePool = []; // Pool for dead/reusable enemy snakes var foods = []; var bullets = []; // (flames array removed) var scoreTxt; var dragNode = null; var lastFireTick = 0; var spawnFoodTick = 0; var spawnEnemyTick = 0; var gameArea = { x: 120, y: 120, w: 2048 - 240, h: 2732 - 240 }; // leave margin for UI // Track current enemy snake length scaling with score var currentEnemySnakeLength = 0; // 0 means default (random 4-6), will be set after 100 score // Add hexagonal tilemap ground image, tile to fill game area var groundTiles = []; var tileW = 1020; var tileH = 1065.8; // Start tiling from (0,0) to ensure no gap in top/left corner for (var y = 0; y < 2732; y += tileH) { for (var x = 0; x < 2048; x += tileW) { var tile = LK.getAsset('hexGround', { anchorX: 0, anchorY: 0, x: x, y: y }); groundTiles.push(tile); game.addChild(tile); } } // Score display scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Initialize player snake playerSnake = new PlayerSnake(); playerSnake.head.x = 1024; playerSnake.head.y = 1800; for (var i = 0; i < playerSnake.segments.length; i++) { playerSnake.segments[i].x = 1024; playerSnake.segments[i].y = 1800 + i * 80; } game.addChild(playerSnake); // Spawn initial food function spawnFood() { var f = new Food(); f.x = gameArea.x + 100 + Math.random() * (gameArea.w - 200); f.y = gameArea.y + 100 + Math.random() * (gameArea.h - 200); foods.push(f); game.addChild(f); } for (var i = 0; i < 8; i++) { spawnFood(); } // Spawn enemy snake function spawnEnemy() { // Determine enemy length based on score var enemyLength = 0; if (currentEnemySnakeLength > 0) { enemyLength = currentEnemySnakeLength; } else { enemyLength = 4 + Math.floor(Math.random() * 3); // default: 4-6 } // Fetch from pool or create new var e; if (enemySnakePool.length > 0) { e = enemySnakePool.pop(); // Remove from previous parent if needed if (e.parent) { e.parent.removeChild(e); } // Reset segments while (e.segments.length > 1) { var seg = e.segments.pop(); seg.destroy(); } // Add segments to match desired length for (var i = 1; i < enemyLength; i++) { var seg = new EnemySegment(); seg.x = e.head.x; seg.y = e.head.y + i * 80; e.addChild(seg); e.segments.push(seg); } e.length = enemyLength; // Reset alpha for all segments for (var i = 0; i < e.segments.length; i++) { e.segments[i].alpha = 1; } e.visible = true; e.alive = true; } else { // Create enemy snake with custom length if needed e = new EnemySnake(); if (currentEnemySnakeLength > 0) { // Remove all but head while (e.segments.length > 1) { var seg = e.segments.pop(); seg.destroy(); } // Add segments to match desired length for (var i = 1; i < enemyLength; i++) { var seg = new EnemySegment(); seg.x = e.head.x; seg.y = e.head.y + i * 80; e.addChild(seg); e.segments.push(seg); } e.length = enemyLength; } } // Spawn at random edge OUTSIDE the visible screen var edge = Math.floor(Math.random() * 4); if (edge === 0) { // top (above screen) e.head.x = gameArea.x + Math.random() * gameArea.w; e.head.y = gameArea.y - 200; } else if (edge === 1) { // bottom (below screen) e.head.x = gameArea.x + Math.random() * gameArea.w; e.head.y = gameArea.y + gameArea.h + 200; } else if (edge === 2) { // left (left of screen) e.head.x = gameArea.x - 200; e.head.y = gameArea.y + Math.random() * gameArea.h; } else { // right (right of screen) e.head.x = gameArea.x + gameArea.w + 200; e.head.y = gameArea.y + Math.random() * gameArea.h; } for (var i = 1; i < e.segments.length; i++) { e.segments[i].x = e.head.x; e.segments[i].y = e.head.y + i * 80; } e.visible = true; e.alive = true; enemySnakes.push(e); game.addChild(e); } for (var i = 0; i < 2; i++) { spawnEnemy(); } // Handle dragging game.down = function (x, y, obj) { dragNode = playerSnake; playerSnake.targetX = x; playerSnake.targetY = y; }; game.move = function (x, y, obj) { if (dragNode) { playerSnake.targetX = x; playerSnake.targetY = y; } }; game.up = function (x, y, obj) { dragNode = null; }; // Helper: check collision between two containers (circle-based) function circlesIntersect(a, b, rA, rB) { var dx = a.x - b.x; var dy = a.y - b.y; var dist = Math.sqrt(dx * dx + dy * dy); return dist < (rA + rB) * 0.5; } // Helper: clamp to game area function clampToArea(x, y) { x = Math.max(gameArea.x + 50, Math.min(gameArea.x + gameArea.w - 50, x)); y = Math.max(gameArea.y + 50, Math.min(gameArea.y + gameArea.h - 50, y)); return { x: x, y: y }; } // Main game update game.update = function () { // Update player snake playerSnake.update(); // Clamp player head to area var clamped = clampToArea(playerSnake.head.x, playerSnake.head.y); playerSnake.head.x = clamped.x; playerSnake.head.y = clamped.y; // Update enemy snakes for (var i = enemySnakes.length - 1; i >= 0; i--) { var e = enemySnakes[i]; if (!e.alive) { // Remove from active array if dead and pooled enemySnakes.splice(i, 1); continue; } e.update(playerSnake.getHeadPos()); // Clamp enemy head to area var ce = clampToArea(e.head.x, e.head.y); e.head.x = ce.x; e.head.y = ce.y; } // Update bullets for (var i = bullets.length - 1; i >= 0; i--) { var b = bullets[i]; b.update(); // Remove if out of bounds if (b.x < gameArea.x - 60 || b.x > gameArea.x + gameArea.w + 60 || b.y < gameArea.y - 60 || b.y > gameArea.y + gameArea.h + 60) { b.destroy(); bullets.splice(i, 1); continue; } // Bullet hits enemy snake if (b.owner === 'player') { for (var j = 0; j < enemySnakes.length; j++) { var e = enemySnakes[j]; if (!e.alive) { continue; } if (circlesIntersect(b, e.head, 60, 60)) { // Hit! b.destroy(); bullets.splice(i, 1); // Shorten enemy snake or kill var killed = false; // Nuke fart effect at enemy head when hit (use EnemyMegaFart for enemy snake) var nukeFart = new EnemyMegaFart(); nukeFart.x = e.head.x; nukeFart.y = e.head.y; game.addChild(nukeFart); nukeFart.play(); if (e.segments.length > 2) { var seg = e.segments.pop(); seg.destroy(); } else { e.die(); killed = true; } LK.setScore(LK.getScore() + 5); scoreTxt.setText(LK.getScore()); // Player fart effect when enemy is killed if (killed) { playerSnake.grow(1); // Flash the head and all body segments for a more visible fart effect for (var f = 0; f < playerSnake.segments.length; f++) { LK.effects.flashObject(playerSnake.segments[f], 0xcccc00, 600); } } break; } } } // Bullet hits player (if we add enemy bullets in future) } // Player eats food for (var i = foods.length - 1; i >= 0; i--) { var food = foods[i]; if (circlesIntersect(playerSnake.head, food, 60, 35)) { food.destroy(); foods.splice(i, 1); playerSnake.grow(1); LK.setScore(LK.getScore() + 1); scoreTxt.setText(LK.getScore()); // MegaFart effect: nuke particle at player head var megaFart = new MegaFart(); megaFart.x = playerSnake.head.x; megaFart.y = playerSnake.head.y; game.addChild(megaFart); megaFart.play(); // Win condition removed } } // Player collides with enemy snake head or body for (var i = 0; i < enemySnakes.length; i++) { var e = enemySnakes[i]; if (!e.alive) { continue; } // Head to head if (circlesIntersect(playerSnake.head, e.head, 60, 60)) { LK.effects.flashScreen(0xff3300, 800); LK.showGameOver(); return; } // Head to body for (var j = 1; j < e.segments.length; j++) { if (circlesIntersect(playerSnake.head, e.segments[j], 60, 40)) { LK.effects.flashScreen(0xff3300, 800); LK.showGameOver(); return; } } } // Player collides with self (not head) for (var i = 2; i < playerSnake.segments.length; i++) { if (circlesIntersect(playerSnake.head, playerSnake.segments[i], 60, 40)) { LK.effects.flashScreen(0xff3300, 800); LK.showGameOver(); return; } } // Spawn food spawnFoodTick++; if (spawnFoodTick > 40 && foods.length < 12) { spawnFood(); spawnFoodTick = 0; } // Spawn enemy spawnEnemyTick++; if (spawnEnemyTick > 120 && enemySnakes.length < 8) { // 120 ticks ≈ 2 seconds at 60fps spawnEnemy(); spawnEnemyTick = 0; } // Check and update enemy snake length scaling with score var score = LK.getScore(); if (score >= 1000) { LK.showYouWin(); return; } if (score >= 100) { // Every 100 points, increase enemy length by 2 (starting at 6) var newLen = 6 + Math.floor((score - 100) / 100) * 2; if (newLen !== currentEnemySnakeLength) { currentEnemySnakeLength = newLen; } } // Minigun: auto-fire at nearest enemy in range if (LK.ticks - lastFireTick > 20) { // 20 ticks ≈ 0.33 seconds at 60fps // ~3 shots/sec (50% faster) var nearest = null; var minDist = 99999; for (var i = 0; i < enemySnakes.length; i++) { var e = enemySnakes[i]; if (!e.alive) { continue; } var dx = e.head.x - playerSnake.head.x; var dy = e.head.y - playerSnake.head.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 600 && dist < minDist) { minDist = dist; nearest = e; } } if (nearest) { fireBulletAt(playerSnake.head.x, playerSnake.head.y, nearest.head.x, nearest.head.y, 'player'); lastFireTick = LK.ticks; } } }; // Fire bullet from (x0,y0) to (x1,y1) function fireBulletAt(x0, y0, x1, y1, owner) { var b = new Bullet(); b.x = x0; b.y = y0; var dx = x1 - x0; var dy = y1 - y0; var dist = Math.sqrt(dx * dx + dy * dy); var speed = 38; b.vx = dx / dist * speed; b.vy = dy / dist * speed; b.owner = owner; // Rotate bullet to face direction of travel if (dist > 0.01) { b.rotation = Math.atan2(dy, dx); } bullets.push(b); game.addChild(b); } // (spawnFlameBurst removed) // Center score text scoreTxt.setText(LK.getScore()); scoreTxt.anchor.set(0.5, 0); // No music or sound for MVP
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Bullet
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bullet = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.vx = 0;
self.vy = 0;
self.owner = null; // 'player' or 'enemy'
self.update = function () {
self.x += self.vx;
self.y += self.vy;
};
return self;
});
// Enemy Snake Head
var EnemyHead = Container.expand(function () {
var self = Container.call(this);
var head = self.attachAsset('enemyHead', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// EnemyMegaFart effect (distinct visuals for enemy snake nuke fart, now asset-based)
var EnemyMegaFart = Container.expand(function () {
var self = Container.call(this);
var fart = self.attachAsset('enemyNukeFart', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
scaleY: 1,
alpha: 0.85
});
// Animate: expand and fade out, then destroy
self.play = function () {
tween(fart, {
scaleX: 2.2,
scaleY: 2.2,
alpha: 0
}, {
duration: 400,
easing: tween.easeOutCubic
});
tween(self, {}, {
duration: 400,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
// Enemy Snake Segment
var EnemySegment = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('enemyBody', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Enemy Snake
var EnemySnake = Container.expand(function () {
var self = Container.call(this);
self.segments = [];
self.head = new EnemyHead();
self.addChild(self.head);
self.segments.push(self.head);
self.length = 4 + Math.floor(Math.random() * 3);
self.speed = 13 + Math.random() * 4;
self.targetX = 1024 + (Math.random() - 0.5) * 800;
self.targetY = 1366 + (Math.random() - 0.5) * 1200;
self.lastMoveX = 0;
self.lastMoveY = 1;
self.alive = true;
// Initialize body
for (var i = 1; i < self.length; i++) {
var seg = new EnemySegment();
seg.x = self.head.x;
seg.y = self.head.y + i * 80;
self.addChild(seg);
self.segments.push(seg);
}
// Move snake
self.moveSnake = function (playerPos) {
// Move toward player, but with some randomness
var dx = 0,
dy = 0;
if (playerPos && typeof playerPos.x === "number" && typeof playerPos.y === "number" && self.head && typeof self.head.x === "number" && typeof self.head.y === "number") {
dx = playerPos.x - self.head.x + (Math.random() - 0.5) * 200;
dy = playerPos.y - self.head.y + (Math.random() - 0.5) * 200;
} else {
// fallback: move randomly if playerPos or self.head is not valid
dx = (Math.random() - 0.5) * 200;
dy = (Math.random() - 0.5) * 200;
}
var dist = Math.sqrt(dx * dx + dy * dy);
var moveX = 0,
moveY = 0;
if (dist > 10) {
moveX = dx / dist;
moveY = dy / dist;
self.lastMoveX = moveX;
self.lastMoveY = moveY;
} else {
moveX = self.lastMoveX;
moveY = self.lastMoveY;
}
self.head.x += moveX * self.speed;
self.head.y += moveY * self.speed;
// Rotate head to face movement direction
if (Math.abs(moveX) > 0.01 || Math.abs(moveY) > 0.01) {
self.head.rotation = Math.atan2(moveY, moveX);
}
// Move body
for (var i = self.segments.length - 1; i > 0; i--) {
var prev = self.segments[i - 1];
var seg = self.segments[i];
var pdx = prev.x - seg.x;
var pdy = prev.y - seg.y;
var pdist = Math.sqrt(pdx * pdx + pdy * pdy);
if (pdist > 80) {
seg.x += pdx / pdist * Math.min(self.speed, pdist - 80);
seg.y += pdy / pdist * Math.min(self.speed, pdist - 80);
}
// Rotate segment to face previous segment
if (i > 0 && (Math.abs(pdx) > 0.01 || Math.abs(pdy) > 0.01)) {
seg.rotation = Math.atan2(pdy, pdx);
}
}
};
// Update
self.update = function (playerPos) {
if (!self.alive) {
return;
}
self.moveSnake(playerPos);
};
// Get head position
self.getHeadPos = function () {
return {
x: self.head.x,
y: self.head.y
};
};
// Get direction vector
self.getDirection = function () {
return {
x: self.lastMoveX,
y: self.lastMoveY
};
};
// Die
self.die = function () {
self.alive = false;
// Fade out all segments, then return to pool
var faded = 0;
var total = self.segments.length;
for (var i = 0; i < self.segments.length; i++) {
tween(self.segments[i], {
alpha: 0
}, {
duration: 400,
easing: tween.linear,
onFinish: function onFinish() {
faded++;
// When all segments faded, return to pool
if (faded === total) {
// Remove from parent if still attached
if (self.parent) {
self.parent.removeChild(self);
}
// Reset state for reuse
self.resetForPool();
// Add to pool
if (enemySnakePool.indexOf(self) === -1) {
enemySnakePool.push(self);
}
}
}
});
}
};
// Add a resetForPool method to clear state for reuse
self.resetForPool = function () {
// Reset alpha and visibility for all segments
for (var i = 0; i < self.segments.length; i++) {
self.segments[i].alpha = 1;
}
self.visible = false;
self.alive = false;
// Optionally reset other state if needed
};
return self;
});
// (Flame Fart Burst removed)
// Food
var Food = Container.expand(function () {
var self = Container.call(this);
var food = self.attachAsset('food', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// MegaFart effect (nuke particle, now asset-based)
var MegaFart = Container.expand(function () {
var self = Container.call(this);
var fart = self.attachAsset('playerNukeFart', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
scaleY: 1,
alpha: 0.85
});
// Animate: expand and fade out, then destroy
self.play = function () {
tween(fart, {
scaleX: 2.2,
scaleY: 2.2,
alpha: 0
}, {
duration: 350,
easing: tween.easeOutCubic
});
tween(self, {}, {
duration: 350,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
// Player Snake
var PlayerSnake = Container.expand(function () {
var self = Container.call(this);
self.segments = [];
self.directions = [];
self.head = new SnakeHead();
self.addChild(self.head);
self.segments.push(self.head);
self.length = 5;
self.speed = 12;
self.targetX = 1024;
self.targetY = 1366;
self.lastMoveX = 0;
self.lastMoveY = -1;
self.growPending = 0;
self.lastFireTick = 0;
// Initialize body
for (var i = 1; i < self.length; i++) {
var seg = new SnakeSegment();
seg.x = self.head.x;
seg.y = self.head.y + i * 80;
self.addChild(seg);
self.segments.push(seg);
}
// Move snake
self.moveSnake = function () {
// Only move if dragNode is playerSnake (mouse/touch is held)
if (_typeof(dragNode) === "object" && dragNode === self) {
// Calculate direction to target
var dx = self.targetX - self.head.x;
var dy = self.targetY - self.head.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var moveX = 0,
moveY = 0;
if (dist > 10) {
moveX = dx / dist;
moveY = dy / dist;
self.lastMoveX = moveX;
self.lastMoveY = moveY;
} else {
moveX = self.lastMoveX;
moveY = self.lastMoveY;
}
// Move head
self.head.x += moveX * self.speed;
self.head.y += moveY * self.speed;
// Rotate head to face movement direction
if (dist > 10) {
self.head.updateRotation(moveX, moveY);
}
// Move body
for (var i = self.segments.length - 1; i > 0; i--) {
var prev = self.segments[i - 1];
var seg = self.segments[i];
var pdx = prev.x - seg.x;
var pdy = prev.y - seg.y;
var pdist = Math.sqrt(pdx * pdx + pdy * pdy);
if (pdist > 80) {
seg.x += pdx / pdist * Math.min(self.speed, pdist - 80);
seg.y += pdy / pdist * Math.min(self.speed, pdist - 80);
}
// Rotate segment to face previous segment
seg.updateRotation(pdx, pdy);
}
}
};
// Grow snake
self.grow = function (n) {
self.growPending += n;
};
// Update
self.update = function () {
self.moveSnake();
// Handle growth
if (self.growPending > 0) {
var last = self.segments[self.segments.length - 1];
var seg = new SnakeSegment();
seg.x = last.x;
seg.y = last.y;
self.addChild(seg);
self.segments.push(seg);
self.growPending--;
}
};
// Get head position
self.getHeadPos = function () {
return {
x: self.head.x,
y: self.head.y
};
};
// Get direction vector
self.getDirection = function () {
return {
x: self.lastMoveX,
y: self.lastMoveY
};
};
return self;
});
// Player Snake Head (uses snakeHead asset, rotates to face movement)
var SnakeHead = Container.expand(function () {
var self = Container.call(this);
var head = self.attachAsset('snakeHead', {
anchorX: 0.5,
anchorY: 0.5
});
// Add a semi-transparent shooting range circle
var rangeRadius = 600; // matches minigun range in game.update
var rangeCircle = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: rangeRadius * 2 / 100,
// centerCircle asset is 100x100
scaleY: rangeRadius * 2 / 100,
alpha: 0.05,
// subtle visibility
x: 0,
y: 0
});
self.addChild(rangeCircle);
self._lastX = 0;
self._lastY = 0;
self._lastRotation = 0;
self.updateRotation = function (dx, dy) {
// Only update if movement is significant
if (Math.abs(dx) > 0.01 || Math.abs(dy) > 0.01) {
self._lastRotation = Math.atan2(dy, dx);
self.rotation = self._lastRotation;
} else {
// If not moving, keep last rotation to prevent shaking
self.rotation = self._lastRotation;
}
};
return self;
});
// Player Snake Segment (uses snakeBody asset, rotates to face previous segment)
var SnakeSegment = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('snakeBody', {
anchorX: 0.5,
anchorY: 0.5
});
self._lastRotation = 0;
self.updateRotation = function (dx, dy) {
if (Math.abs(dx) > 0.01 || Math.abs(dy) > 0.01) {
self._lastRotation = Math.atan2(dy, dx);
self.rotation = self._lastRotation;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181818
});
/****
* Game Code
****/
// Nuke fart assets (player and enemy, can be replaced with custom art)
// (flame asset removed)
// Flame fart burst
// Bullet
// Food
// Enemy snake head
// Enemy snake body
// Snake head (player)
// Snake body segment (player)
// Game variables
// Hexagonal tilemap ground image (slither.io style)
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
var playerSnake;
var enemySnakes = [];
var enemySnakePool = []; // Pool for dead/reusable enemy snakes
var foods = [];
var bullets = [];
// (flames array removed)
var scoreTxt;
var dragNode = null;
var lastFireTick = 0;
var spawnFoodTick = 0;
var spawnEnemyTick = 0;
var gameArea = {
x: 120,
y: 120,
w: 2048 - 240,
h: 2732 - 240
}; // leave margin for UI
// Track current enemy snake length scaling with score
var currentEnemySnakeLength = 0; // 0 means default (random 4-6), will be set after 100 score
// Add hexagonal tilemap ground image, tile to fill game area
var groundTiles = [];
var tileW = 1020;
var tileH = 1065.8;
// Start tiling from (0,0) to ensure no gap in top/left corner
for (var y = 0; y < 2732; y += tileH) {
for (var x = 0; x < 2048; x += tileW) {
var tile = LK.getAsset('hexGround', {
anchorX: 0,
anchorY: 0,
x: x,
y: y
});
groundTiles.push(tile);
game.addChild(tile);
}
}
// Score display
scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Initialize player snake
playerSnake = new PlayerSnake();
playerSnake.head.x = 1024;
playerSnake.head.y = 1800;
for (var i = 0; i < playerSnake.segments.length; i++) {
playerSnake.segments[i].x = 1024;
playerSnake.segments[i].y = 1800 + i * 80;
}
game.addChild(playerSnake);
// Spawn initial food
function spawnFood() {
var f = new Food();
f.x = gameArea.x + 100 + Math.random() * (gameArea.w - 200);
f.y = gameArea.y + 100 + Math.random() * (gameArea.h - 200);
foods.push(f);
game.addChild(f);
}
for (var i = 0; i < 8; i++) {
spawnFood();
}
// Spawn enemy snake
function spawnEnemy() {
// Determine enemy length based on score
var enemyLength = 0;
if (currentEnemySnakeLength > 0) {
enemyLength = currentEnemySnakeLength;
} else {
enemyLength = 4 + Math.floor(Math.random() * 3); // default: 4-6
}
// Fetch from pool or create new
var e;
if (enemySnakePool.length > 0) {
e = enemySnakePool.pop();
// Remove from previous parent if needed
if (e.parent) {
e.parent.removeChild(e);
}
// Reset segments
while (e.segments.length > 1) {
var seg = e.segments.pop();
seg.destroy();
}
// Add segments to match desired length
for (var i = 1; i < enemyLength; i++) {
var seg = new EnemySegment();
seg.x = e.head.x;
seg.y = e.head.y + i * 80;
e.addChild(seg);
e.segments.push(seg);
}
e.length = enemyLength;
// Reset alpha for all segments
for (var i = 0; i < e.segments.length; i++) {
e.segments[i].alpha = 1;
}
e.visible = true;
e.alive = true;
} else {
// Create enemy snake with custom length if needed
e = new EnemySnake();
if (currentEnemySnakeLength > 0) {
// Remove all but head
while (e.segments.length > 1) {
var seg = e.segments.pop();
seg.destroy();
}
// Add segments to match desired length
for (var i = 1; i < enemyLength; i++) {
var seg = new EnemySegment();
seg.x = e.head.x;
seg.y = e.head.y + i * 80;
e.addChild(seg);
e.segments.push(seg);
}
e.length = enemyLength;
}
}
// Spawn at random edge OUTSIDE the visible screen
var edge = Math.floor(Math.random() * 4);
if (edge === 0) {
// top (above screen)
e.head.x = gameArea.x + Math.random() * gameArea.w;
e.head.y = gameArea.y - 200;
} else if (edge === 1) {
// bottom (below screen)
e.head.x = gameArea.x + Math.random() * gameArea.w;
e.head.y = gameArea.y + gameArea.h + 200;
} else if (edge === 2) {
// left (left of screen)
e.head.x = gameArea.x - 200;
e.head.y = gameArea.y + Math.random() * gameArea.h;
} else {
// right (right of screen)
e.head.x = gameArea.x + gameArea.w + 200;
e.head.y = gameArea.y + Math.random() * gameArea.h;
}
for (var i = 1; i < e.segments.length; i++) {
e.segments[i].x = e.head.x;
e.segments[i].y = e.head.y + i * 80;
}
e.visible = true;
e.alive = true;
enemySnakes.push(e);
game.addChild(e);
}
for (var i = 0; i < 2; i++) {
spawnEnemy();
}
// Handle dragging
game.down = function (x, y, obj) {
dragNode = playerSnake;
playerSnake.targetX = x;
playerSnake.targetY = y;
};
game.move = function (x, y, obj) {
if (dragNode) {
playerSnake.targetX = x;
playerSnake.targetY = y;
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Helper: check collision between two containers (circle-based)
function circlesIntersect(a, b, rA, rB) {
var dx = a.x - b.x;
var dy = a.y - b.y;
var dist = Math.sqrt(dx * dx + dy * dy);
return dist < (rA + rB) * 0.5;
}
// Helper: clamp to game area
function clampToArea(x, y) {
x = Math.max(gameArea.x + 50, Math.min(gameArea.x + gameArea.w - 50, x));
y = Math.max(gameArea.y + 50, Math.min(gameArea.y + gameArea.h - 50, y));
return {
x: x,
y: y
};
}
// Main game update
game.update = function () {
// Update player snake
playerSnake.update();
// Clamp player head to area
var clamped = clampToArea(playerSnake.head.x, playerSnake.head.y);
playerSnake.head.x = clamped.x;
playerSnake.head.y = clamped.y;
// Update enemy snakes
for (var i = enemySnakes.length - 1; i >= 0; i--) {
var e = enemySnakes[i];
if (!e.alive) {
// Remove from active array if dead and pooled
enemySnakes.splice(i, 1);
continue;
}
e.update(playerSnake.getHeadPos());
// Clamp enemy head to area
var ce = clampToArea(e.head.x, e.head.y);
e.head.x = ce.x;
e.head.y = ce.y;
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var b = bullets[i];
b.update();
// Remove if out of bounds
if (b.x < gameArea.x - 60 || b.x > gameArea.x + gameArea.w + 60 || b.y < gameArea.y - 60 || b.y > gameArea.y + gameArea.h + 60) {
b.destroy();
bullets.splice(i, 1);
continue;
}
// Bullet hits enemy snake
if (b.owner === 'player') {
for (var j = 0; j < enemySnakes.length; j++) {
var e = enemySnakes[j];
if (!e.alive) {
continue;
}
if (circlesIntersect(b, e.head, 60, 60)) {
// Hit!
b.destroy();
bullets.splice(i, 1);
// Shorten enemy snake or kill
var killed = false;
// Nuke fart effect at enemy head when hit (use EnemyMegaFart for enemy snake)
var nukeFart = new EnemyMegaFart();
nukeFart.x = e.head.x;
nukeFart.y = e.head.y;
game.addChild(nukeFart);
nukeFart.play();
if (e.segments.length > 2) {
var seg = e.segments.pop();
seg.destroy();
} else {
e.die();
killed = true;
}
LK.setScore(LK.getScore() + 5);
scoreTxt.setText(LK.getScore());
// Player fart effect when enemy is killed
if (killed) {
playerSnake.grow(1);
// Flash the head and all body segments for a more visible fart effect
for (var f = 0; f < playerSnake.segments.length; f++) {
LK.effects.flashObject(playerSnake.segments[f], 0xcccc00, 600);
}
}
break;
}
}
}
// Bullet hits player (if we add enemy bullets in future)
}
// Player eats food
for (var i = foods.length - 1; i >= 0; i--) {
var food = foods[i];
if (circlesIntersect(playerSnake.head, food, 60, 35)) {
food.destroy();
foods.splice(i, 1);
playerSnake.grow(1);
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
// MegaFart effect: nuke particle at player head
var megaFart = new MegaFart();
megaFart.x = playerSnake.head.x;
megaFart.y = playerSnake.head.y;
game.addChild(megaFart);
megaFart.play();
// Win condition removed
}
}
// Player collides with enemy snake head or body
for (var i = 0; i < enemySnakes.length; i++) {
var e = enemySnakes[i];
if (!e.alive) {
continue;
}
// Head to head
if (circlesIntersect(playerSnake.head, e.head, 60, 60)) {
LK.effects.flashScreen(0xff3300, 800);
LK.showGameOver();
return;
}
// Head to body
for (var j = 1; j < e.segments.length; j++) {
if (circlesIntersect(playerSnake.head, e.segments[j], 60, 40)) {
LK.effects.flashScreen(0xff3300, 800);
LK.showGameOver();
return;
}
}
}
// Player collides with self (not head)
for (var i = 2; i < playerSnake.segments.length; i++) {
if (circlesIntersect(playerSnake.head, playerSnake.segments[i], 60, 40)) {
LK.effects.flashScreen(0xff3300, 800);
LK.showGameOver();
return;
}
}
// Spawn food
spawnFoodTick++;
if (spawnFoodTick > 40 && foods.length < 12) {
spawnFood();
spawnFoodTick = 0;
}
// Spawn enemy
spawnEnemyTick++;
if (spawnEnemyTick > 120 && enemySnakes.length < 8) {
// 120 ticks ≈ 2 seconds at 60fps
spawnEnemy();
spawnEnemyTick = 0;
}
// Check and update enemy snake length scaling with score
var score = LK.getScore();
if (score >= 1000) {
LK.showYouWin();
return;
}
if (score >= 100) {
// Every 100 points, increase enemy length by 2 (starting at 6)
var newLen = 6 + Math.floor((score - 100) / 100) * 2;
if (newLen !== currentEnemySnakeLength) {
currentEnemySnakeLength = newLen;
}
}
// Minigun: auto-fire at nearest enemy in range
if (LK.ticks - lastFireTick > 20) {
// 20 ticks ≈ 0.33 seconds at 60fps
// ~3 shots/sec (50% faster)
var nearest = null;
var minDist = 99999;
for (var i = 0; i < enemySnakes.length; i++) {
var e = enemySnakes[i];
if (!e.alive) {
continue;
}
var dx = e.head.x - playerSnake.head.x;
var dy = e.head.y - playerSnake.head.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 600 && dist < minDist) {
minDist = dist;
nearest = e;
}
}
if (nearest) {
fireBulletAt(playerSnake.head.x, playerSnake.head.y, nearest.head.x, nearest.head.y, 'player');
lastFireTick = LK.ticks;
}
}
};
// Fire bullet from (x0,y0) to (x1,y1)
function fireBulletAt(x0, y0, x1, y1, owner) {
var b = new Bullet();
b.x = x0;
b.y = y0;
var dx = x1 - x0;
var dy = y1 - y0;
var dist = Math.sqrt(dx * dx + dy * dy);
var speed = 38;
b.vx = dx / dist * speed;
b.vy = dy / dist * speed;
b.owner = owner;
// Rotate bullet to face direction of travel
if (dist > 0.01) {
b.rotation = Math.atan2(dy, dx);
}
bullets.push(b);
game.addChild(b);
}
// (spawnFlameBurst removed)
// Center score text
scoreTxt.setText(LK.getScore());
scoreTxt.anchor.set(0.5, 0);
// No music or sound for MVP