/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Draggable Shape
var DraggableShape = Container.expand(function () {
var self = Container.call(this);
self.shapeType = null; // 'redSquare', etc
self.matchOutline = null; // Reference to the matching outline
self.isMatched = false;
self.asset = null; // The shape asset
self.init = function (shapeType) {
self.shapeType = shapeType;
self.isMatched = false;
// Remove previous asset if any
if (self.asset) self.removeChild(self.asset);
// Attach asset
self.asset = self.attachAsset(shapeType, {
anchorX: 0.5,
anchorY: 0.5
});
// Apply mask if needed
if (shapeType === 'greenTriangle') {
applyTriangleMask(self.asset, 320);
}
if (shapeType === 'yellowStar') {
applyStarMask(self.asset, 320);
}
// New shape masks
if (shapeType === 'purplePentagon') {
applyPentagonMask(self.asset, 320);
}
if (shapeType === 'orangeHexagon') {
applyHexagonMask(self.asset, 320);
}
if (shapeType === 'cyanDiamond') {
applyDiamondMask(self.asset, 320);
}
if (shapeType === 'pinkHeart') {
applyHeartMask(self.asset, 320);
}
if (shapeType === 'brownTrapezoid') {
applyTrapezoidMask(self.asset, 320);
}
if (shapeType === 'tealHeptagon') {
applyHeptagonMask(self.asset, 320);
}
if (shapeType === 'navyOctagon') {
applyOctagonMask(self.asset, 320);
}
};
// Animate to position
self.moveTo = function (x, y, duration, onFinish) {
tween(self, {
x: x,
y: y
}, {
duration: duration || 400,
easing: tween.easeInOut,
onFinish: onFinish
});
};
return self;
});
// Outline Target
var OutlineTarget = Container.expand(function () {
var self = Container.call(this);
self.shapeType = null; // 'redSquareOutline', etc
self.asset = null;
self.init = function (shapeType) {
self.shapeType = shapeType;
if (self.asset) self.removeChild(self.asset);
self.asset = self.attachAsset(shapeType, {
anchorX: 0.5,
anchorY: 0.5
});
// Make outline semi-transparent
self.asset.alpha = 0.35;
// Apply mask if needed
if (shapeType === 'greenTriangleOutline') {
applyTriangleMask(self.asset, 340);
}
if (shapeType === 'yellowStarOutline') {
applyStarMask(self.asset, 340);
}
// New outline masks
if (shapeType === 'purplePentagonOutline') {
applyPentagonMask(self.asset, 340);
}
if (shapeType === 'orangeHexagonOutline') {
applyHexagonMask(self.asset, 340);
}
if (shapeType === 'cyanDiamondOutline') {
applyDiamondMask(self.asset, 340);
}
if (shapeType === 'pinkHeartOutline') {
applyHeartMask(self.asset, 340);
}
if (shapeType === 'brownTrapezoidOutline') {
applyTrapezoidMask(self.asset, 340);
}
if (shapeType === 'tealHeptagonOutline') {
applyHeptagonMask(self.asset, 340);
}
if (shapeType === 'navyOctagonOutline') {
applyOctagonMask(self.asset, 340);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// --- Level Data ---
// Shapes: Red Square, Blue Circle, Green Triangle, Yellow Star
// We'll mask this to triangle
// We'll mask this to star
// Outlines (use transparent fill, colored border)
// Sound for correct match
// Helper: Triangle Mask
// New shapes
function applyTriangleMask(container, size) {
// Create a triangle mask using a box and set its visible area
// Since we can't draw, we use scale/skew to fake a triangle
// We'll just scaleY to 0.5 and rotate
container.rotation = Math.PI / 4;
container.scaleY = 0.58;
}
// Helper: Star Mask
function applyStarMask(container, size) {
// We can't draw a star, so we just use a box for now
// In future, swap to a star asset if available
}
// Helper: Pentagon Mask
function applyPentagonMask(container, size) {
// We can't draw a pentagon, so we just use a box for now
// In future, swap to a pentagon asset if available
}
// Helper: Hexagon Mask
function applyHexagonMask(container, size) {
// We can't draw a hexagon, so we just use a box for now
// In future, swap to a hexagon asset if available
}
// Helper: Heptagon Mask
function applyHeptagonMask(container, size) {
// We can't draw a heptagon, so we just use a box for now
// In future, swap to a heptagon asset if available
}
// Helper: Octagon Mask
function applyOctagonMask(container, size) {
// We can't draw an octagon, so we just use a box for now
// In future, swap to an octagon asset if available
// Optionally, scale down slightly to visually distinguish
container.scaleX = 0.92;
container.scaleY = 0.92;
}
// Helper: Diamond Mask
function applyDiamondMask(container, size) {
// We can't draw a diamond, so we just use a box for now
// In future, swap to a diamond asset if available
container.rotation = Math.PI / 4;
}
// Helper: Heart Mask
function applyHeartMask(container, size) {
// We can't draw a heart, so we just use a box for now
// In future, swap to a heart asset if available
}
// Helper: Trapezoid Mask
function applyTrapezoidMask(container, size) {
// We can't draw a trapezoid, so we just use a box for now
// In future, swap to a trapezoid asset if available
}
// Generate 200 levels with different shape combinations
var allShapes = [{
shape: 'redSquare',
outline: 'redSquareOutline'
}, {
shape: 'blueCircle',
outline: 'blueCircleOutline'
}, {
shape: 'greenTriangle',
outline: 'greenTriangleOutline'
}, {
shape: 'yellowStar',
outline: 'yellowStarOutline'
}, {
shape: 'purplePentagon',
outline: 'purplePentagonOutline'
}, {
shape: 'orangeHexagon',
outline: 'orangeHexagonOutline'
}, {
shape: 'cyanDiamond',
outline: 'cyanDiamondOutline'
}, {
shape: 'pinkHeart',
outline: 'pinkHeartOutline'
}, {
shape: 'brownTrapezoid',
outline: 'brownTrapezoidOutline'
}, {
shape: 'tealHeptagon',
outline: 'tealHeptagonOutline'
}, {
shape: 'navyOctagon',
outline: 'navyOctagonOutline'
}];
var levels = [];
for (var i = 0; i < 200; ++i) {
// Gradually increase number of options: 3 for early, 4 for mid, 5 for late levels
var numShapes;
if (i < 30) {
numShapes = 3;
} else if (i < 100) {
numShapes = 4;
} else {
numShapes = 5;
}
// Pick a unique combination for this level
var indices = [];
for (var j = 0; j < allShapes.length; ++j) indices.push(j);
// Shuffle indices
for (var j = indices.length - 1; j > 0; --j) {
var k = Math.floor(Math.random() * (j + 1));
var tmp = indices[j];
indices[j] = indices[k];
indices[k] = tmp;
}
var levelShapes = [];
for (var j = 0; j < numShapes; ++j) {
levelShapes.push({
shape: allShapes[indices[j]].shape,
outline: allShapes[indices[j]].outline
});
}
levels.push(levelShapes);
}
var currentLevel = 0;
var draggableShapes = [];
var outlineTargets = [];
var dragNode = null;
var dragOffsetX = 0;
var dragOffsetY = 0;
var matchedCount = 0;
// --- GUI: Level Text ---
var levelTxt = new Text2('Level 1', {
size: 120,
fill: 0x333333
});
levelTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(levelTxt);
// --- Helper: Layout ---
function layoutLevel(shapes) {
// Remove previous
for (var i = 0; i < draggableShapes.length; ++i) draggableShapes[i].destroy();
for (var i = 0; i < outlineTargets.length; ++i) outlineTargets[i].destroy();
draggableShapes = [];
outlineTargets = [];
matchedCount = 0;
// Layout outlines in a row near the top, with randomized X positions
var n = shapes.length;
// Reduce spacing and Y for mobile, and bring outlines lower for thumb reach
var spacing = 340;
var outlineY = 520;
var outlineXs = [];
var outlineStartX = 180;
var outlineEndX = 2048 - 180;
var outlineAvailableWidth = outlineEndX - outlineStartX;
for (var i = 0; i < n; ++i) {
outlineXs.push(outlineStartX + Math.round(i * outlineAvailableWidth / (n - 1)));
}
// Shuffle outline Xs for randomness
for (var i = n - 1; i > 0; --i) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = outlineXs[i];
outlineXs[i] = outlineXs[j];
outlineXs[j] = tmp;
}
for (var i = 0; i < n; ++i) {
var outline = new OutlineTarget();
outline.init(shapes[i].outline);
outline.x = outlineXs[i];
outline.y = outlineY;
outline.isFake = false;
game.addChild(outline);
outlineTargets.push(outline);
}
// Layout shapes in a row near the bottom, with randomized X positions
// Bring shapes up for mobile, reduce horizontal padding
var shapeY = 1550;
var shapeXs = [];
var shapeStartX = 180;
var shapeEndX = 2048 - 180;
var shapeAvailableWidth = shapeEndX - shapeStartX;
for (var i = 0; i < n; ++i) {
shapeXs.push(shapeStartX + Math.round(i * shapeAvailableWidth / (n - 1)));
}
// Shuffle shape Xs for randomness
for (var i = n - 1; i > 0; --i) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = shapeXs[i];
shapeXs[i] = shapeXs[j];
shapeXs[j] = tmp;
}
// Shuffle shape order for matching
var shapeOrder = [];
for (var i = 0; i < n; ++i) shapeOrder.push(i);
// Shuffle
for (var i = n - 1; i > 0; --i) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = shapeOrder[i];
shapeOrder[i] = shapeOrder[j];
shapeOrder[j] = tmp;
}
for (var i = 0; i < n; ++i) {
var idx = shapeOrder[i];
var shape = new DraggableShape();
shape.init(shapes[idx].shape);
shape.x = shapeXs[i];
shape.y = shapeY;
shape.matchOutline = outlineTargets[idx];
shape.isFake = false;
game.addChild(shape);
draggableShapes.push(shape);
}
}
// --- Helper: Start Level ---
function startLevel(levelIdx) {
currentLevel = levelIdx;
levelTxt.setText('Level ' + (currentLevel + 1));
layoutLevel(levels[currentLevel]);
}
// --- Helper: Next Level or Win ---
function nextLevelOrWin() {
if (currentLevel + 1 < levels.length) {
LK.setTimeout(function () {
startLevel(currentLevel + 1);
}, 900);
} else {
LK.setTimeout(function () {
LK.showYouWin();
}, 900);
}
}
// --- Dragging Logic ---
function getShapeAt(x, y) {
for (var i = 0; i < draggableShapes.length; ++i) {
var s = draggableShapes[i];
if (s.isMatched) continue;
var dx = x - s.x;
var dy = y - s.y;
var r = 110;
if (dx * dx + dy * dy < r * r) return s;
}
return null;
}
// --- Event Handlers ---
game.down = function (x, y, obj) {
// Only allow one drag at a time
if (dragNode) return;
var shape = getShapeAt(x, y);
if (shape && !shape.isMatched) {
dragNode = shape;
dragOffsetX = x - shape.x;
dragOffsetY = y - shape.y;
// Bring to front
game.removeChild(shape);
game.addChild(shape);
}
};
game.move = function (x, y, obj) {
if (dragNode && !dragNode.isMatched) {
dragNode.x = x - dragOffsetX;
dragNode.y = y - dragOffsetY;
}
};
game.up = function (x, y, obj) {
if (!dragNode) return;
if (dragNode.isMatched) {
dragNode = null;
return;
}
// Check if close to any outline
var outline = dragNode.matchOutline;
var dx = dragNode.x - outline.x;
var dy = dragNode.y - outline.y;
var dist = Math.sqrt(dx * dx + dy * dy);
// Check if dropped close to any outline (not just its own)
var matchedToAny = null;
for (var i = 0; i < outlineTargets.length; ++i) {
var o = outlineTargets[i];
var ddx = dragNode.x - o.x;
var ddy = dragNode.y - o.y;
var d = Math.sqrt(ddx * ddx + ddy * ddy);
if (d < 70) {
matchedToAny = o;
break;
}
}
if (matchedToAny) {
// If not the correct outline, restart from level 1
if (matchedToAny !== outline) {
for (var i = 0; i < draggableShapes.length; ++i) {
var s = draggableShapes[i];
if (!s.isMatched) {
tween(s, {
x: s.x,
y: 2000
}, {
duration: 200
});
}
}
// Restart from level 1 after short delay
LK.setTimeout(function () {
startLevel(0);
}, 400);
dragNode = null;
return;
}
// Snap to outline, mark as matched
dragNode.isMatched = true;
dragNode.moveTo(outline.x, outline.y, 200, function () {
// Animate outline to full alpha
tween(outline.asset, {
alpha: 0.7
}, {
duration: 200
});
});
LK.getSound('match').play();
matchedCount++;
// Animate shape scale
if (dragNode) {
tween(dragNode, {
scaleX: 1.15,
scaleY: 1.15
}, {
duration: 120,
onFinish: function onFinish() {
if (dragNode) {
tween(dragNode, {
scaleX: 1,
scaleY: 1
}, {
duration: 120
});
}
}
});
}
// All matched?
// Only count non-fake shapes for win
var realMatched = 0;
for (var i = 0; i < draggableShapes.length; ++i) {
if (draggableShapes[i].isMatched && !draggableShapes[i].isFake) realMatched++;
}
var realTotal = 0;
for (var i = 0; i < draggableShapes.length; ++i) {
if (!draggableShapes[i].isFake) realTotal++;
}
if (realMatched === realTotal) {
// Animate all shapes up
for (var i = 0; i < draggableShapes.length; ++i) {
tween(draggableShapes[i], {
y: draggableShapes[i].y - 120
}, {
duration: 200
});
}
nextLevelOrWin();
}
} else {
// Animate back to start
var n = draggableShapes.length;
var spacing = 340;
var startX = 2048 / 2 - (n - 1) * spacing / 2;
var shapeY = 1550;
var idx = draggableShapes.indexOf(dragNode);
dragNode.moveTo(startX + idx * spacing, shapeY, 200);
}
dragNode = null;
};
// --- Start Game ---
startLevel(0); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Draggable Shape
var DraggableShape = Container.expand(function () {
var self = Container.call(this);
self.shapeType = null; // 'redSquare', etc
self.matchOutline = null; // Reference to the matching outline
self.isMatched = false;
self.asset = null; // The shape asset
self.init = function (shapeType) {
self.shapeType = shapeType;
self.isMatched = false;
// Remove previous asset if any
if (self.asset) self.removeChild(self.asset);
// Attach asset
self.asset = self.attachAsset(shapeType, {
anchorX: 0.5,
anchorY: 0.5
});
// Apply mask if needed
if (shapeType === 'greenTriangle') {
applyTriangleMask(self.asset, 320);
}
if (shapeType === 'yellowStar') {
applyStarMask(self.asset, 320);
}
// New shape masks
if (shapeType === 'purplePentagon') {
applyPentagonMask(self.asset, 320);
}
if (shapeType === 'orangeHexagon') {
applyHexagonMask(self.asset, 320);
}
if (shapeType === 'cyanDiamond') {
applyDiamondMask(self.asset, 320);
}
if (shapeType === 'pinkHeart') {
applyHeartMask(self.asset, 320);
}
if (shapeType === 'brownTrapezoid') {
applyTrapezoidMask(self.asset, 320);
}
if (shapeType === 'tealHeptagon') {
applyHeptagonMask(self.asset, 320);
}
if (shapeType === 'navyOctagon') {
applyOctagonMask(self.asset, 320);
}
};
// Animate to position
self.moveTo = function (x, y, duration, onFinish) {
tween(self, {
x: x,
y: y
}, {
duration: duration || 400,
easing: tween.easeInOut,
onFinish: onFinish
});
};
return self;
});
// Outline Target
var OutlineTarget = Container.expand(function () {
var self = Container.call(this);
self.shapeType = null; // 'redSquareOutline', etc
self.asset = null;
self.init = function (shapeType) {
self.shapeType = shapeType;
if (self.asset) self.removeChild(self.asset);
self.asset = self.attachAsset(shapeType, {
anchorX: 0.5,
anchorY: 0.5
});
// Make outline semi-transparent
self.asset.alpha = 0.35;
// Apply mask if needed
if (shapeType === 'greenTriangleOutline') {
applyTriangleMask(self.asset, 340);
}
if (shapeType === 'yellowStarOutline') {
applyStarMask(self.asset, 340);
}
// New outline masks
if (shapeType === 'purplePentagonOutline') {
applyPentagonMask(self.asset, 340);
}
if (shapeType === 'orangeHexagonOutline') {
applyHexagonMask(self.asset, 340);
}
if (shapeType === 'cyanDiamondOutline') {
applyDiamondMask(self.asset, 340);
}
if (shapeType === 'pinkHeartOutline') {
applyHeartMask(self.asset, 340);
}
if (shapeType === 'brownTrapezoidOutline') {
applyTrapezoidMask(self.asset, 340);
}
if (shapeType === 'tealHeptagonOutline') {
applyHeptagonMask(self.asset, 340);
}
if (shapeType === 'navyOctagonOutline') {
applyOctagonMask(self.asset, 340);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// --- Level Data ---
// Shapes: Red Square, Blue Circle, Green Triangle, Yellow Star
// We'll mask this to triangle
// We'll mask this to star
// Outlines (use transparent fill, colored border)
// Sound for correct match
// Helper: Triangle Mask
// New shapes
function applyTriangleMask(container, size) {
// Create a triangle mask using a box and set its visible area
// Since we can't draw, we use scale/skew to fake a triangle
// We'll just scaleY to 0.5 and rotate
container.rotation = Math.PI / 4;
container.scaleY = 0.58;
}
// Helper: Star Mask
function applyStarMask(container, size) {
// We can't draw a star, so we just use a box for now
// In future, swap to a star asset if available
}
// Helper: Pentagon Mask
function applyPentagonMask(container, size) {
// We can't draw a pentagon, so we just use a box for now
// In future, swap to a pentagon asset if available
}
// Helper: Hexagon Mask
function applyHexagonMask(container, size) {
// We can't draw a hexagon, so we just use a box for now
// In future, swap to a hexagon asset if available
}
// Helper: Heptagon Mask
function applyHeptagonMask(container, size) {
// We can't draw a heptagon, so we just use a box for now
// In future, swap to a heptagon asset if available
}
// Helper: Octagon Mask
function applyOctagonMask(container, size) {
// We can't draw an octagon, so we just use a box for now
// In future, swap to an octagon asset if available
// Optionally, scale down slightly to visually distinguish
container.scaleX = 0.92;
container.scaleY = 0.92;
}
// Helper: Diamond Mask
function applyDiamondMask(container, size) {
// We can't draw a diamond, so we just use a box for now
// In future, swap to a diamond asset if available
container.rotation = Math.PI / 4;
}
// Helper: Heart Mask
function applyHeartMask(container, size) {
// We can't draw a heart, so we just use a box for now
// In future, swap to a heart asset if available
}
// Helper: Trapezoid Mask
function applyTrapezoidMask(container, size) {
// We can't draw a trapezoid, so we just use a box for now
// In future, swap to a trapezoid asset if available
}
// Generate 200 levels with different shape combinations
var allShapes = [{
shape: 'redSquare',
outline: 'redSquareOutline'
}, {
shape: 'blueCircle',
outline: 'blueCircleOutline'
}, {
shape: 'greenTriangle',
outline: 'greenTriangleOutline'
}, {
shape: 'yellowStar',
outline: 'yellowStarOutline'
}, {
shape: 'purplePentagon',
outline: 'purplePentagonOutline'
}, {
shape: 'orangeHexagon',
outline: 'orangeHexagonOutline'
}, {
shape: 'cyanDiamond',
outline: 'cyanDiamondOutline'
}, {
shape: 'pinkHeart',
outline: 'pinkHeartOutline'
}, {
shape: 'brownTrapezoid',
outline: 'brownTrapezoidOutline'
}, {
shape: 'tealHeptagon',
outline: 'tealHeptagonOutline'
}, {
shape: 'navyOctagon',
outline: 'navyOctagonOutline'
}];
var levels = [];
for (var i = 0; i < 200; ++i) {
// Gradually increase number of options: 3 for early, 4 for mid, 5 for late levels
var numShapes;
if (i < 30) {
numShapes = 3;
} else if (i < 100) {
numShapes = 4;
} else {
numShapes = 5;
}
// Pick a unique combination for this level
var indices = [];
for (var j = 0; j < allShapes.length; ++j) indices.push(j);
// Shuffle indices
for (var j = indices.length - 1; j > 0; --j) {
var k = Math.floor(Math.random() * (j + 1));
var tmp = indices[j];
indices[j] = indices[k];
indices[k] = tmp;
}
var levelShapes = [];
for (var j = 0; j < numShapes; ++j) {
levelShapes.push({
shape: allShapes[indices[j]].shape,
outline: allShapes[indices[j]].outline
});
}
levels.push(levelShapes);
}
var currentLevel = 0;
var draggableShapes = [];
var outlineTargets = [];
var dragNode = null;
var dragOffsetX = 0;
var dragOffsetY = 0;
var matchedCount = 0;
// --- GUI: Level Text ---
var levelTxt = new Text2('Level 1', {
size: 120,
fill: 0x333333
});
levelTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(levelTxt);
// --- Helper: Layout ---
function layoutLevel(shapes) {
// Remove previous
for (var i = 0; i < draggableShapes.length; ++i) draggableShapes[i].destroy();
for (var i = 0; i < outlineTargets.length; ++i) outlineTargets[i].destroy();
draggableShapes = [];
outlineTargets = [];
matchedCount = 0;
// Layout outlines in a row near the top, with randomized X positions
var n = shapes.length;
// Reduce spacing and Y for mobile, and bring outlines lower for thumb reach
var spacing = 340;
var outlineY = 520;
var outlineXs = [];
var outlineStartX = 180;
var outlineEndX = 2048 - 180;
var outlineAvailableWidth = outlineEndX - outlineStartX;
for (var i = 0; i < n; ++i) {
outlineXs.push(outlineStartX + Math.round(i * outlineAvailableWidth / (n - 1)));
}
// Shuffle outline Xs for randomness
for (var i = n - 1; i > 0; --i) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = outlineXs[i];
outlineXs[i] = outlineXs[j];
outlineXs[j] = tmp;
}
for (var i = 0; i < n; ++i) {
var outline = new OutlineTarget();
outline.init(shapes[i].outline);
outline.x = outlineXs[i];
outline.y = outlineY;
outline.isFake = false;
game.addChild(outline);
outlineTargets.push(outline);
}
// Layout shapes in a row near the bottom, with randomized X positions
// Bring shapes up for mobile, reduce horizontal padding
var shapeY = 1550;
var shapeXs = [];
var shapeStartX = 180;
var shapeEndX = 2048 - 180;
var shapeAvailableWidth = shapeEndX - shapeStartX;
for (var i = 0; i < n; ++i) {
shapeXs.push(shapeStartX + Math.round(i * shapeAvailableWidth / (n - 1)));
}
// Shuffle shape Xs for randomness
for (var i = n - 1; i > 0; --i) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = shapeXs[i];
shapeXs[i] = shapeXs[j];
shapeXs[j] = tmp;
}
// Shuffle shape order for matching
var shapeOrder = [];
for (var i = 0; i < n; ++i) shapeOrder.push(i);
// Shuffle
for (var i = n - 1; i > 0; --i) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = shapeOrder[i];
shapeOrder[i] = shapeOrder[j];
shapeOrder[j] = tmp;
}
for (var i = 0; i < n; ++i) {
var idx = shapeOrder[i];
var shape = new DraggableShape();
shape.init(shapes[idx].shape);
shape.x = shapeXs[i];
shape.y = shapeY;
shape.matchOutline = outlineTargets[idx];
shape.isFake = false;
game.addChild(shape);
draggableShapes.push(shape);
}
}
// --- Helper: Start Level ---
function startLevel(levelIdx) {
currentLevel = levelIdx;
levelTxt.setText('Level ' + (currentLevel + 1));
layoutLevel(levels[currentLevel]);
}
// --- Helper: Next Level or Win ---
function nextLevelOrWin() {
if (currentLevel + 1 < levels.length) {
LK.setTimeout(function () {
startLevel(currentLevel + 1);
}, 900);
} else {
LK.setTimeout(function () {
LK.showYouWin();
}, 900);
}
}
// --- Dragging Logic ---
function getShapeAt(x, y) {
for (var i = 0; i < draggableShapes.length; ++i) {
var s = draggableShapes[i];
if (s.isMatched) continue;
var dx = x - s.x;
var dy = y - s.y;
var r = 110;
if (dx * dx + dy * dy < r * r) return s;
}
return null;
}
// --- Event Handlers ---
game.down = function (x, y, obj) {
// Only allow one drag at a time
if (dragNode) return;
var shape = getShapeAt(x, y);
if (shape && !shape.isMatched) {
dragNode = shape;
dragOffsetX = x - shape.x;
dragOffsetY = y - shape.y;
// Bring to front
game.removeChild(shape);
game.addChild(shape);
}
};
game.move = function (x, y, obj) {
if (dragNode && !dragNode.isMatched) {
dragNode.x = x - dragOffsetX;
dragNode.y = y - dragOffsetY;
}
};
game.up = function (x, y, obj) {
if (!dragNode) return;
if (dragNode.isMatched) {
dragNode = null;
return;
}
// Check if close to any outline
var outline = dragNode.matchOutline;
var dx = dragNode.x - outline.x;
var dy = dragNode.y - outline.y;
var dist = Math.sqrt(dx * dx + dy * dy);
// Check if dropped close to any outline (not just its own)
var matchedToAny = null;
for (var i = 0; i < outlineTargets.length; ++i) {
var o = outlineTargets[i];
var ddx = dragNode.x - o.x;
var ddy = dragNode.y - o.y;
var d = Math.sqrt(ddx * ddx + ddy * ddy);
if (d < 70) {
matchedToAny = o;
break;
}
}
if (matchedToAny) {
// If not the correct outline, restart from level 1
if (matchedToAny !== outline) {
for (var i = 0; i < draggableShapes.length; ++i) {
var s = draggableShapes[i];
if (!s.isMatched) {
tween(s, {
x: s.x,
y: 2000
}, {
duration: 200
});
}
}
// Restart from level 1 after short delay
LK.setTimeout(function () {
startLevel(0);
}, 400);
dragNode = null;
return;
}
// Snap to outline, mark as matched
dragNode.isMatched = true;
dragNode.moveTo(outline.x, outline.y, 200, function () {
// Animate outline to full alpha
tween(outline.asset, {
alpha: 0.7
}, {
duration: 200
});
});
LK.getSound('match').play();
matchedCount++;
// Animate shape scale
if (dragNode) {
tween(dragNode, {
scaleX: 1.15,
scaleY: 1.15
}, {
duration: 120,
onFinish: function onFinish() {
if (dragNode) {
tween(dragNode, {
scaleX: 1,
scaleY: 1
}, {
duration: 120
});
}
}
});
}
// All matched?
// Only count non-fake shapes for win
var realMatched = 0;
for (var i = 0; i < draggableShapes.length; ++i) {
if (draggableShapes[i].isMatched && !draggableShapes[i].isFake) realMatched++;
}
var realTotal = 0;
for (var i = 0; i < draggableShapes.length; ++i) {
if (!draggableShapes[i].isFake) realTotal++;
}
if (realMatched === realTotal) {
// Animate all shapes up
for (var i = 0; i < draggableShapes.length; ++i) {
tween(draggableShapes[i], {
y: draggableShapes[i].y - 120
}, {
duration: 200
});
}
nextLevelOrWin();
}
} else {
// Animate back to start
var n = draggableShapes.length;
var spacing = 340;
var startX = 2048 / 2 - (n - 1) * spacing / 2;
var shapeY = 1550;
var idx = draggableShapes.indexOf(dragNode);
dragNode.moveTo(startX + idx * spacing, shapeY, 200);
}
dragNode = null;
};
// --- Start Game ---
startLevel(0);