/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var FusionBall = Container.expand(function (tier) {
var self = Container.call(this);
self.tier = tier || 1;
self.assetId = 'ball_tier' + self.tier;
var ballGraphics = self.attachAsset(self.assetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.gravity = 0.5;
self.bounce = 0.7;
self.lastIntersecting = false;
self.lastPortalUsed = null;
self.portalCooldown = 0;
self.update = function () {
if (self.portalCooldown > 0) {
self.portalCooldown--;
}
// Apply physics
self.velocityY += self.gravity;
self.x += self.velocityX;
self.y += self.velocityY;
// Container bounds collision
var radius = ballGraphics.width / 2;
if (self.x - radius < containerBounds.left) {
self.x = containerBounds.left + radius;
self.velocityX *= -self.bounce;
}
if (self.x + radius > containerBounds.right) {
self.x = containerBounds.right - radius;
self.velocityX *= -self.bounce;
}
if (self.y + radius > containerBounds.bottom) {
self.y = containerBounds.bottom - radius;
self.velocityY *= -self.bounce;
self.velocityX *= 0.8; // Friction
}
// Top boundary - game over condition
if (self.y - radius < containerBounds.top) {
gameOverTriggered = true;
}
};
return self;
});
var Portal = Container.expand(function (isEntrance) {
var self = Container.call(this);
self.isEntrance = isEntrance || false;
var portalGraphics = self.attachAsset(self.isEntrance ? 'portal_entrance' : 'portal_exit', {
anchorX: 0.5,
anchorY: 0.5
});
self.paired = null;
self.animationPhase = 0;
self.update = function () {
self.animationPhase += 0.15;
portalGraphics.rotation = self.animationPhase;
portalGraphics.scaleX = 1 + Math.sin(self.animationPhase * 2) * 0.1;
portalGraphics.scaleY = 1 + Math.sin(self.animationPhase * 2) * 0.1;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x001122
});
/****
* Game Code
****/
// Game variables
var level = 1;
var gameMode = 'normal'; // normal, gravity, speed, chaos, mega
var balls = [];
var portals = [];
var currentBallTier = 1;
var nextBallTier = 1;
var gameOverTriggered = false;
var portalSpawnTimer = 0;
var containerBounds = {};
// UI elements
var levelText, modeText, scoreText, nextBallPreview;
// Initialize container
var container = game.addChild(LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5
}));
container.x = 1024;
container.y = 1366;
container.alpha = 0.3;
// Set container bounds
containerBounds = {
left: 324,
right: 1724,
top: 466,
bottom: 2266
};
// Create UI
levelText = new Text2('Level: 1', {
size: 60,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0);
LK.gui.top.addChild(levelText);
levelText.y = 80;
modeText = new Text2('Mode: Normal', {
size: 50,
fill: 0x00FFFF
});
modeText.anchor.set(0.5, 0);
LK.gui.top.addChild(modeText);
modeText.y = 150;
scoreText = new Text2('Score: 0', {
size: 70,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
scoreText.y = 220;
nextBallPreview = game.addChild(new FusionBall(1));
nextBallPreview.x = 1024;
nextBallPreview.y = 350;
nextBallPreview.alpha = 0.7;
function updateGameMode() {
var newMode = 'normal';
var modeLevel = Math.floor((level - 1) / 5) + 1;
if (modeLevel === 1) newMode = 'normal';else if (modeLevel === 2) newMode = 'gravity';else if (modeLevel === 3) newMode = 'speed';else if (modeLevel === 4) newMode = 'chaos';else newMode = 'mega';
if (newMode !== gameMode) {
gameMode = newMode;
modeText.setText('Mode: ' + gameMode.charAt(0).toUpperCase() + gameMode.slice(1));
LK.getSound('levelup').play();
}
}
function spawnPortalPair() {
if (portals.length >= 4) return;
var portal1 = game.addChild(new Portal(true));
var portal2 = game.addChild(new Portal(false));
// Position portals within container bounds
portal1.x = containerBounds.left + 100 + Math.random() * (containerBounds.right - containerBounds.left - 200);
portal1.y = containerBounds.top + 100 + Math.random() * (containerBounds.bottom - containerBounds.top - 200);
portal2.x = containerBounds.left + 100 + Math.random() * (containerBounds.right - containerBounds.left - 200);
portal2.y = containerBounds.top + 100 + Math.random() * (containerBounds.bottom - containerBounds.top - 200);
// Pair them
portal1.paired = portal2;
portal2.paired = portal1;
portals.push(portal1, portal2);
// Remove portals after 8 seconds
LK.setTimeout(function () {
var index1 = portals.indexOf(portal1);
var index2 = portals.indexOf(portal2);
if (index1 >= 0) {
portal1.destroy();
portals.splice(index1, 1);
}
if (index2 >= 0) {
portal2.destroy();
portals.splice(index2, 1);
}
}, 8000);
}
function checkFusions() {
for (var i = 0; i < balls.length; i++) {
for (var j = i + 1; j < balls.length; j++) {
var ball1 = balls[i];
var ball2 = balls[j];
if (ball1.tier === ball2.tier && ball1.intersects(ball2)) {
// Create fusion ball
var newTier = Math.min(ball1.tier + 1, 7);
var fusionBall = game.addChild(new FusionBall(newTier));
fusionBall.x = (ball1.x + ball2.x) / 2;
fusionBall.y = (ball1.y + ball2.y) / 2;
fusionBall.velocityX = (ball1.velocityX + ball2.velocityX) / 2;
fusionBall.velocityY = (ball1.velocityY + ball2.velocityY) / 2;
// Add to balls array
balls.push(fusionBall);
// Remove original balls
ball1.destroy();
ball2.destroy();
balls.splice(j, 1);
balls.splice(i, 1);
// Update score
var points = newTier * 10;
LK.setScore(LK.getScore() + points);
scoreText.setText('Score: ' + LK.getScore());
LK.getSound('fusion').play();
// Check win condition
if (newTier === 7) {
level++;
levelText.setText('Level: ' + level);
updateGameMode();
}
return; // Only process one fusion per frame
}
}
}
}
function checkPortalCollisions() {
balls.forEach(function (ball) {
if (ball.portalCooldown === 0) {
portals.forEach(function (portal) {
if (ball.intersects(portal) && portal.paired && ball.lastPortalUsed !== portal) {
// Teleport ball
ball.x = portal.paired.x;
ball.y = portal.paired.y;
ball.lastPortalUsed = portal;
ball.portalCooldown = 60;
LK.setScore(LK.getScore() + 5);
scoreText.setText('Score: ' + LK.getScore());
LK.getSound('portal_sound').play();
// Portal effect
tween(portal.paired.getChildAt(0), {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(portal.paired.getChildAt(0), {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
});
}
});
}
});
}
function generateNextBallTier() {
var maxTier = Math.min(3 + Math.floor(level / 3), 5);
nextBallTier = Math.floor(Math.random() * maxTier) + 1;
nextBallPreview.destroy();
nextBallPreview = game.addChild(new FusionBall(nextBallTier));
nextBallPreview.x = 1024;
nextBallPreview.y = 350;
nextBallPreview.alpha = 0.7;
}
// Touch controls - drop ball on tap
game.down = function (x, y, obj) {
if (gameOverTriggered) return;
var newBall = game.addChild(new FusionBall(nextBallTier));
newBall.x = Math.max(containerBounds.left + 40, Math.min(x, containerBounds.right - 40));
newBall.y = containerBounds.top - 50;
newBall.velocityY = 2;
balls.push(newBall);
LK.getSound('drop').play();
generateNextBallTier();
};
// Initialize first ball tier
generateNextBallTier();
// Main game loop
game.update = function () {
if (gameOverTriggered) {
LK.showGameOver();
return;
}
// Update game mode
updateGameMode();
// Spawn portals periodically
portalSpawnTimer++;
if (portalSpawnTimer > 180 + Math.random() * 120) {
spawnPortalPair();
portalSpawnTimer = 0;
}
// Check fusions
checkFusions();
// Check portal collisions
checkPortalCollisions();
// Apply mode-specific effects
if (gameMode === 'gravity') {
balls.forEach(function (ball) {
ball.gravity = 0.8;
});
} else if (gameMode === 'speed') {
balls.forEach(function (ball) {
ball.gravity = 0.3;
ball.velocityX *= 1.02;
});
} else if (gameMode === 'chaos') {
balls.forEach(function (ball) {
if (Math.random() < 0.01) {
ball.velocityX += (Math.random() - 0.5) * 2;
}
});
} else if (gameMode === 'mega') {
balls.forEach(function (ball) {
ball.gravity = 1.0;
if (Math.random() < 0.005) {
ball.velocityX += (Math.random() - 0.5) * 3;
}
});
} else {
balls.forEach(function (ball) {
ball.gravity = 0.5;
});
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var FusionBall = Container.expand(function (tier) {
var self = Container.call(this);
self.tier = tier || 1;
self.assetId = 'ball_tier' + self.tier;
var ballGraphics = self.attachAsset(self.assetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.gravity = 0.5;
self.bounce = 0.7;
self.lastIntersecting = false;
self.lastPortalUsed = null;
self.portalCooldown = 0;
self.update = function () {
if (self.portalCooldown > 0) {
self.portalCooldown--;
}
// Apply physics
self.velocityY += self.gravity;
self.x += self.velocityX;
self.y += self.velocityY;
// Container bounds collision
var radius = ballGraphics.width / 2;
if (self.x - radius < containerBounds.left) {
self.x = containerBounds.left + radius;
self.velocityX *= -self.bounce;
}
if (self.x + radius > containerBounds.right) {
self.x = containerBounds.right - radius;
self.velocityX *= -self.bounce;
}
if (self.y + radius > containerBounds.bottom) {
self.y = containerBounds.bottom - radius;
self.velocityY *= -self.bounce;
self.velocityX *= 0.8; // Friction
}
// Top boundary - game over condition
if (self.y - radius < containerBounds.top) {
gameOverTriggered = true;
}
};
return self;
});
var Portal = Container.expand(function (isEntrance) {
var self = Container.call(this);
self.isEntrance = isEntrance || false;
var portalGraphics = self.attachAsset(self.isEntrance ? 'portal_entrance' : 'portal_exit', {
anchorX: 0.5,
anchorY: 0.5
});
self.paired = null;
self.animationPhase = 0;
self.update = function () {
self.animationPhase += 0.15;
portalGraphics.rotation = self.animationPhase;
portalGraphics.scaleX = 1 + Math.sin(self.animationPhase * 2) * 0.1;
portalGraphics.scaleY = 1 + Math.sin(self.animationPhase * 2) * 0.1;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x001122
});
/****
* Game Code
****/
// Game variables
var level = 1;
var gameMode = 'normal'; // normal, gravity, speed, chaos, mega
var balls = [];
var portals = [];
var currentBallTier = 1;
var nextBallTier = 1;
var gameOverTriggered = false;
var portalSpawnTimer = 0;
var containerBounds = {};
// UI elements
var levelText, modeText, scoreText, nextBallPreview;
// Initialize container
var container = game.addChild(LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5
}));
container.x = 1024;
container.y = 1366;
container.alpha = 0.3;
// Set container bounds
containerBounds = {
left: 324,
right: 1724,
top: 466,
bottom: 2266
};
// Create UI
levelText = new Text2('Level: 1', {
size: 60,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0);
LK.gui.top.addChild(levelText);
levelText.y = 80;
modeText = new Text2('Mode: Normal', {
size: 50,
fill: 0x00FFFF
});
modeText.anchor.set(0.5, 0);
LK.gui.top.addChild(modeText);
modeText.y = 150;
scoreText = new Text2('Score: 0', {
size: 70,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
scoreText.y = 220;
nextBallPreview = game.addChild(new FusionBall(1));
nextBallPreview.x = 1024;
nextBallPreview.y = 350;
nextBallPreview.alpha = 0.7;
function updateGameMode() {
var newMode = 'normal';
var modeLevel = Math.floor((level - 1) / 5) + 1;
if (modeLevel === 1) newMode = 'normal';else if (modeLevel === 2) newMode = 'gravity';else if (modeLevel === 3) newMode = 'speed';else if (modeLevel === 4) newMode = 'chaos';else newMode = 'mega';
if (newMode !== gameMode) {
gameMode = newMode;
modeText.setText('Mode: ' + gameMode.charAt(0).toUpperCase() + gameMode.slice(1));
LK.getSound('levelup').play();
}
}
function spawnPortalPair() {
if (portals.length >= 4) return;
var portal1 = game.addChild(new Portal(true));
var portal2 = game.addChild(new Portal(false));
// Position portals within container bounds
portal1.x = containerBounds.left + 100 + Math.random() * (containerBounds.right - containerBounds.left - 200);
portal1.y = containerBounds.top + 100 + Math.random() * (containerBounds.bottom - containerBounds.top - 200);
portal2.x = containerBounds.left + 100 + Math.random() * (containerBounds.right - containerBounds.left - 200);
portal2.y = containerBounds.top + 100 + Math.random() * (containerBounds.bottom - containerBounds.top - 200);
// Pair them
portal1.paired = portal2;
portal2.paired = portal1;
portals.push(portal1, portal2);
// Remove portals after 8 seconds
LK.setTimeout(function () {
var index1 = portals.indexOf(portal1);
var index2 = portals.indexOf(portal2);
if (index1 >= 0) {
portal1.destroy();
portals.splice(index1, 1);
}
if (index2 >= 0) {
portal2.destroy();
portals.splice(index2, 1);
}
}, 8000);
}
function checkFusions() {
for (var i = 0; i < balls.length; i++) {
for (var j = i + 1; j < balls.length; j++) {
var ball1 = balls[i];
var ball2 = balls[j];
if (ball1.tier === ball2.tier && ball1.intersects(ball2)) {
// Create fusion ball
var newTier = Math.min(ball1.tier + 1, 7);
var fusionBall = game.addChild(new FusionBall(newTier));
fusionBall.x = (ball1.x + ball2.x) / 2;
fusionBall.y = (ball1.y + ball2.y) / 2;
fusionBall.velocityX = (ball1.velocityX + ball2.velocityX) / 2;
fusionBall.velocityY = (ball1.velocityY + ball2.velocityY) / 2;
// Add to balls array
balls.push(fusionBall);
// Remove original balls
ball1.destroy();
ball2.destroy();
balls.splice(j, 1);
balls.splice(i, 1);
// Update score
var points = newTier * 10;
LK.setScore(LK.getScore() + points);
scoreText.setText('Score: ' + LK.getScore());
LK.getSound('fusion').play();
// Check win condition
if (newTier === 7) {
level++;
levelText.setText('Level: ' + level);
updateGameMode();
}
return; // Only process one fusion per frame
}
}
}
}
function checkPortalCollisions() {
balls.forEach(function (ball) {
if (ball.portalCooldown === 0) {
portals.forEach(function (portal) {
if (ball.intersects(portal) && portal.paired && ball.lastPortalUsed !== portal) {
// Teleport ball
ball.x = portal.paired.x;
ball.y = portal.paired.y;
ball.lastPortalUsed = portal;
ball.portalCooldown = 60;
LK.setScore(LK.getScore() + 5);
scoreText.setText('Score: ' + LK.getScore());
LK.getSound('portal_sound').play();
// Portal effect
tween(portal.paired.getChildAt(0), {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(portal.paired.getChildAt(0), {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
});
}
});
}
});
}
function generateNextBallTier() {
var maxTier = Math.min(3 + Math.floor(level / 3), 5);
nextBallTier = Math.floor(Math.random() * maxTier) + 1;
nextBallPreview.destroy();
nextBallPreview = game.addChild(new FusionBall(nextBallTier));
nextBallPreview.x = 1024;
nextBallPreview.y = 350;
nextBallPreview.alpha = 0.7;
}
// Touch controls - drop ball on tap
game.down = function (x, y, obj) {
if (gameOverTriggered) return;
var newBall = game.addChild(new FusionBall(nextBallTier));
newBall.x = Math.max(containerBounds.left + 40, Math.min(x, containerBounds.right - 40));
newBall.y = containerBounds.top - 50;
newBall.velocityY = 2;
balls.push(newBall);
LK.getSound('drop').play();
generateNextBallTier();
};
// Initialize first ball tier
generateNextBallTier();
// Main game loop
game.update = function () {
if (gameOverTriggered) {
LK.showGameOver();
return;
}
// Update game mode
updateGameMode();
// Spawn portals periodically
portalSpawnTimer++;
if (portalSpawnTimer > 180 + Math.random() * 120) {
spawnPortalPair();
portalSpawnTimer = 0;
}
// Check fusions
checkFusions();
// Check portal collisions
checkPortalCollisions();
// Apply mode-specific effects
if (gameMode === 'gravity') {
balls.forEach(function (ball) {
ball.gravity = 0.8;
});
} else if (gameMode === 'speed') {
balls.forEach(function (ball) {
ball.gravity = 0.3;
ball.velocityX *= 1.02;
});
} else if (gameMode === 'chaos') {
balls.forEach(function (ball) {
if (Math.random() < 0.01) {
ball.velocityX += (Math.random() - 0.5) * 2;
}
});
} else if (gameMode === 'mega') {
balls.forEach(function (ball) {
ball.gravity = 1.0;
if (Math.random() < 0.005) {
ball.velocityX += (Math.random() - 0.5) * 3;
}
});
} else {
balls.forEach(function (ball) {
ball.gravity = 0.5;
});
}
};