User prompt
Add variety to the melody by introducing multiple predefined spawn patterns. Alternate between at least four different melody patterns such as: Pattern A: [2, 2, 3, 3, 4, 4, 3, 3, 2] Pattern B: [4, 3, 2, 2, 1, 1, 2, 3] Pattern C: [1, 3, 2, 4, 1, 3, 2, 4] Pattern D: [4, 2, 4, 2, 3, 3, 1, 1] Cycle through these patterns in order to create a richer musical experience while maintaining the beat synchronization.
User prompt
Make the game endless. Loop the background drum track (gamesound3) continuously, and repeat the predefined melody spawn pattern indefinitely. Ensure the game does not end automatically when the music ends or a tile is missed. Allow players to keep playing as long as they like.
User prompt
Create a predefined tile spawn sequence to generate a melodic pattern using the following column order, repeated every 8 beats: [2, 2, 3, 3, 4, 4, 3, 3, 2] Use this pattern to spawn tiles so that when the player taps in time with the beat, it creates a harmonious melody using the note sounds (C4, E4, G4). Make sure tiles appear on beat intervals and always follow this pattern during the entire gameplay duration.
User prompt
Replace the background music with 'gamesounds3' and set MUSIC_DURATION to 22 seconds. Remove all melodic content from the background. Assign melodic note sounds to each column tap: - Column 1: noteA3 - Column 2: noteC4 - Column 3: noteE4 - Column 4: noteG4 Ensure that tapping the tiles produces the melody while the drum track plays underneath.
User prompt
Mute the melodic part of the background music. Only play a rhythmic backing track. Assign melodic note sounds (A3, C4, E4, G4) to the tile taps so the player creates the melody by tapping in time. Sync tile spawn to where notes would naturally occur in the melody.
User prompt
Set the background music to 'gamesound2' and set MUSIC_DURATION to 22 seconds. Adjust tile spawn intervals to match the beat of the track and ensure tile movement follows the rhythm of the song.
User prompt
Update tile tap sounds to match the harmonic scale of the background music. Use noteA3, noteC4, noteE4, and noteG4 for columns 1 to 4 respectively. Each tap should sound harmonious with gamesound1.
User prompt
Assign sound effects to tile taps: - Column 1: noteC4 - Column 2: noteD4 - Column 3: noteE4 - Column 4: noteG4 Play the respective sound when a tile is successfully tapped in each column.
User prompt
Assign sound effects to tile taps: - Column 1: noteC4 - Column 2: noteD4 - Column 3: noteE4 - Column 4: noteG4 Play the respective sound when a tile is successfully tapped in each column.
User prompt
Create a melodic piano tone for the note [C4/D4/E4/G4] that is crisp and game-friendly, lasting about 0.5 seconds.
User prompt
Set the background music to 'gamesound1'. Set the MUSIC_DURATION to [örnek: 38] seconds. Set tile spawn interval to match the beat of gamesound1, e.g., every 375ms if it's 160 BPM. Ensure the music starts with the game and tiles are synchronized to the beat.
Code edit (1 edits merged)
Please save this source code
User prompt
Beat Drop - Rhythm Tap Challenge
Initial prompt
Create a rhythm game with 4 vertical columns. Black tiles fall from the top, timed to the beat of a background music track. Players must tap the tiles as they reach the bottom. Each successful tap plays a sound and gives a point. The game ends when the music finishes and displays a final score.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Column = Container.expand(function () { var self = Container.call(this); var columnGraphics = self.attachAsset('column', { anchorX: 0.5, anchorY: 0, alpha: 0.3 }); var scoreZone = self.attachAsset('scoreZone', { anchorX: 0.5, anchorY: 0.5, y: 2732 - 200, alpha: 0.4 }); self.scoreZoneY = 2732 - 200; return self; }); var Tile = Container.expand(function () { var self = Container.call(this); var tileGraphics = self.attachAsset('tile', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 6; self.columnIndex = 0; self.scored = false; self.missed = false; self.update = function () { self.y += self.speed; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1a1a2e }); /**** * Game Code ****/ var columns = []; var tiles = []; var score = 0; var gameStarted = false; var musicDuration = 22000; // 22 seconds var gameStartTime = 0; var perfectZone = 50; var goodZone = 100; var pianoTones = ['noteA3', 'noteC4', 'noteE4', 'noteG4']; var columnDelays = [0, 50, 100, 150]; // Stagger effects by 50ms per column var lastBeatTime = 0; var beatCount = 0; var baseAnimationIntensity = 1.0; var comboCount = 0; var comboCounter = 0; // Create background overlay for rhythmic effects var backgroundOverlay = LK.getAsset('backgroundOverlay', { anchorX: 0.5, anchorY: 0.5, alpha: 0.05, x: 2048 / 2, y: 2732 / 2 }); game.addChild(backgroundOverlay); // Create back gray overlay for beat animation var backgrayoverlay = LK.getAsset('backgrayoverlay', { anchorX: 0.5, anchorY: 0.5, alpha: 0, x: 2048 / 2, y: 2732 / 2 }); game.addChild(backgrayoverlay); // Create pulse flash overlay at center, behind falling tiles var pulseFlashFull = LK.getAsset('pulseFlash1', { anchorX: 0.5, anchorY: 0.5, alpha: 0, scaleX: 2048 / 100, // Scale to screen width (2048px / 100px asset) scaleY: 2732 / 100, // Scale to screen height (2732px / 100px asset) x: 2048 / 2, y: 2732 / 2 }); game.addChild(pulseFlashFull); // Create UI var scoreTxt = new Text2('Score: 0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Create combo display var comboDisplay = new Text2('', { size: 60, fill: 0xFFD700, fontWeight: 'bold' }); comboDisplay.anchor.set(1, 0); comboDisplay.visible = false; LK.gui.topRight.addChild(comboDisplay); // Position relative to topRight anchor comboDisplay.x = -80; comboDisplay.y = 40; // Create columns var columnWidth = 2048 / 4; for (var i = 0; i < 4; i++) { var column = new Column(); column.x = i * columnWidth + columnWidth / 2; column.y = 0; columns.push(column); game.addChild(column); } // Multiple predefined melody patterns for variety var melodyPatterns = [[1, 1, 2, 2, 3, 3, 2, 2, 1], // Pattern A: C4, C4, E4, E4, G4, G4, E4, E4, C4 [3, 2, 1, 1, 0, 0, 1, 2], // Pattern B: G4, E4, C4, C4, A3, A3, C4, E4 [0, 2, 1, 3, 0, 2, 1, 3], // Pattern C: A3, E4, C4, G4, A3, E4, C4, G4 [3, 1, 3, 1, 2, 2, 0, 0] // Pattern D: G4, C4, G4, C4, E4, E4, A3, A3 ]; var currentPatternIndex = 0; var melodyPattern = melodyPatterns[currentPatternIndex]; var spawnPattern = []; var beatInterval = 500; // 500ms per beat for steady rhythm var patternRepeatDuration = melodyPattern.length * beatInterval; // 9 beats = 4500ms per pattern // Generate spawn pattern by repeating the melody pattern throughout the game duration var currentTime = 500; // Start after 500ms var patternPosition = 0; while (currentTime < musicDuration) { spawnPattern.push({ time: currentTime, columns: [melodyPattern[patternPosition]] }); currentTime += beatInterval; patternPosition = (patternPosition + 1) % melodyPattern.length; } var patternIndex = 0; function spawnTile(columnIndex) { var tile = new Tile(); tile.columnIndex = columnIndex; tile.x = columns[columnIndex].x; tile.y = -60; tiles.push(tile); game.addChild(tile); } function calculateScore(distance) { if (distance <= perfectZone) { return 100; } else if (distance <= goodZone) { return 50; } return 0; } function updateScore(points) { score += points; scoreTxt.setText('Score: ' + score); } function triggerBeatAnimation() { beatCount++; // Calculate animation intensity based on score milestones var scoreMultiplier = 1.0; if (score >= 5000) { scoreMultiplier = 2.0; } else if (score >= 2500) { scoreMultiplier = 1.7; } else if (score >= 1000) { scoreMultiplier = 1.4; } else if (score >= 500) { scoreMultiplier = 1.2; } var animationIntensity = baseAnimationIntensity * scoreMultiplier; // Background pulse - scale and brightness var pulseScale = 1.0 + 0.02 * animationIntensity; // 2% scale increase max var pulseAlpha = 0.05 + 0.01 * animationIntensity; // 1% brightness increase max tween(backgroundOverlay, { scaleX: pulseScale, scaleY: pulseScale, alpha: pulseAlpha }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(backgroundOverlay, { scaleX: 1.0, scaleY: 1.0, alpha: 0.05 }, { duration: 200, easing: tween.easeIn }); } }); // Pulse flash animation - fade in then out on every beat tween(pulseFlashFull, { alpha: 0.35 }, { duration: 0, onFinish: function onFinish() { tween(pulseFlashFull, { alpha: 0 }, { duration: 300, easing: tween.easeOut }); } }); // Subtle rotation effect that increases with score var rotationAmount = beatCount % 8 * 0.01 * animationIntensity; // Very subtle rotation tween(backgroundOverlay, { rotation: rotationAmount }, { duration: 300, easing: tween.easeInOut }); // Back gray overlay beat animation - fade in to 0.35 then out over 300ms tween(backgrayoverlay, { alpha: 0.35 }, { duration: 0, onFinish: function onFinish() { tween(backgrayoverlay, { alpha: 0 }, { duration: 300, easing: tween.easeOut }); } }); } game.down = function (x, y, obj) { if (!gameStarted) { gameStarted = true; gameStartTime = LK.ticks * (1000 / 60); LK.playMusic('gamesound3_backing', { loop: true }); return; } // Determine which column was tapped var columnIndex = Math.floor(x / columnWidth); if (columnIndex < 0) { columnIndex = 0; } if (columnIndex > 3) { columnIndex = 3; } var hitTile = null; var bestDistance = Infinity; // Find the closest tile in the tapped column within scoring range for (var i = 0; i < tiles.length; i++) { var tile = tiles[i]; if (tile.columnIndex === columnIndex && !tile.scored && !tile.missed) { var scoreZoneY = columns[columnIndex].scoreZoneY; var distance = Math.abs(tile.y - scoreZoneY); if (distance <= goodZone && distance < bestDistance) { bestDistance = distance; hitTile = tile; } } } if (hitTile) { hitTile.scored = true; var points = calculateScore(bestDistance); updateScore(points); // Increment combo count comboCount++; comboCounter++; // Update combo display if (comboCounter < 2) { comboDisplay.visible = false; } else { comboDisplay.visible = true; comboDisplay.setText('Combo x' + comboCounter); // Check for color change and scaling on multiples of 10 if (comboCounter % 10 === 0 && comboCounter > 0) { // Change color to bright red temporarily comboDisplay.tint = 0xFF4444; // Scale animation - larger scale for more dramatic effect tween(comboDisplay, { scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(comboDisplay, { scaleX: 1.0, scaleY: 1.0 }, { duration: 400, easing: tween.easeInOut }); } }); // Reset color after 600ms LK.setTimeout(function () { comboDisplay.tint = 0xFFD700; }, 600); } } // Check for 10-hit combo streak if (comboCount % 10 === 0) { // Spawn 'comboGlow' behind tiles with opacity 0.5 and fade it out over 400ms var comboGlow = LK.getAsset('comboGlow', { anchorX: 0.5, anchorY: 0.5, alpha: 0.5, x: 2048 / 2, y: 2732 / 2 }); game.addChild(comboGlow); // Fade out comboGlow over 400ms tween(comboGlow, { alpha: 0 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { comboGlow.destroy(); } }); // Spawn 'comboText' at (screen center X, 100px from top) var comboText = new Text2('Combo x' + comboCount + '!', { size: 200, fill: 0xFFD700 }); comboText.anchor.set(0.5, 0.5); comboText.x = 2048 / 2; comboText.y = 280; // 100px from top comboText.alpha = 0; comboText.scaleX = 0.8; comboText.scaleY = 0.8; game.addChild(comboText); // Fade in over 150ms with scaling popping effect tween(comboText, { alpha: 1, scaleX: 1.2, scaleY: 1.2 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { // Hold for 400ms, then fade out over 300ms LK.setTimeout(function () { tween(comboText, { alpha: 0, scaleX: 1.0, scaleY: 1.0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { comboText.destroy(); } }); }, 400); } }); } // Create color burst effect with bright, saturated colors for each column var burstColors = [0x9932CC, 0x0000FF, 0xFFFF00, 0xFF0000]; // Purple, Blue, Yellow, Red var burstColor = burstColors[columnIndex]; var columnDelay = columnDelays[columnIndex]; // Create column light-up effect LK.setTimeout(function () { var columnOverlay = LK.getAsset('columnOverlay', { anchorX: 0.5, anchorY: 0, tint: burstColor, alpha: 0.4, x: columns[columnIndex].x, y: 0 }); game.addChild(columnOverlay); // Animate the column overlay with pulse effect tween(columnOverlay, { alpha: 0 }, { duration: 350, easing: tween.easeOut, onFinish: function onFinish() { columnOverlay.destroy(); } }); }, columnDelay); // Spawn 'burst1' at the tile's position - limited to 1.5x tile size with vivid colors LK.setTimeout(function () { var burst1 = LK.getAsset('brust1', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.6, tint: burstColor, alpha: 0.9, x: hitTile.x, y: hitTile.y }); game.addChild(burst1); tween(burst1, { scaleX: 1.8, // Limited to 1.5x tile size (400px * 1.5 = 600px, so scale ~1.8 for 100px asset) scaleY: 1.8, alpha: 0 }, { duration: 350, easing: tween.easeOut, onFinish: function onFinish() { burst1.destroy(); } }); }, columnDelay); // Overlay 'glowring1' with matching column color and radial gradient effect LK.setTimeout(function () { var glowRing = LK.getAsset('glowring1', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8, tint: burstColor, alpha: 0.8, x: hitTile.x, y: hitTile.y }); game.addChild(glowRing); tween(glowRing, { scaleX: 1.8, // Limited to 1.5x tile size scaleY: 1.8, alpha: 0 }, { duration: 380, easing: tween.easeInOut, onFinish: function onFinish() { glowRing.destroy(); } }); }, columnDelay + 25); // Create spark line animation near the tile LK.setTimeout(function () { for (var s = 0; s < 4; s++) { var sparkLine = LK.getAsset('sparkline', { anchorX: 0.5, anchorY: 0.5, tint: burstColor, alpha: 0.9, x: hitTile.x + (Math.random() - 0.5) * 100, y: hitTile.y + (Math.random() - 0.5) * 100, rotation: Math.random() * Math.PI * 2 }); game.addChild(sparkLine); tween(sparkLine, { scaleX: 2, scaleY: 0.2, alpha: 0, rotation: sparkLine.rotation + Math.PI }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { sparkLine.destroy(); } }); } }, columnDelay + 50); // Emit 5-6 small 'trail1' particles radiating outward with small movement radius LK.setTimeout(function () { var numTrails = 5 + Math.floor(Math.random() * 2); // 5-6 particles for (var t = 0; t < numTrails; t++) { var trail = LK.getAsset('trail1', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.3, tint: burstColor, alpha: 0.8, x: hitTile.x, y: hitTile.y }); game.addChild(trail); var angle = t / numTrails * Math.PI * 2 + Math.random() * 0.3; var distance = 40 + Math.random() * 40; // Small movement radius within 80px var targetX = hitTile.x + Math.cos(angle) * distance; var targetY = hitTile.y + Math.sin(angle) * distance; tween(trail, { x: targetX, y: targetY, alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { trail.destroy(); } }); } }, columnDelay + 75); // Spawn columnFlash overlay at tile position with upward then downward animation var columnFlashAssets = ['columnFlash1', 'columnFlash2', 'columnFlash3', 'columnFlash4']; var originalY = hitTile.y; var columnFlash = LK.getAsset(columnFlashAssets[columnIndex], { anchorX: 0.5, anchorY: 0.5, alpha: 1.0, x: hitTile.x, y: originalY }); game.addChild(columnFlash); // Animate upward movement over 150ms with ease-out tween(columnFlash, { y: originalY - 100 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { // Animate downward movement back to original position over 150ms with ease-in tween(columnFlash, { y: originalY }, { duration: 150, easing: tween.easeIn, onFinish: function onFinish() { columnFlash.destroy(); } }); } }); // Fade out overlay over full 300ms duration tween(columnFlash, { alpha: 0 }, { duration: 300, easing: tween.easeOut }); // Visual feedback for scoring if (points === 100) { LK.effects.flashObject(hitTile, 0x00ff00, 200); } else if (points === 50) { LK.effects.flashObject(hitTile, 0xffff00, 200); } LK.getSound(pianoTones[columnIndex]).play(); // Remove tile hitTile.destroy(); for (var j = tiles.length - 1; j >= 0; j--) { if (tiles[j] === hitTile) { tiles.splice(j, 1); break; } } } }; game.update = function () { if (!gameStarted) { return; } var currentTime = LK.ticks * (1000 / 60) - gameStartTime; // Beat detection for background animation var currentBeatTime = Math.floor(currentTime / beatInterval) * beatInterval; if (currentBeatTime !== lastBeatTime && currentTime >= 500) { lastBeatTime = currentBeatTime; triggerBeatAnimation(); } // Spawn tiles according to endless repeating pattern with pattern cycling and controlled randomness var patternTime = currentTime % patternRepeatDuration; // Loop the pattern timing var shouldSpawn = false; // Check if we need to switch to the next pattern var patternCycleTime = Math.floor(currentTime / patternRepeatDuration); var newPatternIndex = patternCycleTime % melodyPatterns.length; if (newPatternIndex !== currentPatternIndex) { currentPatternIndex = newPatternIndex; melodyPattern = melodyPatterns[currentPatternIndex]; } for (var p = 0; p < melodyPattern.length; p++) { var spawnTime = p * beatInterval + 500; // Add initial 500ms offset if (Math.abs(patternTime - spawnTime) < 16) { // 16ms tolerance for 60fps var targetColumn = melodyPattern[p]; var finalColumn = targetColumn; // Add controlled randomness - 20% chance to vary the column if (Math.random() < 0.2) { // Define harmonic variations for each base column var harmonicVariations = [[0, 1], // A3 can vary to C4 [1, 2], // C4 can vary to E4 [2, 3, 1], // E4 can vary to G4 or back to C4 [3, 2] // G4 can vary to E4 ]; var variations = harmonicVariations[targetColumn]; if (variations && variations.length > 0) { finalColumn = variations[Math.floor(Math.random() * variations.length)]; } } spawnTile(finalColumn); shouldSpawn = true; break; } } // Update tiles and check for misses for (var i = tiles.length - 1; i >= 0; i--) { var tile = tiles[i]; // Check if tile passed the scoring zone without being hit if (!tile.scored && !tile.missed && tile.y > columns[tile.columnIndex].scoreZoneY + goodZone) { tile.missed = true; comboCount = 0; // Reset combo on miss comboCounter = 0; // Reset combo counter on miss // Update combo display visibility and reset properties comboDisplay.visible = false; comboDisplay.tint = 0xFFD700; comboDisplay.scaleX = 1.0; comboDisplay.scaleY = 1.0; LK.effects.flashObject(tile, 0xff0000, 300); LK.getSound('miss').play(); } // Remove tiles that are off screen if (tile.y > 2732 + 100) { tile.destroy(); tiles.splice(i, 1); } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Column = Container.expand(function () {
var self = Container.call(this);
var columnGraphics = self.attachAsset('column', {
anchorX: 0.5,
anchorY: 0,
alpha: 0.3
});
var scoreZone = self.attachAsset('scoreZone', {
anchorX: 0.5,
anchorY: 0.5,
y: 2732 - 200,
alpha: 0.4
});
self.scoreZoneY = 2732 - 200;
return self;
});
var Tile = Container.expand(function () {
var self = Container.call(this);
var tileGraphics = self.attachAsset('tile', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 6;
self.columnIndex = 0;
self.scored = false;
self.missed = false;
self.update = function () {
self.y += self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
var columns = [];
var tiles = [];
var score = 0;
var gameStarted = false;
var musicDuration = 22000; // 22 seconds
var gameStartTime = 0;
var perfectZone = 50;
var goodZone = 100;
var pianoTones = ['noteA3', 'noteC4', 'noteE4', 'noteG4'];
var columnDelays = [0, 50, 100, 150]; // Stagger effects by 50ms per column
var lastBeatTime = 0;
var beatCount = 0;
var baseAnimationIntensity = 1.0;
var comboCount = 0;
var comboCounter = 0;
// Create background overlay for rhythmic effects
var backgroundOverlay = LK.getAsset('backgroundOverlay', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.05,
x: 2048 / 2,
y: 2732 / 2
});
game.addChild(backgroundOverlay);
// Create back gray overlay for beat animation
var backgrayoverlay = LK.getAsset('backgrayoverlay', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
x: 2048 / 2,
y: 2732 / 2
});
game.addChild(backgrayoverlay);
// Create pulse flash overlay at center, behind falling tiles
var pulseFlashFull = LK.getAsset('pulseFlash1', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
scaleX: 2048 / 100,
// Scale to screen width (2048px / 100px asset)
scaleY: 2732 / 100,
// Scale to screen height (2732px / 100px asset)
x: 2048 / 2,
y: 2732 / 2
});
game.addChild(pulseFlashFull);
// Create UI
var scoreTxt = new Text2('Score: 0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Create combo display
var comboDisplay = new Text2('', {
size: 60,
fill: 0xFFD700,
fontWeight: 'bold'
});
comboDisplay.anchor.set(1, 0);
comboDisplay.visible = false;
LK.gui.topRight.addChild(comboDisplay);
// Position relative to topRight anchor
comboDisplay.x = -80;
comboDisplay.y = 40;
// Create columns
var columnWidth = 2048 / 4;
for (var i = 0; i < 4; i++) {
var column = new Column();
column.x = i * columnWidth + columnWidth / 2;
column.y = 0;
columns.push(column);
game.addChild(column);
}
// Multiple predefined melody patterns for variety
var melodyPatterns = [[1, 1, 2, 2, 3, 3, 2, 2, 1],
// Pattern A: C4, C4, E4, E4, G4, G4, E4, E4, C4
[3, 2, 1, 1, 0, 0, 1, 2],
// Pattern B: G4, E4, C4, C4, A3, A3, C4, E4
[0, 2, 1, 3, 0, 2, 1, 3],
// Pattern C: A3, E4, C4, G4, A3, E4, C4, G4
[3, 1, 3, 1, 2, 2, 0, 0] // Pattern D: G4, C4, G4, C4, E4, E4, A3, A3
];
var currentPatternIndex = 0;
var melodyPattern = melodyPatterns[currentPatternIndex];
var spawnPattern = [];
var beatInterval = 500; // 500ms per beat for steady rhythm
var patternRepeatDuration = melodyPattern.length * beatInterval; // 9 beats = 4500ms per pattern
// Generate spawn pattern by repeating the melody pattern throughout the game duration
var currentTime = 500; // Start after 500ms
var patternPosition = 0;
while (currentTime < musicDuration) {
spawnPattern.push({
time: currentTime,
columns: [melodyPattern[patternPosition]]
});
currentTime += beatInterval;
patternPosition = (patternPosition + 1) % melodyPattern.length;
}
var patternIndex = 0;
function spawnTile(columnIndex) {
var tile = new Tile();
tile.columnIndex = columnIndex;
tile.x = columns[columnIndex].x;
tile.y = -60;
tiles.push(tile);
game.addChild(tile);
}
function calculateScore(distance) {
if (distance <= perfectZone) {
return 100;
} else if (distance <= goodZone) {
return 50;
}
return 0;
}
function updateScore(points) {
score += points;
scoreTxt.setText('Score: ' + score);
}
function triggerBeatAnimation() {
beatCount++;
// Calculate animation intensity based on score milestones
var scoreMultiplier = 1.0;
if (score >= 5000) {
scoreMultiplier = 2.0;
} else if (score >= 2500) {
scoreMultiplier = 1.7;
} else if (score >= 1000) {
scoreMultiplier = 1.4;
} else if (score >= 500) {
scoreMultiplier = 1.2;
}
var animationIntensity = baseAnimationIntensity * scoreMultiplier;
// Background pulse - scale and brightness
var pulseScale = 1.0 + 0.02 * animationIntensity; // 2% scale increase max
var pulseAlpha = 0.05 + 0.01 * animationIntensity; // 1% brightness increase max
tween(backgroundOverlay, {
scaleX: pulseScale,
scaleY: pulseScale,
alpha: pulseAlpha
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(backgroundOverlay, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.05
}, {
duration: 200,
easing: tween.easeIn
});
}
});
// Pulse flash animation - fade in then out on every beat
tween(pulseFlashFull, {
alpha: 0.35
}, {
duration: 0,
onFinish: function onFinish() {
tween(pulseFlashFull, {
alpha: 0
}, {
duration: 300,
easing: tween.easeOut
});
}
});
// Subtle rotation effect that increases with score
var rotationAmount = beatCount % 8 * 0.01 * animationIntensity; // Very subtle rotation
tween(backgroundOverlay, {
rotation: rotationAmount
}, {
duration: 300,
easing: tween.easeInOut
});
// Back gray overlay beat animation - fade in to 0.35 then out over 300ms
tween(backgrayoverlay, {
alpha: 0.35
}, {
duration: 0,
onFinish: function onFinish() {
tween(backgrayoverlay, {
alpha: 0
}, {
duration: 300,
easing: tween.easeOut
});
}
});
}
game.down = function (x, y, obj) {
if (!gameStarted) {
gameStarted = true;
gameStartTime = LK.ticks * (1000 / 60);
LK.playMusic('gamesound3_backing', {
loop: true
});
return;
}
// Determine which column was tapped
var columnIndex = Math.floor(x / columnWidth);
if (columnIndex < 0) {
columnIndex = 0;
}
if (columnIndex > 3) {
columnIndex = 3;
}
var hitTile = null;
var bestDistance = Infinity;
// Find the closest tile in the tapped column within scoring range
for (var i = 0; i < tiles.length; i++) {
var tile = tiles[i];
if (tile.columnIndex === columnIndex && !tile.scored && !tile.missed) {
var scoreZoneY = columns[columnIndex].scoreZoneY;
var distance = Math.abs(tile.y - scoreZoneY);
if (distance <= goodZone && distance < bestDistance) {
bestDistance = distance;
hitTile = tile;
}
}
}
if (hitTile) {
hitTile.scored = true;
var points = calculateScore(bestDistance);
updateScore(points);
// Increment combo count
comboCount++;
comboCounter++;
// Update combo display
if (comboCounter < 2) {
comboDisplay.visible = false;
} else {
comboDisplay.visible = true;
comboDisplay.setText('Combo x' + comboCounter);
// Check for color change and scaling on multiples of 10
if (comboCounter % 10 === 0 && comboCounter > 0) {
// Change color to bright red temporarily
comboDisplay.tint = 0xFF4444;
// Scale animation - larger scale for more dramatic effect
tween(comboDisplay, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(comboDisplay, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 400,
easing: tween.easeInOut
});
}
});
// Reset color after 600ms
LK.setTimeout(function () {
comboDisplay.tint = 0xFFD700;
}, 600);
}
}
// Check for 10-hit combo streak
if (comboCount % 10 === 0) {
// Spawn 'comboGlow' behind tiles with opacity 0.5 and fade it out over 400ms
var comboGlow = LK.getAsset('comboGlow', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
game.addChild(comboGlow);
// Fade out comboGlow over 400ms
tween(comboGlow, {
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
comboGlow.destroy();
}
});
// Spawn 'comboText' at (screen center X, 100px from top)
var comboText = new Text2('Combo x' + comboCount + '!', {
size: 200,
fill: 0xFFD700
});
comboText.anchor.set(0.5, 0.5);
comboText.x = 2048 / 2;
comboText.y = 280; // 100px from top
comboText.alpha = 0;
comboText.scaleX = 0.8;
comboText.scaleY = 0.8;
game.addChild(comboText);
// Fade in over 150ms with scaling popping effect
tween(comboText, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
// Hold for 400ms, then fade out over 300ms
LK.setTimeout(function () {
tween(comboText, {
alpha: 0,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
comboText.destroy();
}
});
}, 400);
}
});
}
// Create color burst effect with bright, saturated colors for each column
var burstColors = [0x9932CC, 0x0000FF, 0xFFFF00, 0xFF0000]; // Purple, Blue, Yellow, Red
var burstColor = burstColors[columnIndex];
var columnDelay = columnDelays[columnIndex];
// Create column light-up effect
LK.setTimeout(function () {
var columnOverlay = LK.getAsset('columnOverlay', {
anchorX: 0.5,
anchorY: 0,
tint: burstColor,
alpha: 0.4,
x: columns[columnIndex].x,
y: 0
});
game.addChild(columnOverlay);
// Animate the column overlay with pulse effect
tween(columnOverlay, {
alpha: 0
}, {
duration: 350,
easing: tween.easeOut,
onFinish: function onFinish() {
columnOverlay.destroy();
}
});
}, columnDelay);
// Spawn 'burst1' at the tile's position - limited to 1.5x tile size with vivid colors
LK.setTimeout(function () {
var burst1 = LK.getAsset('brust1', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6,
tint: burstColor,
alpha: 0.9,
x: hitTile.x,
y: hitTile.y
});
game.addChild(burst1);
tween(burst1, {
scaleX: 1.8,
// Limited to 1.5x tile size (400px * 1.5 = 600px, so scale ~1.8 for 100px asset)
scaleY: 1.8,
alpha: 0
}, {
duration: 350,
easing: tween.easeOut,
onFinish: function onFinish() {
burst1.destroy();
}
});
}, columnDelay);
// Overlay 'glowring1' with matching column color and radial gradient effect
LK.setTimeout(function () {
var glowRing = LK.getAsset('glowring1', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8,
tint: burstColor,
alpha: 0.8,
x: hitTile.x,
y: hitTile.y
});
game.addChild(glowRing);
tween(glowRing, {
scaleX: 1.8,
// Limited to 1.5x tile size
scaleY: 1.8,
alpha: 0
}, {
duration: 380,
easing: tween.easeInOut,
onFinish: function onFinish() {
glowRing.destroy();
}
});
}, columnDelay + 25);
// Create spark line animation near the tile
LK.setTimeout(function () {
for (var s = 0; s < 4; s++) {
var sparkLine = LK.getAsset('sparkline', {
anchorX: 0.5,
anchorY: 0.5,
tint: burstColor,
alpha: 0.9,
x: hitTile.x + (Math.random() - 0.5) * 100,
y: hitTile.y + (Math.random() - 0.5) * 100,
rotation: Math.random() * Math.PI * 2
});
game.addChild(sparkLine);
tween(sparkLine, {
scaleX: 2,
scaleY: 0.2,
alpha: 0,
rotation: sparkLine.rotation + Math.PI
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
sparkLine.destroy();
}
});
}
}, columnDelay + 50);
// Emit 5-6 small 'trail1' particles radiating outward with small movement radius
LK.setTimeout(function () {
var numTrails = 5 + Math.floor(Math.random() * 2); // 5-6 particles
for (var t = 0; t < numTrails; t++) {
var trail = LK.getAsset('trail1', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
tint: burstColor,
alpha: 0.8,
x: hitTile.x,
y: hitTile.y
});
game.addChild(trail);
var angle = t / numTrails * Math.PI * 2 + Math.random() * 0.3;
var distance = 40 + Math.random() * 40; // Small movement radius within 80px
var targetX = hitTile.x + Math.cos(angle) * distance;
var targetY = hitTile.y + Math.sin(angle) * distance;
tween(trail, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
trail.destroy();
}
});
}
}, columnDelay + 75);
// Spawn columnFlash overlay at tile position with upward then downward animation
var columnFlashAssets = ['columnFlash1', 'columnFlash2', 'columnFlash3', 'columnFlash4'];
var originalY = hitTile.y;
var columnFlash = LK.getAsset(columnFlashAssets[columnIndex], {
anchorX: 0.5,
anchorY: 0.5,
alpha: 1.0,
x: hitTile.x,
y: originalY
});
game.addChild(columnFlash);
// Animate upward movement over 150ms with ease-out
tween(columnFlash, {
y: originalY - 100
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
// Animate downward movement back to original position over 150ms with ease-in
tween(columnFlash, {
y: originalY
}, {
duration: 150,
easing: tween.easeIn,
onFinish: function onFinish() {
columnFlash.destroy();
}
});
}
});
// Fade out overlay over full 300ms duration
tween(columnFlash, {
alpha: 0
}, {
duration: 300,
easing: tween.easeOut
});
// Visual feedback for scoring
if (points === 100) {
LK.effects.flashObject(hitTile, 0x00ff00, 200);
} else if (points === 50) {
LK.effects.flashObject(hitTile, 0xffff00, 200);
}
LK.getSound(pianoTones[columnIndex]).play();
// Remove tile
hitTile.destroy();
for (var j = tiles.length - 1; j >= 0; j--) {
if (tiles[j] === hitTile) {
tiles.splice(j, 1);
break;
}
}
}
};
game.update = function () {
if (!gameStarted) {
return;
}
var currentTime = LK.ticks * (1000 / 60) - gameStartTime;
// Beat detection for background animation
var currentBeatTime = Math.floor(currentTime / beatInterval) * beatInterval;
if (currentBeatTime !== lastBeatTime && currentTime >= 500) {
lastBeatTime = currentBeatTime;
triggerBeatAnimation();
}
// Spawn tiles according to endless repeating pattern with pattern cycling and controlled randomness
var patternTime = currentTime % patternRepeatDuration; // Loop the pattern timing
var shouldSpawn = false;
// Check if we need to switch to the next pattern
var patternCycleTime = Math.floor(currentTime / patternRepeatDuration);
var newPatternIndex = patternCycleTime % melodyPatterns.length;
if (newPatternIndex !== currentPatternIndex) {
currentPatternIndex = newPatternIndex;
melodyPattern = melodyPatterns[currentPatternIndex];
}
for (var p = 0; p < melodyPattern.length; p++) {
var spawnTime = p * beatInterval + 500; // Add initial 500ms offset
if (Math.abs(patternTime - spawnTime) < 16) {
// 16ms tolerance for 60fps
var targetColumn = melodyPattern[p];
var finalColumn = targetColumn;
// Add controlled randomness - 20% chance to vary the column
if (Math.random() < 0.2) {
// Define harmonic variations for each base column
var harmonicVariations = [[0, 1],
// A3 can vary to C4
[1, 2],
// C4 can vary to E4
[2, 3, 1],
// E4 can vary to G4 or back to C4
[3, 2] // G4 can vary to E4
];
var variations = harmonicVariations[targetColumn];
if (variations && variations.length > 0) {
finalColumn = variations[Math.floor(Math.random() * variations.length)];
}
}
spawnTile(finalColumn);
shouldSpawn = true;
break;
}
}
// Update tiles and check for misses
for (var i = tiles.length - 1; i >= 0; i--) {
var tile = tiles[i];
// Check if tile passed the scoring zone without being hit
if (!tile.scored && !tile.missed && tile.y > columns[tile.columnIndex].scoreZoneY + goodZone) {
tile.missed = true;
comboCount = 0; // Reset combo on miss
comboCounter = 0; // Reset combo counter on miss
// Update combo display visibility and reset properties
comboDisplay.visible = false;
comboDisplay.tint = 0xFFD700;
comboDisplay.scaleX = 1.0;
comboDisplay.scaleY = 1.0;
LK.effects.flashObject(tile, 0xff0000, 300);
LK.getSound('miss').play();
}
// Remove tiles that are off screen
if (tile.y > 2732 + 100) {
tile.destroy();
tiles.splice(i, 1);
}
}
};