/**** * 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