/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Food Class
var Food = Container.expand(function () {
var self = Container.call(this);
// Food is a yellow ellipse
var foodAsset = self.attachAsset('food', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Food2 Class (rare food)
var Food2 = Container.expand(function () {
var self = Container.call(this);
// Use food2 asset
var foodAsset = self.attachAsset('food2', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Food3 Class (health food)
var Food3 = Container.expand(function () {
var self = Container.call(this);
// Use food3 asset
var foodAsset = self.attachAsset('food3', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Food4 Class (special food every 4th)
var Food4 = Container.expand(function () {
var self = Container.call(this);
// Use food4 asset
var foodAsset = self.attachAsset('food4', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Snake Head Class
var SnakeHead = Container.expand(function () {
var self = Container.call(this);
// Head is a unique asset
var headAsset = self.attachAsset('snakeHead', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Snake Segment Class
var SnakeSegment = Container.expand(function () {
var self = Container.call(this);
// Each segment is a green box
var segmentAsset = self.attachAsset('snakeSegment', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Wall Class
var Wall = Container.expand(function () {
var self = Container.call(this);
// Wall is a red box
var wallAsset = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181818
});
/****
* Game Code
****/
// --- Game Constants ---
// --- Asset Initialization ---
// Snake Head Class
var GRID_SIZE = 180; // Each cell is 180x180 px (increased)
var GRID_COLS = Math.floor(2048 / GRID_SIZE); // 11
var GRID_ROWS = Math.floor(2732 / GRID_SIZE); // 15
var BOARD_WIDTH = GRID_COLS * GRID_SIZE;
var BOARD_HEIGHT = GRID_ROWS * GRID_SIZE;
var BOARD_OFFSET_X = Math.floor((2048 - BOARD_WIDTH) / 2);
var BOARD_OFFSET_Y = Math.floor((2732 - BOARD_HEIGHT) / 2);
var SNAKE_START_LENGTH = 2;
var SNAKE_MAX_LENGTH = (GRID_COLS - 2) * (GRID_ROWS - 2); // Win when snake fills entire playable area
// --- Game State ---
var snake = [];
var snakeDir = {
x: 1,
y: 0
}; // Start moving right
var nextDir = {
x: 1,
y: 0
};
var moveTimer = 0;
var MOVE_INTERVAL = 210; // ms (biraz yavaşlatıldı)
var foods = [];
var walls = [];
var isDead = false;
var isWin = false;
var pendingGrowth = 0;
var lastTouch = null;
var darknessOverlay = null;
var isDarkened = false;
var isSlowed = false;
var originalMoveInterval = MOVE_INTERVAL;
var darknessTimer = null;
var speedTimer = null;
var isMultiplierActive = false;
var multiplierTimer = null;
var scoreMultiplier = 1;
var isYellowBackgroundActive = false;
var yellowBackgroundTimer = null;
var originalBackgroundColor = 0x181818;
var playerLives = 1;
var lastTapTime = 0;
var isDoubleTouch = false;
var speedMultiplier = 1;
var touchStartTime = 0;
var DOUBLE_TAP_THRESHOLD = 300; // ms
// --- Score UI ---
var scoreTxt = new Text2('1', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// --- Health UI ---
var healthTxt = new Text2('♥ 1', {
size: 80,
fill: 0xFF0000
});
healthTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(healthTxt);
// --- Helper Functions ---
function gridToPos(col, row) {
return {
x: BOARD_OFFSET_X + col * GRID_SIZE + GRID_SIZE / 2,
y: BOARD_OFFSET_Y + row * GRID_SIZE + GRID_SIZE / 2
};
}
function posToGrid(x, y) {
var col = Math.floor((x - BOARD_OFFSET_X) / GRID_SIZE);
var row = Math.floor((y - BOARD_OFFSET_Y) / GRID_SIZE);
return {
col: col,
row: row
};
}
function randomFreeCell() {
// Returns a random cell not occupied by snake or wall
var free = [];
for (var r = 1; r < GRID_ROWS - 1; r++) {
for (var c = 1; c < GRID_COLS - 1; c++) {
var occupied = false;
for (var i = 0; i < snake.length; i++) {
if (snake[i].col === c && snake[i].row === r) {
occupied = true;
break;
}
}
for (var f = 0; f < foods.length; f++) {
if (foods[f].col === c && foods[f].row === r) {
occupied = true;
break;
}
}
if (!occupied) free.push({
col: c,
row: r
});
}
}
if (free.length === 0) return null;
return free[Math.floor(Math.random() * free.length)];
}
function setScore(val) {
LK.setScore(val);
scoreTxt.setText(val);
}
// --- Wall Setup ---
function createWalls() {
// Top and bottom
for (var c = 0; c < GRID_COLS; c++) {
var wallTop = new Wall();
var wallBottom = new Wall();
var posTop = gridToPos(c, 0);
var posBottom = gridToPos(c, GRID_ROWS - 1);
wallTop.x = posTop.x;
wallTop.y = posTop.y;
wallTop.col = c;
wallTop.row = 0;
wallBottom.x = posBottom.x;
wallBottom.y = posBottom.y;
wallBottom.col = c;
wallBottom.row = GRID_ROWS - 1;
game.addChild(wallTop);
game.addChild(wallBottom);
walls.push(wallTop, wallBottom);
}
// Left and right
for (var r = 1; r < GRID_ROWS - 1; r++) {
var wallLeft = new Wall();
var wallRight = new Wall();
var posLeft = gridToPos(0, r);
var posRight = gridToPos(GRID_COLS - 1, r);
wallLeft.x = posLeft.x;
wallLeft.y = posLeft.y;
wallLeft.col = 0;
wallLeft.row = r;
wallRight.x = posRight.x;
wallRight.y = posRight.y;
wallRight.col = GRID_COLS - 1;
wallRight.row = r;
game.addChild(wallLeft);
game.addChild(wallRight);
walls.push(wallLeft, wallRight);
}
}
// --- Snake Setup ---
function createSnake() {
var startCol = Math.floor(GRID_COLS / 2);
var startRow = Math.floor(GRID_ROWS / 2);
// First segment is the head
var head = new SnakeHead();
head.col = startCol;
head.row = startRow;
var pos = gridToPos(head.col, head.row);
head.x = pos.x;
head.y = pos.y;
game.addChild(head);
snake.push(head);
// The rest are segments
for (var i = 1; i < SNAKE_START_LENGTH; i++) {
var seg = new SnakeSegment();
seg.col = startCol - i;
seg.row = startRow;
var pos2 = gridToPos(seg.col, seg.row);
seg.x = pos2.x;
seg.y = pos2.y;
game.addChild(seg);
snake.push(seg);
}
}
// --- Food Setup ---
// Track how many foods have been spawned
var foodSpawnCount = 0;
var food4Timers = []; // Track timers for Food4 items
function spawnFood() {
// Determine target food count based on score
var targetFoodCount = LK.getScore() > 10 ? 2 : 1;
// If we already have the target number of foods or more, don't spawn any new ones
if (foods.length >= targetFoodCount) {
return;
}
// Calculate how many foods we need to spawn to reach target
var foodsToSpawn = targetFoodCount - foods.length;
// Spawn the required number of foods
for (var f = 0; f < foodsToSpawn; f++) {
var cell = randomFreeCell();
if (!cell) continue;
foodSpawnCount = typeof foodSpawnCount === "number" ? foodSpawnCount + 1 : 1;
var newFood;
var randomChance = Math.random();
if (foodSpawnCount % 4 === 0) {
newFood = new Food4();
// Start timer to convert Food4 to Food after 10 seconds
var food4Timer = LK.setTimeout(function () {
// Check if this Food4 still exists and hasn't been eaten
var foodIndex = foods.indexOf(newFood);
if (foodIndex !== -1 && newFood instanceof Food4) {
// Convert Food4 to regular Food
var oldCol = newFood.col;
var oldRow = newFood.row;
var oldPos = gridToPos(oldCol, oldRow);
// Remove old Food4
newFood.destroy();
foods.splice(foodIndex, 1);
// Create new Food in same position
var regularFood = new Food();
regularFood.col = oldCol;
regularFood.row = oldRow;
regularFood.x = oldPos.x;
regularFood.y = oldPos.y;
game.addChild(regularFood);
foods.push(regularFood);
}
// Remove timer from tracking array
var timerIndex = food4Timers.indexOf(food4Timer);
if (timerIndex !== -1) {
food4Timers.splice(timerIndex, 1);
}
}, 10000);
// Track the timer
food4Timers.push(food4Timer);
} else if (randomChance < 0.10) {
// 10% chance for Food3
newFood = new Food3();
} else if (randomChance < 0.20) {
// 20% chance for Food2 (but only if not Food3)
newFood = new Food2();
} else {
newFood = new Food();
}
newFood.col = cell.col;
newFood.row = cell.row;
var pos = gridToPos(cell.col, cell.row);
newFood.x = pos.x;
newFood.y = pos.y;
game.addChild(newFood);
foods.push(newFood);
}
}
// --- Collision Checks ---
function isWall(col, row) {
return col === 0 || row === 0 || col === GRID_COLS - 1 || row === GRID_ROWS - 1;
}
function isSnake(col, row, skipHead) {
for (var i = skipHead ? 1 : 0; i < snake.length; i++) {
if (snake[i].col === col && snake[i].row === row) return true;
}
return false;
}
// --- Game Over / Win ---
function triggerGameOver() {
if (isDead) return;
playerLives--;
healthTxt.setText('♥ ' + playerLives);
if (playerLives <= 0) {
isDead = true;
LK.effects.flashScreen(0xff0000, 1000);
// Create a container for the game over display
var goCont = new Container();
// Create large snake head asset (350x350)
var snakeHeadAsset = LK.getAsset('snakeHead', {
anchorX: 0.5,
anchorY: 0.5,
width: 350,
height: 350
});
snakeHeadAsset.x = 350 / 2;
snakeHeadAsset.y = 350 / 2;
// Create large food asset (350x350)
var foodAsset = LK.getAsset('food', {
anchorX: 0.5,
anchorY: 0.5,
width: 350,
height: 350
});
foodAsset.x = 350 + 350 / 2 + 40; // 40px gap between
foodAsset.y = 350 / 2;
// Set container size and center on screen
var totalWidth = 350 * 2 + 40;
var totalHeight = 350;
goCont.x = 0;
goCont.y = 0;
goCont.width = totalWidth;
goCont.height = totalHeight;
goCont.addChild(snakeHeadAsset);
goCont.addChild(foodAsset);
// Show the container as a popup overlay (LK will remove it on reset)
LK.showGameOver({
customContent: goCont,
customContentWidth: totalWidth,
customContentHeight: totalHeight,
customContentY: 120 // Place below the menu (menu is top 100px, so 120px for margin)
});
} else {
// Player still has lives, flash red and continue
LK.effects.flashScreen(0xff0000, 500);
// Reset snake tail length to 1 (only keep the head)
for (var i = snake.length - 1; i > 0; i--) {
snake[i].destroy();
snake.splice(i, 1);
}
// Move snake head to a safe position automatically
var head = snake[0];
var safeCell = randomFreeCell();
if (safeCell) {
// Update head position data
head.col = safeCell.col;
head.row = safeCell.row;
var safePos = gridToPos(safeCell.col, safeCell.row);
// Smoothly animate to safe position
tween(head, {
x: safePos.x,
y: safePos.y
}, {
duration: 300,
easing: tween.easeOut
});
}
}
}
function triggerWin() {
if (isWin) return;
isWin = true;
LK.effects.flashScreen(0x00ff00, 1000);
LK.showYouWin();
}
// --- Input Handling (Touch/Drag) ---
var dragStart = null;
var dragDir = null;
function getDirectionFromDrag(dx, dy) {
if (Math.abs(dx) > Math.abs(dy)) {
return dx > 0 ? {
x: 1,
y: 0
} : {
x: -1,
y: 0
};
} else {
return dy > 0 ? {
x: 0,
y: 1
} : {
x: 0,
y: -1
};
}
}
game.down = function (x, y, obj) {
dragStart = {
x: x,
y: y
};
dragDir = null;
// Double-tap detection
var currentTime = Date.now();
if (currentTime - lastTapTime < DOUBLE_TAP_THRESHOLD) {
// Double tap detected
isDoubleTouch = true;
speedMultiplier = 2;
touchStartTime = currentTime;
} else {
// Single tap
isDoubleTouch = false;
speedMultiplier = 1;
}
lastTapTime = currentTime;
};
game.move = function (x, y, obj) {
if (!dragStart) return;
var dx = x - dragStart.x;
var dy = y - dragStart.y;
if (Math.abs(dx) > 40 || Math.abs(dy) > 40) {
var dir = getDirectionFromDrag(dx, dy);
// Prevent reversing
if ((dir.x !== -snakeDir.x || dir.y !== -snakeDir.y) && (dir.x !== snakeDir.x || dir.y !== snakeDir.y)) {
nextDir = dir;
dragDir = dir;
dragStart = null;
}
}
};
game.up = function (x, y, obj) {
dragStart = null;
dragDir = null;
// Reset speed when touch is released
isDoubleTouch = false;
speedMultiplier = 1;
};
// --- Main Game Loop ---
game.update = function () {
if (isDead || isWin) return;
moveTimer += 1000 / 60;
var currentMoveInterval = MOVE_INTERVAL / speedMultiplier;
if (moveTimer < currentMoveInterval) return;
moveTimer = 0;
// Update direction
if (nextDir.x !== -snakeDir.x || nextDir.y !== -snakeDir.y) {
snakeDir = {
x: nextDir.x,
y: nextDir.y
};
}
// Calculate new head position
var head = snake[0];
var newCol = head.col + snakeDir.x;
var newRow = head.row + snakeDir.y;
// Check collisions
if (isWall(newCol, newRow) || isSnake(newCol, newRow, false)) {
triggerGameOver();
return;
}
// Move snake
// Remove old head and replace with segment
var oldHead = snake[0];
var newSegment = new SnakeSegment();
newSegment.col = oldHead.col;
newSegment.row = oldHead.row;
newSegment.x = oldHead.x;
newSegment.y = oldHead.y;
game.addChild(newSegment);
snake[0].destroy();
snake[0] = newSegment;
// Add new head at the front
var newHead = new SnakeHead();
newHead.col = newCol;
newHead.row = newRow;
var pos = gridToPos(newCol, newRow);
newHead.x = pos.x;
newHead.y = pos.y;
// Set rotation based on direction (asset is upright, so rotate by +90deg for right)
if (snakeDir.x === 1 && snakeDir.y === 0) {
newHead.rotation = Math.PI / 2; // Right
} else if (snakeDir.x === -1 && snakeDir.y === 0) {
newHead.rotation = -Math.PI / 2; // Left
} else if (snakeDir.x === 0 && snakeDir.y === -1) {
newHead.rotation = 0; // Up
} else if (snakeDir.x === 0 && snakeDir.y === 1) {
newHead.rotation = Math.PI; // Down
}
game.addChild(newHead);
snake.unshift(newHead);
// Check food collision
var ateFood = false;
var eatenFood = null;
for (var f = 0; f < foods.length; f++) {
if (foods[f].col === newCol && foods[f].row === newRow) {
ateFood = true;
eatenFood = foods[f];
break;
}
}
if (ateFood) {
// Remove the eaten food from the foods array and destroy it
for (var i = 0; i < foods.length; i++) {
if (foods[i] === eatenFood) {
foods[i].destroy();
foods.splice(i, 1);
break;
}
}
// Calculate score based on food type and multiplier
var pointsToAdd;
if (eatenFood instanceof Food4) {
pointsToAdd = 1 * scoreMultiplier; // Food4 gives 1 point
} else {
pointsToAdd = 2 * scoreMultiplier; // Food and Food2 give 2 points
}
setScore(LK.getScore() + pointsToAdd);
pendingGrowth += 1;
// Play feeding sound
LK.getSound('feeding').play();
// Handle Food3 special effect - give health (max 3 lives) or bonus points if at max health
if (eatenFood instanceof Food3) {
if (playerLives < 3) {
playerLives++;
healthTxt.setText('♥ ' + playerLives);
} else {
// Player is at max health (3 lives), give 50 bonus points
setScore(LK.getScore() + 50);
}
}
// Handle Food2 special effect - 10x multiplier for 5 seconds
if (eatenFood instanceof Food2) {
if (!isMultiplierActive) {
isMultiplierActive = true;
scoreMultiplier = 10;
// Clear any existing multiplier timer
if (multiplierTimer) {
LK.clearTimeout(multiplierTimer);
}
// Remove multiplier after exactly 5 seconds
multiplierTimer = LK.setTimeout(function () {
scoreMultiplier = 1;
isMultiplierActive = false;
multiplierTimer = null;
}, 5000);
}
// Yellow background effect for 5 seconds
if (!isYellowBackgroundActive) {
isYellowBackgroundActive = true;
game.setBackgroundColor(0xFFFF00); // Yellow color
// Clear any existing yellow background timer
if (yellowBackgroundTimer) {
LK.clearTimeout(yellowBackgroundTimer);
}
// Restore original background after exactly 5 seconds
yellowBackgroundTimer = LK.setTimeout(function () {
game.setBackgroundColor(originalBackgroundColor);
isYellowBackgroundActive = false;
yellowBackgroundTimer = null;
}, 5000);
}
}
// Play feeding2 sound only when Food4 is eaten
if (eatenFood instanceof Food4) {
// Clear any associated timer since Food4 was eaten
for (var t = food4Timers.length - 1; t >= 0; t--) {
LK.clearTimeout(food4Timers[t]);
food4Timers.splice(t, 1);
}
LK.getSound('feeding2').play();
// Create darkness effect for 10 seconds
if (!isDarkened) {
isDarkened = true;
darknessOverlay = LK.getAsset('snakeSegment', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732,
tint: 0x000000
});
darknessOverlay.alpha = 0;
game.addChild(darknessOverlay);
// Fade in darkness
tween(darknessOverlay, {
alpha: 0.7
}, {
duration: 500
});
// Clear any existing darkness timer
if (darknessTimer) {
LK.clearTimeout(darknessTimer);
}
// Remove darkness after exactly 5 seconds
darknessTimer = LK.setTimeout(function () {
if (darknessOverlay) {
tween(darknessOverlay, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
if (darknessOverlay) {
darknessOverlay.destroy();
darknessOverlay = null;
}
isDarkened = false;
darknessTimer = null;
}
});
}
}, 5000);
}
// Reduce snake speed by 50% for 10 seconds
if (!isSlowed) {
isSlowed = true;
MOVE_INTERVAL = originalMoveInterval * 1.5; // 50% slower
// Clear any existing speed timer
if (speedTimer) {
LK.clearTimeout(speedTimer);
}
// Restore original speed after exactly 5 seconds
speedTimer = LK.setTimeout(function () {
MOVE_INTERVAL = originalMoveInterval;
isSlowed = false;
speedTimer = null;
}, 5000);
}
}
spawnFood();
// Win condition
if (snake.length >= SNAKE_MAX_LENGTH) {
triggerWin();
return;
}
}
// Remove tail if not growing
if (pendingGrowth > 0) {
pendingGrowth--;
} else {
var tail = snake.pop();
tail.destroy();
}
};
// --- Draw Board Grid Background ---
var gridBg = new Container();
for (var r = 0; r < GRID_ROWS; r++) {
for (var c = 0; c < GRID_COLS; c++) {
// Use a light color for grid lines, and a slightly darker fill for the cell
var cell = LK.getAsset('snakeSegment', {
anchorX: 0.5,
anchorY: 0.5,
x: BOARD_OFFSET_X + c * GRID_SIZE + GRID_SIZE / 2,
y: BOARD_OFFSET_Y + r * GRID_SIZE + GRID_SIZE / 2,
width: GRID_SIZE,
height: GRID_SIZE,
tint: (r + c) % 2 === 0 ? 0x232323 : 0x1a1a1a // subtle checkerboard for clarity
});
// Add a border by overlaying a slightly smaller cell of the same color as background
var border = LK.getAsset('snakeSegment', {
anchorX: 0.5,
anchorY: 0.5,
x: BOARD_OFFSET_X + c * GRID_SIZE + GRID_SIZE / 2,
y: BOARD_OFFSET_Y + r * GRID_SIZE + GRID_SIZE / 2,
width: GRID_SIZE - 8,
height: GRID_SIZE - 8,
tint: (r + c) % 2 === 0 ? 0x181818 : 0x181818 // match background
});
gridBg.addChild(cell);
gridBg.addChild(border);
}
}
game.addChild(gridBg);
// --- Game Initialization ---
createWalls();
createSnake();
spawnFood();
setScore(1);
; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Food Class
var Food = Container.expand(function () {
var self = Container.call(this);
// Food is a yellow ellipse
var foodAsset = self.attachAsset('food', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Food2 Class (rare food)
var Food2 = Container.expand(function () {
var self = Container.call(this);
// Use food2 asset
var foodAsset = self.attachAsset('food2', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Food3 Class (health food)
var Food3 = Container.expand(function () {
var self = Container.call(this);
// Use food3 asset
var foodAsset = self.attachAsset('food3', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Food4 Class (special food every 4th)
var Food4 = Container.expand(function () {
var self = Container.call(this);
// Use food4 asset
var foodAsset = self.attachAsset('food4', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Snake Head Class
var SnakeHead = Container.expand(function () {
var self = Container.call(this);
// Head is a unique asset
var headAsset = self.attachAsset('snakeHead', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Snake Segment Class
var SnakeSegment = Container.expand(function () {
var self = Container.call(this);
// Each segment is a green box
var segmentAsset = self.attachAsset('snakeSegment', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Wall Class
var Wall = Container.expand(function () {
var self = Container.call(this);
// Wall is a red box
var wallAsset = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181818
});
/****
* Game Code
****/
// --- Game Constants ---
// --- Asset Initialization ---
// Snake Head Class
var GRID_SIZE = 180; // Each cell is 180x180 px (increased)
var GRID_COLS = Math.floor(2048 / GRID_SIZE); // 11
var GRID_ROWS = Math.floor(2732 / GRID_SIZE); // 15
var BOARD_WIDTH = GRID_COLS * GRID_SIZE;
var BOARD_HEIGHT = GRID_ROWS * GRID_SIZE;
var BOARD_OFFSET_X = Math.floor((2048 - BOARD_WIDTH) / 2);
var BOARD_OFFSET_Y = Math.floor((2732 - BOARD_HEIGHT) / 2);
var SNAKE_START_LENGTH = 2;
var SNAKE_MAX_LENGTH = (GRID_COLS - 2) * (GRID_ROWS - 2); // Win when snake fills entire playable area
// --- Game State ---
var snake = [];
var snakeDir = {
x: 1,
y: 0
}; // Start moving right
var nextDir = {
x: 1,
y: 0
};
var moveTimer = 0;
var MOVE_INTERVAL = 210; // ms (biraz yavaşlatıldı)
var foods = [];
var walls = [];
var isDead = false;
var isWin = false;
var pendingGrowth = 0;
var lastTouch = null;
var darknessOverlay = null;
var isDarkened = false;
var isSlowed = false;
var originalMoveInterval = MOVE_INTERVAL;
var darknessTimer = null;
var speedTimer = null;
var isMultiplierActive = false;
var multiplierTimer = null;
var scoreMultiplier = 1;
var isYellowBackgroundActive = false;
var yellowBackgroundTimer = null;
var originalBackgroundColor = 0x181818;
var playerLives = 1;
var lastTapTime = 0;
var isDoubleTouch = false;
var speedMultiplier = 1;
var touchStartTime = 0;
var DOUBLE_TAP_THRESHOLD = 300; // ms
// --- Score UI ---
var scoreTxt = new Text2('1', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// --- Health UI ---
var healthTxt = new Text2('♥ 1', {
size: 80,
fill: 0xFF0000
});
healthTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(healthTxt);
// --- Helper Functions ---
function gridToPos(col, row) {
return {
x: BOARD_OFFSET_X + col * GRID_SIZE + GRID_SIZE / 2,
y: BOARD_OFFSET_Y + row * GRID_SIZE + GRID_SIZE / 2
};
}
function posToGrid(x, y) {
var col = Math.floor((x - BOARD_OFFSET_X) / GRID_SIZE);
var row = Math.floor((y - BOARD_OFFSET_Y) / GRID_SIZE);
return {
col: col,
row: row
};
}
function randomFreeCell() {
// Returns a random cell not occupied by snake or wall
var free = [];
for (var r = 1; r < GRID_ROWS - 1; r++) {
for (var c = 1; c < GRID_COLS - 1; c++) {
var occupied = false;
for (var i = 0; i < snake.length; i++) {
if (snake[i].col === c && snake[i].row === r) {
occupied = true;
break;
}
}
for (var f = 0; f < foods.length; f++) {
if (foods[f].col === c && foods[f].row === r) {
occupied = true;
break;
}
}
if (!occupied) free.push({
col: c,
row: r
});
}
}
if (free.length === 0) return null;
return free[Math.floor(Math.random() * free.length)];
}
function setScore(val) {
LK.setScore(val);
scoreTxt.setText(val);
}
// --- Wall Setup ---
function createWalls() {
// Top and bottom
for (var c = 0; c < GRID_COLS; c++) {
var wallTop = new Wall();
var wallBottom = new Wall();
var posTop = gridToPos(c, 0);
var posBottom = gridToPos(c, GRID_ROWS - 1);
wallTop.x = posTop.x;
wallTop.y = posTop.y;
wallTop.col = c;
wallTop.row = 0;
wallBottom.x = posBottom.x;
wallBottom.y = posBottom.y;
wallBottom.col = c;
wallBottom.row = GRID_ROWS - 1;
game.addChild(wallTop);
game.addChild(wallBottom);
walls.push(wallTop, wallBottom);
}
// Left and right
for (var r = 1; r < GRID_ROWS - 1; r++) {
var wallLeft = new Wall();
var wallRight = new Wall();
var posLeft = gridToPos(0, r);
var posRight = gridToPos(GRID_COLS - 1, r);
wallLeft.x = posLeft.x;
wallLeft.y = posLeft.y;
wallLeft.col = 0;
wallLeft.row = r;
wallRight.x = posRight.x;
wallRight.y = posRight.y;
wallRight.col = GRID_COLS - 1;
wallRight.row = r;
game.addChild(wallLeft);
game.addChild(wallRight);
walls.push(wallLeft, wallRight);
}
}
// --- Snake Setup ---
function createSnake() {
var startCol = Math.floor(GRID_COLS / 2);
var startRow = Math.floor(GRID_ROWS / 2);
// First segment is the head
var head = new SnakeHead();
head.col = startCol;
head.row = startRow;
var pos = gridToPos(head.col, head.row);
head.x = pos.x;
head.y = pos.y;
game.addChild(head);
snake.push(head);
// The rest are segments
for (var i = 1; i < SNAKE_START_LENGTH; i++) {
var seg = new SnakeSegment();
seg.col = startCol - i;
seg.row = startRow;
var pos2 = gridToPos(seg.col, seg.row);
seg.x = pos2.x;
seg.y = pos2.y;
game.addChild(seg);
snake.push(seg);
}
}
// --- Food Setup ---
// Track how many foods have been spawned
var foodSpawnCount = 0;
var food4Timers = []; // Track timers for Food4 items
function spawnFood() {
// Determine target food count based on score
var targetFoodCount = LK.getScore() > 10 ? 2 : 1;
// If we already have the target number of foods or more, don't spawn any new ones
if (foods.length >= targetFoodCount) {
return;
}
// Calculate how many foods we need to spawn to reach target
var foodsToSpawn = targetFoodCount - foods.length;
// Spawn the required number of foods
for (var f = 0; f < foodsToSpawn; f++) {
var cell = randomFreeCell();
if (!cell) continue;
foodSpawnCount = typeof foodSpawnCount === "number" ? foodSpawnCount + 1 : 1;
var newFood;
var randomChance = Math.random();
if (foodSpawnCount % 4 === 0) {
newFood = new Food4();
// Start timer to convert Food4 to Food after 10 seconds
var food4Timer = LK.setTimeout(function () {
// Check if this Food4 still exists and hasn't been eaten
var foodIndex = foods.indexOf(newFood);
if (foodIndex !== -1 && newFood instanceof Food4) {
// Convert Food4 to regular Food
var oldCol = newFood.col;
var oldRow = newFood.row;
var oldPos = gridToPos(oldCol, oldRow);
// Remove old Food4
newFood.destroy();
foods.splice(foodIndex, 1);
// Create new Food in same position
var regularFood = new Food();
regularFood.col = oldCol;
regularFood.row = oldRow;
regularFood.x = oldPos.x;
regularFood.y = oldPos.y;
game.addChild(regularFood);
foods.push(regularFood);
}
// Remove timer from tracking array
var timerIndex = food4Timers.indexOf(food4Timer);
if (timerIndex !== -1) {
food4Timers.splice(timerIndex, 1);
}
}, 10000);
// Track the timer
food4Timers.push(food4Timer);
} else if (randomChance < 0.10) {
// 10% chance for Food3
newFood = new Food3();
} else if (randomChance < 0.20) {
// 20% chance for Food2 (but only if not Food3)
newFood = new Food2();
} else {
newFood = new Food();
}
newFood.col = cell.col;
newFood.row = cell.row;
var pos = gridToPos(cell.col, cell.row);
newFood.x = pos.x;
newFood.y = pos.y;
game.addChild(newFood);
foods.push(newFood);
}
}
// --- Collision Checks ---
function isWall(col, row) {
return col === 0 || row === 0 || col === GRID_COLS - 1 || row === GRID_ROWS - 1;
}
function isSnake(col, row, skipHead) {
for (var i = skipHead ? 1 : 0; i < snake.length; i++) {
if (snake[i].col === col && snake[i].row === row) return true;
}
return false;
}
// --- Game Over / Win ---
function triggerGameOver() {
if (isDead) return;
playerLives--;
healthTxt.setText('♥ ' + playerLives);
if (playerLives <= 0) {
isDead = true;
LK.effects.flashScreen(0xff0000, 1000);
// Create a container for the game over display
var goCont = new Container();
// Create large snake head asset (350x350)
var snakeHeadAsset = LK.getAsset('snakeHead', {
anchorX: 0.5,
anchorY: 0.5,
width: 350,
height: 350
});
snakeHeadAsset.x = 350 / 2;
snakeHeadAsset.y = 350 / 2;
// Create large food asset (350x350)
var foodAsset = LK.getAsset('food', {
anchorX: 0.5,
anchorY: 0.5,
width: 350,
height: 350
});
foodAsset.x = 350 + 350 / 2 + 40; // 40px gap between
foodAsset.y = 350 / 2;
// Set container size and center on screen
var totalWidth = 350 * 2 + 40;
var totalHeight = 350;
goCont.x = 0;
goCont.y = 0;
goCont.width = totalWidth;
goCont.height = totalHeight;
goCont.addChild(snakeHeadAsset);
goCont.addChild(foodAsset);
// Show the container as a popup overlay (LK will remove it on reset)
LK.showGameOver({
customContent: goCont,
customContentWidth: totalWidth,
customContentHeight: totalHeight,
customContentY: 120 // Place below the menu (menu is top 100px, so 120px for margin)
});
} else {
// Player still has lives, flash red and continue
LK.effects.flashScreen(0xff0000, 500);
// Reset snake tail length to 1 (only keep the head)
for (var i = snake.length - 1; i > 0; i--) {
snake[i].destroy();
snake.splice(i, 1);
}
// Move snake head to a safe position automatically
var head = snake[0];
var safeCell = randomFreeCell();
if (safeCell) {
// Update head position data
head.col = safeCell.col;
head.row = safeCell.row;
var safePos = gridToPos(safeCell.col, safeCell.row);
// Smoothly animate to safe position
tween(head, {
x: safePos.x,
y: safePos.y
}, {
duration: 300,
easing: tween.easeOut
});
}
}
}
function triggerWin() {
if (isWin) return;
isWin = true;
LK.effects.flashScreen(0x00ff00, 1000);
LK.showYouWin();
}
// --- Input Handling (Touch/Drag) ---
var dragStart = null;
var dragDir = null;
function getDirectionFromDrag(dx, dy) {
if (Math.abs(dx) > Math.abs(dy)) {
return dx > 0 ? {
x: 1,
y: 0
} : {
x: -1,
y: 0
};
} else {
return dy > 0 ? {
x: 0,
y: 1
} : {
x: 0,
y: -1
};
}
}
game.down = function (x, y, obj) {
dragStart = {
x: x,
y: y
};
dragDir = null;
// Double-tap detection
var currentTime = Date.now();
if (currentTime - lastTapTime < DOUBLE_TAP_THRESHOLD) {
// Double tap detected
isDoubleTouch = true;
speedMultiplier = 2;
touchStartTime = currentTime;
} else {
// Single tap
isDoubleTouch = false;
speedMultiplier = 1;
}
lastTapTime = currentTime;
};
game.move = function (x, y, obj) {
if (!dragStart) return;
var dx = x - dragStart.x;
var dy = y - dragStart.y;
if (Math.abs(dx) > 40 || Math.abs(dy) > 40) {
var dir = getDirectionFromDrag(dx, dy);
// Prevent reversing
if ((dir.x !== -snakeDir.x || dir.y !== -snakeDir.y) && (dir.x !== snakeDir.x || dir.y !== snakeDir.y)) {
nextDir = dir;
dragDir = dir;
dragStart = null;
}
}
};
game.up = function (x, y, obj) {
dragStart = null;
dragDir = null;
// Reset speed when touch is released
isDoubleTouch = false;
speedMultiplier = 1;
};
// --- Main Game Loop ---
game.update = function () {
if (isDead || isWin) return;
moveTimer += 1000 / 60;
var currentMoveInterval = MOVE_INTERVAL / speedMultiplier;
if (moveTimer < currentMoveInterval) return;
moveTimer = 0;
// Update direction
if (nextDir.x !== -snakeDir.x || nextDir.y !== -snakeDir.y) {
snakeDir = {
x: nextDir.x,
y: nextDir.y
};
}
// Calculate new head position
var head = snake[0];
var newCol = head.col + snakeDir.x;
var newRow = head.row + snakeDir.y;
// Check collisions
if (isWall(newCol, newRow) || isSnake(newCol, newRow, false)) {
triggerGameOver();
return;
}
// Move snake
// Remove old head and replace with segment
var oldHead = snake[0];
var newSegment = new SnakeSegment();
newSegment.col = oldHead.col;
newSegment.row = oldHead.row;
newSegment.x = oldHead.x;
newSegment.y = oldHead.y;
game.addChild(newSegment);
snake[0].destroy();
snake[0] = newSegment;
// Add new head at the front
var newHead = new SnakeHead();
newHead.col = newCol;
newHead.row = newRow;
var pos = gridToPos(newCol, newRow);
newHead.x = pos.x;
newHead.y = pos.y;
// Set rotation based on direction (asset is upright, so rotate by +90deg for right)
if (snakeDir.x === 1 && snakeDir.y === 0) {
newHead.rotation = Math.PI / 2; // Right
} else if (snakeDir.x === -1 && snakeDir.y === 0) {
newHead.rotation = -Math.PI / 2; // Left
} else if (snakeDir.x === 0 && snakeDir.y === -1) {
newHead.rotation = 0; // Up
} else if (snakeDir.x === 0 && snakeDir.y === 1) {
newHead.rotation = Math.PI; // Down
}
game.addChild(newHead);
snake.unshift(newHead);
// Check food collision
var ateFood = false;
var eatenFood = null;
for (var f = 0; f < foods.length; f++) {
if (foods[f].col === newCol && foods[f].row === newRow) {
ateFood = true;
eatenFood = foods[f];
break;
}
}
if (ateFood) {
// Remove the eaten food from the foods array and destroy it
for (var i = 0; i < foods.length; i++) {
if (foods[i] === eatenFood) {
foods[i].destroy();
foods.splice(i, 1);
break;
}
}
// Calculate score based on food type and multiplier
var pointsToAdd;
if (eatenFood instanceof Food4) {
pointsToAdd = 1 * scoreMultiplier; // Food4 gives 1 point
} else {
pointsToAdd = 2 * scoreMultiplier; // Food and Food2 give 2 points
}
setScore(LK.getScore() + pointsToAdd);
pendingGrowth += 1;
// Play feeding sound
LK.getSound('feeding').play();
// Handle Food3 special effect - give health (max 3 lives) or bonus points if at max health
if (eatenFood instanceof Food3) {
if (playerLives < 3) {
playerLives++;
healthTxt.setText('♥ ' + playerLives);
} else {
// Player is at max health (3 lives), give 50 bonus points
setScore(LK.getScore() + 50);
}
}
// Handle Food2 special effect - 10x multiplier for 5 seconds
if (eatenFood instanceof Food2) {
if (!isMultiplierActive) {
isMultiplierActive = true;
scoreMultiplier = 10;
// Clear any existing multiplier timer
if (multiplierTimer) {
LK.clearTimeout(multiplierTimer);
}
// Remove multiplier after exactly 5 seconds
multiplierTimer = LK.setTimeout(function () {
scoreMultiplier = 1;
isMultiplierActive = false;
multiplierTimer = null;
}, 5000);
}
// Yellow background effect for 5 seconds
if (!isYellowBackgroundActive) {
isYellowBackgroundActive = true;
game.setBackgroundColor(0xFFFF00); // Yellow color
// Clear any existing yellow background timer
if (yellowBackgroundTimer) {
LK.clearTimeout(yellowBackgroundTimer);
}
// Restore original background after exactly 5 seconds
yellowBackgroundTimer = LK.setTimeout(function () {
game.setBackgroundColor(originalBackgroundColor);
isYellowBackgroundActive = false;
yellowBackgroundTimer = null;
}, 5000);
}
}
// Play feeding2 sound only when Food4 is eaten
if (eatenFood instanceof Food4) {
// Clear any associated timer since Food4 was eaten
for (var t = food4Timers.length - 1; t >= 0; t--) {
LK.clearTimeout(food4Timers[t]);
food4Timers.splice(t, 1);
}
LK.getSound('feeding2').play();
// Create darkness effect for 10 seconds
if (!isDarkened) {
isDarkened = true;
darknessOverlay = LK.getAsset('snakeSegment', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732,
tint: 0x000000
});
darknessOverlay.alpha = 0;
game.addChild(darknessOverlay);
// Fade in darkness
tween(darknessOverlay, {
alpha: 0.7
}, {
duration: 500
});
// Clear any existing darkness timer
if (darknessTimer) {
LK.clearTimeout(darknessTimer);
}
// Remove darkness after exactly 5 seconds
darknessTimer = LK.setTimeout(function () {
if (darknessOverlay) {
tween(darknessOverlay, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
if (darknessOverlay) {
darknessOverlay.destroy();
darknessOverlay = null;
}
isDarkened = false;
darknessTimer = null;
}
});
}
}, 5000);
}
// Reduce snake speed by 50% for 10 seconds
if (!isSlowed) {
isSlowed = true;
MOVE_INTERVAL = originalMoveInterval * 1.5; // 50% slower
// Clear any existing speed timer
if (speedTimer) {
LK.clearTimeout(speedTimer);
}
// Restore original speed after exactly 5 seconds
speedTimer = LK.setTimeout(function () {
MOVE_INTERVAL = originalMoveInterval;
isSlowed = false;
speedTimer = null;
}, 5000);
}
}
spawnFood();
// Win condition
if (snake.length >= SNAKE_MAX_LENGTH) {
triggerWin();
return;
}
}
// Remove tail if not growing
if (pendingGrowth > 0) {
pendingGrowth--;
} else {
var tail = snake.pop();
tail.destroy();
}
};
// --- Draw Board Grid Background ---
var gridBg = new Container();
for (var r = 0; r < GRID_ROWS; r++) {
for (var c = 0; c < GRID_COLS; c++) {
// Use a light color for grid lines, and a slightly darker fill for the cell
var cell = LK.getAsset('snakeSegment', {
anchorX: 0.5,
anchorY: 0.5,
x: BOARD_OFFSET_X + c * GRID_SIZE + GRID_SIZE / 2,
y: BOARD_OFFSET_Y + r * GRID_SIZE + GRID_SIZE / 2,
width: GRID_SIZE,
height: GRID_SIZE,
tint: (r + c) % 2 === 0 ? 0x232323 : 0x1a1a1a // subtle checkerboard for clarity
});
// Add a border by overlaying a slightly smaller cell of the same color as background
var border = LK.getAsset('snakeSegment', {
anchorX: 0.5,
anchorY: 0.5,
x: BOARD_OFFSET_X + c * GRID_SIZE + GRID_SIZE / 2,
y: BOARD_OFFSET_Y + r * GRID_SIZE + GRID_SIZE / 2,
width: GRID_SIZE - 8,
height: GRID_SIZE - 8,
tint: (r + c) % 2 === 0 ? 0x181818 : 0x181818 // match background
});
gridBg.addChild(cell);
gridBg.addChild(border);
}
}
game.addChild(gridBg);
// --- Game Initialization ---
createWalls();
createSnake();
spawnFood();
setScore(1);
;
a woman head. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
2d pixel art red brick wall. In-Game asset. 2d. High contrast. No shadows
2d pixel art snake skin. High contrast. No shadows
add 10x text to this pic
kalp şekli pixel art 2d. In-Game asset. High contrast. No shadows