User prompt
Fix the platform movement logic: Platforms should start moving upward **immediately** from the beginning of the game. Their upward speed should **gradually increase** as the player's score (number of successful bounces) increases. - Start with a slow upward movement speed at bounce 0 - Slightly increase the speed every 10 bounces - Make sure the increase is smooth and does not push the ball off-screen - Do not pause or delay platform movement at the beginning Ensure the platforms always move upward and create pressure, but not too fast to break gameplay.
User prompt
increase initial upward movement of the platforms
User prompt
Please remove the previous logic that keeps the platforms static for the first 3 bounces. Platforms should start moving slowly upward immediately from the beginning of the game. Do not delay or pause their movement based on bounce count. Make sure platform movement speed is handled by the difficulty progression logic, not by a bounce threshold.
User prompt
Please remove the previous logic that keeps the platforms static for the first 3 bounces. Platforms should start moving upward immediately from the beginning of the game. Do not delay or pause their movement based on bounce count. Make sure platform movement speed is handled by the difficulty progression logic, not by a bounce threshold.
User prompt
Please fix the following issue: After playing for a while, the platforms start moving upward too fast, causing the ball to go off the top of the screen and become invisible. To fix this, reduce the upward movement speed of the platforms so that the ball always remains visible on screen. The platform speed should increase more gradually over time and stay within limits that keep gameplay fair and visually trackable. The ball must never exit the top of the screen during normal gameplay.
User prompt
we should limit the horizontal movement of the platforms, it harms the experience since we can't move ball horizontally, only let them move slightly
Code edit (1 edits merged)
Please save this source code
User prompt
make the platforms drastically slower
User prompt
increase ball speed after first 3 bounces
User prompt
add less speed to platforms and a tiny bit more for the balls
User prompt
change it to first 3 bounces
User prompt
platforms slide upwards too fast and ball is a bit slow, make the platforms stable for the first 10, after that add them movement slowly accordingly to our difficulty tresholds
User prompt
first platform should be place lower side of the screen to give player time to react
User prompt
the platforms should disappear after the ball bounces on them with the matching color
User prompt
platforms should start from the bottom
Code edit (1 edits merged)
Please save this source code
User prompt
Color Bounce
Initial prompt
Build a complete, mobile-friendly rhythm-reaction game called **Color Bounce** with the following specifications: 🎮 Core Mechanics: 1. A ball automatically bounces downward onto colored platforms. 2. Each platform has one of 4 colors: red, blue, yellow and green. 3. The player must tap to **cycle the ball's color** in the order: red → blue → yellow → green → red... 4. When the ball lands on a platform: - ✅ If the color matches: it bounces again and the game continues. - ❌ If the color doesn't match: the game ends. 5. The ball falls faster as the game progresses — **increase tempo over time.** 🎯 Controls: - Single-tap input anywhere on screen = cycle ball color to next one. 🧠 Difficulty Progression: - Start with 4 colored platforms, slow tempo. - Every 10 bounces, increase fall speed slightly. - After 20 bounces, introduce *moving* platforms (horizontal slide). - After 40 bounces, introduce random gaps (platform missing). - Cap difficulty at 80 bounces for now. 🌈 Visual & Audio Feedback: - Each color has a **distinct glow and trail**. - Add a **particle burst** on successful bounce (small confetti or glow). - Add a **shake + screen flash** on failure. - Add SFX: - Bounce success = pitch-matched ping - Color switch = soft click - Fail = low thud + screen flash 🏆 Score & Game Flow: - Show **score as total successful bounces**. - At game over, show: - Final score - Best score - Retry button ✨ Cosmetic System: - Add **coin pickup items** that appear randomly on platforms (10% chance). - Player earns coins to unlock: - Ball skins (change ball appearance) - Trail styles (fire, smoke, sparkles) - Background themes (night, neon, pixel) - Add a basic **shop UI** with 6 total unlockable items (2 per category).
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { coins: 0, unlockedSkins: [], selectedSkin: 0 }); /**** * Classes ****/ // Ball class var Ball = Container.expand(function () { var self = Container.call(this); // Ball color index: 0=red, 1=blue, 2=green, 3=yellow self.colorIndex = 0; self.colors = ['red', 'blue', 'green', 'yellow']; self.asset = null; // Attach initial asset self.setColor = function (idx) { if (self.asset) { self.removeChild(self.asset); } self.colorIndex = idx; var colorName = self.colors[self.colorIndex]; self.asset = self.attachAsset('ball_' + colorName, { anchorX: 0.5, anchorY: 0.5 }); }; self.cycleColor = function () { var next = (self.colorIndex + 1) % self.colors.length; self.setColor(next); }; // For simple trail effect (MVP: just a quick flash) self.flash = function () { tween(self.asset, { alpha: 0.5 }, { duration: 60, easing: tween.linear, onFinish: function onFinish() { tween(self.asset, { alpha: 1 }, { duration: 80 }); } }); }; // Set initial color self.setColor(0); return self; }); // Coin class var Coin = Container.expand(function () { var self = Container.call(this); self.asset = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { // MVP: no animation }; return self; }); // Platform class var Platform = Container.expand(function () { var self = Container.call(this); // Color index: 0=red, 1=blue, 2=green, 3=yellow self.colorIndex = 0; self.colors = ['red', 'blue', 'green', 'yellow']; self.asset = null; // For moving platforms (future) self.vx = 0; self.setColor = function (idx) { if (self.asset) { self.removeChild(self.asset); } self.colorIndex = idx; var colorName = self.colors[self.colorIndex]; self.asset = self.attachAsset('platform_' + colorName, { anchorX: 0.5, anchorY: 0.5 }); }; // For MVP, platforms are static self.update = function () { // No horizontal movement; platforms remain centered }; // Set initial color self.setColor(0); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181c20 }); /**** * Game Code ****/ // --- Game variables --- // Ball colors (platforms and ball) // SFX // Music (stub, not played in MVP) var ball = null; var platforms = []; var coins = []; var score = 0; var coinCount = 0; var platformSpacing = 400; // vertical distance between platforms // platformSpeed is now calculated dynamically based on score var gravity = 1; // increases as score increases (slightly higher) var bounceVelocity = -31; // initial bounce velocity (slightly higher magnitude) var ballVy = 0; var isFalling = true; var currentPlatformIdx = 0; var lastTapTick = 0; var gameStarted = false; var dragNode = null; // not used, but required by guidelines var lastIntersecting = false; var canTap = true; // prevent double tap in one frame // GUI var scoreTxt = new Text2('0', { size: 120, fill: '#fff' }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var coinTxt = new Text2('0', { size: 80, fill: '#ffd700' }); coinTxt.anchor.set(1, 0); coinTxt.x = -40; coinTxt.y = 20; LK.gui.topRight.addChild(coinTxt); // --- Music Play/Pause and Volume Switch (Top Left) --- var musicPlaying = true; var musicVolume = 1; // 1 = max, 0 = muted // Play music at start LK.playMusic('fallingdown', { loop: true, fade: { start: 0, end: musicVolume, duration: 600 } }); // Play/Pause Button var playPauseBtn = new Text2('⏸', { size: 90, fill: '#fff' }); playPauseBtn.anchor.set(0, 0); playPauseBtn.x = 110; playPauseBtn.y = 10; // Volume Button var volumeBtn = new Text2('🔊', { size: 90, fill: '#fff' }); volumeBtn.anchor.set(0, 0); volumeBtn.x = 220; volumeBtn.y = 10; // Add to top left GUI (leave 100x100 px clear for menu) LK.gui.topLeft.addChild(playPauseBtn); LK.gui.topLeft.addChild(volumeBtn); // Play/Pause toggle playPauseBtn.down = function (x, y, obj) { if (musicPlaying) { LK.stopMusic(); playPauseBtn.setText('▶️'); musicPlaying = false; } else { LK.playMusic('fallingdown', { loop: true, fade: { start: 0, end: musicVolume, duration: 400 } }); playPauseBtn.setText('⏸'); musicPlaying = true; } }; // Volume toggle volumeBtn.down = function (x, y, obj) { if (musicVolume === 1) { musicVolume = 0; volumeBtn.setText('🔇'); if (musicPlaying) { LK.playMusic('fallingdown', { loop: true, fade: { start: 1, end: 0, duration: 300 } }); } } else { musicVolume = 1; volumeBtn.setText('🔊'); if (musicPlaying) { LK.playMusic('fallingdown', { loop: true, fade: { start: 0, end: 1, duration: 300 } }); } } }; // --- Helper functions --- function getRandomColorIdx() { // 0=red, 1=blue, 2=green, 3=yellow return Math.floor(Math.random() * 4); } function spawnPlatform(y, colorIdx) { var plat = new Platform(); plat.setColor(colorIdx); plat.x = 2048 / 2; plat.y = y; // No horizontal movement; platforms remain centered plat.vx = 0; platforms.push(plat); game.addChild(plat); return plat; } function spawnCoin(x, y) { var c = new Coin(); c.x = x; c.y = y - 60; coins.push(c); game.addChild(c); return c; } function resetGameVars() { score = 0; coinCount = 0; // platformSpeed is now calculated dynamically based on score gravity = 1.23; // increased initial gravity for even faster ball bounceVelocity = -43; // increased initial bounce velocity for even faster ball ballVy = 0; isFalling = true; currentPlatformIdx = 0; lastTapTick = 0; canTap = true; gameStarted = false; lastIntersecting = false; // Remove old platforms and coins for (var i = 0; i < platforms.length; ++i) { platforms[i].destroy(); } for (var i = 0; i < coins.length; ++i) { coins[i].destroy(); } platforms = []; coins = []; // Reset lastPlatformY to initial spawn value lastPlatformY = 1600; // Remove ball if (ball) { ball.destroy(); ball = null; } } // --- Game start --- function startGame() { resetGameVars(); // Add background image behind all gameplay elements var bg = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); game.addChild(bg); // Ball and Platforms: fill screen // Place first platform lower to give player time to react var y = 1600; // Lower on screen for first platform var firstPlatformColorIdx = getRandomColorIdx(); ball = new Ball(); ball.setColor(firstPlatformColorIdx); ball.x = 2048 / 2; ball.y = 600; game.addChild(ball); for (var i = 0; i < 8; ++i) { var colorIdx = i === 0 ? firstPlatformColorIdx : getRandomColorIdx(); var plat = spawnPlatform(y, colorIdx); lastPlatformY = plat.y; // Place a coin on every 3rd platform if (i > 0 && i % 3 === 0) { spawnCoin(plat.x, plat.y); } y += platformSpacing; } currentPlatformIdx = 0; isFalling = true; ballVy = 0; score = 0; coinCount = 0; scoreTxt.setText('0'); coinTxt.setText(storage.coins || 0); gameStarted = true; } // --- Input: tap to cycle color --- game.down = function (x, y, obj) { if (!gameStarted) { startGame(); return; } if (!canTap) { return; } canTap = false; if (ball) { ball.cycleColor(); ball.flash(); } lastTapTick = LK.ticks; }; game.up = function (x, y, obj) { canTap = true; }; // --- Main game loop --- game.update = function () { if (!gameStarted) { return; } // Ball physics if (isFalling) { ballVy += gravity; ball.y += ballVy; } // Move platforms upward to simulate ball falling // Platform upward speed: increases slightly at each difficulty level for smooth progression var moveUp = 0; if (score >= 0) { // Base speed (increased even more at first interval) moveUp = 6.2; // For every 7 points, increase speed by 0.27, up to a max var level = Math.floor(score / 7); moveUp += Math.min(level * 0.27, 4.5); // max speed increase is 4.5 (so max moveUp = 10.7) // Prevent platforms from moving up so fast that the ball can exit the top // If the ball is within 200px of the top, pause upward movement if (ball && ball.y < 200) { moveUp = 0; } } for (var i = 0; i < platforms.length; ++i) { platforms[i].y -= moveUp; platforms[i].update(); } for (var i = 0; i < coins.length; ++i) { coins[i].y -= moveUp; coins[i].update(); } // Remove platforms that go off top and immediately recycle/spawn new ones at the bottom to maintain infinite flow if (typeof lastPlatformY === "undefined") { var lastPlatformY = 1600; } // Remove platforms that go off top and robustly recycle to always maintain at least 3 platforms below the ball for (var i = platforms.length - 1; i >= 0; --i) { if (platforms[i].y < -100) { // Remove the platform from the game and array platforms[i].destroy(); platforms.splice(i, 1); } } // After removals, ensure at least 3 platforms exist, all spaced consistently below the lowest // Find the current lowest platform Y var lowestY = -Infinity; for (var j = 0; j < platforms.length; ++j) { if (platforms[j].y > lowestY) { lowestY = platforms[j].y; } } // If there are no platforms left, use the last known platform Y if (lowestY === -Infinity) { lowestY = lastPlatformY; } // Always keep at least 3 platforms while (platforms.length < 3) { var colorIdx = getRandomColorIdx(); var plat = spawnPlatform(lowestY + platformSpacing, colorIdx); lastPlatformY = plat.y; lowestY = plat.y; // Place a coin on every 3rd platform if ((score + platforms.length) % 3 === 0) { spawnCoin(plat.x, plat.y); } } for (var i = coins.length - 1; i >= 0; --i) { if (coins[i].y < -100) { coins[i].destroy(); coins.splice(i, 1); } } // (Platform count is now managed by recycling above. No need for extra always-ensure loop.) // Ball collision with platform (only check nearest platform) var hit = false; for (var i = 0; i < platforms.length; ++i) { var plat = platforms[i]; // Only check platforms below ball if (plat.y > ball.y && plat.y - ball.y < 120) { // Check horizontal overlap var dx = Math.abs(ball.x - plat.x); if (dx < plat.asset.width / 2 - 30) { // Check vertical overlap var dy = Math.abs(ball.y - plat.y); if (dy < 90) { // Ball is landing on platform hit = true; // Only trigger bounce if falling if (ballVy > 0) { // Color match? if (ball.colorIndex === plat.colorIndex) { // Success! ballVy = bounceVelocity; isFalling = true; score += 1; scoreTxt.setText(score); LK.setScore(score); LK.getSound('bounce').play(); // Flash ball ball.flash(); // Remove platform after bounce plat.destroy(); platforms.splice(i, 1); // Increase difficulty if (score === 3) { // After first 3 bounces, increase ball speed (slightly more) gravity += 0.18; bounceVelocity -= 3.5; } // Difficulty increases every 7 points, with smooth transitions and more levels if (score % 7 === 0) { // Level up: increase gravity and platform speed smoothly // Add more levels with different increments for smoothness if (score < 21) { gravity += 0.09; } else if (score < 35) { gravity += 0.08; } else if (score < 56) { gravity += 0.07; } else if (score < 84) { gravity += 0.06; } else { gravity += 0.05; } } } else { // Fail: wrong color LK.getSound('fail').play(); LK.effects.flashScreen(0xff0000, 600); LK.showGameOver(); return; } } } } } } // Ball falls off bottom if (ball.y > 2732 + 100) { LK.getSound('fail').play(); LK.effects.flashScreen(0xff0000, 600); LK.showGameOver(); return; } // Coin collection for (var i = coins.length - 1; i >= 0; --i) { var c = coins[i]; var dx = Math.abs(ball.x - c.x); var dy = Math.abs(ball.y - c.y); if (dx < 90 && dy < 90) { // Collect coin LK.getSound('coin').play(); c.destroy(); coins.splice(i, 1); coinCount += 1; storage.coins = (storage.coins || 0) + 1; coinTxt.setText(storage.coins); // Simple coin pop // (future: animate) } } }; // --- Game over / win handling is automatic by LK --- // --- Start screen: tap to start --- startGame();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
coins: 0,
unlockedSkins: [],
selectedSkin: 0
});
/****
* Classes
****/
// Ball class
var Ball = Container.expand(function () {
var self = Container.call(this);
// Ball color index: 0=red, 1=blue, 2=green, 3=yellow
self.colorIndex = 0;
self.colors = ['red', 'blue', 'green', 'yellow'];
self.asset = null;
// Attach initial asset
self.setColor = function (idx) {
if (self.asset) {
self.removeChild(self.asset);
}
self.colorIndex = idx;
var colorName = self.colors[self.colorIndex];
self.asset = self.attachAsset('ball_' + colorName, {
anchorX: 0.5,
anchorY: 0.5
});
};
self.cycleColor = function () {
var next = (self.colorIndex + 1) % self.colors.length;
self.setColor(next);
};
// For simple trail effect (MVP: just a quick flash)
self.flash = function () {
tween(self.asset, {
alpha: 0.5
}, {
duration: 60,
easing: tween.linear,
onFinish: function onFinish() {
tween(self.asset, {
alpha: 1
}, {
duration: 80
});
}
});
};
// Set initial color
self.setColor(0);
return self;
});
// Coin class
var Coin = Container.expand(function () {
var self = Container.call(this);
self.asset = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
// MVP: no animation
};
return self;
});
// Platform class
var Platform = Container.expand(function () {
var self = Container.call(this);
// Color index: 0=red, 1=blue, 2=green, 3=yellow
self.colorIndex = 0;
self.colors = ['red', 'blue', 'green', 'yellow'];
self.asset = null;
// For moving platforms (future)
self.vx = 0;
self.setColor = function (idx) {
if (self.asset) {
self.removeChild(self.asset);
}
self.colorIndex = idx;
var colorName = self.colors[self.colorIndex];
self.asset = self.attachAsset('platform_' + colorName, {
anchorX: 0.5,
anchorY: 0.5
});
};
// For MVP, platforms are static
self.update = function () {
// No horizontal movement; platforms remain centered
};
// Set initial color
self.setColor(0);
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181c20
});
/****
* Game Code
****/
// --- Game variables ---
// Ball colors (platforms and ball)
// SFX
// Music (stub, not played in MVP)
var ball = null;
var platforms = [];
var coins = [];
var score = 0;
var coinCount = 0;
var platformSpacing = 400; // vertical distance between platforms
// platformSpeed is now calculated dynamically based on score
var gravity = 1; // increases as score increases (slightly higher)
var bounceVelocity = -31; // initial bounce velocity (slightly higher magnitude)
var ballVy = 0;
var isFalling = true;
var currentPlatformIdx = 0;
var lastTapTick = 0;
var gameStarted = false;
var dragNode = null; // not used, but required by guidelines
var lastIntersecting = false;
var canTap = true; // prevent double tap in one frame
// GUI
var scoreTxt = new Text2('0', {
size: 120,
fill: '#fff'
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var coinTxt = new Text2('0', {
size: 80,
fill: '#ffd700'
});
coinTxt.anchor.set(1, 0);
coinTxt.x = -40;
coinTxt.y = 20;
LK.gui.topRight.addChild(coinTxt);
// --- Music Play/Pause and Volume Switch (Top Left) ---
var musicPlaying = true;
var musicVolume = 1; // 1 = max, 0 = muted
// Play music at start
LK.playMusic('fallingdown', {
loop: true,
fade: {
start: 0,
end: musicVolume,
duration: 600
}
});
// Play/Pause Button
var playPauseBtn = new Text2('⏸', {
size: 90,
fill: '#fff'
});
playPauseBtn.anchor.set(0, 0);
playPauseBtn.x = 110;
playPauseBtn.y = 10;
// Volume Button
var volumeBtn = new Text2('🔊', {
size: 90,
fill: '#fff'
});
volumeBtn.anchor.set(0, 0);
volumeBtn.x = 220;
volumeBtn.y = 10;
// Add to top left GUI (leave 100x100 px clear for menu)
LK.gui.topLeft.addChild(playPauseBtn);
LK.gui.topLeft.addChild(volumeBtn);
// Play/Pause toggle
playPauseBtn.down = function (x, y, obj) {
if (musicPlaying) {
LK.stopMusic();
playPauseBtn.setText('▶️');
musicPlaying = false;
} else {
LK.playMusic('fallingdown', {
loop: true,
fade: {
start: 0,
end: musicVolume,
duration: 400
}
});
playPauseBtn.setText('⏸');
musicPlaying = true;
}
};
// Volume toggle
volumeBtn.down = function (x, y, obj) {
if (musicVolume === 1) {
musicVolume = 0;
volumeBtn.setText('🔇');
if (musicPlaying) {
LK.playMusic('fallingdown', {
loop: true,
fade: {
start: 1,
end: 0,
duration: 300
}
});
}
} else {
musicVolume = 1;
volumeBtn.setText('🔊');
if (musicPlaying) {
LK.playMusic('fallingdown', {
loop: true,
fade: {
start: 0,
end: 1,
duration: 300
}
});
}
}
};
// --- Helper functions ---
function getRandomColorIdx() {
// 0=red, 1=blue, 2=green, 3=yellow
return Math.floor(Math.random() * 4);
}
function spawnPlatform(y, colorIdx) {
var plat = new Platform();
plat.setColor(colorIdx);
plat.x = 2048 / 2;
plat.y = y;
// No horizontal movement; platforms remain centered
plat.vx = 0;
platforms.push(plat);
game.addChild(plat);
return plat;
}
function spawnCoin(x, y) {
var c = new Coin();
c.x = x;
c.y = y - 60;
coins.push(c);
game.addChild(c);
return c;
}
function resetGameVars() {
score = 0;
coinCount = 0;
// platformSpeed is now calculated dynamically based on score
gravity = 1.23; // increased initial gravity for even faster ball
bounceVelocity = -43; // increased initial bounce velocity for even faster ball
ballVy = 0;
isFalling = true;
currentPlatformIdx = 0;
lastTapTick = 0;
canTap = true;
gameStarted = false;
lastIntersecting = false;
// Remove old platforms and coins
for (var i = 0; i < platforms.length; ++i) {
platforms[i].destroy();
}
for (var i = 0; i < coins.length; ++i) {
coins[i].destroy();
}
platforms = [];
coins = [];
// Reset lastPlatformY to initial spawn value
lastPlatformY = 1600;
// Remove ball
if (ball) {
ball.destroy();
ball = null;
}
}
// --- Game start ---
function startGame() {
resetGameVars();
// Add background image behind all gameplay elements
var bg = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
game.addChild(bg);
// Ball and Platforms: fill screen
// Place first platform lower to give player time to react
var y = 1600; // Lower on screen for first platform
var firstPlatformColorIdx = getRandomColorIdx();
ball = new Ball();
ball.setColor(firstPlatformColorIdx);
ball.x = 2048 / 2;
ball.y = 600;
game.addChild(ball);
for (var i = 0; i < 8; ++i) {
var colorIdx = i === 0 ? firstPlatformColorIdx : getRandomColorIdx();
var plat = spawnPlatform(y, colorIdx);
lastPlatformY = plat.y;
// Place a coin on every 3rd platform
if (i > 0 && i % 3 === 0) {
spawnCoin(plat.x, plat.y);
}
y += platformSpacing;
}
currentPlatformIdx = 0;
isFalling = true;
ballVy = 0;
score = 0;
coinCount = 0;
scoreTxt.setText('0');
coinTxt.setText(storage.coins || 0);
gameStarted = true;
}
// --- Input: tap to cycle color ---
game.down = function (x, y, obj) {
if (!gameStarted) {
startGame();
return;
}
if (!canTap) {
return;
}
canTap = false;
if (ball) {
ball.cycleColor();
ball.flash();
}
lastTapTick = LK.ticks;
};
game.up = function (x, y, obj) {
canTap = true;
};
// --- Main game loop ---
game.update = function () {
if (!gameStarted) {
return;
}
// Ball physics
if (isFalling) {
ballVy += gravity;
ball.y += ballVy;
}
// Move platforms upward to simulate ball falling
// Platform upward speed: increases slightly at each difficulty level for smooth progression
var moveUp = 0;
if (score >= 0) {
// Base speed (increased even more at first interval)
moveUp = 6.2;
// For every 7 points, increase speed by 0.27, up to a max
var level = Math.floor(score / 7);
moveUp += Math.min(level * 0.27, 4.5); // max speed increase is 4.5 (so max moveUp = 10.7)
// Prevent platforms from moving up so fast that the ball can exit the top
// If the ball is within 200px of the top, pause upward movement
if (ball && ball.y < 200) {
moveUp = 0;
}
}
for (var i = 0; i < platforms.length; ++i) {
platforms[i].y -= moveUp;
platforms[i].update();
}
for (var i = 0; i < coins.length; ++i) {
coins[i].y -= moveUp;
coins[i].update();
}
// Remove platforms that go off top and immediately recycle/spawn new ones at the bottom to maintain infinite flow
if (typeof lastPlatformY === "undefined") {
var lastPlatformY = 1600;
}
// Remove platforms that go off top and robustly recycle to always maintain at least 3 platforms below the ball
for (var i = platforms.length - 1; i >= 0; --i) {
if (platforms[i].y < -100) {
// Remove the platform from the game and array
platforms[i].destroy();
platforms.splice(i, 1);
}
}
// After removals, ensure at least 3 platforms exist, all spaced consistently below the lowest
// Find the current lowest platform Y
var lowestY = -Infinity;
for (var j = 0; j < platforms.length; ++j) {
if (platforms[j].y > lowestY) {
lowestY = platforms[j].y;
}
}
// If there are no platforms left, use the last known platform Y
if (lowestY === -Infinity) {
lowestY = lastPlatformY;
}
// Always keep at least 3 platforms
while (platforms.length < 3) {
var colorIdx = getRandomColorIdx();
var plat = spawnPlatform(lowestY + platformSpacing, colorIdx);
lastPlatformY = plat.y;
lowestY = plat.y;
// Place a coin on every 3rd platform
if ((score + platforms.length) % 3 === 0) {
spawnCoin(plat.x, plat.y);
}
}
for (var i = coins.length - 1; i >= 0; --i) {
if (coins[i].y < -100) {
coins[i].destroy();
coins.splice(i, 1);
}
}
// (Platform count is now managed by recycling above. No need for extra always-ensure loop.)
// Ball collision with platform (only check nearest platform)
var hit = false;
for (var i = 0; i < platforms.length; ++i) {
var plat = platforms[i];
// Only check platforms below ball
if (plat.y > ball.y && plat.y - ball.y < 120) {
// Check horizontal overlap
var dx = Math.abs(ball.x - plat.x);
if (dx < plat.asset.width / 2 - 30) {
// Check vertical overlap
var dy = Math.abs(ball.y - plat.y);
if (dy < 90) {
// Ball is landing on platform
hit = true;
// Only trigger bounce if falling
if (ballVy > 0) {
// Color match?
if (ball.colorIndex === plat.colorIndex) {
// Success!
ballVy = bounceVelocity;
isFalling = true;
score += 1;
scoreTxt.setText(score);
LK.setScore(score);
LK.getSound('bounce').play();
// Flash ball
ball.flash();
// Remove platform after bounce
plat.destroy();
platforms.splice(i, 1);
// Increase difficulty
if (score === 3) {
// After first 3 bounces, increase ball speed (slightly more)
gravity += 0.18;
bounceVelocity -= 3.5;
}
// Difficulty increases every 7 points, with smooth transitions and more levels
if (score % 7 === 0) {
// Level up: increase gravity and platform speed smoothly
// Add more levels with different increments for smoothness
if (score < 21) {
gravity += 0.09;
} else if (score < 35) {
gravity += 0.08;
} else if (score < 56) {
gravity += 0.07;
} else if (score < 84) {
gravity += 0.06;
} else {
gravity += 0.05;
}
}
} else {
// Fail: wrong color
LK.getSound('fail').play();
LK.effects.flashScreen(0xff0000, 600);
LK.showGameOver();
return;
}
}
}
}
}
}
// Ball falls off bottom
if (ball.y > 2732 + 100) {
LK.getSound('fail').play();
LK.effects.flashScreen(0xff0000, 600);
LK.showGameOver();
return;
}
// Coin collection
for (var i = coins.length - 1; i >= 0; --i) {
var c = coins[i];
var dx = Math.abs(ball.x - c.x);
var dy = Math.abs(ball.y - c.y);
if (dx < 90 && dy < 90) {
// Collect coin
LK.getSound('coin').play();
c.destroy();
coins.splice(i, 1);
coinCount += 1;
storage.coins = (storage.coins || 0) + 1;
coinTxt.setText(storage.coins);
// Simple coin pop
// (future: animate)
}
}
};
// --- Game over / win handling is automatic by LK ---
// --- Start screen: tap to start ---
startGame();
blue ball. In-Game asset. 2d. High contrast. No shadows
green ball. In-Game asset. 2d. High contrast. No shadows
red ball. In-Game asset. 2d. High contrast. No shadows
yellow ball. In-Game asset. 2d. High contrast. No shadows
coin. In-Game asset. 2d. High contrast. No shadows
blue platform. In-Game asset. 2d. High contrast. No shadows
green platform. In-Game asset. 2d. High contrast. No shadows
red platform. In-Game asset. 2d. High contrast. No shadows
yellow platform. In-Game asset. 2d. High contrast. No shadows
Create a vertical-scrolling, futuristic game background for a rhythm-based mobile game. The style should be vibrant and glowing, with a deep gradient from dark purple to electric blue. Include subtle abstract shapes like floating geometric particles, soft energy lines, and a faint digital grid. The mood should feel like you're inside a pulsing rhythm tunnel or neon cyber tower. No text, no characters — just atmospheric depth and motion-friendly layers. Must loop seamlessly for vertical scrolling. Style: sleek, minimal, energetic.. In-Game asset. 2d. High contrast. No shadows