/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Firework = Container.expand(function (x, y) {
var self = Container.call(this);
var fireworkGraphics = self.attachAsset('firework', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = x;
self.y = y;
self.zIndex = 10;
// Initial scale
fireworkGraphics.scaleX = 0.1;
fireworkGraphics.scaleY = 0.1;
fireworkGraphics.alpha = 0.8;
self.explode = function () {
// Play firework sound
LK.getSound('fireworkBurst').play();
// Main explosion animation - smaller size
tween(fireworkGraphics, {
scaleX: 5.0,
scaleY: 5.0,
rotation: Math.PI * 3
}, {
duration: 1000,
easing: tween.easeOut
});
// Fade out effect
tween(fireworkGraphics, {
alpha: 0
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
// Create smaller colorful particle burst
var particleCount = 25;
var colors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff, 0x00ffff, 0xffffff, 0xff8000];
for (var i = 0; i < particleCount; i++) {
var angle = i / particleCount * Math.PI * 2;
var radius = 120 + Math.random() * 100;
var targetX = self.x + Math.cos(angle) * radius;
var targetY = self.y + Math.sin(angle) * radius;
var color = colors[Math.floor(Math.random() * colors.length)];
var sparkle = new Particle(self.x, self.y, color);
sparkle.velocityX = Math.cos(angle) * 300;
sparkle.velocityY = Math.sin(angle) * 300;
sparkle.life = 2.5;
sparkle.scaleSpeed = 0.2;
// Make particles smaller
sparkle.scaleX = 1.5;
sparkle.scaleY = 1.5;
if (self.parent) {
self.parent.addChild(sparkle);
}
}
};
return self;
});
var Gem = Container.expand(function (gemType, gridX, gridY) {
var self = Container.call(this);
self.gemType = gemType;
self.gridX = gridX;
self.gridY = gridY;
self.isAnimating = false;
var gemAssets = ['gemRed', 'gemBlue', 'gemGreen', 'gemYellow', 'gemPurple', 'gemOrange'];
var gemGraphics = self.attachAsset(gemAssets[gemType], {
anchorX: 0.5,
anchorY: 0.5
});
self.setGridPosition = function (x, y) {
self.gridX = x;
self.gridY = y;
};
self.animateToPosition = function (targetX, targetY, duration, onComplete) {
if (!duration) duration = 200;
self.isAnimating = true;
tween(self, {
x: targetX,
y: targetY
}, {
duration: duration,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isAnimating = false;
if (onComplete) onComplete();
}
});
};
self.destroy = function () {
// Play gem-specific destruction sound
var gemSounds = ['gemRedDestroy', 'gemBlueDestroy', 'gemGreenDestroy', 'gemYellowDestroy', 'gemPurpleDestroy', 'gemOrangeDestroy'];
if (self.gemType >= 0 && self.gemType < gemSounds.length) {
try {
LK.getSound(gemSounds[self.gemType]).play();
} catch (e) {
console.log('Sound not found:', gemSounds[self.gemType]);
}
}
// Special animation for red gems (gemType 0)
if (self.gemType === 0) {
self.createFlameExplosion();
}
// Special animation for blue gems (gemType 1)
if (self.gemType === 1) {
self.createWaterExplosion();
}
// Special animation for green gems (gemType 2)
if (self.gemType === 2) {
self.createLeafSpiral();
}
// Special animation for yellow gems (gemType 3)
if (self.gemType === 3) {
self.createStarExplosion();
}
// Special animation for purple gems (gemType 4)
if (self.gemType === 4) {
self.createPurpleImplosion();
}
// Special animation for orange gems (gemType 5)
if (self.gemType === 5) {
self.createLavaMelting();
}
// Create tween effect before actually destroying
tween(self, {
alpha: 0,
scaleX: 0.2,
scaleY: 0.2
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
};
self.createFlameExplosion = function () {
var flameCount = 8;
var radius = 100;
for (var i = 0; i < flameCount; i++) {
var angle = i / flameCount * Math.PI * 2;
var flameParticle = LK.getAsset('flameParticle', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial position at gem center
flameParticle.x = self.x;
flameParticle.y = self.y;
flameParticle.zIndex = 5;
// Add to game
if (self.parent) {
self.parent.addChild(flameParticle);
}
// Calculate spiral target position
var targetX = self.x + Math.cos(angle) * radius;
var targetY = self.y + Math.sin(angle) * radius;
// Create spiral rotation and movement animation
tween(flameParticle, {
x: targetX,
y: targetY,
rotation: Math.PI * 4,
// 2 full rotations
scaleX: 3.0,
scaleY: 3.0
}, {
duration: 400,
easing: tween.easeOut
});
// Fade out and disappear
tween(flameParticle, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (flameParticle.parent) {
flameParticle.parent.removeChild(flameParticle);
}
}
});
// Add flame color transition from orange to red
tween(flameParticle, {
tint: 0xff0000
}, {
duration: 300,
easing: tween.easeInOut
});
}
};
self.createWaterExplosion = function () {
var dropletCount = 12;
var maxRadius = 120;
for (var i = 0; i < dropletCount; i++) {
var angle = i / dropletCount * Math.PI * 2;
var waterDroplet = LK.getAsset('waterDroplet', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial position at gem center
waterDroplet.x = self.x;
waterDroplet.y = self.y;
waterDroplet.zIndex = 5;
// Random radius for scattered effect
var radius = 60 + Math.random() * 60;
// Add to game
if (self.parent) {
self.parent.addChild(waterDroplet);
}
// Calculate target position for water droplet
var targetX = self.x + Math.cos(angle) * radius;
var targetY = self.y + Math.sin(angle) * radius;
// Create bouncing water droplet movement
tween(waterDroplet, {
x: targetX,
y: targetY,
scaleX: 2.5,
scaleY: 2.5
}, {
duration: 350,
easing: tween.bounceOut
});
// Create ripple effect with scaling
tween(waterDroplet, {
scaleX: 0.8,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeInOut
});
// Fade out like evaporating water
tween(waterDroplet, {
alpha: 0,
scaleY: 0.2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (waterDroplet.parent) {
waterDroplet.parent.removeChild(waterDroplet);
}
}
});
// Add water color transition from light blue to transparent
tween(waterDroplet, {
tint: 0x00aaff
}, {
duration: 250,
easing: tween.easeInOut
});
}
};
self.createPurpleImplosion = function () {
var vortexCount = 16;
var initialRadius = 150;
for (var i = 0; i < vortexCount; i++) {
var angle = i / vortexCount * Math.PI * 2;
var purpleVortex = LK.getAsset('purpleVortex', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial position in a circle around the gem
var startX = self.x + Math.cos(angle) * initialRadius;
var startY = self.y + Math.sin(angle) * initialRadius;
purpleVortex.x = startX;
purpleVortex.y = startY;
purpleVortex.zIndex = 5;
// Add purple tint and initial scale
purpleVortex.tint = 0x8B00FF;
purpleVortex.scaleX = 0.5;
purpleVortex.scaleY = 0.5;
// Add to game
if (self.parent) {
self.parent.addChild(purpleVortex);
}
// Create spiral inward movement animation
tween(purpleVortex, {
x: self.x,
y: self.y,
rotation: Math.PI * 6,
// 3 full rotations inward
scaleX: 3.5,
scaleY: 3.5
}, {
duration: 600,
easing: tween.easeIn
});
// Fade in then fade out with implosion effect
tween(purpleVortex, {
alpha: 1.0
}, {
duration: 200,
easing: tween.easeOut
});
// Final implosion - scale down and disappear
tween(purpleVortex, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (purpleVortex.parent) {
purpleVortex.parent.removeChild(purpleVortex);
}
}
});
// Color transition from purple to dark purple
tween(purpleVortex, {
tint: 0x4B0082
}, {
duration: 300,
easing: tween.easeInOut
});
}
};
self.createLeafSpiral = function () {
var leafCount = 10;
var spiralRadius = 140;
for (var i = 0; i < leafCount; i++) {
var angle = i / leafCount * Math.PI * 2;
var leafParticle = LK.getAsset('leafParticle', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial position at gem center
leafParticle.x = self.x;
leafParticle.y = self.y;
leafParticle.zIndex = 5;
// Add natural green tint and initial scale
leafParticle.tint = 0x228B22;
leafParticle.scaleX = 0.8;
leafParticle.scaleY = 0.8;
leafParticle.rotation = angle; // Initial rotation based on position
// Add to game
if (self.parent) {
self.parent.addChild(leafParticle);
}
// Create spiral outward movement with natural floating motion
var targetX = self.x + Math.cos(angle) * spiralRadius;
var targetY = self.y + Math.sin(angle) * spiralRadius;
// Add some randomness for natural leaf movement
targetX += (Math.random() - 0.5) * 60;
targetY += (Math.random() - 0.5) * 60;
// Create floating leaf movement animation
tween(leafParticle, {
x: targetX,
y: targetY,
rotation: angle + Math.PI * 3,
// 1.5 full rotations
scaleX: 2.8,
scaleY: 2.8
}, {
duration: 500,
easing: tween.easeOut
});
// Add gentle swaying motion like leaves in wind
tween(leafParticle, {
x: targetX + Math.sin(angle * 2) * 30,
y: targetY + Math.cos(angle * 2) * 20
}, {
duration: 800,
easing: tween.easeInOut
});
// Fade out like autumn leaves
tween(leafParticle, {
alpha: 0,
scaleX: 0.3,
scaleY: 0.3,
rotation: angle + Math.PI * 5 // Continue rotating as it fades
}, {
duration: 700,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (leafParticle.parent) {
leafParticle.parent.removeChild(leafParticle);
}
}
});
// Add leaf color transition from green to brown
tween(leafParticle, {
tint: 0x8B4513 // Brown color
}, {
duration: 400,
easing: tween.easeInOut
});
}
};
self.createStarExplosion = function () {
var starCount = 14;
var maxRadius = 160;
for (var i = 0; i < starCount; i++) {
var angle = i / starCount * Math.PI * 2;
var starParticle = LK.getAsset('starParticle', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial position at gem center
starParticle.x = self.x;
starParticle.y = self.y;
starParticle.zIndex = 5;
// Add golden yellow tint and initial scale
starParticle.tint = 0xFFD700;
starParticle.scaleX = 0.3;
starParticle.scaleY = 0.3;
starParticle.rotation = angle;
// Add to game
if (self.parent) {
self.parent.addChild(starParticle);
}
// Create spiral outward sun burst movement
var radius = 80 + Math.random() * 80;
var targetX = self.x + Math.cos(angle) * radius;
var targetY = self.y + Math.sin(angle) * radius;
// Add sparkle randomness for star effect
targetX += (Math.random() - 0.5) * 40;
targetY += (Math.random() - 0.5) * 40;
// Create explosive star movement animation
tween(starParticle, {
x: targetX,
y: targetY,
rotation: angle + Math.PI * 4,
// 2 full rotations
scaleX: 5.0,
scaleY: 5.0
}, {
duration: 450,
easing: tween.easeOut
});
// Add pulsing sparkle effect
tween(starParticle, {
scaleX: 6.5,
scaleY: 6.5
}, {
duration: 150,
easing: tween.easeInOut
});
// Create secondary sparkle pulse
tween(starParticle, {
scaleX: 4.5,
scaleY: 4.5,
rotation: angle + Math.PI * 6 // Continue rotating
}, {
duration: 300,
easing: tween.easeInOut
});
// Fade out like fading starlight
tween(starParticle, {
alpha: 0,
scaleX: 0.2,
scaleY: 0.2,
rotation: angle + Math.PI * 8 // Final rotation burst
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (starParticle.parent) {
starParticle.parent.removeChild(starParticle);
}
}
});
// Add star color transition from gold to bright white
tween(starParticle, {
tint: 0xFFFFFF // Bright white
}, {
duration: 350,
easing: tween.easeInOut
});
}
};
self.createLavaMelting = function () {
var lavaCount = 12;
var meltRadius = 130;
for (var i = 0; i < lavaCount; i++) {
var angle = i / lavaCount * Math.PI * 2;
var lavaParticle = LK.getAsset('lavaParticle', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial position at gem center
lavaParticle.x = self.x;
lavaParticle.y = self.y;
lavaParticle.zIndex = 5;
// Add molten orange-red tint and initial scale
lavaParticle.tint = 0xFF4500;
lavaParticle.scaleX = 0.6;
lavaParticle.scaleY = 0.6;
lavaParticle.rotation = Math.random() * Math.PI * 2;
// Add to game
if (self.parent) {
self.parent.addChild(lavaParticle);
}
// Create melting downward movement with spiral motion
var radius = 70 + Math.random() * 60;
var targetX = self.x + Math.cos(angle) * radius;
var targetY = self.y + Math.sin(angle) * radius;
// Add downward melting bias
targetY += Math.random() * 80 + 40;
// Add some randomness for natural lava flow
targetX += (Math.random() - 0.5) * 50;
// Create lava melting movement animation
tween(lavaParticle, {
x: targetX,
y: targetY,
rotation: angle + Math.PI * 3,
// 1.5 full rotations
scaleX: 3.2,
scaleY: 3.2
}, {
duration: 550,
easing: tween.easeOut
});
// Add viscous lava dripping effect
tween(lavaParticle, {
y: targetY + 60,
scaleY: 4.0 // Stretch vertically like dripping lava
}, {
duration: 400,
easing: tween.easeIn
});
// Create molten glow pulsing effect
tween(lavaParticle, {
scaleX: 4.0,
tint: 0xFF6600 // Brighter orange
}, {
duration: 300,
easing: tween.easeInOut
});
// Cool down and solidify effect
tween(lavaParticle, {
alpha: 0,
scaleX: 0.4,
scaleY: 0.2,
tint: 0x8B0000,
// Dark red when cooling
rotation: angle + Math.PI * 5 // Continue rotating as it cools
}, {
duration: 650,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (lavaParticle.parent) {
lavaParticle.parent.removeChild(lavaParticle);
}
}
});
// Add secondary heat shimmer effect
tween(lavaParticle, {
scaleX: 2.8,
scaleY: 3.5
}, {
duration: 200,
easing: tween.easeInOut
});
}
};
return self;
});
var Particle = Container.expand(function (x, y, color) {
var self = Container.call(this);
var particleGraphics = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
// Set particle color
particleGraphics.tint = color;
// Set initial position
self.x = x;
self.y = y;
// Random velocity - increased for more dramatic effect
self.velocityX = (Math.random() - 0.5) * 500;
self.velocityY = (Math.random() - 0.5) * 500;
self.gravity = 800;
self.life = 1.5;
self.fadeSpeed = 1.0;
self.scale = 1.0;
self.scaleSpeed = 0.5;
// Add initial scale animation
tween(self, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut
});
self.update = function () {
// Update position based on velocity
self.x += self.velocityX * (1 / 60);
self.y += self.velocityY * (1 / 60);
// Apply gravity (positive for flipped board - particles fall down visually)
self.velocityY += self.gravity * (1 / 60);
// Fade out over time
self.life -= self.fadeSpeed * (1 / 60);
self.alpha = Math.max(0, self.life);
// Scale down over time for dramatic effect
self.scale -= self.scaleSpeed * (1 / 60);
if (self.scale > 0) {
self.scaleX = self.scale;
self.scaleY = self.scale;
}
// Remove when fully faded
if (self.life <= 0) {
self.destroy();
}
};
self.destroy = function () {
if (self.parent) {
self.parent.removeChild(self);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2C1810
});
/****
* Game Code
****/
var GRID_SIZE_X = 6;
var GRID_SIZE_Y = 9;
var CELL_SIZE = 170;
var BOARD_OFFSET_X = (2048 - GRID_SIZE_X * CELL_SIZE) / 2;
var BOARD_OFFSET_Y = (2732 - GRID_SIZE_Y * CELL_SIZE) / 2 + 600;
var gameBoard = [];
var selectedGem = null;
var draggedGem = null;
var dragStartPos = null;
var isDragging = false;
var isSwapping = false;
var score = 0;
var comboMultiplier = 1;
var consecutiveGemsDestroyed = 0;
var fireworkThreshold = 6;
// Timer system - 7.5 minutes in seconds
var gameTimeLimit = 7.5 * 60; // 7.5 minutes = 450 seconds
var remainingTime = gameTimeLimit;
var gameTimer = null;
// Create brown background (scrolling)
var k1 = game.attachAsset('brownBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
k1.zIndex = 1;
// Create second brown background positioned directly above the first one
var k1_second = game.attachAsset('brownBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 - 2732
});
k1_second.zIndex = 1;
// Variables to track background animation state
var backgroundSpeed = 2;
var backgroundY1 = 2732 / 2;
var backgroundY2 = 2732 / 2 - 2732;
// Function to update background positions
function updateBackgrounds() {
// Move both backgrounds down
backgroundY1 += backgroundSpeed;
backgroundY2 += backgroundSpeed;
k1.y = backgroundY1;
k1_second.y = backgroundY2;
// Reset positions when they move off screen
if (backgroundY1 > 2732 / 2 + 2732) {
backgroundY1 = backgroundY2 - 2732;
}
if (backgroundY2 > 2732 / 2 + 2732) {
backgroundY2 = backgroundY1 - 2732;
}
}
// Create board background
var boardBg = game.attachAsset('boardBg', {
anchorX: 0.5,
anchorY: 1.0,
x: 2048 / 2,
y: 2732 + 850
});
boardBg.zIndex = 2;
// Update timer display
function updateTimer() {
var minutes = Math.floor(remainingTime / 60);
var seconds = remainingTime % 60;
var timeText = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
timerTxt.setText(timeText);
}
// Update health bar display
function updateHealthBar() {
// Calculate how many segments should be visible (10 segments total)
var visibleSegments = Math.ceil(playerHealth / 10);
// Update health text
healthTxt.setText('HEALTH: ' + playerHealth);
// Remove segments that should no longer be visible
for (var i = 0; i < healthSegments.length; i++) {
var segment = healthSegments[i];
if (i >= visibleSegments && segment.parent) {
// Animate segment removal with cutting effect
tween(segment, {
scaleX: 0,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function (segmentToRemove) {
return function () {
if (segmentToRemove.parent) {
segmentToRemove.parent.removeChild(segmentToRemove);
}
};
}(segment)
});
}
}
// Add back segments that should be visible but are missing
for (var i = 0; i < visibleSegments; i++) {
var segment = healthSegments[i];
if (segment && !segment.parent) {
// Re-add the segment to the GUI
segment.scaleX = 1;
segment.alpha = 1;
segment.tint = 0x00ff00;
LK.gui.top.addChild(segment);
// Animate segment restoration with growing effect
tween(segment, {
scaleX: 1,
alpha: 1
}, {
duration: 300,
easing: tween.easeOut
});
}
}
// Flash remaining segments red when taking damage
if (playerHealth < maxHealth) {
for (var j = 0; j < visibleSegments; j++) {
if (healthSegments[j] && healthSegments[j].parent) {
tween(healthSegments[j], {
tint: 0xff6666
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function (segmentToFlash) {
return function () {
tween(segmentToFlash, {
tint: 0x00ff00
}, {
duration: 200,
easing: tween.easeInOut
});
};
}(healthSegments[j])
});
}
}
}
}
// Reduce player health
function reduceHealth(amount) {
playerHealth = Math.max(0, playerHealth - amount);
updateHealthBar();
// Flash screen red when taking damage
LK.effects.flashScreen(0xff0000, 300);
// Check for game over
if (playerHealth <= 0) {
LK.showGameOver();
}
}
// Create explosion effect at gem position
function createExplosion(x, y, gemColor) {
var particleCount = 15;
var gemColors = [0xff2222, 0x2222ff, 0x22ff22, 0xffff22, 0xff22ff, 0xff6622];
var color = gemColors[gemColor] || 0xffffff;
for (var i = 0; i < particleCount; i++) {
var particle = new Particle(x, y, color);
game.addChild(particle);
}
}
// Create firework celebration
function createFirework() {
// Create single firework in center upper position
var fireworkX = BOARD_OFFSET_X + GRID_SIZE_X * CELL_SIZE / 2 - 150 + Math.random() * 300;
var fireworkY = BOARD_OFFSET_Y - 500 + Math.random() * 200;
var firework = new Firework(fireworkX, fireworkY);
game.addChild(firework);
// Start explosion immediately
LK.setTimeout(function () {
firework.explode();
}, 100);
}
// Create board frame
var C1 = game.attachAsset('boardFrame', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 125
});
C1.zIndex = 3;
// Create score display
var scoreTxt = new Text2('SCORE: 0', {
size: 60,
fill: 0x000000
});
scoreTxt.anchor.set(0.5, 0);
// Add red shadow effect and red border stroke
if (scoreTxt.style) {
scoreTxt.style.dropShadow = true;
scoreTxt.style.dropShadowColor = "#ff0000";
scoreTxt.style.dropShadowBlur = 3;
scoreTxt.style.dropShadowDistance = 3;
scoreTxt.style.stroke = "#ff0000";
scoreTxt.style.strokeThickness = 4;
}
LK.gui.top.addChild(scoreTxt);
scoreTxt.y = 100;
// Create website text 300px below score
var websiteTxt = new Text2('vayabi.net', {
size: 50,
fill: 0x000000
});
websiteTxt.anchor.set(0.5, 0);
// Add styling for better visibility and make bold
if (websiteTxt.style) {
websiteTxt.style.fontWeight = "bold";
websiteTxt.style.dropShadow = true;
websiteTxt.style.dropShadowColor = "#ff0000";
websiteTxt.style.dropShadowBlur = 3;
websiteTxt.style.dropShadowDistance = 3;
websiteTxt.style.stroke = "#ff0000";
websiteTxt.style.strokeThickness = 2;
}
LK.gui.top.addChild(websiteTxt);
websiteTxt.y = 400; // 100 + 300 = 400 (moved 300px up from original 700)
// Create health system
var playerHealth = 100;
var maxHealth = 100;
// Create health bar background (red)
var healthBarBg = LK.getAsset('healthBarBg', {
anchorX: 0,
anchorY: 0.5
});
healthBarBg.x = 150;
healthBarBg.y = 200;
LK.gui.top.addChild(healthBarBg);
// Create segmented health bar with 10 individual pieces
var healthSegments = [];
var segmentWidth = 40; // Each segment is 40px wide (400px total / 10 segments)
for (var i = 0; i < 10; i++) {
var healthSegment = LK.getAsset('healthBarFg', {
anchorX: 0,
anchorY: 0.5
});
healthSegment.width = segmentWidth;
healthSegment.x = 150 + i * segmentWidth;
healthSegment.y = 200;
healthSegment.segmentIndex = i;
healthSegments.push(healthSegment);
LK.gui.top.addChild(healthSegment);
}
// Create health text
var healthTxt = new Text2('HEALTH: 100', {
size: 50,
fill: 0x000000
});
// Make text bold and add styling for better visibility
if (healthTxt.style) {
healthTxt.style.fontWeight = "bold";
healthTxt.style.stroke = "#ffffff";
healthTxt.style.strokeThickness = 2;
}
healthTxt.anchor.set(0, 0.5);
healthTxt.x = 150;
healthTxt.y = 250;
LK.gui.top.addChild(healthTxt);
// Create timer display
var timerTxt = new Text2('7:30', {
size: 60,
fill: 0x000000
});
// Make timer text bold and add styling for better visibility
if (timerTxt.style) {
timerTxt.style.fontWeight = "bold";
timerTxt.style.stroke = "#ffffff";
timerTxt.style.strokeThickness = 2;
timerTxt.style.dropShadow = true;
timerTxt.style.dropShadowColor = "#ff0000";
timerTxt.style.dropShadowBlur = 3;
timerTxt.style.dropShadowDistance = 3;
}
timerTxt.anchor.set(0, 1); // Anchor to bottom-left for positioning
LK.gui.bottomLeft.addChild(timerTxt);
timerTxt.x = 160; // Move slightly to the right
timerTxt.y = -900; // Move 35 squares (700px) up from previous position
timerTxt.rotation = 340 * Math.PI / 180; // Point towards 340 degrees
// Initialize empty board
function initializeBoard() {
gameBoard = [];
for (var x = 0; x < GRID_SIZE_X; x++) {
gameBoard[x] = [];
for (var y = 0; y < GRID_SIZE_Y; y++) {
gameBoard[x][y] = null;
}
}
}
// Fill board with random gems
function fillBoard() {
for (var x = 0; x < GRID_SIZE_X; x++) {
for (var y = 0; y < GRID_SIZE_Y; y++) {
if (!gameBoard[x][y]) {
var gemType = Math.floor(Math.random() * 6);
var gem = new Gem(gemType, x, y);
var worldPos = gridToWorld(x, y);
gem.x = worldPos.x;
gem.y = worldPos.y;
gem.zIndex = 4;
gameBoard[x][y] = gem;
game.addChild(gem);
}
}
}
}
// Convert grid coordinates to world coordinates
function gridToWorld(gridX, gridY) {
return {
x: BOARD_OFFSET_X + gridX * CELL_SIZE + CELL_SIZE / 2,
y: BOARD_OFFSET_Y + (GRID_SIZE_Y - 1 - gridY) * CELL_SIZE + CELL_SIZE / 2
};
}
// Convert world coordinates to grid coordinates
function worldToGrid(worldX, worldY) {
var gridX = Math.floor((worldX - BOARD_OFFSET_X) / CELL_SIZE);
var gridY = GRID_SIZE_Y - 1 - Math.floor((worldY - BOARD_OFFSET_Y) / CELL_SIZE);
if (gridX < 0 || gridX >= GRID_SIZE_X || gridY < 0 || gridY >= GRID_SIZE_Y) {
return null;
}
return {
x: gridX,
y: gridY
};
}
// Check if two grid positions are adjacent
function areAdjacent(pos1, pos2) {
var dx = Math.abs(pos1.x - pos2.x);
var dy = Math.abs(pos1.y - pos2.y);
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
}
// Check for matches starting at position (2x2 squares and 3-in-a-row)
function checkMatches(startX, startY) {
var gem = gameBoard[startX][startY];
if (!gem) return [];
var matches = [];
var gemType = gem.gemType;
// Check horizontal 3-in-a-row matches
var horizontalCount = 1;
var horizontalGems = [gem];
// Check right direction
for (var x = startX + 1; x < GRID_SIZE_X; x++) {
var rightGem = gameBoard[x][startY];
if (rightGem && rightGem.gemType === gemType) {
horizontalCount++;
horizontalGems.push(rightGem);
} else {
break;
}
}
// Check left direction
for (var x = startX - 1; x >= 0; x--) {
var leftGem = gameBoard[x][startY];
if (leftGem && leftGem.gemType === gemType) {
horizontalCount++;
horizontalGems.unshift(leftGem);
} else {
break;
}
}
// Add horizontal matches if 3 or more
if (horizontalCount >= 3) {
for (var h = 0; h < horizontalGems.length; h++) {
matches.push(horizontalGems[h]);
}
}
// Check vertical 3-in-a-row matches
var verticalCount = 1;
var verticalGems = [gem];
// Check up direction
for (var y = startY + 1; y < GRID_SIZE_Y; y++) {
var upGem = gameBoard[startX][y];
if (upGem && upGem.gemType === gemType) {
verticalCount++;
verticalGems.push(upGem);
} else {
break;
}
}
// Check down direction
for (var y = startY - 1; y >= 0; y--) {
var downGem = gameBoard[startX][y];
if (downGem && downGem.gemType === gemType) {
verticalCount++;
verticalGems.unshift(downGem);
} else {
break;
}
}
// Add vertical matches if 3 or more
if (verticalCount >= 3) {
for (var v = 0; v < verticalGems.length; v++) {
var found = false;
for (var m = 0; m < matches.length; m++) {
if (matches[m] === verticalGems[v]) {
found = true;
break;
}
}
if (!found) {
matches.push(verticalGems[v]);
}
}
}
// Check if this gem is part of a 2x2 square
// Check all possible 2x2 squares this gem could be part of
var squarePositions = [
// Top-left corner
[{
x: startX,
y: startY
}, {
x: startX + 1,
y: startY
}, {
x: startX,
y: startY + 1
}, {
x: startX + 1,
y: startY + 1
}],
// Top-right corner
[{
x: startX - 1,
y: startY
}, {
x: startX,
y: startY
}, {
x: startX - 1,
y: startY + 1
}, {
x: startX,
y: startY + 1
}],
// Bottom-left corner
[{
x: startX,
y: startY - 1
}, {
x: startX + 1,
y: startY - 1
}, {
x: startX,
y: startY
}, {
x: startX + 1,
y: startY
}],
// Bottom-right corner
[{
x: startX - 1,
y: startY - 1
}, {
x: startX,
y: startY - 1
}, {
x: startX - 1,
y: startY
}, {
x: startX,
y: startY
}]];
for (var s = 0; s < squarePositions.length; s++) {
var square = squarePositions[s];
var validSquare = true;
var squareGems = [];
// Check if all positions in this square are valid and contain same gem type
for (var p = 0; p < square.length; p++) {
var pos = square[p];
if (pos.x < 0 || pos.x >= GRID_SIZE_X || pos.y < 0 || pos.y >= GRID_SIZE_Y) {
validSquare = false;
break;
}
var squareGem = gameBoard[pos.x][pos.y];
if (!squareGem || squareGem.gemType !== gemType) {
validSquare = false;
break;
}
squareGems.push(squareGem);
}
// If we found a valid 2x2 square, add all gems to matches
if (validSquare) {
for (var g = 0; g < squareGems.length; g++) {
var found = false;
for (var m = 0; m < matches.length; m++) {
if (matches[m] === squareGems[g]) {
found = true;
break;
}
}
if (!found) {
matches.push(squareGems[g]);
}
}
}
}
return matches;
}
// Find all matches on the board
function findAllMatches() {
var allMatches = [];
var processedGems = [];
for (var x = 0; x < GRID_SIZE_X; x++) {
for (var y = 0; y < GRID_SIZE_Y; y++) {
if (gameBoard[x][y]) {
var matches = checkMatches(x, y);
for (var i = 0; i < matches.length; i++) {
var gem = matches[i];
var found = false;
for (var j = 0; j < processedGems.length; j++) {
if (processedGems[j] === gem) {
found = true;
break;
}
}
if (!found) {
allMatches.push(gem);
processedGems.push(gem);
}
}
}
}
}
return allMatches;
}
// Clear matched gems
function clearMatches(matches) {
if (matches.length === 0) return;
LK.getSound('match').play();
var points = matches.length * 10 * comboMultiplier;
var oldScore = score;
score += points;
scoreTxt.setText('SCORE: ' + score);
// Health gain system - check if player should gain health every 1500 points
var oldHealthGainLevel = Math.floor(oldScore / 1500);
var newHealthGainLevel = Math.floor(score / 1500);
if (newHealthGainLevel > oldHealthGainLevel && playerHealth < maxHealth) {
// Player gains health, but don't exceed max health
var healthToGain = Math.min(10, maxHealth - playerHealth);
playerHealth += healthToGain;
updateHealthBar();
// Flash screen green to show health gain
LK.effects.flashScreen(0x00ff00, 500);
}
// Check for win condition - player reaches 10000 points
if (score >= 10000) {
LK.clearInterval(gameTimer);
LK.showYouWin();
return;
}
// Track consecutive adjacent destroyed gems and trigger fireworks
if (matches.length >= fireworkThreshold) {
// Check if all gems are adjacent to each other (consecutive)
var areConsecutive = true;
if (matches.length >= fireworkThreshold) {
// Sort matches by position to check adjacency
for (var i = 0; i < matches.length - 1; i++) {
var currentGem = matches[i];
var hasAdjacentMatch = false;
for (var j = i + 1; j < matches.length; j++) {
var nextGem = matches[j];
var dx = Math.abs(currentGem.gridX - nextGem.gridX);
var dy = Math.abs(currentGem.gridY - nextGem.gridY);
if (dx === 1 && dy === 0 || dx === 0 && dy === 1) {
hasAdjacentMatch = true;
break;
}
}
if (!hasAdjacentMatch && i < matches.length - 1) {
areConsecutive = false;
break;
}
}
}
if (areConsecutive && matches.length >= fireworkThreshold) {
// Create firework celebration
createFirework();
}
}
for (var i = 0; i < matches.length; i++) {
var gem = matches[i];
// Create explosion effect at gem position
createExplosion(gem.x, gem.y, gem.gemType);
gameBoard[gem.gridX][gem.gridY] = null;
gem.destroy();
}
// After clearing gems, apply gravity and fill empty spaces
LK.setTimeout(function () {
// Apply gravity repeatedly until no more gems fall
function applyGravityRepeatedly() {
var moved = applyGravity();
if (moved) {
LK.setTimeout(applyGravityRepeatedly, 100);
} else {
fillEmptySpaces();
// Check for new matches after falling gems settle
LK.setTimeout(function () {
var newMatches = findAllMatches();
if (newMatches.length > 0) {
comboMultiplier++;
clearMatches(newMatches);
} else {
comboMultiplier = 1;
consecutiveGemsDestroyed = 0; // Reset counter when no more matches
isSwapping = false;
}
}, 500);
}
}
applyGravityRepeatedly();
}, 200);
}
// Apply gravity to make gems fall
function applyGravity() {
var moved = false;
for (var x = 0; x < GRID_SIZE_X; x++) {
// Process each column from top to bottom (since board is flipped, gems fall upward)
var writeIndex = 0;
for (var y = 0; y < GRID_SIZE_Y; y++) {
if (gameBoard[x][y]) {
// If gem is not at the write position, move it up
if (y !== writeIndex) {
var gem = gameBoard[x][y];
gameBoard[x][writeIndex] = gem;
gameBoard[x][y] = null;
gem.setGridPosition(x, writeIndex);
var worldPos = gridToWorld(x, writeIndex);
gem.animateToPosition(worldPos.x, worldPos.y, 300);
moved = true;
}
writeIndex++;
}
}
}
return moved;
}
// Fill empty spaces with new gems
function fillEmptySpaces() {
for (var x = 0; x < GRID_SIZE_X; x++) {
for (var y = 0; y < GRID_SIZE_Y; y++) {
if (!gameBoard[x][y]) {
var gemType = Math.floor(Math.random() * 6);
var gem = new Gem(gemType, x, y);
var worldPos = gridToWorld(x, y);
gem.x = worldPos.x;
gem.y = BOARD_OFFSET_Y - CELL_SIZE * 2; // Start from above the top line of the flipped grid
gem.zIndex = 4;
gameBoard[x][y] = gem;
game.addChild(gem);
gem.animateToPosition(worldPos.x, worldPos.y, 150 + y * 30); // Faster staggered animation timing for flipped board
}
}
}
}
// Swap two gems
function swapGems(gem1, gem2) {
if (isSwapping) return;
isSwapping = true;
// Store original positions
var originalGem1X = gem1.gridX;
var originalGem1Y = gem1.gridY;
var originalGem2X = gem2.gridX;
var originalGem2Y = gem2.gridY;
// Update grid positions
gem1.setGridPosition(gem2.gridX, gem2.gridY);
gem2.setGridPosition(originalGem1X, originalGem1Y);
gameBoard[gem1.gridX][gem1.gridY] = gem1;
gameBoard[gem2.gridX][gem2.gridY] = gem2;
var pos1 = gridToWorld(gem1.gridX, gem1.gridY);
var pos2 = gridToWorld(gem2.gridX, gem2.gridY);
// Animate the swap
gem1.animateToPosition(pos1.x, pos1.y, 200);
gem2.animateToPosition(pos2.x, pos2.y, 200);
LK.setTimeout(function () {
var matches = findAllMatches();
if (matches.length > 0) {
// Valid swap - process matches
clearMatches(matches);
} else {
// Invalid move - reduce health by 10 and animate back to original positions
reduceHealth(10);
gem1.setGridPosition(originalGem1X, originalGem1Y);
gem2.setGridPosition(originalGem2X, originalGem2Y);
gameBoard[gem1.gridX][gem1.gridY] = gem1;
gameBoard[gem2.gridX][gem2.gridY] = gem2;
var pos1 = gridToWorld(gem1.gridX, gem1.gridY);
var pos2 = gridToWorld(gem2.gridX, gem2.gridY);
gem1.animateToPosition(pos1.x, pos1.y, 200);
gem2.animateToPosition(pos2.x, pos2.y, 200);
LK.setTimeout(function () {
isSwapping = false;
}, 200);
}
}, 200);
}
// Game input handling
game.down = function (x, y, obj) {
if (isSwapping) return;
var gridPos = worldToGrid(x, y);
if (!gridPos) return;
var clickedGem = gameBoard[gridPos.x][gridPos.y];
if (!clickedGem) return;
draggedGem = clickedGem;
dragStartPos = {
x: x,
y: y
};
isDragging = true;
draggedGem.alpha = 0.7;
};
game.move = function (x, y, obj) {
if (!isDragging || !draggedGem || isSwapping) return;
// Calculate drag distance
var dragDeltaX = x - dragStartPos.x;
var dragDeltaY = y - dragStartPos.y;
var dragDistance = Math.sqrt(dragDeltaX * dragDeltaX + dragDeltaY * dragDeltaY);
// Only process if drag distance is significant enough
if (dragDistance > CELL_SIZE * 0.3) {
// Determine drag direction
var targetGridX = draggedGem.gridX;
var targetGridY = draggedGem.gridY;
if (Math.abs(dragDeltaX) > Math.abs(dragDeltaY)) {
// Horizontal drag
if (dragDeltaX > 0) {
targetGridX = Math.min(GRID_SIZE_X - 1, draggedGem.gridX + 1);
} else {
targetGridX = Math.max(0, draggedGem.gridX - 1);
}
} else {
// Vertical drag (flipped coordinate system)
if (dragDeltaY > 0) {
targetGridY = Math.max(0, draggedGem.gridY - 1);
} else {
targetGridY = Math.min(GRID_SIZE_Y - 1, draggedGem.gridY + 1);
}
}
// Update visual position to show intended swap
var targetWorldPos = gridToWorld(targetGridX, targetGridY);
draggedGem.x = targetWorldPos.x;
draggedGem.y = targetWorldPos.y;
// Store target position for later use
draggedGem.targetGridX = targetGridX;
draggedGem.targetGridY = targetGridY;
}
};
game.up = function (x, y, obj) {
if (!isDragging || !draggedGem || isSwapping) return;
var startGridPos = {
x: draggedGem.gridX,
y: draggedGem.gridY
};
// Check if we have a target position from dragging
if (draggedGem.targetGridX !== undefined && draggedGem.targetGridY !== undefined) {
var targetGridPos = {
x: draggedGem.targetGridX,
y: draggedGem.targetGridY
};
// Check if target position is valid and adjacent
if (areAdjacent(startGridPos, targetGridPos) && gameBoard[targetGridPos.x][targetGridPos.y]) {
var targetGem = gameBoard[targetGridPos.x][targetGridPos.y];
// Attempt swap
swapGems(draggedGem, targetGem);
} else {
// Invalid swap - animate back to original position
var worldPos = gridToWorld(draggedGem.gridX, draggedGem.gridY);
draggedGem.animateToPosition(worldPos.x, worldPos.y, 200);
}
// Clean up target position
draggedGem.targetGridX = undefined;
draggedGem.targetGridY = undefined;
} else {
// No significant drag - just return to original position
var worldPos = gridToWorld(draggedGem.gridX, draggedGem.gridY);
draggedGem.animateToPosition(worldPos.x, worldPos.y, 200);
}
draggedGem.alpha = 1.0;
draggedGem = null;
dragStartPos = null;
isDragging = false;
};
// Create grid lines around gems
function createGridLines() {
// Create vertical lines
for (var x = 0; x <= GRID_SIZE_X; x++) {
for (var lineY = 0; lineY < GRID_SIZE_Y * CELL_SIZE; lineY += 20) {
var verticalLine = LK.getAsset('particle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.05,
scaleY: 0.5
});
verticalLine.tint = 0x8B4513;
verticalLine.x = BOARD_OFFSET_X + x * CELL_SIZE;
verticalLine.y = BOARD_OFFSET_Y + lineY + 10;
verticalLine.zIndex = 3;
game.addChild(verticalLine);
}
}
// Create horizontal lines
for (var y = 0; y <= GRID_SIZE_Y; y++) {
for (var lineX = 0; lineX < GRID_SIZE_X * CELL_SIZE; lineX += 20) {
var horizontalLine = LK.getAsset('particle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.05
});
horizontalLine.tint = 0x8B4513;
horizontalLine.x = BOARD_OFFSET_X + lineX + 10;
horizontalLine.y = BOARD_OFFSET_Y + y * CELL_SIZE;
horizontalLine.zIndex = 3;
game.addChild(horizontalLine);
}
}
}
// Initialize the game
initializeBoard();
fillBoard();
createGridLines();
// Add game update loop for continuous animations
game.update = function () {
// Update scrolling backgrounds
updateBackgrounds();
// Add gentle rotation animation to all gems
for (var x = 0; x < GRID_SIZE_X; x++) {
for (var y = 0; y < GRID_SIZE_Y; y++) {
var gem = gameBoard[x][y];
if (gem && !gem.isAnimating && !gem.rotationStarted) {
// Create gentle rotation effect
gem.rotationStarted = true;
var delay = (x + y) * 30; // Stagger the rotation animation
LK.setTimeout(function (currentGem) {
return function () {
function createRotation() {
if (currentGem.parent && !currentGem.isAnimating) {
var currentRotation = currentGem.rotation || 0;
tween(currentGem, {
rotation: currentRotation + Math.PI * 2
}, {
duration: 6000,
easing: tween.linear,
onFinish: function onFinish() {
if (currentGem.parent && !currentGem.isAnimating) {
createRotation(); // Continue rotating
}
}
});
}
}
createRotation();
};
}(gem), delay);
}
}
}
};
// Start countdown timer
gameTimer = LK.setInterval(function () {
remainingTime--;
updateTimer();
// Check if time is running out (last 30 seconds) - flash timer red
if (remainingTime <= 30 && remainingTime > 0) {
tween(timerTxt, {
tint: 0xff0000
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(timerTxt, {
tint: 0x000000
}, {
duration: 500,
easing: tween.easeInOut
});
}
});
}
// Game over when time runs out
if (remainingTime <= 0) {
LK.clearInterval(gameTimer);
LK.showGameOver();
}
}, 1000); // Update every 1000ms (1 second)
// Create logo in top-right corner
var logo = LK.getAsset('logo', {
anchorX: 1,
anchorY: 0,
scaleX: 0.15,
scaleY: 0.15
});
logo.x = -50;
logo.y = 0;
logo.zIndex = 15;
LK.gui.topRight.addChild(logo);
// Add logo glow effect
tween(logo, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(logo, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 1500,
easing: tween.easeInOut
});
}
});
// Start background music
LK.playMusic('background');
// Initialize timer display
updateTimer();
// Initial match clearing
LK.setTimeout(function () {
var matches = findAllMatches();
if (matches.length > 0) {
clearMatches(matches);
}
}, 100); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Firework = Container.expand(function (x, y) {
var self = Container.call(this);
var fireworkGraphics = self.attachAsset('firework', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = x;
self.y = y;
self.zIndex = 10;
// Initial scale
fireworkGraphics.scaleX = 0.1;
fireworkGraphics.scaleY = 0.1;
fireworkGraphics.alpha = 0.8;
self.explode = function () {
// Play firework sound
LK.getSound('fireworkBurst').play();
// Main explosion animation - smaller size
tween(fireworkGraphics, {
scaleX: 5.0,
scaleY: 5.0,
rotation: Math.PI * 3
}, {
duration: 1000,
easing: tween.easeOut
});
// Fade out effect
tween(fireworkGraphics, {
alpha: 0
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
// Create smaller colorful particle burst
var particleCount = 25;
var colors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff, 0x00ffff, 0xffffff, 0xff8000];
for (var i = 0; i < particleCount; i++) {
var angle = i / particleCount * Math.PI * 2;
var radius = 120 + Math.random() * 100;
var targetX = self.x + Math.cos(angle) * radius;
var targetY = self.y + Math.sin(angle) * radius;
var color = colors[Math.floor(Math.random() * colors.length)];
var sparkle = new Particle(self.x, self.y, color);
sparkle.velocityX = Math.cos(angle) * 300;
sparkle.velocityY = Math.sin(angle) * 300;
sparkle.life = 2.5;
sparkle.scaleSpeed = 0.2;
// Make particles smaller
sparkle.scaleX = 1.5;
sparkle.scaleY = 1.5;
if (self.parent) {
self.parent.addChild(sparkle);
}
}
};
return self;
});
var Gem = Container.expand(function (gemType, gridX, gridY) {
var self = Container.call(this);
self.gemType = gemType;
self.gridX = gridX;
self.gridY = gridY;
self.isAnimating = false;
var gemAssets = ['gemRed', 'gemBlue', 'gemGreen', 'gemYellow', 'gemPurple', 'gemOrange'];
var gemGraphics = self.attachAsset(gemAssets[gemType], {
anchorX: 0.5,
anchorY: 0.5
});
self.setGridPosition = function (x, y) {
self.gridX = x;
self.gridY = y;
};
self.animateToPosition = function (targetX, targetY, duration, onComplete) {
if (!duration) duration = 200;
self.isAnimating = true;
tween(self, {
x: targetX,
y: targetY
}, {
duration: duration,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isAnimating = false;
if (onComplete) onComplete();
}
});
};
self.destroy = function () {
// Play gem-specific destruction sound
var gemSounds = ['gemRedDestroy', 'gemBlueDestroy', 'gemGreenDestroy', 'gemYellowDestroy', 'gemPurpleDestroy', 'gemOrangeDestroy'];
if (self.gemType >= 0 && self.gemType < gemSounds.length) {
try {
LK.getSound(gemSounds[self.gemType]).play();
} catch (e) {
console.log('Sound not found:', gemSounds[self.gemType]);
}
}
// Special animation for red gems (gemType 0)
if (self.gemType === 0) {
self.createFlameExplosion();
}
// Special animation for blue gems (gemType 1)
if (self.gemType === 1) {
self.createWaterExplosion();
}
// Special animation for green gems (gemType 2)
if (self.gemType === 2) {
self.createLeafSpiral();
}
// Special animation for yellow gems (gemType 3)
if (self.gemType === 3) {
self.createStarExplosion();
}
// Special animation for purple gems (gemType 4)
if (self.gemType === 4) {
self.createPurpleImplosion();
}
// Special animation for orange gems (gemType 5)
if (self.gemType === 5) {
self.createLavaMelting();
}
// Create tween effect before actually destroying
tween(self, {
alpha: 0,
scaleX: 0.2,
scaleY: 0.2
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
};
self.createFlameExplosion = function () {
var flameCount = 8;
var radius = 100;
for (var i = 0; i < flameCount; i++) {
var angle = i / flameCount * Math.PI * 2;
var flameParticle = LK.getAsset('flameParticle', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial position at gem center
flameParticle.x = self.x;
flameParticle.y = self.y;
flameParticle.zIndex = 5;
// Add to game
if (self.parent) {
self.parent.addChild(flameParticle);
}
// Calculate spiral target position
var targetX = self.x + Math.cos(angle) * radius;
var targetY = self.y + Math.sin(angle) * radius;
// Create spiral rotation and movement animation
tween(flameParticle, {
x: targetX,
y: targetY,
rotation: Math.PI * 4,
// 2 full rotations
scaleX: 3.0,
scaleY: 3.0
}, {
duration: 400,
easing: tween.easeOut
});
// Fade out and disappear
tween(flameParticle, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (flameParticle.parent) {
flameParticle.parent.removeChild(flameParticle);
}
}
});
// Add flame color transition from orange to red
tween(flameParticle, {
tint: 0xff0000
}, {
duration: 300,
easing: tween.easeInOut
});
}
};
self.createWaterExplosion = function () {
var dropletCount = 12;
var maxRadius = 120;
for (var i = 0; i < dropletCount; i++) {
var angle = i / dropletCount * Math.PI * 2;
var waterDroplet = LK.getAsset('waterDroplet', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial position at gem center
waterDroplet.x = self.x;
waterDroplet.y = self.y;
waterDroplet.zIndex = 5;
// Random radius for scattered effect
var radius = 60 + Math.random() * 60;
// Add to game
if (self.parent) {
self.parent.addChild(waterDroplet);
}
// Calculate target position for water droplet
var targetX = self.x + Math.cos(angle) * radius;
var targetY = self.y + Math.sin(angle) * radius;
// Create bouncing water droplet movement
tween(waterDroplet, {
x: targetX,
y: targetY,
scaleX: 2.5,
scaleY: 2.5
}, {
duration: 350,
easing: tween.bounceOut
});
// Create ripple effect with scaling
tween(waterDroplet, {
scaleX: 0.8,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeInOut
});
// Fade out like evaporating water
tween(waterDroplet, {
alpha: 0,
scaleY: 0.2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (waterDroplet.parent) {
waterDroplet.parent.removeChild(waterDroplet);
}
}
});
// Add water color transition from light blue to transparent
tween(waterDroplet, {
tint: 0x00aaff
}, {
duration: 250,
easing: tween.easeInOut
});
}
};
self.createPurpleImplosion = function () {
var vortexCount = 16;
var initialRadius = 150;
for (var i = 0; i < vortexCount; i++) {
var angle = i / vortexCount * Math.PI * 2;
var purpleVortex = LK.getAsset('purpleVortex', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial position in a circle around the gem
var startX = self.x + Math.cos(angle) * initialRadius;
var startY = self.y + Math.sin(angle) * initialRadius;
purpleVortex.x = startX;
purpleVortex.y = startY;
purpleVortex.zIndex = 5;
// Add purple tint and initial scale
purpleVortex.tint = 0x8B00FF;
purpleVortex.scaleX = 0.5;
purpleVortex.scaleY = 0.5;
// Add to game
if (self.parent) {
self.parent.addChild(purpleVortex);
}
// Create spiral inward movement animation
tween(purpleVortex, {
x: self.x,
y: self.y,
rotation: Math.PI * 6,
// 3 full rotations inward
scaleX: 3.5,
scaleY: 3.5
}, {
duration: 600,
easing: tween.easeIn
});
// Fade in then fade out with implosion effect
tween(purpleVortex, {
alpha: 1.0
}, {
duration: 200,
easing: tween.easeOut
});
// Final implosion - scale down and disappear
tween(purpleVortex, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (purpleVortex.parent) {
purpleVortex.parent.removeChild(purpleVortex);
}
}
});
// Color transition from purple to dark purple
tween(purpleVortex, {
tint: 0x4B0082
}, {
duration: 300,
easing: tween.easeInOut
});
}
};
self.createLeafSpiral = function () {
var leafCount = 10;
var spiralRadius = 140;
for (var i = 0; i < leafCount; i++) {
var angle = i / leafCount * Math.PI * 2;
var leafParticle = LK.getAsset('leafParticle', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial position at gem center
leafParticle.x = self.x;
leafParticle.y = self.y;
leafParticle.zIndex = 5;
// Add natural green tint and initial scale
leafParticle.tint = 0x228B22;
leafParticle.scaleX = 0.8;
leafParticle.scaleY = 0.8;
leafParticle.rotation = angle; // Initial rotation based on position
// Add to game
if (self.parent) {
self.parent.addChild(leafParticle);
}
// Create spiral outward movement with natural floating motion
var targetX = self.x + Math.cos(angle) * spiralRadius;
var targetY = self.y + Math.sin(angle) * spiralRadius;
// Add some randomness for natural leaf movement
targetX += (Math.random() - 0.5) * 60;
targetY += (Math.random() - 0.5) * 60;
// Create floating leaf movement animation
tween(leafParticle, {
x: targetX,
y: targetY,
rotation: angle + Math.PI * 3,
// 1.5 full rotations
scaleX: 2.8,
scaleY: 2.8
}, {
duration: 500,
easing: tween.easeOut
});
// Add gentle swaying motion like leaves in wind
tween(leafParticle, {
x: targetX + Math.sin(angle * 2) * 30,
y: targetY + Math.cos(angle * 2) * 20
}, {
duration: 800,
easing: tween.easeInOut
});
// Fade out like autumn leaves
tween(leafParticle, {
alpha: 0,
scaleX: 0.3,
scaleY: 0.3,
rotation: angle + Math.PI * 5 // Continue rotating as it fades
}, {
duration: 700,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (leafParticle.parent) {
leafParticle.parent.removeChild(leafParticle);
}
}
});
// Add leaf color transition from green to brown
tween(leafParticle, {
tint: 0x8B4513 // Brown color
}, {
duration: 400,
easing: tween.easeInOut
});
}
};
self.createStarExplosion = function () {
var starCount = 14;
var maxRadius = 160;
for (var i = 0; i < starCount; i++) {
var angle = i / starCount * Math.PI * 2;
var starParticle = LK.getAsset('starParticle', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial position at gem center
starParticle.x = self.x;
starParticle.y = self.y;
starParticle.zIndex = 5;
// Add golden yellow tint and initial scale
starParticle.tint = 0xFFD700;
starParticle.scaleX = 0.3;
starParticle.scaleY = 0.3;
starParticle.rotation = angle;
// Add to game
if (self.parent) {
self.parent.addChild(starParticle);
}
// Create spiral outward sun burst movement
var radius = 80 + Math.random() * 80;
var targetX = self.x + Math.cos(angle) * radius;
var targetY = self.y + Math.sin(angle) * radius;
// Add sparkle randomness for star effect
targetX += (Math.random() - 0.5) * 40;
targetY += (Math.random() - 0.5) * 40;
// Create explosive star movement animation
tween(starParticle, {
x: targetX,
y: targetY,
rotation: angle + Math.PI * 4,
// 2 full rotations
scaleX: 5.0,
scaleY: 5.0
}, {
duration: 450,
easing: tween.easeOut
});
// Add pulsing sparkle effect
tween(starParticle, {
scaleX: 6.5,
scaleY: 6.5
}, {
duration: 150,
easing: tween.easeInOut
});
// Create secondary sparkle pulse
tween(starParticle, {
scaleX: 4.5,
scaleY: 4.5,
rotation: angle + Math.PI * 6 // Continue rotating
}, {
duration: 300,
easing: tween.easeInOut
});
// Fade out like fading starlight
tween(starParticle, {
alpha: 0,
scaleX: 0.2,
scaleY: 0.2,
rotation: angle + Math.PI * 8 // Final rotation burst
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (starParticle.parent) {
starParticle.parent.removeChild(starParticle);
}
}
});
// Add star color transition from gold to bright white
tween(starParticle, {
tint: 0xFFFFFF // Bright white
}, {
duration: 350,
easing: tween.easeInOut
});
}
};
self.createLavaMelting = function () {
var lavaCount = 12;
var meltRadius = 130;
for (var i = 0; i < lavaCount; i++) {
var angle = i / lavaCount * Math.PI * 2;
var lavaParticle = LK.getAsset('lavaParticle', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial position at gem center
lavaParticle.x = self.x;
lavaParticle.y = self.y;
lavaParticle.zIndex = 5;
// Add molten orange-red tint and initial scale
lavaParticle.tint = 0xFF4500;
lavaParticle.scaleX = 0.6;
lavaParticle.scaleY = 0.6;
lavaParticle.rotation = Math.random() * Math.PI * 2;
// Add to game
if (self.parent) {
self.parent.addChild(lavaParticle);
}
// Create melting downward movement with spiral motion
var radius = 70 + Math.random() * 60;
var targetX = self.x + Math.cos(angle) * radius;
var targetY = self.y + Math.sin(angle) * radius;
// Add downward melting bias
targetY += Math.random() * 80 + 40;
// Add some randomness for natural lava flow
targetX += (Math.random() - 0.5) * 50;
// Create lava melting movement animation
tween(lavaParticle, {
x: targetX,
y: targetY,
rotation: angle + Math.PI * 3,
// 1.5 full rotations
scaleX: 3.2,
scaleY: 3.2
}, {
duration: 550,
easing: tween.easeOut
});
// Add viscous lava dripping effect
tween(lavaParticle, {
y: targetY + 60,
scaleY: 4.0 // Stretch vertically like dripping lava
}, {
duration: 400,
easing: tween.easeIn
});
// Create molten glow pulsing effect
tween(lavaParticle, {
scaleX: 4.0,
tint: 0xFF6600 // Brighter orange
}, {
duration: 300,
easing: tween.easeInOut
});
// Cool down and solidify effect
tween(lavaParticle, {
alpha: 0,
scaleX: 0.4,
scaleY: 0.2,
tint: 0x8B0000,
// Dark red when cooling
rotation: angle + Math.PI * 5 // Continue rotating as it cools
}, {
duration: 650,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (lavaParticle.parent) {
lavaParticle.parent.removeChild(lavaParticle);
}
}
});
// Add secondary heat shimmer effect
tween(lavaParticle, {
scaleX: 2.8,
scaleY: 3.5
}, {
duration: 200,
easing: tween.easeInOut
});
}
};
return self;
});
var Particle = Container.expand(function (x, y, color) {
var self = Container.call(this);
var particleGraphics = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
// Set particle color
particleGraphics.tint = color;
// Set initial position
self.x = x;
self.y = y;
// Random velocity - increased for more dramatic effect
self.velocityX = (Math.random() - 0.5) * 500;
self.velocityY = (Math.random() - 0.5) * 500;
self.gravity = 800;
self.life = 1.5;
self.fadeSpeed = 1.0;
self.scale = 1.0;
self.scaleSpeed = 0.5;
// Add initial scale animation
tween(self, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut
});
self.update = function () {
// Update position based on velocity
self.x += self.velocityX * (1 / 60);
self.y += self.velocityY * (1 / 60);
// Apply gravity (positive for flipped board - particles fall down visually)
self.velocityY += self.gravity * (1 / 60);
// Fade out over time
self.life -= self.fadeSpeed * (1 / 60);
self.alpha = Math.max(0, self.life);
// Scale down over time for dramatic effect
self.scale -= self.scaleSpeed * (1 / 60);
if (self.scale > 0) {
self.scaleX = self.scale;
self.scaleY = self.scale;
}
// Remove when fully faded
if (self.life <= 0) {
self.destroy();
}
};
self.destroy = function () {
if (self.parent) {
self.parent.removeChild(self);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2C1810
});
/****
* Game Code
****/
var GRID_SIZE_X = 6;
var GRID_SIZE_Y = 9;
var CELL_SIZE = 170;
var BOARD_OFFSET_X = (2048 - GRID_SIZE_X * CELL_SIZE) / 2;
var BOARD_OFFSET_Y = (2732 - GRID_SIZE_Y * CELL_SIZE) / 2 + 600;
var gameBoard = [];
var selectedGem = null;
var draggedGem = null;
var dragStartPos = null;
var isDragging = false;
var isSwapping = false;
var score = 0;
var comboMultiplier = 1;
var consecutiveGemsDestroyed = 0;
var fireworkThreshold = 6;
// Timer system - 7.5 minutes in seconds
var gameTimeLimit = 7.5 * 60; // 7.5 minutes = 450 seconds
var remainingTime = gameTimeLimit;
var gameTimer = null;
// Create brown background (scrolling)
var k1 = game.attachAsset('brownBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
k1.zIndex = 1;
// Create second brown background positioned directly above the first one
var k1_second = game.attachAsset('brownBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 - 2732
});
k1_second.zIndex = 1;
// Variables to track background animation state
var backgroundSpeed = 2;
var backgroundY1 = 2732 / 2;
var backgroundY2 = 2732 / 2 - 2732;
// Function to update background positions
function updateBackgrounds() {
// Move both backgrounds down
backgroundY1 += backgroundSpeed;
backgroundY2 += backgroundSpeed;
k1.y = backgroundY1;
k1_second.y = backgroundY2;
// Reset positions when they move off screen
if (backgroundY1 > 2732 / 2 + 2732) {
backgroundY1 = backgroundY2 - 2732;
}
if (backgroundY2 > 2732 / 2 + 2732) {
backgroundY2 = backgroundY1 - 2732;
}
}
// Create board background
var boardBg = game.attachAsset('boardBg', {
anchorX: 0.5,
anchorY: 1.0,
x: 2048 / 2,
y: 2732 + 850
});
boardBg.zIndex = 2;
// Update timer display
function updateTimer() {
var minutes = Math.floor(remainingTime / 60);
var seconds = remainingTime % 60;
var timeText = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
timerTxt.setText(timeText);
}
// Update health bar display
function updateHealthBar() {
// Calculate how many segments should be visible (10 segments total)
var visibleSegments = Math.ceil(playerHealth / 10);
// Update health text
healthTxt.setText('HEALTH: ' + playerHealth);
// Remove segments that should no longer be visible
for (var i = 0; i < healthSegments.length; i++) {
var segment = healthSegments[i];
if (i >= visibleSegments && segment.parent) {
// Animate segment removal with cutting effect
tween(segment, {
scaleX: 0,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function (segmentToRemove) {
return function () {
if (segmentToRemove.parent) {
segmentToRemove.parent.removeChild(segmentToRemove);
}
};
}(segment)
});
}
}
// Add back segments that should be visible but are missing
for (var i = 0; i < visibleSegments; i++) {
var segment = healthSegments[i];
if (segment && !segment.parent) {
// Re-add the segment to the GUI
segment.scaleX = 1;
segment.alpha = 1;
segment.tint = 0x00ff00;
LK.gui.top.addChild(segment);
// Animate segment restoration with growing effect
tween(segment, {
scaleX: 1,
alpha: 1
}, {
duration: 300,
easing: tween.easeOut
});
}
}
// Flash remaining segments red when taking damage
if (playerHealth < maxHealth) {
for (var j = 0; j < visibleSegments; j++) {
if (healthSegments[j] && healthSegments[j].parent) {
tween(healthSegments[j], {
tint: 0xff6666
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function (segmentToFlash) {
return function () {
tween(segmentToFlash, {
tint: 0x00ff00
}, {
duration: 200,
easing: tween.easeInOut
});
};
}(healthSegments[j])
});
}
}
}
}
// Reduce player health
function reduceHealth(amount) {
playerHealth = Math.max(0, playerHealth - amount);
updateHealthBar();
// Flash screen red when taking damage
LK.effects.flashScreen(0xff0000, 300);
// Check for game over
if (playerHealth <= 0) {
LK.showGameOver();
}
}
// Create explosion effect at gem position
function createExplosion(x, y, gemColor) {
var particleCount = 15;
var gemColors = [0xff2222, 0x2222ff, 0x22ff22, 0xffff22, 0xff22ff, 0xff6622];
var color = gemColors[gemColor] || 0xffffff;
for (var i = 0; i < particleCount; i++) {
var particle = new Particle(x, y, color);
game.addChild(particle);
}
}
// Create firework celebration
function createFirework() {
// Create single firework in center upper position
var fireworkX = BOARD_OFFSET_X + GRID_SIZE_X * CELL_SIZE / 2 - 150 + Math.random() * 300;
var fireworkY = BOARD_OFFSET_Y - 500 + Math.random() * 200;
var firework = new Firework(fireworkX, fireworkY);
game.addChild(firework);
// Start explosion immediately
LK.setTimeout(function () {
firework.explode();
}, 100);
}
// Create board frame
var C1 = game.attachAsset('boardFrame', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 125
});
C1.zIndex = 3;
// Create score display
var scoreTxt = new Text2('SCORE: 0', {
size: 60,
fill: 0x000000
});
scoreTxt.anchor.set(0.5, 0);
// Add red shadow effect and red border stroke
if (scoreTxt.style) {
scoreTxt.style.dropShadow = true;
scoreTxt.style.dropShadowColor = "#ff0000";
scoreTxt.style.dropShadowBlur = 3;
scoreTxt.style.dropShadowDistance = 3;
scoreTxt.style.stroke = "#ff0000";
scoreTxt.style.strokeThickness = 4;
}
LK.gui.top.addChild(scoreTxt);
scoreTxt.y = 100;
// Create website text 300px below score
var websiteTxt = new Text2('vayabi.net', {
size: 50,
fill: 0x000000
});
websiteTxt.anchor.set(0.5, 0);
// Add styling for better visibility and make bold
if (websiteTxt.style) {
websiteTxt.style.fontWeight = "bold";
websiteTxt.style.dropShadow = true;
websiteTxt.style.dropShadowColor = "#ff0000";
websiteTxt.style.dropShadowBlur = 3;
websiteTxt.style.dropShadowDistance = 3;
websiteTxt.style.stroke = "#ff0000";
websiteTxt.style.strokeThickness = 2;
}
LK.gui.top.addChild(websiteTxt);
websiteTxt.y = 400; // 100 + 300 = 400 (moved 300px up from original 700)
// Create health system
var playerHealth = 100;
var maxHealth = 100;
// Create health bar background (red)
var healthBarBg = LK.getAsset('healthBarBg', {
anchorX: 0,
anchorY: 0.5
});
healthBarBg.x = 150;
healthBarBg.y = 200;
LK.gui.top.addChild(healthBarBg);
// Create segmented health bar with 10 individual pieces
var healthSegments = [];
var segmentWidth = 40; // Each segment is 40px wide (400px total / 10 segments)
for (var i = 0; i < 10; i++) {
var healthSegment = LK.getAsset('healthBarFg', {
anchorX: 0,
anchorY: 0.5
});
healthSegment.width = segmentWidth;
healthSegment.x = 150 + i * segmentWidth;
healthSegment.y = 200;
healthSegment.segmentIndex = i;
healthSegments.push(healthSegment);
LK.gui.top.addChild(healthSegment);
}
// Create health text
var healthTxt = new Text2('HEALTH: 100', {
size: 50,
fill: 0x000000
});
// Make text bold and add styling for better visibility
if (healthTxt.style) {
healthTxt.style.fontWeight = "bold";
healthTxt.style.stroke = "#ffffff";
healthTxt.style.strokeThickness = 2;
}
healthTxt.anchor.set(0, 0.5);
healthTxt.x = 150;
healthTxt.y = 250;
LK.gui.top.addChild(healthTxt);
// Create timer display
var timerTxt = new Text2('7:30', {
size: 60,
fill: 0x000000
});
// Make timer text bold and add styling for better visibility
if (timerTxt.style) {
timerTxt.style.fontWeight = "bold";
timerTxt.style.stroke = "#ffffff";
timerTxt.style.strokeThickness = 2;
timerTxt.style.dropShadow = true;
timerTxt.style.dropShadowColor = "#ff0000";
timerTxt.style.dropShadowBlur = 3;
timerTxt.style.dropShadowDistance = 3;
}
timerTxt.anchor.set(0, 1); // Anchor to bottom-left for positioning
LK.gui.bottomLeft.addChild(timerTxt);
timerTxt.x = 160; // Move slightly to the right
timerTxt.y = -900; // Move 35 squares (700px) up from previous position
timerTxt.rotation = 340 * Math.PI / 180; // Point towards 340 degrees
// Initialize empty board
function initializeBoard() {
gameBoard = [];
for (var x = 0; x < GRID_SIZE_X; x++) {
gameBoard[x] = [];
for (var y = 0; y < GRID_SIZE_Y; y++) {
gameBoard[x][y] = null;
}
}
}
// Fill board with random gems
function fillBoard() {
for (var x = 0; x < GRID_SIZE_X; x++) {
for (var y = 0; y < GRID_SIZE_Y; y++) {
if (!gameBoard[x][y]) {
var gemType = Math.floor(Math.random() * 6);
var gem = new Gem(gemType, x, y);
var worldPos = gridToWorld(x, y);
gem.x = worldPos.x;
gem.y = worldPos.y;
gem.zIndex = 4;
gameBoard[x][y] = gem;
game.addChild(gem);
}
}
}
}
// Convert grid coordinates to world coordinates
function gridToWorld(gridX, gridY) {
return {
x: BOARD_OFFSET_X + gridX * CELL_SIZE + CELL_SIZE / 2,
y: BOARD_OFFSET_Y + (GRID_SIZE_Y - 1 - gridY) * CELL_SIZE + CELL_SIZE / 2
};
}
// Convert world coordinates to grid coordinates
function worldToGrid(worldX, worldY) {
var gridX = Math.floor((worldX - BOARD_OFFSET_X) / CELL_SIZE);
var gridY = GRID_SIZE_Y - 1 - Math.floor((worldY - BOARD_OFFSET_Y) / CELL_SIZE);
if (gridX < 0 || gridX >= GRID_SIZE_X || gridY < 0 || gridY >= GRID_SIZE_Y) {
return null;
}
return {
x: gridX,
y: gridY
};
}
// Check if two grid positions are adjacent
function areAdjacent(pos1, pos2) {
var dx = Math.abs(pos1.x - pos2.x);
var dy = Math.abs(pos1.y - pos2.y);
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
}
// Check for matches starting at position (2x2 squares and 3-in-a-row)
function checkMatches(startX, startY) {
var gem = gameBoard[startX][startY];
if (!gem) return [];
var matches = [];
var gemType = gem.gemType;
// Check horizontal 3-in-a-row matches
var horizontalCount = 1;
var horizontalGems = [gem];
// Check right direction
for (var x = startX + 1; x < GRID_SIZE_X; x++) {
var rightGem = gameBoard[x][startY];
if (rightGem && rightGem.gemType === gemType) {
horizontalCount++;
horizontalGems.push(rightGem);
} else {
break;
}
}
// Check left direction
for (var x = startX - 1; x >= 0; x--) {
var leftGem = gameBoard[x][startY];
if (leftGem && leftGem.gemType === gemType) {
horizontalCount++;
horizontalGems.unshift(leftGem);
} else {
break;
}
}
// Add horizontal matches if 3 or more
if (horizontalCount >= 3) {
for (var h = 0; h < horizontalGems.length; h++) {
matches.push(horizontalGems[h]);
}
}
// Check vertical 3-in-a-row matches
var verticalCount = 1;
var verticalGems = [gem];
// Check up direction
for (var y = startY + 1; y < GRID_SIZE_Y; y++) {
var upGem = gameBoard[startX][y];
if (upGem && upGem.gemType === gemType) {
verticalCount++;
verticalGems.push(upGem);
} else {
break;
}
}
// Check down direction
for (var y = startY - 1; y >= 0; y--) {
var downGem = gameBoard[startX][y];
if (downGem && downGem.gemType === gemType) {
verticalCount++;
verticalGems.unshift(downGem);
} else {
break;
}
}
// Add vertical matches if 3 or more
if (verticalCount >= 3) {
for (var v = 0; v < verticalGems.length; v++) {
var found = false;
for (var m = 0; m < matches.length; m++) {
if (matches[m] === verticalGems[v]) {
found = true;
break;
}
}
if (!found) {
matches.push(verticalGems[v]);
}
}
}
// Check if this gem is part of a 2x2 square
// Check all possible 2x2 squares this gem could be part of
var squarePositions = [
// Top-left corner
[{
x: startX,
y: startY
}, {
x: startX + 1,
y: startY
}, {
x: startX,
y: startY + 1
}, {
x: startX + 1,
y: startY + 1
}],
// Top-right corner
[{
x: startX - 1,
y: startY
}, {
x: startX,
y: startY
}, {
x: startX - 1,
y: startY + 1
}, {
x: startX,
y: startY + 1
}],
// Bottom-left corner
[{
x: startX,
y: startY - 1
}, {
x: startX + 1,
y: startY - 1
}, {
x: startX,
y: startY
}, {
x: startX + 1,
y: startY
}],
// Bottom-right corner
[{
x: startX - 1,
y: startY - 1
}, {
x: startX,
y: startY - 1
}, {
x: startX - 1,
y: startY
}, {
x: startX,
y: startY
}]];
for (var s = 0; s < squarePositions.length; s++) {
var square = squarePositions[s];
var validSquare = true;
var squareGems = [];
// Check if all positions in this square are valid and contain same gem type
for (var p = 0; p < square.length; p++) {
var pos = square[p];
if (pos.x < 0 || pos.x >= GRID_SIZE_X || pos.y < 0 || pos.y >= GRID_SIZE_Y) {
validSquare = false;
break;
}
var squareGem = gameBoard[pos.x][pos.y];
if (!squareGem || squareGem.gemType !== gemType) {
validSquare = false;
break;
}
squareGems.push(squareGem);
}
// If we found a valid 2x2 square, add all gems to matches
if (validSquare) {
for (var g = 0; g < squareGems.length; g++) {
var found = false;
for (var m = 0; m < matches.length; m++) {
if (matches[m] === squareGems[g]) {
found = true;
break;
}
}
if (!found) {
matches.push(squareGems[g]);
}
}
}
}
return matches;
}
// Find all matches on the board
function findAllMatches() {
var allMatches = [];
var processedGems = [];
for (var x = 0; x < GRID_SIZE_X; x++) {
for (var y = 0; y < GRID_SIZE_Y; y++) {
if (gameBoard[x][y]) {
var matches = checkMatches(x, y);
for (var i = 0; i < matches.length; i++) {
var gem = matches[i];
var found = false;
for (var j = 0; j < processedGems.length; j++) {
if (processedGems[j] === gem) {
found = true;
break;
}
}
if (!found) {
allMatches.push(gem);
processedGems.push(gem);
}
}
}
}
}
return allMatches;
}
// Clear matched gems
function clearMatches(matches) {
if (matches.length === 0) return;
LK.getSound('match').play();
var points = matches.length * 10 * comboMultiplier;
var oldScore = score;
score += points;
scoreTxt.setText('SCORE: ' + score);
// Health gain system - check if player should gain health every 1500 points
var oldHealthGainLevel = Math.floor(oldScore / 1500);
var newHealthGainLevel = Math.floor(score / 1500);
if (newHealthGainLevel > oldHealthGainLevel && playerHealth < maxHealth) {
// Player gains health, but don't exceed max health
var healthToGain = Math.min(10, maxHealth - playerHealth);
playerHealth += healthToGain;
updateHealthBar();
// Flash screen green to show health gain
LK.effects.flashScreen(0x00ff00, 500);
}
// Check for win condition - player reaches 10000 points
if (score >= 10000) {
LK.clearInterval(gameTimer);
LK.showYouWin();
return;
}
// Track consecutive adjacent destroyed gems and trigger fireworks
if (matches.length >= fireworkThreshold) {
// Check if all gems are adjacent to each other (consecutive)
var areConsecutive = true;
if (matches.length >= fireworkThreshold) {
// Sort matches by position to check adjacency
for (var i = 0; i < matches.length - 1; i++) {
var currentGem = matches[i];
var hasAdjacentMatch = false;
for (var j = i + 1; j < matches.length; j++) {
var nextGem = matches[j];
var dx = Math.abs(currentGem.gridX - nextGem.gridX);
var dy = Math.abs(currentGem.gridY - nextGem.gridY);
if (dx === 1 && dy === 0 || dx === 0 && dy === 1) {
hasAdjacentMatch = true;
break;
}
}
if (!hasAdjacentMatch && i < matches.length - 1) {
areConsecutive = false;
break;
}
}
}
if (areConsecutive && matches.length >= fireworkThreshold) {
// Create firework celebration
createFirework();
}
}
for (var i = 0; i < matches.length; i++) {
var gem = matches[i];
// Create explosion effect at gem position
createExplosion(gem.x, gem.y, gem.gemType);
gameBoard[gem.gridX][gem.gridY] = null;
gem.destroy();
}
// After clearing gems, apply gravity and fill empty spaces
LK.setTimeout(function () {
// Apply gravity repeatedly until no more gems fall
function applyGravityRepeatedly() {
var moved = applyGravity();
if (moved) {
LK.setTimeout(applyGravityRepeatedly, 100);
} else {
fillEmptySpaces();
// Check for new matches after falling gems settle
LK.setTimeout(function () {
var newMatches = findAllMatches();
if (newMatches.length > 0) {
comboMultiplier++;
clearMatches(newMatches);
} else {
comboMultiplier = 1;
consecutiveGemsDestroyed = 0; // Reset counter when no more matches
isSwapping = false;
}
}, 500);
}
}
applyGravityRepeatedly();
}, 200);
}
// Apply gravity to make gems fall
function applyGravity() {
var moved = false;
for (var x = 0; x < GRID_SIZE_X; x++) {
// Process each column from top to bottom (since board is flipped, gems fall upward)
var writeIndex = 0;
for (var y = 0; y < GRID_SIZE_Y; y++) {
if (gameBoard[x][y]) {
// If gem is not at the write position, move it up
if (y !== writeIndex) {
var gem = gameBoard[x][y];
gameBoard[x][writeIndex] = gem;
gameBoard[x][y] = null;
gem.setGridPosition(x, writeIndex);
var worldPos = gridToWorld(x, writeIndex);
gem.animateToPosition(worldPos.x, worldPos.y, 300);
moved = true;
}
writeIndex++;
}
}
}
return moved;
}
// Fill empty spaces with new gems
function fillEmptySpaces() {
for (var x = 0; x < GRID_SIZE_X; x++) {
for (var y = 0; y < GRID_SIZE_Y; y++) {
if (!gameBoard[x][y]) {
var gemType = Math.floor(Math.random() * 6);
var gem = new Gem(gemType, x, y);
var worldPos = gridToWorld(x, y);
gem.x = worldPos.x;
gem.y = BOARD_OFFSET_Y - CELL_SIZE * 2; // Start from above the top line of the flipped grid
gem.zIndex = 4;
gameBoard[x][y] = gem;
game.addChild(gem);
gem.animateToPosition(worldPos.x, worldPos.y, 150 + y * 30); // Faster staggered animation timing for flipped board
}
}
}
}
// Swap two gems
function swapGems(gem1, gem2) {
if (isSwapping) return;
isSwapping = true;
// Store original positions
var originalGem1X = gem1.gridX;
var originalGem1Y = gem1.gridY;
var originalGem2X = gem2.gridX;
var originalGem2Y = gem2.gridY;
// Update grid positions
gem1.setGridPosition(gem2.gridX, gem2.gridY);
gem2.setGridPosition(originalGem1X, originalGem1Y);
gameBoard[gem1.gridX][gem1.gridY] = gem1;
gameBoard[gem2.gridX][gem2.gridY] = gem2;
var pos1 = gridToWorld(gem1.gridX, gem1.gridY);
var pos2 = gridToWorld(gem2.gridX, gem2.gridY);
// Animate the swap
gem1.animateToPosition(pos1.x, pos1.y, 200);
gem2.animateToPosition(pos2.x, pos2.y, 200);
LK.setTimeout(function () {
var matches = findAllMatches();
if (matches.length > 0) {
// Valid swap - process matches
clearMatches(matches);
} else {
// Invalid move - reduce health by 10 and animate back to original positions
reduceHealth(10);
gem1.setGridPosition(originalGem1X, originalGem1Y);
gem2.setGridPosition(originalGem2X, originalGem2Y);
gameBoard[gem1.gridX][gem1.gridY] = gem1;
gameBoard[gem2.gridX][gem2.gridY] = gem2;
var pos1 = gridToWorld(gem1.gridX, gem1.gridY);
var pos2 = gridToWorld(gem2.gridX, gem2.gridY);
gem1.animateToPosition(pos1.x, pos1.y, 200);
gem2.animateToPosition(pos2.x, pos2.y, 200);
LK.setTimeout(function () {
isSwapping = false;
}, 200);
}
}, 200);
}
// Game input handling
game.down = function (x, y, obj) {
if (isSwapping) return;
var gridPos = worldToGrid(x, y);
if (!gridPos) return;
var clickedGem = gameBoard[gridPos.x][gridPos.y];
if (!clickedGem) return;
draggedGem = clickedGem;
dragStartPos = {
x: x,
y: y
};
isDragging = true;
draggedGem.alpha = 0.7;
};
game.move = function (x, y, obj) {
if (!isDragging || !draggedGem || isSwapping) return;
// Calculate drag distance
var dragDeltaX = x - dragStartPos.x;
var dragDeltaY = y - dragStartPos.y;
var dragDistance = Math.sqrt(dragDeltaX * dragDeltaX + dragDeltaY * dragDeltaY);
// Only process if drag distance is significant enough
if (dragDistance > CELL_SIZE * 0.3) {
// Determine drag direction
var targetGridX = draggedGem.gridX;
var targetGridY = draggedGem.gridY;
if (Math.abs(dragDeltaX) > Math.abs(dragDeltaY)) {
// Horizontal drag
if (dragDeltaX > 0) {
targetGridX = Math.min(GRID_SIZE_X - 1, draggedGem.gridX + 1);
} else {
targetGridX = Math.max(0, draggedGem.gridX - 1);
}
} else {
// Vertical drag (flipped coordinate system)
if (dragDeltaY > 0) {
targetGridY = Math.max(0, draggedGem.gridY - 1);
} else {
targetGridY = Math.min(GRID_SIZE_Y - 1, draggedGem.gridY + 1);
}
}
// Update visual position to show intended swap
var targetWorldPos = gridToWorld(targetGridX, targetGridY);
draggedGem.x = targetWorldPos.x;
draggedGem.y = targetWorldPos.y;
// Store target position for later use
draggedGem.targetGridX = targetGridX;
draggedGem.targetGridY = targetGridY;
}
};
game.up = function (x, y, obj) {
if (!isDragging || !draggedGem || isSwapping) return;
var startGridPos = {
x: draggedGem.gridX,
y: draggedGem.gridY
};
// Check if we have a target position from dragging
if (draggedGem.targetGridX !== undefined && draggedGem.targetGridY !== undefined) {
var targetGridPos = {
x: draggedGem.targetGridX,
y: draggedGem.targetGridY
};
// Check if target position is valid and adjacent
if (areAdjacent(startGridPos, targetGridPos) && gameBoard[targetGridPos.x][targetGridPos.y]) {
var targetGem = gameBoard[targetGridPos.x][targetGridPos.y];
// Attempt swap
swapGems(draggedGem, targetGem);
} else {
// Invalid swap - animate back to original position
var worldPos = gridToWorld(draggedGem.gridX, draggedGem.gridY);
draggedGem.animateToPosition(worldPos.x, worldPos.y, 200);
}
// Clean up target position
draggedGem.targetGridX = undefined;
draggedGem.targetGridY = undefined;
} else {
// No significant drag - just return to original position
var worldPos = gridToWorld(draggedGem.gridX, draggedGem.gridY);
draggedGem.animateToPosition(worldPos.x, worldPos.y, 200);
}
draggedGem.alpha = 1.0;
draggedGem = null;
dragStartPos = null;
isDragging = false;
};
// Create grid lines around gems
function createGridLines() {
// Create vertical lines
for (var x = 0; x <= GRID_SIZE_X; x++) {
for (var lineY = 0; lineY < GRID_SIZE_Y * CELL_SIZE; lineY += 20) {
var verticalLine = LK.getAsset('particle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.05,
scaleY: 0.5
});
verticalLine.tint = 0x8B4513;
verticalLine.x = BOARD_OFFSET_X + x * CELL_SIZE;
verticalLine.y = BOARD_OFFSET_Y + lineY + 10;
verticalLine.zIndex = 3;
game.addChild(verticalLine);
}
}
// Create horizontal lines
for (var y = 0; y <= GRID_SIZE_Y; y++) {
for (var lineX = 0; lineX < GRID_SIZE_X * CELL_SIZE; lineX += 20) {
var horizontalLine = LK.getAsset('particle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.05
});
horizontalLine.tint = 0x8B4513;
horizontalLine.x = BOARD_OFFSET_X + lineX + 10;
horizontalLine.y = BOARD_OFFSET_Y + y * CELL_SIZE;
horizontalLine.zIndex = 3;
game.addChild(horizontalLine);
}
}
}
// Initialize the game
initializeBoard();
fillBoard();
createGridLines();
// Add game update loop for continuous animations
game.update = function () {
// Update scrolling backgrounds
updateBackgrounds();
// Add gentle rotation animation to all gems
for (var x = 0; x < GRID_SIZE_X; x++) {
for (var y = 0; y < GRID_SIZE_Y; y++) {
var gem = gameBoard[x][y];
if (gem && !gem.isAnimating && !gem.rotationStarted) {
// Create gentle rotation effect
gem.rotationStarted = true;
var delay = (x + y) * 30; // Stagger the rotation animation
LK.setTimeout(function (currentGem) {
return function () {
function createRotation() {
if (currentGem.parent && !currentGem.isAnimating) {
var currentRotation = currentGem.rotation || 0;
tween(currentGem, {
rotation: currentRotation + Math.PI * 2
}, {
duration: 6000,
easing: tween.linear,
onFinish: function onFinish() {
if (currentGem.parent && !currentGem.isAnimating) {
createRotation(); // Continue rotating
}
}
});
}
}
createRotation();
};
}(gem), delay);
}
}
}
};
// Start countdown timer
gameTimer = LK.setInterval(function () {
remainingTime--;
updateTimer();
// Check if time is running out (last 30 seconds) - flash timer red
if (remainingTime <= 30 && remainingTime > 0) {
tween(timerTxt, {
tint: 0xff0000
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(timerTxt, {
tint: 0x000000
}, {
duration: 500,
easing: tween.easeInOut
});
}
});
}
// Game over when time runs out
if (remainingTime <= 0) {
LK.clearInterval(gameTimer);
LK.showGameOver();
}
}, 1000); // Update every 1000ms (1 second)
// Create logo in top-right corner
var logo = LK.getAsset('logo', {
anchorX: 1,
anchorY: 0,
scaleX: 0.15,
scaleY: 0.15
});
logo.x = -50;
logo.y = 0;
logo.zIndex = 15;
LK.gui.topRight.addChild(logo);
// Add logo glow effect
tween(logo, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(logo, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 1500,
easing: tween.easeInOut
});
}
});
// Start background music
LK.playMusic('background');
// Initialize timer display
updateTimer();
// Initial match clearing
LK.setTimeout(function () {
var matches = findAllMatches();
if (matches.length > 0) {
clearMatches(matches);
}
}, 100);
Rengi acık mavi gökyüzümsü olsun küçük bulutlar olsun
Su damlası patlama parçacık. In-Game asset. 2d. High contrast. No shadows
Karadelik vortex parçacık spiral. In-Game asset. 2d. High contrast. No shadows
Yeşil yaprak süzülen. In-Game asset. 2d. High contrast. No shadows
Yıldız patlama parlak. In-Game asset. 2d. High contrast. No shadows
Lav topu erimiş. In-Game asset. 2d. High contrast. No shadows
Health bar, grandyan, green,. In-Game asset. 2d. High contrast. No shadows
Hiç birşeye dokunma sadece yeşil olan kısımları kırmızı ile yer değiştir
Make it with candy