/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Food Class
var Food = Container.expand(function () {
var self = Container.call(this);
self.foodType = FOOD_TYPES[0];
var foodAsset = self.attachAsset('food1', {
anchorX: 0.5,
anchorY: 0.5
});
self.setType = function (typeObj) {
self.foodType = typeObj;
// Remove old asset if any
if (self._foodAsset) self._foodAsset.destroy();
self._foodAsset = self.attachAsset(typeObj.id, {
anchorX: 0.5,
anchorY: 0.5
});
};
return self;
});
// Food Score Popup Class
var FoodScorePopup = Container.expand(function () {
var self = Container.call(this);
var txt = new Text2("+0", {
size: 80,
fill: "#fff",
stroke: "#000",
strokeThickness: 8
});
txt.anchor.set(0.5, 0.5);
self.addChild(txt);
self.setScore = function (score) {
txt.setText("+" + score);
};
self.show = function (x, y) {
self.x = x;
self.y = y;
self.alpha = 1;
game.addChild(self);
tween(self, {
y: y - 100,
alpha: 0
}, {
duration: 700,
easing: tween.easeOutCubic,
onComplete: function onComplete() {
self.destroy();
}
});
};
return self;
});
// Snake Head Class (with accessory)
var SnakeHead = Container.expand(function () {
var self = Container.call(this);
var segmentAsset = self.attachAsset('snakeSegment', {
anchorX: 0.5,
anchorY: 0.5
});
// Add accessory (e.g. white ellipse as "hat")
var accessory = self.attachAsset('snakeHeadAccessory', {
anchorX: 0.5,
anchorY: 0.5,
y: -20
});
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;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Food type definitions
// Orange
// Blueberry
// Yellow lemon
// Green melon
// Red apple
// --- Game Constants ---
// --- Asset Initialization ---
var FOOD_TYPES = [{
id: 'food1',
score: 1,
color: 0xff3c28
}, {
id: 'food2',
score: 3,
color: 0x3ecf4a
}, {
id: 'food3',
score: 5,
color: 0xffe156
}, {
id: 'food4',
score: 7,
color: 0x5c6bc0
}, {
id: 'food5',
score: 10,
color: 0xff8a65
}];
var GRID_SIZE = 80; // Each cell is 80x80 px
var GRID_WIDTH = Math.floor(2048 / GRID_SIZE);
var GRID_HEIGHT = Math.floor(2732 / GRID_SIZE);
var MOVE_INTERVAL = 120; // ms between snake moves
// --- Grid Background ---
var gridBg = new Container();
for (var gx = 0; gx < GRID_WIDTH; gx++) {
for (var gy = 0; gy < GRID_HEIGHT; gy++) {
// Draw a faint box for each grid cell
var cell = LK.getAsset('snakeSegment', {
anchorX: 0.5,
anchorY: 0.5,
x: gridToPos(gx, gy).x,
y: gridToPos(gx, gy).y,
width: GRID_SIZE,
height: GRID_SIZE,
alpha: 0.08,
// Very faint
color: 0xcccccc // Subtle gray grid lines
});
gridBg.addChild(cell);
}
}
game.addChild(gridBg);
// --- Game State ---
var snake = []; // Array of SnakeSegment
var snakeDir = {
x: 1,
y: 0
}; // Initial direction: right
var nextDir = {
x: 1,
y: 0
}; // Next direction to turn to
var food = null;
var foodPos = {
x: 0,
y: 0
};
var moveTimer = null;
var isAlive = true;
var pendingGrowth = 0;
// --- Score UI ---
var score = 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// --- Helper Functions ---
function gridToPos(gx, gy) {
// Center the grid in the game area
var offsetX = Math.floor((2048 - GRID_WIDTH * GRID_SIZE) / 2);
var offsetY = Math.floor((2732 - GRID_HEIGHT * GRID_SIZE) / 2);
return {
x: offsetX + gx * GRID_SIZE + GRID_SIZE / 2,
y: offsetY + gy * GRID_SIZE + GRID_SIZE / 2
};
}
function posToGrid(x, y) {
var offsetX = Math.floor((2048 - GRID_WIDTH * GRID_SIZE) / 2);
var offsetY = Math.floor((2732 - GRID_HEIGHT * GRID_SIZE) / 2);
return {
x: Math.floor((x - offsetX) / GRID_SIZE),
y: Math.floor((y - offsetY) / GRID_SIZE)
};
}
function spawnFood() {
// Remove old food if exists
if (food) {
food.destroy();
food = null;
}
// Find empty cell
var emptyCells = [];
for (var gx = 0; gx < GRID_WIDTH; gx++) {
for (var gy = 0; gy < GRID_HEIGHT; gy++) {
var occupied = false;
for (var i = 0; i < snake.length; i++) {
if (snake[i].gx === gx && snake[i].gy === gy) {
occupied = true;
break;
}
}
// Prevent food from spawning on another food (if multiple foods are present)
if (!occupied && food && food.gx === gx && food.gy === gy) {
occupied = true;
}
if (!occupied) {
emptyCells.push({
x: gx,
y: gy
});
}
}
}
if (emptyCells.length === 0) {
// No space left, player wins!
LK.showYouWin();
return;
}
var idx = Math.floor(Math.random() * emptyCells.length);
foodPos = emptyCells[idx];
food = new Food();
// Randomize food type
var foodTypeIdx = Math.floor(Math.random() * FOOD_TYPES.length);
var typeObj = FOOD_TYPES[foodTypeIdx];
food.setType(typeObj);
var pos = gridToPos(foodPos.x, foodPos.y);
food.x = pos.x;
food.y = pos.y;
food.gx = foodPos.x;
food.gy = foodPos.y;
food.foodType = typeObj;
game.addChild(food);
}
// --- Snake Initialization ---
function resetGame() {
// Remove old snake
for (var i = 0; i < snake.length; i++) {
snake[i].destroy();
}
snake = [];
// Remove food
if (food) {
food.destroy();
food = null;
}
// Ensure grid background is at the back
if (gridBg && gridBg.parent) {
gridBg.parent.removeChild(gridBg);
game.addChildAt(gridBg, 0);
}
// Reset state
isAlive = true;
score = 0;
scoreTxt.setText(score);
snakeDir = {
x: 1,
y: 0
};
nextDir = {
x: 1,
y: 0
};
pendingGrowth = 2; // Start with 3 segments
// Place snake in center
var startX = Math.floor(GRID_WIDTH / 2);
var startY = Math.floor(GRID_HEIGHT / 2);
for (var i = 0; i < 3; i++) {
var seg;
if (i === 0) {
seg = new SnakeHead();
} else {
seg = new SnakeSegment();
}
seg.gx = startX - i;
seg.gy = startY;
var pos = gridToPos(seg.gx, seg.gy);
seg.x = pos.x;
seg.y = pos.y;
game.addChild(seg);
snake.push(seg);
}
spawnFood();
// Start move timer
if (moveTimer) LK.clearInterval(moveTimer);
moveTimer = LK.setInterval(moveSnake, MOVE_INTERVAL);
}
// --- Snake Movement ---
function moveSnake() {
if (!isAlive) return;
// Update direction
if ((nextDir.x !== -snakeDir.x || nextDir.y !== -snakeDir.y // Prevent 180 turns
) && (nextDir.x !== snakeDir.x || nextDir.y !== snakeDir.y)) {
snakeDir.x = nextDir.x;
snakeDir.y = nextDir.y;
}
// Calculate new head position
var head = snake[0];
var newGX = head.gx + snakeDir.x;
var newGY = head.gy + snakeDir.y;
// Check wall collision
if (newGX < 0 || newGX >= GRID_WIDTH || newGY < 0 || newGY >= GRID_HEIGHT) {
gameOver();
return;
}
// Check self collision
for (var i = 0; i < snake.length; i++) {
if (snake[i].gx === newGX && snake[i].gy === newGY) {
gameOver();
return;
}
}
// Move segments
var prevGX = head.gx;
var prevGY = head.gy;
var prevPos = gridToPos(prevGX, prevGY);
// Move head
head.gx = newGX;
head.gy = newGY;
var newPos = gridToPos(newGX, newGY);
tween(head, {
x: newPos.x,
y: newPos.y
}, {
duration: MOVE_INTERVAL - 10,
easing: tween.linear
});
// Move body
for (var i = 1; i < snake.length; i++) {
var seg = snake[i];
var tmpGX = seg.gx;
var tmpGY = seg.gy;
var tmpPos = gridToPos(tmpGX, tmpGY);
seg.gx = prevGX;
seg.gy = prevGY;
tween(seg, {
x: prevPos.x,
y: prevPos.y
}, {
duration: MOVE_INTERVAL - 10,
easing: tween.linear
});
prevGX = tmpGX;
prevGY = tmpGY;
prevPos = tmpPos;
}
// Growth
if (pendingGrowth > 0) {
var tail = snake[snake.length - 1];
var newSeg = new SnakeSegment();
newSeg.gx = prevGX;
newSeg.gy = prevGY;
var tailPos = gridToPos(prevGX, prevGY);
newSeg.x = tailPos.x;
newSeg.y = tailPos.y;
game.addChild(newSeg);
snake.push(newSeg);
pendingGrowth--;
}
// Check food collision
if (head.gx === food.gx && head.gy === food.gy) {
var foodScore = food.foodType && food.foodType.score ? food.foodType.score : 1;
score += foodScore;
scoreTxt.setText(score);
pendingGrowth++;
// Show score popup
var popup = new FoodScorePopup();
popup.setScore(foodScore);
popup.show(food.x, food.y);
spawnFood();
}
}
// --- Game Over ---
function gameOver() {
isAlive = false;
LK.effects.flashScreen(0xff0000, 800);
if (moveTimer) LK.clearInterval(moveTimer);
LK.setTimeout(function () {
LK.showGameOver();
}, 800);
}
// --- Input Handling ---
// Touch/drag/swipe: change direction
var dragStart = null;
var dragLast = null;
var dragThreshold = 40; // px
function handleDirectionInput(x, y) {
// Convert to grid
var head = snake[0];
var pos = gridToPos(head.gx, head.gy);
var dx = x - pos.x;
var dy = y - pos.y;
if (Math.abs(dx) > Math.abs(dy)) {
// Horizontal
if (dx > dragThreshold && snakeDir.x !== -1) {
nextDir = {
x: 1,
y: 0
};
} else if (dx < -dragThreshold && snakeDir.x !== 1) {
nextDir = {
x: -1,
y: 0
};
}
} else {
// Vertical
if (dy > dragThreshold && snakeDir.y !== -1) {
nextDir = {
x: 0,
y: 1
};
} else if (dy < -dragThreshold && snakeDir.y !== 1) {
nextDir = {
x: 0,
y: -1
};
}
}
}
// Touch down: start drag
game.down = function (x, y, obj) {
dragStart = {
x: x,
y: y
};
dragLast = {
x: x,
y: y
};
};
// Touch move: detect swipe
game.move = function (x, y, obj) {
if (!dragStart) return;
var dx = x - dragStart.x;
var dy = y - dragStart.y;
if (Math.abs(dx) > dragThreshold || Math.abs(dy) > dragThreshold) {
handleDirectionInput(x, y);
dragStart = {
x: x,
y: y
}; // Reset for next swipe
}
dragLast = {
x: x,
y: y
};
};
// Touch up: tap to turn (if no swipe)
game.up = function (x, y, obj) {
if (!dragStart) return;
var dx = x - dragStart.x;
var dy = y - dragStart.y;
if (Math.abs(dx) < dragThreshold && Math.abs(dy) < dragThreshold) {
// Tap: move toward tapped grid cell, only in cardinal direction, not diagonally
var head = snake[0];
var headGrid = {
x: head.gx,
y: head.gy
};
var tapGrid = posToGrid(x, y);
// If already at tap cell, do nothing
if (headGrid.x !== tapGrid.x || headGrid.y !== tapGrid.y) {
// Determine direction to move: only one axis at a time
var diffX = tapGrid.x - headGrid.x;
var diffY = tapGrid.y - headGrid.y;
if (Math.abs(diffX) > Math.abs(diffY)) {
// Move horizontally
if (diffX > 0) {
nextDir = {
x: 1,
y: 0
};
} else if (diffX < 0) {
nextDir = {
x: -1,
y: 0
};
}
} else if (Math.abs(diffY) > 0) {
// Move vertically
if (diffY > 0) {
nextDir = {
x: 0,
y: 1
};
} else if (diffY < 0) {
nextDir = {
x: 0,
y: -1
};
}
}
}
}
dragStart = null;
dragLast = null;
};
// --- Game Update (not used for movement, but could be used for future features) ---
game.update = function () {
// No per-frame logic needed for MVP
};
// --- Start Game ---
resetGame(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Food Class
var Food = Container.expand(function () {
var self = Container.call(this);
self.foodType = FOOD_TYPES[0];
var foodAsset = self.attachAsset('food1', {
anchorX: 0.5,
anchorY: 0.5
});
self.setType = function (typeObj) {
self.foodType = typeObj;
// Remove old asset if any
if (self._foodAsset) self._foodAsset.destroy();
self._foodAsset = self.attachAsset(typeObj.id, {
anchorX: 0.5,
anchorY: 0.5
});
};
return self;
});
// Food Score Popup Class
var FoodScorePopup = Container.expand(function () {
var self = Container.call(this);
var txt = new Text2("+0", {
size: 80,
fill: "#fff",
stroke: "#000",
strokeThickness: 8
});
txt.anchor.set(0.5, 0.5);
self.addChild(txt);
self.setScore = function (score) {
txt.setText("+" + score);
};
self.show = function (x, y) {
self.x = x;
self.y = y;
self.alpha = 1;
game.addChild(self);
tween(self, {
y: y - 100,
alpha: 0
}, {
duration: 700,
easing: tween.easeOutCubic,
onComplete: function onComplete() {
self.destroy();
}
});
};
return self;
});
// Snake Head Class (with accessory)
var SnakeHead = Container.expand(function () {
var self = Container.call(this);
var segmentAsset = self.attachAsset('snakeSegment', {
anchorX: 0.5,
anchorY: 0.5
});
// Add accessory (e.g. white ellipse as "hat")
var accessory = self.attachAsset('snakeHeadAccessory', {
anchorX: 0.5,
anchorY: 0.5,
y: -20
});
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;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Food type definitions
// Orange
// Blueberry
// Yellow lemon
// Green melon
// Red apple
// --- Game Constants ---
// --- Asset Initialization ---
var FOOD_TYPES = [{
id: 'food1',
score: 1,
color: 0xff3c28
}, {
id: 'food2',
score: 3,
color: 0x3ecf4a
}, {
id: 'food3',
score: 5,
color: 0xffe156
}, {
id: 'food4',
score: 7,
color: 0x5c6bc0
}, {
id: 'food5',
score: 10,
color: 0xff8a65
}];
var GRID_SIZE = 80; // Each cell is 80x80 px
var GRID_WIDTH = Math.floor(2048 / GRID_SIZE);
var GRID_HEIGHT = Math.floor(2732 / GRID_SIZE);
var MOVE_INTERVAL = 120; // ms between snake moves
// --- Grid Background ---
var gridBg = new Container();
for (var gx = 0; gx < GRID_WIDTH; gx++) {
for (var gy = 0; gy < GRID_HEIGHT; gy++) {
// Draw a faint box for each grid cell
var cell = LK.getAsset('snakeSegment', {
anchorX: 0.5,
anchorY: 0.5,
x: gridToPos(gx, gy).x,
y: gridToPos(gx, gy).y,
width: GRID_SIZE,
height: GRID_SIZE,
alpha: 0.08,
// Very faint
color: 0xcccccc // Subtle gray grid lines
});
gridBg.addChild(cell);
}
}
game.addChild(gridBg);
// --- Game State ---
var snake = []; // Array of SnakeSegment
var snakeDir = {
x: 1,
y: 0
}; // Initial direction: right
var nextDir = {
x: 1,
y: 0
}; // Next direction to turn to
var food = null;
var foodPos = {
x: 0,
y: 0
};
var moveTimer = null;
var isAlive = true;
var pendingGrowth = 0;
// --- Score UI ---
var score = 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// --- Helper Functions ---
function gridToPos(gx, gy) {
// Center the grid in the game area
var offsetX = Math.floor((2048 - GRID_WIDTH * GRID_SIZE) / 2);
var offsetY = Math.floor((2732 - GRID_HEIGHT * GRID_SIZE) / 2);
return {
x: offsetX + gx * GRID_SIZE + GRID_SIZE / 2,
y: offsetY + gy * GRID_SIZE + GRID_SIZE / 2
};
}
function posToGrid(x, y) {
var offsetX = Math.floor((2048 - GRID_WIDTH * GRID_SIZE) / 2);
var offsetY = Math.floor((2732 - GRID_HEIGHT * GRID_SIZE) / 2);
return {
x: Math.floor((x - offsetX) / GRID_SIZE),
y: Math.floor((y - offsetY) / GRID_SIZE)
};
}
function spawnFood() {
// Remove old food if exists
if (food) {
food.destroy();
food = null;
}
// Find empty cell
var emptyCells = [];
for (var gx = 0; gx < GRID_WIDTH; gx++) {
for (var gy = 0; gy < GRID_HEIGHT; gy++) {
var occupied = false;
for (var i = 0; i < snake.length; i++) {
if (snake[i].gx === gx && snake[i].gy === gy) {
occupied = true;
break;
}
}
// Prevent food from spawning on another food (if multiple foods are present)
if (!occupied && food && food.gx === gx && food.gy === gy) {
occupied = true;
}
if (!occupied) {
emptyCells.push({
x: gx,
y: gy
});
}
}
}
if (emptyCells.length === 0) {
// No space left, player wins!
LK.showYouWin();
return;
}
var idx = Math.floor(Math.random() * emptyCells.length);
foodPos = emptyCells[idx];
food = new Food();
// Randomize food type
var foodTypeIdx = Math.floor(Math.random() * FOOD_TYPES.length);
var typeObj = FOOD_TYPES[foodTypeIdx];
food.setType(typeObj);
var pos = gridToPos(foodPos.x, foodPos.y);
food.x = pos.x;
food.y = pos.y;
food.gx = foodPos.x;
food.gy = foodPos.y;
food.foodType = typeObj;
game.addChild(food);
}
// --- Snake Initialization ---
function resetGame() {
// Remove old snake
for (var i = 0; i < snake.length; i++) {
snake[i].destroy();
}
snake = [];
// Remove food
if (food) {
food.destroy();
food = null;
}
// Ensure grid background is at the back
if (gridBg && gridBg.parent) {
gridBg.parent.removeChild(gridBg);
game.addChildAt(gridBg, 0);
}
// Reset state
isAlive = true;
score = 0;
scoreTxt.setText(score);
snakeDir = {
x: 1,
y: 0
};
nextDir = {
x: 1,
y: 0
};
pendingGrowth = 2; // Start with 3 segments
// Place snake in center
var startX = Math.floor(GRID_WIDTH / 2);
var startY = Math.floor(GRID_HEIGHT / 2);
for (var i = 0; i < 3; i++) {
var seg;
if (i === 0) {
seg = new SnakeHead();
} else {
seg = new SnakeSegment();
}
seg.gx = startX - i;
seg.gy = startY;
var pos = gridToPos(seg.gx, seg.gy);
seg.x = pos.x;
seg.y = pos.y;
game.addChild(seg);
snake.push(seg);
}
spawnFood();
// Start move timer
if (moveTimer) LK.clearInterval(moveTimer);
moveTimer = LK.setInterval(moveSnake, MOVE_INTERVAL);
}
// --- Snake Movement ---
function moveSnake() {
if (!isAlive) return;
// Update direction
if ((nextDir.x !== -snakeDir.x || nextDir.y !== -snakeDir.y // Prevent 180 turns
) && (nextDir.x !== snakeDir.x || nextDir.y !== snakeDir.y)) {
snakeDir.x = nextDir.x;
snakeDir.y = nextDir.y;
}
// Calculate new head position
var head = snake[0];
var newGX = head.gx + snakeDir.x;
var newGY = head.gy + snakeDir.y;
// Check wall collision
if (newGX < 0 || newGX >= GRID_WIDTH || newGY < 0 || newGY >= GRID_HEIGHT) {
gameOver();
return;
}
// Check self collision
for (var i = 0; i < snake.length; i++) {
if (snake[i].gx === newGX && snake[i].gy === newGY) {
gameOver();
return;
}
}
// Move segments
var prevGX = head.gx;
var prevGY = head.gy;
var prevPos = gridToPos(prevGX, prevGY);
// Move head
head.gx = newGX;
head.gy = newGY;
var newPos = gridToPos(newGX, newGY);
tween(head, {
x: newPos.x,
y: newPos.y
}, {
duration: MOVE_INTERVAL - 10,
easing: tween.linear
});
// Move body
for (var i = 1; i < snake.length; i++) {
var seg = snake[i];
var tmpGX = seg.gx;
var tmpGY = seg.gy;
var tmpPos = gridToPos(tmpGX, tmpGY);
seg.gx = prevGX;
seg.gy = prevGY;
tween(seg, {
x: prevPos.x,
y: prevPos.y
}, {
duration: MOVE_INTERVAL - 10,
easing: tween.linear
});
prevGX = tmpGX;
prevGY = tmpGY;
prevPos = tmpPos;
}
// Growth
if (pendingGrowth > 0) {
var tail = snake[snake.length - 1];
var newSeg = new SnakeSegment();
newSeg.gx = prevGX;
newSeg.gy = prevGY;
var tailPos = gridToPos(prevGX, prevGY);
newSeg.x = tailPos.x;
newSeg.y = tailPos.y;
game.addChild(newSeg);
snake.push(newSeg);
pendingGrowth--;
}
// Check food collision
if (head.gx === food.gx && head.gy === food.gy) {
var foodScore = food.foodType && food.foodType.score ? food.foodType.score : 1;
score += foodScore;
scoreTxt.setText(score);
pendingGrowth++;
// Show score popup
var popup = new FoodScorePopup();
popup.setScore(foodScore);
popup.show(food.x, food.y);
spawnFood();
}
}
// --- Game Over ---
function gameOver() {
isAlive = false;
LK.effects.flashScreen(0xff0000, 800);
if (moveTimer) LK.clearInterval(moveTimer);
LK.setTimeout(function () {
LK.showGameOver();
}, 800);
}
// --- Input Handling ---
// Touch/drag/swipe: change direction
var dragStart = null;
var dragLast = null;
var dragThreshold = 40; // px
function handleDirectionInput(x, y) {
// Convert to grid
var head = snake[0];
var pos = gridToPos(head.gx, head.gy);
var dx = x - pos.x;
var dy = y - pos.y;
if (Math.abs(dx) > Math.abs(dy)) {
// Horizontal
if (dx > dragThreshold && snakeDir.x !== -1) {
nextDir = {
x: 1,
y: 0
};
} else if (dx < -dragThreshold && snakeDir.x !== 1) {
nextDir = {
x: -1,
y: 0
};
}
} else {
// Vertical
if (dy > dragThreshold && snakeDir.y !== -1) {
nextDir = {
x: 0,
y: 1
};
} else if (dy < -dragThreshold && snakeDir.y !== 1) {
nextDir = {
x: 0,
y: -1
};
}
}
}
// Touch down: start drag
game.down = function (x, y, obj) {
dragStart = {
x: x,
y: y
};
dragLast = {
x: x,
y: y
};
};
// Touch move: detect swipe
game.move = function (x, y, obj) {
if (!dragStart) return;
var dx = x - dragStart.x;
var dy = y - dragStart.y;
if (Math.abs(dx) > dragThreshold || Math.abs(dy) > dragThreshold) {
handleDirectionInput(x, y);
dragStart = {
x: x,
y: y
}; // Reset for next swipe
}
dragLast = {
x: x,
y: y
};
};
// Touch up: tap to turn (if no swipe)
game.up = function (x, y, obj) {
if (!dragStart) return;
var dx = x - dragStart.x;
var dy = y - dragStart.y;
if (Math.abs(dx) < dragThreshold && Math.abs(dy) < dragThreshold) {
// Tap: move toward tapped grid cell, only in cardinal direction, not diagonally
var head = snake[0];
var headGrid = {
x: head.gx,
y: head.gy
};
var tapGrid = posToGrid(x, y);
// If already at tap cell, do nothing
if (headGrid.x !== tapGrid.x || headGrid.y !== tapGrid.y) {
// Determine direction to move: only one axis at a time
var diffX = tapGrid.x - headGrid.x;
var diffY = tapGrid.y - headGrid.y;
if (Math.abs(diffX) > Math.abs(diffY)) {
// Move horizontally
if (diffX > 0) {
nextDir = {
x: 1,
y: 0
};
} else if (diffX < 0) {
nextDir = {
x: -1,
y: 0
};
}
} else if (Math.abs(diffY) > 0) {
// Move vertically
if (diffY > 0) {
nextDir = {
x: 0,
y: 1
};
} else if (diffY < 0) {
nextDir = {
x: 0,
y: -1
};
}
}
}
}
dragStart = null;
dragLast = null;
};
// --- Game Update (not used for movement, but could be used for future features) ---
game.update = function () {
// No per-frame logic needed for MVP
};
// --- Start Game ---
resetGame();
ultra realistic apple. In-Game asset. 2d. High contrast. No shadows
create ultra realistic pear. In-Game asset. 2d. High contrast. No shadows
ultra realistic strawberry. In-Game asset. 2d. High contrast. No shadows
ultra realistic orange fruit. In-Game asset. 2d. High contrast. No shadows
snake head 16x16. In-Game asset. 2d. High contrast. No shadows