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