/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Gem = Container.expand(function (type, gridX, gridY) {
var self = Container.call(this);
self.type = type;
self.gridX = gridX;
self.gridY = gridY;
self.isSelected = false;
self.isMatched = false;
self.isFalling = false;
var gemGraphics = self.attachAsset('gem_' + type, {
anchorX: 0.5,
anchorY: 0.5
});
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = GRID_START_X + gridX * CELL_SIZE;
self.y = GRID_START_Y + gridY * CELL_SIZE;
};
self.animateToGridPosition = function (onComplete) {
var targetX = GRID_START_X + self.gridX * CELL_SIZE;
var targetY = GRID_START_Y + self.gridY * CELL_SIZE;
tween(self, {
x: targetX,
y: targetY
}, {
duration: 300,
easing: tween.easeOut,
onFinish: onComplete
});
};
self.highlight = function () {
self.isSelected = true;
gemGraphics.alpha = 0.7;
tween(gemGraphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut
});
};
self.unhighlight = function () {
self.isSelected = false;
gemGraphics.alpha = 1.0;
tween(gemGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200,
easing: tween.easeOut
});
};
self.markForDestroy = function () {
self.isMatched = true;
tween(gemGraphics, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 300,
easing: tween.easeIn
});
};
self.down = function (x, y, obj) {
handleGemClick(self);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2c1810
});
/****
* Game Code
****/
var GRID_SIZE = 8;
var CELL_SIZE = 130;
var GRID_START_X = (2048 - GRID_SIZE * CELL_SIZE) / 2;
var GRID_START_Y = (2732 - GRID_SIZE * CELL_SIZE) / 2 + 100;
var GEM_TYPES = ['red', 'blue', 'green', 'yellow', 'purple', 'orange'];
var grid = [];
var selectedGem = null;
var isAnimating = false;
var matchesToProcess = [];
var currentChapter = storage.chapter || 1;
var score = 0;
var targetScore = 1000 + currentChapter * 500; // Base 1000, +500 per chapter
var gameTimer = Math.max(30, 90 - currentChapter * 5); // Start at 90s, -5s per chapter, min 30s
var timerInterval = null;
var gameStartTime = null;
// Initialize score display
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var chapterTxt = new Text2('Chapter: ' + currentChapter, {
size: 45,
fill: 0x88FF88
});
chapterTxt.anchor.set(0.5, 0);
chapterTxt.y = 80;
LK.gui.top.addChild(chapterTxt);
var targetTxt = new Text2('Target: ' + targetScore, {
size: 40,
fill: 0xFFFF88
});
targetTxt.anchor.set(0.5, 0);
targetTxt.y = 125;
LK.gui.top.addChild(targetTxt);
var timerTxt = new Text2('Time: ' + gameTimer, {
size: 50,
fill: 0xFF4444
});
timerTxt.anchor.set(0.5, 0);
timerTxt.y = 170;
LK.gui.top.addChild(timerTxt);
// Initialize grid
function initializeGrid() {
for (var x = 0; x < GRID_SIZE; x++) {
grid[x] = [];
for (var y = 0; y < GRID_SIZE; y++) {
grid[x][y] = null;
}
}
// Fill grid with gems ensuring no initial matches
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
var availableTypes = GEM_TYPES.slice();
// Remove types that would create matches
if (x >= 2 && grid[x - 1][y] && grid[x - 2][y] && grid[x - 1][y].type === grid[x - 2][y].type) {
var typeIndex = availableTypes.indexOf(grid[x - 1][y].type);
if (typeIndex > -1) availableTypes.splice(typeIndex, 1);
}
if (y >= 2 && grid[x][y - 1] && grid[x][y - 2] && grid[x][y - 1].type === grid[x][y - 2].type) {
var typeIndex = availableTypes.indexOf(grid[x][y - 1].type);
if (typeIndex > -1) availableTypes.splice(typeIndex, 1);
}
var randomType = availableTypes[Math.floor(Math.random() * availableTypes.length)];
var gem = new Gem(randomType, x, y);
gem.setGridPosition(x, y);
grid[x][y] = gem;
game.addChild(gem);
}
}
}
function startTimer() {
gameStartTime = Date.now();
timerInterval = LK.setInterval(function () {
var elapsed = Math.floor((Date.now() - gameStartTime) / 1000);
var remaining = Math.max(0, gameTimer - elapsed);
timerTxt.setText('Time: ' + remaining);
if (remaining <= 0) {
LK.clearInterval(timerInterval);
if (score < targetScore) {
// Don't advance chapter on loss - stay on same chapter
LK.setTimeout(function () {
LK.showGameOver();
}, 100);
}
}
}, 100);
}
function getGemAt(x, y) {
if (x < 0 || x >= GRID_SIZE || y < 0 || y >= GRID_SIZE) return null;
return grid[x][y];
}
function isAdjacent(gem1, gem2) {
var dx = Math.abs(gem1.gridX - gem2.gridX);
var dy = Math.abs(gem1.gridY - gem2.gridY);
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
}
function swapGems(gem1, gem2) {
var tempX = gem1.gridX;
var tempY = gem1.gridY;
gem1.gridX = gem2.gridX;
gem1.gridY = gem2.gridY;
gem2.gridX = tempX;
gem2.gridY = tempY;
grid[gem1.gridX][gem1.gridY] = gem1;
grid[gem2.gridX][gem2.gridY] = gem2;
gem1.animateToGridPosition();
gem2.animateToGridPosition();
}
function findMatches() {
var matches = [];
// Check horizontal matches
for (var y = 0; y < GRID_SIZE; y++) {
var count = 1;
var currentType = grid[0][y] ? grid[0][y].type : null;
for (var x = 1; x < GRID_SIZE; x++) {
var gem = grid[x][y];
if (gem && gem.type === currentType) {
count++;
} else {
if (count >= 3) {
for (var i = x - count; i < x; i++) {
if (grid[i][y]) matches.push(grid[i][y]);
}
}
count = 1;
currentType = gem ? gem.type : null;
}
}
if (count >= 3) {
for (var i = GRID_SIZE - count; i < GRID_SIZE; i++) {
if (grid[i][y]) matches.push(grid[i][y]);
}
}
}
// Check vertical matches
for (var x = 0; x < GRID_SIZE; x++) {
var count = 1;
var currentType = grid[x][0] ? grid[x][0].type : null;
for (var y = 1; y < GRID_SIZE; y++) {
var gem = grid[x][y];
if (gem && gem.type === currentType) {
count++;
} else {
if (count >= 3) {
for (var i = y - count; i < y; i++) {
if (grid[x][i]) matches.push(grid[x][i]);
}
}
count = 1;
currentType = gem ? gem.type : null;
}
}
if (count >= 3) {
for (var i = GRID_SIZE - count; i < GRID_SIZE; i++) {
if (grid[x][i]) matches.push(grid[x][i]);
}
}
}
return matches;
}
function processMatches() {
var matches = findMatches();
if (matches.length === 0) {
isAnimating = false;
return;
}
// Calculate score - fixed points per gem
var pointsPerGem = 50; // Fixed points per exploded gem
var matchScore = matches.length * pointsPerGem;
score += matchScore;
scoreTxt.setText('Score: ' + score);
// Check win condition
if (score >= targetScore) {
LK.clearInterval(timerInterval);
// Advance to next chapter
currentChapter++;
storage.chapter = currentChapter;
LK.setTimeout(function () {
LK.showYouWin();
}, 500);
return;
}
// Mark gems for destruction
for (var i = 0; i < matches.length; i++) {
matches[i].markForDestroy();
grid[matches[i].gridX][matches[i].gridY] = null;
}
LK.getSound('match').play();
// Remove gems after animation
LK.setTimeout(function () {
for (var i = 0; i < matches.length; i++) {
matches[i].destroy();
}
applyGravity();
}, 300);
}
function applyGravity() {
var moved = false;
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = GRID_SIZE - 1; y >= 0; y--) {
if (grid[x][y] === null) {
// Find gem above to fall down
for (var aboveY = y - 1; aboveY >= 0; aboveY--) {
if (grid[x][aboveY] !== null) {
grid[x][y] = grid[x][aboveY];
grid[x][aboveY] = null;
grid[x][y].gridY = y;
grid[x][y].animateToGridPosition();
moved = true;
break;
}
}
}
}
}
if (moved) {
LK.setTimeout(function () {
fillEmptySpaces();
}, 400);
} else {
fillEmptySpaces();
}
}
function fillEmptySpaces() {
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x][y] === null) {
var randomType = GEM_TYPES[Math.floor(Math.random() * GEM_TYPES.length)];
var gem = new Gem(randomType, x, y);
gem.y = GRID_START_Y - CELL_SIZE;
gem.x = GRID_START_X + x * CELL_SIZE;
grid[x][y] = gem;
game.addChild(gem);
gem.animateToGridPosition();
}
}
}
LK.setTimeout(function () {
processMatches();
}, 400);
}
function handleGemClick(clickedGem) {
if (isAnimating) return;
if (selectedGem === null) {
selectedGem = clickedGem;
selectedGem.highlight();
} else if (selectedGem === clickedGem) {
selectedGem.unhighlight();
selectedGem = null;
} else if (isAdjacent(selectedGem, clickedGem)) {
selectedGem.unhighlight();
isAnimating = true;
// Test swap
var gem1 = selectedGem;
var gem2 = clickedGem;
swapGems(gem1, gem2);
LK.getSound('swap').play();
LK.setTimeout(function () {
var matches = findMatches();
if (matches.length > 0) {
selectedGem = null;
processMatches();
} else {
// Swap back
swapGems(gem1, gem2);
LK.setTimeout(function () {
isAnimating = false;
}, 300);
}
}, 300);
selectedGem = null;
} else {
selectedGem.unhighlight();
selectedGem = clickedGem;
selectedGem.highlight();
}
}
game.down = function (x, y, obj) {
// Touch events now handled by individual gems
};
// Initialize the game
initializeGrid();
startTimer();
game.update = function () {
// Game tick updates handled by tween animations and timeouts
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Gem = Container.expand(function (type, gridX, gridY) {
var self = Container.call(this);
self.type = type;
self.gridX = gridX;
self.gridY = gridY;
self.isSelected = false;
self.isMatched = false;
self.isFalling = false;
var gemGraphics = self.attachAsset('gem_' + type, {
anchorX: 0.5,
anchorY: 0.5
});
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = GRID_START_X + gridX * CELL_SIZE;
self.y = GRID_START_Y + gridY * CELL_SIZE;
};
self.animateToGridPosition = function (onComplete) {
var targetX = GRID_START_X + self.gridX * CELL_SIZE;
var targetY = GRID_START_Y + self.gridY * CELL_SIZE;
tween(self, {
x: targetX,
y: targetY
}, {
duration: 300,
easing: tween.easeOut,
onFinish: onComplete
});
};
self.highlight = function () {
self.isSelected = true;
gemGraphics.alpha = 0.7;
tween(gemGraphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut
});
};
self.unhighlight = function () {
self.isSelected = false;
gemGraphics.alpha = 1.0;
tween(gemGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200,
easing: tween.easeOut
});
};
self.markForDestroy = function () {
self.isMatched = true;
tween(gemGraphics, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 300,
easing: tween.easeIn
});
};
self.down = function (x, y, obj) {
handleGemClick(self);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2c1810
});
/****
* Game Code
****/
var GRID_SIZE = 8;
var CELL_SIZE = 130;
var GRID_START_X = (2048 - GRID_SIZE * CELL_SIZE) / 2;
var GRID_START_Y = (2732 - GRID_SIZE * CELL_SIZE) / 2 + 100;
var GEM_TYPES = ['red', 'blue', 'green', 'yellow', 'purple', 'orange'];
var grid = [];
var selectedGem = null;
var isAnimating = false;
var matchesToProcess = [];
var currentChapter = storage.chapter || 1;
var score = 0;
var targetScore = 1000 + currentChapter * 500; // Base 1000, +500 per chapter
var gameTimer = Math.max(30, 90 - currentChapter * 5); // Start at 90s, -5s per chapter, min 30s
var timerInterval = null;
var gameStartTime = null;
// Initialize score display
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var chapterTxt = new Text2('Chapter: ' + currentChapter, {
size: 45,
fill: 0x88FF88
});
chapterTxt.anchor.set(0.5, 0);
chapterTxt.y = 80;
LK.gui.top.addChild(chapterTxt);
var targetTxt = new Text2('Target: ' + targetScore, {
size: 40,
fill: 0xFFFF88
});
targetTxt.anchor.set(0.5, 0);
targetTxt.y = 125;
LK.gui.top.addChild(targetTxt);
var timerTxt = new Text2('Time: ' + gameTimer, {
size: 50,
fill: 0xFF4444
});
timerTxt.anchor.set(0.5, 0);
timerTxt.y = 170;
LK.gui.top.addChild(timerTxt);
// Initialize grid
function initializeGrid() {
for (var x = 0; x < GRID_SIZE; x++) {
grid[x] = [];
for (var y = 0; y < GRID_SIZE; y++) {
grid[x][y] = null;
}
}
// Fill grid with gems ensuring no initial matches
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
var availableTypes = GEM_TYPES.slice();
// Remove types that would create matches
if (x >= 2 && grid[x - 1][y] && grid[x - 2][y] && grid[x - 1][y].type === grid[x - 2][y].type) {
var typeIndex = availableTypes.indexOf(grid[x - 1][y].type);
if (typeIndex > -1) availableTypes.splice(typeIndex, 1);
}
if (y >= 2 && grid[x][y - 1] && grid[x][y - 2] && grid[x][y - 1].type === grid[x][y - 2].type) {
var typeIndex = availableTypes.indexOf(grid[x][y - 1].type);
if (typeIndex > -1) availableTypes.splice(typeIndex, 1);
}
var randomType = availableTypes[Math.floor(Math.random() * availableTypes.length)];
var gem = new Gem(randomType, x, y);
gem.setGridPosition(x, y);
grid[x][y] = gem;
game.addChild(gem);
}
}
}
function startTimer() {
gameStartTime = Date.now();
timerInterval = LK.setInterval(function () {
var elapsed = Math.floor((Date.now() - gameStartTime) / 1000);
var remaining = Math.max(0, gameTimer - elapsed);
timerTxt.setText('Time: ' + remaining);
if (remaining <= 0) {
LK.clearInterval(timerInterval);
if (score < targetScore) {
// Don't advance chapter on loss - stay on same chapter
LK.setTimeout(function () {
LK.showGameOver();
}, 100);
}
}
}, 100);
}
function getGemAt(x, y) {
if (x < 0 || x >= GRID_SIZE || y < 0 || y >= GRID_SIZE) return null;
return grid[x][y];
}
function isAdjacent(gem1, gem2) {
var dx = Math.abs(gem1.gridX - gem2.gridX);
var dy = Math.abs(gem1.gridY - gem2.gridY);
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
}
function swapGems(gem1, gem2) {
var tempX = gem1.gridX;
var tempY = gem1.gridY;
gem1.gridX = gem2.gridX;
gem1.gridY = gem2.gridY;
gem2.gridX = tempX;
gem2.gridY = tempY;
grid[gem1.gridX][gem1.gridY] = gem1;
grid[gem2.gridX][gem2.gridY] = gem2;
gem1.animateToGridPosition();
gem2.animateToGridPosition();
}
function findMatches() {
var matches = [];
// Check horizontal matches
for (var y = 0; y < GRID_SIZE; y++) {
var count = 1;
var currentType = grid[0][y] ? grid[0][y].type : null;
for (var x = 1; x < GRID_SIZE; x++) {
var gem = grid[x][y];
if (gem && gem.type === currentType) {
count++;
} else {
if (count >= 3) {
for (var i = x - count; i < x; i++) {
if (grid[i][y]) matches.push(grid[i][y]);
}
}
count = 1;
currentType = gem ? gem.type : null;
}
}
if (count >= 3) {
for (var i = GRID_SIZE - count; i < GRID_SIZE; i++) {
if (grid[i][y]) matches.push(grid[i][y]);
}
}
}
// Check vertical matches
for (var x = 0; x < GRID_SIZE; x++) {
var count = 1;
var currentType = grid[x][0] ? grid[x][0].type : null;
for (var y = 1; y < GRID_SIZE; y++) {
var gem = grid[x][y];
if (gem && gem.type === currentType) {
count++;
} else {
if (count >= 3) {
for (var i = y - count; i < y; i++) {
if (grid[x][i]) matches.push(grid[x][i]);
}
}
count = 1;
currentType = gem ? gem.type : null;
}
}
if (count >= 3) {
for (var i = GRID_SIZE - count; i < GRID_SIZE; i++) {
if (grid[x][i]) matches.push(grid[x][i]);
}
}
}
return matches;
}
function processMatches() {
var matches = findMatches();
if (matches.length === 0) {
isAnimating = false;
return;
}
// Calculate score - fixed points per gem
var pointsPerGem = 50; // Fixed points per exploded gem
var matchScore = matches.length * pointsPerGem;
score += matchScore;
scoreTxt.setText('Score: ' + score);
// Check win condition
if (score >= targetScore) {
LK.clearInterval(timerInterval);
// Advance to next chapter
currentChapter++;
storage.chapter = currentChapter;
LK.setTimeout(function () {
LK.showYouWin();
}, 500);
return;
}
// Mark gems for destruction
for (var i = 0; i < matches.length; i++) {
matches[i].markForDestroy();
grid[matches[i].gridX][matches[i].gridY] = null;
}
LK.getSound('match').play();
// Remove gems after animation
LK.setTimeout(function () {
for (var i = 0; i < matches.length; i++) {
matches[i].destroy();
}
applyGravity();
}, 300);
}
function applyGravity() {
var moved = false;
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = GRID_SIZE - 1; y >= 0; y--) {
if (grid[x][y] === null) {
// Find gem above to fall down
for (var aboveY = y - 1; aboveY >= 0; aboveY--) {
if (grid[x][aboveY] !== null) {
grid[x][y] = grid[x][aboveY];
grid[x][aboveY] = null;
grid[x][y].gridY = y;
grid[x][y].animateToGridPosition();
moved = true;
break;
}
}
}
}
}
if (moved) {
LK.setTimeout(function () {
fillEmptySpaces();
}, 400);
} else {
fillEmptySpaces();
}
}
function fillEmptySpaces() {
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x][y] === null) {
var randomType = GEM_TYPES[Math.floor(Math.random() * GEM_TYPES.length)];
var gem = new Gem(randomType, x, y);
gem.y = GRID_START_Y - CELL_SIZE;
gem.x = GRID_START_X + x * CELL_SIZE;
grid[x][y] = gem;
game.addChild(gem);
gem.animateToGridPosition();
}
}
}
LK.setTimeout(function () {
processMatches();
}, 400);
}
function handleGemClick(clickedGem) {
if (isAnimating) return;
if (selectedGem === null) {
selectedGem = clickedGem;
selectedGem.highlight();
} else if (selectedGem === clickedGem) {
selectedGem.unhighlight();
selectedGem = null;
} else if (isAdjacent(selectedGem, clickedGem)) {
selectedGem.unhighlight();
isAnimating = true;
// Test swap
var gem1 = selectedGem;
var gem2 = clickedGem;
swapGems(gem1, gem2);
LK.getSound('swap').play();
LK.setTimeout(function () {
var matches = findMatches();
if (matches.length > 0) {
selectedGem = null;
processMatches();
} else {
// Swap back
swapGems(gem1, gem2);
LK.setTimeout(function () {
isAnimating = false;
}, 300);
}
}, 300);
selectedGem = null;
} else {
selectedGem.unhighlight();
selectedGem = clickedGem;
selectedGem.highlight();
}
}
game.down = function (x, y, obj) {
// Touch events now handled by individual gems
};
// Initialize the game
initializeGrid();
startTimer();
game.update = function () {
// Game tick updates handled by tween animations and timeouts
};
hamburger. In-Game asset. 2d. High contrast. No shadows
pizza. In-Game asset. 2d. High contrast. No shadows
chicken doner. In-Game asset. 2d. High contrast. No shadows
chips. In-Game asset. 2d. High contrast. No shadows
coke. In-Game asset. 2d. High contrast. No shadows
onion ring. In-Game asset. 2d. High contrast. No shadows