/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
var facekit = LK.import("@upit/facekit.v1");
/****
* Classes
****/
var BackgroundGrid = Container.expand(function () {
var self = Container.call(this);
// Create horizontal grid lines
self.horizontalLines = [];
for (var i = 0; i < 15; i++) {
var line = self.attachAsset('gridLine', {
anchorX: 0,
anchorY: 0.5,
alpha: 0.8
});
line.y = i * 200 - 400;
line.tint = 0x00FFFF; // Start with bright cyan
self.horizontalLines.push(line);
}
// Create vertical grid lines
self.verticalLines = [];
for (var j = 0; j < 8; j++) {
var vLine = self.attachAsset('gridLineVertical', {
anchorX: 0.5,
anchorY: 0,
alpha: 0.7
});
vLine.x = j * 300;
vLine.tint = 0xFF00FF; // Start with bright magenta
self.verticalLines.push(vLine);
}
self.update = function () {
// Move horizontal lines down
for (var i = 0; i < self.horizontalLines.length; i++) {
var line = self.horizontalLines[i];
line.y += 2;
// Reset line position when it goes off screen
if (line.y > 2800) {
line.y = -100;
}
// Enhanced neon glow effect - brighter and more vibrant
var distanceFromCenter = Math.abs(line.y - 1366);
var maxDistance = 1366;
var baseBrightness = 0.8; // Much brighter base
var fadeEffect = 1 - distanceFromCenter / maxDistance;
line.alpha = Math.max(0.4, baseBrightness * fadeEffect); // Minimum brightness of 0.4
// Add pulsing neon colors
var pulsePhase = (LK.ticks + i * 10) * 0.05;
var pulseBrightness = 0.8 + 0.4 * Math.sin(pulsePhase);
line.alpha = line.alpha * pulseBrightness;
// Cycle through neon colors
var colorPhase = (LK.ticks * 0.01 + i * 0.2) % (Math.PI * 2);
if (colorPhase < Math.PI * 0.66) {
line.tint = 0x00FFFF; // Bright cyan
} else if (colorPhase < Math.PI * 1.33) {
line.tint = 0xFF00FF; // Bright magenta
} else {
line.tint = 0xFFFF00; // Bright yellow
}
}
// Animate vertical lines with neon glow
for (var j = 0; j < self.verticalLines.length; j++) {
var vLine = self.verticalLines[j];
// Enhanced brightness for vertical lines
vLine.alpha = 0.7 + 0.3 * Math.sin((LK.ticks + j * 15) * 0.03);
// Color cycling for vertical lines
var vColorPhase = (LK.ticks * 0.008 + j * 0.3) % (Math.PI * 2);
if (vColorPhase < Math.PI * 0.5) {
vLine.tint = 0x00FFFF; // Cyan
} else if (vColorPhase < Math.PI) {
vLine.tint = 0xFF0066; // Hot pink
} else if (vColorPhase < Math.PI * 1.5) {
vLine.tint = 0x66FF00; // Electric lime
} else {
vLine.tint = 0xFF6600; // Electric orange
}
}
};
return self;
});
var Block = Container.expand(function () {
var self = Container.call(this);
// Array of available block assets
var blockAssets = ['block1', 'block2', 'block3', 'block4'];
// Randomly select a block asset
var selectedAsset = blockAssets[Math.floor(Math.random() * blockAssets.length)];
var blockGraphics = self.attachAsset(selectedAsset, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
self.speed = 8;
self.lane = 0;
self.caught = false;
// Random neon colors
var colors = [0xff00ff, 0x00ffff, 0xffff00, 0xff0066, 0x66ff00];
blockGraphics.tint = colors[Math.floor(Math.random() * colors.length)];
self.update = function () {
self.y += self.speed;
};
return self;
});
var BombBlock = Container.expand(function () {
var self = Container.call(this);
var bombGraphics = self.attachAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
self.speed = 8;
self.lane = 0;
self.caught = false;
// Distinctive red color for danger
bombGraphics.tint = 0xff0000;
// Add warning pulsing animation to make it stand out as dangerous
tween(bombGraphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(bombGraphics, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart the pulsing animation
if (!self.caught) {
tween(bombGraphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.easeInOut
});
}
}
});
}
});
self.update = function () {
self.y += self.speed;
};
return self;
});
var BoosterBlock = Container.expand(function () {
var self = Container.call(this);
var boosterGraphics = self.attachAsset('booster', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
self.speed = 8;
self.lane = 0;
self.caught = false;
// Distinctive golden/yellow color for booster
boosterGraphics.tint = 0xFFD700;
// Add pulsing animation to make it stand out
tween(boosterGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(boosterGraphics, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart the pulsing animation
if (!self.caught) {
tween(boosterGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 500,
easing: tween.easeInOut
});
}
}
});
}
});
self.update = function () {
self.y += self.speed;
};
return self;
});
var Life = Container.expand(function () {
var self = Container.call(this);
var heartGraphics = self.attachAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
// Add neon glow effect with retrowave pink
heartGraphics.tint = 0xff0066;
self.setActive = function (active) {
if (active) {
heartGraphics.alpha = 1.0;
heartGraphics.tint = 0xff0066; // Bright neon pink
} else {
heartGraphics.alpha = 0.3;
heartGraphics.tint = 0x440022; // Dim dark pink
}
};
return self;
});
var MicrophoneIcon = Container.expand(function () {
var self = Container.call(this);
// Create microphone icon using a shape asset
var micGraphics = self.attachAsset('micBar', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 1.5
});
// Style the microphone with synthwave neon colors
micGraphics.tint = 0x00ffcc;
micGraphics.alpha = 0.6;
// Audio detection state
self.isActive = false;
self.lastVolume = 0;
self.update = function () {
if (gameMode === 'microphone') {
var currentVolume = facekit.volume;
// Update visual state based on audio levels
if (currentVolume > 0.3) {
// Active state - bright glow
if (!self.isActive) {
self.isActive = true;
tween(micGraphics, {
tint: 0x00ff66,
alpha: 1.0,
scaleX: 2.4,
scaleY: 1.8
}, {
duration: 100,
easing: tween.easeOut
});
}
// Beat detection - extra bright flash
if (detectBeat()) {
tween(micGraphics, {
tint: 0xffffff,
scaleX: 3.0,
scaleY: 2.2
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(micGraphics, {
tint: 0x00ff66,
scaleX: 2.4,
scaleY: 1.8
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
} else if (currentVolume > 0.1) {
// Medium activity - moderate glow
if (self.isActive) {
tween(micGraphics, {
tint: 0x00ffcc,
alpha: 0.8,
scaleX: 2.2,
scaleY: 1.6
}, {
duration: 200,
easing: tween.easeInOut
});
}
self.isActive = false;
} else {
// Inactive state - dim glow with subtle pulsing
if (self.isActive) {
self.isActive = false;
tween(micGraphics, {
tint: 0x004466,
alpha: 0.5,
scaleX: 2.0,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut
});
}
// Subtle pulsing to show mic is available
var pulsePhase = LK.ticks * 0.02;
micGraphics.alpha = 0.4 + 0.2 * Math.sin(pulsePhase);
}
self.lastVolume = currentVolume;
}
};
return self;
});
var Ship = Container.expand(function (shipType) {
var self = Container.call(this);
// Use the provided shipType or default to classicShip
var assetType = shipType || 'classicShip';
var shipGraphics = self.attachAsset(assetType, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.0,
scaleY: 3.0
});
// Add glow effect
shipGraphics.filters = [];
self.currentLane = 1; // 0 = left, 1 = center, 2 = right
self.targetX = 1024; // center position
self.update = function () {
// Smooth movement to target lane
var diff = self.targetX - self.x;
if (Math.abs(diff) > 2) {
self.x += diff * 0.15;
} else {
self.x = self.targetX;
}
};
self.moveToLane = function (lane) {
self.currentLane = lane;
if (lane === 0) {
self.targetX = 341; // left lane
} else if (lane === 1) {
self.targetX = 1024; // center lane
} else if (lane === 2) {
self.targetX = 1707; // right lane
}
// Add movement animation
tween(shipGraphics, {
scaleX: 3.6,
scaleY: 2.4
}, {
duration: 100,
onFinish: function onFinish() {
tween(shipGraphics, {
scaleX: 3.0,
scaleY: 3.0
}, {
duration: 100
});
}
});
};
return self;
});
var UnlockableShip = Container.expand(function (shipData) {
var self = Container.call(this);
// Use ship data to create the ship graphics
var shipGraphics = self.attachAsset(shipData.assetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
});
// Apply the ship's unique color
shipGraphics.tint = shipData.color;
// Store ship properties
self.shipData = shipData;
self.extraLives = shipData.extraLives;
// Add glow effect for unlocked ships
self.addGlowEffect = function () {
tween(shipGraphics, {
alpha: 1.2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(shipGraphics, {
alpha: 0.8
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
self.addGlowEffect();
}
});
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a0a0a
});
/****
* Game Code
****/
// Create synthwave background with gradient effect
game.setBackgroundColor(0x270050);
// Create moving background grid
var backgroundGrid = game.addChild(new BackgroundGrid());
// Array of synthwave tracks for classic mode
var synthwaveTracks = ['synthwave1', 'synthwave2', 'synthwave3', 'lofitrack', 'retrowave'];
// Ship unlocking system
var unlockedShips = storage.unlockedShips || [];
var availableShipTypes = ['ship1', 'ship2', 'ship3', 'ship4', 'ship5'];
var shipColors = [0xff0066, 0x00ffcc, 0xffcc00, 0x66ff00, 0xcc00ff];
var shipNames = ['Neon Striker', 'Cyber Cruiser', 'Solar Surfer', 'Plasma Rider', 'Void Walker'];
// Generate random ship data
function generateRandomShip() {
var randomIndex = Math.floor(Math.random() * availableShipTypes.length);
var baseColor = shipColors[randomIndex];
// Add some randomization to the color
var colorVariation = Math.floor(Math.random() * 0x333333);
var finalColor = baseColor + colorVariation & 0xffffff;
return {
id: 'ship_' + Date.now() + '_' + Math.floor(Math.random() * 1000),
assetId: availableShipTypes[randomIndex],
color: finalColor,
name: shipNames[randomIndex] + ' MK-' + (Math.floor(Math.random() * 9) + 1),
extraLives: 1,
unlocked: true
};
}
// Current selected ship
var currentShipData = null;
var currentTrackIndex = 0; // Index of currently playing track
var completedTracks = []; // Array to track which tracks have been completed
var musicStartTime = 0; // Time when current music track started
var trackDuration = 60000; // Approximate track duration in milliseconds (60 seconds)
// Game state
var gameMode = 'menu'; // 'menu', 'classic', 'microphone'
var ship;
var blocks = [];
var score = 0;
var combo = 0;
var bestCombo = 0;
var lives = 3;
var maxLives = 3;
var lifeDisplays = [];
var lastSpawnTime = 0;
var spawnInterval = 1000; // 1 second
var gameSpeed = 1.0; // Speed multiplier for the game
var baseBlockSpeed = 8; // Base speed for blocks
var currentMusicTrack = null; // Track current playing music
// Beat detection variables
var audioBuffer = [];
var beatThreshold = 0.4;
var lastBeatTime = 0;
var minBeatInterval = 200; // Minimum ms between beats
var volumeHistory = [];
var bufferSize = 10;
var beatSensitivity = 1.5;
// Lane positions - equal width lanes
var lanePositions = [341, 1024, 1707];
// UI Elements
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0x00FFFF
});
scoreTxt.anchor.set(0, 0);
scoreTxt.x = 50;
scoreTxt.y = 50;
LK.gui.topLeft.addChild(scoreTxt);
var comboTxt = new Text2('Combo: 0', {
size: 50,
fill: 0xFF00FF
});
comboTxt.anchor.set(0, 0);
comboTxt.x = 50;
comboTxt.y = 120;
LK.gui.topLeft.addChild(comboTxt);
// Create life display hearts in top right
for (var lifeIndex = 0; lifeIndex < maxLives; lifeIndex++) {
var lifeHeart = new Life();
lifeHeart.x = -80 - lifeIndex * 80; // Position from right edge
lifeHeart.y = 60;
lifeHeart.setActive(true);
lifeDisplays.push(lifeHeart);
LK.gui.topRight.addChild(lifeHeart);
}
// Create menu window frame with neon border
var menuFrameBorder = LK.getAsset('menuFrameBorder', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6
});
menuFrameBorder.x = 0;
menuFrameBorder.y = -100;
LK.gui.center.addChild(menuFrameBorder);
// Create semi-transparent background panel
var menuFrame = LK.getAsset('menuFrame', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
menuFrame.x = 0;
menuFrame.y = -100;
LK.gui.center.addChild(menuFrame);
// Add pulsing animation to border
tween(menuFrameBorder, {
alpha: 0.8
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(menuFrameBorder, {
alpha: 0.4
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart the pulsing animation
tween(menuFrameBorder, {
alpha: 0.8
}, {
duration: 1000,
easing: tween.easeInOut
});
}
});
}
});
// Create mode selection buttons with neon glow outline
var classicButton = new Text2('1. CLASSIC MODE\nBuilt-in synthwave music', {
size: 60,
fill: 0x00FFFF,
stroke: 0x00FFFF,
strokeThickness: 3
});
classicButton.anchor.set(0.5, 0.5);
classicButton.x = 0;
classicButton.y = -200;
LK.gui.center.addChild(classicButton);
var micButton = new Text2('2. MICROPHONE MODE\nSync with your music', {
size: 60,
fill: 0xFF00FF,
stroke: 0xFF00FF,
strokeThickness: 3
});
micButton.anchor.set(0.5, 0.5);
micButton.x = 0;
micButton.y = -50;
LK.gui.center.addChild(micButton);
var instructionTxt = new Text2('TAP A BUTTON TO SELECT MODE', {
size: 50,
fill: 0xFFFFFF
});
instructionTxt.anchor.set(0.5, 0.5);
instructionTxt.y = 150;
LK.gui.center.addChild(instructionTxt);
// Ship unlock UI elements (initially hidden)
var unlockFrameBorder = LK.getAsset('unlockBorder', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
unlockFrameBorder.x = 0;
unlockFrameBorder.y = 0;
LK.gui.center.addChild(unlockFrameBorder);
var unlockFrame = LK.getAsset('unlockFrame', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
unlockFrame.x = 0;
unlockFrame.y = 0;
LK.gui.center.addChild(unlockFrame);
var unlockTitleTxt = new Text2('SHIP UNLOCKED!', {
size: 60,
fill: 0x00FFFF
});
unlockTitleTxt.anchor.set(0.5, 0.5);
unlockTitleTxt.x = 0;
unlockTitleTxt.y = -120;
unlockTitleTxt.alpha = 0;
LK.gui.center.addChild(unlockTitleTxt);
var unlockDescTxt = new Text2('', {
size: 40,
fill: 0xFFFFFF
});
unlockDescTxt.anchor.set(0.5, 0.5);
unlockDescTxt.x = 0;
unlockDescTxt.y = -60;
unlockDescTxt.alpha = 0;
LK.gui.center.addChild(unlockDescTxt);
var unlockShipDisplay = null;
var unlockContinueTxt = new Text2('TAP TO CONTINUE', {
size: 50,
fill: 0xFF00FF
});
unlockContinueTxt.anchor.set(0.5, 0.5);
unlockContinueTxt.x = 0;
unlockContinueTxt.y = 120;
unlockContinueTxt.alpha = 0;
LK.gui.center.addChild(unlockContinueTxt);
// Create microphone icon (initially hidden)
var microphoneIcon = new MicrophoneIcon();
microphoneIcon.x = -80;
microphoneIcon.y = 200;
microphoneIcon.visible = false;
LK.gui.topRight.addChild(microphoneIcon);
// Create camera disable button
var cameraButton = new Text2('CAM', {
size: 40,
fill: 0x00ffff,
stroke: 0x00ffff,
strokeThickness: 2
});
cameraButton.anchor.set(0.5, 0.5);
cameraButton.x = -80;
cameraButton.y = 300;
cameraButton.visible = false;
LK.gui.topRight.addChild(cameraButton);
// Camera state tracking
var cameraEnabled = true;
// Create lane dividers
var lane1 = game.addChild(LK.getAsset('lane1', {
x: 682,
y: 0
}));
var lane2 = game.addChild(LK.getAsset('lane2', {
x: 1366,
y: 0
}));
// Create ship
ship = game.addChild(new Ship());
ship.x = 1024;
ship.y = 2400;
function startGame(mode) {
gameMode = mode;
score = 0;
combo = 0;
bestCombo = 0;
// Calculate lives based on current ship
var baseLives = 3;
var extraLives = currentShipData ? currentShipData.extraLives : 0;
lives = baseLives + extraLives;
maxLives = lives;
blocks = [];
lastSpawnTime = 0;
gameSpeed = 1.0; // Reset game speed
currentTrackIndex = 0; // Reset track progression
completedTracks = []; // Reset completed tracks
musicStartTime = 0; // Reset music timer
// Reset and update life display for new max lives
// Remove existing life displays
for (var i = 0; i < lifeDisplays.length; i++) {
lifeDisplays[i].destroy();
}
lifeDisplays = [];
// Create new life displays based on current max lives
for (var lifeIndex = 0; lifeIndex < maxLives; lifeIndex++) {
var lifeHeart = new Life();
lifeHeart.x = -80 - lifeIndex * 80;
lifeHeart.y = 60;
lifeHeart.setActive(true);
lifeDisplays.push(lifeHeart);
LK.gui.topRight.addChild(lifeHeart);
}
// Reset beat detection variables
audioBuffer = [];
volumeHistory = [];
lastBeatTime = 0;
// Hide menu elements
classicButton.visible = false;
micButton.visible = false;
instructionTxt.visible = false;
menuFrame.visible = false;
menuFrameBorder.visible = false;
// Destroy existing ship and create new one with appropriate asset
if (ship) {
ship.destroy();
}
// Create ship with mode-specific asset
if (mode === 'classic') {
ship = game.addChild(new Ship('classicShip'));
} else {
ship = game.addChild(new Ship('microphoneShip'));
}
ship.x = 1024;
ship.y = 2400;
// Start background music only in classic mode
if (mode === 'classic') {
// Shuffle the playlist for randomized track order
for (var i = synthwaveTracks.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = synthwaveTracks[i];
synthwaveTracks[i] = synthwaveTracks[j];
synthwaveTracks[j] = temp;
}
// Start with first track in shuffled sequence
currentMusicTrack = synthwaveTracks[currentTrackIndex];
LK.playMusic(currentMusicTrack);
musicStartTime = Date.now(); // Track when music started using current time
// Hide microphone icon and camera button in classic mode
microphoneIcon.visible = false;
cameraButton.visible = false;
} else {
// Stop background music in microphone mode to focus on external audio
currentMusicTrack = null;
LK.stopMusic();
// Facekit plugin automatically handles microphone access when volume is accessed
// No initialization needed - just use facekit.volume for beat detection
// Show microphone icon and camera button in microphone mode
microphoneIcon.visible = true;
cameraButton.visible = true;
// Reset camera state when starting microphone mode
cameraEnabled = true;
cameraButton.setText('CAM');
cameraButton.fill = 0x00ffff;
cameraButton.stroke = 0x00ffff;
}
updateUI();
}
function spawnBlock() {
var block;
var random = Math.random();
// 10% chance to spawn a bomb block
if (random < 0.10) {
block = new BombBlock();
// 15% chance to spawn a booster block
} else if (random < 0.25) {
block = new BoosterBlock();
} else {
block = new Block();
}
var lane;
if (gameMode === 'microphone') {
// Create more musical patterns based on audio intensity
var intensity = facekit.volume;
if (intensity > 0.8) {
// High intensity - spawn in multiple lanes or center
lane = Math.random() > 0.5 ? 1 : Math.floor(Math.random() * 3);
} else if (intensity > 0.5) {
// Medium intensity - prefer outer lanes
lane = Math.random() > 0.5 ? 0 : 2;
} else {
// Low intensity - single random lane
lane = Math.floor(Math.random() * 3);
}
} else {
// Classic mode - random lane
lane = Math.floor(Math.random() * 3);
}
block.lane = lane;
block.x = lanePositions[lane];
block.y = -50;
block.speed = baseBlockSpeed * gameSpeed; // Apply game speed multiplier
blocks.push(block);
game.addChild(block);
}
function updateUI() {
scoreTxt.setText('Score: ' + score);
comboTxt.setText('Combo: ' + combo + ' (Best: ' + bestCombo + ')');
}
function checkCollisions() {
for (var i = blocks.length - 1; i >= 0; i--) {
var block = blocks[i];
// Check if block is in ship's catch zone
if (!block.caught && block.y >= ship.y - 100 && block.y <= ship.y + 100) {
if (block.lane === ship.currentLane) {
// Block caught!
block.caught = true;
// Check if it's a bomb block
if (block instanceof BombBlock) {
// Bomb effects - lose 2 lives and reset combo
lives -= 2;
combo = 0;
// Update life display for lost lives
for (var lifeIdx = Math.max(0, lives); lifeIdx < Math.min(lives + 2, lifeDisplays.length); lifeIdx++) {
lifeDisplays[lifeIdx].setActive(false);
// Add dramatic life loss animation - capture lifeIdx in closure
(function (capturedIdx) {
tween(lifeDisplays[capturedIdx], {
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 300,
onFinish: function onFinish() {
tween(lifeDisplays[capturedIdx], {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300
});
}
});
})(lifeIdx);
}
// Dramatic visual feedback for bomb
LK.effects.flashScreen(0xff0000, 800);
LK.effects.flashObject(ship, 0xff0000, 800);
// Play miss sound for bomb hit
LK.getSound('miss').play();
// Check for game over
if (lives <= 0) {
// Unlock a new ship before showing game over
var newShip = unlockNewShip();
currentShipData = newShip;
// Delay game over to show ship unlock
LK.setTimeout(function () {
LK.showGameOver();
}, 3000);
return;
}
// Check if it's a booster block
} else if (block instanceof BoosterBlock) {
// Booster effects
score += 25 + combo * 3; // More points for booster
combo += 2; // Extra combo bonus
// Increase game speed (cap at 2.5x)
gameSpeed = Math.min(gameSpeed + 0.2, 2.5);
// Apply speed to all existing blocks
for (var j = 0; j < blocks.length; j++) {
blocks[j].speed = baseBlockSpeed * gameSpeed;
}
// Restart music with faster tempo (only in classic mode)
if (gameMode === 'classic' && currentMusicTrack) {
LK.stopMusic();
// Small delay before restarting music to avoid audio glitches
LK.setTimeout(function () {
LK.playMusic(currentMusicTrack);
}, 50);
}
// Special visual feedback for booster
LK.effects.flashScreen(0xFFD700, 300);
LK.effects.flashObject(ship, 0xFFD700, 500);
// Play layered synthwave sounds for booster
LK.getSound('synthBass').play();
LK.setTimeout(function () {
LK.getSound('synthLead').play();
}, 100);
} else {
// Regular block
score += 10 + combo * 2;
combo++;
}
if (combo > bestCombo) {
bestCombo = combo;
}
// Visual feedback
LK.effects.flashObject(block, 0xffffff, 200);
tween(block, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 200
});
// Play musical sound that matches the current track
var trackSounds = {
'synthwave1': ['synthBass', 'synthLead', 'synthDrum', 'synthChord'],
'synthwave2': ['synthLead', 'synthChord', 'synthBass', 'synthDrum'],
'synthwave3': ['synthDrum', 'synthBass', 'synthLead', 'synthChord'],
'lofitrack': ['synthBass', 'synthChord'],
'retrowave': ['synthLead', 'synthDrum', 'synthChord']
};
var soundsForTrack = trackSounds[currentMusicTrack] || ['synthBass', 'synthLead', 'synthDrum', 'synthChord'];
// Calculate beat position based on music timing to stay in rhythm
var baseBPM = 120;
var currentBPM = baseBPM * gameSpeed;
var beatLength = 60000 / currentBPM; // Beat duration in ms
var timeElapsed = Date.now() - musicStartTime;
var beatPosition = Math.floor(timeElapsed / beatLength % soundsForTrack.length);
var rhythmicSound = soundsForTrack[beatPosition];
LK.getSound(rhythmicSound).play();
updateUI();
// Remove block
block.destroy();
blocks.splice(i, 1);
continue;
}
}
// Check if block missed (passed ship)
if (!block.caught && block.y > ship.y + 150) {
if (block.lane === ship.currentLane) {
// Block missed - lose life and reset combo
lives--;
combo = 0;
// Update life display
if (lives >= 0 && lives < lifeDisplays.length) {
var currentLifeIndex = lives; // Capture current lives value
lifeDisplays[currentLifeIndex].setActive(false);
// Add life loss animation
tween(lifeDisplays[currentLifeIndex], {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
onFinish: function onFinish() {
tween(lifeDisplays[currentLifeIndex], {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200
});
}
});
}
LK.effects.flashScreen(0xff0000, 300);
LK.getSound('miss').play();
// Check for game over
if (lives <= 0) {
LK.showGameOver();
return;
}
updateUI();
}
block.caught = true; // Mark as processed
}
// Remove blocks that are off screen
if (block.y > 2800) {
block.destroy();
blocks.splice(i, 1);
}
}
}
function showShipUnlock(newShipData) {
// Show unlock frame with animation
tween(unlockFrameBorder, {
alpha: 0.8
}, {
duration: 300
});
tween(unlockFrame, {
alpha: 0.9
}, {
duration: 300
});
tween(unlockTitleTxt, {
alpha: 1
}, {
duration: 300
});
// Update description text
unlockDescTxt.setText(newShipData.name + '\n+1 Extra Life');
tween(unlockDescTxt, {
alpha: 1
}, {
duration: 300
});
// Create and display the unlocked ship
if (unlockShipDisplay) {
unlockShipDisplay.destroy();
}
unlockShipDisplay = new UnlockableShip(newShipData);
unlockShipDisplay.x = 0;
unlockShipDisplay.y = 20;
LK.gui.center.addChild(unlockShipDisplay);
unlockShipDisplay.addGlowEffect();
tween(unlockContinueTxt, {
alpha: 1
}, {
duration: 300
});
// Add pulsing animation to continue text
tween(unlockContinueTxt, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(unlockContinueTxt, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(unlockContinueTxt, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
}
});
}
function hideShipUnlock() {
tween(unlockFrameBorder, {
alpha: 0
}, {
duration: 300
});
tween(unlockFrame, {
alpha: 0
}, {
duration: 300
});
tween(unlockTitleTxt, {
alpha: 0
}, {
duration: 300
});
tween(unlockDescTxt, {
alpha: 0
}, {
duration: 300
});
tween(unlockContinueTxt, {
alpha: 0
}, {
duration: 300
});
if (unlockShipDisplay) {
tween(unlockShipDisplay, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
unlockShipDisplay.destroy();
unlockShipDisplay = null;
}
});
}
}
function unlockNewShip() {
// Generate a new random ship
var newShip = generateRandomShip();
// Add to unlocked ships
unlockedShips.push(newShip);
// Keep only the 5 most recent ships
if (unlockedShips.length > 5) {
unlockedShips = unlockedShips.slice(-5);
}
// Save to storage
storage.unlockedShips = unlockedShips;
// Show unlock animation
showShipUnlock(newShip);
return newShip;
}
function progressToNextTrack() {
if (gameMode !== 'classic') {
return;
}
// Mark current track as completed
if (completedTracks.indexOf(synthwaveTracks[currentTrackIndex]) === -1) {
completedTracks.push(synthwaveTracks[currentTrackIndex]);
}
// Move to next track
currentTrackIndex++;
// Check if all tracks have been completed
if (currentTrackIndex >= synthwaveTracks.length) {
// Unlock a new ship before showing victory
var newShip = unlockNewShip();
currentShipData = newShip;
// Delay victory screen to show ship unlock
LK.setTimeout(function () {
LK.showYouWin();
}, 3000);
return;
}
// Play next track
currentMusicTrack = synthwaveTracks[currentTrackIndex];
LK.playMusic(currentMusicTrack);
musicStartTime = Date.now(); // Reset music timer using current time
}
function detectBeat() {
var currentVolume = facekit.volume;
var currentTime = Date.now();
// Add current volume to history buffer
volumeHistory.push(currentVolume);
if (volumeHistory.length > bufferSize) {
volumeHistory.shift();
}
// Calculate average volume from buffer
var avgVolume = 0;
for (var i = 0; i < volumeHistory.length; i++) {
avgVolume += volumeHistory[i];
}
avgVolume = avgVolume / volumeHistory.length;
// Detect beat: current volume significantly higher than average
var volumeSpike = currentVolume > avgVolume * beatSensitivity;
var timeSinceLastBeat = currentTime - lastBeatTime;
var beatDetected = volumeSpike && currentVolume > beatThreshold && timeSinceLastBeat > minBeatInterval;
if (beatDetected) {
lastBeatTime = currentTime;
// Add visual feedback for beat detection
LK.effects.flashScreen(0x440088, 100);
return true;
}
return false;
}
function shouldSpawnBlock() {
if (gameMode === 'microphone') {
// Spawn based on beat detection from microphone input
return detectBeat();
} else {
// Classic mode - spawn every second
return LK.ticks - lastSpawnTime >= spawnInterval / (1000 / 60);
}
}
// Button event handlers
classicButton.down = function (x, y, obj) {
if (gameMode === 'menu') {
// Add button press animation
tween(classicButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 100,
onFinish: function onFinish() {
tween(classicButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
}
});
startGame('classic');
}
};
micButton.down = function (x, y, obj) {
if (gameMode === 'menu') {
// Add button press animation
tween(micButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 100,
onFinish: function onFinish() {
tween(micButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
}
});
startGame('microphone');
}
};
// Camera button event handler
cameraButton.down = function (x, y, obj) {
if (gameMode === 'microphone') {
// Toggle camera state
cameraEnabled = !cameraEnabled;
// Update button appearance based on state
if (cameraEnabled) {
cameraButton.setText('CAM');
cameraButton.fill = 0x00ffff;
cameraButton.stroke = 0x00ffff;
} else {
cameraButton.setText('CAM\nOFF');
cameraButton.fill = 0xff0066;
cameraButton.stroke = 0xff0066;
}
// Add button press animation
tween(cameraButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
onFinish: function onFinish() {
tween(cameraButton, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100
});
}
});
// Visual feedback
LK.effects.flashObject(cameraButton, 0xffffff, 200);
}
};
// Touch controls for lane switching during gameplay
game.down = function (x, y, obj) {
if (gameMode === 'menu') {
return;
}
// Check if unlock screen is visible
if (unlockFrame.alpha > 0) {
hideShipUnlock();
return;
}
// Lane switching
if (x < 1024) {
// Left side tapped - move left
if (ship.currentLane > 0) {
ship.moveToLane(ship.currentLane - 1);
}
} else {
// Right side tapped - move right
if (ship.currentLane < 2) {
ship.moveToLane(ship.currentLane + 1);
}
}
};
// Main game update
game.update = function () {
if (gameMode === 'menu') {
// Hide microphone icon and camera button in menu
microphoneIcon.visible = false;
cameraButton.visible = false;
return;
}
// Check for track progression in classic mode
if (gameMode === 'classic' && musicStartTime > 0) {
var currentTime = Date.now();
var timeElapsed = currentTime - musicStartTime;
// Check if current track duration has been reached
if (timeElapsed >= trackDuration) {
progressToNextTrack();
}
}
// Spawn blocks
if (shouldSpawnBlock()) {
spawnBlock();
lastSpawnTime = LK.ticks;
}
// Check collisions
checkCollisions();
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
var facekit = LK.import("@upit/facekit.v1");
/****
* Classes
****/
var BackgroundGrid = Container.expand(function () {
var self = Container.call(this);
// Create horizontal grid lines
self.horizontalLines = [];
for (var i = 0; i < 15; i++) {
var line = self.attachAsset('gridLine', {
anchorX: 0,
anchorY: 0.5,
alpha: 0.8
});
line.y = i * 200 - 400;
line.tint = 0x00FFFF; // Start with bright cyan
self.horizontalLines.push(line);
}
// Create vertical grid lines
self.verticalLines = [];
for (var j = 0; j < 8; j++) {
var vLine = self.attachAsset('gridLineVertical', {
anchorX: 0.5,
anchorY: 0,
alpha: 0.7
});
vLine.x = j * 300;
vLine.tint = 0xFF00FF; // Start with bright magenta
self.verticalLines.push(vLine);
}
self.update = function () {
// Move horizontal lines down
for (var i = 0; i < self.horizontalLines.length; i++) {
var line = self.horizontalLines[i];
line.y += 2;
// Reset line position when it goes off screen
if (line.y > 2800) {
line.y = -100;
}
// Enhanced neon glow effect - brighter and more vibrant
var distanceFromCenter = Math.abs(line.y - 1366);
var maxDistance = 1366;
var baseBrightness = 0.8; // Much brighter base
var fadeEffect = 1 - distanceFromCenter / maxDistance;
line.alpha = Math.max(0.4, baseBrightness * fadeEffect); // Minimum brightness of 0.4
// Add pulsing neon colors
var pulsePhase = (LK.ticks + i * 10) * 0.05;
var pulseBrightness = 0.8 + 0.4 * Math.sin(pulsePhase);
line.alpha = line.alpha * pulseBrightness;
// Cycle through neon colors
var colorPhase = (LK.ticks * 0.01 + i * 0.2) % (Math.PI * 2);
if (colorPhase < Math.PI * 0.66) {
line.tint = 0x00FFFF; // Bright cyan
} else if (colorPhase < Math.PI * 1.33) {
line.tint = 0xFF00FF; // Bright magenta
} else {
line.tint = 0xFFFF00; // Bright yellow
}
}
// Animate vertical lines with neon glow
for (var j = 0; j < self.verticalLines.length; j++) {
var vLine = self.verticalLines[j];
// Enhanced brightness for vertical lines
vLine.alpha = 0.7 + 0.3 * Math.sin((LK.ticks + j * 15) * 0.03);
// Color cycling for vertical lines
var vColorPhase = (LK.ticks * 0.008 + j * 0.3) % (Math.PI * 2);
if (vColorPhase < Math.PI * 0.5) {
vLine.tint = 0x00FFFF; // Cyan
} else if (vColorPhase < Math.PI) {
vLine.tint = 0xFF0066; // Hot pink
} else if (vColorPhase < Math.PI * 1.5) {
vLine.tint = 0x66FF00; // Electric lime
} else {
vLine.tint = 0xFF6600; // Electric orange
}
}
};
return self;
});
var Block = Container.expand(function () {
var self = Container.call(this);
// Array of available block assets
var blockAssets = ['block1', 'block2', 'block3', 'block4'];
// Randomly select a block asset
var selectedAsset = blockAssets[Math.floor(Math.random() * blockAssets.length)];
var blockGraphics = self.attachAsset(selectedAsset, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
self.speed = 8;
self.lane = 0;
self.caught = false;
// Random neon colors
var colors = [0xff00ff, 0x00ffff, 0xffff00, 0xff0066, 0x66ff00];
blockGraphics.tint = colors[Math.floor(Math.random() * colors.length)];
self.update = function () {
self.y += self.speed;
};
return self;
});
var BombBlock = Container.expand(function () {
var self = Container.call(this);
var bombGraphics = self.attachAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
self.speed = 8;
self.lane = 0;
self.caught = false;
// Distinctive red color for danger
bombGraphics.tint = 0xff0000;
// Add warning pulsing animation to make it stand out as dangerous
tween(bombGraphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(bombGraphics, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart the pulsing animation
if (!self.caught) {
tween(bombGraphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.easeInOut
});
}
}
});
}
});
self.update = function () {
self.y += self.speed;
};
return self;
});
var BoosterBlock = Container.expand(function () {
var self = Container.call(this);
var boosterGraphics = self.attachAsset('booster', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
self.speed = 8;
self.lane = 0;
self.caught = false;
// Distinctive golden/yellow color for booster
boosterGraphics.tint = 0xFFD700;
// Add pulsing animation to make it stand out
tween(boosterGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(boosterGraphics, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart the pulsing animation
if (!self.caught) {
tween(boosterGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 500,
easing: tween.easeInOut
});
}
}
});
}
});
self.update = function () {
self.y += self.speed;
};
return self;
});
var Life = Container.expand(function () {
var self = Container.call(this);
var heartGraphics = self.attachAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
// Add neon glow effect with retrowave pink
heartGraphics.tint = 0xff0066;
self.setActive = function (active) {
if (active) {
heartGraphics.alpha = 1.0;
heartGraphics.tint = 0xff0066; // Bright neon pink
} else {
heartGraphics.alpha = 0.3;
heartGraphics.tint = 0x440022; // Dim dark pink
}
};
return self;
});
var MicrophoneIcon = Container.expand(function () {
var self = Container.call(this);
// Create microphone icon using a shape asset
var micGraphics = self.attachAsset('micBar', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 1.5
});
// Style the microphone with synthwave neon colors
micGraphics.tint = 0x00ffcc;
micGraphics.alpha = 0.6;
// Audio detection state
self.isActive = false;
self.lastVolume = 0;
self.update = function () {
if (gameMode === 'microphone') {
var currentVolume = facekit.volume;
// Update visual state based on audio levels
if (currentVolume > 0.3) {
// Active state - bright glow
if (!self.isActive) {
self.isActive = true;
tween(micGraphics, {
tint: 0x00ff66,
alpha: 1.0,
scaleX: 2.4,
scaleY: 1.8
}, {
duration: 100,
easing: tween.easeOut
});
}
// Beat detection - extra bright flash
if (detectBeat()) {
tween(micGraphics, {
tint: 0xffffff,
scaleX: 3.0,
scaleY: 2.2
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(micGraphics, {
tint: 0x00ff66,
scaleX: 2.4,
scaleY: 1.8
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
} else if (currentVolume > 0.1) {
// Medium activity - moderate glow
if (self.isActive) {
tween(micGraphics, {
tint: 0x00ffcc,
alpha: 0.8,
scaleX: 2.2,
scaleY: 1.6
}, {
duration: 200,
easing: tween.easeInOut
});
}
self.isActive = false;
} else {
// Inactive state - dim glow with subtle pulsing
if (self.isActive) {
self.isActive = false;
tween(micGraphics, {
tint: 0x004466,
alpha: 0.5,
scaleX: 2.0,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut
});
}
// Subtle pulsing to show mic is available
var pulsePhase = LK.ticks * 0.02;
micGraphics.alpha = 0.4 + 0.2 * Math.sin(pulsePhase);
}
self.lastVolume = currentVolume;
}
};
return self;
});
var Ship = Container.expand(function (shipType) {
var self = Container.call(this);
// Use the provided shipType or default to classicShip
var assetType = shipType || 'classicShip';
var shipGraphics = self.attachAsset(assetType, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.0,
scaleY: 3.0
});
// Add glow effect
shipGraphics.filters = [];
self.currentLane = 1; // 0 = left, 1 = center, 2 = right
self.targetX = 1024; // center position
self.update = function () {
// Smooth movement to target lane
var diff = self.targetX - self.x;
if (Math.abs(diff) > 2) {
self.x += diff * 0.15;
} else {
self.x = self.targetX;
}
};
self.moveToLane = function (lane) {
self.currentLane = lane;
if (lane === 0) {
self.targetX = 341; // left lane
} else if (lane === 1) {
self.targetX = 1024; // center lane
} else if (lane === 2) {
self.targetX = 1707; // right lane
}
// Add movement animation
tween(shipGraphics, {
scaleX: 3.6,
scaleY: 2.4
}, {
duration: 100,
onFinish: function onFinish() {
tween(shipGraphics, {
scaleX: 3.0,
scaleY: 3.0
}, {
duration: 100
});
}
});
};
return self;
});
var UnlockableShip = Container.expand(function (shipData) {
var self = Container.call(this);
// Use ship data to create the ship graphics
var shipGraphics = self.attachAsset(shipData.assetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
});
// Apply the ship's unique color
shipGraphics.tint = shipData.color;
// Store ship properties
self.shipData = shipData;
self.extraLives = shipData.extraLives;
// Add glow effect for unlocked ships
self.addGlowEffect = function () {
tween(shipGraphics, {
alpha: 1.2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(shipGraphics, {
alpha: 0.8
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
self.addGlowEffect();
}
});
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a0a0a
});
/****
* Game Code
****/
// Create synthwave background with gradient effect
game.setBackgroundColor(0x270050);
// Create moving background grid
var backgroundGrid = game.addChild(new BackgroundGrid());
// Array of synthwave tracks for classic mode
var synthwaveTracks = ['synthwave1', 'synthwave2', 'synthwave3', 'lofitrack', 'retrowave'];
// Ship unlocking system
var unlockedShips = storage.unlockedShips || [];
var availableShipTypes = ['ship1', 'ship2', 'ship3', 'ship4', 'ship5'];
var shipColors = [0xff0066, 0x00ffcc, 0xffcc00, 0x66ff00, 0xcc00ff];
var shipNames = ['Neon Striker', 'Cyber Cruiser', 'Solar Surfer', 'Plasma Rider', 'Void Walker'];
// Generate random ship data
function generateRandomShip() {
var randomIndex = Math.floor(Math.random() * availableShipTypes.length);
var baseColor = shipColors[randomIndex];
// Add some randomization to the color
var colorVariation = Math.floor(Math.random() * 0x333333);
var finalColor = baseColor + colorVariation & 0xffffff;
return {
id: 'ship_' + Date.now() + '_' + Math.floor(Math.random() * 1000),
assetId: availableShipTypes[randomIndex],
color: finalColor,
name: shipNames[randomIndex] + ' MK-' + (Math.floor(Math.random() * 9) + 1),
extraLives: 1,
unlocked: true
};
}
// Current selected ship
var currentShipData = null;
var currentTrackIndex = 0; // Index of currently playing track
var completedTracks = []; // Array to track which tracks have been completed
var musicStartTime = 0; // Time when current music track started
var trackDuration = 60000; // Approximate track duration in milliseconds (60 seconds)
// Game state
var gameMode = 'menu'; // 'menu', 'classic', 'microphone'
var ship;
var blocks = [];
var score = 0;
var combo = 0;
var bestCombo = 0;
var lives = 3;
var maxLives = 3;
var lifeDisplays = [];
var lastSpawnTime = 0;
var spawnInterval = 1000; // 1 second
var gameSpeed = 1.0; // Speed multiplier for the game
var baseBlockSpeed = 8; // Base speed for blocks
var currentMusicTrack = null; // Track current playing music
// Beat detection variables
var audioBuffer = [];
var beatThreshold = 0.4;
var lastBeatTime = 0;
var minBeatInterval = 200; // Minimum ms between beats
var volumeHistory = [];
var bufferSize = 10;
var beatSensitivity = 1.5;
// Lane positions - equal width lanes
var lanePositions = [341, 1024, 1707];
// UI Elements
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0x00FFFF
});
scoreTxt.anchor.set(0, 0);
scoreTxt.x = 50;
scoreTxt.y = 50;
LK.gui.topLeft.addChild(scoreTxt);
var comboTxt = new Text2('Combo: 0', {
size: 50,
fill: 0xFF00FF
});
comboTxt.anchor.set(0, 0);
comboTxt.x = 50;
comboTxt.y = 120;
LK.gui.topLeft.addChild(comboTxt);
// Create life display hearts in top right
for (var lifeIndex = 0; lifeIndex < maxLives; lifeIndex++) {
var lifeHeart = new Life();
lifeHeart.x = -80 - lifeIndex * 80; // Position from right edge
lifeHeart.y = 60;
lifeHeart.setActive(true);
lifeDisplays.push(lifeHeart);
LK.gui.topRight.addChild(lifeHeart);
}
// Create menu window frame with neon border
var menuFrameBorder = LK.getAsset('menuFrameBorder', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6
});
menuFrameBorder.x = 0;
menuFrameBorder.y = -100;
LK.gui.center.addChild(menuFrameBorder);
// Create semi-transparent background panel
var menuFrame = LK.getAsset('menuFrame', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
menuFrame.x = 0;
menuFrame.y = -100;
LK.gui.center.addChild(menuFrame);
// Add pulsing animation to border
tween(menuFrameBorder, {
alpha: 0.8
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(menuFrameBorder, {
alpha: 0.4
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart the pulsing animation
tween(menuFrameBorder, {
alpha: 0.8
}, {
duration: 1000,
easing: tween.easeInOut
});
}
});
}
});
// Create mode selection buttons with neon glow outline
var classicButton = new Text2('1. CLASSIC MODE\nBuilt-in synthwave music', {
size: 60,
fill: 0x00FFFF,
stroke: 0x00FFFF,
strokeThickness: 3
});
classicButton.anchor.set(0.5, 0.5);
classicButton.x = 0;
classicButton.y = -200;
LK.gui.center.addChild(classicButton);
var micButton = new Text2('2. MICROPHONE MODE\nSync with your music', {
size: 60,
fill: 0xFF00FF,
stroke: 0xFF00FF,
strokeThickness: 3
});
micButton.anchor.set(0.5, 0.5);
micButton.x = 0;
micButton.y = -50;
LK.gui.center.addChild(micButton);
var instructionTxt = new Text2('TAP A BUTTON TO SELECT MODE', {
size: 50,
fill: 0xFFFFFF
});
instructionTxt.anchor.set(0.5, 0.5);
instructionTxt.y = 150;
LK.gui.center.addChild(instructionTxt);
// Ship unlock UI elements (initially hidden)
var unlockFrameBorder = LK.getAsset('unlockBorder', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
unlockFrameBorder.x = 0;
unlockFrameBorder.y = 0;
LK.gui.center.addChild(unlockFrameBorder);
var unlockFrame = LK.getAsset('unlockFrame', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
unlockFrame.x = 0;
unlockFrame.y = 0;
LK.gui.center.addChild(unlockFrame);
var unlockTitleTxt = new Text2('SHIP UNLOCKED!', {
size: 60,
fill: 0x00FFFF
});
unlockTitleTxt.anchor.set(0.5, 0.5);
unlockTitleTxt.x = 0;
unlockTitleTxt.y = -120;
unlockTitleTxt.alpha = 0;
LK.gui.center.addChild(unlockTitleTxt);
var unlockDescTxt = new Text2('', {
size: 40,
fill: 0xFFFFFF
});
unlockDescTxt.anchor.set(0.5, 0.5);
unlockDescTxt.x = 0;
unlockDescTxt.y = -60;
unlockDescTxt.alpha = 0;
LK.gui.center.addChild(unlockDescTxt);
var unlockShipDisplay = null;
var unlockContinueTxt = new Text2('TAP TO CONTINUE', {
size: 50,
fill: 0xFF00FF
});
unlockContinueTxt.anchor.set(0.5, 0.5);
unlockContinueTxt.x = 0;
unlockContinueTxt.y = 120;
unlockContinueTxt.alpha = 0;
LK.gui.center.addChild(unlockContinueTxt);
// Create microphone icon (initially hidden)
var microphoneIcon = new MicrophoneIcon();
microphoneIcon.x = -80;
microphoneIcon.y = 200;
microphoneIcon.visible = false;
LK.gui.topRight.addChild(microphoneIcon);
// Create camera disable button
var cameraButton = new Text2('CAM', {
size: 40,
fill: 0x00ffff,
stroke: 0x00ffff,
strokeThickness: 2
});
cameraButton.anchor.set(0.5, 0.5);
cameraButton.x = -80;
cameraButton.y = 300;
cameraButton.visible = false;
LK.gui.topRight.addChild(cameraButton);
// Camera state tracking
var cameraEnabled = true;
// Create lane dividers
var lane1 = game.addChild(LK.getAsset('lane1', {
x: 682,
y: 0
}));
var lane2 = game.addChild(LK.getAsset('lane2', {
x: 1366,
y: 0
}));
// Create ship
ship = game.addChild(new Ship());
ship.x = 1024;
ship.y = 2400;
function startGame(mode) {
gameMode = mode;
score = 0;
combo = 0;
bestCombo = 0;
// Calculate lives based on current ship
var baseLives = 3;
var extraLives = currentShipData ? currentShipData.extraLives : 0;
lives = baseLives + extraLives;
maxLives = lives;
blocks = [];
lastSpawnTime = 0;
gameSpeed = 1.0; // Reset game speed
currentTrackIndex = 0; // Reset track progression
completedTracks = []; // Reset completed tracks
musicStartTime = 0; // Reset music timer
// Reset and update life display for new max lives
// Remove existing life displays
for (var i = 0; i < lifeDisplays.length; i++) {
lifeDisplays[i].destroy();
}
lifeDisplays = [];
// Create new life displays based on current max lives
for (var lifeIndex = 0; lifeIndex < maxLives; lifeIndex++) {
var lifeHeart = new Life();
lifeHeart.x = -80 - lifeIndex * 80;
lifeHeart.y = 60;
lifeHeart.setActive(true);
lifeDisplays.push(lifeHeart);
LK.gui.topRight.addChild(lifeHeart);
}
// Reset beat detection variables
audioBuffer = [];
volumeHistory = [];
lastBeatTime = 0;
// Hide menu elements
classicButton.visible = false;
micButton.visible = false;
instructionTxt.visible = false;
menuFrame.visible = false;
menuFrameBorder.visible = false;
// Destroy existing ship and create new one with appropriate asset
if (ship) {
ship.destroy();
}
// Create ship with mode-specific asset
if (mode === 'classic') {
ship = game.addChild(new Ship('classicShip'));
} else {
ship = game.addChild(new Ship('microphoneShip'));
}
ship.x = 1024;
ship.y = 2400;
// Start background music only in classic mode
if (mode === 'classic') {
// Shuffle the playlist for randomized track order
for (var i = synthwaveTracks.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = synthwaveTracks[i];
synthwaveTracks[i] = synthwaveTracks[j];
synthwaveTracks[j] = temp;
}
// Start with first track in shuffled sequence
currentMusicTrack = synthwaveTracks[currentTrackIndex];
LK.playMusic(currentMusicTrack);
musicStartTime = Date.now(); // Track when music started using current time
// Hide microphone icon and camera button in classic mode
microphoneIcon.visible = false;
cameraButton.visible = false;
} else {
// Stop background music in microphone mode to focus on external audio
currentMusicTrack = null;
LK.stopMusic();
// Facekit plugin automatically handles microphone access when volume is accessed
// No initialization needed - just use facekit.volume for beat detection
// Show microphone icon and camera button in microphone mode
microphoneIcon.visible = true;
cameraButton.visible = true;
// Reset camera state when starting microphone mode
cameraEnabled = true;
cameraButton.setText('CAM');
cameraButton.fill = 0x00ffff;
cameraButton.stroke = 0x00ffff;
}
updateUI();
}
function spawnBlock() {
var block;
var random = Math.random();
// 10% chance to spawn a bomb block
if (random < 0.10) {
block = new BombBlock();
// 15% chance to spawn a booster block
} else if (random < 0.25) {
block = new BoosterBlock();
} else {
block = new Block();
}
var lane;
if (gameMode === 'microphone') {
// Create more musical patterns based on audio intensity
var intensity = facekit.volume;
if (intensity > 0.8) {
// High intensity - spawn in multiple lanes or center
lane = Math.random() > 0.5 ? 1 : Math.floor(Math.random() * 3);
} else if (intensity > 0.5) {
// Medium intensity - prefer outer lanes
lane = Math.random() > 0.5 ? 0 : 2;
} else {
// Low intensity - single random lane
lane = Math.floor(Math.random() * 3);
}
} else {
// Classic mode - random lane
lane = Math.floor(Math.random() * 3);
}
block.lane = lane;
block.x = lanePositions[lane];
block.y = -50;
block.speed = baseBlockSpeed * gameSpeed; // Apply game speed multiplier
blocks.push(block);
game.addChild(block);
}
function updateUI() {
scoreTxt.setText('Score: ' + score);
comboTxt.setText('Combo: ' + combo + ' (Best: ' + bestCombo + ')');
}
function checkCollisions() {
for (var i = blocks.length - 1; i >= 0; i--) {
var block = blocks[i];
// Check if block is in ship's catch zone
if (!block.caught && block.y >= ship.y - 100 && block.y <= ship.y + 100) {
if (block.lane === ship.currentLane) {
// Block caught!
block.caught = true;
// Check if it's a bomb block
if (block instanceof BombBlock) {
// Bomb effects - lose 2 lives and reset combo
lives -= 2;
combo = 0;
// Update life display for lost lives
for (var lifeIdx = Math.max(0, lives); lifeIdx < Math.min(lives + 2, lifeDisplays.length); lifeIdx++) {
lifeDisplays[lifeIdx].setActive(false);
// Add dramatic life loss animation - capture lifeIdx in closure
(function (capturedIdx) {
tween(lifeDisplays[capturedIdx], {
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 300,
onFinish: function onFinish() {
tween(lifeDisplays[capturedIdx], {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300
});
}
});
})(lifeIdx);
}
// Dramatic visual feedback for bomb
LK.effects.flashScreen(0xff0000, 800);
LK.effects.flashObject(ship, 0xff0000, 800);
// Play miss sound for bomb hit
LK.getSound('miss').play();
// Check for game over
if (lives <= 0) {
// Unlock a new ship before showing game over
var newShip = unlockNewShip();
currentShipData = newShip;
// Delay game over to show ship unlock
LK.setTimeout(function () {
LK.showGameOver();
}, 3000);
return;
}
// Check if it's a booster block
} else if (block instanceof BoosterBlock) {
// Booster effects
score += 25 + combo * 3; // More points for booster
combo += 2; // Extra combo bonus
// Increase game speed (cap at 2.5x)
gameSpeed = Math.min(gameSpeed + 0.2, 2.5);
// Apply speed to all existing blocks
for (var j = 0; j < blocks.length; j++) {
blocks[j].speed = baseBlockSpeed * gameSpeed;
}
// Restart music with faster tempo (only in classic mode)
if (gameMode === 'classic' && currentMusicTrack) {
LK.stopMusic();
// Small delay before restarting music to avoid audio glitches
LK.setTimeout(function () {
LK.playMusic(currentMusicTrack);
}, 50);
}
// Special visual feedback for booster
LK.effects.flashScreen(0xFFD700, 300);
LK.effects.flashObject(ship, 0xFFD700, 500);
// Play layered synthwave sounds for booster
LK.getSound('synthBass').play();
LK.setTimeout(function () {
LK.getSound('synthLead').play();
}, 100);
} else {
// Regular block
score += 10 + combo * 2;
combo++;
}
if (combo > bestCombo) {
bestCombo = combo;
}
// Visual feedback
LK.effects.flashObject(block, 0xffffff, 200);
tween(block, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 200
});
// Play musical sound that matches the current track
var trackSounds = {
'synthwave1': ['synthBass', 'synthLead', 'synthDrum', 'synthChord'],
'synthwave2': ['synthLead', 'synthChord', 'synthBass', 'synthDrum'],
'synthwave3': ['synthDrum', 'synthBass', 'synthLead', 'synthChord'],
'lofitrack': ['synthBass', 'synthChord'],
'retrowave': ['synthLead', 'synthDrum', 'synthChord']
};
var soundsForTrack = trackSounds[currentMusicTrack] || ['synthBass', 'synthLead', 'synthDrum', 'synthChord'];
// Calculate beat position based on music timing to stay in rhythm
var baseBPM = 120;
var currentBPM = baseBPM * gameSpeed;
var beatLength = 60000 / currentBPM; // Beat duration in ms
var timeElapsed = Date.now() - musicStartTime;
var beatPosition = Math.floor(timeElapsed / beatLength % soundsForTrack.length);
var rhythmicSound = soundsForTrack[beatPosition];
LK.getSound(rhythmicSound).play();
updateUI();
// Remove block
block.destroy();
blocks.splice(i, 1);
continue;
}
}
// Check if block missed (passed ship)
if (!block.caught && block.y > ship.y + 150) {
if (block.lane === ship.currentLane) {
// Block missed - lose life and reset combo
lives--;
combo = 0;
// Update life display
if (lives >= 0 && lives < lifeDisplays.length) {
var currentLifeIndex = lives; // Capture current lives value
lifeDisplays[currentLifeIndex].setActive(false);
// Add life loss animation
tween(lifeDisplays[currentLifeIndex], {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
onFinish: function onFinish() {
tween(lifeDisplays[currentLifeIndex], {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200
});
}
});
}
LK.effects.flashScreen(0xff0000, 300);
LK.getSound('miss').play();
// Check for game over
if (lives <= 0) {
LK.showGameOver();
return;
}
updateUI();
}
block.caught = true; // Mark as processed
}
// Remove blocks that are off screen
if (block.y > 2800) {
block.destroy();
blocks.splice(i, 1);
}
}
}
function showShipUnlock(newShipData) {
// Show unlock frame with animation
tween(unlockFrameBorder, {
alpha: 0.8
}, {
duration: 300
});
tween(unlockFrame, {
alpha: 0.9
}, {
duration: 300
});
tween(unlockTitleTxt, {
alpha: 1
}, {
duration: 300
});
// Update description text
unlockDescTxt.setText(newShipData.name + '\n+1 Extra Life');
tween(unlockDescTxt, {
alpha: 1
}, {
duration: 300
});
// Create and display the unlocked ship
if (unlockShipDisplay) {
unlockShipDisplay.destroy();
}
unlockShipDisplay = new UnlockableShip(newShipData);
unlockShipDisplay.x = 0;
unlockShipDisplay.y = 20;
LK.gui.center.addChild(unlockShipDisplay);
unlockShipDisplay.addGlowEffect();
tween(unlockContinueTxt, {
alpha: 1
}, {
duration: 300
});
// Add pulsing animation to continue text
tween(unlockContinueTxt, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(unlockContinueTxt, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(unlockContinueTxt, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
}
});
}
function hideShipUnlock() {
tween(unlockFrameBorder, {
alpha: 0
}, {
duration: 300
});
tween(unlockFrame, {
alpha: 0
}, {
duration: 300
});
tween(unlockTitleTxt, {
alpha: 0
}, {
duration: 300
});
tween(unlockDescTxt, {
alpha: 0
}, {
duration: 300
});
tween(unlockContinueTxt, {
alpha: 0
}, {
duration: 300
});
if (unlockShipDisplay) {
tween(unlockShipDisplay, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
unlockShipDisplay.destroy();
unlockShipDisplay = null;
}
});
}
}
function unlockNewShip() {
// Generate a new random ship
var newShip = generateRandomShip();
// Add to unlocked ships
unlockedShips.push(newShip);
// Keep only the 5 most recent ships
if (unlockedShips.length > 5) {
unlockedShips = unlockedShips.slice(-5);
}
// Save to storage
storage.unlockedShips = unlockedShips;
// Show unlock animation
showShipUnlock(newShip);
return newShip;
}
function progressToNextTrack() {
if (gameMode !== 'classic') {
return;
}
// Mark current track as completed
if (completedTracks.indexOf(synthwaveTracks[currentTrackIndex]) === -1) {
completedTracks.push(synthwaveTracks[currentTrackIndex]);
}
// Move to next track
currentTrackIndex++;
// Check if all tracks have been completed
if (currentTrackIndex >= synthwaveTracks.length) {
// Unlock a new ship before showing victory
var newShip = unlockNewShip();
currentShipData = newShip;
// Delay victory screen to show ship unlock
LK.setTimeout(function () {
LK.showYouWin();
}, 3000);
return;
}
// Play next track
currentMusicTrack = synthwaveTracks[currentTrackIndex];
LK.playMusic(currentMusicTrack);
musicStartTime = Date.now(); // Reset music timer using current time
}
function detectBeat() {
var currentVolume = facekit.volume;
var currentTime = Date.now();
// Add current volume to history buffer
volumeHistory.push(currentVolume);
if (volumeHistory.length > bufferSize) {
volumeHistory.shift();
}
// Calculate average volume from buffer
var avgVolume = 0;
for (var i = 0; i < volumeHistory.length; i++) {
avgVolume += volumeHistory[i];
}
avgVolume = avgVolume / volumeHistory.length;
// Detect beat: current volume significantly higher than average
var volumeSpike = currentVolume > avgVolume * beatSensitivity;
var timeSinceLastBeat = currentTime - lastBeatTime;
var beatDetected = volumeSpike && currentVolume > beatThreshold && timeSinceLastBeat > minBeatInterval;
if (beatDetected) {
lastBeatTime = currentTime;
// Add visual feedback for beat detection
LK.effects.flashScreen(0x440088, 100);
return true;
}
return false;
}
function shouldSpawnBlock() {
if (gameMode === 'microphone') {
// Spawn based on beat detection from microphone input
return detectBeat();
} else {
// Classic mode - spawn every second
return LK.ticks - lastSpawnTime >= spawnInterval / (1000 / 60);
}
}
// Button event handlers
classicButton.down = function (x, y, obj) {
if (gameMode === 'menu') {
// Add button press animation
tween(classicButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 100,
onFinish: function onFinish() {
tween(classicButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
}
});
startGame('classic');
}
};
micButton.down = function (x, y, obj) {
if (gameMode === 'menu') {
// Add button press animation
tween(micButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 100,
onFinish: function onFinish() {
tween(micButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
}
});
startGame('microphone');
}
};
// Camera button event handler
cameraButton.down = function (x, y, obj) {
if (gameMode === 'microphone') {
// Toggle camera state
cameraEnabled = !cameraEnabled;
// Update button appearance based on state
if (cameraEnabled) {
cameraButton.setText('CAM');
cameraButton.fill = 0x00ffff;
cameraButton.stroke = 0x00ffff;
} else {
cameraButton.setText('CAM\nOFF');
cameraButton.fill = 0xff0066;
cameraButton.stroke = 0xff0066;
}
// Add button press animation
tween(cameraButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
onFinish: function onFinish() {
tween(cameraButton, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100
});
}
});
// Visual feedback
LK.effects.flashObject(cameraButton, 0xffffff, 200);
}
};
// Touch controls for lane switching during gameplay
game.down = function (x, y, obj) {
if (gameMode === 'menu') {
return;
}
// Check if unlock screen is visible
if (unlockFrame.alpha > 0) {
hideShipUnlock();
return;
}
// Lane switching
if (x < 1024) {
// Left side tapped - move left
if (ship.currentLane > 0) {
ship.moveToLane(ship.currentLane - 1);
}
} else {
// Right side tapped - move right
if (ship.currentLane < 2) {
ship.moveToLane(ship.currentLane + 1);
}
}
};
// Main game update
game.update = function () {
if (gameMode === 'menu') {
// Hide microphone icon and camera button in menu
microphoneIcon.visible = false;
cameraButton.visible = false;
return;
}
// Check for track progression in classic mode
if (gameMode === 'classic' && musicStartTime > 0) {
var currentTime = Date.now();
var timeElapsed = currentTime - musicStartTime;
// Check if current track duration has been reached
if (timeElapsed >= trackDuration) {
progressToNextTrack();
}
}
// Spawn blocks
if (shouldSpawnBlock()) {
spawnBlock();
lastSpawnTime = LK.ticks;
}
// Check collisions
checkCollisions();
};
synthwave neon glow audiosurf or f-zero like ship. In-Game asset. 2d. High contrast. No shadows
faint glowing outlines with a shimmer effect retro synthwave style. In-Game asset. 2d. High contrast. No shadows
a musical note thats bright and neon thats also really cool looking. In-Game asset. 2d. High contrast. No shadows
synthwave bright neon glow audiosurf or f-zero like ship In-Game asset. 2d. High contrast. No shadows
synthwave bright neon glow audiosurf or f-zero like ship In-Game asset. 2d. High contrast. No shadows. facing upright vertical
a musical note thats bright and neon thats also really cool looking. In-Game asset. 2d. High contrast. No shadows
- Shape: a glowing neon **circle or hexagon** - Color: bright **red or magenta** - Inner symbol: a small white or yellow **skull**, **explosion**, or **ā ļø warning icon** in the center - Glow: apply a soft outer glow that pulses slightly - Visual style: match the **synthwave aesthetic** (think neon, arcade, retro-futuristic) - Size: same as the existing note blocks - Animation: gently pulsate or shimmer while falling. In-Game asset. 2d. High contrast. No shadows
a bright laser vertical line retro style. In-Game asset. 2d. High contrast. No shadows. In-Game asset. 2d. High contrast. No shadows
a line that is set to the theme of this game that looks like lightsabre. In-Game asset. 2d. High contrast. No shadows
synthwave bright neon glow audiosurf or f-zero like ship In-Game asset. 2d. High contrast. No shadows. facing upright vertical 3d like