/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 33;
self.targetX = 0;
self.targetY = 0;
self.directionX = 0;
self.directionY = 0;
self.update = function () {
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
};
return self;
});
var Note = Container.expand(function () {
var self = Container.call(this);
// Default note type
self.noteType = 'normal';
self.pointValue = 100;
self.speed = 3;
// Initialize with normal note graphics
var noteGraphics = self.attachAsset('note', {
anchorX: 0.5,
anchorY: 0.5
});
self.graphics = noteGraphics;
self.lane = 0;
self.hasTriggered = false;
self.pulseTimer = 0;
// Method to set note type with different properties
self.setNoteType = function (type) {
self.noteType = type;
// Remove current graphics
self.removeChild(self.graphics);
switch (type) {
case 'fast':
// Mix different note assets for variety
var fastAssets = ['noteRed', 'notePurple'];
var fastAsset = fastAssets[Math.floor(Math.random() * fastAssets.length)];
self.graphics = self.attachAsset(fastAsset, {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 5;
self.pointValue = 150;
break;
case 'slow':
// Mix different note assets for variety
var slowAssets = ['noteBlue', 'note'];
var slowAsset = slowAssets[Math.floor(Math.random() * slowAssets.length)];
self.graphics = self.attachAsset(slowAsset, {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 1.5;
self.pointValue = 200;
break;
case 'bonus':
self.graphics = self.attachAsset('noteGold', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2.5;
self.pointValue = 300;
break;
case 'challenge':
self.graphics = self.attachAsset('notePurple', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 4;
self.pointValue = 250;
break;
case 'multiShot':
self.graphics = self.attachAsset('powerUpGreen', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 150;
break;
case 'slowMotion':
self.graphics = self.attachAsset('powerUpGreen', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 150;
break;
case 'scoreMultiplier':
self.graphics = self.attachAsset('powerUpGreen', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 150;
break;
case 'movingTarget':
self.graphics = self.attachAsset('powerUpRed', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 100;
break;
case 'laserBeam':
self.graphics = self.attachAsset('powerUpBlue', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 200;
break;
case 'shield':
self.graphics = self.attachAsset('powerUpBlue', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 150;
break;
case 'noteDestroyer':
self.graphics = self.attachAsset('powerUpBlue', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 300;
break;
case 'reverseControls':
self.graphics = self.attachAsset('powerUpRed', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 100;
break;
case 'shrinkingTargets':
self.graphics = self.attachAsset('powerUpRed', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 100;
break;
default:
self.graphics = self.attachAsset('note', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 3;
self.pointValue = 100;
}
};
// Method to play sound based on note type
self.playNoteSound = function () {
switch (self.noteType) {
case 'fast':
// Mix different sounds for fast notes
var fastSounds = ['shake', 'guitar'];
var randomFastSound = fastSounds[Math.floor(Math.random() * fastSounds.length)];
LK.getSound(randomFastSound).play();
break;
case 'slow':
// Mix different sounds for slow notes
var slowSounds = ['kick', 'piano', 'violin'];
var randomSlowSound = slowSounds[Math.floor(Math.random() * slowSounds.length)];
LK.getSound(randomSlowSound).play();
break;
case 'bonus':
LK.getSound('hihat').play();
break;
case 'challenge':
LK.getSound('cymbal').play();
break;
case 'multiShot':
case 'slowMotion':
case 'scoreMultiplier':
case 'movingTarget':
case 'laserBeam':
case 'shield':
case 'noteDestroyer':
case 'reverseControls':
case 'shrinkingTargets':
LK.getSound('bass').play();
break;
default:
LK.getSound('violin').play();
}
};
self.update = function () {
self.y += self.speed;
// Add visual effects for special notes
if (self.noteType === 'bonus') {
// Golden notes pulse
self.pulseTimer++;
var scale = 1 + Math.sin(self.pulseTimer * 0.2) * 0.1;
self.graphics.scaleX = scale;
self.graphics.scaleY = scale;
} else if (self.noteType === 'challenge') {
// Purple notes rotate
self.graphics.rotation += 0.05;
} else if (self.noteType === 'multiShot' || self.noteType === 'slowMotion' || self.noteType === 'scoreMultiplier') {
// Good power-ups glow green
self.pulseTimer++;
var scale = 1 + Math.sin(self.pulseTimer * 0.3) * 0.15;
self.graphics.scaleX = scale;
self.graphics.scaleY = scale;
} else if (self.noteType === 'laserBeam' || self.noteType === 'shield' || self.noteType === 'noteDestroyer') {
// New power-ups glow blue
self.pulseTimer++;
var scale = 1 + Math.sin(self.pulseTimer * 0.35) * 0.18;
self.graphics.scaleX = scale;
self.graphics.scaleY = scale;
} else if (self.noteType === 'movingTarget' || self.noteType === 'reverseControls' || self.noteType === 'shrinkingTargets') {
// Bad power-ups pulse red
self.pulseTimer++;
var scale = 1 + Math.sin(self.pulseTimer * 0.4) * 0.2;
self.graphics.scaleX = scale;
self.graphics.scaleY = scale;
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// Add floating gun
var gun = self.attachAsset('gun', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -50
});
self.gun = gun;
// Method to rotate gun towards target with smooth animation
self.aimGunAt = function (targetX, targetY) {
var dx = targetX - self.x;
var dy = targetY - (self.y - 50); // Account for gun offset
var targetRotation = Math.atan2(dy, dx);
// Smooth rotation animation
tween(self.gun, {
rotation: targetRotation
}, {
duration: 150,
easing: tween.easeOut
});
};
return self;
});
var Spike = Container.expand(function () {
var self = Container.call(this);
var spikeGraphics = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
self.speed = 8.0;
self.lane = 0;
self.pulseTimer = 0;
self.update = function () {
self.y += self.speed;
// Add pulsing danger effect
self.pulseTimer++;
var scale = 0.7 + Math.sin(self.pulseTimer * 0.4) * 0.14;
spikeGraphics.scaleX = scale;
spikeGraphics.scaleY = scale;
// Add rotation for more menacing look
spikeGraphics.rotation += 0.08;
};
return self;
});
var TargetBox = Container.expand(function () {
var self = Container.call(this);
var targetGraphics = self.attachAsset('targetBox', {
anchorX: 0.5,
anchorY: 0.5
});
self.lane = 0;
self.isActive = false;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xF0F8FF,
name: 'Cats Are Rhythmic'
});
/****
* Game Code
****/
// Game state variables
var gameState = 'menu'; // 'menu' or 'playing'
var startMenuContainer;
var musicButtons = [];
// Game variables
var player;
var bullets = [];
var notes = [];
var spikes = [];
var targetBoxes = [];
var staffLines = [];
var aimLine;
var deletionAreaY;
var targetBoxPositions = [512, 853, 1194, 1536]; // Define globally for access throughout the game
var score = 0;
var staffY = 400;
var staffSpacing = 80;
// Level system variables
var currentLevel = storage.currentLevel || 1;
var levelScore = storage.levelScore || 0; // Score accumulated in current level
var totalScore = storage.totalScore || 0; // Total score across all levels
var levelThresholds = [0, 1000, 2500, 5000, 8000, 12000, 17000, 23000, 30000, 38000, 47000, 57000, 68000, 80000, 93000, 107000, 122000, 138000, 155000, 173000]; // Score needed for each level
var maxLevel = levelThresholds.length;
var mouseX = 1024;
var noteSpawnTimer = 0;
var noteSpawnInterval = 90; // frames between note spawns
var spikeSpawnTimer = 0;
var spikeSpawnInterval = 300; // frames between spike spawns (much less frequent than notes)
var musicPlaying = false;
var musicGlitchTimer = 0;
var musicGlitchDuration = 18; // 0.3 seconds at 60fps
// Music selection variables
var currentMusicTrack = 'bgmusic';
var musicTracks = ['bgmusic', 'rock_track', 'electronic_track'];
var musicTrackNames = ['Electronic Track', 'Rock Track', 'Soul Track'];
// Note preview system
var previewNotes = [];
var nextNoteTypes = [];
// Combo system variables
var combo = 0;
var maxCombo = 0;
// Feedback text variables
var feedbackTexts = [];
// Power-up system variables
var multiShotActive = false;
var multiShotTimer = 0;
var multiShotDuration = 300; // 5 seconds at 60fps
var slowMotionActive = false;
var slowMotionTimer = 0;
var slowMotionDuration = 300; // 5 seconds at 60fps
var scoreMultiplierActive = false;
var scoreMultiplierTimer = 0;
var scoreMultiplierDuration = 300; // 5 seconds at 60fps
var movingTargetActive = false;
var movingTargetTimer = 0;
var movingTargetDuration = 240; // 4 seconds at 60fps
// Power-up display variables
var powerUpDisplayContainer;
var powerUpIconDisplay;
var powerUpNameDisplay;
var powerUpTimerDisplay;
var powerUpProgressBar;
var activePowerUpType = null;
var activePowerUpMaxDuration = 0;
// New power-up system variables
var laserBeamActive = false;
var laserBeamTimer = 0;
var laserBeamDuration = 180; // 3 seconds at 60fps
var laserBeamGraphics = null;
var shieldActive = false;
var shieldHits = 0;
var maxShieldHits = 1;
var shieldGraphics = null;
var noteDestroyerActive = false;
var noteDestroyerTimer = 0;
var noteDestroyerDuration = 1; // Instant effect, 1 frame duration
// Bad power-up system variables
var reverseControlsActive = false;
var reverseControlsTimer = 0;
var reverseControlsDuration = 300; // 5 seconds at 60fps
var shrinkingTargetsActive = false;
var shrinkingTargetsTimer = 0;
var shrinkingTargetsDuration = 240; // 4 seconds at 60fps
var originalTargetSizes = [];
// Create start menu
function createStartMenu() {
startMenuContainer = new Container();
startMenuContainer.x = 1024; // Center of screen
startMenuContainer.y = 1366; // Center of screen
game.addChild(startMenuContainer);
// Game title
var titleText = new Text2('Cats Are Rhythmic', {
size: 120,
fill: 0x87CEEB
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -400;
startMenuContainer.addChild(titleText);
// Subtitle
var subtitleText = new Text2('Choose Your Music', {
size: 80,
fill: 0x87CEEB
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.y = -300;
startMenuContainer.addChild(subtitleText);
// Create music selection buttons
var buttonSpacing = 200;
var startY = -100;
for (var i = 0; i < musicTracks.length; i++) {
var buttonContainer = new Container();
buttonContainer.x = 0;
buttonContainer.y = startY + i * buttonSpacing;
startMenuContainer.addChild(buttonContainer);
// Button background
var buttonBg = LK.getAsset('targetBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 1.5,
tint: 0x4444FF
});
buttonContainer.addChild(buttonBg);
// Button text
var buttonText = new Text2(musicTrackNames[i], {
size: 60,
fill: 0x87CEEB
});
buttonText.anchor.set(0.5, 0.5);
buttonContainer.addChild(buttonText);
// Store button data
buttonContainer.musicIndex = i;
buttonContainer.background = buttonBg;
buttonContainer.text = buttonText;
// Add click handler
buttonContainer.down = function (x, y, obj) {
selectMusic(this.musicIndex);
};
musicButtons.push(buttonContainer);
}
// Music warning instruction
var musicWarningText = new Text2('MUSIC WONT START TILL YOU HIT A MUSIC NOTE!', {
size: 45,
fill: 0xFF6666
});
musicWarningText.anchor.set(0.5, 0.5);
musicWarningText.y = 450;
startMenuContainer.addChild(musicWarningText);
// Instructions
var instructionText = new Text2('Tap a music option to start the game', {
size: 50,
fill: 0x87CEEB
});
instructionText.anchor.set(0.5, 0.5);
instructionText.y = 500;
startMenuContainer.addChild(instructionText);
}
function selectMusic(musicIndex) {
currentMusicTrack = musicTracks[musicIndex];
// Update active music display
activeMusicDisplay.setText('Music: ' + musicTrackNames[musicIndex]);
// Animate menu exit
tween(startMenuContainer, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
startMenuContainer.destroy();
startMenuContainer = null;
musicButtons = [];
startGame();
}
});
}
function startGame() {
gameState = 'playing';
setupGameElements();
}
function setupGameElements() {
// Setup staff lines
var staffY = 400;
var staffSpacing = 80;
for (var i = 0; i < 5; i++) {
var staffLine = game.addChild(LK.getAsset('staffLine', {
anchorX: 0,
anchorY: 0.5,
x: 0,
y: staffY + i * staffSpacing
}));
staffLines.push(staffLine);
}
// Setup note preview indicators
var targetBoxPositions = [512, 853, 1194, 1536];
for (var i = 0; i < 4; i++) {
var previewNote = LK.getAsset('note', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.8,
x: targetBoxPositions[i],
y: staffY - 100
});
previewNote.visible = false;
game.addChild(previewNote);
previewNotes.push(previewNote);
nextNoteTypes.push(null);
}
// Setup target boxes
var targetY = staffY + 4 * staffSpacing + 200;
var targetBoxPositions = [512, 853, 1194, 1536]; // 4 evenly spaced positions
for (var i = 0; i < 4; i++) {
var targetBox = game.addChild(new TargetBox());
targetBox.x = targetBoxPositions[i];
targetBox.y = targetY;
targetBox.lane = i;
targetBoxes.push(targetBox);
}
// Setup deletion area below target boxes
deletionAreaY = targetY + 250;
// Initialize level system
updateLevelDisplay();
increaseDifficultyForLevel();
// Create visible secret wall
var secretWall = game.addChild(LK.getAsset('secretWall', {
anchorX: 0,
anchorY: 0.5,
x: 0,
y: deletionAreaY
}));
// Setup player
player = game.addChild(new Player());
player.x = 1024;
player.y = 2500;
// Create aim assistance line
aimLine = LK.getAsset('staffLine', {
anchorX: 0,
anchorY: 0.5,
width: 2,
height: 2,
alpha: 0.3,
tint: 0xFF0000
});
game.addChild(aimLine);
}
// Setup score display
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0x000000
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Setup level display
var levelTxt = new Text2('Level: ' + currentLevel, {
size: 60,
fill: 0x0066CC
});
levelTxt.anchor.set(0, 0);
levelTxt.x = 20;
levelTxt.y = 20;
LK.gui.topLeft.addChild(levelTxt);
// Setup level progress display
var levelProgressTxt = new Text2('', {
size: 45,
fill: 0x666666
});
levelProgressTxt.anchor.set(0, 0);
levelProgressTxt.x = 20;
levelProgressTxt.y = 90;
LK.gui.topLeft.addChild(levelProgressTxt);
// Setup combo display
var comboTxt = new Text2('', {
size: 60,
fill: 0xFF6600
});
comboTxt.anchor.set(0.5, 0);
comboTxt.y = 90; // Position below score
LK.gui.top.addChild(comboTxt);
// Display active music (non-interactive)
var activeMusicDisplay = new Text2('Music: ' + musicTrackNames[0], {
size: 50,
fill: 0x000000
});
activeMusicDisplay.anchor.set(1, 0);
activeMusicDisplay.x = -20; // Position from right edge
activeMusicDisplay.y = 20;
LK.gui.topRight.addChild(activeMusicDisplay);
// Create power-up display container on left side of screen
powerUpDisplayContainer = new Container();
powerUpDisplayContainer.x = 50;
powerUpDisplayContainer.y = 200;
powerUpDisplayContainer.alpha = 0;
LK.gui.left.addChild(powerUpDisplayContainer);
// Power-up icon display
powerUpIconDisplay = LK.getAsset('powerUpGreen', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
powerUpIconDisplay.x = 40;
powerUpIconDisplay.y = 40;
powerUpDisplayContainer.addChild(powerUpIconDisplay);
// Power-up name text
powerUpNameDisplay = new Text2('', {
size: 40,
fill: 0x00FF00
});
powerUpNameDisplay.anchor.set(0, 0.5);
powerUpNameDisplay.x = 90;
powerUpNameDisplay.y = 25;
powerUpDisplayContainer.addChild(powerUpNameDisplay);
// Power-up timer text
powerUpTimerDisplay = new Text2('', {
size: 30,
fill: 0xFFFFFF
});
powerUpTimerDisplay.anchor.set(0, 0.5);
powerUpTimerDisplay.x = 90;
powerUpTimerDisplay.y = 55;
powerUpDisplayContainer.addChild(powerUpTimerDisplay);
// Power-up progress bar background
var progressBarBg = LK.getAsset('staffLine', {
anchorX: 0,
anchorY: 0.5,
width: 200,
height: 8,
tint: 0x333333
});
progressBarBg.x = 90;
progressBarBg.y = 75;
powerUpDisplayContainer.addChild(progressBarBg);
// Power-up progress bar fill
powerUpProgressBar = LK.getAsset('staffLine', {
anchorX: 0,
anchorY: 0.5,
width: 200,
height: 8,
tint: 0x00FF00
});
powerUpProgressBar.x = 90;
powerUpProgressBar.y = 75;
powerUpDisplayContainer.addChild(powerUpProgressBar);
// Initialize start menu
createStartMenu();
// Input handling
game.move = function (x, y, obj) {
if (gameState !== 'playing') return;
mouseX = x;
// Apply reverse controls effect
if (reverseControlsActive) {
var centerX = 1024;
var distanceFromCenter = x - centerX;
player.x = centerX - distanceFromCenter;
} else {
player.x = x;
}
// Continuously aim gun at mouse position
player.aimGunAt(x, y);
// Update aim line
var gunX = player.x;
var gunY = player.y - 50;
var dx = x - gunX;
var dy = y - gunY;
var distance = Math.sqrt(dx * dx + dy * dy);
aimLine.x = gunX;
aimLine.y = gunY;
aimLine.width = Math.min(distance, 300); // Limit line length
aimLine.rotation = Math.atan2(dy, dx);
};
game.down = function (x, y, obj) {
if (gameState !== 'playing') return;
// Aim gun at target position
player.aimGunAt(x, y);
if (multiShotActive) {
// Create 3 bullets in spread pattern
for (var i = 0; i < 3; i++) {
var bullet = game.addChild(new Bullet());
bullet.x = player.x;
bullet.y = player.y - 50; // Spawn from gun position
// Calculate direction to mouse position with spread
var dx = x - player.x;
var dy = y - (player.y - 50); // Account for gun offset
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
var angle = Math.atan2(dy, dx);
var spreadAngle = (i - 1) * 0.3; // -0.3, 0, 0.3 radians spread
bullet.directionX = Math.cos(angle + spreadAngle);
bullet.directionY = Math.sin(angle + spreadAngle);
}
bullets.push(bullet);
}
} else {
// Create single bullet from gun position
var bullet = game.addChild(new Bullet());
bullet.x = player.x;
bullet.y = player.y - 50; // Spawn from gun position
// Calculate direction to mouse position
var dx = x - player.x;
var dy = y - (player.y - 50); // Account for gun offset
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
bullet.directionX = dx / distance;
bullet.directionY = dy / distance;
}
bullets.push(bullet);
}
};
// Generate note type based on probabilities
function generateNoteType() {
var random = Math.random();
if (random < 0.05) {
return 'bonus'; // 5% chance for bonus notes
} else if (random < 0.1) {
return 'challenge'; // 5% chance for challenge notes
} else if (random < 0.2) {
return 'fast'; // 10% chance for fast notes
} else if (random < 0.3) {
return 'slow'; // 10% chance for slow notes
} else if (random < 0.33) {
return 'multiShot'; // 3% chance for multi-shot power-up
} else if (random < 0.36) {
return 'slowMotion'; // 3% chance for slow motion power-up
} else if (random < 0.39) {
return 'scoreMultiplier'; // 3% chance for score multiplier power-up
} else if (random < 0.42) {
return 'movingTarget'; // 3% chance for moving target power-up
} else if (random < 0.445) {
return 'laserBeam'; // 2.5% chance for laser beam power-up
} else if (random < 0.47) {
return 'shield'; // 2.5% chance for shield power-up
} else if (random < 0.49) {
return 'noteDestroyer'; // 2% chance for note destroyer power-up
} else if (random < 0.515) {
return 'reverseControls'; // 2.5% chance for reverse controls power-up
} else if (random < 0.54) {
return 'shrinkingTargets'; // 2.5% chance for shrinking targets power-up
} else {
return 'normal'; // 46% chance for normal notes
}
}
// Update note preview display
function updateNotePreview(lane, noteType) {
if (lane >= 0 && lane < previewNotes.length) {
var preview = previewNotes[lane];
nextNoteTypes[lane] = noteType;
// Set preview note appearance based on type
preview.removeChildren();
var assetId = 'note';
var tintColor = 0xFFFFFF;
switch (noteType) {
case 'fast':
assetId = 'noteRed';
break;
case 'slow':
assetId = 'noteBlue';
break;
case 'bonus':
assetId = 'noteGold';
break;
case 'challenge':
assetId = 'notePurple';
break;
case 'multiShot':
case 'slowMotion':
case 'scoreMultiplier':
assetId = 'powerUpGreen';
break;
case 'movingTarget':
case 'reverseControls':
case 'shrinkingTargets':
assetId = 'powerUpRed';
break;
case 'laserBeam':
case 'shield':
case 'noteDestroyer':
assetId = 'powerUpBlue';
break;
}
var previewGraphics = LK.getAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.8
});
preview.addChild(previewGraphics);
preview.visible = true;
// Animate preview appearance
tween(preview, {
alpha: 1.0,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut
});
// Hide preview after delay
tween(preview, {
alpha: 0.6,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300,
easing: tween.easeIn
});
}
}
// Spawn notes
function spawnNote() {
var lane = Math.floor(Math.random() * 4);
var note = game.addChild(new Note());
var targetBoxPositions = [512, 853, 1194, 1536]; // Match target box positions
note.x = targetBoxPositions[lane];
note.y = staffY + lane * staffSpacing - 200;
note.lane = lane;
// Use generated note type
var noteType = generateNoteType();
note.setNoteType(noteType);
// Show preview for next note
var nextLane = Math.floor(Math.random() * 4);
var nextNoteType = generateNoteType();
updateNotePreview(nextLane, nextNoteType);
notes.push(note);
}
// Spawn spikes
function spawnSpike() {
var lane = Math.floor(Math.random() * 4);
var spike = game.addChild(new Spike());
var targetBoxPositions = [512, 853, 1194, 1536]; // Match target box positions
spike.x = targetBoxPositions[lane];
spike.y = staffY + lane * staffSpacing - 200;
spike.lane = lane;
spikes.push(spike);
}
// Check if note is in timing window
function isNoteInTimingWindow(note, targetBox) {
var noteTop = note.y - 35; // Top of note (note height is 70, so 35 from center)
var noteBottom = note.y + 35; // Bottom of note
var targetTop = targetBox.y - 65; // Expanded target area (target height is 130, so 65 from center)
var targetBottom = targetBox.y + 65; // Expanded target area
// Check if any part of the note overlaps with the expanded target area
return noteBottom >= targetTop && noteTop <= targetBottom;
}
// Get timing quality based on note position relative to target center
function getTimingQuality(note, targetBox) {
var distance = Math.abs(note.y - targetBox.y);
if (distance <= 20) {
return 'Perfect!';
} else if (distance <= 40) {
return 'Good';
} else {
return 'Hit';
}
}
// Create particle burst effect
function createParticleBurst(x, y, color, count) {
for (var i = 0; i < count; i++) {
var particle = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
tint: color
});
particle.x = x;
particle.y = y;
game.addChild(particle);
// Random direction and speed
var angle = Math.PI * 2 * i / count + (Math.random() - 0.5) * 0.5;
var speed = 100 + Math.random() * 100;
var targetX = x + Math.cos(angle) * speed;
var targetY = y + Math.sin(angle) * speed;
// Animate particle outward
tween(particle, {
x: targetX,
y: targetY,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
}, {
duration: 800 + Math.random() * 400,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
}
// Check and handle level progression
function checkLevelUp() {
if (currentLevel < maxLevel) {
var nextLevelThreshold = levelThresholds[currentLevel];
if (totalScore >= nextLevelThreshold) {
levelUp();
}
}
}
// Handle level up
function levelUp() {
var previousLevel = currentLevel;
currentLevel++;
// Save level progress
storage.currentLevel = currentLevel;
storage.totalScore = totalScore;
storage.levelScore = 0; // Reset level score for new level
levelScore = 0;
// Create animated level pop-up display
var levelPopUpContainer = new Container();
levelPopUpContainer.x = 1024; // Center of screen
levelPopUpContainer.y = 1366; // Center of screen
levelPopUpContainer.alpha = 0;
levelPopUpContainer.scaleX = 0.1;
levelPopUpContainer.scaleY = 0.1;
game.addChild(levelPopUpContainer);
// Background for pop-up
var popUpBg = LK.getAsset('targetBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4,
scaleY: 3,
tint: 0x000080,
alpha: 0.9
});
levelPopUpContainer.addChild(popUpBg);
// Level text
var levelPopUpText = new Text2('LEVEL ' + currentLevel, {
size: 120,
fill: 0xFFD700
});
levelPopUpText.anchor.set(0.5, 0.5);
levelPopUpText.y = -30;
levelPopUpContainer.addChild(levelPopUpText);
// Level up text
var levelUpText = new Text2('LEVEL UP!', {
size: 80,
fill: 0x00FF00
});
levelUpText.anchor.set(0.5, 0.5);
levelUpText.y = 60;
levelPopUpContainer.addChild(levelUpText);
// Animate pop-up appearance
tween(levelPopUpContainer, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 600,
easing: tween.easeOut
});
// Add pulsing effect to level text
tween(levelPopUpText, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 800,
easing: tween.easeInOut
});
tween(levelPopUpText, {
scaleX: 1,
scaleY: 1
}, {
duration: 800,
easing: tween.easeInOut
});
// Remove pop-up after delay
tween(levelPopUpContainer, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8,
y: 1200
}, {
duration: 800,
easing: tween.easeIn,
onFinish: function onFinish() {
levelPopUpContainer.destroy();
}
});
// Create level up feedback
createFeedbackText('LEVEL UP!', 1024, 800, 0xFFD700);
createFeedbackText('Level ' + currentLevel, 1024, 860, 0x0066CC);
// Flash screen gold for level up
LK.effects.flashScreen(0xFFD700, 800);
// Update level display
updateLevelDisplay();
// Increase difficulty with level
increaseDifficultyForLevel();
}
// Update level display
function updateLevelDisplay() {
if (levelTxt) {
levelTxt.setText('Level: ' + currentLevel);
}
if (currentLevel < maxLevel) {
var nextThreshold = levelThresholds[currentLevel];
var progress = totalScore - (currentLevel > 1 ? levelThresholds[currentLevel - 1] : 0);
var needed = nextThreshold - (currentLevel > 1 ? levelThresholds[currentLevel - 1] : 0);
if (levelProgressTxt) {
levelProgressTxt.setText(progress + ' / ' + needed);
}
} else {
if (levelProgressTxt) {
levelProgressTxt.setText('MAX LEVEL');
}
}
}
// Increase difficulty based on current level
function increaseDifficultyForLevel() {
// Reduce spawn intervals based on level
noteSpawnInterval = Math.max(30, 90 - (currentLevel - 1) * 3);
spikeSpawnInterval = Math.max(120, 300 - (currentLevel - 1) * 8);
}
// Create feedback text with animation
function createFeedbackText(text, x, y, color) {
var feedbackText = new Text2(text, {
size: 50,
fill: color
});
feedbackText.anchor.set(0.5, 0.5);
feedbackText.x = x;
feedbackText.y = y;
feedbackText.alpha = 1;
feedbackText.scaleX = 0.5;
feedbackText.scaleY = 0.5;
game.addChild(feedbackText);
feedbackTexts.push(feedbackText);
// Animate text appearance
tween(feedbackText, {
scaleX: 1.2,
scaleY: 1.2,
y: y - 50
}, {
duration: 300,
easing: tween.easeOut
});
// Fade out and remove
tween(feedbackText, {
alpha: 0,
y: y - 100
}, {
duration: 800,
easing: tween.easeIn,
onFinish: function onFinish() {
feedbackText.destroy();
var index = feedbackTexts.indexOf(feedbackText);
if (index > -1) {
feedbackTexts.splice(index, 1);
}
}
});
}
// Show power-up effect display
function showPowerUpDisplay(powerUpType, duration) {
activePowerUpType = powerUpType;
activePowerUpMaxDuration = duration;
// Set power-up name and icon based on type
var powerUpName = '';
var iconAsset = 'powerUpGreen';
var textColor = 0x00FF00;
switch (powerUpType) {
case 'multiShot':
powerUpName = 'Multi-Shot';
iconAsset = 'powerUpGreen';
textColor = 0x00FF00;
break;
case 'slowMotion':
powerUpName = 'Slow Motion';
iconAsset = 'powerUpGreen';
textColor = 0x00FFFF;
break;
case 'scoreMultiplier':
powerUpName = 'Score x2';
iconAsset = 'powerUpGreen';
textColor = 0xFFD700;
break;
case 'movingTarget':
powerUpName = 'Moving Targets';
iconAsset = 'powerUpRed';
textColor = 0xFF0000;
break;
case 'laserBeam':
powerUpName = 'Laser Beam';
iconAsset = 'powerUpBlue';
textColor = 0x00FFFF;
break;
case 'shield':
powerUpName = 'Shield';
iconAsset = 'powerUpBlue';
textColor = 0x00FFFF;
break;
case 'noteDestroyer':
powerUpName = 'Spike Destroyer';
iconAsset = 'powerUpBlue';
textColor = 0x00FFFF;
break;
case 'reverseControls':
powerUpName = 'Reverse Controls';
iconAsset = 'powerUpRed';
textColor = 0xFF0000;
break;
case 'shrinkingTargets':
powerUpName = 'Shrinking Targets';
iconAsset = 'powerUpRed';
textColor = 0xFF0000;
break;
}
// Update display elements
powerUpDisplayContainer.removeChild(powerUpIconDisplay);
powerUpIconDisplay = LK.getAsset(iconAsset, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
powerUpIconDisplay.x = 40;
powerUpIconDisplay.y = 40;
powerUpDisplayContainer.addChild(powerUpIconDisplay);
powerUpNameDisplay.setText(powerUpName);
powerUpNameDisplay.fill = textColor;
powerUpProgressBar.tint = textColor;
// Animate display appearance
tween(powerUpDisplayContainer, {
alpha: 1,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 300,
easing: tween.easeOut
});
tween(powerUpDisplayContainer, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
// Hide power-up effect display
function hidePowerUpDisplay() {
activePowerUpType = null;
activePowerUpMaxDuration = 0;
tween(powerUpDisplayContainer, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 300,
easing: tween.easeIn
});
}
// Game update loop
game.update = function () {
if (gameState !== 'playing') return;
// Spawn notes
noteSpawnTimer++;
if (noteSpawnTimer >= noteSpawnInterval) {
spawnNote();
noteSpawnTimer = 0;
}
// Spawn spikes
spikeSpawnTimer++;
if (spikeSpawnTimer >= spikeSpawnInterval) {
spawnSpike();
spikeSpawnTimer = 0;
}
// Update and check bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
// Remove bullets that go off screen
if (bullet.x < -50 || bullet.x > 2098 || bullet.y < -50 || bullet.y > 2782) {
bullet.destroy();
bullets.splice(i, 1);
continue;
}
// Spikes are no longer affected by bullets - they can pass through everything
// Check collision with target boxes
for (var j = 0; j < targetBoxes.length; j++) {
var targetBox = targetBoxes[j];
if (bullet.intersects(targetBox)) {
// Check if there's a note in timing window for this lane
var hitNote = null;
for (var k = 0; k < notes.length; k++) {
var note = notes[k];
if (note.lane === targetBox.lane && !note.hasTriggered && isNoteInTimingWindow(note, targetBox)) {
hitNote = note;
break;
}
}
if (hitNote) {
// Get timing quality for feedback
var timingQuality = getTimingQuality(hitNote, targetBox);
var basePoints = hitNote.pointValue;
var bonusPoints = 0;
var feedbackColor = 0x00FF00;
// Handle power-up activation
if (hitNote.noteType === 'multiShot') {
multiShotActive = true;
multiShotTimer = multiShotDuration;
showPowerUpDisplay('multiShot', multiShotDuration);
createFeedbackText('Multi-Shot!', targetBox.x, targetBox.y - 120, 0x00FF00);
} else if (hitNote.noteType === 'slowMotion') {
slowMotionActive = true;
slowMotionTimer = slowMotionDuration;
showPowerUpDisplay('slowMotion', slowMotionDuration);
createFeedbackText('Slow Motion!', targetBox.x, targetBox.y - 120, 0x00FF00);
} else if (hitNote.noteType === 'scoreMultiplier') {
scoreMultiplierActive = true;
scoreMultiplierTimer = scoreMultiplierDuration;
showPowerUpDisplay('scoreMultiplier', scoreMultiplierDuration);
createFeedbackText('Score x2!', targetBox.x, targetBox.y - 120, 0x00FF00);
} else if (hitNote.noteType === 'movingTarget') {
movingTargetActive = true;
movingTargetTimer = movingTargetDuration;
showPowerUpDisplay('movingTarget', movingTargetDuration);
createFeedbackText('Moving Targets!', targetBox.x, targetBox.y - 120, 0xFF0000);
} else if (hitNote.noteType === 'laserBeam') {
laserBeamActive = true;
laserBeamTimer = laserBeamDuration;
showPowerUpDisplay('laserBeam', laserBeamDuration);
createFeedbackText('Laser Beam!', targetBox.x, targetBox.y - 120, 0x00FFFF);
// Create laser beam visual effect
if (laserBeamGraphics) {
laserBeamGraphics.destroy();
}
laserBeamGraphics = LK.getAsset('laserBeam', {
anchorX: 0.5,
anchorY: 0,
x: player.x,
y: 0,
height: 2732,
alpha: 0.8
});
game.addChild(laserBeamGraphics);
} else if (hitNote.noteType === 'shield') {
if (!shieldActive) {
shieldActive = true;
shieldHits = 0;
showPowerUpDisplay('shield', 999); // Show indefinitely until used
createFeedbackText('Shield Active!', targetBox.x, targetBox.y - 120, 0x00FFFF);
// Create shield visual effect
if (shieldGraphics) {
shieldGraphics.destroy();
}
shieldGraphics = LK.getAsset('shieldEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3
});
shieldGraphics.x = 0;
shieldGraphics.y = 0;
player.addChild(shieldGraphics);
// Animate shield appearance
tween(shieldGraphics, {
alpha: 0.6,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.easeOut
});
}
} else if (hitNote.noteType === 'noteDestroyer') {
noteDestroyerActive = true;
noteDestroyerTimer = noteDestroyerDuration;
showPowerUpDisplay('noteDestroyer', noteDestroyerDuration);
createFeedbackText('Spike Destroyer!', targetBox.x, targetBox.y - 120, 0x00FFFF);
// Immediately destroy all spikes on screen
for (var s = spikes.length - 1; s >= 0; s--) {
var spike = spikes[s];
createParticleBurst(spike.x, spike.y, 0xFF4444, 8);
spike.destroy();
spikes.splice(s, 1);
}
// Add bonus points for destroyed spikes
var destroyedCount = spikes.length;
if (destroyedCount > 0) {
score += destroyedCount * 50;
createFeedbackText('+' + destroyedCount * 50 + ' Spike Bonus!', player.x, player.y - 150, 0x00FFFF);
}
} else if (hitNote.noteType === 'reverseControls') {
reverseControlsActive = true;
reverseControlsTimer = reverseControlsDuration;
showPowerUpDisplay('reverseControls', reverseControlsDuration);
createFeedbackText('Reverse Controls!', targetBox.x, targetBox.y - 120, 0xFF0000);
} else if (hitNote.noteType === 'shrinkingTargets') {
shrinkingTargetsActive = true;
shrinkingTargetsTimer = shrinkingTargetsDuration;
showPowerUpDisplay('shrinkingTargets', shrinkingTargetsDuration);
createFeedbackText('Shrinking Targets!', targetBox.x, targetBox.y - 120, 0xFF0000);
// Store original target box sizes
originalTargetSizes = [];
for (var t = 0; t < targetBoxes.length; t++) {
originalTargetSizes.push({
scaleX: targetBoxes[t].scaleX,
scaleY: targetBoxes[t].scaleY
});
// Animate targets shrinking
tween(targetBoxes[t], {
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 500,
easing: tween.easeOut
});
}
}
// Apply combo multiplier and timing bonus
combo++;
if (combo > maxCombo) {
maxCombo = combo;
}
// Timing bonuses
if (timingQuality === 'Perfect!') {
bonusPoints = Math.floor(basePoints * 0.5);
feedbackColor = 0xFFD700; // Gold
} else if (timingQuality === 'Good') {
bonusPoints = Math.floor(basePoints * 0.2);
feedbackColor = 0x00FF00; // Green
} else {
feedbackColor = 0x88FF88; // Light green
}
// Combo multiplier (every 5 combo adds 10% bonus)
var comboMultiplier = 1 + Math.floor(combo / 5) * 0.1;
// Score multiplier power-up
if (scoreMultiplierActive) {
comboMultiplier *= 2;
}
var totalPoints = Math.floor((basePoints + bonusPoints) * comboMultiplier);
score += totalPoints;
levelScore += totalPoints;
totalScore += totalPoints;
// Check for level up
checkLevelUp();
// Save progress
storage.levelScore = levelScore;
storage.totalScore = totalScore;
hitNote.hasTriggered = true;
// Create feedback text
createFeedbackText(timingQuality, targetBox.x, targetBox.y - 80, feedbackColor);
// Create particle burst effect
var particleColor = feedbackColor;
var particleCount = 8;
if (hitNote.noteType === 'bonus') {
particleColor = 0xFFD700;
particleCount = 12; // More particles for bonus notes
} else if (hitNote.noteType === 'challenge') {
particleColor = 0x8844FF;
} else if (hitNote.noteType === 'fast') {
particleColor = 0xFF4444;
} else if (hitNote.noteType === 'slow') {
particleColor = 0x4444FF;
}
createParticleBurst(hitNote.x, hitNote.y, particleColor, particleCount);
// Show combo if >= 5
if (combo >= 5) {
comboTxt.setText('Combo x' + combo);
// Scale combo text briefly for emphasis
tween(comboTxt, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
easing: tween.easeOut
});
tween(comboTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeIn
});
}
// Flash different colors based on note type
var flashColor = 0x00FF00; // Default green
if (hitNote.noteType === 'bonus') flashColor = 0xFFD700; // Gold
else if (hitNote.noteType === 'challenge') flashColor = 0x8844FF; // Purple
else if (hitNote.noteType === 'fast') flashColor = 0xFF4444; // Red
else if (hitNote.noteType === 'slow') flashColor = 0x4444FF; // Blue
LK.effects.flashObject(targetBox, flashColor, 500);
// Play note-specific sound
hitNote.playNoteSound();
// Start or resume music on successful hit
if (!musicPlaying && musicGlitchTimer === 0) {
try {
LK.playMusic(currentMusicTrack);
musicPlaying = true;
} catch (e) {
console.log('Error playing music:', currentMusicTrack);
musicPlaying = false;
}
} else if (musicGlitchTimer > 0) {
// Resume music after glitch
try {
LK.playMusic(currentMusicTrack);
musicGlitchTimer = 0;
} catch (e) {
console.log('Error resuming music:', currentMusicTrack);
}
}
// Remove the note
hitNote.destroy();
var noteIndex = notes.indexOf(hitNote);
if (noteIndex > -1) {
notes.splice(noteIndex, 1);
}
}
// Remove bullet
bullet.destroy();
bullets.splice(i, 1);
// Update score display
scoreTxt.setText('Score: ' + score);
LK.setScore(totalScore); // Use total score for leaderboards
// Update level display
updateLevelDisplay();
break;
}
}
}
// Update power-up timers
if (multiShotTimer > 0) {
multiShotTimer--;
if (multiShotTimer === 0) {
multiShotActive = false;
if (activePowerUpType === 'multiShot') {
hidePowerUpDisplay();
}
}
}
if (slowMotionTimer > 0) {
slowMotionTimer--;
if (slowMotionTimer === 0) {
slowMotionActive = false;
if (activePowerUpType === 'slowMotion') {
hidePowerUpDisplay();
}
}
}
if (scoreMultiplierTimer > 0) {
scoreMultiplierTimer--;
if (scoreMultiplierTimer === 0) {
scoreMultiplierActive = false;
if (activePowerUpType === 'scoreMultiplier') {
hidePowerUpDisplay();
}
}
}
if (movingTargetTimer > 0) {
movingTargetTimer--;
if (movingTargetTimer === 0) {
movingTargetActive = false;
if (activePowerUpType === 'movingTarget') {
hidePowerUpDisplay();
}
// Reset target boxes to original positions
for (var j = 0; j < targetBoxes.length; j++) {
targetBoxes[j].x = targetBoxPositions[j];
}
}
}
// Update new power-up timers
if (laserBeamTimer > 0) {
laserBeamTimer--;
if (laserBeamTimer === 0) {
laserBeamActive = false;
if (laserBeamGraphics) {
laserBeamGraphics.destroy();
laserBeamGraphics = null;
}
if (activePowerUpType === 'laserBeam') {
hidePowerUpDisplay();
}
}
}
if (noteDestroyerTimer > 0) {
noteDestroyerTimer--;
if (noteDestroyerTimer === 0) {
noteDestroyerActive = false;
if (activePowerUpType === 'noteDestroyer') {
hidePowerUpDisplay();
}
}
}
// Update bad power-up timers
if (reverseControlsTimer > 0) {
reverseControlsTimer--;
if (reverseControlsTimer === 0) {
reverseControlsActive = false;
if (activePowerUpType === 'reverseControls') {
hidePowerUpDisplay();
}
}
}
if (shrinkingTargetsTimer > 0) {
shrinkingTargetsTimer--;
if (shrinkingTargetsTimer === 0) {
shrinkingTargetsActive = false;
// Restore original target box sizes
for (var t = 0; t < targetBoxes.length; t++) {
if (originalTargetSizes[t]) {
tween(targetBoxes[t], {
scaleX: originalTargetSizes[t].scaleX,
scaleY: originalTargetSizes[t].scaleY
}, {
duration: 500,
easing: tween.easeOut
});
}
}
if (activePowerUpType === 'shrinkingTargets') {
hidePowerUpDisplay();
}
}
}
// Handle laser beam collision with notes
if (laserBeamActive && laserBeamGraphics) {
// Update laser position to follow player horizontally but stay at top
laserBeamGraphics.x = player.x;
laserBeamGraphics.y = 0;
// Check laser collision with all notes
for (var l = notes.length - 1; l >= 0; l--) {
var note = notes[l];
if (Math.abs(note.x - laserBeamGraphics.x) < 50 && note.y < player.y) {
// Laser hit this note
var laserPoints = note.pointValue;
if (scoreMultiplierActive) {
laserPoints *= 2;
}
score += laserPoints;
createFeedbackText('+' + laserPoints, note.x, note.y - 50, 0x00FFFF);
createParticleBurst(note.x, note.y, 0x00FFFF, 6);
note.destroy();
notes.splice(l, 1);
}
}
}
// Update power-up display timer and progress bar
if (activePowerUpType && activePowerUpMaxDuration > 0) {
var currentTimer = 0;
if (activePowerUpType === 'multiShot') currentTimer = multiShotTimer;else if (activePowerUpType === 'slowMotion') currentTimer = slowMotionTimer;else if (activePowerUpType === 'scoreMultiplier') currentTimer = scoreMultiplierTimer;else if (activePowerUpType === 'movingTarget') currentTimer = movingTargetTimer;else if (activePowerUpType === 'laserBeam') currentTimer = laserBeamTimer;else if (activePowerUpType === 'reverseControls') currentTimer = reverseControlsTimer;else if (activePowerUpType === 'shrinkingTargets') currentTimer = shrinkingTargetsTimer;else if (activePowerUpType === 'shield') {
// Shield shows number of hits remaining instead of timer
powerUpTimerDisplay.setText('Hits: ' + (maxShieldHits - shieldHits));
powerUpProgressBar.width = 200 * ((maxShieldHits - shieldHits) / maxShieldHits);
} else if (activePowerUpType === 'noteDestroyer') currentTimer = noteDestroyerTimer;
// Update timer text (convert frames to seconds) for timed power-ups
if (activePowerUpType !== 'shield') {
var secondsLeft = Math.ceil(currentTimer / 60);
powerUpTimerDisplay.setText(secondsLeft + 's');
// Update progress bar
var progress = currentTimer / activePowerUpMaxDuration;
powerUpProgressBar.width = 200 * progress;
}
}
// Move target boxes if moving target power-up is active
if (movingTargetActive) {
for (var j = 0; j < targetBoxes.length; j++) {
var targetBox = targetBoxes[j];
var originalX = targetBoxPositions[j];
var offset = Math.sin(LK.ticks * 0.05 + j * 0.5) * 100;
targetBox.x = originalX + offset;
}
}
// Update and check notes
for (var i = notes.length - 1; i >= 0; i--) {
var note = notes[i];
// Apply slow motion effect
if (slowMotionActive) {
note.speed *= 0.5;
}
// Check if note has passed through deletion area
if (note.y > deletionAreaY && !note.hasTriggered) {
// Only trigger game over for regular music notes, not power-ups
if (note.noteType !== 'multiShot' && note.noteType !== 'slowMotion' && note.noteType !== 'scoreMultiplier' && note.noteType !== 'movingTarget' && note.noteType !== 'laserBeam' && note.noteType !== 'shield' && note.noteType !== 'noteDestroyer' && note.noteType !== 'reverseControls' && note.noteType !== 'shrinkingTargets') {
// Note crashed into secret wall - game over
LK.showGameOver();
return; // Exit update loop since game is over
} else {
// Power-up notes just get removed without penalty
note.destroy();
notes.splice(i, 1);
continue;
}
}
// Remove notes that go off screen
if (note.y > 2800) {
note.destroy();
notes.splice(i, 1);
}
// Restore note speed after slow motion effect
if (slowMotionActive) {
note.speed *= 2; // Restore original speed for next frame
}
}
// Update and check spikes
for (var i = spikes.length - 1; i >= 0; i--) {
var spike = spikes[i];
// Apply slow motion effect
if (slowMotionActive) {
spike.speed *= 0.5;
}
// Check collision with player - game over if spike hits player (unless shield is active)
if (spike.intersects(player)) {
if (shieldActive && shieldHits < maxShieldHits) {
// Shield absorbs the hit
shieldHits++;
createFeedbackText('Shield Hit!', player.x, player.y - 100, 0x00FFFF);
createParticleBurst(spike.x, spike.y, 0x00FFFF, 10);
LK.effects.flashObject(player, 0x00FFFF, 500);
// Remove the spike
spike.destroy();
spikes.splice(i, 1);
// Deactivate shield if max hits reached
if (shieldHits >= maxShieldHits) {
shieldActive = false;
if (shieldGraphics) {
tween(shieldGraphics, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
shieldGraphics.destroy();
shieldGraphics = null;
}
});
}
if (activePowerUpType === 'shield') {
hidePowerUpDisplay();
}
createFeedbackText('Shield Depleted!', player.x, player.y - 120, 0xFF0000);
}
continue;
} else {
// No shield or shield depleted - game over
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return; // Exit update loop since game is over
}
}
// Spikes can pass through the secret line - no collision check needed
// Remove spikes that go off screen
if (spike.y > 2800) {
spike.destroy();
spikes.splice(i, 1);
}
// Restore spike speed after slow motion effect
if (slowMotionActive) {
spike.speed *= 2; // Restore original speed for next frame
}
}
// Handle music glitch timer
if (musicGlitchTimer > 0) {
musicGlitchTimer--;
if (musicGlitchTimer === 0 && musicPlaying) {
// Resume music after glitch period
try {
LK.playMusic(currentMusicTrack);
} catch (e) {
console.log('Error resuming music after glitch:', currentMusicTrack);
}
}
}
// Increase difficulty over time
if (LK.ticks % 1800 === 0) {
// Every 30 seconds
noteSpawnInterval = Math.max(30, noteSpawnInterval - 5);
// Increase note speed slightly
for (var i = 0; i < notes.length; i++) {
notes[i].speed = Math.min(6, notes[i].speed + 0.1);
}
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 33;
self.targetX = 0;
self.targetY = 0;
self.directionX = 0;
self.directionY = 0;
self.update = function () {
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
};
return self;
});
var Note = Container.expand(function () {
var self = Container.call(this);
// Default note type
self.noteType = 'normal';
self.pointValue = 100;
self.speed = 3;
// Initialize with normal note graphics
var noteGraphics = self.attachAsset('note', {
anchorX: 0.5,
anchorY: 0.5
});
self.graphics = noteGraphics;
self.lane = 0;
self.hasTriggered = false;
self.pulseTimer = 0;
// Method to set note type with different properties
self.setNoteType = function (type) {
self.noteType = type;
// Remove current graphics
self.removeChild(self.graphics);
switch (type) {
case 'fast':
// Mix different note assets for variety
var fastAssets = ['noteRed', 'notePurple'];
var fastAsset = fastAssets[Math.floor(Math.random() * fastAssets.length)];
self.graphics = self.attachAsset(fastAsset, {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 5;
self.pointValue = 150;
break;
case 'slow':
// Mix different note assets for variety
var slowAssets = ['noteBlue', 'note'];
var slowAsset = slowAssets[Math.floor(Math.random() * slowAssets.length)];
self.graphics = self.attachAsset(slowAsset, {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 1.5;
self.pointValue = 200;
break;
case 'bonus':
self.graphics = self.attachAsset('noteGold', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2.5;
self.pointValue = 300;
break;
case 'challenge':
self.graphics = self.attachAsset('notePurple', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 4;
self.pointValue = 250;
break;
case 'multiShot':
self.graphics = self.attachAsset('powerUpGreen', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 150;
break;
case 'slowMotion':
self.graphics = self.attachAsset('powerUpGreen', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 150;
break;
case 'scoreMultiplier':
self.graphics = self.attachAsset('powerUpGreen', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 150;
break;
case 'movingTarget':
self.graphics = self.attachAsset('powerUpRed', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 100;
break;
case 'laserBeam':
self.graphics = self.attachAsset('powerUpBlue', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 200;
break;
case 'shield':
self.graphics = self.attachAsset('powerUpBlue', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 150;
break;
case 'noteDestroyer':
self.graphics = self.attachAsset('powerUpBlue', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 300;
break;
case 'reverseControls':
self.graphics = self.attachAsset('powerUpRed', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 100;
break;
case 'shrinkingTargets':
self.graphics = self.attachAsset('powerUpRed', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 100;
break;
default:
self.graphics = self.attachAsset('note', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 3;
self.pointValue = 100;
}
};
// Method to play sound based on note type
self.playNoteSound = function () {
switch (self.noteType) {
case 'fast':
// Mix different sounds for fast notes
var fastSounds = ['shake', 'guitar'];
var randomFastSound = fastSounds[Math.floor(Math.random() * fastSounds.length)];
LK.getSound(randomFastSound).play();
break;
case 'slow':
// Mix different sounds for slow notes
var slowSounds = ['kick', 'piano', 'violin'];
var randomSlowSound = slowSounds[Math.floor(Math.random() * slowSounds.length)];
LK.getSound(randomSlowSound).play();
break;
case 'bonus':
LK.getSound('hihat').play();
break;
case 'challenge':
LK.getSound('cymbal').play();
break;
case 'multiShot':
case 'slowMotion':
case 'scoreMultiplier':
case 'movingTarget':
case 'laserBeam':
case 'shield':
case 'noteDestroyer':
case 'reverseControls':
case 'shrinkingTargets':
LK.getSound('bass').play();
break;
default:
LK.getSound('violin').play();
}
};
self.update = function () {
self.y += self.speed;
// Add visual effects for special notes
if (self.noteType === 'bonus') {
// Golden notes pulse
self.pulseTimer++;
var scale = 1 + Math.sin(self.pulseTimer * 0.2) * 0.1;
self.graphics.scaleX = scale;
self.graphics.scaleY = scale;
} else if (self.noteType === 'challenge') {
// Purple notes rotate
self.graphics.rotation += 0.05;
} else if (self.noteType === 'multiShot' || self.noteType === 'slowMotion' || self.noteType === 'scoreMultiplier') {
// Good power-ups glow green
self.pulseTimer++;
var scale = 1 + Math.sin(self.pulseTimer * 0.3) * 0.15;
self.graphics.scaleX = scale;
self.graphics.scaleY = scale;
} else if (self.noteType === 'laserBeam' || self.noteType === 'shield' || self.noteType === 'noteDestroyer') {
// New power-ups glow blue
self.pulseTimer++;
var scale = 1 + Math.sin(self.pulseTimer * 0.35) * 0.18;
self.graphics.scaleX = scale;
self.graphics.scaleY = scale;
} else if (self.noteType === 'movingTarget' || self.noteType === 'reverseControls' || self.noteType === 'shrinkingTargets') {
// Bad power-ups pulse red
self.pulseTimer++;
var scale = 1 + Math.sin(self.pulseTimer * 0.4) * 0.2;
self.graphics.scaleX = scale;
self.graphics.scaleY = scale;
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// Add floating gun
var gun = self.attachAsset('gun', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -50
});
self.gun = gun;
// Method to rotate gun towards target with smooth animation
self.aimGunAt = function (targetX, targetY) {
var dx = targetX - self.x;
var dy = targetY - (self.y - 50); // Account for gun offset
var targetRotation = Math.atan2(dy, dx);
// Smooth rotation animation
tween(self.gun, {
rotation: targetRotation
}, {
duration: 150,
easing: tween.easeOut
});
};
return self;
});
var Spike = Container.expand(function () {
var self = Container.call(this);
var spikeGraphics = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
self.speed = 8.0;
self.lane = 0;
self.pulseTimer = 0;
self.update = function () {
self.y += self.speed;
// Add pulsing danger effect
self.pulseTimer++;
var scale = 0.7 + Math.sin(self.pulseTimer * 0.4) * 0.14;
spikeGraphics.scaleX = scale;
spikeGraphics.scaleY = scale;
// Add rotation for more menacing look
spikeGraphics.rotation += 0.08;
};
return self;
});
var TargetBox = Container.expand(function () {
var self = Container.call(this);
var targetGraphics = self.attachAsset('targetBox', {
anchorX: 0.5,
anchorY: 0.5
});
self.lane = 0;
self.isActive = false;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xF0F8FF,
name: 'Cats Are Rhythmic'
});
/****
* Game Code
****/
// Game state variables
var gameState = 'menu'; // 'menu' or 'playing'
var startMenuContainer;
var musicButtons = [];
// Game variables
var player;
var bullets = [];
var notes = [];
var spikes = [];
var targetBoxes = [];
var staffLines = [];
var aimLine;
var deletionAreaY;
var targetBoxPositions = [512, 853, 1194, 1536]; // Define globally for access throughout the game
var score = 0;
var staffY = 400;
var staffSpacing = 80;
// Level system variables
var currentLevel = storage.currentLevel || 1;
var levelScore = storage.levelScore || 0; // Score accumulated in current level
var totalScore = storage.totalScore || 0; // Total score across all levels
var levelThresholds = [0, 1000, 2500, 5000, 8000, 12000, 17000, 23000, 30000, 38000, 47000, 57000, 68000, 80000, 93000, 107000, 122000, 138000, 155000, 173000]; // Score needed for each level
var maxLevel = levelThresholds.length;
var mouseX = 1024;
var noteSpawnTimer = 0;
var noteSpawnInterval = 90; // frames between note spawns
var spikeSpawnTimer = 0;
var spikeSpawnInterval = 300; // frames between spike spawns (much less frequent than notes)
var musicPlaying = false;
var musicGlitchTimer = 0;
var musicGlitchDuration = 18; // 0.3 seconds at 60fps
// Music selection variables
var currentMusicTrack = 'bgmusic';
var musicTracks = ['bgmusic', 'rock_track', 'electronic_track'];
var musicTrackNames = ['Electronic Track', 'Rock Track', 'Soul Track'];
// Note preview system
var previewNotes = [];
var nextNoteTypes = [];
// Combo system variables
var combo = 0;
var maxCombo = 0;
// Feedback text variables
var feedbackTexts = [];
// Power-up system variables
var multiShotActive = false;
var multiShotTimer = 0;
var multiShotDuration = 300; // 5 seconds at 60fps
var slowMotionActive = false;
var slowMotionTimer = 0;
var slowMotionDuration = 300; // 5 seconds at 60fps
var scoreMultiplierActive = false;
var scoreMultiplierTimer = 0;
var scoreMultiplierDuration = 300; // 5 seconds at 60fps
var movingTargetActive = false;
var movingTargetTimer = 0;
var movingTargetDuration = 240; // 4 seconds at 60fps
// Power-up display variables
var powerUpDisplayContainer;
var powerUpIconDisplay;
var powerUpNameDisplay;
var powerUpTimerDisplay;
var powerUpProgressBar;
var activePowerUpType = null;
var activePowerUpMaxDuration = 0;
// New power-up system variables
var laserBeamActive = false;
var laserBeamTimer = 0;
var laserBeamDuration = 180; // 3 seconds at 60fps
var laserBeamGraphics = null;
var shieldActive = false;
var shieldHits = 0;
var maxShieldHits = 1;
var shieldGraphics = null;
var noteDestroyerActive = false;
var noteDestroyerTimer = 0;
var noteDestroyerDuration = 1; // Instant effect, 1 frame duration
// Bad power-up system variables
var reverseControlsActive = false;
var reverseControlsTimer = 0;
var reverseControlsDuration = 300; // 5 seconds at 60fps
var shrinkingTargetsActive = false;
var shrinkingTargetsTimer = 0;
var shrinkingTargetsDuration = 240; // 4 seconds at 60fps
var originalTargetSizes = [];
// Create start menu
function createStartMenu() {
startMenuContainer = new Container();
startMenuContainer.x = 1024; // Center of screen
startMenuContainer.y = 1366; // Center of screen
game.addChild(startMenuContainer);
// Game title
var titleText = new Text2('Cats Are Rhythmic', {
size: 120,
fill: 0x87CEEB
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -400;
startMenuContainer.addChild(titleText);
// Subtitle
var subtitleText = new Text2('Choose Your Music', {
size: 80,
fill: 0x87CEEB
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.y = -300;
startMenuContainer.addChild(subtitleText);
// Create music selection buttons
var buttonSpacing = 200;
var startY = -100;
for (var i = 0; i < musicTracks.length; i++) {
var buttonContainer = new Container();
buttonContainer.x = 0;
buttonContainer.y = startY + i * buttonSpacing;
startMenuContainer.addChild(buttonContainer);
// Button background
var buttonBg = LK.getAsset('targetBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 1.5,
tint: 0x4444FF
});
buttonContainer.addChild(buttonBg);
// Button text
var buttonText = new Text2(musicTrackNames[i], {
size: 60,
fill: 0x87CEEB
});
buttonText.anchor.set(0.5, 0.5);
buttonContainer.addChild(buttonText);
// Store button data
buttonContainer.musicIndex = i;
buttonContainer.background = buttonBg;
buttonContainer.text = buttonText;
// Add click handler
buttonContainer.down = function (x, y, obj) {
selectMusic(this.musicIndex);
};
musicButtons.push(buttonContainer);
}
// Music warning instruction
var musicWarningText = new Text2('MUSIC WONT START TILL YOU HIT A MUSIC NOTE!', {
size: 45,
fill: 0xFF6666
});
musicWarningText.anchor.set(0.5, 0.5);
musicWarningText.y = 450;
startMenuContainer.addChild(musicWarningText);
// Instructions
var instructionText = new Text2('Tap a music option to start the game', {
size: 50,
fill: 0x87CEEB
});
instructionText.anchor.set(0.5, 0.5);
instructionText.y = 500;
startMenuContainer.addChild(instructionText);
}
function selectMusic(musicIndex) {
currentMusicTrack = musicTracks[musicIndex];
// Update active music display
activeMusicDisplay.setText('Music: ' + musicTrackNames[musicIndex]);
// Animate menu exit
tween(startMenuContainer, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
startMenuContainer.destroy();
startMenuContainer = null;
musicButtons = [];
startGame();
}
});
}
function startGame() {
gameState = 'playing';
setupGameElements();
}
function setupGameElements() {
// Setup staff lines
var staffY = 400;
var staffSpacing = 80;
for (var i = 0; i < 5; i++) {
var staffLine = game.addChild(LK.getAsset('staffLine', {
anchorX: 0,
anchorY: 0.5,
x: 0,
y: staffY + i * staffSpacing
}));
staffLines.push(staffLine);
}
// Setup note preview indicators
var targetBoxPositions = [512, 853, 1194, 1536];
for (var i = 0; i < 4; i++) {
var previewNote = LK.getAsset('note', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.8,
x: targetBoxPositions[i],
y: staffY - 100
});
previewNote.visible = false;
game.addChild(previewNote);
previewNotes.push(previewNote);
nextNoteTypes.push(null);
}
// Setup target boxes
var targetY = staffY + 4 * staffSpacing + 200;
var targetBoxPositions = [512, 853, 1194, 1536]; // 4 evenly spaced positions
for (var i = 0; i < 4; i++) {
var targetBox = game.addChild(new TargetBox());
targetBox.x = targetBoxPositions[i];
targetBox.y = targetY;
targetBox.lane = i;
targetBoxes.push(targetBox);
}
// Setup deletion area below target boxes
deletionAreaY = targetY + 250;
// Initialize level system
updateLevelDisplay();
increaseDifficultyForLevel();
// Create visible secret wall
var secretWall = game.addChild(LK.getAsset('secretWall', {
anchorX: 0,
anchorY: 0.5,
x: 0,
y: deletionAreaY
}));
// Setup player
player = game.addChild(new Player());
player.x = 1024;
player.y = 2500;
// Create aim assistance line
aimLine = LK.getAsset('staffLine', {
anchorX: 0,
anchorY: 0.5,
width: 2,
height: 2,
alpha: 0.3,
tint: 0xFF0000
});
game.addChild(aimLine);
}
// Setup score display
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0x000000
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Setup level display
var levelTxt = new Text2('Level: ' + currentLevel, {
size: 60,
fill: 0x0066CC
});
levelTxt.anchor.set(0, 0);
levelTxt.x = 20;
levelTxt.y = 20;
LK.gui.topLeft.addChild(levelTxt);
// Setup level progress display
var levelProgressTxt = new Text2('', {
size: 45,
fill: 0x666666
});
levelProgressTxt.anchor.set(0, 0);
levelProgressTxt.x = 20;
levelProgressTxt.y = 90;
LK.gui.topLeft.addChild(levelProgressTxt);
// Setup combo display
var comboTxt = new Text2('', {
size: 60,
fill: 0xFF6600
});
comboTxt.anchor.set(0.5, 0);
comboTxt.y = 90; // Position below score
LK.gui.top.addChild(comboTxt);
// Display active music (non-interactive)
var activeMusicDisplay = new Text2('Music: ' + musicTrackNames[0], {
size: 50,
fill: 0x000000
});
activeMusicDisplay.anchor.set(1, 0);
activeMusicDisplay.x = -20; // Position from right edge
activeMusicDisplay.y = 20;
LK.gui.topRight.addChild(activeMusicDisplay);
// Create power-up display container on left side of screen
powerUpDisplayContainer = new Container();
powerUpDisplayContainer.x = 50;
powerUpDisplayContainer.y = 200;
powerUpDisplayContainer.alpha = 0;
LK.gui.left.addChild(powerUpDisplayContainer);
// Power-up icon display
powerUpIconDisplay = LK.getAsset('powerUpGreen', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
powerUpIconDisplay.x = 40;
powerUpIconDisplay.y = 40;
powerUpDisplayContainer.addChild(powerUpIconDisplay);
// Power-up name text
powerUpNameDisplay = new Text2('', {
size: 40,
fill: 0x00FF00
});
powerUpNameDisplay.anchor.set(0, 0.5);
powerUpNameDisplay.x = 90;
powerUpNameDisplay.y = 25;
powerUpDisplayContainer.addChild(powerUpNameDisplay);
// Power-up timer text
powerUpTimerDisplay = new Text2('', {
size: 30,
fill: 0xFFFFFF
});
powerUpTimerDisplay.anchor.set(0, 0.5);
powerUpTimerDisplay.x = 90;
powerUpTimerDisplay.y = 55;
powerUpDisplayContainer.addChild(powerUpTimerDisplay);
// Power-up progress bar background
var progressBarBg = LK.getAsset('staffLine', {
anchorX: 0,
anchorY: 0.5,
width: 200,
height: 8,
tint: 0x333333
});
progressBarBg.x = 90;
progressBarBg.y = 75;
powerUpDisplayContainer.addChild(progressBarBg);
// Power-up progress bar fill
powerUpProgressBar = LK.getAsset('staffLine', {
anchorX: 0,
anchorY: 0.5,
width: 200,
height: 8,
tint: 0x00FF00
});
powerUpProgressBar.x = 90;
powerUpProgressBar.y = 75;
powerUpDisplayContainer.addChild(powerUpProgressBar);
// Initialize start menu
createStartMenu();
// Input handling
game.move = function (x, y, obj) {
if (gameState !== 'playing') return;
mouseX = x;
// Apply reverse controls effect
if (reverseControlsActive) {
var centerX = 1024;
var distanceFromCenter = x - centerX;
player.x = centerX - distanceFromCenter;
} else {
player.x = x;
}
// Continuously aim gun at mouse position
player.aimGunAt(x, y);
// Update aim line
var gunX = player.x;
var gunY = player.y - 50;
var dx = x - gunX;
var dy = y - gunY;
var distance = Math.sqrt(dx * dx + dy * dy);
aimLine.x = gunX;
aimLine.y = gunY;
aimLine.width = Math.min(distance, 300); // Limit line length
aimLine.rotation = Math.atan2(dy, dx);
};
game.down = function (x, y, obj) {
if (gameState !== 'playing') return;
// Aim gun at target position
player.aimGunAt(x, y);
if (multiShotActive) {
// Create 3 bullets in spread pattern
for (var i = 0; i < 3; i++) {
var bullet = game.addChild(new Bullet());
bullet.x = player.x;
bullet.y = player.y - 50; // Spawn from gun position
// Calculate direction to mouse position with spread
var dx = x - player.x;
var dy = y - (player.y - 50); // Account for gun offset
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
var angle = Math.atan2(dy, dx);
var spreadAngle = (i - 1) * 0.3; // -0.3, 0, 0.3 radians spread
bullet.directionX = Math.cos(angle + spreadAngle);
bullet.directionY = Math.sin(angle + spreadAngle);
}
bullets.push(bullet);
}
} else {
// Create single bullet from gun position
var bullet = game.addChild(new Bullet());
bullet.x = player.x;
bullet.y = player.y - 50; // Spawn from gun position
// Calculate direction to mouse position
var dx = x - player.x;
var dy = y - (player.y - 50); // Account for gun offset
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
bullet.directionX = dx / distance;
bullet.directionY = dy / distance;
}
bullets.push(bullet);
}
};
// Generate note type based on probabilities
function generateNoteType() {
var random = Math.random();
if (random < 0.05) {
return 'bonus'; // 5% chance for bonus notes
} else if (random < 0.1) {
return 'challenge'; // 5% chance for challenge notes
} else if (random < 0.2) {
return 'fast'; // 10% chance for fast notes
} else if (random < 0.3) {
return 'slow'; // 10% chance for slow notes
} else if (random < 0.33) {
return 'multiShot'; // 3% chance for multi-shot power-up
} else if (random < 0.36) {
return 'slowMotion'; // 3% chance for slow motion power-up
} else if (random < 0.39) {
return 'scoreMultiplier'; // 3% chance for score multiplier power-up
} else if (random < 0.42) {
return 'movingTarget'; // 3% chance for moving target power-up
} else if (random < 0.445) {
return 'laserBeam'; // 2.5% chance for laser beam power-up
} else if (random < 0.47) {
return 'shield'; // 2.5% chance for shield power-up
} else if (random < 0.49) {
return 'noteDestroyer'; // 2% chance for note destroyer power-up
} else if (random < 0.515) {
return 'reverseControls'; // 2.5% chance for reverse controls power-up
} else if (random < 0.54) {
return 'shrinkingTargets'; // 2.5% chance for shrinking targets power-up
} else {
return 'normal'; // 46% chance for normal notes
}
}
// Update note preview display
function updateNotePreview(lane, noteType) {
if (lane >= 0 && lane < previewNotes.length) {
var preview = previewNotes[lane];
nextNoteTypes[lane] = noteType;
// Set preview note appearance based on type
preview.removeChildren();
var assetId = 'note';
var tintColor = 0xFFFFFF;
switch (noteType) {
case 'fast':
assetId = 'noteRed';
break;
case 'slow':
assetId = 'noteBlue';
break;
case 'bonus':
assetId = 'noteGold';
break;
case 'challenge':
assetId = 'notePurple';
break;
case 'multiShot':
case 'slowMotion':
case 'scoreMultiplier':
assetId = 'powerUpGreen';
break;
case 'movingTarget':
case 'reverseControls':
case 'shrinkingTargets':
assetId = 'powerUpRed';
break;
case 'laserBeam':
case 'shield':
case 'noteDestroyer':
assetId = 'powerUpBlue';
break;
}
var previewGraphics = LK.getAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.8
});
preview.addChild(previewGraphics);
preview.visible = true;
// Animate preview appearance
tween(preview, {
alpha: 1.0,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut
});
// Hide preview after delay
tween(preview, {
alpha: 0.6,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300,
easing: tween.easeIn
});
}
}
// Spawn notes
function spawnNote() {
var lane = Math.floor(Math.random() * 4);
var note = game.addChild(new Note());
var targetBoxPositions = [512, 853, 1194, 1536]; // Match target box positions
note.x = targetBoxPositions[lane];
note.y = staffY + lane * staffSpacing - 200;
note.lane = lane;
// Use generated note type
var noteType = generateNoteType();
note.setNoteType(noteType);
// Show preview for next note
var nextLane = Math.floor(Math.random() * 4);
var nextNoteType = generateNoteType();
updateNotePreview(nextLane, nextNoteType);
notes.push(note);
}
// Spawn spikes
function spawnSpike() {
var lane = Math.floor(Math.random() * 4);
var spike = game.addChild(new Spike());
var targetBoxPositions = [512, 853, 1194, 1536]; // Match target box positions
spike.x = targetBoxPositions[lane];
spike.y = staffY + lane * staffSpacing - 200;
spike.lane = lane;
spikes.push(spike);
}
// Check if note is in timing window
function isNoteInTimingWindow(note, targetBox) {
var noteTop = note.y - 35; // Top of note (note height is 70, so 35 from center)
var noteBottom = note.y + 35; // Bottom of note
var targetTop = targetBox.y - 65; // Expanded target area (target height is 130, so 65 from center)
var targetBottom = targetBox.y + 65; // Expanded target area
// Check if any part of the note overlaps with the expanded target area
return noteBottom >= targetTop && noteTop <= targetBottom;
}
// Get timing quality based on note position relative to target center
function getTimingQuality(note, targetBox) {
var distance = Math.abs(note.y - targetBox.y);
if (distance <= 20) {
return 'Perfect!';
} else if (distance <= 40) {
return 'Good';
} else {
return 'Hit';
}
}
// Create particle burst effect
function createParticleBurst(x, y, color, count) {
for (var i = 0; i < count; i++) {
var particle = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
tint: color
});
particle.x = x;
particle.y = y;
game.addChild(particle);
// Random direction and speed
var angle = Math.PI * 2 * i / count + (Math.random() - 0.5) * 0.5;
var speed = 100 + Math.random() * 100;
var targetX = x + Math.cos(angle) * speed;
var targetY = y + Math.sin(angle) * speed;
// Animate particle outward
tween(particle, {
x: targetX,
y: targetY,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
}, {
duration: 800 + Math.random() * 400,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
}
// Check and handle level progression
function checkLevelUp() {
if (currentLevel < maxLevel) {
var nextLevelThreshold = levelThresholds[currentLevel];
if (totalScore >= nextLevelThreshold) {
levelUp();
}
}
}
// Handle level up
function levelUp() {
var previousLevel = currentLevel;
currentLevel++;
// Save level progress
storage.currentLevel = currentLevel;
storage.totalScore = totalScore;
storage.levelScore = 0; // Reset level score for new level
levelScore = 0;
// Create animated level pop-up display
var levelPopUpContainer = new Container();
levelPopUpContainer.x = 1024; // Center of screen
levelPopUpContainer.y = 1366; // Center of screen
levelPopUpContainer.alpha = 0;
levelPopUpContainer.scaleX = 0.1;
levelPopUpContainer.scaleY = 0.1;
game.addChild(levelPopUpContainer);
// Background for pop-up
var popUpBg = LK.getAsset('targetBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4,
scaleY: 3,
tint: 0x000080,
alpha: 0.9
});
levelPopUpContainer.addChild(popUpBg);
// Level text
var levelPopUpText = new Text2('LEVEL ' + currentLevel, {
size: 120,
fill: 0xFFD700
});
levelPopUpText.anchor.set(0.5, 0.5);
levelPopUpText.y = -30;
levelPopUpContainer.addChild(levelPopUpText);
// Level up text
var levelUpText = new Text2('LEVEL UP!', {
size: 80,
fill: 0x00FF00
});
levelUpText.anchor.set(0.5, 0.5);
levelUpText.y = 60;
levelPopUpContainer.addChild(levelUpText);
// Animate pop-up appearance
tween(levelPopUpContainer, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 600,
easing: tween.easeOut
});
// Add pulsing effect to level text
tween(levelPopUpText, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 800,
easing: tween.easeInOut
});
tween(levelPopUpText, {
scaleX: 1,
scaleY: 1
}, {
duration: 800,
easing: tween.easeInOut
});
// Remove pop-up after delay
tween(levelPopUpContainer, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8,
y: 1200
}, {
duration: 800,
easing: tween.easeIn,
onFinish: function onFinish() {
levelPopUpContainer.destroy();
}
});
// Create level up feedback
createFeedbackText('LEVEL UP!', 1024, 800, 0xFFD700);
createFeedbackText('Level ' + currentLevel, 1024, 860, 0x0066CC);
// Flash screen gold for level up
LK.effects.flashScreen(0xFFD700, 800);
// Update level display
updateLevelDisplay();
// Increase difficulty with level
increaseDifficultyForLevel();
}
// Update level display
function updateLevelDisplay() {
if (levelTxt) {
levelTxt.setText('Level: ' + currentLevel);
}
if (currentLevel < maxLevel) {
var nextThreshold = levelThresholds[currentLevel];
var progress = totalScore - (currentLevel > 1 ? levelThresholds[currentLevel - 1] : 0);
var needed = nextThreshold - (currentLevel > 1 ? levelThresholds[currentLevel - 1] : 0);
if (levelProgressTxt) {
levelProgressTxt.setText(progress + ' / ' + needed);
}
} else {
if (levelProgressTxt) {
levelProgressTxt.setText('MAX LEVEL');
}
}
}
// Increase difficulty based on current level
function increaseDifficultyForLevel() {
// Reduce spawn intervals based on level
noteSpawnInterval = Math.max(30, 90 - (currentLevel - 1) * 3);
spikeSpawnInterval = Math.max(120, 300 - (currentLevel - 1) * 8);
}
// Create feedback text with animation
function createFeedbackText(text, x, y, color) {
var feedbackText = new Text2(text, {
size: 50,
fill: color
});
feedbackText.anchor.set(0.5, 0.5);
feedbackText.x = x;
feedbackText.y = y;
feedbackText.alpha = 1;
feedbackText.scaleX = 0.5;
feedbackText.scaleY = 0.5;
game.addChild(feedbackText);
feedbackTexts.push(feedbackText);
// Animate text appearance
tween(feedbackText, {
scaleX: 1.2,
scaleY: 1.2,
y: y - 50
}, {
duration: 300,
easing: tween.easeOut
});
// Fade out and remove
tween(feedbackText, {
alpha: 0,
y: y - 100
}, {
duration: 800,
easing: tween.easeIn,
onFinish: function onFinish() {
feedbackText.destroy();
var index = feedbackTexts.indexOf(feedbackText);
if (index > -1) {
feedbackTexts.splice(index, 1);
}
}
});
}
// Show power-up effect display
function showPowerUpDisplay(powerUpType, duration) {
activePowerUpType = powerUpType;
activePowerUpMaxDuration = duration;
// Set power-up name and icon based on type
var powerUpName = '';
var iconAsset = 'powerUpGreen';
var textColor = 0x00FF00;
switch (powerUpType) {
case 'multiShot':
powerUpName = 'Multi-Shot';
iconAsset = 'powerUpGreen';
textColor = 0x00FF00;
break;
case 'slowMotion':
powerUpName = 'Slow Motion';
iconAsset = 'powerUpGreen';
textColor = 0x00FFFF;
break;
case 'scoreMultiplier':
powerUpName = 'Score x2';
iconAsset = 'powerUpGreen';
textColor = 0xFFD700;
break;
case 'movingTarget':
powerUpName = 'Moving Targets';
iconAsset = 'powerUpRed';
textColor = 0xFF0000;
break;
case 'laserBeam':
powerUpName = 'Laser Beam';
iconAsset = 'powerUpBlue';
textColor = 0x00FFFF;
break;
case 'shield':
powerUpName = 'Shield';
iconAsset = 'powerUpBlue';
textColor = 0x00FFFF;
break;
case 'noteDestroyer':
powerUpName = 'Spike Destroyer';
iconAsset = 'powerUpBlue';
textColor = 0x00FFFF;
break;
case 'reverseControls':
powerUpName = 'Reverse Controls';
iconAsset = 'powerUpRed';
textColor = 0xFF0000;
break;
case 'shrinkingTargets':
powerUpName = 'Shrinking Targets';
iconAsset = 'powerUpRed';
textColor = 0xFF0000;
break;
}
// Update display elements
powerUpDisplayContainer.removeChild(powerUpIconDisplay);
powerUpIconDisplay = LK.getAsset(iconAsset, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
powerUpIconDisplay.x = 40;
powerUpIconDisplay.y = 40;
powerUpDisplayContainer.addChild(powerUpIconDisplay);
powerUpNameDisplay.setText(powerUpName);
powerUpNameDisplay.fill = textColor;
powerUpProgressBar.tint = textColor;
// Animate display appearance
tween(powerUpDisplayContainer, {
alpha: 1,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 300,
easing: tween.easeOut
});
tween(powerUpDisplayContainer, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
// Hide power-up effect display
function hidePowerUpDisplay() {
activePowerUpType = null;
activePowerUpMaxDuration = 0;
tween(powerUpDisplayContainer, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 300,
easing: tween.easeIn
});
}
// Game update loop
game.update = function () {
if (gameState !== 'playing') return;
// Spawn notes
noteSpawnTimer++;
if (noteSpawnTimer >= noteSpawnInterval) {
spawnNote();
noteSpawnTimer = 0;
}
// Spawn spikes
spikeSpawnTimer++;
if (spikeSpawnTimer >= spikeSpawnInterval) {
spawnSpike();
spikeSpawnTimer = 0;
}
// Update and check bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
// Remove bullets that go off screen
if (bullet.x < -50 || bullet.x > 2098 || bullet.y < -50 || bullet.y > 2782) {
bullet.destroy();
bullets.splice(i, 1);
continue;
}
// Spikes are no longer affected by bullets - they can pass through everything
// Check collision with target boxes
for (var j = 0; j < targetBoxes.length; j++) {
var targetBox = targetBoxes[j];
if (bullet.intersects(targetBox)) {
// Check if there's a note in timing window for this lane
var hitNote = null;
for (var k = 0; k < notes.length; k++) {
var note = notes[k];
if (note.lane === targetBox.lane && !note.hasTriggered && isNoteInTimingWindow(note, targetBox)) {
hitNote = note;
break;
}
}
if (hitNote) {
// Get timing quality for feedback
var timingQuality = getTimingQuality(hitNote, targetBox);
var basePoints = hitNote.pointValue;
var bonusPoints = 0;
var feedbackColor = 0x00FF00;
// Handle power-up activation
if (hitNote.noteType === 'multiShot') {
multiShotActive = true;
multiShotTimer = multiShotDuration;
showPowerUpDisplay('multiShot', multiShotDuration);
createFeedbackText('Multi-Shot!', targetBox.x, targetBox.y - 120, 0x00FF00);
} else if (hitNote.noteType === 'slowMotion') {
slowMotionActive = true;
slowMotionTimer = slowMotionDuration;
showPowerUpDisplay('slowMotion', slowMotionDuration);
createFeedbackText('Slow Motion!', targetBox.x, targetBox.y - 120, 0x00FF00);
} else if (hitNote.noteType === 'scoreMultiplier') {
scoreMultiplierActive = true;
scoreMultiplierTimer = scoreMultiplierDuration;
showPowerUpDisplay('scoreMultiplier', scoreMultiplierDuration);
createFeedbackText('Score x2!', targetBox.x, targetBox.y - 120, 0x00FF00);
} else if (hitNote.noteType === 'movingTarget') {
movingTargetActive = true;
movingTargetTimer = movingTargetDuration;
showPowerUpDisplay('movingTarget', movingTargetDuration);
createFeedbackText('Moving Targets!', targetBox.x, targetBox.y - 120, 0xFF0000);
} else if (hitNote.noteType === 'laserBeam') {
laserBeamActive = true;
laserBeamTimer = laserBeamDuration;
showPowerUpDisplay('laserBeam', laserBeamDuration);
createFeedbackText('Laser Beam!', targetBox.x, targetBox.y - 120, 0x00FFFF);
// Create laser beam visual effect
if (laserBeamGraphics) {
laserBeamGraphics.destroy();
}
laserBeamGraphics = LK.getAsset('laserBeam', {
anchorX: 0.5,
anchorY: 0,
x: player.x,
y: 0,
height: 2732,
alpha: 0.8
});
game.addChild(laserBeamGraphics);
} else if (hitNote.noteType === 'shield') {
if (!shieldActive) {
shieldActive = true;
shieldHits = 0;
showPowerUpDisplay('shield', 999); // Show indefinitely until used
createFeedbackText('Shield Active!', targetBox.x, targetBox.y - 120, 0x00FFFF);
// Create shield visual effect
if (shieldGraphics) {
shieldGraphics.destroy();
}
shieldGraphics = LK.getAsset('shieldEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3
});
shieldGraphics.x = 0;
shieldGraphics.y = 0;
player.addChild(shieldGraphics);
// Animate shield appearance
tween(shieldGraphics, {
alpha: 0.6,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.easeOut
});
}
} else if (hitNote.noteType === 'noteDestroyer') {
noteDestroyerActive = true;
noteDestroyerTimer = noteDestroyerDuration;
showPowerUpDisplay('noteDestroyer', noteDestroyerDuration);
createFeedbackText('Spike Destroyer!', targetBox.x, targetBox.y - 120, 0x00FFFF);
// Immediately destroy all spikes on screen
for (var s = spikes.length - 1; s >= 0; s--) {
var spike = spikes[s];
createParticleBurst(spike.x, spike.y, 0xFF4444, 8);
spike.destroy();
spikes.splice(s, 1);
}
// Add bonus points for destroyed spikes
var destroyedCount = spikes.length;
if (destroyedCount > 0) {
score += destroyedCount * 50;
createFeedbackText('+' + destroyedCount * 50 + ' Spike Bonus!', player.x, player.y - 150, 0x00FFFF);
}
} else if (hitNote.noteType === 'reverseControls') {
reverseControlsActive = true;
reverseControlsTimer = reverseControlsDuration;
showPowerUpDisplay('reverseControls', reverseControlsDuration);
createFeedbackText('Reverse Controls!', targetBox.x, targetBox.y - 120, 0xFF0000);
} else if (hitNote.noteType === 'shrinkingTargets') {
shrinkingTargetsActive = true;
shrinkingTargetsTimer = shrinkingTargetsDuration;
showPowerUpDisplay('shrinkingTargets', shrinkingTargetsDuration);
createFeedbackText('Shrinking Targets!', targetBox.x, targetBox.y - 120, 0xFF0000);
// Store original target box sizes
originalTargetSizes = [];
for (var t = 0; t < targetBoxes.length; t++) {
originalTargetSizes.push({
scaleX: targetBoxes[t].scaleX,
scaleY: targetBoxes[t].scaleY
});
// Animate targets shrinking
tween(targetBoxes[t], {
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 500,
easing: tween.easeOut
});
}
}
// Apply combo multiplier and timing bonus
combo++;
if (combo > maxCombo) {
maxCombo = combo;
}
// Timing bonuses
if (timingQuality === 'Perfect!') {
bonusPoints = Math.floor(basePoints * 0.5);
feedbackColor = 0xFFD700; // Gold
} else if (timingQuality === 'Good') {
bonusPoints = Math.floor(basePoints * 0.2);
feedbackColor = 0x00FF00; // Green
} else {
feedbackColor = 0x88FF88; // Light green
}
// Combo multiplier (every 5 combo adds 10% bonus)
var comboMultiplier = 1 + Math.floor(combo / 5) * 0.1;
// Score multiplier power-up
if (scoreMultiplierActive) {
comboMultiplier *= 2;
}
var totalPoints = Math.floor((basePoints + bonusPoints) * comboMultiplier);
score += totalPoints;
levelScore += totalPoints;
totalScore += totalPoints;
// Check for level up
checkLevelUp();
// Save progress
storage.levelScore = levelScore;
storage.totalScore = totalScore;
hitNote.hasTriggered = true;
// Create feedback text
createFeedbackText(timingQuality, targetBox.x, targetBox.y - 80, feedbackColor);
// Create particle burst effect
var particleColor = feedbackColor;
var particleCount = 8;
if (hitNote.noteType === 'bonus') {
particleColor = 0xFFD700;
particleCount = 12; // More particles for bonus notes
} else if (hitNote.noteType === 'challenge') {
particleColor = 0x8844FF;
} else if (hitNote.noteType === 'fast') {
particleColor = 0xFF4444;
} else if (hitNote.noteType === 'slow') {
particleColor = 0x4444FF;
}
createParticleBurst(hitNote.x, hitNote.y, particleColor, particleCount);
// Show combo if >= 5
if (combo >= 5) {
comboTxt.setText('Combo x' + combo);
// Scale combo text briefly for emphasis
tween(comboTxt, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
easing: tween.easeOut
});
tween(comboTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeIn
});
}
// Flash different colors based on note type
var flashColor = 0x00FF00; // Default green
if (hitNote.noteType === 'bonus') flashColor = 0xFFD700; // Gold
else if (hitNote.noteType === 'challenge') flashColor = 0x8844FF; // Purple
else if (hitNote.noteType === 'fast') flashColor = 0xFF4444; // Red
else if (hitNote.noteType === 'slow') flashColor = 0x4444FF; // Blue
LK.effects.flashObject(targetBox, flashColor, 500);
// Play note-specific sound
hitNote.playNoteSound();
// Start or resume music on successful hit
if (!musicPlaying && musicGlitchTimer === 0) {
try {
LK.playMusic(currentMusicTrack);
musicPlaying = true;
} catch (e) {
console.log('Error playing music:', currentMusicTrack);
musicPlaying = false;
}
} else if (musicGlitchTimer > 0) {
// Resume music after glitch
try {
LK.playMusic(currentMusicTrack);
musicGlitchTimer = 0;
} catch (e) {
console.log('Error resuming music:', currentMusicTrack);
}
}
// Remove the note
hitNote.destroy();
var noteIndex = notes.indexOf(hitNote);
if (noteIndex > -1) {
notes.splice(noteIndex, 1);
}
}
// Remove bullet
bullet.destroy();
bullets.splice(i, 1);
// Update score display
scoreTxt.setText('Score: ' + score);
LK.setScore(totalScore); // Use total score for leaderboards
// Update level display
updateLevelDisplay();
break;
}
}
}
// Update power-up timers
if (multiShotTimer > 0) {
multiShotTimer--;
if (multiShotTimer === 0) {
multiShotActive = false;
if (activePowerUpType === 'multiShot') {
hidePowerUpDisplay();
}
}
}
if (slowMotionTimer > 0) {
slowMotionTimer--;
if (slowMotionTimer === 0) {
slowMotionActive = false;
if (activePowerUpType === 'slowMotion') {
hidePowerUpDisplay();
}
}
}
if (scoreMultiplierTimer > 0) {
scoreMultiplierTimer--;
if (scoreMultiplierTimer === 0) {
scoreMultiplierActive = false;
if (activePowerUpType === 'scoreMultiplier') {
hidePowerUpDisplay();
}
}
}
if (movingTargetTimer > 0) {
movingTargetTimer--;
if (movingTargetTimer === 0) {
movingTargetActive = false;
if (activePowerUpType === 'movingTarget') {
hidePowerUpDisplay();
}
// Reset target boxes to original positions
for (var j = 0; j < targetBoxes.length; j++) {
targetBoxes[j].x = targetBoxPositions[j];
}
}
}
// Update new power-up timers
if (laserBeamTimer > 0) {
laserBeamTimer--;
if (laserBeamTimer === 0) {
laserBeamActive = false;
if (laserBeamGraphics) {
laserBeamGraphics.destroy();
laserBeamGraphics = null;
}
if (activePowerUpType === 'laserBeam') {
hidePowerUpDisplay();
}
}
}
if (noteDestroyerTimer > 0) {
noteDestroyerTimer--;
if (noteDestroyerTimer === 0) {
noteDestroyerActive = false;
if (activePowerUpType === 'noteDestroyer') {
hidePowerUpDisplay();
}
}
}
// Update bad power-up timers
if (reverseControlsTimer > 0) {
reverseControlsTimer--;
if (reverseControlsTimer === 0) {
reverseControlsActive = false;
if (activePowerUpType === 'reverseControls') {
hidePowerUpDisplay();
}
}
}
if (shrinkingTargetsTimer > 0) {
shrinkingTargetsTimer--;
if (shrinkingTargetsTimer === 0) {
shrinkingTargetsActive = false;
// Restore original target box sizes
for (var t = 0; t < targetBoxes.length; t++) {
if (originalTargetSizes[t]) {
tween(targetBoxes[t], {
scaleX: originalTargetSizes[t].scaleX,
scaleY: originalTargetSizes[t].scaleY
}, {
duration: 500,
easing: tween.easeOut
});
}
}
if (activePowerUpType === 'shrinkingTargets') {
hidePowerUpDisplay();
}
}
}
// Handle laser beam collision with notes
if (laserBeamActive && laserBeamGraphics) {
// Update laser position to follow player horizontally but stay at top
laserBeamGraphics.x = player.x;
laserBeamGraphics.y = 0;
// Check laser collision with all notes
for (var l = notes.length - 1; l >= 0; l--) {
var note = notes[l];
if (Math.abs(note.x - laserBeamGraphics.x) < 50 && note.y < player.y) {
// Laser hit this note
var laserPoints = note.pointValue;
if (scoreMultiplierActive) {
laserPoints *= 2;
}
score += laserPoints;
createFeedbackText('+' + laserPoints, note.x, note.y - 50, 0x00FFFF);
createParticleBurst(note.x, note.y, 0x00FFFF, 6);
note.destroy();
notes.splice(l, 1);
}
}
}
// Update power-up display timer and progress bar
if (activePowerUpType && activePowerUpMaxDuration > 0) {
var currentTimer = 0;
if (activePowerUpType === 'multiShot') currentTimer = multiShotTimer;else if (activePowerUpType === 'slowMotion') currentTimer = slowMotionTimer;else if (activePowerUpType === 'scoreMultiplier') currentTimer = scoreMultiplierTimer;else if (activePowerUpType === 'movingTarget') currentTimer = movingTargetTimer;else if (activePowerUpType === 'laserBeam') currentTimer = laserBeamTimer;else if (activePowerUpType === 'reverseControls') currentTimer = reverseControlsTimer;else if (activePowerUpType === 'shrinkingTargets') currentTimer = shrinkingTargetsTimer;else if (activePowerUpType === 'shield') {
// Shield shows number of hits remaining instead of timer
powerUpTimerDisplay.setText('Hits: ' + (maxShieldHits - shieldHits));
powerUpProgressBar.width = 200 * ((maxShieldHits - shieldHits) / maxShieldHits);
} else if (activePowerUpType === 'noteDestroyer') currentTimer = noteDestroyerTimer;
// Update timer text (convert frames to seconds) for timed power-ups
if (activePowerUpType !== 'shield') {
var secondsLeft = Math.ceil(currentTimer / 60);
powerUpTimerDisplay.setText(secondsLeft + 's');
// Update progress bar
var progress = currentTimer / activePowerUpMaxDuration;
powerUpProgressBar.width = 200 * progress;
}
}
// Move target boxes if moving target power-up is active
if (movingTargetActive) {
for (var j = 0; j < targetBoxes.length; j++) {
var targetBox = targetBoxes[j];
var originalX = targetBoxPositions[j];
var offset = Math.sin(LK.ticks * 0.05 + j * 0.5) * 100;
targetBox.x = originalX + offset;
}
}
// Update and check notes
for (var i = notes.length - 1; i >= 0; i--) {
var note = notes[i];
// Apply slow motion effect
if (slowMotionActive) {
note.speed *= 0.5;
}
// Check if note has passed through deletion area
if (note.y > deletionAreaY && !note.hasTriggered) {
// Only trigger game over for regular music notes, not power-ups
if (note.noteType !== 'multiShot' && note.noteType !== 'slowMotion' && note.noteType !== 'scoreMultiplier' && note.noteType !== 'movingTarget' && note.noteType !== 'laserBeam' && note.noteType !== 'shield' && note.noteType !== 'noteDestroyer' && note.noteType !== 'reverseControls' && note.noteType !== 'shrinkingTargets') {
// Note crashed into secret wall - game over
LK.showGameOver();
return; // Exit update loop since game is over
} else {
// Power-up notes just get removed without penalty
note.destroy();
notes.splice(i, 1);
continue;
}
}
// Remove notes that go off screen
if (note.y > 2800) {
note.destroy();
notes.splice(i, 1);
}
// Restore note speed after slow motion effect
if (slowMotionActive) {
note.speed *= 2; // Restore original speed for next frame
}
}
// Update and check spikes
for (var i = spikes.length - 1; i >= 0; i--) {
var spike = spikes[i];
// Apply slow motion effect
if (slowMotionActive) {
spike.speed *= 0.5;
}
// Check collision with player - game over if spike hits player (unless shield is active)
if (spike.intersects(player)) {
if (shieldActive && shieldHits < maxShieldHits) {
// Shield absorbs the hit
shieldHits++;
createFeedbackText('Shield Hit!', player.x, player.y - 100, 0x00FFFF);
createParticleBurst(spike.x, spike.y, 0x00FFFF, 10);
LK.effects.flashObject(player, 0x00FFFF, 500);
// Remove the spike
spike.destroy();
spikes.splice(i, 1);
// Deactivate shield if max hits reached
if (shieldHits >= maxShieldHits) {
shieldActive = false;
if (shieldGraphics) {
tween(shieldGraphics, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
shieldGraphics.destroy();
shieldGraphics = null;
}
});
}
if (activePowerUpType === 'shield') {
hidePowerUpDisplay();
}
createFeedbackText('Shield Depleted!', player.x, player.y - 120, 0xFF0000);
}
continue;
} else {
// No shield or shield depleted - game over
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return; // Exit update loop since game is over
}
}
// Spikes can pass through the secret line - no collision check needed
// Remove spikes that go off screen
if (spike.y > 2800) {
spike.destroy();
spikes.splice(i, 1);
}
// Restore spike speed after slow motion effect
if (slowMotionActive) {
spike.speed *= 2; // Restore original speed for next frame
}
}
// Handle music glitch timer
if (musicGlitchTimer > 0) {
musicGlitchTimer--;
if (musicGlitchTimer === 0 && musicPlaying) {
// Resume music after glitch period
try {
LK.playMusic(currentMusicTrack);
} catch (e) {
console.log('Error resuming music after glitch:', currentMusicTrack);
}
}
}
// Increase difficulty over time
if (LK.ticks % 1800 === 0) {
// Every 30 seconds
noteSpawnInterval = Math.max(30, noteSpawnInterval - 5);
// Increase note speed slightly
for (var i = 0; i < notes.length; i++) {
notes[i].speed = Math.min(6, notes[i].speed + 0.1);
}
}
};
Music Note. In-Game asset. 2d. High contrast. No shadows
a circle empty inside, at the edges there is rainbow curly things. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A simple blue cartoon cat standing upright, no accessories or effects, clean and minimal style, no musical notes or particles, not too stylish, designed as the main character for a rhythm game with a cold, minimal theme, light outlines and smooth shading, no background. In-Game asset. 2d. High contrast. No shadows
A dumb looking flat fish like a sardine looking up, ice blue color.. In-Game asset. 2d. High contrast. No shadows
A simple ice blue fishbone icon, clean vector style, no background, minimal design, symmetrical and centered, soft lines, suitable for a 2D rhythm game UI element. In-Game asset. 2d. High contrast. No shadows
Ice blue with a bright color stroke music note. In-Game asset. 2d. High contrast. No shadows
Ice blue with a dark color stroke music notes (note must be simetric).. In-Game asset. 2d. High contrast. No shadows
Good Power Up Green color. In-Game asset. 2d. High contrast. No shadows
Red Color, delete "GOOD" Write "BAD"
Red Spike "Music Note" shape. In-Game asset. 2d. High contrast. No shadows
circle shape, empty inside, transparent , stroke is navy blue.. In-Game asset. 2d. High contrast. No shadows
A huge Laser beam, red. In-Game asset. 2d. High contrast. No shadows