/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highestDotCount: 0,
autoSpawn: true,
spawnInterval: 1000
});
/****
* Classes
****/
var AddButton = Container.expand(function () {
var self = Container.call(this);
var circle = self.attachAsset('addButton', {
anchorX: 0.5,
anchorY: 0.5
});
var plus1 = self.attachAsset('plusSign', {
anchorX: 0.5,
anchorY: 0.5
});
var plus2 = self.attachAsset('plusSign', {
anchorX: 0.5,
anchorY: 0.5,
rotation: Math.PI / 2
});
self.down = function (x, y, obj) {
// Visual feedback
tween(circle, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100,
easing: tween.easeOut
});
};
self.up = function (x, y, obj) {
// Visual feedback
tween(circle, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut
});
// Toggle auto spawn
game.toggleAutoSpawn();
};
return self;
});
var Dot = Container.expand(function () {
var self = Container.call(this);
var dotGraphics = self.attachAsset('dot', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
scaleY: 1
});
self.angle = 0;
self.speed = 0.02 + Math.random() * 0.02;
self.radius = 250 + Math.random() * 100;
self.verticalStretch = 0.7;
self.alive = true;
self.breakDirection = {
x: 0,
y: 0
};
self.resetPosition = function () {
self.x = game.figureCenterX;
self.y = game.figureCenterY;
};
self.update = function () {
if (!self.alive) {
// Dot is breaking away from the orbit
self.x += self.breakDirection.x;
self.y += self.breakDirection.y;
// Apply some gravity to make it fall off screen
self.breakDirection.y += 0.2;
// Check if dot is off screen
if (self.y > 2732 + 100 || self.x < -100 || self.x > 2048 + 100) {
self.destroy();
game.removeDeadDot(self);
}
return;
}
// Update angle based on game speed (gravity)
self.angle += self.speed * game.gravityStrength;
// Calculate position on figure 8 path
var figureX = game.figureCenterX + Math.sin(self.angle) * self.radius;
var figureY = game.figureCenterY + Math.sin(2 * self.angle) * self.radius * self.verticalStretch;
// Smoothly move to the calculated position
self.x = figureX;
self.y = figureY;
};
self.breakOrbit = function (directionX, directionY) {
if (!self.alive) {
return;
}
self.alive = false;
// Set break direction based on current position and tap location
self.breakDirection = {
x: (directionX - self.x) * 0.1,
y: (directionY - self.y) * 0.1
};
// Play break sound
LK.getSound('break').play();
// Make dot fade out as it breaks orbit
tween(dotGraphics, {
alpha: 0
}, {
duration: 1500,
easing: tween.linear
});
};
return self;
});
var GravityControl = Container.expand(function () {
var self = Container.call(this);
var bar = self.attachAsset('gravityControl', {
anchorX: 0.5,
anchorY: 0.5
});
var slider = self.attachAsset('gravitySlider', {
anchorX: 0.5,
anchorY: 0.5
});
var minX = -bar.width / 2 + slider.width / 2;
var maxX = bar.width / 2 - slider.width / 2;
self.dragging = false;
self.updateSliderPosition = function (value) {
// Position the slider based on gravity strength value (0.1 to 2.0)
var normalizedValue = (value - 0.1) / 1.9; // Normalize to 0-1
slider.x = minX + normalizedValue * (maxX - minX);
};
self.getValueFromPosition = function () {
// Calculate gravity value from slider position
var normalizedPos = (slider.x - minX) / (maxX - minX);
return 0.1 + normalizedPos * 1.9; // Map to 0.1-2.0 range
};
self.down = function (x, y, obj) {
var localPos = self.toLocal({
x: x,
y: y
});
if (Math.abs(localPos.x - slider.x) < slider.width && Math.abs(localPos.y - slider.y) < slider.height) {
self.dragging = true;
}
};
self.up = function (x, y, obj) {
self.dragging = false;
};
self.move = function (x, y, obj) {
if (self.dragging) {
var localPos = self.toLocal({
x: x,
y: y
});
slider.x = Math.max(minX, Math.min(maxX, localPos.x));
game.gravityStrength = self.getValueFromPosition();
}
};
return self;
});
var PathVisualizer = Container.expand(function () {
var self = Container.call(this);
self.pathPoints = [];
self.pathDots = [];
self.createPath = function (dotCount) {
// Clear existing path visualization
for (var i = 0; i < self.pathDots.length; i++) {
self.removeChild(self.pathDots[i]);
}
self.pathDots = [];
// Calculate path points for figure 8
self.pathPoints = [];
for (var angle = 0; angle < Math.PI * 2; angle += 0.05) {
var x = game.figureCenterX + Math.sin(angle) * 300;
var y = game.figureCenterY + Math.sin(2 * angle) * 300 * 0.7;
self.pathPoints.push({
x: x,
y: y
});
}
// Create visual path with dots
for (var i = 0; i < self.pathPoints.length; i++) {
var dot = LK.getAsset(i % 5 === 0 ? 'pathHighlight' : 'path', {
anchorX: 0.5,
anchorY: 0.5
});
dot.x = self.pathPoints[i].x;
dot.y = self.pathPoints[i].y;
self.pathDots.push(dot);
self.addChild(dot);
}
};
self.isPointNearPath = function (x, y) {
// Check if a point is near the figure 8 path
for (var i = 0; i < self.pathPoints.length; i++) {
var distance = Math.sqrt(Math.pow(x - self.pathPoints[i].x, 2) + Math.pow(y - self.pathPoints[i].y, 2));
if (distance < 50) {
return true;
}
}
return false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x111133
});
/****
* Game Code
****/
game.dots = [];
game.gravityStrength = 1.0;
game.figureCenterX = 2048 / 2;
game.figureCenterY = 2732 / 2;
game.autoSpawnEnabled = storage.autoSpawn;
game.spawnInterval = storage.spawnInterval;
game.lastSpawnTime = 0;
game.bestTime = 0;
game.currentTime = 0;
game.stopwatchRunning = false;
// Stopwatch display
var stopwatchTxt = new Text2('Time: 0.00', {
size: 60,
fill: 0xFFFFFF
});
stopwatchTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(stopwatchTxt);
stopwatchTxt.x = 0;
stopwatchTxt.y = 50;
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0
});
game.addChild(background);
// Create path visualizer
var pathVisualizer = new PathVisualizer();
game.addChild(pathVisualizer);
pathVisualizer.createPath(50);
// Create add button
var addButton = new AddButton();
addButton.x = 2048 - 150;
addButton.y = 2732 - 150;
game.addChild(addButton);
// Count display
var countTxt = new Text2('Dots: 0', {
size: 80,
fill: 0xFFFFFF
});
countTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(countTxt);
countTxt.x = -countTxt.width - 50;
countTxt.y = 50;
// Auto spawn status display
var autoSpawnTxt = new Text2('Auto: ' + (game.autoSpawnEnabled ? 'ON' : 'OFF'), {
size: 60,
fill: game.autoSpawnEnabled ? "#88ff88" : "#ff8888"
});
autoSpawnTxt.anchor.set(0.5, 0.5);
addButton.addChild(autoSpawnTxt);
autoSpawnTxt.y = -100;
// Game functions
game.createDot = function (x, y) {
var dot = new Dot();
// Randomize starting position on the path
dot.angle = Math.random() * Math.PI * 2;
// Add dot to game
game.dots.push(dot);
game.addChild(dot);
// Update counter
countTxt.setText('Dots: ' + game.dots.length);
// Check high score
if (game.dots.length > storage.highestDotCount) {
storage.highestDotCount = game.dots.length;
highScoreTxt.setText('Best: ' + storage.highestDotCount);
}
// Play pop sound
LK.getSound('pop').play();
// Animate dot appearing
dot.scale.set(0);
tween(dot, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.elasticOut
});
return dot;
};
game.removeDeadDot = function (dot) {
// Remove dead dot from array
var index = game.dots.indexOf(dot);
if (index !== -1) {
game.dots.splice(index, 1);
countTxt.setText('Dots: ' + game.dots.length);
}
};
game.toggleAutoSpawn = function () {
game.autoSpawnEnabled = !game.autoSpawnEnabled;
storage.autoSpawn = game.autoSpawnEnabled;
// Update text
autoSpawnTxt.setText('Auto: ' + (game.autoSpawnEnabled ? 'ON' : 'OFF'));
autoSpawnTxt.setStyle({
fill: game.autoSpawnEnabled ? "#88ff88" : "#ff8888"
});
};
// Event handlers
game.down = function (x, y, obj) {
// Check if the tap was on the path or outside
var onPath = pathVisualizer.isPointNearPath(x, y);
if (onPath) {
// Create a new dot at the figure 8 center
game.createDot();
} else {
// Break orbits of nearby dots
for (var i = 0; i < game.dots.length; i++) {
var dot = game.dots[i];
var distance = Math.sqrt(Math.pow(x - dot.x, 2) + Math.pow(y - dot.y, 2));
if (distance < 150 && dot.alive) {
dot.breakOrbit(x, y);
}
}
}
};
// Main update loop
game.update = function () {
// Auto spawn dots
if (game.autoSpawnEnabled) {
if (LK.ticks - game.lastSpawnTime > game.spawnInterval / 16.67) {
game.lastSpawnTime = LK.ticks;
game.createDot();
// Update stopwatch
if (game.stopwatchRunning) {
game.currentTime += 1 / 60; // Assuming 60 FPS
stopwatchTxt.setText('Time: ' + game.currentTime.toFixed(2));
}
// Check if all dots are still in orbit
var allDotsInOrbit = game.dots.every(function (dot) {
return dot.alive;
});
if (allDotsInOrbit) {
if (!game.stopwatchRunning) {
game.stopwatchRunning = true;
game.currentTime = 0;
}
} else {
if (game.stopwatchRunning) {
game.stopwatchRunning = false;
if (game.currentTime > game.bestTime) {
game.bestTime = game.currentTime;
}
game.currentTime = 0;
}
}
}
}
};
// Start background music
LK.playMusic('ambient', {
fade: {
start: 0,
end: 0.5,
duration: 2000
}
}); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highestDotCount: 0,
autoSpawn: true,
spawnInterval: 1000
});
/****
* Classes
****/
var AddButton = Container.expand(function () {
var self = Container.call(this);
var circle = self.attachAsset('addButton', {
anchorX: 0.5,
anchorY: 0.5
});
var plus1 = self.attachAsset('plusSign', {
anchorX: 0.5,
anchorY: 0.5
});
var plus2 = self.attachAsset('plusSign', {
anchorX: 0.5,
anchorY: 0.5,
rotation: Math.PI / 2
});
self.down = function (x, y, obj) {
// Visual feedback
tween(circle, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100,
easing: tween.easeOut
});
};
self.up = function (x, y, obj) {
// Visual feedback
tween(circle, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut
});
// Toggle auto spawn
game.toggleAutoSpawn();
};
return self;
});
var Dot = Container.expand(function () {
var self = Container.call(this);
var dotGraphics = self.attachAsset('dot', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
scaleY: 1
});
self.angle = 0;
self.speed = 0.02 + Math.random() * 0.02;
self.radius = 250 + Math.random() * 100;
self.verticalStretch = 0.7;
self.alive = true;
self.breakDirection = {
x: 0,
y: 0
};
self.resetPosition = function () {
self.x = game.figureCenterX;
self.y = game.figureCenterY;
};
self.update = function () {
if (!self.alive) {
// Dot is breaking away from the orbit
self.x += self.breakDirection.x;
self.y += self.breakDirection.y;
// Apply some gravity to make it fall off screen
self.breakDirection.y += 0.2;
// Check if dot is off screen
if (self.y > 2732 + 100 || self.x < -100 || self.x > 2048 + 100) {
self.destroy();
game.removeDeadDot(self);
}
return;
}
// Update angle based on game speed (gravity)
self.angle += self.speed * game.gravityStrength;
// Calculate position on figure 8 path
var figureX = game.figureCenterX + Math.sin(self.angle) * self.radius;
var figureY = game.figureCenterY + Math.sin(2 * self.angle) * self.radius * self.verticalStretch;
// Smoothly move to the calculated position
self.x = figureX;
self.y = figureY;
};
self.breakOrbit = function (directionX, directionY) {
if (!self.alive) {
return;
}
self.alive = false;
// Set break direction based on current position and tap location
self.breakDirection = {
x: (directionX - self.x) * 0.1,
y: (directionY - self.y) * 0.1
};
// Play break sound
LK.getSound('break').play();
// Make dot fade out as it breaks orbit
tween(dotGraphics, {
alpha: 0
}, {
duration: 1500,
easing: tween.linear
});
};
return self;
});
var GravityControl = Container.expand(function () {
var self = Container.call(this);
var bar = self.attachAsset('gravityControl', {
anchorX: 0.5,
anchorY: 0.5
});
var slider = self.attachAsset('gravitySlider', {
anchorX: 0.5,
anchorY: 0.5
});
var minX = -bar.width / 2 + slider.width / 2;
var maxX = bar.width / 2 - slider.width / 2;
self.dragging = false;
self.updateSliderPosition = function (value) {
// Position the slider based on gravity strength value (0.1 to 2.0)
var normalizedValue = (value - 0.1) / 1.9; // Normalize to 0-1
slider.x = minX + normalizedValue * (maxX - minX);
};
self.getValueFromPosition = function () {
// Calculate gravity value from slider position
var normalizedPos = (slider.x - minX) / (maxX - minX);
return 0.1 + normalizedPos * 1.9; // Map to 0.1-2.0 range
};
self.down = function (x, y, obj) {
var localPos = self.toLocal({
x: x,
y: y
});
if (Math.abs(localPos.x - slider.x) < slider.width && Math.abs(localPos.y - slider.y) < slider.height) {
self.dragging = true;
}
};
self.up = function (x, y, obj) {
self.dragging = false;
};
self.move = function (x, y, obj) {
if (self.dragging) {
var localPos = self.toLocal({
x: x,
y: y
});
slider.x = Math.max(minX, Math.min(maxX, localPos.x));
game.gravityStrength = self.getValueFromPosition();
}
};
return self;
});
var PathVisualizer = Container.expand(function () {
var self = Container.call(this);
self.pathPoints = [];
self.pathDots = [];
self.createPath = function (dotCount) {
// Clear existing path visualization
for (var i = 0; i < self.pathDots.length; i++) {
self.removeChild(self.pathDots[i]);
}
self.pathDots = [];
// Calculate path points for figure 8
self.pathPoints = [];
for (var angle = 0; angle < Math.PI * 2; angle += 0.05) {
var x = game.figureCenterX + Math.sin(angle) * 300;
var y = game.figureCenterY + Math.sin(2 * angle) * 300 * 0.7;
self.pathPoints.push({
x: x,
y: y
});
}
// Create visual path with dots
for (var i = 0; i < self.pathPoints.length; i++) {
var dot = LK.getAsset(i % 5 === 0 ? 'pathHighlight' : 'path', {
anchorX: 0.5,
anchorY: 0.5
});
dot.x = self.pathPoints[i].x;
dot.y = self.pathPoints[i].y;
self.pathDots.push(dot);
self.addChild(dot);
}
};
self.isPointNearPath = function (x, y) {
// Check if a point is near the figure 8 path
for (var i = 0; i < self.pathPoints.length; i++) {
var distance = Math.sqrt(Math.pow(x - self.pathPoints[i].x, 2) + Math.pow(y - self.pathPoints[i].y, 2));
if (distance < 50) {
return true;
}
}
return false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x111133
});
/****
* Game Code
****/
game.dots = [];
game.gravityStrength = 1.0;
game.figureCenterX = 2048 / 2;
game.figureCenterY = 2732 / 2;
game.autoSpawnEnabled = storage.autoSpawn;
game.spawnInterval = storage.spawnInterval;
game.lastSpawnTime = 0;
game.bestTime = 0;
game.currentTime = 0;
game.stopwatchRunning = false;
// Stopwatch display
var stopwatchTxt = new Text2('Time: 0.00', {
size: 60,
fill: 0xFFFFFF
});
stopwatchTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(stopwatchTxt);
stopwatchTxt.x = 0;
stopwatchTxt.y = 50;
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0
});
game.addChild(background);
// Create path visualizer
var pathVisualizer = new PathVisualizer();
game.addChild(pathVisualizer);
pathVisualizer.createPath(50);
// Create add button
var addButton = new AddButton();
addButton.x = 2048 - 150;
addButton.y = 2732 - 150;
game.addChild(addButton);
// Count display
var countTxt = new Text2('Dots: 0', {
size: 80,
fill: 0xFFFFFF
});
countTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(countTxt);
countTxt.x = -countTxt.width - 50;
countTxt.y = 50;
// Auto spawn status display
var autoSpawnTxt = new Text2('Auto: ' + (game.autoSpawnEnabled ? 'ON' : 'OFF'), {
size: 60,
fill: game.autoSpawnEnabled ? "#88ff88" : "#ff8888"
});
autoSpawnTxt.anchor.set(0.5, 0.5);
addButton.addChild(autoSpawnTxt);
autoSpawnTxt.y = -100;
// Game functions
game.createDot = function (x, y) {
var dot = new Dot();
// Randomize starting position on the path
dot.angle = Math.random() * Math.PI * 2;
// Add dot to game
game.dots.push(dot);
game.addChild(dot);
// Update counter
countTxt.setText('Dots: ' + game.dots.length);
// Check high score
if (game.dots.length > storage.highestDotCount) {
storage.highestDotCount = game.dots.length;
highScoreTxt.setText('Best: ' + storage.highestDotCount);
}
// Play pop sound
LK.getSound('pop').play();
// Animate dot appearing
dot.scale.set(0);
tween(dot, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.elasticOut
});
return dot;
};
game.removeDeadDot = function (dot) {
// Remove dead dot from array
var index = game.dots.indexOf(dot);
if (index !== -1) {
game.dots.splice(index, 1);
countTxt.setText('Dots: ' + game.dots.length);
}
};
game.toggleAutoSpawn = function () {
game.autoSpawnEnabled = !game.autoSpawnEnabled;
storage.autoSpawn = game.autoSpawnEnabled;
// Update text
autoSpawnTxt.setText('Auto: ' + (game.autoSpawnEnabled ? 'ON' : 'OFF'));
autoSpawnTxt.setStyle({
fill: game.autoSpawnEnabled ? "#88ff88" : "#ff8888"
});
};
// Event handlers
game.down = function (x, y, obj) {
// Check if the tap was on the path or outside
var onPath = pathVisualizer.isPointNearPath(x, y);
if (onPath) {
// Create a new dot at the figure 8 center
game.createDot();
} else {
// Break orbits of nearby dots
for (var i = 0; i < game.dots.length; i++) {
var dot = game.dots[i];
var distance = Math.sqrt(Math.pow(x - dot.x, 2) + Math.pow(y - dot.y, 2));
if (distance < 150 && dot.alive) {
dot.breakOrbit(x, y);
}
}
}
};
// Main update loop
game.update = function () {
// Auto spawn dots
if (game.autoSpawnEnabled) {
if (LK.ticks - game.lastSpawnTime > game.spawnInterval / 16.67) {
game.lastSpawnTime = LK.ticks;
game.createDot();
// Update stopwatch
if (game.stopwatchRunning) {
game.currentTime += 1 / 60; // Assuming 60 FPS
stopwatchTxt.setText('Time: ' + game.currentTime.toFixed(2));
}
// Check if all dots are still in orbit
var allDotsInOrbit = game.dots.every(function (dot) {
return dot.alive;
});
if (allDotsInOrbit) {
if (!game.stopwatchRunning) {
game.stopwatchRunning = true;
game.currentTime = 0;
}
} else {
if (game.stopwatchRunning) {
game.stopwatchRunning = false;
if (game.currentTime > game.bestTime) {
game.bestTime = game.currentTime;
}
game.currentTime = 0;
}
}
}
}
};
// Start background music
LK.playMusic('ambient', {
fade: {
start: 0,
end: 0.5,
duration: 2000
}
});