User prompt
play the match tone every time there is a correct match
User prompt
let the game be endless
User prompt
gem_zeus may start coming after 2 moves
User prompt
gem_zeus should not spawn at first but can come later
User prompt
make the combo text vibrate every time a combo is added ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
add animation to combo text ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
show the combo at the bottom of the screen
User prompt
remove multiplierTxt
User prompt
Please fix the bug: 'ReferenceError: comboTxt is not defined' in or related to this line: 'comboTxt; // (no-op, remove combo text update)' Line Number: 483
User prompt
remove the text below the score text
User prompt
remove movesTxt
User prompt
Please fix the bug: 'multiplierTxt is not defined' in or related to this line: 'multiplierTxt.setText('x' + multiplier);' Line Number: 181
User prompt
remove the x1 above the score
User prompt
combo text at the bottom
User prompt
in the combo, transfer from the first explosion until the explosions are over
User prompt
increase each gem_zeus 1x in the combo
User prompt
When an explosion happens, enter an animation of gem_zeus growing and exploding on the screen ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Fix sometimes there are gaps when you move the stone
Code edit (1 edits merged)
Please save this source code
User prompt
Olympus Jewels: Match & Multiply
Initial prompt
gates of olympus style game
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Gem class
var Gem = Container.expand(function () {
var self = Container.call(this);
// Properties
self.gemType = null; // 'red', 'blue', etc.
self.row = 0;
self.col = 0;
self.isMoving = false;
self.isMatched = false;
self.multiplier = 1; // For Zeus or special gems
// Attach asset
self.setType = function (type) {
self.gemType = type;
if (self.gemAsset) {
self.removeChild(self.gemAsset);
}
var assetId = 'gem_' + type;
self.gemAsset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Zeus gem: add a glow effect (tint)
if (type === 'zeus') {
self.gemAsset.tint = 0xadd8ff;
}
};
// Animate match (fade out)
self.animateMatch = function (_onFinish) {
self.isMoving = true;
tween(self.gemAsset, {
alpha: 0
}, {
duration: 250,
easing: tween.easeIn,
onFinish: function onFinish() {
self.isMoving = false;
if (_onFinish) _onFinish();
}
});
};
// Animate swap (move to new position)
self.animateMove = function (targetX, targetY, _onFinish2) {
self.isMoving = true;
tween(self, {
x: targetX,
y: targetY
}, {
duration: 180,
easing: tween.easeInOut,
onFinish: function onFinish() {
self.isMoving = false;
if (_onFinish2) _onFinish2();
}
});
};
// Reset alpha after match
self.resetAlpha = function () {
self.gemAsset.alpha = 1;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a2236
});
/****
* Game Code
****/
// Gems: 6 types + Zeus special
// --- Game Constants ---
var GRID_ROWS = 8;
var GRID_COLS = 8;
var GEM_SIZE = 200; // px, including margin
var GEM_TYPES = ['red', 'blue', 'green', 'yellow', 'purple', 'orange'];
var ZEUS_CHANCE = 0.07; // Chance for Zeus gem to spawn on refill
var MOVES_LIMIT = 30;
// --- Game State ---
var grid = []; // 2D array [row][col] of Gem
var selectedGem = null;
var swappingGem = null;
var canInput = true;
var score = 0;
var movesLeft = MOVES_LIMIT;
var multiplier = 1;
var comboCount = 0;
var powerMeter = 0;
var powerMeterMax = 10;
var isPowerActive = false;
var movesMade = 0; // Track number of moves made
// --- UI Elements ---
var scoreTxt = new Text2('Score: 0', {
size: 90,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var powerMeterTxt = new Text2('⚡ 0/10', {
size: 70,
fill: 0x00E6E6
});
powerMeterTxt.anchor.set(0.5, 0);
LK.gui.bottom.addChild(powerMeterTxt);
// Combo text at the bottom of the screen
var comboTxt = new Text2('', {
size: 100,
fill: "#fff"
});
comboTxt.anchor.set(0.5, 1);
LK.gui.bottom.addChild(comboTxt);
// --- Board Position ---
var boardOffsetX = (2048 - GRID_COLS * GEM_SIZE) / 2;
var boardOffsetY = 300;
// --- Helper Functions ---
function getGemAt(row, col) {
if (row < 0 || row >= GRID_ROWS || col < 0 || col >= GRID_COLS) return null;
return grid[row][col];
}
function setGemAt(row, col, gem) {
grid[row][col] = gem;
if (gem) {
gem.row = row;
gem.col = col;
}
}
function gemWorldPos(row, col) {
return {
x: boardOffsetX + col * GEM_SIZE + GEM_SIZE / 2,
y: boardOffsetY + row * GEM_SIZE + GEM_SIZE / 2
};
}
function randomGemType() {
// Zeus gems only spawn after 2 moves have been made
if (movesMade >= 2 && Math.random() < ZEUS_CHANCE) return 'zeus';
return GEM_TYPES[Math.floor(Math.random() * GEM_TYPES.length)];
}
function updateUI() {
scoreTxt.setText('Score: ' + score);
powerMeterTxt.setText('⚡ ' + powerMeter + '/' + powerMeterMax);
}
function deselectGem() {
if (selectedGem && selectedGem.gemAsset) {
selectedGem.gemAsset.scaleX = 1;
selectedGem.gemAsset.scaleY = 1;
}
selectedGem = null;
}
function selectGem(gem) {
deselectGem();
selectedGem = gem;
if (gem && gem.gemAsset) {
gem.gemAsset.scaleX = 1.2;
gem.gemAsset.scaleY = 1.2;
}
}
function areAdjacent(gem1, gem2) {
if (!gem1 || !gem2) return false;
var dr = Math.abs(gem1.row - gem2.row);
var dc = Math.abs(gem1.col - gem2.col);
return dr + dc === 1;
}
function swapGems(gem1, gem2, animate, onFinish) {
// Swap in grid
var r1 = gem1.row,
c1 = gem1.col;
var r2 = gem2.row,
c2 = gem2.col;
setGemAt(r1, c1, gem2);
setGemAt(r2, c2, gem1);
// Animate
var pos1 = gemWorldPos(r1, c1);
var pos2 = gemWorldPos(r2, c2);
if (animate) {
gem1.animateMove(pos2.x, pos2.y, function () {
gem2.animateMove(pos1.x, pos1.y, function () {
if (onFinish) onFinish();
});
});
} else {
gem1.x = pos2.x;
gem1.y = pos2.y;
gem2.x = pos1.x;
gem2.y = pos1.y;
if (onFinish) onFinish();
}
}
function refillBoard(onFinish) {
var falling = 0;
for (var col = 0; col < GRID_COLS; col++) {
var emptyRows = [];
for (var row = GRID_ROWS - 1; row >= 0; row--) {
if (!grid[row][col]) emptyRows.push(row);
}
// Improved falling logic: process from bottom up, for each cell, if empty, pull down the nearest gem above
for (var row = GRID_ROWS - 1; row >= 0; row--) {
if (!grid[row][col]) {
// Find the nearest gem above
var found = false;
for (var r2 = row - 1; r2 >= 0; r2--) {
var gem = grid[r2][col];
if (gem) {
setGemAt(row, col, gem);
setGemAt(r2, col, null);
var pos = gemWorldPos(row, col);
falling++;
gem.animateMove(pos.x, pos.y, function () {
falling--;
});
found = true;
break;
}
}
// If nothing found above, spawn new gem
if (!found) {
var newGem = new Gem();
var type = randomGemType();
newGem.setType(type);
setGemAt(row, col, newGem);
var pos = gemWorldPos(row, col);
newGem.x = pos.x;
newGem.y = pos.y - GEM_SIZE * 2; // Drop from above
game.addChild(newGem);
falling++;
newGem.animateMove(pos.x, pos.y, function () {
falling--;
});
}
}
}
}
// Wait for all falling to finish
var _wait = function wait() {
if (falling > 0) {
LK.setTimeout(_wait, 40);
} else {
if (onFinish) onFinish();
}
};
_wait();
}
function findMatches() {
var matches = [];
// Horizontal
for (var row = 0; row < GRID_ROWS; row++) {
var streak = 1;
for (var col = 1; col <= GRID_COLS; col++) {
var prev = getGemAt(row, col - 1);
var curr = getGemAt(row, col);
if (curr && prev && curr.gemType === prev.gemType && curr.gemType !== 'zeus') {
streak++;
} else {
if (streak >= 3 && prev && prev.gemType !== 'zeus') {
var match = [];
for (var k = 0; k < streak; k++) match.push(getGemAt(row, col - 1 - k));
matches.push(match);
}
streak = 1;
}
}
}
// Vertical
for (var col = 0; col < GRID_COLS; col++) {
var streak = 1;
for (var row = 1; row <= GRID_ROWS; row++) {
var prev = getGemAt(row - 1, col);
var curr = getGemAt(row, col);
if (curr && prev && curr.gemType === prev.gemType && curr.gemType !== 'zeus') {
streak++;
} else {
if (streak >= 3 && prev && prev.gemType !== 'zeus') {
var match = [];
for (var k = 0; k < streak; k++) match.push(getGemAt(row - 1 - k, col));
matches.push(match);
}
streak = 1;
}
}
}
// Zeus gems: match any adjacent gems
for (var row = 0; row < GRID_ROWS; row++) {
for (var col = 0; col < GRID_COLS; col++) {
var gem = getGemAt(row, col);
if (gem && gem.gemType === 'zeus') {
var adj = [];
for (var dr = -1; dr <= 1; dr++) {
for (var dc = -1; dc <= 1; dc++) {
if (dr === 0 && dc === 0) continue;
var n = getGemAt(row + dr, col + dc);
if (n && n.gemType !== 'zeus') adj.push(n);
}
}
if (adj.length > 0) {
matches.push([gem].concat(adj));
}
}
}
}
return matches;
}
function markMatches(matches) {
for (var i = 0; i < matches.length; i++) {
for (var j = 0; j < matches[i].length; j++) {
var gem = matches[i][j];
if (gem) gem.isMatched = true;
}
}
}
function removeMatches(onFinish) {
var removed = 0;
for (var row = 0; row < GRID_ROWS; row++) {
for (var col = 0; col < GRID_COLS; col++) {
var gem = getGemAt(row, col);
if (gem && gem.isMatched) {
removed++;
(function (gem, row, col) {
gem.animateMatch(function () {
game.removeChild(gem);
setGemAt(row, col, null);
removed--;
});
})(gem, row, col);
}
}
}
// Wait for all to finish
var _wait2 = function wait() {
if (removed > 0) {
LK.setTimeout(_wait2, 40);
} else {
if (onFinish) onFinish();
}
};
_wait2();
}
function resetGemFlags() {
for (var row = 0; row < GRID_ROWS; row++) {
for (var col = 0; col < GRID_COLS; col++) {
var gem = getGemAt(row, col);
if (gem) {
gem.isMatched = false;
gem.resetAlpha();
}
}
}
}
function triggerZeusPower(gem, onFinish) {
// Randomly clear a row or column
var mode = Math.random() < 0.5 ? 'row' : 'col';
var idx = mode === 'row' ? gem.row : gem.col;
var gemsToClear = [];
if (mode === 'row') {
for (var c = 0; c < GRID_COLS; c++) {
var g = getGemAt(idx, c);
if (g && g !== gem) gemsToClear.push(g);
}
} else {
for (var r = 0; r < GRID_ROWS; r++) {
var g = getGemAt(r, idx);
if (g && g !== gem) gemsToClear.push(g);
}
}
// Animate Zeus gem (grow and explode)
LK.getSound('zeus').play();
var originalScaleX = gem.gemAsset.scaleX;
var originalScaleY = gem.gemAsset.scaleY;
tween(gem.gemAsset, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0
}, {
duration: 400,
easing: tween.cubicOut,
onFinish: function onFinish() {
gem.gemAsset.scaleX = originalScaleX;
gem.gemAsset.scaleY = originalScaleY;
gem.gemAsset.alpha = 1;
}
});
LK.effects.flashObject(gem, 0x00ffff, 400);
// Mark for removal
for (var i = 0; i < gemsToClear.length; i++) {
gemsToClear[i].isMatched = true;
}
// Remove Zeus gem itself
gem.isMatched = true;
// Show multiplier popup
var mult = 2 + Math.floor(Math.random() * 3); // 2x-4x
multiplier = mult;
LK.effects.flashScreen(0xadd8ff, 400);
// Animate Zeus lightning (screen flash)
if (onFinish) LK.setTimeout(onFinish, 400);
}
// Combo transfer state: track if we're in a combo chain (from first explosion to end)
if (typeof comboTransferActive === "undefined") {
var comboTransferActive = false;
}
function processMatches(matches, onFinish) {
if (matches.length === 0) {
// Combo chain ends here
comboTransferActive = false;
comboCount = 0;
multiplier = 1;
comboTxt.setText('');
updateUI();
if (onFinish) onFinish();
return;
}
// Combo chain starts on first explosion
if (!comboTransferActive) {
comboTransferActive = true;
// You can add any "combo start" logic here if needed
}
comboCount++;
if (comboCount > 1) {
LK.getSound('match').play();
LK.getSound('combo').play();
comboTxt.setText('Combo x' + comboCount);
// Animate comboTxt: pop and fade in
comboTxt.alpha = 0.2;
comboTxt.scaleX = 1.8;
comboTxt.scaleY = 1.8;
tween(comboTxt, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 350,
easing: tween.cubicOut,
onFinish: function onFinish() {
// Vibrate effect: rapid left-right shake
var originalX = comboTxt.x;
var vibrateTimes = 6;
var vibrateDistance = 24;
var vibrateDuration = 18;
var _vibrateStep = function vibrateStep(i) {
if (i > vibrateTimes) {
comboTxt.x = originalX;
return;
}
var dir = i % 2 === 0 ? 1 : -1;
tween(comboTxt, {
x: originalX + dir * vibrateDistance
}, {
duration: vibrateDuration,
easing: tween.linear,
onFinish: function onFinish() {
tween(comboTxt, {
x: originalX
}, {
duration: vibrateDuration,
easing: tween.linear,
onFinish: function onFinish() {
_vibrateStep(i + 1);
}
});
}
});
};
_vibrateStep(1);
}
});
} else {
LK.getSound('match').play();
comboTxt.setText('');
// Optionally fade out comboTxt if needed
tween(comboTxt, {
alpha: 0
}, {
duration: 200,
easing: tween.linear
});
}
// Increase each gem_zeus multiplier by 1 for each combo
for (var i = 0; i < matches.length; i++) {
for (var j = 0; j < matches[i].length; j++) {
var gem = matches[i][j];
if (gem && gem.gemType === 'zeus') {
if (typeof gem.multiplier !== 'number') gem.multiplier = 1;
gem.multiplier += 1;
}
}
}
// Score: 100 per gem, * multiplier, * combo
var gemsMatched = 0;
var zeusTriggered = false;
for (var i = 0; i < matches.length; i++) {
for (var j = 0; j < matches[i].length; j++) {
var gem = matches[i][j];
if (gem && !gem.isMatched) {
gemsMatched++;
if (gem.gemType === 'zeus') zeusTriggered = true;
}
}
}
// Power meter
powerMeter += gemsMatched;
if (powerMeter > powerMeterMax) powerMeter = powerMeterMax;
// If Zeus, trigger power
if (zeusTriggered) {
for (var i = 0; i < matches.length; i++) {
for (var j = 0; j < matches[i].length; j++) {
var gem = matches[i][j];
if (gem && gem.gemType === 'zeus') {
triggerZeusPower(gem, function () {
markMatches(matches);
removeMatches(function () {
refillBoard(function () {
resetGemFlags();
var newMatches = findMatches();
processMatches(newMatches, onFinish);
});
});
});
return;
}
}
}
} else {
// No Zeus, normal match
markMatches(matches);
removeMatches(function () {
score += gemsMatched * 100 * multiplier * comboCount;
updateUI();
refillBoard(function () {
resetGemFlags();
var newMatches = findMatches();
processMatches(newMatches, onFinish);
});
});
}
}
function trySwapAndMatch(gem1, gem2) {
canInput = false;
LK.getSound('swap').play();
swapGems(gem1, gem2, true, function () {
var matches = findMatches();
if (matches.length > 0) {
movesLeft--;
movesMade++; // Increment movesMade after a valid move
updateUI();
processMatches(matches, function () {
canInput = true;
checkGameEnd();
});
} else {
// No match, swap back
swapGems(gem1, gem2, true, function () {
canInput = true;
});
}
});
}
function checkGameEnd() {
// Endless mode: do nothing, never trigger game over
}
// --- Power Meter Activation ---
function activatePower() {
if (powerMeter < powerMeterMax || isPowerActive) return;
isPowerActive = true;
LK.getSound('powerup').play();
// Randomly clear 2 rows or columns
var cleared = 0;
for (var i = 0; i < 2; i++) {
var mode = Math.random() < 0.5 ? 'row' : 'col';
var idx = Math.floor(Math.random() * (mode === 'row' ? GRID_ROWS : GRID_COLS));
for (var j = 0; j < (mode === 'row' ? GRID_COLS : GRID_ROWS); j++) {
var gem = mode === 'row' ? getGemAt(idx, j) : getGemAt(j, idx);
if (gem) {
gem.isMatched = true;
cleared++;
}
}
}
LK.effects.flashScreen(0xffff00, 600);
removeMatches(function () {
score += cleared * 200 * multiplier;
powerMeter = 0;
isPowerActive = false;
updateUI();
refillBoard(function () {
resetGemFlags();
var newMatches = findMatches();
processMatches(newMatches, function () {
canInput = true;
checkGameEnd();
});
});
});
}
// --- Board Initialization ---
function fillBoardNoMatches() {
// Fill board, avoid initial matches
for (var row = 0; row < GRID_ROWS; row++) {
grid[row] = [];
for (var col = 0; col < GRID_COLS; col++) {
var gem = new Gem();
var type;
do {
// Prevent Zeus from spawning at game start
do {
type = randomGemType();
} while (type === 'zeus');
gem.setType(type);
setGemAt(row, col, gem);
} while (col >= 2 && getGemAt(row, col - 1).gemType === type && getGemAt(row, col - 2).gemType === type || row >= 2 && getGemAt(row - 1, col).gemType === type && getGemAt(row - 2, col).gemType === type);
var pos = gemWorldPos(row, col);
gem.x = pos.x;
gem.y = pos.y;
game.addChild(gem);
}
}
}
// --- Input Handling ---
game.down = function (x, y, obj) {
if (!canInput) return;
// Convert to board coordinates
var bx = x - boardOffsetX;
var by = y - boardOffsetY;
var col = Math.floor(bx / GEM_SIZE);
var row = Math.floor(by / GEM_SIZE);
var gem = getGemAt(row, col);
if (!gem) return;
if (selectedGem === gem) {
deselectGem();
return;
}
if (!selectedGem) {
selectGem(gem);
} else {
if (areAdjacent(selectedGem, gem)) {
swappingGem = gem;
trySwapAndMatch(selectedGem, gem);
deselectGem();
} else {
selectGem(gem);
}
}
};
game.move = function (x, y, obj) {
// No drag-swap for now (tap only)
};
game.up = function (x, y, obj) {
// No drag-swap for now (tap only)
};
// --- Power Meter Tap (activate power) ---
powerMeterTxt.interactive = true;
powerMeterTxt.down = function (x, y, obj) {
if (powerMeter >= powerMeterMax && canInput) {
canInput = false;
activatePower();
}
};
// --- Game Update ---
game.update = function () {
// Play music if not playing
if (!game._musicStarted) {
LK.playMusic('olympus_theme', {
fade: {
start: 0,
end: 1,
duration: 1000
}
});
game._musicStarted = true;
}
// Prevent input during animations
// (Handled by canInput flag)
};
// --- Game Start ---
function startGame() {
// Reset state
grid = [];
selectedGem = null;
swappingGem = null;
canInput = true;
score = 0;
movesLeft = MOVES_LIMIT;
multiplier = 1;
comboCount = 0;
powerMeter = 0;
isPowerActive = false;
movesMade = 0; // Reset movesMade on game start
updateUI();
// Remove all children except UI
for (var i = game.children.length - 1; i >= 0; i--) {
var ch = game.children[i];
game.removeChild(ch);
}
fillBoardNoMatches();
}
startGame(); ===================================================================
--- original.js
+++ change.js
@@ -422,8 +422,9 @@
// You can add any "combo start" logic here if needed
}
comboCount++;
if (comboCount > 1) {
+ LK.getSound('match').play();
LK.getSound('combo').play();
comboTxt.setText('Combo x' + comboCount);
// Animate comboTxt: pop and fade in
comboTxt.alpha = 0.2;
diamond. In-Game asset. 2d. High contrast. No shadows
strawberry. In-Game asset. 2d. High contrast. No shadows
banana. In-Game asset. 2d. High contrast. No shadows
Emerald. In-Game asset. 2d. High contrast. No shadows
eggplant. In-Game asset. 2d. High contrast. No shadows
Orange. In-Game asset. 2d. High contrast. No shadows
white lightning but in a yellow neon frame. In-Game asset. 2d. High contrast. No shadows