User prompt
If player triggers the bomb, destroy current color the bomb indicates. Don’t kill the snake, just destroy that color. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Change bomb trigger imput to double-click. For example; after snake eats a bomb explode current color if player double-clicks the screen.
User prompt
Make snake’ movement speed 2 times faster.
User prompt
Change snake controls swipe. For example; if I swipe down, snake starts goğng down. Same for the othere rotations. Cancel touching screen sides for mobement.
User prompt
Also add arrow key controls for PC.
User prompt
Add the eaten pick up en of the tail.
User prompt
Make: W = Up A = Left S = Down D = Right
User prompt
Add "WASD" control.
User prompt
Please fix the bug: 'Failed to connect to MetaMask' in or related to this line: 'if (moveTimer >= gameSpeed / 1000 * 60) {' Line Number: 378
User prompt
Please fix the bug: 'Failed to connect to MetaMask' in or related to this line: 'if (moveTimer >= gameSpeed / 60) {' Line Number: 378
User prompt
Please fix the bug: 'Failed to connect to MetaMask' in or related to this line: 'if (moveTimer >= gameSpeed / 60 * 60) {' Line Number: 378
Code edit (1 edits merged)
Please save this source code
User prompt
SnakeMatch3
Initial prompt
Create a playable prototype of a game called "SnakeMatch3". It combines classic Snake with match-3 mechanics. Core rules: - The player controls a snake that moves in 4 directions (up, down, left, right) on a grid. - The snake wraps around the screen edges (if it exits from one side, it enters from the opposite). - The snake starts with 1 segment. - When the snake eats a colored pickup (red, yellow, blue), it grows by adding a segment of that color to its tail. - Always show the "next pickup color" in the UI, similar to Tetris. - If 3 or more same-colored segments are next to each other in the tail, they explode and disappear, awarding points. - Occasionally, a bomb pickup spawns. - When the snake collects a bomb, the bomb travels along the tail segments, briefly highlighting each one in order. - The player can press a key to trigger the bomb on the highlighted segment. - The targeted segment is removed and placed at the end of the tail. If this causes 3 same-colored segments to align, they explode and the game continues. If not, the game ends. - The game also ends if the snake collides with its own body. Technical details: - Use simple placeholder graphics (colored squares for pickups and segments, a darker square for the snake head). - Show the grid-based movement clearly, with smooth snapping to the grid. - Add a score counter at the top of the screen. - Make sure the game loop is fast and responsive. Goal: The player must survive as long as possible, collect points by matching 3 segments of the same color, and use bombs strategically to manipulate the snake's body.
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Pickup = Container.expand(function (color, x, y) {
var self = Container.call(this);
self.color = color;
self.gridX = x;
self.gridY = y;
self.isBomb = color === 'bomb';
var assetId = self.isBomb ? 'bombPickup' : 'pickup' + color.charAt(0).toUpperCase() + color.slice(1);
self.graphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.x = PLAYABLE_START_X + x * GRID_SIZE + GRID_SIZE / 2;
self.y = y * GRID_SIZE + GRID_SIZE / 2 + PLAYABLE_START_Y;
return self;
});
var SnakeHead = Container.expand(function (x, y) {
var self = Container.call(this);
self.gridX = x;
self.gridY = y;
self.direction = {
x: 1,
y: 0
};
self.graphics = self.attachAsset('snakeHead', {
anchorX: 0.5,
anchorY: 0.5
});
self.setPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = PLAYABLE_START_X + gridX * GRID_SIZE + GRID_SIZE / 2;
self.y = gridY * GRID_SIZE + GRID_SIZE / 2 + PLAYABLE_START_Y;
};
return self;
});
var SnakeSegment = Container.expand(function (color, x, y) {
var self = Container.call(this);
self.color = color;
self.gridX = x;
self.gridY = y;
self.isHighlighted = false;
var assetId = 'snakeSegment' + color.charAt(0).toUpperCase() + color.slice(1);
self.graphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.setPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = PLAYABLE_START_X + gridX * GRID_SIZE + GRID_SIZE / 2;
self.y = gridY * GRID_SIZE + GRID_SIZE / 2 + PLAYABLE_START_Y;
};
self.highlight = function (highlight) {
self.isHighlighted = highlight;
if (highlight) {
self.graphics.alpha = 0.7;
self.graphics.scaleX = 1.2;
self.graphics.scaleY = 1.2;
} else {
self.graphics.alpha = 1.0;
self.graphics.scaleX = 1.0;
self.graphics.scaleY = 1.0;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
var GRID_SIZE = 100;
var UI_HEIGHT = 50; // Minimal height reserved for UI at top
var PLAYABLE_SIZE = 2048; // Full screen width playable area
var PLAYABLE_HEIGHT = 1500; // Keep original square height
var GRID_WIDTH = Math.floor(PLAYABLE_SIZE / GRID_SIZE);
var GRID_HEIGHT = Math.floor(PLAYABLE_HEIGHT / GRID_SIZE);
var PLAYABLE_START_Y = UI_HEIGHT; // Move playable area to just below UI
var PLAYABLE_START_X = 0; // Start at left edge of screen
var SWIPE_AREA_HEIGHT = 2732 - PLAYABLE_START_Y - PLAYABLE_HEIGHT - 100; // Rest for swipe area minus UI space
var snake = [];
var snakeHead = null;
var pickups = [];
var gameSpeed = 150;
var moveTimer = 0;
var colors = ['red', 'yellow', 'blue'];
var nextPickupColor = colors[Math.floor(Math.random() * colors.length)];
var bombMode = false;
var bombIndex = 0;
var bombTimer = 0;
var gameOver = false;
var foodCounter = 0; // Track number of foods eaten
var baseSpeed = 150; // Base game speed
var bombSpawnCounter = 0; // Track number of foods eaten for bomb spawning
var bombTimers = []; // Track active bomb timers
// UI Elements
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0, 0);
scoreTxt.x = 50;
scoreTxt.y = PLAYABLE_START_Y + PLAYABLE_HEIGHT + 20;
game.addChild(scoreTxt);
var nextColorTxt = new Text2('Next: Red', {
size: 70,
fill: 0xFFFFFF
});
nextColorTxt.anchor.set(1, 0);
nextColorTxt.x = 2048 - 54;
nextColorTxt.y = PLAYABLE_START_Y + PLAYABLE_HEIGHT + 20;
game.addChild(nextColorTxt);
function initializeGame() {
// Create snake head
snakeHead = new SnakeHead(Math.floor(GRID_WIDTH / 2), Math.floor(GRID_HEIGHT / 2));
game.addChild(snakeHead);
snakeHead.setPosition(snakeHead.gridX, snakeHead.gridY);
// Create initial tail segments
for (var i = 1; i <= 3; i++) {
var color = colors[Math.floor(Math.random() * colors.length)];
var segment = new SnakeSegment(color, snakeHead.gridX - i, snakeHead.gridY);
snake.push(segment);
game.addChild(segment);
segment.setPosition(segment.gridX, segment.gridY);
}
spawnRandomPickup();
updateNextColorDisplay();
}
function spawnRandomPickup() {
var attempts = 0;
var x, y;
do {
x = Math.floor(Math.random() * GRID_WIDTH);
y = Math.floor(Math.random() * GRID_HEIGHT);
attempts++;
} while (isPositionOccupied(x, y) && attempts < 100);
if (attempts < 100) {
// Check if we should spawn a bomb every 5 foods
var shouldSpawnBomb = bombSpawnCounter >= 5;
var isBomb = shouldSpawnBomb;
var color = isBomb ? 'bomb' : nextPickupColor;
var pickup = new Pickup(color, x, y);
pickups.push(pickup);
game.addChild(pickup);
if (isBomb) {
// Reset bomb spawn counter
bombSpawnCounter = 0;
// Start 5 second timer for this bomb
var bombTimer = {
pickup: pickup,
timeLeft: 300 // 5 seconds * 60 FPS
};
bombTimers.push(bombTimer);
} else {
nextPickupColor = colors[Math.floor(Math.random() * colors.length)];
updateNextColorDisplay();
}
}
}
function isPositionOccupied(x, y) {
if (snakeHead.gridX === x && snakeHead.gridY === y) return true;
for (var i = 0; i < snake.length; i++) {
if (snake[i].gridX === x && snake[i].gridY === y) return true;
}
for (var j = 0; j < pickups.length; j++) {
if (pickups[j].gridX === x && pickups[j].gridY === y) return true;
}
return false;
}
function updateNextColorDisplay() {
nextColorTxt.setText('Next: ' + nextPickupColor.charAt(0).toUpperCase() + nextPickupColor.slice(1));
}
function moveSnake() {
if (gameOver || bombMode) return;
var newX = snakeHead.gridX + snakeHead.direction.x;
var newY = snakeHead.gridY + snakeHead.direction.y;
// Wrap around screen edges
if (newX < 0) newX = GRID_WIDTH - 1;
if (newX >= GRID_WIDTH) newX = 0;
if (newY < 0) newY = GRID_HEIGHT - 1;
if (newY >= GRID_HEIGHT) newY = 0;
// Check for self collision
for (var i = 0; i < snake.length; i++) {
if (snake[i].gridX === newX && snake[i].gridY === newY) {
gameOver = true;
LK.showGameOver();
return;
}
}
// Move tail segments
if (snake.length > 0) {
for (var j = snake.length - 1; j > 0; j--) {
snake[j].setPosition(snake[j - 1].gridX, snake[j - 1].gridY);
}
snake[0].setPosition(snakeHead.gridX, snakeHead.gridY);
}
// Move head
snakeHead.setPosition(newX, newY);
// Check for pickup collision
for (var k = pickups.length - 1; k >= 0; k--) {
var pickup = pickups[k];
if (pickup.gridX === newX && pickup.gridY === newY) {
if (pickup.isBomb) {
handleBombPickup();
// Remove bomb timer for this pickup
for (var t = bombTimers.length - 1; t >= 0; t--) {
if (bombTimers[t].pickup === pickup) {
bombTimers.splice(t, 1);
break;
}
}
} else {
handleColorPickup(pickup.color);
}
pickup.destroy();
pickups.splice(k, 1);
spawnRandomPickup();
break;
}
}
checkForMatches();
}
function handleColorPickup(color) {
LK.getSound('collect').play();
// Get the position of the last tail segment, or head if no tail exists
var lastX, lastY;
if (snake.length > 0) {
lastX = snake[snake.length - 1].gridX;
lastY = snake[snake.length - 1].gridY;
} else {
lastX = snakeHead.gridX;
lastY = snakeHead.gridY;
}
var newSegment = new SnakeSegment(color, lastX, lastY);
snake.push(newSegment);
game.addChild(newSegment);
LK.setScore(LK.getScore() + 1);
scoreTxt.setText('Score: ' + LK.getScore());
// Increment food counter and check for speed increase
foodCounter++;
bombSpawnCounter++; // Increment bomb spawn counter
if (foodCounter % 5 === 0) {
// Increase speed by reducing gameSpeed (making it 0.05x faster)
gameSpeed = Math.max(30, gameSpeed - gameSpeed * 0.05); // Prevent speed from getting too fast
}
}
function handleBombPickup() {
if (snake.length === 0) return;
LK.getSound('bomb').play();
bombMode = true;
bombIndex = 0;
bombTimer = 0;
// Highlight first segment
snake[bombIndex].highlight(true);
}
function processBombMovement() {
if (!bombMode) return;
bombTimer++;
if (bombTimer >= 30) {
// Move every 30 ticks (0.5 seconds)
snake[bombIndex].highlight(false);
bombIndex++;
if (bombIndex >= snake.length) {
bombIndex = 0;
}
snake[bombIndex].highlight(true);
bombTimer = 0;
}
}
function triggerBomb() {
if (!bombMode || snake.length === 0) return;
snake[bombIndex].highlight(false);
bombMode = false;
// Only remove the specific selected snake segment
var segmentToRemove = snake[bombIndex];
LK.getSound('explode').play();
// Create explosion effect on the selected snake segment
tween(segmentToRemove.graphics, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut
});
// Immediately remove the segment and close the gap
segmentToRemove.destroy();
snake.splice(bombIndex, 1);
// Immediately reposition all segments after the removed one to close the gap
if (bombIndex < snake.length && snake.length > 0) {
// Get the position of the segment before the removed one (or head if removing first segment)
var previousX, previousY;
if (bombIndex === 0) {
// If we removed the first segment, connect remaining segments to head
previousX = snakeHead.gridX;
previousY = snakeHead.gridY;
} else {
// Connect to the segment before the removed one
previousX = snake[bombIndex - 1].gridX;
previousY = snake[bombIndex - 1].gridY;
}
// Reposition all segments from the removed position onwards
for (var i = bombIndex; i < snake.length; i++) {
if (i === bombIndex) {
// First segment after gap connects to previous position
snake[i].setPosition(previousX, previousY);
} else {
// Subsequent segments connect to the previous segment
snake[i].setPosition(snake[i - 1].gridX, snake[i - 1].gridY);
}
}
}
// Award points
var points = 10;
LK.setScore(LK.getScore() + points);
scoreTxt.setText('Score: ' + LK.getScore());
// Flash effect
LK.effects.flashScreen(0xffffff, 300);
}
function checkForMatches() {
var matchFound = false;
var toRemove = [];
for (var i = 0; i < snake.length - 2; i++) {
var count = 1;
var currentColor = snake[i].color;
for (var j = i + 1; j < snake.length; j++) {
if (snake[j].color === currentColor) {
count++;
} else {
break;
}
}
if (count >= 3) {
for (var k = i; k < i + count; k++) {
toRemove.push(k);
}
matchFound = true;
i += count - 1;
}
}
if (toRemove.length > 0) {
LK.getSound('explode').play();
// Remove matched segments
for (var m = toRemove.length - 1; m >= 0; m--) {
var index = toRemove[m];
snake[index].destroy();
snake.splice(index, 1);
}
// Award points
var points = toRemove.length * 10;
LK.setScore(LK.getScore() + points);
scoreTxt.setText('Score: ' + LK.getScore());
// Flash effect
LK.effects.flashScreen(0xffffff, 200);
}
return matchFound;
}
// Swipe detection variables
var swipeStartX = 0;
var swipeStartY = 0;
var minSwipeDistance = 50;
var isSwipeStarted = false;
function handleSwipe(startX, startY, endX, endY) {
if (gameOver) return;
if (bombMode) {
return;
}
var deltaX = endX - startX;
var deltaY = endY - startY;
var swipeDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Check if swipe is long enough
if (swipeDistance < minSwipeDistance) return;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Horizontal swipe
if (deltaX > 0 && snakeHead.direction.x !== -1) {
// Swipe right
snakeHead.direction = {
x: 1,
y: 0
};
} else if (deltaX < 0 && snakeHead.direction.x !== 1) {
// Swipe left
snakeHead.direction = {
x: -1,
y: 0
};
}
} else {
// Vertical swipe
if (deltaY > 0 && snakeHead.direction.y !== -1) {
// Swipe down
snakeHead.direction = {
x: 0,
y: 1
};
} else if (deltaY < 0 && snakeHead.direction.y !== 1) {
// Swipe up
snakeHead.direction = {
x: 0,
y: -1
};
}
}
}
game.down = function (x, y, obj) {
// Single-click bomb trigger
if (bombMode) {
triggerBomb();
return;
}
swipeStartX = x;
swipeStartY = y;
isSwipeStarted = true;
};
game.up = function (x, y, obj) {
if (isSwipeStarted) {
handleSwipe(swipeStartX, swipeStartY, x, y);
isSwipeStarted = false;
}
};
game.update = function () {
if (gameOver) return;
processBombMovement();
// Process bomb timers
for (var i = bombTimers.length - 1; i >= 0; i--) {
var bombTimer = bombTimers[i];
bombTimer.timeLeft--;
// Visual effect - make bomb fade as time runs out
var fadeAlpha = Math.max(0.3, bombTimer.timeLeft / 300);
bombTimer.pickup.graphics.alpha = fadeAlpha;
// Scale effect to show urgency
var scaleEffect = 1 + (1 - bombTimer.timeLeft / 300) * 0.3;
bombTimer.pickup.graphics.scaleX = scaleEffect;
bombTimer.pickup.graphics.scaleY = scaleEffect;
if (bombTimer.timeLeft <= 0) {
// Remove bomb after 5 seconds
var pickupIndex = pickups.indexOf(bombTimer.pickup);
if (pickupIndex !== -1) {
// Fade out effect
tween(bombTimer.pickup.graphics, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
if (pickupIndex !== -1 && pickups[pickupIndex]) {
pickups[pickupIndex].destroy();
pickups.splice(pickupIndex, 1);
spawnRandomPickup();
}
}
});
}
bombTimers.splice(i, 1);
}
}
moveTimer++;
if (moveTimer >= gameSpeed / 16.67) {
moveSnake();
moveTimer = 0;
}
};
// Create playable area border
var playableBorder = new Container();
game.addChild(playableBorder);
// Create border lines for square centered area
var borderTop = LK.getAsset('snakeHead', {
anchorX: 0,
anchorY: 0,
scaleX: PLAYABLE_SIZE / 80,
scaleY: 0.05,
tint: 0xFFFFFF
});
borderTop.x = PLAYABLE_START_X;
borderTop.y = PLAYABLE_START_Y;
playableBorder.addChild(borderTop);
var borderBottom = LK.getAsset('snakeHead', {
anchorX: 0,
anchorY: 0,
scaleX: PLAYABLE_SIZE / 80,
scaleY: 0.05,
tint: 0xFFFFFF
});
borderBottom.x = PLAYABLE_START_X;
borderBottom.y = PLAYABLE_START_Y + PLAYABLE_HEIGHT;
playableBorder.addChild(borderBottom);
var borderLeft = LK.getAsset('snakeHead', {
anchorX: 0,
anchorY: 0,
scaleX: 0.05,
scaleY: PLAYABLE_HEIGHT / 80,
tint: 0xFFFFFF
});
borderLeft.x = PLAYABLE_START_X;
borderLeft.y = PLAYABLE_START_Y;
playableBorder.addChild(borderLeft);
var borderRight = LK.getAsset('snakeHead', {
anchorX: 0,
anchorY: 0,
scaleX: 0.05,
scaleY: PLAYABLE_HEIGHT / 80,
tint: 0xFFFFFF
});
borderRight.x = PLAYABLE_START_X + PLAYABLE_SIZE;
borderRight.y = PLAYABLE_START_Y;
playableBorder.addChild(borderRight);
// Initialize the game
initializeGame();
; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Pickup = Container.expand(function (color, x, y) {
var self = Container.call(this);
self.color = color;
self.gridX = x;
self.gridY = y;
self.isBomb = color === 'bomb';
var assetId = self.isBomb ? 'bombPickup' : 'pickup' + color.charAt(0).toUpperCase() + color.slice(1);
self.graphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.x = PLAYABLE_START_X + x * GRID_SIZE + GRID_SIZE / 2;
self.y = y * GRID_SIZE + GRID_SIZE / 2 + PLAYABLE_START_Y;
return self;
});
var SnakeHead = Container.expand(function (x, y) {
var self = Container.call(this);
self.gridX = x;
self.gridY = y;
self.direction = {
x: 1,
y: 0
};
self.graphics = self.attachAsset('snakeHead', {
anchorX: 0.5,
anchorY: 0.5
});
self.setPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = PLAYABLE_START_X + gridX * GRID_SIZE + GRID_SIZE / 2;
self.y = gridY * GRID_SIZE + GRID_SIZE / 2 + PLAYABLE_START_Y;
};
return self;
});
var SnakeSegment = Container.expand(function (color, x, y) {
var self = Container.call(this);
self.color = color;
self.gridX = x;
self.gridY = y;
self.isHighlighted = false;
var assetId = 'snakeSegment' + color.charAt(0).toUpperCase() + color.slice(1);
self.graphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.setPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = PLAYABLE_START_X + gridX * GRID_SIZE + GRID_SIZE / 2;
self.y = gridY * GRID_SIZE + GRID_SIZE / 2 + PLAYABLE_START_Y;
};
self.highlight = function (highlight) {
self.isHighlighted = highlight;
if (highlight) {
self.graphics.alpha = 0.7;
self.graphics.scaleX = 1.2;
self.graphics.scaleY = 1.2;
} else {
self.graphics.alpha = 1.0;
self.graphics.scaleX = 1.0;
self.graphics.scaleY = 1.0;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
var GRID_SIZE = 100;
var UI_HEIGHT = 50; // Minimal height reserved for UI at top
var PLAYABLE_SIZE = 2048; // Full screen width playable area
var PLAYABLE_HEIGHT = 1500; // Keep original square height
var GRID_WIDTH = Math.floor(PLAYABLE_SIZE / GRID_SIZE);
var GRID_HEIGHT = Math.floor(PLAYABLE_HEIGHT / GRID_SIZE);
var PLAYABLE_START_Y = UI_HEIGHT; // Move playable area to just below UI
var PLAYABLE_START_X = 0; // Start at left edge of screen
var SWIPE_AREA_HEIGHT = 2732 - PLAYABLE_START_Y - PLAYABLE_HEIGHT - 100; // Rest for swipe area minus UI space
var snake = [];
var snakeHead = null;
var pickups = [];
var gameSpeed = 150;
var moveTimer = 0;
var colors = ['red', 'yellow', 'blue'];
var nextPickupColor = colors[Math.floor(Math.random() * colors.length)];
var bombMode = false;
var bombIndex = 0;
var bombTimer = 0;
var gameOver = false;
var foodCounter = 0; // Track number of foods eaten
var baseSpeed = 150; // Base game speed
var bombSpawnCounter = 0; // Track number of foods eaten for bomb spawning
var bombTimers = []; // Track active bomb timers
// UI Elements
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0, 0);
scoreTxt.x = 50;
scoreTxt.y = PLAYABLE_START_Y + PLAYABLE_HEIGHT + 20;
game.addChild(scoreTxt);
var nextColorTxt = new Text2('Next: Red', {
size: 70,
fill: 0xFFFFFF
});
nextColorTxt.anchor.set(1, 0);
nextColorTxt.x = 2048 - 54;
nextColorTxt.y = PLAYABLE_START_Y + PLAYABLE_HEIGHT + 20;
game.addChild(nextColorTxt);
function initializeGame() {
// Create snake head
snakeHead = new SnakeHead(Math.floor(GRID_WIDTH / 2), Math.floor(GRID_HEIGHT / 2));
game.addChild(snakeHead);
snakeHead.setPosition(snakeHead.gridX, snakeHead.gridY);
// Create initial tail segments
for (var i = 1; i <= 3; i++) {
var color = colors[Math.floor(Math.random() * colors.length)];
var segment = new SnakeSegment(color, snakeHead.gridX - i, snakeHead.gridY);
snake.push(segment);
game.addChild(segment);
segment.setPosition(segment.gridX, segment.gridY);
}
spawnRandomPickup();
updateNextColorDisplay();
}
function spawnRandomPickup() {
var attempts = 0;
var x, y;
do {
x = Math.floor(Math.random() * GRID_WIDTH);
y = Math.floor(Math.random() * GRID_HEIGHT);
attempts++;
} while (isPositionOccupied(x, y) && attempts < 100);
if (attempts < 100) {
// Check if we should spawn a bomb every 5 foods
var shouldSpawnBomb = bombSpawnCounter >= 5;
var isBomb = shouldSpawnBomb;
var color = isBomb ? 'bomb' : nextPickupColor;
var pickup = new Pickup(color, x, y);
pickups.push(pickup);
game.addChild(pickup);
if (isBomb) {
// Reset bomb spawn counter
bombSpawnCounter = 0;
// Start 5 second timer for this bomb
var bombTimer = {
pickup: pickup,
timeLeft: 300 // 5 seconds * 60 FPS
};
bombTimers.push(bombTimer);
} else {
nextPickupColor = colors[Math.floor(Math.random() * colors.length)];
updateNextColorDisplay();
}
}
}
function isPositionOccupied(x, y) {
if (snakeHead.gridX === x && snakeHead.gridY === y) return true;
for (var i = 0; i < snake.length; i++) {
if (snake[i].gridX === x && snake[i].gridY === y) return true;
}
for (var j = 0; j < pickups.length; j++) {
if (pickups[j].gridX === x && pickups[j].gridY === y) return true;
}
return false;
}
function updateNextColorDisplay() {
nextColorTxt.setText('Next: ' + nextPickupColor.charAt(0).toUpperCase() + nextPickupColor.slice(1));
}
function moveSnake() {
if (gameOver || bombMode) return;
var newX = snakeHead.gridX + snakeHead.direction.x;
var newY = snakeHead.gridY + snakeHead.direction.y;
// Wrap around screen edges
if (newX < 0) newX = GRID_WIDTH - 1;
if (newX >= GRID_WIDTH) newX = 0;
if (newY < 0) newY = GRID_HEIGHT - 1;
if (newY >= GRID_HEIGHT) newY = 0;
// Check for self collision
for (var i = 0; i < snake.length; i++) {
if (snake[i].gridX === newX && snake[i].gridY === newY) {
gameOver = true;
LK.showGameOver();
return;
}
}
// Move tail segments
if (snake.length > 0) {
for (var j = snake.length - 1; j > 0; j--) {
snake[j].setPosition(snake[j - 1].gridX, snake[j - 1].gridY);
}
snake[0].setPosition(snakeHead.gridX, snakeHead.gridY);
}
// Move head
snakeHead.setPosition(newX, newY);
// Check for pickup collision
for (var k = pickups.length - 1; k >= 0; k--) {
var pickup = pickups[k];
if (pickup.gridX === newX && pickup.gridY === newY) {
if (pickup.isBomb) {
handleBombPickup();
// Remove bomb timer for this pickup
for (var t = bombTimers.length - 1; t >= 0; t--) {
if (bombTimers[t].pickup === pickup) {
bombTimers.splice(t, 1);
break;
}
}
} else {
handleColorPickup(pickup.color);
}
pickup.destroy();
pickups.splice(k, 1);
spawnRandomPickup();
break;
}
}
checkForMatches();
}
function handleColorPickup(color) {
LK.getSound('collect').play();
// Get the position of the last tail segment, or head if no tail exists
var lastX, lastY;
if (snake.length > 0) {
lastX = snake[snake.length - 1].gridX;
lastY = snake[snake.length - 1].gridY;
} else {
lastX = snakeHead.gridX;
lastY = snakeHead.gridY;
}
var newSegment = new SnakeSegment(color, lastX, lastY);
snake.push(newSegment);
game.addChild(newSegment);
LK.setScore(LK.getScore() + 1);
scoreTxt.setText('Score: ' + LK.getScore());
// Increment food counter and check for speed increase
foodCounter++;
bombSpawnCounter++; // Increment bomb spawn counter
if (foodCounter % 5 === 0) {
// Increase speed by reducing gameSpeed (making it 0.05x faster)
gameSpeed = Math.max(30, gameSpeed - gameSpeed * 0.05); // Prevent speed from getting too fast
}
}
function handleBombPickup() {
if (snake.length === 0) return;
LK.getSound('bomb').play();
bombMode = true;
bombIndex = 0;
bombTimer = 0;
// Highlight first segment
snake[bombIndex].highlight(true);
}
function processBombMovement() {
if (!bombMode) return;
bombTimer++;
if (bombTimer >= 30) {
// Move every 30 ticks (0.5 seconds)
snake[bombIndex].highlight(false);
bombIndex++;
if (bombIndex >= snake.length) {
bombIndex = 0;
}
snake[bombIndex].highlight(true);
bombTimer = 0;
}
}
function triggerBomb() {
if (!bombMode || snake.length === 0) return;
snake[bombIndex].highlight(false);
bombMode = false;
// Only remove the specific selected snake segment
var segmentToRemove = snake[bombIndex];
LK.getSound('explode').play();
// Create explosion effect on the selected snake segment
tween(segmentToRemove.graphics, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut
});
// Immediately remove the segment and close the gap
segmentToRemove.destroy();
snake.splice(bombIndex, 1);
// Immediately reposition all segments after the removed one to close the gap
if (bombIndex < snake.length && snake.length > 0) {
// Get the position of the segment before the removed one (or head if removing first segment)
var previousX, previousY;
if (bombIndex === 0) {
// If we removed the first segment, connect remaining segments to head
previousX = snakeHead.gridX;
previousY = snakeHead.gridY;
} else {
// Connect to the segment before the removed one
previousX = snake[bombIndex - 1].gridX;
previousY = snake[bombIndex - 1].gridY;
}
// Reposition all segments from the removed position onwards
for (var i = bombIndex; i < snake.length; i++) {
if (i === bombIndex) {
// First segment after gap connects to previous position
snake[i].setPosition(previousX, previousY);
} else {
// Subsequent segments connect to the previous segment
snake[i].setPosition(snake[i - 1].gridX, snake[i - 1].gridY);
}
}
}
// Award points
var points = 10;
LK.setScore(LK.getScore() + points);
scoreTxt.setText('Score: ' + LK.getScore());
// Flash effect
LK.effects.flashScreen(0xffffff, 300);
}
function checkForMatches() {
var matchFound = false;
var toRemove = [];
for (var i = 0; i < snake.length - 2; i++) {
var count = 1;
var currentColor = snake[i].color;
for (var j = i + 1; j < snake.length; j++) {
if (snake[j].color === currentColor) {
count++;
} else {
break;
}
}
if (count >= 3) {
for (var k = i; k < i + count; k++) {
toRemove.push(k);
}
matchFound = true;
i += count - 1;
}
}
if (toRemove.length > 0) {
LK.getSound('explode').play();
// Remove matched segments
for (var m = toRemove.length - 1; m >= 0; m--) {
var index = toRemove[m];
snake[index].destroy();
snake.splice(index, 1);
}
// Award points
var points = toRemove.length * 10;
LK.setScore(LK.getScore() + points);
scoreTxt.setText('Score: ' + LK.getScore());
// Flash effect
LK.effects.flashScreen(0xffffff, 200);
}
return matchFound;
}
// Swipe detection variables
var swipeStartX = 0;
var swipeStartY = 0;
var minSwipeDistance = 50;
var isSwipeStarted = false;
function handleSwipe(startX, startY, endX, endY) {
if (gameOver) return;
if (bombMode) {
return;
}
var deltaX = endX - startX;
var deltaY = endY - startY;
var swipeDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Check if swipe is long enough
if (swipeDistance < minSwipeDistance) return;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Horizontal swipe
if (deltaX > 0 && snakeHead.direction.x !== -1) {
// Swipe right
snakeHead.direction = {
x: 1,
y: 0
};
} else if (deltaX < 0 && snakeHead.direction.x !== 1) {
// Swipe left
snakeHead.direction = {
x: -1,
y: 0
};
}
} else {
// Vertical swipe
if (deltaY > 0 && snakeHead.direction.y !== -1) {
// Swipe down
snakeHead.direction = {
x: 0,
y: 1
};
} else if (deltaY < 0 && snakeHead.direction.y !== 1) {
// Swipe up
snakeHead.direction = {
x: 0,
y: -1
};
}
}
}
game.down = function (x, y, obj) {
// Single-click bomb trigger
if (bombMode) {
triggerBomb();
return;
}
swipeStartX = x;
swipeStartY = y;
isSwipeStarted = true;
};
game.up = function (x, y, obj) {
if (isSwipeStarted) {
handleSwipe(swipeStartX, swipeStartY, x, y);
isSwipeStarted = false;
}
};
game.update = function () {
if (gameOver) return;
processBombMovement();
// Process bomb timers
for (var i = bombTimers.length - 1; i >= 0; i--) {
var bombTimer = bombTimers[i];
bombTimer.timeLeft--;
// Visual effect - make bomb fade as time runs out
var fadeAlpha = Math.max(0.3, bombTimer.timeLeft / 300);
bombTimer.pickup.graphics.alpha = fadeAlpha;
// Scale effect to show urgency
var scaleEffect = 1 + (1 - bombTimer.timeLeft / 300) * 0.3;
bombTimer.pickup.graphics.scaleX = scaleEffect;
bombTimer.pickup.graphics.scaleY = scaleEffect;
if (bombTimer.timeLeft <= 0) {
// Remove bomb after 5 seconds
var pickupIndex = pickups.indexOf(bombTimer.pickup);
if (pickupIndex !== -1) {
// Fade out effect
tween(bombTimer.pickup.graphics, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
if (pickupIndex !== -1 && pickups[pickupIndex]) {
pickups[pickupIndex].destroy();
pickups.splice(pickupIndex, 1);
spawnRandomPickup();
}
}
});
}
bombTimers.splice(i, 1);
}
}
moveTimer++;
if (moveTimer >= gameSpeed / 16.67) {
moveSnake();
moveTimer = 0;
}
};
// Create playable area border
var playableBorder = new Container();
game.addChild(playableBorder);
// Create border lines for square centered area
var borderTop = LK.getAsset('snakeHead', {
anchorX: 0,
anchorY: 0,
scaleX: PLAYABLE_SIZE / 80,
scaleY: 0.05,
tint: 0xFFFFFF
});
borderTop.x = PLAYABLE_START_X;
borderTop.y = PLAYABLE_START_Y;
playableBorder.addChild(borderTop);
var borderBottom = LK.getAsset('snakeHead', {
anchorX: 0,
anchorY: 0,
scaleX: PLAYABLE_SIZE / 80,
scaleY: 0.05,
tint: 0xFFFFFF
});
borderBottom.x = PLAYABLE_START_X;
borderBottom.y = PLAYABLE_START_Y + PLAYABLE_HEIGHT;
playableBorder.addChild(borderBottom);
var borderLeft = LK.getAsset('snakeHead', {
anchorX: 0,
anchorY: 0,
scaleX: 0.05,
scaleY: PLAYABLE_HEIGHT / 80,
tint: 0xFFFFFF
});
borderLeft.x = PLAYABLE_START_X;
borderLeft.y = PLAYABLE_START_Y;
playableBorder.addChild(borderLeft);
var borderRight = LK.getAsset('snakeHead', {
anchorX: 0,
anchorY: 0,
scaleX: 0.05,
scaleY: PLAYABLE_HEIGHT / 80,
tint: 0xFFFFFF
});
borderRight.x = PLAYABLE_START_X + PLAYABLE_SIZE;
borderRight.y = PLAYABLE_START_Y;
playableBorder.addChild(borderRight);
// Initialize the game
initializeGame();
;