User prompt
Display the current level’s gem color targets and their required amounts in a visible panel at the bottom of the screen. Show icons for each target gem color along with the remaining amount to collect (e.g., red gem icon with “5 left”). Update this target panel live as the player collects gems. Use a clean, clear font and bold gem icons for easy readability. Make sure the panel is visible at all times during gameplay and does not overlap with the game grid.
User prompt
Add a level progression system with 100 levels. Each level should define a set of gem color targets, for example “collect 10 red and 10 orange gems within 20 moves.” Display the target gem colors and their required amounts in a panel at the bottom of the screen. Track progress as the player collects the specified colors. If the player collects all target gems before moves run out, show “Level Complete” and advance to the next level. If moves run out before reaching the targets, show “Game Over.” Define different targets and move counts for each level up to 100. Keep all targets and move counts easy to adjust in a data table for future edits. ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Add these simple gem combination rules without external plugins: • If a color bomb is swapped with a striped gem, transform all gems of the same color as the striped gem into striped gems, then immediately remove them all with a quick fade-out effect. • If a striped gem is swapped with another striped gem, clear both their rows and columns instantly. • If a color bomb is swapped with a cross bomb, clear all gems of the cross bomb’s color, then clear their rows and columns too. Use only instant removal and basic fade animations supported by Upit, no tween plugins.
User prompt
When the player matches 6 gems in a straight line, replace one of the matched gems with a super color bomb special gem. The super color bomb should look visually powerful with a dark core and animated rainbow sparkles. When swapped with any gem, it will clear all gems of that gem’s color and also trigger a screen-wide explosion for a big bonus. Award a very high score when activated, and show an impressive full-board blast animation. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
When the player matches 5 gems in a T or L shape, replace the center gem of the match with a cross bomb special gem. This special gem should have a plus (+) symbol on it and a distinct color to stand out. When the cross bomb is matched or swapped with any other gem, it will clear both its entire row and its entire column in a cross-shaped blast. Award extra score and play a strong cross explosion animation. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
When the player matches 5 gems in a straight line, replace one of the matched gems with a color bomb special gem. The color bomb should look visually distinct, for example black with rainbow sparks. When swapped with any other gem, clear all gems of that gem’s color on the board immediately. Play a strong explosion animation and award a high score bonus. Ensure only one color bomb spawns from each 5-gem match. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Fix the spawning of special striped gems after a match. When creating a striped gem, remove any existing gem in that grid cell completely before placing the striped gem. Ensure the grid data array is updated to reference only the new striped gem and not the previous gem. There must be exactly one gem per grid cell at all times.
User prompt
1. After gems are generated (spawned), immediately check if any 3 or more matching gems are automatically aligned horizontally or vertically. If so, remove them right away and spawn new gems in those positions until no immediate matches exist. 2. Also, ensure that after every match and gem drop, re-align the grid indexes to guarantee that each cell has only one gem, with no overlapping gems. Update the data structure storing the positions to match the current visual gem positions.
User prompt
Fix the gem grid synchronization. Ensure that after any match removal and new gem drop, each grid cell contains exactly one gem and no overlaps. Update the internal grid index positions after gems fall, so there are no duplicate or missing gems. Verify that all match checks use the updated grid positions to detect valid matches correctly.
User prompt
Modify the special striped gem behavior so that it activates its clearing effect when swapped with any other gem, regardless of color. Do not use any external tween or plugin. Instead, use a simple built-in animation or instantly trigger the clear effect. When swapped, immediately clear the row or column and award bonus points. Keep the animation minimal and compatible with standard Upit tools.
User prompt
When the player matches 4 gems in a straight line, transform one of the matched gems into a special striped gem. This gem should look visually distinct with a stripe or sparkle effect. When the striped gem is matched later, it will clear an entire row or column depending on its orientation. Animate a bright line-clearing effect across the board when activated. Award a higher score when these gems are used. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Enhance the match-3 game with advanced match bonuses: • Match 4 gems in a row → create a line clearer gem, which clears an entire row or column when matched • Match 5 gems in a row → create a color bomb gem, which clears all gems of the same color on the board when matched • Match 6 or more gems → create a super bomb gem, which clears all gems in a cross (plus shape) area around it • Special gems should stand out with unique icons and animations • Award higher score bonuses for these special matches • Show short visual effects when these power gems activate • Keep combo chain scoring active when these specials trigger ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
When the player swaps two adjacent gems, animate the swap movement visually even if no match is found. Then, if no match occurs, smoothly animate the gems returning to their original positions after a short delay (0.2–0.3s). If a match occurs, continue as normal. This improves the visual feedback and feels more polished. Ensure only adjacent gems can be swapped. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
When the player swaps two adjacent gems, check immediately if the swap creates a match of 3 or more same-colored gems. • If a valid match occurs, remove the matched gems as normal. • If no match occurs, revert the two gems to their original positions. Do not allow swaps that do not create a match to stay on the board. Ignore any non-adjacent swap attempts completely.
User prompt
Restrict gem swapping to only two directly adjacent tiles, either horizontally or vertically. The player must not be allowed to move non-adjacent gems. Also add a level goal panel at the bottom of the screen showing 3 target gem colors and their required amounts. As the player matches those colors, update the counters on the goal panel. When all targets are reached, show “Level Complete”. If no more moves remain, show “Game Over”. Ignore diagonal or distant swaps. Only adjacent swaps are permitted.
User prompt
Please fix the bug: 'TypeError: textChild.setText is not a function. (In 'textChild.setText(collectedAmounts[i] + '/' + targetAmounts[i])', 'textChild.setText' is undefined)' in or related to this line: 'textChild.setText(collectedAmounts[i] + '/' + targetAmounts[i]);' Line Number: 155
User prompt
Add a level objective system to the match-3 game. At the start of each level, randomly select 3 gem colors as the target to collect. Display these target colors and their required amounts in a small panel at the bottom of the screen. When the player matches and collects these target gems, update the progress counter in the panel. End the level as “cleared” when all targets are collected, or show “Game Over” if moves run out. Do not allow moving non-adjacent gems (only swap adjacent gems).
User prompt
Restrict the player to only swap two directly adjacent gems (up, down, left, right). Diagonal or non-adjacent swaps should not be allowed. When a player taps or drags, validate that the selected gem is adjacent to the target. If not adjacent, do not perform the swap. Keep the classic match-3 rule: only one adjacent swap per move.
Code edit (1 edits merged)
Please save this source code
User prompt
Gem Cascade
Initial prompt
Create a 2D match-3 puzzle game for mobile devices. The game should include: • A grid of colorful gem icons (for example 8x8) • The player can swap two adjacent gems by tapping or dragging • When three or more same-colored gems line up horizontally or vertically, they disappear and award points • New gems should fall from the top to fill empty spaces • Combos (chain reactions) should be detected and scored higher • Show the current score at the top • Include a limited move counter (for example 30 moves per game) • A “Game Over” screen when moves run out • Add a simple sound when gems match and clear Use a bright, cartoon style with clear colors for the gems, and smooth mobile-friendly controls.
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Gem = Container.expand(function (color) {
var self = Container.call(this);
self.gemType = color;
var gemAssetIds = ['redGem', 'blueGem', 'greenGem', 'yellowGem', 'purpleGem', 'orangeGem'];
var gemGraphics = self.attachAsset(gemAssetIds[color], {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.isAnimating = false;
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
};
self.animateToPosition = function (targetX, targetY, onComplete) {
self.isAnimating = true;
tween(self, {
x: targetX,
y: targetY
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isAnimating = false;
if (onComplete) onComplete();
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2a1810
});
/****
* Game Code
****/
var GRID_SIZE = 8;
var GEM_SIZE = 200;
var GRID_OFFSET_X = (2048 - GRID_SIZE * GEM_SIZE) / 2;
var GRID_OFFSET_Y = 400;
var MOVE_LIMIT = 30;
var grid = [];
var selectedGem = null;
var movesLeft = MOVE_LIMIT;
var isProcessing = false;
var targetGemTypes = [];
var targetAmounts = [];
var collectedAmounts = [];
var objectivePanel = null;
var objectiveTxt = null;
// Initialize score display
var scoreTxt = new Text2('Score: 0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Initialize moves display
var movesTxt = new Text2('Moves: 30', {
size: 100,
fill: 0xFFFF44
});
movesTxt.anchor.set(1, 0);
movesTxt.x = -50;
movesTxt.y = 150;
LK.gui.topRight.addChild(movesTxt);
function getRandomGemType() {
return Math.floor(Math.random() * 6);
}
function initializeLevelObjectives() {
// Select 3 random gem types as targets
var availableTypes = [0, 1, 2, 3, 4, 5];
targetGemTypes = [];
targetAmounts = [];
collectedAmounts = [];
for (var i = 0; i < 3; i++) {
var randomIndex = Math.floor(Math.random() * availableTypes.length);
var selectedType = availableTypes[randomIndex];
availableTypes.splice(randomIndex, 1);
targetGemTypes.push(selectedType);
targetAmounts.push(15 + Math.floor(Math.random() * 10)); // 15-24 gems needed
collectedAmounts.push(0);
}
}
function createObjectivePanel() {
// Create objective panel background
objectivePanel = new Container();
objectivePanel.x = 2048 / 2;
objectivePanel.y = 2732 - 200;
LK.gui.addChild(objectivePanel);
// Create objective text
objectiveTxt = new Text2('Objectives:', {
size: 80,
fill: 0xFFFFFF
});
objectiveTxt.anchor.set(0.5, 0);
objectiveTxt.x = 0;
objectiveTxt.y = -150;
objectivePanel.addChild(objectiveTxt);
// Create target gem displays
for (var i = 0; i < 3; i++) {
var gemAssetIds = ['redGem', 'blueGem', 'greenGem', 'yellowGem', 'purpleGem', 'orangeGem'];
var targetGem = LK.getAsset(gemAssetIds[targetGemTypes[i]], {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
targetGem.x = (i - 1) * 200;
targetGem.y = -50;
objectivePanel.addChild(targetGem);
var targetText = new Text2('0/' + targetAmounts[i], {
size: 60,
fill: 0xFFFFFF
});
targetText.anchor.set(0.5, 0);
targetText.x = (i - 1) * 200;
targetText.y = 20;
objectivePanel.addChild(targetText);
}
}
function updateObjectiveDisplay() {
// Update the text for each target
for (var i = 0; i < 3; i++) {
var textChildIndex = 1 + i * 2 + 1; // title(0) + gem(1) + text(2), gem(3) + text(4), etc.
var textChild = objectivePanel.children[textChildIndex];
if (textChild && textChild.setText) {
textChild.setText(collectedAmounts[i] + '/' + targetAmounts[i]);
}
}
}
function checkLevelComplete() {
for (var i = 0; i < 3; i++) {
if (collectedAmounts[i] < targetAmounts[i]) {
return false;
}
}
return true;
}
function createGem(gridX, gridY) {
var gemType = getRandomGemType();
var gem = new Gem(gemType);
gem.setGridPosition(gridX, gridY);
gem.x = GRID_OFFSET_X + gridX * GEM_SIZE + GEM_SIZE / 2;
gem.y = GRID_OFFSET_Y + gridY * GEM_SIZE + GEM_SIZE / 2;
return gem;
}
function initializeGrid() {
for (var y = 0; y < GRID_SIZE; y++) {
grid[y] = [];
for (var x = 0; x < GRID_SIZE; x++) {
var gem = createGem(x, y);
grid[y][x] = gem;
game.addChild(gem);
}
}
}
function getGridPosition(screenX, screenY) {
var gridX = Math.floor((screenX - GRID_OFFSET_X) / GEM_SIZE);
var gridY = Math.floor((screenY - GRID_OFFSET_Y) / GEM_SIZE);
if (gridX >= 0 && gridX < GRID_SIZE && gridY >= 0 && gridY < GRID_SIZE) {
return {
x: gridX,
y: gridY
};
}
return null;
}
function areAdjacent(gem1, gem2) {
var dx = Math.abs(gem1.gridX - gem2.gridX);
var dy = Math.abs(gem1.gridY - gem2.gridY);
// Only horizontal OR vertical adjacency allowed, not diagonal
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
}
function swapGems(gem1, gem2) {
if (isProcessing) return;
var tempGridX = gem1.gridX;
var tempGridY = gem1.gridY;
gem1.setGridPosition(gem2.gridX, gem2.gridY);
gem2.setGridPosition(tempGridX, tempGridY);
grid[gem1.gridY][gem1.gridX] = gem1;
grid[gem2.gridY][gem2.gridX] = gem2;
var targetX1 = GRID_OFFSET_X + gem1.gridX * GEM_SIZE + GEM_SIZE / 2;
var targetY1 = GRID_OFFSET_Y + gem1.gridY * GEM_SIZE + GEM_SIZE / 2;
var targetX2 = GRID_OFFSET_X + gem2.gridX * GEM_SIZE + GEM_SIZE / 2;
var targetY2 = GRID_OFFSET_Y + gem2.gridY * GEM_SIZE + GEM_SIZE / 2;
isProcessing = true;
LK.getSound('swap').play();
gem1.animateToPosition(targetX1, targetY1);
gem2.animateToPosition(targetX2, targetY2, function () {
isProcessing = false;
processMatches();
});
}
function findMatches() {
var matches = [];
// Check horizontal matches
for (var y = 0; y < GRID_SIZE; y++) {
var count = 1;
var currentType = grid[y][0].gemType;
for (var x = 1; x < GRID_SIZE; x++) {
if (grid[y][x].gemType === currentType) {
count++;
} else {
if (count >= 3) {
for (var i = x - count; i < x; i++) {
matches.push({
x: i,
y: y
});
}
}
count = 1;
currentType = grid[y][x].gemType;
}
}
if (count >= 3) {
for (var i = GRID_SIZE - count; i < GRID_SIZE; i++) {
matches.push({
x: i,
y: y
});
}
}
}
// Check vertical matches
for (var x = 0; x < GRID_SIZE; x++) {
var count = 1;
var currentType = grid[0][x].gemType;
for (var y = 1; y < GRID_SIZE; y++) {
if (grid[y][x].gemType === currentType) {
count++;
} else {
if (count >= 3) {
for (var i = y - count; i < y; i++) {
matches.push({
x: x,
y: i
});
}
}
count = 1;
currentType = grid[y][x].gemType;
}
}
if (count >= 3) {
for (var i = GRID_SIZE - count; i < GRID_SIZE; i++) {
matches.push({
x: x,
y: i
});
}
}
}
return matches;
}
function removeMatches(matches) {
if (matches.length === 0) return;
LK.getSound('match').play();
var points = matches.length * 10;
LK.setScore(LK.getScore() + points);
scoreTxt.setText('Score: ' + LK.getScore());
// Track collected target gems
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
var gem = grid[match.y][match.x];
if (gem) {
// Check if this gem type is a target
for (var t = 0; t < targetGemTypes.length; t++) {
if (gem.gemType === targetGemTypes[t]) {
collectedAmounts[t]++;
}
}
gem.destroy();
grid[match.y][match.x] = null;
}
}
// Update objective display
updateObjectiveDisplay();
// Check if level is complete
if (checkLevelComplete()) {
LK.showYouWin();
}
}
function applyGravity() {
var gemsToAnimate = [];
for (var x = 0; x < GRID_SIZE; x++) {
var writeIndex = GRID_SIZE - 1;
// Move existing gems down
for (var y = GRID_SIZE - 1; y >= 0; y--) {
if (grid[y][x] !== null) {
if (y !== writeIndex) {
grid[writeIndex][x] = grid[y][x];
grid[y][x] = null;
grid[writeIndex][x].setGridPosition(x, writeIndex);
var targetY = GRID_OFFSET_Y + writeIndex * GEM_SIZE + GEM_SIZE / 2;
gemsToAnimate.push({
gem: grid[writeIndex][x],
targetY: targetY
});
}
writeIndex--;
}
}
// Create new gems for empty spaces at top
for (var y = writeIndex; y >= 0; y--) {
var newGem = createGem(x, y);
newGem.y = GRID_OFFSET_Y - (writeIndex - y + 1) * GEM_SIZE + GEM_SIZE / 2;
grid[y][x] = newGem;
game.addChild(newGem);
var targetY = GRID_OFFSET_Y + y * GEM_SIZE + GEM_SIZE / 2;
gemsToAnimate.push({
gem: newGem,
targetY: targetY
});
}
}
// Animate all gems
if (gemsToAnimate.length > 0) {
isProcessing = true;
var animationsComplete = 0;
for (var i = 0; i < gemsToAnimate.length; i++) {
var animData = gemsToAnimate[i];
tween(animData.gem, {
y: animData.targetY
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
animationsComplete++;
if (animationsComplete === gemsToAnimate.length) {
isProcessing = false;
processMatches();
}
}
});
}
}
}
function processMatches() {
var matches = findMatches();
if (matches.length > 0) {
removeMatches(matches);
applyGravity();
}
}
function decrementMoves() {
movesLeft--;
movesTxt.setText('Moves: ' + movesLeft);
if (movesLeft <= 0) {
if (checkLevelComplete()) {
LK.showYouWin();
} else {
LK.showGameOver();
}
}
}
var dragStartGem = null;
game.down = function (x, y, obj) {
if (isProcessing) return;
var gridPos = getGridPosition(x, y);
if (!gridPos) return;
var clickedGem = grid[gridPos.y][gridPos.x];
if (!clickedGem) return;
// Store the gem being dragged
dragStartGem = clickedGem;
if (selectedGem === null) {
selectedGem = clickedGem;
tween(selectedGem, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 150
});
} else if (selectedGem === clickedGem) {
tween(selectedGem, {
scaleX: 1,
scaleY: 1
}, {
duration: 150
});
selectedGem = null;
} else if (areAdjacent(selectedGem, clickedGem)) {
tween(selectedGem, {
scaleX: 1,
scaleY: 1
}, {
duration: 150
});
swapGems(selectedGem, clickedGem);
decrementMoves();
selectedGem = null;
} else {
tween(selectedGem, {
scaleX: 1,
scaleY: 1
}, {
duration: 150
});
selectedGem = clickedGem;
tween(selectedGem, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 150
});
}
};
game.up = function (x, y, obj) {
if (isProcessing || !dragStartGem) return;
var gridPos = getGridPosition(x, y);
if (!gridPos) {
dragStartGem = null;
return;
}
var targetGem = grid[gridPos.y][gridPos.x];
if (!targetGem || targetGem === dragStartGem) {
dragStartGem = null;
return;
}
// Only allow swap if gems are adjacent (horizontal or vertical only)
if (areAdjacent(dragStartGem, targetGem)) {
// Clear any existing selection
if (selectedGem) {
tween(selectedGem, {
scaleX: 1,
scaleY: 1
}, {
duration: 150
});
selectedGem = null;
}
swapGems(dragStartGem, targetGem);
decrementMoves();
} else {
// If not adjacent, do nothing - invalid move
}
dragStartGem = null;
};
// Initialize the game
initializeLevelObjectives();
initializeGrid();
createObjectivePanel();
scoreTxt.setText('Score: ' + LK.getScore()); ===================================================================
--- original.js
+++ change.js
@@ -135,9 +135,10 @@
}
function updateObjectiveDisplay() {
// Update the text for each target
for (var i = 0; i < 3; i++) {
- var textChild = objectivePanel.children[i * 2 + 2]; // Skip title (1) + gem/text pairs (2 each)
+ var textChildIndex = 1 + i * 2 + 1; // title(0) + gem(1) + text(2), gem(3) + text(4), etc.
+ var textChild = objectivePanel.children[textChildIndex];
if (textChild && textChild.setText) {
textChild.setText(collectedAmounts[i] + '/' + targetAmounts[i]);
}
}
@@ -181,8 +182,9 @@
}
function areAdjacent(gem1, gem2) {
var dx = Math.abs(gem1.gridX - gem2.gridX);
var dy = Math.abs(gem1.gridY - gem2.gridY);
+ // Only horizontal OR vertical adjacency allowed, not diagonal
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
}
function swapGems(gem1, gem2) {
if (isProcessing) return;
@@ -429,9 +431,9 @@
if (!targetGem || targetGem === dragStartGem) {
dragStartGem = null;
return;
}
- // Only allow swap if gems are adjacent
+ // Only allow swap if gems are adjacent (horizontal or vertical only)
if (areAdjacent(dragStartGem, targetGem)) {
// Clear any existing selection
if (selectedGem) {
tween(selectedGem, {
@@ -443,8 +445,10 @@
selectedGem = null;
}
swapGems(dragStartGem, targetGem);
decrementMoves();
+ } else {
+ // If not adjacent, do nothing - invalid move
}
dragStartGem = null;
};
// Initialize the game
Blue gem square. In-Game asset. 2d. High contrast. No shadows
Green gem circle. In-Game asset. 2d. High contrast. No shadows
Orange gem circle. In-Game asset. 2d. High contrast. No shadows
Red gem circle. In-Game asset. 2d. High contrast. No shadows
Yellow gem pyramid. In-Game asset. 2d. High contrast. No shadows
Purple gem cube. In-Game asset. 2d. High contrast. No shadows
A 2D top-down icon for a match-3 special gem with a bright colored jewel (blue or red) and a bold white plus (+) sign on top. The plus sign should be centered, thick, and clearly visible. The gem should have a shiny, cartoon-style look with soft gradients and a glowing outline. No background, transparent, game-ready.
Color bomb gem. In-Game asset. 2d. High contrast. No shadows
Crossbomb gem. In-Game asset. 2d. High contrast. No shadows
Super colorbomb gem. In-Game asset. 2d. High contrast. No shadows