User prompt
make the menu appear after the final score disapears (5 seconds after it appears) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
make the hitzone alpha 0 when in the main menu and 1 once a song is selected ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
Reduce AI notes from 25% to 10% of player pattern length
User prompt
make the notes bigger when they enter the hit zone ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
add a music track for 8 bit
User prompt
add a button for hip hop song
User prompt
use 5 tracks instead of 8
User prompt
replace the text rythm echo challenge by the title asset
User prompt
add less notes in the ai part
User prompt
remove the final score 5 seconds after we are back to the main screen ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
associate each music to their respective button
User prompt
make sure the background is centered
User prompt
when the song is over, show the player their score and send them back to the main menu
Code edit (1 edits merged)
Please save this source code
User prompt
Rhythm Echo Challenge
Initial prompt
I want to make a game where the user uses buttons to select his songs, and on the first pass of the song they tap the sequence of notes and on the second pass of the song, they have to redo their own pattern. On the third pass of the song, I want Ava to add some extra taps on random to the pattern that the user had entered. When the notes come down the screen, I want them to go down following the bpm of the selected song.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Note = Container.expand(function (beat, track) { var self = Container.call(this); var noteGraphics = self.attachAsset('note', { anchorX: 0.5, anchorY: 0.5 }); self.beat = beat; self.track = track; self.speed = 4; self.hit = false; self.missed = false; self.scaledUp = false; self.update = function () { self.y += self.speed; // Check if note is entering hit zone (scale up) if (!self.scaledUp && self.y >= hitZoneY - 150 && self.y <= hitZoneY + 150) { self.scaledUp = true; tween(noteGraphics, { scaleX: 1.5, scaleY: 1.5 }, { duration: 300, easing: tween.easeOut }); } // Check if note passed hit zone without being hit if (!self.hit && !self.missed && self.y > hitZoneY + 100) { self.missed = true; missedNotes++; } }; return self; }); var SongButton = Container.expand(function (songId, songName, yPos) { var self = Container.call(this); var buttonBg = self.attachAsset('songButton', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.875, scaleY: 1.875 }); var buttonText = new Text2(songName, { size: 50, fill: '#e4f4f5', font: "'Arial Black'", stroke: '#000000', strokeThickness: 3 }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); self.songId = songId; self.x = 1024; self.y = yPos; self.down = function (x, y, obj) { selectSong(self.songId); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1A1A2E }); /**** * Game Code ****/ // Game states var GAME_STATE = { SONG_SELECT: 0, PATTERN_CREATE: 1, PATTERN_RECREATE: 2, AI_CHALLENGE: 3, GAME_OVER: 4 }; var currentState = GAME_STATE.SONG_SELECT; var selectedSong = null; var currentMusic = null; // Pattern data var playerPattern = []; var currentPattern = []; var patternStartTime = 0; var passNumber = 1; // Timing variables var bpm = 120; var beatDuration = 60000 / bpm; // milliseconds per beat var songStartTime = 0; var lastBeatTime = 0; var phaseOneTimer = 30; // 30 seconds for phase one var timerInterval = null; // Game objects var notes = []; var tracks = []; var hitZones = []; var uiElements = []; // Scoring var score = 0; var hitNotes = 0; var missedNotes = 0; // Track positions - spaced wider to prevent hit zone overlap var trackPositions = [350, 650, 950, 1250, 1550]; var hitZoneY = 2200; // UI Title elements var titleText = LK.getAsset('Title', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 500 }); var instructionText = new Text2('Select a Song', { size: 80, fill: '#ECFF86', font: "'Arial Black'" }); instructionText.anchor.set(0.5, 0.5); instructionText.x = 1024; instructionText.y = 775; var scoreText = new Text2('Score: 0', { size: 60, fill: '#FFFFFF', font: "'Arial Black'" }); scoreText.anchor.set(0.5, 0); var passText = new Text2('Pass 1/3', { size: 40, fill: '#FFFFFF', font: "'Arial Black'" }); passText.anchor.set(0.5, 0); var timerText = new Text2('Time left\nto create: 30', { size: 80, fill: '#FFD700', font: "'Arial Black'", align: 'center' }); timerText.anchor.set(0.5, 0.5); timerText.alpha = 0; // Hidden initially LK.gui.top.addChild(scoreText); LK.gui.top.addChild(passText); LK.gui.top.addChild(timerText); passText.x = 0; passText.y = 80; timerText.x = 0; timerText.y = 900; // Initialize game elements function initializeGame() { // Create background var bg = game.addChild(LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366 })); // Create tracks for (var i = 0; i < trackPositions.length; i++) { var track = game.addChild(LK.getAsset('trackLine', { anchorX: 0.5, anchorY: 0, x: trackPositions[i], y: 0 })); track.alpha = 0.3; tracks.push(track); } // Create hit zones var hitZoneLabels = ['Sound', 'Cymbal', 'Drumkick', 'Base', 'Shaker']; for (var i = 0; i < trackPositions.length; i++) { var hitZone = game.addChild(LK.getAsset('hitZone', { anchorX: 0.5, anchorY: 0.5, x: trackPositions[i], y: hitZoneY })); hitZone.alpha = 0; hitZones.push(hitZone); // Add text label for each hit zone var hitZoneText = new Text2(hitZoneLabels[i], { size: 48, fill: '#FFFFFF', font: "'Arial Black'", stroke: '#000000', strokeThickness: 3 }); hitZoneText.anchor.set(0.5, 0.5); hitZoneText.x = trackPositions[i]; hitZoneText.y = hitZoneY + 80; hitZoneText.alpha = 0; game.addChild(hitZoneText); hitZones.push(hitZoneText); } showSongSelection(); } function showSongSelection() { currentState = GAME_STATE.SONG_SELECT; // Ensure title is visible titleText.alpha = 1; game.addChild(titleText); game.addChild(instructionText); // Hide score and pass text during menu scoreText.alpha = 0; passText.alpha = 0; // Play menu music LK.playMusic('menu_music'); // Apply wobble function to instruction text wobbleText(instructionText, 0.1, 800, tween.easeInOut, 300, function () { return currentState !== GAME_STATE.SONG_SELECT; }); // Create song buttons with much increased spacing var songButton1 = game.addChild(new SongButton('song1', 'Electronic Beat', 1000)); var songButton2 = game.addChild(new SongButton('song2', 'Rock Anthem', 1300)); var songButton3 = game.addChild(new SongButton('song3', 'Jazz Fusion', 1600)); var songButton4 = game.addChild(new SongButton('song4', 'Hip Hop', 1900)); var songButton5 = game.addChild(new SongButton('song5', '8-Bit', 2200)); uiElements.push(songButton1, songButton2, songButton3, songButton4, songButton5); } function selectSong(songId) { // Stop menu music LK.stopMusic(); // Map song IDs to actual music asset names var songMapping = { 'song1': 'electronic_beat', 'song2': 'rock_anthem', 'song3': 'jazz', 'song4': 'hip_hop', 'song5': 'eight_bit' }; selectedSong = songMapping[songId]; // Show score and pass text when game starts scoreText.alpha = 1; passText.alpha = 1; // Animate hit zones to visible for (var i = 0; i < hitZones.length; i++) { tween(hitZones[i], { alpha: 1 }, { duration: 500, easing: tween.easeOut }); } // Clear UI elements for (var i = 0; i < uiElements.length; i++) { uiElements[i].destroy(); } uiElements = []; titleText.destroy(); instructionText.destroy(); startPatternCreation(); } function startPatternCreation() { currentState = GAME_STATE.PATTERN_CREATE; passNumber = 1; updatePassText(); var createText = new Text2('Tap to create\nyour rhythm pattern!', { size: 100, fill: '#ECFF86', font: "'Arial Black'", stroke: '#000000', strokeThickness: 4, align: 'center' }); createText.anchor.set(0.5, 0.5); createText.x = 1024; createText.y = 400; game.addChild(createText); uiElements.push(createText); // Apply wobble function to create text wobbleText(createText, 0.1, 800, tween.easeInOut, 300, function () { return currentState !== GAME_STATE.PATTERN_CREATE; }); showCountdown(function () { // Start music currentMusic = selectedSong; LK.playMusic(selectedSong, { volume: 0.25, fade: { start: 0, end: 0.25, duration: 500 } }); songStartTime = Date.now(); patternStartTime = songStartTime; playerPattern = []; // Start phase one timer phaseOneTimer = 30; timerText.alpha = 1; updateTimerDisplay(); timerInterval = LK.setInterval(function () { if (currentState === GAME_STATE.PATTERN_CREATE && phaseOneTimer > 0) { phaseOneTimer--; updateTimerDisplay(); if (phaseOneTimer <= 0) { LK.clearInterval(timerInterval); timerText.alpha = 0; } } }, 1000); // Auto advance after 30 seconds LK.setTimeout(function () { if (currentState === GAME_STATE.PATTERN_CREATE) { startPatternRecreation(); } }, 30000); }); } function startPatternRecreation() { currentState = GAME_STATE.PATTERN_RECREATE; passNumber = 2; updatePassText(); // Clear timer if (timerInterval) { LK.clearInterval(timerInterval); timerInterval = null; } timerText.alpha = 0; // Clear UI for (var i = 0; i < uiElements.length; i++) { uiElements[i].destroy(); } uiElements = []; var recreateText = new Text2('Recreate your pattern\nto score points!', { size: 100, fill: '#ECFF86', font: "'Arial Black'", stroke: '#000000', strokeThickness: 4, align: 'center' }); recreateText.anchor.set(0.5, 0.5); recreateText.x = 1024; recreateText.y = 400; game.addChild(recreateText); uiElements.push(recreateText); // Apply wobble function to recreate text wobbleText(recreateText, 0.1, 800, tween.easeInOut, 300, function () { return currentState !== GAME_STATE.PATTERN_RECREATE; }); showCountdown(function () { // Restart music and prepare pattern LK.stopMusic(); LK.setTimeout(function () { LK.playMusic(selectedSong, { volume: 0.25, fade: { start: 0, end: 0.25, duration: 500 } }); songStartTime = Date.now(); currentPattern = playerPattern.slice(); spawnNotesForPattern(); }, 100); // Auto advance after pattern completion + buffer LK.setTimeout(function () { if (currentState === GAME_STATE.PATTERN_RECREATE) { startAIChallenge(); } }, 35000); }); } function startAIChallenge() { currentState = GAME_STATE.AI_CHALLENGE; passNumber = 3; updatePassText(); // Clear UI for (var i = 0; i < uiElements.length; i++) { uiElements[i].destroy(); } uiElements = []; var challengeText = new Text2('AI Enhanced\nChallenge!', { size: 100, fill: '#FF6B6B', font: "'Arial Black'", stroke: '#000000', strokeThickness: 4, align: 'center' }); challengeText.anchor.set(0.5, 0.5); challengeText.x = 1024; challengeText.y = 400; game.addChild(challengeText); uiElements.push(challengeText); // Apply wobble function to challenge text wobbleText(challengeText, 0.1, 800, tween.easeInOut, 300, function () { return currentState !== GAME_STATE.AI_CHALLENGE; }); // Add AI beats to pattern var enhancedPattern = playerPattern.slice(); var aiPercentage = Math.random() * 0.10 + 0.10; // Random between 10% and 20% var aiBeats = Math.floor(playerPattern.length * aiPercentage); for (var i = 0; i < aiBeats; i++) { var randomTime = Math.random() * 30000; // Random time within 30 seconds var randomTrack = Math.floor(Math.random() * trackPositions.length); enhancedPattern.push({ time: randomTime, track: randomTrack, isAI: true }); } // Sort by time enhancedPattern.sort(function (a, b) { return a.time - b.time; }); showCountdown(function () { // Restart music LK.stopMusic(); LK.setTimeout(function () { LK.playMusic(selectedSong, { volume: 0.25 }); songStartTime = Date.now(); currentPattern = enhancedPattern; spawnNotesForPattern(); }, 100); // Auto end after completion LK.setTimeout(function () { if (currentState === GAME_STATE.AI_CHALLENGE) { endGame(); } }, 40000); }); } function spawnNotesForPattern() { for (var i = 0; i < currentPattern.length; i++) { var beat = currentPattern[i]; var spawnTime = beat.time - 2000; // Spawn 2 seconds before hit time LK.setTimeout(function (beatData) { return function () { if (currentState === GAME_STATE.PATTERN_RECREATE || currentState === GAME_STATE.AI_CHALLENGE) { var note = new Note(beatData, beatData.track); note.x = trackPositions[beatData.track]; note.y = -50; if (beatData.isAI) { note.attachAsset('note', {}).tint = 0xFF6B6B; // Red for AI beats } game.addChild(note); notes.push(note); } }; }(beat), Math.max(0, spawnTime)); } } function updatePassText() { passText.setText('Pass ' + passNumber + '/3'); } function updateTimerDisplay() { timerText.setText('Time left\nto create\n' + phaseOneTimer); if (phaseOneTimer <= 10) { timerText.fill = '#FF6B6B'; // Red when time is running out } else { timerText.fill = '#FFD700'; // Gold normally } } function pulseText(textElement, scaleFrom, scaleTo, duration, easing, delay, stopCondition) { // Set default values if not provided scaleFrom = scaleFrom || 1.0; scaleTo = scaleTo || 1.3; duration = duration || 800; easing = easing || tween.easeInOut; delay = delay || 200; function doPulse() { // Check stop condition if provided if (stopCondition && stopCondition()) { return; } // Scale up tween(textElement, { scaleX: scaleTo, scaleY: scaleTo }, { duration: duration, easing: easing, onFinish: function onFinish() { // Check stop condition again if (stopCondition && stopCondition()) { return; } // Scale back down tween(textElement, { scaleX: scaleFrom, scaleY: scaleFrom }, { duration: duration, easing: easing, onFinish: function onFinish() { // Check if we should continue pulsing if (!stopCondition || !stopCondition()) { LK.setTimeout(doPulse, delay); } } }); } }); } doPulse(); } function wobbleText(textElement, rotationAngle, duration, easing, delay, stopCondition) { // Set default values if not provided rotationAngle = rotationAngle || 0.1; duration = duration || 800; easing = easing || tween.easeInOut; delay = delay || 300; function doWobble() { // Check stop condition if provided if (stopCondition && stopCondition()) { return; } // Wobble left tween(textElement, { rotation: -rotationAngle }, { duration: duration, easing: easing, onFinish: function onFinish() { // Check stop condition again if (stopCondition && stopCondition()) { return; } // Wobble right tween(textElement, { rotation: rotationAngle }, { duration: duration, easing: easing, onFinish: function onFinish() { // Check stop condition again if (stopCondition && stopCondition()) { return; } // Return to center for smooth loop tween(textElement, { rotation: 0 }, { duration: duration, easing: easing, onFinish: function onFinish() { // Check if we should continue wobbling if (!stopCondition || !stopCondition()) { LK.setTimeout(doWobble, delay); } } }); } }); } }); } doWobble(); } function showCountdown(callback) { var countdownText = new Text2('3', { size: 200, fill: '#FF6B6B', font: "'Arial Black'" }); countdownText.anchor.set(0.5, 0.5); countdownText.x = 1024; countdownText.y = 1366; countdownText.alpha = 0; game.addChild(countdownText); // Animate countdown from 3 to 1 var count = 3; function animateCount() { countdownText.setText(count.toString()); countdownText.alpha = 1; countdownText.scaleX = 2; countdownText.scaleY = 2; tween(countdownText, { scaleX: 1, scaleY: 1, alpha: 0 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { count--; if (count > 0) { animateCount(); } else { // Show "GO!" for final countdown countdownText.setText('GO!'); countdownText.alpha = 1; countdownText.scaleX = 2; countdownText.scaleY = 2; tween(countdownText, { scaleX: 1, scaleY: 1, alpha: 0 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { countdownText.destroy(); callback(); } }); } } }); } animateCount(); } function updateScore(points) { score += points; scoreText.setText('Score: ' + score); } function endGame() { currentState = GAME_STATE.GAME_OVER; LK.stopMusic(); var accuracy = hitNotes > 0 ? Math.round(hitNotes / (hitNotes + missedNotes) * 100) : 0; var totalNotes = hitNotes + missedNotes; LK.setScore(score); // Clear any remaining UI elements for (var i = 0; i < uiElements.length; i++) { uiElements[i].destroy(); } uiElements = []; // Create comprehensive final score display var gameCompleteText = new Text2('GAME COMPLETE!', { size: 80, fill: '#ECFF86', font: "'Arial Black'" }); gameCompleteText.anchor.set(0.5, 0.5); gameCompleteText.x = 1024; gameCompleteText.y = 800; game.addChild(gameCompleteText); var finalScoreText = new Text2('Final Score: ' + score, { size: 70, fill: '#FFFFFF', font: "'Arial Black'" }); finalScoreText.anchor.set(0.5, 0.5); finalScoreText.x = 1024; finalScoreText.y = 1000; game.addChild(finalScoreText); var accuracyText = new Text2('Accuracy: ' + accuracy + '%', { size: 60, fill: '#FFFFFF', font: "'Arial Black'" }); accuracyText.anchor.set(0.5, 0.5); accuracyText.x = 1024; accuracyText.y = 1150; game.addChild(accuracyText); var statsText = new Text2('Notes Hit: ' + hitNotes + ' / ' + totalNotes, { size: 50, fill: '#FFFFFF', font: "'Arial Black'" }); statsText.anchor.set(0.5, 0.5); statsText.x = 1024; statsText.y = 1300; game.addChild(statsText); var continueText = new Text2('Tap to continue...', { size: 40, fill: '#ECFF86', font: "'Arial Black'" }); continueText.anchor.set(0.5, 0.5); continueText.x = 1024; continueText.y = 1500; game.addChild(continueText); // Add pulsing animation to continue text function pulseContinue() { if (currentState !== GAME_STATE.GAME_OVER) { return; } tween(continueText, { scaleX: 1.2, scaleY: 1.2 }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { if (currentState !== GAME_STATE.GAME_OVER) { return; } tween(continueText, { scaleX: 1.0, scaleY: 1.0 }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { if (currentState === GAME_STATE.GAME_OVER) { LK.setTimeout(pulseContinue, 200); } } }); } }); } pulseContinue(); // Store UI elements for cleanup uiElements.push(gameCompleteText, finalScoreText, accuracyText, statsText, continueText); } function resetGame() { // Clear timer if (timerInterval) { LK.clearInterval(timerInterval); timerInterval = null; } timerText.alpha = 0; phaseOneTimer = 30; // Clear all game objects for (var i = notes.length - 1; i >= 0; i--) { notes[i].destroy(); } notes = []; for (var i = 0; i < uiElements.length; i++) { uiElements[i].destroy(); } uiElements = []; // Reset hit zones to invisible for (var i = 0; i < hitZones.length; i++) { tween(hitZones[i], { alpha: 0 }, { duration: 300, easing: tween.easeOut }); } // Reset game variables playerPattern = []; currentPattern = []; passNumber = 1; score = 0; hitNotes = 0; missedNotes = 0; selectedSong = null; currentMusic = null; // Clear score display scoreText.setText('Score: 0'); // Return to song selection showSongSelection(); } function handleTap(x, y) { var currentTime = Date.now(); if (currentState === GAME_STATE.GAME_OVER) { // Return to song selection from final score screen resetGame(); return; } if (currentState === GAME_STATE.PATTERN_CREATE) { // Record tap in pattern var relativeTime = currentTime - patternStartTime; var nearestTrack = findNearestTrack(x); playerPattern.push({ time: relativeTime, track: nearestTrack, isAI: false }); LK.getSound('hit_track' + nearestTrack).play(); // Visual feedback var hitZone = hitZones[nearestTrack]; tween(hitZone, { scaleX: 1.2, scaleY: 1.2 }, { duration: 100 }); tween(hitZone, { scaleX: 1, scaleY: 1 }, { duration: 100 }); } else if (currentState === GAME_STATE.PATTERN_RECREATE || currentState === GAME_STATE.AI_CHALLENGE) { // Check for note hits var nearestTrack = findNearestTrack(x); var hitNote = null; var minDistance = Infinity; for (var i = 0; i < notes.length; i++) { var note = notes[i]; if (!note.hit && !note.missed && note.track === nearestTrack) { var distance = Math.abs(note.y - hitZoneY); if (distance < minDistance && distance < 100) { minDistance = distance; hitNote = note; } } } if (hitNote) { hitNote.hit = true; hitNotes++; var points = Math.max(10, 100 - Math.floor(minDistance)); updateScore(points); LK.getSound('hit_track' + nearestTrack).play(); // Visual feedback tween(hitNote, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 200 }); LK.setTimeout(function () { if (hitNote && hitNote.parent) { hitNote.destroy(); var index = notes.indexOf(hitNote); if (index > -1) { notes.splice(index, 1); } } }, 200); } else { LK.getSound('miss').play(); } } } function findNearestTrack(x) { var minDistance = Infinity; var nearestTrack = 0; for (var i = 0; i < trackPositions.length; i++) { var distance = Math.abs(x - trackPositions[i]); if (distance < minDistance) { minDistance = distance; nearestTrack = i; } } return nearestTrack; } // Event handlers game.down = function (x, y, obj) { handleTap(x, y); }; game.update = function () { // Update notes for (var i = notes.length - 1; i >= 0; i--) { var note = notes[i]; // Remove notes that are off screen if (note.y > 2800) { note.destroy(); notes.splice(i, 1); } } // Auto-end game logic removed - endGame() is now only called after phase 3 completion // Check for missed notes and update score display if (LK.ticks % 60 === 0) { // Update every second scoreText.setText('Score: ' + score); } }; // Initialize the game initializeGame();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Note = Container.expand(function (beat, track) {
var self = Container.call(this);
var noteGraphics = self.attachAsset('note', {
anchorX: 0.5,
anchorY: 0.5
});
self.beat = beat;
self.track = track;
self.speed = 4;
self.hit = false;
self.missed = false;
self.scaledUp = false;
self.update = function () {
self.y += self.speed;
// Check if note is entering hit zone (scale up)
if (!self.scaledUp && self.y >= hitZoneY - 150 && self.y <= hitZoneY + 150) {
self.scaledUp = true;
tween(noteGraphics, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut
});
}
// Check if note passed hit zone without being hit
if (!self.hit && !self.missed && self.y > hitZoneY + 100) {
self.missed = true;
missedNotes++;
}
};
return self;
});
var SongButton = Container.expand(function (songId, songName, yPos) {
var self = Container.call(this);
var buttonBg = self.attachAsset('songButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.875,
scaleY: 1.875
});
var buttonText = new Text2(songName, {
size: 50,
fill: '#e4f4f5',
font: "'Arial Black'",
stroke: '#000000',
strokeThickness: 3
});
buttonText.anchor.set(0.5, 0.5);
self.addChild(buttonText);
self.songId = songId;
self.x = 1024;
self.y = yPos;
self.down = function (x, y, obj) {
selectSong(self.songId);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1A1A2E
});
/****
* Game Code
****/
// Game states
var GAME_STATE = {
SONG_SELECT: 0,
PATTERN_CREATE: 1,
PATTERN_RECREATE: 2,
AI_CHALLENGE: 3,
GAME_OVER: 4
};
var currentState = GAME_STATE.SONG_SELECT;
var selectedSong = null;
var currentMusic = null;
// Pattern data
var playerPattern = [];
var currentPattern = [];
var patternStartTime = 0;
var passNumber = 1;
// Timing variables
var bpm = 120;
var beatDuration = 60000 / bpm; // milliseconds per beat
var songStartTime = 0;
var lastBeatTime = 0;
var phaseOneTimer = 30; // 30 seconds for phase one
var timerInterval = null;
// Game objects
var notes = [];
var tracks = [];
var hitZones = [];
var uiElements = [];
// Scoring
var score = 0;
var hitNotes = 0;
var missedNotes = 0;
// Track positions - spaced wider to prevent hit zone overlap
var trackPositions = [350, 650, 950, 1250, 1550];
var hitZoneY = 2200;
// UI Title elements
var titleText = LK.getAsset('Title', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 500
});
var instructionText = new Text2('Select a Song', {
size: 80,
fill: '#ECFF86',
font: "'Arial Black'"
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 1024;
instructionText.y = 775;
var scoreText = new Text2('Score: 0', {
size: 60,
fill: '#FFFFFF',
font: "'Arial Black'"
});
scoreText.anchor.set(0.5, 0);
var passText = new Text2('Pass 1/3', {
size: 40,
fill: '#FFFFFF',
font: "'Arial Black'"
});
passText.anchor.set(0.5, 0);
var timerText = new Text2('Time left\nto create: 30', {
size: 80,
fill: '#FFD700',
font: "'Arial Black'",
align: 'center'
});
timerText.anchor.set(0.5, 0.5);
timerText.alpha = 0; // Hidden initially
LK.gui.top.addChild(scoreText);
LK.gui.top.addChild(passText);
LK.gui.top.addChild(timerText);
passText.x = 0;
passText.y = 80;
timerText.x = 0;
timerText.y = 900;
// Initialize game elements
function initializeGame() {
// Create background
var bg = game.addChild(LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
}));
// Create tracks
for (var i = 0; i < trackPositions.length; i++) {
var track = game.addChild(LK.getAsset('trackLine', {
anchorX: 0.5,
anchorY: 0,
x: trackPositions[i],
y: 0
}));
track.alpha = 0.3;
tracks.push(track);
}
// Create hit zones
var hitZoneLabels = ['Sound', 'Cymbal', 'Drumkick', 'Base', 'Shaker'];
for (var i = 0; i < trackPositions.length; i++) {
var hitZone = game.addChild(LK.getAsset('hitZone', {
anchorX: 0.5,
anchorY: 0.5,
x: trackPositions[i],
y: hitZoneY
}));
hitZone.alpha = 0;
hitZones.push(hitZone);
// Add text label for each hit zone
var hitZoneText = new Text2(hitZoneLabels[i], {
size: 48,
fill: '#FFFFFF',
font: "'Arial Black'",
stroke: '#000000',
strokeThickness: 3
});
hitZoneText.anchor.set(0.5, 0.5);
hitZoneText.x = trackPositions[i];
hitZoneText.y = hitZoneY + 80;
hitZoneText.alpha = 0;
game.addChild(hitZoneText);
hitZones.push(hitZoneText);
}
showSongSelection();
}
function showSongSelection() {
currentState = GAME_STATE.SONG_SELECT;
// Ensure title is visible
titleText.alpha = 1;
game.addChild(titleText);
game.addChild(instructionText);
// Hide score and pass text during menu
scoreText.alpha = 0;
passText.alpha = 0;
// Play menu music
LK.playMusic('menu_music');
// Apply wobble function to instruction text
wobbleText(instructionText, 0.1, 800, tween.easeInOut, 300, function () {
return currentState !== GAME_STATE.SONG_SELECT;
});
// Create song buttons with much increased spacing
var songButton1 = game.addChild(new SongButton('song1', 'Electronic Beat', 1000));
var songButton2 = game.addChild(new SongButton('song2', 'Rock Anthem', 1300));
var songButton3 = game.addChild(new SongButton('song3', 'Jazz Fusion', 1600));
var songButton4 = game.addChild(new SongButton('song4', 'Hip Hop', 1900));
var songButton5 = game.addChild(new SongButton('song5', '8-Bit', 2200));
uiElements.push(songButton1, songButton2, songButton3, songButton4, songButton5);
}
function selectSong(songId) {
// Stop menu music
LK.stopMusic();
// Map song IDs to actual music asset names
var songMapping = {
'song1': 'electronic_beat',
'song2': 'rock_anthem',
'song3': 'jazz',
'song4': 'hip_hop',
'song5': 'eight_bit'
};
selectedSong = songMapping[songId];
// Show score and pass text when game starts
scoreText.alpha = 1;
passText.alpha = 1;
// Animate hit zones to visible
for (var i = 0; i < hitZones.length; i++) {
tween(hitZones[i], {
alpha: 1
}, {
duration: 500,
easing: tween.easeOut
});
}
// Clear UI elements
for (var i = 0; i < uiElements.length; i++) {
uiElements[i].destroy();
}
uiElements = [];
titleText.destroy();
instructionText.destroy();
startPatternCreation();
}
function startPatternCreation() {
currentState = GAME_STATE.PATTERN_CREATE;
passNumber = 1;
updatePassText();
var createText = new Text2('Tap to create\nyour rhythm pattern!', {
size: 100,
fill: '#ECFF86',
font: "'Arial Black'",
stroke: '#000000',
strokeThickness: 4,
align: 'center'
});
createText.anchor.set(0.5, 0.5);
createText.x = 1024;
createText.y = 400;
game.addChild(createText);
uiElements.push(createText);
// Apply wobble function to create text
wobbleText(createText, 0.1, 800, tween.easeInOut, 300, function () {
return currentState !== GAME_STATE.PATTERN_CREATE;
});
showCountdown(function () {
// Start music
currentMusic = selectedSong;
LK.playMusic(selectedSong, {
volume: 0.25,
fade: {
start: 0,
end: 0.25,
duration: 500
}
});
songStartTime = Date.now();
patternStartTime = songStartTime;
playerPattern = [];
// Start phase one timer
phaseOneTimer = 30;
timerText.alpha = 1;
updateTimerDisplay();
timerInterval = LK.setInterval(function () {
if (currentState === GAME_STATE.PATTERN_CREATE && phaseOneTimer > 0) {
phaseOneTimer--;
updateTimerDisplay();
if (phaseOneTimer <= 0) {
LK.clearInterval(timerInterval);
timerText.alpha = 0;
}
}
}, 1000);
// Auto advance after 30 seconds
LK.setTimeout(function () {
if (currentState === GAME_STATE.PATTERN_CREATE) {
startPatternRecreation();
}
}, 30000);
});
}
function startPatternRecreation() {
currentState = GAME_STATE.PATTERN_RECREATE;
passNumber = 2;
updatePassText();
// Clear timer
if (timerInterval) {
LK.clearInterval(timerInterval);
timerInterval = null;
}
timerText.alpha = 0;
// Clear UI
for (var i = 0; i < uiElements.length; i++) {
uiElements[i].destroy();
}
uiElements = [];
var recreateText = new Text2('Recreate your pattern\nto score points!', {
size: 100,
fill: '#ECFF86',
font: "'Arial Black'",
stroke: '#000000',
strokeThickness: 4,
align: 'center'
});
recreateText.anchor.set(0.5, 0.5);
recreateText.x = 1024;
recreateText.y = 400;
game.addChild(recreateText);
uiElements.push(recreateText);
// Apply wobble function to recreate text
wobbleText(recreateText, 0.1, 800, tween.easeInOut, 300, function () {
return currentState !== GAME_STATE.PATTERN_RECREATE;
});
showCountdown(function () {
// Restart music and prepare pattern
LK.stopMusic();
LK.setTimeout(function () {
LK.playMusic(selectedSong, {
volume: 0.25,
fade: {
start: 0,
end: 0.25,
duration: 500
}
});
songStartTime = Date.now();
currentPattern = playerPattern.slice();
spawnNotesForPattern();
}, 100);
// Auto advance after pattern completion + buffer
LK.setTimeout(function () {
if (currentState === GAME_STATE.PATTERN_RECREATE) {
startAIChallenge();
}
}, 35000);
});
}
function startAIChallenge() {
currentState = GAME_STATE.AI_CHALLENGE;
passNumber = 3;
updatePassText();
// Clear UI
for (var i = 0; i < uiElements.length; i++) {
uiElements[i].destroy();
}
uiElements = [];
var challengeText = new Text2('AI Enhanced\nChallenge!', {
size: 100,
fill: '#FF6B6B',
font: "'Arial Black'",
stroke: '#000000',
strokeThickness: 4,
align: 'center'
});
challengeText.anchor.set(0.5, 0.5);
challengeText.x = 1024;
challengeText.y = 400;
game.addChild(challengeText);
uiElements.push(challengeText);
// Apply wobble function to challenge text
wobbleText(challengeText, 0.1, 800, tween.easeInOut, 300, function () {
return currentState !== GAME_STATE.AI_CHALLENGE;
});
// Add AI beats to pattern
var enhancedPattern = playerPattern.slice();
var aiPercentage = Math.random() * 0.10 + 0.10; // Random between 10% and 20%
var aiBeats = Math.floor(playerPattern.length * aiPercentage);
for (var i = 0; i < aiBeats; i++) {
var randomTime = Math.random() * 30000; // Random time within 30 seconds
var randomTrack = Math.floor(Math.random() * trackPositions.length);
enhancedPattern.push({
time: randomTime,
track: randomTrack,
isAI: true
});
}
// Sort by time
enhancedPattern.sort(function (a, b) {
return a.time - b.time;
});
showCountdown(function () {
// Restart music
LK.stopMusic();
LK.setTimeout(function () {
LK.playMusic(selectedSong, {
volume: 0.25
});
songStartTime = Date.now();
currentPattern = enhancedPattern;
spawnNotesForPattern();
}, 100);
// Auto end after completion
LK.setTimeout(function () {
if (currentState === GAME_STATE.AI_CHALLENGE) {
endGame();
}
}, 40000);
});
}
function spawnNotesForPattern() {
for (var i = 0; i < currentPattern.length; i++) {
var beat = currentPattern[i];
var spawnTime = beat.time - 2000; // Spawn 2 seconds before hit time
LK.setTimeout(function (beatData) {
return function () {
if (currentState === GAME_STATE.PATTERN_RECREATE || currentState === GAME_STATE.AI_CHALLENGE) {
var note = new Note(beatData, beatData.track);
note.x = trackPositions[beatData.track];
note.y = -50;
if (beatData.isAI) {
note.attachAsset('note', {}).tint = 0xFF6B6B; // Red for AI beats
}
game.addChild(note);
notes.push(note);
}
};
}(beat), Math.max(0, spawnTime));
}
}
function updatePassText() {
passText.setText('Pass ' + passNumber + '/3');
}
function updateTimerDisplay() {
timerText.setText('Time left\nto create\n' + phaseOneTimer);
if (phaseOneTimer <= 10) {
timerText.fill = '#FF6B6B'; // Red when time is running out
} else {
timerText.fill = '#FFD700'; // Gold normally
}
}
function pulseText(textElement, scaleFrom, scaleTo, duration, easing, delay, stopCondition) {
// Set default values if not provided
scaleFrom = scaleFrom || 1.0;
scaleTo = scaleTo || 1.3;
duration = duration || 800;
easing = easing || tween.easeInOut;
delay = delay || 200;
function doPulse() {
// Check stop condition if provided
if (stopCondition && stopCondition()) {
return;
}
// Scale up
tween(textElement, {
scaleX: scaleTo,
scaleY: scaleTo
}, {
duration: duration,
easing: easing,
onFinish: function onFinish() {
// Check stop condition again
if (stopCondition && stopCondition()) {
return;
}
// Scale back down
tween(textElement, {
scaleX: scaleFrom,
scaleY: scaleFrom
}, {
duration: duration,
easing: easing,
onFinish: function onFinish() {
// Check if we should continue pulsing
if (!stopCondition || !stopCondition()) {
LK.setTimeout(doPulse, delay);
}
}
});
}
});
}
doPulse();
}
function wobbleText(textElement, rotationAngle, duration, easing, delay, stopCondition) {
// Set default values if not provided
rotationAngle = rotationAngle || 0.1;
duration = duration || 800;
easing = easing || tween.easeInOut;
delay = delay || 300;
function doWobble() {
// Check stop condition if provided
if (stopCondition && stopCondition()) {
return;
}
// Wobble left
tween(textElement, {
rotation: -rotationAngle
}, {
duration: duration,
easing: easing,
onFinish: function onFinish() {
// Check stop condition again
if (stopCondition && stopCondition()) {
return;
}
// Wobble right
tween(textElement, {
rotation: rotationAngle
}, {
duration: duration,
easing: easing,
onFinish: function onFinish() {
// Check stop condition again
if (stopCondition && stopCondition()) {
return;
}
// Return to center for smooth loop
tween(textElement, {
rotation: 0
}, {
duration: duration,
easing: easing,
onFinish: function onFinish() {
// Check if we should continue wobbling
if (!stopCondition || !stopCondition()) {
LK.setTimeout(doWobble, delay);
}
}
});
}
});
}
});
}
doWobble();
}
function showCountdown(callback) {
var countdownText = new Text2('3', {
size: 200,
fill: '#FF6B6B',
font: "'Arial Black'"
});
countdownText.anchor.set(0.5, 0.5);
countdownText.x = 1024;
countdownText.y = 1366;
countdownText.alpha = 0;
game.addChild(countdownText);
// Animate countdown from 3 to 1
var count = 3;
function animateCount() {
countdownText.setText(count.toString());
countdownText.alpha = 1;
countdownText.scaleX = 2;
countdownText.scaleY = 2;
tween(countdownText, {
scaleX: 1,
scaleY: 1,
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
count--;
if (count > 0) {
animateCount();
} else {
// Show "GO!" for final countdown
countdownText.setText('GO!');
countdownText.alpha = 1;
countdownText.scaleX = 2;
countdownText.scaleY = 2;
tween(countdownText, {
scaleX: 1,
scaleY: 1,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
countdownText.destroy();
callback();
}
});
}
}
});
}
animateCount();
}
function updateScore(points) {
score += points;
scoreText.setText('Score: ' + score);
}
function endGame() {
currentState = GAME_STATE.GAME_OVER;
LK.stopMusic();
var accuracy = hitNotes > 0 ? Math.round(hitNotes / (hitNotes + missedNotes) * 100) : 0;
var totalNotes = hitNotes + missedNotes;
LK.setScore(score);
// Clear any remaining UI elements
for (var i = 0; i < uiElements.length; i++) {
uiElements[i].destroy();
}
uiElements = [];
// Create comprehensive final score display
var gameCompleteText = new Text2('GAME COMPLETE!', {
size: 80,
fill: '#ECFF86',
font: "'Arial Black'"
});
gameCompleteText.anchor.set(0.5, 0.5);
gameCompleteText.x = 1024;
gameCompleteText.y = 800;
game.addChild(gameCompleteText);
var finalScoreText = new Text2('Final Score: ' + score, {
size: 70,
fill: '#FFFFFF',
font: "'Arial Black'"
});
finalScoreText.anchor.set(0.5, 0.5);
finalScoreText.x = 1024;
finalScoreText.y = 1000;
game.addChild(finalScoreText);
var accuracyText = new Text2('Accuracy: ' + accuracy + '%', {
size: 60,
fill: '#FFFFFF',
font: "'Arial Black'"
});
accuracyText.anchor.set(0.5, 0.5);
accuracyText.x = 1024;
accuracyText.y = 1150;
game.addChild(accuracyText);
var statsText = new Text2('Notes Hit: ' + hitNotes + ' / ' + totalNotes, {
size: 50,
fill: '#FFFFFF',
font: "'Arial Black'"
});
statsText.anchor.set(0.5, 0.5);
statsText.x = 1024;
statsText.y = 1300;
game.addChild(statsText);
var continueText = new Text2('Tap to continue...', {
size: 40,
fill: '#ECFF86',
font: "'Arial Black'"
});
continueText.anchor.set(0.5, 0.5);
continueText.x = 1024;
continueText.y = 1500;
game.addChild(continueText);
// Add pulsing animation to continue text
function pulseContinue() {
if (currentState !== GAME_STATE.GAME_OVER) {
return;
}
tween(continueText, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (currentState !== GAME_STATE.GAME_OVER) {
return;
}
tween(continueText, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (currentState === GAME_STATE.GAME_OVER) {
LK.setTimeout(pulseContinue, 200);
}
}
});
}
});
}
pulseContinue();
// Store UI elements for cleanup
uiElements.push(gameCompleteText, finalScoreText, accuracyText, statsText, continueText);
}
function resetGame() {
// Clear timer
if (timerInterval) {
LK.clearInterval(timerInterval);
timerInterval = null;
}
timerText.alpha = 0;
phaseOneTimer = 30;
// Clear all game objects
for (var i = notes.length - 1; i >= 0; i--) {
notes[i].destroy();
}
notes = [];
for (var i = 0; i < uiElements.length; i++) {
uiElements[i].destroy();
}
uiElements = [];
// Reset hit zones to invisible
for (var i = 0; i < hitZones.length; i++) {
tween(hitZones[i], {
alpha: 0
}, {
duration: 300,
easing: tween.easeOut
});
}
// Reset game variables
playerPattern = [];
currentPattern = [];
passNumber = 1;
score = 0;
hitNotes = 0;
missedNotes = 0;
selectedSong = null;
currentMusic = null;
// Clear score display
scoreText.setText('Score: 0');
// Return to song selection
showSongSelection();
}
function handleTap(x, y) {
var currentTime = Date.now();
if (currentState === GAME_STATE.GAME_OVER) {
// Return to song selection from final score screen
resetGame();
return;
}
if (currentState === GAME_STATE.PATTERN_CREATE) {
// Record tap in pattern
var relativeTime = currentTime - patternStartTime;
var nearestTrack = findNearestTrack(x);
playerPattern.push({
time: relativeTime,
track: nearestTrack,
isAI: false
});
LK.getSound('hit_track' + nearestTrack).play();
// Visual feedback
var hitZone = hitZones[nearestTrack];
tween(hitZone, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100
});
tween(hitZone, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
} else if (currentState === GAME_STATE.PATTERN_RECREATE || currentState === GAME_STATE.AI_CHALLENGE) {
// Check for note hits
var nearestTrack = findNearestTrack(x);
var hitNote = null;
var minDistance = Infinity;
for (var i = 0; i < notes.length; i++) {
var note = notes[i];
if (!note.hit && !note.missed && note.track === nearestTrack) {
var distance = Math.abs(note.y - hitZoneY);
if (distance < minDistance && distance < 100) {
minDistance = distance;
hitNote = note;
}
}
}
if (hitNote) {
hitNote.hit = true;
hitNotes++;
var points = Math.max(10, 100 - Math.floor(minDistance));
updateScore(points);
LK.getSound('hit_track' + nearestTrack).play();
// Visual feedback
tween(hitNote, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 200
});
LK.setTimeout(function () {
if (hitNote && hitNote.parent) {
hitNote.destroy();
var index = notes.indexOf(hitNote);
if (index > -1) {
notes.splice(index, 1);
}
}
}, 200);
} else {
LK.getSound('miss').play();
}
}
}
function findNearestTrack(x) {
var minDistance = Infinity;
var nearestTrack = 0;
for (var i = 0; i < trackPositions.length; i++) {
var distance = Math.abs(x - trackPositions[i]);
if (distance < minDistance) {
minDistance = distance;
nearestTrack = i;
}
}
return nearestTrack;
}
// Event handlers
game.down = function (x, y, obj) {
handleTap(x, y);
};
game.update = function () {
// Update notes
for (var i = notes.length - 1; i >= 0; i--) {
var note = notes[i];
// Remove notes that are off screen
if (note.y > 2800) {
note.destroy();
notes.splice(i, 1);
}
}
// Auto-end game logic removed - endGame() is now only called after phase 3 completion
// Check for missed notes and update score display
if (LK.ticks % 60 === 0) {
// Update every second
scoreText.setText('Score: ' + score);
}
};
// Initialize the game
initializeGame();
dark neon lit background in the tints of green orange yellow and blue. In-Game asset. 2d. High contrast. No shadows
light neon orange yellow green and blue line. In-Game asset. 2d. High contrast. No shadows
text in neon outline no background that says "Rhythm Echo Challenge". In-Game asset. 2d. High contrast. No shadows
neon pink purple red orange oval filled in blue nuaces. In-Game asset. 2d. High contrast. No shadows