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, isSpecial, specialType) {
var self = Container.call(this);
self.gemType = color;
self.isSpecial = isSpecial || false;
self.specialType = specialType || null; // 'striped-horizontal' or 'striped-vertical'
var gemAssetIds = ['redGem', 'blueGem', 'greenGem', 'yellowGem', 'purpleGem', 'orangeGem'];
var assetId = self.isSpecial ? 'stripedGem' : gemAssetIds[color];
var gemGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Add visual stripe effect for special gems
if (self.isSpecial) {
gemGraphics.tint = [0xff4444, 0x4444ff, 0x44ff44, 0xffff44, 0xff44ff, 0xff8844][color];
// Add sparkle animation
tween(gemGraphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(gemGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
}
self.gridX = 0;
self.gridY = 0;
self.isAnimating = false;
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
// Update screen position to match grid position
self.x = GRID_OFFSET_X + gridX * GEM_SIZE + GEM_SIZE / 2;
self.y = GRID_OFFSET_Y + gridY * GEM_SIZE + GEM_SIZE / 2;
};
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, isSpecial, specialType) {
var gemType = getRandomGemType();
var gem = new Gem(gemType, isSpecial, specialType);
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;
// Check if either gem is a striped gem
var hasStripedGem = gem1.isSpecial && (gem1.specialType === 'striped-horizontal' || gem1.specialType === 'striped-vertical') || gem2.isSpecial && (gem2.specialType === 'striped-horizontal' || gem2.specialType === 'striped-vertical');
// Store original positions
var originalX1 = gem1.gridX;
var originalY1 = gem1.gridY;
var originalX2 = gem2.gridX;
var originalY2 = gem2.gridY;
var originalScreenX1 = gem1.x;
var originalScreenY1 = gem1.y;
var originalScreenX2 = gem2.x;
var originalScreenY2 = gem2.y;
// Calculate target positions for the swap animation
var targetX1 = GRID_OFFSET_X + gem2.gridX * GEM_SIZE + GEM_SIZE / 2;
var targetY1 = GRID_OFFSET_Y + gem2.gridY * GEM_SIZE + GEM_SIZE / 2;
var targetX2 = GRID_OFFSET_X + gem1.gridX * GEM_SIZE + GEM_SIZE / 2;
var targetY2 = GRID_OFFSET_Y + gem1.gridY * GEM_SIZE + GEM_SIZE / 2;
isProcessing = true;
LK.getSound('swap').play();
// Animate the visual swap first
gem1.animateToPosition(targetX1, targetY1);
gem2.animateToPosition(targetX2, targetY2, function () {
// After animation completes, handle the swap
// Temporarily swap positions
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;
if (hasStripedGem) {
// If either gem is striped, activate it immediately
var additionalMatches = [];
if (gem1.isSpecial && (gem1.specialType === 'striped-horizontal' || gem1.specialType === 'striped-vertical')) {
var stripedMatches = activateStripedGem(gem1);
additionalMatches = additionalMatches.concat(stripedMatches);
}
if (gem2.isSpecial && (gem2.specialType === 'striped-horizontal' || gem2.specialType === 'striped-vertical')) {
var stripedMatches = activateStripedGem(gem2);
additionalMatches = additionalMatches.concat(stripedMatches);
}
// Process the striped gem effects
if (additionalMatches.length > 0) {
// Award bonus points for striped activation
LK.setScore(LK.getScore() + additionalMatches.length * 20);
scoreTxt.setText('Score: ' + LK.getScore());
// Remove the matched gems
for (var i = 0; i < additionalMatches.length; i++) {
var match = additionalMatches[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();
}
}
isProcessing = false;
applyGravity();
} else {
// Check if this swap creates any matches (for non-striped gems)
var testMatchData = findMatches();
if (testMatchData.matches.length === 0 && testMatchData.specialGems.length === 0) {
// No matches found - animate back to original positions after a delay
LK.setTimeout(function () {
// Revert grid positions
gem1.setGridPosition(originalX1, originalY1);
gem2.setGridPosition(originalX2, originalY2);
grid[originalY1][originalX1] = gem1;
grid[originalY2][originalX2] = gem2;
// Animate gems back to their original positions
gem1.animateToPosition(originalScreenX1, originalScreenY1);
gem2.animateToPosition(originalScreenX2, originalScreenY2, function () {
isProcessing = false;
});
}, 200); // 0.2 second delay before reverting
} else {
// Valid swap - continue with match processing
isProcessing = false;
processMatches();
}
}
});
}
function findMatches() {
var matches = [];
var specialGems = [];
// 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 >= 4) {
// Create striped gem (horizontal)
var centerX = Math.floor((x - count + x - 1) / 2);
specialGems.push({
x: centerX,
y: y,
type: 'striped-horizontal',
color: currentType
});
for (var i = x - count; i < x; i++) {
if (i !== centerX) {
// Don't remove the center gem that becomes striped
matches.push({
x: i,
y: y
});
}
}
} 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 >= 4) {
// Create striped gem (horizontal)
var centerX = Math.floor((GRID_SIZE - count + GRID_SIZE - 1) / 2);
specialGems.push({
x: centerX,
y: y,
type: 'striped-horizontal',
color: currentType
});
for (var i = GRID_SIZE - count; i < GRID_SIZE; i++) {
if (i !== centerX) {
// Don't remove the center gem that becomes striped
matches.push({
x: i,
y: y
});
}
}
} else 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 >= 4) {
// Create striped gem (vertical)
var centerY = Math.floor((y - count + y - 1) / 2);
specialGems.push({
x: x,
y: centerY,
type: 'striped-vertical',
color: currentType
});
for (var i = y - count; i < y; i++) {
if (i !== centerY) {
// Don't remove the center gem that becomes striped
matches.push({
x: x,
y: i
});
}
}
} 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 >= 4) {
// Create striped gem (vertical)
var centerY = Math.floor((GRID_SIZE - count + GRID_SIZE - 1) / 2);
specialGems.push({
x: x,
y: centerY,
type: 'striped-vertical',
color: currentType
});
for (var i = GRID_SIZE - count; i < GRID_SIZE; i++) {
if (i !== centerY) {
// Don't remove the center gem that becomes striped
matches.push({
x: x,
y: i
});
}
}
} else if (count >= 3) {
for (var i = GRID_SIZE - count; i < GRID_SIZE; i++) {
matches.push({
x: x,
y: i
});
}
}
}
return {
matches: matches,
specialGems: specialGems
};
}
function activateStripedGem(gem) {
var matches = [];
LK.getSound('lineBlast').play();
if (gem.specialType === 'striped-horizontal') {
// Clear entire row
for (var x = 0; x < GRID_SIZE; x++) {
if (grid[gem.gridY][x]) {
matches.push({
x: x,
y: gem.gridY
});
}
}
// Create line effect animation
var lineEffect = LK.getAsset('stripedGem', {
anchorX: 0,
anchorY: 0.5,
scaleX: 10,
scaleY: 0.2,
alpha: 0.8
});
lineEffect.x = GRID_OFFSET_X;
lineEffect.y = GRID_OFFSET_Y + gem.gridY * GEM_SIZE + GEM_SIZE / 2;
lineEffect.tint = 0xffff00;
game.addChild(lineEffect);
tween(lineEffect, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
lineEffect.destroy();
}
});
} else if (gem.specialType === 'striped-vertical') {
// Clear entire column
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[y][gem.gridX]) {
matches.push({
x: gem.gridX,
y: y
});
}
}
// Create line effect animation
var lineEffect = LK.getAsset('stripedGem', {
anchorX: 0.5,
anchorY: 0,
scaleX: 0.2,
scaleY: 10,
alpha: 0.8
});
lineEffect.x = GRID_OFFSET_X + gem.gridX * GEM_SIZE + GEM_SIZE / 2;
lineEffect.y = GRID_OFFSET_Y;
lineEffect.tint = 0xffff00;
game.addChild(lineEffect);
tween(lineEffect, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
lineEffect.destroy();
}
});
}
return matches;
}
function removeMatches(matchData) {
var matches = matchData.matches || matchData;
var specialGems = matchData.specialGems || [];
if (matches.length === 0 && specialGems.length === 0) return;
LK.getSound('match').play();
var points = matches.length * 10;
// Bonus points for special gem creation
if (specialGems.length > 0) {
points += specialGems.length * 50;
}
LK.setScore(LK.getScore() + points);
scoreTxt.setText('Score: ' + LK.getScore());
// Check for striped gems being matched and activate their effects
var additionalMatches = [];
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
var gem = grid[match.y][match.x];
if (gem && gem.isSpecial && (gem.specialType === 'striped-horizontal' || gem.specialType === 'striped-vertical')) {
var stripedMatches = activateStripedGem(gem);
additionalMatches = additionalMatches.concat(stripedMatches);
}
}
// 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;
}
}
// Create special striped gems
for (var i = 0; i < specialGems.length; i++) {
var special = specialGems[i];
var stripedGem = createGem(special.x, special.y, true, special.type);
stripedGem.gemType = special.color;
grid[special.y][special.x] = stripedGem;
game.addChild(stripedGem);
}
// Process additional matches from striped gem activations
if (additionalMatches.length > 0) {
for (var i = 0; i < additionalMatches.length; i++) {
var match = additionalMatches[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;
}
}
// Add bonus points for striped gem activation
LK.setScore(LK.getScore() + additionalMatches.length * 20);
scoreTxt.setText('Score: ' + LK.getScore());
}
// Update objective display
updateObjectiveDisplay();
// Check if level is complete
if (checkLevelComplete()) {
LK.showYouWin();
}
}
function applyGravity() {
// First pass: clear all null positions and compact gems downward
for (var x = 0; x < GRID_SIZE; x++) {
var compactedGems = [];
// Collect all non-null gems from this column
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[y][x] !== null) {
compactedGems.push(grid[y][x]);
}
// Clear the grid position
grid[y][x] = null;
}
// Place compacted gems at the bottom of the column
var startY = GRID_SIZE - compactedGems.length;
for (var i = 0; i < compactedGems.length; i++) {
var newY = startY + i;
var gem = compactedGems[i];
grid[newY][x] = gem;
gem.setGridPosition(x, newY);
}
}
// Second pass: create new gems for empty positions at top
var gemsToAnimate = [];
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[y][x] === null) {
var newGem = createGem(x, y);
// Start new gems above the visible area
newGem.y = GRID_OFFSET_Y - (GRID_SIZE - y) * 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
});
}
}
}
// Third pass: animate all existing gems to their correct positions
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
var gem = grid[y][x];
if (gem && !gem.isAnimating) {
var correctY = GRID_OFFSET_Y + y * GEM_SIZE + GEM_SIZE / 2;
if (Math.abs(gem.y - correctY) > 5) {
// Only animate if position is significantly different
gemsToAnimate.push({
gem: gem,
targetY: correctY
});
}
}
}
}
// Animate all gems that need repositioning
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();
}
}
});
}
} else {
// No animations needed, check for matches immediately
processMatches();
}
}
function validateGridSync() {
// Verify that each grid position contains exactly one gem with correct coordinates
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
var gem = grid[y][x];
if (!gem) {
console.error("Missing gem at grid position", x, y);
return false;
}
if (gem.gridX !== x || gem.gridY !== y) {
console.error("Gem position mismatch at", x, y, "gem thinks it's at", gem.gridX, gem.gridY);
gem.setGridPosition(x, y); // Fix the mismatch
}
}
}
return true;
}
function processMatches() {
// Validate grid synchronization before processing matches
validateGridSync();
var matchData = findMatches();
if (matchData.matches.length > 0 || matchData.specialGems.length > 0) {
removeMatches(matchData);
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
@@ -43,8 +43,11 @@
self.isAnimating = false;
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
+ // Update screen position to match grid position
+ self.x = GRID_OFFSET_X + gridX * GEM_SIZE + GEM_SIZE / 2;
+ self.y = GRID_OFFSET_Y + gridY * GEM_SIZE + GEM_SIZE / 2;
};
self.animateToPosition = function (targetX, targetY, onComplete) {
self.isAnimating = true;
tween(self, {
@@ -582,41 +585,63 @@
LK.showYouWin();
}
}
function applyGravity() {
- var gemsToAnimate = [];
+ // First pass: clear all null positions and compact gems downward
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--) {
+ var compactedGems = [];
+ // Collect all non-null gems from this column
+ for (var y = 0; y < GRID_SIZE; 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;
+ compactedGems.push(grid[y][x]);
+ }
+ // Clear the grid position
+ grid[y][x] = null;
+ }
+ // Place compacted gems at the bottom of the column
+ var startY = GRID_SIZE - compactedGems.length;
+ for (var i = 0; i < compactedGems.length; i++) {
+ var newY = startY + i;
+ var gem = compactedGems[i];
+ grid[newY][x] = gem;
+ gem.setGridPosition(x, newY);
+ }
+ }
+ // Second pass: create new gems for empty positions at top
+ var gemsToAnimate = [];
+ for (var x = 0; x < GRID_SIZE; x++) {
+ for (var y = 0; y < GRID_SIZE; y++) {
+ if (grid[y][x] === null) {
+ var newGem = createGem(x, y);
+ // Start new gems above the visible area
+ newGem.y = GRID_OFFSET_Y - (GRID_SIZE - y) * 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
+ });
+ }
+ }
+ }
+ // Third pass: animate all existing gems to their correct positions
+ for (var x = 0; x < GRID_SIZE; x++) {
+ for (var y = 0; y < GRID_SIZE; y++) {
+ var gem = grid[y][x];
+ if (gem && !gem.isAnimating) {
+ var correctY = GRID_OFFSET_Y + y * GEM_SIZE + GEM_SIZE / 2;
+ if (Math.abs(gem.y - correctY) > 5) {
+ // Only animate if position is significantly different
gemsToAnimate.push({
- gem: grid[writeIndex][x],
- targetY: targetY
+ gem: gem,
+ targetY: correctY
});
}
- 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
+ // Animate all gems that need repositioning
if (gemsToAnimate.length > 0) {
isProcessing = true;
var animationsComplete = 0;
for (var i = 0; i < gemsToAnimate.length; i++) {
@@ -634,11 +659,33 @@
}
}
});
}
+ } else {
+ // No animations needed, check for matches immediately
+ processMatches();
}
}
+function validateGridSync() {
+ // Verify that each grid position contains exactly one gem with correct coordinates
+ for (var y = 0; y < GRID_SIZE; y++) {
+ for (var x = 0; x < GRID_SIZE; x++) {
+ var gem = grid[y][x];
+ if (!gem) {
+ console.error("Missing gem at grid position", x, y);
+ return false;
+ }
+ if (gem.gridX !== x || gem.gridY !== y) {
+ console.error("Gem position mismatch at", x, y, "gem thinks it's at", gem.gridX, gem.gridY);
+ gem.setGridPosition(x, y); // Fix the mismatch
+ }
+ }
+ }
+ return true;
+}
function processMatches() {
+ // Validate grid synchronization before processing matches
+ validateGridSync();
var matchData = findMatches();
if (matchData.matches.length > 0 || matchData.specialGems.length > 0) {
removeMatches(matchData);
applyGravity();
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