/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Circle class: represents a tappable circle var TapCircle = Container.expand(function () { var self = Container.call(this); // Create and attach a colored ellipse asset // Color is randomized for each circle var color = TapCircle.randomColor(); var circle = self.attachAsset('tapCircle', { width: TapCircle.RADIUS * 2, height: TapCircle.RADIUS * 2, color: color, shape: 'ellipse', anchorX: 0.5, anchorY: 0.5 }); // Store color for possible future use self.color = color; // Set up a timer for auto-destroy self.lifetime = TapCircle.lifetime; // ms self.spawnTick = LK.ticks; // Mark as alive self.alive = true; // Called every tick by LK engine self.update = function () { // If lifetime expired, trigger game over if (self.alive && (LK.ticks - self.spawnTick) * (1000 / 60) > self.lifetime) { // Flash screen red and end game LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); self.alive = false; } }; // Called when the circle is tapped self.down = function (x, y, obj) { if (!self.alive) return; self.alive = false; // Animate: quick scale up and fade out tween(self, { scaleX: 1.3, scaleY: 1.3, alpha: 0 }, { duration: 180, easing: tween.cubicOut, onFinish: function onFinish() { self.destroy(); } }); // Score is handled in game.down }; return self; }); /**** * Initialize Game ****/ // Static properties for TapCircle var game = new LK.Game({ // No title, no description // Always backgroundColor is black backgroundColor: 0x000000 }); /**** * Game Code ****/ // Score display // Tween plugin for animations (not strictly needed for MVP, but included for future use) // Static properties for TapCircle TapCircle.RADIUS = 140; // px TapCircle.lifetime = 1200; // ms, will decrease as game speeds up // Helper: random pastel color TapCircle.randomColor = function () { // Pastel: high value, medium saturation var hue = Math.floor(Math.random() * 360); var rgb = TapCircle.hsvToRgb(hue / 360, 0.5, 0.95); return rgb[0] << 16 | rgb[1] << 8 | rgb[2]; }; // HSV to RGB helper TapCircle.hsvToRgb = function (h, s, v) { var r, g, b, i, f, p, q, t; i = Math.floor(h * 6); f = h * 6 - i; p = v * (1 - s); q = v * (1 - f * s); t = v * (1 - (1 - f) * s); switch (i % 6) { case 0: r = v, g = t, b = p; break; case 1: r = q, g = v, b = p; break; case 2: r = p, g = v, b = t; break; case 3: r = p, g = q, b = v; break; case 4: r = t, g = p, b = v; break; case 5: r = v, g = p, b = q; break; } return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; }; var scoreTxt = new Text2('0', { size: 150, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Global state var circles = []; var canTap = true; // Prevents double taps in a single frame // Game speed state var minLifetime = 600; // ms, minimum allowed var maxLifetime = 1200; // ms, starting value var speedupEvery = 3; // Speed up every N points var speedupAmount = 80; // ms decrease per speedup // Spawn a new circle at a random position function spawnCircle() { // Remove any existing circles for (var i = circles.length - 1; i >= 0; i--) { if (circles[i] && circles[i].alive) { circles[i].destroy(); } circles.splice(i, 1); } // Calculate safe area (avoid top 200px and bottom 200px, and left/right 150px) var marginX = TapCircle.RADIUS + 150; var marginY = TapCircle.RADIUS + 200; var x = marginX + Math.random() * (2048 - marginX * 2); var y = marginY + Math.random() * (2732 - marginY * 2); // Create and position circle var circle = new TapCircle(); circle.x = x; circle.y = y; circle.scaleX = 1; circle.scaleY = 1; circle.alpha = 1; // Set current lifetime (difficulty) circle.lifetime = TapCircle.lifetime; game.addChild(circle); circles.push(circle); } // Handle tap events game.down = function (x, y, obj) { if (!canTap) return; canTap = false; // Check if tap is inside any alive circle var tapped = false; for (var i = 0; i < circles.length; i++) { var c = circles[i]; if (c && c.alive) { // Convert tap to local circle coordinates var local = c.toLocal(game.toGlobal({ x: x, y: y })); var dx = local.x - 0; var dy = local.y - 0; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= TapCircle.RADIUS) { // Correct tap tapped = true; LK.getSound('tap').play(); c.down(x, y, obj); // Animate and destroy // Score up var newScore = LK.getScore() + 1; LK.setScore(newScore); scoreTxt.setText(newScore); // Speed up game if (newScore % speedupEvery === 0 && TapCircle.lifetime > minLifetime) { TapCircle.lifetime = Math.max(minLifetime, TapCircle.lifetime - speedupAmount); } // Spawn next circle after a short delay LK.setTimeout(function () { spawnCircle(); canTap = true; }, 120); break; } } } if (!tapped) { // Tapped outside any circle: game over LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); } }; // Allow tap again after up game.up = function (x, y, obj) { canTap = true; }; // Main update loop game.update = function () { // Update all circles for (var i = circles.length - 1; i >= 0; i--) { if (circles[i] && circles[i].update) { circles[i].update(); } } }; // Start the game function startGame() { LK.setScore(0); scoreTxt.setText(0); TapCircle.lifetime = maxLifetime; LK.playMusic('music'); spawnCircle(); canTap = true; gameStarted = true; } // Only spawn the first circle after the first tap var gameStarted = false; // Show the start asset at the center of the screen var startAsset = LK.getAsset('start', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); game.addChild(startAsset); game.down = function (x, y, obj) { if (!gameStarted) { // Remove the start asset if (startAsset && startAsset.parent) { startAsset.parent.removeChild(startAsset); } startGame(); return; } if (!canTap) return; canTap = false; // Check if tap is inside any alive circle var tapped = false; for (var i = 0; i < circles.length; i++) { var c = circles[i]; if (c && c.alive) { // Convert tap to local circle coordinates var local = c.toLocal(game.toGlobal({ x: x, y: y })); var dx = local.x - 0; var dy = local.y - 0; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= TapCircle.RADIUS) { // Correct tap tapped = true; LK.getSound('tap').play(); c.down(x, y, obj); // Animate and destroy // Score up var newScore = LK.getScore() + 1; LK.setScore(newScore); scoreTxt.setText(newScore); // Speed up game if (newScore % speedupEvery === 0 && TapCircle.lifetime > minLifetime) { TapCircle.lifetime = Math.max(minLifetime, TapCircle.lifetime - speedupAmount); } // Spawn next circle after a short delay LK.setTimeout(function () { spawnCircle(); canTap = true; }, 120); break; } } } if (!tapped) { // Tapped outside any circle: game over LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); } }; // Allow tap again after up game.up = function (x, y, obj) { canTap = true; };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Circle class: represents a tappable circle
var TapCircle = Container.expand(function () {
var self = Container.call(this);
// Create and attach a colored ellipse asset
// Color is randomized for each circle
var color = TapCircle.randomColor();
var circle = self.attachAsset('tapCircle', {
width: TapCircle.RADIUS * 2,
height: TapCircle.RADIUS * 2,
color: color,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5
});
// Store color for possible future use
self.color = color;
// Set up a timer for auto-destroy
self.lifetime = TapCircle.lifetime; // ms
self.spawnTick = LK.ticks;
// Mark as alive
self.alive = true;
// Called every tick by LK engine
self.update = function () {
// If lifetime expired, trigger game over
if (self.alive && (LK.ticks - self.spawnTick) * (1000 / 60) > self.lifetime) {
// Flash screen red and end game
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
self.alive = false;
}
};
// Called when the circle is tapped
self.down = function (x, y, obj) {
if (!self.alive) return;
self.alive = false;
// Animate: quick scale up and fade out
tween(self, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0
}, {
duration: 180,
easing: tween.cubicOut,
onFinish: function onFinish() {
self.destroy();
}
});
// Score is handled in game.down
};
return self;
});
/****
* Initialize Game
****/
// Static properties for TapCircle
var game = new LK.Game({
// No title, no description
// Always backgroundColor is black
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Score display
// Tween plugin for animations (not strictly needed for MVP, but included for future use)
// Static properties for TapCircle
TapCircle.RADIUS = 140; // px
TapCircle.lifetime = 1200; // ms, will decrease as game speeds up
// Helper: random pastel color
TapCircle.randomColor = function () {
// Pastel: high value, medium saturation
var hue = Math.floor(Math.random() * 360);
var rgb = TapCircle.hsvToRgb(hue / 360, 0.5, 0.95);
return rgb[0] << 16 | rgb[1] << 8 | rgb[2];
};
// HSV to RGB helper
TapCircle.hsvToRgb = function (h, s, v) {
var r, g, b, i, f, p, q, t;
i = Math.floor(h * 6);
f = h * 6 - i;
p = v * (1 - s);
q = v * (1 - f * s);
t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0:
r = v, g = t, b = p;
break;
case 1:
r = q, g = v, b = p;
break;
case 2:
r = p, g = v, b = t;
break;
case 3:
r = p, g = q, b = v;
break;
case 4:
r = t, g = p, b = v;
break;
case 5:
r = v, g = p, b = q;
break;
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
};
var scoreTxt = new Text2('0', {
size: 150,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Global state
var circles = [];
var canTap = true; // Prevents double taps in a single frame
// Game speed state
var minLifetime = 600; // ms, minimum allowed
var maxLifetime = 1200; // ms, starting value
var speedupEvery = 3; // Speed up every N points
var speedupAmount = 80; // ms decrease per speedup
// Spawn a new circle at a random position
function spawnCircle() {
// Remove any existing circles
for (var i = circles.length - 1; i >= 0; i--) {
if (circles[i] && circles[i].alive) {
circles[i].destroy();
}
circles.splice(i, 1);
}
// Calculate safe area (avoid top 200px and bottom 200px, and left/right 150px)
var marginX = TapCircle.RADIUS + 150;
var marginY = TapCircle.RADIUS + 200;
var x = marginX + Math.random() * (2048 - marginX * 2);
var y = marginY + Math.random() * (2732 - marginY * 2);
// Create and position circle
var circle = new TapCircle();
circle.x = x;
circle.y = y;
circle.scaleX = 1;
circle.scaleY = 1;
circle.alpha = 1;
// Set current lifetime (difficulty)
circle.lifetime = TapCircle.lifetime;
game.addChild(circle);
circles.push(circle);
}
// Handle tap events
game.down = function (x, y, obj) {
if (!canTap) return;
canTap = false;
// Check if tap is inside any alive circle
var tapped = false;
for (var i = 0; i < circles.length; i++) {
var c = circles[i];
if (c && c.alive) {
// Convert tap to local circle coordinates
var local = c.toLocal(game.toGlobal({
x: x,
y: y
}));
var dx = local.x - 0;
var dy = local.y - 0;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= TapCircle.RADIUS) {
// Correct tap
tapped = true;
LK.getSound('tap').play();
c.down(x, y, obj); // Animate and destroy
// Score up
var newScore = LK.getScore() + 1;
LK.setScore(newScore);
scoreTxt.setText(newScore);
// Speed up game
if (newScore % speedupEvery === 0 && TapCircle.lifetime > minLifetime) {
TapCircle.lifetime = Math.max(minLifetime, TapCircle.lifetime - speedupAmount);
}
// Spawn next circle after a short delay
LK.setTimeout(function () {
spawnCircle();
canTap = true;
}, 120);
break;
}
}
}
if (!tapped) {
// Tapped outside any circle: game over
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
}
};
// Allow tap again after up
game.up = function (x, y, obj) {
canTap = true;
};
// Main update loop
game.update = function () {
// Update all circles
for (var i = circles.length - 1; i >= 0; i--) {
if (circles[i] && circles[i].update) {
circles[i].update();
}
}
};
// Start the game
function startGame() {
LK.setScore(0);
scoreTxt.setText(0);
TapCircle.lifetime = maxLifetime;
LK.playMusic('music');
spawnCircle();
canTap = true;
gameStarted = true;
}
// Only spawn the first circle after the first tap
var gameStarted = false;
// Show the start asset at the center of the screen
var startAsset = LK.getAsset('start', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
game.addChild(startAsset);
game.down = function (x, y, obj) {
if (!gameStarted) {
// Remove the start asset
if (startAsset && startAsset.parent) {
startAsset.parent.removeChild(startAsset);
}
startGame();
return;
}
if (!canTap) return;
canTap = false;
// Check if tap is inside any alive circle
var tapped = false;
for (var i = 0; i < circles.length; i++) {
var c = circles[i];
if (c && c.alive) {
// Convert tap to local circle coordinates
var local = c.toLocal(game.toGlobal({
x: x,
y: y
}));
var dx = local.x - 0;
var dy = local.y - 0;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= TapCircle.RADIUS) {
// Correct tap
tapped = true;
LK.getSound('tap').play();
c.down(x, y, obj); // Animate and destroy
// Score up
var newScore = LK.getScore() + 1;
LK.setScore(newScore);
scoreTxt.setText(newScore);
// Speed up game
if (newScore % speedupEvery === 0 && TapCircle.lifetime > minLifetime) {
TapCircle.lifetime = Math.max(minLifetime, TapCircle.lifetime - speedupAmount);
}
// Spawn next circle after a short delay
LK.setTimeout(function () {
spawnCircle();
canTap = true;
}, 120);
break;
}
}
}
if (!tapped) {
// Tapped outside any circle: game over
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
}
};
// Allow tap again after up
game.up = function (x, y, obj) {
canTap = true;
};